The acceptance inventory
This is the required behavior inventory. Executed controlled-mock results and limits are recorded in build status; the validation manifest maps all 28 acceptance groups — A to X, SA, SL, GC and AL — to exact passing test nodes. All mutation assertions inspect the provider operation journal and invocation evidence, not only the gateway's HTTP response. Concurrency tests use separate database transactions/workers against real PostgreSQL. Use a controllable clock and deterministic barriers for races.
Original scenarios
| ID | Setup/action | Required result |
|---|---|---|
| A | Permitted workstation and correctly linked authoritative critical incident | ALLOW; one provider mutation, one impact unit and durable evidence. |
| B | Tier-0/domain-controller isolation request | DENY; zero mutations. |
| C | Production target | No dispatch before scoped human approval; one dispatch after valid retry. |
| D | Ten principal autonomous isolations, then target eleven | Eleven requires approval; simultaneous requests cannot exceed autonomous allowance. |
| E | Caller claims low-tier workstation; resolver identifies Tier-0 | DENY; claims never replace trusted facts. |
| F | Unknown operation, raw passthrough or unsupported consequential option | DENY; no protected mutation. |
| G | Change any approved execution field, account, target or security version | Old approval invalid; reevaluate or reject, zero unauthorized mutation. |
| H | Retry a confirmed execution | Prior result; no extra mutation or impact. |
| I | Provider mutates then times out | INDETERMINATE with capacity held; no automatic conflicting retransmission. |
| J | Replay a policy change | Changed simulated decision; zero provider calls or writes to live control state. |
| K | AGENT calls approve/deny/config/reconcile routes | Every path rejected and audited. |
| L | Several principals collectively reach tenant ceiling | Final over-ceiling request denied regardless of principal allowance or human approval. |
| M | Interleave HTTP and MCP requests | Same idempotency claims, resource guards, tenant impact and policy results. |
| N | Erroring Cedar forbid with a broad permit; repeat with an approval-set error | DENY; also deny missing required trusted attributes and configured-set load failures. |
| O | Change version between last gateway read and provider write | Atomic provider precondition rejects mutation; stale authorization recorded. |
| P | Reconcile timeout using exact provider operation evidence | Correct terminal/uncertain state and exact impact preservation. |
Review regressions and specification closure
| ID / patch | Setup/action | Required result |
|---|---|---|
| Q / P1 | Submit permitted primary resource plus Tier-0 target; reverse order; embed target in arguments | All contradictory/multiple-target forms rejected before dispatch. |
| R / P3 | Permute broad permit, production approval, exceeded soft threshold and exceeded hard ceiling | DENY always wins; valid approval cannot waive hard ceiling. |
| S / P1/P6 | Two tenants with colliding local IDs, aliases, accounts and idempotency keys | No foreign object disclosure, approval consumption, replay selection or provider mutation. |
| T / P2 | Unrelated critical incident, agent-editable severity, stale incident, claimed derivation input | No independent authority established; deny or invalidate old approval as appropriate. |
| U / P4 | Change provider resource/bundle/context at dispatch barriers | Conditional provider failure or precommit reauthorization; no old-plan dispatch after a prior bundle change. |
| V / P4 | Simultaneous same-key requests, changed payload, new key/run/alias, crash at each dispatch boundary | One execution claim; conflicts rejected; possible dispatch stays uncertain without automatic resend. |
| W / P5 | Timeout, out-of-band inverse mutation, reconcile; delayed arrival after negative lookup; another actor creates positive state | Real impact never refunded; absent/current-state observations remain uncertain; cancellation tombstone fences late operation. |
| X / P6 | Agent markup in approval fields; cross-site forms; expired/wrong-scope human session | Strings inert; forged/browser-mediated approvals fail; direct agent restrictions remain enforced. |
Additional required edges:
- First-use budget-row races and multiple simultaneous scopes; no negative/double-released reservations.
- Fresh runs, reauthentication and bundle/rule-version changes preserve principal/tenant history.
- Outstanding reservations remain counted beyond rolling-window boundaries until resolved.
- Same-intent pending requests with new keys reuse one approval; completed intentional new-key operations reevaluate and count again.
- Context reattestation with unchanged versions does not mutate a frozen plan; changed/expired dependencies invalidate approval.
- MRTR MAC tampering, unknown/retired keys, wrong tenant/principal/audience/hash, expiry and concurrent approval consumption.
- Modern and one supported older client use identical policy/approval behavior; tool-list cache isolation never substitutes for tools/call authorization.
- Both providers reject agent credentials; fixed connector destinations reject redirect/URL/header injection; secrets absent from responses/logs/errors.
- Audit edit/deletion/reorder/concurrent append/truncation checks against a retained trusted head.
- Replay with unknown approval, hypothetical new execution, uncaptured context and uncertain dependent counters reports UNRESOLVED.
- Every Entra operation, including enable and revoke_sessions, has explicit scoped authorization and operation-specific evidence.
V1 is complete only when A–X and applicable edge cases pass, startup is reproducible, browser approval is verified, tested dependency/client versions are recorded, and the validation package states its mock/coverage limits.
Context contracts
| ID | Setup/action | Required result |
|---|---|---|
| CC1 | Construct catalogs that violate each construction rule in section 3 | Construction fails for each |
| CC2 | Both v1 contracts through resolution and evaluation | Frozen plans, hashes and decisions match the pre-change golden fixture byte for byte |
| CC3 | Synthetic contract serving endpoint.isolate with row-backed String, Long and Boolean facts, an equals derivation and incident mode none | ALLOW performs one provider mutation; a forbidden value denies with zero mutations |
| CC4 | Mapping names a contract missing from the catalog, or one that does not serve its action | INVALID_OPERATION; zero mutations |
| CC5 | Bundle settings with shorter and longer max ages; above-ceiling, empty-writer and unused-contract settings | Shorter denies a fact the default accepts; longer accepts an older fact; invalid settings fail validation |
| CC6 | Source writers outside the effective allowlist, including an agent-writable incident source | DENY; scenario T unchanged |
| CC7 | Bundles whose Cedar schema omits, adds, mistypes or makes optional a contract attribute | Validation fails; the existing packs still activate |
| CC8 | Two tenants with colliding account and resource IDs in resource_facts | No cross-tenant read; runtime cannot insert, update or delete; RLS forced |
| CC9 | Migration upgrade and downgrade round trip; downgrade with rows present | Round trip passes; downgrade refuses |
| CC10 | Replay with candidate bundles that shorten and lengthen max age | Shorter can deny history; longer cannot extend frozen validity; existing replay scenarios unchanged |
| CC11 | Pending approval, then a resource_facts row version changes | Re-attestation with the frozen bundle's settings returns STALE_AUTHORIZATION |
| CC12 | Approval page for endpoint, identity and synthetic contracts | Each shows its contract's display fact as inert text |
Tests live in tests/test_contracts.py, test_context_parity.py, test_resource_facts_schema.py, test_context_resolution.py, test_context_evaluation.py, test_context_bundle_validation.py, test_context_execution.py and test_replay_scenarios.py.
SOC action catalog
| ID | Setup/action | Required result |
|---|---|---|
| SA1 | Construct action catalogs that violate each construction rule in section 3 of the design | Construction fails for each |
| SA2 | All four existing actions through parse, resolution, evaluation, command and journal | Both endpoint fixtures, the context parity fixture and the new identity golden match byte for byte |
| SA3 | Arguments that are nested, listed, null, float, oversized, unknown, missing, extra, mistyped or out of domain, on HTTP and MCP | Rejected before any lookup or mutation; scenario F unchanged |
| SA4 | Contained endpoint, reason false_positive, incident CLOSED | ALLOW; one lift_containment mutation; journal shows contained true to false and binds the arguments |
| SA5 | Same target, reason remediated on a non-production endpoint | REQUIRE_APPROVAL; the approval page shows reason: remediated as inert text; valid retry after approval dispatches once |
| SA6 | Standard identity, OPEN HIGH incident, require_change_at_next_sign_in true | ALLOW; credential transition increments the generation and sets must_change_password; enabled state and session generation unchanged; no password in journal, response or logs |
| SA7 | require_change_at_next_sign_in false, then a privileged identity | REQUIRE_APPROVAL, then DENY with zero mutations |
| SA8 | Same key with a changed argument; new key with the same arguments while pending | IDEMPOTENCY_CONFLICT; attaches to the pending execution |
| SA9 | Synthetic action with a bounded Long argument in and out of range; synthetic second binding for endpoint.isolate through a tenant mapping; a mapping naming an unknown binding | In range resolves and reaches Cedar; out of range is INVALID_ARGUMENTS; the second binding resolves; the unknown binding is INVALID_OPERATION |
| SA10 | Bundles whose action context omits, adds, mistypes or makes optional an argument, or omit a mapped action | Validation fails; the existing packs and the SOC pack still validate and activate |
| SA11 | Provider command with arguments altered after freezing; journal with an inconsistent transition | Provider rejects with 400 and no journal; evidence fails verification and nothing settles |
| SA12 | Isolate then lift the same target; lift while the isolate is unresolved | Two impact units, no refund; while the claim is held the second request is recorded as a DENIED transition with reason RESOURCE_BUSY |
| SA13 | Migration upgrade and downgrade round trip; downgrade with a new-action row in each guarded table | Round trip passes; downgrade refuses; runtime privileges unchanged |
| SA14 | seed-validation output | SOC bundle installed as VALIDATED and written to the output; the demo bundle, its bytes and the browser demonstration unchanged |
| SA15 | Replay with a candidate that forbids reason == "remediated" | The recorded lift becomes a candidate DENY; the report row shows the argument; existing replay scenarios unchanged |
Tests live in tests/test_action_catalog.py, test_domains.py, test_identity_compatibility.py, test_requests.py, test_resolution.py, test_policy_evaluation.py, test_context_bundle_validation.py, test_soc_pack.py, test_soc_provider.py, test_soc_execution.py, test_soc_schema.py, test_replay_scenarios.py, test_replay_verify.py, test_human_surface.py and test_validation_demo.py, with shared fixtures in tests/soc_fixtures.py.
Approval quorum
| ID | Setup/action | Required result |
|---|---|---|
| QA1 | Bundle requires two approvers on the production obligation; first approver approves; agent retries | Approval PENDING with one decision; retry 202 pending; no dispatch |
| QA2 | Second distinct approver approves; agent retries | APPROVED, then CONSUMED; exactly one provider mutation |
| QA3 | The first approver decides again with the same or another credential | INVALID_STATE; one decision row; nothing counted twice |
| QA4 | One approval, then a denial by another approver | DENIED; execution DENIED (HUMAN_DENIED); retry denied; zero mutations |
| QA5 | One approval, then the approval window ends | EXPIRED; a later approval by the second approver is refused; nothing consumed |
| QA6 | Two approvals; before the retry, one approver's credential is revoked or scope removed | STALE_AUTHORIZATION with INVALID_APPROVAL; zero mutations |
| QA7 | Bundle re-activated between the two approvals | Second decision refused as stale; approval INVALIDATED |
| QA8 | Two approvers decide concurrently on two connections | One completes the quorum; two rows; consumed once; one execution |
| QA9 | Quorum rules naming an unknown obligation, required of 1 or 6, or duplicate rules | Bundle validation fails with field locations; existing packs still validate and activate |
| QA10 | Human pages with real Chromium | Progress text; no form for the approver who already decided; a replayed decide POST by that approver refused; markup inert |
| QA11 | Audit and replay | Every approver captured; forged one-approver consumption against a two-approver requirement reports INSUFFICIENT_QUORUM; the parity golden and endpoint fixtures unchanged; the preview shows the candidate's effective requirement |
| QA12 | Migration round trip and check_runtime | Column, table, forced RLS and grants present; runtime cannot update or delete decisions; downgrade refuses while any decision row exists |
Tests live in tests/test_approval_quorum.py.
Observe-only mode
| ID | Setup/action | Required result |
|---|---|---|
| OB1 | Tenant in OBSERVE; production target request over HTTP and MCP | 200 OBSERVED, observed.effect REQUIRE_APPROVAL; no approval, impact reservation, claim or ticket; zero provider invocations |
| OB2 | Development target (would be eligible) | OBSERVED ELIGIBLE; an OBSERVED impact row; zero provider invocations |
| OB3 | Tier-0 target (would be denied) | OBSERVED DENY with the reasons |
| OB4 | Retry with the same key; a new key for the same intent | Same view; a new observed execution; nothing consumed |
| OB5 | Enforcing tenant with one observe_only mapping | That action observed; another action enforced with a real approval and one mutation |
| OB6 | Eleven observed autonomous isolations | The eleventh observes REQUIRE_APPROVAL under the assumed-executed projection; an enforced evaluation in the same tenant ignores observed rows; replay reproduces the decisions |
| OB7 | Switch to ENFORCE with a reason | tenant.mode_changed audited; the next request creates a real pending approval; observed rows unchanged; a pending approval admitted under enforcement keeps its view while the tenant observes |
| OB8 | Mode change by an agent or approver, without a reason, to the current mode or to an unknown mode; admission during an emergency stop | INVALID_IDENTITY, INVALID_REASON, INVALID_STATE, INVALID_MODE; TENANT_STOPPED |
| OB9 | Human pages with real Chromium | Observed badge and effect on Activity, no forms, Approvals empty, mode notice shown |
| OB10 | Audit, replay and preview | Captures carry mode: OBSERVE; a candidate bundle re-evaluates observed decisions; a forged observed execution with a claim, or an OBSERVED transition without observation, is OBSERVED_WITH_EFFECT |
| OB11 | Migration round trip and check_runtime | Column and states present; downgrade refuses while any tenant is in OBSERVE or any OBSERVED row exists |
| OB12 | CLI mode and report commands | Mode set and shown; report counts match the audit chain; non-ADMIN refused |
Tests live in tests/test_observe_mode.py.
Shared limit scopes
| ID | Setup/action | Required result |
|---|---|---|
| SL1 | Two agents in one group under a group ceiling of three with REQUIRE_APPROVAL; a third agent outside the group | The group's fourth isolation by either member is PENDING_APPROVAL with limit:<rule>; every member row retains the group; the outsider reserves at once with an empty membership and the group count is unchanged |
| SL2 | Account rule over three identity actions and endpoint.isolate on the identity account with a DENY ceiling of three; an isolation on the endpoint account and a second rule scoped to that account | The fourth disruptive action on the identity account is DENIED with HARD_LIMIT:<rule>; the endpoint account's isolation neither charges nor triggers it and counts only under its own account's rule; every row retains its account |
| SL3 | Selector environment = production with a total ceiling of two; development isolations in between and at the ceiling | Only production isolations count; development ones reserve without charging; the third production request is PENDING_APPROVAL with limit:<rule> beside the pack's production obligation |
| SL4 | Multi-action budget over endpoint.isolate and endpoint.lift_isolation in the SOC world beside a rule without actions, both at a ceiling of one | Isolate then lift the same target draw on one counter: the lift escalates on the pair rule alone and executes after approval with the endpoint released; the rule without actions counts the isolation only |
| SL5 | Six principals of one group on six connections at a group DENY ceiling of three | Exactly three tickets and three DENIED with HARD_LIMIT:<rule>; three retained rows each with the group; after settlement three confirmed units and a seventh request still denied |
| SL6 | Two reservations by a member, then unassign it and assign a newcomer | The newcomer's snapshot reads the group's two units and its first isolation is the third; the leaver's next isolation retains no group and charges nothing to it; the leaver's earlier rows keep the group |
| SL7 | Rules with a missing or mismatched qualifier, an unknown action, group, account or fact, a selector value outside the domain, empty, oversized or duplicate actions, or actions without action | The model or the store refuses each, the store with its field location (limits.<i>.action, .group_id, .account_id, .selector); a bundle with one valid rule of each scope validates and activates; existing packs validate with unchanged hashes; deleting the group fails the active bundle closed |
| SL8 | Approved candidates under group, account and selector scopes with an autonomous DENY rule at its ceiling and a total rule per scope | The approved retry executes at the hard autonomous threshold of every scope, adds zero autonomous units and one total unit, and its row retains the group and the fact |
| SL9 | Replay and preview over captures with and without recorded membership | Group, account and selector candidates reconstruct from captures alone and recorded decisions match; a candidate group rule over captures without groups reports UNKNOWN_GROUP_MEMBERSHIP and UNRESOLVED while the baseline still matches; a lower-bound denial stands; an unserved selector or unknown listed action is POLICY_ERROR; report.json is deterministic |
| SL10 | Migration round trip; downgrade with a group, a membership or a scoped draft bundle present | Backfilled account_id and facts match executions.frozen and group_ids is null on legacy rows; each downgrade refuses and leaves the revision, forced RLS and check_runtime intact; the runtime cannot insert, update or delete group rows |
| SL11 | Owner CLI and the record page with real Chromium | Group commands are audited and refused for non-ADMIN actors, non-AGENT members and the runtime role; the record page labels every scope and action list as inert text with no link or form in the list |
| SL12 | Observe-only tenant under a group rule and a selector rule | Observed rows carry the attributes; the observe-mode projection triggers both rules; enforced evaluation ignores the observed rows and a member's isolation reserves after the switch to ENFORCE |
A principal with more than 64 memberships fails admission closed with INVALID_CONFIGURATION and an audited rejection, and the 65th assignment is refused at provisioning.
Tests live in tests/test_limit_scopes.py (SL1 to SL6, SL8, SL12 and the membership bound), test_limit_scopes_schema.py (SL10), test_policy_models.py, test_policy_evaluation.py, test_context_bundle_validation.py and test_policy_store.py (SL7), test_admin_cli.py (the SL11 commands and the preview), test_replay_history.py and test_replay_scenarios.py (SL9), test_human_surface.py and test_browser_surfaces.py (the SL11 pages), with shared group, rule and bundle helpers in tests/scope_fixtures.py.
External execution checkpoint
Written first, failing on the unwired code, on real PostgreSQL, with the protected mocks acting as an external executor and the mock's conditional write standing in for a vendor's. The guide is External execution checkpoint.
| ID | Setup/action | Required result |
|---|---|---|
| GC1 | An eligible checkpoint on a mapping with external_executor, in a tenant whose account has no connector | A checkpoint object of exactly grant and act, with operation, expected version, precondition class, both deadlines, report secret and act grant; an UNRESOLVED impact row and a canonical claim exist; admission returns no dispatch ticket and the dispatch path refuses the execution |
| GC2 | Six principals requesting checkpoints for one target on six connections, and separately at a shared group ceiling | Exactly one grant and one claim for the target and the rest RESOURCE_BUSY; at the ceiling exactly the ceiling's grants succeed and the rest carry the hard limit; no negative or double-released counter |
| GC3 | A checkpoint and an agent dispatch against one ceiling, with a total and an autonomous rule | Both charge the same counters in every scope and the total ceiling is reached once across the two paths; an approved candidate adds zero autonomous units on both paths |
| GC4 | A checkpoint that requires approval, at a quorum of one and at a quorum of two | No grant; the existing approval with its window and quorum; the retried checkpoint after the decisions consumes it exactly once and issues one grant |
| GC5 | A denied checkpoint | No grant, no impact row, no claim; the denial is captured and replays to the same decision |
| GC6 | An attested executed outcome, then the identical report again, then a second signed record restating it | EXECUTED at tier ATTESTED, impact confirmed, claim released; a report restating the settled tier, outcome and reason is a no-op returning the same view, whatever record carries it and whatever the retained proof stays, and a report contradicting any of the three is refused |
| GC7 | An asserted executed outcome | EXECUTED at tier ASSERTED, impact confirmed, the claim still held; a run before the act window releases nothing, the run after it releases the claim, and the record and page show the tier |
| GC8 | An asserted non-execution, then an administrator settlement with attested evidence through the connector-free path | The report leaves INDETERMINATE with impact charged and the claim held; only the settlement releases them; the executor cannot reach that path |
| GC9 | An outcome report presented against an ordinary dispatched execution, another principal's checkpoint, an already settled execution, an expired report window, and with a forged or replayed secret | Each refused with no state change, audited without echoing the secret, and the dispatched execution keeps its own owner secret as the only way to settle it |
| GC10 | A grant unreported past its report window, then a recovery run, then a cancellation run | Swept to INDETERMINATE with impact charged and the claim held, listed in the uncertain queue; the cancellation run refuses to settle it as a non-execution |
| GC11 | Two simultaneous outcome reports on one grant, on two connections | Exactly one settles; one confirmation, one release, one audit event |
| GC12 | The target version changed between grant and report, under each precondition class | ENFORCED_BY_PROVIDER returns a non-execution that releases impact and claim; ASSERTED_BY_EXECUTOR records an assertion that releases nothing; UNAVAILABLE carries no version and the grant's own capture records that the change was undetectable |
| GC13 | A plan that expires and an act window that closes between grant and report, and an idempotent retry of a granted checkpoint | The report is still accepted within the report window and settles at its tier; the retry returns the same checkpoint and a re-signed act grant, mints no second secret and reserves nothing new |
| GC14 | Replay of a history containing an attested checkpoint, an asserted checkpoint, an unreported grant, a settled FAILED_EFFECT_UNKNOWN row and a corrected row of each direction | Every decision recomputes; the asserted settlement is never presented as gateway evidence; the unreported grant replays as retained uncertainty before and after the sweep; the tenant still exports, publishes and previews; reports are byte-deterministic. Extended for the reported failure: as written the scenario held no row in the new state, so it could not detect the skipped settlement gate, the ledger's refusal of a second settlement or the terminal gate's before set — and a corrected row of one direction alone cannot detect the last of those, because the after set already catches CANCELLED_NOT_EXECUTED |
| GC15 | Bundle validation of external_executor beside observe_only, without a catalog binding and without a registered executor key; a request naming an executor; and a checkpoint request in a tenant switched to OBSERVE | The model refuses the placement pair and the store refuses the other two with their field locations; the request fields are refused as unknown; the observing tenant returns an OBSERVED view with no grant, reservation or claim; existing bundles validate and activate with unchanged hashes |
| GC16 | Emergency stop between grant and report, and a stop before a checkpoint; and an executor spamming invalid reports | The outstanding grant is not recalled and its report still settles; a new checkpoint under the stop is refused; the stop period is visible in replay; the refusals are bounded and do not starve the tenant's writers |
| GC17 | Migration round trip with a settled checkpoint of each tier, an unreported grant, a settled FAILED_EFFECT_UNKNOWN row and a corrected row retained | Downgrade refuses while any exists; check_runtime passes before and after; tests/fixtures/endpoint-contract-v1.json, tests/fixtures/identity-contract-v1.json and the context parity golden stay byte-identical. Extended for the reported failure: as written it round-tripped a migration with no row in the new state, so the downgrade would have succeeded either way and the clause was never exercised. The corrected row is the case a check of executions.state alone would pass, because only its retained events name the new state |
| GC18 | The synthetic workflow contract end to end with real Chromium for the pages | The story handles grant, denial, approval wait, expiry and report failure; the record and approval pages label the execution externally executed and show class and tier as inert text, with no control that dispatches |
GC6's row was narrowed during implementation and the design carries the correction: what a settled row is closed to is a change of tier, outcome or reason, and a second record that differs in some other way — a later reported time, a signature by a second registered key — but restates the settlement is verified, found to move nothing, and answered as the no-op the first half of the row describes. It retains nothing and appends nothing, so a tenant cannot afterwards see that two of its registered keys attested one settlement; no budget, claim or execution state moves, which is why that is a limit on what the history can answer rather than a reason to refuse the record. A neighbouring record is refused by a different guard: one whose reported time predates the grant is INVALID_RECORD at the verifier.
Tests live in tests/test_checkpoint.py (GC1 to GC16 and GC18's approval-page clause), test_checkpoint_schema.py (GC17), test_checkpoint_workflow.py (GC18's story contract) and test_browser_surfaces.py (GC18's Chromium half), with shared fixtures in tests/checkpoint_fixtures.py; the four prefixes are group GC in ACCEPTANCE_PREFIXES. Supporting evidence for the same unit lives in test_checkpoint_evidence.py, test_admin_cli.py, test_recovery_run.py, test_replay_scenarios.py and test_replay_verify.py.
Provider-specific proof and the live half of the workflow contract are not covered by these rows and stay open until a separately authorized platform trial: every capability here is fixture-verified against the protected mocks.
Trusted context provisioning
| ID | Setup/action | Required result |
|---|---|---|
| CP1 | Inventory manifest with column-backed and row-backed facts and a provider_version for an existing resource, imported under an ADMIN actor | Rows carry the source, version and observation time; the resources row carries the stated provider version; resolution freezes exactly those values; context.imported is audited with the digest and counts and no value |
| CP2 | The same manifest imported again, and a later one with a new version label and identical content, while an approval is pending | No content or version changes; observed_at advances only for the second; the pending approval still passes re-attestation |
| CP3 | A manifest that changes one row-backed fact | That row's version changes and its siblings keep theirs; the pending approval fails re-attestation with STALE_AUTHORIZATION |
| CP4 | An observation earlier than the retained one, one 61 seconds in the future, and a reused version with different content | OBSERVATION_REGRESSION, FUTURE_OBSERVATION, VERSION_REUSED; nothing written; no audit event; 59 seconds ahead is accepted |
| CP5 | Unknown, derived and ambiguous fact names; out-of-domain, wrong-type and oversized values; an incident fact in an inventory manifest | UNKNOWN_FACT, DERIVED_FACT, AMBIGUOUS_FACT, INVALID_VALUE; nothing written; a value must satisfy every contract that declares the fact |
| CP6 | Missing or wrong-kind source, writer outside allowed_writers, unknown account, --tenant differing from the manifest, a non-ADMIN or foreign actor, and a source that exists only in another tenant | INVALID_SOURCE, INVALID_WRITER, INVALID_ACCOUNT, TENANT_MISMATCH, INVALID_ACTOR; no row and no audit event in either tenant |
| CP7 | An entry for a resource the registry does not hold | Skipped and counted by default; the whole manifest refused with --strict (UNKNOWN_RESOURCE) |
| CP8 | Retracting a row-backed and a column-backed fact, then active: false | Row deleted, column nulled, resource deactivated; resolution fails with INVALID_CONTEXT for the missing facts and INVALID_TARGET for the inactive resource |
| CP9 | Incident manifest creating an OPEN CRITICAL incident for a development endpoint, then closing it, then relinking it | endpoint.isolate executes once under the v1 bundle after the first import (the provider version untouched) and is denied after the second; INCIDENT_RELINKED refuses the relink; a retraction on an incident entry is INVALID_MANIFEST |
| CP10 | The harness asset and ticket exports through the adapter, plus unmapped state, priority and class | Both manifests import, decommission the second asset and gate the action on the derived incident identifier; the adapter refuses each unmapped export |
| CP11 | Source create and deactivate through the CLI; an import through a source whose labels the default allowlist permits and one whose extra label it does not; the runtime role against every table the importer writes | Sources audited with sorted labels; the first import resolves, the second fails resolution until a bundle admits the label; deactivation makes its facts fail resolution and a repeat is INVALID_SOURCE; refusals exit 2 without echoing values; check_runtime passes and the runtime role cannot INSERT, UPDATE or DELETE |
| CP12 | A manifest over 16 MiB, one with 10,001 entries, a duplicate entry, a name in both facts and retract, an empty entry, a naive timestamp, an unknown field and an incident entry in an inventory manifest; then a 10,000-entry manifest | MANIFEST_TOO_LARGE or INVALID_MANIFEST before any database work; the large manifest imports in one transaction within the statement timeout |
Tests live in tests/test_context_provisioning.py.
Credential administration pages
| ID | Setup/action | Required result |
|---|---|---|
| CA1 | ADMIN issues an agent credential for a new principal from the page | Principal and credential rows exist; the token is shown once under Cache-Control: no-store; a second GET of the list shows identifiers only; the token authenticates on /v1/me; principal.created and credential.issued carry the acting credential and no secret |
| CA2 | ADMIN revokes a credential; repeats it; tries an administrator credential and an unknown one | Revoked at once and the token no longer authenticates; the repeat, the administrator credential and the unknown one answer 409 with nothing changed; credential.revoked audited |
| CA3 | ADMIN issues an approver credential and assigns the approver to a target and action; the approver signs in with the new token | Assignment listed; the pending approval appears on the approver's list; an agent principal, an unknown target and an unknown action are refused with 400 |
| CA4 | ADMIN unassigns the approver | The pending approval disappears from the approver's list; a repeat answers 409; approval_scope.assigned and approval_scope.revoked audited |
| CA5 | Issue for an ADMIN principal, new:ADMIN, an unknown principal, a malformed principal, an out-of-range or zero lifetime, an extra field; the functions called directly with an ADMIN kind, an ADMIN principal, a 91-day lifetime or a stale clock | Every page attempt answers 400 with no secret; every function call raises its code; no principal or credential is written |
| CA6 | Approver session on the pages and their forms; anonymous; agent bearer | 403 for the approver and the bearer (the bearer refusal audited), 401 anonymous; nothing issued |
| CA7 | The functions with another tenant's setting, without a setting, with an approver or unknown actor, with another tenant's credential, an unknown target or an unknown action | INVALID_ACTOR, INVALID_TENANT, INVALID_CREDENTIAL, INVALID_TARGET, INVALID_ACTION; no row in either tenant; the other tenant's credential still authenticates |
| CA8 | Direct INSERT, UPDATE and DELETE by the runtime role on principals, credentials and approval_scopes; EXECUTE revoked on one function; INSERT granted on principals | Permission denied; check_runtime passes only with EXECUTE on every function and no table write privilege |
| CA9 | Migration round trip | Functions absent after downgrade and present after upgrade; the owner commands still create tenants and principals while downgraded |
| CA10 | Chromium at phone width | Both pages pass the accessibility sweep; the secret field is read-only and the token is absent from the list afterwards; a declined confirmation keeps the credential, an accepted one revokes it; unassign works without scripts |
Tests live in tests/test_admin_pages.py (the definer functions and the grants), tests/test_console_api_access.py and tests/test_browser_console_directory.py.
People page
The list and the person page for a tenant's humans, with deprovisioning (design, tests/test_people_pages.py).
| ID | Setup/action | Required result |
|---|---|---|
| PP1 | ADMIN opens the list, then the deactivated and the ADMIN filters, then a bad state and an unknown key | Every human principal with counts; the deactivated filter empty; the kind filter hides the approver; 400 for the bad state and the unknown key; the page passes the secret check |
| PP2 | New approver with a blank label, then with a label | 400 with the notice; then a new approver with the trimmed label, principal.created and principal.labelled, landing on the person's page and listed |
| PP3 | The person page for an approver, an ADMIN, an unknown id, an agent id and a malformed id | Every section for the approver; "owner tooling" and no deactivate or issue form for the ADMIN; 404 for the other three |
| PP4 | Set a label; set an 81-character one | The label appears beside the prefix on the credentials and approvers pages and is audited; the long one is refused without being echoed |
| PP5 | Issue a credential from the person page, then revoke it | Shown once under Cache-Control: no-store, authenticates as that person, absent from the page afterwards; revoked and refused, credential.revoked audited |
| PP6 | End all sessions on an approver with two sessions; the same on the acting ADMIN | Both sessions of the approver refused at their next request, the ADMIN's session intact, one human_session.revoked with reason ADMIN per row; the ADMIN's own request is 400 |
| PP7 | Deactivate with a blank reason, then with one; repeat; reactivate | 400; then the credential no longer authenticates and the session is 401 in the same change, principal.deactivated carries the reason and the counts, the person leaves the default list and shows under the deactivated filter; the repeat is 409; reactivation restores active, clears deactivated_at, keeps the scopes and issues nothing |
| PP8 | The functions with an ADMIN target, the actor, an approver actor, a blank reason, an unknown principal, a long label, an active target for reactivation and another tenant's fence; an approver session on every route | INVALID_PRINCIPAL, INVALID_ACTOR, INVALID_REASON, INVALID_LABEL, INVALID_STATE, INVALID_TENANT or INVALID_ACTOR; 403 or 400 for the approver session |
| PP9 | Head, catalog and round trip | 0036_people; the three functions in FUNCTIONS; check_runtime passes; downgrade to 0035_session_label and back |
| PP10 | Chromium at phone width | Both pages pass the audit and the contrast probe in both schemes; End all sessions and Deactivate decline when the dialog is dismissed and land when accepted; reactivation works with the script blocked |
Tests live in tests/test_people_pages.py (the migration and the catalog), tests/test_console_api_people.py, tests/test_browser_console_directory.py and test_route_authority.py.
People page: history
Step 3 of the People page: a person's decisions, sign-ins and administrative events (design, rulings in section 13).
| ID | Setup/action | Required result |
|---|---|---|
| PH1 | An approver approves one request and denies another, signs in, and has every scope unassigned; an ADMIN opens their page | Decisions newest first, the denial then the approval, each with its approval prefix and action, still shown after the scopes went; one sign-in with its device and "active"; no administrative events; the page passes the secret check |
| PH2 | An ADMIN labels the approver, ends their session, changes the mode and signs out their own other devices, then opens their own page | Administrative events newest first in words: the mode change to OBSERVE, one "Ended a session of" with the approver's new label and prefix, the label change and the bundle's activation; no reason text, no entry for their own devices, no decisions; their sign-ins include one signed out |
| PH3 | A person with no history; then twenty-five sign-ins | "No decision yet." and "No sign-in yet."; then exactly twenty rows, the last sign-in first and the first five absent |
| PH4 | An approver labelled with markup is deactivated with a reason containing markup; the ADMIN opens their own page | The deactivation is listed with the label escaped as text; the reason appears nowhere; the map of kinds covers policy, credentials, scopes, principals, bindings, sessions and the tenant and nothing else |
| PH5 | Chromium at phone width: the approver's page with decisions and a sign-in, and the ADMIN's page | Both pass the audit and the contrast probe in both schemes; the decisions, sign-ins and administrative events tables are found by their captions with their rows |
Tests live in tests/test_people_history.py and tests/test_browser_console_directory.py.
Agents page
The list and the agent page for a tenant's AGENT principals, the governed half of the Agents page (design, tests/test_agents_pages.py).
| ID | Setup/action | Required result |
|---|---|---|
| AG1 | ADMIN opens the list; the month filters before and after the agent's first request; the deactivated filter; bad filter values, an unknown key and malformed cursors | Only AGENT principals and the month figure with its definition; the agent under "Made no request" until it makes one and under "Made a request" afterwards; the deactivated filter empty; 400 for each bad value; the page passes the secret check |
| AG2 | Fifty-five labelled agents beside the fixture's unlabelled one | Fifty on the first page in label order, the rest on the next with no overlap and the unlabelled agent last, and no further link |
| AG3 | New agent with a blank label, then with a label | 400 with the notice; then an AGENT principal with the trimmed label, principal.created with kind AGENT and principal.labelled, landing on its page and listed |
| AG4 | The agent page; an unknown, an approver's, an ADMIN's and a malformed id; a label; a well-formed label form posted to an approver's id | Every section; 404 for the four ids; the trimmed label shown and audited; 404 for the approver's id with the approver's label unchanged |
| AG5 | Issue a credential from the agent page; revoke another principal's credential through this agent's route; revoke the agent's own | Shown once under Cache-Control: no-store with a link back to the agent, authenticates as the agent, absent from the page afterwards; 404; revoked, refused and audited |
| AG6 | The agent page before any request; after two executed isolations and one pending request, with one live and one expired run; then with the executions moved past the evidence window | The governed action inferred from the active bundle; then verified with the evidence's expiry, two executed and one pending row in recent activity, three requests this month, the live run only and the link to Activity for the agent; then no verified row |
| AG7 | Three executed and one open isolation by one agent and two executed by another; then the confirmed rows moved twenty minutes back | The principal budget reads 4 of 10 and the tenant total 6 of 20 with the store's own usage agreeing, and the other agent 2 of 10; then 4 of 10 and 1 of 20, again as the store counts |
| AG8 | Requests at the first and last instants of the month and the first instant of the next; another agent's in the previous month | This month counts two for the first agent and nothing for the other, the next month one, the previous month the other agent's one; the list reads 01 and the agent page 2; December rolls into January |
| AG9 | Deactivate with a blank reason, then with one; repeat; reactivate; post deactivation to the ADMIN's and the approver's ids; an approver session on every route | 400; then the issued credential no longer authenticates, principal.deactivated carries the reason, the revoked credentials and no session, and the agent leaves the default list for the deactivated one; 409 on repeat; reactivation restores it with no live credential; 404 for both ids; 403 or 400 for the approver |
| AG10 | Chromium at phone width | Both pages pass the audit and the contrast probe in both schemes; Deactivate declines when the dialog is dismissed and lands when accepted; reactivation works with the script blocked |
Tests live in tests/test_console_api_agents.py, tests/test_browser_console_directory.py and test_route_authority.py.
Agents page: bindings
Step 2 of the Agents page: discovered agents, bindings and Govern this agent (design, tests/test_agents_bindings.py).
| ID | Setup/action | Required result |
|---|---|---|
| AB1 | The two functions with an ADMIN, an approver, an unknown and a deactivated principal, an unknown and a gone agent, an approver as actor, another tenant's fence, each side already bound, a blank reason and an unknown binding; the runtime role updating the link or inserting a binding | INVALID_PRINCIPAL, INVALID_AGENT, INVALID_ACTOR, INVALID_TENANT or INVALID_ACTOR, INVALID_STATE and INVALID_REASON as each case names; a bind sets the link and an unbind clears it with who and why; the pair binds again with the history kept; permission denied for the runtime role |
| AB2 | link-agent with an ADMIN, with an agent principal, again, and with another principal; unlink-agent | PRINCIPAL_UNAVAILABLE; one binding by the acting ADMIN with agent.bound; nothing new; LINK_CONFLICT; the binding withdrawn with the reason "withdrawn with unlink-agent" |
| AB3 | One bound pair and two discovered-only agents under a detection profile; each population, source and state filter; bad filter values | The bound pair as one row under its principal and the others under their agent ids; each filter narrows to its rows; a gone agent leaves the default list and shows under gone; 400 |
| AB4 | A discovered-only page; the id of a bound agent; the bound principal's page; an unknown id | The source's facts, Govern this agent and a bind form offering the unbound agent principals, and no governed section; 303 to the principal; both halves with Unbind; 404 |
| AB5 | Bind from the governed side and from the discovered side; bind a principal already bound; a malformed target | 303 to the principal and agent.bound for each pair; 409 with the notice; 400 |
| AB6 | An executed isolation derived as verified reach; Unbind with a blank reason, then with one; unbind again | The verified row names the binding; 400; then the link cleared, the ExecBound row marked gone at once, agent.unbound with the reason, and the agent its own row again; 409 |
| AB7 | Govern this agent on an agent whose display name is longer than a label | Shown once under Cache-Control: no-store; an AGENT credential; the label is the name cut to 80 characters; the binding and the four events name the new principal; what to do next holds the install and MCP lines and never the secret; the agent's id answers 303; a second attempt issues nothing |
| AB8 | The agent leaves its source between the page load and Govern this agent | 409 with the notice, and no principal, credential, binding or audit event added |
| AB9 | Deactivate a bound principal; open a discovered agent's bind form; an approver session on bind, govern and unbind | The binding stays and the source's facts say the governed principal is deactivated; the principal is not offered; 400 or 403 |
| AB10 | Chromium at phone width | The discovered and bound pages pass the audit and the contrast probe in both schemes; Govern this agent lands on the show-once page with what to do next; Unbind declines when the dialog is dismissed and lands when accepted |
Tests live in tests/test_agents_bindings.py, tests/test_console_api_agents.py, tests/test_browser_console_directory.py and test_route_authority.py.
Agents page: tenant line
Step 3 of the Agents page: the tenant line per source (design, tests/test_agents_tenant_line.py).
| ID | Setup/action | Required result |
|---|---|---|
| AT1 | A tenant with no monitoring connection | The line says no connection lists agents and links to Monitoring's connections page, with no zeros; the governed side counts the active agent principal and no binding; the page passes the secret check |
| AT2 | Three agents collected through the fake Graph API under a detection profile; then one bound | One source row listing three; the listed, consequential and not-established totals equal Monitoring's enumerated, with-reach and not-established figures; then "1 of 3" bound on the row and one bound principal on the governed side |
| AT3 | One agent leaves its source; then the only agent with grants has its claims pass their validity | Listed and not established fall by one and still equal Monitoring's; then consequential reach is none, as Monitoring counts it, and the row reads "0 of 2" |
| AT4 | Chromium at phone width with a source | The list with its tenant line passes the audit and the contrast probe in both schemes |
Tests live in tests/test_agents_tenant_line.py and tests/test_browser_console_directory.py.
Tines discovery and collection
| ID | Setup/action | Required result |
|---|---|---|
| TD1 | Register a connection and probe with every capability listed by the fake API | Five capabilities classified with probe times; health COLLECTING; no inventory and no observation written by a probe |
| TD2 | Inventory sync across three pages each of stories, actions and credentials | Allowlisted metadata only; options, credential values, descriptions, emails and addresses absent from every table, snapshot and page; counts match the fixture; an unrecognized action type is outbound = null, never false |
| TD3 | Audit pull over four pages with the page budget at two | Two pages accepted with their evaluations pending; the cursor on the last accepted record; BACKLOG; the next run continues from the overlap and accepts the remainder without duplicates |
| TD4 | The same records served again after the cursor is rewound; then one record reused with different content | No duplicate observations; the reused identifier records source-event-integrity.v1, marks the conflict and leaves the first content in place |
| TD5 | A failure injected between writing a page and advancing the cursor | Nothing written and the cursor untouched; the next run stores each record exactly once |
| TD6 | 429 with Retry-After, 401 and 403 on the audit endpoint; 404 on credentials; three consecutive rate-limited runs then a 403 | RATE_LIMITED with the retry value, AUTH_LOST, unsupported for credentials with the others unaffected; exactly one audit request per run; one collection-health.v1 finding per episode |
| TD6b | A tenant whose plan excludes the audit feature (422 on that endpoint) | The audit and AI capabilities classify unsupported, not unknown; stories and actions stay available; inventory still collects and the run completes; the vendor's message body is never retained |
| TD7 | A cursor older than the retention floor | RETENTION_GAP with a health finding; the connection snapshot shows it |
| TD8 | Configuration changes on a registered story, on an unregistered story, a credential change, a tenant change and an unlisted operation name | MATCH with one monitored-path-change.v1 finding each for the registered story, its action and the credential; NO_MATCH for the unregistered story and the tenant change; INSUFFICIENT_EVIDENCE for the unlisted name |
| TD9 | A discovered story with outbound actions and no source; a source registered; the source revoked | Gap finding, then none while it reports, then a new episode after revocation; the other stories classify as instrumented, no-outbound, disabled and coverage-unknown |
| TD10 | Enhanced AI records with a mapped and an unmapped tool name | The mapped tool run matches under the profile version; the unmapped one stays INSUFFICIENT_EVIDENCE; the model run keeps the model identifier and token counts and no prompt or completion text |
| TD11 | Two tenants collecting from fixtures with the same identifiers; then a response claiming another platform tenant | No cross-tenant rows; every foreign record quarantined with one health finding and nothing stored |
| TD12 | Migration round trip, check_runtime, and the pages in Chromium at phone width | Both tables forced-RLS with no runtime DELETE or TRUNCATE; downgrade refuses while collected evidence is retained; the Inventory and Connections pages pass the accessibility sweep, show every coverage class and the collected evidence label, and carry no fixture secret |
Tests live in tests/test_tines_collection.py, tests/test_console_api_monitoring.py and tests/test_browser_console_monitor.py.
Scout, first source: Entra Agent ID
| ID | Setup/action | Required result |
|---|---|---|
| SR1 | Register an entra_agent_id connection and probe every capability | Five capabilities classified with the moment each was established; a Tines connection beside it is unaffected; a probe writes no agent and no grant |
| SR2 | Enumerate three pages of agent identities and classic service principals | Allowlisted metadata only; no mail address, human name, credential value, hint or token in any table, snapshot, log line or page; an agent listed in both collections is one agent and keeps the directory's own class |
| SR3 | An agent identity carrying properties the vendor has not published | Every undocumented property dropped and counted; nothing raw stored or logged; agentIdentity is an open type and the parser takes fields rather than removing them |
| SR4 | An agent holding a directory role the catalog maps to a consequential action | One inferred reach row on the role path with mode unknown; one ungoverned-reach.v1 finding naming the agent, whose sponsors are what alert routing reads |
| SR5 | Register an operation mapping, an executor and a principal link for that action | A second reach row on the ExecBound path, native; the grant row unchanged and still live; the finding stays open |
| SR6 | The grant is gone at the next collection | The grant and its reach rows marked gone and kept, never deleted; the episode closes; the finding remains for a person to read |
| SR7 | An ADMIN records an acceptance instead | The episode closes with the acceptance; the reach row stays live and renders as accepted with its reason; an expired acceptance opens a new episode |
| SR8 | A blueprint declaring allAllowed roles for a resource application | Inherited grant rows whose path is the blueprint, read once however many agents share it; the declaration resolves to no action, so it is counted as unexplained rather than inferred |
| SR9 | A classic service principal holding a blocked-for-agents permission and an agent-shaped tag | Class service_principal; the tag recorded and shown as evidence and never a promotion; owners read where sponsors do not apply |
| SR10 | An agent with no sponsor, and one whose sponsor read is refused | sponsorless-agent.v1 for the first; INSUFFICIENT_EVIDENCE counted separately for the second, and the page never shows a refused read as nobody |
| SR11 | An observation of the agent acting, then a settled execution under its linked principal | observed then verified, on their own paths and modes, beside the inferred rows; deriving twice changes nothing; evidence older than its currency is not revived; an unlinked agent gets no verified row however many executions settle |
| SR12 | Two tenants reading directories that serve the same object identifiers; a directory that disagrees with the registration | No cross-tenant row; the mismatched directory refused with DIRECTORY_MISMATCH and nothing stored; a directory that will not answer leaves it unverified rather than assumed |
| SR13 | Token failure, 401, 403 on one path, 429 with Retry-After, an oversized body, and a continuation that leaves the pinned origin or the requested path | AUTH_LOST before any Graph request; forbidden for one capability with the others unaffected; the retry value recorded with no retry in the run; the body refused before parsing; four shapes of redirected continuation refused |
| SR14 | Migration round trip, check_runtime, and both pages in Chromium at desktop and phone width | Four tables forced-RLS with no runtime DELETE; both downgrades refuse while agent evidence or findings are retained; the pages pass the accessibility sweep, state the denominator in words, name the two coverage holes, render a lapsed claim as unknown with the moment it lapsed, escape a hostile display name and make no external request |
Tests live in tests/test_scout_schema.py, test_scout_grants.py, test_scout_collection.py, test_scout_run.py, test_scout_reach.py, test_scout_derived.py, test_scout_rules.py, tests/test_console_api_monitoring.py and tests/test_browser_console_monitor.py.
Rulesets
Implemented for #273 by the design in docs/superpowers/specs/2026-09-17-rulesets-design.md and the plan in docs/superpowers/plans/2026-09-18-rulesets.md; tests/test_rulesets.py holds RS1 to RS4, tests/test_pack_rulesets.py holds RS5 and RS6 and tests/test_policy_pages.py holds RS7 to RS9.
| ID | Setup/action | Required result |
|---|---|---|
| RS1 | One ruleset compiled twice, and once with its rules in another order | Identical bytes for all five components; validated by the real Cedar engine, never a stub |
| RS2 | A condition naming an unknown fact or argument, a value outside the declared domain, an ordered operator on text, a ninth condition, a quorum on a rule that is not REVIEW, an action no mapping serves | Refused with the field's location; the model refuses what it cannot mean before the compiler is reached |
| RS3 | A bundle carrying a ruleset, with each of the five components edited on its own while staying well-formed | Validation refuses RULESET_MISMATCH naming the component; a bundle without a ruleset serialises with no ruleset key and validates as before |
| RS4 | A REVIEW rule with a quorum beside one without | The quorum names the positional obligation the engine actually raises for that rule, asked of the engine rather than restated |
| RS5 | The endpoint, identity and SOC packs as rulesets, every combination of classification, environment, incident status and severity, criticality and argument value | Effect, reasons and whether review is owed agree with the hand-written pack on every input; every reachable effect is reached |
| RS6 | Each pack's ruleset builder, recompiled from its own ruleset | Equal to the bundle it built, and the compiler's raw limits already in the stored order; the hand-written builders stay the default fixtures derive variants from |
| RS7 | An ADMIN writes three rules (allow a workstation with an open critical incident, deny a domain controller, review production with a quorum of two), a review of open incidents and a budget, without scripts | Each rule reads as one sentence and the compiled Cedar is shown read only; the draft is created through the compiler and the store and its bundle page shows the rules, the note as text and the Cedar; validation succeeds; the preview names the rule behind each changed decision and marks a review candidate unresolved; activation succeeds; policy.ruleset_compiled is audited with counts |
| RS8 | A condition naming an unknown fact, a text value where a number is declared, and a note containing markup | Refused at the form with the field's location; the note renders as text and appears in no compiled component |
| RS9 | An approver requests the rules editor | 403 |
| RS10 | Every fixture host the seeder writes, read by project() over its facts; and every input of every pack (tests/test_pack_rulesets.py) | The reading equals the pack's stated effect on every host and names a rule for every reading that is not the default denial; over the whole input space the reading equals the kernel's policy decision, a reading owing review names a review rule, and no reading is made with a fact missing; a host with no open incident yields the names of the facts it lacked |
| RS11 | A claimed sandbox's welcome page; GET /policy/rules?start=active and ?start=candidate with a ruleset bundle active, with a hand-written bundle, and with an unknown, foreign or repeated query key; GET /welcome/candidate-bundle.json from a ruleset bundle, pasted and validated | The effect column names the rule behind each host and is labelled as the rules' reading; the editor opens on the active rules and budgets, the candidate caps every autonomous principal budget at three and the draft it creates validates; the hand-written case says so and starts empty; the bad queries are 400; the download is a ruleset bundle whose budgets and limits are both capped, and validation accepts it |
| RS12 | The agent page with a ruleset bundle active, and with a hand-written one | A Rules column names the rules about each governed action in the compiler's order, labelled as the reading and not the decision; the hand-written bundle adds no column and no claim |
Approval deadline
Implemented for #213 by the design in docs/superpowers/specs/2026-09-17-approval-deadline-design.md; tests/test_approval_deadline.py holds AD1 to AD6, AD8, AD10 and AD11, tests/test_client_package.py and tests/test_examples.py hold AD7.
| ID | Setup/action | Required result |
|---|---|---|
| AD1 | A fresh tenant | approval_deadline_seconds is 120 and an approval expires at min(valid_until, created + 120 s); every earlier 120-second test passes unchanged |
| AD2 | Deadline 900, facts valid for an hour; decision at +500 s, retry at +800 s | The approval carries created + 900 s; the retry executes exactly once; a further retry returns the same execution |
| AD3 | Deadline off | expires_at equals the plan's valid_until; a decision after it is STALE_AUTHORIZATION with approval_state: EXPIRED and no mutation; a decision before it executes |
| AD4 | --set 119, 3601, -1, 0, a string, a float or a boolean; the current value; a HUMAN_APPROVER credential; a blank reason | INVALID_DEADLINE, INVALID_STATE, INVALID_IDENTITY, INVALID_REASON; nothing written; each success is audited as tenant.approval_deadline_changed with previous and new values, the reason, principal and credential ids |
| AD5 | Deadline changed while an approval is pending | The pending approval keeps its expiry; the next approval uses the new value |
| AD6 | Export after a decision made at +600 s under 900 | Replay verifies complete with no issues and reproduces every recorded effect, the review decision included |
| AD7 | Deadline 900; the sync client, the async client and the MCP example wait through an expired continuation | Each resends the same intent, keeps polling, and returns EXECUTED after the later decision with one mutation |
| AD8 | Console under 900 and under off | The approval page and listing say (approval deadline) or (context validity); a decision submitted four minutes after the page rendered succeeds and executes once |
| AD10 | Sessions | A load at +8 minutes extends the session to +23 and marks it renewed, at +5 it does not; renewal never passes the absolute cap or the credential's expiry; a revoked session is not renewed; the console re-issues both cookies with the new age and fifteen quiet minutes still end the session |
| AD11 | Migration and runtime catalog | Head 0027_approval_deadline; the column is updatable and the sessions expiry is not; renew_human_session is the only new definer function; downgrade to 0026 and back returns every tenant to 120 |
Approval expiry
The kernel half of #426: an approval past its deadline that nothing touches again stays PENDING, and its execution PENDING_APPROVAL, indefinitely. Implemented by section 13 of docs/superpowers/specs/2026-09-13-enforcement-kernel-design.md; tests/test_approval_lapse.py holds AX1 to AX10, and console/src/routes/-home.test.tsx and tests/test_browser_console.py hold the console clauses of AX9.
| ID | Setup/action | Required result |
|---|---|---|
| AX1 | A pending approval thirty-six hours past a 600-second deadline | No execution_impact row, no resource_claims row, both demo rules projecting zero, and no approval.transition event: the row reserves nothing and claims nothing, so nothing is lost to capacity and the defect is a lifecycle that never terminates |
| AX2 | One recovery run over it | expired is 1; the approval is EXPIRED from PENDING and the execution STALE_AUTHORIZATION from PENDING_APPROVAL, bound to the same approval id and plan hash; no impact row, no claim and no provider mutation appear |
| AX3 | Two further runs | expired is 0 each time, the chain is byte-identical to after the first run, the states are unchanged and the selection is empty |
| AX4 | An approval inside its deadline, then past it but inside the run's grace | Selected by neither run; the approval stays PENDING and the execution PENDING_APPROVAL |
| AX5 | An approved-and-consumed execution and a denied one, both past the deadline | expired is 0; EXECUTED/CONSUMED and DENIED/DENIED are untouched and the single mutation stands |
| AX6 | An approval a human approved and the agent never came back to consume | Swept on the same terms: EXPIRED from APPROVED, execution STALE_AUTHORIZATION, no mutation |
| AX7 | A decision, then an agent retry, in the window after the deadline and before any run | Both answer STALE_AUTHORIZATION with approval_state: EXPIRED and no mutation; a second decision is INVALID_STATE |
| AX8 | A decision and a recovery run racing on two threads past the deadline | Exactly one approval.transition to EXPIRED and one execution.transition to STALE_AUTHORIZATION; the loser adds nothing and holds nothing |
| AX8b | The same race inside the deadline | The sweep always refuses under the fence and the decision always lands: APPROVED, execution PENDING_APPROVAL, no EXPIRED transition |
| AX9 | The pending view, the lapsed view and the overview count, before and after a run | Before: out of pending, in lapsed, overview pending 1. After: still out of pending, still in lapsed, overview pending 0 and one STALE_AUTHORIZATION; an approval that ran out of time never disappears from the one view that says so |
| AX10 | Export and replay after a run | The verified export replays complete with no issues |
Sandbox onboarding
Slots, invitations, the claim and the refresher (design, tests/test_onboarding.py). SO8 onward join with the seeder, the public page and the refresh job.
| ID | Scenario | Expected |
|---|---|---|
| SO1 | Migration and runtime catalog | Head 0029_claim_clock; both tables fenced and forced with runtime SELECT on slots only and no write on either; both definer functions listed; a tenant name in another case is refused by the owner connection; an accidental INSERT or UPDATE grant on either table fails check_runtime closed; downgrade through 0028 to 0027 and back round-trips |
| SO2 | A live invitation claimed with a name | One transaction: the tenant is renamed (whitespace collapsed) and activated, the ADMIN principal and its 30-day credential exist and authenticate, the invitation is redeemed and the slot claimed at the same instant, tenant.claimed is the tenant's first audit event with the new ADMIN as actor and verify_chain passes; the token appears in no repr, payload or log |
| SO3 | Two claims of one code at once; a claim whose name collides after the tenant was renamed inside the function | Exactly one claim succeeds and the other is INVALID_INVITATION; the collision is NAME_TAKEN and leaves the tenant inactive with its placeholder name, no principal, no credential, the invitation unredeemed and the slot unclaimed, so the right name then claims it |
| SO4 | Wrong secret five times; expired; already redeemed; an active tenant with an unclaimed slot; a code naming an ordinary tenant; malformed codes; blank or over-long names; a name taken in another case | failure_count 1 to 4 then revoked on the fifth, after which the right secret is refused; INVALID_INVITATION for the dead and foreign codes without touching the database for malformed ones; INVALID_SLOT for the active tenant; INVALID_NAME; NAME_TAKEN without counting a failure |
| SO5 | After a claim, reads under another tenant's fence and direct runtime writes | The slot, the tenant and its principal are invisible under the other fence; the runtime role cannot insert into or update tenants, principals, credentials, resources, incidents, onboarding_slots or onboarding_invitations, nor read the invitations |
| SO6 | Refresh of a claimed sandbox after an executed isolation, with observation times an hour old | Not a sandbox and an unclaimed slot refuse INVALID_SLOT, an approver refuses INVALID_IDENTITY; the refresh advances observed_at on resources, row-backed facts and incidents, keeps every value, sets the isolated target's version to the resulting_version of the provider's signed receipt and leaves the untouched target at its seeded version; the next plan on the host freezes the reconciled version; a second refresh only moves time |
| SO6b | Refresh with a checkpoint settlement (ATTESTED) and a FAILED_NOT_EXECUTED row against the target | Neither moves the version |
| SO7 | The owner commands over a slots file and an explicit slot | invite-create takes the lowest free slot whatever the file's order, writes the code once to a new owner-only file and prints identifiers only; a slot with a live invitation refuses INVITATION_LIVE; an expired invitation is revoked when the next one is minted; an empty pool refuses INVALID_SLOT; invite-list states each invitation with no code; a revoked code cannot claim and the slot can be invited again; the reissued code claims end to end; a claimed slot, an existing output path, a blank note, a lifetime outside 1 to 720 hours, an ordinary tenant and a broken slots file are refused with nothing written |
| SO8 | sandbox-seed of two slots into the test database | Each slot is an inactive ENFORCE tenant with two accounts, four sources, six mappings, 32 targets with identities, incidents and 58 aliases, the approver's 52 scopes, the jobs ADMIN with one credential and the SOC bundle active at an hour's validity, audited sandbox.seeded then the bundle lifecycle; the jobs credential is refused while the slot waits; boot.run provisions both mock stores from the resource fragments and materializes the account fragments; the connectors fragment parses as gateway configuration; no token appears outside jobs-tokens.json and gateway-connectors.json; a repeated seed, a bad count, seed or origin and an existing directory write nothing; a claimed slot's refresh covers all 32 targets and its jobs credential then authenticates |
| SO9 | sandbox-adopt of the execution world's tenant | The slot row is stamped seeded and claimed at the same instant and sandbox.adopted is the last event under the acting ADMIN; a non-ADMIN actor, an inactive tenant, a second adoption, a bad seed and slot 0 are refused; the adopted tenant's refresh works |
| SO10 | A seeded slot claimed through /onboard | The page holds the new administrator credential, the origin and the name, sets no session cookie and deletes the page cookie; tenant.claimed follows the seeder's events; the credential signs in as ADMIN, opens the credentials page and a welcome page naming the hosts, their effects, what is still to be issued and the slot; the spent code is refused afterwards |
| SO11 | Refusals on /onboard | A wrong secret shows the one sentence and counts one failure; a forged CSRF field, a bearer header, a wrong or missing origin, an extra field, a malformed code and a query string are refused before the database and count nothing; a taken name is 409 with its own sentence and leaves the slot unclaimed; four more wrong secrets revoke the invitation and the right code is then refused; the tenant stays inactive |
| SO11b | A runtime with two sign-in attempts per window | The third attempt on one selector is 429 with Retry-After, another invitation is unaffected, and the database counted only the two attempts that reached it |
| SO12 | /welcome | An approver session is refused 403; the operator-provisioned tenant's ADMIN gets the short form with the administration links and the menu carries the link |
| SO13 | sandbox-refresh --tenants-file and recover --tenants-file over a map naming a claimed slot with hour-old facts, an unclaimed slot, an agent credential and a malformed token | The claimed slot is re-attested (32 targets, observation times fresh) and swept (zero examined); the other three are skipped: INVALID_IDENTITY; both runs exit 0; the single-tenant forms still work, the two forms are exclusive, and a broken map is refused before any tenant runs |
| SO15 | The sandbox describes itself | A claimed slot's ADMIN downloads /welcome/sandbox.json as a no-store attachment whose targets equal the seeded slot file's, with no credential and no secret in it; the approver's session is refused on both downloads |
| SO16 | The candidate bundle | /welcome/candidate-bundle.json derives deterministically from the active bundle (principal autonomous budgets capped at three, tenant total unchanged, a uuid5 bundle id), parses as a bundle, and taken as a draft, validated and previewed over four autonomous isolations it reports a changed decision; a tenant without a slot gets 404 on the description |
| SO17 | The welcome page as a progress page (tests/test_demo_driver.py) | The operator tenant adopted as a slot shows the claim done and every demo step not yet with the 600-second deadline stated; after the decide beat the first governed request, the denial, the refused claim and the console decision are done with times and the time to first governed request is printed; after budget the shared limit row is done; the page names no credential. Every recipe equals its fixture (tests/test_recipes.py) |
| SO18 | The privilege boundary of migration 0042_console_invitations | The runtime role reads every column of onboarding_invitations the console's list needs and is refused token_hash; onboarding_invitation_revoke ends a live invitation, refuses one already revoked, one redeemed and one that is not there, with one code and no diagnostic, and the freed slot takes the next invitation |
| SO19 | Minting from the console, over the runtime connection | An approver and an agent identity are refused before a slot is touched; a showcase batch offers nothing; a mint takes the lowest free slot, returns a code that claims it, and records sandbox.invited in the operator tenant with the identifiers, the expiry and whether a message went out and with no code, address or note, the chain still verifying and the slot tenant's own chain empty; the note and lifetime bounds are the store's, not the form's; a revoke frees the slot and records its own event; an invitation outside the configured pool is refused however its id was typed; a send the relay did not accept leaves a revoked invitation, a free slot and no sandbox.invited; and over a real relay the emailed code is the only one that works, a resend replaces it without taking a second slot, and the old one is then refused |
| SO20 | The Invitations area of the console API (tests/test_console_api_invitations.py) | Every route refuses a bearer 403 and audits it, and answers 401 in JSON with no session; an approver is refused 403 everywhere; a tenant that is not the configured operator, and a deployment with no signups block, answer 404 on every route and report invitations: false on the session; an administrator given a space membership is refused 403 at the next request; the code is in the one minting response and in no listing, header or audit event; the body's bounds are refused without echoing a value; an empty pool is a conflict; an uncertain send leaves a revoked invitation and a free slot; and the digest is neither rendered nor readable |
| SO14 | boot.py with EXECBOUND_JOBS_TOKENS_JSON | The map is written owner-only to jobs.json and scrubbed from the environment; a non-object, an empty or over-64 map, a non-canonical or malformed tenant id and a malformed token are refused by variable name with nothing written; the blueprint carries seven start commands, the recover and sandbox-refresh jobs read the map and the recover job no longer takes the pilot's tenant and token variables |
Open sandbox signups
The public /signup page, slot allocation, the waitlist, the abuse controls and the sandbox lifecycle (design, tests/test_signups.py). Real PostgreSQL and a real local relay throughout, and the page is exercised as a browser posts it rather than by calling the module, because /signup is the one unauthenticated write path a stranger reaches.
| ID | Scenario | Expected |
|---|---|---|
| SU1 | Migration and runtime catalog | Head 0039_signups; both new tables fenced and forced with runtime SELECT on each, no INSERT and no DELETE, UPDATE on exactly the outbox's seven delivery columns and none on the requests; the seven definer functions listed; check_runtime green; downgrade to 0038 and back round-trips |
| SU1b | The lifecycle of a sandbox claimed before the migration | Every slot already claimed is stamped converted by the upgrade, so the adopted pilot and the showcase slot acquire no expiry; a slot claimed after it gets expires_at at the claim plus fourteen days from the trigger |
| SU2 | A submission with a free slot | The address is stored bare and lower-cased with its domain and consent version, the request is INVITED against the lowest free slot, one invitation exists there with a hash nobody holds the preimage of, one invitation message is queued naming the slot, the invitation and the request, and the page carries no secret |
| SU3 | The send and the claim | The drain rekeys the invitation, so the stored hash is not the one allocation wrote; the mailed code claims the sandbox on /onboard unchanged; the sweep follows the claim and the address is retained ninety days from it |
| SU3b | A relay that refuses the recipient | No code is delivered anywhere, the row stays PENDING with one attempt and MAIL_REFUSED, and nothing redeemable exists |
| SU3c | A relay whose daily limit is reached | The message defers for an hour with RATE_LIMITED and spends no attempt |
| SU4 | Two submissions racing for the last free slot, two threads | Exactly one invitation and one INVITED request; the loser is queued rather than refused, and holds a waitlist message |
| SU5 | A disposable domain, a filled honeypot, a missing consent box, a malformed address and a success | One page, one status and one body for all five; each refusal is a row with its code and a thirty-day retention; a malformed address records nothing |
| SU5b | The same address twice, and a refused address again | The second request is ADDRESS_COOLDOWN; a refusal does not start the cooldown, so an address refused once is admitted next time |
| SU5c | A domain past its cap, and a free-mail domain | DOMAIN_CAP after the configured number of live sandboxes; a free-mail domain is exempt from that cap and refused FREE_MAIL_DAILY_CAP at its own lower daily one |
| SU5d | More requests than the hourly cap | GLOBAL_HOURLY_CAP, with refused rows counting toward the caps so the guard bounds a flood rather than only the successes |
| SU6 | Two requests with no free slot, then a batch seeded | Both queue with a waitlist message; the sweep allocates to whoever asked first and leaves the second waiting |
| SU6b | A pool holding only the showcase seed | The request queues and the showcase slot is never invited |
| SU6c | A sweep run three times over a request that is still waiting | One waitlist message, no error, and the request still queued |
| SU6d | An invitation that lapses unredeemed, and a request nobody allocated within the cooldown | Both become EXPIRED with a retention date, so neither a domain cap nor an address outlives the request that made it; the domain has room again, and the address cooldown is deliberately unaffected, because a code that was sent and not used was still a request |
| SU6e | A claim that lands after the invitation's own lifetime | The slot's own row is the fact: the request is followed to CLAIMED rather than lapsed |
| SU7 | A claimed sandbox at day eleven | One lifecycle message queued exactly once however many times the cron runs, notice_at and notice_sent_at both stamped, and the message carries no code |
| SU7b | The same sandbox past its end | The tenant is inactive, its slot deactivated, its own administrator credential refused, tenant.expired appended to its own verified chain under its own ADMIN with the reason, the request EXPIRED, and a second sweep writing nothing |
| SU7c | Extension and conversion | sandbox-extend revives the lapsed sandbox and moves the end, its credential works again, more than thirty days and a non-ADMIN actor are refused, and sandbox-convert clears the end so no later sweep expires it; both events land in the sandbox's own chain |
| SU7d | The refresher against a deactivated sandbox | sandbox-refresh refuses it exactly as it refuses any inactive tenant |
| SU8 | Retention | Addresses past their date are nulled, the domain, the state and every timestamp stay, and the report still counts the rows |
| SU9 | A read of the operator tenant under another tenant's fence, through the runtime role | Nothing is visible; the operator's own fence sees the rows, so the zero is isolation and not absence |
| SU9b | A direct runtime write to the requests table | Refused |
| SU9c | A runtime write to the outbox | The lease columns move; the invitation a message names does not |
| SU9d | onboarding_invite against a claimed slot, a tenant that is not a slot, and a slot holding a live invitation | INVALID_SLOT, INVALID_SLOT, INVITATION_LIVE |
| SU9e | onboarding_invitation_rekey against a redeemed invitation | INVALID_INVITATION |
| SU10 | A signup carried through to a claim, with the product's own log stream captured | The address appears in no structured record, no audit event and no rendered page; the domain does; signups --list is the one place it is rendered |
| SU11 | The vendored disposable snapshot | Sorted, lower case, unique, disjoint from the free-mail constant, matching a subdomain of a listed service, and nothing in the module fetches anything |
| SU12 | review and closed modes | Review records the request and allocates nothing; closed shows the paused page, answers a submission with that same page and records no address at all |
| SU13 | The owner list and the funnel report | Both states listed, an unknown state refused, the report's weekly counts, waitlist depth and pool figures correct, and no address anywhere in it |
| SU14 | A signups block with no mail block | Refused at start; the same file with the relay loads and carries the mode |
| SU14b | Malformed addresses | Refused before the database; a padded, mixed-case address normalizes to one bare lower-cased address and its domain |
| SU14c | A forged CSRF field, a wrong origin and a query string on /signup | 403, 403 and 400 |
Arcade logic extension
Written first, failing on the unwired code, on real PostgreSQL, with a fixture Arcade caller and the protected mocks standing in for the vendor. The guide is Arcade logic extension. Rows live in tests/test_arcade.py (AL1 to AL25 and AL29 to AL34), tests/test_arcade_schema.py (AL26), tests/test_arcade_workflow.py (AL27) and tests/test_browser_surfaces.py (AL28 and AL30's page clause), with shared fixtures in tests/arcade_fixtures.py; those four prefixes are group AL in ACCEPTANCE_PREFIXES. Supporting evidence lives in tests/test_checkpoint.py, tests/test_replay_scenarios.py, tests/test_replay_verify.py, tests/test_recovery_run.py, tests/test_route_authority.py and tests/test_surface_configuration.py.
| ID | Setup/action | Required result |
|---|---|---|
| AL1 | A governed tool's pre hook on a mapping with external_executor, in a tenant with no connector for the account and an unrevoked executor key | {"code": "OK"} and nothing else; one DISPATCH_COMMITTED execution, one UNRESOLVED impact row, one canonical claim, one correlation row holding the Arcade id and the secret; no dispatch ticket and no provider call |
| AL2 | The same pre hook delivered three times under one Arcade execution id, and once more after the frozen plan expires | One execution, one reservation, one claim, one correlation row and one secret; the earlier repeats answer OK from the re-grant and charge nothing; the post-expiry repeat is CHECK_FAILED with the reservation and claim still standing |
| AL3 | Two Arcade execution ids for one intent, concurrently and then sequentially while the first is granted and unsettled; then the same intent again after the first settles | The second is CHECK_FAILED and ExecBound holds one execution, one reservation and one claim; the post-settlement repeat is a fresh decision that resolves, evaluates and counts again, and is CHECK_FAILED on RESOURCE_BUSY until the run releases the claim |
| AL4 | A second Arcade execution id for an intent whose first pre hook was abandoned at the extension's deadline while admission committed, and again for one whose correlation row was written without a secret | Both are CHECK_FAILED because neither row holds the grant's secret; ExecBound holds one execution, one reservation and one claim; neither is answered OK from a re-grant, and the abandoned grant is visible in the uncertain queue |
| AL5 | A denial, a tenant emergency stop, a ninth outstanding grant, a claimed target and a tenant whose key ring cannot attest | Each CHECK_FAILED carrying only a stable code and the execution id where one exists; no input value, vendor string, rule id or secret in error_message; nothing reserved by any of them |
| AL6 | The runtime capacity bound full and the surface rate limiter tripped; separately, a shared hard ceiling reached | The first two answer RATE_LIMIT_EXCEEDED with no execution row; the ceiling answers CHECK_FAILED, and no rule identifier reaches the caller |
| AL7 | The application's own refusals reaching the boundary on each route: the capacity limiter exhausted, and a handler-level 503, on POST /pre and then on POST /post | On the pre path both are 200 with a body of exactly {"code": ...} — RATE_LIMIT_EXCEEDED for the 429 and CHECK_FAILED for the 503 — and neither carries detail; on the post path both are passed through with their original status and body; a 401 is passed through on both |
| AL8 | A tool outside the map; a governed tool at an undeclared version; a tool of an ungoverned toolkit; a tool whose reported behavior contradicts the map's expect | Denied, denied, OK, denied; nothing reserved in any case. The ungoverned pass is asserted against the tenant's chain: the audit head's sequence and hash are read before and after and are unchanged, and no executions row exists for it |
| AL9 | inputs missing the declared target key, carrying it as a non-string, and carrying an undeclared key; then, against a declaration naming an incident key, inputs missing it, carrying an explicit null, carrying a string no UUID reading admits, and carrying a number, and a hook that omits it while the reported metadata names one | Each refused before any kernel call; nothing resolved, reserved or claimed, and the refusal names no key value. The incident refusals answer INVALID_INCIDENT rather than the kernel's opaque code, the tenant's audit head is unmoved across all of them, and the reported id fills nothing |
| AL10 | inputs carrying a second target-shaped key beside the declared one, and an identity tool targeted by hostname | The first is OK on the declared key alone with the other ignored for selection and refused as undeclared where it is undeclared; the second reaches the kernel and the kernel refuses it, which is where that refusal belongs |
| AL11 | An approval-requiring tool: the first hook, a human decision, a retry under a new Arcade id inside the deadline; and a second walk where the deadline passes first | The first is CHECK_FAILED carrying the approvals page and the execution id; the retry adopts the same execution, is answered the execution's first grant with its own secret, consumes one approval exactly once and answers OK; the expired walk reaches STALE_AUTHORIZATION and its next attempt is a new decision and a new approval |
| AL12 | Post hooks carrying success: true, and success: true beside an execution_error | Both settle EXECUTED at tier ASSERTED with impact CONFIRMED, the claim still held until the run after the act window; the self-contradiction is retained on the correlation row, appends no audit event and changes no settlement; the settled rows' correlation secrets are null; both answer OK |
| AL13 | A post hook carrying success: false with and without execution_code, against a DISPATCH_COMMITTED row and against one already INDETERMINATE | Both settle FAILED_EFFECT_UNKNOWN at tier ASSERTED with impact CONFIRMED and confirmed_at stamped, the claim held; outstanding_grants falls by one; the adoption query returns nothing for that intent; the transition payload carries the whole AssertedOutcome with reason REPORTED_FAILURE and tier: ASSERTED beside it; the platform's codes are retained as inert text; both answer OK |
| AL14 | A post hook with no success at all; one whose body exceeds post_bytes; one whose body will not parse | All three answer OK, settle nothing and leave the row where they found it with impact charged and the claim held; the oversized one is recorded as an unreadable report and no part of output is retained or logged; none of the three is a 4xx |
| AL15 | A post hook with content-type: application/json; charset=utf-8, one with application/json, one with Application/JSON, one with two content-type headers and one with content-encoding | The first three are accepted and settle identically; the last two are refused, exactly as the monitoring intake refuses them |
| AL16 | A post hook delivered twice; a second UNKNOWN against an INDETERMINATE row; a post hook arriving before its own pre hook; a post hook whose correlation row is a denial | The first two append and retain nothing and answer OK; the early arrival is the orphan case, recorded bounded and answered OK, and its later pre hook still decides normally; the denial case appends exactly one execution.control_point_contradiction to the tenant's chain, a second delivery of it appends none, and both answer OK |
| AL17 | A post hook carrying inputs that differ from the frozen ones, and one carrying none | The difference appends one execution.control_point_contradiction and is retained as inert text; the absent case appends nothing and claims nothing; neither changes the settlement; a tenant holding both kinds still exports and replays with no UNKNOWN_EVENT_SEMANTICS |
| AL18 | A correlation row's report secret read out of the table by a reader with no credential, then presented on the outcome route as EXECUTED; then a recovery run after the act window | The row settles and the run releases the canonical claim, so the theft does free the target — asserted as the design's stated bound, not as a refusal; the impact stays CONFIRMED and no scope is refunded; the genuine post hook afterwards is answered with the settled view and appends nothing |
| AL19 | A governed pre hook and an ordinary agent dispatch against one ceiling with a total and an autonomous rule; and a group ceiling reached across two Arcade projects of one tenant | Both paths charge the same counters in every scope and the ceiling is reached once across them; an approved candidate adds zero autonomous units on both; the project past the hard ceiling is CHECK_FAILED |
| AL20 | A grant that never receives a post hook, past its report window, then a recovery run, then the console settlement with an ADMIN-signed record; and eight silent uncertain rows followed by a ninth pre hook, followed by one reported failure | Swept to INDETERMINATE with impact charged and the claim held and listed in the uncertain queue; only the signed settlement releases them; the ninth pre hook is CHECK_FAILED with TOO_MANY_GRANTS; the reported failure settles one row terminally and the next pre hook is then answered OK with a grant, which is what says the cap drains on settlement |
| AL21 | The extension's deadline expiring while admission is still in flight | The hook answers CHECK_FAILED within the configured budget; whatever admission commits is retained, visible in the uncertain queue and never refunded; no second request is sent |
| AL22 | A tenant in OBSERVE through both hooks | OK with an OBSERVED row and no reservation, claim or grant; the post hook is recorded and settles nothing and answers OK |
| AL23 | An ADMIN and an approver bearer; unknown, expired and revoked bearers; another tenant's bearer; a credential expiring before the report window | The ADMIN and approver bearers are refused as non-AGENT and audited; the unknown, expired and revoked bearers are refused as unauthenticated and counted; no hook decision is reached in any of those; the foreign bearer reaches only its own tenant's objects; the short credential shortens the report window to its own expiry and its post hook afterwards is refused on identity rather than on the deadline |
| AL24 | Configuration validation: an operation_ref outside ActionRequest.operation_ref's own constraint, a toolkit named as both governed and ungoverned, a target.input outside its own inputs list, an incident.input outside it, a duplicate tool declaration, a deployment with an arcade block and no connectors, and a key reload whose file changes the arcade block | Each invalid file is refused at load as the same opaque invalid-configuration answer every invalid file gets; the connector-free deployment loads; the reload is CONFIGURATION_CHANGED and leaves the running keys and the running tool map untouched |
| AL25 | The create_arcade boundary, GET /health, and the route inventory with and without an arcade block | Every method and raw path but the three answers 404 and a websocket is closed; GET /health is served by the application's readiness route, answers 200 with the database up and 503 with it down, is unauthenticated and names no tenant; tests/test_route_authority.py asserts the exact served set in both constructions and the two new routes carry AGENT authority |
| AL26 | Migration round trip with correlation rows of each kind and an execution in each settled state retained, including a corrected row whose own state is FAILED_NOT_EXECUTED and whose events name the new state; check_runtime; the runtime role against arcade_hooks; a replayed history containing a settled FAILED_EFFECT_UNKNOWN and a corrected one | Downgrade refuses while any of the three constrained columns holds the new state anywhere — asserted with the corrected row as the only holder, which a check of executions.state alone would let through — and succeeds once none does; check_runtime passes before and after; the runtime cannot DELETE; the export recomputes every decision, reconstructs the impact as CONFIRMED for the asserted failure and NONEXECUTION for the corrected one and reports both as settled rather than unresolved; Ledger.correct is asserted as the narrow method it is; the frozen contract fixtures and the context parity golden stay byte-identical |
| AL27 | The fixture Arcade caller over the real HTTP surface, driving every branch and asserting its recorded shapes against execbound_harness.fake_arcade.HOOK_CONTRACT | The walk covers governed permit, denial, approval wait and retry, ungoverned pass, duplicate pre and post delivery, reported success, reported failure, absent success, an oversized body and a missing post hook; the contract dictionary is a module-level constant of the harness rather than a literal inside the test, so the guide, the driver and this walk assert one object and a change to any shape fails in one place |
| AL28 | The record and approval pages for an Arcade-carried execution, with real Chromium | Tool identity, Arcade user id, reported codes and metadata render as inert text with no control that dispatches; the tier is shown with what it rests on; a settled assertion says what still holds its claim and what releases it; a settled FAILED_EFFECT_UNKNOWN row says the effect was never established and that the impact is charged |
| AL29 | The settlement vocabulary, both halves. Every (outcome, reason) spelling on the outcome route, signed and unsigned; TerminalEvidence, ExecutorRecord and JournalRecord asked for FAILED_EFFECT_UNKNOWN and REPORTED_FAILURE; a verifier double returning the new pair, through record_outcome against a dispatched row and through the report route against a grant | Exactly five pairings validate and exactly ten (tier, outcome, reason) combinations settle — four verified at ATTESTED, the same four at no tier on the dispatched path, and two asserted — enumerated in the test rather than sampled; every other spelling is INVALID_REQUEST from parse_report with no execution row read, no fence taken and no secret compared; the three evidence models raise for both new members; a report presenting a signed record beside REPORTED_FAILURE is refused by OutcomeReport's own clause, asserted as that refusal; the verifier double is refused by _proof in both call paths; test_the_terminal_aliases_are_the_evidence_literals passes unchanged |
| AL30 | A row settled FAILED_EFFECT_UNKNOWN at tier ASSERTED, then an ADMIN-signed CANCELLED_NOT_EXECUTED through reconcile; the same walk with an attested VERSION_MISMATCH; a third where the recovery run released the claim before the correction; and, on a corrected row, the correcting record replayed on the report route | Each settles at the verified evidence's own state — FAILED_NOT_EXECUTED, then STALE_AUTHORIZATION — at tier ATTESTED, with the impact rewritten to NONEXECUTION and confirmed_at null; the claim is released, and in the third walk released_at and the earlier execution.claim_released event are unchanged; the chain holds both transitions with both statements retained and the administrator's reason on the second; an ADMIN drives one correction of each direction from the record page's own control, in Chromium, and the page then shows the corrected row and the statement it corrected; a redelivery of the original post hook afterwards is INVALID_STATE in the kernel, answered OK to the platform and settles nothing, while the correcting record on the same route is the restatement it is. The export clause is parametrized over both correction directions: for each, the ledger recomputes with no INVALID_IMPACT_HISTORY and no INCOMPLETE_HISTORY, ReplayReport.complete is true, and the exported row's settlement_tier and terminal time are the correction's |
| AL31 | Everything the correction refuses: an ADMIN-signed EXECUTED against a correctable row; an ADMIN-signed non-execution against an EXECUTED row at tier ASSERTED and at tier ATTESTED; against an already-corrected row; against a row ExecBound dispatched; a second correction; a report carrying a record presented against an uncorrected correctable row; and a row whose retained state, tier or reason is not the correctable trio, each of the three varied on its own | Every one is INVALID_STATE, audited by _rejection and leaving the row, its impact, its claim and its tier untouched; the promotion the checkpoint guide's note refused is still refused with the same code; the report route produces no correction, so a held report secret corrects nothing. Four refusals are asserted individually, because a mutation of any one alone fails nothing: _correctable's state fact and its tier fact, each varied while the other two hold, and each of the two direction guards removed by name |
| AL32 | The granting-path latency measurement: the eligible pre hook end to end against the fixture caller and the protected mocks, at one, four and sixteen concurrent callers of one tenant, with the reservation, the canonical claim, an approval consumption, the executor key ring load, the closing attestation check and the secret mint inside the timed region | Medians and a maximum reported beside scripts/admission_benchmark.py's denied-path figures for the same concurrencies, recorded in the unit's evidence; the arcade block's required deadline_seconds is defended against the measured maximum rather than against a default, and a deployment file omitting it is refused at load |
| AL33 | A tenant whose only executor key is revoked as compromised, holding a live grant issued before the revocation: an attested report, then a bare success: false, then a recovery run past the act window, then an attempted correction with a newly registered key | The attested report is refused and settles nothing; the bare word settles FAILED_EFFECT_UNKNOWN at tier ASSERTED with the impact confirmed; the run releases the canonical claim; the correction is refused because no key is usable at that grant instant, and a key registered after it is refused too — asserted as the design's stated bound, not as a refusal the design claims to make |
| AL34 | A governed tool declaring an incident key and mapped to endpoint.isolate under the released endpoint-isolation.v1, driven end to end: a hook claiming the target's own open critical incident; then hooks claiming an id naming nothing, an incident of another resource of the same account, and an incident of another tenant; then one claiming a second incident of this tenant standing against the same target | The first is {"code": "OK"} with one DISPATCH_COMMITTED row and one UNRESOLVED impact, and the frozen plan carries the claimed id with the contract's incident facts naming it as their source object and nothing set in the claim beside it; the three misdirected claims are each CHECK_FAILED with nothing resolved, reserved or claimed; the second incident of the same target is granted, which is the design's stated bound — the kernel binds a claim to the target and not to relevance — asserted rather than implied |
| AL35 | A governed call the tool map refuses, delivered three ways: an undeclared tool with a usable Arcade execution id, an undeclared input with another, and an undeclared tool whose execution id falls outside the kernel's idempotency class; then a legitimate call reusing the first refusal's id | Each of the first two leaves a correlation row carrying the toolkit, the tool and the version refused, with decision CHECK_FAILED, no execution and no report secret; the unkeyable one is answered and recorded nowhere; the tenant's audit head is unmoved across all three; and the reused id grants, so a refusal row is not a tombstone |
GC7, GC8, GC10, GC12 and GC13 are re-run as part of this unit's evidence rather than afterwards, and what they establish is that nothing old broke; GC14 and GC17 are extended above, because as written neither could detect the defects this unit exists to fix. Provider-specific proof, the live half of the hook contract and every claim about how a real Arcade deployment behaves are not covered by these rows and stay open until a separately authorized trial.
Assistant
Implemented for #330 by the design in docs/superpowers/specs/2026-09-19-assistant-design.md and the plan in docs/superpowers/plans/2026-09-19-assistant.md. The engine's rows are tests/test_assistant_client.py, tests/test_assistant_turns.py, tests/test_assistant_tools.py and tests/test_assistant_loop.py; CI never calls a model, a scripted transport replays every provider answer.
| ID | Setup/action | Required result |
|---|---|---|
| AS1 | A private configuration without an assistant block; with one; with the key in the environment, in the block, in both, in neither; a tenant with the switch off | Without the block runtime.assistant is None and assistant_enabled is false; with it the caps and model load and the key appears in no repr; boot merges the key from EXECBOUND_ASSISTANT_API_KEY when the block carries none, refuses both and neither naming the variable and never the value, and scrubs it; a turn for a tenant whose switch is off is refused ASSISTANT_OFF; an ADMIN sets the switch with a reason and it is audited, a no-op and an approver are refused |
| AS2 | The runtime role against assistant_turns; a person's turns read by another person and by an agent principal | INSERT, UPDATE and DELETE are refused; every write goes through a definer function that checks the actor owns the turn; another person reads nothing and cannot step the turn; an agent can own no turn |
| AS3 | An approver with one scoped pending request and one execution outside the scope; the administrator | The approver's list holds the scoped request only; its detail carries the frozen intent, the facts with sources, the approval with its deadline and the kernel's decision events; the other execution is found: false; the administrator lists both |
| AS4 | Every tool called with an unknown name, a missing or extra argument, a wrong type, a bad uuid or a value outside its bounds | Refused as UNKNOWN_TOOL or INVALID_ARGUMENTS before any read; every tool's wire schema is strict with no additional properties |
| AS5 | An agent label carrying instructions; a scripted answer carrying a Markdown image, a link and an id no tool returned | The label reaches the model inside the untrusted envelope only; the answer is stored as text and cites only the id a tool returned |
| AS6 | A credential-shaped string in a tenant label read by a tool | The turn fails REFUSED_SECRET, is audited, and the transport recorded nothing carrying the string |
| AS7 | An explanation of an approval whose scripted answer recommends a decision; one that explains | The first is FAILED/RECOMMENDATION with no answer stored; the second completes citing the approval; an approval the person cannot read is refused before any model call |
| AS8 | A completed turn read back, retired at 29 days 23 hours and at 30 days | Steps, answer, cited ids, tools and token counts are whole; retirement nulls the text at thirty days, keeps the counts, and is an ADMIN's call; assistant.asked carries the turn id, tools, counts and model and no question or answer |
| AS9 | Nine tool calls; output past the token ceiling; a provider slower than the turn's limit; a rate limit then success; a bad request; a turn left running for ninety seconds; the hourly and daily caps | TOOL_LIMIT, TOKEN_LIMIT, TIME_LIMIT each once with nothing retried past the bound; one retry for the rate limit and none for the bad request; ABANDONED persisted on the first read; PERSON_LIMIT and TENANT_LIMIT counted in the creating transaction |
| AS10 | A console with no assistant block; with one and the tenant switch off; with one and the switch on | Without the block every assistant address answers 404; with it and the switch off a turn is refused ASSISTANT_OFF and the list answers empty; with the switch on a turn is created and answered |
| AS11 | Every assistant route with a bearer credential, with no session, and with another person's turn id | 403 and audited for the credential; a typed 401 in JSON for no session, even to a browser; 404 for another person's turn on the read, the stream and the opened mark |
| AS12 | The four constructions of the application: plain, with an arcade block, with the console, with the console and an assistant block | Each serves exactly its inventory; the assistant block adds its seven routes and nothing else, and all seven are session routes |
| AS13 | A turn watched as it gains steps, one already finished, and one still running at the stream's deadline | One step event per step, each once, then one done event carrying the ending; a finished turn is one done and no wait; the deadline closes the stream saying what the row says |
| AS14 | Every packaged help text against the document it summarises | Each phrase that carries a claim appears in the text and in that document; each text is one bounded paragraph and carries no advice about a decision |
| AS15 | The recover sweep over a tenants file | Each tenant's report carries assistant_turns_retired beside its settlements, so retention runs on the schedule that already visits every tenant |
| AS16 | A draft written as rules, and a scripted proposal the compiler accepts | The answer is the rule as the form takes it, its sentence, and its conditions in the words the editor parses, which parse back to the same conditions; no bundle is created or changed; the turn is the asker's own, completed, carrying the rule's sentence |
| AS17 | Proposals naming an unknown fact, a value outside its domain, an action the draft does not map, an id the draft already uses, an impossible effect or quorum, and answers that are not a rule at all | Each is refused INVALID_PROPOSAL with the field that caused it, the turn reads failed, and nothing is written |
| AS18 | The route as an approver; for a bundle written as a document; for a validated bundle; for a bundle of another tenant; with a bad body | 403 for the approver; the store's own refusal for a bundle with no rules to add to and for one that is not there, neither reaching the model; 400 for a body the model of the request refuses |
Spaces (SP1 to SP14)
Separation of ownership and view inside one tenant (design). A space is not isolation: forced row-level security is a second line of defence for tenants and there is no second line for spaces, so SP1 is the whole of the mitigation for a forgotten predicate and takes its parameter list from the migration's own column list.
| ID | Setup/action | Required result |
|---|---|---|
| SP1 | Two spaces with rows in every space-carrying table; a member of one reads every list, count, chart, search and detail | Parametrized over every table the migration touches: nothing of the other space is returned, on any surface, in either role |
| SP2 | "All spaces" | A tenant administrator sees both; a member of two spaces sees the union of theirs and nothing else; a member of one is offered no "All spaces" entry |
| SP3 | Membership restricts | A tenant administrator given a membership is narrowed at once; the ADMIN space role is refused for a HUMAN_APPROVER principal; the last tenant administrator cannot be narrowed away |
| SP4 | The current space is a preference | Removing a member while their session holds the space refuses the next read; session_set_space refuses another tenant's, an archived-but-unseen and an unknown space alike |
| SP5 | Sharing | An offered account and an offered bundle are usable by another space and editable, retirable and un-offerable only by the owner; an unoffered one is invisible and a mapping on it is refused at admission |
| SP6 | Per-space policy selection | Two spaces select two bundles; each agent is admitted under its own; a bundle no space selects retires; a bundle a space selects cannot be retired; ACTIVE equals "named by a selection row" after every transition |
| SP7 | Limits | A tenant rule counts across spaces; a space rule counts only its own retained rows; an approved candidate is exempt from autonomous and charges total in a space scope as in every other; a missing or doubled qualifier fails validation with its field location |
| SP8 | The tenant-wide claim | Space A's unresolved claim refuses space B's execution on the same canonical target with today's conflict and nothing about A on the wire or the page |
| SP9 | The archive race | An archive and a decision run concurrently on real PostgreSQL; exactly one commits; an archived space refuses new work with SPACE_ARCHIVED at the stop's check point; a DISPATCH_COMMITTED execution is not cancelled by an archive |
| SP10 | Migration round trip | The default space holds everything; every column is backfilled and every CHECK holds; approvers gain memberships and administrators do not; the selection moves off the control row; downgrade refuses with two spaces and otherwise restores the column, the grant and the index; check_runtime passes; the endpoint and identity contract goldens and every frozen fixture are byte-identical |
| SP11 | Console API | The harness's two sweeps on every route; a member is 403 on every administration route; a cross-tenant or unknown space is 404; /session/space answers the fresh session view; the secret check over every fixture response |
| SP12 | The console in Chromium | The switcher in both schemes at 1280 and 390 px, axe clean with zero CSP or Trusted Types violations; the popover reachable and dismissable by keyboard alone; a switch clearing the cache and re-reading every count; an approval link opening in the space that owns it; a space named with markup rendered as text |
| SP13 | Replay | A space candidate rule reconstructs from captures; captures with no recorded space report UNKNOWN_SPACE and stay UNRESOLVED while the baseline still matches; report.json stays deterministic |
| SP14 | Audit | Every administration act appends its event under the tenant's one chain and head; the chain is not split; a space switch appends nothing; owner commands and the console write identical payloads |
SP12 is tests/test_browser_console_spaces.py, in real Chromium, beside
console/src/shell/switcher.test.tsx and console/src/components/spaces/spaces.test.tsx
for the components' own behaviour.