Files
Aura/docs/design/contracts/c29-self-description.md
claude 0d6d5b1324 audit: trace-identity cycle close — C29 honoured, code twins back in lockstep
The drift review found the run handle keying on the blueprint's *content* id,
which hashes `doc` fields and every other C23 debug symbol. C29's Id treatment
forbids a description influencing an identity, and the repository already
carried the right projection unused. The digest now substitutes
`blueprint_identity_json`'s debug-symbol-blind projection (#171) for
`topology_hash` in the hashed value. `manifest.topology_hash` itself is
untouched, so #343's reference semantics and the committed record-line
fingerprint both stand.

That projection blanks more than descriptions — the render name and node, role,
output, tap and gang names — while param openness stays identity-bearing. This
is correct rather than incidental: names are non-load-bearing debug symbols
(C8/C23), so blueprints differing only in them compute bit-identically and one
address is the right answer. The reach is now stated in C22 and in the digest's
own doc, together with the one observable consequence: two structurally
identical blueprints whose declared tap names differ share a directory while
writing differently named tap files, since the write path never prunes (#352).

Two code twins the cycle left stale are corrected. `name_gate`'s doc comment
still carried the C23 clause this cycle superseded and moved to its sidecar,
and the authoring guide claimed a same-identity re-run replaces the directory's
contents — it does not, for the same non-pruning reason.

Bench is report-only and all five fingerprints are unchanged. `campaign_sweep`
reports a peak-RSS NOTICE (+12.8%); the campaign leg is untouched by this cycle
and its fingerprint is stable, so nothing is ratified against it here.

Routed rather than fixed: #353 — naming authority for one flat namespace is
spelled in three crates and the single-run leg has no claim path — filed into
the recorded-stream-store milestone, where the container consolidates anyway; a
data-identity seam recorded on #320 (a run's identity carries the window, never
the data's content, so a corrected archive is a silent overwrite until that
store supplies a recording identity); and a second instance of the non-pruning
class on #352.

The cycle's spec and plan are removed — git-ignored working files, read by the
drift review before deletion.

refs #311
2026-07-27 11:21:22 +02:00

90 lines
5.6 KiB
Markdown

# C29 — Self-description: every surface explains itself
**Guarantee.** Every closed-vocabulary entry the binary exposes carries a
one-line meaning, enforced at the seam where the entry enters the system;
everything entering the content-addressed store describes itself; a refusal
for a missing description names the entry, the field, and the rule. One
shared deterministic gate — `doc_gate` in aura-core, importable by every
seam per [C28](c28-stratification.md); it fails on an empty/whitespace doc
and on a doc that normalizes to the entry's own name — the full id or its
bare display segment after the last `::`, so a namespaced extension entry
cannot pass its short-name alibi
(`DocGateFault::{Empty, RestatesName}`) — guards three seams:
1. **Compile/unit seam (engine-shipped).** `NodeSchema.doc` is a required
field, so the compiler enumerates every construction site; metrics and
tap slots are schema carriers (`MetricSchema` / `TapSchema`, id + doc);
blocks (`BlockSchema.doc`, the pre-existing model) and tap folds (the
registry roster, [C27](c27-declared-taps.md)) already carried theirs.
The coverage walk (`aura-cli/tests/self_description.rs`) asserts the
gate over all five vocabularies — the engine satisfies its own law.
2. **Load seam (extension).** `load_crate` walks every charter-checked
type id and gates the resolved schema's doc; a fault refuses the load
naming type and rule (`ProjectError::UndescribedVocabularyEntry`).
Field presence is compile-enforced for any crate built against the new
core; the load gate catches the shape violations the compiler cannot
see across the cdylib boundary — the empty-string alibi and the name
restatement.
3. **Register seam (data plane).** The registry's public `put_blueprint`
parses the canonical bytes and requires a gate-passing doc on the root
composite and on **every** nested composite (the walk recurses
unconditionally — it does not stop at an unnamed one) — the gate guards
the store boundary itself, so the register verb, every register-then-run
sugar path, and any future caller are gated by construction. The
op-script vocabulary carries the `doc` op as the builder `.doc(...)`'s
twin ([C25](c25-role-model.md): a closed, typed metadata construct).
Process/campaign documents take an additive-optional top-level
`description`, gated only when present (`DocFault::BadDescription`).
4. **Use seam (data plane, construction-time, #317).** The `use`
construction op re-runs the SAME `gate_composite_docs` walk over a
blueprint FETCHED from the store, before it ever splices into a new
composition — a build-time gate on already-registered content, not a
second write-path check. A doc-less entry (reachable only through the
raw in-crate write survivor class, since `put_blueprint`'s own gate keeps
the store clean going forward) cannot enter a NEW composition this way,
but stays readable and runnable on its own — no retroactive invalidation,
the same discipline the Forbids clause states for the register seam.
**Scope at this record's writing (#321).** The gate walks five
vocabularies — process/campaign blocks, metrics, tap slots, tap folds, and
the closed node roster — plus the document `description` and the three
seams above. Recorded residue, tracked as #321 and not silently claimed:
`emit_vocabulary()` and `binding_column_vocabulary()` remain bare name
arrays, and builder schemas outside the closed node roster (e.g. the
internal tap recording sink) carry compile-required but shape-unwalked
docs. Closing #321 widens the walk and removes this paragraph.
**Id treatment.** Description fields that serialize participate in the
content id and are blanked for the identity id, exactly as `Composite.doc`
(`strip_debug_symbols`); `NodeSchema` is not serialized
([C24](c24-blueprint-data.md)), so node docs move no id. Documents have no
identity projection — their pair is: absent `description` is byte-identical
to the field-less form (existing content ids stable), present participates.
A single `aura exec` run's #311 trace-directory handle honours this same
treatment (2026-07-27 audit fix): its digest hashes the blueprint's identity
projection, not `manifest.topology_hash` (a content id), so a
description-only blueprint edit mints no fresh directory for a bit-identical
run (`aura-runner::run_identity_digest`, [C18](c18-registry.md)).
**Forbids.** The engine evaluating description text — the gate is
deterministic string shape, never content judgement (no freetext logic
hole; C17 / domain invariant 10 untouched). A description influencing
execution, identity ids, or determinism (C1). Retroactive invalidation of
already-registered artifacts — the gate guards the write path only (the
raw in-crate write survives as the store-mechanics and back-compat
witness: pre-C29 doc-less entries stay readable). Machine-invented meaning
lines — an authored text is corrected against the source, never weakened
to an empty string or a name restatement.
**Why.** The audience is headless LLM agents and scripts (#319 ruling),
for whom the binary is the only always-present teacher. Field evidence
2026-07-22 (#314): an agent holding only the release binary had to recover
execution semantics and document schemas by CAS forensics — the removed
failure class is exactly that forensic recovery.
Rendering of the carried texts on the help/introspection surfaces shipped
with #315; the agent bootstrap card was retired as superseded (#267 — the
C29 surfaces themselves carry it); the fold introspection surface shipped
with #310 and renders the fold-registry roster — labels exactly as the
`--tap` selector accepts them, doc lines from the registry entries (#332).