Skip to content

Phase 1 / Etch / M1.1.1-HF4 extension additive conflict reject#55

Merged
guysenpai merged 11 commits into
mainfrom
phase-1/etch/hf4-extension-conflict-reject
Jul 18, 2026
Merged

Phase 1 / Etch / M1.1.1-HF4 extension additive conflict reject#55
guysenpai merged 11 commits into
mainfrom
phase-1/etch/hf4-extension-conflict-reject

Conversation

@guysenpai

@guysenpai guysenpai commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

M1.1.1-HF4 — Extension additive conflict: ratify reject

Untagged hotfix (same convention as M1.1.1-HF1/HF2/HF3 — no merge, no tag from this branch; Guy merges). A dedicated post-HF3 spec session ratified reject as the normative, permanent policy for additive extension conflicts, and this aligns the implementation — establishing the invariant cooked ⇒ loadable.

Brief: briefs/m1.1.1-hf4-extension-conflict-reject.md

The invariant (complete — three forms)

The entity's {base components} ∪ {active extensions' components} must be conflict-free. Three rejected forms (never resolved by list order):

  • (a) two active extensions declare the same component;
  • (b) an extension declares a component already carried by the base (or an earlier extension);
  • (c) the same extension is listed twice in the extensions: clause.

Static conflict → fatal cook error E1797 ExtensionAdditiveConflict (distinct static message per form; no output; weld check fails in CI). Dynamic → runtime error.ExtensionComponentConflict (a/b) / error.ExtensionAlreadyActive (c), atomic. The runtime loader logic is UNCHANGED (reject was already implemented in HF3); only its conflict-policy comments were normalized.

What changed (gate by gate)

  • G1loader.zig conflict-policy comments normalized (comment-only, zero logic): dropped "PROVISIONAL"/"open design surface"; state the policy as normative, cite engine-scene-serialization.md.
  • G2 — reclassify + fatalize + remove dead warning infra + rewrite tests (atomic): extension_additive_conflict code W1791E1797; ExtensionAdditiveConflict added to CookError; deleted the Warning struct, Cooked.warnings, Builder.warnings, emitAdditiveConflict, w1791_code, the now-unused token import.
  • G3CLAUDE.md (HF4 Hotfixes row + Open decision RESOLVED) + closing audits.
  • G4cook gate completion (1st Codex review): forms (b)/(c) were unspecified in the original acceptance criteria (only (a) shipped) and the HF3/R1 accessor contract was not honored. detectExtensionConflicts now seeds the count with the entity's BASE components (unifies a/b) and validate.structure-guards each resolved prefab before any accessor getter (P1#3 — no panic on a malformed-but-rehashed prefab); duplicate extension names fail fatally.
  • G4bform (c) resolver independence (2nd Codex review): form (c) had been gated behind base_resolver orelse return, so the public cook (null resolver) let ["X","X"] cook and fail only at load. detectExtensionConflicts split into a resolver-independent lexical pass 1 (form c) + a resolver-gated pass 2 (forms a/b). Two lagging code comments widened to the full invariant.
  • G5 — docs corrected to the complete three-form invariant (CLAUDE.md, brief Recorded deviations logging both review rounds, Closing notes); drift + language audits clean.

