# The Record — High-Level Design (HLD v0.1)

## Executive summary

The Record is a WHO-published, global registry of deaths and serious harms reported in connection with conversational AI chatbots. It is an evidence registry, not a causal-claims database: each public claim is tied to a source, a versioned editorial method, an evidence tier, and a permanent version URL. It gives the general public a restrained regulatory-style site; families, journalists, officials, and vendors governed intake and reply paths; and researchers, courts, regulators, and agents stable APIs, exports, citations, and signed snapshots. Pending submitters see the status of their own submissions, while nothing unverified enters public cases, feeds, search, or counts. (CAP-1.01, CAP-1.02, CAP-2.01, CAP-2.05, CAP-3.01, CAP-3.02, CAP-8.01)

The production data plane uses only Cloudflare: Pages for static public presentation, Workers for the API and editorial application, D1 for relational state, R2 for evidence and immutable release objects, KV for expendable edge state, Queues for bounded asynchronous work, Turnstile for anonymous writes, Access/Zero Trust for privileged identity, and Cloudflare WAF and Analytics for protection and operations. GitHub is the permitted independent source and history plane. Keeping public pages, current exports, schemas, and feeds static and edge-cacheable makes ordinary reads both inexpensive and available when D1 is degraded. Sensitive objects remain in private R2 buckets and never pass through static builds. (CAP-5.03, CAP-5.04, CAP-5.06, CAP-6.01, CAP-6.02, CAP-10.03)

Five design bets govern the system. First, immutable `TRC-...` identifiers and append-only versions are the unit of citation. Second, public state can change only through an exact staged content hash approved by two distinct eligible humans using WebAuthn signatures. Third, the audit chain and weekly corpus manifest are independently checkpointed in locked R2 objects and a signed GitHub history, making silent alteration detectable. Fourth, D1 is the workflow system of record, but generated public artifacts are content-addressed and served from Pages/R2, separating intake availability from public-read availability. Fifth, evidence level (T1–T3), outcome class (F/G), claim status, translation status, and publication status are separate dimensions; the UI never compresses them into a misleading “verified/unverified” badge. (CAP-1.02, CAP-3.03, CAP-3.04, CAP-3.05, CAP-5.01, CAP-7.02, CAP-8.01, CAP-9.01)

## Non-goals

- The Record does not decide legal liability, medical causation, product defect, or the truth of a party’s broader theory. It records sourced claims and official findings with their exact scope and conflict status. (CAP-1.02, CAP-2.11, CAP-7.04)
- It does not treat submission, repetition, virality, a model’s output, or a duplicate report as evidence or as a countable case. (CAP-3.02, CAP-5.01, CAP-5.07)
- It is not an emergency, crisis-counselling, law-enforcement reporting, or clinical service. Intake must display appropriate local emergency guidance without retaining crisis-chat content. (CAP-2.01)
- It is not a general social network: there are no public comments, reactions, follower counts, popularity ranking, or unmoderated uploads. (CAP-3.02, CAP-5.02, CAP-5.07)
- It does not expose confidential editorial notes, sealed evidence, submitter identities, raw IP addresses, privileged vendor material, or private legal matter workspaces through public surfaces. (CAP-3.06, CAP-4.02, CAP-5.02, CAP-5.03, CAP-8.02)
- It does not promise that every third-party attachment can be redistributed. Citations, hashes, rights, access routes, and restrictions are preserved even when bytes cannot be public. (CAP-1.04, CAP-7.01, CAP-7.06)
- It does not use a large language model to verify a source, translate controlling legal text without human review, choose a tier, or publish a mutation. Deterministic tooling may assist triage only. (CAP-5.07, CAP-9.01)
- It does not offer arbitrary GraphQL or database queries in v0.1. Bounded REST filters and downloadable complete snapshots cover research use without an unbounded execution surface. (CAP-5.04, CAP-6.02, CAP-6.05)
- It does not use IPFS, commercial email/SMS, hosted search, hosted analytics, hosted antivirus, or any other non-Cloudflare runtime service. GitHub is used only for source, reviewed infrastructure configuration, and an independent immutable public history. (CAP-5.06, CAP-5.08, CAP-7.02)
- It does not claim strict in-country data residence where Cloudflare cannot contractually provide it. Such sensitive bytes are not accepted until a lawful Cloudflare locality is approved; only permitted metadata, hashes, or attestations cross borders. (CAP-7.06)

## Information architecture

The public origin is a WHO-controlled hostname such as `record.who.int`; the WHO page linking to it and DNS ownership form the publisher proof. Visual language follows WHO/CDC/ECDC conventions: institutional masthead, restrained blue/neutral palette, high information density, data-source and update stamps near every figure, no hero sales copy, testimonials, engagement counters, or animated marketing. A persistent “Evidence and limitations” link sits beside the registry title. Page HTML is server/static rendered, progressively enhanced, and usable without JavaScript. (CAP-1.01, CAP-1.04, CAP-9.02)

### Public URL taxonomy

| URL | Purpose and permanence | Capability trace |
|---|---|---|
| `/` | Plain-language purpose, current signed release, limitations, browse and submit paths. | CAP-1.01, CAP-1.04 |
| `/cases` | Faceted case index; filter state is encoded in normalized query parameters and can be cited/exported. | CAP-1.03, CAP-1.05 |
| `/cases/TRC-01J...` | Permanent current-case page. ID is generated once (UUIDv7 encoded with `TRC-`) and never recycled. | CAP-8.01 |
| `/cases/TRC-01J.../versions/7` | Immutable public version with its own ETag, citation, source manifest, policy version, and signatures. | CAP-3.05, CAP-8.01 |
| `/cases/TRC-01J.../compare?from=5&to=7` | Human field-level diff; equivalent JSON is in the API. | CAP-3.05, CAP-6.04 |
| `/cases/TRC-01J.../citation` | Bluebook, OSCOLA, BibTeX, RIS, and plain citation for current or selected version. | CAP-8.01, CAP-8.03 |
| `/cases/TRC-01J.../summary` | Print-first dated offline summary with canonical URL and “check current record” notice. | CAP-1.06 |
| `/cases/TRC-01J.../disputes/DSP-...` | Public dispute/reply and disposition, excluding protected material. | CAP-4.02, CAP-4.03, CAP-4.05 |
| `/sources/SRC-...` | Source identity, type, lineage, retrieval, rights, hash and access information. | CAP-1.04 |
| `/vendors/VND-...`, `/courts/CRT-...`, `/dockets/...` | Versioned entity pages and sourced case relations. | CAP-1.03, CAP-1.05, CAP-4.06 |
| `/data`, `/data/releases/2026-W29` | Data dictionary, schemas, current export and immutable signed releases. | CAP-6.02, CAP-6.03, CAP-7.01, CAP-7.02 |
| `/methods`, `/methods/v1.3.0` | Plain-language and full citable methods, including tier and disclosure policies. | CAP-3.01 |
| `/analytics` | Suppression-aware trends, denominators, uncertainty, discontinuities, tables and downloads. | CAP-1.05, CAP-7.03 |
| `/submit`, `/submit/family`, `/submit/journalist` | Public guidance and trauma-informed, resumable intake. | CAP-2.01, CAP-2.02, CAP-2.07 |
| `/official`, `/vendor` | Access-protected official and vendor entry points; no confidential state in public HTML. | CAP-2.10, CAP-4.01 |
| `/status`, `/governance`, `/accessibility`, `/licence` | Service commitments, editorial governance/conflicts, accessibility statement and rights. | CAP-7.06, CAP-10.03, CAP-10.04 |
| `/{locale}/...` | Localized presentation for the eleven required locales; canonical content IDs do not change. | CAP-9.01 |

Old human-readable slugs are conveniences only. Every route resolves the stable ID first. Merges create a `308` redirect from duplicate current URLs to the survivor and retain each duplicate’s immutable version URLs. Retraction, withdrawal, privacy/legal removal, and supersession return a minimal `200` tombstone, not `404`; a truly unknown ID returns a machine/human `404`, and a deliberately unavailable non-case resource may return `410`. Direct version URLs are never redirected to current content. (CAP-4.07, CAP-8.01)

