feat(schema): admit WCM as a marketplace stack - #141
Merged
Conversation
Nothing in this repository could describe a Weight Custody Manifest integration. `integrates_with` was an enum of three values, the catalog generator would KeyError on a fourth, and the Marketplace derives its stack facet from that generator, so a WCM listing was not merely unlisted but unrepresentable. Four changes make one possible: - `integrates_with` accepts `wcm`, and the generator labels it `WCM`. - `wcm_roles` and `wcm_conformance_levels` mirror the TRACE role/level pair. Levels are per-layer identifiers (L1-L4) rather than one number, because WCM is four layers and an integration may verify a manifest without brokering a key. `attestation-source` and `evidence-consumer` verify no manifest and are therefore excluded from the level requirement, the same carve-out `external-evidence-source` already gets on the TRACE side. - A `Model & weight custody` Marketplace category, so custody integrations are browsable as a group instead of scattered across Security & policy. - A `weight-custody-manifest` floor of 0.26.0, the current PyPI release. CONTRIBUTING gains a hard rule about links: the WCM source repository is still private, so any github.com/agentrust-io/weight-custody-manifest URL 404s for every anonymous reader. Integrations cite the docs site and PyPI instead. The repository's own pyproject already records shipping that exact dead link once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak
imran-siddique
added a commit
that referenced
this pull request
Aug 27, 2026
* feat(wcm): first four Weight Custody Manifest integrations WCM had no integrations at all. These four cover the layers a deployment actually touches first: evidence out (TRACE), the manifest travelling through supply-chain tooling (in-toto), the manifest gating a download (Hugging Face), and the cluster preconditions it implies (Kyverno). Each is built and tested against the published PyPI release, 0.26.0, and links only to the docs site and PyPI, per the rule added in #141. wcm-trace Layer 2 release decisions and Layer 3 custody state as TRACE v0.2 records. runtime.platform names hardware only when the broker's own cpu_quote_verified check passed, so SoftwareProvider evidence produces software-only and no argument can override it. A refusal is a record too: appraisal.status contraindicated, with the failed checks named. policy.bundle_hash digests the signing pre-image rather than the document, so countersigning does not move a hash consumers compare terms by. wcm-in-toto Statement v1 wrapper. The predicate embeds the jointly-signed manifest whole rather than summarizing it: a DSSE signature answers who built the envelope, which is a different question from who agreed the custody terms, and a summary would leave a consumer trusting the transcription. Verification keeps the subject binding and the manifest signatures as separate reported facts, because a manifest stapled to a different artifact is the attack this format invites. wcm-huggingface Refuses a snapshot whose bytes do not hash to the bound weights_hash. Layer 1 only, and the README says so: a public Hub repository is not an enclave. An immutable commit sha is required unless the caller opts out, because verifying a snapshot fetched from a branch is not a claim anyone can re-check. Failed downloads are left on disk rather than deleted, so the evidence of what was served survives. wcm-kyverno The interesting one is what it refuses to do. Checking pod image digests against required_serving_image.accepted_measurements looks obvious and is wrong: those are launch measurements from a quote, not OCI digests. The failure mode is not a broken policy but someone "fixing" the mismatch by putting registry digests into the field the broker compares against a quote. So the generator enforces what admission genuinely can (confidential runtime class, no process-inspection escape hatches, digest-pinned images, a manifest-hash binding) and prints the boundary as a header comment in the emitted YAML. The artifact digest recipe in wcm-huggingface is duplicated from the WCM examples repository rather than imported, because it is not in the published SDK. It belongs there; this is flagged in the README so a third dialect does not appear. Tests: 82 across the four, all against PyPI 0.26.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak * feat(wcm): telemetry, registry and Azure key-release integrations Three more, and two of them exist mainly to refuse something. wcm-opentelemetry Release decisions and custody state as OTel spans and metrics, under a wcm.* attribute set because GenAI semconv has no vocabulary for weight custody and bending gen_ai.* into meanings its authors did not give it would be worse than a namespace. Every attribute is described in ATTRIBUTES and a test rejects emitting one that is not. Both the claimed platform and whether the broker verified it are exported: a dashboard showing only the claim presents it as a fact. Metrics carry a narrow attribute set on purpose, because a counter keyed by manifest hash is one time series per revision. The README says plainly that telemetry is not evidence and points at wcm-trace. wcm-oci Referrer artifact, and the difficulty is a digest-domain gap: an OCI subject digest names a registry manifest, weights_hash covers weight bytes, and no registry can derive one from the other. So weights_binding is a three-valued string (layer-digest / annotation-only / unbound) rather than a boolean, and `trusted` requires layer-digest. annotation-only may be good enough for a given deployment, but that has to be decided rather than inherited from a property that quietly accepted less. wcm-azure-skr SKR is Layer 2 in Azure's encoding, so the translation is natural until the measurements. WCM's HashValue is 256-bit; x-ms-sevsnpvm-launchmeasurement is 384-bit. They cannot be compared, and on Azure the WCM path binds a SHA-256 PCR 23 digest, a different value from a different chain. A generator that emitted the obvious condition would produce a policy that never matches and an engineer who concludes the CVM is broken; the plausible fix, widening the manifest field, breaks the broker's binding. So measurement_claim is required and its width is checked at generation time. evidence_from_maa_claims takes a claims mapping rather than a JWT so it cannot be mistaken for a verifier. Tests: 68 across the three, against PyPI 0.26.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak * feat(wcm): Trustee, NVIDIA GPU and Confidential Space integrations wcm-coco-trustee Rego resource policy for the confidential-containers key broker. Defaults to the azsnpvtpm/aztdxvtpm TEE names rather than snp/tdx, because that is where a 256-bit WCM measurement and a Trustee claim are the same width; the bare metal names carry 384-bit launch measurements a WCM HashValue cannot hold. Every value interpolated into the Rego is a validated hex digest, a fixed TEE name, or a dotted-identifier claim path: a generator that concatenated arbitrary input into the policy deciding who gets model keys would be an injection vector, and there are tests that try. wcm-nvidia-nras Promoted from the WCM repository's validated hardware tooling rather than written from documentation: the required-claim list is what a real H100 in CC mode produced under nvattest --verifier local. The design point is that two appraisals run and neither replaces the other. NVIDIA checks the RIMs and live OCSP against its own reference data; WCM independently verifies the raw report chain, signature and nonce, which is why the raw evidence is carried through in quote_b64 rather than summarized. Both nonces are checked separately, since replay only needs the weaker check to be missing. wcm-gcp-confidential-space The counterpoint to the Azure integration. Confidential Space identifies a workload by its OCI image digest, which is sha256 and 64 hex, the same shape as a WCM HashValue, so the comparison is meaningful and the generator can emit it. The assumption that a manifest's measurements are image digests is stated in the generated command rather than left implicit, and a measurement of another shape is refused with that reason. Tests: 116 across the three, against PyPI 0.26.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak * feat(wcm): serving runtimes, Agent Manifest binding, ML-BOM, and CI The last four of the wave, plus the CI that runs all fourteen. wcm-vllm The honest part is what it does not claim. Layer 3 says a runtime that loses authorization wipes the key. In Python, half of that is achievable: zeroize() clears the SDK's own buffer, but decrypted weights live in heap copies, pinned host buffers and GPU device memory that no Python code reaches. So on_lapse terminates the process via os._exit, and the README says plainly that termination is the wipe. sys.exit would let a framework catch it and serve the next request anyway. The lease is checked per request, not per token, because vLLM has no cheap per-forward hook, and the README says to size the cadence for that. A lapsed lease and an exhausted operation budget are handled differently: one halts the process, the other asks for re-attestation, and conflating them would take a healthy server down. wcm-triton Not a repository agent and could not be one: those are C. This is the step before Triton starts. It takes a decrypt callable rather than choosing a cipher, because WCM specifies no container format and inventing one inside an integration is how a project ends up with three dialects of the same idea. It removes the staging directory on failure, the opposite of the Hugging Face gate, because Triton scans its repository and a wrong directory is loadable, whereas a failed download is evidence of what a registry served. It also carries the test that keeps wcm-artifact-digest/v1 a single recipe: it imports wcm-huggingface and asserts byte equality over the same tree. Two implementations exist because the recipe is not in the published SDK, and the failure mode of that is silent divergence presenting as tampered weights. wcm-agent-manifest Both SDKs define HashValue with the identical pattern, so weights_hash is a valid model_hash verbatim, and hash_grammars_agree() asserts that rather than assuming it. verify_binding returns three separate facts and a property named describes_the_same_weights, at length on purpose: two documents carrying the same digest is not custody enforcement, and a name like `verified` would invite reading one into the other. wcm-cyclonedx Gets custody terms in front of licence review. Fields with no CycloneDX home are dropped rather than stuffed into properties as free text, which would produce a BOM that reads as enforcement and carries strings. Output is reproducible: no self-stamped clock, because that makes every rebuild look like a change. CI runs all fourteen on 3.11-3.13 against pinned PyPI 0.26.0, with the digest parity check split into its own job so that failure is legible rather than buried in a suite of 344. Marketplace: 18 native integrations to 32, with a WCM stack facet and a Model & weight custody category. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak * fix(wcm): name public keys as public, and build CLI reports from primitives Two CI findings from #142. The generated README integration index was stale. Regenerated; fourteen rows. CodeQL raised two high-severity "logs sensitive data as clear text" alerts, on the verify report in wcm-in-toto and wcm-oci. No key material was ever printed, but the analyser is not wrong to be unable to tell. The taint runs from a file read behind an argument called --key, through a VerificationContext, into a result object whose attributes are then spread into a dict and serialized. From outside, that is indistinguishable from a path that prints a key. Both halves of that are worth fixing on their own terms. The material is PUBLIC key material, and nothing in these modules reads, holds or emits a private key. Calling the flag --key made a reader work that out from context. It is now --public-key, and _load_context is _public_key_context with a docstring saying so. wcm-huggingface gets the same treatment for consistency, even though CodeQL did not flag it: leaving one --key behind is how the next person concludes the three disagree on purpose. The reports are now built by a _report() helper that coerces every value to bool, str or None by name. Previously they spread result attributes into a dict literal, which was correct only because of what those results happen to contain. Now an attribute added later cannot reach stdout by being swept into a dict comprehension somebody wrote in a hurry, and the analyser can see the boundary. 344 tests still pass, and the CLI flag rename is carried through the tests and the three READMEs. These integrations are unreleased, so no caller breaks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
imran-siddique
added a commit
to agentrust-io/agentrust-io.github.io
that referenced
this pull request
Aug 27, 2026
Every link from agentrust-io.com/wcm/ into the WCM repository 404s. The repository is private, and a launch page's entire audience is anonymous readers, so all six were broken for exactly the people the page is for: /blob/main/THREAT-MODEL.md "Full threat model" /issues/78 and /issues/79 the two disclosed open limitations /issues "Review open questions", for security researchers /actions the "574 tests passing" evidence claim /tree/main/conformance/vectors the "91 conformance vectors" claim This is the same trap agentrust-io/integrations#141 made a hard CONTRIBUTING rule about, arriving from the other direction: fourteen integration READMEs now point here as "specification and documentation", so a reader following one to find the spec landed on a page whose own links were dead. Each replacement is a real destination rather than a redirect of the problem: Vectors and test count now point at PyPI, and say why in a title attribute. Both are checkable from the published package, because the sdist ships tests/ and the conformance vectors. That is a stronger claim than a CI badge: a reader can reproduce the numbers instead of being shown a green tick. The two limitations lose their hyperlinks and keep their prose. The paragraph already states both in full; the disclosure was always the point and the link only ever pointed into a tracker nobody outside the org can open. "Full threat model" points at the on-page limits section, which is the public statement of the same thing. Security researchers get a mailto, matching the two personas that already use one. An issue tracker they cannot open is not a way in. The evidence strip had also drifted: pinned at a pre-0.27.0 commit, showing 0.26.0 and 574 tests. Now a952cb6, 0.27.0 and 599, with proof.json regenerated from that commit. launch-page.test.js gains the durable part. It previously asserted the issue LINKS were present, which is how these survived: the check enforced the dead link rather than the disclosure. It now asserts the limitation prose, and separately that no link into the private repository exists anywhere on the page. Verified it fails on a reintroduced link. Delete that check when the repository goes public (weight-custody-manifest#40). Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A Weight Custody Manifest integration was not merely unlisted in the Marketplace, it was unrepresentable.
integrates_withis an enum of three values, so schema validation rejectswcm; andgenerate_marketplace_catalog.pymaps that field throughSTACK_LABELS, so a fourth value raisesKeyErrorand takes the whole catalog build down. The public Marketplace derives its stack filter from that catalog, which means this repository is the only place the gap can be closed.This PR adds no integration. It makes the next fourteen possible.
What changed
integrates_withacceptswcmschema/integration.schema.jsonwcmmaps to theWCMstack labelscripts/generate_marketplace_catalog.pywcm_rolesandwcm_conformance_levelsschema/integration.schema.jsonModel & weight custodycategoryschema/integration.schema.jsonweight-custody-manifestfloor at 0.26.0compatibility.yamlCONTRIBUTING.mdTwo decisions worth reviewing
Levels are a list of layer identifiers, not a number.
trace_conformance_levelis a single integer because TRACE conformance is cumulative. WCM is four independent layers (L1 manifest and joint signature, L2 attestation-gated release, L3 runtime custody, L4 derivative lineage), and a registry adapter that verifies manifests has no business claiming the release gate.wcm_conformance_levels: ["L1", "L4"]says exactly what was run.Two roles are exempt from declaring a level.
attestation-sourcehands evidence to somebody else's verifier;evidence-consumerreads custody output downstream. Neither verifies a manifest. This is the same carve-outexternal-evidence-sourcealready has on the TRACE side, and it exists so an adapter cannot be read as having passed a suite it never ran.The link rule
agentrust-io/weight-custody-manifestis still private (issue #40). Everygithub.com/agentrust-io/weight-custody-manifestURL 404s for anonymous readers, and PyPI renders such links live regardless. WCM's ownpyproject.tomlcarries a comment recording that v0.26.0 shipped with exactly those dead links. CONTRIBUTING now makes citing the docs site and PyPI a hard rule rather than a convention.Tests
Seven new cases across
tests/test_integration_schema.pyandtests/test_generate_marketplace_catalog.py: role requirement, level requirement, the attestation-source carve-out, rejection of a numeric level,tested_againstrecording, the new category, and the stack label reaching the catalog. Full suite: 23 passed.validate_integrationsclean on all 18 existing integrations, andgenerate_marketplace_catalog --checkreports the committed catalog is current, so no existing listing shifts.🤖 Generated with Claude Code
https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak