Implement SECRETS-WP-0008 unblocked layer-model obligations
Load pep-stance.yaml as the live unreachable-engine gate and record named stance fields on privileged evidence. Classify evidence, queue load-bearing records in a local outbox, and add heartbeat/drain commands that never sit on a mutation path. Publish proposed SSH-CA and secret-use evidence contracts without adding an OpenBao SSH-CA write. T02 (access-engine decision records) and T06 (no standing credential) stay wait on external endpoints. Assistant: grok Assistant-Session: 01a04cea-cb33-7c63-bad7-c1b0f9f0076b
This commit is contained in:
parent
57f6c4fa65
commit
3cd9955ac9
16 changed files with 1041 additions and 77 deletions
102
tests/test_evidence_class.py
Normal file
102
tests/test_evidence_class.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
"""§9.6 classification, local outbox, heartbeat, and drain."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
from types import SimpleNamespace
|
||||
|
||||
from secrets_engine.evidence import EvidenceWriter, drain_outbox, write_heartbeat
|
||||
from secrets_engine.evidence_class import classify
|
||||
|
||||
|
||||
def test_load_bearing_record_is_queued_locally_before_jsonl(tmp_path):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="http://hub.invalid", topic_id="t")
|
||||
record = writer.record(
|
||||
"lifecycle-destroy",
|
||||
result="attempt",
|
||||
catalog_id="lane",
|
||||
stage="prod",
|
||||
detail={"value": "should-not-leak"},
|
||||
hub=True,
|
||||
)
|
||||
assert record["evidence_kind"] == "load-bearing"
|
||||
assert record["completeness_claimed"] is False
|
||||
assert record["outbox_queued"] is True
|
||||
assert record["hub_delivery_requested"] is False
|
||||
queued = list((tmp_path / "outbox").glob("*.json"))
|
||||
assert len(queued) == 1
|
||||
queued_payload = json.loads(queued[0].read_text())
|
||||
assert queued_payload["record_id"] == record["record_id"]
|
||||
assert "should-not-leak" not in queued[0].read_text()
|
||||
jsonl = next(tmp_path.glob("evidence-*.jsonl")).read_text()
|
||||
assert record["record_id"] in jsonl
|
||||
|
||||
|
||||
def test_attributive_record_is_not_queued_and_may_post_hub(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"urllib.request.urlopen",
|
||||
lambda *_args, **_kwargs: SimpleNamespace(status=200, read=lambda: b"{}"),
|
||||
)
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="http://hub.invalid", topic_id="t")
|
||||
record = writer.record("apply", result="applied", catalog_id="lane", stage="prod")
|
||||
assert record["evidence_kind"] == "attributive"
|
||||
assert record["hub_delivery_requested"] is True
|
||||
assert not list((tmp_path / "outbox").glob("*.json"))
|
||||
|
||||
|
||||
def test_heartbeat_is_a_positive_claim_not_a_permission(tmp_path):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
record = write_heartbeat(writer, stage="prod")
|
||||
assert record["action"] == "evidence-heartbeat"
|
||||
assert record["result"] == "nothing-to-report"
|
||||
assert record["evidence_kind"] == "heartbeat"
|
||||
assert record["completeness_claimed"] is False
|
||||
assert record["outbox_queued"] is True
|
||||
# Heartbeat must not be used as "no jsonl means nothing happened".
|
||||
assert classify("evidence-heartbeat", "prod").kind == "heartbeat"
|
||||
|
||||
|
||||
def test_drain_without_audit_core_keeps_files(tmp_path):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
writer.record("provision", result="from-file", catalog_id="lane", stage="prod")
|
||||
result = drain_outbox(writer, audit_core_url="")
|
||||
assert result["queued"] == 1
|
||||
assert result["skipped"] == 1
|
||||
assert result["delivered"] == 0
|
||||
assert result["completeness_claimed"] is False
|
||||
assert list((tmp_path / "outbox").glob("*.json"))
|
||||
|
||||
|
||||
def test_drain_audit_core_outage_does_not_raise_or_delete(tmp_path, monkeypatch):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
writer.record("revoke", result="native-access-deactivated", catalog_id="lane", stage="prod")
|
||||
|
||||
def offline(*_args, **_kwargs):
|
||||
raise urllib.error.URLError("audit-core down")
|
||||
|
||||
monkeypatch.setattr("urllib.request.urlopen", offline)
|
||||
result = drain_outbox(writer, audit_core_url="http://audit-core.invalid")
|
||||
assert result["failed"] == 1
|
||||
assert result["delivered"] == 0
|
||||
assert list((tmp_path / "outbox").glob("*.json"))
|
||||
|
||||
|
||||
def test_successful_drain_removes_only_delivered_files(tmp_path, monkeypatch):
|
||||
writer = EvidenceWriter(evidence_dir=tmp_path, hub_url="")
|
||||
writer.record("provision", result="from-file", catalog_id="lane", stage="prod")
|
||||
monkeypatch.setattr(
|
||||
"urllib.request.urlopen",
|
||||
lambda *_args, **_kwargs: SimpleNamespace(status=202, read=lambda: b"{}"),
|
||||
)
|
||||
result = drain_outbox(writer, audit_core_url="http://audit-core.invalid")
|
||||
assert result["delivered"] == 1
|
||||
assert not list((tmp_path / "outbox").glob("*.json"))
|
||||
|
||||
|
||||
def test_classify_is_not_a_permission_api():
|
||||
"""Missing or unknown actions are attributive labels, never a deny."""
|
||||
unknown = classify("not-a-control", "prod")
|
||||
assert unknown.kind == "attributive"
|
||||
assert unknown.completeness_claimed is False
|
||||
assert "permit" not in dir(classify)
|
||||
assert "deny" not in dir(classify)
|
||||
|
|
@ -17,6 +17,8 @@ import yaml
|
|||
from secrets_engine.catalog import validate_entry
|
||||
from secrets_engine.cli import _require_lane_approval
|
||||
from secrets_engine.errors import DecisionError
|
||||
from secrets_engine.evidence_class import SHIPPED_RULES, classify, load_classification_rules
|
||||
from secrets_engine.pep_stance import SHIPPED_STANCE, load_pep_stance
|
||||
|
||||
from tests.test_catalog import VALID
|
||||
|
||||
|
|
@ -25,6 +27,7 @@ SCRIPT = ROOT / "scripts" / "check_layer_conformance.py"
|
|||
DECL = ROOT / "layer.yaml"
|
||||
STANCE = ROOT / "pep-stance.yaml"
|
||||
INTENT = ROOT / "INTENT.md"
|
||||
CLASSIFICATION = ROOT / "evidence-classification.yaml"
|
||||
|
||||
|
||||
def _decl() -> dict:
|
||||
|
|
@ -73,6 +76,7 @@ def test_proposed_capabilities_carry_gap_record_fields():
|
|||
assert cap.get(field), f"{cap.get('id')} missing {field}"
|
||||
assert cap["state"] == "unowned-capability"
|
||||
assert cap["owner_status"] == "proposed"
|
||||
assert cap.get("contract"), f"{cap.get('id')} missing contract"
|
||||
|
||||
|
||||
def test_stance_map_is_total_over_catalog_stages():
|
||||
|
|
@ -85,19 +89,53 @@ def test_stance_map_is_total_over_catalog_stages():
|
|||
assert _stance()["verdict_caching"] == "none"
|
||||
|
||||
|
||||
def test_published_map_equals_shipped_constant_and_loader():
|
||||
"""Changing the YAML without changing SHIPPED_STANCE fails, and the reverse."""
|
||||
published = _stance()["stance"]
|
||||
loaded = load_pep_stance().stance
|
||||
assert published == SHIPPED_STANCE
|
||||
assert loaded == SHIPPED_STANCE
|
||||
|
||||
|
||||
def test_published_prod_stance_equals_shipped_fail_closed(monkeypatch):
|
||||
"""pep-stance.yaml prod: fail_closed must equal _require_lane_approval."""
|
||||
assert _stance()["stance"]["prod"] == "fail_closed"
|
||||
"""Runtime reads pep-stance.yaml; prod fail_closed must equal the gate."""
|
||||
assert load_pep_stance().stance["prod"] == "fail_closed"
|
||||
entry = validate_entry(dict(VALID, stage="prod", approval={"model": "bootstrap-only"}))
|
||||
cfg = SimpleNamespace(hub_url="http://127.0.0.1:8000", bao_addr="http://127.0.0.1:8200")
|
||||
monkeypatch.delenv("SECRETS_ENGINE_UNSAFE_DEMO", raising=False)
|
||||
with pytest.raises(DecisionError, match="live production remains disabled"):
|
||||
with pytest.raises(DecisionError, match="live production remains disabled") as raised:
|
||||
_require_lane_approval(cfg, entry, "apply")
|
||||
assert raised.value.stance["stance_stage"] == "prod"
|
||||
assert raised.value.stance["stance_failure_mode"] == "fail_closed"
|
||||
assert "stance_decision_id" not in raised.value.stance
|
||||
|
||||
|
||||
def test_yaml_is_the_runtime_source(tmp_path, monkeypatch):
|
||||
"""A published map the pin does not match is a test failure; runtime follows YAML."""
|
||||
path = tmp_path / "pep-stance.yaml"
|
||||
path.write_text(
|
||||
yaml.safe_dump(
|
||||
{
|
||||
"stance": {
|
||||
"build": "fail_open",
|
||||
"test": "fail_open",
|
||||
"prod": "fail_open",
|
||||
"unknown": "fail_closed",
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("SECRETS_ENGINE_PEP_STANCE", str(path))
|
||||
entry = validate_entry(dict(VALID, stage="prod", approval={"model": "bootstrap-only"}))
|
||||
cfg = SimpleNamespace(hub_url="http://127.0.0.1:8000", bao_addr="http://127.0.0.1:8200")
|
||||
monkeypatch.delenv("SECRETS_ENGINE_UNSAFE_DEMO", raising=False)
|
||||
assert _require_lane_approval(cfg, entry, "apply") is None
|
||||
|
||||
|
||||
def test_build_stage_is_not_the_production_fail_closed_gate(tmp_path, monkeypatch):
|
||||
"""build is fail_open relative to access-engine: lane approval still applies."""
|
||||
assert _stance()["stance"]["build"] == "fail_open"
|
||||
assert load_pep_stance().stance["build"] == "fail_open"
|
||||
(tmp_path / ".decisions").mkdir()
|
||||
(tmp_path / ".decisions" / "x.yaml").write_text(
|
||||
"id: x\ntitle: approved\nstatus: resolved\nsuperseded_by: null\n"
|
||||
|
|
@ -114,4 +152,38 @@ def test_build_stage_is_not_the_production_fail_closed_gate(tmp_path, monkeypatc
|
|||
)
|
||||
)
|
||||
cfg = SimpleNamespace(hub_url="", bao_addr="http://127.0.0.1:8200")
|
||||
assert _require_lane_approval(cfg, entry, "apply").id == "x"
|
||||
decision = _require_lane_approval(cfg, entry, "apply")
|
||||
assert decision.id == "x"
|
||||
|
||||
|
||||
def test_classification_yaml_equals_shipped_rules():
|
||||
loaded = load_classification_rules()
|
||||
assert tuple(rule["id"] for rule in loaded) == tuple(rule["id"] for rule in SHIPPED_RULES)
|
||||
assert tuple(rule["kind"] for rule in loaded) == tuple(rule["kind"] for rule in SHIPPED_RULES)
|
||||
|
||||
|
||||
def test_classify_does_not_grant_permission():
|
||||
prod_provision = classify("provision", "prod")
|
||||
test_provision = classify("provision", "test")
|
||||
destroy = classify("lifecycle-destroy", "build")
|
||||
apply_prod = classify("apply", "prod")
|
||||
heartbeat = classify("evidence-heartbeat", "prod")
|
||||
assert prod_provision.kind == "load-bearing"
|
||||
assert test_provision.kind == "attributive"
|
||||
assert destroy.kind == "load-bearing"
|
||||
assert apply_prod.kind == "attributive"
|
||||
assert heartbeat.kind == "heartbeat"
|
||||
assert prod_provision.completeness_claimed is False
|
||||
assert CLASSIFICATION.exists()
|
||||
|
||||
|
||||
def test_proposed_contracts_exist_and_forbid_secret_material():
|
||||
ssh = (ROOT / "docs/ssh-ca-signing-contract.md").read_text(encoding="utf-8")
|
||||
secret_use = (ROOT / "docs/secret-use-evidence-contract.md").read_text(encoding="utf-8")
|
||||
assert "proposed" in ssh.lower()
|
||||
assert "proposed" in secret_use.lower()
|
||||
assert "warden sign" in ssh
|
||||
assert "private key" in ssh.lower() or "private keys" in ssh.lower()
|
||||
assert "secret values" in secret_use.lower() or "secret value" in secret_use.lower()
|
||||
assert "audit-core" in secret_use
|
||||
assert "completeness is not claimed" in secret_use.lower()
|
||||
|
|
|
|||
|
|
@ -121,3 +121,12 @@ def test_production_handler_fails_closed_before_backend(tmp_path, monkeypatch):
|
|||
"attempt",
|
||||
"failed-DecisionError",
|
||||
]
|
||||
terminal = records[-1]
|
||||
assert terminal["detail"]["stance_stage"] == "prod"
|
||||
assert terminal["detail"]["stance_failure_mode"] == "fail_closed"
|
||||
assert terminal["detail"]["approval_status"] == "rejected"
|
||||
assert "stance_decision_id" not in terminal["detail"]
|
||||
assert terminal["completeness_claimed"] is False
|
||||
assert "SUPER-SECRET" not in json.dumps(records)
|
||||
outbox = list((tmp_path / "evidence" / "outbox").glob("*.json"))
|
||||
assert outbox, "production provision refusal is load-bearing and must be queued"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue