Le texte normatif est en anglais ; il fait foi. Les titres et cette introduction sont traduits.
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
- The ID MUST be assigned by code, at the moment the record is created.
- 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.
- 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.
- 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.
- 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 ofHMAC-SHA256(key_k, "<label>|" + id + "|" + fingerprint), in Crockford base32, 8 groups of 4.kis 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.
- Recompute the fingerprint from the content of the proof file, following its profile.
- If it differs from the fingerprint written in the file: content altered. Stop.
- Send
{ id, fingerprint (recomputed), seal }to the issuer's endpoint. - If the seal is not valid: not issued. If it is valid for another origin than the one the file states: forged origin. Stop.
- 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.
- 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 asdecision: { fingerprint, seal, sealedAt }; the answer addsdecision: { 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:
- MUST produce UTF-8 JSON that validates against
schema/assay-record-0.1.schema.json. - MUST copy the texts into
contentexactly as they were assessed, sent and returned, without trimming, reformatting or translation. - MUST compute
proof.fingerprint.valuefrom those exact texts with the profile it names. - MUST NOT let a language model write, suggest or complete the
record_id, the fingerprint, the seal or any field offollow_up. - MUST leave
follow_upabsent, or with every field empty, until a person fills it in. - SHOULD name the file
<Issuer name> <record_id>.json.
Issuer
An issuer assigns IDs and seals:
- MUST use a prefix registered in ISSUERS, and MUST NOT issue records under
XTfor real decisions. - MUST assign IDs by code, in the format of Record ID and seal, from a clock synchronised with a public time source.
- MUST keep its sealing keys in a secret store, number them, and keep retired keys available for verification.
- MUST use one label per origin and one for the follow-up.
- 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.
- MUST NOT keep the texts, a list of IDs or a list of fingerprints for verification purposes.
- SHOULD rate-limit sealing and registration, and SHOULD protect them against automated abuse.
Verifier
A tool that verifies proof files:
- MUST recompute every fingerprint from the file, following its profile, and MUST NOT trust a fingerprint as written.
- MUST report "altered" when a recomputed fingerprint differs from the one in the file, and stop there for that part.
- MUST ask the issuer's endpoint to check each seal, sending only the ID, fingerprints, seals and seal times.
- MUST report the origin returned by the issuer, and "forged origin" when it differs from the one the file states.
- MUST show the sealed texts so that a person can compare them with the document they were given.
- MUST NOT present a check of the ID, fingerprint and seal alone as a verification of a document.
- 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, andreaders[]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.
08Exemple
Fichier d’illustration avec un suivi. Le préfixe XT est réservé aux tests : ses sceaux ne sont pas valides. Télécharger · Télécharger le schéma JSON
{
"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"
}
}
}
}