From 4828fb4fa72acde4e2ff5b40f846ca45df49abd1 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:10:52 +0300 Subject: [PATCH 1/8] feat: pin cross-repository PoCI federation policy --- examples/poci-witness/federation/policy.json | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/poci-witness/federation/policy.json diff --git a/examples/poci-witness/federation/policy.json b/examples/poci-witness/federation/policy.json new file mode 100644 index 0000000..10386d1 --- /dev/null +++ b/examples/poci-witness/federation/policy.json @@ -0,0 +1,37 @@ +{ + "profile_id": "proofpath.poci.cross-repo-federation-policy.v0.1", + "producer": { + "repository": "safal207/ProofPath", + "workflow": "safal207/ProofPath/.github/workflows/poci-signed-witness-network.yml", + "attestation_source_sha": "3a5dde912c44e0957204d3df733e712c596455f1", + "attestation_signer_sha": "3a5dde912c44e0957204d3df733e712c596455f1", + "report_sha256": "sha256:87119740993bc570749542ef4e06a8cf0ce44cebd77e88eb5aa1b432d0228fde" + }, + "external_consumer": { + "repository": "safal207/ibex-agent-verification", + "workflow": "safal207/ibex-agent-verification/.github/workflows/proofpath-poci-consumer.yml", + "attestation_source_sha": "24efa1eadd72275bcc29f0c436e46fb508aac113", + "attestation_signer_sha": "24efa1eadd72275bcc29f0c436e46fb508aac113", + "receipt_sha256": "sha256:756a893289f50221194b1e40b2e9fc2f10d5549c15fc9b9c57aa5f02c936d357", + "receipt_root": "sha256:a810c52ebc8e313d535962b668d5eb608a4fab0e74ce9ef9ba64c313a195aa2d" + }, + "expected": { + "round_id": "poci-demo-round-001", + "consensus_root": "sha256:f2bc99fe96f24942e5cc9c87f25c48167d875041e85170dbb354c2bc0cd97f5f", + "source_digest": "sha256:807bde72ed45ae1566443dec20c85b02f0d6734e4aa4bf9a9cfe08ce2e4c97fa", + "graph_set_id": "mg_compute_purchase_001", + "poci_envelope_id": "env_valid_action", + "graph_roots": { + "causal": "sha256:482d5076dc3a80dac8f4ed179491b4d3e153ece780fcebdd81289ffbdc6fd7f4", + "intent": "sha256:3a26e5badd5379f69203dfe972143e0f3969b12e5f5c55cd641aa7f2bc41a0b3", + "authority": "sha256:6af348f1245a5a1f3b59b0070653bbf4d512a792e54554918058f9692e33bb0c", + "state_transition": "sha256:41807cd0bacac6908867ce6839114d3028ec62550eb7cee0fdb50006e3887c10", + "evidence": "sha256:1fed70f4f198ce5a993880e283763db8c0dc576824f042619637b37f6d745899", + "time_continuity": "sha256:4b28006fc3a21f6afce98e4e4e661fa204060be976110bed3bb571af3d7e4ccc" + }, + "transition_cells_root": "sha256:b5d5fd4ba2638fb685913e621295a1c70b5aff550d14ea5b2d8c0f49bdba7f45", + "computed_multigraph_root": "sha256:98543066cb16c06b5300b56749af43b2fdd48eed47650488ac57987156e38219", + "required_domain_count": 2, + "external_consumer_required": true + } +} From 1017089f83926b5100f373455e1934f1021a9bd9 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:11:23 +0300 Subject: [PATCH 2/8] test: add local attested quorum subject --- .../proofpath-attested-quorum-report.json | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 examples/poci-witness/federation/proofpath-attested-quorum-report.json diff --git a/examples/poci-witness/federation/proofpath-attested-quorum-report.json b/examples/poci-witness/federation/proofpath-attested-quorum-report.json new file mode 100644 index 0000000..660c6cc --- /dev/null +++ b/examples/poci-witness/federation/proofpath-attested-quorum-report.json @@ -0,0 +1,71 @@ +{ + "profile_id": "proofpath.poci.multigraph.witness-quorum.v0.1", + "round_id": "poci-demo-round-001", + "decision": "ACCEPT", + "primary_reason_code": null, + "reason_codes": [], + "findings": [], + "quorum": 2, + "configured_witness_count": 3, + "valid_statement_count": 3, + "agreeing_witnesses": ["witness-alpha", "witness-beta", "witness-gamma"], + "dissenting_witnesses": [], + "invalid_witnesses": [], + "non_accepting_witnesses": [], + "process_failures": [], + "statement_summaries": [ + { + "witness_id": "witness-alpha", + "operator_id": "operator-alpha", + "decision": "ACCEPT", + "statement_root": "sha256:3ae5755c181585411cd95617f40a69b1d6954e5e5b5ae4ae81aa0345124b8e8b", + "vote_key": "sha256:e4f27eaddeab6bfca88fc5222b6c1fe9afe66387d346fb83bcfaf0e4ed71c914" + }, + { + "witness_id": "witness-beta", + "operator_id": "operator-beta", + "decision": "ACCEPT", + "statement_root": "sha256:3a4af53de8644174eb947b054518c1763ccc4ad17d19494deb128a3bdb4ff8b3", + "vote_key": "sha256:e4f27eaddeab6bfca88fc5222b6c1fe9afe66387d346fb83bcfaf0e4ed71c914" + }, + { + "witness_id": "witness-gamma", + "operator_id": "operator-gamma", + "decision": "ACCEPT", + "statement_root": "sha256:c7999c57ee16378bf7e6f05d47ac1e9c0ae9faf14933a9d351e5cf58cd832421", + "vote_key": "sha256:e4f27eaddeab6bfca88fc5222b6c1fe9afe66387d346fb83bcfaf0e4ed71c914" + } + ], + "consensus": { + "profile_id": "proofpath.poci.multigraph.witness-quorum.v0.1", + "round_id": "poci-demo-round-001", + "quorum": 2, + "agreeing_witnesses": ["witness-alpha", "witness-beta", "witness-gamma"], + "source_digest": "sha256:807bde72ed45ae1566443dec20c85b02f0d6734e4aa4bf9a9cfe08ce2e4c97fa", + "graph_set_id": "mg_compute_purchase_001", + "poci_envelope_id": "env_valid_action", + "graph_roots": { + "causal": "sha256:482d5076dc3a80dac8f4ed179491b4d3e153ece780fcebdd81289ffbdc6fd7f4", + "intent": "sha256:3a26e5badd5379f69203dfe972143e0f3969b12e5f5c55cd641aa7f2bc41a0b3", + "authority": "sha256:6af348f1245a5a1f3b59b0070653bbf4d512a792e54554918058f9692e33bb0c", + "state_transition": "sha256:41807cd0bacac6908867ce6839114d3028ec62550eb7cee0fdb50006e3887c10", + "evidence": "sha256:1fed70f4f198ce5a993880e283763db8c0dc576824f042619637b37f6d745899", + "time_continuity": "sha256:4b28006fc3a21f6afce98e4e4e661fa204060be976110bed3bb571af3d7e4ccc" + }, + "transition_cells_root": "sha256:b5d5fd4ba2638fb685913e621295a1c70b5aff550d14ea5b2d8c0f49bdba7f45", + "computed_multigraph_root": "sha256:98543066cb16c06b5300b56749af43b2fdd48eed47650488ac57987156e38219" + }, + "consensus_root": "sha256:f2bc99fe96f24942e5cc9c87f25c48167d875041e85170dbb354c2bc0cd97f5f", + "honest_limitations": [ + "witness jobs use separate GitHub-hosted runners", + "one GitHub repository still controls all three jobs", + "external organizational independence is not yet proven" + ], + "valid": true, + "attestation_profile": "github-keyless-slsa-provenance", + "verified_attestation_count": 3, + "signer_workflow": "safal207/ProofPath/.github/workflows/poci-signed-witness-network.yml", + "source_digest": "3a5dde912c44e0957204d3df733e712c596455f1", + "signer_digest": "3a5dde912c44e0957204d3df733e712c596455f1", + "self_hosted_runners_denied": true +} From 3e1933e201d9b2212466a9e4cd131fb91278b97d Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:11:52 +0300 Subject: [PATCH 3/8] test: add attested Ibex consumer receipt --- .../ibex-external-consumer-receipt.json | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/poci-witness/federation/ibex-external-consumer-receipt.json diff --git a/examples/poci-witness/federation/ibex-external-consumer-receipt.json b/examples/poci-witness/federation/ibex-external-consumer-receipt.json new file mode 100644 index 0000000..8750d4a --- /dev/null +++ b/examples/poci-witness/federation/ibex-external-consumer-receipt.json @@ -0,0 +1,55 @@ +{ + "profile_id": "ibex.poci.external-consumer-receipt.v0.1", + "decision": "ACCEPT", + "primary_reason_code": null, + "reason_codes": [], + "findings": [], + "consumer": { + "consumer_id": "ibex-proofpath-poci-consumer", + "repository": "safal207/ibex-agent-verification", + "workflow": "safal207/ibex-agent-verification/.github/workflows/proofpath-poci-consumer.yml", + "commit_sha": "24efa1eadd72275bcc29f0c436e46fb508aac113" + }, + "producer": { + "repository": "safal207/ProofPath", + "code_sha": "a726f2a8b3bd9c6c4469be542e30bb54b230d510", + "attestation_source_sha": "3a5dde912c44e0957204d3df733e712c596455f1", + "attestation_signer_sha": "3a5dde912c44e0957204d3df733e712c596455f1", + "workflow": "safal207/ProofPath/.github/workflows/poci-signed-witness-network.yml", + "report_sha256": "sha256:87119740993bc570749542ef4e06a8cf0ce44cebd77e88eb5aa1b432d0228fde", + "attestation_result_sha256": "sha256:eac7008a5dcdfaf0e7c75eed1dfdbaffa633f61d72301818281ee7ca6a13bef0" + }, + "accepted_consensus": { + "round_id": "poci-demo-round-001", + "consensus_root": "sha256:f2bc99fe96f24942e5cc9c87f25c48167d875041e85170dbb354c2bc0cd97f5f", + "source_digest": "sha256:807bde72ed45ae1566443dec20c85b02f0d6734e4aa4bf9a9cfe08ce2e4c97fa", + "graph_set_id": "mg_compute_purchase_001", + "poci_envelope_id": "env_valid_action", + "graph_roots": { + "causal": "sha256:482d5076dc3a80dac8f4ed179491b4d3e153ece780fcebdd81289ffbdc6fd7f4", + "intent": "sha256:3a26e5badd5379f69203dfe972143e0f3969b12e5f5c55cd641aa7f2bc41a0b3", + "authority": "sha256:6af348f1245a5a1f3b59b0070653bbf4d512a792e54554918058f9692e33bb0c", + "state_transition": "sha256:41807cd0bacac6908867ce6839114d3028ec62550eb7cee0fdb50006e3887c10", + "evidence": "sha256:1fed70f4f198ce5a993880e283763db8c0dc576824f042619637b37f6d745899", + "time_continuity": "sha256:4b28006fc3a21f6afce98e4e4e661fa204060be976110bed3bb571af3d7e4ccc" + }, + "transition_cells_root": "sha256:b5d5fd4ba2638fb685913e621295a1c70b5aff550d14ea5b2d8c0f49bdba7f45", + "computed_multigraph_root": "sha256:98543066cb16c06b5300b56749af43b2fdd48eed47650488ac57987156e38219" + }, + "verification": { + "producer_attestation_verified": true, + "producer_report_digest_verified": true, + "producer_code_sha_verified": true, + "source_recomputed": true, + "six_graph_roots_recomputed": true, + "transition_cells_recomputed": true, + "consumer_attestation_required": true + }, + "honest_limitations": [ + "ProofPath and Ibex use different repositories and workflow identities", + "both repositories are currently controlled by the same GitHub account owner", + "this proves evidence consistency, not objective real-world truth" + ], + "receipt_root": "sha256:a810c52ebc8e313d535962b668d5eb608a4fab0e74ce9ef9ba64c313a195aa2d", + "valid": true +} From 4206841249d6af4cd11053bea1acc72d8a012c78 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:13:54 +0300 Subject: [PATCH 4/8] fix: preserve exact attested quorum bytes --- .../federation/proofpath-attested-quorum-report.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/poci-witness/federation/proofpath-attested-quorum-report.json b/examples/poci-witness/federation/proofpath-attested-quorum-report.json index 660c6cc..998a25a 100644 --- a/examples/poci-witness/federation/proofpath-attested-quorum-report.json +++ b/examples/poci-witness/federation/proofpath-attested-quorum-report.json @@ -8,7 +8,11 @@ "quorum": 2, "configured_witness_count": 3, "valid_statement_count": 3, - "agreeing_witnesses": ["witness-alpha", "witness-beta", "witness-gamma"], + "agreeing_witnesses": [ + "witness-alpha", + "witness-beta", + "witness-gamma" + ], "dissenting_witnesses": [], "invalid_witnesses": [], "non_accepting_witnesses": [], @@ -40,7 +44,11 @@ "profile_id": "proofpath.poci.multigraph.witness-quorum.v0.1", "round_id": "poci-demo-round-001", "quorum": 2, - "agreeing_witnesses": ["witness-alpha", "witness-beta", "witness-gamma"], + "agreeing_witnesses": [ + "witness-alpha", + "witness-beta", + "witness-gamma" + ], "source_digest": "sha256:807bde72ed45ae1566443dec20c85b02f0d6734e4aa4bf9a9cfe08ce2e4c97fa", "graph_set_id": "mg_compute_purchase_001", "poci_envelope_id": "env_valid_action", From e8e4fbc1704acc40e4d42c8067dba5f1d01eb178 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:16:23 +0300 Subject: [PATCH 5/8] feat: add cross-repository PoCI federation verifier --- scripts/verify_poci_cross_repo_federation.py | 515 +++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 scripts/verify_poci_cross_repo_federation.py diff --git a/scripts/verify_poci_cross_repo_federation.py b/scripts/verify_poci_cross_repo_federation.py new file mode 100644 index 0000000..c1b2dbe --- /dev/null +++ b/scripts/verify_poci_cross_repo_federation.py @@ -0,0 +1,515 @@ +#!/usr/bin/env python3 +"""Verify a two-domain ProofPath PoCI cross-repository federation.""" + +from __future__ import annotations + +import argparse +import copy +import hashlib +import json +import sys +from pathlib import Path +from typing import Any, Iterable + +POLICY_PROFILE = "proofpath.poci.cross-repo-federation-policy.v0.1" +PROFILE = "proofpath.poci.cross-repo-federation.v0.1" +PRODUCER_PROFILE = "proofpath.poci.multigraph.witness-quorum.v0.1" +CONSUMER_PROFILE = "ibex.poci.external-consumer-receipt.v0.1" +RECEIPT_DOMAIN = b"ibex:poci:external-consumer:v0.1:receipt\n" +FEDERATION_DOMAIN = b"proofpath:poci:cross-repo-federation:v0.1:root\n" +ATTESTATION_DOMAIN = b"proofpath:poci:cross-repo-federation:v0.1:attestation-result\n" +REQUIRED_GRAPHS = ( + "causal", + "intent", + "authority", + "state_transition", + "evidence", + "time_continuity", +) +DECISION_RANK = {"ACCEPT": 0, "BLOCK": 1, "CHALLENGE": 2} +EXIT_CODE = {"ACCEPT": 0, "BLOCK": 3, "CHALLENGE": 4} + + +class DuplicateKeyError(ValueError): + """Raised when JSON repeats an object key.""" + + +def _pairs(items: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in items: + if key in result: + raise DuplicateKeyError(f"duplicate JSON key: {key}") + result[key] = value + return result + + +def load_value(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=_pairs) + except (OSError, json.JSONDecodeError, DuplicateKeyError) as exc: + raise ValueError(f"invalid JSON in {path}: {exc}") from exc + + +def load_object(path: Path) -> dict[str, Any]: + value = load_value(path) + if not isinstance(value, dict): + raise ValueError(f"expected JSON object in {path}") + return value + + +def _contains_float(value: Any) -> bool: + if isinstance(value, float): + return True + if isinstance(value, dict): + return any(_contains_float(item) for item in value.values()) + if isinstance(value, list): + return any(_contains_float(item) for item in value) + return False + + +def canonical_json_bytes(value: Any) -> bytes: + if _contains_float(value): + raise ValueError("floating-point values are forbidden") + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + + +def _digest(domain: bytes, value: Any) -> str: + return "sha256:" + hashlib.sha256(domain + canonical_json_bytes(value)).hexdigest() + + +def file_digest(path: Path) -> str: + return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() + + +def compute_consumer_receipt_root(receipt: dict[str, Any]) -> str: + normalized = copy.deepcopy(receipt) + normalized["receipt_root"] = None + return _digest(RECEIPT_DOMAIN, normalized) + + +def compute_federation_root(report: dict[str, Any]) -> str: + normalized = copy.deepcopy(report) + normalized["federation_root"] = None + return _digest(FEDERATION_DOMAIN, normalized) + + +def _finding(code: str, decision: str, path: str, message: str) -> dict[str, str]: + return {"code": code, "decision": decision, "path": path, "message": message} + + +def _object(value: Any) -> dict[str, Any]: + return value if isinstance(value, dict) else {} + + +def _attestation_digest(value: Any | None) -> str | None: + if value is None: + return None + return _digest(ATTESTATION_DOMAIN, value) + + +def verify_federation( + policy: dict[str, Any], + producer_report: dict[str, Any], + consumer_receipt: dict[str, Any], + *, + producer_report_digest: str, + consumer_receipt_digest: str, + producer_attestation_result: Any | None, + consumer_attestation_result: Any | None, +) -> dict[str, Any]: + findings: list[dict[str, str]] = [] + producer_policy = _object(policy.get("producer")) + consumer_policy = _object(policy.get("external_consumer")) + expected = _object(policy.get("expected")) + + def add(code: str, decision: str, path: str, message: str) -> None: + findings.append(_finding(code, decision, path, message)) + + if policy.get("profile_id") != POLICY_PROFILE: + add("FEDERATION_POLICY_INVALID", "BLOCK", "$.profile_id", "unsupported policy") + + required_policy_fields = ( + "repository", + "workflow", + "attestation_source_sha", + "attestation_signer_sha", + ) + for namespace, value in ( + ("producer", producer_policy), + ("external_consumer", consumer_policy), + ): + for key in required_policy_fields: + if not isinstance(value.get(key), str) or not value.get(key): + add( + "FEDERATION_POLICY_INVALID", + "BLOCK", + f"$.{namespace}.{key}", + "repository, workflow, source SHA, and signer SHA must be pinned", + ) + + expected_roots = expected.get("graph_roots") + if not isinstance(expected_roots, dict) or set(expected_roots) != set(REQUIRED_GRAPHS): + add( + "FEDERATION_POLICY_INVALID", + "BLOCK", + "$.expected.graph_roots", + "exactly six graph roots must be pinned", + ) + expected_roots = {} + + if expected.get("required_domain_count") != 2: + add( + "FEDERATION_POLICY_INVALID", + "BLOCK", + "$.expected.required_domain_count", + "v0.1 requires exactly two repository domains", + ) + if expected.get("external_consumer_required") is not True: + add( + "FEDERATION_POLICY_INVALID", + "BLOCK", + "$.expected.external_consumer_required", + "an external consumer domain is mandatory", + ) + if producer_policy.get("repository") == consumer_policy.get("repository"): + add( + "FEDERATION_DOMAIN_NOT_DISTINCT", + "BLOCK", + "$.external_consumer.repository", + "producer and consumer repositories must differ", + ) + if producer_policy.get("workflow") == consumer_policy.get("workflow"): + add( + "FEDERATION_DOMAIN_NOT_DISTINCT", + "BLOCK", + "$.external_consumer.workflow", + "producer and consumer workflows must differ", + ) + + if producer_report_digest != producer_policy.get("report_sha256"): + add( + "FEDERATION_PRODUCER_SUBJECT_MISMATCH", + "CHALLENGE", + "$.producer.report_sha256", + "producer report bytes differ from the pinned attested subject", + ) + if consumer_receipt_digest != consumer_policy.get("receipt_sha256"): + add( + "FEDERATION_CONSUMER_SUBJECT_MISMATCH", + "CHALLENGE", + "$.external_consumer.receipt_sha256", + "consumer receipt bytes differ from the pinned attested subject", + ) + if producer_attestation_result is None: + add( + "FEDERATION_PRODUCER_ATTESTATION_MISSING", + "BLOCK", + "$.producer.attestation", + "verified producer attestation result is required", + ) + if consumer_attestation_result is None: + add( + "FEDERATION_CONSUMER_ATTESTATION_MISSING", + "BLOCK", + "$.external_consumer.attestation", + "verified external-consumer attestation result is required", + ) + + producer_consensus = _object(producer_report.get("consensus")) + producer_valid = ( + producer_report.get("profile_id") == PRODUCER_PROFILE + and producer_report.get("decision") == "ACCEPT" + and producer_report.get("valid") is True + and producer_report.get("attestation_profile") + == "github-keyless-slsa-provenance" + and producer_report.get("verified_attestation_count") == 3 + and producer_report.get("signer_workflow") == producer_policy.get("workflow") + and producer_report.get("source_digest") + == producer_policy.get("attestation_source_sha") + and producer_report.get("signer_digest") + == producer_policy.get("attestation_signer_sha") + and producer_report.get("self_hosted_runners_denied") is True + ) + if not producer_valid: + add( + "FEDERATION_PRODUCER_REPORT_INVALID", + "BLOCK", + "$.producer_report", + "producer report violates the pinned accepted-quorum profile", + ) + + consumer_metadata = _object(consumer_receipt.get("consumer")) + consumer_producer = _object(consumer_receipt.get("producer")) + consumer_consensus = _object(consumer_receipt.get("accepted_consensus")) + consumer_verification = _object(consumer_receipt.get("verification")) + required_verification_flags = ( + "producer_attestation_verified", + "producer_report_digest_verified", + "producer_code_sha_verified", + "source_recomputed", + "six_graph_roots_recomputed", + "transition_cells_recomputed", + "consumer_attestation_required", + ) + consumer_valid = ( + consumer_receipt.get("profile_id") == CONSUMER_PROFILE + and consumer_receipt.get("decision") == "ACCEPT" + and consumer_receipt.get("valid") is True + and consumer_metadata.get("repository") == consumer_policy.get("repository") + and consumer_metadata.get("workflow") == consumer_policy.get("workflow") + and consumer_metadata.get("commit_sha") + == consumer_policy.get("attestation_source_sha") + and consumer_producer.get("repository") == producer_policy.get("repository") + and consumer_producer.get("workflow") == producer_policy.get("workflow") + and consumer_producer.get("attestation_source_sha") + == producer_policy.get("attestation_source_sha") + and consumer_producer.get("attestation_signer_sha") + == producer_policy.get("attestation_signer_sha") + and consumer_producer.get("report_sha256") + == producer_policy.get("report_sha256") + and all(consumer_verification.get(key) is True for key in required_verification_flags) + ) + if not consumer_valid: + add( + "FEDERATION_CONSUMER_RECEIPT_INVALID", + "BLOCK", + "$.consumer_receipt", + "external consumer receipt violates the pinned verification profile", + ) + + try: + computed_receipt_root = compute_consumer_receipt_root(consumer_receipt) + except (TypeError, ValueError) as exc: + computed_receipt_root = None + add( + "FEDERATION_CONSUMER_RECEIPT_ROOT_MISMATCH", + "CHALLENGE", + "$.consumer_receipt", + str(exc), + ) + declared_receipt_root = consumer_receipt.get("receipt_root") + if ( + computed_receipt_root != declared_receipt_root + or declared_receipt_root != consumer_policy.get("receipt_root") + ): + add( + "FEDERATION_CONSUMER_RECEIPT_ROOT_MISMATCH", + "CHALLENGE", + "$.external_consumer.receipt_root", + "consumer receipt root is not bound to the pinned receipt bytes", + ) + + field_pairs = { + "round_id": (producer_report.get("round_id"), consumer_consensus.get("round_id")), + "consensus_root": ( + producer_report.get("consensus_root"), + consumer_consensus.get("consensus_root"), + ), + "source_digest": ( + producer_consensus.get("source_digest"), + consumer_consensus.get("source_digest"), + ), + "graph_set_id": ( + producer_consensus.get("graph_set_id"), + consumer_consensus.get("graph_set_id"), + ), + "poci_envelope_id": ( + producer_consensus.get("poci_envelope_id"), + consumer_consensus.get("poci_envelope_id"), + ), + "graph_roots": ( + producer_consensus.get("graph_roots"), + consumer_consensus.get("graph_roots"), + ), + "transition_cells_root": ( + producer_consensus.get("transition_cells_root"), + consumer_consensus.get("transition_cells_root"), + ), + "computed_multigraph_root": ( + producer_consensus.get("computed_multigraph_root"), + consumer_consensus.get("computed_multigraph_root"), + ), + } + for key, (producer_value, consumer_value) in field_pairs.items(): + expected_value = expected.get(key) + if producer_value != expected_value: + add( + "FEDERATION_PRODUCER_CONSENSUS_MISMATCH", + "CHALLENGE", + f"$.expected.{key}", + f"producer consensus differs: {key}", + ) + if consumer_value != expected_value: + add( + "FEDERATION_CONSUMER_CONSENSUS_MISMATCH", + "CHALLENGE", + f"$.expected.{key}", + f"consumer consensus differs: {key}", + ) + if producer_value != consumer_value: + add( + "FEDERATION_CROSS_DOMAIN_MISMATCH", + "CHALLENGE", + f"$.consensus.{key}", + f"producer and consumer disagree: {key}", + ) + + unique = { + (item["code"], item["path"], item["message"]): item for item in findings + } + findings = sorted( + unique.values(), + key=lambda item: ( + -DECISION_RANK[item["decision"]], + item["code"], + item["path"], + item["message"], + ), + ) + primary = findings[0] if findings else None + decision = primary["decision"] if primary else "ACCEPT" + + domains = [ + { + "role": "producer", + "repository": producer_policy.get("repository"), + "workflow": producer_policy.get("workflow"), + "attestation_source_sha": producer_policy.get("attestation_source_sha"), + "attestation_signer_sha": producer_policy.get("attestation_signer_sha"), + "subject_sha256": producer_report_digest, + "attestation_result_digest": _attestation_digest( + producer_attestation_result + ), + }, + { + "role": "external_consumer", + "repository": consumer_policy.get("repository"), + "workflow": consumer_policy.get("workflow"), + "attestation_source_sha": consumer_policy.get("attestation_source_sha"), + "attestation_signer_sha": consumer_policy.get("attestation_signer_sha"), + "subject_sha256": consumer_receipt_digest, + "subject_root": declared_receipt_root, + "attestation_result_digest": _attestation_digest( + consumer_attestation_result + ), + }, + ] + + report: dict[str, Any] = { + "profile_id": PROFILE, + "decision": decision, + "primary_reason_code": primary["code"] if primary else None, + "reason_codes": sorted({item["code"] for item in findings}), + "findings": findings, + "domain_count": 2, + "external_consumer_required": True, + "domains": domains, + "consensus": { + "round_id": expected.get("round_id"), + "consensus_root": expected.get("consensus_root"), + "source_digest": expected.get("source_digest"), + "graph_set_id": expected.get("graph_set_id"), + "poci_envelope_id": expected.get("poci_envelope_id"), + "graph_roots": expected_roots, + "transition_cells_root": expected.get("transition_cells_root"), + "computed_multigraph_root": expected.get("computed_multigraph_root"), + }, + "verification": { + "producer_subject_verified": ( + producer_report_digest == producer_policy.get("report_sha256") + ), + "producer_attestation_verified": producer_attestation_result is not None, + "external_consumer_subject_verified": ( + consumer_receipt_digest == consumer_policy.get("receipt_sha256") + ), + "external_consumer_attestation_verified": ( + consumer_attestation_result is not None + ), + "external_consumer_receipt_root_verified": ( + computed_receipt_root == declared_receipt_root + == consumer_policy.get("receipt_root") + ), + "cross_domain_consensus_equal": all( + producer_value == consumer_value == expected.get(key) + for key, (producer_value, consumer_value) in field_pairs.items() + ), + }, + "honest_limitations": [ + "the two attestations use different repositories and workflow identities", + "both repositories are currently controlled by the same GitHub account owner", + "federation proves committed evidence consistency, not objective real-world truth", + ], + "federation_root": None, + "valid": decision == "ACCEPT", + } + report["federation_root"] = compute_federation_root(report) + return report + + +def main(argv: Iterable[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("policy", type=Path) + parser.add_argument("--producer-report", type=Path, required=True) + parser.add_argument("--consumer-receipt", type=Path, required=True) + parser.add_argument("--producer-attestation-result", type=Path, required=True) + parser.add_argument("--consumer-attestation-result", type=Path, required=True) + parser.add_argument("--output", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(list(argv) if argv is not None else None) + + try: + policy = load_object(args.policy) + producer_report = load_object(args.producer_report) + consumer_receipt = load_object(args.consumer_receipt) + producer_attestation_result = load_value(args.producer_attestation_result) + consumer_attestation_result = load_value(args.consumer_attestation_result) + report = verify_federation( + policy, + producer_report, + consumer_receipt, + producer_report_digest=file_digest(args.producer_report), + consumer_receipt_digest=file_digest(args.consumer_receipt), + producer_attestation_result=producer_attestation_result, + consumer_attestation_result=consumer_attestation_result, + ) + code = EXIT_CODE[report["decision"]] + except (OSError, ValueError, TypeError, KeyError) as exc: + report = { + "profile_id": PROFILE, + "decision": "BLOCK", + "primary_reason_code": "FEDERATION_INTERNAL_FAIL_CLOSED", + "reason_codes": ["FEDERATION_INTERNAL_FAIL_CLOSED"], + "findings": [ + _finding("FEDERATION_INTERNAL_FAIL_CLOSED", "BLOCK", "$", str(exc)) + ], + "domain_count": 0, + "external_consumer_required": True, + "federation_root": None, + "valid": False, + } + report["federation_root"] = compute_federation_root(report) + code = 1 + + text = ( + json.dumps(report, indent=2, ensure_ascii=False) + if args.pretty + else json.dumps( + report, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ) + ) + "\n" + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(text, encoding="utf-8") + else: + sys.stdout.write(text) + return code + + +if __name__ == "__main__": + raise SystemExit(main()) From 700e673072d7923c7a3ebf2ee208d8afab2df8c1 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:18:13 +0300 Subject: [PATCH 6/8] test: add cross-repository federation mutations --- tests/test_poci_cross_repo_federation.py | 235 +++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 tests/test_poci_cross_repo_federation.py diff --git a/tests/test_poci_cross_repo_federation.py b/tests/test_poci_cross_repo_federation.py new file mode 100644 index 0000000..b3c2c30 --- /dev/null +++ b/tests/test_poci_cross_repo_federation.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +import copy +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) + +from verify_poci_cross_repo_federation import ( # noqa: E402 + CONSUMER_PROFILE, + POLICY_PROFILE, + PRODUCER_PROFILE, + REQUIRED_GRAPHS, + compute_consumer_receipt_root, + compute_federation_root, + verify_federation, +) + + +class PoCICrossRepoFederationTests(unittest.TestCase): + def make_case(self): + roots = { + name: "sha256:" + format(index + 1, "x") * 64 + for index, name in enumerate(REQUIRED_GRAPHS) + } + expected = { + "round_id": "round-1", + "consensus_root": "sha256:" + "a" * 64, + "source_digest": "sha256:" + "b" * 64, + "graph_set_id": "graph-set-1", + "poci_envelope_id": "envelope-1", + "graph_roots": roots, + "transition_cells_root": "sha256:" + "c" * 64, + "computed_multigraph_root": "sha256:" + "d" * 64, + "required_domain_count": 2, + "external_consumer_required": True, + } + producer_policy = { + "repository": "example/producer", + "workflow": "example/producer/.github/workflows/producer.yml", + "attestation_source_sha": "producer-merge", + "attestation_signer_sha": "producer-merge", + "report_sha256": "sha256:producer-subject", + } + consumer_policy = { + "repository": "example/consumer", + "workflow": "example/consumer/.github/workflows/consumer.yml", + "attestation_source_sha": "consumer-merge", + "attestation_signer_sha": "consumer-merge", + "receipt_sha256": "sha256:consumer-subject", + "receipt_root": None, + } + policy = { + "profile_id": POLICY_PROFILE, + "producer": producer_policy, + "external_consumer": consumer_policy, + "expected": expected, + } + producer_report = { + "profile_id": PRODUCER_PROFILE, + "round_id": expected["round_id"], + "decision": "ACCEPT", + "valid": True, + "consensus_root": expected["consensus_root"], + "consensus": { + key: expected[key] + for key in ( + "source_digest", + "graph_set_id", + "poci_envelope_id", + "graph_roots", + "transition_cells_root", + "computed_multigraph_root", + ) + }, + "attestation_profile": "github-keyless-slsa-provenance", + "verified_attestation_count": 3, + "signer_workflow": producer_policy["workflow"], + "source_digest": producer_policy["attestation_source_sha"], + "signer_digest": producer_policy["attestation_signer_sha"], + "self_hosted_runners_denied": True, + } + consumer_receipt = { + "profile_id": CONSUMER_PROFILE, + "decision": "ACCEPT", + "primary_reason_code": None, + "reason_codes": [], + "findings": [], + "consumer": { + "consumer_id": "external-consumer", + "repository": consumer_policy["repository"], + "workflow": consumer_policy["workflow"], + "commit_sha": consumer_policy["attestation_source_sha"], + }, + "producer": { + "repository": producer_policy["repository"], + "workflow": producer_policy["workflow"], + "attestation_source_sha": producer_policy["attestation_source_sha"], + "attestation_signer_sha": producer_policy["attestation_signer_sha"], + "report_sha256": producer_policy["report_sha256"], + }, + "accepted_consensus": { + key: expected[key] + for key in ( + "round_id", + "consensus_root", + "source_digest", + "graph_set_id", + "poci_envelope_id", + "graph_roots", + "transition_cells_root", + "computed_multigraph_root", + ) + }, + "verification": { + "producer_attestation_verified": True, + "producer_report_digest_verified": True, + "producer_code_sha_verified": True, + "source_recomputed": True, + "six_graph_roots_recomputed": True, + "transition_cells_recomputed": True, + "consumer_attestation_required": True, + }, + "honest_limitations": [], + "receipt_root": None, + "valid": True, + } + consumer_receipt["receipt_root"] = compute_consumer_receipt_root( + consumer_receipt + ) + consumer_policy["receipt_root"] = consumer_receipt["receipt_root"] + return policy, producer_report, consumer_receipt + + def run_case(self, policy, producer_report, consumer_receipt, **overrides): + args = { + "producer_report_digest": "sha256:producer-subject", + "consumer_receipt_digest": "sha256:consumer-subject", + "producer_attestation_result": [{"verified": "producer"}], + "consumer_attestation_result": [{"verified": "consumer"}], + } + args.update(overrides) + return verify_federation( + policy, producer_report, consumer_receipt, **args + ) + + def test_valid_two_domain_federation_accepts(self): + result = self.run_case(*self.make_case()) + self.assertEqual(result["decision"], "ACCEPT") + self.assertTrue(result["valid"]) + self.assertEqual(result["domain_count"], 2) + self.assertEqual(len(result["consensus"]["graph_roots"]), 6) + self.assertEqual(result["federation_root"], compute_federation_root(result)) + + def test_producer_subject_substitution_challenges(self): + result = self.run_case( + *self.make_case(), producer_report_digest="sha256:other" + ) + self.assertEqual(result["decision"], "CHALLENGE") + self.assertIn("FEDERATION_PRODUCER_SUBJECT_MISMATCH", result["reason_codes"]) + + def test_consumer_subject_substitution_challenges(self): + result = self.run_case( + *self.make_case(), consumer_receipt_digest="sha256:other" + ) + self.assertEqual(result["decision"], "CHALLENGE") + self.assertIn("FEDERATION_CONSUMER_SUBJECT_MISMATCH", result["reason_codes"]) + + def test_missing_producer_attestation_blocks(self): + result = self.run_case( + *self.make_case(), producer_attestation_result=None + ) + self.assertEqual(result["decision"], "BLOCK") + self.assertIn("FEDERATION_PRODUCER_ATTESTATION_MISSING", result["reason_codes"]) + + def test_missing_consumer_attestation_blocks(self): + result = self.run_case( + *self.make_case(), consumer_attestation_result=None + ) + self.assertEqual(result["decision"], "BLOCK") + self.assertIn("FEDERATION_CONSUMER_ATTESTATION_MISSING", result["reason_codes"]) + + def test_same_repository_domain_blocks(self): + policy, producer, consumer = self.make_case() + policy["external_consumer"]["repository"] = policy["producer"]["repository"] + consumer["consumer"]["repository"] = policy["producer"]["repository"] + consumer["receipt_root"] = compute_consumer_receipt_root(consumer) + policy["external_consumer"]["receipt_root"] = consumer["receipt_root"] + result = self.run_case(policy, producer, consumer) + self.assertEqual(result["decision"], "BLOCK") + self.assertIn("FEDERATION_DOMAIN_NOT_DISTINCT", result["reason_codes"]) + + def test_external_graph_substitution_challenges(self): + policy, producer, consumer = self.make_case() + consumer["accepted_consensus"]["graph_roots"] = dict( + consumer["accepted_consensus"]["graph_roots"] + ) + consumer["accepted_consensus"]["graph_roots"]["authority"] = ( + "sha256:" + "f" * 64 + ) + consumer["receipt_root"] = compute_consumer_receipt_root(consumer) + policy["external_consumer"]["receipt_root"] = consumer["receipt_root"] + result = self.run_case(policy, producer, consumer) + self.assertEqual(result["decision"], "CHALLENGE") + self.assertIn("FEDERATION_CROSS_DOMAIN_MISMATCH", result["reason_codes"]) + + def test_external_receipt_root_tamper_challenges(self): + policy, producer, consumer = self.make_case() + consumer["receipt_root"] = "sha256:" + "0" * 64 + result = self.run_case(policy, producer, consumer) + self.assertEqual(result["decision"], "CHALLENGE") + self.assertIn( + "FEDERATION_CONSUMER_RECEIPT_ROOT_MISMATCH", result["reason_codes"] + ) + + def test_external_workflow_substitution_blocks(self): + policy, producer, consumer = self.make_case() + consumer["consumer"]["workflow"] = ( + "example/consumer/.github/workflows/other.yml" + ) + consumer["receipt_root"] = compute_consumer_receipt_root(consumer) + policy["external_consumer"]["receipt_root"] = consumer["receipt_root"] + result = self.run_case(policy, producer, consumer) + self.assertEqual(result["decision"], "BLOCK") + self.assertIn("FEDERATION_CONSUMER_RECEIPT_INVALID", result["reason_codes"]) + + def test_federation_root_is_deterministic(self): + case = self.make_case() + left = self.run_case(*copy.deepcopy(case)) + right = self.run_case(*copy.deepcopy(case)) + self.assertEqual(left["federation_root"], right["federation_root"]) + + +if __name__ == "__main__": + unittest.main() From 7c542acf4c3a067323c8b7e2b56e56482c315d2a Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:19:22 +0300 Subject: [PATCH 7/8] ci: add two-domain PoCI federation gate --- .../workflows/poci-cross-repo-federation.yml | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 .github/workflows/poci-cross-repo-federation.yml diff --git a/.github/workflows/poci-cross-repo-federation.yml b/.github/workflows/poci-cross-repo-federation.yml new file mode 100644 index 0000000..e44c08a --- /dev/null +++ b/.github/workflows/poci-cross-repo-federation.yml @@ -0,0 +1,142 @@ +name: PoCI Cross-Repository Federation + +on: + push: + branches: [main] + paths: + - ".github/workflows/poci-cross-repo-federation.yml" + - "docs/POCI_CROSS_REPO_FEDERATION.md" + - "examples/poci-witness/federation/**" + - "scripts/verify_poci_cross_repo_federation.py" + - "tests/test_poci_cross_repo_federation.py" + pull_request: + paths: + - ".github/workflows/poci-cross-repo-federation.yml" + - "docs/POCI_CROSS_REPO_FEDERATION.md" + - "examples/poci-witness/federation/**" + - "scripts/verify_poci_cross_repo_federation.py" + - "tests/test_poci_cross_repo_federation.py" + +concurrency: + group: poci-federation-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + actions: read + id-token: write + attestations: write + +jobs: + federation: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Compile and run federation mutations + run: | + python3 -m py_compile \ + scripts/verify_poci_cross_repo_federation.py \ + tests/test_poci_cross_repo_federation.py + python3 -m unittest tests.test_poci_cross_repo_federation -v + + - name: Verify ProofPath producer attestation + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p artifacts/poci/federation/attestation-results + gh attestation verify \ + examples/poci-witness/federation/proofpath-attested-quorum-report.json \ + --repo safal207/ProofPath \ + --signer-workflow \ + safal207/ProofPath/.github/workflows/poci-signed-witness-network.yml \ + --source-digest 3a5dde912c44e0957204d3df733e712c596455f1 \ + --signer-digest 3a5dde912c44e0957204d3df733e712c596455f1 \ + --cert-oidc-issuer https://token.actions.githubusercontent.com \ + --deny-self-hosted-runners \ + --format json \ + > artifacts/poci/federation/attestation-results/producer.json + + - name: Verify Ibex external-consumer attestation + env: + GH_TOKEN: ${{ github.token }} + run: | + gh attestation verify \ + examples/poci-witness/federation/ibex-external-consumer-receipt.json \ + --repo safal207/ibex-agent-verification \ + --signer-workflow \ + safal207/ibex-agent-verification/.github/workflows/proofpath-poci-consumer.yml \ + --source-digest 24efa1eadd72275bcc29f0c436e46fb508aac113 \ + --signer-digest 24efa1eadd72275bcc29f0c436e46fb508aac113 \ + --cert-oidc-issuer https://token.actions.githubusercontent.com \ + --deny-self-hosted-runners \ + --format json \ + > artifacts/poci/federation/attestation-results/external-consumer.json + + - name: Build cross-repository federation report + run: | + python3 scripts/verify_poci_cross_repo_federation.py \ + examples/poci-witness/federation/policy.json \ + --producer-report \ + examples/poci-witness/federation/proofpath-attested-quorum-report.json \ + --consumer-receipt \ + examples/poci-witness/federation/ibex-external-consumer-receipt.json \ + --producer-attestation-result \ + artifacts/poci/federation/attestation-results/producer.json \ + --consumer-attestation-result \ + artifacts/poci/federation/attestation-results/external-consumer.json \ + --pretty \ + --output artifacts/poci/federation/federation-report.json + + - name: Guard accepted two-domain federation + run: | + python3 - <<'PY' + import json + from pathlib import Path + + report = json.loads( + Path("artifacts/poci/federation/federation-report.json").read_text( + encoding="utf-8" + ) + ) + if report.get("decision") != "ACCEPT" or report.get("valid") is not True: + raise SystemExit("cross-repository federation did not ACCEPT") + if report.get("domain_count") != 2: + raise SystemExit("federation must contain exactly two domains") + if report.get("external_consumer_required") is not True: + raise SystemExit("external consumer must be mandatory") + domains = report.get("domains", []) + repositories = {item.get("repository") for item in domains} + workflows = {item.get("workflow") for item in domains} + if len(repositories) != 2 or len(workflows) != 2: + raise SystemExit("repository and workflow identities must differ") + verification = report.get("verification", {}) + if not verification or any(value is not True for value in verification.values()): + raise SystemExit("all federation verification flags must be true") + if len(report.get("consensus", {}).get("graph_roots", {})) != 6: + raise SystemExit("federation consensus must commit six graph roots") + root = report.get("federation_root") + if not isinstance(root, str) or not root.startswith("sha256:"): + raise SystemExit("federation root is missing") + print("PASS two-domain PoCI federation (ProofPath + Ibex)") + PY + + - name: Attest final federation report + uses: actions/attest@v4 + with: + subject-path: artifacts/poci/federation/federation-report.json + + - name: Upload federation evidence + uses: actions/upload-artifact@v4 + with: + name: poci-cross-repo-federation-${{ github.run_id }} + path: artifacts/poci/federation + if-no-files-found: error + retention-days: 14 From 0a821562a7336c918ab76006dd47d8dc8f8a6fa6 Mon Sep 17 00:00:00 2001 From: Aleksey Safonov <55020240+safal207@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:19:51 +0300 Subject: [PATCH 8/8] docs: define PoCI cross-repository federation --- docs/POCI_CROSS_REPO_FEDERATION.md | 125 +++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 docs/POCI_CROSS_REPO_FEDERATION.md diff --git a/docs/POCI_CROSS_REPO_FEDERATION.md b/docs/POCI_CROSS_REPO_FEDERATION.md new file mode 100644 index 0000000..89123ca --- /dev/null +++ b/docs/POCI_CROSS_REPO_FEDERATION.md @@ -0,0 +1,125 @@ +# PoCI cross-repository federation v0.1 + +Status: executable experimental profile +Profile: `proofpath.poci.cross-repo-federation.v0.1` + +## Purpose + +A three-runner quorum inside ProofPath proves process and runner separation. An +Ibex consumer receipt proves that a second repository can verify the producer +attestation and independently reproduce the six-graph transition space. The +federation profile closes the loop by verifying both keyless identities and +binding their common consensus into one federation root. + +```text +ProofPath three-runner quorum + ↓ ProofPath Sigstore identity +Ibex external recomputation + ↓ Ibex Sigstore identity +ProofPath federation verifier + ↓ +two-domain federation root +``` + +## Required domains + +Version 0.1 requires exactly two distinct repository/workflow domains: + +1. **Producer domain** — the attested ProofPath quorum report. +2. **External-consumer domain** — the attested Ibex consumer receipt. + +Different jobs inside one workflow do not count as different federation +domains. The repository and signer-workflow identities must both differ. + +## Verified bindings + +The federation verifier checks: + +- exact SHA-256 bytes of the producer report and consumer receipt; +- successful keyless attestation verification for both subjects; +- pinned repository, signer workflow, source SHA, signer SHA, GitHub OIDC issuer, + and GitHub-hosted runner boundary; +- accepted producer quorum with three verified witness attestations; +- accepted external consumer receipt; +- the external receipt's own domain-separated integrity root; +- the external receipt's binding back to the exact ProofPath producer report; +- equality of round, consensus root, source digest, graph set, envelope, + six graph roots, transition-cell root, and multi-graph root. + +## Decision semantics + +```text +CHALLENGE > BLOCK > ACCEPT +``` + +- `ACCEPT` — both domains are attested and agree exactly. +- `BLOCK` — a required domain, attestation, identity, profile, or verification + flag is absent or invalid. +- `CHALLENGE` — committed bytes, roots, or consensus fields contradict each + other or the pinned policy. + +Representative reason codes: + +- `FEDERATION_POLICY_INVALID` +- `FEDERATION_DOMAIN_NOT_DISTINCT` +- `FEDERATION_PRODUCER_ATTESTATION_MISSING` +- `FEDERATION_CONSUMER_ATTESTATION_MISSING` +- `FEDERATION_PRODUCER_SUBJECT_MISMATCH` +- `FEDERATION_CONSUMER_SUBJECT_MISMATCH` +- `FEDERATION_PRODUCER_REPORT_INVALID` +- `FEDERATION_CONSUMER_RECEIPT_INVALID` +- `FEDERATION_CONSUMER_RECEIPT_ROOT_MISMATCH` +- `FEDERATION_PRODUCER_CONSENSUS_MISMATCH` +- `FEDERATION_CONSUMER_CONSENSUS_MISMATCH` +- `FEDERATION_CROSS_DOMAIN_MISMATCH` + +## Canonical federation root + +The complete federation report is copied, `federation_root` is set to `null`, +canonical JSON is encoded, and SHA-256 is computed with domain prefix: + +```text +proofpath:poci:cross-repo-federation:v0.1:root\n +``` + +The root includes digests of the actual `gh attestation verify` result objects, +so it commits to the two provenance-verification executions as well as their +subjects. + +## Mutation coverage + +The focused corpus covers: + +- producer subject substitution; +- external-consumer subject substitution; +- missing producer or consumer attestation; +- same-repository pseudo-federation; +- graph-root substitution; +- external receipt-root tampering; +- external workflow substitution; +- deterministic federation roots. + +## Reviewer command + +After obtaining both `gh attestation verify --format json` result files: + +```bash +python3 scripts/verify_poci_cross_repo_federation.py \ + examples/poci-witness/federation/policy.json \ + --producer-report \ + examples/poci-witness/federation/proofpath-attested-quorum-report.json \ + --consumer-receipt \ + examples/poci-witness/federation/ibex-external-consumer-receipt.json \ + --producer-attestation-result producer-attestation.json \ + --consumer-attestation-result consumer-attestation.json \ + --pretty +``` + +## Honest boundary + +ProofPath and Ibex now provide different repositories, commits, workflows, +runners, artifacts, and Sigstore identities. Both repositories are currently +controlled by the same GitHub account owner. This proves repository/workflow +federation and independent recomputation, not independent organizational +governance. The federation proves committed evidence consistency, not objective +real-world truth.