Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +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-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula doctor probe

PYTHON ?= python3
RUBY ?= ruby
Expand All @@ -16,12 +16,13 @@ FAIL_POLICY := examples/policy-admission.missing.json
FAIL_GRANT := examples/agent-registry-grant.missing.json
RECEIPT_DIR := examples
POLICY_DIR := examples
GRANT_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-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-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula

validate-json:
$(PYTHON) scripts/validate-json.py
Expand Down Expand Up @@ -58,11 +59,17 @@ validate-policy-fabric:
$(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-agent-registry:
$(PYTHON) scripts/validate-agent-registry.py
$(PYTHON) scripts/resolve-agent-registry-grant.py $(LOCAL_AGENTPOD) --grant-dir $(GRANT_DIR) --grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation --requested-agent-identity-ref urn:srcos:agent:local-inference-provider --session-ref urn:srcos:session:local-bootstrap --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --pretty >/tmp/agent-machine-registry-resolve-active.json
$(PYCLI) registry resolve $(LOCAL_AGENTPOD) --grant-dir $(GRANT_DIR) --grant-id urn:srcos:agent-machine:agent-registry-grant:active-render-only --requested-agent-identity-ref urn:srcos:agent:local-inference-provider --session-ref urn:srcos:session:local-bootstrap --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --pretty >/tmp/agent-machine-pycli-registry-resolve-render-only.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
$(PYCLI) activate evaluate $(LOCAL_AGENTPOD) $(READY_POLICY) --grant-dir $(GRANT_DIR) --grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --requested-agent-identity-ref urn:srcos:agent:local-inference-provider --session-ref urn:srcos:session:local-bootstrap --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-grant-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:
Expand Down
7 changes: 6 additions & 1 deletion bin/agent-machine
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ Usage:
agent-machine render quadlet <agentpod.json> [--compare <file>]
agent-machine render k8s <agentpod.json> [--compare <file>]
agent-machine policy resolve <agentpod.json> --policy-dir <dir> --deployment-receipt-id <id> [--expected-status allowed]
agent-machine activate evaluate <agentpod.json> [policy.json] <grant.json> --deployment-receipt-id <id> [--policy-dir <dir>] [--storage-receipt-dir <dir>] [--pretty]
agent-machine registry resolve <agentpod.json> --grant-dir <dir> --requested-agent-identity-ref <ref> --session-ref <ref> [--expected-status active]
agent-machine activate evaluate <agentpod.json> [policy.json] [grant.json] --deployment-receipt-id <id> [--policy-dir <dir>] [--grant-dir <dir>] [--storage-receipt-dir <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
Expand Down Expand Up @@ -280,6 +281,10 @@ case "$COMMAND" in
shift || true
delegate_python_cli policy "$@"
;;
registry)
shift || true
delegate_python_cli registry "$@"
;;
activate)
shift || true
delegate_python_cli activate "$@"
Expand Down
144 changes: 144 additions & 0 deletions docs/architecture/agent-registry-grant-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# AgentRegistryGrant Resolution

Agent Machine now has a local Agent Registry grant resolver for bootstrap and dry-run activation flows. This is a deterministic local-store resolver, not a production Agent Registry client.

## Purpose

Activation evaluation should not require callers to manually select an `AgentRegistryGrant` forever. The resolver lets Agent Machine scan explicit files or directories and select the matching grant by request shape.

The resolver supports:

- explicit grant files;
- local grant store directories;
- request matching by AgentPod ID, requested agent identity, session, optional AgentMachine, workroom, and topic;
- disambiguation by grant ID or expected grant status;
- fail-closed missing-grant stub generation;
- semantic validation through governance rules;
- activation evaluation using a locally resolved grant.

## Current commands

Resolve a grant from a local store by explicit grant ID:

```bash
agent-machine registry resolve \
examples/local-podman-llama-cpp.agent-pod.json \
--grant-dir examples \
--grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--agent-machine-id urn:srcos:agent-machine:m2-asahi-local \
--pretty
```

Resolve a grant by status and request shape:

```bash
agent-machine registry resolve \
examples/local-podman-llama-cpp.agent-pod.json \
--grant-dir examples \
--expected-status revoked \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--agent-machine-id urn:srcos:agent-machine:m2-asahi-local \
--workroom-ref urn:srcos:workroom:local-default \
--topic-ref urn:srcos:topic:agent-machine \
--pretty
```

Evaluate activation using a resolved registry grant:

```bash
agent-machine activate evaluate \
examples/local-podman-llama-cpp.agent-pod.json \
examples/policy-admission.allowed-activation.json \
--grant-dir examples \
--grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation \
--deployment-receipt-id urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--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
```

## Fail-closed behavior

If no matching grant is found and missing stubs are allowed, the resolver emits a synthetic `AgentRegistryGrant` with:

```text
grant.status = missing
grant.authorizationGranted = false
grant.revocationStatus = unavailable
```

That stub denies requested scopes and causes `ActivationDecision` to fail closed.

If `--no-missing-stub` is provided and no grant matches, resolution fails.

## Ambiguity behavior

If multiple grants match the request, resolution fails unless the caller disambiguates with:

```text
--grant-id <id>
```

or:

```text
--expected-status active|revoked|expired|denied|missing|unknown
```

This is deliberate. Silent selection among conflicting grants would be unsafe.

## Scope behavior

The resolver can construct a requested scope for generated missing stubs from CLI arguments:

```text
--provider-id
--model-ref
--tool-ref
--storage-scope-ref
--evidence-scope-ref
```

For resolved real grants, schema and governance validation ensure `scope.allowed` does not exceed `request.requestedScope`.

## Bootstrap boundary

This resolver is not a production Agent Registry client. It does not:

- call a remote Agent Registry endpoint;
- verify grant signatures;
- resolve revocations online;
- prove session freshness;
- bind identity to live proof-of-life;
- prove grant freshness beyond the contents of local artifacts.

It is the bootstrap adapter shape that a real Agent Registry client can replace.

## Validation

Agent Registry resolver validation is part of:

```bash
make validate-agent-registry
make validate
```

The validation path checks:

- directory scanning;
- schema and semantic validation;
- ambiguity rejection;
- active activation grant resolution;
- revoked grant resolution;
- generated missing stubs;
- grant-id disambiguation;
- CLI registry resolution;
- activation evaluation using a resolved local grant.
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Agent Machine is a bootstrap runtime-control substrate for SourceOS agent worklo
| [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. |
| [AgentRegistryGrant resolution](architecture/agent-registry-grant-resolution.md) | Local Agent Registry grant resolver and fail-closed missing-grant 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. |
Expand Down Expand Up @@ -95,6 +96,7 @@ validate-render
validate-evidence
validate-governance
validate-policy-fabric
validate-agent-registry
validate-activation
validate-supply-chain
validate-release-bundle
Expand All @@ -114,7 +116,7 @@ Current blockers:
- real image digest pinning/provenance from trusted build artifacts;
- real release bundle signature verification;
- real Policy Fabric client or endpoint;
- real Agent Registry grant resolver;
- real Agent Registry client or endpoint;
- real AgentPlane evidence submission/staging client;
- local LVM provisioning/probe implementation;
- TopoLVM runtime integration beyond skeleton manifests;
Expand Down
17 changes: 17 additions & 0 deletions scripts/resolve-agent-registry-grant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""Resolve AgentRegistryGrant from local Agent Registry 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.agent_registry import main # noqa: E402

if __name__ == "__main__":
raise SystemExit(main())
128 changes: 128 additions & 0 deletions scripts/validate-agent-registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Validate local Agent Registry grant 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.agent_registry import ( # noqa: E402
load_agent_registry_grants,
requested_scope_from_inputs,
resolve_agent_registry_grant,
validate_agent_registry_grant_payload,
)

AGENTPOD_ID = "urn:srcos:agent-machine:agent-pod:local-podman-llama-cpp"
AGENT_MACHINE_ID = "urn:srcos:agent-machine:m2-asahi-local"
IDENTITY_REF = "urn:srcos:agent:local-inference-provider"
SESSION_REF = "urn:srcos:session:local-bootstrap"
WORKROOM_REF = "urn:srcos:workroom:local-default"
TOPIC_REF = "urn:srcos:topic:agent-machine"
PROVIDER_ID = "urn:srcos:agent-machine:inference-provider:asahi-llama-cpp"
ISSUED_AT = "2026-05-04T12:51:00Z"


def expect_status(grant: dict, expected: str, label: str) -> None:
observed = grant.get("grant", {}).get("status")
if observed != expected:
raise AssertionError(f"{label}: expected status={expected}, observed {observed}")
validate_agent_registry_grant_payload(grant, REPO_ROOT, source=label)
print(f"VALID registry resolve {label} status={expected}")


def expect_ambiguous(grants: list[dict]) -> None:
try:
resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
allow_missing_stub=False,
root=REPO_ROOT,
)
except AssertionError as exc:
if "ambiguous AgentRegistryGrant" not in str(exc):
raise
print("VALID registry resolve ambiguous grant requires disambiguation")
return
raise AssertionError("expected ambiguous AgentRegistryGrant resolution to fail")


def main() -> int:
grants = load_agent_registry_grants(directories=[REPO_ROOT / "examples"], root=REPO_ROOT)
if len(grants) < 4:
raise AssertionError("expected at least four AgentRegistryGrant examples")

expect_ambiguous(grants)

active_activation = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
grant_id="urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation",
root=REPO_ROOT,
)
expect_status(active_activation, "active", "active-activation")

revoked = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
expected_status="revoked",
root=REPO_ROOT,
)
expect_status(revoked, "revoked", "revoked")

missing = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref="urn:srcos:agent:unresolved-provider",
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
allow_missing_stub=True,
requested_scope=requested_scope_from_inputs(
provider_id=PROVIDER_ID,
tool_refs=["urn:srcos:tool:start-provider", "urn:srcos:tool:mount-cache"],
),
issued_at=ISSUED_AT,
root=REPO_ROOT,
)
expect_status(missing, "missing", "generated-missing-stub")

by_id = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
grant_id="urn:srcos:agent-machine:agent-registry-grant:active-render-only",
root=REPO_ROOT,
)
expect_status(by_id, "active", "grant-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
3 changes: 3 additions & 0 deletions scripts/validate-package.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
def main() -> int:
import agent_machine
import agent_machine.activation
import agent_machine.agent_registry
import agent_machine.cli
import agent_machine.evidence
import agent_machine.governance
Expand Down Expand Up @@ -57,6 +58,8 @@ def main() -> int:
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 agent_machine.agent_registry.DEFAULT_ISSUED_AT != "1970-01-01T00:00:00Z":
raise AssertionError("unexpected agent_registry default issued_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":
Expand Down
Loading
Loading