Probity Evidence-Scope Disclosure, v1
predicateType: https://getprobity.dev/predicate/v1/evidence-scope-disclosure
An evidence-scope disclosure is a signed statement about a published piece of evidence: which layers of the detection stack it covers, which tier it sits at, whether it is a real capture or a representative fixture standing in for one, and - in prose the producer writes against its own interest - what it deliberately does not show. Every other predicate in this standard says something about a subject. This one says something about the evidence, and its whole job is to travel the limits alongside the claim so a reader cannot mistake a demonstration for a measurement.
It is not a verdict. It carries no PASS / DEGRADED / FAIL, no per-attack result, and no containment chain. A security-verdict or an adversarial-execution-evidence statement carries an outcome; a disclosure carries the boundary around one. The two compose: a verdict says what happened, a disclosure says how far what happened generalizes.
This predicate rides the DSSE / in-toto envelope specified in signing-envelope.md without deviation. Read that document first; everything below concerns the predicate body and the checks a verifier performs after the envelope verifies.
The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, MAY, and OPTIONAL are to be interpreted as in RFC 2119.
Why this type exists
The disclosure body was originally emitted under the
signed-evidence predicate type, whose specification defines a completely
different structure (merkle_evidence, value_binding, fold_pins). Two structurally unrelated
bodies shared one type URI, and both verified only because every shipped verifier gates on the
predicateType string and on envelope integrity, never on the body. A reader who dereferenced the
type URI - the entire reason the type is a URL - got a document describing fields the statement did
not carry. This type exists so the URI resolves to the shape the bytes actually have.
There is no alias. https://getprobity.dev/predicate/v1/signed-evidence is not, and never was, a
valid type for a disclosure body, and a producer MUST NOT emit one under it.
Subject
The subject is the artifact the evidence is about, by digest.
"subject": [{"name": "<artifact_name>", "digest": {"sha256": "<64-hex>"}}]
The digest is 64 lowercase hexadecimal characters with no sha256: prefix. The subject of a
disclosure is whatever the evidence was gathered from - a scanned agent image, a pinned corpus
manifest - and a producer MUST bind a digest a third party can recompute from bytes it can obtain,
never a name alone.
Predicate body
{
"evidence_type": "multi-server-benchmark",
"tier": "static",
"coverage_map": {"static": true, "runtime": false, "hosting": false},
"honest_scope": "<prose the producer writes against its own interest>",
"is_synthetic_fixture": false,
"reproduce": "research/68-bakeoff-corpus/run_all.sh",
"benchmark": { "...": "the variant section selected by evidence_type" }
}
The body has a REQUIRED core, an OPTIONAL reproduce pointer, and exactly one REQUIRED
variant section named by evidence_type. Both the core and each variant section are closed: a
producer MUST NOT emit a member not listed below, and the reference producer refuses to sign a
section carrying one.
Core
| Field | Req | Type | Semantics |
|---|---|---|---|
evidence_type | REQUIRED | string, closed enum | Which kind of evidence this is, and therefore which variant section the body carries. See the registry below. |
tier | REQUIRED | string, closed enum static | runtime | live | The deepest layer the evidence reaches. Mechanically determined by coverage_map; see the consistency rule below. |
coverage_map | REQUIRED | object, closed, three REQUIRED booleans | Which layers the evidence covers: static, runtime, hosting. At least one MUST be true. |
honest_scope | REQUIRED | non-empty string | The producer's prose statement of what the evidence does NOT show. Self-declared; see the observation table. |
is_synthetic_fixture | REQUIRED | boolean | true when the evidence is a really-signed REPRESENTATIVE of a shape rather than a capture of a run that happened. Self-declared; see the observation table. |
reproduce | OPTIONAL | non-empty string | A repository-relative path to the recipe that regenerates the evidence. A pointer, not a guarantee: see What this predicate does NOT assert. |
The coverage_map / tier consistency rule. tier MUST equal the deepest layer set true in
coverage_map, under the ordering static < runtime < live, where hosting: true maps to live
and runtime: true maps to runtime. A verifier MUST reject a statement whose tier disagrees with
its coverage_map. This is the one field pair in the body a verifier can check for free, and it
exists so a disclosure cannot claim a deeper tier than the coverage it admits to.
coverage_map is closed at exactly three members. All three are REQUIRED and all three are
booleans; a false is a positive statement that the layer is not covered, never an omission. A
producer MUST NOT signal "not covered" by leaving a member out, and a verifier MUST reject a
coverage_map that is missing one.
The evidence_type registry
evidence_type is a CLOSED vocabulary. It selects the variant section, and every legal value has
exactly one:
evidence_type | Variant section | What the variant carries |
|---|---|---|
live-attestation | live_attestation | The hardware-attestation posture of a long-running, continuously observed agent |
multi-server-benchmark | benchmark | A head-to-head scanner comparison over a pinned multi-server corpus |
A verifier MUST reject an evidence_type outside this registry, and MUST reject a statement whose
body carries a variant section other than the one its evidence_type names (fail-closed; no warn
tier, and no statement carrying two). Adding a value is a spec revision that publishes a new registry
row together with the variant section's field table, never a silent additive change a deployed
verifier is expected to tolerate.
live_attestation
REQUIRED when and only when evidence_type == "live-attestation".
| Field | Req | Type | Semantics |
|---|---|---|---|
integrity_tier | REQUIRED | string, closed enum roadmap | attested | attested means a hardware-rooted quote is carried in this statement. roadmap means the substrate primitives are built and the quote is not yet emitted. |
tee_quote | REQUIRED, nullable | base64 string or null | The hardware attestation quote. |
ephemeral_pubkey | REQUIRED, nullable | base64 string or null | The per-session public key bound into the quote. |
observer_in_tcb | REQUIRED | boolean | Whether the observing host is inside the attested trusted computing base. |
The integrity-tier consistency rule. integrity_tier == "roadmap" if and only if BOTH
tee_quote and ephemeral_pubkey are null. A verifier MUST reject any other combination. Without
this rule a statement could declare attested and carry nothing, which is precisely the confusion
the field exists to prevent.
A verifier MUST NOT read integrity_tier == "attested" as a verified quote. The rule above is a
structural check on the statement, not a validation of the quote's chain; validating a quote requires
the vendor's endorsement material, which a disclosure does not carry.
benchmark
REQUIRED when and only when evidence_type == "multi-server-benchmark".
| Field | Req | Type | Semantics |
|---|---|---|---|
name | REQUIRED | non-empty string | What was compared against what, in one line. |
corpus | REQUIRED | object, closed | The composition of the pinned input corpus, by count. |
metadata_layer_scanners | REQUIRED | object, non-empty | One tally per scanner run at the metadata layer, keyed by scanner id. |
self_authored_vuln_server_tool_depth | REQUIRED | object, non-empty | Per-scanner tool-depth result on the self-authored vulnerable server, keyed by scanner id. |
source_code_layer_semgrep | REQUIRED | object, closed | The source-code-layer scoreboard, kept separate from the metadata layer. |
corpus carries exactly servers, third_party, self_authored, malicious and
benign_controls, each a non-negative integer. third_party + self_authored and
malicious + benign_controls MUST each equal servers: a corpus that does not partition twice has
lost a row somewhere between the manifest and the statement.
Each value of metadata_layer_scanners carries exactly malicious_servers,
malicious_servers_caught, benign_controls, benign_tools_total,
benign_tool_false_positives and malicious_tool_attack_findings, each a non-negative integer, with
malicious_servers_caught <= malicious_servers and
benign_tool_false_positives <= benign_tools_total.
Each value of self_authored_vuln_server_tool_depth is either null (that scanner produced no row
for the server) or an object carrying exactly caught_tools and total_tools, non-negative integers
with caught_tools <= total_tools. The key set of self_authored_vuln_server_tool_depth MUST equal
the key set of metadata_layer_scanners: a scanner present in one and absent from the other is a
comparison with a hole in it.
source_code_layer_semgrep carries exactly malicious_caught and benign_false_positives, each a
"<caught>/<total>" string of two non-negative integers, and note, a non-empty string. The source
layer is a separate scoreboard and MUST NOT be summed with the metadata layer; the note says so in
the statement so the separation travels with the numbers.
Observed from outside versus self-declared (load-bearing)
This is the section a reader of a disclosure must not skip. The members of this predicate fall into three classes, and confusing the third for the first is the exact failure this type is shaped to prevent.
Class 1 - checkable from the statement and the trust anchor alone. The DSSE signature, the
keyid binding, the subject digest binding, the closed member sets and value domains above, the
coverage_map / tier consistency rule, the integrity_tier consistency rule, the corpus
partition identities, and the scanner key-set equality. A verifier needs nothing but the bytes and a
configured public key.
Class 2 - producer measurements, recomputable only against inputs the auditor supplies. Every
integer under benchmark. These were produced by running the named scanners over the pinned corpus.
An auditor holding the same corpus and the same scanner versions can re-derive them - that is what
reproduce points at - but the statement does not let anyone re-derive them from itself. A
re-derivation that disagrees is a real finding; the absence of one is not a confirmation.
Class 3 - self-declared, and not a measurement of anything.
| Member | Why it is self-declared |
|---|---|
honest_scope | Prose. Nothing observes it, nothing constrains it, and no verifier can confirm it is complete. It is the producer stating limits against its own interest, which is why it is worth carrying and exactly why it MUST NOT be read as a finding. |
is_synthetic_fixture | A producer asserting whether its own artifact stands for a run or captures one. A false here is a claim, not evidence; nothing in the statement distinguishes a real capture from a well-formed fixture that says it is real. |
evidence_type, tier, coverage_map | The producer's own account of what it covered. The consistency rule ties them to each other, never to the world: a producer that ran nothing can emit an internally consistent map. |
every member of live_attestation | Including observer_in_tcb and integrity_tier. The consistency rule is structural. No member of this section is grounded by anything in the statement. |
reproduce | A path. The statement does not commit to the recipe's contents by digest, so it is a pointer to where a reader should go, never a promise of what is there. |
A consumer building an automated decision on a disclosure MUST treat class 3 as untrusted input from
the party being described. In particular, a policy that admits an artifact because
is_synthetic_fixture == false has delegated its decision to the producer.
Producer validation
The reference producer validates the core and the selected variant section against the tables above -
closed member sets, the enum domains, non-empty strings where REQUIRED, non-negative integers, the
coverage_map / tier rule, the integrity_tier rule, the corpus partitions, the scanner key-set
equality, and a 64-lowercase-hex subject digest - and raises rather than signing a malformed
predicate. This is the standard's producer-strict rule: a producer that cannot vouch for a member
refuses to sign it.
Every number a producer emits under benchmark MUST be derived from the run artifacts, never typed
by hand. The two reference generators do this: the corpus disclosure derives its counts from
matrix.json and its subject digest from the bytes of corpus/manifest.jsonl, and the live
disclosure derives its body from the previously signed statement after verifying that statement's
signature.
The validated body is serialized under RFC 8785 (JSON Canonicalization Scheme) to produce the exact payload bytes the DSSE signature commits to, the same canonicalization every other predicate in this standard uses.
Verification (third-party, offline)
The envelope-level procedure is signing-envelope.md §Verification procedure
and is not restated here. In summary: confirm payloadType == application/vnd.in-toto+json, decode
payload from base64 and keep the bytes verbatim (never re-serialize), resolve each signature's
REQUIRED keyid against the configured trust anchor, and verify the raw ed25519 signature over
PAE("application/vnd.in-toto+json", <payload bytes>).
Two properties of the trust model are load-bearing here and a verifier MUST enforce both.
- A signing key is authorized per predicate type. A published verification key carries the set of
predicate types it may sign, and a verifier MUST reject a statement whose
predicateTypeis not in the resolving key's authorized set.https://getprobity.dev/predicate/v1/evidence-scope-disclosureis in the authorized set of the producer's evidence-signing key (keyid427a1092..., published at/.well-known/verification-keys.json). - Transparency-log upload is deliberately disabled, and a verifier MUST NOT require one. Signing publishes nothing to any log, on purpose: the evidence stays exactly as private as the artifact it describes. A verifier MUST NOT treat the absence of a log entry as a defect and MUST NOT be built to require one.
Once the envelope verifies, a verifier performs the predicate-specific checks:
- Confirm
predicateTypeis exactlyhttps://getprobity.dev/predicate/v1/evidence-scope-disclosureand that the resolving key is authorized for it. - Check the core against its field table: closed member set,
evidence_typein the registry,tierin its enum,coverage_mapclosed with three booleans and at least onetrue, non-emptyhonest_scope, booleanis_synthetic_fixture. - Confirm
tierequals the deepesttruelayer ofcoverage_map. - Confirm the body carries exactly the variant section its
evidence_typenames, and check that section against its field table - including theintegrity_tierrule forlive_attestation, and the partition identities, bounds and key-set equality forbenchmark. - Treat every class-3 member above as a producer assertion. There is no step that verifies one.
- OPTIONALLY re-run the recipe at
reproduceagainst the subject the statement binds, and compare the class-2 integers.
No live VM, no hypervisor access, and no network access are required at any step. Step 6 is checkable only against inputs the auditor brings; see the honesty section below.
What this predicate does NOT assert (coverage)
- A verdict. No PASS / DEGRADED / FAIL, and none may be inferred. Outcomes live in security-verdict and adversarial-execution-evidence.
- That the evidence was captured from a real run. The signature establishes that the producer
committed to these values, not that anything executed.
is_synthetic_fixture == falseis the producer saying so, and is exactly as trustworthy as the producer. - That
honest_scopeis complete. It states limits the producer chose to state. Nothing enumerates the ones it did not. - That the recipe at
reproduceexists, runs, or produces these numbers. The path is not digest-committed. A consumer that needs the recipe pinned obtains its digest out of band. - Anything about the substrate. The byte-identity, fold-correctness and clone-uniqueness families
are substrate-integrity's, and a disclosure's
coverage_mapsays nothing about them. - A validated hardware quote.
integrity_tier == "attested"is a structural declaration; the quote's endorsement chain is out of scope and is not carried. - Revocation. Nothing in the statement expires. A consumer that needs revocation obtains it out of band.
What a verifier can and cannot independently check (honesty)
- A verifier CAN confirm the statement is authentic under a key it configured and authorized for this predicate type, that it binds exactly the subject digest it intended, and that the core and the selected variant section are well-formed and internally consistent under the closed contract. All of that is offline from the statement and the key alone.
- A verifier CAN re-derive the
benchmarkintegers only when it supplies the external inputs - the pinned corpus, the scanner builds, the recipe. That is a real check, but relative to anchors the auditor brings, never derivable from the statement. - A verifier CANNOT, from the statement alone or with any anchor, establish that
honest_scopeis complete, thatis_synthetic_fixtureis truthful, that thecoverage_mapreflects work that happened, or that any member oflive_attestationdescribes a real machine. These are disclosures, and the type is named for what they are.
Status and maturity
- Format, producer, and signing: implemented. Two disclosures ship signed and verify offline: the
multi-server corpus bake-off (
website/public/verify-bakeoff/, generated byresearch/68-bakeoff-corpus/sign_corpus_evidence.py) and the always-on live-attestation shape (website/public/verify-tier3/, generated bywebsite/public/verify-tier3/sign_live_attestation_disclosure.py). Both are regenerable; neither is hand-edited. - Verifier recognition: implemented. The predicate type is in the recognized set of the packaged
TypeScript verifier (
packages/verify), its website twin, the standalone Python verifier (website/public/verify/probity-verify.py) and the admission rego, and in the authorized-predicate set of the published evidence-signing key. - Standardization status: wave 2. The standard README lists this type as a wave-2 family. This document is normative for the type URI and is published so the URI resolves; wave-2 membership concerns the standardization track, not whether the format is stable.
Related standards
- signing-envelope.md - the DSSE / in-toto envelope,
keyidderivation, trust root, and offline-verify procedure this predicate uses without deviation. - signed-evidence - a DIFFERENT type with a different body
(
merkle_evidence,value_binding,fold_pins). A disclosure is not a signed-evidence statement and MUST NOT be emitted under that type. - security-verdict and adversarial-execution-evidence - the outcome-bearing predicates a disclosure bounds rather than replaces.
- in-toto attestation - ALIGNED. The payload is an
in-toto Statement v1; subject binding and
predicateTyperouting are used as in-toto defines them. - DSSE - ALIGNED, PAEv1 and ed25519, no deviation.
- SLSA VSA - RELATED, not overlapping. A VSA summarizes a policy evaluation's outcome; a disclosure carries no outcome and instead bounds one.
Versioning
This is the evidence-scope-disclosure v1 predicate. The core and every variant section are closed at the producer: a producer MUST NOT emit a member not defined above. Per the standard's forward-compatibility rule, a verifier MUST ignore a member it does not recognize rather than rejecting the statement, and an additive optional member is a minor change that stays within v1.
The evidence_type, tier and integrity_tier vocabularies are CLOSED registries and are
fail-closed at this version: a verifier MUST reject a value it does not recognize. Adding a value -
and, for evidence_type, the variant section it selects - is a spec revision that publishes a new
registry row, never a silent additive change.
A change to the core's meaning, to the coverage_map layer set, to the evidence_type-to-variant
mapping, or to the consistency rules is breaking and ships as a new major-version URI.