Make repeated documentation bootstrap idempotent (#758) - #765
Conversation
Collection refused two contributions for one component of one package only when they named different assets. An exact repetition — what a profile that called one bootstrap twice produces — passed. Whether the second call also installed a provider, a launcher or an execution policy is not a question this boundary can answer, and the assembly is wrong either way, so it refuses now. It has to refuse at collection rather than in the named form's index. A document that writes bare `<Syntax />` builds no index, and one that writes no `<Syntax>` at all builds no reference either, so deferring would let both run to completion on an assembly nobody validated. SYN25l proves all three document shapes refuse, with a single bootstrap as the positive control. architecture.md said the assembled contributions travel by value on the execution installation, which stopped being true when the installation field was removed. It now states what is implemented: bootstraps install additive namespaced middleware, core is the terminal, and execution collects once after trusted bootstrap and snapshots by value before the root import. The rest is vocabulary the rename left behind — the `xmd syntax` broken-pipe diagnostic an operator actually reads, the `--json` help text, comments in `plan-component.ts` and `protected.ts`, the spec's "lexical observation", and the test helpers and authored bindings in the Syntax suite. Uses of "observation" that name the error-observation boundary, the document-target catalog and the diagnostic catalog are other product concepts and are left alone.
|
Follow-up to #759, which merged as Cherry-picked unchanged from Reopened #758 until this lands. |
PR #765: Make repeated documentation bootstrap idempotent (#758)8 files, +702 / -211 Scope🔴 PR has 913 lines changed. Split into focused PRs. 🟡 913 lines changed. PRs under 400 receive more thorough review. StructuralOxlint structural signals:
SlopOxlint slop signals:
Static AnalysisOxlint: 22 diagnostics across 2 files (9 rules) no-base-to-string (5): packages/cli/src/plan-component.ts CorrectnessNo extraneous code patterns detected. |
The Syntax section still said contributions "travel by value on the execution installation", describing the `ExecutionInstallation.documentation` field #758 removed. It now states what is implemented: a package bootstrap installs its registrations and additive `Documentation` middleware together, canonical core is the middleware terminal, and canonical execution collects the composed contributions once after trusted bootstrap and snapshots them by value before the root import. That captured snapshot — not a field on an installation — is what `xmd syntax` and an authored `<Syntax>` both consume. The duplicate rule is stated where it is enforced: any second contribution for one owning package and component refuses during collection, an exact repetition included, because collection is the only boundary every execution passes through. SYN25l joins the Tier SYN table with the three document shapes it covers and its positive control. The rest finishes the vocabulary audit this feature promised, in the focused specification section and the Syntax and `xmd syntax` test titles, helpers and comments. Uses of "observation" that name the import chain's own observing, the document-target catalog and the diagnostic catalog are other product concepts and are untouched. No runtime behavior changes.
Refusing every second contribution broke `xmd workflow`. The repository-composition vocabulary is deliberately installed twice — once by an ordinary run's bootstrap and again inside the workflow attachment, because either may be the only one — and the attachment's scope descends from the run's, so both wrappers sit in one chain. Collection saw two identical contributions and refused, and seven CI shards failed on it. `installation.ts` says so in its own words: "One array, three consumers." The repeat is the product's layering, not a wrong assembly, and an identical contribution cannot change what a document is told — which is the thing the refusal exists to protect. So a bootstrap that finds its own statement already in the chain does not append it again. Two contributions are the same statement when they agree on the owning package, the asset and the exact set of components; differing in any of the three makes them two statements, and two statements about one component still refuse at collection. SYN25l now proves the repeat is idempotent — the named form, the bare form and a document with no `<Syntax>` all work, and the collected snapshot holds the contribution exactly once — with two bootstraps naming different assets as the control that keeps it from passing vacuously. The specification and architecture.md say the same.
|
Correction to this PR's own risk note. I wrote that no first-party profile bootstraps a package twice. That was wrong, and CI caught it: seven deno shards failed, all on workflow tiers.
Resolved as idempotent for an identical contribution: a bootstrap that finds its own statement already in the chain does not append it again. Two contributions are the same statement when they agree on owning package, asset and the exact set of components. Two that disagree — one component from two different assets — still refuse at collection, in either order. SYN25l now proves the repeat is idempotent across the named form, the bare form and a document with no |
Coalescing on owner, asset and component set left the documentation text out, so two bootstraps naming one path and reading different bytes — a stale build tree, a substituted reader — would have coalesced silently and kept whichever ran first. Equality now covers all four values a contribution is: owning package, asset identity, exact text, and component-name set. By value, so a fresh object each ask and a name set built in a different order are still the same statement. What reaches collection is therefore a real disagreement, and its refusal says which of the three differs rather than guessing. Two owners documenting a same-spelled component still coexist, because documentation joins by name and origin; so do disjoint sets from one owner. SYN25l becomes five rows: value-identical repetition adds nothing for the named form, the bare form and a document with no `<Syntax>`, with one bootstrap capturing exactly what two capture; a changed asset, text or overlapping set refuses in either order; distinct owner and disjoint set are the controls that keep that from over-refusing; overlapping executions stay isolated; and a layered trusted bootstrap keeps every layer's registrations *and* one documentation value. The specification and architecture.md state the four values, the coalescing rule, and the two non-conflicts.
|
Amended duplicate contract implemented at Complete-value idempotence. Equality now covers all four values a contribution is — owning package, asset identity, exact documentation text, component-name set — by value, so a fresh object per ask and a differently ordered name set are still the same statement. My previous Refusal preserved for ambiguity. Two non-identical contributions overlapping one owning-package/component-name pair refuse before root import, in either order, with the message naming which of asset, text or component set differs. No asset-only comparison, no last-write-wins. A distinct owner is not a conflict — documentation joins by name and origin — and disjoint sets from one owner coexist. SYN25l replaced by SYN25l.1–.5, matching the handoff: value-identical repetition across named/bare/no- Focused evidence, all exit 0:
Specification and |
`Documentation` is public. A package may compose `around(...)` itself and hand back two value-identical contributions without going through `contributeDocumentation()`, and that assembly is exactly as valid — but classification lived in the helper, so collection saw two contributions for one owner and component and refused. Correctness depended on which spelling a package happened to choose. Collection now owns it: snapshot the composed chain by value first, fold every completely identical contribution to one, then classify what is left. The helper appends and nothing more, so there is one rule in one place rather than two that can drift. Non-identical contributions overlapping one owner and component still refuse before root execution in either order, and a different owner or a disjoint set is still no conflict. SYN25l.6 installs two direct middleware wrappers that never call the helper — fresh objects, the same four values, name sets in opposite orders — and requires the named form, the bare form and a no-`<Syntax>` root to work with one contribution captured, then requires the three disagreements reached that way to refuse in both orders. Moving classification back into the helper fails that row and passes every other one in the tier.
|
Architecture and independent verification both pass on signed head
Auto-merge may proceed once required delivery checks and the current-main integration requirement are satisfied. |
|
The current-main integration head
Auto-merge may proceed when the restarted required checks are green. |
Why
PR #759 merged before its final correction was pushed. PR #765 carries that follow-up work, but the product layering contract has now changed: the same declarative package bootstrap may legitimately be entered by inherited and local trusted host layers.
The signed correction commit
5c62cca2f365a41c7786b11a8cebfafd3733752cimplements the former rule that every second documentation contribution for one owner/component refuses. Its earlier architecture PASS was correct for that former contract. It does not approve the idempotent behavior now required, so PR #765 needs a new exact-commit handoff and architecture verdict for the amended behavior.Outcome
One package bootstrap and two value-identical package bootstraps produce the same registrations and documentation. An identical documentation repetition contributes nothing new and succeeds. A genuinely conflicting contribution still refuses before root execution, regardless of middleware order.
Complete contribution equality
The collector snapshots and compares the complete contribution:
Equality is by captured value, not JavaScript object identity. Component names compare as a set, independent of insertion order.
An exact repetition is folded to one captured contribution. Asset-only equality is insufficient because changed text or a changed component-name set must not be hidden.
Conflict rule
Two non-identical contributions that overlap on one owning-package/component-name pair refuse the whole collection before root import. Reversing middleware or bootstrap order cannot change the result and cannot select a winner.
A different owner does not conflict merely because it documents the same component spelling: named documentation joins by component name and origin. Different owners are unequal and are not coalesced. Disjoint component sets from one owner also coexist.
Lifecycle and scope
<Evaluate allow>.Collection is already execution-scoped. “Refuse only within one scope” is therefore not a separate policy; it describes the contribution list both the former strict rule and the new idempotent rule inspect. Strict refusal would incorrectly reject nested or independently layered hosts that inherit and locally enter the same declarative package bootstrap even though the captured values are identical and no winner is needed.
Correction guide
Start from signed commit
5c62cca2f365a41c7786b11a8cebfafd3733752c, the reviewed predecessor whose strict-repeat behavior this amendment supersedes. A later branch head is implementation state, not architecture approval, until it is handed off and reviewed by exact commit.Keep:
Replace:
capturedDocumentation();Frozen evidence
Replace SYN25l with discriminating evidence that:
Set.<Syntax>, bare<Syntax />, and a document containing no<Syntax>all accept the identical repetition. The no-Syntax root reaches a sentinel effect.allownarrowing. A non-identical overlap refuses before child root or fragment effects.The controls must fail asset-only comparison, object-identity comparison, unconditional repeat refusal, last-write-wins behavior, and document-time collection.
Review guide
Start with
packages/core/src/documentation-api.ts, then review SYN25l and the nested-run/Evaluate-layering controls inpackages/core/tests/syntax-component.test.ts. Confirm the architecture and executable-MDX specification state the same complete-value rule.The focused review remains independent of CI. Return a new signed feedback SHA and the exact focused commands and results as soon as the frozen evidence passes.
Scope confirmation
<Syntax>behavior and protected ownership remain unchanged.<Evaluate>consumes the corrected captured reference; it does not receive another collector or duplicate policy.