Integrity of records (closes gap 9)
Decisions, approvals, gate results and run records are leaves in an append-only Merkle log whose checkpoints are anchored outside our control, so an auditor can check any record and any two points in history without replaying the whole log, and nobody inside the agency can rewrite history unnoticed.¶
The first design used a linear SHA-256 chain anchored hourly in a retention-locked bucket. The review found three weaknesses. Proving that two chain heads are consistent needs a full replay. Anyone who controls both the writer and the anchor bucket can rewrite history before the next anchor. And key rotation and erasure were unspecified. Draft 2 keeps the good parts (canonical JSON, Cloud KMS signatures, domain separation, corrections as new entries) and changes the structure.¶
Log structure. A Merkle tree log in the Certificate Transparency style (RFC 9162), run on an open implementation of RFC 9162 Merkle hashing (informative examples: Tessera, the reference; Trillian). Any record has a logarithmic inclusion proof, and any two checkpoints have a consistency proof that shows the later tree extends the earlier one.¶
Canonical form (normative). A record’s canonical bytes MUST be its JSON Canonicalization Scheme serialisation (RFC 8785), encoded as UTF-8. Before canonicalisation, producers MUST write instants per the time profile (section 4: RFC 3339, UTC, Z, millisecond precision) and calendar dates as ISO 8601 full-dates. Outside the estimand, they MUST omit members whose value is JSON null; inside the estimand, null is invalid (section 5). Producers MUST reject non-finite numbers and duplicate keys. Values that need more precision than an IEEE 754 double MUST travel as strings.¶
Log entry. The record appended under a domain tag is a log entry with the members record_id, kind, content_hash (SHA-256 of the RFC 8785 bytes of the complete stored record, governance members included; this differs from the artifact content hash of section 6), key_version, signed_at and, only when the record relates to a person, subject_ref (section 18, Erasure). The JSON Schema log_entry is part of the contract.¶
- 1. RecordA decision, approval, gate result or run record, written as canonical JSON. Personal data never enters a leaf (G-INT-04).
- 2. Leaf hashSHA-256 of the canonical JSON with a domain tag:
ump/decision/v1,ump/gate/v1orump/run/v1. - 3. Merkle treeLeaves join in pairs up to one root hash. Append-only, in the Certificate Transparency style (RFC 9162).
- 4. Signed tree headSize, root hash and timestamp, signed with a key held in Cloud KMS. Published at least hourly.
- 5. Anchored outside our controlAn RFC 3161 timestamp, a retention-locked bucket run by a different team, and optionally a hash in a public log such as Sigstore Rekor.
- 6. CheckedA short inclusion proof shows one record is in the log. The independent verifier checks consistency between heads and raises
integrity.brokenon any failure.
Illustrative tree with four leaves. To prove L2 is in the log you need only L1 and H34, not the whole log. The proof grows with the logarithm of the log's size.
Nobody inside the agency can rewrite history unnoticed. Each record becomes a leaf, leaves roll up to one signed root, and that root is stamped and anchored where the log's own operators cannot reach: an outside time-stamping authority and a bucket run by a different team.
The design detects tampering. It does not stop someone with write access from appending a false record. For approvals, prevention comes from signatures tied to named approvers and the rule that no API token can approve.
Your browser hashes the tag ump/decision/v1 and this text with SHA-256. Illustrative: the log's own byte layout is not shown here.
leaf computing
Leaf hash. leaf_input = UTF-8(domain_tag) || 0x0A || canonical bytes of the log entry. leaf_hash = SHA-256(0x00 || leaf_input). Interior nodes use SHA-256(0x01 || left || right), as in RFC 9162 section 2.1.1. Domain tags take the form ump/<kind>/v<n> and are registered (section 21). The current tags are ump/decision/v1, ump/gate/v1, ump/run/v1, ump/approval/v1 and ump/vintage/v1. A tag’s version MUST change whenever its record schema changes in a way that alters canonical bytes.¶
Checkpoints. At least hourly, the log publishes a checkpoint in the C2SP tlog-checkpoint format (origin, decimal tree size, base64 root hash), signed as a C2SP signed note with an Ed25519 log key. The origin line is the log’s registered name. A checkpoint’s time is the genTime of the RFC 3161 token held beside it, never a line in the body. Other RFC 9162 logs MAY be used if they emit this format.¶
Anchoring outside our control. Each checkpoint is:¶
- timestamped by an RFC 3161 time-stamping authority, with messageImprint SHA-256 over the checkpoint’s signed-note bytes as first published by the log (body, blank line and the log’s own signature line), before any cosignature is added, a fresh nonce and certReq true. The TSA chain and its revocation data are archived with the token. Tokens are renewed per RFC 4998 before the TSA certificate or the hash algorithm expires;¶
- written to a retention-locked GCS bucket in a separate project administered by a different team;¶
- optionally published as a hash only to a public transparency log such as Sigstore Rekor;¶
- cosigned by at least one witness outside the agency, per C2SP tlog-cosignature (SHOULD, proposed).¶
A client or auditor MUST check that a checkpoint served by the API is consistent with the latest checkpoint in the anchor bucket before it trusts any proof against it. A mismatch raises integrity.broken.¶
Read interface. The log MUST serve the C2SP tlog-tiles read API (checkpoint, hash tiles, entry bundles) at a public read-only origin, so that the verifier computes inclusion and consistency proofs without access to UMP’s databases. The core API adds convenience routes (section 19). An inclusion proof is valid only against a checkpoint whose signature and anchor the client has verified, and whose consistency with the client’s last trusted checkpoint it has also verified.¶
An independent verifier, run by the framework owner’s team and not the platform team, fetches every checkpoint, checks consistency proofs, and raises integrity.broken on any failure. The verifier also raises integrity.broken when no anchored checkpoint arrives within 2 hours (proposed).¶
Threat model¶
| Actor | Can they rewrite history? | What stops or detects it |
|---|---|---|
| Analyst with database access¶ | No | Mirror edits fail verification against the log |
| Log operator¶ | Not undetected | Consistency proofs fail against checkpoints already timestamped and anchored elsewhere |
| KMS administrator¶ | Can forge new signatures, not old timestamps | RFC 3161 timestamps and external anchors predate the forgery; separation of duties |
| Operator who also controls the anchor bucket¶ | Not undetected | The TSA timestamp and the public log are outside their control |
The design detects tampering; it does not prevent someone with write access from appending a false record. Prevention for approvals comes from signatures tied to named approvers and the rule that no API token can approve.¶
Keys. Approver and log keys live in Cloud KMS. Each approval record names the approver key version that signed it; each checkpoint names its log key by key hash. Old key versions are disabled for signing but never destroyed while records signed with them are retained, so old signatures stay verifiable after rotation. Rotation is annual, or immediate on suspected compromise. Log operator, key custodian and anchor-bucket administrator are three different people, an organisational control checked when roles are configured and audited at deployment review, not a gate.¶
Approval statement. An approval is a record holding the approver principal (as its commitment, see Erasure), the artifact ID and revision, the content hash, the hash of the gate result set, the acknowledged fallback codes, the decision and the time. It is signed as a JWS (RFC 7515) over its RFC 8785 bytes. For the Full materiality tier, the approver MUST complete step-up authentication at NIST SP 800-63B AAL2 or above with a phishing-resistant authenticator (proposed), and the authentication event ID is part of the statement. An approver key manifest links key versions to principal commitments.¶
Log keys. Log verification keys are published in a key manifest that is itself a log leaf (tag ump/key-manifest/v1, provisional). A new log key is introduced by a checkpoint signed with both the old and the new key. After a suspected compromise, checkpoints later than the last externally anchored or witnessed one are untrusted until a witness cosigns a consistent checkpoint under the new key.¶
Erasure. Personal data never enters a leaf (G-INT-04). Personal data means personal data as defined in GDPR Art. 4(1), including pseudonymous identifiers and approver names. Before sealing, every such field in a record is replaced by a commitment HMAC-SHA-256(k_subject, value). The per-subject key and the plaintext are held outside the log. subject_ref in the log entry is HMAC-SHA-256(k_subject, person_ref). An erasure request destroys the subject’s key and the plaintext, and appends a tombstone record (tag ump/tombstone/v1, provisional) naming the affected record IDs. The stored record body is unchanged, so its content_hash and inclusion proof still verify. Verifiers and G-INT-03 MUST report a tombstoned field as verified and redacted, never as integrity.broken. An edited mirror body fails against content_hash. The retention-period conflict with records rules is a legal question flagged in section 24.¶
Open formats. Amazon QLDB’s end of support in July 2025 left users with a migration path that lacked cryptographic verification. UMP’s log, proofs and anchors use open formats the agency controls.¶