Assay RecordDRAFT 0.1
Specification · draft 0.1

The Assay Record format

Normative text for writers, issuers and verifiers of proof files. The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.

Version 0.1-draftPublished 27 September 2026Reference implementation RiftveilSteward Human Frontier

01Proof file

Assay Record · draft 0.1. One JSON file per record, UTF-8, named <Issuer name> <record_id>.json (Riftveil: Riftveil RV-260927-143205-7K3M-Q9XD-2B8F.json).

{
  "format": "assay-record",
  "version": "0.1-draft",
  "record_id": "RV-260927-143205-7K3M-Q9XD-2B8F",
  "issuer": "RV",
  "origin": "generated",
  "issued_at": "2026-09-27T14:32:05Z",
  "assessment": { "protocol": "1.3.3", "model": "claude-sonnet-5", "assistant": "riftveil.ai", "language": "fr" },
  "content": { "project": "…", "context": "…", "report": "…" },
  "proof": {
    "fingerprint": { "alg": "sha-256", "profile": "riftveil-report-v1", "value": "…64 hex…" },
    "seal": { "value": "S1-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX", "verify": "https://riftveil.ai/api/verify" }
  },
  "notice": "…"
}
Field Rule
origin generated (the issuer produced the report) or registered (produced elsewhere, only its fingerprint was sent). Covered by the seal through its label.
assessment For riftveil-report-v1, protocol and model are in the fingerprint. For riftveil-content-v1, assistant is. language is informative.
content The exact texts. They stay with the person who holds the file; the issuer keeps nothing.
proof.fingerprint Recomputed by every verifier from content (and the fields above). Never trusted as written.
proof.seal Checked by the issuer's endpoint against the recomputed fingerprint.

Two forms of the same record

