Spec Studio

The comment form is a second anchor representation

spec:decisions.anchor-comment-formDecision Record · featureready floor ready

Packs: pack:self-hosting-v1

Design: no design entries · no pinned declarations · no decisions

Findings: no errors, no warnings

specs/decisions/anchor-comment-form.sdp.md

No open questions and no blocking questions; implementation binding none, verifier binding none, and expected-outcome oracle none.

Intent
outcome
Admit a doc comment carrying reserved @sdp* tags as a second extracted representation of the anchor, feeding the same closed envelope as the constant form, so code that cannot import the Protocol binds at its real site and comment prose still authors nothing.
Decision
context
The source-commentary ruling this record replaced (MD-35, deleted because nothing in it still needed its own text; it reads in git at 90c75dd) refused two things in one record: comment prose authoring graph content, and identity tags in doc comments. Its rationale said the refusal "is about prose, not identity", while its operative alternative also refused structured identity tags; the annotation scout under docs/lineage/ names that mismatch and finds that gen 1's drift was duplicate identities, scan scope, and syntax handling, never an extracted identity tag as such. The first adopter showed the cost of the single form: every one of its code anchors sits in a test or a harness because its runtime code cannot import this package, byFile on a source file returns nothing, and a hand-kept join stands in for the binding. The original design (docs/lineage/v0-design/04-authoring-surfaces.md §2) supported three marker styles feeding one extractor, and its package shape (docs/lineage/v0-design/02-system-architecture.md §3) kept the marker library free of runtime dependencies so product code could import it. The owner's direction was to move closer to that design.
decision
An anchor has two extracted representations that feed one closed envelope, the constant form as ruled and a comment form. The comment form is a top-level /** … */ block in a .ts or .tsx file carrying reserved camelCase, TSDoc-compatible tags; @sdpAnchor <id> opens it, and the id's namespace selects the flavor exactly as the constant builders do. Comment prose still authors nothing: only the reserved tags are read, and a comment that does not open with /** is never read. Trust is by reserved grammar, not by builder import, so no import is required. The same id in two forms is a duplicate id. The package also ships a zero-dependency subpath @libar-dev/software-delivery-protocol/anchors exporting the id builders and the three anchor builders and nothing else, for runtimes that may import but must not load node:* modules or ts-morph; the extractor trusts that specifier as a Protocol builder module.
rationale
  • Hard to reverse: a second write path from source becomes a contract for the extractor, the validators, and every adopter's code. Surprising without context: the replaced ruling refused the obvious parity move, and this one admits exactly the half its own rationale left open while keeping the half it was about. Real trade-off: a tag grammar in a comment gets no compiler check and the extractor gains a second parser; in exchange, code that cannot import the package binds at its real site and byFile answers for it.
  • What survives from the replaced ruling: comment prose authors nothing; a comment's wording beyond the reserved tags produces no graph finding; a comment that states law other surfaces depend on still promotes into a Spec under the promotion law, and restating promoted law in a comment violates exclusive promotion (MD-10), so the comment demotes to local commentary plus a Spec pointer; one graph language stays, because both forms feed one envelope and one extraction path (one validation path, MD-14).
consequences
  • The reserved tags are @sdpAnchor, @sdpLabel, @sdpSatisfies, @sdpVerifies, @sdpModels, @sdpReferences, @sdpComponent, @sdpUses, @sdpRole, @sdpLayer, and @sdpContext; the spelling is camelCase so TSDoc tooling reads each as one tag.
  • @sdpAnchor <id> opens the anchor; impl:, api:, and component: ids are code anchors, test: is a test anchor, oracle: is an oracle anchor.
  • Target tags by flavor are @sdpSatisfies and @sdpReferences on a code anchor, @sdpVerifies on a test anchor, and @sdpModels on an oracle anchor; lists are comma-separated.
  • Structural tags are admitted on code anchors only: @sdpComponent, @sdpUses, @sdpRole; a component anchor additionally takes @sdpLayer and @sdpContext.
  • @sdpLabel is admitted on any flavor.
  • Any other @sdp* tag is an envelope error, because the envelope is closed exactly as it is in the constant form; a line that opens with @sdp is a reserved tag line, so a misspelling such as @sdp-anchor or @sdpAnchor: is refused rather than read as prose.
  • Attachment: every top-level /** … */ block that contains a reserved tag is one anchor with exactly one @sdpAnchor; a block with reserved tags and no @sdpAnchor is an envelope error, and a block with two is an envelope error, as any repeated tag is.
  • Blocks above the first import, trailing blocks with no statement beneath them, and comment-only files are read; a /** … */ block with a reserved tag in a nested position, inside a function body, a class member, an object literal, or a JSX expression, is a misplaced-tag error, and a nested comment the top level would not read stays unread.
  • The block's first line is the binding's file and line.
  • The binding grain of both forms is the file: a block's line locates the anchor and names no declaration, and no attachment kind or symbol name is recorded. Symbol-level reach, if it arrives, is derived under the inferred claim and joins anchors by position; a mismatch is never a finding.
  • Cardinalities: each reserved tag appears at most once per block; @sdpModels takes one target; a list tag is non-empty, carries no empty item, and repeats no target.
  • Prose precedes the tags; after the first reserved tag, a non-empty line that does not open a tag is a refused continuation, and an ordinary TSDoc tag such as @param stays lawful and unread.
  • Parsing reads the raw comment text with a tag grammar; it does not depend on the TypeScript JSDoc tag parser.
  • The extractor's raw-text prefilter admits any file that contains the @sdp prefix, so a file with no Protocol import is still read.
  • The same id written in both forms reports through the duplicate-id validator.
  • The /anchors subpath is ESM with type declarations, built from the leaf id and anchor modules, carries no dependency, loads no node:* module, and its dependency closure is tested; a runtime that imports it ships nothing beyond the id and anchor builders.
  • The decorator form stays an unextracted representation.
