Prepare reviewed durable case execution kernel and accepted layer pins
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 02:21:03 +02:00
parent 3eb648846e
commit 1b8261917e
17 changed files with 2163 additions and 34 deletions

View file

@ -22,7 +22,7 @@ assent: docs/adr/ADR-0010
# INTENT
> **ops-warden is Staff, and PEP-shaped.** Declared here in its own voice, per
> `security-layer-model_v0.7` §11 — a layer stated *about* a repository by another
> `security-layer-model_v0.8` §11 — a layer stated *about* a repository by another
> repository is not a declaration. The standard is **accepted**; its operative form
> is `net-kingdom/SECURITY-COMPANION.md`. ops-warden's assent is `ADR-0010`, and its
> reviews of v0.4, v0.6 and v0.7 are in `history/`.

View file

@ -0,0 +1,93 @@
{
"owner": "ops-warden",
"accepted_standard_sha256": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b",
"accepted_source": "net-kingdom/canon/standards/security-layer-model_v0.8.md",
"acceptance_receipt": "net-kingdom/docs/security-layer-v0.8-acceptance.md",
"tests": {
"layer_conformance_passed": 31,
"source": "staged modified scripts/tests + read-only current source support",
"limitations": "mechanical declared scope only, not complete estate/runtime conformance",
"routing": {
"full_suite_passed": 99,
"full_suite_failed": 1,
"failure": "staging fixture missing unchanged wiki links; not product regression",
"focused_after_fixture_repair_passed": 2,
"focused_cases": [
"every_wiki_ref_anchor_resolves",
"route_find_layer_declaration_resolves_to_reference_playbook"
],
"environment": "WARDEN_MEMORY=0 and explicit staged WARDEN_ROUTING_CATALOG; no installed catalog/memory mutation"
}
},
"checker_runs": [
{
"argv": [],
"returncode": 0,
"stdout": "validated against: net-kingdom/canon/standards/security-layer-model_v0.8.md (accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)\nscope: single repository ops-warden (catalogued in \u00a74); INTENT.md frontmatter, layer.yaml, src/warden/**/*.py\nPASS \u2014 4 module(s) with Tooling contact, all declared; validated against net-kingdom/canon/standards/security-layer-model_v0.8.md (accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)\n",
"stderr": ""
},
{
"argv": [
"--report"
],
"returncode": 0,
"stdout": "validated against: net-kingdom/canon/standards/security-layer-model_v0.8.md (accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)\nscope: single repository ops-warden (catalogued in \u00a74); INTENT.md frontmatter, layer.yaml, src/warden/**/*.py\nops-warden \u2014 layer: Staff (declared in INTENT.md; \u00a711 governing form)\n layer.yaml: derived from INTENT.md, layer: staff\ndeclared by docs/adr/ADR-0010\n\n \u00a75.3 ssh-ca-signing-write src/warden/vault.py -> secrets-engine review 2026-11-28\n \u00a75.3 desk-paste-once-provision src/warden/desk.py -> secrets-engine review 2026-11-28\n \u00a75.1 taint-metadata-read src/warden/taint.py\n \u00a75.2 access-proxy-conduit src/warden/proxy.py\n \u00a75.2 caller-identity-token src/warden/caller_identity.py\n\n2 declared gap(s) \u2014 tracked non-conformance, not conformance (\u00a711).\n\nPASS \u2014 every direct Tooling contact maps to a declared shape; validated against net-kingdom/canon/standards/security-layer-model_v0.8.md (accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)\n",
"stderr": ""
}
],
"meaningful_regressions": {
"unicode_token_original_fold": "staff",
"original_accepted_non_ascii_token": true
},
"source_changes": [
{
"repo": "ops-warden",
"path": "scripts/check_layer_conformance.py",
"staged_path": "/tmp/layer-migration-stage/ops-warden/scripts/check_layer_conformance.py",
"original_sha256": "064be430b64072fd1620e6adc19b4e24a1790c9df06144a9670cd262faf66e8a",
"staged_sha256": "7262fae9fae7ed454c0765cc6859e83a74e59ef6eade849991e0b8ab9c66757b"
},
{
"repo": "ops-warden",
"path": "tests/test_layer_conformance.py",
"staged_path": "/tmp/layer-migration-stage/ops-warden/tests/test_layer_conformance.py",
"original_sha256": "3d39b0a24a65263b2be9f18add2b9c69e12b7926716d610e8200d5c788c1b696",
"staged_sha256": "93e3a8805460c4a6a4fd78734cf4dd85947a46e23d16a0d6898a43e8ec39275d"
},
{
"repo": "ops-warden",
"path": "INTENT.md",
"staged_path": "/tmp/layer-migration-stage/ops-warden/INTENT.md",
"original_sha256": "766c2d44851be7078a031575710843cd7d1278d665e874882c8e90980dcd0704",
"staged_sha256": "3e9382fefa52ee0970dfac133cc799656e5034760b26dccc1bf9bab65fc538d9"
},
{
"repo": "ops-warden",
"path": "registry/routing/catalog.yaml",
"staged_path": "/tmp/layer-migration-stage/ops-warden/registry/routing/catalog.yaml",
"original_sha256": "32b0093cf5b93025d2aa90397247fef6ccce73cde5f477895a90ad09ddb48643",
"staged_sha256": "09ee40df21b0ad8cad420be4613afcb1731967f68128631a94f9b96f3f31b318"
},
{
"repo": "ops-warden",
"path": "tests/test_routing.py",
"staged_path": "/tmp/layer-migration-stage/ops-warden/tests/test_routing.py",
"original_sha256": "e449a4e169ccb7021d1bc547c420628b2b618d713915b90cf68d0bcaf5d5430f",
"staged_sha256": "43998fccf67b1f0d1a6c8bde1a4768a32f8418fb72a8eda5bf321dd96c097d35"
},
{
"repo": "ops-warden",
"path": "wiki/playbooks/netkingdom-layer-declaration.md",
"staged_path": "/tmp/layer-migration-stage/ops-warden/wiki/playbooks/netkingdom-layer-declaration.md",
"original_sha256": "2dbef2235afdc33203fb01582220370051cf6c9eefc7c7d482ff05ebd615fb64",
"staged_sha256": "6a409a1c474ce8759b01cf5a62e5b95bba297e167a05fd8f8b616abdf6414306"
}
],
"knowledge_orientation": {
"package_version": "0.2.6",
"package_digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"start_status": "withheld due changed sources; source reviewed directly"
},
"native_runtime_changed": false,
"pushed": false
}

View file

@ -0,0 +1,32 @@
{
"initial_kernel_sha256": "eb6ee0cd00b2704904782e71c214c34a91d060086b49fbd4ba1b86b2290da7a7",
"initial_fixture_tests_passed": 23,
"initial_independent_probe": {
"fd_output_escaped": true,
"provenance_exception_escaped": true,
"fixture_only": true,
"native_execution": false,
"subprocess_exit": 1
},
"findings_disposition": {
"unbounded_source_parsing": "fixed:256KiB caps before JSON/YAML",
"python_only_output_withholding": "fixed:serialized fd-level+Python containment",
"provenance_error_payload": "fixed:contained verifier with fixed Refused; BaseException sanitized preserving running",
"aggregate_revocation": "fixed:separate required broker/parent receipt proofs",
"prior_attempt_receipt_reuse": "fixed:result/proof purposes include all Pins+operation+sequence+nonce attempt identity",
"undeclared_secret_parameters": "author self-audit fixed explicit acknowledged-type parameter gate"
},
"final_kernel_sha256": "3636d089bfa9dd3fe66971529305354ba1e7ed46572eeceaabeb3c9a064b76a8",
"final_fixture_tests_passed": 51,
"refined_containment_probe": {
"fd_output_escaped": false,
"provenance_exception_escaped": false,
"fixture_only": true,
"native_execution": false,
"subprocess_exit": 0
},
"refined_containment_probe_scope": "independent probe at preceding4edb183 kernel, unchanged containment in final3636d089; final51tests reverify all regressions",
"failed_outcomes_preserved": true,
"refinements_are_same_review_trial": true,
"native_verified": false
}

View file

@ -0,0 +1,55 @@
# Independent SecurityCase kernel review — 2026-10-11
Disposition: **agree to bounded local kernel preparation** at SHA-256
`3636d089bfa9dd3fe66971529305354ba1e7ed46572eeceaabeb3c9a064b76a8`.
Review ID `WARDEN-REVIEW-20261011-CASE-KERNEL-v1`. Exact source/test/owner records
and accepted design are hashed in the adjacent sources receipt. This implements
part of accepted design 78ab1439, not live authority or executable type acceptance.
The initial inspected kernel passed 23 fixture tests but independent synthetic
probes proved fd output escaped and raw provenance errors reached tracebacks.
Review also identified absent parser caps, aggregate revocation proof and missing
per-attempt binding. The findings receipt retains the failed revision and its
actual outcomes. Refinements are one trial, not separate scored successes.
The final exact revision independently passes **51 tests** (37 kernel, 14 reducer).
Source review confirms 256 KiB pre-parser bounds; closed descriptor metadata;
exact case/type/catalog/descriptor pins; independently checked acceptance,
structural materialization and refreshed caller-session provenance; and explicit
undeclared/fixed/secret parameter refusal. Caller-asserted human labels cannot
satisfy the injected verifier. Agent mint remains refused after human admission.
Private SQLite running intent commits before dispatch. Crash recovery persists
uncertainty without redispatch; success/no-change cannot replay. Safe retry needs
verified failed-before-change, unchanged pins, explicit retry and declared safety.
Each persisted attempt binds all exact Pins, operation, sequence and nonce. Owner
results and separate broker-grant/parent-session revocation proof purposes must
match it; stale proof reuse and absence of either required proof cannot succeed.
Reconciliation is a separate owner callback, not credential regeneration.
Serialized Python plus OS-fd containment discards adapter/verifier output without
payload retention, including inherited synchronous subprocess output. Typed
errors and sanitized interruptions preserve running intent without raw messages.
This contains trusted fixed owner callbacks; it is no sandbox for arbitrary
in-process hostile code or background children. Production adapters must reuse
admitted attended-login containment and tie installed code to owner source pins.
Rollback passes only verified case-created objects in reverse order, excludes
shared objects, refuses no-change undo and stops on uncertain/lost results.
Limits of 32 created identifiers and 128 transitions bound local receipts; owner
archival must precede further dispatch. Independent provenance checks remain
injected contracts, not an implemented authentication system.
No installed production adapters, native provenance verifier or dispatch CLI
exist. No dynamic import, inferred shell execution or live argv adapter was
introduced. Descriptive types lack accepted machine descriptors and remain
plan-only; no CLI may dispatch this harness as a native run. Full ordered
run-sheet orchestration, verification inventory, contained login integration,
owner adapter/type acceptance, actual materialization and first live case proof
remain WARDEN-WP-0044-T03 and existing owner tasks. Uncertain undo still requires
owner reconciliation. T03 remains in progress.
Fake tests verify local persistence/refusal/withholding only. They prove no
native identity, OpenBao grant/policy, consumer enforcement, independent audit
custody or production revocation. No token, live Secret, login, mutation or push
occurred during this review.