### Page model and navigation

The main navigation has five tasks: Browse cases, Data and analytics, Methods, Submit or correct, and About/governance. Search accepts everyday terms, controlled vocabulary aliases, normalized docket numbers, stable IDs, vendors/products, courts, jurisdictions, and source titles. Result cards show outcome class, tier, claim-status summary, jurisdiction, event year granularity, latest version date, and the limitation “association recorded; causation not implied.” Filters use inclusive `OR` within a facet and `AND` between facets; conflicting include/exclude choices are rejected. Active filters are removable chips represented in the URL, and a “copy reproducible query” action records query, UTC execution, dataset, schema, vocabulary, tier-policy version, and result count. (CAP-1.01, CAP-1.02, CAP-1.03)

A case page orders information as: identification and current status; plain-language registry summary; outcome/event chronology; claim-by-claim findings with citations and conflict markers; exact official wording where applicable; source table and provenance; vendor/official/family replies; privacy-separated family context; changes/history; citation/export; and limitations. Family tribute is visually and semantically labelled “Family-provided context” and can never be rendered inside verified findings. Every status has text and icon, never colour alone. (CAP-1.02, CAP-1.04, CAP-1.07, CAP-4.03, CAP-9.02)

Pending work is not publicly searchable because exposing existence can violate embargo, consent, or protected-identity requirements. An authenticated submitter receives `/track/SUB-...` and sees coarse states—received, identity/evidence checks, editorial review, awaiting submitter, staged, published, closed—with explanations and next actions. Public aggregate queue metrics use delayed, coarse counts and never expose a submission, receipt time, queue position, reviewer, or correlation metadata. This interprets “submissions displayed with current status while pending” as displayed to the authorized submitter, not published as allegations. (CAP-2.05, CAP-2.07, CAP-2.08, CAP-5.02, CAP-10.02)

## Data model

D1 contains workflow and normalized public metadata. Large bytes live in R2, addressed from D1 by opaque key and SHA-256; public release rows point to immutable artifacts. All IDs are application-generated UUIDv7 with a type prefix, all timestamps are UTC RFC 3339 with millisecond precision internally, and public time precision is reduced where correlation or identity risk requires it. Mutable tables use optimistic `row_version`; published truth is never updated in place but projected from append-only `Version` and `EditorAction` records. Foreign keys are enabled, and every query is a prepared statement. (CAP-3.05, CAP-5.01, CAP-5.02)

```mermaid
erDiagram
    CASE ||--o{ SUBMISSION : concerns
    CASE ||--|{ VERSION : has
    CASE ||--o{ DISPUTE : challenged_by
    CASE }o--o{ SOURCE : supported_by
    CASE ||--o{ ATTACHMENT : governs
    SUBMISSION ||--o{ ATTACHMENT : supplies
    SUBMISSION ||--o{ EDITOR_ACTION : reviewed_through
    VERSION ||--o{ EDITOR_ACTION : authorized_by
    VERSION }o--o{ SOURCE : cites
    VERSION ||--o{ ATTACHMENT : publishes_derivative
    VERSION }o--o{ LANGUAGE : rendered_in
    DISPUTE ||--o{ DISPUTE_REPLY : contains
    DISPUTE ||--o{ ATTACHMENT : evidences
    EDITOR_ACTION }o--|| EDITOR_ACTION : chains_to

    CASE {
      text case_id PK
      text canonical_slug
      text outcome_class
      text verification_tier
      text lifecycle_status
      int current_version_no
    }
    SUBMISSION {
      text submission_id PK
      text case_id FK
      text submitter_type
      text workflow_state
      text idempotency_hash
      text governing_policy_id
    }
    SOURCE {
      text source_id PK
      text source_type
      text title
      text publisher
      text content_sha256
      text rights_code
    }
    ATTACHMENT {
      text attachment_id PK
      text r2_object_key
      text sha256
      text confidentiality
      text scan_state
      text licence_override
    }
    VERSION {
      text version_id PK
      text case_id FK
      int version_no
      text content_sha256
      text public_r2_key
      text publication_state
    }
    EDITOR_ACTION {
      text action_id PK
      text version_id FK
      text previous_action_hash
      text action_hash
      text actor_key_id
      blob webauthn_signature
    }
    DISPUTE {
      text dispute_id PK
      text case_id FK
      text challenger_type
      text state
      text response_due_at
    }
    DISPUTE_REPLY {
      text reply_id PK
      text dispute_id FK
      text reply_type
      text visibility
      text body_hash
    }
    LANGUAGE {
      text language_id PK
      text bcp47_tag
      text direction
      text fallback_tag
      text status
    }
```

The diagram shows the required domain entities; join and policy tables below keep many-to-many relations normalized. No confidential field is placed in a public projection table. (CAP-3.06, CAP-5.03)

### Entity definitions

**Case.** Primary key `case_id`. Fields: non-authoritative `canonical_slug`; lifecycle (`draft`, `published`, `disputed`, `retracted`, `withdrawn`, `legal_hold`, `privacy_removed`, `merged`, `superseded`); `outcome_class` (`F`, `G`, `unknown`); `verification_tier` (`T1`, `T2`, `T3`); claim-status aggregate; event date value plus precision; event/residence/reporting jurisdictions separately; publishable subject name form; product/vendor/model entity IDs with attribution certainty; harm mechanisms; versioned health-code mappings; docket/court/legal-theory/prevention-report links; `current_version_no`; created/current-published timestamps; and disclosure-policy ID. `Case` has zero-to-many submissions, one-to-many versions, zero-to-many disputes and sources through joins, and optionally `merged_into_case_id`. Identity-sensitive review fields live in a separate encrypted private-subject object, not this row. (CAP-1.03, CAP-1.05, CAP-2.02, CAP-4.07, CAP-7.04, CAP-8.01)

**Submission.** Primary key `submission_id`; optional many-to-one `case_id` because new intake may not yet match a case. Fields: submitter principal ID (pseudonymous), submitter type, contact-channel reference, relationship/capacity assurance and expiry, source language, encrypted R2 payload key/hash, public attribution consent separately from identity, embargo/legal/family-consent holds, jurisdiction, governing method version, workflow state and state reason, owner stable staff ID, received/updated/due timestamps, idempotency key hash, duplicate candidate group, batch/row IDs, withdrawal state, and retention date. A submitter sees this entity through a scoped capability; it never feeds counts. (CAP-2.01, CAP-2.03, CAP-2.05, CAP-2.06, CAP-2.07, CAP-2.08, CAP-2.09, CAP-2.10, CAP-3.02)

**Source.** Primary key `source_id`. Fields: type (`official`, `court`, `peer_reviewed`, `journalism`, `family_testimony`, `vendor_statement`, `other`), title, author, publisher, dates, canonical and archive locators, retrieval timestamp, jurisdiction, court/docket/filing metadata, original language, official office/capacity, content and normalized-file SHA-256, MIME/size, redistribution status, rights statement, access limitation, replacement/supersession link, and provenance-parent source ID. `CaseSource` and `VersionSource` carry claim IDs, exact passage selectors, support/contradict/mentions relation, source version, and editor assessment. A source may support many cases and versions; each published factual claim must have at least one relation or explicit `unsupported` status. (CAP-1.02, CAP-1.04, CAP-2.11, CAP-7.03)

**Attachment.** Primary key `attachment_id`. Fields: submission/case/dispute/source owner FKs; unguessable private R2 key; immutable original SHA-256 and byte length; detected MIME; original filename stored only in private metadata; uploader/capacity; acquisition and custody events; source locator; confidentiality, redaction, seal, redistribution and quarantine states; scan engine/rule version and result; encryption envelope/key version; licence (`CC-BY-4.0` default only when rights holder authorizes) or per-file override; retention/locality policy; derivative-of ID; public R2 key only for approved derivative; and access log class. An original can have many redacted/safe derivatives, each with its own hash and approval. Bytes are never overwritten. (CAP-2.04, CAP-5.03, CAP-5.07, CAP-7.06)