alternatives
  • Keeping the constant form as the single binding syntax was refused: the adopter's runtime code cannot import this package, so its anchors would stay in tests and the graph would keep answering nothing for source files.
  • Extracting the comment form through the TypeScript JSDoc tag parser was refused: it would couple the reserved grammar to the compiler's tag model and its versioning, where a tag grammar over the raw comment text is owned here.
  • A hyphenated spelling such as @sdp-anchor was refused: TSDoc tooling splits a hyphenated tag at the hyphen, so the tag would render as @sdp with a stray suffix in every documentation projection.
  • Parsing comment prose into Spec sections stays refused: it inverts the separation of intent from binding and recreates a shadow intent carrier beside the Specs.
Verification

Verifier bindings

No verifier binding recorded.

Design view

Requirement drawing

  • Declared verifying examples: no declared verifying example.
  • Test or oracle bindings: no recorded binding.
Requirement drawing for spec:decisions.anchor-comment-form
The diagram as text
  • spec:decisions.anchor-comment-form: The comment form is a second anchor representation · declared Spec
  • spec:model.anchors: Source anchors bind code without carrying intent · declared Spec
  • impl:protocol.anchor-extraction: anchor reification seam: the constant form and the comment form · anchored code · src/extract/anchors.ts:121
  • impl:protocol.anchors-subpath: zero-dependency subpath exporting the id and anchor builders only · anchored code · src/anchors.ts:8
  • spec:decisions.anchor-comment-form refines spec:model.anchors · declared
  • impl:protocol.anchor-extraction references spec:decisions.anchor-comment-form · anchored
  • impl:protocol.anchors-subpath references spec:decisions.anchor-comment-form · anchored
Mermaid source

Copyable source; the Studio draws static SVG.

Preferred syntax: requirementDiagram. Chosen syntax: flowchart TB because references require exact labels unavailable in requirement syntax.

Decisions

No decision record named.

Runtime

No implementation neighborhood recorded.

Runtime observation: not tracked.

Evidence

Runtime observation: not tracked.

Build attestations: not tracked.

Deployments: not tracked.

Bills of materials: not tracked.

Impact
Relations of spec:decisions.anchor-comment-form
The diagram as text
  • spec:consumers.authoring-on-ramp: Authors move one Spec from intent to reviewed evidence
  • spec:decisions.anchor-comment-form: The comment form is a second anchor representation
  • spec:decisions.one-validation-path: Validation follows one graph
  • spec:extraction.runnable-modules: Derived runnable modules freeze the registrar interface
  • spec:model.anchors: Source anchors bind code without carrying intent
  • spec:model.spec-sections: Spec sections carry typed detail and direct verifier semantics
  • spec:decisions.anchor-comment-form dependsOn spec:decisions.one-validation-path · declared — both anchor forms feed the same extraction and graph-validation path
  • spec:decisions.anchor-comment-form refines spec:model.anchors · declared
  • spec:consumers.authoring-on-ramp decidedBy spec:decisions.anchor-comment-form · declared — the skill teaches both extracted anchor forms and their silent non-binding hazards under this ruling
  • spec:extraction.runnable-modules decidedBy spec:decisions.anchor-comment-form · declared
  • spec:model.anchors decidedBy spec:decisions.anchor-comment-form · declared
  • spec:model.spec-sections decidedBy spec:decisions.anchor-comment-form · declared — it restates comment promotion for the comment form
Mermaid source

Copyable source; the Studio draws static SVG.

Preferred syntax: flowchart. Chosen syntax: labelled flowchart, preserving exact relations, claims and folds.

Rests on

Rested on by

Distance 1

Distance 2

Distance 3

Distance 4

Distance 5

Distance 6

Distance 7

Distance 8

Relations

Relation and directionOther endClaimReasonExercised keys
dependsOn · outgoingValidation follows one graph
spec:decisions.one-validation-path · resolves
declaredboth anchor forms feed the same extraction and graph-validation pathNone
refines · outgoingSource anchors bind code without carrying intent
spec:model.anchors · resolves
declaredNoneNone
decidedBy · incomingAuthors move one Spec from intent to reviewed evidence
spec:consumers.authoring-on-ramp · resolves
declaredthe skill teaches both extracted anchor forms and their silent non-binding hazards under this rulingNone
decidedBy · incomingDerived runnable modules freeze the registrar interface
spec:extraction.runnable-modules · resolves
declaredNoneNone
decidedBy · incomingSource anchors bind code without carrying intent
spec:model.anchors · resolves
declaredNoneNone
decidedBy · incomingSpec sections carry typed detail and direct verifier semantics
spec:model.spec-sections · resolves
declaredit restates comment promotion for the comment formNone

Bindings

Implementation binding: none

Design reference: present

  • impl:protocol.anchor-extraction · anchor reification seam: the constant form and the comment form · src/extract/anchors.ts:121 · role extractor · component component:protocol.extract · layer application · context protocol
  • impl:protocol.anchors-subpath · zero-dependency subpath exporting the id and anchor builders only · src/anchors.ts:8 · role contract · component component:protocol.model · layer domain · context protocol

Verifier binding: none

Expected-outcome oracle: none

Runtime observation: not tracked.

Run results: not recorded in the graph.

Findings

None.

Composing (0)

Composing needs the script.