ConfirmRecord
Interface: ConfirmRecord
Defined in: src/atom/types.ts:2780
One row of the confirm journal: the auditable trail of high-effect asks and
how they were answered. A needs-confirm ask lands an 'ask' row (carrying
its receipts); the human's answer lands 'approved' (the confirmed fire,
linked by transitionId) or 'declined'. The three rows of one gate share
an askId.
Kept SEPARATE from the gap ledger by design: a gated action is not unmet
demand — the capability exists, it awaited consent — so mixing the two would
poison the "what to build next" triage signal the gap ledger feeds. Rows are
token-lean and injection-safe (ids + structural facts; the only free text,
note, is length-capped, and receipts carries authored strings plus one
structured runtime DATA field, willUse — the input the human is shown).
kind GROWS, and a consumer should be written for that. Three words shipped in
0.6.0; SessionOptions.requireHumanApproval adds four, because the
library can now record facts it previously could not have: a human's ALLOW
standing on its own BEFORE any fire, a durable ALWAYS ALLOW, the moment an
approval was spent, and a crossing attempt that had no valid yes. What never
happens is a kind CHANGING meaning: every value keeps exactly what it had, and
a new one is always a new fact, never an old one relabelled.
WHY FOUR NEW KINDS AND NOT A scope: 'once' | 'always' FIELD. A new field is
silently ignored by a consumer that does not know it exists, so a DURABLE grant
would be counted as a one-time yes by every 0.6-era filter — and here being
missed is a security misreading, not a cosmetic one. A new kind is unmissable.
So read a row by the kind you know and let the rest fall through; an exhaustive
never check over the old three is the one consumer shape this stops
compiling.
Properties
affordanceId
affordanceId:
string
Defined in: src/atom/types.ts:2804
askId
askId:
string
Defined in: src/atom/types.ts:2803
Links the ask → decision → fire rows of one high-effect gate. On an
'always-approved' row it is that policy's own id ('grant#1'), carried by
every 'used' row the grant authorizes — so the journal shows how many times
a standing yes was exercised.
by?
optionalby?:string
Defined in: src/atom/types.ts:2824
Who answered — an operator id, an email, your host's label. Optional.
enforced?
optionalenforced?:true
Defined in: src/atom/types.ts:2833
Present (true) on every row the enforcement path wrote — so an auditor can separate rows the gate will honour from the pre-enforcement journal's rows, without inferring it from a kind.
expiresAt?
optionalexpiresAt?:number
Defined in: src/atom/types.ts:2848
When a standing grant stops authorizing (epoch ms). Absent = no time limit.
kind
kind:
"refused"|"approved"|"declined"|"always-approved"|"ask"|"used"|"revoked"
Defined in: src/atom/types.ts:2796
'ask'— a high-effect gate opened; carries the receipts.'approved'— a human's ALLOW. Single-use: one yes, one fire.'always-approved'— a human's ALWAYS ALLOW: a scoped standing policy, never consumed, and deliberately NOT bound to an input (seescopeInstance).'declined'— a no. From the human's own door (Session.declineAsk) it is terminal for that askId; any other decline under enforcement is a report that closes nothing, and says so withrelayed.'used'— an approval was SPENT by a fire (transitionId).'refused'— a crossing attempt with no valid yes (rejectionReason).'revoked'— a yes was withdrawn: a standing grant (Session.revokeAlwaysApprove) or a single unspent approval (Session.revokeAsk). Always a NEW row referencing the askId — the answered row it withdraws is never rewritten.
node
node:
string
Defined in: src/atom/types.ts:2807
note?
optionalnote?:string
Defined in: src/atom/types.ts:2826
Free-text note (length-capped). On a decline, typically why.
principal
principal:
Principal
Defined in: src/atom/types.ts:2810
Who asked ('ask'), or the principal that recorded the decision.
receipts?
optionalreceipts?:ConfirmReceipts
Defined in: src/atom/types.ts:2813
The receipts that rode this ask (present on 'ask' rows).
rejectionReason?
optionalrejectionReason?:"APPROVAL_REQUIRED"|"APPROVAL_SPENT"|"APPROVAL_MISMATCH"|"APPROVAL_STALE"|"APPROVAL_DECLINED"|"APPROVAL_REVOKED"
Defined in: src/atom/types.ts:2856
Why a crossing attempt was refused ('refused' rows) — joins the gap ledger.
relayed?
optionalrelayed?:true
Defined in: src/atom/types.ts:2844
Present (true) on a 'declined' row that RELAYS a refusal instead of
recording the human's own decision — an agent's report, or any
Session.declineConfirm under enforcement. The ask it names is still
OPEN: nothing was closed, and groundTruth() keeps saying the person is
deciding. Without it an auditor would have to infer the difference from
principal, which on this row is the caller's claim rather than a fact — and
a fabricated no that reads like a real one is the same forgery as a
fabricated yes. A human's no (declineAsk) never carries this.
scopeInstance?
optionalscopeInstance?:string
Defined in: src/atom/types.ts:2846
An ALWAYS ALLOW scoped to one row of a list (an order id). Absent = any instance.
stateVersion?
optionalstateVersion?:number
Defined in: src/atom/types.ts:2854
The STATE version when the decision was recorded — the anchor for HumanApprovalPolicy.refuseWhenWorldMoved. Stamped always; enforced only when asked.
timestamp
timestamp:
number
Defined in: src/atom/types.ts:2806
Epoch milliseconds when the row was recorded.
transitionId?
optionaltransitionId?:string
Defined in: src/atom/types.ts:2821
The TransitionRecord.id of the fire this row is about. Present on 'used'
rows, and on a 'approved' row written by the pre-enforcement default path
(where the fire IS what closed the ask). Deliberately ABSENT on an
approveAsk row: no fire has happened yet — that is the whole change.
version
version:
number
Defined in: src/atom/types.ts:2808