in-toto predicate: measurement/v1

A signed statement about one measurement run, carried in an in-toto Statement v1 envelope.

predicateType
https://councilof.ai/attestations/measurement/v1
Statement
https://in-toto.io/Statement/v1
DSSE payloadType
application/vnd.in-toto+json
Signature
Ed25519, key resolved via did:web:councilof.ai
Canonicalisation
Recursively sorted keys, no whitespace — byte-identical to RFC 8785 for ASCII/number payloads
Issuer
CSOAI Ltd (Council of AI), UK company 16939677

Why this exists

in-toto carries predicates for build provenance, SBOMs, test results and vulnerability scans. It carries none for the result of measuring an AI system. Existing model-signing work signs model weights; the evaluation-reporting literature standardises the form of a report but adds no cryptographic layer. This predicate is the missing piece: the outcome of a measurement run, signed at the time it was produced, verifiable offline by a stranger.

What it establishes — and what it does not

An attestation under this predicate is evidence of what was measured, and when, by the issuer. It is not a certification, an endorsement, or a conformity mark, and must not be presented as one. A measured slot is not a score.

Verifying the signature establishes that the named key produced these bytes and that the bytes have not changed. It says nothing about the state of that key now: offline verification is a computation over the parameters you hold, while revocation is a property of the present. A consumer must not treat a signature that verifies as evidence that the signing key is still valid. Where a decision depends on revocation state, the key-resolution path and the staleness you accept are operational parameters of your deployment and must be stated by it. Recorded against our own rule as correction C-2026-0902-09.

Shape

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{ "name": "<artefact>", "digest": { "sha256": "<hex>" } }],
  "predicateType": "https://councilof.ai/attestations/measurement/v1",
  "predicate": {
    "axis": "<axis-slug>",
    "status": "MEASURED | UNMEASURED | UNCHECKABLE",
    "n": 0,
    "method": "<deterministic-facts | fleet-comparison | …>",
    "as_of": "<RFC 3339>",
    "source_urls": ["…"]
  }
}

Status words are a closed set. A fetch failure is UNCHECKABLE, never a fabricated 0.000. An axis with no signed card is UNMEASURED and stays visibly empty.

Verify one

Offline, zero dependencies, three states rather than two: gspc-verify.mjs (Apache-2.0). Rule: HOW-TO-VERIFY.md. Live board: GET /api/gspc.