View file

@ -0,0 +1,46 @@
{
"review_id": "WARDEN-REVIEW-20261011-CASE-KERNEL-v1",
"disposition": "agree-bounded-local-kernel",
"approved_design_sha256": "78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c",
"sources": [
{
"owner_path": "ops-warden/src/warden/case_run.py",
"reviewed_source_path": "/tmp/warden-case-execution-stage/src/warden/case_run.py",
"sha256": "3636d089bfa9dd3fe66971529305354ba1e7ed46572eeceaabeb3c9a064b76a8"
},
{
"owner_path": "ops-warden/tests/test_case_run.py",
"reviewed_source_path": "/tmp/warden-case-execution-stage/tests/test_case_run.py",
"sha256": "1e70ceb858af0eb3a126132a3c3606706120c0caf58ca9d45deb43f217705c7e"
},
{
"owner_path": "ops-warden/docs/security-case-execution-kernel-v1.md",
"reviewed_source_path": "/tmp/warden-case-execution-stage/docs/security-case-execution-kernel-v1.md",
"sha256": "ed5b6432ada71d1065fc837e3638e248cd4803241b6430d6ded07c25c0094c8d"
},
{
"owner_path": "ops-warden/workplans/WARDEN-WP-0044-security-case-follow-through.md",
"reviewed_source_path": "/tmp/warden-case-execution-stage/workplans/WARDEN-WP-0044-security-case-follow-through.md",
"sha256": "c0029545bd2d8c015ae0cac87f6d53a2e5ee935b7bf5d5e1b81bf1ff5dbdc4f8"
},
{
"owner_path": "/home/worsch/ops-warden/docs/security-case-execution-proposal.md",
"sha256": "78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c"
},
{
"owner_path": "/home/worsch/net-kingdom/docs/security-case-execution-owner-review.md",
"sha256": "0f1637bfddbbdf50d336974791b7fbf4646efe7e32ca04beb3bf52effd0a28e6"
},
{
"owner_path": "/home/worsch/the-custodian/docs/evidence/2026-10-10-security-consolidation/human-acceptance-receipts.json",
"sha256": "85007938d246b6fedb1ef323386720261e4ac55a9bf95f92c4915b226da357a7"
},
{
"owner_path": "/home/worsch/ops-warden/src/warden/proxy.py",
"sha256": "2d12c432f21f529c5d67a59d584bffb55b8286bdc19ed0ec26d795f889ad989f"
}
],
"findings_sha256": "d583435077e15cd5239c1c748d1fc079a73b70d8a9ef6c61d79869e93aa1699b",
"live_authority": false,
"native_complete": false
}

View file

@ -0,0 +1,86 @@
# Closed case execution kernel v1 — 2026-10-11
Owner implementation record for WARDEN-WP-0044-T03. The architectural proposal
at SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
was accepted through memo `infd-20261010-security-case-execution`, version 1,
approval e46b149b-ec3c-4b3b-98a7-dcb4934448ca, confirmed disposition
disp-cf44dacb-2117-4de7-8f90-cfa99f84f325 at 2026-10-10T23:23:31Z. The original
approved proposal remains byte-identical. That decision accepts source design;
it grants no execution session, credential or deployment authority.
`src/warden/case_run.py` advances the pure reducer into an executable injected
owner harness with private durable receipts. Descriptor version
`warden.case.execution.v1` is a closed local implementation seam, not an accepted
NetKingdom type schema. It contains fixed owner/adapter/source digest, actor,
lane, target parameter, effect, reviewed created-object identifiers declared
retry safety and explicit required revocation scopes (parent-session and broker-grant).
A mint descriptor cannot omit either scope. Unknown fields, free shell text and secret parameters are refused.
An acknowledged type must additionally pin the descriptor bytes under its
`execution_descriptors` map and independent owner evidence must authenticate
that exact machine-seam revision. Existing descriptive types lack this map and
remain plan-only. Their acknowledged labels do not authorize this new seam.
There are no installed owner adapters, native provenance verifier or dispatch CLI
in this change. Callables are injected only through a fixed adapter registry,
with immutable owner reference/source pins. A real integration must supply
independent owner/native verification or explicit attended manual evidence;
a bare JSON receipt identifier is insufficient. Fixture verifiers in tests
are deliberately fake and attest no native case. Caller sessions are independently
verified for exact principal/actor/lane at admission and again before dispatch;
input actor labels alone are never sufficient. Agent mint is refused, including
when WARDEN_AGENT_ID appears after initial human admission.
Each admission pins exact case/type/descriptor/catalog bytes, acceptance reference
and run-sheet identity. Actual ops-mason materialization evidence is required
against the case source, separately from its status label. Case parameters supply
an exact metadata target. Source/session changes cannot inherit journal success.
Owner results have a closed metadata shape, result evidence fingerprint and
separate independently authenticated grant/parent-session revocation evidence
fingerprints. An aggregate revoked boolean alone cannot satisfy either scope. Unknown result payloads, unverified evidence, absent revocation and
created objects outside the reviewed scope become uncertain, never success.
Python and OS stdout/stderr, including inherited child-process output, are
discarded without buffering or retention under a serialized fd containment
context. Independent provenance verification uses the same containment and
sanitizes exceptions. All source inputs are capped at 256 KiB before parsing.
Error text is not copied to the journal. KeyboardInterrupt/SystemExit and other
owner interruptions are rethrown with a fixed message while retaining running
intent for crash recovery.
The journal uses an owner-private 0700 directory and 0600 files, refuses symlinks
and insecure modes, uses SQLite FULL synchronization and commits running intent
before calling the adapter. A separate process lock prevents concurrent mutation.
A crash between dispatch and final receipt leaves running; the next lock holder
persists uncertain before refusing replay. Both intent and outcome remain in
history. Each persisted attempt has a unique identity bound to all exact pins,
operation (run/reconcile/rollback), sequence and nonce. Adapter results and both
revocation proof purposes carry that identity. Prior-attempt evidence cannot
verify a safe retry, even if a caller rewrites result labels. Descriptor-created
objects are capped at 32 and histories at 128 transitions, with dispatch stopped
before archival is required. A throw or lost response is not failed-before-change. A safe retry needs
verified failed-before-change, unchanged pins, descriptor retry-safe and explicit
retry. Success and no-change cannot replay.
Reconciliation is an explicit fixed owner callback, never the mutation callback.
It may verify an already-present native CAS result as no-change/success, or prove
failed-before-change; it never tells Warden to regenerate a credential. Uncertain
or failed-after-change requires this owner-native inspection before any retry.
Owner proof and revocation remain mandatory. Rollback visits only objects in a
verified creation receipt, in reverse order; no-change has no undo and shared
objects are excluded. Unknown/lost undo blocks another rollback.
The kernel operates individual admitted steps. Full ordered run-sheet orchestration,
verification inventory, contained login integration, native owner adapters and
CLI activation remain WARDEN-WP-0044-T03; no overall native-complete result is
implemented. Current Forgejo source is not materialized and has no acknowledged
machine descriptors. Its first native run remains an acceptance requirement,
with real materialization, separately authenticated broker-grant and parent-session
revocation receipts, session/owner receipts and consumer enforcement. This
implementation neither promotes a type nor changes any case status.
Validation: 37 new kernel tests plus 14 reducer tests pass (51 total). Tests open
an independent SQLite connection inside dispatch to prove running was committed,
crash after mutation and reopen a fresh journal to prove no duplicate invocation,
lose a response then reconcile without mint, require explicit retry, check exact
pins/session expiry, withhold malicious output and unconfirmed revocation, and
prove created-only reverse undo plus uncertain-undo refusal. Ruff formatting and
checks pass. No live token or target action occurred.

View file

