Record reviewed case source evidence (10/13)
This commit is contained in:
parent
285e590009
commit
30e41c948e
20 changed files with 4331 additions and 0 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"owners": [
|
||||
{
|
||||
"owner": "ops-warden",
|
||||
"published_commit": "b593c879831378db7dc62adfabdfedf2d44ddd38"
|
||||
},
|
||||
{
|
||||
"owner": "net-kingdom",
|
||||
"published_commit": "befb0bc68cd25b625804fce559476eefd702521d"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-platform",
|
||||
"published_commit": "104ed90f42a9a86bf9fda343b49b65a85c9ee4b9"
|
||||
}
|
||||
],
|
||||
"native_target_gates_closed": 0,
|
||||
"all16_native_gates_remain_open": true,
|
||||
"NK48_task_count": 35,
|
||||
"NK48_new_tasks": {
|
||||
"T32": "done: independent mint guard regression",
|
||||
"T33": "done: exact proposed issuer-source reference",
|
||||
"T34": "progress: real Apps inverse backend absent",
|
||||
"T35": "done: authenticated catalog fixtures"
|
||||
},
|
||||
"safe_ordinary_unit_result": "785 passed,4 deselected",
|
||||
"native_or_credential_actions": false,
|
||||
"PARAMETER_PINS_or_draft_promotions": false,
|
||||
"failed_and_unknown_outcomes_retained": true
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from pathlib import Path
|
||||
import hashlib,json,subprocess
|
||||
r=Path('/home/worsch/the-custodian');p=Path(__file__).resolve().parent;base=p.parent
|
||||
def git(root,*a):return subprocess.check_output(['git','-C',str(root),*a],text=True).strip()
|
||||
expected={'ops-warden':'b593c879831378db7dc62adfabdfedf2d44ddd38','net-kingdom':'befb0bc68cd25b625804fce559476eefd702521d','railiance-platform':'104ed90f42a9a86bf9fda343b49b65a85c9ee4b9'}
|
||||
owner_state=[]
|
||||
for owner,want in expected.items():
|
||||
owner_root=r.parent/owner;assert git(owner_root,'rev-parse','HEAD')==want
|
||||
assert git(owner_root,'ls-remote','origin','refs/heads/main').split()[0]==want
|
||||
owner_state.append({'owner':owner,'published_commit':want})
|
||||
receipt=json.loads((base/'warden-safe-unit-integration-r1/projection.json').read_text())
|
||||
assert receipt['ok'] and receipt['status']=='applied' and receipt['commit']==expected['ops-warden']
|
||||
assert receipt['receipt']['instance_role']=='primary' and receipt['receipt']['instance_label']=='railiance01'
|
||||
assert receipt['receipt']['outcome']['counts']['retired']==0
|
||||
names=['nk48-r5-f1-implementation-r1','nk48-r5-f2-implementation-r1','nk48-r5-f2-review-r1','nk48-r5-f2-publication-r1','nk48-r5-f3-implementation-r1','nk48-r5-f3-review-r1','nk48-r5-f3-implementation-r2','warden-1.3-kernel-fixes-r1','warden-1.3-kernel-review-r2','warden-kernel-source-integration-r1','nk48-r5-source-followthrough-r1','nk48-catalog-fixture-update-r1','nk48-catalog-fixture-publication-r1','nk48-catalog-fixture-publication-r2','nk48-issuer-reference-integration-r1','nk48-current-source-publication-r3','key39-infra-finalization-r1','warden-safe-unit-runner-r1','warden-safe-unit-runner-review-r1','warden-safe-unit-integration-r1']
|
||||
owned_docs=['docs/netkingdom-target-security-architecture.md','docs/netkingdom-target-implementation.md','workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md']
|
||||
state={'owners':owner_state,'native_target_gates_closed':0,'all16_native_gates_remain_open':True,'NK48_task_count':35,'NK48_new_tasks':{'T32':'done: independent mint guard regression','T33':'done: exact proposed issuer-source reference','T34':'progress: real Apps inverse backend absent','T35':'done: authenticated catalog fixtures'},'safe_ordinary_unit_result':'785 passed,4 deselected','native_or_credential_actions':False,'PARAMETER_PINS_or_draft_promotions':False,'failed_and_unknown_outcomes_retained':True}
|
||||
(p/'owner-source-state.json').write_text(json.dumps(state,indent=2)+'\n')
|
||||
paths=[str((base/n).relative_to(r)) for n in names]+owned_docs+[str((p/'owner-source-state.json').relative_to(r)),str((p/'prepare.py').relative_to(r))]
|
||||
new=git(r,'ls-files','--others','--exclude-standard','--',*paths).splitlines()
|
||||
changed=git(r,'diff','--name-only','HEAD','--',*paths).splitlines()
|
||||
files=[]
|
||||
for name in sorted(set(new+changed)):
|
||||
f=r/name
|
||||
if any(x in {'publication-backup','consistency-backup','backup','test-home','synthetic-parent-home'} for x in f.parts) or name.endswith('.backup') or f.name.startswith('WORK-RECORDS.md'):continue
|
||||
assert not f.is_symlink()
|
||||
files.append({'path':name,'sha256':hashlib.sha256(f.read_bytes()).hexdigest()})
|
||||
assert len(files)>200
|
||||
protected_names=git(r,'diff','--name-only','HEAD').splitlines();protected_names=[n for n in protected_names if n not in owned_docs]
|
||||
assert len(protected_names)==8,'Unexpected unrelated source changes; inspect before proceeding'
|
||||
protected=[{'path':n,'sha256':hashlib.sha256((r/n).read_bytes()).hexdigest(),'index':git(r,'ls-files','--stage','--',n)} for n in protected_names]
|
||||
(p/'protected-state.json').write_text(json.dumps(protected,indent=2)+'\n')
|
||||
head=git(r,'rev-parse','HEAD');assert git(r,'ls-remote','origin','refs/heads/main').split()[0]==head
|
||||
cfg={'owner':'the-custodian','base_revision':head,'message':'Record reviewed case admission fixes, exact issuer source and isolated unit checks','files':files}
|
||||
(p/'publication-inputs.json').write_text(json.dumps(cfg,indent=2)+'\n')
|
||||
print(json.dumps({'base':head,'files':len(files),'bytes':sum((r/x['path']).stat().st_size for x in files),'protected_unrelated_paths':len(protected),'source_only':True}))
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
diff --git a/src/warden/case_run.py b/src/warden/case_run.py
|
||||
index 2f66583..60692b8 100644
|
||||
--- a/src/warden/case_run.py
|
||||
+++ b/src/warden/case_run.py
|
||||
@@ -587,7 +587,18 @@ def _catalog_lane(catalog_source: bytes, descriptor: Descriptor):
|
||||
raise Refused("descriptor owner does not fit catalog lane owner_repo")
|
||||
if descriptor.effect in ("mint", "change") and entry.risk != "high":
|
||||
raise Refused("catalog lane risk does not cover the descriptor effect")
|
||||
- return entry
|
||||
+ return entry, entries
|
||||
+
|
||||
+
|
||||
+def _attended_admission(descriptor, actor, entry, entries):
|
||||
+ from .routing.models import attended_lane_issue
|
||||
+
|
||||
+ if (actor != "human" or "WARDEN_AGENT_ID" in os.environ
|
||||
+ or "parent-session" not in descriptor.revocation_scopes):
|
||||
+ raise Refused("attended step requires a human parent session")
|
||||
+ issue = attended_lane_issue(entry, entries.get)
|
||||
+ if issue:
|
||||
+ raise Refused(issue[1])
|
||||
|
||||
|
||||
def _step_mode(case_type: dict, case: dict, descriptor: Descriptor) -> str:
|
||||
@@ -711,14 +722,10 @@ class Executor:
|
||||
actor != "human" or os.getenv("WARDEN_AGENT_ID")
|
||||
):
|
||||
raise Refused("agent mint is forbidden")
|
||||
- entry = _catalog_lane(catalog_source, descriptor)
|
||||
+ entry, entries = _catalog_lane(catalog_source, descriptor)
|
||||
mode = _step_mode(case_type, case, descriptor)
|
||||
- if mode == "attended" and (
|
||||
- actor != "human"
|
||||
- or os.getenv("WARDEN_AGENT_ID")
|
||||
- or "parent-session" not in descriptor.revocation_scopes
|
||||
- ):
|
||||
- raise Refused("attended step requires a human parent session")
|
||||
+ if mode == "attended":
|
||||
+ _attended_admission(descriptor, actor, entry, entries)
|
||||
if mode == "owner" and (
|
||||
entry.effective_delegation.mode != "native"
|
||||
or entry.owner_repo != descriptor.owner
|
||||
@@ -796,7 +803,11 @@ class Executor:
|
||||
descriptor.allowed_created,
|
||||
descriptor.retry_safe,
|
||||
)
|
||||
- self._admitted[invocation] = (descriptor, initial, session_evidence)
|
||||
+ # Keep the mode and exact authenticated catalog bytes with this admission;
|
||||
+ # dispatch must not substitute a later ambient catalog or lose its mode.
|
||||
+ self._admitted[invocation] = (
|
||||
+ descriptor, initial, session_evidence, mode, catalog_source
|
||||
+ )
|
||||
return descriptor, invocation, initial
|
||||
|
||||
def _result(
|
||||
@@ -861,7 +872,7 @@ class Executor:
|
||||
descriptor.adapter_source,
|
||||
):
|
||||
raise Refused("adapter changed after admission")
|
||||
- session = self._admitted[invocation][2]
|
||||
+ _, _, session, mode, catalog_source = self._admitted[invocation]
|
||||
if (
|
||||
self._verify_evidence(
|
||||
f"caller-session:{invocation.actor}:{invocation.principal}:{invocation.lane}",
|
||||
@@ -873,6 +884,9 @@ class Executor:
|
||||
raise Refused("caller session no longer verified")
|
||||
if descriptor.effect == "mint" and os.getenv("WARDEN_AGENT_ID"):
|
||||
raise Refused("agent mint is forbidden")
|
||||
+ if mode == "attended":
|
||||
+ entry, entries = _catalog_lane(catalog_source, descriptor)
|
||||
+ _attended_admission(descriptor, invocation.actor, entry, entries)
|
||||
return adapter
|
||||
|
||||
def _attempt(self, invocation: Invocation, state: StepState, operation: str):
|
||||
diff --git a/src/warden/case_type.py b/src/warden/case_type.py
|
||||
index 986d74f..1454cd3 100644
|
||||
--- a/src/warden/case_type.py
|
||||
+++ b/src/warden/case_type.py
|
||||
@@ -24,6 +24,7 @@ from typing import Iterable, List, Optional
|
||||
import yaml
|
||||
|
||||
from warden.routing.catalog import Catalog
|
||||
+from warden.routing.models import attended_lane_issue
|
||||
|
||||
VALUE_HANDLING = ("none", "generated_in_custody")
|
||||
REQUIRED_STEP_FIELDS = ("id", "owner", "entry_point", "auth", "warden_executes", "attended", "value_handling")
|
||||
@@ -138,13 +139,10 @@ def check_follow_through(doc: dict, catalog: Optional[Catalog] = None) -> Follow
|
||||
findings.append(Finding("catalog", "LANE-MISSING", where, f"lane '{lane}' is not in the catalog"))
|
||||
elif not entry.is_active or not entry.reviewed:
|
||||
findings.append(Finding("catalog", "LANE-UNREVIEWED", where, f"lane '{lane}' is not active and reviewed"))
|
||||
- elif attended and entry.lane == "attended-change":
|
||||
- session = catalog.get(entry.session_lane or "")
|
||||
- if session is None or not session.is_active or not session.reviewed or session.lane != "login":
|
||||
- findings.append(Finding("catalog", "LANE-KIND", where,
|
||||
- f"attended-change lane '{lane}' session_lane '{entry.session_lane}' does not resolve to an active, reviewed login lane"))
|
||||
- elif attended and entry.lane != "login":
|
||||
- findings.append(Finding("catalog", "LANE-KIND", where, f"attended step's lane '{lane}' is not a login or attended-change lane"))
|
||||
+ elif attended:
|
||||
+ issue = attended_lane_issue(entry, catalog.get)
|
||||
+ if issue:
|
||||
+ findings.append(Finding("catalog", issue[0], where, issue[1]))
|
||||
|
||||
if executes:
|
||||
warden_steps.append(sid)
|
||||
diff --git a/src/warden/routing/models.py b/src/warden/routing/models.py
|
||||
index 21abe64..c691b33 100644
|
||||
--- a/src/warden/routing/models.py
|
||||
+++ b/src/warden/routing/models.py
|
||||
@@ -8,7 +8,7 @@ pointers only (the no-double-source rule, enforced in `catalog.py`).
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
-from typing import List, Optional
|
||||
+from typing import Callable, List, Optional
|
||||
|
||||
|
||||
# Risk grade vocabulary (ADR-0007). Grades outside LOW_RISK_GRADES — including
|
||||
@@ -324,3 +324,20 @@ class RouteEntry:
|
||||
elif any(t in h or h in t for h in haystack):
|
||||
score += 1
|
||||
return score
|
||||
+
|
||||
+
|
||||
+def attended_lane_issue(
|
||||
+ entry: RouteEntry, get_entry: Callable[[str], Optional[RouteEntry]]
|
||||
+) -> Optional[tuple[str, str]]:
|
||||
+ """Shared planner/executor boundary, using one parsed catalog snapshot."""
|
||||
+ if not entry.is_active or not entry.reviewed:
|
||||
+ return ("LANE-UNREVIEWED", f"lane '{entry.id}' is not active and reviewed")
|
||||
+ if entry.lane == "attended-change":
|
||||
+ session = get_entry(entry.session_lane or "")
|
||||
+ if (session is None or not session.is_active or not session.reviewed
|
||||
+ or session.lane != "login"):
|
||||
+ return ("LANE-KIND", f"attended-change lane '{entry.id}' session_lane "
|
||||
+ f"'{entry.session_lane}' does not resolve to an active, reviewed login lane")
|
||||
+ elif entry.lane != "login":
|
||||
+ return ("LANE-KIND", f"attended step's lane '{entry.id}' is not a login or attended-change lane")
|
||||
+ return None
|
||||
diff --git a/tests/test_case_1_3_integration.py b/tests/test_case_1_3_integration.py
|
||||
index 342bdda..bb13c09 100644
|
||||
--- a/tests/test_case_1_3_integration.py
|
||||
+++ b/tests/test_case_1_3_integration.py
|
||||
@@ -1,10 +1,15 @@
|
||||
"""Integration: the real net-kingdom 1.3 candidate against this kernel and catalog.
|
||||
|
||||
Reads the actual 1.3 type, its forgejo example and the committed composition
|
||||
-artifact from net-kingdom, plus this branch's real routing catalog. Only two things
|
||||
-are simulated, both in memory: draft lanes flipped to active (promotion needs owner
|
||||
-confirmation) and a 1.3 PARAMETER_PINS entry (PIN-1 waits for the founder's
|
||||
-acknowledgment). Trust is fixture-only, and nothing is dispatched.
|
||||
+artifact from net-kingdom, plus this branch's real routing catalog. The descriptor
|
||||
+map and metadata come from those committed files. All authority is fixture-only:
|
||||
+we change the proposed type to acknowledged, change the example case to
|
||||
+materialized and replace its acceptance record, promote draft lanes in memory,
|
||||
+and temporarily install the 1.3 parameter pin. Stub adapters replace native
|
||||
+adapters; an unconditional evidence verifier simulates every acceptance and caller
|
||||
+session. WARDEN_AGENT_ID is removed to simulate a human runtime. No adapter is
|
||||
+dispatched. This proves conditional source admission, not authenticated owner
|
||||
+acceptance, native execution or readiness; unresolved Apps sessions stay refused.
|
||||
"""
|
||||
from dataclasses import asdict
|
||||
import hashlib
|
||||
@@ -99,12 +104,15 @@ def test_artifact_descriptors_are_the_type_map():
|
||||
assert {s: digest(encode(d)) for s, d in descs.items()} == type_doc()["spec"]["execution_descriptors"]
|
||||
|
||||
|
||||
-def test_all_seven_admitted_once_promoted_and_pinned(tmp_path):
|
||||
+def test_conditional_admission_keeps_unresolved_apps_sessions_refused(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
- admitted, invocation, _ = admit(tmp_path, desc)
|
||||
- assert admitted == desc
|
||||
- assert (invocation.step, invocation.actor, invocation.target) == (step, "human", "forgejo")
|
||||
-
|
||||
+ if step in ("deliver-consumer-secret", "hand-over"):
|
||||
+ with pytest.raises(Refused, match="does not resolve to an active, reviewed login lane"):
|
||||
+ admit(tmp_path, desc)
|
||||
+ else:
|
||||
+ admitted, invocation, _ = admit(tmp_path, desc)
|
||||
+ assert admitted == desc
|
||||
+ assert (invocation.step, invocation.actor, invocation.target) == (step, "human", "forgejo")
|
||||
|
||||
def test_draft_lane_steps_refused_until_promoted(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
diff --git a/tests/test_case_admission_modes.py b/tests/test_case_admission_modes.py
|
||||
index e425020..5069491 100644
|
||||
--- a/tests/test_case_admission_modes.py
|
||||
+++ b/tests/test_case_admission_modes.py
|
||||
@@ -53,6 +53,7 @@ def test_attended_mode_admits_human_parent_session(rig, monkeypatch):
|
||||
admitted = admit(
|
||||
rig,
|
||||
step=mode_step(False, True),
|
||||
+ entries=[dict(lane_entry(), lane="login")],
|
||||
desc=dict(effect="change", revocation_scopes=("parent-session",)),
|
||||
)
|
||||
assert admitted[1].actor == "human"
|
||||
diff --git a/tests/test_case_attended_boundary.py b/tests/test_case_attended_boundary.py
|
||||
new file mode 100644
|
||||
index 0000000..ba07b33
|
||||
--- /dev/null
|
||||
+++ b/tests/test_case_attended_boundary.py
|
||||
@@ -0,0 +1,113 @@
|
||||
+"""Synthetic attended admission/dispatch regressions; no owner/native actions."""
|
||||
+from dataclasses import replace
|
||||
+
|
||||
+import pytest
|
||||
+
|
||||
+from tests.test_case_admission_modes import admit, mode_step
|
||||
+from tests.test_case_run import fixture_catalog, lane_entry, make_rig
|
||||
+from warden.case_receipt_state import Outcome, Refused
|
||||
+from warden.case_type import check_follow_through
|
||||
+from warden.routing.catalog import CatalogError, _parse_entry, load_catalog
|
||||
+
|
||||
+
|
||||
+def attended(rig, entries):
|
||||
+ return admit(rig, step=mode_step(False, True), entries=entries,
|
||||
+ desc=dict(effect="change", revocation_scopes=("parent-session",)))
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("operation", ["run", "rollback", "reconcile"])
|
||||
+@pytest.mark.parametrize("marker", ["agent-1", "", None])
|
||||
+def test_identity_change_refuses_before_any_write_or_callback(tmp_path, monkeypatch, operation, marker):
|
||||
+ monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
+ rig = make_rig(tmp_path)
|
||||
+ executor = rig[0]
|
||||
+ admitted = attended(rig, [dict(lane_entry(), lane="login")])
|
||||
+ executor.evidence_verifier = lambda *_: True
|
||||
+ if operation == "rollback":
|
||||
+ executor.run(*admitted)
|
||||
+ elif operation == "reconcile":
|
||||
+ old = executor.adapters[admitted[0].adapter]
|
||||
+ def lost(_):
|
||||
+ raise RuntimeError("synthetic lost reply")
|
||||
+ executor.adapters[old.reference] = replace(old, invoke=lost)
|
||||
+ executor.run(*admitted)
|
||||
+ calls = []
|
||||
+ old = executor.adapters[admitted[0].adapter]
|
||||
+ callback = getattr(old, "invoke" if operation == "run" else operation)
|
||||
+ def observed(*args):
|
||||
+ calls.append(args)
|
||||
+ return callback(*args)
|
||||
+ executor.adapters[old.reference] = replace(old, **{
|
||||
+ "invoke" if operation == "run" else operation: observed,
|
||||
+ })
|
||||
+ def rows():
|
||||
+ return tuple(executor.journal.db.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
|
||||
+ for table in ("intents", "receipts"))
|
||||
+ before = rows()
|
||||
+ if marker is None:
|
||||
+ result = getattr(executor, operation)(*admitted)
|
||||
+ assert result.last.outcome is {"run": Outcome.SUCCESS,
|
||||
+ "rollback": Outcome.ROLLED_BACK,
|
||||
+ "reconcile": Outcome.NO_CHANGE}[operation]
|
||||
+ assert len(calls) == 1
|
||||
+ assert rows() != before
|
||||
+ return
|
||||
+ monkeypatch.setenv("WARDEN_AGENT_ID", marker)
|
||||
+ with pytest.raises(Refused, match="attended step requires a human parent session"):
|
||||
+ getattr(executor, operation)(*admitted)
|
||||
+ assert rows() == before
|
||||
+ assert not calls
|
||||
+
|
||||
+
|
||||
+def entries_for(case):
|
||||
+ change = dict(lane_entry(), lane="attended-change", session_lane="login-lane")
|
||||
+ login = dict(lane_entry("login-lane"), lane="login")
|
||||
+ if case == "missing":
|
||||
+ return [change]
|
||||
+ if case == "draft-login":
|
||||
+ login["status"] = "draft"
|
||||
+ elif case == "unreviewed-login":
|
||||
+ login["reviewed"] = ""
|
||||
+ elif case == "wrong-kind-login":
|
||||
+ login["lane"] = "secret"
|
||||
+ elif case == "draft-change":
|
||||
+ change["status"] = "draft"
|
||||
+ elif case == "unreviewed-change":
|
||||
+ change["reviewed"] = ""
|
||||
+ elif case == "missing-session-field":
|
||||
+ del change["session_lane"]
|
||||
+ elif case in ("secret", "ceremony"):
|
||||
+ change["lane"] = case
|
||||
+ del change["session_lane"]
|
||||
+ return [change, login]
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("case, code", [
|
||||
+ ("valid", None), ("missing", "LANE-KIND"), ("draft-login", "LANE-KIND"),
|
||||
+ ("unreviewed-login", "LANE-KIND"), ("wrong-kind-login", "LANE-KIND"),
|
||||
+ ("draft-change", "LANE-UNREVIEWED"), ("unreviewed-change", "LANE-UNREVIEWED"),
|
||||
+ ("secret", "LANE-KIND"), ("ceremony", "LANE-KIND"),
|
||||
+])
|
||||
+def test_attended_change_planner_and_executor_agree(tmp_path, monkeypatch, case, code):
|
||||
+ monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
+ entries = entries_for(case)
|
||||
+ path = tmp_path / "catalog.yaml"
|
||||
+ path.write_bytes(fixture_catalog(*entries))
|
||||
+ catalog = load_catalog(path)
|
||||
+ step = dict(mode_step(False, True)[0], attended_justification="synthetic test",
|
||||
+ entry_point="owner/test", auth="synthetic session", value_handling="none")
|
||||
+ doc = {"spec": {"follow_through": [step], "rollback": [{"reverses": step["id"]}]}}
|
||||
+ report = check_follow_through(doc, catalog)
|
||||
+ assert [f.code for f in report.findings] == ([] if code is None else [code])
|
||||
+ rig = make_rig(tmp_path)
|
||||
+ if code:
|
||||
+ with pytest.raises(Refused):
|
||||
+ attended(rig, entries)
|
||||
+ assert rig[0].journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] == 0
|
||||
+ else:
|
||||
+ assert attended(rig, entries)[1].lane == "issuer-lane"
|
||||
+
|
||||
+
|
||||
+def test_attended_change_missing_session_is_parser_rejection():
|
||||
+ with pytest.raises(CatalogError, match="must name session_lane"):
|
||||
+ _parse_entry(entries_for("missing-session-field")[0], 0)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,173 @@
|
|||
"""SecurityCaseType follow-through check (WARDEN-WP-0044-T01).
|
||||
|
||||
Checks the ``follow_through`` section of a NetKingdom SecurityCaseType against
|
||||
the rules ops-warden needs before it can plan a run (canon
|
||||
``security-case-types_v0.1`` section on follow_through; ops-warden review
|
||||
``history/2026-10-08-oidc-rp-follow-through-review.md``, R1-R3, R5-R7).
|
||||
|
||||
Two kinds of finding:
|
||||
|
||||
* ``schema`` — the type itself is wrong; net-kingdom fixes the type.
|
||||
* ``catalog`` — the type is right but a lane it names is not yet a reviewed,
|
||||
active catalog entry; the lane's owner supplies it. ``warden case plan``
|
||||
(T02) refuses to plan while any catalog finding remains.
|
||||
|
||||
Read-only: it parses YAML and the catalog, executes nothing, and never handles a
|
||||
secret value.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
from warden.routing.catalog import Catalog
|
||||
from warden.routing.models import attended_lane_issue
|
||||
|
||||
VALUE_HANDLING = ("none", "generated_in_custody")
|
||||
REQUIRED_STEP_FIELDS = ("id", "owner", "entry_point", "auth", "warden_executes", "attended", "value_handling")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
kind: str # "schema" | "catalog"
|
||||
code: str
|
||||
where: str
|
||||
message: str
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return {"kind": self.kind, "code": self.code, "where": self.where, "message": self.message}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FollowThroughReport:
|
||||
type_id: str
|
||||
version: str
|
||||
findings: List[Finding]
|
||||
attended_lanes: List[str] # one attended session per lane, in step order
|
||||
warden_steps: List[str]
|
||||
|
||||
@property
|
||||
def schema_ok(self) -> bool:
|
||||
return not any(f.kind == "schema" for f in self.findings)
|
||||
|
||||
@property
|
||||
def plannable(self) -> bool:
|
||||
return not self.findings
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return {
|
||||
"type": self.type_id,
|
||||
"version": self.version,
|
||||
"schema_ok": self.schema_ok,
|
||||
"plannable": self.plannable,
|
||||
"warden_steps": self.warden_steps,
|
||||
"attended_sessions": self.attended_lanes,
|
||||
"findings": [f.as_dict() for f in self.findings],
|
||||
}
|
||||
|
||||
|
||||
def load_type(path: Path) -> dict:
|
||||
with open(path) as fh:
|
||||
doc = yaml.safe_load(fh)
|
||||
if not isinstance(doc, dict) or doc.get("kind") != "SecurityCaseType":
|
||||
raise ValueError(f"{path}: not a SecurityCaseType document")
|
||||
return doc
|
||||
|
||||
|
||||
def _bool(step: dict, key: str) -> Optional[bool]:
|
||||
value = step.get(key)
|
||||
return value if isinstance(value, bool) else None
|
||||
|
||||
|
||||
def check_follow_through(doc: dict, catalog: Optional[Catalog] = None) -> FollowThroughReport:
|
||||
spec = doc.get("spec") or {}
|
||||
steps = spec.get("follow_through") or []
|
||||
findings: List[Finding] = []
|
||||
attended_lanes: List[str] = []
|
||||
warden_steps: List[str] = []
|
||||
|
||||
def schema(code: str, where: str, message: str) -> None:
|
||||
findings.append(Finding("schema", code, where, message))
|
||||
|
||||
if not steps:
|
||||
schema("FT-EMPTY", "follow_through", "type declares no follow-through steps")
|
||||
|
||||
seen: set = set()
|
||||
for index, step in enumerate(steps):
|
||||
sid = step.get("id") if isinstance(step, dict) else None
|
||||
where = f"follow_through[{sid or index}]"
|
||||
if not isinstance(step, dict):
|
||||
schema("FT-SHAPE", where, "step is not a mapping")
|
||||
continue
|
||||
for key in REQUIRED_STEP_FIELDS:
|
||||
if key not in step or step[key] in (None, ""):
|
||||
schema("FT-FIELD", where, f"missing required field '{key}'")
|
||||
if sid in seen:
|
||||
schema("FT-DUP", where, "duplicate step id")
|
||||
seen.add(sid)
|
||||
|
||||
executes = _bool(step, "warden_executes")
|
||||
attended = _bool(step, "attended")
|
||||
for key, value in (("warden_executes", executes), ("attended", attended)):
|
||||
if key in step and value is None:
|
||||
schema("FT-BOOL", where, f"'{key}' must be a boolean")
|
||||
|
||||
# R3: values never pass through the run.
|
||||
handling = step.get("value_handling")
|
||||
if handling is not None and handling not in VALUE_HANDLING:
|
||||
schema("FT-VALUE", where, f"value_handling '{handling}' not in {list(VALUE_HANDLING)}")
|
||||
if handling == "generated_in_custody" and attended:
|
||||
schema("FT-VALUE", where, "a generated-in-custody step may not run in an attended session")
|
||||
|
||||
# Attended steps: justified, and never executed by warden itself.
|
||||
if attended:
|
||||
if not step.get("attended_justification"):
|
||||
schema("FT-ATTEND", where, "attended step carries no attended_justification")
|
||||
if executes:
|
||||
schema("FT-ATTEND", where, "a step is either warden_executes or attended, not both")
|
||||
|
||||
# R1: a lane for every step warden executes or groups into a session.
|
||||
lane = step.get("lane")
|
||||
if (executes or attended) and not lane:
|
||||
schema("FT-LANE", where, "warden_executes or attended step names no catalog lane")
|
||||
if lane and catalog is not None:
|
||||
entry = catalog.get(lane)
|
||||
if entry is None:
|
||||
findings.append(Finding("catalog", "LANE-MISSING", where, f"lane '{lane}' is not in the catalog"))
|
||||
elif not entry.is_active or not entry.reviewed:
|
||||
findings.append(Finding("catalog", "LANE-UNREVIEWED", where, f"lane '{lane}' is not active and reviewed"))
|
||||
elif attended:
|
||||
issue = attended_lane_issue(entry, catalog.get)
|
||||
if issue:
|
||||
findings.append(Finding("catalog", issue[0], where, issue[1]))
|
||||
|
||||
if executes:
|
||||
warden_steps.append(sid)
|
||||
if attended and lane and lane not in attended_lanes:
|
||||
attended_lanes.append(lane)
|
||||
|
||||
# R7: verification never reads a value.
|
||||
for index, item in enumerate(spec.get("verification") or []):
|
||||
if not isinstance(item, dict) or item.get("reads_secret_value") is not False:
|
||||
vid = item.get("id", index) if isinstance(item, dict) else index
|
||||
schema("VER-VALUE", f"verification[{vid}]", "reads_secret_value must be explicitly false")
|
||||
|
||||
# Every step must be reversible (or explicitly the consumer's to undo).
|
||||
reversed_ids = {r.get("reverses") for r in spec.get("rollback") or [] if isinstance(r, dict)}
|
||||
for sid in sorted(s for s in seen if s and s not in reversed_ids):
|
||||
schema("RB-MISSING", f"follow_through[{sid}]", "no rollback entry reverses this step")
|
||||
|
||||
return FollowThroughReport(
|
||||
type_id=str(spec.get("id", "")),
|
||||
version=str(spec.get("version", "")),
|
||||
findings=findings,
|
||||
attended_lanes=attended_lanes,
|
||||
warden_steps=warden_steps,
|
||||
)
|
||||
|
||||
|
||||
def summarize(findings: Iterable[Finding]) -> str:
|
||||
return "\n".join(f"[{f.kind}] {f.code} {f.where}: {f.message}" for f in findings)
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
"""Data model for routing catalog entries.
|
||||
|
||||
A `RouteEntry` is a pointer: it names the owner and the authoritative doc for a
|
||||
credential need. Only the SSH lane (`warden_executes: true`) may carry an authored
|
||||
`steps` block and a `cert_command` pattern — every other entry is identifiers and
|
||||
pointers only (the no-double-source rule, enforced in `catalog.py`).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
|
||||
# Risk grade vocabulary (ADR-0007). Grades outside LOW_RISK_GRADES — including
|
||||
# the "ungraded" default and any value from a newer catalog — are treated as
|
||||
# high by is_high_risk, so the read-boundary fails safe in both directions.
|
||||
LOW_RISK_GRADES = frozenset({"standard", "low", "accepted"})
|
||||
GRADED_RISK = frozenset({"standard", "low", "accepted", "high", "critical"})
|
||||
|
||||
|
||||
@dataclass
|
||||
class RotationGuide:
|
||||
"""Structured-but-advisory renewal guidance for a lane (WARDEN-WP-0026 T06).
|
||||
|
||||
Held in the ops-warden registry, never in OpenBao. ``steps`` are authored
|
||||
advisory prose (screened for secret material like every catalog string) — they
|
||||
tell an operator *how* to renew, they are not executed here. ``method`` is
|
||||
``rotate`` (provider re-mints the same kind of credential) or ``re-establish``
|
||||
(regenerate from source, e.g. a new age keypair + re-encrypt). ``automatable``
|
||||
is a hint for a future Strand-B executable driver (WARDEN-WP-0027).
|
||||
"""
|
||||
method: str # "rotate" | "re-establish"
|
||||
steps: List[str]
|
||||
owner: str
|
||||
automatable: bool = False
|
||||
|
||||
|
||||
# Risk classes for agent read-boundary (WARDEN-WP-0026 T04).
|
||||
# high — recovery escrow, upload tokens, admin PATs, high-spend provider keys.
|
||||
# Agent identities must not hold raw data-read (metadata/capabilities only).
|
||||
# standard — ordinary workload secrets (ESO-fed, non-escrow); normal least-privilege.
|
||||
VALID_RISK = ("standard", "high")
|
||||
|
||||
# Delegation modes (WARDEN-WP-0030). Absence of a block is implicit interim.
|
||||
# native — intended owner already fronts the lane (route-primary / pointer)
|
||||
# interim — ops-warden covers a gap; intended_owner + blocked_on required
|
||||
# permanent — ops-warden is the designed owner of this front door (SSH only today)
|
||||
VALID_DELEGATION_MODES = ("native", "interim", "permanent")
|
||||
VALID_WORKLOAD_APPLICABILITY = ("applicable", "not-applicable")
|
||||
|
||||
IMPLICIT_DELEGATION_BLOCKED_ON = (
|
||||
"unclassified — no delegation block; treat as a question, not a settlement"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Delegation:
|
||||
"""Who should own this front door, and what is missing (WARDEN-WP-0030).
|
||||
|
||||
Pointer-layer only: names the intended owner and the blocker. Does not
|
||||
restate how that owner will implement their front door.
|
||||
"""
|
||||
|
||||
mode: str # native | interim | permanent
|
||||
intended_owner: Optional[str] = None
|
||||
blocked_on: Optional[str] = None
|
||||
reviewed: Optional[str] = None
|
||||
verified: Optional[str] = None
|
||||
implicit: bool = False
|
||||
|
||||
#: How `reviewed` was established. The distinction exists because a date
|
||||
#: bumped by editing the entry looks identical to one bumped by re-checking
|
||||
#: the blocker, and on 2026-08-21 six lanes read as freshly reviewed when
|
||||
#: only some had actually been re-verified (WARDEN-WP-0033-T05).
|
||||
#:
|
||||
#: `asked-and-waiting` deliberately does NOT count as verification: it is the
|
||||
#: state the secrets-engine lanes sat in for ten days while looking fresh.
|
||||
VERIFICATION_METHODS = (
|
||||
"owner-confirmed", # the intended owner stated the blocker's status
|
||||
"source-read", # re-derived from the owner's code, canon, or CCR
|
||||
"asked-and-waiting", # a question is outstanding — NOT verification
|
||||
"unverified", # carried forward without a check
|
||||
)
|
||||
#: Methods that mean the claim was actually re-established.
|
||||
VERIFYING_METHODS = ("owner-confirmed", "source-read")
|
||||
|
||||
@property
|
||||
def is_verified(self) -> bool:
|
||||
"""True only when the blocker was re-established, not merely re-edited."""
|
||||
return self.verified in self.VERIFYING_METHODS
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"mode": self.mode,
|
||||
"intended_owner": self.intended_owner,
|
||||
"blocked_on": self.blocked_on,
|
||||
"reviewed": self.reviewed,
|
||||
"verified": self.verified,
|
||||
"is_verified": self.is_verified,
|
||||
"implicit": self.implicit,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkloadReference:
|
||||
"""Authoritative workload join for a catalog lane (WARDEN-WP-0032).
|
||||
|
||||
Managed deployables use the Repo Manager v1 ``rapp_id``/``name`` tuple.
|
||||
Independently governed operational workloads use ``name`` plus an owner
|
||||
declaration reference. An applicable lane whose owner has not published an
|
||||
identity remains explicitly ``unknown``; it is never inferred from the
|
||||
credential path or repository name.
|
||||
"""
|
||||
|
||||
applicability: str # applicable | not-applicable
|
||||
rapp_id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
deployable: Optional[str] = None
|
||||
declaration_ref: Optional[str] = None
|
||||
reason: Optional[str] = None
|
||||
unknown_reason: Optional[str] = None
|
||||
|
||||
@property
|
||||
def resolution(self) -> str:
|
||||
if self.applicability == "not-applicable":
|
||||
return "not-applicable"
|
||||
if self.unknown_reason:
|
||||
return "unknown"
|
||||
return "resolved"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"applicability": self.applicability,
|
||||
"rapp_id": self.rapp_id,
|
||||
"name": self.name,
|
||||
"deployable": self.deployable,
|
||||
"declaration_ref": self.declaration_ref,
|
||||
"reason": self.reason,
|
||||
"unknown_reason": self.unknown_reason,
|
||||
"resolution": self.resolution,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RouteEntry:
|
||||
id: str
|
||||
title: str
|
||||
need_keywords: List[str]
|
||||
owner_repo: str
|
||||
subsystem: str
|
||||
warden_executes: bool
|
||||
wiki_ref: str
|
||||
canon_ref: str
|
||||
reviewed: str
|
||||
status: str # "active" | "draft"
|
||||
# Explicit workload applicability and authoritative join. Never inferred.
|
||||
workload_ref: Optional[WorkloadReference] = None
|
||||
# SSH lane only — None/empty for routed (non-executed) needs.
|
||||
steps: List[str] = field(default_factory=list)
|
||||
cert_command: Optional[str] = None
|
||||
# Structured handoff (WP-0014) — optional, allowed on any lane. These are
|
||||
# *templates and pointers* the `warden access` assist layer renders (and, for
|
||||
# exec_capable lanes, proxies). They are NOT authored procedure prose and they
|
||||
# never carry a secret value — only placeholders (`<...>`) and field names.
|
||||
# Validation in catalog.py enforces the no-secret-material rule on every one.
|
||||
auth_method: Optional[str] = None # how the caller authenticates to the owner
|
||||
path_template: Optional[str] = None # owner-side path with `<...>` placeholders
|
||||
fetch_command: Optional[str] = None # command skeleton run *as the caller*
|
||||
exec_capable: bool = False # may `warden access --fetch/--exec` proxy it
|
||||
policy_ref: Optional[str] = None # flex-auth check the fetch path runs first
|
||||
# Proxy lane semantics (WP-0014 T4):
|
||||
# "secret" — read a value (gated by flex-auth secret-read; caller must already
|
||||
# be authenticated; value transits via inherit-stdout or child env).
|
||||
# "login" — interactive auth bootstrap (OIDC/MFA). No secret-read gate (you have
|
||||
# no identity yet), no caller-auth precheck (the point is to get one),
|
||||
# run interactively as the caller; warden never captures the token.
|
||||
# "ceremony" — attended owner operation such as Shamir seal/unseal. It is a
|
||||
# pointer plus approval boundary, never an executable access lane.
|
||||
lane: str = "secret"
|
||||
session_lane: Optional[str] = None # attended-change: login lane whose session establishes it
|
||||
# Owner-native exec front door (WP-0019). When `exec_owner` is set, that subsystem
|
||||
# (e.g. secrets-engine) provides the PRIMARY way to run a secret-backed command; the
|
||||
# catalog routes to it and keeps ops-warden's own --fetch/--exec proxy as a transparent
|
||||
# fallback (route-primary, proxy-fallback). Pointers/templates only — never a value.
|
||||
exec_owner: Optional[str] = None # subsystem owning the native exec (e.g. secrets-engine)
|
||||
exec_command: Optional[str] = None # e.g. "secrets-engine exec --catalog <id> -- <cmd>"
|
||||
pointer_command: Optional[str] = None # e.g. "secrets-engine route <id> --json"
|
||||
# Rotation / re-establishment guidance (WP-0026 T06) — advisory, no secret values.
|
||||
rotation: Optional[RotationGuide] = None
|
||||
# Agent read-boundary risk class (WP-0026 T04). high → agents use wrap/out/exec only.
|
||||
# Default is "ungraded", which FAILS SAFE: it is treated as high. Before
|
||||
# ADR-0007 this defaulted to "standard", so a lane that simply omitted the
|
||||
# field was silently placed outside the read-boundary (RISK-F-0003) — the
|
||||
# control was never relaxed by decision, it was never reached.
|
||||
risk: str = "ungraded" # "standard" | "high" | "ungraded"
|
||||
# Delegation register (WP-0030). None → implicit interim with unknown owner.
|
||||
delegation: Optional[Delegation] = None
|
||||
|
||||
@property
|
||||
def is_active(self) -> bool:
|
||||
return self.status == "active"
|
||||
|
||||
@property
|
||||
def is_graded(self) -> bool:
|
||||
"""False when this lane carries no explicit risk grade (ADR-0007)."""
|
||||
return self.risk in GRADED_RISK
|
||||
|
||||
@property
|
||||
def is_high_risk(self) -> bool:
|
||||
"""True when this lane is on the agent raw-read deny list (WP-0026 T04).
|
||||
|
||||
Anything not explicitly graded low is high. An ungraded lane, or one
|
||||
carrying a grade this version does not recognise, is treated as high
|
||||
rather than waved through — ADR-0007: absence is not a grade.
|
||||
"""
|
||||
return self.risk not in LOW_RISK_GRADES
|
||||
|
||||
def risk_for_zone(
|
||||
self,
|
||||
*,
|
||||
effective_zone: str = "unknown",
|
||||
admission: str = "unknown",
|
||||
synthetic_only: bool = False,
|
||||
) -> str:
|
||||
"""Resolve an absent grade using security-zones_v0.1 section 5.1.
|
||||
|
||||
Explicit grades always win. The sole lower default is a satisfied
|
||||
``z0-experimental`` workload proven synthetic-only. Every other zone,
|
||||
failed/unknown admission, and missing context fails safe to at least
|
||||
``high``; z3 reports ``critical`` (which the read boundary treats as
|
||||
high). Catalog CI still requires explicit grades, so this is the safe
|
||||
runtime behavior for malformed or newer external catalogs.
|
||||
"""
|
||||
if self.is_graded:
|
||||
return self.risk
|
||||
if (
|
||||
effective_zone == "z0-experimental"
|
||||
and admission == "satisfied"
|
||||
and synthetic_only
|
||||
):
|
||||
return "standard"
|
||||
if effective_zone == "z3-critical" and admission == "satisfied":
|
||||
return "critical"
|
||||
return "high"
|
||||
|
||||
@property
|
||||
def has_rotation(self) -> bool:
|
||||
"""True when this lane carries renewal guidance (WP-0026 T06)."""
|
||||
return self.rotation is not None
|
||||
|
||||
@property
|
||||
def vends_secret(self) -> bool:
|
||||
"""True when this lane hands back a rotatable static secret value.
|
||||
|
||||
Rotation guidance (WP-0026 T06) applies to these. It excludes the SSH lane
|
||||
(short-lived certs — renewal is re-issuance), ``login`` lanes (re-auth, no
|
||||
stored value), and pure routing pointers with no secret path (tunnel,
|
||||
principals, emission sinks, policy checks).
|
||||
"""
|
||||
if self.warden_executes or self.lane != "secret":
|
||||
return False
|
||||
return bool(self.path_template or self.fetch_command or self.exec_owner)
|
||||
|
||||
@property
|
||||
def has_native_exec(self) -> bool:
|
||||
"""True when an owner-native exec front door is the primary path for this lane."""
|
||||
return bool(self.exec_owner and self.exec_command)
|
||||
|
||||
@property
|
||||
def effective_delegation(self) -> Delegation:
|
||||
"""Declared delegation, or implicit interim with an unknown owner.
|
||||
|
||||
Absence of a ``delegation:`` block is a question (WP-0030), not a
|
||||
settlement that ops-warden owns the front door.
|
||||
"""
|
||||
if self.delegation is not None:
|
||||
return self.delegation
|
||||
return Delegation(
|
||||
mode="interim",
|
||||
intended_owner=None,
|
||||
blocked_on=IMPLICIT_DELEGATION_BLOCKED_ON,
|
||||
reviewed=None,
|
||||
implicit=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_interim(self) -> bool:
|
||||
"""True when this lane is a tracked gap (explicit or implicit)."""
|
||||
return self.effective_delegation.mode == "interim"
|
||||
|
||||
@property
|
||||
def has_handoff(self) -> bool:
|
||||
"""True when structured assist fields are present (advisory richness)."""
|
||||
return any((self.auth_method, self.path_template, self.fetch_command))
|
||||
|
||||
@property
|
||||
def resolvable(self) -> bool:
|
||||
"""True when `warden access --fetch` can run this lane with no further input.
|
||||
|
||||
A resolvable lane is active, exec_capable, and its fetch command (with the path
|
||||
inlined) carries no unresolved ``<...>`` placeholder. Template lanes — like the
|
||||
generic ``openbao-api-key`` or the ``<domain>``-parameterized login — are *not*
|
||||
resolvable until an owner ships concrete names. Lets an automated caller know
|
||||
whether ``--fetch`` will work *before* attempting it (whynot-design request).
|
||||
"""
|
||||
if not (self.is_active and self.exec_capable and self.fetch_command):
|
||||
return False
|
||||
blob = f"{self.fetch_command} {self.path_template or ''}"
|
||||
return "<" not in blob and ">" not in blob
|
||||
|
||||
def match_score(self, tokens: List[str]) -> int:
|
||||
"""Keyword-overlap score against need_keywords, title, and id.
|
||||
|
||||
Pure ranking helper — no I/O, no external calls.
|
||||
"""
|
||||
haystack = set(k.lower() for k in self.need_keywords)
|
||||
haystack.update(self.id.lower().replace("-", " ").split())
|
||||
haystack.update(self.title.lower().replace("-", " ").split())
|
||||
score = 0
|
||||
for tok in tokens:
|
||||
t = tok.lower()
|
||||
if t in haystack:
|
||||
score += 2
|
||||
elif any(t in h or h in t for h in haystack):
|
||||
score += 1
|
||||
return score
|
||||
|
||||
|
||||
def attended_lane_issue(
|
||||
entry: RouteEntry, get_entry: Callable[[str], Optional[RouteEntry]]
|
||||
) -> Optional[tuple[str, str]]:
|
||||
"""Shared planner/executor boundary, using one parsed catalog snapshot."""
|
||||
if not entry.is_active or not entry.reviewed:
|
||||
return ("LANE-UNREVIEWED", f"lane '{entry.id}' is not active and reviewed")
|
||||
if entry.lane == "attended-change":
|
||||
session = get_entry(entry.session_lane or "")
|
||||
if (session is None or not session.is_active or not session.reviewed
|
||||
or session.lane != "login"):
|
||||
return ("LANE-KIND", f"attended-change lane '{entry.id}' session_lane "
|
||||
f"'{entry.session_lane}' does not resolve to an active, reviewed login lane")
|
||||
elif entry.lane != "login":
|
||||
return ("LANE-KIND", f"attended step's lane '{entry.id}' is not a login or attended-change lane")
|
||||
return None
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
"""Integration: the real net-kingdom 1.3 candidate against this kernel and catalog.
|
||||
|
||||
Reads the actual 1.3 type, its forgejo example and the committed composition
|
||||
artifact from net-kingdom, plus this branch's real routing catalog. The descriptor
|
||||
map and metadata come from those committed files. All authority is fixture-only:
|
||||
we change the proposed type to acknowledged, change the example case to
|
||||
materialized and replace its acceptance record, promote draft lanes in memory,
|
||||
and temporarily install the 1.3 parameter pin. Stub adapters replace native
|
||||
adapters; an unconditional evidence verifier simulates every acceptance and caller
|
||||
session. WARDEN_AGENT_ID is removed to simulate a human runtime. No adapter is
|
||||
dispatched. This proves conditional source admission, not authenticated owner
|
||||
acceptance, native execution or readiness; unresolved Apps sessions stay refused.
|
||||
"""
|
||||
from dataclasses import asdict
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from warden import case_inputs as inputs
|
||||
from warden.case_receipt_state import Refused
|
||||
from warden.case_run import Adapter, Descriptor, Executor, Journal, digest
|
||||
from warden.case_type import check_follow_through
|
||||
from warden.routing.catalog import load_catalog
|
||||
|
||||
NK = Path(os.environ.get("WARDEN_TEST_NETKINGDOM_ROOT",
|
||||
Path(__file__).resolve().parents[2] / "net-kingdom"))
|
||||
TYPE_DIR = NK / "security-case-type-revisions/1.3/oidc-browser-relying-party"
|
||||
ARTIFACT = NK / "sso-mfa/k8s/keycape/descriptor-composition-1.3.candidate.json"
|
||||
CATALOG = Path(__file__).resolve().parents[1] / "registry/routing/catalog.yaml"
|
||||
|
||||
pytestmark = pytest.mark.skipif(not (TYPE_DIR / "type.yaml").is_file() or not ARTIFACT.is_file(),
|
||||
reason="net-kingdom 1.3 candidate not available")
|
||||
|
||||
DRAFT_LANES = {"keycape-attended-case-change", "platform-secret-delivery",
|
||||
"consumer-owner-deployment", "apps-consumer-handover"}
|
||||
|
||||
|
||||
def encode(desc): # net-kingdom compose-1.2-descriptors.py encode()
|
||||
return json.dumps(asdict(desc), sort_keys=True, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def type_doc(status="acknowledged"):
|
||||
doc = yaml.safe_load((TYPE_DIR / "type.yaml").read_bytes())
|
||||
doc["spec"]["status"] = status
|
||||
return doc
|
||||
|
||||
|
||||
def catalog_bytes(promote):
|
||||
raw = yaml.safe_load(CATALOG.read_bytes())
|
||||
if promote:
|
||||
for entry in raw["entries"]:
|
||||
if entry["id"] in DRAFT_LANES and entry.get("status") == "draft":
|
||||
entry["status"] = "active"
|
||||
return yaml.safe_dump(raw).encode()
|
||||
|
||||
|
||||
def descriptors():
|
||||
art = json.loads(ARTIFACT.read_text())
|
||||
out = {}
|
||||
for row in art["steps"]:
|
||||
d = dict(row["descriptor"])
|
||||
for key in ("allowed_created", "revocation_scopes"):
|
||||
d[key] = tuple(d[key])
|
||||
out[row["step"]] = Descriptor(**d)
|
||||
return out
|
||||
|
||||
|
||||
def pin_13():
|
||||
params = type_doc()["spec"]["parameters"]
|
||||
fp = hashlib.sha256(json.dumps(params, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
return mock.patch.dict(inputs.PARAMETER_PINS, {"1.3": fp})
|
||||
|
||||
|
||||
def unused(*_):
|
||||
raise AssertionError("admission must not dispatch")
|
||||
|
||||
|
||||
def admit(tmp_path, desc, *, promote=True, pinned=True, status="acknowledged"):
|
||||
case = yaml.safe_load((TYPE_DIR / "examples/forgejo.yaml").read_bytes())
|
||||
case["spec"].update(status="materialized", acceptance={
|
||||
"at": "2026-10-17", "record": "fixture-only acceptance, not an owner record",
|
||||
"by": [{"name": "fixture", "role": "owner"}, {"name": "fixture", "role": "founder"}]})
|
||||
adapter = Adapter(desc.adapter, desc.adapter_source, unused, unused)
|
||||
executor = Executor(Journal(tmp_path / desc.step), {adapter.reference: adapter},
|
||||
lambda purpose, source, evidence: True,
|
||||
browser_inputs=inputs.BrowserInputs(NK))
|
||||
with mock.patch.dict(os.environ), (pin_13() if pinned else mock.patch.dict({})):
|
||||
os.environ.pop("WARDEN_AGENT_ID", None)
|
||||
return executor.admit(yaml.safe_dump(case).encode(), yaml.safe_dump(type_doc(status)).encode(),
|
||||
encode(desc), catalog_bytes(promote), principal="founder",
|
||||
actor=desc.actor, lane=desc.lane,
|
||||
acceptance_evidence="e-acceptance", materialization_evidence="e-mason",
|
||||
descriptor_evidence="e-descriptor", session_evidence="e-session")
|
||||
|
||||
|
||||
def test_artifact_descriptors_are_the_type_map():
|
||||
descs = descriptors()
|
||||
assert len(descs) == 7
|
||||
assert {s: digest(encode(d)) for s, d in descs.items()} == type_doc()["spec"]["execution_descriptors"]
|
||||
|
||||
|
||||
def test_conditional_admission_keeps_unresolved_apps_sessions_refused(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
if step in ("deliver-consumer-secret", "hand-over"):
|
||||
with pytest.raises(Refused, match="does not resolve to an active, reviewed login lane"):
|
||||
admit(tmp_path, desc)
|
||||
else:
|
||||
admitted, invocation, _ = admit(tmp_path, desc)
|
||||
assert admitted == desc
|
||||
assert (invocation.step, invocation.actor, invocation.target) == (step, "human", "forgejo")
|
||||
|
||||
def test_draft_lane_steps_refused_until_promoted(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
if desc.lane in DRAFT_LANES:
|
||||
with pytest.raises(Refused, match="descriptor lane is not active in the catalog"):
|
||||
admit(tmp_path, desc, promote=False)
|
||||
else:
|
||||
admit(tmp_path, desc, promote=False) # mint-secret's lane is already active
|
||||
|
||||
|
||||
def test_unpinned_1_3_refused_by_browser_validation(tmp_path):
|
||||
with pytest.raises(Refused, match="failed exact-version validation"):
|
||||
admit(tmp_path, descriptors()["mint-secret"], pinned=False)
|
||||
|
||||
|
||||
def test_proposed_type_refused(tmp_path):
|
||||
with pytest.raises(Refused, match="type is not acknowledged"):
|
||||
admit(tmp_path, descriptors()["mint-secret"], status="proposed")
|
||||
|
||||
|
||||
def test_type_check_findings_against_the_real_catalog(tmp_path):
|
||||
def findings(promote):
|
||||
path = tmp_path / f"catalog-{promote}.yaml"
|
||||
path.write_bytes(catalog_bytes(promote))
|
||||
report = check_follow_through(type_doc("proposed"), load_catalog(path))
|
||||
return {(f.code, f.where) for f in report.findings}
|
||||
|
||||
draft = findings(False)
|
||||
assert {code for code, _ in draft} == {"LANE-UNREVIEWED"}
|
||||
assert len(draft) == 6 # six steps use the four draft lanes; mint-secret's lane is active
|
||||
|
||||
promoted = findings(True)
|
||||
# Only the two railiance-apps lanes remain: their session_lane awaits owner confirmation.
|
||||
assert {code for code, _ in promoted} == {"LANE-KIND"}
|
||||
assert len(promoted) == 2
|
||||
assert all("deliver-consumer-secret" in where or "hand-over" in where for _, where in promoted)
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
"""KERNEL-2 lane resolution and KERNEL-3 mode-aware admission (fixture only)."""
|
||||
from dataclasses import asdict, replace
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from warden.case_receipt_state import Refused
|
||||
from warden.case_run import Descriptor, digest
|
||||
from tests.test_case_run import MINT_STEP, fixture_catalog, lane_entry, make_rig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rig(tmp_path):
|
||||
return make_rig(tmp_path)
|
||||
|
||||
EVIDENCE = {"acceptance", "materialization", "descriptor", "session"}
|
||||
|
||||
|
||||
def admit(rig, *, step=None, entries=None, catalog=None, desc=None, **over):
|
||||
executor, _, _, sources, kwargs = rig
|
||||
case, typ, descriptor, _ = sources
|
||||
if desc is not None:
|
||||
base = Descriptor.decode(descriptor)
|
||||
descriptor = json.dumps(asdict(replace(base, **desc))).encode()
|
||||
doc = yaml.safe_load(typ)
|
||||
doc["spec"]["execution_descriptors"] = {"mint-secret": digest(descriptor)}
|
||||
if step is not None:
|
||||
doc["spec"]["follow_through"] = step
|
||||
typ = yaml.safe_dump(doc).encode()
|
||||
if catalog is None:
|
||||
catalog = fixture_catalog(*(entries or [lane_entry()]))
|
||||
executor.evidence_verifier = lambda purpose, source, evidence: evidence in EVIDENCE
|
||||
return executor.admit(case, typ, descriptor, catalog, **{**kwargs, **over})
|
||||
|
||||
|
||||
def mode_step(executes, attended):
|
||||
return [dict(MINT_STEP, warden_executes=executes, attended=attended)]
|
||||
|
||||
|
||||
def refused(message, rig, **kw):
|
||||
with pytest.raises(Refused, match=message):
|
||||
admit(rig, **kw)
|
||||
|
||||
|
||||
# Positive modes
|
||||
def test_warden_mode_admits(rig):
|
||||
assert admit(rig)[1].lane == "issuer-lane"
|
||||
|
||||
|
||||
def test_attended_mode_admits_human_parent_session(rig, monkeypatch):
|
||||
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
admitted = admit(
|
||||
rig,
|
||||
step=mode_step(False, True),
|
||||
entries=[dict(lane_entry(), lane="login")],
|
||||
desc=dict(effect="change", revocation_scopes=("parent-session",)),
|
||||
)
|
||||
assert admitted[1].actor == "human"
|
||||
|
||||
|
||||
def test_owner_mode_admits_native_owner_lane(rig):
|
||||
admitted = admit(rig, step=mode_step(False, False), desc=dict(effect="change"))
|
||||
assert admitted[1].owner == "owner"
|
||||
|
||||
|
||||
# KERNEL-2
|
||||
@pytest.mark.parametrize(
|
||||
"entries, catalog, message",
|
||||
[
|
||||
(None, b"not: [a catalog", "invalid catalog source shape"),
|
||||
([lane_entry("other-lane")], None, "descriptor lane is not in the catalog"),
|
||||
([lane_entry(status="draft")], None, "descriptor lane is not active in the catalog"),
|
||||
([{k: v for k, v in lane_entry().items() if k != "delegation"}], None,
|
||||
"descriptor lane has no catalog delegation record"),
|
||||
([{k: v for k, v in lane_entry().items() if k != "risk"}], None,
|
||||
"descriptor lane carries no risk grade"),
|
||||
([lane_entry(owner="someone-else")], None,
|
||||
"descriptor owner does not fit catalog lane owner_repo"),
|
||||
([lane_entry(risk="standard")], None,
|
||||
"catalog lane risk does not cover the descriptor effect"),
|
||||
],
|
||||
)
|
||||
def test_catalog_lane_refusals(rig, entries, catalog, message):
|
||||
refused(message, rig, entries=entries, catalog=catalog)
|
||||
|
||||
|
||||
# KERNEL-3: the step must declare dispatch and agree with the descriptor
|
||||
@pytest.mark.parametrize(
|
||||
"step, message",
|
||||
[
|
||||
([], "type step does not declare how the kernel dispatches it"),
|
||||
(mode_step(True, True), "type step does not declare how the kernel dispatches it"),
|
||||
([{k: v for k, v in MINT_STEP.items() if k != "lane"}],
|
||||
"type step does not declare how the kernel dispatches it"),
|
||||
([dict(MINT_STEP, warden_executes="yes")],
|
||||
"type step does not declare how the kernel dispatches it"),
|
||||
([dict(MINT_STEP, lane="other-lane")], "type step lane or owner disagrees with descriptor"),
|
||||
([dict(MINT_STEP, owner="other")], "type step lane or owner disagrees with descriptor"),
|
||||
],
|
||||
)
|
||||
def test_step_declaration_refusals(rig, step, message):
|
||||
refused(message, rig, step=step)
|
||||
|
||||
|
||||
def test_attended_refuses_agent_session(rig, monkeypatch):
|
||||
monkeypatch.setenv("WARDEN_AGENT_ID", "agent-1")
|
||||
refused("attended step requires a human parent session", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change"))
|
||||
|
||||
|
||||
def test_attended_refuses_agent_actor(rig):
|
||||
refused("attended step requires a human parent session", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change", actor="agent"),
|
||||
actor="agent")
|
||||
|
||||
|
||||
def test_attended_refuses_caller_lane_other_than_step_lane(rig):
|
||||
refused("principal actor or lane mismatch", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change"), lane="login-lane")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entry, desc",
|
||||
[
|
||||
(lane_entry(delegation={"mode": "interim", "intended_owner": "owner",
|
||||
"blocked_on": "fixture", "reviewed": "2026-10-17"}),
|
||||
dict(effect="change")),
|
||||
(lane_entry(owner="owner/other"), dict(effect="change")),
|
||||
(lane_entry(warden_executes=True), dict(effect="change")),
|
||||
(lane_entry(), {}), # owner step may not be a Warden-brokered mint
|
||||
],
|
||||
)
|
||||
def test_owner_mode_refusals(rig, entry, desc):
|
||||
refused("owner step lane is not the owner's native path", rig,
|
||||
step=mode_step(False, False), entries=[entry], desc=desc)
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
"""Synthetic attended admission/dispatch regressions; no owner/native actions."""
|
||||
from dataclasses import replace
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.test_case_admission_modes import admit, mode_step
|
||||
from tests.test_case_run import fixture_catalog, lane_entry, make_rig
|
||||
from warden.case_receipt_state import Outcome, Refused
|
||||
from warden.case_type import check_follow_through
|
||||
from warden.routing.catalog import CatalogError, _parse_entry, load_catalog
|
||||
|
||||
|
||||
def attended(rig, entries):
|
||||
return admit(rig, step=mode_step(False, True), entries=entries,
|
||||
desc=dict(effect="change", revocation_scopes=("parent-session",)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("operation", ["run", "rollback", "reconcile"])
|
||||
@pytest.mark.parametrize("marker", ["agent-1", "", None])
|
||||
def test_identity_change_refuses_before_any_write_or_callback(tmp_path, monkeypatch, operation, marker):
|
||||
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
rig = make_rig(tmp_path)
|
||||
executor = rig[0]
|
||||
admitted = attended(rig, [dict(lane_entry(), lane="login")])
|
||||
executor.evidence_verifier = lambda *_: True
|
||||
if operation == "rollback":
|
||||
executor.run(*admitted)
|
||||
elif operation == "reconcile":
|
||||
old = executor.adapters[admitted[0].adapter]
|
||||
def lost(_):
|
||||
raise RuntimeError("synthetic lost reply")
|
||||
executor.adapters[old.reference] = replace(old, invoke=lost)
|
||||
executor.run(*admitted)
|
||||
calls = []
|
||||
old = executor.adapters[admitted[0].adapter]
|
||||
callback = getattr(old, "invoke" if operation == "run" else operation)
|
||||
def observed(*args):
|
||||
calls.append(args)
|
||||
return callback(*args)
|
||||
executor.adapters[old.reference] = replace(old, **{
|
||||
"invoke" if operation == "run" else operation: observed,
|
||||
})
|
||||
def rows():
|
||||
return tuple(executor.journal.db.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
|
||||
for table in ("intents", "receipts"))
|
||||
before = rows()
|
||||
if marker is None:
|
||||
result = getattr(executor, operation)(*admitted)
|
||||
assert result.last.outcome is {"run": Outcome.SUCCESS,
|
||||
"rollback": Outcome.ROLLED_BACK,
|
||||
"reconcile": Outcome.NO_CHANGE}[operation]
|
||||
assert len(calls) == 1
|
||||
assert rows() != before
|
||||
return
|
||||
monkeypatch.setenv("WARDEN_AGENT_ID", marker)
|
||||
with pytest.raises(Refused, match="attended step requires a human parent session"):
|
||||
getattr(executor, operation)(*admitted)
|
||||
assert rows() == before
|
||||
assert not calls
|
||||
|
||||
|
||||
def entries_for(case):
|
||||
change = dict(lane_entry(), lane="attended-change", session_lane="login-lane")
|
||||
login = dict(lane_entry("login-lane"), lane="login")
|
||||
if case == "missing":
|
||||
return [change]
|
||||
if case == "draft-login":
|
||||
login["status"] = "draft"
|
||||
elif case == "unreviewed-login":
|
||||
login["reviewed"] = ""
|
||||
elif case == "wrong-kind-login":
|
||||
login["lane"] = "secret"
|
||||
elif case == "draft-change":
|
||||
change["status"] = "draft"
|
||||
elif case == "unreviewed-change":
|
||||
change["reviewed"] = ""
|
||||
elif case == "missing-session-field":
|
||||
del change["session_lane"]
|
||||
elif case in ("secret", "ceremony"):
|
||||
change["lane"] = case
|
||||
del change["session_lane"]
|
||||
return [change, login]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case, code", [
|
||||
("valid", None), ("missing", "LANE-KIND"), ("draft-login", "LANE-KIND"),
|
||||
("unreviewed-login", "LANE-KIND"), ("wrong-kind-login", "LANE-KIND"),
|
||||
("draft-change", "LANE-UNREVIEWED"), ("unreviewed-change", "LANE-UNREVIEWED"),
|
||||
("secret", "LANE-KIND"), ("ceremony", "LANE-KIND"),
|
||||
])
|
||||
def test_attended_change_planner_and_executor_agree(tmp_path, monkeypatch, case, code):
|
||||
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
entries = entries_for(case)
|
||||
path = tmp_path / "catalog.yaml"
|
||||
path.write_bytes(fixture_catalog(*entries))
|
||||
catalog = load_catalog(path)
|
||||
step = dict(mode_step(False, True)[0], attended_justification="synthetic test",
|
||||
entry_point="owner/test", auth="synthetic session", value_handling="none")
|
||||
doc = {"spec": {"follow_through": [step], "rollback": [{"reverses": step["id"]}]}}
|
||||
report = check_follow_through(doc, catalog)
|
||||
assert [f.code for f in report.findings] == ([] if code is None else [code])
|
||||
rig = make_rig(tmp_path)
|
||||
if code:
|
||||
with pytest.raises(Refused):
|
||||
attended(rig, entries)
|
||||
assert rig[0].journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] == 0
|
||||
else:
|
||||
assert attended(rig, entries)[1].lane == "issuer-lane"
|
||||
|
||||
|
||||
def test_attended_change_missing_session_is_parser_rejection():
|
||||
with pytest.raises(CatalogError, match="must name session_lane"):
|
||||
_parse_entry(entries_for("missing-session-field")[0], 0)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
root=Path('/tmp/warden-kernel-fix-r1')
|
||||
tree=root/os.environ.get('FIX_TEST_TREE', 'ops-warden')
|
||||
env={'HOME':str(root/'home'),'PATH':'/usr/bin:/bin','PYTHONPATH':str(root/'guard')+':'+str(tree/'src')+':/home/worsch/phase-memory/src','PYTHONDONTWRITEBYTECODE':'1','WARDEN_TEST_NETKINGDOM_ROOT':str(root/'net-kingdom'),'LANG':'C.UTF-8'}
|
||||
python='/tmp/warden-kernel-review-r1/final/.venv/bin/python'
|
||||
with (root/sys.argv[1]).open('w') as out:
|
||||
r=subprocess.run([python,*sys.argv[2:]],cwd=tree,env=env,stdout=out,stderr=subprocess.STDOUT)
|
||||
print(r.returncode)
|
||||
sys.exit(r.returncode)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import sys
|
||||
|
||||
bound_sockets = []
|
||||
|
||||
def deny_network(event, args):
|
||||
if event == 'socket.bind':
|
||||
bound_sockets.append(args[0])
|
||||
if event == 'socket.getaddrinfo' and args[0] not in {'localhost', '127.0.0.1', '::1'}:
|
||||
raise OSError('independent review: network disabled')
|
||||
if event == 'socket.connect':
|
||||
address = args[1]
|
||||
for server in bound_sockets:
|
||||
try:
|
||||
bound = server.getsockname()
|
||||
if address == bound and bound[0] in {'127.0.0.1', '::1'}:
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
raise OSError('independent review: network disabled')
|
||||
|
||||
sys.addaudithook(deny_network)
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"schema": "custodian.warden-1.3-kernel-fixes.v1",
|
||||
"date": "2026-10-17",
|
||||
"role": "implementation successor; not independent review or owner/native acceptance",
|
||||
"status": "candidate prepared; no canonical apply/commit/push",
|
||||
"base_ops_warden": "5abde817775d6304e3cf5a46948717a1606981bf",
|
||||
"net_kingdom": "001a38ca2aa85155ce4840c4da22aca6e2346171",
|
||||
"accepted_kernel_1": "117fec9241e7b985dfb187b992e173d0a5dc3700",
|
||||
"existing_review": {
|
||||
"path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review/kernel-review-r1.json",
|
||||
"sha256": "sha256:43c96da94605082b4924ccd8ef41a1f8737d7e9ada95047ff16415b4614905dc",
|
||||
"disposition": "retained unchanged; reuse existing KERNEL-1 validation; its initial ambient-lookup boundary deviation remains adverse/unknown and is not counted as compliant success"
|
||||
},
|
||||
"patch": {
|
||||
"file": "candidate.patch",
|
||||
"sha256": "sha256:1a3530a1e469fc03c2c29f1b66fa3ba1904e56bf2abcfb242c7a2ed015fb3e74",
|
||||
"applies_to_base": "git apply --check succeeded against frozen base archive"
|
||||
},
|
||||
"files": {
|
||||
"src/warden/case_run.py": {
|
||||
"base": "sha256:e071b485162b2db5c4a9da5821917b7ff81a07980270530ed91e987796591ddd",
|
||||
"candidate": "sha256:1bc73e30ac718fd75bbaff2a739495fc051aeaec73802ac71f0583cb2d53ab0a"
|
||||
},
|
||||
"src/warden/case_type.py": {
|
||||
"base": "sha256:3ee1004b7c0ac3975be157408376cf14acc0c1e1e1623d79d3179540040f40ce",
|
||||
"candidate": "sha256:7b659b8d682863cd28df24025be4b10dd7ca3df724350a22d360c6af9615bca7"
|
||||
},
|
||||
"src/warden/routing/models.py": {
|
||||
"base": "sha256:2da3bc90c451ed9b66c2d43035951c2a777c2b896201e9a87966a7a8f7ead426",
|
||||
"candidate": "sha256:b85372b2572a49af62e9534f26a33963b894633c7cd43dd2a28416b8959cba3e"
|
||||
},
|
||||
"tests/test_case_1_3_integration.py": {
|
||||
"base": "sha256:591cc4e5859e1cebce29912b19b7ac1479bf49373fe04425542d1ecd32c74192",
|
||||
"candidate": "sha256:4862c5a004075eb18733d354bee7dda3f55e04a6ee8ecb44b76166347c121158"
|
||||
},
|
||||
"tests/test_case_admission_modes.py": {
|
||||
"base": "sha256:3f5c648cec9e075818a616207aff508bdbc9ae95b100f5b8400a86fcae826182",
|
||||
"candidate": "sha256:9110d6b6286cf0f85268d4970d1fd403250da3f04351d70b1fd2d9d7a93e2d89"
|
||||
},
|
||||
"tests/test_case_attended_boundary.py": {
|
||||
"base": null,
|
||||
"candidate": "sha256:4615e653f06144305ea1a84e98bc6260e16ed41ef9e5db2d0f162ef9f723ef2d"
|
||||
}
|
||||
},
|
||||
"unchanged_pins": {
|
||||
"src/warden/case_inputs.py": "sha256:1334b0cd9897e763cfea2d306e997c659f3f284ecc3f9612140c2104835da7c7",
|
||||
"registry/routing/catalog.yaml": "sha256:ba9634a92e584dac8c42e8624ab52adce06e79a285209cb64c0daa4dac9cd7d8",
|
||||
"registry/generated/high-risk-data-paths.yaml": "sha256:5cd9c03b86751f7197cb5661a53a86d59765137958b215ec14bb343156f216d0"
|
||||
},
|
||||
"changes": {
|
||||
"KR1-F1": "Retain mode with admission. Recheck human actor, absence of WARDEN_AGENT_ID (including empty marker), and parent-session scope in common run/rollback/reconcile guard, after caller-session revalidation and before journal writes/owner callbacks.",
|
||||
"KR1-F2": "Retain exact authenticated whole-catalog bytes. Parse and validate that snapshot at admission and attended dispatch; share attended lane active/reviewed/login-or-attended-change/session-login rule with planner. Pending Apps sessions stay refused, even under hypothetical in-memory draft promotion.",
|
||||
"KR1-F3": "Integration docstring enumerates type acknowledgment, case materialization/replacement acceptance, promotion, pin, stub adapters, unconditional evidence verifier and human environment simulation. Positive expectation covers five resolvable steps; two Apps steps refused.",
|
||||
"KR1-F4": "Sibling-independent 19-test matrix: six marked runtime negatives, three unmarked run/rollback/reconcile positive controls, nine planner/executor lane cases, one missing-session parser rejection."
|
||||
},
|
||||
"validation": {
|
||||
"baseline_regressions": "Final 19 new tests on unmodified frozen production source:13 failed,6 passed. Six runtime identity and seven invalid lane/session/review negatives fail because original code does not refuse. Six controls pass.",
|
||||
"initial_test_authoring_errors": "First run:16 failures, due fixture lane_entry argument misuse; second:15 failures/1 pass, including planner schema omissions. These are invalid setup attempts, retained in logs. Corrected meaningful pre-expansion baseline:13 failed/3 passed; final with positive controls:13 failed/6 passed.",
|
||||
"candidate_new_regressions": "19 passed in 1.57s",
|
||||
"focused": "99 passed in 13.95s; expanded:129 passed,2 skipped in 16.40s (home-relative canonical lookups; later supplied frozen NetKingdom link)",
|
||||
"full_suite": "765 passed,4 integration-marked deselected in 68.11s; uninterrupted final suite includes resolved home-relative lookups",
|
||||
"command": "python3 /tmp/warden-kernel-fix-r1/run.py candidate-full.log -m pytest -q",
|
||||
"lint": "python -m ruff check .: All checks passed; git diff --check passed",
|
||||
"isolation": "Allowlisted environment; HOME=/tmp/warden-kernel-fix-r1/home with only frozen NetKingdom symlink and three public workload declarations. No ambient credentials copied or inventoried. Python socket audit guard denies all remote connections and allows only loopback endpoints bound by same test process. PATH=/usr/bin:/bin. Reused existing scratch venv interpreter/dependencies read-only; no live or native owner callback used.",
|
||||
"limits": "Four repository integration-marked tests remain deselected. No new native/provenance claim; independent successor review still required.",
|
||||
"patch_check_attempts": "First check incorrectly used the regression base tree, where the new test already existed; refused with already exists. Fresh untouched archive check then passed; no source conflict."
|
||||
},
|
||||
"separate_test_isolation_proposal": {
|
||||
"file": "test-isolation-proposal.patch",
|
||||
"sha256": "sha256:0f6c46303ec24f337e4190ec84773ccefb4833241233b4b7d56bd1927eba838c",
|
||||
"included_in_candidate": false,
|
||||
"description": "Autouse fixture only in tests/test_access.py replaces ambient identity helper with deterministic no-token metadata. Existing explicit identity-display test overrides it. Production advisory helper unchanged; separately subprocess-mocked helper tests retained.",
|
||||
"regression": "Synthetic BAO_TOKEN sentinel plus forbidden subprocess callback:1 failed against base, with no subprocess/native lookup permitted. Proposed fixture:26 access/helper tests passed in1.87s.",
|
||||
"limit": "This narrow fixture is not a whole-suite sandbox; allowlisted HOME/environment and network guard remain required. Root to create live owner handoff for broader isolation work."
|
||||
},
|
||||
"boundaries": {
|
||||
"canonical_owner_edits": false,
|
||||
"credentials_or_secret_reads": false,
|
||||
"native_actions": false,
|
||||
"grant": "none",
|
||||
"catalog_promotion": false,
|
||||
"owner_login_selection": false,
|
||||
"PARAMETER_PINS_changed": false,
|
||||
"draft_lanes_changed": false
|
||||
},
|
||||
"follow_up": [
|
||||
"Independent review of candidate patch and exact file pins before owner application.",
|
||||
"Owner confirmation/login choices and catalog promotion remain separate gates.",
|
||||
"Founder exact-type acknowledgment and PIN-1 remain separate; no pin added.",
|
||||
"Native WARDEN-WP-0044-T03 evidence and downstream acceptance remain outside source tests.",
|
||||
"Root handles progress logging, publication and live test-isolation handoff."
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
diff --git a/tests/test_access.py b/tests/test_access.py
|
||||
index c4286aa..1736a45 100644
|
||||
--- a/tests/test_access.py
|
||||
+++ b/tests/test_access.py
|
||||
@@ -4,6 +4,8 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
+import pytest
|
||||
+
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from warden.access import agent_read_boundary_identity, expand_handoff, policy_gate_status
|
||||
@@ -13,6 +15,34 @@ from warden.routing.models import RouteEntry
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
+@pytest.fixture(autouse=True)
|
||||
+def synthetic_openbao_advisory_identity(monkeypatch):
|
||||
+ """Advisory CLI tests must not inspect or use workstation credentials.
|
||||
+
|
||||
+ Identity-display tests override this stub explicitly. The real helper has
|
||||
+ separate subprocess-mocked unit coverage in test_openbao_caller.py.
|
||||
+ """
|
||||
+ monkeypatch.setattr(
|
||||
+ "warden.openbao_caller.describe_ambient_openbao_caller",
|
||||
+ lambda *args, **kwargs: {
|
||||
+ "source": "none", "entity_id": None, "display_name": None,
|
||||
+ "auth_path": None, "policies": [], "oidc_role": None, "ttl": None,
|
||||
+ "error": "synthetic test: no ambient OpenBao token",
|
||||
+ },
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def test_advisory_does_not_invoke_ambient_lookup(monkeypatch):
|
||||
+ monkeypatch.setenv("BAO_TOKEN", "fixture-only-never-a-real-token")
|
||||
+ def forbidden(*args, **kwargs):
|
||||
+ raise AssertionError("advisory unit tests must not launch a token lookup")
|
||||
+ monkeypatch.setattr("warden.openbao_caller.subprocess.run", forbidden)
|
||||
+ result = runner.invoke(app, ["access", "npm token", "--json"])
|
||||
+ assert result.exit_code == 0
|
||||
+ assert json.loads(result.stdout)["openbao_caller"]["source"] == "none"
|
||||
+
|
||||
+
|
||||
+
|
||||
def _repo_catalog() -> Path:
|
||||
return Path(__file__).resolve().parents[1] / "registry" / "routing" / "catalog.yaml"
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
"""Invoke only pinned, offline validator child; keep socket/native denial in child."""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import subprocess
|
||||
import warden.case_inputs as inputs
|
||||
import pytest
|
||||
|
||||
PREAMBLE = '''import sys as _review_sys\ndef _review_guard(event,args):\n if event.startswith('socket.') or event in {'subprocess.Popen','os.system','os.exec','os.posix_spawn'}:\n raise RuntimeError('review validator: network/native disabled')\n if event=='open' and isinstance(args[0],str) and (args[0].startswith('/home/') or args[0].startswith('/root/')):\n raise RuntimeError('review validator: ambient home denied')\n_review_sys.addaudithook(_review_guard)\n'''
|
||||
old_run = subprocess.run
|
||||
|
||||
def run_pinned(args,*pos,**kw):
|
||||
if (type(args) is not list or len(args)!=5 or args[:3]!=[sys.executable,'-I','-c']
|
||||
or args[3]!=inputs._VALIDATE or not str(args[4]).startswith('/tmp/warden-case-validator-')
|
||||
or kw.get('env')!={}):
|
||||
raise RuntimeError('review validator: unexpected process refused')
|
||||
return old_run([*args[:3],PREAMBLE+args[3],args[4]],*pos,**kw)
|
||||
inputs.subprocess.run=run_pinned
|
||||
raise SystemExit(pytest.main(['-q','tests/test_case_1_3_integration.py']))
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
{
|
||||
"schema": "custodian.warden-1.3-kernel-review-r2.v1",
|
||||
"date": "2026-10-17",
|
||||
"role": "independent successor source reviewer; not candidate author",
|
||||
"disposition": "accept candidate source delta for KR1-F1, KR1-F2, KR1-F3 and KR1-F4",
|
||||
"scope": "Exact candidate.patch on frozen 5abde817775d6304e3cf5a46948717a1606981bf; separate test-isolation-proposal.patch reviewed and accepted as narrow test hygiene. No native acceptance, external owner assent, catalog promotion, founder acknowledgment or PARAMETER_PINS authorization.",
|
||||
"inputs": {
|
||||
"accepted_kernel_1": "117fec9241e7b985dfb187b992e173d0a5dc3700",
|
||||
"frozen_branch": "5abde817775d6304e3cf5a46948717a1606981bf",
|
||||
"net_kingdom": "001a38ca2aa85155ce4840c4da22aca6e2346171",
|
||||
"candidate.patch": "sha256:1a3530a1e469fc03c2c29f1b66fa3ba1904e56bf2abcfb242c7a2ed015fb3e74",
|
||||
"implementation-r1.json": "sha256:b9ee843bba417c82ad77ac8a221cf64dd5088aa640528524b2e0ef0c129d3184",
|
||||
"test-isolation-proposal.patch": "sha256:0f6c46303ec24f337e4190ec84773ccefb4833241233b4b7d56bd1927eba838c",
|
||||
"retained_kernel-review-r1.json": "sha256:43c96da94605082b4924ccd8ef41a1f8737d7e9ada95047ff16415b4614905dc",
|
||||
"candidate_files_verified": {
|
||||
"src/warden/case_run.py": "sha256:1bc73e30ac718fd75bbaff2a739495fc051aeaec73802ac71f0583cb2d53ab0a",
|
||||
"src/warden/case_type.py": "sha256:7b659b8d682863cd28df24025be4b10dd7ca3df724350a22d360c6af9615bca7",
|
||||
"src/warden/routing/models.py": "sha256:b85372b2572a49af62e9534f26a33963b894633c7cd43dd2a28416b8959cba3e",
|
||||
"tests/test_case_1_3_integration.py": "sha256:4862c5a004075eb18733d354bee7dda3f55e04a6ee8ecb44b76166347c121158",
|
||||
"tests/test_case_admission_modes.py": "sha256:9110d6b6286cf0f85268d4970d1fd403250da3f04351d70b1fd2d9d7a93e2d89",
|
||||
"tests/test_case_attended_boundary.py": "sha256:4615e653f06144305ea1a84e98bc6260e16ed41ef9e5db2d0f162ef9f723ef2d"
|
||||
},
|
||||
"unchanged_pins_verified": {
|
||||
"src/warden/case_inputs.py": "sha256:1334b0cd9897e763cfea2d306e997c659f3f284ecc3f9612140c2104835da7c7",
|
||||
"registry/routing/catalog.yaml": "sha256:ba9634a92e584dac8c42e8624ab52adce06e79a285209cb64c0daa4dac9cd7d8",
|
||||
"registry/generated/high-risk-data-paths.yaml": "sha256:5cd9c03b86751f7197cb5661a53a86d59765137958b215ec14bb343156f216d0"
|
||||
}
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "KR1-F1",
|
||||
"status": "resolved in source",
|
||||
"evidence": "The cached tuple now retains attended mode. All run/rollback/reconcile paths enter _admission before journal lock/load/save or owner callbacks. Caller-session evidence is reverified, then attended human actor, presence of any WARDEN_AGENT_ID key (including empty), and parent-session scope are checked. Independent changed actor/principal/descriptor-scope/session probes reject with no journal change/callback; both marker variants reject on all three cached operations."
|
||||
},
|
||||
{
|
||||
"id": "KR1-F2",
|
||||
"status": "resolved in source",
|
||||
"evidence": "Admission authenticates the complete catalog digest and parses its exact bytes. The same bytes are retained and reparsed at attended dispatch. Shared attended_lane_issue checks active/reviewed login or attended-change whose session_lane resolves to active/reviewed login in that same parsed dictionary. The 9-case author matrix checks planner/executor parity and the parser case checks missing session_lane. Independent dispatch interception verifies byte-identical catalog input on run/rollback/reconcile despite a later invalid ambient catalog file. The two unresolved Apps steps refuse in actual frozen composition integration."
|
||||
},
|
||||
{
|
||||
"id": "KR1-F3",
|
||||
"status": "resolved in source",
|
||||
"evidence": "Docstring explicitly declares acknowledged-type substitution, materialized example plus replaced acceptance, in-memory promotion and parameter pin, stub adapters, unconditional evidence verifier and removed agent marker. Test expects five conditional admissions and two Apps refusals, with no adapter dispatch. These are fixture admissions, not authenticated events."
|
||||
},
|
||||
{
|
||||
"id": "KR1-F4",
|
||||
"status": "resolved in source",
|
||||
"evidence": "New 19-test attended matrix has no sibling source dependency; 13 tests fail against unchanged frozen production source and 6 controls pass. Reviewer-owned 22 tests add independent changed authority and catalog snapshot checks. Combined frozen baseline: 22 failed,19 passed; candidate tests:41 passed within focused set. No control is weakened."
|
||||
}
|
||||
],
|
||||
"additional_blocking_findings": [],
|
||||
"candidate_correction_required": false,
|
||||
"test_isolation_proposal": {
|
||||
"disposition": "accept narrow test-only patch",
|
||||
"production_changes": false,
|
||||
"baseline_without_proposal": "25 original access/helper tests pass in empty home; they do not alone prove absence of ambient lookup. Appended sentinel regression fails (1 failed in0.51s), with forbidden mocked subprocess callback raising before any process starts.",
|
||||
"with_proposal": "26 access/helper tests pass in2.16s. Autouse fixture stubs the lazy advisory identity function; existing explicit display test overrides it. Real helper coverage remains separately subprocess-mocked.",
|
||||
"limits": "Module-local fixture is not an execution sandbox, does not fix production behavior and does not protect unrelated tests. Parent must retain broader isolation handoff."
|
||||
},
|
||||
"validation": [
|
||||
{
|
||||
"command": "python3 /tmp/warden-kernel-review-r2/run.py focused-corrected.log -m pytest -q tests/test_review_r2_independent.py tests/test_case_attended_boundary.py tests/test_case_admission_modes.py",
|
||||
"result": "64 passed in2.55s"
|
||||
},
|
||||
{
|
||||
"command": "python3 /tmp/warden-kernel-review-r2/run-baseline.py baseline-corrected.log -m pytest -q tests/test_case_attended_boundary.py tests/test_review_r2_independent.py",
|
||||
"result": "22 failed,19 passed in2.11s; expected differential:13 original defect probes,6 independent identity marker probes,3 reparse assertions fail on predecessor"
|
||||
},
|
||||
{
|
||||
"command": "python3 /tmp/warden-kernel-review-r2/run.py integration-safe.log /tmp/warden-kernel-review-r2/integration_runner.py",
|
||||
"result": "6 passed in10.11s; actual pinned metadata validator executes as isolated Python child with guard preamble; native owner callbacks remain forbidden"
|
||||
},
|
||||
{
|
||||
"command": "python3 /tmp/warden-kernel-review-r2/run.py final-focused.log -m pytest -q tests/test_review_r2_independent.py tests/test_case_attended_boundary.py tests/test_case_admission_modes.py tests/test_access.py tests/test_openbao_caller.py",
|
||||
"result": "90 passed in4.22s, including separate isolation proposal"
|
||||
}
|
||||
],
|
||||
"invalid_initial_attempts": {
|
||||
"independent.log": "23 failed,47 passed.21 reviewer tests used lane_entry(lane=login), which changes lane id rather than kind; corrected to dict(lane_entry(),lane=login).2 integration tests were refused by strict subprocess guard at pinned validator launch. No owner/native callback occurred. These are setup/isolation-control refusals, not candidate defects or passing evidence.",
|
||||
"baseline.log": "Same initial reviewer fixture mistake; superseded by preserved corrected baseline. Earlier author tests still reproduced13 defects."
|
||||
},
|
||||
"isolation": {
|
||||
"home": "/tmp/warden-kernel-review-r2/home (empty, independently asserted)",
|
||||
"environment": "Allowlisted HOME,PATH=/usr/bin:/bin,PYTHONPATH pointing only to scratch guard and source,PYTHONDONTWRITEBYTECODE,WARDEN_TEST_NETKINGDOM_ROOT,LANG. No ambient home/environment/token copied, inspected or inventoried. Synthetic BAO_TOKEN sentinel exists only under monkeypatch.",
|
||||
"interpreter": "Read-only existing scratch venv /tmp/warden-kernel-review-r1/final/.venv/bin/python; no dependency install/network",
|
||||
"controls": "Socket audit events denied. Process creation denied except the exact reviewed offline BrowserInputs validator command (empty child environment, pinned source modules copied by production code). Reviewer wrapper prepends child audit denial for sockets, further subprocess, and ambient /home or /root file reads. Final focused run also denies ambient home reads in parent. Guard probes independently demonstrate socket/native lookup refusal.",
|
||||
"native_actions": false,
|
||||
"credential_access": false,
|
||||
"canonical_edits": false,
|
||||
"new_commits": false
|
||||
},
|
||||
"limitations": [
|
||||
"Focused independent review, not a new full-suite certification; author full-suite claims are not independently adopted.",
|
||||
"Existing KERNEL-1 review remains retained, including its prior ambient-lookup deviation and unknown outcome; this successor does not erase or reclassify it.",
|
||||
"WARDEN_AGENT_ID is a source marker boundary, not proof of isolated human authentication; the injected evidence verifier and owner adapter remain required trust roots.",
|
||||
"No real native execution, owner evidence provenance, login choice, original-created-object readback or revocation event was exercised.",
|
||||
"No external owner assent, founder exact-type acknowledgment, 1.3 pin, native T03 or downstream acceptance is supplied.",
|
||||
"Exact-type known fixed pins and all draft lanes remain unchanged; no catalog was promoted."
|
||||
],
|
||||
"knowledge_orientation": {
|
||||
"package_version": "0.2.6",
|
||||
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
|
||||
"validated_start_status": "withheld/orientation-only (exit2; changed source repositories)",
|
||||
"source_review": "Read start/local-work cards as orientation only, actual ops-warden AGENTS.md/INTENT.md, frozen NetKingdom SECURITY-COMPANION.md and scoped production/test source directly. No inferred authority from withheld card."
|
||||
},
|
||||
"follow_up": [
|
||||
"Root may integrate exact reviewed source and narrow separate test patch within its existing authorization.",
|
||||
"Keep original adverse review intact and hand off broader test isolation as live owner work.",
|
||||
"Owner login/catalog promotion, founder exact type/PIN-1, authenticated native T03 and downstream acceptance remain separate gates."
|
||||
],
|
||||
"grants": "none",
|
||||
"packet_hashes": {
|
||||
"final-focused.log": "sha256:53adf310d91cbaaa401731f7680654bf8a0bf0f5aa955b22bbc9b6adafacb793",
|
||||
"focused-corrected.log": "sha256:e942df3c21caf8eef7e94cfc70ba98b8005da909079fb9adb8502b4c7ede60ec",
|
||||
"reviewer-probes.py": "sha256:e52025893ca0755b3a62e2585cf2f16002a5b705f7cfb0582b05435d3b9e336a",
|
||||
"integration_runner.py": "sha256:d77da107d67ec827f19e0c46045a2802081a62283c3bdb5bfec35feabb454234",
|
||||
"isolation-proposal.log": "sha256:a2a8b7c722b4b682291e588ccc374559218555adbf75b3102784d7dba2223202",
|
||||
"run.py": "sha256:c6f931ba4ff9b0ef42627030a016dd7ebe3f353b3024f1c40b80df890aefe59f",
|
||||
"baseline-corrected.log": "sha256:1becfd551cea8b80bc0821f266396c69ea435d644d734a605379da342902ad37",
|
||||
"isolation-sentinel-baseline.log": "sha256:69907c6283e73d78a50c871ff3f021da07c00b8902330a4f7b6fce72fc5b0071",
|
||||
"isolation-baseline.log": "sha256:caa59ec08f580e3130ece8f55340e0ffbf170b205cd6e8c7263d5cb0a8948cfa",
|
||||
"integration-safe.log": "sha256:e4bfb3379648c76417228ff5049df1fdbfc09f81a979500e549e1eed41a61c4e",
|
||||
"run-baseline.py": "sha256:02e2a409cd23fb52f518c202c923877fdcc381f708b4e8d77bad04e034b46e73",
|
||||
"independent.log": "sha256:ffb5623ba301aebcf597a5009d7cfe3e04356fd59f1e4bc858084f3e8d0e4c5a",
|
||||
"setup.py": "sha256:fcdeb50a196ce5afda6243b9fb51584af58ba5d55e75a4caed017f742d560500",
|
||||
"sitecustomize.py": "sha256:6b7cec873fa1fca6767b77f514bec499adb5e1e7745dbc8eddf36771917ad4f4"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
"""Reviewer-owned synthetic probes; no native owner acceptance."""
|
||||
from dataclasses import asdict, replace
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from tests.test_case_run import make_rig, fixture_catalog, lane_entry
|
||||
from warden.case_run import digest
|
||||
from warden.case_receipt_state import Refused, Outcome
|
||||
|
||||
|
||||
def setup(tmp_path, monkeypatch, *, entries=None):
|
||||
monkeypatch.delenv('WARDEN_AGENT_ID', raising=False)
|
||||
rig = make_rig(tmp_path)
|
||||
ex, original, calls, sources, kwargs = rig
|
||||
case, typ, _, _ = sources
|
||||
d = replace(original[0], effect='change', revocation_scopes=('parent-session',))
|
||||
ds = json.dumps(asdict(d)).encode()
|
||||
t = yaml.safe_load(typ)
|
||||
t['spec']['execution_descriptors'] = {d.step: digest(ds)}
|
||||
t['spec']['follow_through'] = [dict(id=d.step,owner=d.owner,lane=d.lane,warden_executes=False,attended=True)]
|
||||
catalog = fixture_catalog(*(entries or [dict(lane_entry(),lane='login')]))
|
||||
ex.evidence_verifier = lambda *_: True
|
||||
admitted = ex.admit(case,yaml.safe_dump(t).encode(),ds,catalog,**kwargs)
|
||||
return ex, admitted, calls, catalog
|
||||
|
||||
|
||||
def count(ex):
|
||||
return tuple(ex.journal.db.execute('SELECT COUNT(*) FROM '+name).fetchone()[0] for name in ['intents','receipts'])
|
||||
|
||||
|
||||
def prepare(ex, admitted, operation):
|
||||
if operation == 'rollback':
|
||||
assert ex.run(*admitted).last.outcome is Outcome.SUCCESS
|
||||
if operation == 'reconcile':
|
||||
old = ex.adapters[admitted[0].adapter]
|
||||
def lost(_):
|
||||
raise RuntimeError('lost synthetic response')
|
||||
ex.adapters[old.reference] = replace(old,invoke=lost)
|
||||
assert ex.run(*admitted).last.outcome is Outcome.UNCERTAIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize('operation',['run','rollback','reconcile'])
|
||||
@pytest.mark.parametrize('tamper',['marker','empty-marker','actor','principal','scopes','expired-session'])
|
||||
def test_each_cached_operation_rejects_changed_authority(tmp_path,monkeypatch,operation,tamper):
|
||||
ex,args,calls,_ = setup(tmp_path,monkeypatch)
|
||||
prepare(ex,args,operation)
|
||||
callbacks=[]
|
||||
old=ex.adapters[args[0].adapter]
|
||||
def forbidden(*_):
|
||||
callbacks.append('called')
|
||||
raise AssertionError('must not dispatch')
|
||||
ex.adapters[old.reference]=replace(old,invoke=forbidden,rollback=forbidden,reconcile=forbidden)
|
||||
before=count(ex)
|
||||
if tamper in ('marker','empty-marker'):
|
||||
monkeypatch.setenv('WARDEN_AGENT_ID', '' if tamper=='empty-marker' else 'review-agent')
|
||||
elif tamper=='actor':args=(args[0],replace(args[1],actor='agent'),args[2])
|
||||
elif tamper=='principal':args=(args[0],replace(args[1],principal='other'),args[2])
|
||||
elif tamper=='scopes':args=(replace(args[0],revocation_scopes=()),args[1],args[2])
|
||||
else:ex.evidence_verifier=lambda *_: False
|
||||
with pytest.raises(Refused):getattr(ex,operation)(*args)
|
||||
assert count(ex)==before
|
||||
assert callbacks==[]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('operation',['run','rollback','reconcile'])
|
||||
def test_dispatch_reparses_exact_admitted_catalog(tmp_path,monkeypatch,operation):
|
||||
ex,args,_,catalog = setup(tmp_path,monkeypatch)
|
||||
prepare(ex,args,operation)
|
||||
import warden.case_run as source
|
||||
parse=source._catalog_lane
|
||||
seen=[]
|
||||
def checked(blob,descriptor):
|
||||
seen.append(blob)
|
||||
return parse(blob,descriptor)
|
||||
monkeypatch.setattr(source,'_catalog_lane',checked)
|
||||
# A later ambient file must not replace the authenticated admission snapshot.
|
||||
monkeypatch.setenv('WARDEN_ROUTING_CATALOG',str(tmp_path/'untrusted.yaml'))
|
||||
(tmp_path/'untrusted.yaml').write_bytes(fixture_catalog(dict(lane_entry(),lane='secret')))
|
||||
result=getattr(ex,operation)(*args)
|
||||
assert result.last.outcome in (Outcome.SUCCESS,Outcome.ROLLED_BACK,Outcome.NO_CHANGE)
|
||||
assert seen==[catalog]
|
||||
assert isinstance(catalog,bytes)
|
||||
|
||||
|
||||
def test_isolation_denies_network_and_native_process():
|
||||
assert os.environ.get('BAO_TOKEN') is None
|
||||
assert os.environ.get('VAULT_TOKEN') is None
|
||||
assert list(__import__('pathlib').Path.home().glob('*'))==[]
|
||||
with pytest.raises(RuntimeError,match='sockets disabled'):socket.socket()
|
||||
with pytest.raises(RuntimeError,match='subprocess disabled'):subprocess.run(['bao','token','lookup'])
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from pathlib import Path
|
||||
import subprocess,sys
|
||||
r=Path('/tmp/warden-kernel-review-r2')
|
||||
env={'HOME':str(r/'home'),'PATH':'/usr/bin:/bin','PYTHONPATH':str(r/'guard')+':'+str(r/'baseline/src'),'PYTHONDONTWRITEBYTECODE':'1','WARDEN_TEST_NETKINGDOM_ROOT':str(r/'net-kingdom'),'LANG':'C.UTF-8'}
|
||||
p='/tmp/warden-kernel-review-r1/final/.venv/bin/python'
|
||||
with (r/sys.argv[1]).open('w') as log:
|
||||
result=subprocess.run([p,*sys.argv[2:]],cwd=r/'baseline',env=env,stdout=log,stderr=subprocess.STDOUT)
|
||||
print(result.returncode)
|
||||
sys.exit(result.returncode)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from pathlib import Path
|
||||
import subprocess,sys
|
||||
r=Path('/tmp/warden-kernel-review-r2')
|
||||
env={'HOME':str(r/'home'),'PATH':'/usr/bin:/bin','PYTHONPATH':str(r/'guard')+':'+str(r/'candidate/src'),'PYTHONDONTWRITEBYTECODE':'1','WARDEN_TEST_NETKINGDOM_ROOT':str(r/'net-kingdom'),'LANG':'C.UTF-8'}
|
||||
p='/tmp/warden-kernel-review-r1/final/.venv/bin/python'
|
||||
with (r/sys.argv[1]).open('w') as log:
|
||||
result=subprocess.run([p,*sys.argv[2:]],cwd=r/'candidate',env=env,stdout=log,stderr=subprocess.STDOUT)
|
||||
print(result.returncode)
|
||||
sys.exit(result.returncode)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from pathlib import Path
|
||||
import subprocess,tarfile,io,hashlib,json
|
||||
root=Path('/tmp/warden-kernel-review-r2')
|
||||
packet=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1')
|
||||
for name,repo,rev in [('candidate','ops-warden','5abde817775d6304e3cf5a46948717a1606981bf'),('net-kingdom','net-kingdom','001a38ca2aa85155ce4840c4da22aca6e2346171')]:
|
||||
tree=root/name; tree.mkdir()
|
||||
archive=subprocess.check_output(['git','-C','/home/worsch/'+repo,'archive',rev])
|
||||
with tarfile.open(fileobj=io.BytesIO(archive)) as t:t.extractall(tree,filter='data')
|
||||
subprocess.run(['git','apply',str(packet/'candidate.patch')],cwd=root/'candidate',check=True)
|
||||
(root/'home').mkdir(); (root/'guard').mkdir()
|
||||
(root/'guard/sitecustomize.py').write_text('''import sys\ndef guard(event,args):\n if event.startswith('socket.'):\n raise RuntimeError('review isolation: sockets disabled')\n if event in {'subprocess.Popen','os.system','os.exec','os.posix_spawn'}:\n raise RuntimeError('review isolation: subprocess disabled')\nsys.addaudithook(guard)\n''')
|
||||
(root/'run.py').write_text('''from pathlib import Path\nimport subprocess,sys\nr=Path('/tmp/warden-kernel-review-r2')\nenv={'HOME':str(r/'home'),'PATH':'/usr/bin:/bin','PYTHONPATH':str(r/'guard')+':'+str(r/'candidate/src'),'PYTHONDONTWRITEBYTECODE':'1','WARDEN_TEST_NETKINGDOM_ROOT':str(r/'net-kingdom'),'LANG':'C.UTF-8'}\np='/tmp/warden-kernel-review-r1/final/.venv/bin/python'\nwith (r/sys.argv[1]).open('w') as log:\n result=subprocess.run([p,*sys.argv[2:]],cwd=r/'candidate',env=env,stdout=log,stderr=subprocess.STDOUT)\nprint(result.returncode)\nsys.exit(result.returncode)\n''')
|
||||
print('Archived public source and applied exact patch; isolated runner created.')
|
||||
Loading…
Add table
Add a link
Reference in a new issue