Commit a80a5e1
authored
feat(nodeShell): assert node registration-identity invariants (#14443)
**STACKED — merging lands on `matt/be-5050-node-shell-state` (#14257,
which itself targets `feature/ecs-migration`), NOT `main`.**
## ELI-5
When a node joins or leaves a graph, the app files its "shell state"
into a store so the renderer can draw it. Two ways that filing can go
wrong today fail silently: filing a node that is already filed under a
*different* workflow strands the old entry, and un-filing a state the
store never held leaves a ghost the renderer keeps drawing. Both now
trip an assertion (throws in DEV, reports via the assert reporter in
prod) instead of drifting quietly. No behavior changes when the
invariants hold.
## What changed
`src/core/graph/nodeShell/nodeShellState.ts`
- `registerNodeState` asserts `node._graphId` is unset or already equals
`graph.rootGraph.id` before overwriting it. Membership is by state
identity, so a node re-registered under a second root graph leaves its
first bucket entry behind with nothing to remove it.
- `unregisterNodeState` captures `deleteNode`'s boolean instead of
discarding it and asserts it. `false` means the bucket did not contain
`node._state` — identity drift, e.g. `_state` reassigned after
registration.
Both messages name the node id. `_graphId` is cleared before the
assertion so a failing teardown still leaves the node detached rather
than half-registered.
## Deliberately not shipped: the duplicate-`NodeState` assertion
The third invariant asked for — asserting the store never holds two
distinct `NodeState` objects for one `(graphId, id)` — was implemented
(an auxiliary forward `Map<string, NodeState>` plus a `Map<NodeState,
string>` reverse map per bucket, so a renumbered node still de-indexes
by identity) and then **reverted**, because it is not a regression net
on this base: it fires on legitimate existing behavior.
Evidence, all from the suite on this branch:
- `LGraph.serialise.test.ts > can (de)serialise node / group titles` —
`new LGraph(data)` (`LGraph.ts:476` → `configure:2636` → `add:1092`)
keeps the *persisted* graph id, so a round-tripped copy shares a bucket
with the graph it was serialised from and collides on every node id.
- `LGraph.test.ts > Link serialization goldens` (4 tests) and
`LGraph.test.ts > deduplicateSubgraphNodeIds (via configure) > warns
when configuring a host with legacy proxyWidgets…` fail the same way.
That is 6 tests, green on this base and green again with the assertion
removed — so this is the assertion's premise being wrong, not a latent
bug those tests were hiding. `subgraphDeduplication.ts` guarantees
id-uniqueness *within* one root graph; it says nothing about two live
`LGraph` instances that carry the same id. Making duplicate ids
assertable means giving each `LGraph` instance its own bucket identity
rather than keying buckets by graph id — a separate change with real
blast radius (`getGraphNodesFor` callers, `clearGraph`, the
`canvasStore.rootGraphId` readers), not a rider on this one.
`docs/architecture/node-data-store.md` records both the two shipped
invariants and this gap.
## Tests
`nodeShellState.test.ts` gains the two cases; both are red without the
production change (verified by stashing it). The cross-root case needs
an explicit `second.id = createUuidv4()` — two fresh `LGraph`s both sit
at `zeroUuid` until `configure`, so they share a bucket and the
invariant genuinely holds.
## Verification
Targeted: `nodeShellState.test.ts`, `nodeDataStore.test.ts`,
`useNodeReplacement.test.ts`, `LGraph.test.ts`,
`LGraph.serialise.test.ts` — 82 tests pass, 0 fail. `oxfmt --check`
clean.
Full `vitest run` was executed and is the reason the third assertion was
reverted (it caught the 6 failures above). That run is **not** a
clean-suite claim: this worktree borrows the parent clone's
`node_modules`, which predates the `minisearch` dependency this branch
adds, so 222 suites failed to resolve imports for reasons unrelated to
the diff. Of the tests that did run, the only failures attributable to
the diff were the 6 named above; the rest (`assetService`,
`useAssetGridSelection`, `onboardingCloudRoutes`, …) are the same
load-dependent set #14257 reported. A reviewer should treat repo-wide CI
as the authority here, not this local run. Repo-wide `pnpm typecheck`
was likewise not run locally — the diff is 11 lines of source plus
tests, and both changed files typecheck under vitest's transform.1 parent af6567c commit a80a5e1
3 files changed
Lines changed: 97 additions & 6 deletions
File tree
- docs/architecture
- src/core/graph/nodeShell
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
135 | 153 | | |
136 | 154 | | |
137 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
73 | 90 | | |
74 | | - | |
| 91 | + | |
75 | 92 | | |
76 | 93 | | |
77 | 94 | | |
| |||
85 | 102 | | |
86 | 103 | | |
87 | 104 | | |
88 | | - | |
| 105 | + | |
89 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
90 | 111 | | |
91 | 112 | | |
92 | 113 | | |
| |||
0 commit comments