**Version.** Primary key `version_id`; unique `(case_id, version_no)`. Fields: canonical JSON R2 key, JSON SHA-256, prior version ID, schema/vocabulary/method/disclosure versions, translation-set version, staged/public/retracted state, author action ID, exact approval-set hash, published timestamp, public manifest key, diff key, correction/supersession metadata, and release ID. Case public content is canonical JSON with deterministic key order and Unicode normalization; HTML, JSON, CSV, PDF summary and API projections are reproducible derivatives. A version cites many sources and translations and has at least two qualifying signed approvals before public state. (CAP-3.01, CAP-3.04, CAP-3.05, CAP-6.03, CAP-8.01, CAP-9.01)

**EditorAction.** Primary key `action_id`. Append-only fields: action type; object type/ID; exact before/after content hashes; affected JSON pointers; reason code and free-text rationale hash; policy version; actor principal, public stable staff ID, role and affiliation at action time; WebAuthn credential/key ID; UTC time; prior global action hash; prior object action hash; action payload hash; WebAuthn signature and authenticator data; proposal/approval group ID; source refs; restricted-detail R2 pointer; system request ID. Each action joins to zero or one earlier action on both chains and to the exact Version. A redacted public projection keeps event ID, time bucket, stable accountable identity/custodian, action, rationale category and hashes. (CAP-3.04, CAP-3.05, CAP-3.06, CAP-4.08, CAP-5.01)

**Dispute.** Primary key `dispute_id`; many-to-one Case and challenged Version. Fields: challenger principal/type/capacity assurance, challenged JSON pointers and findings, proposed remedy, confidential evidence refs, received time, state, public neutral-notice flag, assigned editor, escalation, recusal/conflict records, response deadline, extension reason/new deadline, outcome, rationale/policy version, tier impact, first-decision action, appeal parent, and public version ID. A class review has a parent dispute and `DisputeCase` members but produces a separately approved action per case. (CAP-4.01, CAP-4.02, CAP-4.05, CAP-4.08, CAP-4.09)

**DisputeReply.** Primary key `reply_id`; many-to-one Dispute. Fields: author principal/capacity, reply type (`challenger`, `registry_decision`, `appeal`, `vendor_statement`, `official_update`), submitted/published times, source language, confidential body R2 hash, approved public body/hash, attribution, word/safety review, redaction/rejection reason, supersedes reply ID, and Version ID. Replies append beside the entry; they never replace registry findings. (CAP-4.02, CAP-4.03, CAP-4.05)

**Language.** Primary key `language_id` (BCP 47 tag, such as `en`, `ar`, `zh-Hans`); unique tag. Fields: English/local name, script, direction, CLDR plural family, enabled surfaces, fallback tag, typography token set and status. `Translation` joins Language to Version, Source passage, UI message catalog or methodology version and records source/target hashes, method (`original`, `human`, `machine_assisted`), translator/reviewer stable IDs, confidence, terminology notes, controlling-original locator, review state and staleness. Languages have many translations; each translation belongs to exactly one immutable content version. (CAP-9.01)

Supporting entities include `Claim` (the unit of status and source support), `EntityNameHistory` for vendors/products/ownership, `ControlledTerm` and mappings, `Hold`, `ActorPrincipal`, `Credential`, `ApprovalGroup`, `AuditCheckpoint`, `Release`, `AccessGrant`, `Notification`, `SavedQuery`, `WorkflowTransition`, `JurisdictionPolicy`, `Docket`, `Court`, and normalized join tables. These support historic attribution, alerts, governance, reproducible cohorts and sovereign handling without overloading the nine domain entities. (CAP-1.03, CAP-1.05, CAP-2.08, CAP-7.03, CAP-7.04, CAP-7.05, CAP-7.06, CAP-10.01, CAP-10.02, CAP-10.04)

## Verification tiers & workflow

The Record uses two orthogonal axes. `T1/T2/T3` describe the reviewed strength and public verifiability of evidence under a named policy version; they do not express causation, severity, blame, or statistical risk. `F/G` describe outcome, not confidence. Keeping the axes separate prevents a fatal outcome from being mistaken for strongly verified evidence and a grave non-fatal harm from being treated as less credible. Claim-level statuses (`supported`, `disputed`, `contradicted`, `unsupported`, `superseded`) remain visible even when a case has an aggregate tier. (CAP-1.02, CAP-3.01, CAP-3.03, CAP-7.04)

| Code | Definition and minimum evidence bar | Counting/publication rule | Trace |
|---|---|---|---|
| **T1 — official or primary-record verified** | A qualifying official finding or primary legal/medical record whose issuing authority/capacity and bytes are authenticated, whose relevant exact passage supports inclusion, and whose privacy/rights checks pass. The direct coroner path assigns T1 after capacity, signature/hash, completeness and controlling-language mapping pass; editors do not re-adjudicate the official finding. Other T1 routes follow the versioned corroboration tests in methodology. | Public and countable only after two-person integrity/privacy/publication approval of the exact staged version. “Official basis” names office/jurisdiction and never implies WHO adopted the official interpretation. | CAP-1.04, CAP-2.11, CAP-3.01, CAP-3.03 |
| **T2 — independently corroborated public reporting** | At least two independently controlled, named, retrievable sources with distinct URLs or stable locators, adequate provenance and no unresolved identity/duplicate failure. Merely syndicated copies, circular citations and two pages repeating one unnamed source count as one source. Contradictory evidence must be displayed. | May be published and counted under a separately reported T2 cohort after four-eyes approval. It is never labelled “officially verified.” | CAP-1.02, CAP-3.02, CAP-3.03 |
| **T3 — under editorial review** | A relevant submission with at least one identifiable lead but insufficient T1/T2 evidence or incomplete review. T3 is a private editorial tier, not a public allegation class. | Visible to authorized submitter/editors as “under review”; excluded from public case pages, search, feeds, analytics and counts until ordinary publication criteria pass. If policy later permits a public leads register, that is a separate approval-gate decision and dataset. | CAP-3.02, CAP-3.03, CAP-5.01 |
| **F — fatal outcome** | A source records a death relevant to the scoped chatbot interaction. Date precision and the source’s own causation language are preserved. | Counted only with a published T1/T2 case; always filterable separately and never presented as “caused by” absent an exact supported claim. | CAP-1.05, CAP-7.04 |
| **G — grave non-fatal harm** | Serious non-fatal harm meeting the versioned inclusion policy: for example, life-threatening event, inpatient care, enduring disability, violence toward another, or clinically significant psychiatric harm. Ambiguous harm remains unclassified. | Counted only with a published T1/T2 case and reported separately from F; one event cannot be double-counted merely because several mechanisms apply. | CAP-7.03, CAP-7.04 |

The F/G interpretation is an HLD policy proposal because the requirements name the codes but do not define them; Jess must approve it. Outcome may progress from G to F through a new version without erasing the earlier state. Tier may rise or fall independently, and every change carries the evidence basis, method version and two signatures. (CAP-3.03, CAP-3.05)

```mermaid
stateDiagram-v2
    [*] --> RECEIVED: idempotent receipt
    RECEIVED --> QUARANTINED: cheap validation / upload scan
    QUARANTINED --> TRIAGE: accepted bytes and schema
    TRIAGE --> DUPLICATE_REVIEW: possible match
    DUPLICATE_REVIEW --> TRIAGE: distinct or linked
    TRIAGE --> WITHDRAWN: pre-publication withdrawal
    TRIAGE --> EVIDENCE_REVIEW: assigned editor
    EVIDENCE_REVIEW --> AWAITING_SUBMITTER: structured question
    AWAITING_SUBMITTER --> EVIDENCE_REVIEW: response
    EVIDENCE_REVIEW --> HOLD: embargo / seal / consent / locality
    HOLD --> EVIDENCE_REVIEW: authorized release
    EVIDENCE_REVIEW --> REJECTED: criteria not met
    EVIDENCE_REVIEW --> STAGED: tier and privacy gates pass
    STAGED --> SECOND_REVIEW: proposer WebAuthn-signs hash
    SECOND_REVIEW --> EVIDENCE_REVIEW: changes requested
    SECOND_REVIEW --> NOTICE: second eligible signer approves exact hash
    NOTICE --> STAGED: vendor response changes content
    NOTICE --> PUBLISHED: notice satisfied or signed exception
    PUBLISHED --> UPDATE_REVIEW: correction / new evidence / dispute
    UPDATE_REVIEW --> STAGED: new version, never overwrite
    PUBLISHED --> TOMBSTONE: retraction / privacy or legal removal
    TOMBSTONE --> UPDATE_REVIEW: new lawful evidence
```

