Skip to content

Annotations, closer to the original design: the comment form, references, and role, layer and context on anchors - #29

Merged
darko-mijic merged 29 commits into
mainfrom
feature/annotations-v0
Oct 10, 2026
Merged

darko-mijic merged 29 commits into
mainfrom
feature/annotations-v0

Conversation

@darko-mijic

@darko-mijic darko-mijic commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

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. byFile on 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 as implemented, because claiming to realize a Spec was the only way code could name one.

What the owner ruled

Ratified name Ruling Carrying Spec Stated
the comment form (MD-36) A top-level /** … */ 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 /anchors subpath. The file is the binding grain of both forms. Supersedes "source commentary never enters the graph" (MD-35). spec:decisions.anchor-comment-form ready
bindings are optional, plural, and may reference a design (MD-37) satisfies is optional and plural and verifies is plural. A code anchor with no target is lawful. A new references edge 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 own dependsOn or refines, never by a reference from the code. Targets name whole Specs. spec:decisions.anchor-binding-grain ready
architectural significance is annotated where it is realized (MD-38) A code anchor may carry a role, and a component: anchor a layer from a closed set of five and a bounded context. 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-annotation ready

The owner stated ready on all three on 2026-10-10. The binding grain's ready came 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 impl:protocol.census-page
 * @sdpLabel renders the derived census and runtime taxonomy projection
 * @sdpSatisfies spec:consumers.census-page
 * @sdpComponent component:protocol.projections
 * @sdpReferences spec:decisions.architectural-annotation
 * @sdpRole projection
 */

function compareCodeUnits(left: string, right: string): number {
  • Eleven reserved tags. They are camelCase so TSDoc tools read each one as a single tag. @sdpAnchor <id> opens the anchor, and the id's namespace picks the flavor, as the constant builders do.
  • Top level only. A tagged block inside a function, a class, an object literal or a JSX expression is an error. So is a misspelled tag such as @sdp-anchor, which an early build on this branch read as prose and dropped. Prose before the tags stays commentary.
  • Bound by file and line. The block's first line is the anchor's site, so byFile answers 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.
  • Its own grammar. The extractor reads the raw comment text with a line grammar, not the TypeScript JSDoc parser. A file is read for the comment form when it contains @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 /anchors subpath

Some code may import the package but must not load node:* modules or ts-morph. For it, @libar-dev/software-delivery-protocol/anchors exports 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 references edge

A 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 implemented stays a whole-Spec fact. The new references edge 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 stated ready, ignores it. The edge runs one way. A design that builds on code that already exists says so on its own side, by dependsOn or refines to 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 for byFile, and claims nothing. The graph schema moves to 0.8.0 (src/graph/schema.ts), and the closed edge list grows from eleven types to twelve.

Role, layer and context

role names the architectural pattern a unit plays, such as service, decider or projection. 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. layer takes one of edge, application, domain, adapter or infrastructure, and context names the bounded context. Both go on component: anchors only. Role and context values are single lowercase kebab tokens and are never normalized, so readModel is refused rather than merged into read-model. None of the three confers anything.

Reader, projections, recipes and skills

  • The reader. A Spec's context lists the units that reference it next to the units that realize it. byFile returns comment-form anchors at their own site. blastRadius follows references, names the edge type and claim in each reason, and lists changed units that bind no Spec under unlinked.
  • The projections. The census renders the role, layer and context taxonomy and the references edges, 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.
  • Recipe 27, references into a design. For each member of a Pack it puts four facts side by side: which units reference it, which realize it, whether a verifier is bound, and which implemented Specs it builds on through its own dependsOn or refines, 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.
  • The skills. All three teach the new forms and recipes. sdp-authoring writes out both forms, how to choose between satisfies and references, 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.
  • The vocabulary. CONTEXT.md gains 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", so layer keeps 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 satisfies optional 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 carried layer and bounded_context, which return here as layer and context.

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 the component: 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. role comes from the first generation, not the original design. The references edge 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:

  • 107 code units, 17 of them in the comment form. src/cli/sdp.ts dropped its builder imports. Two units came with the rulings: impl:protocol.graph-edge-types in src/graph/schema.ts and impl:protocol.anchors-subpath in src/anchors.ts.
  • All 13 engine components declare a layer and a context. 87 units carry a role, across 12 values.
  • 6 references edges 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 dependsOn or refines. spec:consumers.spec-studio depends on the reader and refines the projections model; spec:consumers.spec-studio.data depends 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 at da56a34. 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 that byFile still 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, including query, use-case and operator-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

  • Contract first. Plan 40 was written as the contract every session and agent read (bef1a4e). The engine, the corpus and the skills changed together in 24 commits.
  • A design review of the contract. An independent, read-only review (gpt-6-astra) ran after the first engine work and before the reader, the projections and the self-binding were built. It brought camelCase tags in place of hyphenated ones that TSDoc splits, the attachment and cardinality rules, separate trust routes for the two forms, identity-only units in blast radius, the restatement of every surviving rule, and recipe 27 without a ladder (0443534, 2163cf2). The extractor was revised to match (3b32e34).
  • A read of the finished branch. A second read-only review, by the same model, raised seven findings. Six are applied, among them the misspelled tag that vanished silently and a nested-tag scan that missed blocks before a closing brace and inside JSX. The seventh, the reference direction, went to the owner and was ruled: the Studio references came out. Plan 40's Review section lists each.
  • The owner's rulings. Ten questions on the arc went to three independent advisors (gpt-6-astra, an Opus agent, a Fable agent), and the owner ruled each. Plan 40's Rulings section records them, with the panel where it split. The Protocol side landed as one change (52e8bea): the decision text, the self-binding, recipe 27, the skills, the vocabulary and the oracle together.
  • A read of the rulings. A third read-only review by the same model read the rulings as landed and raised seven findings, two of them holds. Six are applied (f23e358). The authoring skill's satisfies checklist reads every promise a Spec carries, not only its Design entries. The adopter's byFile acceptance 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.
  • A pre-merge review. An external review of the open PRs raised two findings on this branch, both confirmed and fixed. F6: the census's unitsBy and recipe 28's taxonomy copied 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 /anchors subpath test saw only import … from specifiers, so a side-effect import "x" was invisible, and it forbade only node: and ts-morph. The scanner now reads every runtime import form, including side-effect imports, export … from, import() and require(), 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 its belongsTo edges 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 between import and its specifier, an import with attributes and a spaced require were 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 as a4bafdc). 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.
  • Tests. Two fixture corpora cover the forms and their refusals. A test checks the subpath's import closure. The recipe tests run recipes 27 and 28, including the builds-on column, and run recipe 4 on a file that holds only an identity-only unit.
  • The full gate. npm run check passed on the tree committed as a4bafdc, 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 at 0ae4400, 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:

