@fractalboxdev/flare-dispatch-substrate-contract
Interfaces
Section titled “Interfaces”SubstrateFacade
Section titled “SubstrateFacade”What a consumer’s service binding exposes. Implemented by the substrate’s WorkerEntrypoint classes (one named entrypoint per consumer — that binding choice, made in reviewed wrangler config, IS the consumer identity; no runtime field carries it, ADR-0009).
The spec writes the admission surface as admission.enqueue/attempt/release;
it is flattened to methods here because RPC method calls on a plain
entrypoint are the simplest shape that structured-clones. Mapping:
admission.enqueue → admissionEnqueue, etc.
Methods
Section titled “Methods”ensureSandbox()
Section titled “ensureSandbox()”ensureSandbox( key, recipe,admission): Promise<EnsureOutcome>;Bring the keyed environment to the state the recipe describes.
{mode:'refuse'} fails fast with admission-refused when the pool is
full; {mode:'queue'} expects the consumer to have driven
admissionEnqueue/Attempt to admission first, and refuses (never blocks)
when it has not. A queued execution that has waited maxQueueAgeMs or
longer is refused with timedOut: true and loses its queue row.
Parameters
Section titled “Parameters”string
recipe
Section titled “recipe”admission
Section titled “admission”Returns
Section titled “Returns”Promise<EnsureOutcome>
execUnderGrant()
Section titled “execUnderGrant()”execUnderGrant(key, input): Promise<ExecOutcome>;Run one command under the recipe’s grant. The whole fence — stale-revoke, ensure, apply grant, run, kill-before-revoke — executes inside the substrate, which derives the grant from the recipe and its own container identity. Consumers never construct grants.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<ExecOutcome>
readFile()
Section titled “readFile()”readFile(key, path): Promise<ReadFileOutcome>;Read one container file’s full text. No grant is involved and no command runs — this is a read of the execution’s own filesystem, behind the same ticket gate every other call crosses.
Parameters
Section titled “Parameters”string
string
Returns
Section titled “Returns”Promise<ReadFileOutcome>
startDetached()
Section titled “startDetached()”startDetached(key, input): Promise<StartDetachedOutcome>;Start a process that outlives this call, under no grant at all (ADR-0012).
The substrate’s fence spares it when a later execUnderGrant tears down,
so a dev server started here is still listening when the next command
dials localhost — and still cannot reach the network between fences.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<StartDetachedOutcome>
detachedStatus()
Section titled “detachedStatus()”detachedStatus(key, processId): Promise<DetachedStatusOutcome>;Poll one detached process. There is deliberately no waitForExit: a
consumer waits in its own durable steps, the shape admission already uses,
because a Worker call that blocks for a twenty-minute agent turn is not a
call.
Parameters
Section titled “Parameters”string
processId
Section titled “processId”string
Returns
Section titled “Returns”Promise<DetachedStatusOutcome>
stopDetached()
Section titled “stopDetached()”stopDetached(key, processId): Promise<{ ok: true; stopped: boolean;}>;Kill one detached process and forget it. Idempotent.
Parameters
Section titled “Parameters”string
processId
Section titled “processId”string
Returns
Section titled “Returns”Promise<{
ok: true;
stopped: boolean;
}>
checkpoint()
Section titled “checkpoint()”checkpoint(key, reason): Promise<CheckpointOutcome>;Snapshot the workspace and stop the container; releases the admission slot.
Parameters
Section titled “Parameters”string
reason
Section titled “reason”Returns
Section titled “Returns”Promise<CheckpointOutcome>
abort()
Section titled “abort()”abort(key): Promise<AbortOutcome>;Kill + stop, skipping the snapshot — the consumer’s off-switch. Idempotent.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<AbortOutcome>
admissionEnqueue()
Section titled “admissionEnqueue()”admissionEnqueue(key, recipe): Promise<QueuePosition>;Join the pool’s FIFO line (idempotent). Pool is policy-selected from the recipe.
Parameters
Section titled “Parameters”string
recipe
Section titled “recipe”Returns
Section titled “Returns”Promise<QueuePosition>
admissionAttempt()
Section titled “admissionAttempt()”admissionAttempt(key, recipe): Promise<AttemptOutcome>;One claim attempt — the consumer hibernates between attempts in its own durable steps. The recipe rides along (as on every call) so the pool is re-derived by policy, never read back from consumer state.
Parameters
Section titled “Parameters”string
recipe
Section titled “recipe”Returns
Section titled “Returns”Promise<AttemptOutcome>
admissionRelease()
Section titled “admissionRelease()”admissionRelease(key): Promise<void>;Release the slot or leave the line. Idempotent.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<void>
denials()
Section titled “denials()”denials(key): Promise<readonly DenialEvent[]>;The execution’s egress denials, aggregated (ADR-0005). Retrieved alongside
its artifacts — a consumer renders them into a run’s diagnostics or a
thread; the container is never told any of it. Empty when nothing was
refused, which is also what a report-mode run wants to see before it
graduates to enforce.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<readonly DenialEvent[]>
poolStatus()
Section titled “poolStatus()”poolStatus(): Promise<PoolStatus>;Per-pool, per-consumer occupancy.
Returns
Section titled “Returns”Promise<PoolStatus>
Type Aliases
Section titled “Type Aliases”SubstrateRepoRef
Section titled “SubstrateRepoRef”type SubstrateRepoRef = { owner: string; name: string; ref?: string;};Properties
Section titled “Properties”owner: string;name: string;optional ref?: string;Branch, tag or sha. Defaults to the repository’s default branch.
GrantProfileName
Section titled “GrantProfileName”type GrantProfileName = | "public-repo-read" | "js-install" | "rust-install" | "browser-fetch" | "cf-api" | "github-api-read";Named, substrate-reviewed grant profiles (ADR-0005). A recipe may select
among them; it can never define one. The full catalog is served — the host
sets, method/path rules and deny-overrides live in the substrate’s
engine/profiles.ts, which is the only place they can be changed.
EnforcementPosition
Section titled “EnforcementPosition”type EnforcementPosition = "legacy" | "report" | "enforce";Per-run rollout position for the egress floor (ADR-0005). A run graduates
legacy → report → enforce, and only after a clean report window.
legacy— the pre-substrate posture: every host reachable, nothing inspected, nothing recorded. What a dispatcher run had before adoption.report— the same reachability, but every request is decided against the grant the run would get and each refusal is recorded as a would-be denial (would-deny: …). This is the grant-authoring loop; it blocks nothing. It records the missing-host case as well as the wrong-path one, because the position admits every host precisely so each request reaches the engine. It is still bounded by what the container runtime routes through that engine — so a clean window is evidence about observed traffic, not a proof that a profile is complete.enforce— deny-all with the composed grant: unadmitted hosts never leave the container, admitted ones are method/path-asserted, refusals are 403s.
Absent ⇒ enforce. A consumer that forgets the field gets the floor, never
the opt-out.
SubstrateRecipe
Section titled “SubstrateRecipe”type SubstrateRecipe = { version: number; repo?: SubstrateRepoRef; lfs?: boolean; profiles?: readonly GrantProfileName[]; targets?: readonly string[]; enforcement?: EnforcementPosition;};What the substrate needs to build (or restore) an execution environment and
to derive its egress grant. The security property rides with it: repo must
come from an input no model authored — fractalbot parses it from the human’s
message and freezes it (its ADR-0005); dispatcher runs carry it in reviewed
definitions. version is what makes restore-or-rebuild decidable without the
substrate ever reading state back from a consumer.
Properties
Section titled “Properties”version
Section titled “version”version: number;optional repo?: SubstrateRepoRef;Absent for work that needs a shell but no repository — and then NO egress.
optional lfs?: boolean;Admits the LFS object host, whose paths cannot be repo-scoped. Off by default.
profiles?
Section titled “profiles?”optional profiles?: readonly GrantProfileName[];Profile selection (never definition). Omitted ⇒ derived from repo alone.
targets?
Section titled “targets?”optional targets?: readonly string[];Concrete hostnames for a profile that accepts dynamic targets (today only
browser-fetch: an e2e run drives an app whose host is a dispatch input).
The security property is where the check happens, not that one happens: a consumer validates each host against the host-pattern schema declared in its reviewed run definition and fails the dispatch when it does not match (ADR-0005). By the time a host reaches here it has already passed that gate, so the substrate’s job is narrower — refuse a target when no selected profile accepts one, and admit nothing that is not listed.
enforcement?
Section titled “enforcement?”optional enforcement?: EnforcementPosition;Rollout position for this run’s egress floor. Absent ⇒ enforce.
Only reviewed consumer code sets it; no dispatch payload reaches this field.
CredentialDescriptor
Section titled “CredentialDescriptor”type CredentialDescriptor = { secretName: string; host: string; headerTemplate: string;};How a credential attaches to one host, for the writes that cannot ride worker-side writeback (ADR-0006). The descriptor names a secret; it never carries one, and nothing in it ever reaches a container.
The shape is exported here so consumers can read what a profile attaches
(documentation, refusal text) — never author it. Descriptors are frozen in
the substrate’s reviewed catalog and selected by GrantProfileName, the same
rule ADR-0005 applies to grants: a payload may select among pre-authored
definitions, never define one. A consumer-supplied descriptor would let a
dispatch body name the secret it wants injected, which is the whole attack.
Properties
Section titled “Properties”secretName
Section titled “secretName”secretName: string;The substrate Worker’s own secret binding name (CLOUDFLARE_API_TOKEN).
Resolvable only against a frozen allowlist inside the substrate, so a
mis-authored descriptor cannot reach TICKET_SECRET.
host: string;The exact host this credential attaches to. Never a glob — see below.
headerTemplate
Section titled “headerTemplate”headerTemplate: string;The header line to send, Name: value, with {{secret}} as the single
substitution point — e.g. authorization: Bearer {{secret}}. A line rather
than a pair because the ADR’s descriptor is a triple; it is parsed and
validated once at catalog authoring time, and a template with a CR/LF, a
malformed name, or anything other than exactly one {{secret}} is refused
before it can be used.
PoolName
Section titled “PoolName”type PoolName = "lean" | "browser" | "agent" | "task";One pool per image class. Consumers never choose one — this union exists so refusals and poolStatus() can name what they observed.
AdmissionMode
Section titled “AdmissionMode”type AdmissionMode = | { mode: "refuse";} | { mode: "queue"; maxQueueAgeMs: number;};Consumer-chosen wait semantics. refuse fails fast with a typed reason (an
interactive task must never silently queue behind CI); queue is driven by
the consumer’s own durable machinery via admissionEnqueue/Attempt/Release —
ensureSandbox never blocks on a queue in either mode.
QueuePosition
Section titled “QueuePosition”type QueuePosition = { pool: PoolName; position: number; poolBusy: number; cap: number;};Properties
Section titled “Properties”pool: PoolName;position
Section titled “position”position: number;Live queued executions ahead of this one (0 when next, or when admitted).
poolBusy
Section titled “poolBusy”poolBusy: number;Live admitted executions in the pool.
cap: number;AttemptOutcome
Section titled “AttemptOutcome”type AttemptOutcome = | { admitted: true; expiresAt: number;} | { admitted: false;} & QueuePosition;Union Members
Section titled “Union Members”Type Literal
Section titled “Type Literal”{ admitted: true; expiresAt: number;}admitted
Section titled “admitted”admitted: true;expiresAt
Section titled “expiresAt”expiresAt: number;ms-epoch the admission ticket expires; heartbeat by exec’ing.
{
admitted: false;
} & QueuePosition
PoolStatus
Section titled “PoolStatus”type PoolStatus = { pools: readonly { pool: PoolName; cap: number; busy: number; queued: number; byConsumer: Readonly<Record<string, number>>; }[];};Properties
Section titled “Properties”pools: readonly { pool: PoolName; cap: number; busy: number; queued: number; byConsumer: Readonly<Record<string, number>>;}[];ApprovalAttestation
Section titled “ApprovalAttestation”type ApprovalAttestation = { taskId: string; ordinal: number; commandSha256: string; approvedBy: string; approvedAt: number;};The attestation that satisfies the irreversible-command floor at exec. Who
may assert differs by consumer: fractalbot after a human approval lands
(approvedBy = the Slack user id); dispatcher runs pre-assert in reviewed
definitions (approvedBy: "run-definition"). The gate is per
(taskId, ordinal) — a decision for step 3 cannot satisfy step 7 — and
commandSha256 binds it to the exact command text.
Properties
Section titled “Properties”taskId
Section titled “taskId”taskId: string;ordinal
Section titled “ordinal”ordinal: number;commandSha256
Section titled “commandSha256”commandSha256: string;Lowercase hex SHA-256 of the exact command string passed to exec.
approvedBy
Section titled “approvedBy”approvedBy: string;approvedAt
Section titled “approvedAt”approvedAt: number;AdmissionRefused
Section titled “AdmissionRefused”type AdmissionRefused = { kind: "admission-refused"; pool: PoolName; poolBusy: number; cap: number; position?: number; queuedForMs?: number; retryAfterMs?: number; timedOut?: boolean;};Fail-fast admission refusal ({mode:‘refuse’}), or a queue-mode timeout.
Properties
Section titled “Properties”kind: "admission-refused";pool: PoolName;poolBusy
Section titled “poolBusy”poolBusy: number;cap: number;position?
Section titled “position?”optional position?: number;queuedForMs?
Section titled “queuedForMs?”optional queuedForMs?: number;retryAfterMs?
Section titled “retryAfterMs?”optional retryAfterMs?: number;timedOut?
Section titled “timedOut?”optional timedOut?: boolean;true when a {mode:'queue'} execution has waited maxQueueAgeMs or longer
without admission. The substrate has released its queue row, so polling
again re-enqueues at the back; stop and report instead.
ApprovalRequired
Section titled “ApprovalRequired”type ApprovalRequired = { kind: "approval-required"; rule: string;};The command matches the irreversible floor and no attestation was carried.
Properties
Section titled “Properties”kind: "approval-required";rule: string;The floor rule that matched, e.g. “git push”. Never the command itself.
AttestationRejected
Section titled “AttestationRejected”type AttestationRejected = { kind: "attestation-rejected"; reason: string;};An attestation was carried but does not satisfy the gate.
Properties
Section titled “Properties”kind: "attestation-rejected";reason
Section titled “reason”reason: string;BudgetStop
Section titled “BudgetStop”type BudgetStop = { kind: "budget-stop"; scope: "execution" | "consumer"; meter: { spentUsd: number; capUsd: number; };};A budget stop from the metered tier (ADR-0009), carrying meter state.
Properties
Section titled “Properties”kind: "budget-stop";scope: "execution" | "consumer";meter: { spentUsd: number; capUsd: number;};spentUsd
Section titled “spentUsd”spentUsd: number;capUsd
Section titled “capUsd”capUsd: number;RecipeRejected
Section titled “RecipeRejected”type RecipeRejected = { kind: "recipe-rejected"; reason: string;};The recipe cannot be served: malformed repo, unknown/unserved profile.
Properties
Section titled “Properties”kind: "recipe-rejected";reason
Section titled “reason”reason: string;TicketRejected
Section titled “TicketRejected”type TicketRejected = { kind: "ticket-rejected"; reason: string;};The admission ticket is missing, expired, or fails verification — fail closed.
Properties
Section titled “Properties”kind: "ticket-rejected";reason
Section titled “reason”reason: string;SandboxUnavailable
Section titled “SandboxUnavailable”type SandboxUnavailable = { kind: "sandbox-unavailable"; reason: string;};Infrastructure failure surfaced as a typed fact, never a naked throw.
Properties
Section titled “Properties”kind: "sandbox-unavailable";reason
Section titled “reason”reason: string;SubstrateRefusal
Section titled “SubstrateRefusal”type SubstrateRefusal = | AdmissionRefused | ApprovalRequired | AttestationRejected | BudgetStop | RecipeRejected | TicketRejected | SandboxUnavailable;SandboxKey
Section titled “SandboxKey”type SandboxKey = string;The consumer’s name for one execution environment. fractalbot uses
team:channel:thread:taskId (one sandbox per task, its ADR-0002); the
dispatcher uses its execution id. Opaque to the substrate beyond uniqueness
within a consumer; the substrate namespaces per consumer, so two consumers’
keys can never collide.
EnsureResult
Section titled “EnsureResult”type EnsureResult = { generation: number; rebuilt: boolean;};Properties
Section titled “Properties”generation
Section titled “generation”generation: number;Bumped on every rebuild — how a caller detects it is not on the tree it left.
rebuilt
Section titled “rebuilt”rebuilt: boolean;EnsureOutcome
Section titled “EnsureOutcome”type EnsureOutcome = | { ok: true;} & EnsureResult | { ok: false; refusal: SubstrateRefusal;};ExecInput
Section titled “ExecInput”type ExecInput = { recipe: SubstrateRecipe; command: string; idempotencyKey: string; logPath: string; timeoutMs?: number; tailBytes?: number; lfs?: boolean; approval?: ApprovalAttestation;};Properties
Section titled “Properties”recipe
Section titled “recipe”recipe: SubstrateRecipe;Rides every call so the substrate can restore-or-rebuild statelessly.
command
Section titled “command”command: string;The one possibly-model-authored value on this path. Passed through, never wrapped.
idempotencyKey
Section titled “idempotencyKey”idempotencyKey: string;Stable across retries of one durable step, distinct across steps. A
retried call with the same key joins the in-flight command or returns its
recorded receipt (deduped: true) — it never re-runs (ADR-0003).
logPath
Section titled “logPath”logPath: string;Path under the execution’s artifact prefix that stdout+stderr stream to.
timeoutMs?
Section titled “timeoutMs?”optional timeoutMs?: number;tailBytes?
Section titled “tailBytes?”optional tailBytes?: number;optional lfs?: boolean;approval?
Section titled “approval?”optional approval?: ApprovalAttestation;Required when the command matches the irreversible floor (ADR-0007).
DetachedProcess
Section titled “DetachedProcess”type DetachedProcess = { id: string; startedAt: number;};A process the substrate is not synchronously awaiting (ADR-0012).
The id is substrate-assigned and opaque — never a container pid, which is not a stable name across a restart and would let a consumer address a process it did not start.
Properties
Section titled “Properties”id: string;startedAt
Section titled “startedAt”startedAt: number;DetachedStatus
Section titled “DetachedStatus”type DetachedStatus = | { state: "running";} | { state: "exited"; exitCode: number;} | { state: "gone"; reason: string;};What a detached process is doing. gone is its own state rather than an
error: a container that slept, restarted or was checkpointed no longer has
the process, and a consumer polling from a durable step needs to tell that
apart from “still running” without catching a throw.
Union Members
Section titled “Union Members”Type Literal
Section titled “Type Literal”{ state: "running";}Type Literal
Section titled “Type Literal”{ state: "exited"; exitCode: number;}exitCode is -1 when the container reported the process as finished
without one — a signal death is the usual case. Treat it as “ended, code
unknown” rather than as a real status, and never as success.
Type Literal
Section titled “Type Literal”{ state: "gone"; reason: string;}StartDetachedInput
Section titled “StartDetachedInput”type StartDetachedInput = { recipe: SubstrateRecipe; command: string; idempotencyKey: string; logPath: string; approval?: ApprovalAttestation;};Start a process that outlives the call. No grant is applied — a detached process runs under the container’s deny-all floor (ADR-0012), so anything it needs from the network has to happen inside a fenced exec instead.
command still crosses the ADR-0007 approval floor: starting a floor command
detached must not be a way around the floor.
Properties
Section titled “Properties”recipe
Section titled “recipe”recipe: SubstrateRecipe;command
Section titled “command”command: string;idempotencyKey
Section titled “idempotencyKey”idempotencyKey: string;Stable across retries of one durable step. A retry returns the same process.
logPath
Section titled “logPath”logPath: string;Path under the execution’s artifact prefix that the process’s output streams to.
approval?
Section titled “approval?”optional approval?: ApprovalAttestation;Required when the command matches the irreversible floor (ADR-0007).
StartDetachedOutcome
Section titled “StartDetachedOutcome”type StartDetachedOutcome = | { ok: true; process: DetachedProcess;} | { ok: false; refusal: SubstrateRefusal;};DetachedStatusOutcome
Section titled “DetachedStatusOutcome”type DetachedStatusOutcome = | { ok: true; status: DetachedStatus;} | { ok: false; refusal: SubstrateRefusal;};ExecReceipt
Section titled “ExecReceipt”type ExecReceipt = { exitCode: number; durationMs: number; deduped: boolean; tail: string; truncated: boolean;};The bounded receipt for one command; full output lives in artifacts.
Properties
Section titled “Properties”exitCode
Section titled “exitCode”exitCode: number;durationMs
Section titled “durationMs”durationMs: number;deduped
Section titled “deduped”deduped: boolean;True when this call joined an earlier run of the same idempotency key.
tail: string;Clamped tail of combined stdout+stderr — bounded by tailBytes.
truncated
Section titled “truncated”truncated: boolean;ExecOutcome
Section titled “ExecOutcome”type ExecOutcome = | { ok: true; receipt: ExecReceipt; ensured: EnsureResult; granted: readonly string[]; killed: number;} | { ok: false; refusal: SubstrateRefusal;};Union Members
Section titled “Union Members”Type Literal
Section titled “Type Literal”{ ok: true; receipt: ExecReceipt; ensured: EnsureResult; granted: readonly string[]; killed: number;}ok: true;receipt
Section titled “receipt”receipt: ExecReceipt;ensured
Section titled “ensured”ensured: EnsureResult;granted
Section titled “granted”granted: readonly string[];Hosts admitted for this command. Empty when the recipe grants no egress.
killed
Section titled “killed”killed: number;Processes the pre-revoke kill reported.
Type Literal
Section titled “Type Literal”{ ok: false; refusal: SubstrateRefusal;}ReadFileOutcome
Section titled “ReadFileOutcome”type ReadFileOutcome = | { ok: true; content: string;} | { ok: false; refusal: SubstrateRefusal;};A container file read back into the consumer’s Worker. The companion to
execUnderGrant for output too large for a receipt tail — a run writes the
full text to a file and reads it here (the dispatcher’s pr-review does this
with git diff --output). Bounding what a consumer does with the content is
the consumer’s problem; bounding what leaves the container is not this call’s
job — the workload already authored the bytes.
CheckpointReason
Section titled “CheckpointReason”type CheckpointReason = | "turn-boundary" | "awaiting-approval" | "final" | string & {};Why a checkpoint is being taken. Open set; these are the expected values.
CheckpointOutcome
Section titled “CheckpointOutcome”type CheckpointOutcome = | { ok: true;} | { ok: false; refusal: SubstrateRefusal;};AbortOutcome
Section titled “AbortOutcome”type AbortOutcome = { ok: true; killed: number;};Properties
Section titled “Properties”ok: true;killed
Section titled “killed”killed: number;DenialEvent
Section titled “DenialEvent”type DenialEvent = { host: string; method: string; path: string; reason: string; count: number;};One egress denial, aggregated per execution — platform 520s and handler 403s both land here. Retrievable with the execution’s artifacts; never surfaced into the container (oracle resistance).
Properties
Section titled “Properties”host: string;method
Section titled “method”method: string;path: string;reason
Section titled “reason”reason: string;count: number;Variables
Section titled “Variables”CONTRACT_VERSION
Section titled “CONTRACT_VERSION”const CONTRACT_VERSION: 1 = 1;Bumped on any breaking change to an exported shape.
SUBSTRATE_RECIPE_KEYS
Section titled “SUBSTRATE_RECIPE_KEYS”const SUBSTRATE_RECIPE_KEYS: readonly keyof SubstrateRecipe[];Every field a recipe declares, as a value rather than a type — the runtime witness of this contract’s shape.
ADR-0010’s “no pool or image input” was enforced only by the absence of a
field in SubstrateRecipe, and a TypeScript type is erased. A consumer
sending { version: 1, pool: "agent" } over RPC had it silently ignored — the
correct outcome — but nothing asserted the ignoring, and nothing failed if a
later refactor threaded such a field into pool selection.
This is what the substrate projects a recipe through before its admission
policy reads a field (apps/substrate/src/admission/pools.ts), so an
undeclared key is dropped by construction rather than ignored by luck. A
projection and not a refusal, deliberately: additive optional fields are
non-breaking here, so a newer consumer’s recipe legitimately carries keys an
older substrate build has never heard of.
Consumers can read it to check what a build of this contract understands.
Functions
Section titled “Functions”repoSlug()
Section titled “repoSlug()”function repoSlug(repo): string;owner/name — the form egress path rules are asserted against.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
isRefusalKind()
Section titled “isRefusalKind()”function isRefusalKind<K>(refusal, kind): refusal is Extract<AdmissionRefused, { kind: K }> | Extract<ApprovalRequired, { kind: K }> | Extract<AttestationRejected, { kind: K }> | Extract<BudgetStop, { kind: K }> | Extract<RecipeRejected, { kind: K }> | Extract<TicketRejected, { kind: K }> | Extract<SandboxUnavailable, { kind: K }>;Type Parameters
Section titled “Type Parameters”K extends
| "admission-refused"
| "approval-required"
| "attestation-rejected"
| "budget-stop"
| "recipe-rejected"
| "ticket-rejected"
| "sandbox-unavailable"
Parameters
Section titled “Parameters”refusal
Section titled “refusal”K
Returns
Section titled “Returns”refusal is Extract<AdmissionRefused, { kind: K }> | Extract<ApprovalRequired, { kind: K }> | Extract<AttestationRejected, { kind: K }> | Extract<BudgetStop, { kind: K }> | Extract<RecipeRejected, { kind: K }> | Extract<TicketRejected, { kind: K }> | Extract<SandboxUnavailable, { kind: K }>