diff --git a/Makefile b/Makefile index 5f86a3c..366b048 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +.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 .PHONY: validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts doctor probe PYTHON ?= python3 @@ -22,6 +23,7 @@ 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-policy-fabric 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-agent-registry validate-superconscious-runtime-plan validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts validate-json: @@ -69,7 +71,6 @@ validate-superconscious-runtime-plan: validate-superconscious-runtime-plan: $(PYTHON) scripts/validate-superconscious-runtime-plan.py - 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 examples --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-evaluate-activation-allowed.json diff --git a/bin/agent-machine b/bin/agent-machine index 1b9f8c6..9f03341 100755 --- a/bin/agent-machine +++ b/bin/agent-machine @@ -69,6 +69,7 @@ Usage: agent-machine render quadlet [--compare ] agent-machine render k8s [--compare ] 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] agent-machine registry resolve --grant-dir --requested-agent-identity-ref --session-ref [--expected-status active] agent-machine activate evaluate [policy.json] --deployment-receipt-id [--policy-dir ] [--storage-receipt-dir ] [--pretty] agent-machine steer stub-response [--status not_configured|noop] [--pretty] diff --git a/src/agent_machine/cli.py b/src/agent_machine/cli.py index e45a246..6049b11 100644 --- a/src/agent_machine/cli.py +++ b/src/agent_machine/cli.py @@ -215,6 +215,98 @@ 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, + ) + storage_receipt_refs = list(args.storage_receipt_ref or []) + 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=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, + decided_at=args.decided_at, + decision_id=args.decision_id, + root=REPO_ROOT, + ) + activation.validate_activation_decision_payload(decision, REPO_ROOT) + if args.pretty: + print(json.dumps(decision, indent=2, sort_keys=True)) + else: + print(json.dumps(decision, sort_keys=True, separators=(",", ":"))) 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) print(json.dumps(policy, indent=2 if args.pretty else None, sort_keys=True)) return 0 @@ -394,6 +486,7 @@ def build_parser() -> argparse.ArgumentParser: render_k8s.add_argument("agentpod_json", type=Path) 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") @@ -428,6 +521,9 @@ def build_parser() -> argparse.ArgumentParser: 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") add_registry_resolver_args(activate_evaluate) activate_evaluate.add_argument("--storage-receipt-ref", action="append", default=[]) activate_evaluate.add_argument("--storage-receipt-file", action="append", type=Path, default=[])