OptionalresumeTarget: ApprovalResumeTargetOptionalresumeTarget: ApprovalResumeTargetTriage fan-out: apply ONE decision to up to MAX_APPROVAL_BATCH_DECIDE records, each through the EXISTING decide() — per-record CAS, SoD, audit, and resume semantics untouched; the one-decision-per-suspension model is not widened. Partial success is the contract: each record's outcome (or typed failure) is reported in the envelope, never as a thrown error. Only record-INDEPENDENT failures reject the whole batch: the caller's role, malformed ids, the cap, and a malformed decision.
Sequential, not Promise.all — preserves audit ordering and avoids hammering the store's CAS with concurrent writes (D1/DO contention); a batch is a reviewer clicking once, not a throughput path.
CAS-transitions a stale open record (pending/claimed/escalated, bound to a suspension its step has since moved past) straight to 'rejected'. host-kit's reconcileApprovalsForSummary calls this before filing a fresh record for a step whose only open record no longer matches the run's current (suspendedAt, resumeCount) fingerprint. Without this transition, every poll would re-list the stale record through the open-step uniqueness index and file nothing.
Deliberately bypasses decide(): a rejection decision resumes the run with declined semantics via #resume(), and a stale record must die WITHOUT touching a run that is already suspended at a DIFFERENT (current) fingerprint — this method never calls #resume(). 'rejected' (not 'approved') is the deliberate terminal choice: connectorGrantsForLeg (grants.ts) reads ONLY status: 'approved' records when deriving a leg's grants, so a superseded record is excluded by its STATUS alone, not merely by its stale fingerprint — it can never mint even if a future change loosened the fingerprint check.
Never routed by router.ts (an ApprovalService method the HTTP surface never wires up) — reachable only from host-kit's reconcileApprovalsForSummary, itself only invoked from createRunRouter's optional reconcileApprovals hook on a status() read, never from a request body. Authorized like create() (CAN_CREATE, not CAN_REVIEW): the only principal that ever calls this is the same system principal create() already accepts for reconcile-filed records — superseding is the symmetric "un-file" half of that same self-healing operation, not a reviewer decision.
Returns null — mirroring the store's own CAS contract, rather than throwing — when the record is unknown or already left the OPEN set (a real decision won the race): the caller backs off instead of treating a lost race as an error, the same "null = lost a race, skip quietly" contract sweepSLA uses for its own store-level CAS.
Supersede on behalf of an AUTOMATED principal — the reconcile bridge's
half of createAsPrincipal, and authorized the same way.
Superseding is bookkeeping, not a decision: the record's suspension
fingerprint no longer matches the run, so it can never be resumed and is
closed to stop it shadowing the fresh filing. It is deliberately not
reachable through decide, so this does not give automation a decision.
File an approval on behalf of an AUTOMATED principal — the trusted entry for platform bridges (suspension reconcile, agent host) that have no person behind them.
These callers used to fabricate
role: 'operator'to satisfy CAN_CREATE. They cannot simply project onto a role instead: automated principals project to the least-privileged role precisely so they can never decide, andvieweris not in CAN_CREATE. So the role gate is replaced here — not widened — by a trusted-kind check.There is deliberately NO principal-taking claim/decide/delegate. Filing a request is trusted platform work; deciding one is a human judgement, and an automated principal approving its own request is the separation-of-duties hole this whole model exists to close.