docs: preserve reviewed NetKingdom session artifacts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 10s

This commit is contained in:
codex 2026-11-01 23:32:37 +01:00
parent 1fc8eed19c
commit e63355aa4c
50 changed files with 9232 additions and 0 deletions

View file

@ -0,0 +1,53 @@
{
"observed_at": "2026-10-17T06:49:35.014746+00:00",
"bytecode_suppressed": true,
"pytest_cache_disabled": true,
"ruff_cache_disabled": true,
"checks": [
{
"name": "canonical-focused",
"argv": [
"/home/worsch/ops-warden/.venv/bin/python",
"-B",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"tests/test_case_run.py",
"tests/test_case_original_context_consistency.py"
],
"exit_code": 0,
"stdout": "...................................................... [100%]\n54 passed in 1.92s\n",
"stderr": ""
},
{
"name": "canonical-ruff",
"argv": [
"/home/worsch/ops-warden/.venv/bin/ruff",
"check",
"--no-cache",
"src/warden/case_run.py",
"tests/test_case_original_context_consistency.py"
],
"exit_code": 0,
"stdout": "All checks passed!\n",
"stderr": ""
},
{
"name": "canonical-format",
"argv": [
"/home/worsch/ops-warden/.venv/bin/ruff",
"format",
"--check",
"--no-cache",
"src/warden/case_run.py",
"tests/test_case_original_context_consistency.py"
],
"exit_code": 0,
"stdout": "2 files already formatted\n",
"stderr": ""
}
],
"native_calls": false
}

View file

@ -0,0 +1,2 @@
...................................................... [100%]
54 passed in 1.92s

View file

@ -0,0 +1,26 @@
{
"observed_at": "2026-10-17T06:49:09.624471+00:00",
"bytecode_suppressed": true,
"pytest_cache_disabled": true,
"ruff_cache_disabled": true,
"checks": [
{
"name": "canonical-focused",
"argv": [
"/home/worsch/ops-warden/.venv/bin/python",
"-B",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"tests/test_case_run.py",
"tests/test_case_original_context_consistency.py"
],
"exit_code": 2,
"stdout": "\n==================================== ERRORS ====================================\n_______ ERROR collecting tests/test_case_original_context_consistency.py _______\nImportError while importing test module '/home/worsch/ops-warden/tests/test_case_original_context_consistency.py'.\nHint: make sure your test modules/packages have valid Python names.\nTraceback:\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ntests/test_case_original_context_consistency.py:14: in <module>\n from test_case_run import rig as rig\nE ModuleNotFoundError: No module named 'test_case_run'\n=========================== short test summary info ============================\nERROR tests/test_case_original_context_consistency.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.23s\n",
"stderr": ""
}
],
"native_calls": false
}

View file

@ -0,0 +1,92 @@
{
"files": [
{
"path": "after.json",
"sha256": "sha256:f25824546658f48a4faebf7d50ff79ff9d28945441fef93a1574667937a38672"
},
{
"path": "before.json",
"sha256": "sha256:0e7bf27b2a864cd167b6a535b50073036b21e83ad5cddba21d81d140e2d2111f"
},
{
"path": "canonical-checks.json",
"sha256": "sha256:d1919a93949fa7392547ad66fe4719dfe3d02f3d70ba83ee544d4307085f722e"
},
{
"path": "canonical-focused.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "canonical-focused.stdout.txt",
"sha256": "sha256:7ec6e18dc428aca8fd9aa197b47f5f9e8fdb4c3131262723cf4f58cad45c684e"
},
{
"path": "canonical-format.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "canonical-format.stdout.txt",
"sha256": "sha256:3bc53bf3e981a98a34a852e175bf9b77af841edea74fca595d9aedcbaf9a4938"
},
{
"path": "canonical-ruff.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "canonical-ruff.stdout.txt",
"sha256": "sha256:82b3e6a6c090a57601d22943bd23fca9218d1031dbe5a7b754092f9a156b4f18"
},
{
"path": "failed-canonical-collection.json",
"sha256": "sha256:914c95e62018ad5243a780d0522152c23109cb37ec202c611e6e1238a5dd63d1"
},
{
"path": "new-test-import-predecessor.py",
"sha256": "sha256:8483bb5915701e8bd0749e85097e11076ffd539084ca68e9ec1c565babc157d1"
},
{
"path": "owner-followthrough.patch",
"sha256": "sha256:d729c6078a5bc398edd22a12f5589f50f7544e857cd7096157240478af193271"
},
{
"path": "postimages/docs/security-case-execution-kernel-v1.md",
"sha256": "sha256:e5570f542ec3ef4e6c11332cfe48fea16bf90cde5b9a5d926f5a068b7cafdbc7"
},
{
"path": "postimages/src/warden/case_run.py",
"sha256": "sha256:77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768"
},
{
"path": "postimages/tests/test_case_original_context_consistency.py",
"sha256": "sha256:10c5187cbc49222017319249ccb408816497ec6e006859d23003f3b2b375445b"
},
{
"path": "postimages/workplans/WARDEN-WP-0044-security-case-follow-through.md",
"sha256": "sha256:721a90b2ed8c896257c34a1451eb642cb8824c8263e37c483fb61b36adc54088"
},
{
"path": "preimages/docs/security-case-execution-kernel-v1.md",
"sha256": "sha256:0eb563dbaea739fba928d04708c2c2bdac592783bbe68e1271041b0d1b1994cb"
},
{
"path": "preimages/src/warden/case_run.py",
"sha256": "sha256:0574036ca7b74bdb9124f6af4888e63ec231ee60f5c9f34cbe2fb1acee39f671"
},
{
"path": "preimages/workplans/WARDEN-WP-0044-security-case-follow-through.md",
"sha256": "sha256:44686df53dac792e46791878c3ccf8c187bb14e2de33eeac411be2005f7f2aa1"
},
{
"path": "retained-evidence-integrity.json",
"sha256": "sha256:18183540e961551b3e652a08b177658e3bdbd305ff13191537b94db56c2603ea"
},
{
"path": "unrelated-WORK-RECORDS.index",
"sha256": "sha256:c61591efad763a3dcb86751f5bf46f03fec8a162bfe06c4272032fea15d2988f"
},
{
"path": "unrelated-WORK-RECORDS.worktree",
"sha256": "sha256:b6af9b1cb21e3a64b2ef8a5e732bc35c39cdeaef6af7a18f24824e8ec9fb5a76"
}
]
}

View file

@ -0,0 +1,198 @@
"""Original-intent consistency checks with generated metadata only.
Reuse the existing synthetic owner rig. These checks do not provide a native
holder or independently authenticate original creation from local SQL/JSON.
"""
from dataclasses import asdict, replace
import json
from pathlib import Path
import sqlite3
import subprocess
import sys
import pytest
from test_case_run import rig as rig
from warden.case_receipt_state import Outcome, Refused
from warden.case_run import OriginalIntent
def interrupted(rig):
executor, args, calls, sources, kwargs = rig
old = executor.adapters[args[0].adapter]
seen = []
def lost(inv):
calls.append(inv)
raise TimeoutError("generated lost response")
def original(ctx):
seen.append(ctx)
return old.reconcile(ctx.attempt)
executor.adapters[old.reference] = replace(
old, invoke=lost, reconcile=None, reconcile_original=original
)
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
return executor, args, calls, old, seen
def alter_payload(executor, **changes):
row = executor.journal.db.execute(
"SELECT seq,payload FROM intents ORDER BY seq DESC LIMIT 1"
).fetchone()
obj = json.loads(row[1])
obj.update(changes)
executor.journal.db.execute(
"UPDATE intents SET payload=? WHERE seq=?", (json.dumps(obj), row[0])
)
def test_original_actor_mismatch_is_refused(rig):
executor, args, calls, old, _ = interrupted(rig)
alter_payload(executor, actor="agent")
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_payload_attempt_id_must_match_sql_column_and_original_receipt(rig):
executor, args, calls, old, _ = interrupted(rig)
actual = calls[0].attempt_id
alter_payload(executor, attempt_id="attempt-unrelated")
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
assert (
executor.journal.db.execute("SELECT attempt_id FROM intents").fetchone()[0]
== actual
)
def test_orphan_intent_without_original_running_receipt_is_refused(rig):
executor, args, calls, old, _ = interrupted(rig)
executor.journal.db.execute(
"DELETE FROM receipts WHERE seq=(SELECT MIN(seq) FROM receipts)"
)
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_serializer_refuses_arbitrary_values_outside_normal_admit(rig):
_, args, _, _, _ = rig
inv = replace(
args[1],
target={"unexpected": "synthetic-owner-payload"},
operation="run",
attempt_id="attempt-fixture",
)
record = OriginalIntent("mint-secret:fixture-client", inv, "sha256:" + "1" * 64)
with pytest.raises(Refused):
OriginalIntent.decode(record.payload())
# This is a direct serialization API gap, not an observed normal-admit leak.
# Owner trust is the existing synthetic fixture; this proves call shape only.
@pytest.mark.parametrize(
"field,value",
[
("principal", "another-person"),
("tenant", "another-tenant"),
("realm", "another-realm"),
],
)
def test_existing_lookup_does_refuse_stored_principal_tenant_realm_mismatch(
rig, field, value
):
executor, args, _, old, _ = interrupted(rig)
alter_payload(executor, **{field: value})
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_existing_atomic_insert_failure_prevents_dispatch_and_both_records(rig):
executor, args, calls, _, _ = rig
executor.journal.db.execute(
"CREATE TRIGGER fail_receipt BEFORE INSERT ON receipts BEGIN SELECT RAISE(ABORT,'fixture'); END"
)
with pytest.raises(sqlite3.IntegrityError):
executor.run(*args)
assert not calls
assert executor.journal.db.execute("SELECT COUNT(*) FROM intents").fetchone() == (
0,
)
assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
0,
)
def test_existing_missing_original_refuses_before_callback(rig):
executor, args, _, _, seen = interrupted(rig)
executor.journal.db.execute("DELETE FROM intents")
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
def test_existing_current_parent_revocation_remains_mandatory(rig):
executor, args, _, _, _ = interrupted(rig)
old = executor.evidence_verifier
def refuse_session(purpose, source, evidence):
return (
False
if purpose.startswith("session-revocation:")
else old(purpose, source, evidence)
)
executor.evidence_verifier = refuse_session
with pytest.raises(Refused):
executor.reconcile(*args)
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
def test_existing_intent_survives_actual_distinct_interpreter(rig):
executor, args, calls, old, _ = interrupted(rig)
code = """import json,sys
from pathlib import Path
from warden.case_run import Journal,Invocation
from warden.case_receipt_state import Pins
obj=json.load(sys.stdin);obj['pins']=Pins(**obj['pins']);j=Journal(Path(sys.argv[1]))
with j.locked():
i=j.original_intent('mint-secret:fixture-client',Invocation(**obj),sys.argv[2])
print(json.dumps({'attempt':i.invocation.attempt_id,'operation':i.invocation.operation}))
"""
result = subprocess.run(
[sys.executable, "-B", "-c", code, str(executor.journal.directory), old.source],
input=json.dumps(asdict(args[1])),
text=True,
capture_output=True,
timeout=15,
env={
"PATH": "/usr/bin:/bin",
"PYTHONPATH": str(Path(__file__).parents[1] / "src"),
"PYTHONDONTWRITEBYTECODE": "1",
},
check=True,
)
assert json.loads(result.stdout) == {
"attempt": calls[0].attempt_id,
"operation": "run",
}

View file

