From 32c5e1ea7f34cb281b11372fa4bcea9cc2ab8553 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Wed, 6 May 2026 01:13:22 -0400 Subject: [PATCH 1/9] Add local Policy Fabric admission resolver --- src/agent_machine/policy_fabric.py | 298 +++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 src/agent_machine/policy_fabric.py diff --git a/src/agent_machine/policy_fabric.py b/src/agent_machine/policy_fabric.py new file mode 100644 index 0000000..d4f0358 --- /dev/null +++ b/src/agent_machine/policy_fabric.py @@ -0,0 +1,298 @@ +"""Local Policy Fabric admission resolver for Agent Machine. + +This module is a bootstrap stand-in for a real Policy Fabric client. It resolves +secret-free PolicyAdmission artifacts from explicit files or local stores and can +produce a fail-closed missing-admission stub when no policy decision is present. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +from agent_machine.contracts import load_json, schema_by_kind +from agent_machine.governance import validate_policy_admission_semantics + +DEFAULT_DECIDED_AT = "1970-01-01T00:00:00Z" + + +def validate_payload_against_kind(value: dict[str, Any], kind: str, root: Path | None = None) -> None: + schema_path = schema_by_kind(root)[kind] + schema_payload = load_json(schema_path) + try: + from jsonschema.validators import validator_for + except ImportError as exc: # pragma: no cover + raise RuntimeError( + "Missing dependency: jsonschema. Install with `python -m pip install -r requirements-dev.txt`." + ) from exc + validator_cls = validator_for(schema_payload) + validator_cls.check_schema(schema_payload) + validator = validator_cls(schema_payload) + errors = sorted(validator.iter_errors(value), key=lambda err: list(err.path)) + if errors: + rendered = [] + for err in errors: + location = "/".join(str(part) for part in err.path) or "" + rendered.append(f" - {location}: {err.message}") + raise AssertionError(f"{kind} failed schema validation:\n" + "\n".join(rendered)) + + +def validate_policy_admission_payload(policy: dict[str, Any], root: Path | None = None, source: str = "") -> None: + validate_payload_against_kind(policy, "PolicyAdmission", root) + validate_policy_admission_semantics(policy, source=source) + + +def iter_json_files(directory: Path) -> list[Path]: + if not directory.exists(): + raise AssertionError(f"policy store directory does not exist: {directory}") + if not directory.is_dir(): + raise AssertionError(f"policy store path is not a directory: {directory}") + return sorted(path for path in directory.rglob("*.json") if path.is_file()) + + +def load_policy_admissions( + *, + files: list[Path] | None = None, + directories: list[Path] | None = None, + root: Path | None = None, +) -> list[dict[str, Any]]: + """Load PolicyAdmission objects from files and/or local store directories.""" + policies: list[dict[str, Any]] = [] + seen_paths: set[Path] = set() + + for path in files or []: + resolved = path.resolve() + seen_paths.add(resolved) + value = load_json(path) + if not isinstance(value, dict): + raise AssertionError(f"{path}: policy admission file root must be an object") + if value.get("kind") != "PolicyAdmission": + raise AssertionError(f"{path}: expected kind=PolicyAdmission") + validate_policy_admission_payload(value, root, source=str(path)) + policies.append(value) + + for directory in directories or []: + for path in iter_json_files(directory): + resolved = path.resolve() + if resolved in seen_paths: + continue + value = load_json(path) + if isinstance(value, dict) and value.get("kind") == "PolicyAdmission": + validate_policy_admission_payload(value, root, source=str(path)) + policies.append(value) + seen_paths.add(resolved) + + policy_ids: dict[str, dict[str, Any]] = {} + for policy in policies: + policy_id = policy.get("id") + if not isinstance(policy_id, str): + raise AssertionError("PolicyAdmission loaded without string id") + if policy_id in policy_ids: + raise AssertionError(f"duplicate PolicyAdmission id loaded: {policy_id}") + policy_ids[policy_id] = policy + return policies + + +def request_matches( + policy: dict[str, Any], + *, + agentpod_id: str, + request_type: str, + deployment_receipt_id: str, + agent_machine_id: str | None = None, + provider_id: str | None = None, +) -> bool: + request = policy.get("request", {}) + if request.get("agentPodId") != agentpod_id: + return False + if request.get("requestType") != request_type: + return False + if request.get("deploymentReceiptId") != deployment_receipt_id: + return False + if agent_machine_id and request.get("agentMachineId") != agent_machine_id: + return False + if provider_id and request.get("providerId") != provider_id: + return False + return True + + +def missing_policy_admission_stub( + *, + agentpod_id: str, + request_type: str, + deployment_receipt_id: str, + decided_at: str, + agent_machine_id: str | None = None, + provider_id: str | None = None, +) -> dict[str, Any]: + suffix = agentpod_id.split(":")[-1] + return { + "specVersion": "0.1.0", + "id": f"urn:srcos:agent-machine:policy-admission:missing-{suffix}-{request_type}", + "kind": "PolicyAdmission", + "request": { + "requestId": f"urn:srcos:agent-machine:policy-request:missing-{suffix}-{request_type}", + "requestType": request_type, + "agentMachineId": agent_machine_id or "urn:srcos:agent-machine:unknown", + "agentPodId": agentpod_id, + "providerId": provider_id, + "deploymentReceiptId": deployment_receipt_id, + "planDigest": None, + "manifestDigest": None, + }, + "decision": { + "status": "missing", + "authorizationGranted": False, + "decisionRef": None, + "decisionDigest": None, + "reason": "No matching PolicyAdmission was resolved; activation must fail closed.", + "policyBundleRef": None, + "policyBundleDigest": None, + }, + "scope": { + "allowed": { + "networkExposure": [], + "storageClasses": [], + "volumeClasses": [], + "providerIds": [], + "cacheReuse": False, + "sideEffects": [], + }, + "denied": { + "networkExposure": ["loopback", "host", "cluster", "ingress"], + "storageClasses": ["filesystem", "local-lvm", "topolvm-k8s", "tmpfs", "object-store", "remote-volume"], + "volumeClasses": ["agent-models", "agent-cache-hot", "agent-cache-warm", "agent-cache-cold", "agent-scratch", "agent-evidence", "agent-artifacts"], + "providerIds": [provider_id] if provider_id else [], + "cacheReuse": True, + "sideEffects": ["start-provider", "mount-cache", "reuse-cache", "public-ingress"], + }, + }, + "obligations": { + "requiredReceipts": ["deployment", "storage", "runtime"], + "expiresAt": None, + "revocationRef": None, + "constraints": ["fail-closed-until-policy-decision-present"], + }, + "receiptSafety": { + "includeRawContent": False, + "rawPromptContentIncluded": False, + "rawKvCacheContentIncluded": False, + "secretValuesIncluded": False, + "privateMemoryIncluded": False, + }, + "decidedAt": decided_at, + "labels": { + "sourceos.policy.resolver": "local-store", + "sourceos.activation.fail-closed": "true", + }, + } + + +def resolve_policy_admission( + *, + policies: list[dict[str, Any]], + agentpod_id: str, + request_type: str, + deployment_receipt_id: str, + agent_machine_id: str | None = None, + provider_id: str | None = None, + policy_id: str | None = None, + expected_status: str | None = None, + allow_missing_stub: bool = True, + decided_at: str = DEFAULT_DECIDED_AT, + root: Path | None = None, +) -> dict[str, Any]: + """Resolve one PolicyAdmission or return a fail-closed missing stub. + + Ambiguity is a hard failure. A caller may disambiguate by policy_id or expected_status. + """ + if policy_id: + matches = [policy for policy in policies if policy.get("id") == policy_id] + else: + matches = [ + policy + for policy in policies + if request_matches( + policy, + agentpod_id=agentpod_id, + request_type=request_type, + deployment_receipt_id=deployment_receipt_id, + agent_machine_id=agent_machine_id, + provider_id=provider_id, + ) + ] + if expected_status: + matches = [policy for policy in matches if policy.get("decision", {}).get("status") == expected_status] + + if len(matches) == 1: + validate_policy_admission_payload(matches[0], root, source=str(matches[0].get("id"))) + return matches[0] + if not matches: + if not allow_missing_stub: + raise AssertionError("no matching PolicyAdmission found") + stub = missing_policy_admission_stub( + agentpod_id=agentpod_id, + request_type=request_type, + deployment_receipt_id=deployment_receipt_id, + agent_machine_id=agent_machine_id, + provider_id=provider_id, + decided_at=decided_at, + ) + validate_policy_admission_payload(stub, root, source="missing-policy-stub") + return stub + + ids = ", ".join(sorted(str(policy.get("id")) for policy in matches)) + raise AssertionError(f"ambiguous PolicyAdmission match; disambiguate with policy_id or expected_status: {ids}") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Resolve PolicyAdmission from local Policy Fabric files/stores") + parser.add_argument("agentpod_json", type=Path) + parser.add_argument("--policy-file", action="append", type=Path, default=[]) + parser.add_argument("--policy-dir", action="append", type=Path, default=[]) + parser.add_argument("--request-type", default="activation") + parser.add_argument("--deployment-receipt-id", required=True) + parser.add_argument("--agent-machine-id") + parser.add_argument("--provider-id") + parser.add_argument("--policy-id") + parser.add_argument("--expected-status", choices=["missing", "allowed", "denied", "not-required", "unknown"]) + parser.add_argument("--no-missing-stub", action="store_true") + parser.add_argument("--decided-at", default=DEFAULT_DECIDED_AT) + parser.add_argument("--pretty", action="store_true") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + agentpod = load_json(args.agentpod_json) + if not isinstance(agentpod, dict) or agentpod.get("kind") != "AgentPod": + raise AssertionError(f"{args.agentpod_json}: expected kind=AgentPod") + policies = load_policy_admissions(files=args.policy_file, directories=args.policy_dir) + policy = resolve_policy_admission( + policies=policies, + agentpod_id=str(agentpod.get("id")), + request_type=args.request_type, + deployment_receipt_id=args.deployment_receipt_id, + agent_machine_id=args.agent_machine_id, + provider_id=args.provider_id, + policy_id=args.policy_id, + expected_status=args.expected_status, + allow_missing_stub=not args.no_missing_stub, + decided_at=args.decided_at, + ) + if args.pretty: + print(json.dumps(policy, indent=2, sort_keys=True)) + else: + print(json.dumps(policy, sort_keys=True, separators=(",", ":"))) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, RuntimeError) as exc: + print(str(exc), file=sys.stderr) + raise SystemExit(1) from exc From befbba622f72d85debf4138d71b640f6f77cb016 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Wed, 6 May 2026 01:18:56 -0400 Subject: [PATCH 2/9] Add Policy Fabric resolver validation on current main --- Makefile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dd6c1f9..535d96b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula doctor probe +.PHONY: validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula doctor probe PYTHON ?= python3 RUBY ?= ruby @@ -15,12 +15,13 @@ READY_GRANT := examples/agent-registry-grant.active-activation.json FAIL_POLICY := examples/policy-admission.missing.json FAIL_GRANT := examples/agent-registry-grant.missing.json RECEIPT_DIR := examples +POLICY_DIR := examples DEPLOYMENT_RECEIPT_ID := urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa DECIDED_AT := 2026-05-04T12:51:00Z PYCLI := PYTHONPATH=src $(PYTHON) -m agent_machine.cli PYMOD := PYTHONPATH=src $(PYTHON) -m -validate: validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula +validate: validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-json: $(PYTHON) scripts/validate-json.py @@ -52,10 +53,16 @@ validate-evidence: validate-governance: $(PYTHON) scripts/validate-governance.py +validate-policy-fabric: + $(PYTHON) scripts/validate-policy-fabric.py + $(PYTHON) scripts/resolve-policy-admission.py $(LOCAL_AGENTPOD) --policy-dir $(POLICY_DIR) --expected-status allowed --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp --pretty >/tmp/agent-machine-policy-resolve-allowed.json + $(PYCLI) policy resolve $(LOCAL_AGENTPOD) --policy-dir $(POLICY_DIR) --expected-status denied --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp --pretty >/tmp/agent-machine-pycli-policy-resolve-denied.json + validate-activation: $(PYTHON) scripts/validate-activation.py $(PYTHON) scripts/evaluate-activation.py $(LOCAL_AGENTPOD) $(READY_POLICY) $(READY_GRANT) --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-evaluate-activation-allowed.json $(PYCLI) activate evaluate $(LOCAL_AGENTPOD) $(FAIL_POLICY) $(FAIL_GRANT) --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-fail-closed --pretty >/tmp/agent-machine-pycli-evaluate-activation-fail-closed.json + $(PYCLI) activate evaluate $(LOCAL_AGENTPOD) $(READY_GRANT) --policy-dir $(POLICY_DIR) --expected-status allowed --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-pycli-resolved-policy-activation-allowed.json $(BOOTSTRAP_CLI) activate evaluate $(LOCAL_AGENTPOD) $(READY_POLICY) $(READY_GRANT) --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-bootstrap-evaluate-activation-allowed.json validate-supply-chain: From 2ec366bdc72d15e0543e83f3ee6e5835b651b71f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Wed, 6 May 2026 11:20:01 -0400 Subject: [PATCH 3/9] Add Python CLI PolicyAdmission resolver command --- src/agent_machine/cli.py | 105 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/src/agent_machine/cli.py b/src/agent_machine/cli.py index b2c27f6..7cdd4fb 100644 --- a/src/agent_machine/cli.py +++ b/src/agent_machine/cli.py @@ -261,8 +261,79 @@ def cmd_render_k8s(args: argparse.Namespace) -> int: return 0 +def cmd_policy_resolve(args: argparse.Namespace) -> int: + policy_fabric = import_renderer(lambda: __import__("agent_machine.policy_fabric", fromlist=["_unused"])) + agentpod = load_json(args.agentpod_json) + policies = policy_fabric.load_policy_admissions(files=args.policy_file, directories=args.policy_dir, root=REPO_ROOT) + policy = policy_fabric.resolve_policy_admission( + policies=policies, + agentpod_id=str(agentpod.get("id")), + request_type=args.request_type, + deployment_receipt_id=args.deployment_receipt_id, + agent_machine_id=args.agent_machine_id, + provider_id=args.provider_id, + policy_id=args.policy_id, + expected_status=args.expected_status, + allow_missing_stub=not args.no_missing_stub, + decided_at=args.decided_at, + root=REPO_ROOT, + ) + if args.pretty: + print(json.dumps(policy, indent=2, sort_keys=True)) + else: + print(json.dumps(policy, sort_keys=True, separators=(",", ":"))) + return 0 + + +def resolve_activation_policy_and_grant(args: argparse.Namespace, agentpod: dict[str, Any], policy_fabric: Any) -> tuple[dict[str, Any], dict[str, Any]]: + """Resolve activation policy/grant from explicit files or local policy store.""" + policy_json = args.policy_json + grant_json = args.grant_json + + # Backward-compatible shorthand: + # agent-machine activate evaluate --policy-dir examples ... + # argparse first assigns the single optional positional to policy_json, so we + # reinterpret it as grant_json when a policy store/resolver option is present. + resolver_requested = bool(args.policy_file or args.policy_dir or args.policy_id or args.expected_status) + if grant_json is None and policy_json is not None and resolver_requested: + grant_json = policy_json + policy_json = None + + if grant_json is None: + raise AssertionError( + "grant JSON is required. Use either ` ` " + "or ` --policy-dir `" + ) + + if policy_json is not None: + return load_json(policy_json), load_json(grant_json) + + policies = policy_fabric.load_policy_admissions( + files=args.policy_file, + directories=args.policy_dir, + root=REPO_ROOT, + ) + policy = policy_fabric.resolve_policy_admission( + policies=policies, + agentpod_id=str(agentpod.get("id")), + request_type="activation", + deployment_receipt_id=args.deployment_receipt_id, + agent_machine_id=args.agent_machine_id, + provider_id=args.provider_id, + policy_id=args.policy_id, + expected_status=args.expected_status, + allow_missing_stub=not args.no_missing_stub, + decided_at=args.decided_at, + root=REPO_ROOT, + ) + return policy, load_json(grant_json) + + def cmd_activate_evaluate(args: argparse.Namespace) -> int: activation = import_renderer(lambda: __import__("agent_machine.activation", fromlist=["_unused"])) + policy_fabric = import_renderer(lambda: __import__("agent_machine.policy_fabric", fromlist=["_unused"])) + agentpod = load_json(args.agentpod_json) + policy, grant = resolve_activation_policy_and_grant(args, agentpod, policy_fabric) storage_receipts = activation.load_storage_receipts( files=args.storage_receipt_file, directories=args.storage_receipt_dir, @@ -271,9 +342,9 @@ def cmd_activate_evaluate(args: argparse.Namespace) -> int: if not storage_receipt_refs and storage_receipts: storage_receipt_refs = [str(receipt.get("id")) for receipt in storage_receipts] decision = activation.evaluate_activation( - agentpod=load_json(args.agentpod_json), - policy=load_json(args.policy_json), - grant=load_json(args.grant_json), + agentpod=agentpod, + policy=policy, + grant=grant, deployment_receipt_id=args.deployment_receipt_id, storage_receipt_refs=storage_receipt_refs, storage_receipts=storage_receipts if storage_receipts else None, @@ -333,13 +404,37 @@ def build_parser() -> argparse.ArgumentParser: render_k8s.add_argument("--compare", type=Path) render_k8s.set_defaults(func=cmd_render_k8s) + policy = subcommands.add_parser("policy", help="Resolve Policy Fabric admission artifacts") + policy_subcommands = policy.add_subparsers(dest="policy_command", required=True) + policy_resolve = policy_subcommands.add_parser("resolve", help="Resolve a PolicyAdmission from local files/stores") + policy_resolve.add_argument("agentpod_json", type=Path) + policy_resolve.add_argument("--policy-file", action="append", type=Path, default=[]) + policy_resolve.add_argument("--policy-dir", action="append", type=Path, default=[]) + policy_resolve.add_argument("--request-type", default="activation") + policy_resolve.add_argument("--deployment-receipt-id", required=True) + policy_resolve.add_argument("--agent-machine-id") + policy_resolve.add_argument("--provider-id") + policy_resolve.add_argument("--policy-id") + policy_resolve.add_argument("--expected-status", choices=["missing", "allowed", "denied", "not-required", "unknown"]) + policy_resolve.add_argument("--no-missing-stub", action="store_true") + policy_resolve.add_argument("--decided-at", default="1970-01-01T00:00:00Z") + policy_resolve.add_argument("--pretty", action="store_true") + policy_resolve.set_defaults(func=cmd_policy_resolve) + activate = subcommands.add_parser("activate", help="Evaluate activation readiness") activate_subcommands = activate.add_subparsers(dest="activate_command", required=True) activate_evaluate = activate_subcommands.add_parser("evaluate", help="Evaluate AgentPod activation decision") activate_evaluate.add_argument("agentpod_json", type=Path) - activate_evaluate.add_argument("policy_json", type=Path) - activate_evaluate.add_argument("grant_json", type=Path) + activate_evaluate.add_argument("policy_json", type=Path, nargs="?") + activate_evaluate.add_argument("grant_json", type=Path, nargs="?") activate_evaluate.add_argument("--deployment-receipt-id", required=True) + activate_evaluate.add_argument("--policy-file", action="append", type=Path, default=[]) + activate_evaluate.add_argument("--policy-dir", action="append", type=Path, default=[]) + activate_evaluate.add_argument("--policy-id") + activate_evaluate.add_argument("--expected-status", choices=["missing", "allowed", "denied", "not-required", "unknown"]) + activate_evaluate.add_argument("--no-missing-stub", action="store_true") + activate_evaluate.add_argument("--agent-machine-id") + activate_evaluate.add_argument("--provider-id") activate_evaluate.add_argument("--storage-receipt-ref", action="append", default=[]) activate_evaluate.add_argument("--storage-receipt-file", action="append", type=Path, default=[]) activate_evaluate.add_argument("--storage-receipt-dir", action="append", type=Path, default=[]) From 510475b6dd134f885d4c0b10338d40f442bb81b2 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Wed, 6 May 2026 11:31:55 -0400 Subject: [PATCH 4/9] Add PolicyAdmission resolver wrapper --- scripts/resolve-policy-admission.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 scripts/resolve-policy-admission.py diff --git a/scripts/resolve-policy-admission.py b/scripts/resolve-policy-admission.py new file mode 100644 index 0000000..7be355f --- /dev/null +++ b/scripts/resolve-policy-admission.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +"""Resolve PolicyAdmission from local Policy Fabric files/stores.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / "src" +if str(SRC_ROOT) not in sys.path: + sys.path.insert(0, str(SRC_ROOT)) + +from agent_machine.policy_fabric import main # noqa: E402 + +if __name__ == "__main__": + raise SystemExit(main()) From b46b392bcc2fc1042adc30f2a7fbfc56d2e67941 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Wed, 6 May 2026 13:01:05 -0400 Subject: [PATCH 5/9] Add Policy Fabric resolver validation wrapper --- scripts/validate-policy-fabric.py | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 scripts/validate-policy-fabric.py diff --git a/scripts/validate-policy-fabric.py b/scripts/validate-policy-fabric.py new file mode 100644 index 0000000..2597ee1 --- /dev/null +++ b/scripts/validate-policy-fabric.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Validate local Policy Fabric admission resolution behavior.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / "src" +if str(SRC_ROOT) not in sys.path: + sys.path.insert(0, str(SRC_ROOT)) + +from agent_machine.policy_fabric import ( # noqa: E402 + load_policy_admissions, + resolve_policy_admission, + validate_policy_admission_payload, +) + +AGENTPOD_ID = "urn:srcos:agent-machine:agent-pod:local-podman-llama-cpp" +AGENT_MACHINE_ID = "urn:srcos:agent-machine:m2-asahi-local" +PROVIDER_ID = "urn:srcos:agent-machine:inference-provider:asahi-llama-cpp" +DEPLOYMENT_RECEIPT_ID = "urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +DECIDED_AT = "2026-05-04T12:51:00Z" + + +def expect_status(policy: dict, expected: str, label: str) -> None: + observed = policy.get("decision", {}).get("status") + if observed != expected: + raise AssertionError(f"{label}: expected status={expected}, observed {observed}") + validate_policy_admission_payload(policy, REPO_ROOT, source=label) + print(f"VALID policy resolve {label} status={expected}") + + +def expect_ambiguous(policies: list[dict]) -> None: + try: + resolve_policy_admission( + policies=policies, + agentpod_id=AGENTPOD_ID, + request_type="activation", + deployment_receipt_id=DEPLOYMENT_RECEIPT_ID, + agent_machine_id=AGENT_MACHINE_ID, + provider_id=PROVIDER_ID, + allow_missing_stub=False, + root=REPO_ROOT, + ) + except AssertionError as exc: + if "ambiguous PolicyAdmission" not in str(exc): + raise + print("VALID policy resolve ambiguous activation requires disambiguation") + return + raise AssertionError("expected ambiguous PolicyAdmission resolution to fail") + + +def main() -> int: + policies = load_policy_admissions(directories=[REPO_ROOT / "examples"], root=REPO_ROOT) + if len(policies) < 4: + raise AssertionError("expected at least four PolicyAdmission examples") + + expect_ambiguous(policies) + + allowed = resolve_policy_admission( + policies=policies, + agentpod_id=AGENTPOD_ID, + request_type="activation", + deployment_receipt_id=DEPLOYMENT_RECEIPT_ID, + agent_machine_id=AGENT_MACHINE_ID, + provider_id=PROVIDER_ID, + expected_status="allowed", + root=REPO_ROOT, + ) + expect_status(allowed, "allowed", "allowed-activation") + + denied = resolve_policy_admission( + policies=policies, + agentpod_id=AGENTPOD_ID, + request_type="activation", + deployment_receipt_id=DEPLOYMENT_RECEIPT_ID, + agent_machine_id=AGENT_MACHINE_ID, + provider_id=PROVIDER_ID, + expected_status="denied", + root=REPO_ROOT, + ) + expect_status(denied, "denied", "denied-activation") + + missing = resolve_policy_admission( + policies=policies, + agentpod_id=AGENTPOD_ID, + request_type="activation", + deployment_receipt_id=DEPLOYMENT_RECEIPT_ID, + agent_machine_id=AGENT_MACHINE_ID, + provider_id="urn:srcos:agent-machine:inference-provider:unresolved-provider", + allow_missing_stub=True, + decided_at=DECIDED_AT, + root=REPO_ROOT, + ) + expect_status(missing, "missing", "generated-missing-stub") + + by_id = resolve_policy_admission( + policies=policies, + agentpod_id=AGENTPOD_ID, + request_type="activation", + deployment_receipt_id=DEPLOYMENT_RECEIPT_ID, + policy_id="urn:srcos:agent-machine:policy-admission:allowed-loopback-activation", + root=REPO_ROOT, + ) + expect_status(by_id, "allowed", "policy-id") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, RuntimeError) as exc: + print(str(exc), file=sys.stderr) + raise SystemExit(1) from exc From 30297b14c6158e7c3bcf24e47e13daceffb22edd Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 01:36:31 -0400 Subject: [PATCH 6/9] Validate Policy Fabric package import --- scripts/validate-package.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/validate-package.py b/scripts/validate-package.py index cc3e700..a5b12e4 100644 --- a/scripts/validate-package.py +++ b/scripts/validate-package.py @@ -18,6 +18,7 @@ def main() -> int: import agent_machine.cli import agent_machine.evidence import agent_machine.governance + import agent_machine.policy_fabric import agent_machine.release_bundle import agent_machine.supply_chain import agent_machine.renderers.k8s @@ -54,6 +55,8 @@ def main() -> int: raise AssertionError("supply_chain.is_sha256_digest rejected valid digest") if agent_machine.release_bundle.DEFAULT_REPOSITORY != "SourceOS-Linux/agent-machine": raise AssertionError("unexpected release_bundle default repository") + if agent_machine.policy_fabric.DEFAULT_DECIDED_AT != "1970-01-01T00:00:00Z": + raise AssertionError("unexpected policy_fabric default decided_at") if str(default_model_cache_path()) != "/var/lib/agent-machine/models": raise AssertionError("unexpected default model cache path") if str(default_evidence_path()) != "/var/lib/agent-machine/evidence": From fdf10c5812fe177ca23e71f537c76f4a227649d6 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 01:37:25 -0400 Subject: [PATCH 7/9] Delegate policy resolution from bootstrap CLI --- bin/agent-machine | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/agent-machine b/bin/agent-machine index 1f34a33..9bfb7f3 100644 --- a/bin/agent-machine +++ b/bin/agent-machine @@ -58,7 +58,8 @@ Usage: agent-machine render receipt [--pretty] [--artifact-path ] agent-machine render quadlet [--compare ] agent-machine render k8s [--compare ] - agent-machine activate evaluate --deployment-receipt-id [--storage-receipt-dir ] [--storage-receipt-file ] [--pretty] + agent-machine policy resolve --policy-dir --deployment-receipt-id [--expected-status allowed] + agent-machine activate evaluate [policy.json] --deployment-receipt-id [--policy-dir ] [--storage-receipt-dir ] [--pretty] This is the bootstrap CLI. It is intentionally conservative: it discovers host/runtime hints and never emits secrets, raw prompts, raw KV-cache contents, or credentials. EOF @@ -275,6 +276,10 @@ case "$COMMAND" in shift || true delegate_python_cli render "$@" ;; + policy) + shift || true + delegate_python_cli policy "$@" + ;; activate) shift || true delegate_python_cli activate "$@" From 10550f2d2b87b066c6212e84fb929d22d012819d Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 01:38:42 -0400 Subject: [PATCH 8/9] Document local PolicyAdmission resolver --- .../policy-admission-resolution.md | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 docs/architecture/policy-admission-resolution.md diff --git a/docs/architecture/policy-admission-resolution.md b/docs/architecture/policy-admission-resolution.md new file mode 100644 index 0000000..81e5766 --- /dev/null +++ b/docs/architecture/policy-admission-resolution.md @@ -0,0 +1,120 @@ +# PolicyAdmission Resolution + +Agent Machine now has a local Policy Fabric admission resolver for bootstrap and dry-run activation flows. This is a deterministic local-store resolver, not a production Policy Fabric client. + +## Purpose + +Activation evaluation should not require callers to manually pick a `PolicyAdmission` file forever. The resolver lets Agent Machine scan explicit files or directories and select the matching `PolicyAdmission` by request shape. + +The resolver supports: + +- explicit policy files; +- local policy store directories; +- request matching by AgentPod ID, request type, deployment receipt ID, AgentMachine ID, and provider ID; +- disambiguation by policy ID or expected status; +- fail-closed missing-admission stub generation; +- semantic validation through governance rules. + +## Current commands + +Resolve a policy decision from a local store: + +```bash +agent-machine policy resolve \ + examples/local-podman-llama-cpp.agent-pod.json \ + --policy-dir examples \ + --expected-status allowed \ + --deployment-receipt-id urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + --agent-machine-id urn:srcos:agent-machine:m2-asahi-local \ + --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp \ + --pretty +``` + +Evaluate activation using a resolved policy from a local store: + +```bash +agent-machine activate evaluate \ + examples/local-podman-llama-cpp.agent-pod.json \ + examples/agent-registry-grant.active-activation.json \ + --policy-dir examples \ + --expected-status allowed \ + --deployment-receipt-id urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + --agent-machine-id urn:srcos:agent-machine:m2-asahi-local \ + --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp \ + --storage-receipt-dir examples \ + --decided-at 2026-05-04T12:51:00Z \ + --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed \ + --pretty +``` + +Evaluate activation with an explicit policy file: + +```bash +agent-machine activate evaluate \ + examples/local-podman-llama-cpp.agent-pod.json \ + examples/policy-admission.allowed-activation.json \ + examples/agent-registry-grant.active-activation.json \ + --deployment-receipt-id urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + --storage-receipt-dir examples \ + --pretty +``` + +## Fail-closed behavior + +If no matching policy is found and missing stubs are allowed, the resolver emits a synthetic `PolicyAdmission` with: + +```text +decision.status = missing +decision.authorizationGranted = false +``` + +That stub denies activation-sensitive scopes and causes `ActivationDecision` to fail closed. + +If `--no-missing-stub` is provided and no policy matches, resolution fails. + +## Ambiguity behavior + +If multiple policy decisions match the request, resolution fails unless the caller disambiguates with: + +```text +--policy-id +``` + +or: + +```text +--expected-status allowed|denied|missing|not-required|unknown +``` + +This is deliberate. Silent selection among conflicting policy decisions would be unsafe. + +## Bootstrap boundary + +This resolver is not a production Policy Fabric client. It does not: + +- call a remote Policy Fabric endpoint; +- verify policy bundle signatures; +- resolve revocations online; +- evaluate policy source code; +- prove freshness beyond the contents of local artifacts. + +It is the bootstrap adapter shape that a real Policy Fabric client can replace. + +## Validation + +Policy resolver validation is part of: + +```bash +make validate-policy-fabric +make validate +``` + +The validation path checks: + +- directory scanning; +- schema and semantic validation; +- ambiguity rejection; +- allowed/denied disambiguation; +- generated missing stubs; +- CLI policy resolution; +- activation evaluation using a resolved local policy. From 92ead711ddfdd9d1322732dc303a02f0d61fb021 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 01:41:17 -0400 Subject: [PATCH 9/9] Link PolicyAdmission resolution docs --- docs/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.md b/docs/index.md index 71f869b..6f9f25d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,7 @@ Agent Machine is a bootstrap runtime-control substrate for SourceOS agent worklo | [AgentPod manifest generation](architecture/agentpod-manifest-generation.md) | Contract-to-plan-to-manifest generation rules. | | [Deployment safety](architecture/deployment-safety.md) | Skeleton-vs-production manifest rules and safety gates. | | [Receipt chain](architecture/receipt-chain.md) | AgentPod source to plan, manifest, receipt, policy, registry, and AgentPlane evidence. | +| [PolicyAdmission resolution](architecture/policy-admission-resolution.md) | Local Policy Fabric admission resolver and fail-closed missing-decision behavior. | | [Image digest pinning and provenance](architecture/image-digest-pinning-and-provenance.md) | Supply-chain strict-mode gate for digest-pinned release-candidate artifacts. | | [Release evidence bundle](architecture/release-evidence-bundle.md) | Deterministic validation/source/inventory/render/supply-chain/readiness bundle. | | [Signed release bundle envelope](architecture/signed-release-bundle-envelope.md) | Signing envelope contract for release evidence bundles. | @@ -93,9 +94,11 @@ validate-quadlet validate-render validate-evidence validate-governance +validate-policy-fabric validate-activation validate-supply-chain validate-release-bundle +validate-sourceos-projections validate-package validate-cli validate-formula