pnpm --silent sdp validate . --exclude explorations --exclude examples --exclude test/fixtures/import/parity | tail -1
validate: 0 errors · 14 warnings (conformance + honesty over the one graph)

The warnings print on stderr above that line. They are 5 honesty/gaps, a ready Spec with no verifier bound, and 9 conformance/prose-mentions, a Spec id in prose with no declared relation. The self-hosting oracle pins both lists. The command also writes generated/ 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. #referenceDirection asked whether references may point from existing code at a design that builds on it; ruled one way only, the code answers to the design, on spec:decisions.anchor-binding-grain. #entryAddressTargets asked 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. #attachmentIdentity asked whether a code unit should record the symbol beneath its block; ruled no, the file is the binding grain of both forms, on spec:decisions.anchor-comment-form. #roleVocabulary asked whether a corpus may declare its roles with a validator behind them; ruled no, roles stay a free vocabulary that the census makes visible, on spec:decisions.architectural-annotation.

Pressure is most wanted on two of those rulings:

  • One direction for references. Two of the three advisors preferred a direction-neutral edge. The ruling relies on every design that builds on code having a Spec to depend on. Where does code exist that a design builds on but no Spec satisfies, so the builds-on column cannot see it?
  • The whole-Spec grain. A part of a Spec that needs its own realization tracking becomes a child Spec. Does that split Specs finer than their review wants, for a corpus that keeps its design as keyed entries?

Upcoming

