feat(posture): add deterministic feedback proposals
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
parent
dc8da422f8
commit
cfc9e7d0cb
19 changed files with 1428 additions and 16 deletions
261
tools/posture-feedback/tests/test_posture_feedback.py
Normal file
261
tools/posture-feedback/tests/test_posture_feedback.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
TOOL_PATH = pathlib.Path(__file__).resolve().parents[1] / "posture_feedback.py"
|
||||
SPEC = importlib.util.spec_from_file_location("posture_feedback", TOOL_PATH)
|
||||
feedback = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC and SPEC.loader
|
||||
sys.modules[SPEC.name] = feedback
|
||||
SPEC.loader.exec_module(feedback)
|
||||
|
||||
|
||||
def declaration(service="example") -> dict:
|
||||
return {
|
||||
"schema_version": "0.1",
|
||||
"framework": "netkingdom-tenancy-posture",
|
||||
"service": service,
|
||||
"role": "test-service",
|
||||
"responsible_repo": "example-owner",
|
||||
"tenancy": {
|
||||
"current": {"I": 1, "A": 1, "E": 0, "P": 0, "R": 1, "V": 0},
|
||||
"target": {"I": 1, "A": 1, "E": 0, "P": 0, "R": 1, "V": 0},
|
||||
"reviewed": "2026-08-01",
|
||||
"review_due": "2026-12-31",
|
||||
"service_class": "interactive",
|
||||
"reason": {
|
||||
"I": "floor explained",
|
||||
"A": "floor explained",
|
||||
"E": "floor explained",
|
||||
"P": "floor explained",
|
||||
"R": "floor explained",
|
||||
"V": "floor explained",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write_declaration(tmp_path: pathlib.Path, document: dict, name="tenancy.yaml") -> pathlib.Path:
|
||||
path = tmp_path / name
|
||||
path.write_text(yaml.safe_dump(document, sort_keys=False), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def build(path: pathlib.Path, *, as_of="2026-08-23T12:00:00Z", horizon=14, fail_on="none"):
|
||||
return feedback.build_report(
|
||||
[path],
|
||||
as_of=feedback.parse_timestamp(as_of),
|
||||
horizon_days=horizon,
|
||||
fail_on=fail_on,
|
||||
)
|
||||
|
||||
|
||||
def classes(report: dict) -> list[str]:
|
||||
return [item["class"] for item in report["findings"]]
|
||||
|
||||
|
||||
def test_checked_in_reference_emits_five_proposals_and_no_authority():
|
||||
repo_root = pathlib.Path(__file__).resolve().parents[3]
|
||||
path = repo_root / "examples/posture-feedback/expired-e2.yaml"
|
||||
|
||||
report = build(path)
|
||||
|
||||
assert report["summary"] == {
|
||||
"total": 5,
|
||||
"high": 1,
|
||||
"medium": 3,
|
||||
"low": 1,
|
||||
"unknown_owner": 0,
|
||||
}
|
||||
assert report["automation"] == {
|
||||
"mode": "proposal-only",
|
||||
"external_write_permitted": False,
|
||||
"policy_mutation_permitted": False,
|
||||
"declaration_mutation_permitted": False,
|
||||
}
|
||||
|
||||
|
||||
def test_missing_adversarial_freshness_is_unknown_without_owner_inference(tmp_path):
|
||||
document = declaration()
|
||||
document["tenancy"]["current"]["E"] = 2
|
||||
document["tenancy"]["target"]["E"] = 2
|
||||
document["tenancy"]["reason"].pop("E")
|
||||
document["evidence"] = {"E2": "docs/evidence/e2.md"}
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report = build(path)
|
||||
|
||||
finding = next(item for item in report["findings"] if item["class"] == "evidence-freshness-unknown")
|
||||
assert finding["owner"] == "unknown"
|
||||
assert finding["evidence_state"] == "unknown"
|
||||
|
||||
|
||||
def test_exact_evidence_expiry_is_still_valid_but_due(tmp_path):
|
||||
document = declaration()
|
||||
document["tenancy"]["current"]["E"] = 2
|
||||
document["tenancy"]["target"]["E"] = 2
|
||||
document["tenancy"]["reason"].pop("E")
|
||||
document["evidence"] = {"E2": "docs/evidence/e2.md"}
|
||||
document["evidence_freshness"] = {
|
||||
"E2": {
|
||||
"kind": "adversarial",
|
||||
"observed_at": "2026-08-22T12:00:00Z",
|
||||
"valid_until": "2026-08-23T12:00:00Z",
|
||||
"responsible_repo": "evidence-owner",
|
||||
"scope": "bounded",
|
||||
"remediation": "repeat",
|
||||
}
|
||||
}
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report = build(path, horizon=0)
|
||||
|
||||
assert "evidence-expired" not in classes(report)
|
||||
assert "evidence-due-soon" in classes(report)
|
||||
|
||||
|
||||
def test_review_due_date_expires_after_utc_calendar_day(tmp_path):
|
||||
document = declaration()
|
||||
document["tenancy"]["review_due"] = "2026-08-23"
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
due_today = build(path, as_of="2026-08-23T23:59:59Z", horizon=0)
|
||||
overdue = build(path, as_of="2026-08-24T00:00:00Z", horizon=0)
|
||||
|
||||
assert "posture-review-due-soon" in classes(due_today)
|
||||
assert "posture-review-overdue" not in classes(due_today)
|
||||
assert "posture-review-overdue" in classes(overdue)
|
||||
|
||||
|
||||
def test_posture_owner_is_unknown_when_not_declared(tmp_path):
|
||||
document = declaration()
|
||||
del document["responsible_repo"]
|
||||
document["tenancy"]["review_due"] = "2026-08-01"
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report = build(path)
|
||||
|
||||
finding = next(item for item in report["findings"] if item["class"] == "posture-review-overdue")
|
||||
assert finding["owner"] == "unknown"
|
||||
assert report["summary"]["unknown_owner"] == 1
|
||||
|
||||
|
||||
def test_multi_service_declaration_is_evaluated_per_service(tmp_path):
|
||||
first = declaration("first")
|
||||
second = declaration("second")
|
||||
for entry in (first, second):
|
||||
entry.pop("schema_version")
|
||||
entry.pop("framework")
|
||||
first["tenancy"]["gap"] = {"A": "first gap"}
|
||||
second["tenancy"]["gap"] = {"V": "second gap"}
|
||||
document = {
|
||||
"schema_version": "0.1",
|
||||
"framework": "netkingdom-tenancy-posture",
|
||||
"services": [first, second],
|
||||
}
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report = build(path)
|
||||
|
||||
assert {(item["service"], item["control"]) for item in report["findings"]} == {
|
||||
("first", "A"),
|
||||
("second", "V"),
|
||||
}
|
||||
|
||||
|
||||
def test_mechanical_evidence_without_expiry_creates_no_freshness_finding(tmp_path):
|
||||
document = declaration()
|
||||
document["tenancy"]["current"]["A"] = 2
|
||||
document["tenancy"]["target"]["A"] = 2
|
||||
document["tenancy"]["reason"].pop("A")
|
||||
document["evidence"] = {"A2": "tests/authorization.py"}
|
||||
document["evidence_freshness"] = {
|
||||
"A2": {
|
||||
"kind": "mechanical",
|
||||
"observed_at": "2026-08-22T12:00:00Z",
|
||||
"responsible_repo": "example-owner",
|
||||
"scope": "continuous test",
|
||||
"remediation": "repair the test",
|
||||
}
|
||||
}
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report = build(path)
|
||||
|
||||
assert not any(item["class"].startswith("evidence-") for item in report["findings"])
|
||||
|
||||
|
||||
def test_report_digest_is_stable_for_identical_inputs(tmp_path):
|
||||
path = write_declaration(tmp_path, declaration())
|
||||
|
||||
first = build(path)
|
||||
second = build(path)
|
||||
|
||||
assert first == second
|
||||
assert first["report_digest"].startswith("sha256:")
|
||||
|
||||
|
||||
def test_workspace_source_identifier_is_portable_and_not_an_owner_inference():
|
||||
repo_root = pathlib.Path(__file__).resolve().parents[3]
|
||||
path = repo_root / "examples/posture-feedback/expired-e2.yaml"
|
||||
|
||||
report = build(path)
|
||||
|
||||
assert {item["source"] for item in report["findings"]} == {
|
||||
"net-kingdom/examples/posture-feedback/expired-e2.yaml"
|
||||
}
|
||||
gap = next(item for item in report["findings"] if item["class"] == "declared-gap")
|
||||
zone = next(item for item in report["findings"] if item["class"].startswith("zone-"))
|
||||
assert gap["owner"] == "net-kingdom"
|
||||
assert zone["owner"] == "team:platform-security"
|
||||
assert all(item["owner"] != item["source"] for item in report["findings"])
|
||||
|
||||
|
||||
def test_fail_on_threshold_changes_ok_not_findings(tmp_path):
|
||||
document = declaration()
|
||||
document["tenancy"]["gap"] = {"R": "declared gap"}
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
report_only = build(path, fail_on="none")
|
||||
failing = build(path, fail_on="low")
|
||||
|
||||
assert report_only["ok"] is True
|
||||
assert failing["ok"] is False
|
||||
assert report_only["findings"] == failing["findings"]
|
||||
|
||||
|
||||
def test_invalid_declaration_is_reported_and_cli_exits_two(tmp_path, capsys):
|
||||
document = declaration()
|
||||
del document["role"]
|
||||
path = write_declaration(tmp_path, document)
|
||||
|
||||
exit_code = feedback.main(
|
||||
[
|
||||
"--as-of",
|
||||
"2026-08-23T12:00:00Z",
|
||||
"--fail-on",
|
||||
"none",
|
||||
str(path),
|
||||
]
|
||||
)
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
|
||||
assert exit_code == 2
|
||||
assert payload["ok"] is False
|
||||
assert payload["validation_errors"]
|
||||
|
||||
|
||||
def test_timestamp_without_offset_is_rejected():
|
||||
try:
|
||||
feedback.parse_timestamp("2026-08-23T12:00:00")
|
||||
except ValueError as exc:
|
||||
assert "explicit UTC offset" in str(exc)
|
||||
else:
|
||||
raise AssertionError("timezone-naive timestamp was accepted")
|
||||
Loading…
Add table
Add a link
Reference in a new issue