feat: adopt security zones and explicit workload refs
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0291a-1e87-7151-9934-fcbfe3f65eb1
This commit is contained in:
parent
12c637cbf2
commit
7ce58ae638
52 changed files with 1547 additions and 658 deletions
|
|
@ -55,12 +55,79 @@ def _caring_descriptor(actor_type: str, resource_id: str) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
def build_registry(inventory: dict[str, Any]) -> dict[str, Any]:
|
||||
def _resolved_by_workload(zone_resolutions: dict[str, Any] | None) -> dict[str, Any]:
|
||||
records = (zone_resolutions or {}).get("records") or []
|
||||
resolved: dict[str, Any] = {}
|
||||
for record in records:
|
||||
workload_id = str(record.get("workload_id") or "")
|
||||
if not workload_id:
|
||||
continue
|
||||
if workload_id in resolved:
|
||||
raise ValueError(f"duplicate security-zone resolution for {workload_id!r}")
|
||||
resolved[workload_id] = record
|
||||
return resolved
|
||||
|
||||
|
||||
def _zone_attributes(
|
||||
actor: str,
|
||||
entry: dict[str, Any],
|
||||
resolutions: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
subject = entry.get("zone_subject")
|
||||
if not isinstance(subject, dict):
|
||||
return {
|
||||
"security_zone": "unknown",
|
||||
"security_zone_admission": "unknown",
|
||||
"security_zone_reason": "catalog_applicability_absent",
|
||||
}
|
||||
applicability = subject.get("applicability")
|
||||
if applicability == "not-applicable":
|
||||
reason = str(subject.get("reason") or "").strip()
|
||||
if not reason:
|
||||
raise ValueError(f"{actor}.zone_subject.reason is required")
|
||||
return {
|
||||
"security_zone": "unknown",
|
||||
"security_zone_admission": "not-applicable",
|
||||
"security_zone_reason": reason,
|
||||
}
|
||||
if applicability != "applicable":
|
||||
raise ValueError(
|
||||
f"{actor}.zone_subject.applicability must be applicable or not-applicable"
|
||||
)
|
||||
workload_id = str(subject.get("workload_id") or "").strip()
|
||||
if not workload_id:
|
||||
return {
|
||||
"security_zone": "unknown",
|
||||
"security_zone_admission": "unknown",
|
||||
"security_zone_reason": "workload_reference_absent",
|
||||
}
|
||||
record = resolutions.get(workload_id)
|
||||
if record is None:
|
||||
return {
|
||||
"workload_id": workload_id,
|
||||
"security_zone": "unknown",
|
||||
"security_zone_admission": "unknown",
|
||||
"security_zone_reason": "workload_resolution_absent",
|
||||
}
|
||||
return {
|
||||
"workload_id": workload_id,
|
||||
"security_zone": str(record.get("effective_zone") or "unknown"),
|
||||
"security_zone_declared": record.get("declared_zone"),
|
||||
"security_zone_admission": str(record.get("admission") or "unknown"),
|
||||
"security_zone_reason": str(record.get("admission_reason") or "unknown"),
|
||||
"security_zone_revision": record.get("membership_revision"),
|
||||
}
|
||||
|
||||
|
||||
def build_registry(
|
||||
inventory: dict[str, Any], zone_resolutions: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
actors: dict[str, Any] = inventory.get("actors") or {}
|
||||
resources: list[dict[str, Any]] = []
|
||||
subjects: list[dict[str, Any]] = []
|
||||
groups: dict[str, list[str]] = {gid: [] for gid in GROUP_BY_TYPE.values()}
|
||||
relationships: list[dict[str, Any]] = []
|
||||
resolutions = _resolved_by_workload(zone_resolutions)
|
||||
|
||||
for name, entry in sorted(actors.items()):
|
||||
actor_type = str(entry["type"])
|
||||
|
|
@ -74,7 +141,6 @@ def build_registry(inventory: dict[str, Any]) -> dict[str, Any]:
|
|||
"id": resource_id,
|
||||
"type": "ssh-certificate",
|
||||
"labels": ["ssh-signing", actor_type],
|
||||
"trust_zone": "platform",
|
||||
"owner": "team:platform-security",
|
||||
"attributes": {
|
||||
"actor_id": name,
|
||||
|
|
@ -82,6 +148,7 @@ def build_registry(inventory: dict[str, Any]) -> dict[str, Any]:
|
|||
"allowed_subjects": [name, f"iam:{name}"],
|
||||
"allowed_principals": principals,
|
||||
"max_ttl_hours": ttl_hours,
|
||||
**_zone_attributes(name, entry, resolutions),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
@ -156,8 +223,8 @@ def build_registry(inventory: dict[str, Any]) -> dict[str, Any]:
|
|||
"caring_profiles": ["caring-0.4.0-rc2"],
|
||||
"metadata": {
|
||||
"flex_auth_contract": "protected-system-v0",
|
||||
"ops_warden_policy_gate": "v2",
|
||||
"policy_enabled_config": "policy.enabled",
|
||||
"ops_warden_policy_gate": "security-zones-v0.1",
|
||||
"security_zone_standard": "security-zones_v0.1",
|
||||
"tenant": "tenant:platform",
|
||||
},
|
||||
}
|
||||
|
|
@ -186,14 +253,24 @@ def main() -> None:
|
|||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("inventory", type=Path, help="ops-warden inventory.yaml")
|
||||
parser.add_argument("-o", "--output", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--zone-resolutions",
|
||||
type=Path,
|
||||
help="zone-engine resolved-view JSON; absent references remain unknown",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
inventory = yaml.safe_load(args.inventory.read_text()) or {}
|
||||
registry = build_registry(inventory)
|
||||
zone_resolutions = (
|
||||
json.loads(args.zone_resolutions.read_text())
|
||||
if args.zone_resolutions is not None
|
||||
else None
|
||||
)
|
||||
registry = build_registry(inventory, zone_resolutions)
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(registry, indent=2) + "\n")
|
||||
print(f"Wrote {args.output} ({len(registry['subjects'])} actors)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Readiness gate for the flex-auth ``policy.enabled`` flip (WARDEN-WP-0031).
|
||||
"""Readiness gate for the zone-aware flex-auth caller identity.
|
||||
|
||||
flex-auth deployed ``flex-auth-ops-warden`` (FLEX-WP-0016) in ``callerAuth.mode:
|
||||
warn``: it authenticates the caller with a Kubernetes TokenReview and binds
|
||||
``resource.system: ops-warden`` to ``system:serviceaccount:ops-warden:ops-warden``,
|
||||
but a caller that sends no ``Authorization`` header only produces a
|
||||
``caller authentication warning`` and is still served. That pin cannot move to
|
||||
``enforce`` — and therefore ``policy.enabled: true`` cannot be set — until
|
||||
ops-warden's calling side actually presents a token.
|
||||
``enforce`` until ops-warden's calling side actually presents a token. The
|
||||
former repo-wide ``policy.enabled`` switch is retired by WARDEN-WP-0032.
|
||||
|
||||
This script asserts the calling side *without* flipping anything:
|
||||
|
||||
|
|
@ -57,9 +57,7 @@ def run_checks(config_path: Optional[Path], url: Optional[str]) -> List[Check]:
|
|||
return [("fail", "warden.yaml", str(e))]
|
||||
|
||||
policy = cfg.policy
|
||||
checks.append(
|
||||
("ok", "warden.yaml", f"loaded; policy.enabled={str(policy.enabled).lower()}")
|
||||
)
|
||||
checks.append(("ok", "warden.yaml", "loaded; security-zones_v0.1 profile"))
|
||||
|
||||
mode = policy.caller_auth.mode
|
||||
if mode == "none":
|
||||
|
|
@ -84,12 +82,12 @@ def run_checks(config_path: Optional[Path], url: Optional[str]) -> List[Check]:
|
|||
)
|
||||
|
||||
target = url or policy.flex_auth_url
|
||||
if url is None and not policy.enabled:
|
||||
if target is None:
|
||||
checks.append(
|
||||
(
|
||||
"skip",
|
||||
"live /v1/check",
|
||||
f"policy.enabled=false; pass --url to smoke {target} anyway",
|
||||
"policy.flex_auth_url is absent; pass --url to run the live smoke",
|
||||
)
|
||||
)
|
||||
return checks
|
||||
|
|
@ -188,13 +186,13 @@ def main() -> int:
|
|||
if failed:
|
||||
print(
|
||||
f"\nNOT READY — {len(failed)} check(s) failed. "
|
||||
"Do not ask flex-auth to enforce, and do not set policy.enabled: true."
|
||||
"Do not ask flex-auth to enforce caller authentication."
|
||||
)
|
||||
return 1
|
||||
print(
|
||||
"\nREADY — the calling side presents an identity. Next: tell flex-auth to set "
|
||||
"callerAuth.mode: enforce on flex-auth-ops-warden, re-run this check, then set "
|
||||
"policy.enabled: true with fail_closed: true."
|
||||
"callerAuth.mode: enforce on flex-auth-ops-warden and re-run this check. "
|
||||
"Zone-specific PEP failure modes already replace the retired global switches."
|
||||
)
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -136,8 +136,13 @@ def main() -> int:
|
|||
if args.check:
|
||||
current = args.out.read_text() if args.out.exists() else ""
|
||||
# generated_at always differs; compare everything else.
|
||||
strip = lambda t: "\n".join(l for l in t.splitlines() if not l.startswith("generated_at:"))
|
||||
if strip(current) != strip(content):
|
||||
def strip_generated_at(text: str) -> str:
|
||||
return "\n".join(
|
||||
line for line in text.splitlines()
|
||||
if not line.startswith("generated_at:")
|
||||
)
|
||||
|
||||
if strip_generated_at(current) != strip_generated_at(content):
|
||||
print(f"STALE: {args.out} does not match the catalog. Re-run without --check.")
|
||||
return 1
|
||||
print(f"fresh: {args.out} matches the catalog ({count} concrete paths)")
|
||||
|
|
|
|||
|
|
@ -66,9 +66,8 @@ ca_key: $SMOKE_DIR/ca_key
|
|||
state_dir: $SMOKE_DIR/state
|
||||
inventory_path: $INVENTORY
|
||||
policy:
|
||||
enabled: true
|
||||
flex_auth_url: http://$ADDR
|
||||
fail_closed: true
|
||||
zone_registry_path: $REGISTRY
|
||||
tenant: tenant:platform
|
||||
system: ops-warden
|
||||
EOF
|
||||
|
|
@ -106,9 +105,8 @@ vault:
|
|||
inventory_path: $INVENTORY
|
||||
state_dir: $SMOKE_DIR/state-vault
|
||||
policy:
|
||||
enabled: true
|
||||
flex_auth_url: http://$ADDR
|
||||
fail_closed: true
|
||||
zone_registry_path: $REGISTRY
|
||||
tenant: tenant:platform
|
||||
system: ops-warden
|
||||
EOF
|
||||
|
|
@ -118,4 +116,4 @@ EOF
|
|||
python3 -c "import json,sys; e=json.loads(sys.argv[1]); assert e.get('backend')=='vault' and e.get('policy_decision_id'); print('vault policy_decision_id:', e['policy_decision_id'])" "$VAULT_LINE"
|
||||
fi
|
||||
|
||||
echo "OK — production registry policy gate smoke passed"
|
||||
echo "OK — production registry policy gate smoke passed"
|
||||
|
|
|
|||
234
scripts/report_workload_join.py
Executable file → Normal file
234
scripts/report_workload_join.py
Executable file → Normal file
|
|
@ -1,128 +1,156 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Report the lane -> workload join, for the security-zone model (ZONE-WP-0001-T03).
|
||||
"""Report explicit lane -> workload resolution for security-zones_v0.1.
|
||||
|
||||
A security zone's subject is the **workload**, not the lane — policy is about the
|
||||
running thing and whoever answers for it. ops-warden's catalog is a credential
|
||||
surface, so on its own it cannot name that subject. The other side of the join
|
||||
lives in `rapp-*/declarations/rapp.yaml`, which declares `workload_identity`
|
||||
along with `data_classification` and `criticality`; ops-warden's
|
||||
`registry/policy/security-posture.yaml` then maps classification to a minimum
|
||||
maturity via `dataclass_floor`.
|
||||
The catalog owner declares whether each lane is workload-applicable. Managed
|
||||
deployables use the exact Repo Manager v1 ``(rapp_id, name, deployable?)``
|
||||
reference. Independently governed operational workloads use ``name`` plus an
|
||||
owner declaration reference. Unknown and not-applicable are explicit results.
|
||||
|
||||
This script computes the join and reports its coverage. It asserts nothing it
|
||||
cannot derive: a lane whose workload cannot be established is reported as
|
||||
unmatched rather than guessed, because the unmatched set is the informative
|
||||
output — those are lanes existing for something that is not a declared workload.
|
||||
|
||||
Read-only. Touches no secret value and no live system.
|
||||
|
||||
Usage:
|
||||
python scripts/report_workload_join.py [--rapp-root ~] [--json]
|
||||
This script never parses a credential path, consults ``owner_repo`` as an
|
||||
identity hint, or substitutes a repository name. It reads declarations only.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
_SRC = Path(__file__).resolve().parent.parent / "src"
|
||||
if _SRC.is_dir() and str(_SRC) not in sys.path:
|
||||
sys.path.insert(0, str(_SRC))
|
||||
|
||||
import yaml # noqa: E402
|
||||
import yaml
|
||||
|
||||
|
||||
def load_rapp_workloads(root: Path) -> Dict[str, Dict[str, Any]]:
|
||||
"""workload name -> declaration, from every rapp-*/declarations/rapp.yaml."""
|
||||
out: Dict[str, Dict[str, Any]] = {}
|
||||
def load_rapp_workloads(root: Path) -> dict[tuple[str, str], dict[str, Any]]:
|
||||
"""Exact Repo Manager v1 key -> authoritative declaration projection."""
|
||||
out: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
for decl in sorted(root.glob("rapp-*/declarations/rapp.yaml")):
|
||||
try:
|
||||
data = yaml.safe_load(decl.read_text()) or {}
|
||||
except yaml.YAMLError:
|
||||
continue
|
||||
identity = data.get("workload_identity") or {}
|
||||
rapp_id = data.get("rapp_id")
|
||||
name = identity.get("name")
|
||||
if not name:
|
||||
if not rapp_id or not name:
|
||||
continue
|
||||
out[str(name)] = {
|
||||
"rapp_id": data.get("rapp_id"),
|
||||
deployables = []
|
||||
for member in (data.get("composition") or {}).get("member_repos") or []:
|
||||
deployables.extend(str(value) for value in member.get("deployables") or [])
|
||||
out[(str(rapp_id), str(name))] = {
|
||||
"source": str(decl),
|
||||
"deployables": sorted(set(deployables)),
|
||||
"data_classification": data.get("data_classification"),
|
||||
"criticality": data.get("criticality"),
|
||||
"readiness_state": data.get("readiness_state"),
|
||||
"bound_reefs": data.get("bound_reefs"),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def candidate_workloads(path_template: Optional[str]) -> List[str]:
|
||||
"""Workload names a path could plausibly name — never a single guess.
|
||||
|
||||
The convention is `platform/workloads/<domain>/<workload>/<bundle>`, but it
|
||||
is not applied consistently: `platform/workloads/rapp-qonto/keycape-client`
|
||||
has one level fewer, so position alone cannot say which segment is the
|
||||
workload. Both are offered and matched against declarations.
|
||||
"""
|
||||
if not path_template or "<" in path_template:
|
||||
return []
|
||||
parts = [p for p in path_template.split("/") if p]
|
||||
if len(parts) >= 4 and parts[1] == "workloads":
|
||||
return [parts[3], parts[2]]
|
||||
if parts and parts[0] == "tenants" and len(parts) >= 3:
|
||||
return [parts[2], parts[1]]
|
||||
return []
|
||||
def _direct_declaration_path(
|
||||
declaration_ref: str, *, catalog_path: Path, estate_root: Path
|
||||
) -> Path:
|
||||
ref = Path(declaration_ref)
|
||||
if ref.is_absolute():
|
||||
return ref
|
||||
local = catalog_path.resolve().parents[2] / ref
|
||||
return local if local.exists() else estate_root / ref
|
||||
|
||||
|
||||
def build(catalog_path: Path, rapp_root: Path) -> Dict[str, Any]:
|
||||
entries = (yaml.safe_load(catalog_path.read_text()) or {}).get("entries", [])
|
||||
declared = load_rapp_workloads(rapp_root)
|
||||
floor = (
|
||||
yaml.safe_load(
|
||||
(catalog_path.parent.parent / "policy" / "security-posture.yaml").read_text()
|
||||
def _resolve_direct(
|
||||
ref: dict[str, Any], *, catalog_path: Path, estate_root: Path
|
||||
) -> tuple[dict[str, Any] | None, str | None]:
|
||||
source = _direct_declaration_path(
|
||||
str(ref["declaration_ref"]), catalog_path=catalog_path, estate_root=estate_root
|
||||
)
|
||||
if not source.exists():
|
||||
return None, f"declaration not found: {source}"
|
||||
try:
|
||||
declaration = yaml.safe_load(source.read_text()) or {}
|
||||
except yaml.YAMLError as exc:
|
||||
return None, f"invalid declaration YAML: {exc}"
|
||||
identity = declaration.get("workload_identity") or {}
|
||||
if identity.get("name") != ref.get("name"):
|
||||
return None, (
|
||||
f"declared workload_identity.name={identity.get('name')!r}, "
|
||||
f"expected {ref.get('name')!r}"
|
||||
)
|
||||
or {}
|
||||
).get("dataclass_floor", {})
|
||||
context = (declaration.get("zones") or {}).get("context", {})
|
||||
return {
|
||||
"source": str(source),
|
||||
"data_classification": context.get("data_classification"),
|
||||
"criticality": context.get("criticality"),
|
||||
"maturity": context.get("maturity"),
|
||||
"declared_zone": (declaration.get("zones") or {}).get("membership"),
|
||||
}, None
|
||||
|
||||
matched, unmatched, no_path = [], [], []
|
||||
|
||||
def build(catalog_path: Path, estate_root: Path) -> dict[str, Any]:
|
||||
entries = (yaml.safe_load(catalog_path.read_text()) or {}).get("entries", [])
|
||||
managed = load_rapp_workloads(estate_root)
|
||||
posture_path = catalog_path.parent.parent / "policy" / "security-posture.yaml"
|
||||
floor = (yaml.safe_load(posture_path.read_text()) or {}).get("dataclass_floor", {})
|
||||
|
||||
resolved: list[dict[str, Any]] = []
|
||||
unknown: list[dict[str, Any]] = []
|
||||
not_applicable: list[dict[str, Any]] = []
|
||||
for entry in entries:
|
||||
lane = entry.get("id")
|
||||
cands = candidate_workloads(entry.get("path_template"))
|
||||
if not cands:
|
||||
no_path.append({"lane": lane, "risk": entry.get("risk")})
|
||||
lane = str(entry.get("id"))
|
||||
ref = entry.get("workload_ref") or {}
|
||||
applicability = ref.get("applicability")
|
||||
if applicability == "not-applicable":
|
||||
not_applicable.append({"lane": lane, "reason": ref.get("reason")})
|
||||
continue
|
||||
hit = next((c for c in cands if c in declared), None)
|
||||
if hit is None:
|
||||
unmatched.append(
|
||||
{"lane": lane, "risk": entry.get("risk"), "candidates": cands}
|
||||
if applicability != "applicable":
|
||||
unknown.append({"lane": lane, "reason": "applicability missing or invalid"})
|
||||
continue
|
||||
if ref.get("unknown_reason"):
|
||||
unknown.append({"lane": lane, "reason": ref["unknown_reason"]})
|
||||
continue
|
||||
|
||||
projection: dict[str, Any] | None
|
||||
error: str | None = None
|
||||
if ref.get("rapp_id"):
|
||||
key = (str(ref.get("rapp_id")), str(ref.get("name")))
|
||||
projection = managed.get(key)
|
||||
if projection is None:
|
||||
error = f"Repo Manager reference does not resolve: {key[0]}/{key[1]}"
|
||||
elif ref.get("deployable") and ref["deployable"] not in projection["deployables"]:
|
||||
error = f"deployable {ref['deployable']!r} is not declared by {key[0]}/{key[1]}"
|
||||
else:
|
||||
projection, error = _resolve_direct(
|
||||
ref, catalog_path=catalog_path, estate_root=estate_root
|
||||
)
|
||||
if error or projection is None:
|
||||
unknown.append({"lane": lane, "reason": error or "reference unresolved"})
|
||||
continue
|
||||
decl = declared[hit]
|
||||
cls = decl.get("data_classification")
|
||||
matched.append(
|
||||
|
||||
classification = projection.get("data_classification")
|
||||
resolved.append(
|
||||
{
|
||||
"lane": lane,
|
||||
"workload": hit,
|
||||
"risk": entry.get("risk"),
|
||||
"data_classification": cls,
|
||||
"criticality": decl.get("criticality"),
|
||||
"min_maturity": floor.get(cls),
|
||||
"unmapped_classification": bool(cls) and cls not in floor,
|
||||
"workload_ref": ref,
|
||||
"source": projection.get("source"),
|
||||
"data_classification": classification,
|
||||
"criticality": projection.get("criticality"),
|
||||
"maturity": projection.get("maturity") or floor.get(classification),
|
||||
"declared_zone": projection.get("declared_zone"),
|
||||
"unmapped_classification": bool(classification) and classification not in floor,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"declared_workloads": declared,
|
||||
"matched": matched,
|
||||
"unmatched": unmatched,
|
||||
"no_path": no_path,
|
||||
"dataclass_floor": floor,
|
||||
"contract": "helixforge.workload-reference/v1",
|
||||
"resolved": resolved,
|
||||
"unknown": unknown,
|
||||
"not_applicable": not_applicable,
|
||||
"ok": len(resolved) + len(unknown) + len(not_applicable) == len(entries),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--rapp-root", type=Path, default=Path.home())
|
||||
parser.add_argument(
|
||||
"--estate-root", "--rapp-root", dest="estate_root", type=Path, default=Path.home()
|
||||
)
|
||||
parser.add_argument("--json", action="store_true")
|
||||
parser.add_argument(
|
||||
"--catalog",
|
||||
|
|
@ -133,40 +161,24 @@ def main() -> int:
|
|||
/ "catalog.yaml",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
report = build(args.catalog, Path(os.path.expanduser(str(args.rapp_root))))
|
||||
report = build(args.catalog, args.estate_root)
|
||||
if args.json:
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
return 0
|
||||
return 0 if report["ok"] else 1
|
||||
|
||||
print("lane -> workload join\n")
|
||||
print(f" declared workloads (rapp-*): {len(report['declared_workloads'])}")
|
||||
print(f" lanes matched to a workload: {len(report['matched'])}")
|
||||
print(f" lanes unmatched: {len(report['unmatched'])}")
|
||||
print(f" lanes with no usable path: {len(report['no_path'])}\n")
|
||||
|
||||
if report["matched"]:
|
||||
print("MATCHED")
|
||||
for m in report["matched"]:
|
||||
flag = " <-- classification unmapped by dataclass_floor" if m[
|
||||
"unmapped_classification"
|
||||
] else ""
|
||||
print(
|
||||
f" {m['lane']:34} -> {str(m['workload']):16} "
|
||||
f"{str(m['data_classification']):13} crit={str(m['criticality']):9} "
|
||||
f"min={str(m['min_maturity']):4} risk={m['risk']}{flag}"
|
||||
)
|
||||
if report["unmatched"]:
|
||||
print("\nUNMATCHED — a lane exists for something no rapp declares")
|
||||
for u in report["unmatched"]:
|
||||
print(
|
||||
f" {u['lane']:34} risk={str(u['risk']):9} "
|
||||
f"candidates={', '.join(u['candidates'])}"
|
||||
)
|
||||
if report["no_path"]:
|
||||
print("\nNO USABLE PATH — not a KV lane, or a pattern rather than an address")
|
||||
print(" " + ", ".join(n["lane"] for n in report["no_path"]))
|
||||
return 0
|
||||
print("explicit lane -> workload resolution\n")
|
||||
print(f" resolved: {len(report['resolved'])}")
|
||||
print(f" unknown: {len(report['unknown'])}")
|
||||
print(f" not-applicable: {len(report['not_applicable'])}\n")
|
||||
for row in report["resolved"]:
|
||||
ref = row["workload_ref"]
|
||||
prefix = f"{ref.get('rapp_id')}/" if ref.get("rapp_id") else ""
|
||||
print(f"RESOLVED {row['lane']:34} -> {prefix}{ref.get('name')}")
|
||||
for row in report["unknown"]:
|
||||
print(f"UNKNOWN {row['lane']:34} {row['reason']}")
|
||||
for row in report["not_applicable"]:
|
||||
print(f"NOT-APPLICABLE {row['lane']:34} {row['reason']}")
|
||||
return 0 if report["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue