diff --git a/Makefile b/Makefile index 6f87b9c..9e6fb2c 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-package validate-cli validate-formula doctor probe +.PHONY: validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-activation validate-supply-chain validate-release-bundle validate-package validate-cli validate-formula doctor probe PYTHON ?= python3 RUBY ?= ruby @@ -20,7 +20,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-activation validate-supply-chain validate-package validate-cli validate-formula +validate: validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-activation validate-supply-chain validate-release-bundle validate-package validate-cli validate-formula validate-json: $(PYTHON) scripts/validate-json.py @@ -62,6 +62,10 @@ validate-supply-chain: $(PYTHON) scripts/validate-supply-chain.py $(PYMOD) agent_machine.supply_chain $(PINNED_AGENTPOD) --strict +validate-release-bundle: + $(PYTHON) scripts/validate-release-bundle.py + $(PYTHON) scripts/generate-release-evidence.py --pretty >/tmp/agent-machine-release-evidence-bundle.json + validate-package: $(PYTHON) scripts/validate-package.py diff --git a/contracts/release-evidence-bundle.schema.json b/contracts/release-evidence-bundle.schema.json new file mode 100644 index 0000000..10e5052 --- /dev/null +++ b/contracts/release-evidence-bundle.schema.json @@ -0,0 +1,142 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:srcos:agent-machine:schema:release-evidence-bundle:v0.1.0", + "title": "ReleaseEvidenceBundle", + "description": "Secret-free release evidence bundle tying together validation proof, commit identity, schema inventory, rendered artifact digests, supply-chain posture, and known blockers.", + "type": "object", + "additionalProperties": false, + "required": [ + "specVersion", + "id", + "kind", + "release", + "source", + "validation", + "inventories", + "renderedArtifacts", + "supplyChain", + "readiness", + "knownBlockers", + "receiptSafety", + "generatedAt" + ], + "properties": { + "specVersion": { "type": "string", "const": "0.1.0" }, + "id": { + "type": "string", + "pattern": "^urn:srcos:agent-machine:release-evidence-bundle:[a-z0-9][a-z0-9-]*$" + }, + "kind": { "type": "string", "const": "ReleaseEvidenceBundle" }, + "release": { + "type": "object", + "additionalProperties": false, + "required": ["name", "maturity", "productionReady"], + "properties": { + "name": { "type": "string" }, + "maturity": { "type": "string", "enum": ["prototype", "bootstrap-ready", "release-candidate", "production-blocked", "production-ready"] }, + "productionReady": { "type": "boolean" }, + "notes": { "type": "array", "items": { "type": "string" } } + } + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": ["repository", "branch", "commitSha"], + "properties": { + "repository": { "type": "string" }, + "branch": { "type": "string" }, + "commitSha": { "type": "string", "pattern": "^[a-f0-9]{40}$" }, + "pullRequest": { "type": ["integer", "null"], "minimum": 1 } + } + }, + "validation": { + "type": "object", + "additionalProperties": false, + "required": ["canonicalCommand", "status", "workflowRunId", "workflowJobName"], + "properties": { + "canonicalCommand": { "type": "string" }, + "status": { "type": "string", "enum": ["passed", "failed", "unknown", "not-run"] }, + "workflowRunId": { "type": ["integer", "null"], "minimum": 1 }, + "workflowJobName": { "type": ["string", "null"] }, + "validatedAt": { "type": ["string", "null"] } + } + }, + "inventories": { + "type": "object", + "additionalProperties": false, + "required": ["schemas", "examples", "docs"], + "properties": { + "schemas": { "$ref": "#/$defs/digestedFileList" }, + "examples": { "$ref": "#/$defs/digestedFileList" }, + "docs": { "$ref": "#/$defs/digestedFileList" } + } + }, + "renderedArtifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "artifactKind", "digest"], + "properties": { + "name": { "type": "string" }, + "artifactKind": { "type": "string" }, + "path": { "type": ["string", "null"] }, + "digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } + } + } + }, + "supplyChain": { + "type": "object", + "additionalProperties": false, + "required": ["strictModeAvailable", "strictExamples", "mutableBootstrapExamples"], + "properties": { + "strictModeAvailable": { "type": "boolean" }, + "strictExamples": { "$ref": "#/$defs/digestedFileList" }, + "mutableBootstrapExamples": { "$ref": "#/$defs/digestedFileList" } + } + }, + "readiness": { + "type": "object", + "additionalProperties": false, + "required": ["bootstrapReady", "productionReady", "releaseGateRef"], + "properties": { + "bootstrapReady": { "type": "boolean" }, + "productionReady": { "type": "boolean" }, + "releaseGateRef": { "type": "string" }, + "statusRef": { "type": ["string", "null"] } + } + }, + "knownBlockers": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "receiptSafety": { + "type": "object", + "additionalProperties": false, + "required": ["includeRawContent", "rawPromptContentIncluded", "rawKvCacheContentIncluded", "secretValuesIncluded", "privateMemoryIncluded"], + "properties": { + "includeRawContent": { "type": "boolean", "const": false }, + "rawPromptContentIncluded": { "type": "boolean", "const": false }, + "rawKvCacheContentIncluded": { "type": "boolean", "const": false }, + "secretValuesIncluded": { "type": "boolean", "const": false }, + "privateMemoryIncluded": { "type": "boolean", "const": false } + } + }, + "generatedAt": { "type": "string" } + }, + "$defs": { + "digestedFileList": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["path", "digest"], + "properties": { + "path": { "type": "string" }, + "digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } + } + } + } + } +} diff --git a/docs/architecture/release-evidence-bundle.md b/docs/architecture/release-evidence-bundle.md new file mode 100644 index 0000000..beb65ac --- /dev/null +++ b/docs/architecture/release-evidence-bundle.md @@ -0,0 +1,101 @@ +# Release Evidence Bundle + +Agent Machine release evidence bundles are deterministic, secret-free summaries of what was validated, what source revision was evaluated, which contract/example/doc inventories were present, which rendered artifacts were derived, what supply-chain posture was available, and which blockers remain. + +A bundle is not a signature by itself. It is the structured payload that future signing, transparency, and release-promotion flows can sign and publish. + +## Decision + +Agent Machine defines a `ReleaseEvidenceBundle` contract for bootstrap and release-candidate evidence. + +The bundle records: + +- repository identity; +- branch and commit SHA; +- optional pull request number; +- validation command and workflow run ID; +- schema inventory with file digests; +- example inventory with file digests; +- documentation inventory with file digests; +- rendered artifact digests; +- supply-chain strict-mode availability; +- readiness state; +- known blockers; +- receipt-safety flags. + +## Current implementation + +Implemented now: + +- `contracts/release-evidence-bundle.schema.json`; +- `examples/release-evidence-bundle.bootstrap.json`; +- `src/agent_machine/release_bundle.py`; +- `scripts/generate-release-evidence.py`; +- `scripts/validate-release-bundle.py`; +- `make validate-release-bundle`. + +## Validation commands + +Generate a bundle from the current checkout: + +```bash +python3 scripts/generate-release-evidence.py --pretty +``` + +Validate bundle example and generated output: + +```bash +python3 scripts/validate-release-bundle.py +``` + +Full validation: + +```bash +make validate +``` + +## Bootstrap behavior + +The bootstrap bundle is intentionally secret-free and production-blocked. It may report `validation.status=unknown` when generated outside CI, but it still validates its schema, inventories, rendered artifact digests, supply-chain posture, known blockers, and receipt-safety posture. + +## Release-candidate behavior + +A release candidate should set: + +```text +validation.status = passed +validation.workflowRunId = +source.commitSha = +source.pullRequest = +``` + +It must also have no unresolved release-candidate blockers for the relevant maturity level. + +## Production blockers + +The current bundle deliberately retains production blockers, including: + +- main-branch CI visibility and branch protection policy; +- real image signature/provenance verification; +- real Policy Fabric client or endpoint; +- real Agent Registry grant resolver; +- real AgentPlane evidence submission or staging client; +- local LVM provisioning/probe implementation; +- TopoLVM runtime integration beyond skeleton manifests; +- provider discovery and controlled provider activation; +- M2 Asahi host measurement and provider readiness data; +- signed release evidence bundle; +- rollback, teardown, and wipe workflows. + +## Future hardening + +Future release bundle work should add: + +- signed bundle envelopes; +- provenance attestation references; +- transparency-log submission references; +- generated SBOM references; +- real image signature verification result; +- branch protection status; +- release artifact digests; +- rollback and wipe evidence references. diff --git a/docs/index.md b/docs/index.md index 6b3f78a..e59526e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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. | | [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. | | [Runtime package layout](architecture/runtime-package-layout.md) | Migration from loose scripts to `src/agent_machine/` package modules. | | [Homebrew Python dependencies](architecture/homebrew-python-dependencies.md) | Current dependency strategy for render/evaluation commands. | | [Local LVM and TopoLVM profile](architecture/local-lvm-and-topolvm-profile.md) | Local and Kubernetes storage/cache/evidence profile. | @@ -71,6 +72,7 @@ Important contract families: | `PolicyAdmission` | Policy Fabric admission decision/stub. | | `AgentRegistryGrant` | Agent Registry grant/stub. | | `ActivationDecision` | Final dry-run activation decision. | +| `ReleaseEvidenceBundle` | Secret-free release validation/source/inventory/render/supply-chain/readiness evidence. | ## Validation @@ -91,6 +93,7 @@ validate-evidence validate-governance validate-activation validate-supply-chain +validate-release-bundle validate-package validate-cli validate-formula @@ -111,5 +114,5 @@ Current blockers: - TopoLVM runtime integration beyond skeleton manifests; - provider discovery and controlled provider activation implementation; - M2 Asahi host measurement/provider readiness data; -- release evidence bundle; +- signed release evidence bundle; - rollback, teardown, and wipe workflows. diff --git a/examples/release-evidence-bundle.bootstrap.json b/examples/release-evidence-bundle.bootstrap.json new file mode 100644 index 0000000..69ce87b --- /dev/null +++ b/examples/release-evidence-bundle.bootstrap.json @@ -0,0 +1,89 @@ +{ + "specVersion": "0.1.0", + "id": "urn:srcos:agent-machine:release-evidence-bundle:bootstrap-v0-example", + "kind": "ReleaseEvidenceBundle", + "release": { + "name": "agent-machine-bootstrap-v0", + "maturity": "bootstrap-ready", + "productionReady": false, + "notes": [ + "Example bundle shape only.", + "The generator produces a complete bundle from repository contents." + ] + }, + "source": { + "repository": "SourceOS-Linux/agent-machine", + "branch": "main", + "commitSha": "0cba4c4774982ce4705c27f7c4ec1c0bcdfb0725", + "pullRequest": 11 + }, + "validation": { + "canonicalCommand": "make validate", + "status": "passed", + "workflowRunId": 25327418937, + "workflowJobName": "Validate contracts, examples, CLI, formula, and docs", + "validatedAt": "2026-05-04T15:21:00Z" + }, + "inventories": { + "schemas": [ + { + "path": "contracts/release-evidence-bundle.schema.json", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ], + "examples": [ + { + "path": "examples/local-podman-llama-cpp.agent-pod.json", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ], + "docs": [ + { + "path": "BOOTSTRAP_STATUS.md", + "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + } + ] + }, + "renderedArtifacts": [ + { + "name": "local-agentpod-plan", + "artifactKind": "AgentPodDeploymentPlan", + "path": null, + "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + } + ], + "supplyChain": { + "strictModeAvailable": true, + "strictExamples": [ + { + "path": "examples/local-podman-llama-cpp.pinned.agent-pod.json", + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + } + ], + "mutableBootstrapExamples": [ + { + "path": "examples/local-podman-llama-cpp.agent-pod.json", + "digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + ] + }, + "readiness": { + "bootstrapReady": true, + "productionReady": false, + "releaseGateRef": "docs/architecture/world-class-release-gate.md", + "statusRef": "BOOTSTRAP_STATUS.md" + }, + "knownBlockers": [ + "main-branch-ci-visibility-and-branch-protection-policy", + "real-policy-fabric-client-or-endpoint", + "real-agentplane-evidence-submission-or-staging-client" + ], + "receiptSafety": { + "includeRawContent": false, + "rawPromptContentIncluded": false, + "rawKvCacheContentIncluded": false, + "secretValuesIncluded": false, + "privateMemoryIncluded": false + }, + "generatedAt": "2026-05-04T15:21:00Z" +} diff --git a/scripts/generate-release-evidence.py b/scripts/generate-release-evidence.py new file mode 100644 index 0000000..faa7f53 --- /dev/null +++ b/scripts/generate-release-evidence.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +"""Generate an Agent Machine ReleaseEvidenceBundle.""" + +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.release_bundle import main # noqa: E402 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate-package.py b/scripts/validate-package.py index b980768..1c6f31f 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.release_bundle import agent_machine.supply_chain import agent_machine.renderers.k8s import agent_machine.renderers.plan @@ -38,6 +39,7 @@ def main() -> int: "AgentPlaneRuntimeEvidence", "AgentRegistryGrant", "PolicyAdmission", + "ReleaseEvidenceBundle", "StorageReceipt", } missing = sorted(required_kinds - set(mapping)) @@ -49,6 +51,8 @@ def main() -> int: raise AssertionError("stable_text_digest must return sha256: prefixed digest") if not agent_machine.supply_chain.is_sha256_digest("sha256:" + "a" * 64): 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 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": diff --git a/scripts/validate-release-bundle.py b/scripts/validate-release-bundle.py new file mode 100644 index 0000000..e113906 --- /dev/null +++ b/scripts/validate-release-bundle.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Validate ReleaseEvidenceBundle examples and generated output.""" + +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.contracts import load_json # noqa: E402 +from agent_machine.release_bundle import ( # noqa: E402 + DEFAULT_COMMIT_SHA, + DEFAULT_GENERATED_AT, + DEFAULT_REPOSITORY, + generate_release_bundle, + validate_release_bundle, +) + +EXAMPLE = REPO_ROOT / "examples" / "release-evidence-bundle.bootstrap.json" + + +def main() -> int: + example = load_json(EXAMPLE) + validate_release_bundle(example, REPO_ROOT) + print(f"VALID release bundle example {EXAMPLE.relative_to(REPO_ROOT)}") + + generated = generate_release_bundle( + root=REPO_ROOT, + repository=DEFAULT_REPOSITORY, + branch="main", + commit_sha=DEFAULT_COMMIT_SHA, + pull_request=None, + workflow_run_id=None, + validation_status="unknown", + workflow_job_name="Validate contracts, examples, CLI, formula, and docs", + generated_at=DEFAULT_GENERATED_AT, + validated_at=None, + ) + validate_release_bundle(generated, REPO_ROOT) + if generated["kind"] != "ReleaseEvidenceBundle": + raise AssertionError("generated bundle kind mismatch") + if generated["receiptSafety"]["secretValuesIncluded"] is not False: + raise AssertionError("generated bundle must be secret-free") + if not generated["inventories"]["schemas"]: + raise AssertionError("generated bundle missing schema inventory") + if not generated["renderedArtifacts"]: + raise AssertionError("generated bundle missing rendered artifacts") + print("VALID generated release evidence bundle") + 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 diff --git a/src/agent_machine/contracts.py b/src/agent_machine/contracts.py index 9c60d59..b5f6f7e 100644 --- a/src/agent_machine/contracts.py +++ b/src/agent_machine/contracts.py @@ -55,6 +55,7 @@ def schema_by_kind(root: Path | None = None) -> dict[str, Path]: "DeploymentReceipt": base / "deployment-receipt.schema.json", "InferenceProvider": base / "inference-provider.schema.json", "PolicyAdmission": base / "policy-admission.schema.json", + "ReleaseEvidenceBundle": base / "release-evidence-bundle.schema.json", "StorageReceipt": base / "storage-receipt.schema.json", } diff --git a/src/agent_machine/release_bundle.py b/src/agent_machine/release_bundle.py new file mode 100644 index 0000000..13774b6 --- /dev/null +++ b/src/agent_machine/release_bundle.py @@ -0,0 +1,259 @@ +"""Release evidence bundle generator for Agent Machine. + +The release evidence bundle is a deterministic, secret-free summary of the +bootstrap/runtime-control substrate: validation proof, source identity, +contract/example/doc inventories, rendered artifact digests, supply-chain +posture, readiness, and known blockers. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +from agent_machine.activation import evaluate_activation +from agent_machine.contracts import load_json, schema_by_kind +from agent_machine.digest import stable_digest, stable_text_digest +from agent_machine.paths import repo_root_from_file +from agent_machine.renderers import k8s as k8s_renderer +from agent_machine.renderers import plan as plan_renderer +from agent_machine.renderers import quadlet as quadlet_renderer + +DEFAULT_GENERATED_AT = "1970-01-01T00:00:00Z" +DEFAULT_COMMIT_SHA = "0000000000000000000000000000000000000000" +DEFAULT_REPOSITORY = "SourceOS-Linux/agent-machine" + + +def digest_file(path: Path) -> str: + return stable_text_digest(path.read_text(encoding="utf-8")) + + +def digested_files(root: Path, directory: str, pattern: str = "*") -> list[dict[str, str]]: + base = root / directory + if not base.exists(): + return [] + items: list[dict[str, str]] = [] + for path in sorted(base.rglob(pattern)): + if not path.is_file(): + continue + rel = path.relative_to(root).as_posix() + # Avoid circularity: checked-in bundle examples should not define the + # inventory digest of the generated release bundle itself. + if rel.startswith("examples/release-evidence-bundle"): + continue + items.append({"path": rel, "digest": digest_file(path)}) + return items + + +def artifact_digest(value: Any) -> str: + if isinstance(value, str): + return stable_text_digest(value) + return stable_digest(value) + + +def rendered_artifacts(root: Path) -> list[dict[str, str | None]]: + local_agentpod_path = root / "examples" / "local-podman-llama-cpp.agent-pod.json" + k8s_agentpod_path = root / "examples" / "k8s-topolvm.agent-pod.json" + local_agentpod = load_json(local_agentpod_path) + k8s_agentpod = load_json(k8s_agentpod_path) + + local_plan = plan_renderer.render_plan(local_agentpod_path, local_agentpod) + k8s_plan = plan_renderer.render_plan(k8s_agentpod_path, k8s_agentpod) + local_receipt = plan_renderer.render_receipt( + local_agentpod_path, + local_agentpod, + local_plan, + "/tmp/agent-machine-local-agentpod-plan.json", + ) + local_quadlet = quadlet_renderer.render_quadlet(local_agentpod) + k8s_yaml = k8s_renderer.dump_documents(k8s_renderer.render_documents(k8s_agentpod)) + + activation_decision = evaluate_activation( + agentpod=local_agentpod, + policy=load_json(root / "examples" / "policy-admission.allowed-activation.json"), + grant=load_json(root / "examples" / "agent-registry-grant.active-activation.json"), + deployment_receipt_id="urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + storage_receipt_refs=["urn:srcos:agent-machine:storage-receipt:local-lvm-warm-cache"], + storage_receipts=[load_json(root / "examples" / "local-lvm-warm-cache.storage-receipt.json")], + decided_at="2026-05-04T12:51:00Z", + decision_id="urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed", + root=root, + ) + + return [ + {"name": "local-agentpod-plan", "artifactKind": "AgentPodDeploymentPlan", "path": None, "digest": artifact_digest(local_plan)}, + {"name": "k8s-agentpod-plan", "artifactKind": "AgentPodDeploymentPlan", "path": None, "digest": artifact_digest(k8s_plan)}, + {"name": "local-deployment-receipt", "artifactKind": "DeploymentReceipt", "path": None, "digest": artifact_digest(local_receipt)}, + {"name": "local-quadlet", "artifactKind": "QuadletContainer", "path": "deploy/quadlet/agent-machine-llama-cpp.container", "digest": artifact_digest(local_quadlet)}, + {"name": "k8s-yaml", "artifactKind": "KubernetesYaml", "path": "deploy/k8s/llama-cpp-topolvm-pod.yaml", "digest": artifact_digest(k8s_yaml)}, + {"name": "local-activation-decision", "artifactKind": "ActivationDecision", "path": "examples/activation-decision.allowed.json", "digest": artifact_digest(activation_decision)}, + ] + + +def known_production_blockers() -> list[str]: + return sorted( + [ + "main-branch-ci-visibility-and-branch-protection-policy", + "real-image-signature-and-provenance-verification", + "real-policy-fabric-client-or-endpoint", + "real-agent-registry-grant-resolver", + "real-agentplane-evidence-submission-or-staging-client", + "local-lvm-provisioning-and-probe-implementation", + "topolvm-runtime-integration-beyond-skeleton-manifests", + "provider-discovery-and-controlled-provider-activation", + "m2-asahi-host-measurement-and-provider-readiness-data", + "signed-release-evidence-bundle", + "rollback-teardown-and-wipe-workflows", + ] + ) + + +def generate_release_bundle( + *, + root: Path, + repository: str, + branch: str, + commit_sha: str, + pull_request: int | None, + workflow_run_id: int | None, + validation_status: str, + workflow_job_name: str | None, + generated_at: str, + validated_at: str | None, +) -> dict[str, Any]: + return { + "specVersion": "0.1.0", + "id": "urn:srcos:agent-machine:release-evidence-bundle:bootstrap-v0", + "kind": "ReleaseEvidenceBundle", + "release": { + "name": "agent-machine-bootstrap-v0", + "maturity": "bootstrap-ready", + "productionReady": False, + "notes": [ + "Bootstrap dry-run runtime-control substrate only.", + "Does not start providers or mutate privileged runtime directories.", + ], + }, + "source": { + "repository": repository, + "branch": branch, + "commitSha": commit_sha, + "pullRequest": pull_request, + }, + "validation": { + "canonicalCommand": "make validate", + "status": validation_status, + "workflowRunId": workflow_run_id, + "workflowJobName": workflow_job_name, + "validatedAt": validated_at, + }, + "inventories": { + "schemas": digested_files(root, "contracts", "*.json"), + "examples": digested_files(root, "examples", "*.json"), + "docs": digested_files(root, "docs", "*.md"), + }, + "renderedArtifacts": rendered_artifacts(root), + "supplyChain": { + "strictModeAvailable": True, + "strictExamples": [ + { + "path": "examples/local-podman-llama-cpp.pinned.agent-pod.json", + "digest": digest_file(root / "examples" / "local-podman-llama-cpp.pinned.agent-pod.json"), + } + ], + "mutableBootstrapExamples": [ + { + "path": "examples/local-podman-llama-cpp.agent-pod.json", + "digest": digest_file(root / "examples" / "local-podman-llama-cpp.agent-pod.json"), + }, + { + "path": "examples/k8s-topolvm.agent-pod.json", + "digest": digest_file(root / "examples" / "k8s-topolvm.agent-pod.json"), + }, + ], + }, + "readiness": { + "bootstrapReady": True, + "productionReady": False, + "releaseGateRef": "docs/architecture/world-class-release-gate.md", + "statusRef": "BOOTSTRAP_STATUS.md", + }, + "knownBlockers": known_production_blockers(), + "receiptSafety": { + "includeRawContent": False, + "rawPromptContentIncluded": False, + "rawKvCacheContentIncluded": False, + "secretValuesIncluded": False, + "privateMemoryIncluded": False, + }, + "generatedAt": generated_at, + } + + +def validate_release_bundle(bundle: dict[str, Any], root: Path) -> None: + schema = load_json(schema_by_kind(root)["ReleaseEvidenceBundle"]) + 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) + validator_cls.check_schema(schema) + validator = validator_cls(schema) + errors = sorted(validator.iter_errors(bundle), 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("ReleaseEvidenceBundle failed schema validation:\n" + "\n".join(rendered)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Generate Agent Machine release evidence bundle") + parser.add_argument("--repository", default=DEFAULT_REPOSITORY) + parser.add_argument("--branch", default="main") + parser.add_argument("--commit-sha", default=DEFAULT_COMMIT_SHA) + parser.add_argument("--pull-request", type=int) + parser.add_argument("--workflow-run-id", type=int) + parser.add_argument("--validation-status", choices=["passed", "failed", "unknown", "not-run"], default="unknown") + parser.add_argument("--workflow-job-name", default="Validate contracts, examples, CLI, formula, and docs") + parser.add_argument("--generated-at", default=DEFAULT_GENERATED_AT) + parser.add_argument("--validated-at") + parser.add_argument("--pretty", action="store_true") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + root = repo_root_from_file(__file__) + bundle = generate_release_bundle( + root=root, + repository=args.repository, + branch=args.branch, + commit_sha=args.commit_sha, + pull_request=args.pull_request, + workflow_run_id=args.workflow_run_id, + validation_status=args.validation_status, + workflow_job_name=args.workflow_job_name, + generated_at=args.generated_at, + validated_at=args.validated_at, + ) + validate_release_bundle(bundle, root) + if args.pretty: + print(json.dumps(bundle, indent=2, sort_keys=True)) + else: + print(json.dumps(bundle, 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