OptionalbranchId: stringOptionaleventLog: EventLogOptionalisDecider: booleanOptionalbranchOptionalcaptureOptionalcaptureOptionalchildrenOptionaldescriptionHuman-readable description from builder (set by traverser before execution).
OptionalnextOptionalparentOptionalreadSource coverage, shared only by frames of this runtime.
ReadonlyrunThe run this frame belongs to — its identity on every event, and the run namespace it reads and writes in (runAddress), read once, when the frame is built (C3). Only useAddressOf moves the address after that, and only before the first write.
Unique per-execution-step identifier. Set by traverser before stage execution.
Unique stage identifier from the builder (matches spec node id).
OptionalsubflowSubflow identifier (set by traverser when this is a subflow entry point).
OptionaltagsDeclared tags (9.21.0) for the stage this context is executing. Set by
the traverser beside runtimeStageId (per-node identity — NOT inherited
by createNext / createChild), recorded on the first bundle
commit() writes, then released so a routine second commit on the same
context (a fork child's fan-out repeat, a mount's exit bundle) carries
none — the same once-per-execution law _untrackedSources keeps.
The attempt now running; undefined for a stage the attempt runner does not run.
The attempts of this execution so far, in order — its own and its calls'; undefined when there are none.
The ended series' summary; undefined while it runs and without a policy.
Whether this frame has committed — a call's record arriving after it is late (P2, ScopeFacade · call).
The run's signal, as StageRunner last saw it.
How many tracked reads of this frame read a value the run's rule selects
(a key it masks whole, or one with secret fields). A boundary that maps
what a stage function READ into a new name — the parallelForEach items
selector — compares it before and after, as a subflow mapper's taint does
(memory/redaction.ts · MapperTaint). Always 0 without a policy or a mark.
selectedReads as the taint law asks it (RedactionRule · retainStageText, serveThreshold): a stage's
own effect key may be made of a selected value, so holding one counts as a read of it.
Optionalpath: string[]Optionalpath: string[]Optionaloptions: {Optionalpath: string[]Optionalpath: string[]Optionaldescription: stringReset only at the node's execution stamp, never between attempts.
Start an execution's side-effect bookkeeping (FlowchartTraverser · executeStage, before attempt 1) — reset,
because a frame can be re-used by a later execution. keyTainted: the stage's own key may be made of a selected
value, so a key a $call passes on ($attempt().idempotencyKey) is served the same way.
Native function-entry fact, including the direct one-call path with no retry runner.
The signal the calls of stage attempt stageAttempt run under besides its own (P2): aborted when that attempt is
over — however it ended — and when the frame commits, so no call outlives the attempt that made it (no
background retry, no wait, no listener left on the run's signal). Already aborted after the commit.
Capture from this frame's OWN log. A manual/unaddressable frame stays unknown.
Flush staged writes to shared memory and RELEASE the per-stage staging state (#13b).
phase (9.39.0) is the caller's statement of what a CONTINUATION commit
is — 'exit' from a subflow mount's exit, 'repeat' from a fork
fan-out's settle. It is stamped on the bundle (CommitBundle.phase) only
when this frame has ALREADY committed: the FIRST bundle of an execution
is the stage's own and never carries a phase — so a mount without an
outputMapper (a lazy mount, every parallelForEach branch), whose exit
is its only bundle, records that bundle as its own, tags and all.
Commit is the stage's lifecycle end: the record frame's buffer and
first-touch base are only needed DURING execution, as the read snapshot +
net-change diff base, so the record frame releases them once the commit
has landed and the observer has seen it (RecordFrame · release — why a
frame that kept them would retain one state generation per executed
stage, and why a re-used frame re-anchors on current state, is there).
RE-USE AFTER COMMIT stays correct because both re-create lazily:
workingCopy was reset on
commit, falling reads through to live state) but kept the ORIGINAL
baseSnapshot as diff base — unreachable in practice: the engine's
only writes after a commit are the subflow merge-back into a committed
branch parent (a decider or fork frame commits before its branch runs);
the other "write after commit"
sites (SubflowExecutor seed → replaces the context; resume → fresh
context via leaf.createNext) never re-use a committed context.ScopeFacade, sealed by the commit observer — 9.22.0),
because the frame's re-usability is exactly what the engine paths
above need._stageWrites / _stageReads are NOT released — snapshotSelf()
reads them post-run for the execution-tree snapshot.Optionalphase: CommitPhaseCreate (or return) this context's linked successor.
MEMOIZED: the first call creates this.next; every later call returns
that SAME context and IGNORES its arguments. In normal traversal each
context advances exactly once, so the memo never bites — but a caller
expecting a fresh context for different stageName/stageId args gets
the old one silently. Dev mode (enableDevMode()) warns on that
mismatch (backlog B4).
Throw away everything this stage has STAGED, without committing any of it.
The isolation primitive behind declarative retry: when a non-final attempt
fails, the writes it staged must not be visible to the next attempt, must
not reach shared state, and must not appear in the commit log or in this
stage's snapshot. Dropping the staging state achieves all four at once —
there is no rollback machinery to invoke because nothing was ever applied
(M1: the transaction buffer holds writes until commit() flushes them).
What is released, and why each one matters for the next attempt:
RecordFrame · discard) — the buffer
(the next attempt starts with nothing staged), the
first-touch base (it re-anchors on committed state as
it stands NOW — a sibling fork branch may legitimately
have committed in between) and the readKeys list (#P1:
a discarded attempt's reads must never reach the next
attempt's TraceEntry.readKeys);_stageWrites / _pendingWrites / _stageReads — the snapshot
payload; without the reset the execution tree would
report writes that were discarded, which is the exact
lie this feature exists to prevent (the pending map
holds bare references — dropping it un-clones nothing);_untrackedSources — the D2 honesty markers, released with the rest.What is deliberately KEPT: debug (logs, metrics, errors, flow messages)
and the attempt records (10.1). Both are the append-only record of what
every attempt did, and the retry feature's whole point is that the earlier
attempts stay visible.
Also KEPT: what the attempt read under a selected name — the count
(_selectedReads, so selectedReads and taintedReads still
count it) and the objects it read (_selectedObjects, so stageWrite
still hands their rule on). A later attempt can hold what a failed one
read (a closure, a module variable), and the library cannot tell, so a
selected read in any attempt marks the later ones: a threshold or break
reason they serve is masked, and such an object keeps its rule when a
later attempt writes it under another name. This is the conservative
side for redaction (test/lib/engine/security/retry-isolation.test.ts).
No commit is recorded and no observer fires — a discarded attempt is not a cursor stop, because from shared state's point of view it never happened.
Attempt attempt ended without an answer: from now on its scope is fenced — a write, an emit or a read
through it is refused (ScopeFacade). The runner calls this the moment the attempt's signal aborts.
Stamp the series' summary — once, when the runner ends the series.
The run's policy this frame retains, encodes and scrubs under.
Returns the redacted mirror if installed, else undefined.
The installed redaction rule, if any (the facade's lookup).
Returns the SharedMemory instance (needed by scope layer).
Tracked read. The returned value is BORROWED — see the contract on
ScopeFacade.getValue. Read-tracking cost is policy-gated (#14):
'full' keeps a clone of the value in _stageReads (the default), once
for each object a key holds (keepOnce); 'summary' records a
cheap marker, 'off' records nothing.
Optionalkey: stringOptionaldescription: stringRead state without tracking in _stageReads or paying structuredClone cost. Used by ScopeFacade.getValueSilent() for array proxy internal operations.
Optionalkey: stringWhether attempt attempt has ended — its scope may no longer touch the stage.
Does committed state hold key at its root, as it stands now? A name is asked, no value is read.
value is about to be written at path/key in PIECES: a subflow's merge-back merges a plain
object field by field and appends a list as a new array. The write funnel then sees only the
pieces, so an object this frame read under a selected name would lose its rule on the way (step 2
of stageWrite, which follows an object by identity). Asked here with the whole value
first, the target inherits what a whole write would have given it.
getValue for an answer that hands the stage a NAME, never the value: a key listing and its per-key check.
The same read and the same rows, but it does not count for the taint law (taintedReads): a stage that knows
a name holds nothing a rule selects. Only ScopeFacade · getStateKeys and listsKey ask it.
Optionalkey: stringA keyless touching $call started: an interrupt() after it would re-send it on resume (StageRunner).
Delegate the explicit retained-error fact; attention owns validation of its proof.
RFC-003 D2: record that this stage consumed an untracked read path.
Called by ScopeFacade (getArgs/getEnv/unshadowed getValueSilent);
surfaced as CommitBundle.untrackedSources on this stage's commit.
OptionalshouldRedact: booleanThe next call's ordinal within stage attempt stageAttempt, 1-based (attempt/call.ts · callSeries).
The answered call may feed subsequent writes in this live owning attempt.
Capture-only invocation note; independent of staged business-state writes.
A read past the tracked funnel still reads what the rule selects: a stage's silent read leaves no row but the value is in its hands all the same, by the key's verdict or, with no key, by noteKeylessRead. Counted, never retained or reported.
A read of one ROOT key of live committed state, recorded as a read of THIS step. The mount of a
subflow reads this way: its two mappers (engine/handlers/mapperScope.ts) and its merge-back,
which reads what is there before it adds to it (SubflowInputMapper · applyOutputMapping). The
value is live state at the root, exactly what those were handed before. What is new is that the
read is RECORDED: on the frame's read prefix, under the rule's verdict, in the taint count and
in stageReads. A key the state does not hold reads undefined and records nothing, as before.
WHY NOT getValue: that serves the frame's own view, pinned at its first touch. A mount's
output mapping runs after the subflow has run, and reads state as it is THEN; a pinned view
would hand it state as it was when the mount began.
WHAT stageReads KEEPS IS THE RUN'S OWN SETTING (readTracking), as for every read of every
step: a copy under 'full', a marker under 'summary', nothing under 'off'. The first cut kept
a marker whatever the setting said, to spare a mount in a loop the copy. That broke what 'full'
promises a reader of the record (every read of a step is there as a value), and a consumer's own
test caught it. The copy is the setting's price, here as for a stage; bench/mapper-reads.ts
measures it, and 'summary' is the setting for a run that hands large values on.
WITH READ SOURCES ON (readSources: true) this read has no source of its own: the record's
source-capturing door reads the frame's view, not live root state. The capture says so
(untracked-read, status partial) and never reports a run with such a read as fully captured.
A read of key as THIS frame's writes see it (its own address, then the root), recorded as a read
of this step like any other. A subflow's merge-back reads this way before it merges a plain object
field by field: what comes out under key is what was there with those fields changed, and the
fields are written where the frame writes. (For a mount whose frame writes at the root, which is
every mount the builder makes, this is the root key; read at the root regardless, a frame that
writes under a namespace would be given the wrong writer.) A key that is not there reads
undefined and records nothing: that is all this door adds to getValue.
Append one attempt's record — the attempt runner calls this as each attempt ends.
Record a write an ended attempt tried — a name, never the value. Append-only evidence like the attempts.
A call's (or an observed) attempt record: kept beside the stage's own attempts — the first
MAX_CALL_RECORDS, the rest counted — and reported to the run's onAttempt either way. Like the stage's
records, NOT cleared by discardStaged: a failed attempt's calls are evidence too.
Optionalevidence: Readonly<Record<string, unknown>>The names of the root keys of committed state, as they stand now: what a subflow mapper may read
(engine/handlers/mapperScope.ts). Names only; a value is read through readLiveRoot.
handed is what user code was given in place of value, an object this frame read under a
selected name: the redaction rule hands a subflow mapper a VIEW of a record whose fields are
selected, not the record. It carries the same rule, so that a copy of it written under another
name is found by identity (inheritRule, stageWrite) as the record itself would be.
Register an observer that fires after commit() applies patches. Used by ScopeFacade to dispatch ScopeRecorder.onCommit events.
Optionalpath: string[]Root-level (un-namespaced) write — the subflow seed and merge-back path.
Optionaldescription: stringOptionalpath: string[]Optionalpath: string[]OptionalshouldRedact: booleanOptionaldescription: stringOptionaloperationOverride: "set" | "delete"Optionaldescription: stringOptionalshouldRedact: booleanWrite and read at ANOTHER frame's address (its run namespace) while this
frame keeps its own identity on the record — stage, stageId,
runtimeStageId, tags. A subflow mount's merge-back is the one caller
(SubflowExecutor · executeSubflow, R13): the mount frame of a branch or
fork child lands its values where its parent writes, as before, and its
bundle now names the mount instead of the stage before it. Refused once
the frame has staged anything — its buffer's address is fixed then. The
decision is the engine's (namespace ids, as since R13); the record frame
takes the address it comes to (RecordFrame · useAddress).
The attempt about to run (and the run's signal) — StageRunner sets both before it builds the scope.
Where this execution's call records are reported — the engine sets it before the stage runs.
Install the run's policy (runPolicy.ts) on this frame — called on a
runtime's root by ExecutionRuntime (at construction, and by
ExecutionRuntime.usePolicy on a same-executor resume). Descendants get
the same reference through createNext/createChild; a
subflow's runtime is constructed with it, so its seed frame and every
frame after commit under the run's dials too.
Install a parallel redacted mirror. Subsequent commit() calls apply
the already-computed redacted patches — the ones the log records — to
this mirror, beside the raw patches on the heap (foottrace's RecordFrame · commit).
Child / next contexts inherit the mirror via createNext / createChild.
Called on a runtime's root frame by ExecutionRuntime when the run's policy keeps a mirror.
Install a redaction rule — the ONE owner of "what does the policy say
about this path" (memory/redaction.ts) — as a NEW policy for this frame
(the shared one is never edited). Under an executor the rule arrives
with the run's policy; this door is for a ScopeFacade on a bare frame
(unit tests, hand-built scopes), which brings its own rule. Every staged
write and every tracked read asks it, so a write that never passes a
facade — a subflow seed, an outputMapper merge-back, a resume re-seed —
is retained under the same verdict as a facade write. Absent: every
verdict is 'clear' unless the caller passed an explicit flag.
The native root-log collector; copied to same-log frames, never child runtimes.