Metrology Contract

The contract that defines a metrology lab, so you can run one without running ours.

The Mandrel Project ships the Metrology Lab: a service that holds an agent registry, runs the promotion workflow, and provides fleet visibility and an audit trail.

Not every deployment wants to run it, and none should have to in order to use Mandrel. So the role that service fills is defined separately, as a contract: mandrel-metrology-spec, Apache 2.0.

Anything satisfying the contract is a metrology lab. The managed service is one conforming implementation. A CI pipeline with a signing key is another.

Artifacts before APIs

The contract splits in two, and the split is the point.

Artifacts are documents produced and consumed — the Realized Spec, the verification key set, exception grants. Enforcement depends only on these, and verification is offline by construction: the Collet checks a signature against a key it already holds and never calls the authority that produced the document.

APIs are how a lab that is a service exposes lifecycle operations. Useful, but nothing in the enforcement path requires them.

graph LR
    SS[Source Spec] --> AUTH
    PP[Platform Policy] --> AUTH

    subgraph AUTH["Any conforming authority"]
        M[Managed Lab]
        H[Self-hosted lab]
        CI[bake + sign in CI]
    end

    subgraph ART["Artifacts"]
        RS[Realized Spec + signature]
        VK[Verification key set]
        EG[Exception grant]
    end

    AUTH --> ART
    ART --> COL[Collet<br/>verifies offline]

The consequence worth internalizing: producing realized specs needs no HTTP server, no database and no uptime. A pipeline job that bakes, signs and writes a file satisfies the mandatory profile completely.

Conformance profiles

ProfileRequiredContains
CoreyesProduce a valid signed Realized Spec; publish a verification key
RegistrynoAgent lifecycle and promotion API
RefDatanoPlatform policy and chip rate table serving
ApprovalnoHITL step-up token issuance — only where the operator’s IdP cannot do RFC 9470
DelegationnoRFC 8693 assertion minting — only where the IdP cannot do token exchange

A client with no lab implements Core and nothing else. A client replacing the managed service implements Core, Registry and RefData. Approval and Delegation exist because some identity providers cannot do the job; they should be the exception, not the assumption.

What Core actually requires

Three documents in the contract repository are normative. They are not reproduced here — duplicating a normative document onto a website is how the two drift.

DocumentPins down
docs/canonicalization.mdRFC 8785 (JCS) plus the YAML 1.2 core-schema profile it needs, and the two hashes
docs/signing.mdSignature placement and coverage, the claim set, algorithm agility, verification order
docs/baking.mdCeilings and injection, workload reference resolution, extension injection

The canonicalization document is longer than it looks like it should be, for a reason worth repeating: the divergence between implementations is not in the JSON step. It is in the YAML step. A library that resolves an unquoted 2026-01-01 to a timestamp, or yes to a boolean, produces a different hash from a document that neither implementation rejected — and the signature then fails to verify with nothing pointing at the cause.

Conformance is falsifiable

vectors/ in the contract repository holds the test cases. They are the enforcement mechanism: a MUST with no test vector is a suggestion.

Rejection cases carry as much weight as acceptance cases. An implementation that accepts a document it should reject produces a hash where a conformant one produces an error, and the resulting signature is valid to one party and absent to the other.

A conformant vector runner must fail when it executes zero cases, must assert the specific rejection reason rather than merely that something failed, and must fail when no rejection cases ran. The reference runner in mandrel-go/pkg/realized implements all three, plus a negative control that runs the vectors against the implementation this contract replaces and fails if they stop diverging.

Running a lab in CI

The minimum viable metrology lab is a policy file, a pipeline and a key.

graph LR
    PP[platform-policy.yaml<br/>governance repo] --> BAKE[bake]
    AS[mandrel.yaml<br/>agent repo] --> BAKE
    BAKE --> RS[Realized Spec]
    RS --> SIGN[sign]
    SIGN --> OUT[Artifact, OCI,<br/>or Git tag]

Git becomes the audit trail, protected branches become the promotion gate.

Be honest about what this gives up. A CI-signed spec is only as trustworthy as the pipeline: anyone who can modify the CI definition or read the signing variable can bake their own ceilings. Mitigate with protected branches and tags, protected and masked variables, signed commits, and preferably keyless signing so provenance lands in a transparency log rather than resting on a shared secret.

There is also one capability this shape cannot provide:

Agent-name uniqueness. The contract requires the signature’s sub to be unique, but uniqueness is a property of a namespace, not of a document. Two repositories can promote the same agent name with no shared registry to notice. An authority holding a registry gets it for free; a CI-only lab does not, and the operator is responsible for it out of band — a naming convention, a namespace per team, or a shared registry.

That is the trade being made, and it should be made deliberately rather than discovered.