From ee1410a665bbf2d5fe461832a5eb6ac0f2e37f2e Mon Sep 17 00:00:00 2001 From: Rajnish Tiwari <121179385+rajnisht7@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:59:10 +0530 Subject: [PATCH 01/11] Update sbom.yml --- .github/workflows/sbom.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 3fe4f109..584504dd 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -9,22 +9,21 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" -permissions: - contents: write - id-token: write - jobs: sbom: runs-on: ubuntu-latest + permissions: + contents: write + id-token: write steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Generate SBOM - uses: anchore/sbom-action@v0 + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 with: format: spdx-json output-file: sbom.spdx.json - name: Upload SBOM artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sbom path: sbom.spdx.json From c388ae87da0af137819b0a7cf705274b504c201b Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 03:33:45 +0530 Subject: [PATCH 02/11] initial commit --- src/cmcp_runtime/agent_manifest.py | 41 ++++++++++++++++++++++ src/cmcp_runtime/startup.py | 1 + tests/unit/test_agent_manifest.py | 48 ++++++++++++++++++++++++-- tests/unit/test_agent_manifest_cose.py | 2 ++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/cmcp_runtime/agent_manifest.py b/src/cmcp_runtime/agent_manifest.py index d54c60fd..29098549 100644 --- a/src/cmcp_runtime/agent_manifest.py +++ b/src/cmcp_runtime/agent_manifest.py @@ -14,6 +14,7 @@ import agent_manifest as agent_manifest_sdk from cmcp_runtime.errors import ConfigError +from cmcp_runtime.config import EnforcementMode SIGNED_FIELDS: tuple[str, ...] = tuple(agent_manifest_sdk.SIGNED_FIELDS) @@ -23,6 +24,21 @@ SubjectSource = Literal["config", "svid", "manifest-dev"] +# Spec 6.2: cMCP's own enforcement modes ("enforcing"/"advisory"/"silent", +# cmcp_runtime.config.EnforcementMode) and the Agent Manifest spec's +# ("enforce"/"advisory"/"audit-only") name the same three states differently. +# This is the single place that mapping is defined -- see +# agentrust-io/cmcp#576 and agentrust-io/agent-manifest#178. +_ENFORCEMENT_MODE_TO_MANIFEST: dict[EnforcementMode, str] = { + EnforcementMode.ENFORCING: "enforce", + EnforcementMode.ADVISORY: "advisory", + EnforcementMode.SILENT: "audit-only", +} + + +def enforcement_mode_for_manifest(mode: EnforcementMode) -> str: + """Map cMCP's enforcement mode to the Agent Manifest spec's vocabulary.""" + return _ENFORCEMENT_MODE_TO_MANIFEST[mode] @dataclass(frozen=True) class AgentManifestBinding: @@ -46,6 +62,14 @@ class AgentManifestBinding: #: and an intent statement is business context. A verifier that wants the #: text fetches the manifest it already has to fetch to check the signature. intent_hash: str | None = None + #: Spec 6.2. The runtime's own enforcement mode (cmcp_runtime.config. + #: EnforcementMode), present only when verify_agent_manifest_binding was + #: given one -- which, by the time this object exists, means it has + #: already been cross-checked against the manifest's declared + #: artifacts.policy_bundle.enforcement_mode. A mismatch raises before this + #: object is constructed, so a populated value here is an attested match, + #: not merely "what the caller asked for". + enforcement_mode: EnforcementMode | None = None def _b64url_decode(value: str) -> bytes: @@ -203,6 +227,14 @@ def _raise_for_sdk_result(result: Any, *, require_runtime_artifacts: bool) -> No return mismatch_fields = {str(detail.field) for detail in result.mismatch_details} + # Checked before the plain "policy_bundle" case: the hash can match while + # only the enforcement mode disagrees (spec 6.2), and that deserves its + # own message rather than being reported as a hash mismatch. + if "policy_bundle.enforcement_mode" in mismatch_fields: + raise ConfigError( + "Agent Manifest policy bundle enforcement_mode does not match the " + "runtime's attested enforcement mode" + ) if "policy_bundle" in mismatch_fields: raise ConfigError("Agent Manifest policy bundle hash does not match runtime policy") if "tool_manifest" in mismatch_fields: @@ -236,6 +268,7 @@ def _verify_with_sdk( *, policy_bundle_hash: str | None = None, tool_catalog_hash: str | None = None, + enforcement_mode: EnforcementMode | None = None, require_runtime_artifacts: bool = False, envelope: bytes | None = None, ) -> None: @@ -247,6 +280,11 @@ def _verify_with_sdk( agent_manifest_sdk.VerificationContext( policy_bundle_hash=policy_bundle_hash, tool_catalog_hash=tool_catalog_hash, + enforcement_mode=( + enforcement_mode_for_manifest(enforcement_mode) + if enforcement_mode is not None + else None + ), trusted_keys=_trusted_keys_for_sdk(trusted_keys), ), agent_manifest_sdk.RevocationStore(), @@ -314,6 +352,7 @@ def verify_agent_manifest_binding( authenticated_subject: str | None, policy_bundle_hash: str, tool_catalog_hash: str, + enforcement_mode: EnforcementMode | None = None, authenticated_subject_source: str | None = None, allow_dev_subject_from_manifest: bool = False, now: datetime | None = None, @@ -341,6 +380,7 @@ def verify_agent_manifest_binding( trusted_keys, policy_bundle_hash=policy_bundle_hash, tool_catalog_hash=tool_catalog_hash, + enforcement_mode=enforcement_mode, require_runtime_artifacts=True, envelope=envelope, ) @@ -377,4 +417,5 @@ def verify_agent_manifest_binding( # intent taken from an unverified manifest is an intent anyone could # have written, which is the failure the field exists to prevent. intent_hash=agent_manifest_sdk.intent_hash(manifest), + enforcement_mode=enforcement_mode, ) diff --git a/src/cmcp_runtime/startup.py b/src/cmcp_runtime/startup.py index 7737bdbe..88725291 100644 --- a/src/cmcp_runtime/startup.py +++ b/src/cmcp_runtime/startup.py @@ -641,6 +641,7 @@ def run_startup(config_path: str) -> RuntimeContext: authenticated_subject=config.agent_manifest.authenticated_subject, policy_bundle_hash=policy_bundle.bundle_hash, tool_catalog_hash=catalog.catalog_hash, + enforcement_mode=config.enforcement_mode, allow_dev_subject_from_manifest=config.dev_mode, ) except ConfigError as exc: diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index 8b98344d..2ec54efd 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -20,6 +20,7 @@ verify_agent_manifest_binding, ) from cmcp_runtime.errors import ConfigError +from cmcp_runtime.config import EnforcementMode POLICY_HASH = "sha256:" + "a" * 64 CATALOG_HASH = "sha256:" + "b" * 64 @@ -91,11 +92,11 @@ def test_valid_manifest_binds_subject_policy_and_catalog() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) assert binding.manifest_id == manifest["manifest_id"] assert binding.agent_id == AGENT_ID - assert binding.subject_source == "config" - assert binding.issuer_key_id == key_id + assert binding.enforcement_mode == EnforcementMode.ENFORCING def test_binding_verification_delegates_to_sdk_with_encoded_keys(monkeypatch) -> None: @@ -131,6 +132,7 @@ def fake_verify_manifest(manifest_arg, context, revocation_store): authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) assert binding.manifest_id == manifest["manifest_id"] @@ -151,6 +153,7 @@ def test_dev_subject_fallback_is_marked_as_manifest_dev() -> None: authenticated_subject=None, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, allow_dev_subject_from_manifest=True, ) assert binding.authenticated_subject == AGENT_ID @@ -167,9 +170,45 @@ def test_subject_mismatch_fails_closed() -> None: authenticated_subject="spiffe://factory.example/agent/other/dev", policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) + +def test_enforcement_mode_mismatch_fails_closed() -> None: + # The manifest's policy_bundle declares "enforce" (see _signed_manifest); + # a runtime that is only attested as running in advisory mode must not + # bind, even though the policy_bundle hash itself matches. See + # agentrust-io/cmcp#576 / agent-manifest spec 6.2. + priv, pub, key_id = _keypair() + manifest = _signed_manifest(priv, key_id) + with pytest.raises(ConfigError, match="enforcement_mode"): + verify_agent_manifest_binding( + manifest, + {key_id: pub}, + authenticated_subject=AGENT_ID, + policy_bundle_hash=POLICY_HASH, + tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ADVISORY, + ) + + +def test_enforcement_mode_not_provided_fails_closed() -> None: + # A runtime that can't or doesn't attest its enforcement mode must not be + # treated as matching a manifest that declares one -- unattested is not + # evidence of compliance. + priv, pub, key_id = _keypair() + manifest = _signed_manifest(priv, key_id) + with pytest.raises(ConfigError, match="enforcement_mode"): + verify_agent_manifest_binding( + manifest, + {key_id: pub}, + authenticated_subject=AGENT_ID, + policy_bundle_hash=POLICY_HASH, + tool_catalog_hash=CATALOG_HASH, + ) + + def test_tampered_manifest_signature_fails_closed() -> None: priv, pub, key_id = _keypair() manifest = _signed_manifest(priv, key_id) @@ -181,6 +220,7 @@ def test_tampered_manifest_signature_fails_closed() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) @@ -194,6 +234,7 @@ def test_policy_hash_drift_fails_closed() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash="sha256:" + "0" * 64, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) @@ -207,6 +248,7 @@ def test_catalog_hash_drift_fails_closed() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash="sha256:" + "0" * 64, + enforcement_mode=EnforcementMode.ENFORCING, ) @@ -220,6 +262,7 @@ def test_expired_manifest_fails_closed() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, now=datetime(2026, 6, 17, tzinfo=UTC), ) @@ -284,4 +327,5 @@ def test_a_mislabelled_post_quantum_manifest_fails_closed_cleanly() -> None: authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, ) diff --git a/tests/unit/test_agent_manifest_cose.py b/tests/unit/test_agent_manifest_cose.py index a916a217..1612abe1 100644 --- a/tests/unit/test_agent_manifest_cose.py +++ b/tests/unit/test_agent_manifest_cose.py @@ -26,6 +26,7 @@ load_agent_manifest_document, verify_agent_manifest_binding, ) +from cmcp_runtime.config import EnforcementMode from cmcp_runtime.errors import ConfigError POLICY_HASH = "sha256:" + "a" * 64 @@ -90,6 +91,7 @@ def _bind(loaded, trusted_keys): authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, + enforcement_mode=EnforcementMode.ENFORCING, envelope=loaded.envelope, ) From a17953027bd15f4db72d202af2bdf7d2c4d8c403 Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 03:38:37 +0530 Subject: [PATCH 03/11] fix test Signed-off-by: rajnisht7 --- src/cmcp_runtime/startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmcp_runtime/startup.py b/src/cmcp_runtime/startup.py index 88725291..0e8c0c14 100644 --- a/src/cmcp_runtime/startup.py +++ b/src/cmcp_runtime/startup.py @@ -641,7 +641,7 @@ def run_startup(config_path: str) -> RuntimeContext: authenticated_subject=config.agent_manifest.authenticated_subject, policy_bundle_hash=policy_bundle.bundle_hash, tool_catalog_hash=catalog.catalog_hash, - enforcement_mode=config.enforcement_mode, + enforcement_mode=config.attestation.enforcement_mode, allow_dev_subject_from_manifest=config.dev_mode, ) except ConfigError as exc: From c7ef7090ce4621d262c3a70c176a95e717c8db24 Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 03:48:04 +0530 Subject: [PATCH 04/11] fix lint --- src/cmcp_runtime/agent_manifest.py | 2 +- tests/unit/test_agent_manifest.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cmcp_runtime/agent_manifest.py b/src/cmcp_runtime/agent_manifest.py index 29098549..15afa4d3 100644 --- a/src/cmcp_runtime/agent_manifest.py +++ b/src/cmcp_runtime/agent_manifest.py @@ -13,8 +13,8 @@ import agent_manifest as agent_manifest_sdk -from cmcp_runtime.errors import ConfigError from cmcp_runtime.config import EnforcementMode +from cmcp_runtime.errors import ConfigError SIGNED_FIELDS: tuple[str, ...] = tuple(agent_manifest_sdk.SIGNED_FIELDS) diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index 2ec54efd..c152cf6e 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -19,8 +19,8 @@ signing_pre_image, verify_agent_manifest_binding, ) -from cmcp_runtime.errors import ConfigError from cmcp_runtime.config import EnforcementMode +from cmcp_runtime.errors import ConfigError POLICY_HASH = "sha256:" + "a" * 64 CATALOG_HASH = "sha256:" + "b" * 64 @@ -132,7 +132,7 @@ def fake_verify_manifest(manifest_arg, context, revocation_store): authenticated_subject=AGENT_ID, policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, - enforcement_mode=EnforcementMode.ENFORCING, + enforcement_mode=EnforcementMode.ENFORCING, ) assert binding.manifest_id == manifest["manifest_id"] @@ -207,7 +207,7 @@ def test_enforcement_mode_not_provided_fails_closed() -> None: policy_bundle_hash=POLICY_HASH, tool_catalog_hash=CATALOG_HASH, ) - + def test_tampered_manifest_signature_fails_closed() -> None: priv, pub, key_id = _keypair() From dff1fbc3837fffcdbc34bed419f5a9292884bf0b Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 04:19:46 +0530 Subject: [PATCH 05/11] fix test --- tests/unit/test_agent_manifest.py | 39 ++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index c152cf6e..cba51dd2 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -27,6 +27,29 @@ AGENT_ID = "spiffe://factory.example/agent/material-movement/dev" +def _agent_manifest_sdk_supports_enforcement_mode() -> bool: + """Feature-detect enforcement_mode support in the installed agent-manifest SDK. + + cmcp#584 depends on agent-manifest#345 (open, unreleased). Until that ships + and cmcp's pyproject.toml pin is bumped, the installed VerificationContext + silently drops an unknown enforcement_mode kwarg (Pydantic ignores unknown + fields by default), so the mismatch never surfaces as a ConfigError. + """ + from agent_manifest import VerificationContext + + return "enforcement_mode" in VerificationContext.model_fields + + +requires_enforcement_mode_support = pytest.mark.skipif( + not _agent_manifest_sdk_supports_enforcement_mode(), + reason=( + "installed agent-manifest does not support enforcement_mode yet " + "(tracked in agentrust-io/agent-manifest#345); remove this skip once " + "it ships and cmcp's pyproject.toml pin is bumped" + ), +) + + def _b64url(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode() @@ -174,7 +197,7 @@ def test_subject_mismatch_fails_closed() -> None: ) - +@requires_enforcement_mode_support def test_enforcement_mode_mismatch_fails_closed() -> None: # The manifest's policy_bundle declares "enforce" (see _signed_manifest); # a runtime that is only attested as running in advisory mode must not @@ -193,6 +216,7 @@ def test_enforcement_mode_mismatch_fails_closed() -> None: ) +@requires_enforcement_mode_support def test_enforcement_mode_not_provided_fails_closed() -> None: # A runtime that can't or doesn't attest its enforcement mode must not be # treated as matching a manifest that declares one -- unattested is not @@ -329,3 +353,16 @@ def test_a_mislabelled_post_quantum_manifest_fails_closed_cleanly() -> None: tool_catalog_hash=CATALOG_HASH, enforcement_mode=EnforcementMode.ENFORCING, ) + + +def test_enforcement_mode_skip_markers_are_still_needed() -> None: + # Canary: once agent-manifest#345 ships and cmcp's pyproject.toml pin is + # bumped past it, this starts failing on purpose -- that is the signal to + # delete requires_enforcement_mode_support and its two @-markers above. + if _agent_manifest_sdk_supports_enforcement_mode(): + pytest.fail( + "installed agent-manifest now supports enforcement_mode -- remove " + "requires_enforcement_mode_support and the @requires_enforcement_mode_support " + "markers on test_enforcement_mode_mismatch_fails_closed and " + "test_enforcement_mode_not_provided_fails_closed, then delete this test" + ) \ No newline at end of file From 36ee203ebb89a5229466df108766ae56ee32547c Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 04:37:51 +0530 Subject: [PATCH 06/11] fix lint --- tests/unit/test_agent_manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index cba51dd2..a6b7abb4 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -365,4 +365,4 @@ def test_enforcement_mode_skip_markers_are_still_needed() -> None: "requires_enforcement_mode_support and the @requires_enforcement_mode_support " "markers on test_enforcement_mode_mismatch_fails_closed and " "test_enforcement_mode_not_provided_fails_closed, then delete this test" - ) \ No newline at end of file + ) From 89155a817295cf26fd20667a0d463f28d687d2b4 Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 04:56:21 +0530 Subject: [PATCH 07/11] fix coverage --- src/cmcp_runtime/agent_manifest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cmcp_runtime/agent_manifest.py b/src/cmcp_runtime/agent_manifest.py index 15afa4d3..1c8fadca 100644 --- a/src/cmcp_runtime/agent_manifest.py +++ b/src/cmcp_runtime/agent_manifest.py @@ -231,7 +231,12 @@ def _raise_for_sdk_result(result: Any, *, require_runtime_artifacts: bool) -> No # only the enforcement mode disagrees (spec 6.2), and that deserves its # own message rather than being reported as a hash mismatch. if "policy_bundle.enforcement_mode" in mismatch_fields: - raise ConfigError( + raise ConfigError( # pragma: no cover -- exercised by + # test_enforcement_mode_mismatch_fails_closed / + # test_enforcement_mode_not_provided_fails_closed, currently + # skipped pending agentrust-io/agent-manifest#345 (see + # tests/unit/test_agent_manifest.py). Remove this pragma + # alongside the skip markers once that ships. "Agent Manifest policy bundle enforcement_mode does not match the " "runtime's attested enforcement mode" ) From 005332afca7dcfd5366740328a385fb2671722a6 Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 10:48:15 +0530 Subject: [PATCH 08/11] remove skipping tests --- src/cmcp_runtime/agent_manifest.py | 10 ++------ tests/unit/test_agent_manifest.py | 39 +----------------------------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/src/cmcp_runtime/agent_manifest.py b/src/cmcp_runtime/agent_manifest.py index 1c8fadca..16a7ee90 100644 --- a/src/cmcp_runtime/agent_manifest.py +++ b/src/cmcp_runtime/agent_manifest.py @@ -27,8 +27,7 @@ # Spec 6.2: cMCP's own enforcement modes ("enforcing"/"advisory"/"silent", # cmcp_runtime.config.EnforcementMode) and the Agent Manifest spec's # ("enforce"/"advisory"/"audit-only") name the same three states differently. -# This is the single place that mapping is defined -- see -# agentrust-io/cmcp#576 and agentrust-io/agent-manifest#178. +# This is the single place that mapping is defined _ENFORCEMENT_MODE_TO_MANIFEST: dict[EnforcementMode, str] = { EnforcementMode.ENFORCING: "enforce", EnforcementMode.ADVISORY: "advisory", @@ -231,12 +230,7 @@ def _raise_for_sdk_result(result: Any, *, require_runtime_artifacts: bool) -> No # only the enforcement mode disagrees (spec 6.2), and that deserves its # own message rather than being reported as a hash mismatch. if "policy_bundle.enforcement_mode" in mismatch_fields: - raise ConfigError( # pragma: no cover -- exercised by - # test_enforcement_mode_mismatch_fails_closed / - # test_enforcement_mode_not_provided_fails_closed, currently - # skipped pending agentrust-io/agent-manifest#345 (see - # tests/unit/test_agent_manifest.py). Remove this pragma - # alongside the skip markers once that ships. + raise ConfigError( "Agent Manifest policy bundle enforcement_mode does not match the " "runtime's attested enforcement mode" ) diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index a6b7abb4..c152cf6e 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -27,29 +27,6 @@ AGENT_ID = "spiffe://factory.example/agent/material-movement/dev" -def _agent_manifest_sdk_supports_enforcement_mode() -> bool: - """Feature-detect enforcement_mode support in the installed agent-manifest SDK. - - cmcp#584 depends on agent-manifest#345 (open, unreleased). Until that ships - and cmcp's pyproject.toml pin is bumped, the installed VerificationContext - silently drops an unknown enforcement_mode kwarg (Pydantic ignores unknown - fields by default), so the mismatch never surfaces as a ConfigError. - """ - from agent_manifest import VerificationContext - - return "enforcement_mode" in VerificationContext.model_fields - - -requires_enforcement_mode_support = pytest.mark.skipif( - not _agent_manifest_sdk_supports_enforcement_mode(), - reason=( - "installed agent-manifest does not support enforcement_mode yet " - "(tracked in agentrust-io/agent-manifest#345); remove this skip once " - "it ships and cmcp's pyproject.toml pin is bumped" - ), -) - - def _b64url(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode() @@ -197,7 +174,7 @@ def test_subject_mismatch_fails_closed() -> None: ) -@requires_enforcement_mode_support + def test_enforcement_mode_mismatch_fails_closed() -> None: # The manifest's policy_bundle declares "enforce" (see _signed_manifest); # a runtime that is only attested as running in advisory mode must not @@ -216,7 +193,6 @@ def test_enforcement_mode_mismatch_fails_closed() -> None: ) -@requires_enforcement_mode_support def test_enforcement_mode_not_provided_fails_closed() -> None: # A runtime that can't or doesn't attest its enforcement mode must not be # treated as matching a manifest that declares one -- unattested is not @@ -353,16 +329,3 @@ def test_a_mislabelled_post_quantum_manifest_fails_closed_cleanly() -> None: tool_catalog_hash=CATALOG_HASH, enforcement_mode=EnforcementMode.ENFORCING, ) - - -def test_enforcement_mode_skip_markers_are_still_needed() -> None: - # Canary: once agent-manifest#345 ships and cmcp's pyproject.toml pin is - # bumped past it, this starts failing on purpose -- that is the signal to - # delete requires_enforcement_mode_support and its two @-markers above. - if _agent_manifest_sdk_supports_enforcement_mode(): - pytest.fail( - "installed agent-manifest now supports enforcement_mode -- remove " - "requires_enforcement_mode_support and the @requires_enforcement_mode_support " - "markers on test_enforcement_mode_mismatch_fails_closed and " - "test_enforcement_mode_not_provided_fails_closed, then delete this test" - ) From 9dd39d246918ab39e478922b5d60df51349448eb Mon Sep 17 00:00:00 2001 From: rajnisht7 Date: Thu, 27 Aug 2026 10:53:04 +0530 Subject: [PATCH 09/11] update --- tests/unit/test_agent_manifest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/test_agent_manifest.py b/tests/unit/test_agent_manifest.py index c152cf6e..e977f895 100644 --- a/tests/unit/test_agent_manifest.py +++ b/tests/unit/test_agent_manifest.py @@ -178,8 +178,7 @@ def test_subject_mismatch_fails_closed() -> None: def test_enforcement_mode_mismatch_fails_closed() -> None: # The manifest's policy_bundle declares "enforce" (see _signed_manifest); # a runtime that is only attested as running in advisory mode must not - # bind, even though the policy_bundle hash itself matches. See - # agentrust-io/cmcp#576 / agent-manifest spec 6.2. + # bind, even though the policy_bundle hash itself matches. priv, pub, key_id = _keypair() manifest = _signed_manifest(priv, key_id) with pytest.raises(ConfigError, match="enforcement_mode"): From f4084e7b73732de7b998b5fc15f8c8e5dd69cf5d Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Thu, 27 Aug 2026 16:36:32 -0700 Subject: [PATCH 10/11] fix: require agent-manifest 0.11.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aa39a82e..6cc0f2bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "agentrust-trace>=0.5", # 0.11.1 is the floor: the chained TPM verifier forwards ParsedSignature so # the envelope's declared signature scheme and digest remain authoritative. - "agent-manifest>=0.11.1", + "agent-manifest>=0.11.2", "cryptography>=50.0,<51.0", "pyyaml>=6.0", "httpx>=0.27", From 9f00f44ddadae99e2691a8bbe72d7bf5febd39ca Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Thu, 27 Aug 2026 16:36:48 -0700 Subject: [PATCH 11/11] docs: explain agent-manifest dependency floor --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6cc0f2bb..a2ce039b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,8 @@ classifiers = [ requires-python = ">=3.11" dependencies = [ "agentrust-trace>=0.5", - # 0.11.1 is the floor: the chained TPM verifier forwards ParsedSignature so - # the envelope's declared signature scheme and digest remain authoritative. + # 0.11.2 is the floor: verification binds the manifest's declared policy + # enforcement mode to the runtime's attested enforcement mode. "agent-manifest>=0.11.2", "cryptography>=50.0,<51.0", "pyyaml>=6.0",