Form Holds Kept by Used for
Proof file Everything above, content included The signer, with the PDF, in the decision file Verifying that a text is intact
Register entry Everything except content A register (Microsoft 365, the client's servers, or hosted) Counting, following up, auditing the structure

A register never needs the project: the fingerprint lets it prove which content a record belongs to without holding it. Whoever holds the proof file holds the content: share it as you would share the report.

Follow-up and decision (follow_up, optional)

Filled in by the person who decides, never by a model, after the report. It has its own fingerprint and its own seal, so it can be added without touching the seal of the assessment.

"follow_up": {
  "profile": "riftveil-decision-v1",
  "verified_by": { "last_name": "…", "first_name": "…", "role": "…" },
  "checked": "What the person checked themselves",
  "questions": [ { "n": 1, "question": "…", "suggested_owner": "CFO", "assigned_to": "…" } ],
  "decision": "proceed | conditions | stop",
  "conditions": "…",
  "place": "…",
  "signature": { "method": "drawn | typed", "typed": "…", "image": "data:image/png;base64,…" },
  "decided_at": "2026-09-27T15:16:39Z",
  "proof": {
    "fingerprint": { "alg": "sha-256", "profile": "riftveil-decision-v1", "value": "…64 hex…" },
    "seal": { "value": "S1-…", "sealed_at": "2026-09-27T15:16:39Z", "verify": "https://riftveil.ai/api/verify" }
  }
}
Field Rule
Fingerprint input JSON.stringify({ v: 1, id, report_fingerprint, verified_by, checked, questions, decision, conditions, place, signature }), with every field present (empty strings when not filled). report_fingerprint is the recomputed fingerprint of the assessment: a follow-up belongs to one report only.
proof.seal Issued by the issuer on the follow-up fingerprint and the issuer's clock (sealed_at); it requires a valid seal of the assessment. Only fingerprints are sent: names, decision and signature never leave the signer's device.
Unsealed follow-up Allowed. A verifier shows it as present but unsealed: nothing proves when it was written or that it was not changed.
Signature A simple electronic signature made on screen. The seal proves when the follow-up existed and that it has not changed, never who wrote it.

Later versions of this draft add outcome (what became of the decision) and readers[] (the chain of second readers), each with its own fingerprint.

02Record ID and seal

Assay Record · draft 0.1 · normative for every implementation, including Riftveil.

The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.

1. Format

<ISSUER>-<YYMMDD>-<HHMMSS>-<RRRR>-<RRRR>-<RRRR>
RV-260927-143205-7K3M-Q9XD-2B8F
Part Rule
ISSUER 2 to 6 letters A to Z, registered in ISSUERS.md. RV is Riftveil. An implementation MUST NOT use a prefix it has not registered. XT is reserved for tests and MUST NOT appear in a record used for a decision.
YYMMDD UTC date at which the ID is assigned (years 2000 to 2099).
HHMMSS UTC time at which the ID is assigned, to the second, from a clock synchronised with a public time source.
RRRR-RRRR-RRRR 60 bits from a cryptographically secure random generator, written in Crockford base32 (0-9 A-Z without I L O U), 12 characters in 3 groups of 4.

Pattern: ^[A-Z]{2,6}-\d{6}-\d{6}-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$

2. Assignment

  1. The ID MUST be assigned by code, at the moment the record is created.
  2. The ID MUST NOT be produced, suggested or completed by a language model. Models repeat "random" values; an ID written by a model is not unique.
  3. The ID MUST NOT be derived from the content (a hash of the project or of the report): two identical projects assessed twice are two records.
  4. An ID MUST NOT be reused, even for a new version of the same record. A new version gets a new ID and points to the previous one.
  5. Readers MUST accept lower case and spaces; the canonical form is upper case with hyphens.

3. Why duplicates are practically excluded

  • Two issuers cannot collide: the prefixes differ.
  • Within one issuer, two IDs can only collide if they are assigned in the same second and draw the same 60 random bits (1 chance in 1.15 × 10¹⁸ for any pair).
  • At 1 million records a day for one issuer, the probability of a single duplicate is about 1 in 500 million per year. At 1,000 records per second, about 1 in 70,000 per year.
  • This is a probability, not a mathematical impossibility. An issuer that needs absolute uniqueness MAY also check each new ID against its own register and draw again on a match; it MUST NOT shorten the random part to do so.

4. Seal (recommended)

An issuer SHOULD seal each record so that anyone can check, without the issuer storing anything, that the ID was issued by it for this content.

  • Fingerprint. SHA-256, in hexadecimal, of the content the issuer defines as the record content. The definition (profile) MUST be published and MUST include the ID whenever the issuer produced the content itself.
  • Seal. S<k>- followed by the first 160 bits of HMAC-SHA256(key_k, "<label>|" + id + "|" + fingerprint), in Crockford base32, 8 groups of 4. k is the key number; a new key gets a new number and old keys stay available for verification.
  • One label per origin. An issuer that both produces reports and registers reports produced elsewhere MUST use a different label for each, so that a seal can never be presented as the other origin.
  • Key. It MUST stay secret with the issuer, in a secret store, never in code, a shared file or a conversation.
  • Verification endpoint. The issuer MUST publish one that answers valid or not valid, and the origin, for { id, fingerprint, seal }, and stores nothing.
  • A valid seal proves who issued the ID, when, for which fingerprint and with which origin. It never says the project is right.

Riftveil profiles

Origin Label Profile Fingerprint input
generated on riftveil.ai riftveil-seal-v1 riftveil-report-v1 JSON.stringify({ v: 1, id, protocol, model, project, context, report })
produced elsewhere, registered riftveil-register-v1 riftveil-content-v1 JSON.stringify({ v: 1, assistant, project, context, report })

| follow-up and decision | riftveil-decision-v1 | riftveil-decision-v1 | see proof-file.md; the HMAC input adds the seal time: "riftveil-decision-v1|" + id + "|" + fingerprint + "|" + sealed_at |

Strings are used exactly as sent to and returned by the model (generated) or as pasted by the person who registers (registered). Verification: POST https://riftveil.ai/api/verify.

5. Proof file

The proof file is the record itself, in JSON: see proof-file.md. A record is verified on its proof file, never on a printout, a screenshot or a PDF alone: the ID, the fingerprint and the seal can be copied onto an altered document, the text of a proof file cannot be altered without the check failing. See verification.md.

A timestamp from a trusted time-stamping authority (RFC 3161, qualified under eIDAS in the European Union) MAY be added in proof.timestamp in a later draft; it would prove the date independently of the issuer. Riftveil does not add one yet.

03Verification

The rule

A record is verified on its proof file, never on a printout, a screenshot or a PDF alone.

  1. Recompute the fingerprint from the content of the proof file, following its profile.
  2. If it differs from the fingerprint written in the file: content altered. Stop.
  3. Send { id, fingerprint (recomputed), seal } to the issuer's endpoint.
  4. If the seal is not valid: not issued. If it is valid for another origin than the one the file states: forged origin. Stop.
  5. Otherwise: valid. Show the content of the file and ask the person to compare it with the document they were given. If they differ, the document was altered, whatever it says.
  6. If the file holds a follow_up: recompute its fingerprint (it includes the report fingerprint recomputed in step 1). Different: follow-up altered. No seal: follow-up present, not sealed. Otherwise send it with the request of step 3 as decision: { fingerprint, seal, sealedAt }; the answer adds decision: { valid, sealedAt }.

Checking the three values alone (ID, fingerprint, seal) only proves that they belong together, not that a given text is the record.

Riftveil endpoints

Endpoint Body Answer
POST https://riftveil.ai/api/verify { id, fingerprint, seal, decision? } { valid: true, id, issuedAt, origin, decision? } or { valid: false }
POST https://riftveil.ai/api/register { fingerprint, profile: "riftveil-content-v1" } { id, seal, issuedAt, origin: "registered", profile }
POST https://riftveil.ai/api/decide { id, report_fingerprint, report_seal, fingerprint } { seal, sealedAt }

/api/verify accepts requests from any site (CORS), receives fingerprints only and stores nothing. /api/register and /api/decide are used from riftveil.ai pages, behind a bot check; they also receive fingerprints only and keep nothing but a daily counter per hashed IP address. https://riftveil.ai/issuers.json lists the registered prefixes.

Pages and widget

  • https://riftveil.ai/verify.html (FR: /fr/verify.html): verification by proof file, or by the three values with a warning.
  • https://riftveil.ai/register.html (FR: /fr/register.html): registration of a report produced elsewhere; the fingerprint is computed in the browser and the proof file is downloaded.
  • Widget for any page (it verifies the assessment; the follow-up is shown on riftveil.ai/verify):
<div data-riftveil-verify data-lang="fr"></div>
<script src="https://riftveil.ai/embed/verify.js" defer></script>

The host page's Content-Security-Policy must allow script-src and connect-src https://riftveil.ai.

What remains possible, and what to do about it

Risk Status
Altered text in a PDF Detected when the proof file is used; not detected on a PDF alone. A qualified electronic seal on the PDF is planned.
ID copied from another record Detected: the proof file shows the other record's text.
Registered report presented as generated Detected: one label per origin.
Fabricated project assessed for real Not detectable, and not the purpose: the seal proves provenance and integrity, never truth.
Theft of the sealing key Key in a secret store, rotation by key number without invalidating earlier seals.
Nobody verifies Organisational: make the proof file mandatory in the decision file.

04Conformance

Assay Record · draft 0.1. The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.

A conformance claim names the role and the draft version: "writes Assay Record 0.1 proof files", "verifies Assay Record 0.1 proof files", "issues Assay Record 0.1 records under prefix XX".

Writer

A tool that writes proof files:

  1. MUST produce UTF-8 JSON that validates against schema/assay-record-0.1.schema.json.
  2. MUST copy the texts into content exactly as they were assessed, sent and returned, without trimming, reformatting or translation.
  3. MUST compute proof.fingerprint.value from those exact texts with the profile it names.
  4. MUST NOT let a language model write, suggest or complete the record_id, the fingerprint, the seal or any field of follow_up.
  5. MUST leave follow_up absent, or with every field empty, until a person fills it in.
  6. SHOULD name the file <Issuer name> <record_id>.json.

Issuer

An issuer assigns IDs and seals:

  1. MUST use a prefix registered in ISSUERS, and MUST NOT issue records under XT for real decisions.
  2. MUST assign IDs by code, in the format of Record ID and seal, from a clock synchronised with a public time source.
  3. MUST keep its sealing keys in a secret store, number them, and keep retired keys available for verification.
  4. MUST use one label per origin and one for the follow-up.
  5. MUST publish a verification endpoint that answers valid or not valid, the origin, and for a follow-up the seal time, for fingerprints only, and stores nothing.
  6. MUST NOT keep the texts, a list of IDs or a list of fingerprints for verification purposes.
  7. SHOULD rate-limit sealing and registration, and SHOULD protect them against automated abuse.

Verifier

A tool that verifies proof files:

  1. MUST recompute every fingerprint from the file, following its profile, and MUST NOT trust a fingerprint as written.
  2. MUST report "altered" when a recomputed fingerprint differs from the one in the file, and stop there for that part.
  3. MUST ask the issuer's endpoint to check each seal, sending only the ID, fingerprints, seals and seal times.
  4. MUST report the origin returned by the issuer, and "forged origin" when it differs from the one the file states.
  5. MUST show the sealed texts so that a person can compare them with the document they were given.
  6. MUST NOT present a check of the ID, fingerprint and seal alone as a verification of a document.
  7. SHOULD state in its result that a valid record proves provenance and integrity, never that a decision was right.

05Security and privacy considerations

Assay Record · draft 0.1.

What is protected

Threat Protection Residual risk
Text altered after issue Fingerprint recomputed from the file by every verifier None on the proof file; a PDF shown alone proves nothing
ID or seal copied onto another document Verification shows the texts sealed under that ID A person must compare the texts with the document
Registered report presented as generated One seal label per origin None
Follow-up moved to another report Its fingerprint includes the report fingerprint None
Follow-up edited after sealing Its own fingerprint and seal, with the issuer's time None if sealed; an unsealed follow-up proves nothing
Seal time moved The time is inside the sealed message None
Forged seal HMAC-SHA256 truncated to 160 bits, secret key Only the issuer can check a seal; a timestamp by a third party is planned
Stolen sealing key Secret store, numbered keys, rotation Records sealed with a stolen key cannot be told apart; the key number allows a cut-off date
Mass sealing by robots Bot check, per-IP and global daily limits None known

What is not protected, by design

  • The truth of the project or the quality of the review. A seal proves provenance and integrity only.
  • The identity of the signer. An on-screen signature is a simple electronic signature; the seal proves when the follow-up existed, not who wrote it.
  • Verification without the issuer. With HMAC seals, only the issuer can say whether a seal is valid. Public-key signatures or RFC 3161 timestamps are planned, so that a record can be checked without asking its issuer.

Privacy

  • The issuer receives fingerprints, never texts, names, decisions or signatures. A SHA-256 fingerprint cannot be turned back into the text.
  • The issuer stores no texts and no list of IDs or fingerprints. Only short-lived rate-limit counters, keyed on a secret hash of the IP address, are kept.
  • Whoever holds a proof file holds the texts, and the names in the follow-up. Share it as you would share the report.
  • A register entry without the texts is planned, for registers that must not hold the project.

06Registered issuer prefixes

An implementation that writes Assay Records needs its own prefix before its first record used for a decision. Request one by pull request on this file: prefix, name, contact, URL of the verification endpoint.

Prefix Issuer Verification Since
RV Riftveil (riftveil.ai) POST https://riftveil.ai/api/verify 2026-09-27
XT Reserved for tests, never valid for a decision none 2026-09-27

07Versions

Version Date Changes
0.1-draft 2026-09-27 First public draft: proof file, record ID, profiles riftveil-report-v1, riftveil-content-v1, riftveil-decision-v1, HMAC seals, follow-up with decision, verification rules, JSON Schema, test vectors.

Planned for 0.2

  • Canonical JSON (RFC 8785) as a fingerprint input, alongside the current profiles.
  • Timestamps from a trusted time-stamping authority (RFC 3161) in proof.timestamp.
  • Register entry: the record without content.
  • Per-finding follow-up, outcome, and readers[] with their own fingerprints.

Compatibility rule

A new draft never changes how an existing profile is computed. A verifier for 0.2 verifies every 0.1 proof file.

Governance

Human Frontier stewards the format; Riftveil is the reference implementation. While the draft is open, comments go to hello@riftveil.ai. Changes are published in this table before they apply.

08Example

Illustrative record with a follow-up. Prefix XT is reserved for tests: its seals are not valid. Download · Download the JSON Schema

{
  "format": "assay-record",
  "version": "0.1-draft",
  "record_id": "XT-260927-143205-7K3M-Q9XD-2B8F",
  "issuer": "XT",
  "origin": "generated",
  "issued_at": "2026-09-27T14:32:05Z",
  "assessment": {
    "protocol": "1.3.3",
    "model": "claude-sonnet-5",
    "assistant": "riftveil.ai",
    "language": "en"
  },
  "content": {
    "project": "Based on our analysis, we recommend consolidating our three cloud vendors into one five-year contract from Q1 2027. The chosen vendor offers a 22% discount in year one, full migration support and a single point of contact. Savings are estimated at EUR 1.4M over five years.",
    "context": "CFO, board decision next month",
    "report": "**Record**\n- Triage: CRITICAL · Levels 0, 1, 2, 3, 4\n- Findings: 3 · CRITICAL 1 · HIGH 1 · MODERATE 1 · LOW 0\n- Assumptions: 2 with a named verifier · 0 without\n- Questions: 2 · 2 with an owner · 0 without\n- Highest-value question: \"What does leaving the five-year contract cost if the price rises after year one?\"\n- Independence: separate session, same model family\n\n## 1. Context and triage\nCriteria: reversibility HIGH · people affected MODERATE · financial commitment HIGH → two criteria at HIGH or above → CRITICAL.\n\n## 2. What holds up\n- The recommendation names one vendor, one start date and one contract length: it can be checked.\n\n## 3. What deserves scrutiny\n#### Year-one discount presented as the saving · CRITICAL\nObserved: \"a 22% discount in year one\" supports \"savings are estimated at EUR 1.4M over five years\".\nWhy it matters: a discount limited to year one says nothing about years two to five.\nWhat you should check: the price schedule for every year of the contract.\n\n#### Exit cost not mentioned · HIGH\nObserved: \"one five-year contract\" with no exit terms.\nWhy it matters: a single vendor for five years is hard to leave if prices or service change.\nWhat you should check: exit clauses, notice periods and data return terms.\n\n#### Migration effort reduced to \"full migration support\" · MODERATE\nObserved: \"full migration support and a single point of contact\".\nWhy it matters: support from the vendor does not cover the internal work.\nWhat you should check: which workloads can move, when, and at what internal cost.\n\n## 4. Assumptions the project relies on\n1. Prices after year one stay close to year one. Who can verify: Procurement.\n2. All workloads can move before the current contracts end. Who can verify: CIO.\n\n## 5. Questions before you decide\n1. What does leaving the five-year contract cost if the price rises after year one? Owner: Procurement\n2. Which workloads cannot move before the old contracts end? Owner: CIO\n\nNo verdict. The decision is yours.\n"
  },
  "proof": {
    "fingerprint": {
      "alg": "sha-256",
      "profile": "riftveil-report-v1",
      "value": "68ff259fb9a33d25ea8e460b1f13c17bf845b7c86f93d5a84e779648f97f0cda"
    },
    "seal": {
      "value": "S1-QC08-7YG2-CSF5-288J-NXXS-AY5R-XGE3-ECN2",
      "verify": "https://riftveil.ai/api/verify"
    }
  },
  "notice": "Illustrative example. XT is the prefix reserved for tests: this record was not issued by any issuer and its seals are not valid.",
  "follow_up": {
    "profile": "riftveil-decision-v1",
    "verified_by": {
      "last_name": "Martin",
      "first_name": "Claire",
      "role": "Chief Financial Officer"
    },
    "checked": "Year-two to year-five prices re-read in the draft contract with procurement.",
    "questions": [
      {
        "n": 1,
        "question": "What does leaving the five-year contract cost if the price rises after year one?",
        "suggested_owner": "Procurement",
        "assigned_to": "Paul Durand"
      },
      {
        "n": 2,
        "question": "Which workloads cannot move before the old contracts end?",
        "suggested_owner": "CIO",
        "assigned_to": "Nadia Benali"
      }
    ],
    "decision": "conditions",
    "conditions": "Exit clause capped at six months of fees before signature.",
    "place": "Brussels",
    "signature": {
      "method": "typed",
      "typed": "Claire Martin",
      "image": ""
    },
    "decided_at": "2026-09-27T16:05:40Z",
    "proof": {
      "fingerprint": {
        "alg": "sha-256",
        "profile": "riftveil-decision-v1",
        "value": "ab99fe11d917d7937d51695d1da49db60437f14ed48ff9df8f552c94b595f5eb"
      },
      "seal": {
        "value": "S1-7M5T-CP2P-HT9Q-MXVV-A6ZJ-9CPM-YGCX-AX1S",
        "sealed_at": "2026-09-27T16:05:40Z",
        "verify": "https://riftveil.ai/api/verify"
      }
    }
  }
}