footprintjs 9.47.0 → 9.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CLAUDE.md +8 -8
- package/dist/esm/lib/builder/FlowChartBuilder.js +5 -4
- package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +1 -1
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.d.ts +1 -1
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +4 -3
- package/dist/esm/lib/engine/types.d.ts +1 -1
- package/dist/esm/lib/engine/types.js +1 -1
- package/dist/esm/lib/ids/reservedIds.d.ts +34 -0
- package/dist/esm/lib/ids/reservedIds.js +45 -0
- package/dist/esm/lib/ids/runtimeStageId.d.ts +13 -31
- package/dist/esm/lib/ids/runtimeStageId.js +12 -43
- package/dist/esm/lib/memory/DiagnosticCollector.d.ts +1 -1
- package/dist/esm/lib/memory/DiagnosticCollector.js +1 -1
- package/dist/esm/lib/memory/StageContext.d.ts +2 -1
- package/dist/esm/lib/memory/StageContext.js +1 -1
- package/dist/esm/lib/memory/backtrack.d.ts +3 -2
- package/dist/esm/lib/memory/backtrack.js +4 -10
- package/dist/esm/lib/memory/frameTypes.d.ts +98 -0
- package/dist/esm/lib/memory/frameTypes.js +14 -0
- package/dist/esm/lib/memory/index.d.ts +4 -2
- package/dist/esm/lib/memory/index.js +1 -2
- package/dist/esm/lib/memory/runPolicy.d.ts +2 -1
- package/dist/esm/lib/memory/runPolicy.js +1 -1
- package/dist/esm/lib/memory/types.d.ts +41 -98
- package/dist/esm/lib/memory/types.js +7 -8
- package/dist/esm/lib/runner/ExecutionRuntime.d.ts +2 -1
- package/dist/esm/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/esm/lib/runner/FlowChartExecutor.d.ts +2 -1
- package/dist/esm/lib/runner/FlowChartExecutor.js +1 -1
- package/dist/esm/lib/runner/getSubtreeSnapshot.d.ts +1 -1
- package/dist/esm/lib/runner/getSubtreeSnapshot.js +1 -1
- package/dist/esm/lib/slice/keysReadSources.d.ts +3 -3
- package/dist/esm/lib/slice/keysReadSources.js +3 -4
- package/dist/esm/lib/time-travel/bundles.d.ts +3 -3
- package/dist/esm/lib/time-travel/bundles.js +2 -2
- package/dist/esm/lib/time-travel/commitStops.d.ts +2 -2
- package/dist/esm/lib/time-travel/commitStops.js +1 -1
- package/dist/esm/lib/time-travel/tagStops.js +1 -1
- package/dist/esm/lib/time-travel/types.d.ts +23 -14
- package/dist/esm/lib/time-travel/types.js +9 -9
- package/dist/esm/trace.d.ts +5 -4
- package/dist/esm/trace.js +4 -4
- package/dist/lib/builder/FlowChartBuilder.js +5 -4
- package/dist/lib/engine/traversal/FlowchartTraverser.js +6 -5
- package/dist/lib/engine/types.js +1 -1
- package/dist/lib/ids/reservedIds.js +49 -0
- package/dist/lib/ids/runtimeStageId.js +26 -58
- package/dist/lib/memory/DiagnosticCollector.js +1 -1
- package/dist/lib/memory/StageContext.js +1 -1
- package/dist/lib/memory/backtrack.js +4 -10
- package/dist/lib/memory/frameTypes.js +15 -0
- package/dist/lib/memory/index.js +2 -5
- package/dist/lib/memory/runPolicy.js +1 -1
- package/dist/lib/memory/types.js +6 -11
- package/dist/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/lib/runner/FlowChartExecutor.js +1 -1
- package/dist/lib/runner/getSubtreeSnapshot.js +1 -1
- package/dist/lib/slice/keysReadSources.js +3 -4
- package/dist/lib/time-travel/bundles.js +2 -2
- package/dist/lib/time-travel/commitStops.js +1 -1
- package/dist/lib/time-travel/tagStops.js +1 -1
- package/dist/lib/time-travel/types.js +9 -9
- package/dist/trace.js +32 -32
- package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +1 -1
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +1 -1
- package/dist/types/lib/engine/types.d.ts +1 -1
- package/dist/types/lib/ids/reservedIds.d.ts +34 -0
- package/dist/types/lib/ids/runtimeStageId.d.ts +13 -31
- package/dist/types/lib/memory/DiagnosticCollector.d.ts +1 -1
- package/dist/types/lib/memory/StageContext.d.ts +2 -1
- package/dist/types/lib/memory/backtrack.d.ts +3 -2
- package/dist/types/lib/memory/frameTypes.d.ts +98 -0
- package/dist/types/lib/memory/index.d.ts +4 -2
- package/dist/types/lib/memory/runPolicy.d.ts +2 -1
- package/dist/types/lib/memory/types.d.ts +41 -98
- package/dist/types/lib/runner/ExecutionRuntime.d.ts +2 -1
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +2 -1
- package/dist/types/lib/runner/getSubtreeSnapshot.d.ts +1 -1
- package/dist/types/lib/slice/keysReadSources.d.ts +3 -3
- package/dist/types/lib/time-travel/bundles.d.ts +3 -3
- package/dist/types/lib/time-travel/commitStops.d.ts +2 -2
- package/dist/types/lib/time-travel/types.d.ts +23 -14
- package/dist/types/trace.d.ts +5 -4
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -22,7 +22,7 @@ This is the footprint.js library — the flowchart pattern for backend code. Sel
|
|
|
22
22
|
| `footprintjs/detach` | Fire-and-forget child charts and their drivers |
|
|
23
23
|
| `footprintjs/zod` | Opt-in zod bridge (`defineScopeFromZod`, …) — the core never imports zod |
|
|
24
24
|
|
|
25
|
-
The module map (one line per `src/lib/` directory) and the layering rule — a file imports only its own layer or below — live in one place each, so this file keeps no copy that can drift: `CLAUDE.md` ("Module map" and "The fence", shipped with the package) and the layer table in `scripts/layering.config.cjs`, enforced by lint (`import/no-restricted-paths` zones + `import/no-cycle`) and `npm run check:layering` (value-level cycles + upward runtime edges).
|
|
25
|
+
The module map (one line per `src/lib/` directory) and the layering rule — a file imports only its own layer or below — live in one place each, so this file keeps no copy that can drift: `CLAUDE.md` ("Module map" and "The fence", shipped with the package) and the layer table in `scripts/layering.config.cjs`, enforced by lint (`import/no-restricted-paths` zones + `import/no-cycle`) and `npm run check:layering` (value-level cycles + upward runtime edges + the closed record: a `RECORD_FILES` file imports only record files, by value or by type).
|
|
26
26
|
|
|
27
27
|
## Key API
|
|
28
28
|
|
|
@@ -1119,7 +1119,7 @@ npm run build # tsc (CJS) + tsc -p tsconfig.esm.json (ESM) + scripts/p
|
|
|
1119
1119
|
npm test # full suite (vitest)
|
|
1120
1120
|
npm run test:examples # type-check examples/, build, then run the fork example guard
|
|
1121
1121
|
npm run lint
|
|
1122
|
-
npm run check:layering # the layering rule, from scripts/layering.config.cjs
|
|
1122
|
+
npm run check:layering # the layering rule and the closed record, from scripts/layering.config.cjs
|
|
1123
1123
|
npm run check:doc-snippets # strict, isolated import-bearing TS fences; see scripts/doc-snippets/README.md
|
|
1124
1124
|
```
|
|
1125
1125
|
|
package/CLAUDE.md
CHANGED
|
@@ -8,23 +8,23 @@ Entry points: `footprintjs` (public API) · `/recorders` (factories) · `/trace`
|
|
|
8
8
|
|
|
9
9
|
| src/lib/ | one job |
|
|
10
10
|
|---|---|
|
|
11
|
-
| capture/ | retention policies + bounded payload summarization (leaf; shared by tracking dials AND deferred-observer capture) + `valueKinds` (the ONE classifier of value kinds: `deepEqual`'s arms and what freezing seals, 9.44.2) + the shared leaves `invokeHook` (the one recorder-hook invoker both delivery tiers use), `circular` (dev-mode cycle probe), `summarizeValue` (the narrative one-liner); `lib/devMode.ts` — the dev-mode flag — is its sibling leaf (all four moved out of scope/ and recorder/ in F0: they closed the one memory ⇄ scope ⇄ recorder module cycle) |
|
|
12
|
-
| memory/ | transactional state: SharedMemory heap, StageContext frames, `RecordFrame` (the record half of a frame, L3, C3: the address, the first-touch base, the two-tier read, the lazy buffer (private since C4: staged only through `write(path, value, verb, scrub?)`, which carries a redaction verdict's bytes), the readKeys list, and the commit through `recordCommit`; StageContext composes it; with `SharedMemory` and `EventLog` it is the public writer on `/write`, C5 — the engine writes through the same classes), the log's scrub (`scrub.ts`, L2: `scrubPatch` / `redactPatch`, C4), TransactionBuffer staging (+ admission.ts, the admitted record's check; deltaEncoding.ts, the delta encoder's helpers), EventLog commit log — PLUS the ONE verb law (`verbs.ts`, L1: `applyVerb`, `foldRows`, `foldKey`; leaves `paths.ts` / `equality.ts` / `merge.ts`; `utils.ts` re-exports them) and trace analysis (backtrack.ts causalChain, commitLogUtils.ts) |
|
|
11
|
+
| capture/ | retention policies + bounded payload summarization (leaf; shared by tracking dials AND deferred-observer capture) + `valueKinds` (the ONE classifier of value kinds: `deepEqual`'s arms and what freezing seals, 9.44.2; `valueKinds` and `freeze` are record files — `RECORD_FILES`, C6) + the shared leaves `invokeHook` (the one recorder-hook invoker both delivery tiers use), `circular` (dev-mode cycle probe), `summarizeValue` (the narrative one-liner); `lib/devMode.ts` — the dev-mode flag — is its sibling leaf (all four moved out of scope/ and recorder/ in F0: they closed the one memory ⇄ scope ⇄ recorder module cycle) |
|
|
12
|
+
| memory/ | transactional state: SharedMemory heap, StageContext frames, `RecordFrame` (the record half of a frame, L3, C3: the address, the first-touch base, the two-tier read, the lazy buffer (private since C4: staged only through `write(path, value, verb, scrub?)`, which carries a redaction verdict's bytes), the readKeys list, and the commit through `recordCommit`; StageContext composes it; with `SharedMemory` and `EventLog` it is the public writer on `/write`, C5 — the engine writes through the same classes), the log's scrub (`scrub.ts`, L2: `scrubPatch` / `redactPatch`, C4), TransactionBuffer staging (+ admission.ts, the admitted record's check; deltaEncoding.ts, the delta encoder's helpers), EventLog commit log — PLUS the ONE verb law (`verbs.ts`, L1: `applyVerb`, `foldRows`, `foldKey`; leaves `paths.ts` / `equality.ts` / `merge.ts`; `utils.ts` re-exports them) and trace analysis (backtrack.ts causalChain, commitLogUtils.ts). The record's types are in `types.ts`, which imports nothing; `ExecutionTree` there is the tree as the readers read it (C6). The frame's types are in `frameTypes.ts` (L4: `StageSnapshot`, `FlowMessage`, the retention dials, C6) |
|
|
13
13
|
| schema/ | duck-typed validation (`.safeParse/.parse` ⇒ 'parseable') |
|
|
14
14
|
| builder/ | fluent DSL → FlowChartSpec → StageNode graph; build-time StructureRecorder channel. NOT standalone: imports runner's makeRunnable (FlowChartBuilder.ts:18) |
|
|
15
15
|
| scope/ | ScopeFacade per stage (tracked get/set, $emit, frozen args/env); built-in ScopeRecorders in scope/recorders/ |
|
|
16
16
|
| reactive/ | TypedScope deep Proxy over the facade — four write laws + the honest boundary (what `find`/`for…of` hands back is raw and WARNED ABOUT in dev mode, not intercepted; a proxy held past its stage IS refused — `ScopeFacade` · `assertLive`); handles.ts = the registry of every proxy the factories built → the value behind it, asked wherever an app value enters the record (the set traps since 9.24.0 — no JSON round-trip any more; fan-out `resolveItems`, subflow in/out mappers, `$setValue`/`$update`/`$batchArray` since 9.23.3, after a fan-out over object items seeded the handle and lost every child); see src/lib/reactive/README.md |
|
|
17
17
|
| decide/ | decide()/select() evidence capture |
|
|
18
|
-
| recorder/ | stores (KeyedStore/SequenceStore/BoundaryStateStore), CombinedRecorder routing, built-ins (Topology/InOut/ControlDep/Quality) |
|
|
19
|
-
| slice/ | variable-first backward slicing (triage query layer): sliceForKey (anchor at last writer → causalChain) + arrayProvenance/elementProvenance (append-fold element births — the agent mega-key fix) + KeysReadSource strategies. Pure post-hoc queries; imports memory/ ONLY (see src/lib/slice/README.md) |
|
|
20
|
-
| time-travel/ | the READER'S cursor over a finished trace (9.17.0): `timeTravel()` stops/moves/marks/`drill()`, `stateAt()` fold + honesty basis, `TimeTravelStrategy<TMeta>` seam (one shipped: `commitStops`). 9.18.0 composer's side: axis.ts (`splitAxis` = the ONE guard of the `[start,…stages,end]` contract; `filterStops` = filter + re-partition + `Stop.meta` vocabulary + `Stop.prologue` on an absorbing start — a FLAG, deliberately not a 5th StopKind), bundles.ts (per-bundle narrowing of `unknown[]` rows → index-holding GAPS in `FoldedState.skipped`), chain.ts (`timeTravel([paused, resumed])`: commit indices stay RUN-LOCAL + `Stop.sourceIdx`; refused unless execution indices are monotonic across legs and no id repeats). Read-time only — never a second live cursor.
|
|
18
|
+
| recorder/ | stores (KeyedStore/SequenceStore/BoundaryStateStore), CombinedRecorder routing, built-ins (Topology/InOut/ControlDep/Quality); `CommitRangeIndex` is a record file, ranked L3 (C6) |
|
|
19
|
+
| slice/ | variable-first backward slicing (triage query layer): sliceForKey (anchor at last writer → causalChain) + arrayProvenance/elementProvenance (append-fold element births — the agent mega-key fix) + KeysReadSource strategies. Pure post-hoc queries; imports memory/ ONLY (record files; its tree is `ExecutionTree`, C6) (see src/lib/slice/README.md) |
|
|
20
|
+
| time-travel/ | the READER'S cursor over a finished trace (9.17.0): `timeTravel()` stops/moves/marks/`drill()`, `stateAt()` fold + honesty basis, `TimeTravelStrategy<TMeta>` seam (one shipped: `commitStops`). 9.18.0 composer's side: axis.ts (`splitAxis` = the ONE guard of the `[start,…stages,end]` contract; `filterStops` = filter + re-partition + `Stop.meta` vocabulary + `Stop.prologue` on an absorbing start — a FLAG, deliberately not a 5th StopKind), bundles.ts (per-bundle narrowing of `unknown[]` rows → index-holding GAPS in `FoldedState.skipped`), chain.ts (`timeTravel([paused, resumed])`: commit indices stay RUN-LOCAL + `Stop.sourceIdx`; refused unless execution indices are monotonic across legs and no id repeats). Read-time only — never a second live cursor. Record files only (`RECORD_FILES`, C6): memory/ + `ids/runtimeStageId` + `capture/freeze` + `capture/valueKinds`; the tree it reads is the record's `ExecutionTree`, a supertype of `StageSnapshot` (9.48.0) (see src/lib/time-travel/README.md) |
|
|
21
21
|
| pause/ | PauseSignal / FlowchartCheckpoint types + `interrupt(scope, payload)` (throws InterruptSignal; converted to PauseSignal at the StageRunner boundary, `pausedBy:'interrupt'`) |
|
|
22
22
|
| engine/ | FlowchartTraverser + handlers/; narrative/ = the whole FlowRecorder channel (dispatcher + 9 strategies), not just text; `lib/ids/branchSegment.ts` (moved to the `ids/` leaf in 9.37.0) = the ONE owner of the generated-segment grammar (`<stageId>~<index>`, marker `~` RESERVED); `executeStage` (:720) = the ONE funnel every stage fn passes through — and therefore the ONE owner of the declarative `retry` loop; `handlers/ResumeEntry.ts` = the ONE owner of a resume's ONE-SHOT re-entry (M2, 9.28.0: start + one hop per subflow on the pause path, each level's dispatcher continuation read from the chart, waiting sibling pauses queued at their fan-out) |
|
|
23
23
|
| runner/ | FlowChartExecutor wiring — composes `attach.ts` (RunObservers) · `resume.ts` · `checkpoint.ts` · `snapshot.ts` · `options.ts` (F9, 9.41.0); (runtime, recorders, pause/resume, snapshot, DeferredObserverTier). RunContext.ts is a fluent run builder, NOT the threaded per-run context |
|
|
24
24
|
| observer-queue/ | pure deferred-delivery pipeline (engine imports IT, never reverse) |
|
|
25
25
|
| contract/, detach/ | I/O schema normalization (OpenAPI generation belongs to runner/RunnableChart); fire-and-forget child charts (drivers passed explicitly) |
|
|
26
26
|
|
|
27
|
-
**The fence (F0):** a file imports only its own layer or BELOW — L0 values/leaves · L1 verbs · L2 staging+commit · L3 the log as a read model + the record half of a frame (`RecordFrame`) · L4 frame+run policy+the redaction verdict (`redaction.ts`, C4) · L5 scope/recorders/hooks · L6 engine · L7 builder+executor · L8 the `src/*.ts` entry barrels. The table is DATA in `scripts/layering.config.cjs` (most specific pattern wins; an unplaced file is an error, and `memory/` has no directory default because it spans L0–L4) and has three readers, none with a copy: `.eslintrc.js` (generated `import/no-restricted-paths` zones + `import/no-cycle`, src only), `npm run check:layering` (value-level cycles at module AND file level + upward RUNTIME edges; type-only imports excluded — the set equals the compiled CJS `require` graph), `test/architecture/layering.test.ts`. Three edges are named on purpose — builder → `runner/RunnableChart.ts` (`makeRunnable`), engine → `reactive/handles.ts`, scope → `detach/spawn.ts` — plus a short `TYPE_ONLY_ALLOWANCES` list (upward
|
|
27
|
+
**The fence (F0):** a file imports only its own layer or BELOW — L0 values/leaves · L1 verbs · L2 staging+commit · L3 the log as a read model + the record half of a frame (`RecordFrame`) · L4 frame+run policy+the redaction verdict (`redaction.ts`, C4) · L5 scope/recorders/hooks · L6 engine · L7 builder+executor · L8 the `src/*.ts` entry barrels. The table is DATA in `scripts/layering.config.cjs` (most specific pattern wins; an unplaced file is an error, and `memory/` has no directory default because it spans L0–L4) and has three readers, none with a copy: `.eslintrc.js` (generated `import/no-restricted-paths` zones + `import/no-cycle`, src only), `npm run check:layering` (value-level cycles at module AND file level + upward RUNTIME edges; type-only imports excluded — the set equals the compiled CJS `require` graph), `test/architecture/layering.test.ts`. Three edges are named on purpose — builder → `runner/RunnableChart.ts` (`makeRunnable`), engine → `reactive/handles.ts`, scope → `detach/spawn.ts` — plus a short `TYPE_ONLY_ALLOWANCES` list (upward import-type declarations tsc erases, which the lint rule cannot tell apart; none starts below L4 since C6); both lists are live-checked and only shrink. **The second rule (C6, 9.48.0): the record is closed.** `RECORD_FILES` (same file; 44 files, all L0–L3) is the record: a record file imports only record files, by value OR by type — `check:layering` fails on an import out of it (`recordEscapes`), on a stale entry, or on a record file above L3, and compiles the list on its own (`recordAlone`: that program must load nothing else and report no diagnostic, which catches `import()` type references and package imports); ESLint has the import zone. A new file that is the record's goes into `RECORD_FILES` as well as LAYERS. R1 = R2 = 0. A new src file under an unsplit directory inherits its layer; one under `memory/` (or an exact-path entry) must be added to LAYERS — an unplaced file fails lint and the script. Public symbols have ONE canonical door each — `test/architecture/exports.test.ts` fails on any symbol two of the seven `package.json` doors both hand out unless it is in `SECOND_DOORS` (19 standing in three reasoned groups, plus 15 `keptUntil: '10.0.0'` old `/advanced` doors of C5's moves; an alias counts as a door).
|
|
28
28
|
|
|
29
29
|
## Core state & flow
|
|
30
30
|
- `SharedMemory` (memory/SharedMemory.ts) — address-scoped heap: the frame's record frame hands it the address (`RecordFrame · address`; the engine decides it in `runAddress.ts · runAddress` / `StageContext · useAddressOf` from the ONE L4 constant `runAddress.ts · RUN_NAMESPACE`, C2–C4); committed state is **immutable-after-swap**, COPY-ON-WRITE since 9.29.0: every write (`applyPatch` → `verbs · nextGeneration`, `setValue`/`updateValue`) copies the root + the containers on each written path and SHARES every other subtree with the previous generation, then swaps. A generation is never edited; the seed is detached once at construction. Design: docs/design/2026-10-copy-on-write-commit.md.
|
|
@@ -53,10 +53,10 @@ Entry points: `footprintjs` (public API) · `/recorders` (factories) · `/trace`
|
|
|
53
53
|
- **Closed seams that look open**: ExecutionEnv (fixed type); decide() operators (evaluator.ts:19 module-private); METHOD_ROUTES; the executeNodeStep phase chain; emit dispatcher (rides scope channel).
|
|
54
54
|
|
|
55
55
|
## Change-impact map
|
|
56
|
-
- **Trace verbs** (`set|merge|append|delete`, memory/types.ts
|
|
56
|
+
- **Trace verbs** (`set|merge|append|delete`, `memory/types.ts · TraceEntry.verb`) → ONE law, `memory/verbs.ts` (L1) — F2 deleted the three replicas. `applyVerb` is the ONLY function with an arm per verb (a fifth verb in the union stops compiling there; `TRAITS` — `isTotal` / `recordsTail` — says what a verb IS, pinned against the step), `placeVerb` puts its value or removes the key (`ABSENT`), and two loops fold it: `foldRows` folds ONE bundle's rows into a state with the clone DISCIPLINE as a parameter (`'private'` = the PUBLIC `applySmartMerge`, which keeps its 9.28.0 contract: base deep-cloned, fully detached result — 9.29.0; `'pathCopy'` = the engine's `nextGeneration` — live commit + redacted mirror — and the folds' `applySmartMergeInto` — `stateAt`, `EventLog.materialise`; `'byReference'` = `dryFold`, the admitted record's comparison fold — 9.30.0), and `foldKey` folds ONE path's rows across a log — `commitLogUtils · commitValueAt` (anchored at the last total row) and `slice/elementProvenance · arrayProvenance` (an OBSERVER of that fold: it keeps the births and has no verb switch); causalChain/findLastWriter only match paths. A bundle's merge delta is detached ONCE per bundle (`RecordedPayload`): `deepSmartMerge` dedups an array union BY REFERENCE, so the merge rows of one bundle must see the same objects (a per-row clone made `commitValueAt` double the elements — the 9.29.0 reader bug, fixed here); `foldKey` narrows that copy to its one path. **An unknown verb is REFUSED** (`UnknownVerbError`, public on `/trace`; R2) by `foldRows` (so `applySmartMerge`), `commitValueAt`, `arrayProvenance` — they used to fold it as a `merge`; `time-travel/bundles · bundleRefusal` asks the same `isVerb` and makes it a GAP instead. Pinned: the three old replicas live on as the CONTROL in test/lib/memory/property/verb-law-differential.property.test.ts (kept for the packet's lifetime). The delta encoder keeps VOCABULARY only since 9.30.0 (it picks a verb; its values come from `dryFold` of the stage's 'full' rows — `TransactionBuffer · fullRowsValue`; `replayPathVerbs`/`replayFamilyVerbs` deleted). NOT a second loop (9.22.1): `verbs.ts · supersededByNextSet` — the ONE repeated-path skip (CONSECUTIVE same-path `set` rows: only the last is applied), asked by `foldRows` before its step; its commit-side twin is the last-materialised-path memo in `toChangeOnlyPayload`. Rows are never dropped from the log; a non-consecutive skip is WRONG (a descendant op in between coerces/shells the container) — pinned by test/lib/memory/scenario/repeated-path-byte-identity.test.ts. Since 9.23.0 the clone in that memo loop (and every `structuredClone` in `toDeltaPayload`) is THE clone — the patch trees hold references until commit; no verb switch was touched.
|
|
57
57
|
- **Write address** → ONE constant, `runAddress.ts · RUN_NAMESPACE` (an L4 leaf, C4), read by `runAddress` / `useAddressOf` and by `RedactionRule · verdictOfRead` / `retainState`; the frame's `RecordFrame` holds the address (C3). `SharedMemory` and the nested writers take the address as data and refuse a non-array; `TransactionBuffer` takes it too (C2, 9.45.0). `test/architecture/write-address.test.ts` refuses the namespace below L4, as a literal or a property — no exceptions since C4.
|
|
58
58
|
- **StageContext.commit / dials** → ONE owner since 9.35.0 (F5): `memory/runPolicy.ts` — a frozen `RunPolicy` (the four dials + the `RedactionRule` + the mirror flag) built ONCE by `FlowChartExecutor · createTraverser` (run AND resume), handed to `ExecutionRuntime`, held BY REFERENCE by every `StageContext` (`inheritRun` in createNext/createChild) and by its `RecordFrame` as its encoding (`useEncoding`, through `StageContext · adoptPolicy`, the one place a frame's policy changes) and passed by `SubflowExecutor · executeSubflow` to the nested runtime BEFORE the seed. A fifth dial = the policy type + its consumer. The old per-dial `use*`/`get*` and `enableRedactedMirror` were removed in 9.36.0 (no compat shims — owner ruling). `commit()`'s FIRST act (9.23.0) is `materialiseWrites` — the `writeTracking` retained form is taken there, once per key, from `_pendingWrites` (bare references + the write-time verdict); anything that reads `_stageWrites` goes through `retainedWrites()` (a non-consuming fold, so a mid-run snapshot is honest), and `discardStaged` drops the pending map with the rest.
|
|
59
|
-
- **runtimeStageId format** (`#`/`/` delimiters) → ONE grammar owner since 9.37.0 (F7): `src/lib/ids/` (L0
|
|
59
|
+
- **runtimeStageId format** (`#`/`/` delimiters) → ONE grammar owner since 9.37.0 (F7): `src/lib/ids/` (L0 leaves: `runtimeStageId.ts` — the record's half, imports nothing: build/parse/read — `parseRuntimeStageId`, `isExecutionKey`, `stageIdOf`, `subflowPathOf`; `reservedIds.ts` — the engine's id doors' ONE refusal, `refuseReservedId` (C6, 9.48.0); `branchSegment.ts` — the `~` grammar). Every parser (ScopeFacade subflow path, subflowResults dual-keying, narrative buffering, store keys) calls it; none re-derives the grammar. R5: every id door (22 at build, plus the run-time dynamic-StageNode door) refuses `#` and `/`; stage ids may contain `~`. `TraversalContext` has ONE constructor (`engine/traversalContext.ts`); `depth` = subflow nesting read off the runtimeStageId on every stamp. Brands `RuntimeStageId`/`ExecutionIndex`/`CommitIdx` on `/trace` (types only).
|
|
60
60
|
- **Subflow id prefixing** → ONE prefixer since 9.37.0: `engine/graph/prefixNodeTree.ts`, used by the builder AND the traverser (eager, lazy and `parallelForEach` mounts — lazy/branch roots were unprefixed before 9.37.0, so branch ids collided); change it + stageMap key composition + resume drilling; the builder's eager mounts all go through `FlowChartBuilder · _mountSubflow` (F10). test/lib/engine/branch-segment-prefixer-equivalence.test.ts stays as a guard.
|
|
61
61
|
- **Checkpoint shape** (format 2 since 9.44.0, LEAN: only what resume reads + the pause's record — no `executionTree`/`subflowResults`; size independent of run length, pinned by test/lib/pause/checkpoint-size.test.ts; 9.39–9.43 refuse format 2, so upgrade readers before writers) → `runner/checkpoint.ts · buildPauseCheckpoint` ↔ `pause/record.ts · decodeCheckpoint` (one upcaster drops v0/v1 fields) ↔ `runner/resume.ts · planResume` (validation via `pause/record.ts · decodeCheckpoint`) ↔ `ResumeEntry.plan` (the path's mounts + captures + dispatcher continuations, the `pendingPauses` queue; refused up front when unwalkable, ambiguous, or a pending record is not a sibling) ↔ `SubflowExecutor · executeSubflow` resume hop (seed from the capture in place of the mapper's values — the mapper still runs, for the args — FIRST entry only) ↔ `ChildrenExecutor · raisePauses` (fills `pendingPauses`). 9.28.0 adds ONE optional field, `pendingPauses` (present only when parallel siblings wait); the redaction law adds `redactionMarks` (names only, present only when the run marked something); every stored checkpoint enters through `pause/record.ts · decodeCheckpoint` (`checkpointVersion: 1`, one upcaster from unversioned v0 which drops the legacy `continuationStageId`, one validator, one message set; 9.39.0). Pinned: a 9.27.0 checkpoint resumes, test/lib/pause/resume-real-chart-9.27.0-checkpoints.test.ts.
|
|
62
62
|
- **Committed-state writers** (copy-on-write, 9.29.0) → anything that writes into `SharedMemory`'s state, a fold's working copy or the buffer's working copy must copy before it writes: `pathOps · ownSpine` for the containers on the path, `ownedRootOf` for the root, `own`/`adopt` for what it created. An in-place edit of a container it did not create edits every generation sharing it — the counted guard stays green while the generations property fails. A new READ path through `TransactionBuffer` goes through `get` (private after the first write) unless it only compares (`peek`); a read the working copy cannot answer is served from LIVE state (`RecordFrame · read`, as on 9.28.0) after `TransactionBuffer · detachBase` gives the diff base a private copy at that path — a private copy of the value handed out instead would change live state wherever 9.28.0 let an in-place edit in (a fork child's read of a parent key). `TransactionBuffer · merge` privatises its base first: `deepSmartMerge` dedups arrays BY REFERENCE, so the identity of the base is part of the bytes.
|