Review defects addressed

  • 1st round (3 P1 + 1 minor): form (b) unspecified; form (c) unspecified; accessor-contract R1 violation (P1#3); BaseResolver comment ("warning" → "check").
  • 2nd round (2 defects): form (c) not detected under a null resolver (public cook path); two code comments still describing the two-extension-only case.

Validation checklist

  • grep -rn "W1791" src/ tests/ tools/ empty
  • grep -rniE "last.?extension.?wins|last extension in the list wins" src/ tests/ empty
  • zig build test green — Debug and ReleaseSafe (every gate)
  • test-extensions 23/23 (8 cook conflict tests incl. forms a/b/c, resolver-less duplicate, disjoint-OK, fail-on-first, cooked ⇒ loadable contract, malformed-but-rehashed no-panic; + a runtime dynamic-reject)
  • zig fmt --check green
  • zig build lint green
  • commit-msg hook green on every commit
  • pre-push gate (build + test + test-release) green on all pushes
  • Not merged, not tagged (per brief — untagged hotfix; Guy merges)

guysenpai added 11 commits July 18, 2026 14:37
Ratify reject as the normative policy for additive extension conflicts
(M1.1.1-HF4). A conflict — two active extensions declaring the same
component on one entity — was a non-fatal cook diagnostic no production
code read, so the cook silently produced .scene.bin files the loader then
refused to load. Fatalizing the static conflict establishes the invariant
`cooked ⇒ loadable`.

- diagnostics: reclassify `extension_additive_conflict` to a fatal E1797
  (was a non-fatal warning); doc comment states reject/fatal
- scene_cook: add `ExtensionAdditiveConflict` to CookError; the additive
  gate `fail`s on the first conflicting component (static message,
  short-circuit) and the dead warning infra is removed (Warning struct,
  Cooked.warnings, Builder.warnings, and all plumbing; unused token import)
- tests: rewrite the conflict block to fatal semantics + add the
  cooked-is-loadable contract test and a runtime dynamic-reject test
Close M1.1.1-HF4 (extension additive conflict: ratify reject). CLAUDE.md:
current-state HF4 mention, +1 Hotfixes-table row (untagged), the
extension-conflict Open decision marked RESOLVED (reject ratified, E1797).
Brief: G3 execution log, recorded deviations (none), closing notes,
Status CLOSED. Language + drift audits clean.
The ratified `cooked ⇒ loadable` invariant is "base + active extensions
conflict-free" — three forms. The prior gate covered only (a) two extensions
declaring the same component; a post-G3 review found forms (b)/(c) and an
accessor-contract gap still open (over-declared invariant). Completed here.

- detectExtensionConflicts: seed `counts` with the entity's BASE component
  names at 1 each (from eb.comp_ids via registry.componentName), then count
  each extension — a name reaching 2 is fatal, unifying (a) ext-vs-ext and
  (b) ext-vs-base; drop the `ext_len < 2` early-out (a lone extension can
  conflict with base). `eb.comp_ids` threaded from the caller.
- form (c): a repeated extension name now fails fatally (distinct static
  message) instead of silently dedup-continuing (which masked a load-time
  ExtensionAlreadyActive).
- P1#3: validate.structure runs after verifyHash and before any accessor
  getter (failure → skip), closing a panic on a malformed-but-rehashed prefab.
- BaseResolver comment: "warning (§30.5)" → "check (fatal E1797)".
- tests: +3 (form b, form c, malformed-but-rehashed no-panic).
Form (c) (the same extension listed twice) was gated behind
`base_resolver orelse return`, so `cook` — which always passes a null
resolver — let `extensions: ["X","X"]` cook and then fail at load with
`ExtensionAlreadyActive`. Form (c) is a pure lexical check and must not
depend on the resolver.

- detectExtensionConflicts split into two passes: pass 1 dedups the
  extension names (fatal E1797 on the first duplicate) with NO resolver;
  then the `base_resolver orelse return` guard; then pass 2 (base seed +
  extension schemas → forms a/b) with the resolver.
- widen two lagging code comments to the full 3-form invariant
  (diagnostics enum + CookError.ExtensionAdditiveConflict doc).
- test: cook(null resolver) on a duplicate extension → E1797 (the path the
  resolver-gated form-(c) test could not reach).
Post-review documentation close. The ratified invariant is "base + active
extensions conflict-free" (three forms a/b/c); the docs from G1-G3 described
only the two-extension case (form a). Aligns docs + drift-audit remediation.

- CLAUDE.md: HF4 Hotfixes row + the extension-conflict Open decision rewritten
  to the full a/b/c invariant (widened E1797, two-pass detector, runtime
  ExtensionComponentConflict a/b + ExtensionAlreadyActive c); Last updated.
- brief: Recorded deviations logs BOTH Codex rounds (1st: forms b/c + accessor
  contract R1 unspecified; 2nd: form c under null resolver + comment drift);
  Closing notes refreshed (best-effort scoped to a/b, form c always fatal,
  23/23).
- drift remediation (comment-only, zero logic): the test section header and the
  loader deactivate doc widened from the two-extension phrasing to "base or
  extension" declarants.
@guysenpai
guysenpai merged commit 921d455 into main Jul 18, 2026
10 checks passed
@guysenpai
guysenpai deleted the phase-1/etch/hf4-extension-conflict-reject branch July 18, 2026 23:15
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