From f5a83d296a6bd3b78f98972dca9ac02514f737ee Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:05:03 -0400 Subject: [PATCH 1/6] Add Trust Chain supply-chain validation schema --- ...chain-validation-artifact.v0.1.schema.json | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 schemas/trust-chain/supply-chain-validation-artifact.v0.1.schema.json diff --git a/schemas/trust-chain/supply-chain-validation-artifact.v0.1.schema.json b/schemas/trust-chain/supply-chain-validation-artifact.v0.1.schema.json new file mode 100644 index 0000000..76ef1e6 --- /dev/null +++ b/schemas/trust-chain/supply-chain-validation-artifact.v0.1.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.dev/agentplane/trust-chain/supply-chain-validation-artifact.v0.1.schema.json", + "title": "SupplyChainValidationArtifact v0.1", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "artifact_id", "artifact_type", "runtime_asset_ref", "scope", "evidence_refs", "policy_decision_ref", "guardrail_decision_ref", "validation_artifact_ref", "replay_artifact_ref", "runtime_receipt_ref", "decision", "effects", "non_claims"], + "properties": { + "schema_version": {"const": "0.1"}, + "artifact_id": {"type": "string", "pattern": "^agentplane:supply-chain-validation-artifact:"}, + "artifact_type": {"const": "SupplyChainValidationArtifact"}, + "runtime_asset_ref": {"type": "string", "pattern": "^runtime-asset://"}, + "scope": {"type": "object", "additionalProperties": false, "required": ["environment", "risk_tier"], "properties": {"environment": {"enum": ["preview", "staging", "production"]}, "risk_tier": {"enum": ["internal", "enterprise", "regulated_enterprise"]}}}, + "evidence_refs": {"type": "object", "additionalProperties": false, "required": ["sbom_ref", "vex_ref", "lockfile_ref", "signature_ref", "scan_record_ref"], "properties": {"sbom_ref": {"type": ["string", "null"]}, "vex_ref": {"type": ["string", "null"]}, "lockfile_ref": {"type": ["string", "null"]}, "signature_ref": {"type": ["string", "null"]}, "scan_record_ref": {"type": ["string", "null"]}, "promotion_evidence_ref": {"type": ["string", "null"]}, "rollback_evidence_ref": {"type": ["string", "null"]}}}, + "policy_decision_ref": {"type": ["string", "null"]}, + "guardrail_decision_ref": {"type": ["string", "null"]}, + "validation_artifact_ref": {"type": ["string", "null"]}, + "replay_artifact_ref": {"type": ["string", "null"]}, + "runtime_receipt_ref": {"type": ["string", "null"]}, + "decision": {"enum": ["validated", "blocked", "review_required"]}, + "effects": {"type": "object", "additionalProperties": false, "required": ["execution_allowed", "promotion_allowed", "repair_required", "human_review_required"], "properties": {"execution_allowed": {"type": "boolean"}, "promotion_allowed": {"type": "boolean"}, "repair_required": {"type": "boolean"}, "human_review_required": {"type": "boolean"}}}, + "remediation": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["step_id", "authority", "required_before_execution", "instruction"], "properties": {"step_id": {"type": "string"}, "authority": {"type": "string"}, "required_before_execution": {"type": "boolean"}, "instruction": {"type": "string"}}}}, + "non_claims": {"type": "array", "items": {"type": "string"}, "minItems": 1} + } +} From 886404b837fa19cf31e0e7c60a6fc6257cbd854c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:38:08 -0400 Subject: [PATCH 2/6] Add valid Trust Chain supply-chain validation fixture --- .../supply-chain-validation.valid.json | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/fixtures/trust-chain/supply-chain-validation.valid.json diff --git a/tests/fixtures/trust-chain/supply-chain-validation.valid.json b/tests/fixtures/trust-chain/supply-chain-validation.valid.json new file mode 100644 index 0000000..fba68da --- /dev/null +++ b/tests/fixtures/trust-chain/supply-chain-validation.valid.json @@ -0,0 +1,37 @@ +{ + "schema_version": "0.1", + "artifact_id": "agentplane:supply-chain-validation-artifact:runtime-asset-demo-001", + "artifact_type": "SupplyChainValidationArtifact", + "runtime_asset_ref": "runtime-asset://lattice-forge/prophet-python-ml@0.1.0", + "scope": { + "environment": "production", + "risk_tier": "regulated_enterprise" + }, + "evidence_refs": { + "sbom_ref": "sbom://lattice-forge/prophet-python-ml/0.1.0/spdx.json", + "vex_ref": "vex://lattice-forge/prophet-python-ml/0.1.0/vex.json", + "lockfile_ref": "lockfile://lattice-forge/prophet-python-ml/0.1.0/flake.lock", + "signature_ref": "sigstore://lattice-forge/prophet-python-ml/0.1.0/bundle.sigstore", + "scan_record_ref": "scan://lattice-forge/prophet-python-ml/0.1.0/osv-scan.json", + "promotion_evidence_ref": "promotion://lattice-forge/prophet-python-ml/0.1.0/stable", + "rollback_evidence_ref": "rollback://lattice-forge/prophet-python-ml/0.0.1" + }, + "policy_decision_ref": "policy://trust-chain/dependency-admission/regulated-enterprise-production", + "guardrail_decision_ref": "guardrail:decision:trust-chain-runtime-allow-001", + "validation_artifact_ref": "agentplane:validation:runtime-asset-demo-001", + "replay_artifact_ref": "agentplane:replay:runtime-asset-demo-001", + "runtime_receipt_ref": "runtime-receipt://trust-chain/demo-runtime-asset-001", + "decision": "validated", + "effects": { + "execution_allowed": true, + "promotion_allowed": true, + "repair_required": false, + "human_review_required": false + }, + "remediation": [], + "non_claims": [ + "This fixture does not perform live package scanning.", + "This fixture does not claim IBM/Red Hat Lightwell integration.", + "This fixture is a contract example and not a production certification by itself." + ] +} From 246e25f7d6b8c04bc7f3d34ed9150de59c28ffb2 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:39:02 -0400 Subject: [PATCH 3/6] Add blocked Trust Chain supply-chain validation fixture --- .../supply-chain-validation.blocked.json | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/fixtures/trust-chain/supply-chain-validation.blocked.json diff --git a/tests/fixtures/trust-chain/supply-chain-validation.blocked.json b/tests/fixtures/trust-chain/supply-chain-validation.blocked.json new file mode 100644 index 0000000..8a84ed4 --- /dev/null +++ b/tests/fixtures/trust-chain/supply-chain-validation.blocked.json @@ -0,0 +1,49 @@ +{ + "schema_version": "0.1", + "artifact_id": "agentplane:supply-chain-validation-artifact:runtime-asset-demo-002", + "artifact_type": "SupplyChainValidationArtifact", + "runtime_asset_ref": "runtime-asset://lattice-forge/legacy-python-ml@0.0.3", + "scope": { + "environment": "production", + "risk_tier": "regulated_enterprise" + }, + "evidence_refs": { + "sbom_ref": "sbom://lattice-forge/legacy-python-ml/0.0.3/spdx.json", + "vex_ref": null, + "lockfile_ref": "lockfile://lattice-forge/legacy-python-ml/0.0.3/flake.lock", + "signature_ref": "sigstore://lattice-forge/legacy-python-ml/0.0.3/bundle.sigstore", + "scan_record_ref": "scan://lattice-forge/legacy-python-ml/0.0.3/osv-scan.json", + "promotion_evidence_ref": null, + "rollback_evidence_ref": null + }, + "policy_decision_ref": "policy://trust-chain/dependency-admission/regulated-enterprise-production", + "guardrail_decision_ref": "guardrail:decision:trust-chain-runtime-deny-001", + "validation_artifact_ref": null, + "replay_artifact_ref": null, + "runtime_receipt_ref": null, + "decision": "blocked", + "effects": { + "execution_allowed": false, + "promotion_allowed": false, + "repair_required": true, + "human_review_required": true + }, + "remediation": [ + { + "step_id": "patch-runtime-asset", + "authority": "SocioProphet/lattice-forge", + "required_before_execution": true, + "instruction": "Replace or patch the vulnerable RuntimeAsset and emit fresh SBOM, VEX, scan, signature, promotion, and rollback evidence." + }, + { + "step_id": "rerun-agentplane-validation", + "authority": "SocioProphet/agentplane", + "required_before_execution": true, + "instruction": "Run validation and replay after the patched RuntimeAsset is available." + } + ], + "non_claims": [ + "This fixture is expected to remain blocked until required evidence exists.", + "This fixture does not perform live package scanning." + ] +} From e8c778da0171f54653dce59c65281c5d7f83a42a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:39:54 -0400 Subject: [PATCH 4/6] Add Trust Chain supply-chain validation tool --- ...ate_trust_chain_supply_chain_validation.py | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tools/validate_trust_chain_supply_chain_validation.py diff --git a/tools/validate_trust_chain_supply_chain_validation.py b/tools/validate_trust_chain_supply_chain_validation.py new file mode 100644 index 0000000..da28bfb --- /dev/null +++ b/tools/validate_trust_chain_supply_chain_validation.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "schemas" / "trust-chain" / "supply-chain-validation-artifact.v0.1.schema.json" +VALID_FIXTURE = ROOT / "tests" / "fixtures" / "trust-chain" / "supply-chain-validation.valid.json" +BLOCKED_FIXTURE = ROOT / "tests" / "fixtures" / "trust-chain" / "supply-chain-validation.blocked.json" + +REQUIRED_PRODUCTION_REFS = { + "sbom_ref", + "vex_ref", + "lockfile_ref", + "signature_ref", + "scan_record_ref", + "promotion_evidence_ref", + "rollback_evidence_ref", +} + + +class ValidationError(Exception): + pass + + +def fail(message: str) -> None: + raise ValidationError(message) + + +def load_json(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise ValidationError(f"missing file: {path.relative_to(ROOT)}") from exc + except json.JSONDecodeError as exc: + raise ValidationError(f"invalid JSON in {path.relative_to(ROOT)}: {exc}") from exc + + +def json_type_name(value: Any) -> str: + if value is None: + return "null" + if isinstance(value, bool): + return "boolean" + if isinstance(value, int) and not isinstance(value, bool): + return "integer" + if isinstance(value, float): + return "number" + if isinstance(value, str): + return "string" + if isinstance(value, list): + return "array" + if isinstance(value, dict): + return "object" + return type(value).__name__ + + +def type_matches(value: Any, expected: str) -> bool: + actual = json_type_name(value) + if expected == "number": + return actual in {"integer", "number"} + return actual == expected + + +def validate_schema(schema: dict[str, Any], value: Any, path: str = "$") -> None: + if "const" in schema and value != schema["const"]: + fail(f"{path}: expected const {schema['const']!r}, got {value!r}") + if "enum" in schema and value not in schema["enum"]: + fail(f"{path}: {value!r} not in enum {schema['enum']!r}") + expected_type = schema.get("type") + if expected_type is not None: + expected_types = expected_type if isinstance(expected_type, list) else [expected_type] + if not any(type_matches(value, item) for item in expected_types): + fail(f"{path}: expected type {expected_types!r}, got {json_type_name(value)!r}") + if isinstance(value, dict): + required = schema.get("required", []) + for key in required: + if key not in value: + fail(f"{path}: missing required property {key!r}") + properties = schema.get("properties", {}) + if schema.get("additionalProperties") is False: + extra = sorted(set(value) - set(properties)) + if extra: + fail(f"{path}: unexpected properties {extra!r}") + additional = schema.get("additionalProperties") + for key, item in value.items(): + child_schema = properties.get(key) + if child_schema is None and isinstance(additional, dict): + child_schema = additional + if child_schema is not None: + validate_schema(child_schema, item, f"{path}.{key}") + if isinstance(value, list): + item_schema = schema.get("items") + if item_schema is not None: + for index, item in enumerate(value): + validate_schema(item_schema, item, f"{path}[{index}]") + + +def validate_common(record: dict[str, Any], path: Path) -> None: + if not str(record.get("runtime_asset_ref", "")).startswith("runtime-asset://"): + fail(f"{path}: runtime_asset_ref must be runtime-asset://") + if record.get("scope", {}).get("environment") == "production" and record.get("scope", {}).get("risk_tier") == "regulated_enterprise": + if record.get("decision") == "validated": + refs = record.get("evidence_refs", {}) + missing = sorted(key for key in REQUIRED_PRODUCTION_REFS if not refs.get(key)) + if missing: + fail(f"{path}: validated production record missing refs: {missing}") + for ref_key in ("policy_decision_ref", "guardrail_decision_ref", "validation_artifact_ref", "replay_artifact_ref", "runtime_receipt_ref"): + if not record.get(ref_key): + fail(f"{path}: validated production record missing {ref_key}") + + +def validate_valid(record: dict[str, Any], path: Path) -> None: + validate_common(record, path) + if record.get("decision") != "validated": + fail(f"{path}: valid fixture must have decision=validated") + effects = record.get("effects", {}) + if effects.get("execution_allowed") is not True or effects.get("promotion_allowed") is not True: + fail(f"{path}: valid fixture must allow execution and promotion") + if effects.get("repair_required") is not False or effects.get("human_review_required") is not False: + fail(f"{path}: valid fixture must not require repair or review") + + +def validate_blocked(record: dict[str, Any], path: Path) -> None: + validate_common(record, path) + if record.get("decision") != "blocked": + fail(f"{path}: blocked fixture must have decision=blocked") + effects = record.get("effects", {}) + if effects.get("execution_allowed") is not False or effects.get("promotion_allowed") is not False: + fail(f"{path}: blocked fixture must deny execution and promotion") + if effects.get("repair_required") is not True or effects.get("human_review_required") is not True: + fail(f"{path}: blocked fixture must require repair and human review") + remediation = record.get("remediation", []) + if not isinstance(remediation, list) or len(remediation) < 1: + fail(f"{path}: blocked fixture requires remediation") + for item in remediation: + if item.get("required_before_execution") is not True: + fail(f"{path}: remediation must be required before execution") + if not item.get("authority"): + fail(f"{path}: remediation requires authority") + + +def main() -> int: + try: + schema = load_json(SCHEMA) + valid = load_json(VALID_FIXTURE) + blocked = load_json(BLOCKED_FIXTURE) + validate_schema(schema, valid) + validate_schema(schema, blocked) + validate_valid(valid, VALID_FIXTURE) + validate_blocked(blocked, BLOCKED_FIXTURE) + except ValidationError as exc: + print(f"ERR: {exc}", file=sys.stderr) + return 2 + print("OK: Trust Chain supply-chain validation artifacts passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 2e30e1ecadfca92aa40080282a1bfe59757d9d41 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:51:41 -0400 Subject: [PATCH 5/6] Test Trust Chain supply-chain validation artifacts --- ...est_trust_chain_supply_chain_validation.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tools/tests/test_trust_chain_supply_chain_validation.py diff --git a/tools/tests/test_trust_chain_supply_chain_validation.py b/tools/tests/test_trust_chain_supply_chain_validation.py new file mode 100644 index 0000000..a932e7c --- /dev/null +++ b/tools/tests/test_trust_chain_supply_chain_validation.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from tools.validate_trust_chain_supply_chain_validation import main as validate_trust_chain_supply_chain_validation + + +ROOT = Path(__file__).resolve().parents[2] +VALID_FIXTURE = ROOT / "tests" / "fixtures" / "trust-chain" / "supply-chain-validation.valid.json" +BLOCKED_FIXTURE = ROOT / "tests" / "fixtures" / "trust-chain" / "supply-chain-validation.blocked.json" + + +def test_trust_chain_supply_chain_validation_artifacts_validate() -> None: + assert validate_trust_chain_supply_chain_validation() == 0 + + +def test_valid_supply_chain_artifact_allows_execution_and_promotion() -> None: + fixture = json.loads(VALID_FIXTURE.read_text(encoding="utf-8")) + assert fixture["decision"] == "validated" + assert fixture["effects"]["execution_allowed"] is True + assert fixture["effects"]["promotion_allowed"] is True + assert fixture["effects"]["repair_required"] is False + assert fixture["effects"]["human_review_required"] is False + + +def test_blocked_supply_chain_artifact_blocks_execution_and_requires_repair() -> None: + fixture = json.loads(BLOCKED_FIXTURE.read_text(encoding="utf-8")) + assert fixture["decision"] == "blocked" + assert fixture["effects"]["execution_allowed"] is False + assert fixture["effects"]["promotion_allowed"] is False + assert fixture["effects"]["repair_required"] is True + assert fixture["effects"]["human_review_required"] is True + assert fixture["remediation"] From 59fa59c1e2a712a3925ea4df19a90429745b0d58 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:00:48 -0400 Subject: [PATCH 6/6] Document Trust Chain supply-chain validation artifacts --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 9ba0877..2501901 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,43 @@ Agentplane treats execution as evidence-producing work. The current public evide - `NetworkDoorPlanEvidence` - `ExternalModelProviderRouteEvidence` - `NativeAssistantBridgeEvidence` +- `SupplyChainValidationArtifact` The Network Door / BYOM / Native Assistant evidence types are non-mutating by default. They record policy posture, references, route decisions, hash-only prompt/destination evidence, and side-effect flags without directly mutating firewall state, installing mesh components, contacting model providers, invoking native assistant APIs, or storing credentials. --- +## Prophet Trust Chain supply-chain validation + +Agentplane owns the validation, replay, and receipt evidence slice of Prophet Trust Chain. The platform standard and admission contract live in `SocioProphet/prophet-platform`: + +- `docs/standards/PROPHET_TRUST_CHAIN_V0.md` +- `docs/TRUST_CHAIN_ADMISSION_CONTRACT.md` +- `docs/standards/PROPHET_TRUST_CHAIN_IMPLEMENTATION_MAP.md` + +The first Agentplane Trust Chain slice defines `SupplyChainValidationArtifact`, which binds runtime supply-chain evidence to Agentplane validation/replay/receipt evidence. + +Relevant files: + +- `schemas/trust-chain/supply-chain-validation-artifact.v0.1.schema.json` +- `tests/fixtures/trust-chain/supply-chain-validation.valid.json` +- `tests/fixtures/trust-chain/supply-chain-validation.blocked.json` +- `tools/validate_trust_chain_supply_chain_validation.py` +- `tools/tests/test_trust_chain_supply_chain_validation.py` + +Validation: + +```bash +python3 tools/validate_trust_chain_supply_chain_validation.py +python3 -m pytest -q tools/tests/test_trust_chain_supply_chain_validation.py +``` + +The valid fixture requires SBOM, VEX, lockfile, signature, scan, promotion, rollback, policy, guardrail, validation, replay, and runtime receipt references before production-scope execution and promotion are allowed. The blocked fixture denies execution and promotion, requires repair and human review, and preserves remediation authority. + +Boundary: Agentplane records validation, replay, and runtime receipt evidence. It does not perform live package scanning, certify runtime production readiness by itself, replace Lattice Forge runtime evidence, replace Policy Fabric policy profiles, or replace Guardrail Fabric action admission. + +--- + ## Prerequisites | Tool | Purpose |