GovernanceReceipt — Core Predicate (v1)
Status: Draft — tracking @appitudeio/governance v1.0.0-rc.1. The wire
format described here is frozen against the rc.1 shape; it becomes 1.0.0
when @appitudeio/governance v1.0.0 final ships, not before (governance
ADR 0015 §4).
- predicateType:
https://schemas.governedwork.com/attestation/governance-receipt/v1 - Core schema
$id:https://schemas.governedwork.com/attestation/governance-receipt/v1/schema.json
This document is the field-by-field specification of the core governance
predicate. It is the authoritative prose companion to
../schema/governance-receipt-v1.json;
when prose and schema disagree, the schema is normative for structure, the prose
is normative for semantics. This document uses MUST, MUST NOT, SHOULD, SHOULD
NOT, and MAY with their RFC 2119 meanings.
1. Layer map (read this first)
The name GovernanceReceipt is also used by Microsoft's Agent Governance
Toolkit (AGT) for a different artifact at a different granularity. Governance
ADR 0015 §6 requires every public document to lead with this disambiguation. The
names collide; the layers do not.
| Artifact | Granularity | Namespace |
|---|---|---|
Microsoft AGT GovernanceReceipt |
per tool call | microsoft/agent-governance-toolkit |
governance-spec GovernanceReceipt (core — this document) |
per governed action | governedwork.com/... |
Steerboard InvocationReceipt (profile) |
per invocation (aggregate of one or more governed actions) | governedwork.com/.../profiles/steerboard-invocation-receipt/... |
The core artifact specified here is per governed action: one observer, one
principal, one action, one target, one execution. An invocation that spans
several governed actions is an aggregate, expressed by a profile that
references one or more core receipts by { id, hash } — never by embedding them
(§8). Microsoft AGT's per-tool-call receipt is a narrower unit again; the two
can be composed at the in-toto subject[] layer (see
../envelopes/in-toto-predicate.md) but
are not the same artifact.
2. Purpose
A GovernanceReceipt is the per-governed-action evidence artifact: a
domain-agnostic, signed record that a single governed action was authorized,
executed, and is provably attributable to a recorder. It serves agent
governance first and standards-mapping second.
Its spine is CADF (DMTF DSP0262) — the Cloud Auditing Data Federation
domain-agnostic audit-event record (Observer / Initiator / Action / Target /
Outcome). CADF was built to audit any cloud-resource action, so the same five
fields carry an email send, a workflow run, or a deployment with zero
fiction — that genericity is the entire point of the artifact. Onto that spine
the receipt adds first-class governance linkage (intentRef,
decisionRef, approvalRef) whose W3C PROV wasAssociatedWith semantics tie
the recorded action back to the decision and approval that authorized it.
The receipt body carries the CADF event plus the governance linkage. The
cryptographic proof is the layer around it: the predicate is augmented with
a signer and recordedAt (together forming the AttestationPredicate,
§3.1), carried as the predicate of an in-toto v1 Statement, DSSE-signed. A
signature cannot be a field inside the body it signs, so the proof is the signed
bundle, not a field of the receipt — see
../envelopes/in-toto-predicate.md.
3. The signed predicate
The artifact that is signed and verified is the AttestationPredicate: the
full GovernanceReceipt body plus the two evidence-layer fields a standalone
in-toto consumer needs to resolve who signed and when.
AttestationPredicate = GovernanceReceipt + {
signer: Signer, // who signed (DID-shaped) — §4.7
recordedAt: string // when the attestation was minted — §4.8
}
The schema in ../schema/governance-receipt-v1.json
validates this full AttestationPredicate, because that is what appears on the
wire inside the Statement's predicate.
3.1 Field inventory
AttestationPredicate {
// Version envelope
schemaVersion: "1.0.0" // shape version (SemVer) — REQUIRED §4.0
// CADF six (neutral spine)
observer: Identity // CADF observer — REQUIRED §4.1
principal: Identity // CADF initiator — REQUIRED §4.2
action: string // CADF action name — REQUIRED §4.3
resource: ResourceRef // CADF target — REQUIRED §4.4
execution: Execution // what happened — REQUIRED §4.5
// Governance linkage (W3C PROV wasAssociatedWith; first-class)
intentRef: string // REQUIRED §4.6
decisionRef: DecisionRef // REQUIRED §5
approvalRef: ApprovalRef|null // REQUIRED (nullable) §6
// Evidence layer (added by AttestationPredicate)
signer: Signer // REQUIRED §4.7
recordedAt: string // REQUIRED §4.8
}
All objects are closed (additionalProperties: false); no open extension
key-map exists anywhere in the core. There are no user-controlled keys and no
numbers in the signed predicate (every field is a branded id, an enum, or a
string), which is what keeps canonicalization byte-stable across consumers.
4. Field-by-field reference
4.0 schemaVersion (REQUIRED) — "1.0.0"
The GovernanceReceipt shape version, full SemVer 2.0.0. The predicateType
URI carries the MAJOR only (…/governance-receipt/v1); schemaVersion pins the
precise shape version IN the signed predicate, so a verifier can distinguish a
1.0.0 receipt from a future 1.1.0 without parsing the URI. A receipt minted
before this field existed (the pre-1.0 rc-era, under the same /v1 URI) is not
recognized — the schema requires the field (const: "1.0.0"), and a conformant
verifier rejects a missing or unrecognized value fail-closed. A future 1.1.0 is
a conscious spec-version + ADR event (governance ADR 0018).
4.1 observer (REQUIRED) — Identity
The CADF observer: the kernel that recorded the event, by its own
did:web Identity. A receipt MUST name its recorder. Distinct from signer
(§4.7) as a role, though in practice the same kernel identity fills both. See
§4.9 for the Identity shape.
Standards: CADF DSP0262 observer; W3C DID Core (the recorder is a DID subject).
4.2 principal (REQUIRED) — Identity
The CADF initiator: who acted. Its role ("access-subject") is expressed by
position (this field), not by a role[] field — the XACML 3.0 subject-category
vocabulary is the named reference model for these positional roles, adopted as
documentation, not materialized as a field (governance ADR 0014 #I-2).
Standards: CADF initiator; XACML 3.0 subject-categories; W3C DID Core.
4.3 action (REQUIRED) — string (branded ActionName)
The action name — a dot-namespaced, channel-bearing string
("contact.customer.email", "workflow.run"). It is the join key into the
kernel's trusted ActionDefinition registry; the traits of the action
(classification, reversibility, externality) live in that trusted registry and
are committed to via decisionRef.actionDefinitionHash (§5), never carried on
the receipt directly. Deliberately an open string, not a closed enum: the kernel
serves many consumers, each with its own action vocabulary.
Standards: CADF DSP0262 action taxonomy (with a documented "communicate/send"
extension to cloud-CRUD verbs, ADR 0014 #A-2); AuthZEN action.name.
4.4 resource (REQUIRED) — ResourceRef
The CADF target: what was acted upon. A closed, kind-discriminated union (no
open key-map). kind is one of:
kind |
Meaning | Extra fields |
|---|---|---|
invocation |
one governed AI invocation (the substrate's primary subject) | — |
capability |
a capability definition or version | — |
workflow |
a workflow definition or version | — |
external |
a downstream effect (mail.send, notification.dispatch, …) |
optional system |
Every variant carries a required id (string) and an optional scopeId (the
tenant / project / namespace under which id is addressable; omitted by
non-multi-tenant consumers). Only the external variant additionally carries an
optional system. The union is closed: a consumer maps its own branded IDs into
id + scopeId at the substrate boundary rather than adding a new kind.
Standards: CADF DSP0262 target.
4.5 execution (REQUIRED) — Execution
What the governed action actually did. Generalizes CADF's Action/Outcome plus the executor pointer.
| Field | Type | Req. | Semantics |
|---|---|---|---|
kind |
string |
REQUIRED | executor class ("communication.send", "workflow.run", "http.call", …). A free string — the substrate carries no opinion on a consumer's executor taxonomy. |
ref |
string |
REQUIRED | opaque pointer to the executed thing (a Slack ts, a message id, a run id). |
outcome |
GovernanceOutcome enum |
REQUIRED | success | failure | pending — the CADF Outcome taxonomy, narrowed to the three values a governed action produces. |
performedAt |
string (date-time) |
REQUIRED | CADF eventTime. |
payload |
ExecutionPayload | null |
REQUIRED (nullable) | binds what ran to the approved intent (§4.5.1). |
4.5.1 execution.payload — ExecutionPayload | null
| Field | Type | Req. | Semantics |
|---|---|---|---|
hash |
Sha256Hex |
REQUIRED | canonical-JSON SHA-256 of the payload that actually executed (the real message body / deploy parameters / request). It rides in the signed predicate, so tampering it fails verification. |
derivedFromIntentHash |
Sha256Hex |
REQUIRED | the intentHash of the approved/decided intent this payload was derived from. |
payload is REQUIRED (non-null) for an executed outcome (success /
failure — an attempt was made, so what was attempted MUST be bound) and MAY be
null for a non-executed pending outcome. This is a trust-chain invariant
enforced at emit (§7, rule 4). The kernel models approve-as-is (no
edit-on-approve), so today hash === derivedFromIntentHash whenever the executed
payload is the intent body; the two fields stay distinct so a future
revise flow (edit → new intent → re-approve) can record a payload causally
derived from — but not byte-identical to — the approved intent without
pretending they are the same.
Standards: CADF Action/Outcome; the payload binding is governance-native (no CADF-native slot).
4.6 intentRef (REQUIRED) — string (branded IntentId)
The caller-minted idempotency key of the ActionIntent this receipt is about —
the receipt's stable identity and the join key everywhere downstream. One
governed action = one receipt: intentRef is unique within a store instance
(governance ADR 0014, review resolution 1). It also names the in-toto subject
(subject[0].name = "governance:<intentRef>", see the envelope doc).
Standards: W3C PROV wasAssociatedWith (linkage); CADF extension attribute
(no native intent slot, ADR 0014 #E-1).
4.7 signer (REQUIRED) — Signer
The DID subject that signed, exercising an assertionMethod key (W3C DID Core
§5.3: assertionMethod = issuer-of-evidence). Part of the
AttestationPredicate, not the GovernanceReceipt body.
| Field | Type | Req. | Semantics |
|---|---|---|---|
identity |
Identity |
REQUIRED | the DID subject doing the signing. Its did is OPTIONAL at the type level but a verifiable signer MUST carry a did — the verifier resolves the verification method from it and fails closed when it is absent. |
keyId |
string |
REQUIRED | the verificationMethod fragment within the DID document (the part after #), e.g. "key-1". |
algorithm |
SignatureAlg enum |
REQUIRED | Ed25519 | ECDSA_SHA_256 | RSASSA_PSS_SHA_256. MUST equal the producing signature's algorithm. The verifier supports only Ed25519 (did:key publicKeyMultibase) and ECDSA_SHA_256 (did:web publicKeyJwk); a bundle claiming RSASSA_PSS_SHA_256 fails closed (no verify path). |
Standards: W3C DID Core §5.3 verification relationships; in-toto functionary.
4.8 recordedAt (REQUIRED) — string (ISO-8601 date-time)
The instant the attestation was minted. Part of the AttestationPredicate. It
is the default valid-at-time instant for verifying the signer's key validity
(§ envelope doc). Security note: recordedAt is self-asserted by the signer;
a verifier that needs compromise-resistant revocation MUST supply a trusted
observation time instead of trusting it. Typed as a plain ISO-8601 string here
(see judgment calls in the schema), validated format: date-time.
4.9 Identity (shared shape)
observer, principal, signer.identity, and approvalRef.approver are all
the same unified Identity.
| Field | Type | Req. | Semantics |
|---|---|---|---|
did |
string (DID URI) |
OPTIONAL | W3C DID URI (did:<method>:<id>). Present for DID-addressable actors; absent for session-only humans or not-yet-DID machine workloads (governance ADR 0014 #I-1). Required in practice for a signer (§4.7). |
kind |
IdentityKind enum |
REQUIRED | agent | human | service | machine. |
ref |
string (branded IdentityRef) |
REQUIRED | the stable join key: the DID when present, else a consumer-supplied principal id. |
Standards: W3C DID Core (an identity is a DID subject); SPIFFE/SPIRE
(machine workload identity).
5. decisionRef (REQUIRED) — DecisionRef
The signed link from the attested action back to the Decision that authorized
it. It is a reference, not the full decision: the heavy intent + decision
bodies live in the Decision-pillar stores; this envelope lifts the load-bearing,
tamper-evident facts into the signed predicate.
| Field | Type | Req. | Semantics |
|---|---|---|---|
intentId |
string (IntentId) |
REQUIRED | the join key — the same caller-minted id as intentRef. |
intentHash |
Sha256Hex |
REQUIRED | canonical-JSON SHA-256 of the decided intent body. On the direct-allow path (approvalRef === null) this is the only signed anchor binding what executed to what was authorized (§7). |
outcome |
enum | REQUIRED | the decided outcome: allow | deny | require_approval | require_info. Almost always allow on an attested action (a denied action does not run); the full union is carried so a policy-violation incident receipt can record the actual outcome. |
decidedAt |
string (date-time) |
REQUIRED | when the decision was made. |
actionDefinitionHash |
Sha256Hex |
REQUIRED | canonical-JSON SHA-256 of the trusted ActionDefinition the decision resolved. Commits the signed receipt to the exact trait-set that authorized the action; an auditor recomputes it from the archived registry entry. Always present (a receipt is only emitted for an authorized action, which always resolved a definition). |
actionDefinitionVersion |
string |
OPTIONAL | human-readable definition version label. |
policyBundleHash |
Sha256Hex |
REQUIRED | hash of the policy world the decision was evaluated under — directly inspectable, so an auditor can confirm which bundle decided and detect that the live bundle has since changed. |
decisionProvenanceHash |
Sha256Hex |
REQUIRED | umbrella anchor: canonical-JSON SHA-256 of the whole DecisionProvenance (action definition + policy world + grants considered + caller-context hash). actionDefinitionHash and policyBundleHash are inspectable shortcuts into this same envelope. |
Trust model (rc.1, honest): verify() does NOT cross-check decisionRef
against a decision store — the kernel is the trusted signer, so a verifier trusts
the linkage the kernel signed. Independent third-party re-derivation (recomputing
the hashes from archived inputs) is the audit path the hashes enable, but is not
performed by verify().
Standards: W3C PROV wasAssociatedWith; CADF extension attribute (ADR 0014
#E-1); decision-provenance envelope (governance issue #21).
6. approvalRef (REQUIRED, nullable) — ApprovalRef | null
The signed link back to the ApprovalRequest that authorized the action when it
required a human gate, or null for a direct allow (a permit with no
@requires_approval obligation — the action ran without a human gate). Carrying
the resolution snapshot here (not a bare ID) makes the receipt self-contained: an
auditor reads who approved, when, and for which intent body straight from the
signed predicate.
When non-null:
| Field | Type | Req. | Semantics |
|---|---|---|---|
approvalRequestId |
string (ApprovalRequestId) |
REQUIRED | the kernel-minted join key into the approval store. |
approvalRequestHash |
Sha256Hex |
REQUIRED | umbrella anchor: canonical-JSON SHA-256 of the approval-request resolution snapshot ({ id, intentHash, state, approver, createdAt, expiresAt, resolution? } — the full request minus the embedded intent body, which is bound separately by intentHash). |
approver |
Identity |
REQUIRED | who was required to decide. |
state |
ApprovalState enum |
REQUIRED | pending | approved | rejected | expired. A receipt for an executed action always references an approved approval (§7, rule 5). pending appears only on an incident receipt for an unresolved request. |
intentHash |
Sha256Hex |
REQUIRED | canonical-JSON SHA-256 of the intent body that was approved — the divergence detector. |
requestedAt |
string (date-time) |
REQUIRED | when the approval was raised (the request's createdAt, exposed under the audit-facing name requestedAt). |
resolvedAt |
string (date-time) |
OPTIONAL | when the approval was resolved. Present IFF state is approved / rejected; ABSENT for expired (no human resolver) and pending (unresolved). |
expiresAt |
string (date-time) |
REQUIRED | when the approval window lapses. |
Trust model (rc.1, honest): as with decisionRef, verify() does not
cross-check this against an approval store; it trusts the kernel-signed linkage.
Standards: GNAP grant-request lifecycle (approval states); W3C PROV
wasAssociatedWith; WS-HumanTask 1.1 (prior art, simplified — ADR 0012 §5).
7. Trust-chain invariants
The invariants below are enforced at two independent points, mapped per-invariant in the enforcement matrix at the end of this section:
- The kernel's
emit()runs a fail-closed structural gate (assertExecutionEvidence) before signing — a receipt that violates a kernel-enforced invariant is never minted. The gate binds every intent reference against the ground-truthActionIntentbody the caller supplies (its hash is recomputed, not trusted from the field), so the chainsubject → decision → approval → executionis welded to one and the same intent. - The conformance validator (
scripts/validate.mjs, run in this repo's CI) independently re-checks every invariant that is checkable from the signed statement alone — it has no ground-truth intent, so recomputed-hash rules are out of its reach. Each validator check carries a stable rule ID;fixtures/invalid/manifest.jsondeclares which rule each invalid fixture exercises.
Subject = intent.
intentRefMUST equal the providedintent.id.Decision faithfully references the intent.
decisionRef.intentIdMUST equalintent.idanddecisionRef.intentHashMUST equal the canonical-JSON SHA-256 of the real intent body. (This is recomputed at emit; a fabricateddecisionRef.intentHashis rejected.)Approval (if present) legitimized the same intent body. When
approvalRef !== null,approvalRef.intentHashMUST equaldecisionRef.intentHash— one intent chain, not two independent refs.Executed attempts bind what ran. For an executed outcome (
success/failure),execution.payloadMUST be non-null. Apendingreceipt MAY omit it.No real execution under a non-approved approval. When the outcome is executed and
approvalRef !== null,approvalRef.stateMUST beapproved. Arejected/expired/pendingapproval can never back an executed receipt.Payload derives from the decided intent. When
execution.payload !== null,execution.payload.derivedFromIntentHashMUST equaldecisionRef.intentHash. Because rule 2 has weldeddecisionRef.intentHashto the real intent hash, this transitively binds the executed payload to ground truth on both the approval and direct-allow paths.Executed success requires an allow decision. When
execution.outcomeissuccess,decisionRef.outcomeMUST beallow. (Deliberately scoped tosuccess— afailure/pendingincident receipt recording an unauthorized attempt stays representable.)A
require_approvaldecision references its approval. WhendecisionRef.outcomeisrequire_approval,approvalRefMUST NOT benull.The signed bytes are I-JSON. A conformant statement MUST NOT contain duplicate object keys — RFC 8785 (JCS) is defined over I-JSON, and a duplicate-key document is ambiguous across parsers (last-wins and first-wins parsers read different content from the same bytes). Verifiers MUST reject such a document before computing any digest (see envelopes/in-toto-predicate.md §4 step 2).
Together: a receipt can never claim a decision, approval, or payload for a different intent than the one it names.
7.1 Enforcement matrix
| # | Invariant | Kernel emit() |
Conformance validator | Validator rule ID |
|---|---|---|---|---|
| 1 | intentRef = ground-truth intent.id |
✅ | — (needs the ground-truth intent) | — |
| 2 | decision binds the intent (id + recomputed hash) | ✅ | partial — id binding only | invariant-2 |
| 3 | approval binds the same intent body | ✅ | partial — transitively via invariant-6-approval |
— |
| 4 | executed ⇒ non-null payload | ✅ | ✅ | invariant-4 |
| 5 | executed + approval ⇒ approved state |
✅ | ✅ | invariant-5 |
| 6 | payload derives from the decided intent | ✅ | ✅ | invariant-6-direct / invariant-6-approval |
| 7 | success ⇒ allow decision |
✅ — and STRICTER: the kernel also rejects deny+failure (an attempt is an attempt; rule 7, decision-denies-execution) |
✅ | invariant-7 |
| 8 | require_approval ⇒ non-null approvalRef |
✅ — scoped to executed receipts (rule 8, decision-requires-approval) |
✅ | invariant-8 |
| 9 | signed bytes are I-JSON (no duplicate keys) | ✅ (JSON.stringify cannot emit duplicates) |
✅ | json-strict |
The validator additionally enforces structural rules with no invariant number:
the Statement envelope shape (envelope), exact _type / predicateType URIs
(statement-type / predicate-type), subject-name binding (subject-name),
the JCS subject digest (digest), and core-schema validation (schema) — which
includes the schemaVersion: "1.0.0" const pin (§4.0): a missing or unrecognized
shape version is rejected on the schema rule (fixture missing-schema-version),
the spec-side mirror of the kernel verifier's predicate-version gate (ADR 0018).
Every invariant is now enforced at both points (rows 7/8 flipped when
governance kernel rules 7–9 landed, 2026-06-03). When enforcement status
changes, this matrix — and only this matrix — changes; prose elsewhere never
tracks it. On the direct-allow path the anchor
is derivedFromIntentHash === decisionRef.intentHash; on the approval path
the additional anchor is approvalRef.intentHash === decisionRef.intentHash and
approvalRef.state === approved for executed actions.
verify() proves cryptographic integrity + signer identity; it deliberately does
not re-run these construction invariants (they were enforced at emit) and
does not prove historical decision replay (that decide() / approval actually
ran at some past time) — that is the future audit/replay layer.
8. What this spec does NOT constrain
Per governance ADR 0015 §5, this spec constrains exactly one thing: the JSON
that emit() produces — the signed predicate and its DSSE/in-toto envelope. It
does not constrain:
- kernel types, ports, adapters, or any internal structure of
@appitudeio/governance; - how decisions, approvals, or the
ActionDefinitionregistry are implemented or stored; - the consumer's executor taxonomy (
execution.kindis a free string); - the consumer's action vocabulary (
actionis an open branded string).
The conformance authority is asymmetric and time-bound (ADR 0015 §5): while
governance is -rc, the spec is published from the kernel's frozen shape
(kernel → spec); after v1.0.0 final, the conformance ratchet holds the kernel to
the published contract (spec → kernel), and breaking it becomes a conscious
spec-version event.
9. Profiles
A profile is a separate artifact (its own schema, its own optional signature) that enriches one or more core receipts with consumer-specific fields. Profiles are governed by these rules (ADR 0014 Pillar 3; ADR 0015 §2):
- Reference, never embed. A profile references core receipts by
{ id: ReceiptId, hash: Sha256Hex }. A single-action projection carriesgovernanceReceiptRef; an aggregate (e.g. a per-invocation profile spanning several governed actions) carriesgovernanceReceiptRefs[]. Cardinality is the profile's choice; the reference shape is not. A profile MUST NOT embed core receipt bodies. - One-way containment. Reference runs profile → core, never core → profile.
The signed core carries no
projectionRefs[]— a signed immutable predicate cannot hold a mutable forward-list of later-created profiles. Discovering "which profiles exist for receipt X" is an out-of-band index concern. - Optional signing, two forms only. A profile artifact is either (a) an
unsigned projection document, or (b) an independently signed profile predicate
under the same
governedwork.comfamily (https://schemas.governedwork.com/attestation/profiles/<name>/v1). Even when signed, it references core receipts by{ id, hash }— never by embedding. - Core verification is independent. A core receipt verifies with no knowledge of any profile's existence or signature.
The Steerboard InvocationReceipt (per-invocation aggregate carrying
controlProfile, disclosureProofs, policyWarnings, economics,
modelCallManifests, …) is the first profile, at
profiles/steerboard-invocation-receipt/, carrying governanceReceiptRefs[].
The deep regulatory mappings that reference profile fields live with that
profile (see ../mappings/core/traceability.md
§ scope); this core's mappings cover only core fields.