In order, with what brings each back:

  1. Done: the binding grain's ready. The owner ruled the reference direction and the entry-address question on 2026-10-10, MD-37 states ready, and the engine's Studio references came out in the same change (52e8bea).
  2. ready on spec:model.structural-patterns and spec:protocol.structural-self-binding. Both are stated defined, and their floor reaches ready (recipe 11).
  3. The queue PR 28 left. Recipe 2's drift alarm lists 16 implemented Specs still stated defined, none of them from this arc. Each waits on the owner's ready.
  4. The adopter applies the rulings. Designs ahead of their code: SDP #27 and round 2 on the history view libar-platform#12 re-pins to this head: its reverse references come out, a per-Spec pass settles its twelve partly built Specs and its components together, its layer and context rules are recorded, its byFile coverage 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:

  • Status, readiness, intent, behavior or verification on code. Role, layer and context say what a unit is, never where it stands.
  • Inferring structure from imports or file paths as an anchored or declared fact.
  • A Spec-side list of its own code bindings.
  • A second graph, or a separate architecture file.
  • A pattern: namespace, a Pattern node type, a closed role enum, or a role registry checked by a validator.
  • A "partly implemented" delivery fact, or folding a design reference into satisfies.
  • An implements field beside satisfies.
  • Reading comment prose into Spec sections, or depending on the TypeScript JSDoc parser.
  • Promoting anything inferred without a human edit.
  • References from code a design builds on. The design states that dependency by its own relation.
  • An entry address as an anchor target.

🤖 Generated with Claude Code

darko-mijic and others added 22 commits October 9, 2026 19:35
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>
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too 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
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3db3c59a-ff30-44d5-9d55-b6627208b8d7

📥 Commits

Reviewing files that changed from the base of the PR and between 415b095 and a4bafdc.


