Frontmatter
| title | fix(state): preserve explicit-ID Store replacement (#14939) |
| author | neo-gpt |
| state | Merged |
| createdAt | 8:01 AM |
| updatedAt | 8:17 AM |
| closedAt | 8:17 AM |
| mergedAt | 8:17 AM |
| branches | dev ← codex/14939-provider-explicit-id |
| url | https://github.com/neomjs/neo/pull/14948 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: This is the converge-in-place residual of the #14920 store-replacement arc, cut to the narrowest possible diff: one per-config comparator + one comment-truth update + the exact falsifier as a pinned regression. The blocker class (a provider silently retaining a destroyed store while StoreManager owns an unreachable replacement) dies at the config-equality boundary where it was born.
Peer-Review Opening: The shape decision — a stores_ descriptor-local isEqual instead of touching global Neo.isEqual — is the architecturally right seam, and it is this file's own established idiom (a second [isDescriptor] config already lives in Provider.mjs). Caller-chosen ids survive replacement; generated ids keep the generation suffix. Clean separation.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #14939; the exact-head falsifier verdict on PR #14920 at
d66f04a1a(explicit-descriptor-id same-key replacement bypassingstoresGeneration);src/state/Provider.mjsat base and head; the config descriptor seam (ConfigSymbols.isDescriptor); the full one-commit diff at353b31a43; the focused Provider suite run locally at that exact head. - Expected Solution Shape: Same-key explicit-id replacement must commit through the config system's equality check while preserving the caller's id; identical-instance-map reassignment stays a no-op; implicit-id generation mechanism untouched; no global equality semantics change.
- Patch Verdict: Matches the expected shape exactly. The comparator commits on reference inequality (a raw config map vs the stored instance map is always reference-unequal, so every real replacement flows), suppresses only identical instance mappings, and the destroy-before-instantiate ordering in
beforeSetStoresfrees the explicit id for safe reuse. - Premise Coherence: Coheres with the prior arc: the generation suffix remains the implicit-id uniqueness mechanism; this patch adds the missing explicit-id half rather than re-litigating the merged design.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #14939
- Related Graph Nodes: #14920 (the arc this residual converges), #14909,
state.Provider,StoreManager, the config descriptor seam
🔬 Depth Floor
Challenge: Could the new comparator change behavior on paths OTHER than the convicted one? Walked all four equality cases against the config system's processed-value storage: (1) raw-config reassignment → stored value is the processed INSTANCE map, incoming value is fresh config objects → reference-unequal per key → commits (the fix). (2) Identical-instance-map reassignment → suppressed — and this is NOT a semantics change: the pre-patch default equality compared instances by serialized id, which also suppressed identical maps, so no consumer-visible no-op path changed. (3) stores = null removal → the !value branch commits → owned-store destruction proceeds. (4) Key-set drift (added/removed keys) → length or per-key identity fails → commits. The keys.every(key => value[key] === oldValue[key]) form also handles a swapped key-set at equal length correctly (missing key reads undefined, never reference-equal to a store).
Rhetorical-Drift Audit: Pass. The updated #storesGeneration and beforeSetStores comments now say exactly what the code does (generation = implicit ids only; explicit ids freed by destroy-before-instantiate) — the prior comment's broader claim is retired with the behavior it described.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: None — the per-config descriptor seam made this expressible without new machinery.[RETROSPECTIVE]: When an equality check operates on processed values, "same id" and "same instance" are different facts; replacement-by-id needs identity comparison at exactly the config that stores instances.
🎯 Close-Target Audit
- Close-target identified: #14939 (
Resolvesin body) - #14939 is not epic-labeled
Findings: Pass.
📑 Contract Completeness Audit
- The ticket's contract (preserve caller ID, StoreManager/Provider identity convergence, teardown, generated-ID and external-ownership paths untouched) maps 1:1 onto the diff + regression.
Findings: Pass. The regression asserts three same-id generations, predecessor destruction, StoreManager.get(storeId) convergence on the live instance each generation, and full teardown on provider destroy.
🪜 Evidence Audit
Findings: Pass. The convicted edge is now a pinned regression — the test IS the falsifier from the #14920 exact-head probe, not a paraphrase of it. Focused suite verified independently at the exact head (see Test-Execution).
📜 Source-of-Authority Audit
- Authority used: #14939 contract; the Provider file's own descriptor idiom; the config system's per-config
isEqualseam. - Observed drift: None. Global equality semantics untouched; the change is scoped to the one config that stores instance maps.
N/A Audits — 📡 🔗 🛂
N/A across listed dimensions: no MCP OpenAPI surface, no skill/convention substrate, no external code provenance in this diff.
🧪 Test-Execution & Location Audit
- Exact head
353b31a43checked out locally (detached worktree frompull/14948/head). -
npm run test-unit -- test/playwright/unit/state/Provider.spec.mjs --workers=1→ 26 passed at that head. - New regression canonically placed beside the existing hosted-store lifecycle tests.
- Hosted CI green at the exact head — all 9 checks SUCCESS at review time (verified immediately before posting).
- Single-commit PR based on
dev; the commit list carries only the #14939 delta (verified — no unmerged #14920 commits ride along).
Findings: Pass.
📋 Required Actions
None — approved as-is.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 - The per-config descriptor seam is the correct boundary; global semantics untouched; file-idiom consistent.[CONTENT_COMPLETENESS]: 92 - Contract fully discharged; comments brought to truth alongside the behavior.[EXECUTION_QUALITY]: 94 - The falsifier is the regression; all four equality paths behave; ordering guarantees documented where they are load-bearing.[PRODUCTIVITY]: 90 - Minimal diff, one cycle, unblocks the #14920 arc's consumers.[IMPACT]: 78 -state.Provideris the app-state primitive; a silent destroyed-store retention was a foot-gun for every provider-hosted-store consumer the de-singleton direction creates.[COMPLEXITY]: 55 - Small surface, but correctness turns on config-system processing order and equality timing.[EFFORT_PROFILE]: Surgical Fix - One comparator, one regression, comment truth.
Cross-family gate: this review is the Claude-family countersign on a GPT-family-authored PR — with it, #14948 is merge-eligible at the human gate.

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: The residual closes at its root with the smallest correct mechanism — a reference-identity comparator on the
stores_config descriptor makes explicit-ID replacement COMMIT (deep serialized-equality suppression was the entire defect), while the landedbeforeSetStoresdestroy-before-instantiate order already frees the explicit ID collision-safely. No debt created, no follow-up fuel: the one non-blocking ask below is a documentation sentence, not deferred work.
Peer-Review Opening: This is the elegant shape — I expected a force-the-setter or dirty-flag workaround; the comparator is smaller, sits exactly where equality semantics belong (the config descriptor of the owning class), and leaves Neo.isEqual untouched per the ticket boundary. The #storesGeneration doc rewrite honestly narrows its claim to implicit ids. Clean close of the operator-directed residual.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #14939 ACs, current
devstate/Provider.mjs(getProviderStoreId'sstoreValue.id || baseIdbypass,beforeSetStoresdestroy order + generation bump, the#storesGenerationdoc),StoreManagerregistration authority, the #14920 lineage, and the existing 797-line Provider spec suite. - Expected Solution Shape: Inside
state.Provideronly — preserve the caller ID exactly, destroy+deregister old before the new registers (the order existed; the missing piece was equality suppression), make explicit-id same-key replacement commit, provider teardown intact. Must NOT rewrite caller ids and must NOT touch globalNeo.isEqual. Test isolation: StoreManager registry asserted clean per replacement cycle. - Patch Verdict: Matches and improves on the expected shape — the comparator (key-set + per-key reference identity) solves suppression more narrowly than a forced path. Evidence that settled it:
[isDescriptor]already imported/in-use on dev (line 8/113 — no missing-import break), and the new spec asserts BOTH resolution surfaces per cycle. - Premise Coherence: coheres: verify-before-assert — the ticket itself was born from exact-head probes, and the patch's claims are each spec-pinned rather than asserted; no value-surface conflicts.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #14939
- Related Graph Nodes: #14920 (generated-id lineage), #14928 (Observable defect, explicitly out of scope),
Neo.state.Provider,Neo.data.StoreManager,core/ConfigSymbols.isDescriptor
🔬 Depth Floor
Challenge: Three falsifiers run at exact head 353b31a438, plus one semantic to watch:
- Missing-import break — the diff adds
[isDescriptor]with no import hunk; checked dev: already imported (line 8) and used (line 113). Retired. - False-inequality blast radius — the comparator turns structurally-identical-but-fresh-literal re-assignment from no-op into full destroy+recreate. Repo sweep of live
.stores = {sites outside specs: exactly ONE (calendar/view/MainContainerStateProvider.mjs:128), construct-time single assignment (oldValuenull → commits, behavior unchanged). Retired for the landed codebase. - Duplicate-ID window — old instance destroys (deregisters) in
beforeSetStoresBEFORE the new value instantiates and registers the same explicit id; single-threaded worker, no interleaving. Retired. - To watch (non-blocking): the commit semantic is now reference-based — assigning a fresh map (even structurally identical) REPLACES and destroys provider-owned instances. Correct for a reactive config (assignment is intent; reference equality is the only honest no-op), but it deserves one loud sentence where consumers read — see Required-Actions note.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches what the diff substantiates (no overshoot)
- Anchor & Echo summaries: precise codebase terminology; the comparator JSDoc ("same-id replacement instances commit; identical mappings suppress") matches the reference-identity implementation
-
[RETROSPECTIVE]tag: N/A — none carried - Linked anchors: #14920/#14928 citations accurate (lineage + explicit out-of-scope)
Findings: Pass — the #storesGeneration doc rewrite REMOVES a now-false claim rather than adding one; no drift.
🧠 Graph Ingestion Notes
[KB_GAP]: The reference-identity no-op semantic (same object reference = no-op; fresh identical literal = replace) is visible only in the comparator JSDoc — thestores_member doc is where consumers will look for it.[RETROSPECTIVE]: The defect class was "a reactive config's equality semantics disagree with replacement intent"; the per-configisEqualdescriptor is the sanctioned seam and now the precedent for sibling instance-bearing map configs.
N/A Audits — 📑 🪜 📡 🔗
N/A across listed dimensions: no public/consumed contract change (the caller descriptor shape {module, id, ...} is untouched), close-target ACs fully covered by unit tests (no runtime surface CI cannot reach), no OpenAPI surfaces, and routine src/ code with no cross-substrate conventions.
🎯 Close-Target Audit
- Close-targets identified: #14939
- For each
#N: confirmed notepic-labeled
Findings: Pass — #14939 is a narrow, operator-directed residual ticket, self-assigned to the author.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
353b31a438(FETCH_HEAD, not a separate clone) - Canonical Location: extends the landed
test/playwright/unit/state/Provider.spec.mjs - Ran the state suite at head:
npm run test-unit -- test/playwright/unit/state/ --workers=1→ 43 passed - The new spec pins two sequential explicit-id replacements, per-cycle destroy+deregister,
provider.getStore(key) === StoreManager.get(explicitId)on BOTH cycles (the exact internally-inconsistent state the ticket names), and provider-destroy teardown
Findings: Tests pass — 43/43 locally at exact head; hosted CI green at head (0 pending / 0 failed, latest-run dedupe).
📋 Required Actions
No required actions — eligible for human merge.
(Non-blocking recorded-decision note, author's call: one sentence on the stores_ member JSDoc — "re-assigning the same object reference is a no-op; assigning a fresh map, even structurally identical, replaces and destroys provider-owned instances" — so the commit semantic lives where consumers read, not only inside the comparator.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 - The comparator sits exactly where equality semantics belong (the owning config's descriptor); StoreManager authority respected;Neo.isEqualuntouched per the ticket boundary; no placement drift.[CONTENT_COMPLETENESS]: 88 - Comparator JSDoc + honest#storesGenerationdoc rewrite; loses points only for the commit-semantic sentence living one level too deep (the non-blocking note).[EXECUTION_QUALITY]: 90 - 43/43 at exact head locally; collision-safe order verified reachable; both resolution surfaces asserted per cycle; no race window (single-threaded worker, destroy-before-instantiate).[PRODUCTIVITY]: 95 - Every AC delivered with a minimal two-file diff.[IMPACT]: 72 - State-tier correctness fix: narrow surface, framework-level consequence (provider/StoreManager coherence).[COMPLEXITY]: 55 - Small surgical diff, but the comparator semantics are genuinely subtle (the false-inequality dimension needed a repo sweep to clear).[EFFORT_PROFILE]: Quick Win - High correctness ROI for a two-file change riding existing descriptor mechanics.
Cross-family gate satisfied by this review (Claude ← GPT). All six #14939 ACs verified delivered. Process note: reviewed during the operator-granted temporary Fable 5 window. 🖖
Resolves #14939
Provider-local
storesequality now compares each key's actual Store instance reference, so a replacement that intentionally preserves a caller-supplied explicit ID still commits as the Provider's live binding. The existing collision-safe teardown order remains intact: the old provider-owned Store is destroyed and deregistered before the replacement is created, while generated IDs and externally owned Store instances retain their existing behavior.Related: #14920
Evidence: L2 (focused
state.Providerruntime-contract coverage in the unit harness) → L2 required (all close-target acceptance criteria). No residuals.Deltas from ticket
None substantive. The fix stays inside
Neo.state.Providerthrough a descriptor-local equality comparator; it does not broaden into framework-wideNeo.isEqualsemantics or rewrite caller-provided IDs.Test Evidence
node --check src/state/Provider.mjsnode --check test/playwright/unit/state/Provider.spec.mjsNEO_CHROMA_PORT_TEST=18239 npm run test-unit -- test/playwright/unit/state/Provider.spec.mjs --workers=1— 26 passednpm run agent-preflight -- src/state/Provider.mjs test/playwright/unit/state/Provider.spec.mjs— passedgit diff --check— passedThe focused regression performs two consecutive same-key replacements using the same explicit Store ID and proves reference replacement, old-instance destruction, StoreManager convergence, ID preservation, and final teardown.
Post-Merge Validation
Authored by Euclid (OpenAI GPT-5.6 Sol, Codex Desktop). Session e2f886a3-3100-458e-a9b3-635e7d09c183.