@ -0,0 +1,136 @@
# SecurityCase execution contract proposal — 2026-10-10
Status: proposed owner review; not an accepted runtime protocol or authority.
Owner: ops-warden. Related: WARDEN-WP-0044-T03, NK-WP-0048, MASON-WP-0010.
`warden case plan` now gives a valid descriptive run, but the type's
`entry_point`, `auth`, verification `check` and rollback `action` fields are
prose. No `warden case run` or `rollback` implementation exists. A case becoming
`materialized` cannot turn those descriptions into executable commands.
The proposal preserves caller authority and routes by owner. It introduces no
new credential, policy grant, shell command inferred from prose, or authority
for Warden to configure a consumer. Architectural contract acceptance belongs to
net-kingdom and adapter interfaces to their owners. Founder review is needed
only if the acknowledged type's authorization/session boundary changes; the
current absence of a machine contract is not an excuse to fabricate one.
## Proposed machine seam
Keep human descriptions. Add a separately validated `execution` reference to
steps and checks, resolving to owner-provided closed adapters. An adapter has:
- immutable reference (owner repo, interface id, version, source digest);
- closed parameter schema (no secret-sensitivity fields; no free shell text);
- declared actor mode and catalog lane, which must agree with the case plan;
- explicit side-effect class and target identifier deriving from case parameters;
- idempotency/reconciliation contract and metadata-only receipt schema;
- supported rollback reference or explicit owner-only undo.
Warden does not invent an argv contract for ExternalSecrets, group membership,
KeyCape registration or Forgejo configuration. Owner adapters may internally
call their reviewed tools. Warden invokes only the fixed entry point provided by
that adapter, with validated separate argv fields, and refuses unknown adapters
or templates. `entry_point` prose never goes to `shell=True` or `sh -c`.
An owner may initially supply a receipt-only adapter: the run sheet records that
step as pending until the owner performs it and provides independent evidence.
This gives useful resumable orchestration before every live adapter exists,
without implying native completion or transferring ownership.
## Run sheet and receipts
The run sheet pins exact case bytes (`case-ref`), accepted type bytes/version,
owner adapter digests, catalog revision, acceptance reference and ordered step
identities. Changed pins require a newly reviewed sheet; never inherit previous
success into a different exact revision without explicit reconciliation.
Each attempt yields an immutable metadata receipt containing these pins, step
id, owner, target, attempt id, result enum and evidence-reference fingerprints.
Allowed results: pending-owner, not-started, running, verified-success,
verified-no-change, failed-before-change, failed-after-change, uncertain.
Neither subprocess output nor arbitrary owner payload is copied into receipts.
Adapter error strings are not evidence and must not be logged verbatim.
Stop on failure or uncertainty. A thrown exception or lost process receipt is
uncertain, not failed-before-change. Uncertain/failed-after-change blocks retry
until the owner reconciles target state. `verified-success` and
`verified-no-change` are not repeated. Retry after failed-before-change requires
the same pins and adapter's explicit retry-safe classification; a replay-safe
CAS0 receipt is reconciled, not retried into secret generation. State persistence
must write running before dispatch and result afterwards; interruption between
those writes leaves uncertain on resume. Outbox failures remain visible and do
not get rewritten as verified audit delivery.
Owner receipt ingestion must authenticate provenance or be explicitly manual
attended evidence, never a bare trusted JSON argument. Source/local receipts are
attributive preparation until their owner contract defines independent native
verification. Overall native completion requires every applicable step and
verification plus confirmed session revocation; pending-owner is never success.
## Sessions, values and rollback
Reuse `proxy_attended_login_exec` containment/output suppression/revocation
rather than add a second login helper. A key-cape login lane and the OpenBao
broker issuer role are distinct grants; the executor must represent both and
never silently share/inherit a token. Grouping is permitted only within the
same declared identity/lane and preserves follow-through order. An agent
principal cannot dispatch generated-in-custody mint-secret. The broker continues
to rederive the exact case-ref independently.
Rollback traverses successful step receipts in reverse order and only invokes
an owner's reviewed undo for target changes that receipt proves were created
by this case. Existing shared groups, membership and policies cannot be deleted
by interpreting a type rollback sentence literally. 'No change' yields no undo;
failed/uncertain states require reconciliation first. Do not restore a prior
secret value by extracting it into Warden; custody rollback is owner-native.
## Owner acceptance requests to prepare
1. net-kingdom: accept the closed machine seam separately from descriptive type
fields, document materialized = structural acceptance, and avoid gating that
state on mint-secret which is itself a follow-through step. Pin the revised
schema/type and keep type acceptance provenance explicit.
2. railiance-platform: provide a generate adapter over existing `credential.py`
exact `--grant`, `--client-id`, `--case-file`, `--case-ref` and receipt result
codes; clarify reconcile semantics for already-present CAS0 and grant/session
revocation evidence. No new credential is required by this proposal.
3. key-cape/net-kingdom: provide registration/group/env adapters and verify/undo
contracts in the existing attended boundary; do not hand Warden a user-engine
runtime token or a new broad Kubernetes credential.
4. railiance-apps/Forgejo: provide consumer OIDC + groups enforcement adapter or
receipt-only step, exact auth binding for CCR-2026-0042, and source/native
group-denial verification. This stays consumer ownership.
5. ops-mason: give a hash-pinned materialization receipt and list of objects
created vs pre-existing, sufficient for collision checks and scoped rollback.
No coordination messages have been sent by preparation of this proposal.
## Implementation and verification after owner seam acceptance
Implement `case_run.py` with injected typed adapters and receipt storage, then
CLI run-sheet/receipt import/resume/rollback commands. Keep case_plan read-only.
Add type machine-seam validation without turning valid descriptive planning
into an unreviewed source-breaking schema migration.
Required tests: rejected changed pins; unaccepted/unmaterialized cases; unsupported
adapter/argv refusal; agent mint refusal; pending owner keeps native gate open;
verified success never replayed; lost receipt resumes as uncertain; crash after
running persistence prevents duplicate side effect; failed-before-change retries
only with declared retry-safe adapter; failed-after-change needs owner reconcile;
malicious/secret output excluded; inability to revoke remains unverified;
rollback visits created successful objects only, preserves shared memberships,
and stops on uncertain undo. Inject runners; no live login or target required.
WARDEN-WP-0044-T03 remains open. This packet resolves the precise source contract
deficit; it is not case materialization, a native run, or a type decision.
## Local reducer prototype
`src/warden/case_receipt_state.py` is proposed local source preparation, not an
accepted wire format. Its pure immutable transitions can be tested without any
owner adapter or shell dispatch. Persist the returned running intent before an
external side effect; on a lost result, recover marks uncertainty and blocks
replay. No receipt provenance, durable storage, CLI dispatch or live action is
implemented. `tests/test_case_receipt_state.py` covers the failure boundaries.

View file

@ -51,8 +51,8 @@ entries:
subsystem: NetKingdom layer-conformance routing
warden_executes: false
wiki_ref: wiki/playbooks/netkingdom-layer-declaration.md#worker-checklist
canon_ref: net-kingdom/canon/standards/security-layer-model_v0.7.md
reviewed: "2026-09-04"
canon_ref: net-kingdom/canon/standards/security-layer-model_v0.8.md
reviewed: "2026-10-11"
status: active
delegation:
mode: permanent

View file