📒 Files selected for processing (124)
  • .agents/skills/sdp-agent-surface/SKILL.md
  • .agents/skills/sdp-authoring/SKILL.md
  • .agents/skills/sdp-sessions/SKILL.md
  • .prettierignore
  • AGENTS.md
  • CONTEXT.md
  • README.md
  • check-prose-schema.mjs
  • docs/agent-surface/recipes.md
  • docs/concept/06-consumers-and-projections.md
  • docs/concept/DECISIONS.md
  • package.json
  • plans/40-annotations-closer-to-v0.md
  • specs/decisions/anchor-binding-grain.sdp.md
  • specs/decisions/anchor-comment-form.sdp.md
  • specs/decisions/architectural-annotation.sdp.md
  • specs/extraction/derive-graph.sdp.md
  • specs/extraction/pack-member-order.manifest-order-kept.sdp.md
  • specs/extraction/schema-versioning.declared-version.sdp.md
  • specs/model/anchors.sdp.md
  • specs/model/structural-patterns.sdp.md
  • specs/protocol/structural-self-binding.sdp.md
  • src/adapters/vitest.ts
  • src/anchors.ts
  • src/cli/build-command.ts
  • src/cli/census-command.ts
  • src/cli/gherkin-command.ts
  • src/cli/import-command.ts
  • src/cli/mermaid-command.ts
  • src/cli/new-spec-command.ts
  • src/cli/output.ts
  • src/cli/q-command.ts
  • src/cli/sdp.ts
  • src/cli/validate-view-command.ts
  • src/codegen/contracts.ts
  • src/extract/anchors.ts
  • src/extract/carrier.ts
  • src/extract/derive.ts
  • src/extract/discover.ts
  • src/extract/gherkin.ts
  • src/extract/index.ts
  • src/extract/markdown-body-owner-behavior.ts
  • src/extract/markdown-body.ts
  • src/extract/markdown-inline-code.ts
  • src/extract/markdown-pack.ts
  • src/extract/markdown.ts
  • src/extract/protocol-bindings.ts
  • src/extract/reify.ts
  • src/extract/serialize.ts
  • src/graph/delivery-facts.ts
  • src/graph/example-space.ts
  • src/graph/oracle-bindings.ts
  • src/graph/schema.ts
  • src/ids.ts
  • src/import/emit-markdown.ts
  • src/import/import.ts
  • src/model/anchors.ts
  • src/model/descriptors.ts
  • src/model/pack.ts
  • src/model/relations.ts
  • src/model/sections.ts
  • src/model/spec.ts
  • src/notation/slots.ts
  • src/projections/census.ts
  • src/projections/design-review-context.ts
  • src/projections/design-review-pages.ts
  • src/projections/design-review.ts
  • src/projections/gherkin-view.ts
  • src/projections/mermaid.ts
  • src/reader/reader.ts
  • src/runner/index.ts
  • src/testing/index.ts
  • src/validate/contracts.ts
  • src/validate/graph-index.ts
  • src/validate/readiness-floor.ts
  • src/validate/validators.ts
  • test/anchor-corpora.test.ts
  • test/anchor-forms.test.ts
  • test/anchor-trust.test.ts
  • test/anchors-subpath.test.ts
  • test/builders.typecheck.ts
  • test/census.test.ts
  • test/check-self-hosting-gates.test.ts
  • test/cli.test.ts
  • test/design-review.test.ts
  • test/extract.test.ts
  • test/fixtures/checkout-v1/expected-census/index.md
  • test/fixtures/checkout-v1/expected-design-review/index.md
  • test/fixtures/checkout-v1/expected-graph.json
  • test/fixtures/extract/annotation-forms/expected-graph.json
  • test/fixtures/extract/annotation-forms/specs/gate.sdp.md.txt
  • test/fixtures/extract/annotation-forms/specs/history-posture.sdp.md.txt
  • test/fixtures/extract/annotation-forms/specs/history-rebuild.sdp.md.txt
  • test/fixtures/extract/annotation-forms/specs/rebuild.sdp.md.txt
  • test/fixtures/extract/annotation-forms/src/comment-form.ts.txt
  • test/fixtures/extract/annotation-forms/src/constant-form.ts.txt
  • test/fixtures/extract/annotation-refusals/specs/gate.sdp.md.txt
  • test/fixtures/extract/annotation-refusals/specs/history-posture.sdp.md.txt
  • test/fixtures/extract/annotation-refusals/specs/history-rebuild.sdp.md.txt
  • test/fixtures/extract/annotation-refusals/specs/rebuild.sdp.md.txt
  • test/fixtures/extract/annotation-refusals/src/comment-refusals.ts.txt
  • test/fixtures/extract/annotation-refusals/src/constant-refusals.ts.txt
  • test/fixtures/extract/invalid-missing-envelope-fields/bare-binding.ts.txt
  • test/gherkin-view.test.ts
  • test/graph-schema.test.ts
  • test/helpers/fixture-graph.ts
  • test/helpers/map-sets.ts
  • test/package-smoke.test.ts
  • test/reader.test.ts
  • test/recipes.test.ts
  • test/self-hosting-graph.test.ts
  • test/self-hosting-oracle/anchors.ts
  • test/self-hosting-oracle/decisions.ts
  • test/self-hosting-oracle/declared-relations.ts
  • test/self-hosting-oracle/extraction.ts
  • test/self-hosting-oracle/index.ts
  • test/self-hosting-oracle/model.ts
  • test/self-hosting-oracle/protocol.ts
  • test/self-hosting-oracle/structural-edges.ts
  • test/skills.test.ts
  • test/structural-anchors.test.ts
  • tsconfig.json
  • tsup.config.ts
  • vitest.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/projections/census.ts Outdated
const value = unit[attribute];

if (value !== undefined) {
grouped.set(value, [...(grouped.get(value) ?? []), unit.id]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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

Evidence from the check

  • The authored executable harness loads the actual census source and recipe body, measures grouping and timing, and asserts equivalent outputs, making the check reproducible.

Evidence from the check

  • The authored command runner executes both harness modes and captures their commands, working directory, exit codes, stdout, and stderr, preserving the execution record.

Command output from the check

  • The original census and recipe were executed at five input sizes, showing quadratic bucket-copy growth and substantial runtime at 20,000 units.

Command output from the check

  • 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.

View artifacts

T-Rex Ran code and verified through T-Rex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +52 to +55
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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

Evidence from the check

  • 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.

Evidence from the check

  • 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.

Command output from the check

  • 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.

Command output from the check

  • 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.

View artifacts

T-Rex Ran code and verified through T-Rex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

darko-mijic and others added 5 commits October 10, 2026 09:39
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>
darko-mijic and others added 2 commits October 10, 2026 11:55
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>
@darko-mijic
darko-mijic changed the base branch from feature/adopter-round-2 to main October 10, 2026 10:10
@darko-mijic
darko-mijic merged commit 822aea4 into main Oct 10, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant