Skip to content

docs(emitters): define what a generation keeps for the next one - #1336

Open
OmarAlJarrah wants to merge 7 commits into
docs/emitters-kotlin-reserved-setsfrom
docs/emitters-generations
Open

OmarAlJarrah wants to merge 7 commits into
docs/emitters-kotlin-reserved-setsfrom
docs/emitters-generations

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

What one generation has to leave for the next, so that a regeneration honors what was shipped and edited. These issues are one contract, the generation manifest, and the rename claims they all rest on, so they land together.

What the manifest records (#713) (docs/emitters/write-integrate.md#what-the-manifest-records):

  • The document's digest, not the spec's. It is canonical JSON with sources left out, the digest Plan.Version already takes, so reformatting or moving a spec is no new generation.
  • A digest per written file, over bytes normalized after formatting (BOM stripped, \n line endings, trailing newlines dropped). A file with the header whose bytes no longer match was edited outside its regions, and is reported, never overwritten.
  • No-op regeneration. When every input and file digest matches, nothing is written.
  • Recorded choices: the options version (the behavior this SDK pins, docs/emitter-design: target options are free-form, with no validation, versioning or lifecycle #706), the steps that ran, files written once (matched by their origins' correlation keys, so a rename does not write one twice), and each entity's correlation key, base name and symbol.
  • Its own schema version. An older manifest is migrated forward, never refused, since it holds choices the spec cannot recompute.

Regions follow their entity (#714). A keep region belongs to the entity it sits in or follows, and is recorded by ID, correlation key and name. When a rename moves the entity to another file, the region moves with it. A region whose entity is gone is reported and its file kept. A merge mode beyond additive would take standard three-way merge semantics, never one of its own.

A key that matches nothing (#716). A hint, a runtime-policy override, a naming overlay or an overlay whose key no entity has draws emitters/unmatched-key. The warning names the entity whose correlation key matches, as the likely new home. It never aborts a run. The runtime-policy and hint documents from #1332 now point at this one rule.

Shipped names (#583) (docs/emitters/refine/naming.md#collisions-and-the-names-a-generation-shipped).

  • The issue was right that ID-keying and determinism cannot keep the incumbent's name. Only the prior generation knows it, so the prior manifest is now an input of collision resolution.
  • An entity keeps its prior symbol while its ID, correlation key, unit and base name all match. Everyone else takes the rendered name, or the lowest free numeric suffix, in ID order.
  • Only the choice is pinned: a renamed operationId still renames. An ID alone pins nothing (ir: an ID derived from an array index names a different entity after the array changes #678), as the issue's comment required.
  • A first generation is order-independent, since ID order is.
  • The purity rule's input tuple now names Prior, which it omitted although names now read it.

README blocks (#705). The docs emitter returns sections as blocks with stable IDs between markers. Integration replaces only those blocks: text outside them is kept byte for byte, frozen blocks are kept, removals are reported, and the table of contents is rebuilt last from the merged file's headings.

The Go merge adapter uses dst (#794). A spike at Go 1.27.3 and dst v0.28.0 added a type and a method from a new generation to a file holding a keep region, three ways:

  • go/ast nodes moved and printed: every moved doc comment was misplaced, and one landed inside GetZone's body.
  • go/ast to locate declarations, source text spliced in, astutil.AddImport: correct, but it cannot reach inside a declaration, as a field-level merge (open question 5) would need.
  • dst: correct, imports managed, and a file with generics, a range-over-func iterator and min round-trips byte for byte.

dst is MIT-licensed, v0 and maintained in bursts, last released in 2026. The documents pin it exactly, allow it in emitters/golang alone (packages.md states the archtest entry the package will need), and make the spike's cases the adapter's tests.

IDs and renames (#677). The statements across docs/ that #677 lists promised that IDs survive any rename, though an operation's ID is built from its path and a component's from its name. Each now says what holds: a rename of what the ID is not built from (an operationId, a title) is "same ID, new symbol", and a path or component rename is carried by the correlation key. That covers architecture.md, ir/identity.md, prior-art.md (both sites), reference-learnings.md, and in emitters/: plan/overview.md, shaping-hints.md, write-integrate.md, surface-verification.md, T-8 and the README's INV3 row. #419 (stating the answer in docs/ir/open-questions.md) and #679 (the correlation-key pass) stay open.

T-37 pins shipped names, and T-38 the manifest and integration rules.

Closes #713
Closes #714
Closes #716
Closes #583
Closes #705
Closes #794
Closes #677

Test plan

  • The spike programs ran as described, their three outputs compared, and the modern-syntax round trip reported identical: true.
  • dst licence (MIT, from its LICENSE), tags (v0.28.0 latest) and commit history checked through the GitHub API.
  • The manifest sample, extracted verbatim, passes go vet.
  • Every site docs: several design documents say IDs survive renames, which pointer-derived IDs don't #677 lists was swept, and each statement's new wording checked against how the OpenAPI compiler builds operation and component IDs.
  • CI: the doc tests over the new anchors and links.

Stacked on #1335 (#1329 ← #1331 ← #1332 ← #1334 ← #1335). Merge in order. After each base lands, "Update branch" is a merge of main that conflicts nowhere.

The manifest recorded hashes, a file list and the entity-to-symbol map,
too little for a regeneration to honor what the last one did: an edited
generated file was overwritten, a no-op rewrote every file, a reformatted
spec looked new, and keep regions were found by path.

The manifest now records the document's digest instead of the spec's,
a normalized digest per written file, the options version, the steps
that ran, files written once, each entity's correlation key, base name
and symbol, and each keep region by its entity, under a schema version
migrated forward. Regions follow their entity to its new file. Every
ID-keyed input that matches nothing is reported with its likely new
home. A README is merged as blocks the user writes around.

Collisions now read the prior manifest: a shipped symbol keeps its name
while its ID and correlation key agree, and only newcomers take a
suffix, so adding an endpoint renames nothing. The prior manifest joins
the inputs the purity rule names.

A spike at Go 1.27.3 chose dst for the Go merge adapter: go/ast nodes
moved between files lost their doc comments, one into a function body,
while dst and a text splice were both correct, and only dst reaches
inside a declaration. Claims that IDs survive any rename are narrowed to
renames that keep the pointer.
Collision resolution now keeps the names the prior generation shipped,
read from its manifest, but the refiner context carried no prior state,
so the naming step had nothing to read it from. Ctx.Prior carries the
PriorState the emitter is given.
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