docs(canon): reconcile workload and tenant grouping semantics
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
parent
ad2057acff
commit
bee22db620
21 changed files with 1118 additions and 59 deletions
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import pathlib
|
||||
import unittest
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ SPEC = importlib.util.spec_from_file_location("tenancy_posture_validate", MODULE
|
|||
assert SPEC and SPEC.loader
|
||||
VALIDATE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(VALIDATE)
|
||||
SCHEMA = json.loads(VALIDATE.SCHEMA.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def declaration() -> dict:
|
||||
|
|
@ -30,10 +32,38 @@ def declaration() -> dict:
|
|||
}
|
||||
|
||||
|
||||
def zone_declaration() -> dict:
|
||||
return {
|
||||
"standard": "security-zones_v0.1",
|
||||
"membership": "z2-continuity",
|
||||
"responsible_party": "ops-warden",
|
||||
"justification": "foundational access path",
|
||||
"context": {
|
||||
"maturity": "M2",
|
||||
"criticality": "high",
|
||||
"data_classification": "confidential",
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"ref": "docs/evidence/example-zone.md",
|
||||
"supports": ["M2", "continuity-dependency"],
|
||||
}
|
||||
],
|
||||
"reviewed": "2026-08-22",
|
||||
"review_due": "2026-11-22",
|
||||
}
|
||||
|
||||
|
||||
class SemanticValidationTests(unittest.TestCase):
|
||||
def validate(self, document: dict) -> list[str]:
|
||||
return VALIDATE.validate_semantics(document, pathlib.Path("tenancy.yaml"))
|
||||
|
||||
def validate_full(self, document: dict, tmp_path: pathlib.Path) -> list[str]:
|
||||
import yaml
|
||||
|
||||
tmp_path.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
return VALIDATE.validate(tmp_path, SCHEMA)
|
||||
|
||||
def test_floor_vector_with_reasons_is_valid(self) -> None:
|
||||
self.assertEqual([], self.validate(declaration()))
|
||||
|
||||
|
|
@ -68,6 +98,140 @@ class SemanticValidationTests(unittest.TestCase):
|
|||
}
|
||||
self.assertIn("service names must be unique", self.validate(document)[0])
|
||||
|
||||
def test_workload_identity_name_must_match_service(self) -> None:
|
||||
document = declaration()
|
||||
document["workload_identity"] = {
|
||||
"name": "different",
|
||||
"kind": "operational-control-plane",
|
||||
"responsible_repo": "example",
|
||||
"identity_bindings": [
|
||||
{
|
||||
"scheme": "iam-profile",
|
||||
"authority": "key-cape",
|
||||
"subject": "example-prod",
|
||||
"principal_type": "service",
|
||||
}
|
||||
],
|
||||
}
|
||||
self.assertIn(
|
||||
"workload_identity.name must equal service", self.validate(document)[0]
|
||||
)
|
||||
|
||||
def test_workload_identity_bindings_are_unique(self) -> None:
|
||||
document = declaration()
|
||||
binding = {
|
||||
"scheme": "iam-profile",
|
||||
"authority": "key-cape",
|
||||
"subject": "example-prod",
|
||||
"principal_type": "service",
|
||||
}
|
||||
document["workload_identity"] = {
|
||||
"name": "example",
|
||||
"kind": "platform-service",
|
||||
"responsible_repo": "example",
|
||||
"identity_bindings": [binding, binding],
|
||||
}
|
||||
self.assertIn(
|
||||
"workload identity bindings must be unique", self.validate(document)[0]
|
||||
)
|
||||
|
||||
def test_zones_require_authoritative_workload_identity(self) -> None:
|
||||
import tempfile
|
||||
|
||||
document = declaration()
|
||||
document["zones"] = zone_declaration()
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "tenancy.yaml"
|
||||
errors = self.validate_full(document, path)
|
||||
self.assertTrue(any("workload_identity" in error for error in errors))
|
||||
|
||||
def test_operational_workload_may_declare_zones(self) -> None:
|
||||
import tempfile
|
||||
|
||||
document = declaration()
|
||||
document["workload_identity"] = {
|
||||
"name": "example",
|
||||
"kind": "operational-control-plane",
|
||||
"responsible_repo": "ops-warden",
|
||||
"identity_bindings": [
|
||||
{
|
||||
"scheme": "ssh-certificate",
|
||||
"authority": "ops-warden",
|
||||
"subject": "agt-example",
|
||||
"principal_type": "agent",
|
||||
"environment": "prod",
|
||||
}
|
||||
],
|
||||
}
|
||||
document["zones"] = zone_declaration()
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "tenancy.yaml"
|
||||
self.assertEqual([], self.validate_full(document, path))
|
||||
|
||||
def test_zone_membership_uses_canonical_catalog(self) -> None:
|
||||
import tempfile
|
||||
|
||||
document = declaration()
|
||||
document["workload_identity"] = {
|
||||
"name": "example",
|
||||
"kind": "application",
|
||||
"responsible_repo": "example",
|
||||
"declaration_ref": "rapp-example/declarations/rapp.yaml",
|
||||
"identity_bindings": [
|
||||
{
|
||||
"scheme": "iam-profile",
|
||||
"authority": "key-cape",
|
||||
"subject": "example-prod",
|
||||
"principal_type": "service",
|
||||
}
|
||||
],
|
||||
}
|
||||
document["zones"] = zone_declaration()
|
||||
document["zones"]["membership"] = "permissive-default"
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "tenancy.yaml"
|
||||
errors = self.validate_full(document, path)
|
||||
self.assertTrue(any("membership" in error for error in errors))
|
||||
|
||||
def test_zone_review_due_must_be_after_reviewed(self) -> None:
|
||||
document = declaration()
|
||||
document["zones"] = zone_declaration()
|
||||
document["zones"]["review_due"] = document["zones"]["reviewed"]
|
||||
self.assertIn(
|
||||
"zones.review_due must be after zones.reviewed",
|
||||
self.validate(document)[0],
|
||||
)
|
||||
|
||||
def test_multi_service_zones_must_not_be_top_level(self) -> None:
|
||||
import tempfile
|
||||
|
||||
entry = declaration()
|
||||
document = {
|
||||
"schema_version": "0.1",
|
||||
"framework": "netkingdom-tenancy-posture",
|
||||
"services": [entry],
|
||||
"zones": zone_declaration(),
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "tenancy.yaml"
|
||||
errors = self.validate_full(document, path)
|
||||
self.assertTrue(errors)
|
||||
|
||||
def test_multi_service_zone_requires_identity_on_same_entry(self) -> None:
|
||||
import tempfile
|
||||
|
||||
entry = declaration()
|
||||
entry["zones"] = zone_declaration()
|
||||
document = {
|
||||
"schema_version": "0.1",
|
||||
"framework": "netkingdom-tenancy-posture",
|
||||
"services": [entry],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "tenancy.yaml"
|
||||
errors = self.validate_full(document, path)
|
||||
self.assertTrue(any("workload_identity" in error for error in errors))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -38,6 +38,30 @@ def validate_semantics(document: dict[str, Any], path: pathlib.Path) -> list[str
|
|||
|
||||
for entry in entries:
|
||||
name = entry["service"]
|
||||
workload_identity = entry.get("workload_identity")
|
||||
if workload_identity:
|
||||
if workload_identity["name"] != name:
|
||||
errors.append(
|
||||
f"{name}: workload_identity.name must equal service"
|
||||
)
|
||||
bindings = workload_identity["identity_bindings"]
|
||||
binding_keys = [
|
||||
(
|
||||
binding["scheme"],
|
||||
binding["authority"],
|
||||
binding["subject"],
|
||||
binding.get("environment"),
|
||||
)
|
||||
for binding in bindings
|
||||
]
|
||||
if len(binding_keys) != len(set(binding_keys)):
|
||||
errors.append(f"{name}: workload identity bindings must be unique")
|
||||
zones = entry.get("zones")
|
||||
if zones:
|
||||
zone_reviewed = dt.date.fromisoformat(zones["reviewed"])
|
||||
zone_review_due = dt.date.fromisoformat(zones["review_due"])
|
||||
if zone_review_due <= zone_reviewed:
|
||||
errors.append(f"{name}: zones.review_due must be after zones.reviewed")
|
||||
posture = entry["tenancy"]
|
||||
current = posture["current"]
|
||||
reason = posture.get("reason", {})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue