Establish the project-as-crate authoring loop: aura new, Aura.toml discovery, cdylib vocabulary loading #180
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Decision provenance
Chosen 2026-07-02 as the next milestone from a grounded comparison of six candidate thrusts, each assessed against the design ledger (docs/design/INDEX.md), the code, and the tracker. The user ratified the recommendation verbatim: "ok" (2026-07-02).
Alternatives considered and set aside:
Sequencing revision (recorded)
An earlier recorded sequencing decision placed this layer late — "after the World layer ships and the experiment API hardens". The World families shipped (milestone "World/C21: run + orchestrate harness families from blueprint-data", closed 2026-07-01). The experiment API has not hardened, but C24 (the blueprint as a serializable, World-owned data value; docs/design/INDEX.md) changed what that precondition gates: a v1 project layer needs only project node logic plus a vocabulary resolver, not the experiment-builder API. That seam is built and tested today:
blueprint_from_jsonresolves each primitive's type identity through an injected&dyn Fn(&str) -> Option<PrimitiveBuilder>(crates/aura-engine/src/blueprint_serde.rs:165), and the std vocabulary's charter names the project-cdylib path through the same seam (crates/aura-std/src/vocabulary.rs:1-10). Experiments therefore stay CLI-verb-driven within this milestone.Scope
In:
aura newscaffolder: cdylib crate-type, empty [workspace] table, engine dependencies, a sample node plus blueprint, Aura.toml, a project CLAUDE.md fragment.Out (deferred, each with its home):
mpsc::Senderand construction-arg builders (LinComb(arity),CostSum(n_costs),SimBroker(pip_size),Session(..)) are deliberately absent from the resolvable vocabulary (crates/aura-std/src/vocabulary.rs:11-16, the scope note from #155) — so a project signal keeps running through the existing R-harness wrapping within this milestone.Design decisions — the project-as-crate load boundary (settled autonomously)
Method: ten enumerated forks were populated by five opposed synthetic-user stances, each answer carrying a source citation or an explicit ungrounded flag; forks converging on cited ground were decided as grounded, splits were resolved by derivation from repo sources, and the two riskiest derivations were adversarially reviewed — both reviews upheld the conclusion while pinning one missing constraint each (items 1 and 4). All decisions are vetoable. Status: design settled — ready for spec production.
Export protocol: one versioned descriptor symbol, two ABI tiers. Options: a single query-fn export / a table-fn export / a descriptor struct. Chosen: descriptor — with the pinned constraint that the compatibility stamp is a C-ABI-readable prefix (
#[repr(C)], C-typed fields only) verified before any Rust-ABI field (resolver fn, type list) is touched: a stamp cannot certify the channel it is itself transported by. Ground: the export surface is at least three items anyway (resolver + enumerable type list + stamp — crates/aura-std/src/vocabulary.rs:58-64 documents that afn(&str)->Option<...>resolver cannot be listed, so introspection reads the list), and separately-dlsym'd lockstep symbols are the same drift class #160 guards against in std_vocabulary's hand-synced copies.Stamp content: rustc version + aura-core crate version; mismatch refuses (runtime failure, exit 1).
PrimitiveBuilderis an aura-core type crossing the boundary from the project's own aura-core compilation (crates/aura-engine/src/blueprint_serde.rs:165), so layout compatibility requires both the toolchain (C13, the same-toolchain hot-reload contract) and the crate version; a build-profile/feature fingerprint guards no named risk and is deferred until one is demonstrated.Panic across the boundary: propagate. A load-boundary
catch_unwindcannot guard an eval-time panic in the run loop (category mismatch); the CLI is one-shot per invocation, so propagation and process-abort coincide; the codebase has nocatch_unwindprecedent and the exit-code taxonomy (docs/design/INDEX.md:1080-1086) keeps a loud crash the honest authoring-bug signal.Vocabulary identity: duplicate-id hard error plus a mandatory
::-namespace prefix for project ids. Options: bare ids + hard collision error / project shadows std / std shadows project / namespaced ids. Chosen: hard error AND prefix charter — project type-ids carry a namespace prefix (<ns>::Vwap), std ids stay bare, the loader refuses unprefixed project ids and any duplicate in the merged project ∪ std set. Bare ids with only a collision error leave a stale-reference hole: when aura-std later ships a node name a project already used, the forced project rename makes OLD project blueprints silently resolve the bare id to the new std node — a silent wrong graph, which the load path forbids (crates/aura-engine/src/blueprint_serde.rs:105). Prefixing closes the hole structurally while no external blueprint artifacts exist yet. Pinned constraint from adversarial review: the separator must be disjoint from the param-path dot — a dot-prefixed type id would flow through the op-script default-name rule (lowercased type label, docs/design/INDEX.md:1983) into node names containing '.', corrupting the by-name param address spacecheck_param_namespace_injectiveprotects — hence::, and the default-name rule strips the namespace (typemyproj::Vwap→ default node namevwap). One verification rides into spec production: member_key filesystem sanitization for param paths containing::.Zero-arg restriction inherited. Project vocabularies resolve zero-argument builders only, exactly as std_vocabulary scopes itself (crates/aura-std/src/vocabulary.rs:11-18); construction-arg widening remains the queued additive extension (#156), shared by std and project through the same seam.
v1 export surface: vocabulary only. No composite/blueprint-factory exports — a composite already travels as pure data (
NodeData::Compositerecursion, crates/aura-engine/src/blueprint_serde.rs:44-47), and factory exports would smuggle compiled topology back past the blueprint-as-data contract (C24).Aura.toml v1: paths only. IN: the data archive root and the runs dir — both replace live hardcoded placeholders (crates/aura-cli/src/main.rs:1510-1512; the ingest DEFAULT_DATA_PATH) — plus the project namespace. OUT: instrument/pip metadata — the C15 realization deleted the authored floor (cycle 0074) and #124 collapsed to recorded-sidecar-geometry-or-refuse; docs/project-layout.md:33 is stale on this point and is corrected this cycle. OUT: default symbol/window/broker — no present consumer; deferred demand-driven per the repeatedly ratified default-simple discipline.
Build responsibility: the author builds; aura only loads. The authoring-surface contract already assigns the build step to the author ("writes the Rust, builds it, runs it via the aura CLI" — docs/design/INDEX.md:1206-1208); aura reports which dylib it loaded (path + stamp) instead of orchestrating cargo. Recorded v1 reading of C13's "hot-reload": per-invocation load of the freshest build — the process restart IS the reload; no in-process swap exists.
Loader home: a module inside aura-cli. The engine cannot hold it (it stays vocabulary-agnostic — crates/aura-std/src/vocabulary.rs:7-8), and the repo's ratified precedents are extract-on-second-consumer (the aura-analysis split, C16 realization) and research-side dependencies confined to the leaf binary (the clap admission, C14 realization);
libloadingenters under the same per-case review and can never be linked into the frozen deploy artifact.Dylib location:
cargo metadata. A hand-rolledtarget/convention silently loads the wrong artifact under custom target dirs or workspace setups, and an authored path field in Aura.toml is the redundant-authored-state shape the C15 realization already deleted once; asking cargo's own introspection cannot diverge from what cargo built. Profile: debug by default (cargo's own default), release opt-in per invocation; a missing artifact refuses with a build hint.Enumerable companion required. The descriptor carries the type-id list beside the resolver (the
std_vocabulary_typespattern); at load, every listed id is cross-checked to resolve, so build-free introspection can never silently omit project types.Run provenance: the manifest records the loaded project dylib. A run built from project logic is no longer identified by the engine commit alone — the same gap the registry contract's refinement named for topology (docs/design/INDEX.md:1305-1313), one layer down.
RunManifestgains a Tier-1 additive project-provenance field: a content hash of the loaded dylib, plus the project git commit best-effort.Load-and-hold scope boundary goes into the ledger. The never-unload rule is trivially satisfied only because the CLI is one-shot per invocation; a future long-running host (the open local-server thread) must re-solve reload (host restart or subprocess isolation), never in-process dlclose. To be recorded with the C13 realization when the cycle lands.
Multi-crate composition inside a project. A project's exported vocabulary is ONE merged set the project composes in ordinary Rust (chaining the tables of its shared node-crate dependencies); the loader sees only the merged result and applies the same duplicate refusal; per-crate
::-prefixes are the recommended convention for shared crates.Rejected framings, recorded: inverting the host relation (the project as top-level binary statically linking the CLI as a library — no dylib at all) contradicts the ratified milestone frame ("the aura host loads and runs it", docs/project-layout.md:24-25; C13/C16) and forfeits the shared-binary UX for no cited gain. A post-load behavioural canary as a substitute for the stamp was set aside: a canary cannot prove layout compatibility; the two-tier C-ABI stamp is the sound gate.
Spec auto-sign (cycle 0102)
The cycle-0102 spec (docs/specs/0102-project-load-boundary.md — the project-as-crate load boundary: two-tier cdylib descriptor, loader with stamp gate and vocabulary charter, Aura.toml walk-up discovery, merged project-plus-std resolution across all blueprint verbs, manifest provenance) is signed. The signature is a grounding-check PASS: 7 load-bearing assumptions about current behaviour, each ratified by a named currently-green test; greenfield surfaces have no current behaviour to ratify and were excluded per that agent's contract. No human signed; the sign is vetoable and reverts forward (the spec commit is unpushed).
One refinement over the design-decision log recorded earlier on this issue: Aura.toml v1 carries strictly paths (data archive root, runs dir) and NO namespace field — the project namespace lives solely in the dylib descriptor, so it cannot drift against a second authored copy; the same anti-drift rationale that excluded the dylib path and the instrument/pip fields.
Design reconciliation (specify, cycle 0103 — the aura new scaffolder)
The scaffolder's design is settled by the sources (the scope list in this issue's body, docs/project-layout.md, and the cycle-0102 fixture crates/aura-cli/tests/fixtures/demo-project/ as the living template). Two load-bearing forks resolved before spec production:
ns::Typecharter), overridable via --namespace. Basis: derived — predictable one-to-one mapping from the one argument the user already supplies, mirroring cargo's crate-name normalization; the charter validates it at load anyway.Minor shapes going straight into the spec (cargo-new-mirror conventions, not preference forks): refusal on an existing target directory (runtime refusal, exit 1), best-effort git init, no cargo build inside the scaffolder (the author builds — the cycle-0102 decision that aura only loads).
Spec auto-sign (cycle 0103)
The cycle-0103 spec (docs/specs/0103-aura-new-scaffolder.md — the aura new scaffolder: one command emits a buildable, runnable project crate parameterized from the cycle-0102 fixture; path-dep engine wiring per the reconciliation comment above in this thread, dated 2026-07-02; a load-bypass so scaffolding works inside unbuilt trees) is signed. The signature is a grounding-check PASS: 8 load-bearing current-behaviour assumptions, each ratified by a named currently-green test. No human signed; the sign is vetoable and reverts forward (the spec commit is unpushed).
Auto-sign: spec 0104 — topology-identity hash (#171)
Cycle 0104's spec (
docs/specs/0104-topology-identity-hash.md, committedb3a2e9c) was signed autonomously via a grounding-check PASS (independent fresh-context agent), per the /boss auto-sign protocol. A vetoable notify was sent.What the spec covers: an additive identity hash — two blueprints expressing the same topology (op-script-built vs Rust-builder-built, or twins differing only in debug names) get the same id via
aura graph introspect --identity-id. The identity projection blanks every non-load-bearing debug symbol (composite name, instance names, bound-param names, role names, output names) while keeping everything load-bearing (type ids, node order, edges, role targets+order, output pairs+order, bound positions/kinds/values — so param openness stays identity-bearing).topology_hashand all three of its roles (introspect content id, reproduction-store key, reproduce fetch anchor) stay byte-for-byte untouched; no manifest field and no store keyed by the identity hash this cycle (deferred until a consumer exists). Design basis: the cycle-0104 reconciliation comment on #171 (the refuted "redefine topology_hash" shape → sibling id).Grounding evidence (all five current-behaviour assumptions ratified by named green tests):
signal_serializes_to_canonical_golden(aura-engine blueprint_serde).--content-idcontract incl. bad-doc refusal —graph_introspect_content_id_is_deterministic_and_distinguishes(+..._rejects_a_bad_document).topology_hashroles —topology_hash_is_the_content_id_of_the_canonical_form,blueprint_store_round_trips_by_content_id,aura_reproduce_re_derives_a_persisted_sweep_family, pluscontent_id_is_stable_across_the_store_round_trip.boundarray (bound slot present, open slot absent) — the golden test'sfast-bound /slow-open shape.Reviewer notes carried into planning (non-blocking):
--content-id --identity-idtogether relaxes the current exactly-one flag dispatch (graph_construct.rs, count!=1 → exit 2). The usage tests must be adjusted deliberately.Status: spec signed and committed; the cycle is ready for planning and implementation.
Cycle 0104 closed — topology-identity hash landed (#171)
Commits
b3a2e9c(spec, boss-signed) →39cbd44(plan) →45fb06d(implementation,closes #171on push) →7b429f9(audit close, drift-clean). Suite 884 passed / 0 failed (873 baseline + 11 new); clippy and doc build clean; every existing pin (canonical golden,--content-id,topology_hash, store round-trip, reproduce e2e) green unchanged.What shipped:
blueprint_identity_json(aura-engine; canonical form with all non-load-bearing debug symbols blanked, C23/invariant 11) +aura graph introspect --identity-idbeside--content-id, sharing the onecontent_idSHA-256 primitive. Same topology → same identity id across authoring paths (op-script vs Rust builder, renamed twins), proven by a cross-path twin test and e2e. The introspect dispatch was deliberately relaxed: the two id flags form one group and may combine (one build, both ids, one per line, content id first) — with a new usage string and a test on the previously uncovered count!=1 exit-2 path.Derived decisions this iteration (orchestrator, recorded for audit):
nested_composite_interior_names_are_identity_blindandrenamed_role_and_output_share_identity_json_not_canonical_jsonbeyond the plan's four. Derived: kept — they differentially cover the recursion arm and the role/output strip arms, which the planned tests never exercised; the architect confirmed them as genuinely distinct, non-redundant coverage.content id,identity id(Avoid: identity hash, topology-identity hash), andtopology hashadded to docs/glossary.md — the terms are now user-facing CLI surface; README and ledger aligned to the canonical term.Audit drift review: three doc-mirror items (two stale #171-deferral passages in the design ledger, the README introspect table, the glossary gap), all resolved inline in the audit-close commit. Whole-harness / structural-axis content-addressing remains deferred; the identity id stays introspection-only (no manifest field, no store key) until a dedup consumer exists — the deferral recorded on #171.
Status: cycle closed drift-clean; milestone queue continues with the vocabulary three-copy guard (#160).
Auto-sign: spec 0105 — std-vocabulary roster macro (#160)
Cycle 0105's spec (
docs/specs/0105-std-vocabulary-roster-macro.md, committed103a82d) was signed autonomously via a grounding-check PASS (independent fresh-context agent), per the /boss auto-sign protocol. A vetoable notify was sent.What the spec covers: the three hand-kept copies of the std-vocabulary roster in
crates/aura-std/src/vocabulary.rs(thestd_vocabularymatch arms, thestd_vocabulary_types()list, the test's inline 22-key list) collapse into one declarative-macro source (std_vocabulary_roster!, invoked once with the"TypeId" => Typepairs). Byte-preserving: both fn signatures, the 22 ids, and their order unchanged; every consumer untouched; the vocabulary stays a closed compiled-in set (invariant 9 / C24 — compile-time expansion, no registry). Adding a zero-arg node becomes one roster line plus a conscious count-pin bump. Accepted residual per the #160 reconciliation: a node never rostered at all stays unguarded (no zero-arg-builder enumeration exists) and fails safe in both directions.Grounding evidence (all seven current-behaviour assumptions ratified by named green tests): the two vocabulary unit tests (
std_vocabulary_resolves_known_and_rejects_unknown,std_vocabulary_types_lists_exactly_the_resolvable_keys), thePrimitiveBuilder::label()oracle (asserted inside the first), and the consumer pinsgraph_introspect_vocabulary_lists_the_node_types,unknown_node_type_fails_named, thetests/project_load.rsmerged-vocabulary suite (introspect_vocabulary_lists_project_types,vocabulary_charter_violation_refuses_end_to_end), andsignal_serializes_to_canonical_golden. Reviewer note: no consumer test pins the list order — the spec commits to preserving it anyway (trivially held by the macro's literal expansion).Status: spec signed and committed; the cycle is ready for planning and implementation.
Cycle 0105 closed — std-vocabulary roster macro landed (#160)
Commits
103a82d(spec, boss-signed) →e0c745a(plan) →57f401f(implementation,closes #160on push) →bd32c4f(audit close, drift-clean). Suite 885 passed / 0 failed (884 baseline + 1 new e2e); clippy and doc build clean; every consumer pin green unchanged.What shipped: the three hand-kept copies of the 22-key zero-arg roster in
crates/aura-std/src/vocabulary.rs(resolver match arms,std_vocabulary_types()list, test inline array) collapsed into one privatestd_vocabulary_roster!macro invoked once — resolver and enumerable list now agree by construction, closing the #160 drift mode. Byte-preserving (same signatures, keys, order;lib.rsand every consumer untouched); the vocabulary stays a closed compiled-in set (invariant 9 / C24, compile-time expansion, no registry). Adding a zero-arg node is one roster line plus the conscious count-pin bumps.Derived decisions this iteration (orchestrator, recorded for audit):
graph_introspect_vocabulary_lists_exactly_the_closed_roster_countbeyond the plan (which predicted "no new test, 884 unchanged"). Derived: kept — it pins the roster count throughEnv::type_ids()and the CLI print loop across the real process boundary, a plumbing path no roster-internal test can see; the plan's acceptance line was consciously superseded and disclosed in the iter commit body.Status: cycle closed drift-clean. Milestone queue: #181 (template↔fixture lockstep, needs a design pass) and the milestone fieldtest remain.
Decision log: #181 resolved by ledger declaration (2026-07-02)
The template-vs-fixture lockstep question (#181) is resolved via its option (c): the aura-new templates are declared the canonical project shape, the 0102 demo-project fixture an intentionally frozen known-good twin — no equality test, no fixture regeneration. Full options/rationale (incl. the skeptic-pass correction of an over-claimed guard rationale) are reproduced self-contained in issues/181#issuecomment — see the resolution comment on #181 dated 2026-07-02. Ledger note landed in docs/design/INDEX.md (commit
81ac6fd, closes #181 on push).Status: the milestone's substantive work items (#171, #160, #181) are all resolved in local history; the milestone fieldtest is the remaining close gate.
Milestone fieldtest GREEN — close gate satisfied (2026-07-02)
The end-to-end milestone fieldtest ran as a downstream consumer from the public interface only (no engine source, no engine tests) and returned GREEN with 0 bugs: scaffold -> build -> deterministic run with project provenance; a genuinely authored node through rebuild -> op-script build -> run (provenance tracking the fresh dylib); renamed-twin id semantics (content ids differ, identity ids equal, combinable output); misuse paths refused with named causes. Evidence corpus committed at fieldtests/milestone-project-environment/ (commit
ad83a4b).Findings triage: F3/F6 (doc-gaps) fixed inline (commit
6915624); F1 (nested git init gitlink) -> #182; F2 (parameterized starter node, highest-value gap) -> #183; F4 (loader UnknownNodeType Debug-leak on the run path) -> #184; F5 (missing-Aura.toml hint) -> #185. None blocks the milestone.Status: every milestone work item (#171, #160, #181) is resolved in local history (commits close them on push), audit-closed drift-clean, suite 885/0, and the milestone fieldtest is green. The formal milestone close and the push are user-reserved acts; #182-#185 are post-milestone polish awaiting triage.
Milestone formally closed (2026-07-04)
All milestone work items (#171, #160, #181) shipped and auto-closed on the
2026-07-03 push (d7c935d..6915624); the close gate was satisfied by the green
milestone fieldtest (evidence corpus committed at
fieldtests/milestone-project-environment/, commit
ad83a4b, 0 bugs).Post-milestone polish triage since: #182 closed 2026-07-04 as resolved by the
shipped #204 fix (aura new skips git init inside an existing work tree —
the fix matches the shape this milestone's fieldtest finding F1 proposed);
#183 (parameterized starter node), #184 (loader prose), #185 (missing-Aura.toml
hint) remain open as detached polish.
The Gitea milestone container "Project environment — the project-as-crate
authoring loop" is closed together with this reference issue.