foottrace 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +73 -2
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/lib/capture/freeze.d.ts +91 -0
- package/dist/esm/lib/capture/freeze.js +313 -0
- package/dist/esm/lib/capture/ownData.d.ts +18 -0
- package/dist/esm/lib/capture/ownData.js +35 -0
- package/dist/esm/lib/capture/valueKinds.d.ts +57 -0
- package/dist/esm/lib/capture/valueKinds.js +141 -0
- package/dist/esm/lib/ids/runtimeStageId.d.ts +173 -0
- package/dist/esm/lib/ids/runtimeStageId.js +193 -0
- package/dist/esm/lib/memory/EventLog.d.ts +105 -0
- package/dist/esm/lib/memory/EventLog.js +147 -0
- package/dist/esm/lib/memory/RecordFrame.d.ts +232 -0
- package/dist/esm/lib/memory/RecordFrame.js +295 -0
- package/dist/esm/lib/memory/SharedMemory.d.ts +46 -0
- package/dist/esm/lib/memory/SharedMemory.js +87 -0
- package/dist/esm/lib/memory/TransactionBuffer.d.ts +521 -0
- package/dist/esm/lib/memory/TransactionBuffer.js +946 -0
- package/dist/esm/lib/memory/admission.d.ts +81 -0
- package/dist/esm/lib/memory/admission.js +152 -0
- package/dist/esm/lib/memory/backtrack.d.ts +286 -0
- package/dist/esm/lib/memory/backtrack.js +456 -0
- package/dist/esm/lib/memory/commitLogUtils.d.ts +217 -0
- package/dist/esm/lib/memory/commitLogUtils.js +383 -0
- package/dist/esm/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/esm/lib/memory/deltaEncoding.js +121 -0
- package/dist/esm/lib/memory/equality.d.ts +93 -0
- package/dist/esm/lib/memory/equality.js +261 -0
- package/dist/esm/lib/memory/eventPosition.d.ts +18 -0
- package/dist/esm/lib/memory/eventPosition.js +1 -0
- package/dist/esm/lib/memory/honesty.d.ts +93 -0
- package/dist/esm/lib/memory/honesty.js +91 -0
- package/dist/esm/lib/memory/keyPaths.d.ts +125 -0
- package/dist/esm/lib/memory/keyPaths.js +226 -0
- package/dist/esm/lib/memory/logModel.d.ts +76 -0
- package/dist/esm/lib/memory/logModel.js +363 -0
- package/dist/esm/lib/memory/merge.d.ts +26 -0
- package/dist/esm/lib/memory/merge.js +51 -0
- package/dist/esm/lib/memory/pathOps.d.ts +118 -0
- package/dist/esm/lib/memory/pathOps.js +292 -0
- package/dist/esm/lib/memory/paths.d.ts +56 -0
- package/dist/esm/lib/memory/paths.js +60 -0
- package/dist/esm/lib/memory/placeholders.d.ts +39 -0
- package/dist/esm/lib/memory/placeholders.js +39 -0
- package/dist/esm/lib/memory/recordCommit.d.ts +45 -0
- package/dist/esm/lib/memory/recordCommit.js +60 -0
- package/dist/esm/lib/memory/scrub.d.ts +52 -0
- package/dist/esm/lib/memory/scrub.js +72 -0
- package/dist/esm/lib/memory/types.d.ts +209 -0
- package/dist/esm/lib/memory/types.js +9 -0
- package/dist/esm/lib/memory/utils.d.ts +73 -0
- package/dist/esm/lib/memory/utils.js +150 -0
- package/dist/esm/lib/memory/verbs.d.ts +333 -0
- package/dist/esm/lib/memory/verbs.js +426 -0
- package/dist/esm/lib/recorder/CommitRangeIndex.d.ts +147 -0
- package/dist/esm/lib/recorder/CommitRangeIndex.js +206 -0
- package/dist/esm/lib/slice/elementProvenance.d.ts +81 -0
- package/dist/esm/lib/slice/elementProvenance.js +189 -0
- package/dist/esm/lib/slice/forwardSliceForKey.d.ts +88 -0
- package/dist/esm/lib/slice/forwardSliceForKey.js +242 -0
- package/dist/esm/lib/slice/index.d.ts +26 -0
- package/dist/esm/lib/slice/index.js +25 -0
- package/dist/esm/lib/slice/keyIndex.d.ts +137 -0
- package/dist/esm/lib/slice/keyIndex.js +292 -0
- package/dist/esm/lib/slice/keyTimeline.d.ts +49 -0
- package/dist/esm/lib/slice/keyTimeline.js +112 -0
- package/dist/esm/lib/slice/keysReadSources.d.ts +51 -0
- package/dist/esm/lib/slice/keysReadSources.js +94 -0
- package/dist/esm/lib/slice/serialize.d.ts +78 -0
- package/dist/esm/lib/slice/serialize.js +277 -0
- package/dist/esm/lib/slice/sliceForKey.d.ts +40 -0
- package/dist/esm/lib/slice/sliceForKey.js +78 -0
- package/dist/esm/lib/slice/types.d.ts +567 -0
- package/dist/esm/lib/slice/types.js +24 -0
- package/dist/esm/lib/time-travel/axis.d.ts +119 -0
- package/dist/esm/lib/time-travel/axis.js +178 -0
- package/dist/esm/lib/time-travel/bundles.d.ts +77 -0
- package/dist/esm/lib/time-travel/bundles.js +135 -0
- package/dist/esm/lib/time-travel/chain.d.ts +93 -0
- package/dist/esm/lib/time-travel/chain.js +187 -0
- package/dist/esm/lib/time-travel/commitStops.d.ts +55 -0
- package/dist/esm/lib/time-travel/commitStops.js +201 -0
- package/dist/esm/lib/time-travel/index.d.ts +22 -0
- package/dist/esm/lib/time-travel/index.js +20 -0
- package/dist/esm/lib/time-travel/stateAt.d.ts +102 -0
- package/dist/esm/lib/time-travel/stateAt.js +152 -0
- package/dist/esm/lib/time-travel/tagStops.d.ts +72 -0
- package/dist/esm/lib/time-travel/tagStops.js +101 -0
- package/dist/esm/lib/time-travel/timeTravel.d.ts +70 -0
- package/dist/esm/lib/time-travel/timeTravel.js +359 -0
- package/dist/esm/lib/time-travel/types.d.ts +470 -0
- package/dist/esm/lib/time-travel/types.js +22 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/paths.d.ts +4 -0
- package/dist/esm/paths.js +4 -0
- package/dist/esm/write.d.ts +49 -0
- package/dist/esm/write.js +47 -0
- package/dist/index.js +67 -0
- package/dist/lib/capture/freeze.js +319 -0
- package/dist/lib/capture/ownData.js +40 -0
- package/dist/lib/capture/valueKinds.js +145 -0
- package/dist/lib/ids/runtimeStageId.js +209 -0
- package/dist/lib/memory/EventLog.js +151 -0
- package/dist/lib/memory/RecordFrame.js +299 -0
- package/dist/lib/memory/SharedMemory.js +91 -0
- package/dist/lib/memory/TransactionBuffer.js +950 -0
- package/dist/lib/memory/admission.js +158 -0
- package/dist/lib/memory/backtrack.js +462 -0
- package/dist/lib/memory/commitLogUtils.js +398 -0
- package/dist/lib/memory/deltaEncoding.js +127 -0
- package/dist/lib/memory/equality.js +265 -0
- package/dist/lib/memory/eventPosition.js +2 -0
- package/dist/lib/memory/honesty.js +94 -0
- package/dist/lib/memory/keyPaths.js +242 -0
- package/dist/lib/memory/logModel.js +371 -0
- package/dist/lib/memory/merge.js +55 -0
- package/dist/lib/memory/pathOps.js +308 -0
- package/dist/lib/memory/paths.js +66 -0
- package/dist/lib/memory/placeholders.js +42 -0
- package/dist/lib/memory/recordCommit.js +64 -0
- package/dist/lib/memory/scrub.js +77 -0
- package/dist/lib/memory/types.js +10 -0
- package/dist/lib/memory/utils.js +169 -0
- package/dist/lib/memory/verbs.js +443 -0
- package/dist/lib/recorder/CommitRangeIndex.js +210 -0
- package/dist/lib/slice/elementProvenance.js +194 -0
- package/dist/lib/slice/forwardSliceForKey.js +246 -0
- package/dist/lib/slice/index.js +42 -0
- package/dist/lib/slice/keyIndex.js +312 -0
- package/dist/lib/slice/keyTimeline.js +116 -0
- package/dist/lib/slice/keysReadSources.js +100 -0
- package/dist/lib/slice/serialize.js +285 -0
- package/dist/lib/slice/sliceForKey.js +82 -0
- package/dist/lib/slice/types.js +25 -0
- package/dist/lib/time-travel/axis.js +183 -0
- package/dist/lib/time-travel/bundles.js +143 -0
- package/dist/lib/time-travel/chain.js +192 -0
- package/dist/lib/time-travel/commitStops.js +205 -0
- package/dist/lib/time-travel/index.js +31 -0
- package/dist/lib/time-travel/stateAt.js +159 -0
- package/dist/lib/time-travel/tagStops.js +105 -0
- package/dist/lib/time-travel/timeTravel.js +363 -0
- package/dist/lib/time-travel/types.js +23 -0
- package/dist/paths.js +14 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/lib/capture/freeze.d.ts +91 -0
- package/dist/types/lib/capture/ownData.d.ts +18 -0
- package/dist/types/lib/capture/valueKinds.d.ts +57 -0
- package/dist/types/lib/ids/runtimeStageId.d.ts +173 -0
- package/dist/types/lib/memory/EventLog.d.ts +105 -0
- package/dist/types/lib/memory/RecordFrame.d.ts +232 -0
- package/dist/types/lib/memory/SharedMemory.d.ts +46 -0
- package/dist/types/lib/memory/TransactionBuffer.d.ts +521 -0
- package/dist/types/lib/memory/admission.d.ts +81 -0
- package/dist/types/lib/memory/backtrack.d.ts +286 -0
- package/dist/types/lib/memory/commitLogUtils.d.ts +217 -0
- package/dist/types/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/types/lib/memory/equality.d.ts +93 -0
- package/dist/types/lib/memory/eventPosition.d.ts +18 -0
- package/dist/types/lib/memory/honesty.d.ts +93 -0
- package/dist/types/lib/memory/keyPaths.d.ts +125 -0
- package/dist/types/lib/memory/logModel.d.ts +76 -0
- package/dist/types/lib/memory/merge.d.ts +26 -0
- package/dist/types/lib/memory/pathOps.d.ts +118 -0
- package/dist/types/lib/memory/paths.d.ts +56 -0
- package/dist/types/lib/memory/placeholders.d.ts +39 -0
- package/dist/types/lib/memory/recordCommit.d.ts +45 -0
- package/dist/types/lib/memory/scrub.d.ts +52 -0
- package/dist/types/lib/memory/types.d.ts +209 -0
- package/dist/types/lib/memory/utils.d.ts +73 -0
- package/dist/types/lib/memory/verbs.d.ts +333 -0
- package/dist/types/lib/recorder/CommitRangeIndex.d.ts +147 -0
- package/dist/types/lib/slice/elementProvenance.d.ts +81 -0
- package/dist/types/lib/slice/forwardSliceForKey.d.ts +88 -0
- package/dist/types/lib/slice/index.d.ts +26 -0
- package/dist/types/lib/slice/keyIndex.d.ts +137 -0
- package/dist/types/lib/slice/keyTimeline.d.ts +49 -0
- package/dist/types/lib/slice/keysReadSources.d.ts +51 -0
- package/dist/types/lib/slice/serialize.d.ts +78 -0
- package/dist/types/lib/slice/sliceForKey.d.ts +40 -0
- package/dist/types/lib/slice/types.d.ts +567 -0
- package/dist/types/lib/time-travel/axis.d.ts +119 -0
- package/dist/types/lib/time-travel/bundles.d.ts +77 -0
- package/dist/types/lib/time-travel/chain.d.ts +93 -0
- package/dist/types/lib/time-travel/commitStops.d.ts +55 -0
- package/dist/types/lib/time-travel/index.d.ts +22 -0
- package/dist/types/lib/time-travel/stateAt.d.ts +102 -0
- package/dist/types/lib/time-travel/tagStops.d.ts +72 -0
- package/dist/types/lib/time-travel/timeTravel.d.ts +70 -0
- package/dist/types/lib/time-travel/types.d.ts +470 -0
- package/dist/types/paths.d.ts +4 -0
- package/dist/types/write.d.ts +49 -0
- package/dist/write.js +56 -0
- package/package.json +48 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scrub.ts — the commit log's scrub: the placeholder at every redacted path of a patch (C4, L2).
|
|
3
|
+
*
|
|
4
|
+
* The RECORD's half of a redaction. A redacted path is DATA by the time it reaches here — the
|
|
5
|
+
* transaction buffer's `redactedPaths`, registered by `RecordFrame · write` from the scrub it was
|
|
6
|
+
* handed — and this file only writes `LOG_PLACEHOLDER` (`placeholders.ts`) where such a path holds
|
|
7
|
+
* a value. It never decides WHAT is secret: that is the engine's verdict (`redaction.ts`, L4 —
|
|
8
|
+
* `RedactionRule` and the write decision), which also owns the OTHER placeholder, the scope channel's
|
|
9
|
+
* `SCOPE_PLACEHOLDER`. Before C4 both halves lived in `redaction.ts`, so the record imported the
|
|
10
|
+
* engine's policy module to write a string.
|
|
11
|
+
*
|
|
12
|
+
* THE LAW:
|
|
13
|
+
* 1. A path is scrubbed only where it holds a DEFINED value — scrubbing never invents a field.
|
|
14
|
+
* 2. Paths are scrubbed in the set's order, against the tree as scrubbed so far: a path under one
|
|
15
|
+
* already scrubbed finds a string, not a container, and is left alone.
|
|
16
|
+
* 3. The input patch is never edited. `scrubPatch` copies only the spine of each scrubbed path
|
|
17
|
+
* (and nothing at all when nothing is scrubbed); `redactPatch` hands back a fresh deep copy.
|
|
18
|
+
*/
|
|
19
|
+
import type { MemoryPatch } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* The commit log's scrub: `patch` with {@link LOG_PLACEHOLDER} at every path in `redactedPaths` that
|
|
22
|
+
* holds a defined value — the copy `recordCommit` records and feeds the redacted mirror.
|
|
23
|
+
*
|
|
24
|
+
* CLONE-FREE (9.33.0). The patch is the transaction buffer's commit-time payload, already the record's
|
|
25
|
+
* own copy (`TransactionBuffer · commit` clones each surviving path once; the buffer is dropped at
|
|
26
|
+
* commit), so the scrub copies only what it must not edit in place:
|
|
27
|
+
* - no path to scrub (no policy, no per-call mark — the common case) → `patch` ITSELF, no copy at all;
|
|
28
|
+
* - otherwise → a new root and a shallow copy of each container on a scrubbed path (`pathOps ·
|
|
29
|
+
* ownSpine`), every other subtree shared with `patch`. `patch` is never edited.
|
|
30
|
+
* The engine called the public {@link redactPatch} (a whole `structuredClone`) twice per commit before
|
|
31
|
+
* 9.33.0. The bytes are the same: a scrubbed path holds the placeholder, every other path the value the
|
|
32
|
+
* buffer cloned (pinned by the 9.18.1 / 9.19.1 redaction byte tests).
|
|
33
|
+
*
|
|
34
|
+
* Paths are scrubbed in the set's order, against the tree as scrubbed so far: a path under one already
|
|
35
|
+
* scrubbed finds a string, not a container, and is left alone (as before).
|
|
36
|
+
*
|
|
37
|
+
* INTERNAL — the result shares structure with `patch`. A caller outside the commit path wants
|
|
38
|
+
* {@link redactPatch}.
|
|
39
|
+
*
|
|
40
|
+
* @param redactedPaths DELIM-joined paths (`TransactionBuffer`'s `redactedPaths`; a bundle's
|
|
41
|
+
* `redactedPaths` array works too).
|
|
42
|
+
*/
|
|
43
|
+
export declare function scrubPatch(patch: MemoryPatch, redactedPaths: Iterable<string>): MemoryPatch;
|
|
44
|
+
/**
|
|
45
|
+
* Private historical copy-and-scrub helper, with its contract unchanged since FootPrint 4.x:
|
|
46
|
+
* a fresh deep copy of `patch` (`structuredClone`) with
|
|
47
|
+
* {@link LOG_PLACEHOLDER} at every listed path that holds a defined value. Shares nothing with `patch`
|
|
48
|
+
* and never edits it. (It lived in `memory/utils.ts` until 9.33.0 and in `memory/redaction.ts` until
|
|
49
|
+
* C4; the live commit path uses the clone-free {@link scrubPatch}.) Kept here to preserve the
|
|
50
|
+
* extracted implementation and its tests; this helper has no foottrace public entry point.
|
|
51
|
+
*/
|
|
52
|
+
export declare function redactPatch(patch: MemoryPatch, redactedSet: Set<string>): MemoryPatch;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts — the record's types: a bundle, its rows, its patches, its encoding, and the
|
|
3
|
+
* execution tree as the record's readers read it.
|
|
4
|
+
*
|
|
5
|
+
* Imports nothing. The standalone record names nothing outside itself: the engine's frame
|
|
6
|
+
* types — the stage snapshot, flow messages and retention dials — remain in FootPrint's
|
|
7
|
+
* `frameTypes.ts`. The closed record graph is enforced by scripts/check-layering.mjs.
|
|
8
|
+
*/
|
|
9
|
+
/** A flat key-value bag representing a state patch (overwrite or merge). */
|
|
10
|
+
export interface MemoryPatch {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
/** A single entry in the chronological operation trace. */
|
|
14
|
+
export interface TraceEntry {
|
|
15
|
+
/** Canonical path string (segments joined by DELIM). */
|
|
16
|
+
path: string;
|
|
17
|
+
/**
|
|
18
|
+
* Per-write read provenance (#P1) — the keys this stage had TRACKED-READ at
|
|
19
|
+
* the moment this path was (last) written: the temporal-prefix attribution
|
|
20
|
+
* that lets slices link a specific write to only the reads that could have
|
|
21
|
+
* fed it (a stage reading `a,b` and writing `x,y` no longer implies x←b).
|
|
22
|
+
* ABSENT unless the `writeProvenance: 'reads-prefix'` dial is on — charts
|
|
23
|
+
* that never enable it keep byte-identical commit logs. Read prefixes only
|
|
24
|
+
* grow during a stage, so under delta mode's one-entry-per-path dedup the
|
|
25
|
+
* LAST write's prefix == the union across all of that path's writes.
|
|
26
|
+
* Honest ceiling: a write with NO tracked reads before it records `[]` —
|
|
27
|
+
* "depended on no tracked reads" is information, not absence.
|
|
28
|
+
*/
|
|
29
|
+
readKeys?: string[];
|
|
30
|
+
/**
|
|
31
|
+
* - `'set'` — hard overwrite; `overwrite[path]` holds the full final value.
|
|
32
|
+
* - `'merge'` — deep union merge; `updates[path]` holds the accumulated delta.
|
|
33
|
+
* - `'append'` — (#13c-B, produced only under {@link CommitValuesMode}
|
|
34
|
+
* `'delta'`) the path's final value is its base value plus a tail of new
|
|
35
|
+
* trailing elements; `overwrite[path]` holds ONLY the tail. Replay
|
|
36
|
+
* reconstructs by concatenation. NOT idempotent — delta-mode bundles
|
|
37
|
+
* carry exactly one trace entry per surviving path.
|
|
38
|
+
* - `'delete'` — (#13c-B, produced only under `'delta'`; absorbs backlog
|
|
39
|
+
* B8) the key was explicitly removed via `deleteValue()`. Replay removes
|
|
40
|
+
* the key. `overwrite[path]` still ENUMERATES the path (value
|
|
41
|
+
* `undefined`) so key-set consumers keep seeing the changed key.
|
|
42
|
+
*
|
|
43
|
+
* `overwrite` values are therefore VERB-QUALIFIED: consumers that read
|
|
44
|
+
* `bundle.overwrite[key]` as "the full value written" must use
|
|
45
|
+
* `commitValueAt(commitLog, idx, key)` (from `foottrace`) when the
|
|
46
|
+
* log may contain delta-mode bundles.
|
|
47
|
+
*/
|
|
48
|
+
verb: 'set' | 'merge' | 'append' | 'delete';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* RFC-003 D2 — read paths that BYPASS read tracking:
|
|
52
|
+
* - `'args'` — the stage called `getArgs()` / `$getArgs()` with non-empty
|
|
53
|
+
* run input (frozen, untracked by design)
|
|
54
|
+
* - `'env'` — the stage called `getEnv()` / `$getEnv()` with a non-empty
|
|
55
|
+
* execution environment (frozen, untracked by design)
|
|
56
|
+
* - `'silent'` — the stage performed a silent read (`getValueSilent` /
|
|
57
|
+
* `getValueDirect`) of a key it never tracked-read in the same stage.
|
|
58
|
+
* Silent reads SHADOWED by a tracked read of the same key in the same
|
|
59
|
+
* stage (TypedScope array-proxy internals, `$batchArray`) are NOT
|
|
60
|
+
* flagged — their read→write edge is already captured.
|
|
61
|
+
*/
|
|
62
|
+
export type UntrackedSource = 'args' | 'env' | 'silent';
|
|
63
|
+
/** The atomic bundle produced by TransactionBuffer.commit(). */
|
|
64
|
+
export interface CommitBundle {
|
|
65
|
+
/** Auto-assigned step index (set by EventLog.record). */
|
|
66
|
+
idx?: number;
|
|
67
|
+
/** Human-readable stage name. */
|
|
68
|
+
stage: string;
|
|
69
|
+
/** Stable stage identifier (matches spec node id). */
|
|
70
|
+
stageId: string;
|
|
71
|
+
/** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex */
|
|
72
|
+
runtimeStageId: string;
|
|
73
|
+
/** Chronological write log for deterministic replay. */
|
|
74
|
+
trace: TraceEntry[];
|
|
75
|
+
/** Paths that should be redacted in UI (sensitive data). */
|
|
76
|
+
redactedPaths: string[];
|
|
77
|
+
/** Hard overwrite patches. */
|
|
78
|
+
overwrite: MemoryPatch;
|
|
79
|
+
/** Deep merge patches. */
|
|
80
|
+
updates: MemoryPatch;
|
|
81
|
+
/**
|
|
82
|
+
* RFC-003 D2 honesty markers — untracked read paths this stage consumed
|
|
83
|
+
* (see {@link UntrackedSource}). ABSENT when the stage used none, so
|
|
84
|
+
* charts that never touch those paths keep byte-identical commit logs.
|
|
85
|
+
* Causal-slice consumers (`causalChain`/`formatCausalChain`) surface this
|
|
86
|
+
* as "slice may be incomplete here". Residual limitation (by design):
|
|
87
|
+
* values smuggled through JS closures are undetectable.
|
|
88
|
+
*/
|
|
89
|
+
untrackedSources?: ReadonlyArray<UntrackedSource>;
|
|
90
|
+
/**
|
|
91
|
+
* Declared tags (9.21.0) — the names the author put on this stage at build
|
|
92
|
+
* time (`FlowChartBuilder.tag` / `options.tags`), stamped here by the
|
|
93
|
+
* traverser so a stored recording carries its own milestones without a
|
|
94
|
+
* consumer's id conventions. ABSENT when the stage declares none, so an
|
|
95
|
+
* untagged chart's log is byte-identical to 9.20.0 (the law
|
|
96
|
+
* `untrackedSources` keeps). Recorded on the FIRST bundle of each execution
|
|
97
|
+
* of the stage — the stage's own bundle, the one without a `phase` — so
|
|
98
|
+
* retry attempts stamp it once, a continuation (a fork child's `'repeat'`,
|
|
99
|
+
* a mount's `'exit'` after its merge-back) carries none, and a mount whose
|
|
100
|
+
* exit is its only bundle (no `outputMapper`) carries them on that bundle. Free strings: footprintjs assigns
|
|
101
|
+
* them no meaning; `tagStops` (foottrace) keeps a stop when any of
|
|
102
|
+
* them matches.
|
|
103
|
+
*/
|
|
104
|
+
tags?: readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* Which CONTINUATION of a stage's execution this bundle is (9.39.0) —
|
|
107
|
+
* stamped by the WRITER. THE LAW: the FIRST bundle per `runtimeStageId` is
|
|
108
|
+
* the stage's own and carries NO `phase`; only a bundle after it can:
|
|
109
|
+
*
|
|
110
|
+
* - `'exit'` — a subflow mount's exit commit (`SubflowExecutor`) after its
|
|
111
|
+
* merge-back bundle (`outputMapper`). A mount without a merge-back (a lazy
|
|
112
|
+
* mount, every `parallelForEach` branch, any mount with no mapper) has
|
|
113
|
+
* its exit as its ONLY bundle — its own, so no `phase`; the execution
|
|
114
|
+
* tree names it a mount.
|
|
115
|
+
* - `'repeat'` — the fan-out's settle commit of a fork child's frame
|
|
116
|
+
* (`ChildrenExecutor`), after the child's own bundle.
|
|
117
|
+
*
|
|
118
|
+
* A reader groups a stage's bundles by `runtimeStageId` and reads this
|
|
119
|
+
* field to know what each continuation is; it never infers it from the
|
|
120
|
+
* log's shape. A log written before 9.39.0 carries no `phase` anywhere —
|
|
121
|
+
* see `inferLegacyPhases` (foottrace) for how it is still read.
|
|
122
|
+
*/
|
|
123
|
+
phase?: CommitPhase;
|
|
124
|
+
}
|
|
125
|
+
/** The continuation a bundle records — see {@link CommitBundle.phase}. */
|
|
126
|
+
export type CommitPhase = 'exit' | 'repeat';
|
|
127
|
+
/**
|
|
128
|
+
* Per-write read-provenance policy (#P1) — the fourth dial of the
|
|
129
|
+
* readTracking/writeTracking/commitValues family, same 6-site propagation
|
|
130
|
+
* pattern (executor option → ExecutionRuntime.use* → root StageContext →
|
|
131
|
+
* createNext/createChild inheritance → SubflowExecutor duck-push).
|
|
132
|
+
*
|
|
133
|
+
* - `'off'` (default) — zero cost, byte-identical commit logs.
|
|
134
|
+
* - `'reads-prefix'` — every committed {@link TraceEntry} carries
|
|
135
|
+
* `readKeys`: the keys tracked-read BEFORE that write (temporal-prefix
|
|
136
|
+
* attribution). Cost: one Set-to-array copy per write. Consumed by
|
|
137
|
+
* `causalChain`'s `edgeAttribution: 'per-write'` and the slice layer.
|
|
138
|
+
*/
|
|
139
|
+
export type WriteProvenanceMode = 'off' | 'reads-prefix';
|
|
140
|
+
/**
|
|
141
|
+
* Policy for how commit-bundle VALUES are encoded into the commit log
|
|
142
|
+
* (#13c-B) — completes the `readTracking`/`writeTracking` dial family.
|
|
143
|
+
* Unlike those two (which gate lossy snapshot bookkeeping), this dial is
|
|
144
|
+
* **lossless in both modes** — it changes the commit log's *encoding*,
|
|
145
|
+
* never its *information*; any step's full state stays exactly
|
|
146
|
+
* reconstructable by replay.
|
|
147
|
+
*
|
|
148
|
+
* - `'full'` (default) — every surviving `set` path stores the full final
|
|
149
|
+
* value, one trace entry per operation. Byte-identical to the historical
|
|
150
|
+
* behavior.
|
|
151
|
+
* - `'delta'` — two changes, both replay-covered by `applySmartMerge`:
|
|
152
|
+
* 1. **`append` detection**: when a path's net change is "the base array
|
|
153
|
+
* plus new trailing elements" (strict prefix), the bundle records ONLY
|
|
154
|
+
* the tail under a `verb: 'append'` trace entry — the growing-history
|
|
155
|
+
* commit log becomes linear in tail size instead of O(N²) retained.
|
|
156
|
+
* A real `verb: 'delete'` entry replaces the `set: undefined` flattening
|
|
157
|
+
* for `deleteValue()` (closes the documented MemoryPatch limitation).
|
|
158
|
+
* 2. **One trace entry per surviving path** (append is not idempotent on
|
|
159
|
+
* replay): the verb is resolved from the path's base→final relationship
|
|
160
|
+
* and op mix; entries are ordered by each path's LAST touch.
|
|
161
|
+
*
|
|
162
|
+
* Honest cost note: append detection is NEW wall work — an O(|base|)
|
|
163
|
+
* structural prefix compare per array-set path per commit (today's
|
|
164
|
+
* `deepEqual` fast-fails on length in O(1) for a grown array). On a hit the
|
|
165
|
+
* commit gets cheaper in both wall and heap (the O(|final|) clone shrinks to
|
|
166
|
+
* O(|tail|)); on a miss it pays compare + full clone. `'full'` pays zero —
|
|
167
|
+
* the detection branch is mode-gated.
|
|
168
|
+
*
|
|
169
|
+
* Set via `new FlowChartExecutor(chart, { commitValues })` or
|
|
170
|
+
* `executor.setCommitValues(mode)` (before `run()`). The active mode is
|
|
171
|
+
* surfaced as the snapshot discriminant `RuntimeSnapshot.commitValues`.
|
|
172
|
+
*/
|
|
173
|
+
export type CommitValuesMode = 'full' | 'delta';
|
|
174
|
+
/**
|
|
175
|
+
* The execution tree as the record's readers read it (C6): the fields a reader touches and
|
|
176
|
+
* nothing else. `commitStops` (and so `tagStops` and every strategy built on it) reads the mount
|
|
177
|
+
* marker (`subflowId`) and the root's `id`; `keysReadFromExecutionTree` reads the keys of
|
|
178
|
+
* `stageReads`; both key on `runtimeStageId` and walk `next` and `children`.
|
|
179
|
+
*
|
|
180
|
+
* A structural type the record owns, so the readers name nothing of the engine. The engine's
|
|
181
|
+
* `StageSnapshot` (`getSnapshot().executionTree`) is one — a supertype of it, so every caller
|
|
182
|
+
* that hands a snapshot's tree over compiles unchanged — and so is a stored recording's parsed
|
|
183
|
+
* JSON with these fields. Every field is optional: a reader that finds one absent reads on.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* import { type ExecutionTree, keysReadFromExecutionTree } from 'foottrace';
|
|
188
|
+
*
|
|
189
|
+
* const tree: ExecutionTree = {
|
|
190
|
+
* runtimeStageId: 'seed#0',
|
|
191
|
+
* next: { runtimeStageId: 'decide#1', stageReads: { creditTier: 'A' } },
|
|
192
|
+
* };
|
|
193
|
+
* keysReadFromExecutionTree(tree).lookup('decide#1'); // ['creditTier']
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export interface ExecutionTree {
|
|
197
|
+
/** The stage's id (prefixed inside a subflow). `commitStops` reads the root's to place a subflow's seed. */
|
|
198
|
+
readonly id?: string;
|
|
199
|
+
/** `[subflowPath/]stageId#executionIndex` — what a reader keys on. A node without one is not a stage. */
|
|
200
|
+
readonly runtimeStageId?: string;
|
|
201
|
+
/** Present on a subflow mount: the stop there says `kind: 'mount'`. */
|
|
202
|
+
readonly subflowId?: string;
|
|
203
|
+
/** What the stage tracked-read, by key. Only the KEYS are read: a `'summary'` marker serves as well as a value. */
|
|
204
|
+
readonly stageReads?: Readonly<Record<string, unknown>>;
|
|
205
|
+
/** The stage that ran next. */
|
|
206
|
+
readonly next?: ExecutionTree;
|
|
207
|
+
/** The stages that ran as this one's children (a fork's branches, a decider's chosen branch). */
|
|
208
|
+
readonly children?: readonly ExecutionTree[];
|
|
209
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils.ts — nested-object helpers for the heap, and the memory leaves' one
|
|
3
|
+
* re-export surface.
|
|
4
|
+
*
|
|
5
|
+
* The helpers here (`setNestedValue`, `updateNestedValue`, `updateValue`,
|
|
6
|
+
* `getNestedValue`) walk a nested object by path with
|
|
7
|
+
* prototype-pollution protection. Everything else this file used to hold has an
|
|
8
|
+
* owner of its own and is re-exported, so no importer moved: the path codec
|
|
9
|
+
* (`paths.ts`), structural equality (`equality.ts`) and the union merge
|
|
10
|
+
* (`merge.ts`) at L0, and the verb law (`verbs.ts`) at L1 — `applySmartMerge`,
|
|
11
|
+
* `nextGeneration`, `applySmartMergeInto`, `dryFold`, `supersededByNextSet`.
|
|
12
|
+
* The log's scrub, `redactPatch`, moved out in 9.33.0; it is the record's own, `scrub.ts` (C4).
|
|
13
|
+
* The writers take an ADDRESS, a path prefix their caller computed, and never
|
|
14
|
+
* name one (C2: the engine decides where a stage writes, `runAddress.ts · runAddress`).
|
|
15
|
+
* Zero external dependencies.
|
|
16
|
+
*/
|
|
17
|
+
export { deepEqual } from './equality.js';
|
|
18
|
+
export { deepSmartMerge } from './merge.js';
|
|
19
|
+
export { DELIM, normalisePath, pathSegments } from './paths.js';
|
|
20
|
+
export { applySmartMerge, applySmartMergeInto, dryFold, nextGeneration, supersededByNextSet } from './verbs.js';
|
|
21
|
+
type NestedObject = {
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The two places a value at `path` can sit: under `address` (`runPath`, absent
|
|
26
|
+
* for the root address `[]`) and at the root (`globalPath`). `address` is a
|
|
27
|
+
* path prefix the caller computed — for the engine, a frame's run namespace
|
|
28
|
+
* (`runAddress.ts · runAddress`, C2), held by its record frame (`RecordFrame · address`, C3).
|
|
29
|
+
*/
|
|
30
|
+
export declare function getRunAndGlobalPaths(address?: readonly string[], path?: (string | number)[]): {
|
|
31
|
+
runPath: (string | number)[] | undefined;
|
|
32
|
+
globalPath: (string | number)[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Refuse the WHOLE selector before creating a container or copying a generation.
|
|
36
|
+
* The address's array contract is checked even when another selector is denied.
|
|
37
|
+
* Payload keys are data, not selectors, and are deliberately not inspected.
|
|
38
|
+
* @internal Shared by the nested writers and SharedMemory's pre-copy boundary.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isDeniedWritePath(address: readonly string[] | undefined, path: readonly (string | number)[] | undefined, field: string | number): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Sets a value at a nested path under `address`, creating intermediate objects as needed.
|
|
43
|
+
* A denied address/path/field leaves `obj` unchanged, including its prototype.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setNestedValue<T>(obj: NestedObject, address: readonly string[], path: string[], field: string, value: T, defaultValues?: unknown): NestedObject;
|
|
46
|
+
/**
|
|
47
|
+
* Updates a value in the object at a nested path under `address`.
|
|
48
|
+
* A denied address/path/field leaves `obj` unchanged, including its prototype.
|
|
49
|
+
* - Arrays: concatenate
|
|
50
|
+
* - Objects: shallow merge at each level
|
|
51
|
+
* - Primitives: replace
|
|
52
|
+
*/
|
|
53
|
+
export declare function updateNestedValue<T>(obj: any, address: readonly string[], path: (string | number)[], field: string | number, value: T, defaultValues?: unknown): any;
|
|
54
|
+
/**
|
|
55
|
+
* In-place value update with merge semantics.
|
|
56
|
+
* - Arrays (non-empty): concatenate onto existing
|
|
57
|
+
* - Arrays (empty): direct replace — writing `[]` clears the field
|
|
58
|
+
* - Objects (non-empty): shallow merge (spread)
|
|
59
|
+
* - Objects (empty): direct replace — writing `{}` clears the field
|
|
60
|
+
* - Primitives: direct assignment
|
|
61
|
+
*
|
|
62
|
+
* Note on empty arrays: both `value && Array.isArray(value)` and
|
|
63
|
+
* `Array.isArray(value)` evaluate the same for arrays — `[]` is truthy in
|
|
64
|
+
* JavaScript, so the `&&` guard was never the issue. The actual bug was the
|
|
65
|
+
* concat path: `[...cur, ...[]]` silently returned `cur` unchanged when `value`
|
|
66
|
+
* was `[]`, making `updateValue(obj, 'tags', [])` a no-op instead of a clear.
|
|
67
|
+
* The fix is the explicit `value.length === 0` early-return branch.
|
|
68
|
+
*/
|
|
69
|
+
export declare function updateValue(object: any, key: string | number, value: any): void;
|
|
70
|
+
/**
|
|
71
|
+
* Gets a value at a nested path with prototype-pollution protection.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getNestedValue(root: any, path: (string | number)[], field?: string | number): any;
|