Optionalpolicy: RedactionPolicyOptionalmarked: Set<string>Optionalbehind: (value: unknown) => unknownDoes a read with no key hand over something this rule selects? It hands over everything under path, a record
handed out whole: the path's own verdict, or a selected name at any depth inside it (carriesSelectedKey).
A policy that selects nothing present says no. StageContext · noteKeylessRead counts the answer for the taint law.
A subflow mapper's copy inherits the fields of the value it copied:
target is a key the mapper wrote; paths the source key's field paths
(declared or themselves inherited). Run-wide, by name, like a mark.
Nothing can mask a diagnostic entry or a thrown value — no state selector, no mark, no diagnostic selector: the collectors' and the error paths' fast path (a no-policy run never reads a payload, including borrowed getters).
Nothing can mask flow-message text — only policy.diagnostics selects it (retainFlowText).
True when this rule has nothing to say about ANY path — no key, pattern
or field in the policy and no key marked so far. The no-policy fast
path: StageContext skips the verdict (and its path allocation) on
every tracked read and staged write while this holds. A per-call
setValue(key, value, true) marks a key and ends it.
Key-level verdict: marked, listed in policy.keys, or matching a pattern.
Whether this rule ITSELF keeps key hidden: a mark, or fields a mapper handed it. Not what the
policy selects: no delete ends that, so it needs no place.
A value landed at place, at or under key. Noted only for a name this rule itself keeps.
What the logger is handed: the thrown value itself unless the policy masked its text.
Mark key for the rest of the run. place, when the caller knows it, is where the hidden value
landed (markPlaces.ts · placeOf); without one the name is marked and its place follows with the write
that lands the value (landed).
Optionalplace: stringThe shared marked-keys set — for the @internal sharing protocol and decide().
The run's marks and the fields mappers handed to new keys, as NAMES — what
a pause carries to the resumed run (FlowchartCheckpoint.redactionMarks).
undefined when there are none (no policy, no per-call mark).
Seed a resumed run's rule with the marks its pause carried (marksForCheckpoint).
The retained / served form of a value at a path: the placeholder, a
scrubbed CLONE, or the value itself (same reference — recorders receive
borrowed live references for clear values, as they always have).
placeholder defaults to the scope-tier '[REDACTED]'; the mirror seed
passes the log's 'REDACTED'.
The retained form of a BOUNDARY record — root input/output and a subflow's
mapped seed, before any observer sees it. retainRecord decides each
top key; then every NESTED own key is a key too: secret when its own name
or its dotted path is redacted at key level (keys: ['secret'] covers
{ wrapper: { secret } }; a root array's elements are walked the same
way), and its declared fields scrubbed inside it (fields: { profile: ['token'] } covers { wrapper: { profile: { token } } }). Decided per
PATH, copy-on-write per path (servedByPath): an object shared by
two paths is masked at the one a path rule names and served as it is at
the other; a cycle edge lands on the served copy, never on an unscrubbed
original; the live value is never touched. Returns the SAME object when
nothing nested is secret, and an inert rule enumerates nothing. State
keeps its own path verdicts (verdict); this walk is for records
handed out whole.
Retain a NAMED diagnostic entry — address is [channel, ...path, name]
($debug('token', v) → ['logs', 'token']; $log(v) → ['logs', 'messages']). THE MAPPING: the entry is the record { [name]: value }
handed out whole, so it is retained as a boundary record holding the
state key name would be — the state verdict at [...path, name] (keys,
patterns and marks at every dotted prefix and at the name itself; the top
key's fields), then every nested own key whose name or dotted path is
selected (retainBoundary's walk). The channel takes no part:
logs.token, errors.token and metrics.token are all the state key
token. policy.diagnostics (diagnostic-only selectors, rooted at the
channel) applies on top. Clear values keep their identity.
Retain an emitted payload BEFORE dispatch/capture. Emit names are not state paths: no key-length cap, marks or field scrub applies here. A clear payload keeps its identity; diagnostic bags are untouched.
Retain flow-message text (flowMessages.description / .rationale): text
the engine composes has no name of its own, so no state selector reaches
it — only policy.diagnostics selects it.
An idempotency key as records serve it (P3, design §6). The key is a VALUE, and the documented way to make one
is from the input or the state (order-${args.orderId}) — so it is text the stage made: retainStageText.
The retained form of a STATE record (state read, mirror seed): each own enumerable string key through retain. Fields are paths inside that keyed value. Scalars and root arrays pass through. Boundary records add the nested-key walk: retainBoundary. Returns the SAME object when unchanged. An inert rule does not enumerate the value, so even an accessor is untouched on the no-policy fast path.
The served form of a thrown value, decided ONCE at its error site from the
text the diagnostic collector retained for errors.stageExecutionError
(kept) and from the value itself — a record handed out whole, masked
when one of its own keys, at any depth, carries a selected name.
Unmasked: today's structured info. Masked: the error's name/code kept,
no issues (they quote the input) and no raw error (it holds the text
and the keys); the message is the placeholder when the text was selected,
the text itself when only a key was — remembered so onRunFailed, the
logger and a fork envelope serve the same form (servedError). The
thrown value itself stays real.
Text a stage's own code made, as records serve it — an idempotency key, the text a decision rule threw, a break
reason. Selected by its NAME (the diagnostic entry at address: a state rule of that name, or policy.diagnostics)
AND by its SOURCES, conservatively, the subflow-mapper taint law's way (sourcesSelected): it is then served
whole as the placeholder. Never value equality. An inert rule never reads it.
It is a string once it is made, so nothing can change it afterwards. An object where the type says text (a JS caller,
an Error whose message is one) is a hole: turning it into text runs its toString AFTER the sources were asked,
and that code can read a selected value.
The retained form of a whole STATE — retainRecord over the root
keys AND over every run namespace under runs (fork children write
there; a checkpoint's sharedState carries it back as a seed). This is
what seeds the redacted mirror: the store's starting state, scrubbed
with the log's placeholder, so a policy key that arrives by seed and is
never re-written is still served as the placeholder.
What any observer is served for a thrown value — retry, throttle, run failure, a fork envelope, the logger: its remembered stage form, else the same decision made now (and remembered), else its structured info.
A filter's threshold as a condition row keeps it. THE ONE LAW for a threshold: every condition row asks it, once. A threshold is author code's value and may have been copied from anything, so it is served only when nothing says it holds a selected value:
verdictAt: keys, patterns, marks AND fields, never the marked set alone);in element being a record handed out whole
under its key's name (carriesSelectedKey), and it was not read from a value this run served masked.Otherwise the row serves a hole, never a partial scrub. Conservative: the count is per frame, so once a stage read a selected value every later threshold in it is hidden. A threshold in a stage with no selected read, under a key nothing selects, stays visible: it is what makes the decision explainable.
An inert rule reads nothing and keeps the value itself, the reference, for its whole life. behind turns a threshold
that IS a scope handle ({ in: s.allowed }) into the state behind it: the library's own Proxy is no reason to hide
state nothing selects. A handle inside an object the stage built stays a Proxy like any other, so a hole.
The key is gone: end its mark, and take back any fields a mapper gave it. A policy verdict survives it.
With place (where the delete landed), only that place is taken away, and the mark ends when it
was the LAST one: another key of the same name, in another sub-flowchart or parallel branch, may
still hold the hidden value. A delete at a place where no value of that name landed takes nothing
and changes nothing — it removed no value this mark stands for (and a value may still be on its
way, markPlaces.ts · pendingPlace). Without place the caller does not say where, and the mark
ends whatever places it has: that is for a direct caller who knows what it deleted. The engine
always says where.
Optionalplace: stringThe verdict for a user-level path — ['profile'] for a scope key,
['profile', 'auth'] for a nested write such as a subflow seed.
Every dotted prefix is tested at key level (profile, then
profile.auth), so a policy that names a key covers everything written
beneath it. fields are declared relative to the TOP key; for a nested
path they are re-based onto the value being written, and a field that
names the written path exactly makes the whole value secret.
The verdict for key written or read under path — the common
single-segment case (path empty: every facade write and read) decides
on the key string alone, no array allocated.
The verdict of a key a mapper read — runs (the run namespaces, RUN_NAMESPACE) is selected when any namespaced key is.
StaticapplyApply a verdict to a value — see retain.
StaticholdsDoes value hold one of paths — an own key of that name, or a dotted path inside it?
StaticscrubA deep clone of value with the given dot-paths replaced by the
placeholder — undefined when value is not an object (there is nothing
to scrub in a scalar; the caller keeps the value). The clone refuses an
uncloneable value. A path names a literal key ('a.b' as one property)
and, when dotted, the nested path; each is scrubbed where it exists, per
PATH — a node the clone shares between two paths is masked at the named
one only (servedByPath). Paths that do not exist are ignored —
scrubbing never invents a field.
The answers one
decide()/select()asks, live, over the stage's sources at each question.