Repository navigation
Annotations, closer to the original design: the comment form, references, and role, layer and context on anchors - #29
Conversation
The owner's direction of 2026-10-09, translated into three held rulings (the comment form, the binding grain, architectural annotation) and the engine, corpus, skill and adopter work that realizes them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…chor types satisfies becomes optional and plural on a code anchor, verifies plural on a test anchor; references, role, layer and context join the closed envelope. The builders stay identity functions; the fixture helper reads an anchor's flavor from its id namespace now that satisfies may be absent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The comment form (MD-36) supersedes the source-commentary refusal, keeping its prose half and reversing its identity half; the binding grain (MD-37) makes satisfies optional and plural, verifies plural, and adds the non-conferring references edge; architectural annotation (MD-38) supersedes structural anchors confer nothing and architectural significance rides existing primitives, restating what survives and ruling role, layer and context on the anchor. Each states defined and waits for the owner's ready. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
spec:model.anchors names the two representations, the anchors subpath, plural targets, the identity-only anchor, references, role, layer, context and the declared component, and is decided by the new rulings. spec:model.structural-patterns is retitled: architecturally significant units are annotated where they are realized. The edge contract closes at twelve with references, one satisfies edge per target. The engine's self-binding adds layer and context on every component, a role on every significant unit, and references from reader and projection units to the Studio Specs they are the design context for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tractor @libar-dev/software-delivery-protocol/anchors ships the id builders and the three anchor builders alone (no node:* module, no ts-morph in the bundle), so a runtime that may import but must not load the engine can still bind constant-form anchors. The extractor trusts the specifier as it trusts the bare one, and the raw-text prefilter admits a file carrying @sdp-anchor so the comment form can be read without an import. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CONTEXT.md gains constant form, comment form, references, role, layer and context, amends the anchor row to its two representations with annotation a rejected synonym and "annotate" meaning "write an anchor", and closes the edge list at twelve. The registry adds MD-36 to MD-38 as held and marks MD-30, MD-34 and MD-35 superseded with forward pointers. The key-decisions line names the three new rulings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or both anchor forms A /** … */ block leading a top-level statement and carrying reserved @sdp-* lines reifies into the same closed envelope as the constant form, with no import required; a line grammar over the raw comment text, never the JSDoc tag parser. satisfies becomes optional and plural, verifies plural; references, role, layer and context are read into the envelope, and the derivation mints one binding edge per target. A parity test proves identical content yields identical graph data in either form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
camelCase tags, the attachment and cardinality rules, the separate prefilters, identity-only nodes in blastRadius, the survivors clause, recipe 27 without a ladder, the oracle's model, and the adopter's coverage-preserving migration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reserved tags are camelCase and TSDoc-compatible (@sdpAnchor and kin), with attachment, cardinality, prefilter and subpath consequences and an open question on attachment identity. The survivors of the three superseded records are restated one per line: one-level membership, unique non-empty targets, self-reference refused, cycles as data, warn-level lint, whole-envelope refusal, dependsOn for semantic need, negative constraints as intent, no satisfies on decisions, exclusive promotion. Architectural annotation states that the component: anchor is anchored architecture, the kebab token grammar for role and context, the five layers, and the reviewed package mapping. The binding grain states byFile, blastRadius, drift-alarm and recipe-1 behaviour for identity-only nodes and references. The glossary follows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… top-level block The design review's contract: TSDoc-compatible @sdpAnchor … @sdpContext tags; every top-level /** block is read, leading, trailing, above an import or alone in a file, one block one anchor; a reserved tag in a nested position is a misplaced-reserved-tag error, never ignored; a wrapped line after the first tag is a refused continuation; empty list items and a plural @sdpModels are refused; role and context are one token each in both forms. The prefilter admits any file carrying @sdp, and src/anchors.ts joins the relative trusted builder modules. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ext on the CodeNode The twelfth edge is references, anchored CodeNode to Spec, minted beside satisfies and conferring nothing; the CodeNode carries its structural attributes; one binding edge per target. Goldens and version pins moved to 0.8.0 (the projection goldens are regenerated by the projections wave). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
references joins memberOf and uses in the structural validator and the extraction-boundary withhold list: a CodeNode source, a Spec target, unique edges, and never a target the same anchor also satisfies. A dangling target reports through referential integrity and confers nothing; an empty or repeated list is refused at reification before it becomes an edge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two defused corpora under test/fixtures/extract: annotation-forms, a comment-form file that imports nothing beside a constant-form file on the anchors subpath, with plural targets, references into a decision, a role, and a component with layer and context, pinned as a golden graph; and annotation-refusals, every refusal of both forms with its exact finding. A third test reads the built dist/anchors.js and asserts its import closure reaches no node: module and no ts-morph. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…st radius SpecContext gains references: the code units written against the Spec, with label, file, line and role, beside the implementations and conferring nothing. Implementation and reference rows share one decode that carries role, and layer and context on a component. blastRadius traverses references as a binding, naming the edge type and claim, and lists changed code units with no Spec linkage as unlinked: recorded, so never coverage-unknown, and never read as coverage. byFile already returns such a unit with an empty Spec list; a referenced Spec is now reached from its source file. The three entry adapters stay three. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ferenced-by The census structural section counts code units apart from memberOf, uses and references edges, and renders the layer, context and role taxonomies as value to units tables so an owner can reconcile synonyms: the closed layer set at zero with foreign values unrecognized, the corpus-owned role and context vocabularies as the graph holds them. A references table maps each unit to its Specs. The explicit empty state stays. A Design Review Spec page lists Referenced by with file and line, and every code unit row shows its role, and layer and context on a component. Mermaid and Gherkin are untouched. The checkout census golden is regenerated; the Design Review golden is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…role taxonomy Recipe 27 takes a Pack id and returns one row per member with three independent facts side by side: the units that reference the member, the units that realize it, and whether a verifier is bound. An empty column reads as unbound, never as not built, and the facts form no ladder. Recipe 28 reads the role, layer and context taxonomy from the code units alone, each value with its units, and counts references edges apart from the units that carry them. Both run verbatim in the recipe check, against ground truth computed from the raw graph and a probe that shows a reference confers nothing. The reading skill names twelve edge types with references described, the code unit's role, layer and context, byFile's lawful empty Spec list, and blast radius's unlinked list. The session skill routes Implement and Review through the two recipes, and the handbook counts twenty-eight runnable bodies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes in sdp-authoring The binding section writes out the constant form and the comment form in full, with a worked comment block, and says when to pick each: the package import, the zero-dependency /anchors subpath for runtimes that may import but must not load node modules, or the comment form for code that may import nothing. It teaches optional and plural satisfies, plural verifies, references as the non-conferring edge, an identity-only anchor, role with the lineage set and its token grammar, the five layers one line each, and context. The curation guidance from the first generation is restated: annotations are curated, never a coverage quota; one owner per identity; a realizing unit documents its local how; a unit written against an unfinished Spec references it rather than satisfying it. The skill check pins the three new rulings in place of the superseded structural one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve 2 gate close The reader and projections bind through the comment form; every engine component declares layer and context, and significant units carry a role. The self-hosting oracle separates expected nodes from binding edges, zero-to-many per anchor, with the reviewed significant-unit set kept as a declaration. Pins, the manifest-order example's schema version, the README recipe count, and the annotation-forms golden are re-measured under plan 40. This is the state the independent branch read reviewed; its findings fold in the next commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A line that opens with @sdp is a reserved tag line, so a misspelled tag refuses its block instead of reading as prose. The nested-tag scan walks every comment in the file, closing delimiters and JSX expressions included, and judges a nested comment by the grammar the top level reads. Recipe 4 returns the changed units that bind no Spec. The comment-form decision states the envelope error for two openers and the lawful TSDoc tag; the glossary and sdp-authoring teach unattached blocks, token values, identity-only anchors, both has-verifier routes, loud and silent mistakes, and the stale-binding limit. The concept document names schema 0.8.0. The direction of the Studio references is held for the owner as an open question on the binding-grain decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…al annotation MD-36 and MD-38 state `ready`; MD-37 stays held on #referenceDirection and #entryAddressTargets. The registry rows read durable, plan 40 and the AGENTS header read executed, the oracle readiness rows and the frozen distribution are re-derived (ready 152, defined 59). Full gate green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 124 files, which is 24 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configuration
📒 Files selected for processing (124)
You can disable this status message by setting the
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| const value = unit[attribute]; | ||
|
|
||
| if (value !== undefined) { | ||
| grouped.set(value, [...(grouped.get(value) ?? []), unit.id]); |
There was a problem hiding this comment.
Grouping repeatedly copies units
unitsBy copies the entire group each time it adds a unit. When 10,000 units share a role or context, one grouping operation copies 49,995,000 existing entries. Recipe 28 repeats the same pattern. Executing both paths confirmed the quadratic growth; at 20,000 units sharing both attributes, each path took approximately two seconds locally.
This is a non-blocking performance concern for large corpora. Create each group's array once and append to it instead, in both the census and recipe 28.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Artifacts
- The authored executable harness loads the actual census source and recipe body, measures grouping and timing, and asserts equivalent outputs, making the check reproducible.
- The authored command runner executes both harness modes and captures their commands, working directory, exit codes, stdout, and stderr, preserving the execution record.
- The original census and recipe were executed at five input sizes, showing quadratic bucket-copy growth and substantial runtime at 20,000 units.
- The same paths and fixtures were executed with only the append statements replaced in memory, showing no repeated bucket replacements and equivalent output at lower runtime.
There was a problem hiding this comment.
Fixed in 5636824: unitsBy and recipe 28's taxonomy create each bucket once and push to it, with the same output and order, and a census test groups 20,000 units sharing one role and one context and asserts the exact rows.
| const closure = importClosure(builtSubpath); | ||
| expect(closure.filter((specifier) => specifier.startsWith("node:"))).toEqual([]); | ||
| expect(closure.filter((specifier) => /(^|\/)ts-morph(\/|$)/u.test(specifier))).toEqual([]); | ||
| expect(readFileSync(builtSubpath, "utf8")).not.toMatch(/\bts-morph\b|["']node:/u); |
There was a problem hiding this comment.
Dependency test misses imports
The new test does not fully protect the zero-dependency promise for /anchors. SPECIFIER_PATTERN misses side-effect imports such as import "some-runtime", and the assertions permit external dependencies other than node:* and ts-morph.
Both gaps were reproduced with the unchanged test: an injected side-effect dependency executed without appearing in the closure, while a named external import appeared in the closure but still passed. A transitive side-effect node:fs import also passed.
This is a non-blocking regression-test gap, not an unwanted dependency in the current build. Recognize every import form, reject all external runtime dependencies, and cover these cases in the test.
Artifacts
- The executed harness copies the original test and built bundle into disposable roots and adds controlled imports and execution diagnostics, making the validation reproducible without changing shared output.
- The executed shell script runs the before and after harness phases and records their commands, working directory, exit codes, and observed output, providing the exact execution procedure.
- Vitest ran against the unmodified bundle and named node:fs and ts-morph mutations, showing that the baseline passes and both forbidden-import controls fail at the original assertions.
- Vitest ran against external side-effect, external named, and transitive node:fs side-effect mutations, showing that all three pass despite executing the injected dependency paths.
There was a problem hiding this comment.
Fixed in 5d75a08: the scanner reads every runtime import form, including side-effect imports, export … from, import() and require(), and the test asserts that every specifier in the transitive closure is relative, with a scanner test on a temporary tree.
The rulings of plan 40 close the arc's four open questions and land on the Protocol side as one change. - A `references` edge says the code answers to a design without claiming to realize it. A design that builds on existing code states that by its own `dependsOn` or `refines`. The engine's fourteen Studio references come out; units reference the decisions they answer to. - Recipe 27 gains a derived builds-on column: the implemented Specs a member depends on or refines, with their implementing units. - Anchor targets name whole Specs; an entry-address target is refused. - Roles stay a free, corpus-owned vocabulary with no validator. - The file is the binding grain of both anchor forms. - Two merge rules: a reference names a design relationship, never a test or a count; a component never references a Spec its members satisfy. - CONTEXT.md's loose uses of layer become level. - MD-37 states ready; the self-binding Spec, the oracle, and the frozen readiness distribution move with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sdp-authoring checklist for satisfies reads every promise a Spec carries, not only its Design entries, and reads a parent again after a split before it keeps satisfies. Plan 40's adopter showcase and its acceptance line pin the byFile pairs the honest anchors yield, never the deleted sidecar's list. The self-binding rule separates the decisions a unit follows and the designs it answers to without wholly realizing them from a Spec it wholly realizes. The architectural-annotation decision scopes its refusal of negative constraints to the Protocol's validators and leaves a corpus its own gate. R6's isolation check resolves each uses endpoint to its component before comparing contexts. Two fixture carriers, their golden, and a recipe test label read "answers to". The entry-address alternative keeps R2's quoted wording; plan 40's Review note records it as an owner item. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The census `unitsBy` and recipe 28's `taxonomy` spread the bucket on every insertion, so grouping n units under one value copied the bucket n times. Each bucket is now created on first sight and pushed to; ordering and rendered output are identical, and the census goldens are unchanged. A census test groups 20,000 units sharing one role and one context, inserted in reverse, and asserts the exact Contexts and Roles rows and their order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…relative specifiers only The dependency check's scanner needed `from`, so a side-effect `import "x"` was invisible, and its assertions forbade only `node:` and ts-morph. The scanner now reads every runtime import form: side-effect import, `import ... from`, `export ... from`, `export * from`, `import()`, and `require()`, and the check asserts that every specifier in the transitive closure is relative; the text check on the built file stays. A scanner test on a temporary tree covers a direct side-effect import, a transitive side-effect import two files deep, and a named external import, with the dynamic and CommonJS forms on the way: the closure lists each, and the relative-only predicate rejects each external one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An independent read of the 2026-10-10 repairs by gpt-6-astra raised four findings; this branch takes the two on its own repairs and merges the parent's. F7: the `/anchors` subpath scanner was a token regex, so a dynamic import with a space before the parenthesis, a comment between `import` and its specifier, an import with attributes, and a `require` with a space were invisible, and a call spelled inside a string was read as one. The scanner now walks the TypeScript syntax tree: every runtime form in source order, type-only forms excluded, text never read as a call. A second scanner test holds those controls. F6: the 20,000-unit census test passed against the old bucket spreading too. Two deterministic operation counts now tell pushing from replacing: the census renders two graphs that differ only in their role values and the Map-set difference must equal the extra buckets, and recipe 28's compiled body must set exactly its two buckets. A `countMapSets` helper counts `Map.prototype.set` calls without a clock or a spy; `compileBody` is exported from the query command so the recipe suite compiles the body as the sink does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stacked on #28 (
feature/adopter-round-2). This PR executes plan 40. Source code can now carry its own place in the graph: which Specs it realizes, which designs it answers to, and which architectural part it plays. The first generation of this tool did that well. This PR brings it back and leaves out what went wrong there.The owner set the direction on 2026-10-09: move closer to the original design, and bring back annotations on the architecturally significant units of a codebase and on how they relate. PR 28 had raised the question. Its Studio claim map came from an agent reading code for shapes that resemble each claim, and two ruled records kept annotations out of the code. Both rested on an account of how the first generation's annotations drifted. The annotation scout went back to that history at the source and corrected the account. This PR replaces those two records and a third with three new rulings. It then carries the rulings through the engine, the projections, the recipes and the skills. On 2026-10-10 the owner closed the four questions those rulings had left open, and all three now state
ready.What the scout corrected
The superseded rulings cited the first generation,
@libar-dev/architect, as a warning: its tag vocabulary shrank from about 50 tags to about 26, which they read as an open structural vocabulary drifting. The scout found that the cut was a cleanup of the formal specification. No 50-entry registry ever shipped at runtime. What drifted was concrete. Two files shared one identity. Throwaway fixtures sat inside the production scan. The parser misread some tag syntax. The scout also found that the refusal of doc-comment tags contradicted itself. Its rationale said the refusal "is about prose, not identity", while its operative clause refused identity tags as well.The first adopter, libar-dev/libar-platform, showed what the refusal cost. Its runtime code may not import this package, so all 40 of its code anchors sat in tests or the test harness.
byFileon a source file returned nothing, and a hand-kept JSON file stood in for the binding. Specs were split so an anchor could claim only what its code does. A stub read asimplemented, because claiming to realize a Spec was the only way code could name one.What the owner ruled
/** … */block of reserved@sdp*tags is a second form of an anchor, read into the same closed envelope as the constant form. No import is needed, comment prose still authors nothing, and the package ships a zero-dependency/anchorssubpath. The file is the binding grain of both forms. Supersedes "source commentary never enters the graph" (MD-35).spec:decisions.anchor-comment-formreadysatisfiesis optional and plural andverifiesis plural. A code anchor with no target is lawful. A newreferencesedge says the code answers to a design without claiming to realize it, and confers no delivery fact. It runs one way: a design that builds on existing code says so by its owndependsOnorrefines, never by a reference from the code. Targets name whole Specs.spec:decisions.anchor-binding-grainreadyrole, and acomponent:anchor alayerfrom a closed set of five and a boundedcontext. Roles stay a free, corpus-owned vocabulary with no validator. Every rule of the two superseded rulings that still holds is restated inside it. Supersedes "structural anchors confer nothing" (MD-30) and "architectural significance rides existing primitives" (MD-34).spec:decisions.architectural-annotationreadyThe owner stated
readyon all three on 2026-10-10. The binding grain'sreadycame with the rulings that closed its two open questions; see "Closed questions" below.What is new
The comment form
An anchor binds one place in the code into the graph. Until now it had one form, a constant built with a builder imported from this package. The comment form writes the same anchor with no import. Here is the engine's census page, in
src/projections/census.ts:@sdpAnchor <id>opens the anchor, and the id's namespace picks the flavor, as the constant builders do.@sdp-anchor, which an early build on this branch read as prose and dropped. Prose before the tags stays commentary.byFileanswers for the source file. The file is the binding grain: the line locates the anchor and names no declaration, and no symbol is recorded. The same id written in both forms is a duplicate id.@sdp. The constant form keeps its separate, import-based trust.The extractor is
src/extract/anchors.ts. Its refusals have their own fixture corpus,comment-refusals.ts.txt.The
/anchorssubpathSome code may import the package but must not load
node:*modules or ts-morph. For it,@libar-dev/software-delivery-protocol/anchorsexports the id builders and the three anchor builders, and nothing else (src/anchors.ts). It builds to one ESM file with no import statement, and a test checks that closure on the built output.Optional, plural bindings and the
referencesedgeA code anchor may now satisfy zero, one or several Specs, and a test anchor may verify several. Each target confers its fact as before, and
implementedstays a whole-Spec fact. The newreferencesedge runs from a code unit to a Spec and says the code answers to that design without claiming to realize it: the unit follows the design or realizes part of it, so a change to the design asks the code to follow. It confers no delivery fact and moves no readiness floor. The drift alarm, which lists Specs implemented but not statedready, ignores it. The edge runs one way. A design that builds on code that already exists says so on its own side, bydependsOnorrefinesto the Spec that code satisfies, and recipe 27's builds-on column reads that relation back. A target names a whole Spec, never an entry inside one. A target named in both lists is an error. An anchor with neither list is identity-only. It puts a code unit in the graph for structure and forbyFile, and claims nothing. The graph schema moves to0.8.0(src/graph/schema.ts), and the closed edge list grows from eleven types to twelve.Role, layer and context
rolenames the architectural pattern a unit plays, such asservice,deciderorprojection. The corpus owns that vocabulary. No validator checks it against a list. Instead, the census shows every value with the units that carry it, so two spellings of one role are visible and easy to fix.layertakes one ofedge,application,domain,adapterorinfrastructure, andcontextnames the bounded context. Both go oncomponent:anchors only. Role and context values are single lowercase kebab tokens and are never normalized, soreadModelis refused rather than merged intoread-model. None of the three confers anything.Reader, projections, recipes and skills
byFilereturns comment-form anchors at their own site.blastRadiusfollowsreferences, names the edge type and claim in each reason, and lists changed units that bind no Spec underunlinked.referencesedges, and counts units apart from edges (src/projections/census.ts). The Design Review Spec page gains a "Referenced by" list, and its component rows show layer and context (design-review-context.ts). Mermaid and Gherkin are untouched, so the shipped projections stay frozen (MD-32) as ruled.dependsOnorrefines, with the units that realize them. They form no ladder. An empty column reads as unbound, never as "not built", and a member that builds on built code is not itself built. Recipe 28, roles, layers and contexts, lists every value the anchors state. Recipe 4 now returns the unlinked units. The catalog holds 28 recipes, each run by a test.sdp-authoringwrites out both forms, how to choose betweensatisfiesandreferences, which way a reference runs, and how to curate anchors. Annotations are curated and never a coverage quota, an identity has one owner, and a unit's comment documents its local how, never a paraphrase of its Spec.CONTEXT.mdgains constant form, comment form, references, role, layer and context. "Annotation" and "marker" stay rejected synonyms of anchor. In prose, "annotate" means "write an anchor". Its loose uses of "layer" now say "level", solayerkeeps its one architectural meaning.Where it follows the original design, and where it departs
The original design let three marker styles feed one extractor, made
satisfiesoptional and plural, and kept its marker library free of runtime dependencies. It also drew the line this PR keeps: markers carry identity and structure, never readiness, intent, behavior or verification. Its Component node carriedlayerandbounded_context, which return here aslayerandcontext.The departures are deliberate. Tags are camelCase, not the dotted
@akg.node, and lists are comma-separated, not braced. The grammar is owned here, not delegated to the TSDoc parser. There is no separate/arch/components.ts, because thecomponent:anchor is the declaration and sits where the component is realized. A component that no code realizes yet is a Spec, never an invented code unit.rolecomes from the first generation, not the original design. Thereferencesedge is new to both lineages; the scout proposed it as the smallest extension. The original design's#sub-segment for a child of a Spec does not return as an anchor target: a target names a whole Spec.The engine annotates itself
The Protocol self-hosts, so its own code is the first corpus for the new forms. Measured on 2026-10-10 at
0ae4400, the merge of #28's repairs; re-run rather than inherit:src/cli/sdp.tsdropped its builder imports. Two units came with the rulings:impl:protocol.graph-edge-typesinsrc/graph/schema.tsandimpl:protocol.anchors-subpathinsrc/anchors.ts.referencesedges from 6 units point at the 3 decisions of this arc. Each unit references the decision it answers to: the extractor and the subpath answer to the comment form, the derive step and the edge list to the binding grain, the anchor model and the census page to architectural annotation. No unit references a Studio Spec.That closes a loop PR 28 opened. There, an agent mapped the Studio's 81 claims to code by reading for shapes that resemble each claim. An earlier build of this branch wrote 14 references from engine code into the Studio Pack; an independent review read them as pointing the wrong way, and the owner's ruling took them out. Recipe 27 now reads the Studio's dependence on the engine from the Studio's side. No member is referenced, implemented or verified, and 9 of the 12 members build on implemented engine Specs through their own
dependsOnorrefines.spec:consumers.spec-studiodepends on the reader and refines the projections model;spec:consumers.spec-studio.datadepends on the reader, Pack member order and the next-rung floor. Each of those rows names the units that realize the Spec it builds on. The claim map is now a projection of Spec relations and bindings. It works per member, not per claim: a part of a Spec that needs its own tracking becomes its own Spec.The self-hosting oracle changed shape to match (
test/self-hosting-oracle/anchors.ts). Expected nodes are kept apart from expected edges, an anchor may carry zero to many bindings, and the owner-reviewed set of significant units is preserved rather than generated.The first adopter
libar-dev/libar-platform#12, branch
unit/design-in-the-graph, adopted this branch atda56a34. Its source now carries 45 comment-form units and 12 component anchors with no Protocol import, so its runtime pays nothing. No test file keeps an implementation anchor. The hand-kept join,design/tools/anchor-sites.json, is deleted, and a coverage test asserts thatbyFilestill names each of the 50 file-and-Spec pairs the join listed. Its components declare layers and contexts. Its units name their roles in the corpus's own vocabulary, includingquery,use-caseandoperator-entry, values the Protocol never listed. It also met the reference direction and the entry-address question in its own corpus: some of its references pointed from existing code at a design that builds on it, and twelve of its Specs are satisfied by code that realizes only part of their Design. Both were evidence for the rulings. Its numbers are in its description, measured on 2026-10-09; it applies the rulings when it re-pins (see "Upcoming").How it was built and checked
bef1a4e). The engine, the corpus and the skills changed together in 24 commits.0443534,2163cf2). The extractor was revised to match (3b32e34).52e8bea): the decision text, the self-binding, recipe 27, the skills, the vocabulary and the oracle together.f23e358). The authoring skill'ssatisfieschecklist reads every promise a Spec carries, not only its Design entries. The adopter'sbyFileacceptance pins the pairs its honest anchors yield, not the deleted join's list. The self-binding rule tells a design a unit answers to from one it wholly realizes. The architectural-annotation decision scopes its refusal of negative architecture constraints to the Protocol's validators, so a corpus can enforce one in its own gate. The adopter's isolation check resolves members to their components. Fixtures that still read "written against" now read "answers to". One is declined: R2 quotes the entry-address alternative's wording, so a sharper rationale is an owner item. Plan 40's Rulings section ends with the review.unitsByand recipe 28'staxonomycopied the whole bucket on every insertion, so grouping cost grew with the square of the units sharing one role or context. Each bucket is now created once and pushed to, with the same output and order, and a census test groups 20,000 units sharing one role and one context and asserts the exact rows (5636824). F7: the/anchorssubpath test saw onlyimport … fromspecifiers, so a side-effectimport "x"was invisible, and it forbade onlynode:andts-morph. The scanner now reads every runtime import form, including side-effect imports,export … from,import()andrequire(), and asserts that every specifier in the transitive closure is relative, with a scanner test on a temporary tree (5d75a08). Three findings on the parent were repaired in Adopter round 2: Pack order, question keys, the next rung, recipes 25 and 26, and the Studio captured ahead of its code #28 and merged here (0ae4400): an invalid open-question key drops alone instead of taking the array and its blocking sibling with it (F1,294f9e7), a Pack's members list and itsbelongsToedges must agree (F3,f4b4a33), and recipe 26 walks dependency cycles with an explicit stack instead of recursing (F8,9d809e5). An independent read of those repairs by gpt-6-astra then raised four findings, all applied. F7's scanner was still a token regex: a dynamic import with a space before the parenthesis, a comment betweenimportand its specifier, an import with attributes and a spacedrequirewere invisible, and a call spelled inside a string was read as one; it now walks the TypeScript syntax tree, and a second scanner test holds those controls. F6's 20,000-unit test passed against the old bucket spreading too; two deterministic operation counts now fail when a bucket is replaced, the Map-set difference between two census graphs that differ only in their role values and the exact set count of recipe 28's compiled body (8a37870). F3's edge-side pass in Adopter round 2: Pack order, question keys, the next rung, recipes 25 and 26, and the Studio captured ahead of its code #28 still named an unlisted edge whose source is absent or no Spec beside referential integrity's or the edge contract's finding; it defers such a source now (6b96885, merged here asa4bafdc). The fourth corrected a question count in Adopter round 2: Pack order, question keys, the next rung, recipes 25 and 26, and the Studio captured ahead of its code #28's description.npm run checkpassed on the tree committed asa4bafdc, the merge of Adopter round 2: Pack order, question keys, the next rung, recipes 25 and 26, and the Studio captured ahead of its code #28's astra narrowing: 74 test files and 1,448 tests, plus the 85 CLI tests.Try it
From the repository root, after
npm ci && npm run build. Measured on 2026-10-10 at0ae4400, the merge of #28's repairs; re-run rather than inherit these numbers.A source file anchored by a comment block. It realizes one Spec and references the decision it answers to:
pnpm --silent sdp:q 'return g.byFile("src/projections/census.ts")' --json{ "path": "src/projections/census.ts", "nodes": [{ "id": "impl:protocol.census-page", "nodeType": "CodeNode", "line": 20 }], "specs": ["spec:consumers.census-page", "spec:decisions.architectural-annotation"] }Blast radius keeps the two edges apart:
pnpm --silent sdp:q 'const r = g.blastRadius(["src/projections/census.ts"]); return r.impactedSpecs.map((s) => ({ id: s.id, via: s.reasons.map((x) => x.throughBinding && `${x.throughBinding.edgeType} (${x.throughBinding.claim})`) }))' --json[ { "id": "spec:consumers.census-page", "via": ["satisfies (anchored)"] }, { "id": "spec:decisions.architectural-annotation", "via": ["references (anchored)"] } ]A referenced decision carries no delivery fact:
pnpm --silent sdp:q 'const c = g.specContext("spec:decisions.architectural-annotation"); return { statedReadiness: c.statedReadiness, deliveryFacts: c.deliveryFacts, implementations: c.implementations.length, references: c.references.map((u) => `${u.codeId} at ${u.file}:${u.line}`) }' --json{ "statedReadiness": "ready", "deliveryFacts": [], "implementations": 0, "references": [ "impl:protocol.anchor-model at src/model/anchors.ts:101", "impl:protocol.census-page at src/projections/census.ts:20" ] }Recipe 27 over the Studio Pack, taken from the catalog as written:
R=docs/agent-surface/recipes.md pnpm --silent sdp:q "$(awk '/^## 27\./{f=1} /^## 28\./{f=0} f' "$R" | awk '/^```js/{c=1; next} /^```/{if (c) exit} c')" --json{ "found": true, "id": "pack:spec-studio-v1", "totals": { "members": 12, "withReferences": 0, "withImplementations": 0, "withVerifier": 0, "withBuildsOn": 9, "unbound": 12 }, "rows": [ { "id": "spec:consumers.spec-studio", "resolved": true, "referencedBy": [], "implementedBy": [], "hasVerifier": false, "buildsOn": [ { "id": "spec:consumers.reader", "via": "dependsOn", "implementedBy": [ { "id": "component:protocol.reader", "file": "src/reader/reader.ts" }, { "id": "impl:protocol.reader", "file": "src/reader/reader.ts" }, { "id": "impl:protocol.reader-impact", "file": "src/reader/reader.ts" } ] }, { "id": "spec:consumers.projections-model", "via": "refines", "implementedBy": [ { "id": "component:protocol.projections", "file": "src/projections/design-review.ts" }, { "id": "impl:protocol.projections-model", "file": "src/projections/design-review.ts" } ] } ] }, … { "id": "spec:consumers.spec-studio.shell", "resolved": true, "referencedBy": [], "implementedBy": [], "hasVerifier": false, "buildsOn": [] }, … ] }Recipe 28, the architecture vocabulary the anchors state:
pnpm --silent sdp:q "$(awk '/^## 28\./{f=1} f' "$R" | awk '/^```js/{c=1; next} /^```/{if (c) exit} c')" --json{ "totals": { "codeUnits": 107, "withRole": 87, "withLayer": 13, "withContext": 13, "references": 6, "referencingUnits": 6, "referencedSpecs": 3 }, "roles": [{ "value": "adapter", "units": ["impl:protocol.example-runner-adapter"] }, …], "layers": [ { "value": "adapter", "units": ["component:protocol.adapters", "component:protocol.testing"] }, { "value": "application", "units": ["component:protocol.codegen", "component:protocol.extract", "component:protocol.import", "component:protocol.reader", "component:protocol.runner"] }, { "value": "domain", "units": ["component:protocol.graph", "component:protocol.model", "component:protocol.notation", "component:protocol.validate"] }, { "value": "edge", "units": ["component:protocol.cli", "component:protocol.projections"] } ], "contexts": [{ "value": "protocol", "units": ["component:protocol.adapters", "component:protocol.cli", …] }] }The whole corpus:
The warnings print on stderr above that line. They are 5
honesty/gaps, areadySpec with no verifier bound, and 9conformance/prose-mentions, a Spec id in prose with no declared relation. The self-hosting oracle pins both lists. The command also writesgenerated/and prints the graph:228 specs · 2 packs · 235 anchors → 465 nodes · 1076 edges.Closed questions, and feedback wanted
This PR opened with four questions for the owner, and the rulings of 2026-10-10 closed each.
#referenceDirectionasked whetherreferencesmay point from existing code at a design that builds on it; ruled one way only, the code answers to the design, onspec:decisions.anchor-binding-grain.#entryAddressTargetsasked whether a target may name one keyed Design entry; ruled no, a target names a whole Spec and a part that needs its own tracking becomes its own Spec, on the same decision.#attachmentIdentityasked whether a code unit should record the symbol beneath its block; ruled no, the file is the binding grain of both forms, onspec:decisions.anchor-comment-form.#roleVocabularyasked whether a corpus may declare its roles with a validator behind them; ruled no, roles stay a free vocabulary that the census makes visible, onspec:decisions.architectural-annotation.Pressure is most wanted on two of those rulings:
Upcoming
In order, with what brings each back:
ready. The owner ruled the reference direction and the entry-address question on 2026-10-10, MD-37 statesready, and the engine's Studio references came out in the same change (52e8bea).readyonspec:model.structural-patternsandspec:protocol.structural-self-binding. Both are stateddefined, and their floor reachesready(recipe 11).defined, none of them from this arc. Each waits on the owner'sready.byFilecoverage test pins the pairs its honest anchors yield rather than the deleted join's list, and a check fails when a platform component, or a member of one, uses a unit from another context, so the platform core carries no orders-inventory specifics. Renaming its numbered capability layers (candidate "stage") is an owner item. After this PR merges, the adopter re-pins to the merge commit.Refused, and staying refused:
pattern:namespace, a Pattern node type, a closed role enum, or a role registry checked by a validator.satisfies.implementsfield besidesatisfies.🤖 Generated with Claude Code