Each transition is a D1 transaction that verifies current state, role, case assignment, holds, required evidence, row version and idempotency key, then appends `WorkflowTransition` and `EditorAction`. Queues trigger notifications and projections only after commit; consumers are idempotent because Queues are at-least-once. A queue message can never authorize publication: the publication Worker repeats all invariants synchronously. (CAP-2.05, CAP-2.08, CAP-3.02, CAP-3.04, CAP-5.04)

Four-eyes means two distinct active human principals, neither a service account, both eligible for the case and free of recorded conflict. The proposer creates a canonical staged JSON hash and signs a WebAuthn challenge binding action type, case ID, version ID, content hash, method version, expiry and a server nonce. The second reviewer sees the complete diff and evidence checklist and signs the same hash. Any byte or governed metadata change invalidates both approvals. Tier promotion, publication, unpublication, merge, retraction, downgrade, privacy/legal removal, revert, source replacement, translation of a material claim and policy change require Editor + SeniorEditor or two SeniorEditors; emergency takedown may hide risky current content immediately but requires a second signature within 24 hours and leaves a signed tombstone. EditorialDirector breaks assignment deadlocks but cannot self-approve. (CAP-3.04, CAP-3.05, CAP-4.07, CAP-4.08, CAP-5.01, CAP-9.01)

Vendor notice begins only after content is otherwise publishable. The default release time is at least 72 hours after a disclosure-safe notice. Embargo, seal, family consent and source-safety holds prevent the notice itself until release is lawful. An imminent-public-interest exception requires SeniorEditor plus EditorialDirector signatures, a specific public exception marker, and notice as soon as safe. This resolves the notice-versus-confidentiality conflict without creating a vendor veto. (CAP-2.08, CAP-4.04)

## Roles & permissions

Cloudflare Access authenticates editors and enrolled officials; application RBAC and resource attributes authorize every operation. Access identity alone grants no D1 or R2 access. Scope includes case assignment, jurisdiction, confidentiality class, conflict/recusal, assurance level and session freshness. Public stable staff IDs provide accountability; private identity details are retained for WHO custody and disclosed only under governance policy. (CAP-2.10, CAP-4.08, CAP-5.03, CAP-5.05, CAP-10.04)

| Capability | Editor | SeniorEditor | EditorialDirector | Submitter | Vendor | Public |
|---|---|---|---|---|---|---|
| Read published cases, sources, history, releases | Yes | Yes | Yes | Yes | Yes | Yes | 
| Create/track own submission; see own private evidence | May assist assigned | May assist assigned | Oversight | Own only | Own official dispute/notice only | Anonymous submit; track only with scoped credential |
| Read confidential case/evidence | Assigned and clearance-matched | Assigned/appeal and clearance-matched | Break-glass with reason; not automatic | Own supplied objects only | Own supplied objects/notice package only | No |
| Triage, link duplicate, request evidence, draft | Assigned | Yes | Yes | Respond only | Respond only | No |
| Propose version/tier/correction | Yes, sign | Yes, sign | Yes, sign | Suggest through intake | Suggest through dispute | Suggest correction |
| Second-approve T2/routine version | If eligible, distinct from proposer | Yes | Yes | No | No | No |
| Second-approve T1, downgrade, merge, removal, revert | No unless policy designates senior eligibility | Yes | Yes | No | No | No |
| Publish exact doubly approved version | Transactional system action | Transactional system action | Transactional system action | No | No | No |
| Decide first dispute | Assigned if non-material | Yes | Yes | No | No | No |
| Hear appeal | Only if uninvolved panel member | Yes, uninvolved | Assigns/quorate, cannot review own decision | Appeal own decision | Appeal own decision | File correction |
| Manage methods, vocabularies, roles and keys | Propose | Propose/approve content | Propose; second approver still required | No | No | Read versions |
| Break-glass/private export | No | Two-person approved | Initiates with distinct SeniorEditor approval | No | No | No |

The submitter role covers family, journalist and general submitters with different assurance and consent attributes. “Vendor” requires verified organization authority and can access only that vendor’s notice/dispute workspace; it cannot see embargoed case existence or other vendors. Official submitters use the Submitter role plus `official_capacity` and jurisdiction attributes, not an editorial role. Service actors may render, notify and checkpoint, but cannot satisfy a human approval slot. (CAP-2.03, CAP-2.07, CAP-2.10, CAP-4.02, CAP-5.01)

## Cloudflare architecture

The architecture separates public delivery, transactional workflow, evidence custody and independent checkpoints. That separation is the principal availability and insider-risk control: no one database query dynamically constructs the canonical public history, and no public host can address a private evidence key. (CAP-5.01, CAP-5.03, CAP-5.04, CAP-5.06)

```mermaid
flowchart TB
  U[Public browser / research agent] --> CF[Cloudflare DNS, TLS, DDoS, WAF]
  S[Submitter] --> CF
  E[Editor / official / vendor] --> A[Cloudflare Access + phishing-resistant IdP/MFA]
  A --> CF
  CF --> P[Pages: static UI, methods, schemas]
  CF --> W[API Worker: REST, workflow, authorization]
  W --> T[Turnstile Siteverify for anonymous writes]
  W --> D[(D1: relational workflow and audit rows)]
  W --> K[(KV: expendable quotas, nonce/session deny state)]
  W --> R[(R2 private: originals, confidential payloads)]
  W --> Q[[Queues: projection, scan, notification, release jobs]]
  Q --> C[Consumer Workers]
  C --> D
  C --> R
  C --> RP[(R2 public/locked: versions, exports, manifests)]
  RP --> CF
  C --> G[GitHub public signed history]
  P --> AN[Cloudflare Web Analytics]
  W --> OA[Cloudflare operational analytics/logs]

  subgraph Trust boundaries
    P
    RP
    W
    D
    K
    R
    Q
  end
```

Public traffic first receives Cloudflare TLS, DDoS and WAF treatment. Static assets, signed snapshots and immutable case versions normally terminate at edge cache without a D1 read. Dynamic filtered queries and authenticated writes enter a dedicated Worker. The Worker uses binding credentials rather than public database/object endpoints. Separate Cloudflare accounts are preferred for production and recovery, with least-privilege API tokens and distinct WHO administrators; preview and development contain synthetic data only. GitHub has no production secret capable of reading private R2 or D1. (CAP-5.03, CAP-5.04, CAP-5.06)

