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
|
|
@ -89,6 +89,41 @@ class SemanticValidationTests(unittest.TestCase):
|
|||
document["tenancy"]["review_due"] = "2026-08-16"
|
||||
self.assertIn("review_due precedes reviewed", self.validate(document)[0])
|
||||
|
||||
def test_evidence_freshness_must_reference_evidence_key(self) -> None:
|
||||
document = declaration()
|
||||
document["evidence_freshness"] = {
|
||||
"E2": {
|
||||
"kind": "adversarial",
|
||||
"observed_at": "2026-08-22T22:10:25Z",
|
||||
"valid_until": "2026-08-23T22:10:25Z",
|
||||
"responsible_repo": "example",
|
||||
"scope": "bounded tenant probes",
|
||||
"remediation": "repeat the bounded run",
|
||||
}
|
||||
}
|
||||
self.assertIn(
|
||||
"evidence_freshness E2 has no evidence entry",
|
||||
self.validate(document)[0],
|
||||
)
|
||||
|
||||
def test_evidence_freshness_expiry_must_follow_observation(self) -> None:
|
||||
document = declaration()
|
||||
document["evidence"] = {"E2": "docs/evidence/e2.md"}
|
||||
document["evidence_freshness"] = {
|
||||
"E2": {
|
||||
"kind": "adversarial",
|
||||
"observed_at": "2026-08-22T22:10:25Z",
|
||||
"valid_until": "2026-08-22T22:10:25Z",
|
||||
"responsible_repo": "example",
|
||||
"scope": "bounded tenant probes",
|
||||
"remediation": "repeat the bounded run",
|
||||
}
|
||||
}
|
||||
self.assertIn(
|
||||
"valid_until must be after observed_at",
|
||||
self.validate(document)[0],
|
||||
)
|
||||
|
||||
def test_service_names_are_unique(self) -> None:
|
||||
entry = declaration()
|
||||
document = {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ def validate_semantics(document: dict[str, Any], path: pathlib.Path) -> list[str
|
|||
reason = posture.get("reason", {})
|
||||
gap = posture.get("gap", {})
|
||||
evidence = entry.get("evidence", {})
|
||||
evidence_freshness = entry.get("evidence_freshness", {})
|
||||
|
||||
reviewed = dt.date.fromisoformat(posture["reviewed"])
|
||||
review_due = dt.date.fromisoformat(posture["review_due"])
|
||||
|
|
@ -92,6 +93,22 @@ def validate_semantics(document: dict[str, Any], path: pathlib.Path) -> list[str
|
|||
f"{name}: implemented {axis}{level} must be above current {axis}{current_level}"
|
||||
)
|
||||
|
||||
for key, freshness in evidence_freshness.items():
|
||||
if key not in evidence:
|
||||
errors.append(f"{name}: evidence_freshness {key} has no evidence entry")
|
||||
observed_at = dt.datetime.fromisoformat(
|
||||
freshness["observed_at"].replace("Z", "+00:00")
|
||||
)
|
||||
valid_until_raw = freshness.get("valid_until")
|
||||
if valid_until_raw:
|
||||
valid_until = dt.datetime.fromisoformat(
|
||||
valid_until_raw.replace("Z", "+00:00")
|
||||
)
|
||||
if valid_until <= observed_at:
|
||||
errors.append(
|
||||
f"{name}: evidence_freshness {key} valid_until must be after observed_at"
|
||||
)
|
||||
|
||||
provider = entry.get("provider", {})
|
||||
for axis, reach in provider.get("axes", {}).items():
|
||||
available = reach["available"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue