Milestone-close gate for 'Project environment — the project-as-crate authoring loop' (#180), run as a downstream consumer from the public interface only (README, project-layout, glossary, ledger, --help; no crates/*/src, no crates/*/tests). Verdict: GREEN — 0 bugs. Tasks (all ran, evidence in the corpus): (1) aura new -> cargo build -> run twice byte-identical -> manifest project provenance {namespace, dylib_sha256}; (2) a REAL authored node (Negate) in the scaffolded lib.rs -> rebuild -> graph build resolves the project type -> run flips total_pips sign, dylib_sha256 tracks the fresh build; (3) renamed-twin id semantics: content-ids differ, identity-ids equal, combinable output content-first, content-id == manifest topology_hash; (4) misuse paths: charter violation named + refused exit 1, missing --release build names path + fix, outside-project refused. Findings (non-blocking, triaged separately): F1 friction (aura new git init nests a gitlink under the engine tree), F2 doc-gap (no public example of a bound-param build closure — the Identity template is param-less), F3 doc-gap (glossary manifest entry lacks the project provenance block), F4 friction (run leaks raw UnknownNodeType Debug where graph build phrases house-style), F5 doc-gap (outside-project run surfaces UnknownNodeType instead of a no-Aura.toml hint), F6 doc-gap (scaffolded Aura.toml omits the documented data-archive-root key). refs #180
11 KiB
Fieldtest — milestone "Project environment — the project-as-crate authoring loop" (#180) — 2026-07-02
Status: Draft — awaiting orchestrator triage Author: fieldtester (dispatched as the milestone-close gate)
Scope
The milestone-close gate for the project-as-crate authoring loop: aura new
scaffolds a buildable cdylib project; inside it the aura host discovers
Aura.toml, cargo-builds + loads the project cdylib, merges the project
vocabulary with aura-std, and runs blueprints with project provenance in the
manifest; aura graph introspect --vocabulary | --content-id | --identity-id
introspects the merged surface and topology ids.
Run entirely from the public interface — README.md,
docs/project-layout.md, docs/glossary.md, docs/design/INDEX.md
(C13/C16/C17/C24), aura … --help, and the fieldtests corpus shape. No
crates/*/src and no crates/*/tests were read; no rustdoc was consulted. The
only node-authoring template available was the scaffolded Identity node.
Artefact under test: target/debug/aura, rebuilt from HEAD via
cargo build -p aura-cli before the run; the project cdylib rebuilt with
cargo build in the project between edits.
Tasks
All fixtures under fieldtests/milestone-project-environment/. The scaffolded
project is momentum-lab/.
Task 1 — scaffold -> build -> run -> run-again -> provenance — WORKS
Commands (verbatim):
aura new momentum-lab # -> "created project `momentum-lab` (namespace `momentum_lab`)"
cd momentum-lab && cargo build # clean
aura run blueprints/signal.json # -> s1_starter_run.json
aura run blueprints/signal.json (x2, diff) # byte-IDENTICAL (determinism)
- The emitted crate is buildable as-is:
crate-type=["cdylib"], an empty[workspace]table (so it builds under the engine tree), path-dep onaura-core, aCLAUDE.md,.gitignore, anAura.toml([paths] runs), asrc/lib.rswith a workedIdentitynode registered viaaura_project!, and a starterblueprints/signal.jsonthat already wires the project's ownmomentum_lab::Identitynode at the graph tail — so the very first run exercises the merged vocabulary, not just std. - The run report's manifest carries the milestone's headline provenance:
"project":{"namespace":"momentum_lab","dylib_sha256":"ceda123f…"}. Two runs are byte-identical (C1). Evidence:s1_starter_run.json.
Task 2 — author your own node (the real authoring loop) — WORKS
Edited momentum-lab/src/lib.rs to add a Negate (f64 sign-flip) node
following the scaffolded Identity template, registered it in
vocabulary()/type_ids(), then:
cargo build # clean
aura graph introspect --vocabulary # now lists momentum_lab::Negate
aura graph build < s2_negate.ops.json > s2_negate.bp.json # resolves the project node
aura run s2_negate.bp.json # -> s2_negate_run.json
- The op-list (
s2_negate.ops.json, canonical authoring form per README) wiresprice -> SMA/SMA -> Sub -> Bias -> momentum_lab::Negate.graph buildresolved the project node and emitted a runnable blueprint (s2_negate.bp.json). - The new node visibly changed behaviour:
total_pipswent+0.34185 -> -0.34185(exact sign flip), the R block changed accordingly, and the manifest'sdylib_sha256updated (ceda123f… -> 70263c83…) — proof the host loaded the fresh build per invocation. Deterministic on re-run. Evidence:s2_negate_run.json.
Task 3 — introspection: --vocabulary / --content-id / --identity-id — WORKS
aura graph introspect --vocabulary # 2 project + 22 std = 24 lines
aura graph introspect --node momentum_lab::Negate # in value:F64 / out value:F64
aura graph introspect --content-id < s2_negate.ops.json (A)
aura graph introspect --content-id < s3_negate_renamed.ops.json (B)
aura graph introspect --identity-id < {A,B}
aura graph introspect --content-id --identity-id < A # both, content first
--vocabularylists project types first, then std alphabetically;--noderesolves a project node's ports. Renamed-twin test (A vs B = same topology, different node names): content-ids differ (bd6c70…vs9dc729…), identity-ids are equal (b02fbb…), and the combined flag prints content-id first — exactly as README / theidentity idglossary entry document. Cross-check:content-id(A)equals thetopology_hashstamped ins2_negate_run.json's manifest. Evidence:s3_id_comparison.out.
Task 4 — misuse paths a newcomer hits — WORKS (one friction)
- Charter violation (registered a non-
momentum_lab::-prefixed type id, rebuilt): refused at load, exit 1,aura: project vocabulary rejected: type id+ "NoNamespace" +lacks the project prefix+ "momentum_lab::" +— names both sides, house-style prose. Reverted after capture. Evidence:s4_charter_violation.err`. --releasewith only a debug build: exit 1, names the exact missing path + the fix (run cargo build in the project first). Evidence:s4_release_missing.err.- Outside any project:
--vocabulary-> std-only (22); a project blueprint is refused (see Finding F5). Walk-up discovery from a nested subdir (momentum-lab/blueprints/) correctly findsAura.toml(24 entries).
Findings
[friction] F1 — aura new leaves a nested .git that snags the enclosing tree
- Task 1.
aura newrunsgit init; the scaffold containsmomentum-lab/.git. - Correct for a real standalone project (C16: "its own git history"). But
docs/project-layout.mdexplicitly supports scaffolding under the engine tree "before it moves to its own repo" — the exact reason the empty[workspace]table is emitted. That handles cargo's walk-up; nothing handles git's walk-up: the nested repo becomes an embedded gitlink in the enclosing repo, sogit add fieldtests/…/momentum-labwould add a submodule pointer, not the files. I had torm -rf momentum-lab/.gitfor the corpus to be committable. - Recommended: skip
git initwhen an enclosing.gitis detected, or document the scaffold-under-a-repo wrinkle beside the[workspace]note.
[doc-gap] F2 — no worked example of a node that reads a bound param
- Task 2. The only authoring template is the scaffolded
Identity(param-less; build closure|_| Box::new(..)). No public doc (README, project-layout, ledger, --help) shows how aPrimitiveBuilderbuild closure consumes a bound param — the natural next step (a tunable, sweepable node). I could author a param-less node from the surface alone, but a first parametric node cannot be written from the public docs without reading engine source/rustdoc. - Recommended: have
aura newemit one param-bearing node in the template (or document the build-closure param-read in project-layout.md). Highest-value gap.
[doc-gap] F3 — the manifest project provenance block is undocumented
- Task 1. The manifest's new
"project":{namespace,dylib_sha256}block is the milestone's headline provenance, but the glossarymanifestentry still defines a manifest as "(node-commit + params + data-window + seed + broker profile)" — noproject. A consumer inspecting the JSON meets an undocumented field. - Recommended: add
project(namespace + dylib hash) to the glossary manifest definition.
[friction] F4 — run leaks a raw Rust Debug enum for an unknown node type
- Task 4. Same logical error, two phrasings:
graph build(stdin) ->op 5 (add): unknown node type "momentum_lab::Negate"(exit 1, prose);run(argv file) ->aura: s2_negate.bp.json: UnknownNodeType("momentum_lab::Negate")(exit 2, raw Debug-formatted enum variant). The exit-code split is correct and documented (C14: argv-file content = exit 2, stdin data = exit 1), but therunpath'sUnknownNodeType("…")breaks the lowercase house style every other diagnostic uses. Evidence:s4_outside_run.err,s4_outside_build.err. - Recommended: render the loader error on the
runpath in house-style prose (e.g.unknown node type "momentum_lab::Negate").
[doc-gap] F5 — "outside a project" is not distinctly diagnosed
- Task 4. Running a project blueprint with no
Aura.tomlup-tree surfaces the downstream symptom (UnknownNodeType), not the likely cause ("not inside a project / no Aura.toml found"). A newcomer who cd'd out of their project reads "unknown node type" rather than "you are not in a project". - Recommended: when a blueprint names a
namespace::Typeand no project was discovered, hint that noAura.tomlwas found up-tree.
[doc-gap] F6 — scaffolded Aura.toml omits the documented data-archive-root path
- Task 1. Glossary/
project-layout.mdsayAura.tomlcarries "the data archive root and the runs dir"; the emitted file has only[paths] runs = "runs". No concrete key name for the data archive root is documented or scaffolded, so a newcomer wantingaura run --real <symbol>has no scaffolded slot to point at their recorded data. Low severity for a synthetic-default v1. - Recommended: scaffold the data-archive-root key (commented) or drop it from the Aura.toml prose until a consumer reads it.
[working] W1 — the merged-vocabulary authoring loop is real and clean
- Tasks 1-3. Edit lib.rs ->
cargo build-> the nextaurainvocation loads the fresh dylib (manifestdylib_sha256tracks the rebuild), merges 2 project + 22 std types, resolves the project node ingraph build,run, and everyintrospectmode, and produces deterministic runs. Walk-upAura.tomldiscovery works from nested subdirs. The whole authoring loop the milestone promises works end-to-end from the public docs.
[working] W2 — id semantics and diagnostics are precise
- Tasks 3-4.
--content-id/--identity-idbehave exactly as documented (name-blind identity id equal across renamed twins; byte-exact content id differs; combinable, content-first; content-id == manifesttopology_hash). The charter refusal and the--release-missing message are both clear, exit-code-correct, and actionable. One micro-nit inside W2's otherwise-great--releasemessage: it says "or pass --release …" even when--releasewas already passed (the generic hint doesn't adapt to the current mode).
Milestone verdict: GREEN
A downstream consumer can run the entire project-as-crate authoring loop —
aura new -> cargo build -> run (deterministic, with project provenance) ->
author a real node -> rebuild -> introspect the merged vocabulary -> topology ids
— from the public docs alone, with zero behavioural bugs. All six findings are
friction/doc-gap polish, not loop blockers:
| Finding | Class | Recommended action |
|---|---|---|
F1 nested .git snags enclosing tree |
friction | plan (skip git-init under a repo, or document) |
| F2 no param-node authoring example | doc-gap | plan (scaffold a param node) — highest-value gap |
F3 manifest project block undocumented |
doc-gap | tighten glossary |
F4 run leaks Debug enum on unknown type |
friction | plan (house-style the loader error) |
| F5 "outside a project" not distinctly diagnosed | doc-gap | plan (hint on missing Aura.toml) |
| F6 Aura.toml data-archive-root un-scaffolded/undocumented | doc-gap | tighten docs |
| W1/W2 loop + id semantics correct | working | carry-on |