| POST | /legal-hold · GET /legal-holds | Per-domain legal holds; held ids are frozen (purge/DSAR defer). Both Admin. |
| POST | /legal-hold/{id}/release | Release a hold — Admin plus the DPO role (an asymmetry on purpose: releasing a hold is a privacy decision, not just an ops one) |
| POST | /breach · /breach/{id}/event · /breach/{id}/close | Breach-notification workflow (open / append event / close) — all Admin plus the DPO role |
| GET | /breaches · /breaches/{id} | Breach register + detail — Admin plus the DPO role |
| GET | /workflow/scoreboard | Workflow outcome/efficiency scoreboard over recent runs (DPO/admin; rates in integer ten-thousandths, fail-closed audit linkage). Since v1.28.62 carries the ASI09 approval-fatigue telemetry: review_independence_risk (0|1, the client detector’s verdict server-side), approval_uniformity_ratio (integer ten-thousandths), review_decisions_window — parity-pinned to the console’s rubber-stamp arithmetic |
| GET | /workflow/reflection/corpus?since=&limit=&partition=all|train|holdout | The de-identified disagreement-corpus export (DPO/admin dual gate; audited per call). Bounded page (1..=500), every row carries its frozen train/holdout partition (pure function of the run id over a pinned constant — stable across exports), identifiers render as content digests, excerpts pass the read-seam sanitizer with unconditional PII masking; the raw case input never exports |
| POST | /workflow/calibration/sign | Monthly human-signed workflow calibration gate (DPO/admin; one signature per calendar month, audited) |
| POST | /accounts | Create the account record — the deliberately-not-a-CRM record layer (accounts are workflow_runs rows of kind account). Body {name, domain}; the name is screened + bounded 1..=256 (control/invisible-refused), id/owner/status/clock are server-derived; record + audit land in ONE tx (Write on the domain + workflow role) |
| GET | /accounts/{id} | The account view: record + derived stage (latest pipeline row else lead) + the pipeline timeline. Absent and non-account ids answer the SAME probe-blind 404 (Write on the domain + workflow role) |
| POST | /accounts/{id}/pipeline | Advance the stage over the CLOSED ratified vocabulary (lead → qualified → proposal → closed_won | closed_lost; self-transitions refuse). decision_ref REQUIRED — 400 decision_ref_required/decision_ref_invalid; unknown stage → pipeline_stage_unknown; illegal edge → illegal_stage_transition naming source→target; archived refuses (account_archived). The appended row carries {stage, decision_ref, prev_stage} + audit in ONE tx. The classifier NEVER advances a stage |
| POST | /accounts/{id}/requests/{run_id}/link | Attach one request run to the account: an additive account:link row under the ACCOUNT’s run id + audit, atomic; re-links append new audited rows (never mutated); archived refuses (Write on the account’s domain + workflow role) |
| GET | /accounts/{id}/requests?limit= | The bounded per-account history (1..=500, default 100): link rows joined to their request runs’ headlines + recorded decision rows — the pure decision join (Write on the domain + workflow role) |
| GET | /accounts?limit= | The bounded account listing (1..=500, default 100) — THE exfiltration surface: DPO/admin dual gate + an audited global row per call naming the principal, the filter, and the count |
| GET | /workflow/kappa/queue?limit= | The κ labeling bench’s rater queue: the mined disagreement tuples assigned to the caller’s slot (the slot derives from the authenticated principal, never the client; echoed in the response), both frozen partitions, bounded 1..=500 (default 100). Rows carry the machine’s proposal (read-seam masked), the phase, the partition, and the rater’s OWN latest label — never another rater’s, never the governed truth (Write on global + calibrate) |
| POST | /workflow/kappa/labels | Capture one blind judgment: body {digest, label, run_id}, the label from the CLOSED ratified vocabulary (agree | disagree | uncertain), the slot from the principal. Exactly-once + append-only under the tuple’s run id: a re-submitted latest judgment is the no-op receipt, a changed judgment appends a supersession row; ONE audit row per created label (ids + counts, never label text). Absent tuple and absent assignment answer the SAME probe-blind 404 (Write on global + calibrate) |
| GET | /workflow/kappa/report?limit= | The per-rater-pair κ report: one cell per (domain × frozen partition × slot pair), integer ten-thousandths, latest-wins; degenerate pairs name themselves (NO_KAPPA + the κ fn’s own refusal). meets_bar (κ ≥ 0.70 = 7000 units) is REPORTED DATA — the κ value never auto-gates anything. THE exfiltration surface: DPO/admin dual gate + calibrate + an audited global row per call (1..=500, default 100) |
| GET | /workflow/agreement/queue?limit= | The agreement-labelling path’s reviewer queue: REAL delivery_traces rows carrying a populated model_ref, oldest first, bounded 1..=500 (default 100). Rows carry the trace’s metadata (read-seam masked) and the CALLER’S own latest verdict — never another reviewer’s. The machine’s verdict is NOT in the rows: it has no field on the type a reviewer reads, so blindness is the core’s output type, not a discipline. A NULL model_ref row is not labelable and never appears. The response echoes the caller’s slot and reviewer id (Write on global + calibrate) |
| POST | /workflow/agreement/labels | Bind one verdict to one REAL run row: body {run_id, subject_id, verdict}, the verdict from the CLOSED vocabulary (confirmed | overturned | uncertain), the reviewer IS the authenticated principal and the slot derives from it (the client never names the judge). Exactly-once + append-only under the run id: a re-submitted latest verdict is the no-op receipt, a changed verdict appends a supersession row; the audit rows land inside the caller’s transaction. The machine’s verdict is DERIVED from the run row and FROZEN into the label, so flipping the run’s outcome later cannot silently re-score a judgment made against what the row said then. A label without a reviewer is refused before any write (400 reviewer_required) — the field the frozen corpus lacks. An absent run row answers the probe-blind 404 (Write on global + calibrate) |
| GET | /workflow/agreement/report?limit= | Agreement with the operator over the labeled set, in two halves that are never blended. rows is one cell per (domain × reviewer): n_confirmed / n_overturned / n_uncertain reported SEPARATELY (collapsing the last two would let clean uncertainty masquerade as failure) and raw_agreement_units = confirmed / labeled in integer ten-thousandths. distinct_reviewers rides each cell so the single-rater era is readable FROM THE DATA — 1 means no inter-rater reliability exists behind the number. It is counted PER REVIEWER KIND (reviewer_kind, closed to operator today) alongside distinct_reviewer_kinds, so a reviewer of a different kind can never inflate an operator cell into looking like an inter-rater era. pairs is one cell per (domain × reviewer PAIR) with Cohen’s κ, DELEGATED to the shared pure function; it is EMPTY in a single-rater era, because a pair that does not exist is not a reliability. A pair is emitted only between raters of the SAME kind — a cross-kind comparison is a different quantity with a different ceiling, and admitting a second kind is a dated amendment to the vocabulary, never a value a client supplies. Every number here is DATA — nothing gates on it, no promotion bar is applied, and this round computes no N. THE exfiltration surface: DPO/admin dual gate + calibrate + an audited global row per call (1..=500, default 100) |
| GET | /workflow/wizard/packs | The ratified wizard pack catalog: the three operator-ratified packs as read-only, validated data — {packs: [{id, question_count, pack}], count}, every entry re-validated through the total pack validator at read time; the templates are compile-time-embedded from the committed corpus files (ONE source of truth), never runtime-fs. The SvelteTauri shell renderer branches CLIENT-SIDE on the packs’ total next-maps; answers never ride this route (Read on global — any authenticated principal) |
| POST | /workflow/decision-runs | Execute one decision-pipeline run over the request’s ask and persist its trace (digests and refs only — the raw query is hashed before anything durable). Body {config, rules_config, run_id, mode: deterministic|exploratory, request_id, question_id?, question_kind?, question_ids, query, proposal?}; the rules table must digest to the config’s bound model (400 model_digest_mismatch otherwise) and hostile configs refuse named (400 config_invalid). With proposal: true AND an escalated outcome, an escalation proposal queues in the SAME transaction carrying the run’s provenance ref — the promotion gate reads the ref’s mode: an EXPLORATORY run can propose, never promote (exploratory_mode_not_promotable); the human path for exploratory output is re-running deterministically. 201 {trace_id, action, escalation?, output?, records, proposal_id?} (Write on the run’s domain + workflow role; absent/foreign run = probe-blind 404) |
| GET | /workflow/decision-runs/{id} | The stored trace document verbatim by ROW id — run identity, pipeline version, mode, config hash, model refs, input/context digests, per-stage records (digests, trust tiers, timing), the outcome; the raw query text is unrepresentable in it. Absent ids answer the probe-blind 404; the read is audited (Read on global + workflow role) |
| POST | /workflow/decision-runs/{id}/replay-diff | Re-execute a stored run under ITS OWN recorded conditions: the supplied config must canonical-hash to the trace’s config_hash (409 config_hash_mismatch otherwise) and the rules table must digest to the bound model. Re-runs with LIVE retrieval (a changed corpus shows up as an honest mismatch — poison visibility) and reports {trace_id, config_hash, config_hash_match, replay_input_digest, input_digest_match, stages: [{stage, match, stored_outputs_digest, replayed_outputs_digest}], all_match} — DATA, never a status; timing is provenance and never compared; the replay persists NOTHING. POST (not GET) because the config + rules documents are large structured bodies and the body re-carries the run’s input fields (the trace binds them only as digests) (Write on the run’s domain + workflow role) |
| GET | /workflow/decision-runs?limit=&run_id= | The bounded decision-run listing (1..=50, default 20, newest-first, optional run_id filter): {rows: [{id, run_id, mode, pipeline_version, config_hash, created_at, stage_count}], count} — bounded columns ONLY, the trace documents never ride a listing. THE exfiltration surface: DPO/admin dual gate + an audited global row per call |
| GET | /workflow/model-registry?limit=&status=&kind= | The bounded model-registry listing (1..=50, default 20, newest-first; optional closed status and kind filters): {rows, count}. Artifact digests are represented only by artifact_digest_present; digest values ride the single-row read. Admin plus DPO role, audited global read (the registry’s exfiltration surface) |
| GET | /workflow/model-registry/{model_ref} | One registered model identity by the whole-segment id@version citation. Read on global + audited; malformed refs are 400 model_ref_invalid, and absent rows use the probe-blind 404. The response requires a server-computed lowercase 64-hex row_digest (SHA-256 over the canonical compact RegistryRow serialization); copy the exact row and row_digest into a registry_lifecycle proposal. The row carries identity, vocabulary, lifecycle, and digest references only — never weights or evaluation contents. Promotion and retirement have no direct route: they use the existing human proposal gate |
| POST | /workflow/model-registry/register | Register an operator-supplied model identity as candidate. The deterministic-rules arm requires the in-body rules document and the server derives/stores only its identity and canonical digest; learned/reranker arms declare identity and digest references. Admin on global + audited; learned registrations require artifact_digest; duplicate identities are a loud 409 model_already_registered |
| POST | /workflow/decision-evals | Evaluate a bounded, digest-pinned, explicitly non-authoritative operator-declared judgment manifest against persisted decision traces. The body is {idempotency_key, target, judgment_set}; it carries closed labels, evidence IDs, and digests only—never raw query/evidence text. Missing/invalid source data is 400 judgment_set_unavailable; learned targets require an artifact digest. The record and checked human/operator acceptance audit commit atomically; acceptance_state=operator_accepted_non_authoritative is not a detached signature. Admin on global + DPO; no registry status change or automatic promotion. |
| GET | /workflow/decision-evals/{id} | Read one digest-verified evaluation record by stable eval_<32 hex> id. Admin on global + DPO, audited when found, probe-blind 404 for absent records. The response contains bounded manifest metadata, aggregate leg statuses, and acceptance data only; no raw case content, weights, or secrets. |
| GET | /workflow/decision-evals?limit= | Bounded newest-first evaluation metadata listing (1..=50, default 20), Admin on global + DPO, audited per call. Full manifests and reports never ride the listing; missing legs remain explicit unavailable values rather than zeroes. |
| POST | /workflow/delivery/runs | Open a delivery run on the EXISTING run engine with kind=delivery — no second engine, no schema widening. The body is {domain, goal, tier, policy_digest?, config_digest?, budgets?}; tier is the closed set observe|propose|bounded-auto|delegated and the trace mode is DERIVED from it, never taken from the client. Any budgets supplied are STORED as evidence and are not enforced — no route consults a ceiling. A delivery run carries no jurisdiction, so no law-version stamp is written. Write on the target domain + the workflow role. |
| POST | /workflow/delivery/runs/{id}/advance | Advance one phase. ONE transaction: the step row, the revision CAS, the trace row, and a fail-closed audit row commit together or not at all. Legal only for the five ADJACENT phases — a skip and a rewind are both 409; a lost CAS is 409 delivery_gate_stale_revision and the whole pass rolls back rather than overwriting the winner. The run closes completed only at the terminal phase, inside the engine’s existing closed status set. An optional artifact ({id, content, quality_gate?}) rides the pass and is filed, in the same transaction, as a PENDING proposal with no disposition — the executor proposes, only the gate disposes. On a build pass the quality_gate is evaluated first and an artifact whose evidence is not a live surface is 409 delivery_quality_gate_refused with nothing written; artifact content that the content screen rejects is 400 artifact_screened_reject and a quarantine verdict is 409 artifact_screened_quarantine. The artifact’s SHA-256 is derived server-side (there is no digest field to supply) and the content is stored verbatim so the approval digest binds one shape. The response’s proposal_id is that proposal, or 0 when the pass carried none. Write on the run’s domain + the workflow role. |
| POST | /workflow/delivery/runs/{id}/answer | Clear the run’s pending_question through the same revision CAS, recording that an answer happened. The answer is operator-authored prose on a run the operator owns: stored in the run’s own state, bounded to 2000 chars, and never copied into a trace row. A run with no pending question is 409. Write on the run’s domain + the workflow role. |
| POST | /workflow/delivery/runs/{id}/gates | Evaluate the phase gate. A DISPOSITION, never a mutation: the run’s phase, status, and revision are untouched and the only writes are the trace row and its audit. Pure and offline; deny wins. A terminal phase and an illegal move are denied; a value outside the closed phase vocabulary is denied/closed-vocabulary rather than a nearest-match guess; a tier that may not promote is told prompt, and the human’s advance route is the disposal. 200 whatever the verdict — a deny is a recorded outcome, not a transport error. No budget ceiling is consulted. Write on the run’s domain + the workflow role. |
| GET | /workflow/delivery/bindings | The standing authorities this machine holds to read external systems on behalf of ONE domain: {bindings, intents_pending, observed_pending, untrusted_pending}, each binding carrying {id, domain, target_kind, target_ref, endpoint, authority_digest, capabilities, active, updated_at}. domain is a required query parameter and the surface is domain-scoped, because a binding resolves to one tenant’s authority and an unscoped resolve would be a cross-tenant leak. The authority_digest covers the endpoint, the stable external ref, and the secret’s FILE NAME — never the secret and never its path; a digest computed over secret material is a credential at rest in a hash column. capabilities is the operator’s declared surface parsed with deny_unknown_fields: an unknown field or capability is a REFUSED binding, and a block this server cannot parse renders as the literal "unparseable" rather than as a default that would read as unconstrained. registry/deploy/pm/incident are declared and consumer-less — no adapter reads them. The pending counters are the ops signal that an intent which is merely not-yet-promoted is distinguishable from one that was lost, and from a FORGED row whose key is not a kernel mint (untrusted_pending should be zero). There is no write route: consent is given by configuring a binding at boot and withdrawn with active = 0, never by a request, because a request must never be able to create or widen an authority. Serving this list grants no authority, approves nothing, and makes no compliance finding — authorship is not authority. Read on the queried domain + the workflow role. |
| POST | /workflow/delivery/releases | File a governed release: the machine’s proposal to move ONE artifact toward ONE external authority. The kernel names everything that binds — the artifact digest is derived from the run’s own typed-artifact bytes (never a request field) and the authority binding is resolved from the run’s own domain and the named target kind — while the request names only {run_id, target_kind, ref, environment, commit_sha?}. Lands proposed. The agent preset is refused before any work (agents hold write:*; this is the write family whose consequences reach another system). Write on the run’s domain + the workflow role. |
| POST | /workflow/delivery/releases/{id}/approve | Record the approval as COLUMNS on the release row — no sixth table. The binding is three-way: the content digest (kernel-written from the release row), the authority digest recomputed from the binding row as it is now, and the run’s state revision as it is now. An approval that binds content but not the target is replayable against a different external system; one that binds both but not the revision is replayable across a later phase pass. The expiry is measured from approved_at and is evaluated inside the promote transaction, fail-closed at the boundary. The approving principal is recorded from the authenticated caller, never asserted from the body. Approve and promote are separate requests by design. Write on the release’s domain + the workflow role; the agent preset is refused. |
| POST | /workflow/delivery/releases/{id}/promote | The promotion gate. One transaction re-verifies everything before the pure crate gate reads anything: the signature chain (offline verifier — a broken chain is a typed refusal before the gate), the live digest re-derived from the artifact bytes as they exist now, the authority (recomputed; drift is 409), the run’s revision (unchanged since the approval), the approver’s principal (the kill-switch), and the tier (the run’s state and the chain’s signed predicate must agree). Then the crate’s total gate decides, deny-wins, first reason reported in push order. The trace mode is carried and deliberately unread — authority comes from the tier, never from how a trace was produced. A permitted promotion walks the crate’s one-step-at-a-time transition law in the same transaction, lands promoted, records the post-hoc budget draw (elapsed minutes and the one artifact moved; spent moves only when a producer exists), and mints the dispatch intents — promotion IS the outbox write, so nothing here touches the network. The ledger’s belief moves only when the inbound authority observation reconciles; the crank never writes verified_at. Budgets are enforced at PROMOTION TIME, inside the promote transaction — not at a hostcall seam, which the delivery loop never touches (the hostcall Budget is a 30 s wall clock with no run/kind/spend; the DO’s clause was stale on four measured grounds and the re-scope is recorded). Every enforced budget kind needs explicit, unexhausted headroom; blast_radius is never enforced (crate law). confirm is the human disposition act on a prompt verdict. Write on the release’s domain + the workflow role; the agent preset is refused. |
| POST | /workflow/delivery/due | The /due crank — the valet precedent, transplanted: request-scoped (the cron recipe IS the scheduler), a bounded batch that DRAINS, remaining reported AND audited, and a hard in-handler batch cap (no route-level limiter exists; the cap is the egress storm’s only gate). Selects pending, kernel-authentic intent rows whose release is promoted, re-verifies EACH before any network contact (authenticity, release status, the approval’s currency against the live digest, the approver’s principal, the chain’s verification — all re-run because the world moves between mint and drain), dispatches through the R42 pinned read-egress path with NO connection held, and marks each succeeded row delivered through the guarded pending → delivered update — a concurrent drain is a receipt. The ledger’s belief moves only when the inbound authority observation reconciles; the crank never writes verified_at. Write on the body’s domain + the workflow role; the agent preset is refused. |
| GET | /workflow/delivery/releases | The release census: {releases, cap} — every release row in ONE domain (domain is a required query parameter), newest first, capped with the cap disclosed in the payload. The approval columns ride the row because the row IS the approval artifact; every text field passes the read seam. Serving this list grants no authority, approves nothing, and makes no compliance finding. Read on the queried domain + the workflow role. |
| GET | /workflow/delivery/runs | The run census’s listing: {runs, cap, default_limit} — every delivery run in one domain, oldest first, keyset-paginated on the id (?after_id=) so a caller never sees a row twice; ?limit= is clamped in the core — the cap is law, not a request field — and both bounds are disclosed. Phase and tier are read from the run’s own state; the state bytes themselves are not echoed (the engine-exact view is the machine surface). Read on the queried domain + the workflow role. |
| GET | /workflow/delivery/runs/{id} | One delivery run’s head. The domain resolve comes first (probe-blind 404), and a non-delivery run reads as absent rather than as a wrong-kind error — the collapse that keeps this surface from being an existence oracle. Read on the run’s domain + the workflow role, probe-blind. |
| GET | /workflow/delivery/runs/{id}/steps | The run’s steps in id order ({steps}), capped like every list surface. Same probe-blind collapse as the head read. Read on the run’s domain + the workflow role, probe-blind. |
| POST | /webhooks/delivery/{kind} | An inbound authority observation, on the delivery sub-family of the EXISTING public /webhooks/ family. It adds no new public path: it authenticates with the shipped GitHub HMAC verifier over the raw body and lands in the same bounded queue as every other verified webhook, so the replay window, the delivery-id idempotency, and the flood cap are the consent boundary it actually passes through rather than properties it re-implements. The observation is never trusted ahead of reconciliation — a verified body says only that these bytes came from the configured sender, and what the ledger believes comes from the authority itself, read through the shared egress family; the 200 reports the reconciled verdict, not the claim. The run, the domain, and the secret root are resolved server-side from the configured binding, so a body claiming a different tenant is ignored; an observation with no open run in that domain is refused rather than attached to an arbitrary one. kind is github (a vcs binding) or actions (a ci binding), and anything else is refused by name. A mismatch is recorded as typed evidence for a human to decide: whether an external system’s data may be read, retained, or re-published is a question for a human with the contract in hand, and this surface decides none of it. The signature shows the holder of the configured secret sent these bytes; it says nothing about whether their contents are true. |
| GET | /workflow/delivery/outcomes | The derived delivery read model — a read-time-only cluster over the domain’s own audited release rows and authority-fact findings. Throughput and instability are a coupled cluster; change_fail_rate is the control (its readings carry role: "control") and the cluster carries the recorded framing (leading indicators for organizational performance; lagging for delivery practices), so no client can render a bare throughput number as a performance verdict — one route, one response object, no field decomposition. domain is a required query parameter; the surface is domain-scoped (a release row resolves to one tenant). window is an optional integer number of days, default 30, bounded 1..=366 and validated in the core — out of bounds is 400 window_out_of_bounds, never a silent clamp (OWASP LLM10 unbounded consumption is the threat; the bound is the control; there is no model call, so no injection surface is added). Every metric carries a typed state — computed with a value, or insufficient with a closed reason (window_empty, no_vcs_revision_recorded, no_incident_facts, no_rework_signal, insufficient_history); an absent metric is never rendered 0, and a zero is never rendered absent. Where DORA (DevOps Research and Assessment) names are used at all they carry dora_name + definition_match: proxy + a one-line definition note; the native measures (approval_to_promotion_elapsed, governed_release_cadence) are named natively and the native elapsed measure is never presented as DORA change lead time. Metrics vocabulary only; no thresholds or tables reproduced — no benchmark thresholds, tables, figures, or performance bands anywhere, and the run’s OWN history (own_baseline, a fixed 90-day window) is the only baseline. The change-fail rate is the count of the window’s promoted releases whose run carries a delivery authority contradiction (the closed delivery:% source vocabulary narrowed by the typed confidence column; the claim text is never read) over all of the window’s promoted releases — a contradiction on a run whose release is not promoted in-window is out of the denominator. Commit-anchored change lead time computes only when the release’s commit_sha joins to a recorded vcs commit-time fact; no production writer records such a fact today, so the live branch is insufficient (no_vcs_revision_recorded) — the honest answer, not an approximation; the computed branch is implemented and unit-proven so the metric is correct the day the facts exist. Transparency and auditability by design: a governed operator reads derived facts over their own audited records; it makes no automated decision about a person, so no AI Act high-risk duty is triggered by this code; it carries no EU DORA obligation and makes no operational-resilience claim. Nothing is persisted — a pure query, deterministic for (window, now), writing no findings row, no counter, no scalar. Read on the queried domain + the workflow role. |
| GET | /workflow/delivery/runs/{id}/attestations | The run’s signed attestation chain and its UNCONDITIONAL verification verdict: {run_id, chain, verdict}, each link carrying verified and, when false, a named refusal from a closed vocabulary. ?verify=1 is accepted and is an explicit request for the IDENTICAL payload — no parameter can switch verification off, and a non-verifying chain is reported per link rather than hidden or degraded into a mark that reads as verified. The single 409 is a chain that could not be READ. The raw signed envelope is not returned: it is canonical bytes carrying a base64 signature. Read on the run’s domain + the workflow role, probe-blind. Not DSSE — the project envelope convention, which verifies against no DSSE verifier; the subject_digest/predicate_type/predicate names mirror the in-toto Statement v1 model as adjacency only (not an in-toto Statement, no _type); no SLSA provenance and no SLSA build level; the IETF WIMSE agent-audit drafts are contemporaneous prior art, not a standard. Authorship is not authority — signer_did proves who signed, with no PKI, no revocation oracle, and no key epoch, so a rotated key leaves history verifiable. A valid signature says nothing about whether the act was permitted. |
| GET | /workflow/delivery/runs/{id}/replay-verify | Re-derives the run’s stored trace and reports whether it is internally consistent: {run_id, window, order_ok, compared, matched, mismatched, diffs, event_log, generated_at}. For each trace row, in ORDINAL order, the row’s content address is recomputed from its own stored columns and compared with the address stored beside it; the ordinal series is separately checked for contiguity, and a gap or descent is reported as an order diff. A mismatch is DATA, never a status — the request is 200 and the reader is handed what was stored, what the columns imply, and which comparison failed. Models are never re-run: the comparator lives in a crate whose entire dependency set is serde/serde_json/sha2, so the zero-model property is structural, and the verdict says nothing about whether an outcome was correct. ADJACENCY: POST /workflow/decision-runs/{id}/replay-diff publishes a similar concept under similar wire keys; the two are not unified and share no code — that route RE-EXECUTES the pipeline and loads a bound model, where this one does not re-execute anything. THE CEILING: this is tamper EVIDENCE over stored bytes, not tamper-proofing — an attacker who edits a column AND recomputes the address leaves nothing to detect here; it does not bind a row to the signed attestation chain (the chain is what binds; this checks); and it is not a compliance finding — a verified replay authorises nothing, because authorship is not authority. Classification, retention, and any legal sufficiency of this output are operator-and-counsel determinations. The window is bounded at 500 rows and the bound is disclosed in every response. Read on the run’s domain + the workflow role, probe-blind. |
| GET | /workflow/delivery/runs/{id}/trace | The run’s stored trace rows in ordinal order, plus the attestation chain head READ from storage (null before the first link, never a fabricated address) and the same bounded, self-disclosing ddl_* narrative appendix the replay verdict carries: {run_id, window, rows, attestation_root, event_log, generated_at}. It rides the same read function and the same window function as the verdict, so the two apply identical logic to storage: any difference you observe between them is a change in storage, not a difference of method. They are two separate requests with no shared snapshot, so this is not a consistency guarantee across a moving run — this one answers “what is actually there”, which is the question a reader has when the verdict reports that something did not line up. Serving these bytes is not an endorsement of them — the rows are operator-authored text and digests, returned as stored. The window is bounded at 500 rows and the bound is disclosed in every response. Read on the run’s domain + the workflow role, probe-blind. |
| POST | /workflow/delivery/runs/{id}/advance (R40 additions) | The pass now also signs an attestation link and appends it to the run’s chain, in the SAME transaction (step row → CAS → trace row → link → proposal seam → session log → audit last), and the trace row’s attestation_root names the chain head. An optional model ({key, config_digest}) names the registry row the pass executed under: the server resolves it, and the signed predicate carries the row’s artifact digest, so a model name with no bytes behind it is 409 delivery_model_digest_missing; the registry refusals are four distinct codes (delivery_model_not_registered / delivery_model_not_promoted / delivery_model_retired / delivery_model_digest_missing). Key posture, fail-closed: a pass REFUSES with 409 delivery_attestation_refused when the host has no usable operator key — an absent key and a refused one are different causes of the same code, and neither ever degrades into an unsigned link. A run on a keyless host therefore never advances past its admission. delivery_traces also gained a stored seq ordinal, so every trc_ id is re-addressed once (consumer-affecting). |
| POST | /workflow/claim-schemas | Author a claim schema — a human artifact, forever. Only a human principal may write one, and a self-authored schema is refused at ADMISSION rather than warned about. The body is {domain, version, body} where body is the TYPED slot document (predicate, type, disjointness class, bounds) — JSON Schema is deliberately not used: a schema document is a syntax contract, and the contradiction arithmetic needs a disjointness class, which JSON Schema can express only as a comment. The stored authored_by is mapped from the typed principal kind INSIDE the service core, so no request body can name its own author; the table’s CHECK is a tripwire on the write path and NOT an identity proof. 201 {domain, version, body_digest, authored_by, authored}. The budget consequence is real: one human artifact per domain, recurring forever (Write on global + the workflow role, human principal only). |
| POST | /workflow/claims | Propose a claim. A claim is a TYPED tuple — {claim_id, domain, subject, predicate, object} — against a ratified schema, so a free-text proposal cannot mint one. Every slot must be filled: a slot that defaulted its way to ratified is the same failure in a narrower column. The claim lands pending, invisible to every recall surface. 201 {claim_id, status, created_by} (Write on global + the workflow role; audited). |
| GET | /workflow/claims?limit= | The gated claim read — the loop’s only reader. Joins on status='ratified' AND recall_visible=1, the same two columns the database fence protects, so a bypassed trigger and an unreachable row are two independent locks on one fact. The surface has no parameter that could reach unratified material, so it cannot be asked for any. {claims: [...], limit}, bounded 1..=50 default 20, newest-first, every emitted field through the read seam (Read on global + the workflow role; audited). |
| GET | /workflow/claims/{id} | Read one claim for the promotion screen — the ONE surface besides the service core that may see a claim that is not yet ratified, which is why it is a separate operation rather than a flag on the gated read. Authorization precedes the lookup, so an absent id is probe-blind (Read on global + the workflow role; audited). |
| POST | /workflow/claims/{id}/verify | Run the gate. Six deterministic checks in a fixed order — shape, bounds, referential, citation resolvability, contradiction, premise discipline — each a pure function over rows: no model, no score, no threshold, no judgement tie-break. Citation resolution is delegated to the byte-range resolver over ADMITTED bytes, never a live substring match. The response carries the verdict and a CLOSED refusal code and never the failing byte offset, the adjacent text, or which evidence item was at fault — a location hint handed back to a generator turns the gate into an oracle it can be searched against, so the detailed diagnostic goes to the audit chain and the promotion screen only (Write on global + the workflow role; audited). |
| POST | /workflow/claims/{id}/promote | DISABLED — the loop ships inert. The route exists, is authorized, is audited, and returns {claim_id, status: "refused", reason: "promotion_disabled"} in EVERY configuration, for every actor, whether or not a token was presented. A deterministic gate’s honesty is a MEASURED property, not an architectural one, and no long-run out-of-sample figure has been published; promotion stays disabled until one exists and has a NAMED OWNER. The switch is a compile-time constant with no environment variable and no flag behind it. The attempt is audited whether or not it succeeds, because a promotion path that only records its successes is one whose refusals are invisible (Write on global + the workflow role). |
| GET | /workflow/runs/{id} · /workflow/runs/{id}/steps · /workflow/runs/{id}/suggestions | Run row (state sanitized at the read seam), steps, retrieval-backed suggestions (Read on the run’s domain). Since v1.28.72 the suggestions response carries evidence_recorded: true|false — the KCS evidence side-effect fires only for callers holding Write on the domain AND the workflow role (Read-only callers get the body unchanged, nothing recorded) |
| GET | /workflow/runs/{id}/report | The run’s recorded-rows report at a pinned law version — a pure rendering of its gate records (workflow_steps) and workflow audit rows, labeled with the pinned law_version (absent pin = the run’s own intake stamp); law_version_mismatch is advisory ONLY; reads are not audited so the report stays byte-reproducible (Read on the run’s domain) |
| POST | /workflow/cases/{id}/gdl | The operator case-launch boundary: launch one GDL case episode on a FRESH run (kind troubleshoot, status active, revision 0, empty state) through the real server-configured provider. The accepted body is {ticket} only. Provider destination/model/secret are server-owned via BRAIN_GDL_PROVIDER_BASE_URL, BRAIN_GDL_PROVIDER_MODEL, BRAIN_GDL_PROVIDER_SECRET_FILE, and BRAIN_GDL_PROVIDER_SECRET_ROOT; legacy caller fields return 400 gdl_request_migrated and are never used. JWT callers need domain Write plus the workflow role; role-less JWTs, unknown roles, and agent@loopback bearers are refused before secret/DNS/provider work. Production endpoints require HTTPS, reject userinfo/fragments/queries/unsafe shapes, pass the existing address screen with DNS pinning, and never follow redirects. The request has a 25-second total body deadline; receiver cancellation drops the in-flight HTTP future. A provider failure after admission is durably terminal and non-retryable: the first launch returns 503 gdl_provider_failed, and a later launch against that run returns 409 gdl_provider_failed without replaying provider work. Outcomes otherwise use the existing GDL vocabulary — a pending capture PROPOSAL (human-approved later) or a Handoff/route/escalation; nothing publishes automatically. Provider errors expose stable codes only; raw bodies, credentials, secret paths, and secret-bearing URLs are not reflected. |
| POST | /workflow/runs/{id}/steering | Queue a steering message: blocklist-screened, Write + approve-class role gate, bounded inbox drop-oldest at 100 |
| POST | /workflow/runs | Open a governed run ({domain, kind, state_json} → {run_id, revision}); Write + workflow role gate; open + audit row commit atomically. valet/% kinds vet the envelope at the fence: the what label must pass the injection screen (400 screen_rejected) and the state must be a readable valet envelope (400 valet_state_invalid) — v1.28.63 |
| GET | /workflow/runs/{id}/state | Engine-exact {state_json, revision} (machine CAS round-trip; NOT read-seam sanitized — the human view is GET /workflow/runs/{id}); Read + workflow role gate; audited read |
| PUT | /workflow/runs/{id}/state | CAS advance (200 {revision} / 409 {actual_revision}); Write + workflow role gate. status is a CLOSED vocabulary — active | cancelled | closed | completed | fired | resolved (v1.28.63); unknown values refuse 400 unknown_status with an audit row |
| POST | /workflow/runs/{id}/events | Outbox enqueue, exactly-once by idempotency key ({first, event_id}; optional parent_event_id links ancestry); Write + workflow role gate. RESERVED topics (v1.28.63): channel/*, steering, workflow/valet* are kernel-only — the route refuses them 400 topic_reserved (audited outbox_reserved_refused on the workflow chain) |
| GET | /workflow/runs/{id}/events?branch= | The lineage read: ordered events with parent_id links (Read on the run’s domain); branch=<event_id> narrows to that event’s ancestor chain, root-first; since=<event_id> backfills a reconnect gap |
| GET | /workflow/runs/{id}/context?at_event=&budget= | The derived context window (Fathom): latest checkpoint at-or-before the anchor + delta + finding digests + open question; field-budgeted, delta drops oldest-first (truncated flag) — the consumer contract for unbounded sessions (Read on the run’s domain) |
| POST | /workflow/runs/{id}/rewind | Rewind = branch, never delete: verify the target is a workflow/checkpoint event (or the run root), CAS-restore its state snapshot appending a branches[] marker, audit — one tx ({ok, revision, branched_from}); Write + approve role gate |
| GET | /workflow/runs/{id}/handoff | The I-PASS handoff packet assembled from the run’s records (illness/patient/action/situation/safety + handoff_complete = status=="completed"); Read on the run’s domain |
| POST | /workflow/runs/{id}/handover/offer | Relay: offer a one-click handover {to_principal, overlap_minutes?} — gated by the packet-completeness check (open question, un-breached SLA, current step, linked evidence/checkpoint, resolved escalation); an incomplete packet refuses 400 packet_incomplete with details.missing and writes nothing. Offer + lineage event (workflow/handover) + audit land in one tx; retried POSTs are idempotent (Write on the run’s domain + workflow role gate) |
| POST | /workflow/runs/{id}/handover/{offer_id}/accept | Accept an offer: in ONE WorkflowTx the offer state moves and the run owner CAS-transfers to the acceptor; the SLA clock is untouched and the reply points at the resume-at checkpoint. Deciding a decided offer replays {moved:false} (Write on the run’s domain) |
| POST | /workflow/runs/{id}/handover/{offer_id}/decline | Decline an offer with a REQUIRED reason {reason} — screened, ≤ 4000 chars, stored + audited (an audited refusal beats a silent bounce). 400 reason_required / reason_too_long (Write on the run’s domain) |
| POST | /workflow/runs/{id}/handoff/decision | The operator’s handoff decision {transition: delivered|cancelled, decision_ref} — the machine-generated handoff moves ONLY on an operator decision carrying a decision reference (screened, ≤ 256 chars, the audit-recovery handle); the lifecycle row + audit land in ONE tx. 400 decision_ref_required (the machine never closes a handoff on its own authority) / decision_ref_invalid / unknown_transition (Write on the run’s domain + workflow role gate) |
| POST | /workflow/runs/{id}/back-referral/return | The receiver’s release: {contract_key, report, decision_ref} flips the return contract to returned. A report missing a required field refuses 400 report_incomplete with details.missing (the B3 law at the surface); late is computed at the server clock; release row + audit in ONE tx. 400 decision_ref_required / decision_ref_invalid / contract_key_required / report_invalid; contract-absent answers 404 probe-blind (Write on the run’s domain + workflow role gate) |
| POST | /workflow/runs/{id}/complaint/lifecycle | Goodwill: advance the ISO 10002 lifecycle one legal step {to} over the CLOSED table (received → acknowledged → investigated → remedy_proposed → remedy_approved → closed → adr_referred); anything else refuses 400 complaint_invalid. Lineage event (workflow/complaint) + audit land in ONE tx (Write on the run’s domain + workflow role gate) |
| POST | /workflow/runs/{id}/complaint/remedy | Goodwill: propose a remedy from the matrix {kind: repair|replace|refund|goodwill_payment|explanation_only, amount_cents, code_clause_id, tier} — always a PENDING HITL proposal citing its legal basis and its published code-of-conduct clause; a contradiction with the published promise is flagged on the packet, never silently blocked; nothing financial executes here. Approval rides the standard gate with deterministic role-tier caps; over cap it escalates exactly one level with the packet attached. Response carries the Attestation provenance mark (Art 50(2) AIGEN, ed25519-signed; provenance::verify refuses tampering) (Write on the run’s domain + workflow role gate) |
| GET | /workflow/runs/{id}/complaint/adr-packet?member_state= | Goodwill: the ISO 10003 external-dispute packet — run identity, audited remedy history, and the competent NATIONAL ADR body from the DPO-maintained registry (knowledge.source='adr_body'). The EU ODR platform is discontinued (Reg. 2024/3228); every packet states that basis. Humans file. Unregistered member state denies loudly. Carries the Attestation provenance mark over the post-read-seam boundary bytes (Read on the run’s domain) |
| POST | /workflow/runs/{id}/complaint/ack | Advocate: acknowledge the complaint — the legal received → acknowledged step with its dedicated audit marker so the monthly register measures ack-SLA attainment (ISO 10002: within the hour). Lineage event + audit in ONE tx (Write on the run’s domain + workflow role gate) |
| POST | /workflow/complaints/ack-sweep | Advocate: one overdue-acknowledgment sweep over every active complaint past its ack deadline — exactly one workflow/complaint/ack_overdue alert per run on the alert bus, audited, idempotent per run, bounded. Global scope (Write + workflow role gate) |
| POST | /workflow/outreach/campaign | Outreach: propose a campaign {domain, channel: email|sms|call, purpose: care_followup|retention|recall_notice, template_id, audience[]≤1000} as a pending HITL proposal — raw audience identifiers are hashed at the door, and the deterministic consent gate excludes every recipient without an in-force grant BEFORE filing (each included recipient carries its consent proof; zero eligible recipients refuses 400 outreach_invalid). NOTHING sends here — approved campaigns export for CRM-side execution (Write + workflow role gate) |
| GET | /workflow/outreach/campaign/{id} | Outreach: the export packet for an APPROVED campaign only — recipients with their consent proof plus the template reference, for the CRM connector feed or operator export; pending/rejected campaigns export nothing (404). Emitted text passes the read seam, then the Attestation provenance mark signs the boundary bytes (Read + workflow role gate) |
| GET | /workflow/outreach/consent?subject=&channel=&purpose=&domain= | Outreach: the deterministic verdict for one (hashed subject, channel, purpose) triple — absent/revoked/expired all DENY, only an in-force grant reads granted; the proof row (granted_at/expires_at/provenance) rides every verdict. The raw subject never leaves the handler (Read + workflow role gate) |
| POST | /workflow/runs/{id}/outreach/followup | Outreach / Order-of-Care: schedule the post-close proactive check for a CLOSED complaint run whose state carries subject — one pending HITL proposal due at the policy interval (default 7 days after close), gated on an in-force care_followup consent. No consent → loud 400 outreach_invalid and nothing filed (a gate, not a warning); lineage event + audit land in ONE tx (Write on the run’s domain + workflow role gate) |
| GET | /ops/handovers?domain=&now= | The follow-the-sun board: active runs ranked by SLA remaining (recorded deadline wins, else P3-from-created), flagged while now sits inside the ring boundary’s derived overlap window (Read on the domain) |
| POST | /workflow/runs/{id}/notes | Channel: post a case note {content} — screened at write (empty/≤4000/prompt-injection blocklist) and stored through the invisible-strip + markdown-ref seam; @skill:<tag> / @principal mentions resolve into swarm invites (invite row + case/note lineage event that drains to /events as the Crew ping — visible to ?kinds=workflow subscribers holding Read on the domain). Dead mentions refuse 400 mentions_unresolved with the list (over-vocabulary tokens included); >16 resolved invitees refuse 400 invite_limit; a run at its channel ceiling refuses 409 channel_full — evidence is never drop-oldest-deleted. {content, kind:"reask"} additionally marks the operator re-ask: the note rides as usual PLUS one case/reask lineage event (the effort proxy’s marked source; the CLI twin is brain workflow note <run> <text> --reask). Note + invites + events + audit land in ONE tx (Write on the run’s domain) |
| GET | /workflow/runs/{id}/notes?limit=&offset= | The channel view: chronological notes + invites for one run, policy-expired rows hidden before the page split (case-note retention kind), every string on the read seam, bounded page 1..=500 (Read on the domain) |
| POST | /workflow/runs/{id}/notes/{invite_id}/accept | Accept an invite into the channel: CAS pending → accepted on the invite row in one tx with its lineage event + audit; replaying a decided invite returns {moved:false}. Ownership never moves (Write on the run’s domain) |
| POST | /ops/agents/cards | Mesh: provision (or re-sign) an agent’s A2A-shaped card {domain, principal, name, description?, capabilities?} — Ed25519-signed with the UMP operator key at provisioning; no key refuses 409 operator_key_missing (Admin on the domain) |
| GET | /ops/agents/cards?domain= | The domain’s verified agent cards — each re-verified against the current operator key before it leaves the server; a tampered card fails the whole list closed (400 card_tampered) (Read on the domain) |
| POST | /ops/agents/revoke | The ASI03/07 kill-switch: revoke a principal {principal, reason?} — every card use, delegation dispatch, and result submission re-checks revocation and refuses closed (403 principal_revoked); every ACTIVE run where the principal owns in-flight delegation work drains through the existing run-cancel path; revocation + hash-chained audit + drain in ONE tx; identity-wide (Admin on global) |
| GET | /ops/agents/revocations | The kill-switch register: newest-first {principal, revoked_at, reason, revoked_by} rows; the hash-chained audit chain carries the full story (Read on global) |
| GET | /ops/agents/bom | Live agent bill of materials (AgBOM, CycloneDX 1.6 shape): models, knowledge stores, enforcement posture — regenerated per request, never a build snapshot (Read on global) |
| GET | /ops/authz/explain?route=&method= | R47: the gate row for a route PATTERN plus the caller’s own verdict and a closed reason (allow/defer/deny with route_ungated, method_not_permitted, capability_deny_only, no_principal, public_path, presentation_gated). Deliberately refuses a ?roles= set (400 authz_explain_role_set_refused) — it will never answer “what would another role get” — and answers a probe-blind 404 for a route with no gate row. Echoes the BRAIN_RBAC_ROLELESS_POSTURE in force (Admin on global) |
| POST | /workflow/runs/{id}/delegations | Mesh delegation {to_principal, task}: the target’s card is verified FIRST (unknown/tampered refuses 400 agent_unknown / card_tampered, nothing written); then row + delegation/request lineage event (ids+actors only, never task content) + audit in ONE tx. Task screened like notes; per-run ceiling refuses 409 delegations_full (Write on the run’s domain) |
| GET | /workflow/runs/{id}/delegations?limit=&offset= | The run’s delegation view: chronological work orders with state (requested/completed) and results, every string on the read seam, bounded page (Read on the domain) |
| POST | /workflow/runs/{id}/delegations/{delegation_id}/result | The delegatee’s exactly-once result {result} — screened, CAS requested → completed in one tx with the delegation/result child lineage event + audit; non-delegatees refuse 400 not_delegatee, replays refuse 409 conflict (“this delegation already returned its result”) (Write on the run’s domain) |
| POST | /workflow/runs/{id}/answer | The AskHuman closer: digest-bound to the live pending_question, appends answers[], clears the question, CAS — one tx; Write + approve role gate |
| GET | /workflow/runs/{id}/steering?since= | Drain the advisory steering outbox (Read on the run’s domain) |
| POST | /workflow/plugins/mount | UI-plugin mount/unmount evidence (Art 12 record-keeping): server verifies the claimed bundle SHA-256 against the boot manifest before writing the audited row (409 on uncertified bytes) |