Adopt self-description as a ledger design principle — every surface explains itself #316
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 (user, 2026-07-23): self-description becomes a general design goal, to be recorded in the design ledger — every surface, not only the CLI, must be able to explain itself. The 2026-07-22 field test (triage in #314; CLI-help/introspection subset in #315) showed the cost of its absence: an agent holding only the release binary had to reverse-engineer execution semantics and document schemas forensically.
Surfaces in scope beyond #315's help and introspection audit: error messages (already at the target bar — the
--tracerefusals name #224 and the working verbs), scaffolded project docs (aura new/aura nodes new), the generated bootstrap card (#267), data-plane documents (blueprints, process/campaign documents) and their introspection, the registered vocabulary of native node crates, run artifacts (manifests), HTML surfaces.Open design fork — mandatory doc-strings for the whole vocabulary, including the data plane:
process introspect --vocabulary).register— what enters the content-addressed store describes itself; loose working files stay unconstrained, avoiding placeholder pressure during authoring. Composites surface in the vocabulary by name, so they need a meaning entry like any other member.graph introspect --content-idvs--identity-id).Status: direction decided (self-description as a ledger-recorded principle); the mandatory-doc-string scope and the register-gate shape are the open forks for the design work.
Design reconciliation (specify)
Spec: self-description. The forks below are still listed open on this issue; this records their resolution.
descriptionfield for process/campaign documents.doc: &'static str(BlockSchema, crates/aura-research/src/lib.rs:178-182) — the model. Nodes gain adocfield onNodeSchema(crates/aura-core/src/node.rs:344), compile-time-required and not serialized — blueprints reference nodes by type identity and carry no node logic (C24), so node docs never move a content id. Metrics:metric_vocabulary()(crates/aura-research/src/lib.rs:749) promotes from a bare name array to a schema carrier with a doc line — one carrier, no fourth roster site beside the #190-guarded triple. Tap slots (tap_vocabulary(), lib.rs:780) likewise. Tap folds: the fold registry already carries a doc line per entry (#283); no new fold mechanics this cycle (surface pending #310).load_crate, crates/aura-runner/src/project.rs:532-570) every resolved schema must pass the gate; a miss refuses with the type and field named. Compile-time enforcement cannot cross the cdylib ABI boundary, so load time is the earliest honest seam.aura graph registerrequires a gate-passingdocon the root composite and every named nested composite — composites surface by name, so each needs a meaning entry. Loose working files stay unconstrained; the gate binds where the content-addressed store is entered. Process/campaign documents gain an additive-optionaldescription(gated only when present): the mandatory fork in the issue body names blueprints/composites only, and a mandatory document field would shift every generated document's content id and break the generating verbs for no ratified gain.Composite.doc), and blast-radius containment (the additive-optional pattern is byte-stable for absent fields, pinned byunknown_optional_field_is_tolerated_byte_identically, crates/aura-engine/src/blueprint_serde.rs:447).Basis: derived — the issue body names exactly this counterweight ("non-empty, not merely restating the name"); anything stronger has the engine judging prose, which brushes the non-load-bearing guardrail (doc-strings are never evaluated, domain invariant 10 untouched).
docbehaves (strip_debug_symbolsblanksb.doc, crates/aura-engine/src/blueprint_serde.rs:156; pinned bygraph_doc_field_moves_content_id_but_not_identity_id, crates/aura-cli/tests/graph_construct.rs:1033). Process/campaign documents have only a content id, which their optionaldescriptionparticipates in.Basis: derived — the mechanism already exists and is test-pinned; a new field mirrors it rather than inventing a second treatment.
NodeSchema, not the cdylib descriptor — the descriptor stays a resolver table; the doc travels in the schema its vocabulary fn resolves, so std and extension nodes share one carrier and one gate.Basis: derived — C8 places the static signature on the value-empty recipe; a second descriptor-level doc channel would drift from the schema.
The ledger records the principle as a new contract (self-description — every surface explains itself; doc-strings are non-load-bearing metadata, never evaluated). Scope boundary within the milestone: this issue lands storage, enforcement, and the authored std meaning texts; the help/introspection surfacing lands with #315; the bootstrap card with #267.
Autonomous sign-off (specify)
The self-description design spec — description carriers for every closed vocabulary, one shared deterministic shape gate, three enforcement seams (compile/unit for engine-shipped entries, load for native node crates, register for the content-addressed store), the authored std meaning texts, an additive-optional document
description, and the ledger contract recording the principle — was signed autonomously. The signature is the independent grounding verification's PASS: every load-bearing assumption the spec makes about current codebase behaviour is ratified by a currently-green test (12/12 on the second dispatch; the first dispatch returned BLOCK on a mis-stated node-construction shape, which was corrected to the real struct-literal surface before fresh re-verification). No human signed.Status: spec signed — ready for implementation planning.
Load-seam refinements (iteration 2)
Three small forks derived while turning the spec's load-seam section into the iteration plan; recorded per the decision-log discipline.
ProjectError::UndescribedVocabularyEntry { type_id: String }, while its error-handling section requires refusal prose naming entry + field + rule. With only the type id the prose could not distinguish the empty-doc alibi from a name restatement, so the variant is{ type_id: String, fault: DocGateFault }and the prose arm maps the fault to "has an empty doc" / "has a doc that merely restates its name". The normative error-handling text wins over the sketch.undescribed-project: one resolvableund::Opaquenode withdoc: ""), modelled on the existing charter-violation fixture — the empty doc is compile-legal, so only a real load through the dylib path proves the seam. The passing path needs no new fixture: both existing extension fixtures carry gate-clean docs and are already pinned by the load e2e suite.Status: iteration 1 (compile/unit seam) complete and green on the run branch; iteration 2 (load seam) planned — ready for implementation.
Register-seam refinements (iteration 3)
Four forks derived while planning the register seam; recorded per the decision-log discipline.
put_blueprintnow parses the canonical bytes and walks root + nested composite docs before writing, so the verb, every register-then-run sugar path, and any future caller are gated by construction. The raw write survives as a private in-crateput_blueprint_uncheckedfor store-mechanics tests — which doubles as the witness that pre-C29 doc-less store entries stay readable (never-retroactive). A CLI-side gate at the N call sites was rejected: it is bypassable by the next caller, and admission policy belongs to the boundary it protects. Canonical bytes that do not parse as a blueprint refuse too — the gate cannot certify what it cannot read.doc: Noneanddoc: ""are the same fault ("carries no doc") — otherwise omission would be the trivial bypass of the shape gate.descriptionholds by construction; the meaningful document pair is: absent is byte-identical to today (already pinned), present moves the content id (new test).Status: iteration 2 (load seam) complete and green on the run branch; iteration 3 (register seam + document description) planned — ready for implementation.
Op-script doc slot (iteration 3, mid-iteration fork)
The store-boundary gate exposed an anticipated hole: op-script-built composites (
GraphSession::finish) deliberately carry no doc surface — the construction vocabulary's own comment marks it as "until that vocabulary grows a slot" (a #125 scope cut). Under C29 a doc-less op-script product can no longer register, which broke the previously-green op-script register path.Decision: the op vocabulary grows the anticipated slot. A new
docop —{"op":"doc","text":"..."}, the op-script twin of the Rust builder's.doc(...)— a closed, typed metadata construct with no logic content (squarely inside the C25 closed-vocabulary/P1 pattern). At most one per script; a second is a fault, matching the vocabulary's existing duplicate-fault family. Rejected alternatives: auto-deriving a doc from the script (an alibi doc — the principle forbids machine-invented meaning lines), and exempting op-script products from the store gate (the trivial bypass that would hollow the seam).Also recorded: the implement run's third task discarded the first two tasks' edits via a file-level checkout; the loop's snapshot guard caught it and the content was restored from the recovery snapshot and re-verified — the run continues on a checkpoint commit.
Status: iteration 3 tasks 1–3 checkpointed on the run branch; register e2e, op-script doc slot, and full gates remain — ready to continue implementation.
Fieldtest fork: RestatesName vs the namespaced short name
The cycle's fieldtest surfaced an underspecified reading in the C29 gate: at the load seam the entry name is the full namespaced type id, so an extension node
ns::Scalewithdoc: "Scale"— its own display name, the most natural alibi — passes the gate, while the equivalent composite alibi at the register seam is caught (composite names are bare).Decision: tighten, not ratify. The gate exists to refuse docs that add nothing beyond the name, and the bare display segment restates the name in every sense that matters to a reader.
doc_gateadditionally refuses a doc that normalizes to the name's final::-segment; for unnamespaced entries the two checks coincide, so no shipped entry changes behaviour. The C29 contract sentence gains the precision (full id or its bare display segment). Rejected alternative: ratifying the full-id-only reading — it would keep the most natural alibi legal at exactly the seam built for extension authors.