@ -58,16 +58,16 @@ DECL = ROOT / "layer.yaml"
VALID_SHAPES = {"5.1", "5.2", "5.3"}
# What every run checks against, printed on every run (GH-DEC-2026-020 §4,
# GH-DEC-2026-021 §2). The accepted text is v0.7 at net-kingdom@66dc491; the
# decision records whose rulings this checker enforces beyond v0.7 are named with it.
# Accepted v0.8 publication; version provenance belongs to this run, never
# the standing layer declaration (§11). Exact publisher artifact is retained
# in net-kingdom/docs/security-layer-v0.8-acceptance.md.
VALIDATED_AGAINST = (
"net-kingdom/canon/standards/security-layer-model_v0.7.md (net-kingdom@66dc491) "
"as amended by GH-DEC-2026-017, GH-DEC-2026-020 and GH-DEC-2026-021 "
"(A9-A13, A12 r3; gate-house@39d9287)"
"net-kingdom/canon/standards/security-layer-model_v0.8.md "
"(accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)"
)
# What every run ranges over. pep-stance.yaml is deliberately outside it.
SCOPE = "INTENT.md frontmatter, layer.yaml, src/warden/**/*.py"
SCOPE = ("single repository ops-warden (catalogued in §4); "
"INTENT.md frontmatter, layer.yaml, src/warden/**/*.py")
# A12 r2: a version of the standard or companion in any key or value of the
# declaration. Keys: anything naming a standard/companion version. Values: a
@ -131,7 +131,8 @@ LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"}
def _fold(value: str) -> str:
"""ASCII case-fold, per §3 as amended: two spellings of a token are one token."""
return str(value).strip().encode("ascii", "ignore").decode().lower()
return str(value).strip().translate(str.maketrans(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"))
def load_governing_layer() -> str:

View file

@ -0,0 +1,146 @@
"""Proposed, pure case receipt reducer for WARDEN-WP-0044-T03.
This prototype executes nothing and implements no accepted owner adapter API.
Callers MUST durably persist a returned running receipt before a side effect.
Only metadata identifiers/digests enter this model; no command output is accepted.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from enum import Enum
import re
_ID = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}\Z")
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
class Refused(ValueError):
"""A transition requires an independent owner reconciliation or new review."""
class Outcome(str, Enum):
RUNNING = "running"
SUCCESS = "verified-success"
NO_CHANGE = "verified-no-change"
FAILED_BEFORE = "failed-before-change"
FAILED_AFTER = "failed-after-change"
UNCERTAIN = "uncertain"
ROLLBACK_RUNNING = "rollback-running"
ROLLED_BACK = "verified-rolled-back"
ROLLBACK_UNCERTAIN = "rollback-uncertain"
@dataclass(frozen=True)
class Pins:
"""Exact reviewed case/type/adapter/catalog/acceptance/run-sheet digests."""
case: str
case_type: str
adapter: str
catalog: str
acceptance: str
run_sheet: str
def __post_init__(self) -> None:
if not all(_DIGEST.fullmatch(value) for value in vars(self).values()):
raise Refused("pins require sha256 digests")
@dataclass(frozen=True)
class Receipt:
attempt: int
outcome: Outcome
created: tuple[str, ...] = ()
@dataclass(frozen=True)
class StepState:
step_id: str
owner: str
pins: Pins
allowed_created: tuple[str, ...] = ()
retry_safe: bool = False
receipts: tuple[Receipt, ...] = ()
def __post_init__(self) -> None:
if not all(_ID.fullmatch(v) for v in (self.step_id, self.owner, *self.allowed_created)):
raise Refused("only metadata identifiers are supported")
if len(set(self.allowed_created)) != len(self.allowed_created):
raise Refused("duplicate reviewed object identifier")
@property
def last(self) -> Receipt | None:
return self.receipts[-1] if self.receipts else None
def _same_pins(state: StepState, pins: Pins) -> None:
if state.pins != pins:
raise Refused("pins changed; a newly reviewed sheet is required")
def _append(state: StepState, outcome: Outcome, created: tuple[str, ...] = ()) -> StepState:
receipt = Receipt(len(state.receipts) + 1, outcome, created)
return replace(state, receipts=(*state.receipts, receipt))
def begin(state: StepState, pins: Pins, *, retry: bool = False) -> StepState:
"""Return intent to dispatch. Persist before calling an owner adapter."""
_same_pins(state, pins)
last = state.last
if last is None:
if retry:
raise Refused("no failed attempt to retry")
elif not (last.outcome is Outcome.FAILED_BEFORE and state.retry_safe and retry):
raise Refused("step cannot dispatch or replay without owner reconciliation")
return _append(state, Outcome.RUNNING)
def finish(state: StepState, pins: Pins, outcome: Outcome,
*, created: tuple[str, ...] = ()) -> StepState:
"""Accept only an adapter's verified, closed metadata result, never output."""
_same_pins(state, pins)
if not state.last or state.last.outcome is not Outcome.RUNNING:
raise Refused("no running side effect")
if not isinstance(outcome, Outcome) or outcome not in (Outcome.SUCCESS, Outcome.NO_CHANGE, Outcome.FAILED_BEFORE,
Outcome.FAILED_AFTER, Outcome.UNCERTAIN):
raise Refused("unsupported adapter result")
if len(set(created)) != len(created) or not set(created) <= set(state.allowed_created):
raise Refused("created objects are outside the reviewed case")
if created and outcome not in (Outcome.SUCCESS, Outcome.FAILED_AFTER):
raise Refused("this result cannot claim created objects")
return _append(state, outcome, created)
def recover(state: StepState) -> StepState:
"""Lost result after persisted dispatch is uncertain, never safe to retry."""
if state.last and state.last.outcome is Outcome.RUNNING:
return _append(state, Outcome.UNCERTAIN)
if state.last and state.last.outcome is Outcome.ROLLBACK_RUNNING:
return _append(state, Outcome.ROLLBACK_UNCERTAIN, state.last.created)
return state
def rollback_objects(state: StepState, pins: Pins) -> tuple[str, ...]:
"""Reviewed identifiers only, reverse created order; no shared-object undo."""
_same_pins(state, pins)
last = state.last
if last is None or last.outcome is Outcome.NO_CHANGE:
return ()
if last.outcome is not Outcome.SUCCESS:
raise Refused("rollback needs a verified successful creation receipt")
return tuple(reversed(last.created))
def begin_rollback(state: StepState, pins: Pins) -> StepState:
objects = rollback_objects(state, pins)
if not objects:
raise Refused("no case-created objects to undo")
return _append(state, Outcome.ROLLBACK_RUNNING, objects)
def finish_rollback(state: StepState, pins: Pins, *, verified: bool) -> StepState:
_same_pins(state, pins)
if not state.last or state.last.outcome is not Outcome.ROLLBACK_RUNNING:
raise Refused("no running rollback")
outcome = Outcome.ROLLED_BACK if verified else Outcome.ROLLBACK_UNCERTAIN
return _append(state, outcome, state.last.created)

714
src/warden/case_run.py Normal file
View file

@ -0,0 +1,714 @@
"""Closed local execution kernel v1; no installed adapters or live CLI dispatch.
An owner must acknowledge each descriptor and provide an independent provenance
verifier. Descriptive case types remain plan-only. Nothing executes shell prose.
"""
from __future__ import annotations
from contextlib import contextmanager, redirect_stderr, redirect_stdout
from dataclasses import asdict, dataclass, replace
import fcntl
import hashlib
import json
import os
from pathlib import Path
import re
import sqlite3
import threading
import uuid
from typing import Callable
import yaml
from .case_receipt_state import (
Outcome,
Pins,
Receipt,
Refused,
StepState,
begin,
finish,
recover,
begin_rollback,
finish_rollback,
)
IDENTIFIER = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9_.:/@-]{0,191}\Z")
MAX_SOURCE_BYTES = 262144
MAX_HISTORY = 128
OUTPUT_LOCK = threading.RLock()
def bounded_source(value: bytes):
if type(value) is not bytes or len(value) > MAX_SOURCE_BYTES:
raise Refused("source exceeds bounded input contract")
@contextmanager
def contained_output():
"""Discard Python and inherited OS stdout/stderr; serialize process fd changes."""
with OUTPUT_LOCK:
saved = []
null = os.open(os.devnull, os.O_WRONLY)
try:
for target in (1, 2):
saved.append((target, os.dup(target)))
os.dup2(null, target)
with redirect_stdout(_Discard()), redirect_stderr(_Discard()):
try:
yield
except Exception:
raise Refused("owner callback failed") from None
except BaseException:
raise OwnerInterrupted() from None
finally:
for target, original in saved:
os.dup2(original, target)
os.close(original)
os.close(null)
DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
def digest(value: bytes) -> str:
return "sha256:" + hashlib.sha256(value).hexdigest()
@dataclass(frozen=True, slots=True)
class Descriptor:
version: str
step: str
owner: str
adapter: str
adapter_source: str
actor: str
lane: str
target_parameter: str
effect: str
allowed_created: tuple[str, ...]
retry_safe: bool
revocation_scopes: tuple[str, ...]
@classmethod
def decode(cls, source: bytes) -> Descriptor:
bounded_source(source)
try:
obj = json.loads(source)
if set(obj) != set(cls.__dataclass_fields__):
raise ValueError
if not isinstance(obj["allowed_created"], list):
raise ValueError
obj["allowed_created"] = tuple(obj["allowed_created"])
if not isinstance(obj["revocation_scopes"], list):
raise ValueError
obj["revocation_scopes"] = tuple(obj["revocation_scopes"])
result = cls(**obj)
identifiers = (
result.step,
result.owner,
result.adapter,
result.lane,
result.target_parameter,
*result.allowed_created,
)
if result.version != "warden.case.execution.v1" or not all(
isinstance(v, str) and IDENTIFIER.fullmatch(v) for v in identifiers
):
raise ValueError
if not DIGEST.fullmatch(result.adapter_source) or result.actor not in (
"human",
"agent",
):
raise ValueError
if (
result.effect not in ("read", "change", "mint")
or type(result.retry_safe) is not bool
):
raise ValueError
scopes = set(result.revocation_scopes)
if (
len(scopes) != len(result.revocation_scopes)
or not scopes <= {"parent-session", "broker-grant"}
or "parent-session" not in scopes
):
raise ValueError
if result.effect == "mint" and "broker-grant" not in scopes:
raise ValueError
if len(result.allowed_created) > 32:
raise ValueError
if len(set(result.allowed_created)) != len(result.allowed_created):
raise ValueError
return result
except (ValueError, TypeError, KeyError):
raise Refused("invalid closed execution descriptor") from None
@dataclass(frozen=True, slots=True)
class OwnerResult:
outcome: Outcome
evidence: str
created: tuple[str, ...] = ()
revoked: bool = False
session_revocation_evidence: str | None = None
grant_revocation_evidence: str | None = None
attempt_id: str = ""
operation: str = ""
@dataclass(frozen=True, slots=True)
class Invocation:
step: str
owner: str
target: str
actor: str
principal: str
lane: str
pins: Pins
attempt_id: str = ""
operation: str = "admission"
@dataclass(frozen=True, slots=True)
class Adapter:
reference: str
source: str
invoke: Callable[[Invocation], OwnerResult]
verify: Callable[[Invocation, OwnerResult], bool]
rollback: Callable[[Invocation, tuple[str, ...]], OwnerResult] | None = None
reconcile: Callable[[Invocation], OwnerResult] | None = None
class Journal:
"""Private SQLite journal plus process lock; transaction commits before dispatch."""
def __init__(self, directory: Path):
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
if (
directory.is_symlink()
or directory.stat().st_uid != os.getuid()
or directory.stat().st_mode & 0o077
):
raise Refused("journal directory is not private")
self.directory = directory
for name in ("journal.sqlite", "execution.lock"):
path = directory / name
fd = os.open(path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
try:
if os.fstat(fd).st_uid != os.getuid() or os.fstat(fd).st_mode & 0o077:
raise Refused("journal file is not private")
finally:
os.close(fd)
self.db = sqlite3.connect(directory / "journal.sqlite", isolation_level=None)
self.db.execute("PRAGMA synchronous=FULL")
self.db.execute(
"CREATE TABLE IF NOT EXISTS receipts (seq INTEGER PRIMARY KEY, binding TEXT NOT NULL, payload TEXT NOT NULL)"
)
@contextmanager
def locked(self):
with (self.directory / "execution.lock").open("rb") as fh:
try:
fcntl.flock(fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise Refused("case execution already active") from None
try:
yield
finally:
fcntl.flock(fh, fcntl.LOCK_UN)
def load(self, binding: str, initial: StepState) -> StepState:
row = self.db.execute(
"SELECT payload FROM receipts WHERE binding=? ORDER BY seq DESC LIMIT 1",
(binding,),
).fetchone()
if row is None:
return initial
obj = json.loads(row[0])
expected = {
"step_id",
"owner",
"pins",
"allowed_created",
"retry_safe",
"receipts",
"journal_schema",
"evidence_digest",
"session_revoked",
"session_revocation_digest",
"grant_revocation_digest",
"attempt_id",
"operation",
}
if (
set(obj) != expected
or obj["journal_schema"] != "warden.case.step-journal.v1"
):
raise Refused("unknown journal receipt schema")
state = StepState(
obj["step_id"],
obj["owner"],
Pins(**obj["pins"]),
tuple(obj["allowed_created"]),
obj["retry_safe"],
tuple(
Receipt(r["attempt"], Outcome(r["outcome"]), tuple(r["created"]))
for r in obj["receipts"]
),
)
if (
state.step_id,
state.owner,
state.pins,
state.allowed_created,
state.retry_safe,
) != (
initial.step_id,
initial.owner,
initial.pins,
initial.allowed_created,
initial.retry_safe,
):
raise Refused("journal pins changed")
return state
def save(
self,
binding: str,
state: StepState,
evidence: OwnerResult | None = None,
invocation: Invocation | None = None,
):
self.db.execute("BEGIN IMMEDIATE")
try:
self.db.execute(
"INSERT INTO receipts(binding,payload) VALUES (?,?)",
(
binding,
json.dumps(
asdict(state)
| {
"journal_schema": "warden.case.step-journal.v1",
"attempt_id": invocation.attempt_id if invocation else None,
"operation": invocation.operation if invocation else None,
"evidence_digest": digest(evidence.evidence.encode())
if evidence
else None,
"session_revoked": evidence.revoked if evidence else None,
"session_revocation_digest": digest(
evidence.session_revocation_evidence.encode()
)
if evidence and evidence.session_revocation_evidence
else None,
"grant_revocation_digest": digest(
evidence.grant_revocation_evidence.encode()
)
if evidence and evidence.grant_revocation_evidence
else None,
},
separators=(",", ":"),
),
),
)
self.db.execute("COMMIT")
except BaseException:
self.db.execute("ROLLBACK")
raise
class Executor:
"""Injected fixed owner adapters and provenance verifier; none ship by default.
evidence verifier must authenticate owner custody/provenance or explicitly
attest attended manual evidence; a bare JSON reference is never sufficient.
It receives purpose, exact source digest, and named evidence reference.
"""
def __init__(
self,
journal: Journal,
adapters: dict[str, Adapter],
evidence_verifier: Callable[[str, str, str], bool],
):
self.journal, self.adapters, self.evidence_verifier = (
journal,
dict(adapters),
evidence_verifier,
)
self._admitted = {}
def admit(
self,
case_source: bytes,
type_source: bytes,
descriptor_source: bytes,
catalog_source: bytes,
*,
principal: str,
actor: str,
lane: str,
acceptance_evidence: str,
materialization_evidence: str,
descriptor_evidence: str,
session_evidence: str,
) -> tuple[Descriptor, Invocation, StepState]:
for source in (case_source, type_source, descriptor_source, catalog_source):
bounded_source(source)
descriptor = Descriptor.decode(descriptor_source)
try:
case = yaml.safe_load(case_source)["spec"]
case_type = yaml.safe_load(type_source)["spec"]
if not isinstance(case, dict) or not isinstance(case_type, dict):
raise ValueError
except (yaml.YAMLError, KeyError, TypeError, ValueError):
raise Refused("invalid case or type source shape") from None
if case.get("status") != "materialized" or not case.get("acceptance"):
raise Refused("case is not accepted and structurally materialized")
if case_type.get("status") not in ("acknowledged", "implemented"):
raise Refused("type is not acknowledged")
if case.get("type") != f"{case_type['id']}@{case_type['version']}":
raise Refused("case type pin mismatch")
if case_type.get("execution_descriptors", {}).get(descriptor.step) != digest(
descriptor_source
):
raise Refused(
"type has no exact acknowledged machine descriptor; plan only"
)
for purpose, source, evidence in (
("case-acceptance", digest(case_source), acceptance_evidence),
(
"ops-mason-materialization",
digest(case_source),
materialization_evidence,
),
(
"owner-descriptor-acceptance:" + descriptor.owner,
digest(descriptor_source),
descriptor_evidence,
),
("type-machine-seam-acceptance", digest(type_source), descriptor_evidence),
("catalog-routing-acceptance", digest(catalog_source), descriptor_evidence),
(
f"caller-session:{actor}:{principal}:{lane}",
digest(case_source),
session_evidence,
),
):
if (
not isinstance(evidence, str)
or not IDENTIFIER.fullmatch(evidence)
or self._verify_evidence(purpose, source, evidence) is not True
):
raise Refused("independent owner evidence not verified")
if (
not IDENTIFIER.fullmatch(principal)
or actor != descriptor.actor
or lane != descriptor.lane
):
raise Refused("principal actor or lane mismatch")
if descriptor.effect == "mint" and (
actor != "human" or os.getenv("WARDEN_AGENT_ID")
):
raise Refused("agent mint is forbidden")
parameters = case.get("parameters", {})
declared = {
item["name"]: item
for item in case_type.get("parameters", [])
if isinstance(item, dict) and isinstance(item.get("name"), str)
}
if (
not isinstance(parameters, dict)
or descriptor.target_parameter not in declared
):
raise Refused("target parameter is not declared by acknowledged type")
for name in parameters:
definition = declared.get(name)
if (
definition is None
or definition.get("sensitivity") not in ("public", "operational")
or definition.get("case_settable") is False
):
raise Refused("unknown, fixed or secret-sensitivity case parameter")
target = parameters.get(descriptor.target_parameter)
if not isinstance(target, str) or not IDENTIFIER.fullmatch(target):
raise Refused("target must be exact case metadata")
adapter = self.adapters.get(descriptor.adapter)
if adapter is None or (adapter.reference, adapter.source) != (
descriptor.adapter,
descriptor.adapter_source,
):
raise Refused("unknown adapter or owner source pin")
sheet = json.dumps(
[
descriptor.step,
descriptor.owner,
target,
actor,
principal,
lane,
acceptance_evidence,
materialization_evidence,
descriptor_evidence,
session_evidence,
],
separators=(",", ":"),
).encode()
pins = Pins(
digest(case_source),
digest(type_source),
digest(descriptor_source),
digest(catalog_source),
digest(acceptance_evidence.encode()),
digest(sheet),
)
invocation = Invocation(
descriptor.step, descriptor.owner, target, actor, principal, lane, pins
)
initial = StepState(
descriptor.step,
descriptor.owner,
pins,
descriptor.allowed_created,
descriptor.retry_safe,
)
self._admitted[invocation] = (descriptor, initial, session_evidence)
return descriptor, invocation, initial
def _result(
self, adapter: Adapter, invocation: Invocation, result: OwnerResult
) -> OwnerResult:
if type(result) is not OwnerResult or not isinstance(result.outcome, Outcome):
raise Refused("unknown owner result shape")
if type(result.created) is not tuple or not all(
isinstance(v, str) and IDENTIFIER.fullmatch(v) for v in result.created
):
raise Refused("owner created-object result invalid")
if not isinstance(result.evidence, str) or not IDENTIFIER.fullmatch(
result.evidence
):
raise Refused("owner evidence identifier invalid")
if adapter.verify(invocation, result) is not True or result.revoked is not True:
raise Refused("owner result or session revocation unverified")
if (result.attempt_id, result.operation) != (
invocation.attempt_id,
invocation.operation,
):
raise Refused("owner receipt belongs to another attempt")
descriptor = self._admitted[
replace(invocation, attempt_id="", operation="admission")
][0]
proof_fields = {
"parent-session": (
f"session-revocation:{invocation.lane}:{invocation.operation}:{invocation.attempt_id}",
result.session_revocation_evidence,
),
"broker-grant": (
f"grant-revocation:{invocation.lane}:{invocation.operation}:{invocation.attempt_id}",
result.grant_revocation_evidence,
),
}
proofs = [proof_fields[scope] for scope in descriptor.revocation_scopes]
for purpose, evidence in proofs:
if (
not isinstance(evidence, str)
or not IDENTIFIER.fullmatch(evidence)
or self._verify_evidence(purpose, invocation.pins.case, evidence)
is not True
):
raise Refused("independent grant or parent-session revocation absent")
return result
def _verify_evidence(self, purpose, source, evidence):
try:
with contained_output():
return self.evidence_verifier(purpose, source, evidence) is True
except Exception:
raise Refused("independent evidence verification failed") from None
def _admission(
self, descriptor: Descriptor, invocation: Invocation, initial: StepState
):
if self._admitted.get(invocation, ())[:2] != (descriptor, initial):
raise Refused("execution requires verified admission on this executor")
adapter = self.adapters.get(descriptor.adapter)
if adapter is None or (adapter.reference, adapter.source) != (
descriptor.adapter,
descriptor.adapter_source,
):
raise Refused("adapter changed after admission")
session = self._admitted[invocation][2]
if (
self._verify_evidence(
f"caller-session:{invocation.actor}:{invocation.principal}:{invocation.lane}",
invocation.pins.case,
session,
)
is not True
):
raise Refused("caller session no longer verified")
if descriptor.effect == "mint" and os.getenv("WARDEN_AGENT_ID"):
raise Refused("agent mint is forbidden")
return adapter
def _attempt(self, invocation: Invocation, state: StepState, operation: str):
identity = json.dumps(
[asdict(invocation.pins), operation, state.last.attempt, uuid.uuid4().hex],
separators=(",", ":"),
).encode()
return replace(
invocation,
attempt_id="attempt-" + hashlib.sha256(identity).hexdigest(),
operation=operation,
)
def run(
self,
descriptor: Descriptor,
invocation: Invocation,
initial: StepState,
*,
retry=False,
) -> StepState:
adapter = self._admission(descriptor, invocation, initial)
with self.journal.locked():
binding = f"{invocation.step}:{invocation.target}"
state = self.journal.load(binding, initial)
recovered = recover(state)
if recovered != state:
self.journal.save(binding, recovered)
state = recovered
if len(state.receipts) > MAX_HISTORY - 4:
raise Refused("bounded journal history requires owner archival")
running = begin(state, invocation.pins, retry=retry)
dispatched = self._attempt(invocation, running, "run")
self.journal.save(binding, running, invocation=dispatched)
evidence = None
try:
with contained_output():
result = self._result(
adapter, dispatched, adapter.invoke(dispatched)
)
completed = finish(
running, invocation.pins, result.outcome, created=result.created
)
evidence = result
except Exception:
completed = finish(running, invocation.pins, Outcome.UNCERTAIN)
self.journal.save(binding, completed, evidence, dispatched)
return completed
def rollback(
self, descriptor: Descriptor, invocation: Invocation, initial: StepState
) -> StepState:
adapter = self._admission(descriptor, invocation, initial)
with self.journal.locked():
binding = f"{invocation.step}:{invocation.target}"
state = self.journal.load(binding, initial)
if recover(state) != state:
self.journal.save(binding, recover(state))
raise Refused("lost rollback result requires owner reconciliation")
if adapter.rollback is None:
raise Refused("owner has no reviewed undo adapter")
if len(state.receipts) > MAX_HISTORY - 4:
raise Refused("bounded journal history requires owner archival")
running = begin_rollback(state, invocation.pins)
dispatched = self._attempt(invocation, running, "rollback")
self.journal.save(binding, running, invocation=dispatched)
evidence = None
try:
with contained_output():
result = self._result(
adapter,
dispatched,
adapter.rollback(dispatched, running.last.created),
)
verified = result.outcome is Outcome.ROLLED_BACK and not result.created
evidence = result if verified else None
except Exception:
verified = False
completed = finish_rollback(running, invocation.pins, verified=verified)
self.journal.save(binding, completed, evidence, dispatched)
return completed
def reconcile(
self, descriptor: Descriptor, invocation: Invocation, initial: StepState
) -> StepState:
"""Explicit owner-native state inspection; never redispatches the mutation."""
adapter = self._admission(descriptor, invocation, initial)
if adapter.reconcile is None:
raise Refused("owner reconciliation adapter absent")
with self.journal.locked():
binding = f"{invocation.step}:{invocation.target}"
state = recover(self.journal.load(binding, initial))
if not state.last or state.last.outcome not in (
Outcome.UNCERTAIN,
Outcome.FAILED_AFTER,
):
raise Refused("no uncertain mutation to reconcile")
if len(state.receipts) > MAX_HISTORY - 4:
raise Refused("bounded journal history requires owner archival")
state = replace(
state,
receipts=(
*state.receipts,
Receipt(len(state.receipts) + 1, Outcome.UNCERTAIN),
),
)
dispatched = self._attempt(invocation, state, "reconcile")
self.journal.save(binding, state, invocation=dispatched)
with contained_output():
result = self._result(
adapter, dispatched, adapter.reconcile(dispatched)
)
if result.outcome not in (
Outcome.NO_CHANGE,
Outcome.SUCCESS,
Outcome.FAILED_BEFORE,
):
raise Refused("owner did not reconcile target state")
# Validate created-object scope using the same closed result reducer.
finish(
begin(
StepState(
state.step_id,
state.owner,
state.pins,
state.allowed_created,
state.retry_safe,
),
state.pins,
),
state.pins,
result.outcome,
created=result.created,
)
reconciled = StepState(
state.step_id,
state.owner,
state.pins,
state.allowed_created,
state.retry_safe,
(
*state.receipts,
Receipt(len(state.receipts) + 1, result.outcome, result.created),
),
)
self.journal.save(binding, reconciled, result, dispatched)
return reconciled
class OwnerInterrupted(KeyboardInterrupt):
"""Sanitized interruption preserves persisted running/crash recovery semantics."""
def __init__(self):
super().__init__("owner execution interrupted")
class _Discard:
"""Never retain or forward adapter output, including unbounded malicious text."""
def write(self, text):
return len(text)
def flush(self):
pass

View file

@ -0,0 +1,124 @@
"""Offline prototype regression cases; no owner adapter, credential or live use."""
from dataclasses import FrozenInstanceError, replace
import pytest
from warden.case_receipt_state import (
Outcome, Pins, Refused, StepState, begin, begin_rollback, finish,
finish_rollback, recover, rollback_objects,
)
@pytest.fixture
def pins():
return Pins(*["sha256:" + character * 64 for character in "abcdef"])
@pytest.fixture
def state(pins):
return StepState("mint-secret", "railiance-platform", pins, ("case-policy", "case-role"))
def test_records_running_before_dispatch_without_mutating_history(state, pins):
running = begin(state, pins)
assert state.receipts == ()
assert running.last.outcome is Outcome.RUNNING
with pytest.raises(FrozenInstanceError):
running.last.attempt = 8
def test_changed_exact_revision_refuses_dispatch(state, pins):
with pytest.raises(Refused):
begin(state, replace(pins, adapter="sha256:" + "0" * 64))
def test_lost_receipt_becomes_uncertain_and_cannot_retry(state, pins):
resumed = recover(begin(state, pins))
assert resumed.last.outcome is Outcome.UNCERTAIN
assert len(resumed.receipts) == 2
with pytest.raises(Refused):
begin(replace(resumed, retry_safe=True), pins, retry=True)
@pytest.mark.parametrize("outcome", [Outcome.SUCCESS, Outcome.NO_CHANGE])
def test_verified_success_or_no_change_never_replayed(state, pins, outcome):
done = finish(begin(state, pins), pins, outcome)
with pytest.raises(Refused):
begin(done, pins, retry=True)
def test_retry_requires_verified_no_change_failure_and_safe_adapter(state, pins):
failed = finish(begin(state, pins), pins, Outcome.FAILED_BEFORE)
with pytest.raises(Refused):
begin(failed, pins, retry=True)
safe = replace(failed, retry_safe=True)
with pytest.raises(Refused):
begin(safe, pins)
retry = begin(safe, pins, retry=True)
assert retry.last.outcome is Outcome.RUNNING
assert len(retry.receipts) == 3
def test_failure_after_change_does_not_become_safe_retry_or_rollback(state, pins):
failed = finish(begin(state, pins), pins, Outcome.FAILED_AFTER, created=("case-policy",))
with pytest.raises(Refused):
begin(replace(failed, retry_safe=True), pins, retry=True)
with pytest.raises(Refused):
rollback_objects(failed, pins)
def test_rollback_only_created_not_all_reviewed_objects(state, pins):
done = finish(begin(state, pins), pins, Outcome.SUCCESS, created=("case-role",))
assert rollback_objects(done, pins) == ("case-role",)
assert "case-policy" not in rollback_objects(done, pins)
undo = begin_rollback(done, pins)
verified = finish_rollback(undo, pins, verified=True)
assert verified.last.outcome is Outcome.ROLLED_BACK
with pytest.raises(Refused):
begin_rollback(verified, pins)
def test_rollback_reverse_order_and_uncertain_undo_blocks_replay(state, pins):
done = finish(begin(state, pins), pins, Outcome.SUCCESS,
created=("case-policy", "case-role"))
assert rollback_objects(done, pins) == ("case-role", "case-policy")
uncertain = recover(begin_rollback(done, pins))
assert uncertain.last.outcome is Outcome.ROLLBACK_UNCERTAIN
with pytest.raises(Refused):
begin_rollback(uncertain, pins)
def test_unverified_rollback_cannot_claim_success(state, pins):
done = finish(begin(state, pins), pins, Outcome.SUCCESS, created=("case-role",))
failed = finish_rollback(begin_rollback(done, pins), pins, verified=False)
assert failed.last.outcome is Outcome.ROLLBACK_UNCERTAIN
def test_refuses_shared_objects_or_false_no_change_creation(state, pins):
running = begin(state, pins)
for outcome, created in [(Outcome.SUCCESS, ("shared-group",)),
(Outcome.NO_CHANGE, ("case-policy",)),
(Outcome.FAILED_BEFORE, ("case-policy",)),
(Outcome.SUCCESS, ("case-policy", "case-policy"))]:
with pytest.raises(Refused):
finish(running, pins, outcome, created=created)
def test_no_raw_payload_or_output_receipt_interface(state, pins):
with pytest.raises(TypeError):
finish(begin(state, pins), pins, Outcome.SUCCESS, output="sensitive output")
with pytest.raises(Refused):
StepState("step", "owner", pins, ("path with spaces or output",))
with pytest.raises(Refused):
Pins("not-a-digest", pins.case_type, pins.adapter, pins.catalog, pins.acceptance, pins.run_sheet)
def test_out_of_order_result_and_noop_recovery(state, pins):
assert recover(state) is state
with pytest.raises(Refused):
finish(state, pins, Outcome.SUCCESS)
with pytest.raises(Refused):
finish_rollback(state, pins, verified=True)
def test_result_must_be_closed_enum_not_string(state, pins):
with pytest.raises(Refused):
finish(begin(state, pins), pins, "verified-success")

631
tests/test_case_run.py Normal file
View file

@ -0,0 +1,631 @@
"""Injected owner harness; these receipts are not native owner attestations."""
from dataclasses import asdict, replace
import json
import os
import subprocess
import sys
import sqlite3
import pytest
import yaml
from warden.case_receipt_state import Outcome, Refused
from warden.case_run import Adapter, Descriptor, Executor, Journal, OwnerResult, digest
@pytest.fixture
def rig(tmp_path):
desc = Descriptor(
"warden.case.execution.v1",
"mint-secret",
"owner",
"owner/mint@v1",
"sha256:" + "1" * 64,
"human",
"issuer-lane",
"client_id",
"mint",
("case-policy", "case-role"),
True,
("parent-session", "broker-grant"),
)
descriptor = json.dumps(asdict(desc)).encode()
typ = yaml.safe_dump(
{
"spec": {
"id": "test",
"version": "1",
"status": "acknowledged",
"parameters": [
{
"name": "client_id",
"sensitivity": "operational",
"case_settable": True,
}
],
"execution_descriptors": {"mint-secret": digest(descriptor)},
}
}
).encode()
case = yaml.safe_dump(
{
"spec": {
"id": "case-test",
"type": "test@1",
"status": "materialized",
"acceptance": "acceptance",
"parameters": {"client_id": "fixture-client"},
}
}
).encode()
calls = []
def invoke(inv):
calls.append(inv)
return bound(
inv,
OwnerResult(
Outcome.SUCCESS,
"owner-receipt",
("case-policy", "case-role"),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
)
adapter = Adapter(
"owner/mint@v1",
desc.adapter_source,
invoke,
lambda inv, result: result.evidence == "owner-receipt",
lambda inv, objects: bound(
inv,
OwnerResult(
Outcome.ROLLED_BACK,
"owner-receipt",
(),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
),
lambda inv: bound(
inv,
OwnerResult(
Outcome.NO_CHANGE,
"owner-receipt",
(),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
),
)
verified = {
("case-acceptance", digest(case), "acceptance"),
("ops-mason-materialization", digest(case), "materialization"),
("owner-descriptor-acceptance:owner", digest(descriptor), "descriptor"),
("type-machine-seam-acceptance", digest(typ), "descriptor"),
("catalog-routing-acceptance", digest(b"catalog"), "descriptor"),
("caller-session:human:operator:issuer-lane", digest(case), "session"),
("session-revocation:issuer-lane", digest(case), "session-revoked"),
("grant-revocation:issuer-lane", digest(case), "grant-revoked"),
}
def verifier(purpose, source, evidence):
if purpose.startswith(("session-revocation:", "grant-revocation:")):
scope, lane, operation, attempt = purpose.split(":")
return (
source == digest(case)
and lane == "issuer-lane"
and operation in ("run", "rollback", "reconcile")
and evidence
== ("session-" if scope == "session-revocation" else "grant-") + attempt
)
return (purpose, source, evidence) in verified
executor = Executor(
Journal(tmp_path / "private"), {"owner/mint@v1": adapter}, verifier
)
kwargs = dict(
principal="operator",
actor="human",
lane="issuer-lane",
acceptance_evidence="acceptance",
materialization_evidence="materialization",
descriptor_evidence="descriptor",
session_evidence="session",
)
admitted = executor.admit(case, typ, descriptor, b"catalog", **kwargs)
return (executor, admitted, calls, (case, typ, descriptor, b"catalog"), kwargs)
def test_commit_running_is_visible_from_separate_connection_before_dispatch(rig):
executor, args, calls, _, _ = rig
old = executor.adapters[args[0].adapter]
def inspect(inv):
other = sqlite3.connect(executor.journal.directory / "journal.sqlite")
payload = json.loads(
other.execute(
"SELECT payload FROM receipts ORDER BY seq DESC LIMIT 1"
).fetchone()[0]
)
assert payload["receipts"][-1]["outcome"] == "running"
return old.invoke(inv)
executor.adapters[old.reference] = replace(old, invoke=inspect)
assert executor.run(*args).last.outcome is Outcome.SUCCESS
with pytest.raises(Refused):
executor.run(*args)
assert len(calls) == 1
def test_crash_and_fresh_process_resume_never_duplicates_effect(rig):
executor, args, calls, sources, kwargs = rig
old = executor.adapters[args[0].adapter]
def crash(inv):
calls.append(inv)
raise KeyboardInterrupt
executor.adapters[old.reference] = replace(old, invoke=crash)
with pytest.raises(KeyboardInterrupt):
executor.run(*args)
fresh = Executor(
Journal(executor.journal.directory),
{old.reference: old},
executor.evidence_verifier,
)
admitted = fresh.admit(*sources, **kwargs)
with pytest.raises(Refused):
fresh.run(*admitted, retry=True)
state = fresh.journal.load("mint-secret:fixture-client", admitted[2])
assert state.last.outcome is Outcome.UNCERTAIN
assert len(calls) == 1
def test_lost_result_preserved_and_cas_reconciliation_does_not_mint(rig):
executor, args, calls, _, _ = rig
old = executor.adapters[args[0].adapter]
def lost(inv):
calls.append(inv)
raise TimeoutError("SECRET-HIDDEN")
executor.adapters[old.reference] = replace(old, invoke=lost)
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
assert executor.reconcile(*args).last.outcome is Outcome.NO_CHANGE
with pytest.raises(Refused):
executor.run(*args, retry=True)
assert len(calls) == 1
assert (
b"SECRET-HIDDEN"
not in (executor.journal.directory / "journal.sqlite").read_bytes()
)
def test_failed_before_change_requires_explicit_retry_and_safe_owner(rig):
executor, args, calls, _, _ = rig
old = executor.adapters[args[0].adapter]
def no_change(inv):
calls.append(inv)
return bound(
inv,
OwnerResult(
Outcome.FAILED_BEFORE,
"owner-receipt",
(),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
)
executor.adapters[old.reference] = replace(old, invoke=no_change)
assert executor.run(*args).last.outcome is Outcome.FAILED_BEFORE
with pytest.raises(Refused):
executor.run(*args)
assert executor.run(*args, retry=True).last.outcome is Outcome.FAILED_BEFORE
assert len(calls) == 2
@pytest.mark.parametrize(
"result",
[
OwnerResult(
Outcome.SUCCESS,
"owner-receipt",
("shared-policy",),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
OwnerResult(
Outcome.SUCCESS,
"owner-receipt",
(),
False,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
OwnerResult(
Outcome.SUCCESS,
"unknown-receipt",
(),
True,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
{"outcome": "verified-success", "stdout": "SECRET-HIDDEN"},
],
)
def test_unknown_secret_payload_created_scope_or_missing_revocation_never_succeeds(
rig, result, capsys
):
executor, args, _, _, _ = rig
old = executor.adapters[args[0].adapter]
def malicious(inv):
print("SECRET-HIDDEN" * 1000)
return result
executor.adapters[old.reference] = replace(old, invoke=malicious)
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
assert capsys.readouterr().out == ""
assert (
b"SECRET-HIDDEN"
not in (executor.journal.directory / "journal.sqlite").read_bytes()
)
def test_rollback_reverses_only_verified_case_created_objects(rig):
executor, args, _, _, _ = rig
old = executor.adapters[args[0].adapter]
undos = []
def undo(inv, objects):
undos.append(objects)
return old.rollback(inv, objects)
executor.adapters[old.reference] = replace(old, rollback=undo)
executor.run(*args)
assert executor.rollback(*args).last.outcome is Outcome.ROLLED_BACK
assert undos == [("case-role", "case-policy")]
assert "shared-policy" not in undos[0]
def test_uncertain_rollback_cannot_replay(rig):
executor, args, _, _, _ = rig
old = executor.adapters[args[0].adapter]
executor.run(*args)
executor.adapters[old.reference] = replace(
old,
rollback=lambda inv, objs: bound(
inv,
OwnerResult(
Outcome.ROLLED_BACK,
"owner-receipt",
(),
False,
session_revocation_evidence="session-revoked",
grant_revocation_evidence="grant-revoked",
),
),
)
assert executor.rollback(*args).last.outcome is Outcome.ROLLBACK_UNCERTAIN
with pytest.raises(Refused):
executor.rollback(*args)
@pytest.mark.parametrize(
"change", ["materialization", "type-prose", "actor", "lane", "source", "target"]
)
def test_exact_admission_requires_owner_evidence_and_closed_source_pins(rig, change):
executor, _, _, sources, kwargs = rig
case, typ, descriptor, catalog = sources
kwargs = dict(kwargs)
if change == "materialization":
kwargs["materialization_evidence"] = "bare-json"
if change == "type-prose":
obj = yaml.safe_load(typ)
obj["spec"].pop("execution_descriptors")
typ = yaml.safe_dump(obj).encode()
if change == "actor":
kwargs["actor"] = "agent"
if change == "lane":
kwargs["lane"] = "other-lane"
if change == "source":
obj = json.loads(descriptor)
obj["adapter_source"] = "sha256:" + "2" * 64
descriptor = json.dumps(obj).encode()
if change == "target":
obj = yaml.safe_load(case)
obj["spec"]["parameters"]["client_id"] = "other-target"
case = yaml.safe_dump(obj).encode()
with pytest.raises(Refused):
executor.admit(case, typ, descriptor, catalog, **kwargs)
def test_agent_env_cannot_dispatch_after_human_admission(rig, monkeypatch):
executor, args, calls, _, _ = rig
monkeypatch.setenv("WARDEN_AGENT_ID", "test-agent")
with pytest.raises(Refused):
executor.run(*args)
assert calls == []
def test_forged_invocation_cannot_bypass_admission(rig):
executor, args, calls, _, _ = rig
with pytest.raises(Refused):
executor.run(args[0], replace(args[1], target="other-target"), args[2])
assert calls == []
def test_journal_rejects_unsafe_directory_and_symlink(tmp_path):
unsafe = tmp_path / "unsafe"
unsafe.mkdir(mode=493)
with pytest.raises(Refused):
Journal(unsafe)
private = tmp_path / "private"
private.mkdir(mode=448)
(private / "journal.sqlite").symlink_to(tmp_path / "outside")
with pytest.raises(OSError):
Journal(private)
def test_process_lock_blocks_parallel_dispatch(rig):
executor, args, calls, _, _ = rig
with executor.journal.locked():
with pytest.raises(Refused):
executor.run(*args)
assert calls == []
def test_changed_catalog_pins_cannot_inherit_old_success(rig):
executor, args, calls, sources, kwargs = rig
executor.run(*args)
with pytest.raises(Refused):
executor.admit(*sources[:3], b"changed-catalog", **kwargs)
assert len(calls) == 1
def test_private_journal_does_not_retain_owner_payload_or_accept_unknown_schema(rig):
executor, args, _, _, _ = rig
executor.run(*args)
row = executor.journal.db.execute(
"SELECT seq,payload FROM receipts ORDER BY seq DESC LIMIT 1"
).fetchone()
payload = json.loads(row[1])
assert payload["journal_schema"] == "warden.case.step-journal.v1"
assert payload["evidence_digest"] == digest(b"owner-receipt")
assert payload["session_revoked"] is True
assert "owner-receipt" not in row[1]
payload["stdout"] = "UNTRUSTED-OUTPUT"
executor.journal.db.execute(
"UPDATE receipts SET payload=? WHERE seq=?", (json.dumps(payload), row[0])
)
with pytest.raises(Refused):
executor.run(*args)
def test_expired_caller_session_cannot_use_cached_admission(rig):
executor, args, calls, _, _ = rig
old = executor.evidence_verifier
executor.evidence_verifier = lambda purpose, source, evidence: (
False
if purpose.startswith("caller-session:")
else old(purpose, source, evidence)
)
with pytest.raises(Refused):
executor.run(*args)
assert calls == []
@pytest.mark.parametrize("index", [0, 1, 2, 3])
def test_source_sizes_are_bounded_before_parsing(rig, index):
executor, _, _, sources, kwargs = rig
oversized = list(sources)
oversized[index] = b"x" * 262145
with pytest.raises(Refused, match="bounded"):
executor.admit(*oversized, **kwargs)
def test_owner_fd_and_inherited_subprocess_output_is_contained(rig, capfd):
executor, args, _, _, _ = rig
old = executor.adapters[args[0].adapter]
def leaking(inv):
os.write(1, b"SYNTHETIC-SECRET")
os.write(2, b"SYNTHETIC-SECRET")
subprocess.run([sys.executable, "-c", 'print("SYNTHETIC-SECRET")'], check=True)
return old.invoke(inv)
executor.adapters[old.reference] = replace(old, invoke=leaking)
assert executor.run(*args).last.outcome is Outcome.SUCCESS
observed = capfd.readouterr()
assert "SYNTHETIC-SECRET" not in observed.out + observed.err
def test_evidence_provider_output_and_error_never_escape(rig, capfd):
executor, _, calls, sources, kwargs = rig
def broken(*args):
print("SYNTHETIC-SECRET")
os.write(2, b"SYNTHETIC-SECRET")
raise RuntimeError("SYNTHETIC-SECRET")
executor.evidence_verifier = broken
with pytest.raises(Refused) as caught:
executor.admit(*sources, **kwargs)
assert "SYNTHETIC-SECRET" not in str(caught.value)
observed = capfd.readouterr()
assert "SYNTHETIC-SECRET" not in observed.out + observed.err
assert calls == []
@pytest.mark.parametrize("missing", ["session", "grant"])
def test_broker_grant_and_parent_session_have_separate_required_evidence(rig, missing):
executor, args, _, _, _ = rig
old = executor.adapters[args[0].adapter]
result = old.invoke(args[1])
if missing == "session":
result = replace(result, session_revocation_evidence=None)
else:
result = replace(result, grant_revocation_evidence=None)
executor.adapters[old.reference] = replace(old, invoke=lambda inv: result)
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
def test_mint_descriptor_cannot_omit_broker_grant_scope(rig):
descriptor = json.loads(rig[3][2])
descriptor["revocation_scopes"] = ["parent-session"]
with pytest.raises(Refused):
Descriptor.decode(json.dumps(descriptor).encode())
def test_unsafe_owner_step_cannot_retry_even_after_verified_failed_before(
rig, tmp_path
):
executor, args, _, sources, kwargs = rig
descriptor = replace(args[0], retry_safe=False)
descriptor_source = json.dumps(asdict(descriptor)).encode()
type_doc = yaml.safe_load(sources[1])
type_doc["spec"]["execution_descriptors"][descriptor.step] = digest(
descriptor_source
)
type_source = yaml.safe_dump(type_doc).encode()
old_verifier = executor.evidence_verifier
def verifier(purpose, source, evidence):
if purpose == "owner-descriptor-acceptance:owner":
return source == digest(descriptor_source) and evidence == "descriptor"
if purpose == "type-machine-seam-acceptance":
return source == digest(type_source) and evidence == "descriptor"
return old_verifier(purpose, source, evidence)
adapter = executor.adapters[descriptor.adapter]
def failed(inv):
return bound(
inv,
OwnerResult(
Outcome.FAILED_BEFORE,
"owner-receipt",
(),
True,
"session-revoked",
"grant-revoked",
),
)
fresh = Executor(
Journal(tmp_path / "unsafe-retry"),
{adapter.reference: replace(adapter, invoke=failed)},
verifier,
)
admitted = fresh.admit(
sources[0], type_source, descriptor_source, sources[3], **kwargs
)
assert fresh.run(*admitted).last.outcome is Outcome.FAILED_BEFORE
with pytest.raises(Refused):
fresh.run(*admitted, retry=True)
def bound(inv, result):
return replace(
result,
attempt_id=inv.attempt_id,
operation=inv.operation,
session_revocation_evidence=("session-" + inv.attempt_id)
if result.session_revocation_evidence
else None,
grant_revocation_evidence=("grant-" + inv.attempt_id)
if result.grant_revocation_evidence
else None,
)
def test_old_grant_and_session_proofs_cannot_verify_a_new_retry(rig):
executor, args, _, _, _ = rig
adapter = executor.adapters[args[0].adapter]
retained = []
def attempts(inv):
if not retained:
result = bound(
inv,
OwnerResult(
Outcome.FAILED_BEFORE,
"owner-receipt",
(),
True,
"session-revoked",
"grant-revoked",
),
)
retained.append(result)
return result
# Even a caller rewriting labels cannot authenticate prior revocation
# evidence for the newly persisted attempt identity.
return replace(retained[0], attempt_id=inv.attempt_id, operation=inv.operation)
executor.adapters[adapter.reference] = replace(adapter, invoke=attempts)
assert executor.run(*args).last.outcome is Outcome.FAILED_BEFORE
assert executor.run(*args, retry=True).last.outcome is Outcome.UNCERTAIN
def test_owner_interrupt_payload_is_sanitized_with_running_intent_retained(rig, capfd):
executor, args, _, _, _ = rig
adapter = executor.adapters[args[0].adapter]
def interrupted(inv):
os.write(2, b"SYNTHETIC-SECRET")
raise KeyboardInterrupt("SYNTHETIC-SECRET")
executor.adapters[adapter.reference] = replace(adapter, invoke=interrupted)
with pytest.raises(KeyboardInterrupt) as caught:
executor.run(*args)
assert "SYNTHETIC-SECRET" not in str(caught.value)
assert "SYNTHETIC-SECRET" not in "".join(capfd.readouterr())
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.RUNNING
)
def test_attempt_identity_changes_with_exact_catalog_revision_and_new_journal(
rig, tmp_path
):
executor, args, calls, sources, kwargs = rig
executor.run(*args)
original_attempt = calls[-1].attempt_id
new_catalog = b"independently-reviewed-catalog-v2"
old_verifier = executor.evidence_verifier
def verifier(purpose, source, evidence):
if purpose == "catalog-routing-acceptance":
return source == digest(new_catalog) and evidence == "descriptor"
return old_verifier(purpose, source, evidence)
fresh = Executor(Journal(tmp_path / "new-revision"), executor.adapters, verifier)
revised = fresh.admit(*sources[:3], new_catalog, **kwargs)
assert fresh.run(*revised).last.outcome is Outcome.SUCCESS
assert calls[-1].attempt_id != original_attempt
assert calls[-1].pins.catalog != calls[0].pins.catalog
def test_secret_sensitivity_parameter_cannot_reach_admission_or_journal(rig):
executor, _, _, sources, kwargs = rig
type_doc = yaml.safe_load(sources[1])
type_doc["spec"]["parameters"][0]["sensitivity"] = "secret"
source = yaml.safe_dump(type_doc).encode()
old = executor.evidence_verifier
executor.evidence_verifier = lambda purpose, digest_value, evidence: (
True
if purpose == "type-machine-seam-acceptance"
else old(purpose, digest_value, evidence)
)
with pytest.raises(Refused, match="secret-sensitivity"):
executor.admit(sources[0], source, sources[2], sources[3], **kwargs)

View file

@ -1,4 +1,4 @@
"""Layer-model conformance (security-layer-model_v0.4 §5, §11).
"""Layer-model conformance (security-layer-model_v0.8 §5, §11).
Two things are checked here. §11 makes one of them mechanical: every direct
Tooling client maps to a declared shape. §5.2 asks for the other: the conduit's
@ -317,3 +317,35 @@ def test_the_unknown_cell_is_marked_as_a_declared_gap():
assert "WARDEN-WP-0040" in text
else:
assert stance["unknown"] == "fail_closed"
def test_accepted_v08_run_provenance_and_catalog_scope():
checker = _checker()
assert "security-layer-model_v0.8.md" in checker.VALIDATED_AGAINST
assert "sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b" in checker.VALIDATED_AGAINST
assert "single repository ops-warden (catalogued in §4)" in checker.SCOPE
def test_reference_semantics_propagate_identity_through_nested_sequences():
checker = _checker()
for data in (
{"nested": {"standard": ["Security Layer Model v0.8"]}},
{"companion": [["Security Companion 0.3"]]},
{"standard": {"alternate": ["Security Layer Model v0.7"]}},
):
# A dictionary begins a fresh identity key; only sequence descendants
# inherit the immediate standard:/companion: identity in the reference.
if isinstance(data.get("standard"), dict):
assert checker.find_version_pins(data) == []
else:
assert checker.find_version_pins(data), data
assert checker.find_version_pins({"rationale": "Assented at v0.7; migrated at v0.8."}) == []
assert checker.find_version_pins({"intent_version": "0.2.0", "schema_version": "0.1"}) == []
def test_ascii_folding_never_discards_non_ascii_letters():
checker = _checker()
assert checker._fold("Staff") == "staff"
assert checker._fold("Engine") == "engine"
# Deleting non-ASCII bytes would turn this fifth token into Staff.
assert checker._fold("Stαaff") not in checker.LAYER_VOCABULARY

View file

@ -173,7 +173,7 @@ def test_route_find_layer_declaration_resolves_to_reference_playbook():
assert entry.wiki_ref.endswith(
"netkingdom-layer-declaration.md#worker-checklist"
)
assert entry.canon_ref.endswith("security-layer-model_v0.7.md")
assert entry.canon_ref.endswith("security-layer-model_v0.8.md")
assert entry.exec_capable is False

View file

@ -11,11 +11,10 @@ reference declaration artifacts. It does not redefine the layer model.
## Worker checklist
1. Read `net-kingdom/SECURITY-COMPANION.md`, then use the accepted
`net-kingdom/canon/standards/security-layer-model_v0.7.md` for the normative
layer, Tooling-contact, and PEP obligations. `v0.8` is proposed, not accepted,
and its acceptance flip is held on amendments A9–A13 (`GH-DEC-2026-019`) —
but those amendments' substance **already governs** through the decision
record that authorises each of them, so declare to the shape below now.
`net-kingdom/canon/standards/security-layer-model_v0.8.md` for the normative
layer, Tooling-contact, and PEP obligations. Exact publication acceptance is
`net-kingdom/docs/security-layer-v0.8-acceptance.md`. Assent to a previous
boundary is provenance; it is not blanket assent to this current text.
2. **Declare in your own `INTENT.md` frontmatter.** That is the declaration
(`GH-DEC-2026-017` §1). Use ops-warden's `layer.yaml` as the machine-readable
reference *form*, which is a **derived** artifact that must be marked derived,
@ -168,23 +167,23 @@ def find_version_pins(node, where="", identity=False): # walk every parsed key
rule"): **not reached** (`GH-DEC-2026-021` §1, A12 r3). A revision cited in
prose is provenance, not a pin. The reference detector does not flag a bare
`vN.N` outside a `standard:`/`companion:` value, and no allowlist is needed.
A12 r3 is out for assent; if it is rejected in favour of the literal reading,
the reference is widened to any `vN.N` in any value.
Accepted v0.8 carries A12 r3; arbitrary prose versions remain provenance.
**Name the text in force.** While v0.8's acceptance flip is held
(`GH-DEC-2026-019`), `VALIDATED_AGAINST` names the **accepted** text —
`security-layer-model_v0.7.md`, with a net-kingdom commit — plus the decision
records whose rulings the checker enforces beyond v0.7 (`GH-DEC-2026-017`,
`GH-DEC-2026-020`, `GH-DEC-2026-021`), with a gate-house commit recommended
(`GH-DEC-2026-021` §2). Naming the proposed v0.8 states a check against text
that does not yet govern. Change it in the same commit that follows the flip.
ops-warden's constant is the example:
**Name the text in force.** Following the accepted v0.8 flip,
`VALIDATED_AGAINST` names accepted `security-layer-model_v0.8.md` and its exact
publication digest. The output states single-repository scope, the repository's
§4 membership, and the owner-specific declaration/source checks. These are
run provenance, never standard-version pins in standing declarations. The
ops-warden constant is the reference:
```text
net-kingdom/canon/standards/security-layer-model_v0.7.md (net-kingdom@66dc491)
as amended by GH-DEC-2026-017, GH-DEC-2026-020 and GH-DEC-2026-021 (A9-A13, A12 r3; gate-house@39d9287)
net-kingdom/canon/standards/security-layer-model_v0.8.md (accepted; sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)
```
Old versions remain in rejection fixtures and historical review citations.
They must not be replaced mechanically: those fixtures prove that declaration
pins stay forbidden after the normative text changes.
**Citation.** Cite the ruling by the decision's body section: `GH-DEC-2026-017`
§5, statute A12 (now A12 r2). This page's "§5" is canonical
(`GH-DEC-2026-020`); the decision's `rationale:` part numbers are a summary and

View file

@ -9,7 +9,7 @@ flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-09"
updated: "2026-10-11"
depends_on: []
related_repos:
- net-kingdom
@ -142,9 +142,9 @@ reaches the plan unrendered.
```task
id: WARDEN-WP-0044-T03
status: wait
status: progress
depends_on: [WARDEN-WP-0044-T02, MASON-WP-0010-T03]
blocking_reason: "The Forgejo case must be materialized (MASON-WP-0010-T03) before a run can be built and proven against it."
blocking_reason: "Local durable execution kernel and injected failure harness implemented. Native acceptance still requires authenticated owner machine descriptors/adapters, actual Forgejo materialization receipt, contained session integration, ordered verification and first native run."
priority: high
state_hub_task_id: "bcbf6c91-2c04-5c16-ae8c-5d6c06fd6b89"
```
@ -217,3 +217,37 @@ Let a type contribute reviewed catalog entries once, so that every case of that
type routes without a new catalog edit. Cases add parameter values, not routes.
`warden route find` should be able to answer "which SecurityCaseType covers this
need?" before anyone builds by hand.
2026-10-10 consolidation source prototype (not an accepted owner adapter API):
`src/warden/case_receipt_state.py` provides a pure immutable receipt reducer,
with exact case/type/adapter/catalog/acceptance/run-sheet pins, running intent
before dispatch, uncertain recovery after a lost result, no success replay,
explicit retry-safe failed-before-change retry and created-only reverse undo.
It executes nothing and accepts no command output. The caller must durably
persist running before any future side effect; storage/authenticated owner
receipts/live adapters are not implemented. Focused offline tests cover changed
pins, lost receipts, retry/refusal, uncertain rollback and shared-object
preservation. Source proposal: `docs/security-case-execution-proposal.md`.
T03 remains open: typed owner execution/receipt contracts require review, the
Forgejo structural materialization needs owner receipts, and CLI dispatch,
contained sessions, native verification and provenance remain unimplemented.
There is a circularity to resolve with net-kingdom/ops-mason: materialization
cannot require mint-secret if mint-secret is itself a follow-through step whose
planning requires materialized. No case status is changed by this prototype.
2026-10-11: the exact execution-design memo version1 has confirmed native human
acceptance (approval e46b149b-ec3c-4b3b-98a7-dcb4934448ca). The approved proposal
bytes remain unchanged. `src/warden/case_run.py` now implements a closed v1 local
execution seam, independent admission/evidence callbacks, private transactional
SQLite receipt storage, process locking, running-before-dispatch, crash recovery
to uncertainty, explicit safe retry, separately required broker-grant and parent-session revocation
proofs, native owner reconciliation and created-only
reverse rollback. Thirty-seven kernel tests and fourteen reducer tests pass;
ruff checks pass. See `docs/security-case-execution-kernel-v1.md` for exact scope
and receipt provenance. No installed native adapter or live CLI execution is
claimed. Current prose types stay plan-only; acknowledged descriptor/type machine
seams and authenticated owner/caller receipts are still required. T03 progresses
for implementation but cannot finish before ordered native Forgejo acceptance.