@ -0,0 +1,515 @@
--- a/src/warden/case_run.py
+++ b/src/warden/case_run.py
@@ -180,16 +180,76 @@
"""Durable record of the original mutating dispatch, written before dispatch.
Binds the original Invocation (attempt, step, owner, target, actor, principal,
- lane), all pins, the admitted adapter source and the tenant/realm provenance
- the caller was admitted with. It is evidence of what Warden dispatched, not
- owner proof of creation: owners still authenticate created-only provenance.
+ lane), all pins and the admitted adapter source. Current admit leaves tenant
+ and realm empty; their reserved fields are not admitted provenance. This is
+ local dispatch correspondence, not owner proof of creation. Owners still
+ authenticate original created-only provenance independently.
"""
binding: str
invocation: Invocation
source: str
+ def validate(self) -> None:
+ """Fixed bounded metadata only; tenant/realm are not authority claims.
+
+ Current admit leaves tenant/realm empty. Nonempty future coordinates need
+ separate owner provenance; serializability alone must never supply it.
+ """
+ if (
+ type(self.invocation) is not Invocation
+ or type(self.invocation.pins) is not Pins
+ ):
+ raise Refused("exact original invocation required")
+ i = self.invocation
+ fields = (i.step, i.owner, i.target, i.principal, i.lane, i.attempt_id)
+ if (
+ not all(
+ type(value) is str and IDENTIFIER.fullmatch(value) for value in fields
+ )
+ or type(i.actor) is not str
+ or i.actor not in ("human", "agent")
+ or type(i.operation) is not str
+ or i.operation not in ("run", "rollback")
+ or not all(
+ type(value) is str and (not value or IDENTIFIER.fullmatch(value))
+ for value in (i.tenant, i.realm)
+ )
+ or type(self.source) is not str
+ or not DIGEST.fullmatch(self.source)
+ or type(self.binding) is not str
+ or self.binding != f"{i.step}:{i.target}"
+ ):
+ raise Refused("invalid original intent metadata")
+ if set(Invocation.__dataclass_fields__) != {
+ "step",
+ "owner",
+ "target",
+ "actor",
+ "principal",
+ "lane",
+ "pins",
+ "attempt_id",
+ "operation",
+ "tenant",
+ "realm",
+ }:
+ raise Refused("original invocation field contract changed")
+ if not all(
+ type(v) is str and DIGEST.fullmatch(v)
+ for v in (
+ i.pins.case,
+ i.pins.case_type,
+ i.pins.adapter,
+ i.pins.catalog,
+ i.pins.acceptance,
+ i.pins.run_sheet,
+ )
+ ):
+ raise Refused("invalid original intent pins")
+
def payload(self) -> str:
+ self.validate()
return json.dumps(
{"schema": INTENT_SCHEMA, "binding": self.binding, "source": self.source}
| asdict(self.invocation),
@@ -199,13 +259,44 @@
@classmethod
def decode(cls, raw: str) -> OriginalIntent:
- obj = json.loads(raw)
- fields = {f for f in Invocation.__dataclass_fields__}
- if set(obj) != fields | {"schema", "binding", "source"} or obj["schema"] != INTENT_SCHEMA:
- raise Refused("unknown original intent schema")
- inv = {k: obj[k] for k in fields}
- inv["pins"] = Pins(**inv["pins"])
- return cls(obj["binding"], Invocation(**inv), obj["source"])
+ def unique(pairs):
+ result = {}
+ for key, value in pairs:
+ if key in result:
+ raise Refused("duplicate original intent field")
+ result[key] = value
+ return result
+
+ try:
+ if type(raw) is not str or len(raw.encode()) > MAX_SOURCE_BYTES:
+ raise Refused("original intent input exceeds bound")
+ obj = json.loads(raw, object_pairs_hook=unique)
+ fields = {
+ "step",
+ "owner",
+ "target",
+ "actor",
+ "principal",
+ "lane",
+ "pins",
+ "attempt_id",
+ "operation",
+ "tenant",
+ "realm",
+ }
+ if (
+ type(obj) is not dict
+ or set(obj) != fields | {"schema", "binding", "source"}
+ or obj["schema"] != INTENT_SCHEMA
+ ):
+ raise Refused("unknown original intent schema")
+ inv = {k: obj[k] for k in fields}
+ inv["pins"] = Pins(**inv["pins"])
+ result = cls(obj["binding"], Invocation(**inv), obj["source"])
+ result.validate()
+ return result
+ except (TypeError, ValueError, KeyError):
+ raise Refused("invalid original intent record") from None
@dataclass(frozen=True, slots=True)
@@ -256,28 +347,76 @@
"CREATE TABLE IF NOT EXISTS intents (seq INTEGER PRIMARY KEY, attempt_id TEXT NOT NULL UNIQUE, binding TEXT NOT NULL, payload TEXT NOT NULL)"
)
- def original_intent(self, binding: str, current: Invocation, source: str) -> OriginalIntent:
+ def original_intent(
+ self, binding: str, current: Invocation, source: str
+ ) -> OriginalIntent:
"""Latest original run intent for binding, refused unless it matches exactly.
Missing or mismatched records refuse; the step stays uncertain.
"""
row = self.db.execute(
- "SELECT payload FROM intents WHERE binding=? ORDER BY seq DESC LIMIT 1",
+ "SELECT attempt_id,payload FROM intents WHERE binding=? ORDER BY seq DESC LIMIT 1",
(binding,),
).fetchone()
if row is None:
raise Refused("original intent record missing; step stays uncertain")
- intent = OriginalIntent.decode(row[0])
+ intent = OriginalIntent.decode(row[1])
o = intent.invocation
+ if row[0] != o.attempt_id:
+ raise Refused("original intent attempt identity differs")
if intent.binding != binding or o.operation != "run":
raise Refused("original intent binding mismatch")
if o.pins != current.pins:
raise Refused("original intent pins mismatch")
if intent.source != source:
raise Refused("original intent source mismatch")
- for name in ("step", "owner", "target", "principal", "lane", "tenant", "realm"):
+ for name in (
+ "step",
+ "owner",
+ "target",
+ "actor",
+ "principal",
+ "lane",
+ "tenant",
+ "realm",
+ ):
if getattr(o, name) != getattr(current, name):
raise Refused(f"original intent {name} mismatch")
+ # Match the persisted original running receipt. An orphan context or a
+ # payload from another attempt cannot become the current lookup key.
+ receipts = self.db.execute(
+ "SELECT payload FROM receipts WHERE binding=? ORDER BY seq DESC LIMIT ?",
+ (binding, MAX_HISTORY + 1),
+ ).fetchall()
+ if len(receipts) > MAX_HISTORY:
+ raise Refused("original intent receipt history exceeds bound")
+ matched = False
+ try:
+ for (raw,) in receipts:
+ if type(raw) is not str or len(raw.encode()) > MAX_SOURCE_BYTES:
+ raise Refused("original receipt input exceeds bound")
+ payload = json.loads(raw)
+ if (
+ payload.get("attempt_id") != o.attempt_id
+ or payload.get("operation") != "run"
+ ):
+ continue
+ last = payload.get("receipts", [])[-1]
+ if last.get("outcome") != "running":
+ continue
+ if (
+ payload.get("journal_schema") != "warden.case.step-journal.v1"
+ or payload.get("step_id") != o.step
+ or payload.get("owner") != o.owner
+ or payload.get("pins") != asdict(o.pins)
+ ):
+ raise Refused("original running receipt binding differs")
+ matched = True
+ break
+ except (TypeError, ValueError, KeyError, IndexError, AttributeError):
+ raise Refused("invalid original running receipt") from None
+ if not matched:
+ raise Refused("original running receipt missing; no backfill")
return intent
@contextmanager
@@ -355,6 +494,23 @@
invocation: Invocation | None = None,
intent: OriginalIntent | None = None,
):
+ if intent is not None:
+ if type(intent) is not OriginalIntent:
+ raise Refused("exact original intent required")
+ intent.validate()
+ expected = (
+ Outcome.RUNNING
+ if intent.invocation.operation == "run"
+ else Outcome.ROLLBACK_RUNNING
+ )
+ if (
+ intent.binding != binding
+ or intent.invocation != invocation
+ or intent.invocation.pins != state.pins
+ or not state.last
+ or state.last.outcome is not expected
+ ):
+ raise Refused("original intent and running receipt differ")
self.db.execute("BEGIN IMMEDIATE")
try:
if intent is not None:
--- /dev/null
+++ b/tests/test_case_original_context_consistency.py
@@ -0,0 +1,198 @@
+"""Original-intent consistency checks with generated metadata only.
+
+Reuse the existing synthetic owner rig. These checks do not provide a native
+holder or independently authenticate original creation from local SQL/JSON.
+"""
+
+from dataclasses import asdict, replace
+import json
+from pathlib import Path
+import sqlite3
+import subprocess
+import sys
+import pytest
+from tests.test_case_run import rig as rig
+from warden.case_receipt_state import Outcome, Refused
+from warden.case_run import OriginalIntent
+
+
+def interrupted(rig):
+ executor, args, calls, sources, kwargs = rig
+ old = executor.adapters[args[0].adapter]
+ seen = []
+
+ def lost(inv):
+ calls.append(inv)
+ raise TimeoutError("generated lost response")
+
+ def original(ctx):
+ seen.append(ctx)
+ return old.reconcile(ctx.attempt)
+
+ executor.adapters[old.reference] = replace(
+ old, invoke=lost, reconcile=None, reconcile_original=original
+ )
+ assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
+ return executor, args, calls, old, seen
+
+
+def alter_payload(executor, **changes):
+ row = executor.journal.db.execute(
+ "SELECT seq,payload FROM intents ORDER BY seq DESC LIMIT 1"
+ ).fetchone()
+ obj = json.loads(row[1])
+ obj.update(changes)
+ executor.journal.db.execute(
+ "UPDATE intents SET payload=? WHERE seq=?", (json.dumps(obj), row[0])
+ )
+
+
+def test_original_actor_mismatch_is_refused(rig):
+ executor, args, calls, old, _ = interrupted(rig)
+ alter_payload(executor, actor="agent")
+ with pytest.raises(Refused):
+ executor.journal.original_intent(
+ "mint-secret:fixture-client", args[1], old.source
+ )
+
+
+def test_payload_attempt_id_must_match_sql_column_and_original_receipt(rig):
+ executor, args, calls, old, _ = interrupted(rig)
+ actual = calls[0].attempt_id
+ alter_payload(executor, attempt_id="attempt-unrelated")
+ with pytest.raises(Refused):
+ executor.journal.original_intent(
+ "mint-secret:fixture-client", args[1], old.source
+ )
+ assert (
+ executor.journal.db.execute("SELECT attempt_id FROM intents").fetchone()[0]
+ == actual
+ )
+
+
+def test_orphan_intent_without_original_running_receipt_is_refused(rig):
+ executor, args, calls, old, _ = interrupted(rig)
+ executor.journal.db.execute(
+ "DELETE FROM receipts WHERE seq=(SELECT MIN(seq) FROM receipts)"
+ )
+ assert (
+ executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
+ is Outcome.UNCERTAIN
+ )
+ with pytest.raises(Refused):
+ executor.journal.original_intent(
+ "mint-secret:fixture-client", args[1], old.source
+ )
+
+
+def test_serializer_refuses_arbitrary_values_outside_normal_admit(rig):
+ _, args, _, _, _ = rig
+ inv = replace(
+ args[1],
+ target={"unexpected": "synthetic-owner-payload"},
+ operation="run",
+ attempt_id="attempt-fixture",
+ )
+ record = OriginalIntent("mint-secret:fixture-client", inv, "sha256:" + "1" * 64)
+ with pytest.raises(Refused):
+ OriginalIntent.decode(record.payload())
+ # This is a direct serialization API gap, not an observed normal-admit leak.
+
+ # Owner trust is the existing synthetic fixture; this proves call shape only.
+
+
+@pytest.mark.parametrize(
+ "field,value",
+ [
+ ("principal", "another-person"),
+ ("tenant", "another-tenant"),
+ ("realm", "another-realm"),
+ ],
+)
+def test_existing_lookup_does_refuse_stored_principal_tenant_realm_mismatch(
+ rig, field, value
+):
+ executor, args, _, old, _ = interrupted(rig)
+ alter_payload(executor, **{field: value})
+ with pytest.raises(Refused):
+ executor.journal.original_intent(
+ "mint-secret:fixture-client", args[1], old.source
+ )
+
+
+def test_existing_atomic_insert_failure_prevents_dispatch_and_both_records(rig):
+ executor, args, calls, _, _ = rig
+ executor.journal.db.execute(
+ "CREATE TRIGGER fail_receipt BEFORE INSERT ON receipts BEGIN SELECT RAISE(ABORT,'fixture'); END"
+ )
+ with pytest.raises(sqlite3.IntegrityError):
+ executor.run(*args)
+ assert not calls
+ assert executor.journal.db.execute("SELECT COUNT(*) FROM intents").fetchone() == (
+ 0,
+ )
+ assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
+ 0,
+ )
+
+
+def test_existing_missing_original_refuses_before_callback(rig):
+ executor, args, _, _, seen = interrupted(rig)
+ executor.journal.db.execute("DELETE FROM intents")
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert not seen
+ assert (
+ executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
+ is Outcome.UNCERTAIN
+ )
+
+
+def test_existing_current_parent_revocation_remains_mandatory(rig):
+ executor, args, _, _, _ = interrupted(rig)
+ old = executor.evidence_verifier
+
+ def refuse_session(purpose, source, evidence):
+ return (
+ False
+ if purpose.startswith("session-revocation:")
+ else old(purpose, source, evidence)
+ )
+
+ executor.evidence_verifier = refuse_session
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert (
+ executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
+ is Outcome.UNCERTAIN
+ )
+
+
+def test_existing_intent_survives_actual_distinct_interpreter(rig):
+ executor, args, calls, old, _ = interrupted(rig)
+ code = """import json,sys
+from pathlib import Path
+from warden.case_run import Journal,Invocation
+from warden.case_receipt_state import Pins
+obj=json.load(sys.stdin);obj['pins']=Pins(**obj['pins']);j=Journal(Path(sys.argv[1]))
+with j.locked():
+ i=j.original_intent('mint-secret:fixture-client',Invocation(**obj),sys.argv[2])
+ print(json.dumps({'attempt':i.invocation.attempt_id,'operation':i.invocation.operation}))
+"""
+ result = subprocess.run(
+ [sys.executable, "-B", "-c", code, str(executor.journal.directory), old.source],
+ input=json.dumps(asdict(args[1])),
+ text=True,
+ capture_output=True,
+ timeout=15,
+ env={
+ "PATH": "/usr/bin:/bin",
+ "PYTHONPATH": str(Path(__file__).parents[1] / "src"),
+ "PYTHONDONTWRITEBYTECODE": "1",
+ },
+ check=True,
+ )
+ assert json.loads(result.stdout) == {
+ "attempt": calls[0].attempt_id,
+ "operation": "run",
+ }
--- a/docs/security-case-execution-kernel-v1.md
+++ b/docs/security-case-execution-kernel-v1.md
@@ -125,3 +125,30 @@
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.
+
+
+## Original-intent consistency boundary — 2026-10-17
+
+`OriginalIntent` and the optional additive
+`reconcile_original(ReconcileContext(original, attempt))` callback retain the
+original run separately from a fresh reconciliation attempt. Existing one-argument
+reconcile adapters remain source-compatible. Intent and pre-dispatch receipt are
+committed in one SQLite transaction; lookup now requires their exact attempt,
+actor, step/owner/target, source and pin correspondence. Missing legacy context,
+an orphan receipt, changed context or an invalid bounded field contract refuses;
+there is no backfill or new runtime holder.
+
+Original intent serialization accepts only the fixed reviewed Invocation/Pins
+field contract and bounded operational identifiers. Duplicate, oversized and
+malformed original records refuse. Current Executor.admit leaves tenant and realm
+empty: those reserved fields are not admitted tenant/realm provenance, and future
+nonempty coordinates need an authoritative owner contract. Raw owner payloads,
+credentials, parameters and caller output are not part of this context contract.
+
+Local dispatch correspondence is not independent owner proof. A real owner must
+still authenticate the exact original created-only result and original revocation,
+current native revision and the fresh attempt's session/grant evidence before
+recovery can count. No actual original holder/verifier or native adapter is added.
+NetKingdom's current registration adapter still has no reconciliation callback;
+source tests do not authorize its post-restart undo, replay or a changed caller/
+session/run-sheet migration. WARDEN-WP-0044-T03 remains a native acceptance gate.
--- a/workplans/WARDEN-WP-0044-security-case-follow-through.md
+++ b/workplans/WARDEN-WP-0044-security-case-follow-through.md
@@ -168,13 +168,36 @@
tenant, realm — plus admitted adapter source) in the same SQLite transaction as
the pre-dispatch journal row. `Journal.original_intent(binding, current, source)`
returns it after restart and refuses on a missing record or any
-pins/source/step/owner/target/principal/lane/tenant/realm mismatch, before any
+pins/source/step/owner/target/actor/principal/lane/tenant/realm mismatch, before any
journal write, so the step stays uncertain. An adapter's
`reconcile_original(ReconcileContext(original, attempt))` receives the original
intent separately from the new reconcile attempt. This is Warden's record of what
-it dispatched, not owner created-only provenance; tenant/realm are as supplied at
-admission. Owner descriptors/adapters, contained session integration, Forgejo
+it dispatched, not owner created-only provenance. Current Executor.admit leaves
+tenant/realm empty; the reserved coordinates establish no admitted provenance.
+Owner descriptors/adapters, contained session integration, Forgejo
materialization and the native run remain open.
+
+2026-10-17 source consistency follow-through: retained the additive intents table
+and reconcile_original callback and fixed four reproduced local defects: original
+actor mismatch, payload/SQL attempt-ID mismatch, orphan intent without its
+matching original running receipt, and arbitrary nonmetadata values accepted by
+the original-intent serializer. Exact bounded field validation, duplicate-free
+bounded decode and save-time intent/invocation/running-state equality now preserve
+the original receipt linkage before owner lookup. No new context store, native
+verifier, trust root, adapter activation or authority migration was introduced.
+
+The actual canonical focused kernel and new consistency suite passes 54 checks;
+owner-installed Ruff check/format passes. The existing generated-data/synthetic
+owner fixture and a distinct interpreter prove source correspondence/refusal,
+not independent native custody or creation. Earlier comparison probes and failed
+preparation are retained. Current caller and fresh session/grant revocation checks
+remain required; original owner payload/revocation, exact native readback and
+created-only provenance still need independent authenticated owner admission.
+No JSON/SQL context, unknown tenant/realm, changed run-sheet or legacy missing
+intent supplies recovery, replay or undo authority. T03 remains progress and all
+native acceptance gates/statuses/dependencies/UUIDs remain unchanged. Evidence:
+Custodian original-context-compare-r2 and original-context-owner-followthrough-r3
+under docs/changes/netkingdom-target-2026-10-17/; publication/projection is separate.
Agreed with railiance-platform (RPF-WP-0054-T01, 2026-10-09):
- `mint-secret` runs `credential.py generate --grant keycape/client-secret-generate

View file

@ -0,0 +1,154 @@
# 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.
## Browser input projection — 2026-10-16 source preparation
An explicitly configured `BrowserInputs(netkingdom_root)` now validates browser
cases before admission. It calls the real NetKingdom type/case validator over
verified producer bytes in a disposable Python process (`-I`, empty environment,
timeout, no inherited output). The validator, composer, capability contract and lazy evidence
modules have exact source pins; missing or changed code refuses. There is no
automatic discovery or download. A producer upgrade needs reviewed pin changes.
Only the exact reviewed 1.1/1.2 browser parameter definitions qualify. A digest
of each complete parameter definition list prevents reclassifying secrets as
metadata, loosening constraints, or adding fields under a recognized version.
The complete case passes producer validation, including required fields,
constraints and credential-shape checks. Explicit fixed/default overrides,
unknown fields and secret references refuse. Security-sensitive browser metadata
can therefore be present without being passed to an owner adapter: the only
projection is its descriptor's exact public/operational target identifier.
No raw parameter values are added to invocations or persisted receipts.
`require_implemented=False` validates the acknowledged type's source, not its
execution authority. Existing descriptor/type/source/catalog/caller provenance
gates stay mandatory. Neither historical browser type contains the required
exact machine-descriptor map, and neither is promoted by this change. There are
still no installed native owner adapters, authenticated provenance verifier,
contained session/ordered run orchestration or live CLI dispatch. A journal digest
is a fingerprint and cannot authenticate native evidence. Completion still needs
owner-admitted descriptors and actual structural, mint/delivery, consumer and
revocation/rollback evidence. Local fixture success is not native acceptance.
Tests use the actual producer checkout (override sibling location with
`WARDEN_TEST_NETKINGDOM_ROOT`), the real Executor, SQLite journal and injected
test-only owner adapter/evidence verifier. Their descriptor extensions and case
materialized labels exist only in memory. Canonical case/type bytes stay intact.
Producer integration refinement: the accepted NetKingdom browser-case patch
`d32a7f51cbf3012bee3e4bb329f4259dea867eff1c59a2b01a65d72774488d05`
is now the tested producer revision. Its evidence module is imported lazily only
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.
## Original-intent consistency boundary — 2026-10-17
`OriginalIntent` and the optional additive
`reconcile_original(ReconcileContext(original, attempt))` callback retain the
original run separately from a fresh reconciliation attempt. Existing one-argument
reconcile adapters remain source-compatible. Intent and pre-dispatch receipt are
committed in one SQLite transaction; lookup now requires their exact attempt,
actor, step/owner/target, source and pin correspondence. Missing legacy context,
an orphan receipt, changed context or an invalid bounded field contract refuses;
there is no backfill or new runtime holder.
Original intent serialization accepts only the fixed reviewed Invocation/Pins
field contract and bounded operational identifiers. Duplicate, oversized and
malformed original records refuse. Current Executor.admit leaves tenant and realm
empty: those reserved fields are not admitted tenant/realm provenance, and future
nonempty coordinates need an authoritative owner contract. Raw owner payloads,
credentials, parameters and caller output are not part of this context contract.
Local dispatch correspondence is not independent owner proof. A real owner must
still authenticate the exact original created-only result and original revocation,
current native revision and the fresh attempt's session/grant evidence before
recovery can count. No actual original holder/verifier or native adapter is added.
NetKingdom's current registration adapter still has no reconciliation callback;
source tests do not authorize its post-restart undo, replay or a changed caller/
session/run-sheet migration. WARDEN-WP-0044-T03 remains a native acceptance gate.

View file

@ -0,0 +1,982 @@
"""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_inputs import BrowserInputs
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"
tenant: str = ""
realm: str = ""
INTENT_SCHEMA = "warden.case.original-intent.v1"
@dataclass(frozen=True, slots=True)
class OriginalIntent:
"""Durable record of the original mutating dispatch, written before dispatch.
Binds the original Invocation (attempt, step, owner, target, actor, principal,
lane), all pins and the admitted adapter source. Current admit leaves tenant
and realm empty; their reserved fields are not admitted provenance. This is
local dispatch correspondence, not owner proof of creation. Owners still
authenticate original created-only provenance independently.
"""
binding: str
invocation: Invocation
source: str
def validate(self) -> None:
"""Fixed bounded metadata only; tenant/realm are not authority claims.
Current admit leaves tenant/realm empty. Nonempty future coordinates need
separate owner provenance; serializability alone must never supply it.
"""
if (
type(self.invocation) is not Invocation
or type(self.invocation.pins) is not Pins
):
raise Refused("exact original invocation required")
i = self.invocation
fields = (i.step, i.owner, i.target, i.principal, i.lane, i.attempt_id)
if (
not all(
type(value) is str and IDENTIFIER.fullmatch(value) for value in fields
)
or type(i.actor) is not str
or i.actor not in ("human", "agent")
or type(i.operation) is not str
or i.operation not in ("run", "rollback")
or not all(
type(value) is str and (not value or IDENTIFIER.fullmatch(value))
for value in (i.tenant, i.realm)
)
or type(self.source) is not str
or not DIGEST.fullmatch(self.source)
or type(self.binding) is not str
or self.binding != f"{i.step}:{i.target}"
):
raise Refused("invalid original intent metadata")
if set(Invocation.__dataclass_fields__) != {
"step",
"owner",
"target",
"actor",
"principal",
"lane",
"pins",
"attempt_id",
"operation",
"tenant",
"realm",
}:
raise Refused("original invocation field contract changed")
if not all(
type(v) is str and DIGEST.fullmatch(v)
for v in (
i.pins.case,
i.pins.case_type,
i.pins.adapter,
i.pins.catalog,
i.pins.acceptance,
i.pins.run_sheet,
)
):
raise Refused("invalid original intent pins")
def payload(self) -> str:
self.validate()
return json.dumps(
{"schema": INTENT_SCHEMA, "binding": self.binding, "source": self.source}
| asdict(self.invocation),
separators=(",", ":"),
sort_keys=True,
)
@classmethod
def decode(cls, raw: str) -> OriginalIntent:
def unique(pairs):
result = {}
for key, value in pairs:
if key in result:
raise Refused("duplicate original intent field")
result[key] = value
return result
try:
if type(raw) is not str or len(raw.encode()) > MAX_SOURCE_BYTES:
raise Refused("original intent input exceeds bound")
obj = json.loads(raw, object_pairs_hook=unique)
fields = {
"step",
"owner",
"target",
"actor",
"principal",
"lane",
"pins",
"attempt_id",
"operation",
"tenant",
"realm",
}
if (
type(obj) is not dict
or set(obj) != fields | {"schema", "binding", "source"}
or obj["schema"] != INTENT_SCHEMA
):
raise Refused("unknown original intent schema")
inv = {k: obj[k] for k in fields}
inv["pins"] = Pins(**inv["pins"])
result = cls(obj["binding"], Invocation(**inv), obj["source"])
result.validate()
return result
except (TypeError, ValueError, KeyError):
raise Refused("invalid original intent record") from None
@dataclass(frozen=True, slots=True)
class ReconcileContext:
"""Original intent and the new reconciliation attempt, kept separate."""
original: OriginalIntent
attempt: Invocation
@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
reconcile_original: Callable[[ReconcileContext], 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)"
)
self.db.execute(
"CREATE TABLE IF NOT EXISTS intents (seq INTEGER PRIMARY KEY, attempt_id TEXT NOT NULL UNIQUE, binding TEXT NOT NULL, payload TEXT NOT NULL)"
)
def original_intent(
self, binding: str, current: Invocation, source: str
) -> OriginalIntent:
"""Latest original run intent for binding, refused unless it matches exactly.
Missing or mismatched records refuse; the step stays uncertain.
"""
row = self.db.execute(
"SELECT attempt_id,payload FROM intents WHERE binding=? ORDER BY seq DESC LIMIT 1",
(binding,),
).fetchone()
if row is None:
raise Refused("original intent record missing; step stays uncertain")
intent = OriginalIntent.decode(row[1])
o = intent.invocation
if row[0] != o.attempt_id:
raise Refused("original intent attempt identity differs")
if intent.binding != binding or o.operation != "run":
raise Refused("original intent binding mismatch")
if o.pins != current.pins:
raise Refused("original intent pins mismatch")
if intent.source != source:
raise Refused("original intent source mismatch")
for name in (
"step",
"owner",
"target",
"actor",
"principal",
"lane",
"tenant",
"realm",
):
if getattr(o, name) != getattr(current, name):
raise Refused(f"original intent {name} mismatch")
# Match the persisted original running receipt. An orphan context or a
# payload from another attempt cannot become the current lookup key.
receipts = self.db.execute(
"SELECT payload FROM receipts WHERE binding=? ORDER BY seq DESC LIMIT ?",
(binding, MAX_HISTORY + 1),
).fetchall()
if len(receipts) > MAX_HISTORY:
raise Refused("original intent receipt history exceeds bound")
matched = False
try:
for (raw,) in receipts:
if type(raw) is not str or len(raw.encode()) > MAX_SOURCE_BYTES:
raise Refused("original receipt input exceeds bound")
payload = json.loads(raw)
if (
payload.get("attempt_id") != o.attempt_id
or payload.get("operation") != "run"
):
continue
last = payload.get("receipts", [])[-1]
if last.get("outcome") != "running":
continue
if (
payload.get("journal_schema") != "warden.case.step-journal.v1"
or payload.get("step_id") != o.step
or payload.get("owner") != o.owner
or payload.get("pins") != asdict(o.pins)
):
raise Refused("original running receipt binding differs")
matched = True
break
except (TypeError, ValueError, KeyError, IndexError, AttributeError):
raise Refused("invalid original running receipt") from None
if not matched:
raise Refused("original running receipt missing; no backfill")
return intent
@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,
intent: OriginalIntent | None = None,
):
if intent is not None:
if type(intent) is not OriginalIntent:
raise Refused("exact original intent required")
intent.validate()
expected = (
Outcome.RUNNING
if intent.invocation.operation == "run"
else Outcome.ROLLBACK_RUNNING
)
if (
intent.binding != binding
or intent.invocation != invocation
or intent.invocation.pins != state.pins
or not state.last
or state.last.outcome is not expected
):
raise Refused("original intent and running receipt differ")
self.db.execute("BEGIN IMMEDIATE")
try:
if intent is not None:
self.db.execute(
"INSERT INTO intents(attempt_id,binding,payload) VALUES (?,?,?)",
(intent.invocation.attempt_id, binding, intent.payload()),
)
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],
*,
browser_inputs: BrowserInputs | None = None,
):
self.journal, self.adapters, self.evidence_verifier = (
journal,
dict(adapters),
evidence_verifier,
)
self._admitted = {}
self.browser_inputs = browser_inputs
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")
if case_type.get("id") == "oidc-browser-relying-party":
if type(self.browser_inputs) is not BrowserInputs:
raise Refused("browser metadata validator is not configured")
target = self.browser_inputs.target(
yaml.safe_load(case_source),
yaml.safe_load(type_source),
descriptor.target_parameter,
)
else:
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,
intent=OriginalIntent(binding, dispatched, adapter.source),
)
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,
intent=OriginalIntent(binding, dispatched, adapter.source),
)
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 and adapter.reconcile_original 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),
),
)
original = (
self.journal.original_intent(binding, invocation, adapter.source)
if adapter.reconcile_original is not None
else None
)
dispatched = self._attempt(invocation, state, "reconcile")
self.journal.save(binding, state, invocation=dispatched)
with contained_output():
result = self._result(
adapter,
dispatched,
adapter.reconcile_original(ReconcileContext(original, dispatched))
if original is not None
else 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,198 @@
"""Original-intent consistency checks with generated metadata only.
Reuse the existing synthetic owner rig. These checks do not provide a native
holder or independently authenticate original creation from local SQL/JSON.
"""
from dataclasses import asdict, replace
import json
from pathlib import Path
import sqlite3
import subprocess
import sys
import pytest
from tests.test_case_run import rig as rig
from warden.case_receipt_state import Outcome, Refused
from warden.case_run import OriginalIntent
def interrupted(rig):
executor, args, calls, sources, kwargs = rig
old = executor.adapters[args[0].adapter]
seen = []
def lost(inv):
calls.append(inv)
raise TimeoutError("generated lost response")
def original(ctx):
seen.append(ctx)
return old.reconcile(ctx.attempt)
executor.adapters[old.reference] = replace(
old, invoke=lost, reconcile=None, reconcile_original=original
)
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
return executor, args, calls, old, seen
def alter_payload(executor, **changes):
row = executor.journal.db.execute(
"SELECT seq,payload FROM intents ORDER BY seq DESC LIMIT 1"
).fetchone()
obj = json.loads(row[1])
obj.update(changes)
executor.journal.db.execute(
"UPDATE intents SET payload=? WHERE seq=?", (json.dumps(obj), row[0])
)
def test_original_actor_mismatch_is_refused(rig):
executor, args, calls, old, _ = interrupted(rig)
alter_payload(executor, actor="agent")
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_payload_attempt_id_must_match_sql_column_and_original_receipt(rig):
executor, args, calls, old, _ = interrupted(rig)
actual = calls[0].attempt_id
alter_payload(executor, attempt_id="attempt-unrelated")
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
assert (
executor.journal.db.execute("SELECT attempt_id FROM intents").fetchone()[0]
== actual
)
def test_orphan_intent_without_original_running_receipt_is_refused(rig):
executor, args, calls, old, _ = interrupted(rig)
executor.journal.db.execute(
"DELETE FROM receipts WHERE seq=(SELECT MIN(seq) FROM receipts)"
)
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_serializer_refuses_arbitrary_values_outside_normal_admit(rig):
_, args, _, _, _ = rig
inv = replace(
args[1],
target={"unexpected": "synthetic-owner-payload"},
operation="run",
attempt_id="attempt-fixture",
)
record = OriginalIntent("mint-secret:fixture-client", inv, "sha256:" + "1" * 64)
with pytest.raises(Refused):
OriginalIntent.decode(record.payload())
# This is a direct serialization API gap, not an observed normal-admit leak.
# Owner trust is the existing synthetic fixture; this proves call shape only.
@pytest.mark.parametrize(
"field,value",
[
("principal", "another-person"),
("tenant", "another-tenant"),
("realm", "another-realm"),
],
)
def test_existing_lookup_does_refuse_stored_principal_tenant_realm_mismatch(
rig, field, value
):
executor, args, _, old, _ = interrupted(rig)
alter_payload(executor, **{field: value})
with pytest.raises(Refused):
executor.journal.original_intent(
"mint-secret:fixture-client", args[1], old.source
)
def test_existing_atomic_insert_failure_prevents_dispatch_and_both_records(rig):
executor, args, calls, _, _ = rig
executor.journal.db.execute(
"CREATE TRIGGER fail_receipt BEFORE INSERT ON receipts BEGIN SELECT RAISE(ABORT,'fixture'); END"
)
with pytest.raises(sqlite3.IntegrityError):
executor.run(*args)
assert not calls
assert executor.journal.db.execute("SELECT COUNT(*) FROM intents").fetchone() == (
0,
)
assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
0,
)
def test_existing_missing_original_refuses_before_callback(rig):
executor, args, _, _, seen = interrupted(rig)
executor.journal.db.execute("DELETE FROM intents")
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
def test_existing_current_parent_revocation_remains_mandatory(rig):
executor, args, _, _, _ = interrupted(rig)
old = executor.evidence_verifier
def refuse_session(purpose, source, evidence):
return (
False
if purpose.startswith("session-revocation:")
else old(purpose, source, evidence)
)
executor.evidence_verifier = refuse_session
with pytest.raises(Refused):
executor.reconcile(*args)
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
def test_existing_intent_survives_actual_distinct_interpreter(rig):
executor, args, calls, old, _ = interrupted(rig)
code = """import json,sys
from pathlib import Path
from warden.case_run import Journal,Invocation
from warden.case_receipt_state import Pins
obj=json.load(sys.stdin);obj['pins']=Pins(**obj['pins']);j=Journal(Path(sys.argv[1]))
with j.locked():
i=j.original_intent('mint-secret:fixture-client',Invocation(**obj),sys.argv[2])
print(json.dumps({'attempt':i.invocation.attempt_id,'operation':i.invocation.operation}))
"""
result = subprocess.run(
[sys.executable, "-B", "-c", code, str(executor.journal.directory), old.source],
input=json.dumps(asdict(args[1])),
text=True,
capture_output=True,
timeout=15,
env={
"PATH": "/usr/bin:/bin",
"PYTHONPATH": str(Path(__file__).parents[1] / "src"),
"PYTHONDONTWRITEBYTECODE": "1",
},
check=True,
)
assert json.loads(result.stdout) == {
"attempt": calls[0].attempt_id,
"operation": "run",
}

View file

@ -0,0 +1,312 @@
---
id: WARDEN-WP-0044
type: workplan
title: "Follow through on KnownSecurityCases along their type's route"
domain: infotech
repo: ops-warden
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-11"
depends_on: []
related_repos:
- net-kingdom
- ops-mason
- key-cape
- railiance-platform
state_hub_workstream_id: "f7e81013-6868-5d99-8528-ba75a442eca9"
---
# WARDEN-WP-0044 — Follow through on KnownSecurityCases along their type's route
## Why
NetKingdom is introducing SecurityCaseTypes (net-kingdom `NK-WP-0048`, canon
`security-case-types_v0.1`, proposed). Once a case of an implemented type is
accepted and ops-mason has materialized its structure (`MASON-WP-0010`),
ops-warden carries out the remaining steps. These are the owners' declared entry
points, run on the authentication path the type defines, followed by the type's
verification.
Today ops-warden routes needs and, for `exec_capable` lanes, proxies fetches as
the caller. It does not yet know about cases, and each new integration ends with
a founder running a hand-written script (for example the proposed KEY-WP-0038-T02
rollout). This plan gives ops-warden the follow-through role, without widening
what it may do: each step still runs under its owner's entry point and the
caller's identity.
## Tasks
```task
id: WARDEN-WP-0044-T01
status: done
depends_on: [NK-WP-0048-T03]
priority: high
state_hub_task_id: "ef0920fc-418e-52cb-acff-9a5c0c728bf5"
```
### T01 — Define the type's follow-through section
Propose the `follow_through` part of the SecurityCaseType schema together with
net-kingdom. For each step it names the owner entry point, `warden_executes`,
the auth method and lane, whether an attended human login is unavoidable (with
the type's justification), and the verification command. Write it for the first
type, `oidc-browser-relying-party`, covering key-cape client registration,
custody minting and LLDAP group membership.
2026-10-08: NK-WP-0048-T03/T04 landed with a drafted section. Review is in
`history/2026-10-08-oidc-rp-follow-through-review.md`. It requires three
changes before T02: a per-step `lane`, a narrow `warden_executes`, and
`value_handling`. It also lists four refinements. This task is done when
net-kingdom acknowledges the review (NK-WP-0048-T05).
2026-10-09: R4 was withdrawn in favour of ops-mason's per-case
`{consuming_service}-keycape-client`, with the entry body templated in the type
(R4′, see the addendum in the review).
2026-10-09: the revised 1.0 type and canon now carry R1–R3, R4′, R5–R7.
`warden case check-type <type.yaml>` (`src/warden/case_type.py`, tests in
`tests/test_case_type.py`) checks a type's follow-through mechanically: the
live type is schema-conformant, with one attended session
(`key-cape-oidc-login`). It is not yet plannable, because two lanes are not
in the catalog: `identity-provisioner-groups` (net-kingdom) and
`keycape-client-secret-generate` (railiance-platform). These gate T02, not
T01. T01 still closes on net-kingdom's acknowledgement (NK-WP-0048-T05).
2026-10-09: ops-warden cannot add `identity-provisioner-groups` as the type
declares it. The provisioner accepts only user-engine's runtime token and
admits only user-engine pods, so no caller-identity path exists. R8 (review
addendum) asks net-kingdom either to move `ensure-groups` into the existing
attended session (recommended) or to give the provisioner an operator
credential. `keycape-client-secret-generate` needs a generate-in-custody entry
point from railiance-platform, and the broker does not have one yet. Both were
sent to their owners.
2026-10-09: done. net-kingdom took R8 option 1, so `ensure-groups` runs attended
in the `register-client` session and `identity-provisioner-groups` is dropped
(net-kingdom `0e86f95`). The founder acknowledged the type with memo r4 (hub
decision `e418263e`, NK-WP-0048-T05 done).
```task
id: WARDEN-WP-0044-T02
status: done
depends_on: [WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "5f68c991-f490-58f8-a121-92f74073d272"
```
### T02 — Plan a case run
`warden case plan <case.yaml>` checks that the case is accepted and materialized,
resolves each step to its catalog entry and entry point, and prints the run:
which steps warden executes, which run as the attended caller, what the
verification is. No secret values; no execution.
Tests: refuse an unaccepted or unmaterialized case; refuse a step whose catalog
entry is missing or not reviewed; show attended steps explicitly.
2026-10-09: done. `warden case plan <case.yaml> [--type] [--json]`
(`src/warden/case_plan.py`, 14 tests in `tests/test_case_plan.py`). It refuses:
- a case that is not `materialized`, or has no acceptance record
- a type that is mismatched or not acknowledged
- any `case check-type` finding, including a missing or unreviewed lane
- undeclared, type-fixed or secret-sensitivity parameters
- a placeholder the case does not set, or an unsupported `when`
It fills type defaults (`issuer`), evaluates `<param> == <value>` conditions,
renders owner, entry point, auth and ready_check, groups attended steps per
lane, and lists the applicable verification.
The live Forgejo case is correctly refused: it is `accepted`, not
materialized. A materialized copy, with a stand-in lane for
`keycape-client-secret-generate`, renders six steps: two attended steps plus
`register-client` in one `key-cape-oidc-login` session, one warden step
(`mint-secret`), and two owner steps. Seven checks follow.
Observation for net-kingdom (not blocking): `bind-env` runs before
`register-client`, in type order. Its entry carries a literal
`KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`, which is not a `{param}` placeholder, so it
reaches the plan unrendered.
2026-10-09 follow-up:
- **Format specs.** `_render` supports net-kingdom's `{param:env}` spec (upper
case, `-` as `_`, the same as `keycape-client.py env_name`) and refuses any
other spec or `!conversion`.
- **Case reference.** The plan prints `case-ref: <case_id>@sha256:<hex>` over the
case file's exact bytes. This is the reference proposed to railiance-platform
for `credential.py generate --case-ref` (RPF-WP-0054-T01); the broker
re-derives it from the file.
- 18 tests now.
```task
id: WARDEN-WP-0044-T03
status: progress
depends_on: [WARDEN-WP-0044-T02, MASON-WP-0010-T03]
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"
```
### T03 — Run and verify a case
`warden case run <case.yaml>` executes the plan. Owner entry points run through
the existing proxy as the caller; steps that need an attended login use the
existing contained login envelope (silent command, session revoked afterwards),
grouped into as few attended sessions as possible. Then it runs the type's
verification and writes a receipt without values. `warden case rollback`
reverses exactly this case's steps.
Acceptance: the Forgejo reference case (NK-WP-0048-T06) reaches `live` through
this path, with no case-specific script.
2026-10-17: original-intent lookup/context interface for NK-WP-0048-T15, source
and synthetic tests only. `src/warden/case_run.py`: `Executor.run` writes an
`OriginalIntent` (schema `warden.case.original-intent.v1`: the dispatched run
Invocation — attempt, step, owner, target, actor, principal, lane, all six pins,
tenant, realm — plus admitted adapter source) in the same SQLite transaction as
the pre-dispatch journal row. `Journal.original_intent(binding, current, source)`
returns it after restart and refuses on a missing record or any
pins/source/step/owner/target/actor/principal/lane/tenant/realm mismatch, before any
journal write, so the step stays uncertain. An adapter's
`reconcile_original(ReconcileContext(original, attempt))` receives the original
intent separately from the new reconcile attempt. This is Warden's record of what
it dispatched, not owner created-only provenance. Current Executor.admit leaves
tenant/realm empty; the reserved coordinates establish no admitted provenance.
Owner descriptors/adapters, contained session integration, Forgejo
materialization and the native run remain open.
2026-10-17 source consistency follow-through: retained the additive intents table
and reconcile_original callback and fixed four reproduced local defects: original
actor mismatch, payload/SQL attempt-ID mismatch, orphan intent without its
matching original running receipt, and arbitrary nonmetadata values accepted by
the original-intent serializer. Exact bounded field validation, duplicate-free
bounded decode and save-time intent/invocation/running-state equality now preserve
the original receipt linkage before owner lookup. No new context store, native
verifier, trust root, adapter activation or authority migration was introduced.
The actual canonical focused kernel and new consistency suite passes 54 checks;
owner-installed Ruff check/format passes. The existing generated-data/synthetic
owner fixture and a distinct interpreter prove source correspondence/refusal,
not independent native custody or creation. Earlier comparison probes and failed
preparation are retained. Current caller and fresh session/grant revocation checks
remain required; original owner payload/revocation, exact native readback and
created-only provenance still need independent authenticated owner admission.
No JSON/SQL context, unknown tenant/realm, changed run-sheet or legacy missing
intent supplies recovery, replay or undo authority. T03 remains progress and all
native acceptance gates/statuses/dependencies/UUIDs remain unchanged. Evidence:
Custodian original-context-compare-r2 and original-context-owner-followthrough-r3
under docs/changes/netkingdom-target-2026-10-17/; publication/projection is separate.
Agreed with railiance-platform (RPF-WP-0054-T01, 2026-10-09):
- `mint-secret` runs `credential.py generate --grant keycape/client-secret-generate
--client-id <id> --case-ref <case-ref> --case-file <case.yaml>` as the caller's
child process. The broker re-derives the case-ref and checks status and
client_id.
- warden refuses that step when `WARDEN_AGENT_ID` is set.
2026-10-10: railiance-platform implemented the command (RPF-WP-0054-T03). It is
catalogued as draft `keycape-client-secret-generate`, with `exec_owner`
railiance-platform and `exec_capable: false`. `warden case check-type` now
reports the lane as LANE-UNREVIEWED rather than LANE-MISSING. Before it can be
activated:
- the founder binds the issuer policy to the operator OIDC role
- key-cape confirms the secret format
- the RPF-WP-0054-T05 probe passes
2026-10-10 (later):
- **Broker lane.** The issuer role is bound (CCR-2026-0043) and the T05 probe
passed (railiance-platform `21b8af3`). Only key-cape's format confirmation is
left. The playbook now includes the issuer-role login, a 15m token with
unlimited uses that `warden case run` must group with the attended session.
- **Env spec.** net-kingdom adopted `{client_id:env}` as a 1.0 patch
(`9a9b8e3`). The plan renders `KEYCAPE_FORGEJO_CLIENT_SECRET`.
- **Forgejo case.** Rendered and built by ops-mason. It is not yet
`materialized`: MASON-WP-0010-T03 waits on the railiance-platform CCRs and
their founder apply.
2026-10-10: `keycape-client-secret-generate` activated on railiance-platform's
go-ahead (message 20973a9c) after key-cape confirmed the format (4e0213b3).
`warden case check-type` on the live type now reports `plannable=True`. The
only thing T03 still waits on is the Forgejo case reaching `materialized`
(MASON-WP-0010-T03).
2026-10-10: net-kingdom moved the type to 1.1 (`e2174ce`) and re-pinned the
Forgejo case. 1.1 adds the verification `consumer-enforces-groups` and requires
the hand-over to enforce `access_groups`. `check-type` on 1.1 reports
`plannable=True`, and a materialized copy renders six steps and the new check.
The case-ref digest covers the whole case file, including `status`, so it
changes when the case becomes `materialized`. `warden case run` therefore takes
it from the file at run time, and the broker re-derives it from the same file.
```task
id: WARDEN-WP-0044-T04
status: wait
depends_on: [WARDEN-WP-0044-T03]
blocking_reason: "Catalog integration follows a proven run."
priority: medium
state_hub_task_id: "4a0fa4a7-5ca9-518a-ab92-a79a5e2cf93e"
```
### T04 — Catalog entries from types
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.
2026-10-16 source preparation (isolated checkout; no native acceptance):
T03's browser parameter mismatch now has a producer-validator-backed projection
in `case_inputs.py`, explicitly configured on the real Executor. Exact 1.1/1.2
parameter definitions and all four reviewed producer modules are pinned; full case
validation admits reviewed security-sensitive metadata while unknown/fixed/secret
inputs and type/producer drift refuse. Only the reviewed descriptor target
reaches the owner callback; no parameter journal payload is added. Actual
Executor/producer tests cover positive versions and refusal/containment paths.
Acknowledged machine descriptors, authenticated owner evidence, real Forgejo
materialization and contained native sessions remain mandatory and unimplemented;
T03 stays progress, T04 stays wait. No type or case status changed.
2026-10-16 producer source refresh: independently reviewed NetKingdom
`015694ee414c802dc2ef1fa0120dfed610086e69` optional descriptor-map repair.
Only the validator/evidence source pins change; exact parameter and transitive
source identity remain enforced. Actual canonical producer, Executor and reducer
suite passes 87 checks, including 14 new malformed-map integration cases; all
four missing/changed modules still refuse. Review:
`docs/reviews/2026-10-16-browser-producer-pin-refresh.md`. No adapter attestation,
native acceptance, credential use or privileged dispatch. T03 remains progress.

View file

@ -0,0 +1,127 @@
# 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.
## Browser input projection — 2026-10-16 source preparation
An explicitly configured `BrowserInputs(netkingdom_root)` now validates browser
cases before admission. It calls the real NetKingdom type/case validator over
verified producer bytes in a disposable Python process (`-I`, empty environment,
timeout, no inherited output). The validator, composer, capability contract and lazy evidence
modules have exact source pins; missing or changed code refuses. There is no
automatic discovery or download. A producer upgrade needs reviewed pin changes.
Only the exact reviewed 1.1/1.2 browser parameter definitions qualify. A digest
of each complete parameter definition list prevents reclassifying secrets as
metadata, loosening constraints, or adding fields under a recognized version.
The complete case passes producer validation, including required fields,
constraints and credential-shape checks. Explicit fixed/default overrides,
unknown fields and secret references refuse. Security-sensitive browser metadata
can therefore be present without being passed to an owner adapter: the only
projection is its descriptor's exact public/operational target identifier.
No raw parameter values are added to invocations or persisted receipts.
`require_implemented=False` validates the acknowledged type's source, not its
execution authority. Existing descriptor/type/source/catalog/caller provenance
gates stay mandatory. Neither historical browser type contains the required
exact machine-descriptor map, and neither is promoted by this change. There are
still no installed native owner adapters, authenticated provenance verifier,
contained session/ordered run orchestration or live CLI dispatch. A journal digest
is a fingerprint and cannot authenticate native evidence. Completion still needs
owner-admitted descriptors and actual structural, mint/delivery, consumer and
revocation/rollback evidence. Local fixture success is not native acceptance.
Tests use the actual producer checkout (override sibling location with
`WARDEN_TEST_NETKINGDOM_ROOT`), the real Executor, SQLite journal and injected
test-only owner adapter/evidence verifier. Their descriptor extensions and case
materialized labels exist only in memory. Canonical case/type bytes stay intact.
Producer integration refinement: the accepted NetKingdom browser-case patch
`d32a7f51cbf3012bee3e4bb329f4259dea867eff1c59a2b01a65d72774488d05`
is now the tested producer revision. Its evidence module is imported lazily only
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.

View file

@ -0,0 +1,826 @@
"""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_inputs import BrowserInputs
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"
tenant: str = ""
realm: str = ""
INTENT_SCHEMA = "warden.case.original-intent.v1"
@dataclass(frozen=True, slots=True)
class OriginalIntent:
"""Durable record of the original mutating dispatch, written before dispatch.
Binds the original Invocation (attempt, step, owner, target, actor, principal,
lane), all pins, the admitted adapter source and the tenant/realm provenance
the caller was admitted with. It is evidence of what Warden dispatched, not
owner proof of creation: owners still authenticate created-only provenance.
"""
binding: str
invocation: Invocation
source: str
def payload(self) -> str:
return json.dumps(
{"schema": INTENT_SCHEMA, "binding": self.binding, "source": self.source}
| asdict(self.invocation),
separators=(",", ":"),
sort_keys=True,
)
@classmethod
def decode(cls, raw: str) -> OriginalIntent:
obj = json.loads(raw)
fields = {f for f in Invocation.__dataclass_fields__}
if set(obj) != fields | {"schema", "binding", "source"} or obj["schema"] != INTENT_SCHEMA:
raise Refused("unknown original intent schema")
inv = {k: obj[k] for k in fields}
inv["pins"] = Pins(**inv["pins"])
return cls(obj["binding"], Invocation(**inv), obj["source"])
@dataclass(frozen=True, slots=True)
class ReconcileContext:
"""Original intent and the new reconciliation attempt, kept separate."""
original: OriginalIntent
attempt: Invocation
@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
reconcile_original: Callable[[ReconcileContext], 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)"
)
self.db.execute(
"CREATE TABLE IF NOT EXISTS intents (seq INTEGER PRIMARY KEY, attempt_id TEXT NOT NULL UNIQUE, binding TEXT NOT NULL, payload TEXT NOT NULL)"
)
def original_intent(self, binding: str, current: Invocation, source: str) -> OriginalIntent:
"""Latest original run intent for binding, refused unless it matches exactly.
Missing or mismatched records refuse; the step stays uncertain.
"""
row = self.db.execute(
"SELECT payload FROM intents WHERE binding=? ORDER BY seq DESC LIMIT 1",
(binding,),
).fetchone()
if row is None:
raise Refused("original intent record missing; step stays uncertain")
intent = OriginalIntent.decode(row[0])
o = intent.invocation
if intent.binding != binding or o.operation != "run":
raise Refused("original intent binding mismatch")
if o.pins != current.pins:
raise Refused("original intent pins mismatch")
if intent.source != source:
raise Refused("original intent source mismatch")
for name in ("step", "owner", "target", "principal", "lane", "tenant", "realm"):
if getattr(o, name) != getattr(current, name):
raise Refused(f"original intent {name} mismatch")
return intent
@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,
intent: OriginalIntent | None = None,
):
self.db.execute("BEGIN IMMEDIATE")
try:
if intent is not None:
self.db.execute(
"INSERT INTO intents(attempt_id,binding,payload) VALUES (?,?,?)",
(intent.invocation.attempt_id, binding, intent.payload()),
)
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],
*,
browser_inputs: BrowserInputs | None = None,
):
self.journal, self.adapters, self.evidence_verifier = (
journal,
dict(adapters),
evidence_verifier,
)
self._admitted = {}
self.browser_inputs = browser_inputs
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")
if case_type.get("id") == "oidc-browser-relying-party":
if type(self.browser_inputs) is not BrowserInputs:
raise Refused("browser metadata validator is not configured")
target = self.browser_inputs.target(
yaml.safe_load(case_source),
yaml.safe_load(type_source),
descriptor.target_parameter,
)
else:
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,
intent=OriginalIntent(binding, dispatched, adapter.source),
)
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,
intent=OriginalIntent(binding, dispatched, adapter.source),
)
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 and adapter.reconcile_original 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),
),
)
original = (
self.journal.original_intent(binding, invocation, adapter.source)
if adapter.reconcile_original is not None
else None
)
dispatched = self._attempt(invocation, state, "reconcile")
self.journal.save(binding, state, invocation=dispatched)
with contained_output():
result = self._result(
adapter,
dispatched,
adapter.reconcile_original(ReconcileContext(original, dispatched))
if original is not None
else 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,289 @@
---
id: WARDEN-WP-0044
type: workplan
title: "Follow through on KnownSecurityCases along their type's route"
domain: infotech
repo: ops-warden
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-11"
depends_on: []
related_repos:
- net-kingdom
- ops-mason
- key-cape
- railiance-platform
state_hub_workstream_id: "f7e81013-6868-5d99-8528-ba75a442eca9"
---
# WARDEN-WP-0044 — Follow through on KnownSecurityCases along their type's route
## Why
NetKingdom is introducing SecurityCaseTypes (net-kingdom `NK-WP-0048`, canon
`security-case-types_v0.1`, proposed). Once a case of an implemented type is
accepted and ops-mason has materialized its structure (`MASON-WP-0010`),
ops-warden carries out the remaining steps. These are the owners' declared entry
points, run on the authentication path the type defines, followed by the type's
verification.
Today ops-warden routes needs and, for `exec_capable` lanes, proxies fetches as
the caller. It does not yet know about cases, and each new integration ends with
a founder running a hand-written script (for example the proposed KEY-WP-0038-T02
rollout). This plan gives ops-warden the follow-through role, without widening
what it may do: each step still runs under its owner's entry point and the
caller's identity.
## Tasks
```task
id: WARDEN-WP-0044-T01
status: done
depends_on: [NK-WP-0048-T03]
priority: high
state_hub_task_id: "ef0920fc-418e-52cb-acff-9a5c0c728bf5"
```
### T01 — Define the type's follow-through section
Propose the `follow_through` part of the SecurityCaseType schema together with
net-kingdom. For each step it names the owner entry point, `warden_executes`,
the auth method and lane, whether an attended human login is unavoidable (with
the type's justification), and the verification command. Write it for the first
type, `oidc-browser-relying-party`, covering key-cape client registration,
custody minting and LLDAP group membership.
2026-10-08: NK-WP-0048-T03/T04 landed with a drafted section. Review is in
`history/2026-10-08-oidc-rp-follow-through-review.md`. It requires three
changes before T02: a per-step `lane`, a narrow `warden_executes`, and
`value_handling`. It also lists four refinements. This task is done when
net-kingdom acknowledges the review (NK-WP-0048-T05).
2026-10-09: R4 was withdrawn in favour of ops-mason's per-case
`{consuming_service}-keycape-client`, with the entry body templated in the type
(R4′, see the addendum in the review).
2026-10-09: the revised 1.0 type and canon now carry R1–R3, R4′, R5–R7.
`warden case check-type <type.yaml>` (`src/warden/case_type.py`, tests in
`tests/test_case_type.py`) checks a type's follow-through mechanically: the
live type is schema-conformant, with one attended session
(`key-cape-oidc-login`). It is not yet plannable, because two lanes are not
in the catalog: `identity-provisioner-groups` (net-kingdom) and
`keycape-client-secret-generate` (railiance-platform). These gate T02, not
T01. T01 still closes on net-kingdom's acknowledgement (NK-WP-0048-T05).
2026-10-09: ops-warden cannot add `identity-provisioner-groups` as the type
declares it. The provisioner accepts only user-engine's runtime token and
admits only user-engine pods, so no caller-identity path exists. R8 (review
addendum) asks net-kingdom either to move `ensure-groups` into the existing
attended session (recommended) or to give the provisioner an operator
credential. `keycape-client-secret-generate` needs a generate-in-custody entry
point from railiance-platform, and the broker does not have one yet. Both were
sent to their owners.
2026-10-09: done. net-kingdom took R8 option 1, so `ensure-groups` runs attended
in the `register-client` session and `identity-provisioner-groups` is dropped
(net-kingdom `0e86f95`). The founder acknowledged the type with memo r4 (hub
decision `e418263e`, NK-WP-0048-T05 done).
```task
id: WARDEN-WP-0044-T02
status: done
depends_on: [WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "5f68c991-f490-58f8-a121-92f74073d272"
```
### T02 — Plan a case run
`warden case plan <case.yaml>` checks that the case is accepted and materialized,
resolves each step to its catalog entry and entry point, and prints the run:
which steps warden executes, which run as the attended caller, what the
verification is. No secret values; no execution.
Tests: refuse an unaccepted or unmaterialized case; refuse a step whose catalog
entry is missing or not reviewed; show attended steps explicitly.
2026-10-09: done. `warden case plan <case.yaml> [--type] [--json]`
(`src/warden/case_plan.py`, 14 tests in `tests/test_case_plan.py`). It refuses:
- a case that is not `materialized`, or has no acceptance record
- a type that is mismatched or not acknowledged
- any `case check-type` finding, including a missing or unreviewed lane
- undeclared, type-fixed or secret-sensitivity parameters
- a placeholder the case does not set, or an unsupported `when`
It fills type defaults (`issuer`), evaluates `<param> == <value>` conditions,
renders owner, entry point, auth and ready_check, groups attended steps per
lane, and lists the applicable verification.
The live Forgejo case is correctly refused: it is `accepted`, not
materialized. A materialized copy, with a stand-in lane for
`keycape-client-secret-generate`, renders six steps: two attended steps plus
`register-client` in one `key-cape-oidc-login` session, one warden step
(`mint-secret`), and two owner steps. Seven checks follow.
Observation for net-kingdom (not blocking): `bind-env` runs before
`register-client`, in type order. Its entry carries a literal
`KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`, which is not a `{param}` placeholder, so it
reaches the plan unrendered.
2026-10-09 follow-up:
- **Format specs.** `_render` supports net-kingdom's `{param:env}` spec (upper
case, `-` as `_`, the same as `keycape-client.py env_name`) and refuses any
other spec or `!conversion`.
- **Case reference.** The plan prints `case-ref: <case_id>@sha256:<hex>` over the
case file's exact bytes. This is the reference proposed to railiance-platform
for `credential.py generate --case-ref` (RPF-WP-0054-T01); the broker
re-derives it from the file.
- 18 tests now.
```task
id: WARDEN-WP-0044-T03
status: progress
depends_on: [WARDEN-WP-0044-T02, MASON-WP-0010-T03]
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"
```
### T03 — Run and verify a case
`warden case run <case.yaml>` executes the plan. Owner entry points run through
the existing proxy as the caller; steps that need an attended login use the
existing contained login envelope (silent command, session revoked afterwards),
grouped into as few attended sessions as possible. Then it runs the type's
verification and writes a receipt without values. `warden case rollback`
reverses exactly this case's steps.
Acceptance: the Forgejo reference case (NK-WP-0048-T06) reaches `live` through
this path, with no case-specific script.
2026-10-17: original-intent lookup/context interface for NK-WP-0048-T15, source
and synthetic tests only. `src/warden/case_run.py`: `Executor.run` writes an
`OriginalIntent` (schema `warden.case.original-intent.v1`: the dispatched run
Invocation — attempt, step, owner, target, actor, principal, lane, all six pins,
tenant, realm — plus admitted adapter source) in the same SQLite transaction as
the pre-dispatch journal row. `Journal.original_intent(binding, current, source)`
returns it after restart and refuses on a missing record or any
pins/source/step/owner/target/principal/lane/tenant/realm mismatch, before any
journal write, so the step stays uncertain. An adapter's
`reconcile_original(ReconcileContext(original, attempt))` receives the original
intent separately from the new reconcile attempt. This is Warden's record of what
it dispatched, not owner created-only provenance; tenant/realm are as supplied at
admission. Owner descriptors/adapters, contained session integration, Forgejo
materialization and the native run remain open.
Agreed with railiance-platform (RPF-WP-0054-T01, 2026-10-09):
- `mint-secret` runs `credential.py generate --grant keycape/client-secret-generate
--client-id <id> --case-ref <case-ref> --case-file <case.yaml>` as the caller's
child process. The broker re-derives the case-ref and checks status and
client_id.
- warden refuses that step when `WARDEN_AGENT_ID` is set.
2026-10-10: railiance-platform implemented the command (RPF-WP-0054-T03). It is
catalogued as draft `keycape-client-secret-generate`, with `exec_owner`
railiance-platform and `exec_capable: false`. `warden case check-type` now
reports the lane as LANE-UNREVIEWED rather than LANE-MISSING. Before it can be
activated:
- the founder binds the issuer policy to the operator OIDC role
- key-cape confirms the secret format
- the RPF-WP-0054-T05 probe passes
2026-10-10 (later):
- **Broker lane.** The issuer role is bound (CCR-2026-0043) and the T05 probe
passed (railiance-platform `21b8af3`). Only key-cape's format confirmation is
left. The playbook now includes the issuer-role login, a 15m token with
unlimited uses that `warden case run` must group with the attended session.
- **Env spec.** net-kingdom adopted `{client_id:env}` as a 1.0 patch
(`9a9b8e3`). The plan renders `KEYCAPE_FORGEJO_CLIENT_SECRET`.
- **Forgejo case.** Rendered and built by ops-mason. It is not yet
`materialized`: MASON-WP-0010-T03 waits on the railiance-platform CCRs and
their founder apply.
2026-10-10: `keycape-client-secret-generate` activated on railiance-platform's
go-ahead (message 20973a9c) after key-cape confirmed the format (4e0213b3).
`warden case check-type` on the live type now reports `plannable=True`. The
only thing T03 still waits on is the Forgejo case reaching `materialized`
(MASON-WP-0010-T03).
2026-10-10: net-kingdom moved the type to 1.1 (`e2174ce`) and re-pinned the
Forgejo case. 1.1 adds the verification `consumer-enforces-groups` and requires
the hand-over to enforce `access_groups`. `check-type` on 1.1 reports
`plannable=True`, and a materialized copy renders six steps and the new check.
The case-ref digest covers the whole case file, including `status`, so it
changes when the case becomes `materialized`. `warden case run` therefore takes
it from the file at run time, and the broker re-derives it from the same file.
```task
id: WARDEN-WP-0044-T04
status: wait
depends_on: [WARDEN-WP-0044-T03]
blocking_reason: "Catalog integration follows a proven run."
priority: medium
state_hub_task_id: "4a0fa4a7-5ca9-518a-ab92-a79a5e2cf93e"
```
### T04 — Catalog entries from types
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.
2026-10-16 source preparation (isolated checkout; no native acceptance):
T03's browser parameter mismatch now has a producer-validator-backed projection
in `case_inputs.py`, explicitly configured on the real Executor. Exact 1.1/1.2
parameter definitions and all four reviewed producer modules are pinned; full case
validation admits reviewed security-sensitive metadata while unknown/fixed/secret
inputs and type/producer drift refuse. Only the reviewed descriptor target
reaches the owner callback; no parameter journal payload is added. Actual
Executor/producer tests cover positive versions and refusal/containment paths.
Acknowledged machine descriptors, authenticated owner evidence, real Forgejo
materialization and contained native sessions remain mandatory and unimplemented;
T03 stays progress, T04 stays wait. No type or case status changed.
2026-10-16 producer source refresh: independently reviewed NetKingdom
`015694ee414c802dc2ef1fa0120dfed610086e69` optional descriptor-map repair.
Only the validator/evidence source pins change; exact parameter and transitive
source identity remain enforced. Actual canonical producer, Executor and reducer
suite passes 87 checks, including 14 new malformed-map integration cases; all
four missing/changed modules still refuse. Review:
`docs/reviews/2026-10-16-browser-producer-pin-refresh.md`. No adapter attestation,
native acceptance, credential use or privileged dispatch. T03 remains progress.

View file

@ -0,0 +1,60 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — ops-warden
**Domain:** infotech
**Last synced:** 2026-10-16 00:05 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Adopt unknown -> fail_closed behind signing-target classification coverage
Progress: 1/4 done | workplan_id: `c8ee441e-1be1-5219-910c-e79ff23cc9ec`
**Open tasks:**
- ! Tasks `611f0901`
*(wait: Warden's own declaration cannot classify the repair-path target workloads; the owner must declare the continuity actor/target and its z2-continuity resolution (see 2026-09-28 review).)*
- ! Tasks `54ad4864`
*(wait: Owners of agt-codex-interhub-bootstrap (ops-hub) and agt-state-hub-bridge (ops-bridge) must publish authoritative workload identity declarations; atm-backup-daily declared by activity-core and compiled 2026-10-09 (z2-protected, admission unsatisfied: M1 below M2 floor).)*
- ! Tasks `93eaf1f2`
*(wait: Gated on T01 and on layer model v0.8 (or successor) reaching accepted; never convert against a proposed standard.)*
### Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule
Progress: 5/6 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef`
**Open tasks:**
- ! Tasks `7d1b3c82`
*(wait: Two items to route, not absorb: ops-mason has published no stance map; the second is another repository's to own.)*
### Follow through on KnownSecurityCases along their type's route
Progress: 2/4 done | workplan_id: `f7e81013-6868-5d99-8528-ba75a442eca9`
**Open tasks:**
- ! T03 — Run and verify a case `4a0fa4a7`
*(wait: Catalog integration follows a proven run.)*
- ► T02 — Plan a case run `bcbf6c91`
### Tamper-resistant credential governance + mass rotation/lockdown (Strand B)
Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
**Open tasks:**
- · Task: Graded lockdown / break-glass with explicit trust-root `cae498ee`
### Repoint the whynot-design npm lane to Forgejo
Progress: 2/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e`
**Open tasks:**
- ! Prove routing and publication `a8b1b855`
*(wait: Hold kept by D3 until SECRETS-WP-0006-T06 moves publishing off the legacy whynot-design npm path; then rotate and prove a fresh publish.)*
## Inbox Hygiene
**Stale unread:** 2 message(s) older than 3 day(s) — triage at session start.
**Missing thread_id:** 3 unread message(s) lack supersession chains.
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,188 @@
# Work Records — ops-warden
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
| workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
| workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md |
| workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md |
| workplan | WARDEN-WP-ADHOC-2026-09-08 | finished | — | workplans/ADHOC-2026-09-08.md |
| workplan | WARDEN-WP-ADHOC-2026-10-03 | finished | — | workplans/ADHOC-2026-10-03.md |
| workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| workplan | WARDEN-WP-0019 | finished | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| workplan | WARDEN-WP-0020 | finished | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| workplan | WARDEN-WP-0021 | finished | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| workplan | WARDEN-WP-0022 | finished | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| workplan | WARDEN-WP-0023 | finished | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| workplan | WARDEN-WP-0024 | finished | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| workplan | WARDEN-WP-0025 | finished | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| workplan | WARDEN-WP-0026 | finished | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| workplan | WARDEN-WP-0027 | blocked | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| workplan | WARDEN-WP-0028 | finished | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| workplan | WARDEN-WP-0029 | finished | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
| workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md |
| workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| workplan | WARDEN-WP-0034 | active | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| workplan | WARDEN-WP-0037 | blocked | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| workplan | WARDEN-WP-0039 | finished | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| workplan | WARDEN-WP-0040 | active | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| workplan | WARDEN-WP-0041 | finished | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| workplan | WARDEN-WP-0042 | finished | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| workplan | WARDEN-WP-0043 | finished | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| workplan | WARDEN-WP-0044 | active | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
| task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T01 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T02 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T03 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T04 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T05 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T06 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
| task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T04 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0017-T01 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T02 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T03 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0018-T01 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T02 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T03 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0019-T01 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0019-T02 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0020-T01 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T02 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T03 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T04 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T05 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0021-T01 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T02 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T03 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T04 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T05 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0022-T01 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T02 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T03 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T04 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0023-T01 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T02 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T03 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T04 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T05 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T06 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T07 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0024-T01 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T02 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T03 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T04 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T05 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T06 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T07 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0025-T01 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T02 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T03 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T04 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T05 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0026-T01 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T02 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T03 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T04 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T05 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T06 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T07 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0027-T01 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T02 | todo | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T03 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0028-T01 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T02 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T03 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T04 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T05 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T06 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T07 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0029-T01 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T02 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T03 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T04 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T05 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0030-T01 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T02 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T03 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T04 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T05 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0031-T01 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T02 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T03 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T04 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T05 | cancel | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0032-T01 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T02 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T03 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T04 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T05 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T06 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T07 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0034-T01 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T02 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T03 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T04 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T05 | wait | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T06 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0038-T01 | done | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T03 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0040-T01 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T02 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T03 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T04 | done | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0041-T01 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T02 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T03 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T04 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0042-T01 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0042-T02 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0043-T01 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T02 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T03 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0044-T01 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T02 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T03 | progress | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T04 | wait | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0002 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0003 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,10 @@
[
{
"path": ".custodian-brief.md",
"index": "100644 34573d262327057c11628ce8a7ca78292968545b 0\t.custodian-brief.md"
},
{
"path": "WORK-RECORDS.md",
"index": "100644 34cf268862326a5832a7d0be9e84eb296e5601bb 0\tWORK-RECORDS.md"
}
]

View file

@ -0,0 +1,25 @@
{
"exit_code": 0,
"commit": "117fec9241e7b985dfb187b992e173d0a5dc3700",
"base": "a45cf475bc4dbc929e0eecbc1f806a9229d934e2",
"files": [
{
"path": "src/warden/case_run.py",
"sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768"
},
{
"path": "tests/test_case_original_context_consistency.py",
"sha256": "10c5187cbc49222017319249ccb408816497ec6e006859d23003f3b2b375445b"
},
{
"path": "docs/security-case-execution-kernel-v1.md",
"sha256": "e5570f542ec3ef4e6c11332cfe48fea16bf90cde5b9a5d926f5a068b7cafdbc7"
},
{
"path": "workplans/WARDEN-WP-0044-security-case-follow-through.md",
"sha256": "721a90b2ed8c896257c34a1451eb642cb8824c8263e37c483fb61b36adc54088"
}
],
"stdout": "[main 117fec9] fix(case): validate original intent and bind its running receipt\n 4 files changed, 421 insertions(+), 17 deletions(-)\n create mode 100644 tests/test_case_original_context_consistency.py\n",
"stderr": "{\"blocked_blob_ids\": [], \"scanned_blobs\": 4, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"clean\"}\n"
}

View file

@ -0,0 +1,23 @@
{
"owner": "ops-warden",
"base_revision": "a45cf475bc4dbc929e0eecbc1f806a9229d934e2",
"message": "fix(case): validate original intent and bind its running receipt",
"files": [
{
"path": "src/warden/case_run.py",
"sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768"
},
{
"path": "tests/test_case_original_context_consistency.py",
"sha256": "10c5187cbc49222017319249ccb408816497ec6e006859d23003f3b2b375445b"
},
{
"path": "docs/security-case-execution-kernel-v1.md",
"sha256": "e5570f542ec3ef4e6c11332cfe48fea16bf90cde5b9a5d926f5a068b7cafdbc7"
},
{
"path": "workplans/WARDEN-WP-0044-security-case-follow-through.md",
"sha256": "721a90b2ed8c896257c34a1451eb642cb8824c8263e37c483fb61b36adc54088"
}
]
}

View file

@ -0,0 +1,14 @@
[
{
"packet": "original-context-r1",
"manifest_sha256": "sha256:c91e5a764712ec78d98fa3b74c652de734c2f4f524c290b851a2fa1c79d40f4f",
"all_manifest_files_unchanged": true,
"mismatches": []
},
{
"packet": "original-context-compare-r2",
"manifest_sha256": "sha256:b98cedced069a744614d1086fda38b6db0fafb8a43d6bbcee0ad5e25cb860e05",
"all_manifest_files_unchanged": true,
"mismatches": []
}
]

View file

@ -0,0 +1,187 @@
# Work Records — ops-warden
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
| workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
| workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md |
| workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md |
| workplan | WARDEN-WP-ADHOC-2026-09-08 | finished | — | workplans/ADHOC-2026-09-08.md |
| workplan | WARDEN-WP-ADHOC-2026-10-03 | finished | — | workplans/ADHOC-2026-10-03.md |
| workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| workplan | WARDEN-WP-0019 | finished | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| workplan | WARDEN-WP-0020 | finished | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| workplan | WARDEN-WP-0021 | finished | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| workplan | WARDEN-WP-0022 | finished | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| workplan | WARDEN-WP-0023 | finished | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| workplan | WARDEN-WP-0024 | finished | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| workplan | WARDEN-WP-0025 | finished | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| workplan | WARDEN-WP-0026 | finished | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| workplan | WARDEN-WP-0027 | blocked | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| workplan | WARDEN-WP-0028 | finished | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| workplan | WARDEN-WP-0029 | finished | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
| workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md |
| workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| workplan | WARDEN-WP-0034 | backlog | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| workplan | WARDEN-WP-0037 | blocked | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| workplan | WARDEN-WP-0039 | finished | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| workplan | WARDEN-WP-0040 | backlog | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| workplan | WARDEN-WP-0041 | finished | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| workplan | WARDEN-WP-0042 | finished | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| workplan | WARDEN-WP-0043 | finished | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| workplan | WARDEN-WP-0044 | active | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
| task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T01 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T02 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T03 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T04 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T05 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T06 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
| task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T04 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0017-T01 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T02 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T03 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0018-T01 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T02 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T03 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0019-T01 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0019-T02 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0020-T01 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T02 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T03 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T04 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T05 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0021-T01 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T02 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T03 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T04 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T05 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0022-T01 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T02 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T03 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T04 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0023-T01 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T02 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T03 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T04 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T05 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T06 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T07 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0024-T01 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T02 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T03 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T04 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T05 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T06 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T07 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0025-T01 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T02 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T03 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T04 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T05 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0026-T01 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T02 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T03 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T04 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T05 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T06 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T07 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0027-T01 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T02 | todo | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T03 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0028-T01 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T02 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T03 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T04 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T05 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T06 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T07 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0029-T01 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T02 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T03 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T04 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T05 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0030-T01 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T02 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T03 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T04 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T05 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0031-T01 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T02 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T03 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T04 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T05 | cancel | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0032-T01 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T02 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T03 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T04 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T05 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T06 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T07 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0034-T01 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T02 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T03 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T04 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T05 | wait | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T06 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T03 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0040-T01 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T02 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T03 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T04 | done | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0041-T01 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T02 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T03 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T04 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0042-T01 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0042-T02 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0043-T01 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T02 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T03 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0044-T01 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T02 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T03 | progress | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T04 | wait | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0002 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0003 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,188 @@
# Work Records — ops-warden
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
| workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
| workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md |
| workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md |
| workplan | WARDEN-WP-ADHOC-2026-09-08 | finished | — | workplans/ADHOC-2026-09-08.md |
| workplan | WARDEN-WP-ADHOC-2026-10-03 | finished | — | workplans/ADHOC-2026-10-03.md |
| workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| workplan | WARDEN-WP-0019 | finished | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| workplan | WARDEN-WP-0020 | finished | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| workplan | WARDEN-WP-0021 | finished | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| workplan | WARDEN-WP-0022 | finished | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| workplan | WARDEN-WP-0023 | finished | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| workplan | WARDEN-WP-0024 | finished | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| workplan | WARDEN-WP-0025 | finished | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| workplan | WARDEN-WP-0026 | finished | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| workplan | WARDEN-WP-0027 | blocked | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| workplan | WARDEN-WP-0028 | finished | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| workplan | WARDEN-WP-0029 | finished | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
| workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md |
| workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| workplan | WARDEN-WP-0034 | active | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| workplan | WARDEN-WP-0037 | blocked | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| workplan | WARDEN-WP-0039 | finished | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| workplan | WARDEN-WP-0040 | active | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| workplan | WARDEN-WP-0041 | finished | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| workplan | WARDEN-WP-0042 | finished | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| workplan | WARDEN-WP-0043 | finished | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| workplan | WARDEN-WP-0044 | active | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
| task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T01 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T02 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T03 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T04 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T05 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-09-08-T06 | done | — | workplans/ADHOC-2026-09-08.md |
| task | WARDEN-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
| task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0016-T04 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
| task | WARDEN-WP-0017-T01 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T02 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0017-T03 | done | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
| task | WARDEN-WP-0018-T01 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T02 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0018-T03 | done | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
| task | WARDEN-WP-0019-T01 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0019-T02 | done | — | workplans/WARDEN-WP-0019-route-to-secrets-engine.md |
| task | WARDEN-WP-0020-T01 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T02 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T03 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T04 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0020-T05 | done | — | workplans/WARDEN-WP-0020-ops-warden-worker.md |
| task | WARDEN-WP-0021-T01 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T02 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T03 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T04 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0021-T05 | done | — | workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md |
| task | WARDEN-WP-0022-T01 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T02 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T03 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0022-T04 | done | — | workplans/WARDEN-WP-0022-audit-trail-and-activity.md |
| task | WARDEN-WP-0023-T01 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T02 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T03 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T04 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T05 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T06 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0023-T07 | done | — | workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md |
| task | WARDEN-WP-0024-T01 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T02 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T03 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T04 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T05 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T06 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0024-T07 | done | — | workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md |
| task | WARDEN-WP-0025-T01 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T02 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T03 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T04 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0025-T05 | done | — | workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md |
| task | WARDEN-WP-0026-T01 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T02 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T03 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T04 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T05 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T06 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0026-T07 | done | — | workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md |
| task | WARDEN-WP-0027-T01 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T02 | todo | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0027-T03 | cancel | — | workplans/WARDEN-WP-0027-credential-governance-lockdown.md |
| task | WARDEN-WP-0028-T01 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T02 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T03 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T04 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T05 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T06 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0028-T07 | done | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
| task | WARDEN-WP-0029-T01 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T02 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T03 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T04 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0029-T05 | done | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
| task | WARDEN-WP-0030-T01 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T02 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T03 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T04 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0030-T05 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
| task | WARDEN-WP-0031-T01 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T02 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T03 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T04 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0031-T05 | cancel | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
| task | WARDEN-WP-0032-T01 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T02 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T03 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T04 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T05 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T06 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0032-T07 | done | — | workplans/WARDEN-WP-0032-security-zones.md |
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
| task | WARDEN-WP-0034-T01 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T02 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T03 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T04 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T05 | wait | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0034-T06 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | WARDEN-WP-0038-T01 | done | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0039-T03 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | WARDEN-WP-0040-T01 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T02 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T03 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0040-T04 | done | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | WARDEN-WP-0041-T01 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T02 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T03 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0041-T04 | done | — | workplans/WARDEN-WP-0041-catalog-lane-scaffolding.md |
| task | WARDEN-WP-0042-T01 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0042-T02 | done | — | workplans/WARDEN-WP-0042-openbao-structure-survey-route.md |
| task | WARDEN-WP-0043-T01 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T02 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0043-T03 | done | — | workplans/WARDEN-WP-0043-platform-supplier-credentials.md |
| task | WARDEN-WP-0044-T01 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T02 | done | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T03 | progress | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| task | WARDEN-WP-0044-T04 | wait | — | workplans/WARDEN-WP-0044-security-case-follow-through.md |
| intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0002 | closed | — | intakes/intakes.md |
| intake | WARDEN-IN-0003 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,176 @@
# 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.
## Browser input projection — 2026-10-16 source preparation
An explicitly configured `BrowserInputs(netkingdom_root)` now validates browser
cases before admission. It calls the real NetKingdom type/case validator over
verified producer bytes in a disposable Python process (`-I`, empty environment,
timeout, no inherited output). The validator, composer, capability contract and lazy evidence
modules have exact source pins; missing or changed code refuses. There is no
automatic discovery or download. A producer upgrade needs reviewed pin changes.
Only the exact reviewed 1.1/1.2 browser parameter definitions qualify. A digest
of each complete parameter definition list prevents reclassifying secrets as
metadata, loosening constraints, or adding fields under a recognized version.
The complete case passes producer validation, including required fields,
constraints and credential-shape checks. Explicit fixed/default overrides,
unknown fields and secret references refuse. Security-sensitive browser metadata
can therefore be present without being passed to an owner adapter: the only
projection is its descriptor's exact public/operational target identifier.
No raw parameter values are added to invocations or persisted receipts.
`require_implemented=False` validates the acknowledged type's source, not its
execution authority. Existing descriptor/type/source/catalog/caller provenance
gates stay mandatory. Neither historical browser type contains the required
exact machine-descriptor map, and neither is promoted by this change. There are
still no installed native owner adapters, authenticated provenance verifier,
contained session/ordered run orchestration or live CLI dispatch. A journal digest
is a fingerprint and cannot authenticate native evidence. Completion still needs
owner-admitted descriptors and actual structural, mint/delivery, consumer and
revocation/rollback evidence. Local fixture success is not native acceptance.
Tests use the actual producer checkout (override sibling location with
`WARDEN_TEST_NETKINGDOM_ROOT`), the real Executor, SQLite journal and injected
test-only owner adapter/evidence verifier. Their descriptor extensions and case
materialized labels exist only in memory. Canonical case/type bytes stay intact.
Producer integration refinement: the accepted NetKingdom browser-case patch
`d32a7f51cbf3012bee3e4bb329f4259dea867eff1c59a2b01a65d72774488d05`
is now the tested producer revision. Its evidence module is imported lazily only
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.
## Original-attempt context candidate — 2026-10-17
Source-review candidate only; no native holder, adapter installation or execution
permission is introduced. The local reconcile callback changes explicitly from
`reconcile(fresh_invocation)` to
`reconcile(fresh_invocation, original_attempt_context)`. Existing one-argument
callbacks fail closed; no signature probing or silent fallback is used. Exact
adapter source/descriptor admission remains required for a real integration.
Journal initialization adds `attempt_context_v1(receipt_seq, invocation_digest)`.
The companion fingerprint is inserted in the same SQLite transaction as each
invocation-bearing receipt, before dispatch. Existing v1 receipt JSON, IDs and
source pins are unchanged. The fingerprint hashes only a fixed validated field
projection: exact Invocation class, bounded step/owner/target/principal/lane/
attempt identifiers, closed actor/operation values and the six Pins digests.
No arbitrary attributes, parameters, OwnerResult payload, evidence identifiers,
credentials or raw callback output are added to this table. Original fields are
reconstructed from current exact admission and the original intent's attempt ID;
they must match that stored fingerprint. Legacy rows are never backfilled and
missing/mismatched context refuses before an owner callback.
The original context identifies the original run intent row/digest and the latest
same-attempt evidence/session/grant fingerprints. Recovery rows lacking invocation
and later interrupted reconciliation attempts do not replace the original run.
The fresh reconciliation attempt remains distinct, with its normal current owner
and session/grant verification. Before any reconciled result can be accepted,
independent evidence verification must also accept purpose
`original-attempt-context:<original-attempt-id>:<fresh-reconcile-attempt-id>`,
source equal to the exact OriginalAttemptContext digest, and the returned owner
evidence reference. This is a new source interface, not existing native assent.
Journal hashes are lookup/correspondence context, never independent authentication.
No actual independent original-attempt holder/readback or verifier is supplied.
NetKingdom's registration adapter still has no reconcile callback and cannot undo
a prior-process creation. Missing original owner proof stays uncertain, including
when the local intent/fingerprint is intact; no native result is inferred from
matching JSON or current target bytes. Ordinary exact pins continue to apply:
a changed caller/session/run-sheet requires separately reviewed migration and
cannot inherit old journal state. Interrupted rollback remains its existing
rollback-uncertain owner gate, not a new supported automatic recovery path.
The focused tests reuse existing synthetic owner/issuer trust and explicitly do
not provide a native JSON trust holder. They cover atomic context/intent visibility,
a distinct interpreter with no original Adapter closure, refusal without independent
original proof, legacy/missing/wrong-attempt/current-context refusal, interrupted
reconciliation continuity, paired-proof replay refusal and transaction failure
before effect. Existing reducer/owner/session/revocation checks remain required.

View file

@ -0,0 +1,2 @@
[pytest]
pythonpath = src

View file

@ -0,0 +1,3 @@
"""OpsWarden — SSH CA and certificate lifecycle manager."""
__version__ = "0.1.0"

View file

@ -0,0 +1,98 @@
"""Reviewed browser metadata projection; no native trust or command dispatch.
The producer validator and all reviewed producer modules are pinned below.
Their verified bytes run in a disposable tree, without caller environment or
ambient Python imports. Type parameters must match the exact reviewed version.
Machine descriptor extensions still require Executor's independent admission.
"""
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import tempfile
from .case_receipt_state import Refused
# Reviewed producer: net-kingdom 015694ee414c802dc2ef1fa0120dfed610086e69.
# Review: docs/reviews/2026-10-16-browser-producer-pin-refresh.md
SOURCE_PINS = {
"tools/security-case-types/security_case_evidence.py": "da8efa59c52ad73283397ebe639ae259d0cb9c6348b5cb829135c24230dcdc75",
"tools/security-case-types/security_case_validator.py": "1bcc62e9bc374c711363295549f3cd5bb0b32952671ed4b08fddc7f74742959b",
"tools/security-scenario-composer/security_scenario_composer.py": "9f0cc69930aa8ee2cc3fb92236e6a99728df17b8c0fe36e1250241c38fc01c4f",
"tools/playbook-capability-contract/playbook_contract_validator.py": "2e1e0c809d4bc9d64c2d974cc23fecd1c5636785154c2554edd9191852fd926d",
}
PARAMETER_PINS = {
"1.1": "9ec2d6113bf70dbd35cfc247412363ff187e792bc82e24159a6f78e090aa3e4c",
"1.2": "c04385d176f5f70257fb0fa6fef7e48ab26ed7cf26f6b1c7f14b16237b0ebea6",
}
_VALIDATE = """
import json, sys
from pathlib import Path
sys.path.insert(0, str(Path(sys.argv[1]) / 'tools/security-case-types'))
import security_case_validator as v
case, typ = json.load(sys.stdin)
issues = v.validate_type(typ) + v.validate_case(case, typ, require_implemented=False)
print('valid' if not v.has_errors(issues) else 'invalid')
"""
class BrowserInputs:
"""Explicit owner configuration, never inferred from a case-supplied path."""
def __init__(self, netkingdom_root: Path):
self.root = Path(netkingdom_root)
def target(self, case: dict, typ: dict, target_parameter: str) -> str:
try:
spec = typ["spec"]
parameters = spec["parameters"]
fingerprint = hashlib.sha256(
json.dumps(parameters, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
if (
spec["id"] != "oidc-browser-relying-party"
or PARAMETER_PINS.get(spec["version"]) != fingerprint
):
raise ValueError
declared = {item["name"]: item for item in parameters}
definition = declared.get(target_parameter, {})
if (
definition.get("sensitivity") not in ("public", "operational")
or definition.get("case_settable") is not True
):
raise ValueError
# No secret references or newly classified metadata are grandfathered.
for name in case["spec"]["parameters"]:
item = declared.get(name, {})
if item.get("case_settable") is not True or item.get(
"sensitivity"
) not in ("public", "operational", "security_sensitive"):
raise ValueError
with tempfile.TemporaryDirectory(
prefix="warden-case-validator-"
) as directory:
for relative, expected in SOURCE_PINS.items():
source = (self.root / relative).read_bytes()
if hashlib.sha256(source).hexdigest() != expected:
raise ValueError
dest = Path(directory) / relative
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(source)
result = subprocess.run(
[sys.executable, "-I", "-c", _VALIDATE, directory],
input=json.dumps([case, typ]),
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
env={},
timeout=10,
check=True,
)
if result.stdout != "valid\n":
raise ValueError
return case["spec"]["parameters"][target_parameter]
except (OSError, ValueError, TypeError, KeyError, subprocess.SubprocessError):
raise Refused("browser metadata failed exact-version validation") from None

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)

View file

@ -0,0 +1,916 @@
"""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_inputs import BrowserInputs
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"
def invocation_fingerprint(invocation: Invocation) -> str:
"""Fingerprint a fixed validated projection; retain no owner payload or refs.
Invocation is not a generic metadata container. Accept only this exact class,
its reviewed identifier fields and closed Pins. Never serialize arbitrary
attributes, case parameters, OwnerResult.evidence or callback output.
"""
if type(invocation) is not Invocation or type(invocation.pins) is not Pins:
raise Refused("exact invocation metadata required")
identifiers = (
invocation.step,
invocation.owner,
invocation.target,
invocation.principal,
invocation.lane,
invocation.attempt_id,
)
if (
not all(
type(value) is str and IDENTIFIER.fullmatch(value) for value in identifiers
)
or invocation.actor not in ("human", "agent")
or invocation.operation not in ("run", "rollback", "reconcile")
):
raise Refused("bounded invocation metadata required")
pins = [
invocation.pins.case,
invocation.pins.case_type,
invocation.pins.adapter,
invocation.pins.catalog,
invocation.pins.acceptance,
invocation.pins.run_sheet,
]
if not all(type(value) is str and DIGEST.fullmatch(value) for value in pins):
raise Refused("exact invocation pins required")
projection = [
"warden.case.invocation-fingerprint.v1",
*identifiers,
invocation.actor,
invocation.operation,
*pins,
]
return digest(json.dumps(projection, separators=(",", ":")).encode())
@dataclass(frozen=True, slots=True)
class OriginalAttemptContext:
"""Local lookup context, never independently authenticated owner evidence.
Original fields are reconstructed from current exact admission and checked
against the fingerprint atomically stored with the original running intent.
Journal fingerprints cannot establish native creation or authorize an undo.
"""
original: Invocation
intent_sequence: int
intent_digest: str
result_sequence: int
evidence_digest: str | None
session_revocation_digest: str | None
grant_revocation_digest: str | None
@property
def digest(self) -> str:
projection = [
"warden.case.original-context.v1",
invocation_fingerprint(self.original),
self.intent_sequence,
self.intent_digest,
self.result_sequence,
self.evidence_digest,
self.session_revocation_digest,
self.grant_revocation_digest,
]
return digest(json.dumps(projection, separators=(",", ":")).encode())
@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, OriginalAttemptContext], 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)"
)
# Additive local storage v1; no existing receipt payload is rewritten or
# backfilled. Legacy attempts without this atomic companion fail closed.
self.db.execute(
"CREATE TABLE IF NOT EXISTS attempt_context_v1 "
"(receipt_seq INTEGER PRIMARY KEY, invocation_digest 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,
):
# Validate/hash only the fixed field contract before opening the write.
fingerprint = (
invocation_fingerprint(invocation) if invocation is not None else None
)
self.db.execute("BEGIN IMMEDIATE")
try:
inserted = 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=(",", ":"),
),
),
)
if fingerprint is not None:
self.db.execute(
"INSERT INTO attempt_context_v1(receipt_seq,invocation_digest) "
"VALUES (?,?)",
(inserted.lastrowid, fingerprint),
)
self.db.execute("COMMIT")
except BaseException:
self.db.execute("ROLLBACK")
raise
def original_context(
self, binding: str, invocation: Invocation
) -> OriginalAttemptContext:
"""Locate original run intent across recovery/reconcile rows, never trust it.
Called under the existing journal lock after exact current admission and
state validation. No owner receipt/reference/payload is hydrated here.
"""
rows = self.db.execute(
"SELECT seq,payload FROM receipts WHERE binding=? "
"ORDER BY seq DESC LIMIT ?",
(binding, MAX_HISTORY + 1),
).fetchall()
if len(rows) > MAX_HISTORY:
raise Refused("bounded original context requires owner archival")
try:
decoded = [(seq, raw, json.loads(raw)) for seq, raw in rows]
intent = next(
(
row
for row in decoded
if row[2].get("operation") == "run"
and row[2].get("receipts")
and row[2]["receipts"][-1].get("outcome") == "running"
),
None,
)
if intent is None:
raise Refused("original run context absent; no legacy backfill")
sequence, raw, payload = intent
attempt = payload.get("attempt_id")
original = replace(invocation, attempt_id=attempt, operation="run")
fingerprint = invocation_fingerprint(original)
# Match both old journal binding and independently re-admitted fields.
if (
payload.get("journal_schema") != "warden.case.step-journal.v1"
or payload.get("pins") != asdict(invocation.pins)
or payload.get("step_id") != invocation.step
or payload.get("owner") != invocation.owner
or binding != f"{invocation.step}:{invocation.target}"
):
raise Refused("original context differs from current admission")
latest = next(
row
for row in decoded
if row[2].get("attempt_id") == attempt
and row[2].get("operation") == "run"
)
for seq, _, row in (intent, latest):
stored = self.db.execute(
"SELECT invocation_digest FROM attempt_context_v1 "
"WHERE receipt_seq=?",
(seq,),
).fetchone()
if (
stored is None
or stored[0] != fingerprint
or row.get("pins") != asdict(invocation.pins)
or row.get("step_id") != invocation.step
or row.get("owner") != invocation.owner
):
raise Refused("original context fingerprint absent or changed")
proofs = [
latest[2].get(field)
for field in (
"evidence_digest",
"session_revocation_digest",
"grant_revocation_digest",
)
]
if any(
value is not None
and (type(value) is not str or not DIGEST.fullmatch(value))
for value in proofs
):
raise Refused("original evidence fingerprint invalid")
return OriginalAttemptContext(
original, sequence, digest(raw.encode()), latest[0], *proofs
)
except (TypeError, ValueError, KeyError, AttributeError, StopIteration):
raise Refused("original context unavailable or malformed") from None
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],
*,
browser_inputs: BrowserInputs | None = None,
):
self.journal, self.adapters, self.evidence_verifier = (
journal,
dict(adapters),
evidence_verifier,
)
self._admitted = {}
self.browser_inputs = browser_inputs
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")
if case_type.get("id") == "oidc-browser-relying-party":
if type(self.browser_inputs) is not BrowserInputs:
raise Refused("browser metadata validator is not configured")
target = self.browser_inputs.target(
yaml.safe_load(case_source),
yaml.safe_load(type_source),
descriptor.target_parameter,
)
else:
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")
original = self.journal.original_context(binding, invocation)
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 = adapter.reconcile(dispatched, original)
# Local context is only a lookup key. Require the independent
# owner verifier to bind its exact original/fresh attempt pair
# and context to the returned evidence before accepting a result.
if (
type(result) is not OwnerResult
or self._verify_evidence(
f"original-attempt-context:{original.original.attempt_id}:"
f"{dispatched.attempt_id}",
original.digest,
result.evidence,
)
is not True
):
raise Refused("independent original context evidence absent")
result = self._result(adapter, dispatched, result)
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,291 @@
"""Context persistence/refusal only, without a native holder or trusted JSON.
Existing fixture owner/issuer trust is synthetic. The new fresh-process trial
proves context survives; missing independent proof must retain uncertainty.
"""
from dataclasses import asdict, replace
import json
from pathlib import Path
import sqlite3
import subprocess
import sys
import pytest
from test_case_run import bound, rig as rig
from warden.case_receipt_state import Outcome, Pins, Refused
from warden.case_run import (
Executor,
Invocation,
Journal,
OwnerResult,
invocation_fingerprint,
)
def lose(rig, hard=False):
executor, args, calls, sources, kwargs = rig
old = executor.adapters[args[0].adapter]
def lost(inv):
calls.append(inv)
if hard:
raise KeyboardInterrupt
raise TimeoutError("owner output must not persist")
executor.adapters[old.reference] = replace(old, invoke=lost)
if hard:
with pytest.raises(KeyboardInterrupt):
executor.run(*args)
else:
assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
return executor, args, calls, sources, kwargs, old
def test_context_committed_with_intent_before_effect_without_new_raw_fields(rig):
executor, args, calls, _, _ = rig
old = executor.adapters[args[0].adapter]
def inspect(inv):
with sqlite3.connect(executor.journal.directory / "journal.sqlite") as other:
payload, fingerprint = other.execute(
"SELECT r.payload,c.invocation_digest FROM receipts r JOIN attempt_context_v1 c ON c.receipt_seq=r.seq ORDER BY r.seq DESC LIMIT 1"
).fetchone()
row = json.loads(payload)
assert (
row["attempt_id"] == inv.attempt_id
and row["receipts"][-1]["outcome"] == "running"
)
assert fingerprint == invocation_fingerprint(inv)
assert inv.principal not in payload and inv.principal not in fingerprint
assert set(row) == {
"step_id",
"owner",
"pins",
"allowed_created",
"retry_safe",
"receipts",
"journal_schema",
"evidence_digest",
"session_revoked",
"session_revocation_digest",
"grant_revocation_digest",
"attempt_id",
"operation",
}
return old.invoke(inv)
executor.adapters[old.reference] = replace(old, invoke=inspect)
assert executor.run(*args).last.outcome is Outcome.SUCCESS and len(calls) == 1
def test_fresh_process_recovers_identity_but_unproved_reconcile_stays_uncertain(rig):
executor, args, calls, sources, kwargs, old = lose(rig, hard=True)
original = calls[-1]
code = """import json,sys
from pathlib import Path
from warden.case_run import Journal,Invocation
from warden.case_receipt_state import Pins
obj=json.load(sys.stdin);obj['pins']=Pins(**obj['pins']);j=Journal(Path(sys.argv[1]))
with j.locked():
c=j.original_context('mint-secret:fixture-client',Invocation(**obj))
print(json.dumps({'id':c.original.attempt_id,'op':c.original.operation,'evidence':c.evidence_digest}))
"""
env = {
"PATH": "/usr/bin:/bin",
"PYTHONPATH": str(Path(__file__).parents[1] / "src"),
"PYTHONDONTWRITEBYTECODE": "1",
}
result = subprocess.run(
[sys.executable, "-B", "-c", code, str(executor.journal.directory)],
input=json.dumps(asdict(args[1])),
text=True,
capture_output=True,
env=env,
timeout=15,
check=True,
)
recovered = json.loads(result.stdout)
assert recovered == {"id": original.attempt_id, "op": "run", "evidence": None}
seen = []
def reconcile(fresh, context):
seen.append((fresh, context))
assert fresh.attempt_id != context.original.attempt_id == original.attempt_id
assert fresh.operation == "reconcile" and context.original.operation == "run"
return bound(
fresh, OwnerResult(Outcome.SUCCESS, "owner-receipt", ("case-policy",), True)
)
def reject_original(purpose, source, evidence):
return (
False
if purpose.startswith("original-attempt-context:")
else executor.evidence_verifier(purpose, source, evidence)
)
reopened = Executor(
Journal(executor.journal.directory),
{old.reference: replace(old, reconcile=reconcile)},
reject_original,
)
admitted = reopened.admit(*sources, **kwargs)
with pytest.raises(Refused):
reopened.reconcile(*admitted)
assert len(seen) == len(calls) == 1
assert (
reopened.journal.load("mint-secret:fixture-client", admitted[2]).last.outcome
is Outcome.UNCERTAIN
)
with pytest.raises(Refused):
reopened.run(*admitted, retry=True)
with pytest.raises(Refused):
reopened.rollback(*admitted)
def test_legacy_context_not_backfilled_and_refused_before_callback(rig):
executor, args, calls, _, _, old = lose(rig)
executor.journal.db.execute("DROP TABLE attempt_context_v1")
executor.journal = Journal(executor.journal.directory)
seen = []
executor.adapters[old.reference] = replace(old, reconcile=lambda *v: seen.append(v))
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen and len(calls) == 1
assert executor.journal.db.execute(
"SELECT COUNT(*) FROM attempt_context_v1"
).fetchone() == (0,)
@pytest.mark.parametrize(
"field,value",
[
("principal", "other"),
("actor", "agent"),
("lane", "other-lane"),
("target", "other-target"),
("owner", "other-owner"),
("pins", Pins(*["sha256:" + "f" * 64] * 6)),
],
)
def test_wrong_readmitted_original_context_refused(rig, field, value):
executor, args, _, _, _, _ = lose(rig)
with executor.journal.locked(), pytest.raises(Refused):
executor.journal.original_context(
"mint-secret:fixture-client", replace(args[1], **{field: value})
)
def test_wrong_original_attempt_or_missing_row_link_refuses_callback(rig):
executor, args, _, _, _, old = lose(rig)
row = executor.journal.db.execute(
"SELECT seq,payload FROM receipts ORDER BY seq LIMIT 1"
).fetchone()
payload = json.loads(row[1])
payload["attempt_id"] = "attempt-wrong"
executor.journal.db.execute(
"UPDATE receipts SET payload=? WHERE seq=?", (json.dumps(payload), row[0])
)
seen = []
executor.adapters[old.reference] = replace(old, reconcile=lambda *v: seen.append(v))
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen
executor.journal.db.execute(
"DELETE FROM attempt_context_v1 WHERE receipt_seq=?", (row[0],)
)
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen
def test_context_write_failure_rolls_back_intent_and_prevents_effect(rig):
executor, args, calls, _, _ = rig
executor.journal.db.execute(
"CREATE TRIGGER fail_context BEFORE INSERT ON attempt_context_v1 BEGIN SELECT RAISE(ABORT,'fixture'); END"
)
with pytest.raises(sqlite3.IntegrityError):
executor.run(*args)
assert not calls
assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
0,
)
@pytest.mark.parametrize(
"bad",
[
{},
object(),
Invocation(
"step",
"owner",
"target",
"human",
"actor",
"lane",
Pins(*["sha256:" + "1" * 64] * 6),
attempt_id={"owner": "payload"},
operation="run",
),
],
)
def test_arbitrary_context_never_serialized_or_persisted(rig, bad):
executor, args, calls, _, _ = rig
with pytest.raises(Refused):
executor.journal.save("binding", args[2], invocation=bad)
assert not calls
assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
0,
)
def test_interrupted_reconcile_preserves_original_run_for_followup(rig):
executor, args, calls, _, _, old = lose(rig)
seen = []
def interrupted(fresh, original):
seen.append((fresh, original))
raise KeyboardInterrupt
executor.adapters[old.reference] = replace(old, reconcile=interrupted)
for _ in range(2):
with pytest.raises(KeyboardInterrupt):
executor.reconcile(*args)
assert seen[0][0].attempt_id != seen[1][0].attempt_id
assert seen[0][1].original == seen[1][1].original == calls[0]
assert seen[0][1].digest == seen[1][1].digest and len(calls) == 1
def test_prior_context_proof_cannot_authenticate_new_reconcile(rig):
executor, args, calls, _, _, _ = lose(rig)
old = executor.evidence_verifier
purposes = []
def verify(purpose, source, evidence):
if purpose.startswith("original-attempt-context:"):
purposes.append((purpose, source, evidence))
return purpose.endswith(":attempt-from-another-reconcile")
return old(purpose, source, evidence)
executor.evidence_verifier = verify
with pytest.raises(Refused):
executor.reconcile(*args)
assert len(purposes) == 1 and purposes[0][0].split(":")[1] == calls[0].attempt_id
assert purposes[0][0].split(":")[2] != calls[0].attempt_id
assert (
executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
is Outcome.UNCERTAIN
)
def test_one_argument_legacy_callback_cannot_ignore_original_context(rig):
executor, args, _, _, _, old = lose(rig)
seen = []
def legacy(inv):
seen.append(inv)
executor.adapters[old.reference] = replace(old, reconcile=legacy)
with pytest.raises(Refused):
executor.reconcile(*args)
assert not seen

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")

View file

@ -0,0 +1,644 @@
"""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, original: 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("original-attempt-context:"):
# Existing fixture trust only; exact lookup context, not a real holder.
context = executor.journal.original_context(
"mint-secret:fixture-client", admitted[1]
)
parts = purpose.split(":")
return (
len(parts) == 3
and parts[1] == context.original.attempt_id
and parts[2] != context.original.attempt_id
and source == context.digest
and evidence == "owner-receipt"
)
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

@ -0,0 +1 @@
{"phase":"candidate test setup","outcome":"failed before tests","reason":"Relative packet paths were applied while cwd was already candidate; no source edit or tests executed."}

View file

@ -0,0 +1,2 @@
.................................................................... [100%]
68 passed in 2.06s

View file

@ -0,0 +1 @@
3 files already formatted

View file

@ -0,0 +1 @@
All checks passed!

View file

@ -0,0 +1,108 @@
{
"files": [
{
"path": "candidate/docs/security-case-execution-kernel-v1.md",
"sha256": "sha256:1299999413739c92a9d41a591baeeb1e8cd912e425db30468e15f7cccedee8ec"
},
{
"path": "candidate/pytest.ini",
"sha256": "sha256:363381771127e3479fe49773659cc2a8dc2b2f727459ebd057ac39e79e98a94e"
},
{
"path": "candidate/src/warden/__init__.py",
"sha256": "sha256:047e0feba479474235f0707a207655ecfddfb33cd4f54069134419b8ca4ea24a"
},
{
"path": "candidate/src/warden/case_inputs.py",
"sha256": "sha256:1334b0cd9897e763cfea2d306e997c659f3f284ecc3f9612140c2104835da7c7"
},
{
"path": "candidate/src/warden/case_receipt_state.py",
"sha256": "sha256:a2cda7f187e77bd76d4d209505638de44a587fae0bc033ec75c23ee9ea9f73bc"
},
{
"path": "candidate/src/warden/case_run.py",
"sha256": "sha256:3b1e923b34fbdd7e3166c3963451363099f8cab27f677de5dba70313561172be"
},
{
"path": "candidate/tests/test_case_original_context.py",
"sha256": "sha256:dd3a365be56254755b72ff9119215239e8b7e3311ce39bcc5c4dd0b9baf213ac"
},
{
"path": "candidate/tests/test_case_receipt_state.py",
"sha256": "sha256:6ca261660744ea187b7a7aa79cfa5e0d05e7e53af72f9c12d6c9ed375a4130e9"
},
{
"path": "candidate/tests/test_case_run.py",
"sha256": "sha256:004b1730a88c28ff3c047070cf8ed2606737b2a328873e3c2d8dcf0c7c573bac"
},
{
"path": "failed-preparation-r1.json",
"sha256": "sha256:b8d3b020565dafa13af6ebe935cc830d4d35d30a720b79a7498863d9b28af6b2"
},
{
"path": "focused-tests.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "focused-tests.stdout.txt",
"sha256": "sha256:c744439c895a0dfd417791c9cf21d3c55cb3d416ccc724bad6fd66da615d5c70"
},
{
"path": "format.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "format.stdout.txt",
"sha256": "sha256:eee76f865de5a9f2456950f710ae5d5f9b5bd0ea02aa54f614c5eed6642a0bfd"
},
{
"path": "lint.stderr.txt",
"sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "lint.stdout.txt",
"sha256": "sha256:82b3e6a6c090a57601d22943bd23fca9218d1031dbe5a7b754092f9a156b4f18"
},
{
"path": "original-context.patch",
"sha256": "sha256:99bc0e6e4e3e1d86267f15cb668f4e0e8147c3c4b227d8ffed917d8e91e42df9"
},
{
"path": "owner-review.json",
"sha256": "sha256:048756d19b76c064b9ab17f475ce42420261b450976714b21e819d4b3e7ea57b"
},
{
"path": "patch-check.json",
"sha256": "sha256:358a1dfa2e454d8386e1c1ba0e03f607774a83169acd03ec7230527f6a0c7247"
},
{
"path": "preimages/docs/security-case-execution-kernel-v1.md",
"sha256": "sha256:0eb563dbaea739fba928d04708c2c2bdac592783bbe68e1271041b0d1b1994cb"
},
{
"path": "preimages/src/warden/case_run.py",
"sha256": "sha256:afb5ce9c59b9748f85c9f1d4af04b255087632c58de8dcd8c9426fc1ed4db068"
},
{
"path": "preimages/tests/test_case_run.py",
"sha256": "sha256:1e70ceb858af0eb3a126132a3c3606706120c0caf58ca9d45deb43f217705c7e"
},
{
"path": "retained-preparation-failures.json",
"sha256": "sha256:6bf6d4e17224a67366a66e405469b612096f79060744e1e4efbcc9070b58e7de"
},
{
"path": "source-references.json",
"sha256": "sha256:87b82dfdca444710f4079011f1ad80e1b6524485a6c6b5dc6bfc204fef734b11"
},
{
"path": "superseded-mixed-base.patch",
"sha256": "sha256:af34199a9940508e3ccc3c3eeb7800e8e760af3943a74aec78941f0fc61dce68"
},
{
"path": "verification.json",
"sha256": "sha256:e38169127cc4eb700878198c85d43c7141efc4827dde682c0eb9a1866e3c6207"
}
]
}

View file

@ -0,0 +1,636 @@
--- a/src/warden/case_run.py
+++ b/src/warden/case_run.py
@@ -170,6 +170,83 @@
operation: str = "admission"
+def invocation_fingerprint(invocation: Invocation) -> str:
+ """Fingerprint a fixed validated projection; retain no owner payload or refs.
+
+ Invocation is not a generic metadata container. Accept only this exact class,
+ its reviewed identifier fields and closed Pins. Never serialize arbitrary
+ attributes, case parameters, OwnerResult.evidence or callback output.
+ """
+ if type(invocation) is not Invocation or type(invocation.pins) is not Pins:
+ raise Refused("exact invocation metadata required")
+ identifiers = (
+ invocation.step,
+ invocation.owner,
+ invocation.target,
+ invocation.principal,
+ invocation.lane,
+ invocation.attempt_id,
+ )
+ if (
+ not all(
+ type(value) is str and IDENTIFIER.fullmatch(value) for value in identifiers
+ )
+ or invocation.actor not in ("human", "agent")
+ or invocation.operation not in ("run", "rollback", "reconcile")
+ ):
+ raise Refused("bounded invocation metadata required")
+ pins = [
+ invocation.pins.case,
+ invocation.pins.case_type,
+ invocation.pins.adapter,
+ invocation.pins.catalog,
+ invocation.pins.acceptance,
+ invocation.pins.run_sheet,
+ ]
+ if not all(type(value) is str and DIGEST.fullmatch(value) for value in pins):
+ raise Refused("exact invocation pins required")
+ projection = [
+ "warden.case.invocation-fingerprint.v1",
+ *identifiers,
+ invocation.actor,
+ invocation.operation,
+ *pins,
+ ]
+ return digest(json.dumps(projection, separators=(",", ":")).encode())
+
+
+@dataclass(frozen=True, slots=True)
+class OriginalAttemptContext:
+ """Local lookup context, never independently authenticated owner evidence.
+
+ Original fields are reconstructed from current exact admission and checked
+ against the fingerprint atomically stored with the original running intent.
+ Journal fingerprints cannot establish native creation or authorize an undo.
+ """
+
+ original: Invocation
+ intent_sequence: int
+ intent_digest: str
+ result_sequence: int
+ evidence_digest: str | None
+ session_revocation_digest: str | None
+ grant_revocation_digest: str | None
+
+ @property
+ def digest(self) -> str:
+ projection = [
+ "warden.case.original-context.v1",
+ invocation_fingerprint(self.original),
+ self.intent_sequence,
+ self.intent_digest,
+ self.result_sequence,
+ self.evidence_digest,
+ self.session_revocation_digest,
+ self.grant_revocation_digest,
+ ]
+ return digest(json.dumps(projection, separators=(",", ":")).encode())
+
+
@dataclass(frozen=True, slots=True)
class Adapter:
reference: str
@@ -177,7 +254,7 @@
invoke: Callable[[Invocation], OwnerResult]
verify: Callable[[Invocation, OwnerResult], bool]
rollback: Callable[[Invocation, tuple[str, ...]], OwnerResult] | None = None
- reconcile: Callable[[Invocation], OwnerResult] | None = None
+ reconcile: Callable[[Invocation, OriginalAttemptContext], OwnerResult] | None = None
class Journal:
@@ -204,6 +281,12 @@
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)"
+ )
+ # Additive local storage v1; no existing receipt payload is rewritten or
+ # backfilled. Legacy attempts without this atomic companion fail closed.
+ self.db.execute(
+ "CREATE TABLE IF NOT EXISTS attempt_context_v1 "
+ "(receipt_seq INTEGER PRIMARY KEY, invocation_digest TEXT NOT NULL)"
)
@contextmanager
@@ -280,9 +363,13 @@
evidence: OwnerResult | None = None,
invocation: Invocation | None = None,
):
+ # Validate/hash only the fixed field contract before opening the write.
+ fingerprint = (
+ invocation_fingerprint(invocation) if invocation is not None else None
+ )
self.db.execute("BEGIN IMMEDIATE")
try:
- self.db.execute(
+ inserted = self.db.execute(
"INSERT INTO receipts(binding,payload) VALUES (?,?)",
(
binding,
@@ -311,10 +398,98 @@
),
),
)
+ if fingerprint is not None:
+ self.db.execute(
+ "INSERT INTO attempt_context_v1(receipt_seq,invocation_digest) "
+ "VALUES (?,?)",
+ (inserted.lastrowid, fingerprint),
+ )
self.db.execute("COMMIT")
except BaseException:
self.db.execute("ROLLBACK")
raise
+
+ def original_context(
+ self, binding: str, invocation: Invocation
+ ) -> OriginalAttemptContext:
+ """Locate original run intent across recovery/reconcile rows, never trust it.
+
+ Called under the existing journal lock after exact current admission and
+ state validation. No owner receipt/reference/payload is hydrated here.
+ """
+ rows = self.db.execute(
+ "SELECT seq,payload FROM receipts WHERE binding=? "
+ "ORDER BY seq DESC LIMIT ?",
+ (binding, MAX_HISTORY + 1),
+ ).fetchall()
+ if len(rows) > MAX_HISTORY:
+ raise Refused("bounded original context requires owner archival")
+ try:
+ decoded = [(seq, raw, json.loads(raw)) for seq, raw in rows]
+ intent = next(
+ (
+ row
+ for row in decoded
+ if row[2].get("operation") == "run"
+ and row[2].get("receipts")
+ and row[2]["receipts"][-1].get("outcome") == "running"
+ ),
+ None,
+ )
+ if intent is None:
+ raise Refused("original run context absent; no legacy backfill")
+ sequence, raw, payload = intent
+ attempt = payload.get("attempt_id")
+ original = replace(invocation, attempt_id=attempt, operation="run")
+ fingerprint = invocation_fingerprint(original)
+ # Match both old journal binding and independently re-admitted fields.
+ if (
+ payload.get("journal_schema") != "warden.case.step-journal.v1"
+ or payload.get("pins") != asdict(invocation.pins)
+ or payload.get("step_id") != invocation.step
+ or payload.get("owner") != invocation.owner
+ or binding != f"{invocation.step}:{invocation.target}"
+ ):
+ raise Refused("original context differs from current admission")
+ latest = next(
+ row
+ for row in decoded
+ if row[2].get("attempt_id") == attempt
+ and row[2].get("operation") == "run"
+ )
+ for seq, _, row in (intent, latest):
+ stored = self.db.execute(
+ "SELECT invocation_digest FROM attempt_context_v1 "
+ "WHERE receipt_seq=?",
+ (seq,),
+ ).fetchone()
+ if (
+ stored is None
+ or stored[0] != fingerprint
+ or row.get("pins") != asdict(invocation.pins)
+ or row.get("step_id") != invocation.step
+ or row.get("owner") != invocation.owner
+ ):
+ raise Refused("original context fingerprint absent or changed")
+ proofs = [
+ latest[2].get(field)
+ for field in (
+ "evidence_digest",
+ "session_revocation_digest",
+ "grant_revocation_digest",
+ )
+ ]
+ if any(
+ value is not None
+ and (type(value) is not str or not DIGEST.fullmatch(value))
+ for value in proofs
+ ):
+ raise Refused("original evidence fingerprint invalid")
+ return OriginalAttemptContext(
+ original, sequence, digest(raw.encode()), latest[0], *proofs
+ )
+ except (TypeError, ValueError, KeyError, AttributeError, StopIteration):
+ raise Refused("original context unavailable or malformed") from None
class Executor:
@@ -660,6 +835,7 @@
raise Refused("no uncertain mutation to reconcile")
if len(state.receipts) > MAX_HISTORY - 4:
raise Refused("bounded journal history requires owner archival")
+ original = self.journal.original_context(binding, invocation)
state = replace(
state,
receipts=(
@@ -670,9 +846,22 @@
dispatched = self._attempt(invocation, state, "reconcile")
self.journal.save(binding, state, invocation=dispatched)
with contained_output():
- result = self._result(
- adapter, dispatched, adapter.reconcile(dispatched)
- )
+ result = adapter.reconcile(dispatched, original)
+ # Local context is only a lookup key. Require the independent
+ # owner verifier to bind its exact original/fresh attempt pair
+ # and context to the returned evidence before accepting a result.
+ if (
+ type(result) is not OwnerResult
+ or self._verify_evidence(
+ f"original-attempt-context:{original.original.attempt_id}:"
+ f"{dispatched.attempt_id}",
+ original.digest,
+ result.evidence,
+ )
+ is not True
+ ):
+ raise Refused("independent original context evidence absent")
+ result = self._result(adapter, dispatched, result)
if result.outcome not in (
Outcome.NO_CHANGE,
Outcome.SUCCESS,
--- a/tests/test_case_run.py
+++ b/tests/test_case_run.py
@@ -89,7 +89,7 @@
grant_revocation_evidence="grant-revoked",
),
),
- lambda inv: bound(
+ lambda inv, original: bound(
inv,
OwnerResult(
Outcome.NO_CHANGE,
@@ -113,6 +113,19 @@
}
def verifier(purpose, source, evidence):
+ if purpose.startswith("original-attempt-context:"):
+ # Existing fixture trust only; exact lookup context, not a real holder.
+ context = executor.journal.original_context(
+ "mint-secret:fixture-client", admitted[1]
+ )
+ parts = purpose.split(":")
+ return (
+ len(parts) == 3
+ and parts[1] == context.original.attempt_id
+ and parts[2] != context.original.attempt_id
+ and source == context.digest
+ and evidence == "owner-receipt"
+ )
if purpose.startswith(("session-revocation:", "grant-revocation:")):
scope, lane, operation, attempt = purpose.split(":")
return (
--- /dev/null
+++ b/tests/test_case_original_context.py
@@ -0,0 +1,291 @@
+"""Context persistence/refusal only, without a native holder or trusted JSON.
+
+Existing fixture owner/issuer trust is synthetic. The new fresh-process trial
+proves context survives; missing independent proof must retain uncertainty.
+"""
+
+from dataclasses import asdict, replace
+import json
+from pathlib import Path
+import sqlite3
+import subprocess
+import sys
+import pytest
+from test_case_run import bound, rig as rig
+from warden.case_receipt_state import Outcome, Pins, Refused
+from warden.case_run import (
+ Executor,
+ Invocation,
+ Journal,
+ OwnerResult,
+ invocation_fingerprint,
+)
+
+
+def lose(rig, hard=False):
+ executor, args, calls, sources, kwargs = rig
+ old = executor.adapters[args[0].adapter]
+
+ def lost(inv):
+ calls.append(inv)
+ if hard:
+ raise KeyboardInterrupt
+ raise TimeoutError("owner output must not persist")
+
+ executor.adapters[old.reference] = replace(old, invoke=lost)
+ if hard:
+ with pytest.raises(KeyboardInterrupt):
+ executor.run(*args)
+ else:
+ assert executor.run(*args).last.outcome is Outcome.UNCERTAIN
+ return executor, args, calls, sources, kwargs, old
+
+
+def test_context_committed_with_intent_before_effect_without_new_raw_fields(rig):
+ executor, args, calls, _, _ = rig
+ old = executor.adapters[args[0].adapter]
+
+ def inspect(inv):
+ with sqlite3.connect(executor.journal.directory / "journal.sqlite") as other:
+ payload, fingerprint = other.execute(
+ "SELECT r.payload,c.invocation_digest FROM receipts r JOIN attempt_context_v1 c ON c.receipt_seq=r.seq ORDER BY r.seq DESC LIMIT 1"
+ ).fetchone()
+ row = json.loads(payload)
+ assert (
+ row["attempt_id"] == inv.attempt_id
+ and row["receipts"][-1]["outcome"] == "running"
+ )
+ assert fingerprint == invocation_fingerprint(inv)
+ assert inv.principal not in payload and inv.principal not in fingerprint
+ assert set(row) == {
+ "step_id",
+ "owner",
+ "pins",
+ "allowed_created",
+ "retry_safe",
+ "receipts",
+ "journal_schema",
+ "evidence_digest",
+ "session_revoked",
+ "session_revocation_digest",
+ "grant_revocation_digest",
+ "attempt_id",
+ "operation",
+ }
+ return old.invoke(inv)
+
+ executor.adapters[old.reference] = replace(old, invoke=inspect)
+ assert executor.run(*args).last.outcome is Outcome.SUCCESS and len(calls) == 1
+
+
+def test_fresh_process_recovers_identity_but_unproved_reconcile_stays_uncertain(rig):
+ executor, args, calls, sources, kwargs, old = lose(rig, hard=True)
+ original = calls[-1]
+ code = """import json,sys
+from pathlib import Path
+from warden.case_run import Journal,Invocation
+from warden.case_receipt_state import Pins
+obj=json.load(sys.stdin);obj['pins']=Pins(**obj['pins']);j=Journal(Path(sys.argv[1]))
+with j.locked():
+ c=j.original_context('mint-secret:fixture-client',Invocation(**obj))
+ print(json.dumps({'id':c.original.attempt_id,'op':c.original.operation,'evidence':c.evidence_digest}))
+"""
+ env = {
+ "PATH": "/usr/bin:/bin",
+ "PYTHONPATH": str(Path(__file__).parents[1] / "src"),
+ "PYTHONDONTWRITEBYTECODE": "1",
+ }
+ result = subprocess.run(
+ [sys.executable, "-B", "-c", code, str(executor.journal.directory)],
+ input=json.dumps(asdict(args[1])),
+ text=True,
+ capture_output=True,
+ env=env,
+ timeout=15,
+ check=True,
+ )
+ recovered = json.loads(result.stdout)
+ assert recovered == {"id": original.attempt_id, "op": "run", "evidence": None}
+ seen = []
+
+ def reconcile(fresh, context):
+ seen.append((fresh, context))
+ assert fresh.attempt_id != context.original.attempt_id == original.attempt_id
+ assert fresh.operation == "reconcile" and context.original.operation == "run"
+ return bound(
+ fresh, OwnerResult(Outcome.SUCCESS, "owner-receipt", ("case-policy",), True)
+ )
+
+ def reject_original(purpose, source, evidence):
+ return (
+ False
+ if purpose.startswith("original-attempt-context:")
+ else executor.evidence_verifier(purpose, source, evidence)
+ )
+
+ reopened = Executor(
+ Journal(executor.journal.directory),
+ {old.reference: replace(old, reconcile=reconcile)},
+ reject_original,
+ )
+ admitted = reopened.admit(*sources, **kwargs)
+ with pytest.raises(Refused):
+ reopened.reconcile(*admitted)
+ assert len(seen) == len(calls) == 1
+ assert (
+ reopened.journal.load("mint-secret:fixture-client", admitted[2]).last.outcome
+ is Outcome.UNCERTAIN
+ )
+ with pytest.raises(Refused):
+ reopened.run(*admitted, retry=True)
+ with pytest.raises(Refused):
+ reopened.rollback(*admitted)
+
+
+def test_legacy_context_not_backfilled_and_refused_before_callback(rig):
+ executor, args, calls, _, _, old = lose(rig)
+ executor.journal.db.execute("DROP TABLE attempt_context_v1")
+ executor.journal = Journal(executor.journal.directory)
+ seen = []
+ executor.adapters[old.reference] = replace(old, reconcile=lambda *v: seen.append(v))
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert not seen and len(calls) == 1
+ assert executor.journal.db.execute(
+ "SELECT COUNT(*) FROM attempt_context_v1"
+ ).fetchone() == (0,)
+
+
+@pytest.mark.parametrize(
+ "field,value",
+ [
+ ("principal", "other"),
+ ("actor", "agent"),
+ ("lane", "other-lane"),
+ ("target", "other-target"),
+ ("owner", "other-owner"),
+ ("pins", Pins(*["sha256:" + "f" * 64] * 6)),
+ ],
+)
+def test_wrong_readmitted_original_context_refused(rig, field, value):
+ executor, args, _, _, _, _ = lose(rig)
+ with executor.journal.locked(), pytest.raises(Refused):
+ executor.journal.original_context(
+ "mint-secret:fixture-client", replace(args[1], **{field: value})
+ )
+
+
+def test_wrong_original_attempt_or_missing_row_link_refuses_callback(rig):
+ executor, args, _, _, _, old = lose(rig)
+ row = executor.journal.db.execute(
+ "SELECT seq,payload FROM receipts ORDER BY seq LIMIT 1"
+ ).fetchone()
+ payload = json.loads(row[1])
+ payload["attempt_id"] = "attempt-wrong"
+ executor.journal.db.execute(
+ "UPDATE receipts SET payload=? WHERE seq=?", (json.dumps(payload), row[0])
+ )
+ seen = []
+ executor.adapters[old.reference] = replace(old, reconcile=lambda *v: seen.append(v))
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert not seen
+ executor.journal.db.execute(
+ "DELETE FROM attempt_context_v1 WHERE receipt_seq=?", (row[0],)
+ )
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert not seen
+
+
+def test_context_write_failure_rolls_back_intent_and_prevents_effect(rig):
+ executor, args, calls, _, _ = rig
+ executor.journal.db.execute(
+ "CREATE TRIGGER fail_context BEFORE INSERT ON attempt_context_v1 BEGIN SELECT RAISE(ABORT,'fixture'); END"
+ )
+ with pytest.raises(sqlite3.IntegrityError):
+ executor.run(*args)
+ assert not calls
+ assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
+ 0,
+ )
+
+
+@pytest.mark.parametrize(
+ "bad",
+ [
+ {},
+ object(),
+ Invocation(
+ "step",
+ "owner",
+ "target",
+ "human",
+ "actor",
+ "lane",
+ Pins(*["sha256:" + "1" * 64] * 6),
+ attempt_id={"owner": "payload"},
+ operation="run",
+ ),
+ ],
+)
+def test_arbitrary_context_never_serialized_or_persisted(rig, bad):
+ executor, args, calls, _, _ = rig
+ with pytest.raises(Refused):
+ executor.journal.save("binding", args[2], invocation=bad)
+ assert not calls
+ assert executor.journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone() == (
+ 0,
+ )
+
+
+def test_interrupted_reconcile_preserves_original_run_for_followup(rig):
+ executor, args, calls, _, _, old = lose(rig)
+ seen = []
+
+ def interrupted(fresh, original):
+ seen.append((fresh, original))
+ raise KeyboardInterrupt
+
+ executor.adapters[old.reference] = replace(old, reconcile=interrupted)
+ for _ in range(2):
+ with pytest.raises(KeyboardInterrupt):
+ executor.reconcile(*args)
+ assert seen[0][0].attempt_id != seen[1][0].attempt_id
+ assert seen[0][1].original == seen[1][1].original == calls[0]
+ assert seen[0][1].digest == seen[1][1].digest and len(calls) == 1
+
+
+def test_prior_context_proof_cannot_authenticate_new_reconcile(rig):
+ executor, args, calls, _, _, _ = lose(rig)
+ old = executor.evidence_verifier
+ purposes = []
+
+ def verify(purpose, source, evidence):
+ if purpose.startswith("original-attempt-context:"):
+ purposes.append((purpose, source, evidence))
+ return purpose.endswith(":attempt-from-another-reconcile")
+ return old(purpose, source, evidence)
+
+ executor.evidence_verifier = verify
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert len(purposes) == 1 and purposes[0][0].split(":")[1] == calls[0].attempt_id
+ assert purposes[0][0].split(":")[2] != calls[0].attempt_id
+ assert (
+ executor.journal.load("mint-secret:fixture-client", args[2]).last.outcome
+ is Outcome.UNCERTAIN
+ )
+
+
+def test_one_argument_legacy_callback_cannot_ignore_original_context(rig):
+ executor, args, _, _, _, old = lose(rig)
+ seen = []
+
+ def legacy(inv):
+ seen.append(inv)
+
+ executor.adapters[old.reference] = replace(old, reconcile=legacy)
+ with pytest.raises(Refused):
+ executor.reconcile(*args)
+ assert not seen
--- a/docs/security-case-execution-kernel-v1.md
+++ b/docs/security-case-execution-kernel-v1.md
@@ -125,3 +125,52 @@
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.
+
+
+## Original-attempt context candidate — 2026-10-17
+
+Source-review candidate only; no native holder, adapter installation or execution
+permission is introduced. The local reconcile callback changes explicitly from
+`reconcile(fresh_invocation)` to
+`reconcile(fresh_invocation, original_attempt_context)`. Existing one-argument
+callbacks fail closed; no signature probing or silent fallback is used. Exact
+adapter source/descriptor admission remains required for a real integration.
+
+Journal initialization adds `attempt_context_v1(receipt_seq, invocation_digest)`.
+The companion fingerprint is inserted in the same SQLite transaction as each
+invocation-bearing receipt, before dispatch. Existing v1 receipt JSON, IDs and
+source pins are unchanged. The fingerprint hashes only a fixed validated field
+projection: exact Invocation class, bounded step/owner/target/principal/lane/
+attempt identifiers, closed actor/operation values and the six Pins digests.
+No arbitrary attributes, parameters, OwnerResult payload, evidence identifiers,
+credentials or raw callback output are added to this table. Original fields are
+reconstructed from current exact admission and the original intent's attempt ID;
+they must match that stored fingerprint. Legacy rows are never backfilled and
+missing/mismatched context refuses before an owner callback.
+
+The original context identifies the original run intent row/digest and the latest
+same-attempt evidence/session/grant fingerprints. Recovery rows lacking invocation
+and later interrupted reconciliation attempts do not replace the original run.
+The fresh reconciliation attempt remains distinct, with its normal current owner
+and session/grant verification. Before any reconciled result can be accepted,
+independent evidence verification must also accept purpose
+`original-attempt-context:<original-attempt-id>:<fresh-reconcile-attempt-id>`,
+source equal to the exact OriginalAttemptContext digest, and the returned owner
+evidence reference. This is a new source interface, not existing native assent.
+Journal hashes are lookup/correspondence context, never independent authentication.
+
+No actual independent original-attempt holder/readback or verifier is supplied.
+NetKingdom's registration adapter still has no reconcile callback and cannot undo
+a prior-process creation. Missing original owner proof stays uncertain, including
+when the local intent/fingerprint is intact; no native result is inferred from
+matching JSON or current target bytes. Ordinary exact pins continue to apply:
+a changed caller/session/run-sheet requires separately reviewed migration and
+cannot inherit old journal state. Interrupted rollback remains its existing
+rollback-uncertain owner gate, not a new supported automatic recovery path.
+
+The focused tests reuse existing synthetic owner/issuer trust and explicitly do
+not provide a native JSON trust holder. They cover atomic context/intent visibility,
+a distinct interpreter with no original Adapter closure, refusal without independent
+original proof, legacy/missing/wrong-attempt/current-context refusal, interrupted
+reconciliation continuity, paired-proof replay refusal and transaction failure
+before effect. Existing reducer/owner/session/revocation checks remain required.

View file

@ -0,0 +1,97 @@
{
"schema": "custodian.warden-original-context-source-review.v1",
"status": "artifact-only candidate for root review; not applied, published, synced or admitted",
"scope": "WARDEN-WP-0044-T03 source context prerequisite for NK-WP-0048-T15; not a native holder or original registration recovery implementation.",
"owner_heads": {
"ops-warden": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"net-kingdom": "503a4f617502baf38f9a09be11308a015072ef92",
"audit-core": "64509d2c4039ca1249f9e8aabf2bae030e3640cf"
},
"changed_paths": [
{
"path": "src/warden/case_run.py",
"verified_base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"before_sha256": "sha256:afb5ce9c59b9748f85c9f1d4af04b255087632c58de8dcd8c9426fc1ed4db068",
"after_sha256": "sha256:3b1e923b34fbdd7e3166c3963451363099f8cab27f677de5dba70313561172be",
"current_canonical_sha256": "sha256:0574036ca7b74bdb9124f6af4888e63ec231ee60f5c9f34cbe2fb1acee39f671",
"current_matches_HEAD": false
},
{
"path": "tests/test_case_run.py",
"verified_base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"before_sha256": "sha256:1e70ceb858af0eb3a126132a3c3606706120c0caf58ca9d45deb43f217705c7e",
"after_sha256": "sha256:004b1730a88c28ff3c047070cf8ed2606737b2a328873e3c2d8dcf0c7c573bac",
"current_canonical_sha256": "sha256:bc7264848e76e3576821523fec0c4925c4446eaf470f859fe245c98ea072d139",
"current_matches_HEAD": false
},
{
"path": "tests/test_case_original_context.py",
"verified_base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"before_sha256": null,
"after_sha256": "sha256:dd3a365be56254755b72ff9119215239e8b7e3311ce39bcc5c4dd0b9baf213ac",
"current_canonical_sha256": null,
"current_matches_HEAD": true
},
{
"path": "docs/security-case-execution-kernel-v1.md",
"verified_base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"before_sha256": "sha256:0eb563dbaea739fba928d04708c2c2bdac592783bbe68e1271041b0d1b1994cb",
"after_sha256": "sha256:1299999413739c92a9d41a591baeeb1e8cd912e425db30468e15f7cccedee8ec",
"current_canonical_sha256": "sha256:0eb563dbaea739fba928d04708c2c2bdac592783bbe68e1271041b0d1b1994cb",
"current_matches_HEAD": true
}
],
"why_smallest_authorized_slice": "Current Journal saves attempt_id and proof fingerprints but discards full original invocation context; Executor.reconcile passes only a fresh attempt. Preserve exact correspondence using a fingerprint-only companion table rather than persisting arbitrary Invocation or owner payload. Reconstruct original from independently readmitted exact fields and refuse legacy/missing/mismatch. Actual creation must still be independently authenticated.",
"new_local_interfaces": {
"storage": "Additive attempt_context_v1(receipt_seq INTEGER PRIMARY KEY, invocation_digest TEXT NOT NULL); same SQLite transaction as existing receipt insert. No v1 payload or historical-row rewrite/backfill.",
"fixed_projection": "Only exact dataclass Invocation and Pins; explicit bounded identifier fields, closed actor/operation, six exact digest fields. The table stores only SHA256; no raw projected strings.",
"callback": "Adapter.reconcile(fresh Invocation, OriginalAttemptContext) -> OwnerResult; explicit source API change. Legacy one-argument callables fail before their function body.",
"proof": "Independent verifier must authenticate original-attempt-context:<original-id>:<fresh-id>, exact context digest, returned owner evidence, then normal Adapter.verify/current-attempt identity/session/grant checks. A permissive test verifier does not attest native creation."
},
"acceptance_evidence": {
"focused_tests": 68,
"existing_kernel_reducer_cases": 51,
"new_context_cases": 17,
"actual_distinct_interpreter": true,
"actual_sqlite_separate_connection_before_dispatch": true,
"lint": "owner-installed Ruff check and format check pass",
"scope": "Synthetic source adapters/owner evidence only; fresh interpreter proves local context persistence and no-proof refusal, not native original-attempt readback."
},
"safety_and_compatibility": {
"default_adapter_registry_empty": true,
"new_native_holder": false,
"new_replay_or_undo_authority": false,
"netkingdom_adapter_unchanged": "No reconcile callback; post-restart undo still refused.",
"v1_receipt_payload_fields_unchanged": true,
"old_source_bindings_preserved": true,
"missing_legacy_context": "Fail closed; no backfill.",
"caller_session_migration": "Existing exact run-sheet/pins checks stay; different current session/reference cannot inherit previous state. No new grant/scope migration.",
"rollback_uncertainty": "Existing rollback-uncertain gate unchanged; no automatic original rollback recovery.",
"source_trust": "Fingerprint-only Journal context is not independent authority. Actual owner/context verifier/readback must be admitted for meaningful recovery."
},
"remaining_exact_owner_work": [
"Root independently reviews this local storage/API change before canonical application/publication.",
"Warden admits exact adapter/verifier source and purpose contract; no installed native verifier is present.",
"NetKingdom/actual evidence owner supplies authenticated original payload/readback, source/tenant/principal provenance, exact created-only original success and current native revision checks; no matching holder is inferred from Audit custody.",
"Original and fresh parent-session/grant revocation remain separately verified. Current session/pin changes need separately scoped migration design if required.",
"Only after that source integration should exact separately authorized native restart/lost-receipt and created-only undo acceptance be proposed."
],
"failures_retained": "failed-preparation-r1.json and retained-preparation-failures.json preserve wrong-cwd, unavailable PATH Ruff and fixture-import/plugin preparation failures. None are native executions.",
"canonical_changes": false,
"native_calls": false,
"credentials_read": false,
"new_workplans": false,
"tests_mirroring_generic_backend_added": false,
"base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"concurrent_edit_conflict": {
"status": "STOP before canonical apply; root must select/reconcile concurrent variant",
"files": [
"src/warden/case_run.py",
"tests/test_case_run.py"
],
"concurrent_variant": "Actual canonical source now adds tenant/realm, full OriginalIntent serialization, intents table and reconcile_original. These edits appeared after the artifact-only candidate copied the initially clean source. This agent did not write them.",
"candidate_base": "Exact Git HEAD preimages, not current concurrent dirty source. original-context.patch is now rebased to that clean verified HEAD snapshot.",
"superseded_patch": "superseded-mixed-base.patch retained for error history; DO NOT APPLY. It was assembled using the concurrent dirty preimage and could remove those changes.",
"authority": "No canonical code is applied, published, synced or approved by this packet."
}
}

View file

@ -0,0 +1,8 @@
{
"base_commit": "d97652a3bb5fa733b063d71a1b74068efcccf572",
"cwd": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/original-context-r1/preimages",
"exit_code": 0,
"stdout": "",
"stderr": "",
"scope": "Read-only check against exact HEAD fixture, not permission to overwrite concurrent canonical changes."
}

View file

@ -0,0 +1,127 @@
# 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.
## Browser input projection — 2026-10-16 source preparation
An explicitly configured `BrowserInputs(netkingdom_root)` now validates browser
cases before admission. It calls the real NetKingdom type/case validator over
verified producer bytes in a disposable Python process (`-I`, empty environment,
timeout, no inherited output). The validator, composer, capability contract and lazy evidence
modules have exact source pins; missing or changed code refuses. There is no
automatic discovery or download. A producer upgrade needs reviewed pin changes.
Only the exact reviewed 1.1/1.2 browser parameter definitions qualify. A digest
of each complete parameter definition list prevents reclassifying secrets as
metadata, loosening constraints, or adding fields under a recognized version.
The complete case passes producer validation, including required fields,
constraints and credential-shape checks. Explicit fixed/default overrides,
unknown fields and secret references refuse. Security-sensitive browser metadata
can therefore be present without being passed to an owner adapter: the only
projection is its descriptor's exact public/operational target identifier.
No raw parameter values are added to invocations or persisted receipts.
`require_implemented=False` validates the acknowledged type's source, not its
execution authority. Existing descriptor/type/source/catalog/caller provenance
gates stay mandatory. Neither historical browser type contains the required
exact machine-descriptor map, and neither is promoted by this change. There are
still no installed native owner adapters, authenticated provenance verifier,
contained session/ordered run orchestration or live CLI dispatch. A journal digest
is a fingerprint and cannot authenticate native evidence. Completion still needs
owner-admitted descriptors and actual structural, mint/delivery, consumer and
revocation/rollback evidence. Local fixture success is not native acceptance.
Tests use the actual producer checkout (override sibling location with
`WARDEN_TEST_NETKINGDOM_ROOT`), the real Executor, SQLite journal and injected
test-only owner adapter/evidence verifier. Their descriptor extensions and case
materialized labels exist only in memory. Canonical case/type bytes stay intact.
Producer integration refinement: the accepted NetKingdom browser-case patch
`d32a7f51cbf3012bee3e4bb329f4259dea867eff1c59a2b01a65d72774488d05`
is now the tested producer revision. Its evidence module is imported lazily only
by `check-evidence`, outside this admission path; Warden nevertheless pins and
copies it with the complete reviewed producer set. Missing or modified bytes in
any of the four modules refuse admission. The test covers every dependency.

View file

@ -0,0 +1,727 @@
"""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_inputs import BrowserInputs
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],
*,
browser_inputs: BrowserInputs | None = None,
):
self.journal, self.adapters, self.evidence_verifier = (
journal,
dict(adapters),
evidence_verifier,
)
self._admitted = {}
self.browser_inputs = browser_inputs
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")
if case_type.get("id") == "oidc-browser-relying-party":
if type(self.browser_inputs) is not BrowserInputs:
raise Refused("browser metadata validator is not configured")
target = self.browser_inputs.target(
yaml.safe_load(case_source),
yaml.safe_load(type_source),
descriptor.target_parameter,
)
else:
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,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

@ -0,0 +1,22 @@
[
{
"phase": "initial command path",
"outcome": "failed before source edit/tests",
"detail": "Relative packet paths from candidate cwd; corrected working directory. Original failed-preparation-r1.json retained."
},
{
"phase": "lint executable lookup",
"outcome": "ruff command unavailable in PATH",
"detail": "Used existing owner .venv/bin/ruff without installing anything."
},
{
"phase": "first owner lint",
"outcome": "12F401/F811 test-fixture import issues",
"detail": "Source code lint passed; changed new test fixture reuse to pytest plugin registration; no control/source behavior changed."
},
{
"phase": "pytest plugin reuse attempt",
"outcome": "51 existing tests passed,17 new setup errors;2 lint E402",
"detail": "test module plugin registration did not expose fixture. Restored explicit fixture re-export; source control unchanged."
}
]