Validate cadence contract and require functional MFA verification
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ea3-7939-7b63-8125-699f8b50bedd
This commit is contained in:
parent
d4d61b722e
commit
4e07d60ff1
34 changed files with 1640 additions and 364 deletions
27
tools/emission-cadence-profile/README.md
Normal file
27
tools/emission-cadence-profile/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Emission Cadence Security Profile Checker
|
||||
|
||||
This checker applies the NetKingdom security overlay in
|
||||
`canon/standards/emission-cadence-security-profile_v0.1.md` only after the
|
||||
declaration passes an explicitly supplied InfoTechCanon contract schema.
|
||||
|
||||
It deliberately contains no fallback copy of the generic schema. The event
|
||||
classes passed with `--load-bearing`, `--rare-load-bearing`, and
|
||||
`--attributive` come from the source's authoritative inventory; the checker
|
||||
does not infer them from names, payloads, or observed traffic.
|
||||
|
||||
Run its tests with:
|
||||
|
||||
```bash
|
||||
make emission-cadence-profile-test
|
||||
```
|
||||
|
||||
Supply `../info-tech-canon/infospace/schemas/emission-cadence.schema.yaml`
|
||||
with `--contract-schema`. The canon profile records its version and SHA-256.
|
||||
Security fields are under each entry's `extensions.netkingdom` namespace.
|
||||
|
||||
The test suite runs contract integration tests directly against a sibling
|
||||
InfoTechCanon checkout; these tests explicitly skip when it is unavailable.
|
||||
The small unit-test schema is a test double, not a fallback contract.
|
||||
|
||||
The profile is proposed: current approval-engine and qonto-assistant owner
|
||||
instances still require migration. Passing a worked example is not adoption.
|
||||
418
tools/emission-cadence-profile/emission_cadence_profile.py
Normal file
418
tools/emission-cadence-profile/emission_cadence_profile.py
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = ["jsonschema>=4.23,<5", "PyYAML>=6,<7"]
|
||||
# ///
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import jsonschema
|
||||
import yaml
|
||||
|
||||
PROFILE_ID = "netkingdom-emission-cadence-security-profile-v0.1"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Finding:
|
||||
level: str
|
||||
code: str
|
||||
event_class: str | None
|
||||
message: str
|
||||
|
||||
|
||||
def load_document(path: Path) -> Any:
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
return yaml.safe_load(handle)
|
||||
|
||||
|
||||
def _finding(level: str, code: str, event_class: str | None, message: str) -> Finding:
|
||||
return Finding(level=level, code=code, event_class=event_class, message=message)
|
||||
|
||||
|
||||
def _schema_findings(schema: Any, declaration: Any) -> list[Finding]:
|
||||
try:
|
||||
jsonschema.Draft202012Validator.check_schema(schema)
|
||||
except jsonschema.SchemaError as exc:
|
||||
return [_finding("MUST", "contract-schema-invalid", None, exc.message)]
|
||||
|
||||
validator = jsonschema.Draft202012Validator(
|
||||
schema, format_checker=jsonschema.FormatChecker()
|
||||
)
|
||||
findings: list[Finding] = []
|
||||
for error in sorted(
|
||||
validator.iter_errors(declaration),
|
||||
key=lambda item: tuple(str(part) for part in item.path),
|
||||
):
|
||||
location = "/" + "/".join(str(part) for part in error.path)
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"contract-validation-failed",
|
||||
None,
|
||||
f"{location}: {error.message}",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def _entries(declaration: Any) -> tuple[list[dict[str, Any]], list[Finding]]:
|
||||
if not isinstance(declaration, dict):
|
||||
return [], [
|
||||
_finding(
|
||||
"MUST", "declaration-not-object", None, "declaration must be an object"
|
||||
)
|
||||
]
|
||||
raw_entries = declaration.get("sources")
|
||||
if not isinstance(raw_entries, list):
|
||||
return [], [
|
||||
_finding(
|
||||
"MUST",
|
||||
"sources-unavailable",
|
||||
None,
|
||||
"the imported contract must expose cadence entries at /sources",
|
||||
)
|
||||
]
|
||||
entries: list[dict[str, Any]] = []
|
||||
for index, item in enumerate(raw_entries):
|
||||
if not isinstance(item, dict):
|
||||
return [], [
|
||||
_finding(
|
||||
"MUST",
|
||||
"source-entry-not-object",
|
||||
None,
|
||||
f"/sources/{index} must be an object",
|
||||
)
|
||||
]
|
||||
entries.append(item)
|
||||
return entries, []
|
||||
|
||||
|
||||
def evaluate_profile(
|
||||
declaration: Any,
|
||||
*,
|
||||
load_bearing: set[str],
|
||||
rare_load_bearing: set[str],
|
||||
attributive: set[str],
|
||||
) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
overlap = (load_bearing | rare_load_bearing) & attributive
|
||||
for event_class in sorted(overlap):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"inventory-class-conflict",
|
||||
event_class,
|
||||
"the source inventory classifies the event as both load-bearing and attributive",
|
||||
)
|
||||
)
|
||||
|
||||
entries, structural = _entries(declaration)
|
||||
if structural:
|
||||
return findings + structural
|
||||
|
||||
indexed: dict[str, dict[str, Any]] = {}
|
||||
source_ids: set[str] = set()
|
||||
for entry in entries:
|
||||
source_id = entry.get("source_id")
|
||||
if isinstance(source_id, str):
|
||||
if source_id in source_ids:
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"duplicate-source-id",
|
||||
None,
|
||||
"source_id values must be unique",
|
||||
)
|
||||
)
|
||||
source_ids.add(source_id)
|
||||
event_class = entry.get("event_class")
|
||||
if not isinstance(event_class, str) or not event_class:
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"event-class-unavailable",
|
||||
None,
|
||||
"each cadence entry needs event_class",
|
||||
)
|
||||
)
|
||||
continue
|
||||
if event_class in indexed:
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"duplicate-event-class",
|
||||
event_class,
|
||||
"only one cadence entry is permitted per event class",
|
||||
)
|
||||
)
|
||||
continue
|
||||
indexed[event_class] = entry
|
||||
|
||||
required_load_bearing = load_bearing | rare_load_bearing
|
||||
for event_class in sorted(required_load_bearing):
|
||||
entry = indexed.get(event_class)
|
||||
if entry is None:
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"load-bearing-cadence-missing",
|
||||
event_class,
|
||||
"a source-declared load-bearing class must declare cadence",
|
||||
)
|
||||
)
|
||||
elif (
|
||||
entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class")
|
||||
!= "load-bearing"
|
||||
):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"evidence-class-mismatch",
|
||||
event_class,
|
||||
"cadence evidence_class must match the source inventory: load-bearing",
|
||||
)
|
||||
)
|
||||
|
||||
for event_class in sorted(attributive):
|
||||
entry = indexed.get(event_class)
|
||||
if entry is None:
|
||||
findings.append(
|
||||
_finding(
|
||||
"SHOULD",
|
||||
"attributive-cadence-missing",
|
||||
event_class,
|
||||
"an attributive class should declare cadence; completeness must not be claimed",
|
||||
)
|
||||
)
|
||||
elif (
|
||||
entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class")
|
||||
!= "attributive"
|
||||
):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"evidence-class-mismatch",
|
||||
event_class,
|
||||
"cadence evidence_class must match the source inventory: attributive",
|
||||
)
|
||||
)
|
||||
|
||||
for event_class in sorted(rare_load_bearing):
|
||||
entry = indexed.get(event_class)
|
||||
if entry is None:
|
||||
continue
|
||||
if entry.get("form") != "heartbeat-or-reconciliation":
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rare-form-invalid",
|
||||
event_class,
|
||||
"rare load-bearing evidence requires heartbeat-or-reconciliation",
|
||||
)
|
||||
)
|
||||
if (
|
||||
entry.get("extensions", {}).get("netkingdom", {}).get("rate_monitoring")
|
||||
!= "forbidden"
|
||||
):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rare-rate-monitoring-not-forbidden",
|
||||
event_class,
|
||||
"rate_monitoring must be forbidden for rare load-bearing evidence",
|
||||
)
|
||||
)
|
||||
heartbeat = entry.get("heartbeat")
|
||||
if not isinstance(heartbeat, dict):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rare-heartbeat-missing",
|
||||
event_class,
|
||||
"rare load-bearing evidence requires a positive heartbeat",
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not heartbeat.get("assertion"):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"heartbeat-assertion-missing",
|
||||
event_class,
|
||||
"heartbeat must carry a positive assertion",
|
||||
)
|
||||
)
|
||||
if heartbeat.get("missing") != "finding":
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"heartbeat-missing-not-finding",
|
||||
event_class,
|
||||
"a missing heartbeat must be a finding",
|
||||
)
|
||||
)
|
||||
reconciliation = entry.get("reconciliation", {})
|
||||
local = reconciliation.get("compare_local")
|
||||
observed = reconciliation.get("compare_observed")
|
||||
divergence = reconciliation.get("divergence")
|
||||
if local is None or observed is None:
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rare-reconciliation-missing",
|
||||
event_class,
|
||||
"reconciliation must compare source and evidence-engine counts",
|
||||
)
|
||||
)
|
||||
if divergence != "finding":
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"reconciliation-divergence-not-finding",
|
||||
event_class,
|
||||
"reconciliation divergence must be a finding",
|
||||
)
|
||||
)
|
||||
|
||||
for event_class in sorted(required_load_bearing - rare_load_bearing):
|
||||
entry = indexed.get(event_class)
|
||||
if (
|
||||
entry is None
|
||||
or entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class")
|
||||
!= "load-bearing"
|
||||
):
|
||||
continue
|
||||
if entry.get("form") == "expected-rate":
|
||||
window = entry.get("window_seconds", entry.get("window"))
|
||||
if (
|
||||
not window
|
||||
or (
|
||||
isinstance(window, str)
|
||||
and not any(int(part) > 0 for part in re.findall(r"\d+", window))
|
||||
)
|
||||
or isinstance(window, bool)
|
||||
or (isinstance(window, (int, float)) and window <= 0)
|
||||
):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rate-window-invalid",
|
||||
event_class,
|
||||
"expected-rate load-bearing evidence needs a positive window",
|
||||
)
|
||||
)
|
||||
if (
|
||||
isinstance(entry.get("expected_min"), bool)
|
||||
or not isinstance(entry.get("expected_min"), int)
|
||||
or entry["expected_min"] <= 0
|
||||
):
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"expected-min-invalid",
|
||||
event_class,
|
||||
"expected-rate load-bearing evidence needs expected_min > 0",
|
||||
)
|
||||
)
|
||||
if entry.get("drop_below") != "finding":
|
||||
findings.append(
|
||||
_finding(
|
||||
"MUST",
|
||||
"rate-drop-not-finding",
|
||||
event_class,
|
||||
"a drop below the declared load-bearing rate must be a finding",
|
||||
)
|
||||
)
|
||||
|
||||
return sorted(
|
||||
findings,
|
||||
key=lambda item: (item.level, item.code, item.event_class or "", item.message),
|
||||
)
|
||||
|
||||
|
||||
def build_report(
|
||||
contract_schema: Any,
|
||||
declaration: Any,
|
||||
*,
|
||||
contract_schema_path: str,
|
||||
declaration_path: str,
|
||||
load_bearing: set[str],
|
||||
rare_load_bearing: set[str],
|
||||
attributive: set[str],
|
||||
fail_on_should: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
findings = _schema_findings(contract_schema, declaration)
|
||||
contract_valid = not findings
|
||||
if contract_valid:
|
||||
findings.extend(
|
||||
evaluate_profile(
|
||||
declaration,
|
||||
load_bearing=load_bearing,
|
||||
rare_load_bearing=rare_load_bearing,
|
||||
attributive=attributive,
|
||||
)
|
||||
)
|
||||
must_count = sum(item.level == "MUST" for item in findings)
|
||||
should_count = sum(item.level == "SHOULD" for item in findings)
|
||||
return {
|
||||
"profile": PROFILE_ID,
|
||||
"contract_schema": contract_schema_path,
|
||||
"declaration": declaration_path,
|
||||
"contract_valid": contract_valid,
|
||||
"conformant": must_count == 0 and (not fail_on_should or should_count == 0),
|
||||
"summary": {"must": must_count, "should": should_count},
|
||||
"findings": [asdict(item) for item in findings],
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate an imported emission-cadence declaration against the NetKingdom profile."
|
||||
)
|
||||
parser.add_argument("declaration", type=Path)
|
||||
parser.add_argument("--contract-schema", required=True, type=Path)
|
||||
parser.add_argument(
|
||||
"--load-bearing", action="append", default=[], metavar="EVENT_CLASS"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rare-load-bearing", action="append", default=[], metavar="EVENT_CLASS"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--attributive", action="append", default=[], metavar="EVENT_CLASS"
|
||||
)
|
||||
parser.add_argument("--fail-on-should", action="store_true")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
try:
|
||||
schema = load_document(args.contract_schema)
|
||||
declaration = load_document(args.declaration)
|
||||
except (OSError, yaml.YAMLError) as exc:
|
||||
print(json.dumps({"error": str(exc)}, sort_keys=True), file=sys.stderr)
|
||||
return 2
|
||||
report = build_report(
|
||||
schema,
|
||||
declaration,
|
||||
contract_schema_path=str(args.contract_schema),
|
||||
declaration_path=str(args.declaration),
|
||||
load_bearing=set(args.load_bearing),
|
||||
rare_load_bearing=set(args.rare_load_bearing),
|
||||
attributive=set(args.attributive),
|
||||
fail_on_should=args.fail_on_should,
|
||||
)
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
return 0 if report["conformant"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
TOOL_PATH = pathlib.Path(__file__).resolve().parents[1] / "emission_cadence_profile.py"
|
||||
SPEC = importlib.util.spec_from_file_location("emission_cadence_profile", TOOL_PATH)
|
||||
profile = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC and SPEC.loader
|
||||
sys.modules[SPEC.name] = profile
|
||||
SPEC.loader.exec_module(profile)
|
||||
|
||||
|
||||
CONTRACT_SCHEMA = {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "source", "sources"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "0.1"},
|
||||
"source": {"type": "string"},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["source_id", "event_class", "form"],
|
||||
"properties": {
|
||||
"event_class": {"type": "string", "minLength": 1},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "object"},
|
||||
},
|
||||
"form": {"enum": ["expected-rate", "heartbeat-or-reconciliation"]},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def rare_entry() -> dict:
|
||||
return {
|
||||
"source_id": "example.audit.deny",
|
||||
"event_class": "audit.deny",
|
||||
"extensions": {
|
||||
"netkingdom": {
|
||||
"evidence_class": "load-bearing",
|
||||
"rate_monitoring": "forbidden",
|
||||
}
|
||||
},
|
||||
"form": "heartbeat-or-reconciliation",
|
||||
"heartbeat": {
|
||||
"event_class": "audit.heartbeat",
|
||||
"interval": "24h",
|
||||
"assertion": "nothing-to-report",
|
||||
"missing": "finding",
|
||||
},
|
||||
"reconciliation": {
|
||||
"compare_local": "source_transition_counts.audit.deny",
|
||||
"compare_observed": "evidence_counts.audit.deny",
|
||||
"divergence": "finding",
|
||||
"undrained_local": "lag-not-divergence",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def declaration(*entries: dict) -> dict:
|
||||
return {"schema_version": "0.1", "source": "example", "sources": list(entries)}
|
||||
|
||||
|
||||
def report(document: dict, *, load=(), rare=(), attributive=(), schema=CONTRACT_SCHEMA):
|
||||
return profile.build_report(
|
||||
schema,
|
||||
document,
|
||||
contract_schema_path="info-tech-canon/schema.json",
|
||||
declaration_path="source/cadence.yaml",
|
||||
load_bearing=set(load),
|
||||
rare_load_bearing=set(rare),
|
||||
attributive=set(attributive),
|
||||
)
|
||||
|
||||
|
||||
def codes(result: dict) -> set[str]:
|
||||
return {item["code"] for item in result["findings"]}
|
||||
|
||||
|
||||
def test_valid_rare_load_bearing_requires_both_positive_controls() -> None:
|
||||
result = report(declaration(rare_entry()), rare={"audit.deny"})
|
||||
|
||||
assert result["contract_valid"] is True
|
||||
assert result["conformant"] is True
|
||||
assert result["findings"] == []
|
||||
|
||||
|
||||
def test_contract_validation_runs_before_profile() -> None:
|
||||
result = report({"source": "example"}, rare={"audit.deny"})
|
||||
|
||||
assert result["contract_valid"] is False
|
||||
assert codes(result) == {"contract-validation-failed"}
|
||||
assert "load-bearing-cadence-missing" not in codes(result)
|
||||
|
||||
|
||||
def test_missing_and_mismatched_source_inventory_classes_fail() -> None:
|
||||
wrong = rare_entry()
|
||||
wrong["extensions"]["netkingdom"]["evidence_class"] = "attributive"
|
||||
|
||||
missing = report(declaration(), load={"audit.deny"})
|
||||
mismatched = report(declaration(wrong), rare={"audit.deny"})
|
||||
|
||||
assert "load-bearing-cadence-missing" in codes(missing)
|
||||
assert "evidence-class-mismatch" in codes(mismatched)
|
||||
assert not missing["conformant"]
|
||||
assert not mismatched["conformant"]
|
||||
|
||||
|
||||
def test_rare_rate_form_is_rejected_but_not_inferred_from_event_name() -> None:
|
||||
rate = {
|
||||
"source_id": "example.audit.deny",
|
||||
"event_class": "audit.deny",
|
||||
"extensions": {
|
||||
"netkingdom": {
|
||||
"evidence_class": "load-bearing",
|
||||
"rate_monitoring": "forbidden",
|
||||
}
|
||||
},
|
||||
"form": "expected-rate",
|
||||
"window": "24h",
|
||||
"expected_min": 1,
|
||||
"drop_below": "finding",
|
||||
}
|
||||
|
||||
del rate["extensions"]["netkingdom"]["rate_monitoring"]
|
||||
explicit_rare = report(declaration(rate), rare={"audit.deny"})
|
||||
source_says_volume = report(declaration(rate), load={"audit.deny"})
|
||||
|
||||
assert "rare-form-invalid" in codes(explicit_rare)
|
||||
assert "rare-rate-monitoring-not-forbidden" in codes(explicit_rare)
|
||||
assert explicit_rare["conformant"] is False
|
||||
assert source_says_volume["conformant"] is True
|
||||
|
||||
|
||||
def test_rare_class_requires_heartbeat_and_reconciliation() -> None:
|
||||
item = rare_entry()
|
||||
del item["heartbeat"]
|
||||
item["reconciliation"] = {"divergence": "ignored"}
|
||||
|
||||
result = report(declaration(item), rare={"audit.deny"})
|
||||
|
||||
assert {
|
||||
"rare-heartbeat-missing",
|
||||
"rare-reconciliation-missing",
|
||||
"reconciliation-divergence-not-finding",
|
||||
} <= codes(result)
|
||||
|
||||
|
||||
def test_attributive_coverage_is_advisory_by_default() -> None:
|
||||
result = report(declaration(), attributive={"audit.allow"})
|
||||
|
||||
assert result["conformant"] is True
|
||||
assert result["summary"] == {"must": 0, "should": 1}
|
||||
assert codes(result) == {"attributive-cadence-missing"}
|
||||
|
||||
|
||||
def test_duplicate_event_classes_and_inventory_conflict_fail() -> None:
|
||||
result = report(
|
||||
declaration(rare_entry(), copy.deepcopy(rare_entry())),
|
||||
rare={"audit.deny"},
|
||||
attributive={"audit.deny"},
|
||||
)
|
||||
|
||||
assert "duplicate-event-class" in codes(result)
|
||||
assert "inventory-class-conflict" in codes(result)
|
||||
assert result["conformant"] is False
|
||||
|
||||
|
||||
def test_expected_rate_load_bearing_has_positive_threshold_and_finding() -> None:
|
||||
rate = {
|
||||
"source_id": "example.audit.decision",
|
||||
"event_class": "audit.decision",
|
||||
"extensions": {
|
||||
"netkingdom": {
|
||||
"evidence_class": "load-bearing",
|
||||
"rate_monitoring": "forbidden",
|
||||
}
|
||||
},
|
||||
"form": "expected-rate",
|
||||
"window_seconds": 0,
|
||||
"expected_min": 0,
|
||||
"drop_below": "log",
|
||||
}
|
||||
|
||||
result = report(declaration(rate), load={"audit.decision"})
|
||||
|
||||
assert {
|
||||
"rate-window-invalid",
|
||||
"expected-min-invalid",
|
||||
"rate-drop-not-finding",
|
||||
} <= codes(result)
|
||||
|
||||
|
||||
# Integration uses the owner artifact directly; never vendor a generic schema.
|
||||
UPSTREAM = pathlib.Path(__file__).resolve().parents[4] / "info-tech-canon"
|
||||
SCHEMA_PATH = UPSTREAM / "infospace/schemas/emission-cadence.schema.yaml"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def upstream_schema():
|
||||
if not SCHEMA_PATH.is_file():
|
||||
pytest.skip("InfoTechCanon checkout required for contract integration")
|
||||
return yaml.safe_load(SCHEMA_PATH.read_text())
|
||||
|
||||
|
||||
def canonical_document():
|
||||
document = declaration(rare_entry())
|
||||
document["declaration_id"] = "example.audit"
|
||||
return document
|
||||
|
||||
|
||||
def test_published_contract_and_namespaced_overlay(upstream_schema):
|
||||
document = canonical_document()
|
||||
assert report(document, rare={"audit.deny"}, schema=upstream_schema)["conformant"]
|
||||
document["sources"][0]["evidence_class"] = "load-bearing"
|
||||
assert not report(document, rare={"audit.deny"}, schema=upstream_schema)[
|
||||
"contract_valid"
|
||||
]
|
||||
|
||||
|
||||
def test_published_contract_does_not_substitute_for_profile(upstream_schema):
|
||||
document = canonical_document()
|
||||
del document["sources"][0]["reconciliation"]
|
||||
result = report(document, rare={"audit.deny"}, schema=upstream_schema)
|
||||
assert result["contract_valid"]
|
||||
assert "rare-reconciliation-missing" in codes(result)
|
||||
assert not result["conformant"]
|
||||
|
||||
|
||||
def test_duplicate_source_ids_fail_even_with_distinct_event_classes(upstream_schema):
|
||||
document = canonical_document()
|
||||
other = copy.deepcopy(document["sources"][0])
|
||||
other["event_class"] = "audit.revocation"
|
||||
document["sources"].append(other)
|
||||
result = report(
|
||||
document, rare={"audit.deny", "audit.revocation"}, schema=upstream_schema
|
||||
)
|
||||
assert result["contract_valid"]
|
||||
assert "duplicate-source-id" in codes(result)
|
||||
assert not result["conformant"]
|
||||
|
||||
|
||||
def test_should_policy_and_cli(tmp_path, capsys):
|
||||
schema = tmp_path / "schema.json"
|
||||
document = tmp_path / "declaration.yaml"
|
||||
import json
|
||||
|
||||
schema.write_text(json.dumps(CONTRACT_SCHEMA))
|
||||
document.write_text(json.dumps(declaration()))
|
||||
args = [
|
||||
str(document),
|
||||
"--contract-schema",
|
||||
str(schema),
|
||||
"--attributive",
|
||||
"audit.allow",
|
||||
]
|
||||
assert profile.main(args) == 0
|
||||
assert profile.main(args + ["--fail-on-should"]) == 1
|
||||
schema.write_text('{"type": "invalid"}')
|
||||
assert profile.main(args) == 1
|
||||
assert "contract-schema-invalid" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("window", ["PT0S", "P0D", "PT00H00M00S"])
|
||||
def test_published_contract_zero_duration_is_not_a_positive_profile_window(
|
||||
upstream_schema, window
|
||||
):
|
||||
document = canonical_document()
|
||||
entry = document["sources"][0]
|
||||
entry.pop("heartbeat")
|
||||
entry.pop("reconciliation")
|
||||
entry.update(
|
||||
form="expected-rate", window=window, expected_min=1, drop_below="finding"
|
||||
)
|
||||
result = report(document, load={"audit.deny"}, schema=upstream_schema)
|
||||
assert result["contract_valid"]
|
||||
assert "rate-window-invalid" in codes(result)
|
||||
assert not result["conformant"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue