OptionalresumeTarget: ApprovalResumeTargetFile 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,
and viewer is not in CAN_CREATE. So the role gate is replaced here — not
widened — by a kind-and-tenant 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.
OptionalresumeTarget: 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: approvedConnectorsForLeg (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 actor that ever calls this is the same system actor 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.
The tenant this service is bound to. Exposed so the host-kit bridges can mint their own bookkeeping principal against it instead of making every host construct one and vouch for it.