Current limits and prices below are design baselines as checked on 13 July 2026; CI records a quarterly pricing review. Cloudflare’s official [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/), [Pages limits](https://developers.cloudflare.com/pages/platform/limits/), [D1 pricing](https://developers.cloudflare.com/d1/platform/pricing/), [R2 pricing](https://developers.cloudflare.com/r2/pricing/), [KV pricing](https://developers.cloudflare.com/kv/platform/pricing/), and [Queues pricing](https://developers.cloudflare.com/queues/platform/pricing/) are the controlling sources. (CAP-5.04, CAP-10.03)

| Component | Role and reason chosen | Free-tier budget / paid threshold | Failure mode and designed fallback | Trace |
|---|---|---|---|---|
| **Pages** | Publishes the static shell, localized UI catalogs, methods, OpenAPI/schema files, accessibility pages and generated current case HTML. Static delivery minimizes dynamic attack surface and cost. | Static asset requests and bandwidth are free/unlimited; 500 builds/month, one concurrent build, 20,000 files/site, 25 MiB/file. Pages Functions would share Workers’ 100,000 requests/day; this design does not use them, avoiding overlapping runtimes. | Build failure leaves the last atomic deployment live. If the Git integration fails, the reviewed pipeline uses `wrangler pages deploy`. Case count can exceed the file limit by serving immutable case/version JSON/HTML from cached R2 through the Worker; shard static indexes, not case IDs. | CAP-1.01, CAP-5.04, CAP-9.02 |
| **Workers** | One routed TypeScript API Worker plus queue/cron consumers implements REST, validation, RBAC, signing verification, projections and bounded analytics. Central middleware makes privacy and error behavior consistent. | Free: 100,000 requests/day shared with Pages Functions and 10 ms CPU/invocation. Workers Paid is **$5 USD/account/month minimum**, including 10 million requests/month then **$0.30/million**, and 30 million CPU-ms then **$0.02/million CPU-ms**. | Dynamic endpoints return typed `503` with `Retry-After`; cached static cases, feeds and last signed export remain readable. Writes that cannot commit fail closed and can be retried with the same idempotency key. CPU-heavy work is chunked through Queues. | CAP-5.04, CAP-6.05, CAP-10.03 |
| **D1** | Holds cases, relations, workflow, access metadata, approvals, disputes and append-only audit rows. SQL constraints, transactions, foreign keys and indexes make four-eyes and count eligibility enforceable rather than conventional. | Free: 5 million rows read/day, 100,000 rows written/day, 5 GB total. Paid is included in the **$5/month Workers Paid** minimum: 25 billion reads/month then **$0.001/million**, 50 million writes then **$1/million**, first 5 GB then **$0.75/GB-month**. Time Travel is 7 days free, 30 days paid. | On quota/outage, public static artifacts remain live; dynamic reads degrade to last signed snapshot; writes return retryable `503`, never an unlogged local success. Nightly logical exports and audit checkpoints go to locked R2. Recovery restores into a clean D1 database and verifies every chain/release before DNS binding changes. | CAP-3.04, CAP-5.01, CAP-5.06 |
| **R2** | Four non-public buckets isolate quarantine originals, approved confidential evidence, public derivatives/releases, and recovery copies. Object keys contain no PII. R2 supports large objects, checksums, no egress fee, retention locks and byte-for-byte snapshots. | Standard free: 10 GB-month, 1 million Class A writes/lists and 10 million Class B reads/month; free egress. Above that: **$0.015/GB-month**, **$4.50/million Class A**, **$0.36/million Class B**. Infrequent Access is not used because its free tier is zero and it costs **$0.01/GB-month**, **$9/million A**, **$0.90/million B**, plus **$0.01/GB retrieval**. | Direct private bucket access is disabled. If R2 reads fail, citations/metadata remain available and attachments show a non-misleading unavailable state. Dual account recovery copies plus GitHub manifests detect loss. Writes use new content-addressed keys; object lock prevents operator deletion during retention. | CAP-1.04, CAP-2.04, CAP-5.03, CAP-7.02 |
| **Workers KV** | Stores expendable short-lived rate buckets, Turnstile replay markers, capability-session revocations, cache-generation pointers and non-authoritative feature flags. It is selected for edge-local low-latency reads, never as canonical state because propagation is eventually consistent. | Free: 100,000 reads, 1,000 writes, 1,000 deletes and 1,000 lists/day; 1 GB stored, 25 MiB/value, one write/second to the same key. Workers Paid (within **$5/month** minimum): 10 million reads then **$0.50/million**; 1 million writes/deletes/lists then **$5/million**; 1 GB then **$0.50/GB-month**. | A KV failure fails closed for writes and sensitive reads, but public static GETs continue. Durable submission idempotency and final authorization remain in D1, so stale KV can at most impose an extra challenge/denial, never grant or duplicate a mutation. Sharded keys avoid hot-key write limits. | CAP-5.04, CAP-5.05, CAP-6.05 |
| **Queues** | Buffers post-commit attachment scanning, derivative generation, translation tasks, alerts, static projection and snapshot creation. At-least-once delivery is handled with D1 unique job/idempotency IDs. | Free: 10,000 operations/day with fixed 24-hour retention; a normal message costs roughly three operations (write/read/delete). Paid, after the **$5/month Workers Paid** minimum, includes 1 million operations/month then **$0.40/million** and supports up to 14-day retention. | Consumers retry with exponential delay and a dead-letter queue; D1 shows stalled jobs. If backlog approaches free retention, intake accepts metadata but pauses uploads and nonessential alerts; a cron reconciler scans committed outbox rows to recreate lost/expired messages. Publication is synchronous and never depends solely on a queue event. | CAP-2.05, CAP-5.04, CAP-10.01 |
| **Turnstile** | Protects anonymous submission, correction, dispute and upload-initiation POSTs. The Worker validates token, hostname, action and cdata server-side and records a replay hash. It is never placed on public GET/API/export routes. | Free: up to 20 widgets/account, 10 hostnames/widget, unlimited challenges and seven-day analytics. Enterprise Turnstile is **$custom quote (no public dollar list price)** and is not required. | If verification is unavailable, existing draft saves continue for recently verified scoped sessions, but new anonymous writes receive accessible retry guidance and a human contact route; public reads remain unaffected. A challenge is abuse evidence, not identity or truth. | CAP-2.01, CAP-3.02, CAP-6.05 |
| **Access / Zero Trust** | Gates `/editor/*`, `/official/*`, `/vendor/*` and private APIs, enforcing phishing-resistant MFA and WHO group membership. The app verifies the signed Access JWT audience/issuer on every request, then performs its own RBAC. | Zero Trust Free supports up to 50 users at **$0**. Pay-as-you-go is **$7/user/month** with no user limit; use once active privileged users exceed 50 or the required support/SLA exceeds free. | If Access is unavailable, no privileged mutation or private evidence read occurs. Public site/API continue. Short-lived app sessions cannot outlive Access identity; revocation and device anomaly force fresh authentication. Two break-glass accounts require hardware keys and dual custody. | CAP-2.10, CAP-5.05 |
| **WAF / DDoS** | Cloudflare’s network DDoS controls, Free Managed Ruleset, five Free custom rules and one Free rate rule block common injection, invalid methods and abusive write patterns before Worker cost. Worker limits add identity/behavior context that Free WAF lacks. | Free plan: **$0**, five custom rules, one 10-second rate-limit rule, Free Managed Ruleset and sampled Security Events. Pro is **$25/month monthly or $20/month billed annually** and adds broader managed rules, 20 custom rules, two rate rules and controllable bot features; Business is **$250/month monthly or $200/month billed annually**. Enterprise and advanced bot/upload products are not assumed. | False positives are tested in preview and can be rolled back through reviewed configuration. `/api/v1`, feeds and exports never receive a CAPTCHA; Worker typed throttles protect them. During WAF control-plane trouble, static cache remains available and all write validation still executes in Worker. | CAP-5.04, CAP-5.07, CAP-6.05 |
| **Analytics** | Cloudflare Web Analytics measures aggregate public page performance without user-level profiles; Workers/D1/R2 dashboards measure quota and failures. Application counters store coarse workflow/SLO metrics without raw public IP or sensitive case content. | Web Analytics is **$0** and supports proxied sites without a site-count limit; Free has no custom injection rules. Product dashboard retention varies by plan, so audit/security evidence is written as minimized events to D1/R2, not assumed from dashboards. | Analytics loss never affects serving or correctness. SLO calculations mark missing intervals rather than treating them as zero. No third-party beacon fallback is permitted. | CAP-5.02, CAP-10.02, CAP-10.03 |
| **Snippets** | Not enabled in the baseline. Their potential role—small header/redirect logic—is already handled by static `_headers`, `_redirects`, WAF rules and the Worker, preserving versioned code in GitHub. This explicitly accounts for the requested component without buying a second execution plane. | Snippets are unavailable on Free (**$0 gives 0 snippets**). They require at least Pro at **$25/month monthly or $20/month billed annually**, which provides 25 snippets; therefore baseline cost is **$0**. | There is no Snippets dependency or failure mode. If later approved, a snippet must be reproducibly deployed, contain no secrets, stay under its 5 ms/2 MB/32 KB limits and fail to equivalent Worker/static behavior. | CAP-5.06, CAP-10.03 |

Static public artifacts have cache keys that include locale, API/schema version and content hash, never authorization. Current-case URLs use short revalidation plus `stale-if-error`; immutable versions/releases use `public, max-age=31536000, immutable`. Private responses use `Cache-Control: no-store`, `Vary` is explicit, and a Worker never caches an Access-authorized response at a public edge. (CAP-5.02, CAP-5.03, CAP-6.04)

Strict national data localization cannot be inferred from a D1 location hint. If a jurisdiction requires contractual in-country processing that baseline Cloudflare cannot supply, intake blocks restricted bytes and allows a hash/official attestation only. Cloudflare Enterprise Data Localization is a possible later Cloudflare-only dependency at **$custom quote; Cloudflare publishes no list-dollar price** and therefore cannot pass procurement until WHO records the actual quote. It is not part of the v0.1 cost baseline. (CAP-7.06)

## API surface

REST `/api/v1` is normative. GraphQL is deferred because arbitrary field graphs complicate authorization, row-read budgets, cache keys and query-cost enforcement; it may later expose persisted, allowlisted queries only. Public GET routes require no account, cookie, JavaScript or Turnstile. They return content negotiation, strong ETag, `Last-Modified`, `Vary`, request ID, API/contract version, rate-limit headers, canonical link and licence; conditional reads return `304`. Errors use `application/problem+json`, stable codes and no stack, SQL, object key, account existence or PII. (CAP-5.04, CAP-6.03, CAP-6.04, CAP-6.05)

### Endpoint catalogue

| Method and path | Contract | Trace |
|---|---|---|
| `GET /.well-known/the-record.json` | Stable discovery of canonical site/API, OpenAPI, JSON Schemas, current/full export, feeds, health, licence and key set; includes deprecations/moves. | CAP-6.01 |
| `GET /api/openapi-3.1.json`, `GET /schemas/{name}/{semver}.json` | Immutable OpenAPI 3.1 and self-identifying JSON Schema; stable schema route points to current compatible version. | CAP-6.01, CAP-6.03 |
| `GET /api/v1/cases` | Cursor-paginated, bounded filters for tier, outcome, date, jurisdictions, vendor/product/model, court/docket, legal theory, mechanism, code and status. Returns applied normalized query and release/version context. | CAP-1.03, CAP-6.06 |
| `GET /api/v1/cases/{case_id}` | Current public projection and links to sources, history, disputes, citations and canonical HTML. | CAP-1.02, CAP-8.01 |
| `GET /api/v1/cases/{case_id}/versions/{n}` | Exact immutable version; tombstone semantics preserved. | CAP-3.05, CAP-8.01 |
| `GET /api/v1/cases/{case_id}/diff?from={n}&to={n}` | JSON Patch plus semantic field changes and immutable version links. | CAP-6.04 |
| `GET /api/v1/sources/{source_id}` | Public source/provenance/rights/hash metadata; signed short-lived public derivative link only if redistribution is lawful. | CAP-1.04 |
| `GET /api/v1/releases`, `GET /api/v1/releases/{id}/manifest` | Release catalogue, manifest, checksums, signatures, method/schema/vocabulary IDs and correction links. | CAP-7.02, CAP-7.03 |
| `GET /data/current/cases.jsonl.gz`, `.csv`, `GET /data/releases/{id}/...` | Complete current streaming export and immutable release files. Full export is a prebuilt R2 object, never a live unbounded D1 query. | CAP-6.02, CAP-7.01 |
| `GET /feed/atom.xml`, `/feed/rss.xml`, `/api/v1/changes?cursor=` | Stable event IDs, timestamps, action, case/version/diff links; archived cursors cover at least 24 months, while releases provide full reconciliation. | CAP-6.04 |
| `GET /health` | Minimal `{status, time, api_version, current_release}`; does not reveal storage or topology. A separate Access-gated readiness path is operational only. | CAP-10.03 |
| `POST /api/v1/submissions` | JSON metadata intake after Turnstile; returns `202`, stable `submission_id`, scoped tracking token and idempotent receipt. | CAP-2.01, CAP-3.02 |
| `POST /api/v1/submissions/{id}/uploads` | Creates a size/type/count-scoped one-use upload session; Worker streams bytes to quarantine R2 and hashes them. | CAP-2.04, CAP-5.04 |
| `GET /api/v1/submissions/{id}` | Authorized submitter status, requests and public preview; opaque capability or authenticated principal must match. | CAP-2.05 |
| `POST /api/v1/cases/{id}/corrections`, `/disputes` | Specific challenged fields, proposed remedy and evidence; Turnstile for public, Access/vendor assurance for counsel. | CAP-4.01, CAP-4.02 |
| `POST /api/v1/official/submissions`, `/batches` | Access-protected, authority-scoped official channel; an eventual mTLS profile uses Access service credentials plus document signatures. | CAP-2.09, CAP-2.10 |

Every list has deterministic ordering (`updated_at`, then stable ID), a signed opaque cursor with maximum page size 100, a documented null meaning and a maximum of 20 filter values per facet. Expensive analytics are precomputed per release. Rate policy starts at 60 dynamic list requests/minute and 600/hour per privacy-preserving network bucket; 300/minute with a free research API token; prebuilt exports are cache-limited by Cloudflare rather than per-record API calls. All responses advertise `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`; excess is JSON `429` with accurate `Retry-After`. Network is one signal, never identity, and raw IP is neither persisted with case records nor exposed. (CAP-5.02, CAP-5.04, CAP-6.05)

`robots.txt` and `/ai.txt` explicitly permit discovery, schemas, API, feeds and snapshots under the published rate policy; they disallow private/editor routes and high-rate HTML scraping. Bot Fight Mode is not applied to public API/export paths because it cannot be safely exempted on Free; WAF and Worker quotas protect those paths without human challenges. API clients reconcile from the weekly release then use changes/cursors, so legitimate complete retrieval is cheaper than scraping. (CAP-5.04, CAP-5.08, CAP-6.05)

### OpenAPI 3.1 outline

```yaml
openapi: 3.1.0
info:
  title: The Record Public and Intake API
  version: 1.0.0
  license: {name: CC BY 4.0, identifier: CC-BY-4.0}
servers: [{url: https://record.who.int/api/v1}]
security: []
paths:
  /cases:
    get:
      operationId: listCases
      parameters: [{$ref: '#/components/parameters/Cursor'}, {$ref: '#/components/parameters/IfNoneMatch'}]
      responses:
        '200': {$ref: '#/components/responses/CasePage'}
        '304': {$ref: '#/components/responses/NotModified'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /cases/{case_id}:
    get:
      operationId: getCase
      responses: {'200': {$ref: '#/components/responses/Case'}, '404': {$ref: '#/components/responses/Problem'}}
  /submissions:
    post:
      operationId: createSubmission
      security: [{turnstileToken: []}]
      parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
      responses: {'202': {$ref: '#/components/responses/Receipt'}, '422': {$ref: '#/components/responses/Problem'}}
components:
  securitySchemes:
    turnstileToken: {type: apiKey, in: header, name: CF-Turnstile-Token}
    accessJwt: {type: openIdConnect, openIdConnectUrl: https://record.who.int/.well-known/access-configuration}
  schemas:
    Case: {$ref: 'https://record.who.int/schemas/case/1.0.0.json'}
    Problem: {type: object, required: [type, title, status, code, request_id]}
```

The complete checked-in contract describes every parameter, enum, example, error, privacy omission, pagination and security scheme. CI validates it, performs compatibility checks, and generates no server logic from unreviewed remote schemas. Deprecation uses `Deprecation`, `Sunset` and `Link: rel="successor-version"`; a breaking API major remains supported for at least 12 months and immutable schemas/releases indefinitely. (CAP-6.01, CAP-6.03)

Exports use CC-BY 4.0 for registry-authored data, cite WHO/The Record and release ID, and carry row/object-level `rights` fields for third-party attachments. CSV prefixes spreadsheet-formula-leading text with a safe apostrophe in a separately documented display column while canonical identifiers remain quoted strings; UTF-8, ISO dates and null conventions are explicit. REDCap/DSpace transfer packages are versioned mappings, not live integrations with non-Cloudflare services. (CAP-7.01, CAP-7.06, CAP-10.04)

## Immutability & audit log

“Immutable” means alteration is prevented where practical and independently detectable everywhere else; it does not mean sensitive allegations must remain publicly discoverable. D1 permissions and application transactions prevent ordinary mutation, R2 retention locks protect checkpoint objects, WebAuthn signatures bind two humans to exact actions, a dual hash chain exposes row removal/reordering, signed release manifests authenticate public bytes, and GitHub supplies an administratively separate public history. A lawful removal creates a new signed state and minimally redacts public projections; it cannot rewrite a previously released object, although access to unlawfully identifying bytes may be disabled and the manifest records the legal basis. (CAP-2.06, CAP-3.05, CAP-4.07, CAP-5.01)

### Action signing and chain

For each governed action, the Worker applies RFC 8785 JSON Canonicalization to `{action_id, object_id, action_type, before_hash, after_hash, policy_version, reason_code, occurred_at, actor_public_id, previous_object_hash, previous_global_hash, approval_group_id}` and computes `action_hash = SHA-256(canonical_bytes)`. The editor’s browser invokes WebAuthn with `challenge = SHA-256("TheRecordAction-v1" || action_hash || server_nonce || expires_at)`. The non-exportable private key stays in the editor’s hardware security key or platform authenticator; D1 stores credential public key, credential ID, sign counter, attestation assurance, signature, authenticator data and client data. The Worker verifies origin, RP ID, user presence, user verification, nonce, expiry, counter and active credential. A second distinct eligible editor repeats this for the identical content and approval-set hash. (CAP-3.04, CAP-5.05)

The D1 transaction verifies both signatures, current head hashes and separation of duty, inserts the action/version and advances heads with compare-and-swap. It cannot update/delete `EditorAction` through application SQL. A Cloudflare Worker secret holds the system Ed25519 release-checkpoint private key; only a small checkpoint Worker binding can use it, not the public API Worker. Its public key and rotations are published at `/.well-known/the-record-keys.json`, WHO governance pages and GitHub. Because a Cloudflare account administrator could replace code or secrets, the system key alone is not sufficient: the release’s two named human WebAuthn approvals and GitHub’s separate signed history remain required. System-key rotation uses an old-key-signed transition plus two director/senior WebAuthn signatures; emergency loss uses a pre-published offline WHO recovery-key ceremony. (CAP-5.01, CAP-5.06, CAP-5.08)

Every hour, the checkpoint Worker writes a canonical manifest with global head, per-case heads, row count, latest release, previous checkpoint hash and D1 Time Travel bookmark to a new R2 object with retention lock, then Ed25519-signs its hash. Every week, a release job freezes canonical public JSONL/CSV, source manifest, data dictionary, methods, schemas, count reconstruction SQL/spec, changelog and correction overlay; hashes every file; builds a Merkle root; and writes a signed release manifest to public locked R2. The release is not advertised until a second Worker re-reads every object, verifies hashes/counts/signatures and records a successful audit action. (CAP-5.01, CAP-7.02, CAP-7.03)

GitHub receives the small manifests, public keys, audit checkpoint heads and release metadata—not confidential evidence—through a least-privilege release branch. A protected pull request requires a verified signed commit and independent reviewer before merge. Git history therefore provides an externally visible sequence controlled separately from the Cloudflare data plane. GitHub is not the live database, does not receive secrets and cannot publish a case. (CAP-5.06, CAP-7.02)

### Public verification

A verifier downloads a release manifest, resolves the declared Ed25519 key valid at its timestamp, verifies the detached signature, hashes each desired artifact, checks its Merkle proof/root, checks the previous-release hash, and optionally compares the checkpoint head committed in GitHub. `/verify` performs the same work entirely in the browser with pinned first-party code, and `tools/verify-release.ts` in GitHub is the reproducible command-line verifier. A version citation includes case ID, version, release ID, artifact SHA-256 and signature key ID. Later corrections are separate signed overlays linked from old releases; old bytes never masquerade as current. (CAP-6.02, CAP-7.02, CAP-8.01, CAP-8.02)

Three immutability options were assessed. **R2 alone** is operationally simple but shares the Cloudflare administrative failure domain. **Git-backed signed history plus R2** keeps bulky content in locked R2 while publishing small checkpoints in GitHub and is the recommendation. **IPFS pinning plus Git/R2** adds distribution but requires a reliable non-Cloudflare pinning operator, has deletion/privacy conflicts, and violates the runtime boundary; it is rejected for v0.1. Signed downloadable R2 snapshots and GitHub manifests provide the permitted alternate distribution path without pretending a domain or vendor can never fail. (CAP-5.06, CAP-5.08, CAP-7.02)

## Security posture

The threat model assumes anonymous trolls, coordinated influence operators, hostile submitters, abusive scrapers, litigants, compromised editor accounts, malicious insiders, dependency compromise, ransomware, prompt injection and domain/DNS takeover. It also assumes Cloudflare operational failure. “Hack-proof” is implemented as fail-closed mutation, least privilege, dual human authorization, independent evidence gates, append-only/tamper-evident history, minimized sensitive data and recoverable signed public state; no design claims absolute invulnerability. (CAP-5.01 through CAP-5.08)

### Persona 09 attack-to-control mapping

| Attack story and named attack | Named mitigation and verification | Trace |
|---|---|---|
| **US-09-01 — silent insider edit/delete** | **Dual-Signed Exact-Version Gate:** D1 state constraints, proposer/approver WebAuthn signatures over one content hash, append-only dual hash chain, locked hourly R2 checkpoints, signed GitHub heads, and alert on missing/reordered actions. Revert creates a new approved version. Quarterly exercise attempts DB/history deletion and must be detected from an independent checkpoint. | CAP-3.04, CAP-3.05, CAP-5.01 |
| **US-09-02 — fake cases, T3→T1 inflation, fabricated feed observations** | **Canonical Count Gate:** tier-specific required-source constraints, independent reviewer, `published_version` eligibility view, and feeds/counts generated only from doubly approved canonical versions. T3 never enters public surfaces. Release reconciliation proves every count item resolves to a signed case/version. | CAP-3.02, CAP-3.03, CAP-5.01 |
| **US-09-03 — mass false submissions and family impersonation** | **Assured Intake and Duplicate Shield:** Turnstile and layered quotas, idempotency keys, content/source/entity/velocity similarity for human triage, private relationship verification, and no priority/count effect from repetition. Red-team batches test that variants yield one review cluster and no public artifact. | CAP-2.03, CAP-3.02, CAP-5.04 |
| **US-09-04 — submitter/source deanonymization by timing or metadata** | **Correlation-Minimized Publication:** public attribution is an explicit separately revocable consent; intake identity and attribution are separate objects; EXIF/filename/IP/receipt/queue/reviewer timing is stripped; public timestamps are coarsened/delayed; small cells suppressed. Privacy tests compare UI/API/export/feed for cross-surface leakage. | CAP-2.07, CAP-5.02 |
| **US-09-05 — attachment/note exfiltration through guessed IDs or compromised editor** | **Private-Object Broker:** opaque keys, private buckets, Worker-mediated case-assignment authorization, no existence oracle, short-lived one-use downloads, `no-store`, abnormal egress stop, private-note schema boundary and break-glass dual approval. Canary-object access tests alert without containing real PII. | CAP-3.06, CAP-5.03 |
| **US-09-06 — distributed scraping/resource exhaustion** | **Static-First Sanctioned Bulk Path:** immutable cached cases/exports, bounded/indexed queries, signed cursors, KV/network/token behavior quotas, Cloudflare DDoS/WAF, typed 429s and no CAPTCHA on compliant API reads. Load tests prove full export reconciliation does not consume D1 rows. | CAP-5.04, CAP-6.02, CAP-6.05 |
| **US-09-07 — write/upload flood and retry duplication** | **Cheap-First Intake Valve:** content length/type/count limits before R2/D1 work, Turnstile, one-use upload grants, streaming hash, per-principal/network/storage quotas, D1 idempotency uniqueness, queue backpressure and separate editorial routes. Chaos retries must produce one receipt/object. | CAP-2.04, CAP-5.04 |
| **US-09-08 — credential stuffing/session theft** | **Phishing-Resistant Privilege:** Access, hardware/WebAuthn MFA, no password-only editors, audience-bound short sessions, Secure/HttpOnly/SameSite cookies, CSRF/origin checks, anomaly reauthentication, device/session revocation and two-person break-glass. Stolen-cookie exercise cannot sign a mutation without the WebAuthn key. | CAP-5.05 |
| **US-09-09 — malicious dependency/ransomware/backups destroyed** | **Reproducible Least-Privilege Recovery Plane:** locked dependencies and hashes, reviewed lockfile changes, pinned actions, SBOM, minimal Worker bindings, separate production/recovery accounts, D1 Time Travel, locked R2 logical exports, Git checkpoints and clean restore drills. Recovery must meet RPO/RTO and chain verification. | CAP-5.06 |
| **US-09-10 — stored XSS/active PDF payload** | **Inert Evidence Rendering:** contextual escaping, no raw HTML, URL allowlist/normalization, strict CSP, quarantine, structural file scan, no direct original rendering, safe derivatives on isolated attachment hostname and forced download. A malicious-file corpus is tested each release. | CAP-5.07 |
| **US-09-11 — prompt injection/model-induced mutation** | **No Model Authority:** submitted text is delimited hostile evidence; v0.1 uses no model in canonical workflow; any future model has no secrets/write binding, schema-constrained output, logged inputs/outputs and mandatory independent evidence/human approval. Prompt-injection tests cannot call tools or alter D1. | CAP-5.07 |
| **US-09-12 — censorship, counterfeit mirror, dangling DNS/domain takeover** | **Published Trust Root and Domain Lifecycle:** WHO-controlled DNS/account with hardware MFA, DNSSEC, CAA, scoped deploy tokens, certificate monitoring, inventory/retirement checklist, no dangling records, signed R2 snapshots and GitHub checkpoints. A mirror is authentic only when hashes/signatures validate against the WHO-published key. | CAP-5.08 |

### Edge and application controls

Free WAF custom rules are consolidated into five reviewable policies: (1) block methods other than `GET/HEAD/OPTIONS` except exact POST/PUT routes; (2) block malformed paths, traversal, unsafe encodings and unexpected content types; (3) managed-challenge anonymous write routes on clear threat signals while exempting Access service traffic; (4) block oversized declared bodies and direct private/admin host access; (5) block known emergency indicators in the single custom IP list. The one Free rate rule targets `/api/v1/submissions*` and upload initialization. Free Managed Ruleset covers common injection. Worker controls remain authoritative because Free WAF cannot express identity, JSON schema, cumulative storage or cross-network behavior. (CAP-3.02, CAP-5.04, CAP-5.07)

Turnstile appears at the final anonymous create/correction/dispute step, not on trauma-intake reading, draft navigation or public machine GETs. The server checks action (`submission`, `correction`, `dispute`), hostname and token freshness before allocating an object. Accessible error copy provides retry and human assistance. A pass does not relax schema, quota, evidence or editorial review. (CAP-2.01, CAP-3.02, CAP-6.05, CAP-9.02)

Access policies require WHO group plus phishing-resistant MFA for editors, verified organization group plus MFA for officials/vendors, and a service token only for narrowly scoped institutional APIs. The Worker verifies Access JWT `iss`, `aud`, `exp`, signature and principal, maps it to an active D1 role, and enforces resource assignment/confidentiality. Sessions are random 256-bit opaque IDs whose SHA-256 hashes are stored server-side with five-minute idle/30-minute privileged maximum, rotated after authentication and privilege change. Cookies use `__Host-`, `Secure`, `HttpOnly`, `SameSite=Strict`, path `/`; unsafe requests require CSRF token, exact Origin/Fetch-Metadata and fresh WebAuthn for governed actions. No bearer credential is stored in local storage. (CAP-5.03, CAP-5.05)

Security headers are `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`, `Content-Security-Policy: default-src 'none'; script-src 'self' <build-hashes>; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src https://challenges.cloudflare.com only on intake; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; object-src 'none'; require-trusted-types-for 'script'` where supported, plus `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, restrictive `Permissions-Policy`, COOP/CORP appropriate to the attachment boundary and no MIME sniffing. First-party JS/CSS is content-hashed; any required static external asset is vendored, pinned and covered by SRI. There are no third-party analytics scripts. (CAP-5.07, CAP-9.02)

### Attachment safety and privacy

Upload grants declare maximum 25 MiB per public form object (larger official files require an authenticated chunked route), allowed type and count. The Worker streams to quarantine R2 while calculating SHA-256, checks extension/MIME/magic consistency, rejects polyglots and archives by default, and invokes a queue scan. The Cloudflare Worker scanner uses pinned open-source parsers/WASM to impose decompression/page/object limits; for PDF it rejects encryption, JavaScript, launch/open actions, embedded files, rich media, XFA, external references and malformed cross-reference structures; images are decoded and re-encoded with metadata removed. Office documents are rejected in v0.1 and must be converted by the submitter to a supported inert form. Scan rule/version/result is signed into provenance. (CAP-2.04, CAP-5.07)

An accepted original remains private and is never opened inline. A separately hashed redacted derivative undergoes visual human comparison, privacy review and two-person publication approval. Public derivatives are served from `evidence.record.who.int` with sandbox-oriented CSP, `Content-Disposition: attachment` unless the safe viewer supports the format, `nosniff`, no credentials and no referrer. Failure or uncertainty quarantines the object; metadata/citation can proceed only if policy allows. Hash mismatch immediately blocks access/publication and creates a security action. This scan detects dangerous structure, not all malware; isolation and no-execution are therefore mandatory even after “pass.” (CAP-1.04, CAP-2.04, CAP-5.02, CAP-5.03, CAP-5.07)

Logs use request IDs and coarse security categories, not names, email, evidence text, raw tokens, private R2 keys or form bodies. Public errors never distinguish “private object exists” from absent, and authentication errors do not reveal account enrollment. IP-derived rate keys are daily HMACs with a rotating secret and expire in KV; they are not joined to submissions. Operational events with a lawful need use restricted D1/R2 retention and documented access. Small analytics cells default to suppression below 5, with a policy-adjustable threshold and complementary suppression to prevent subtraction attacks. (CAP-1.05, CAP-5.02, CAP-5.03)

### Backup, ransom and incident recovery

Recovery objectives are proposed as RPO one hour for D1/audit, zero loss for already published locked versions, and RTO four hours for public signed snapshot service / 24 hours for authenticated editorial service. D1 Time Travel covers 7 days Free or 30 days on the **$5/month Workers Paid** plan. Hourly heads, nightly encrypted logical D1 exports, new evidence manifests and weekly full public releases are written to retention-locked recovery R2 in a separate Cloudflare account whose credentials production Workers cannot delete. Private evidence backup is encrypted with a recovery public key; the offline private recovery shares remain under WHO dual custody. GitHub stores no private bytes. (CAP-5.06)

For ransom/corruption, responders freeze writes and Access sessions, preserve the compromised environment, publish a signed status notice, create clean Worker/D1 resources from a reviewed signed commit, restore the last clean logical export, replay valid append-only events/object manifests to the chosen RPO, verify dual chains, signatures, source hashes and release counts, rotate all Cloudflare/API/system keys, require editors to re-enrol compromised credentials, and only then switch the WHO hostname. Old environment remains isolated for forensics. A quarterly restore into an empty account must reconstruct a sample legal snapshot byte-for-byte and document measured RPO/RTO; failed drills block ordinary releases. (CAP-5.01, CAP-5.06, CAP-8.02, CAP-10.03)
