open.csr roadmap research · 2026-07-27 · Part 2

The framework: sign the diff, not the anecdote

A medical writer reading the CSR proposes a display change in plain language. An agent classifies it, documents the requirement, edits the spec, and the pipeline regenerates a draft version of the display — inserted into a preview of the document. The biostatistician reviews the computed ARD diff and signs. The writer reviews every sentence whose number moved and signs. Merge is the transaction. Everything else in this part is the discipline that makes that loop trustworthy.

The one-paragraph version

The change request becomes a versioned artifact the moment it is made. The review it triggers is sized by the computed ARD diff, not the request text — a presentation change is proven cosmetic by an unchanged ARD; an analysis change puts the value-level diff in front of the statistician. The result is a draft iteration (or a fork) of the display, rendered into the document on a PR preview, watermarked, while the approved version stays live. Each role signs its own competency: the biostatistician signs the ARD diff, the medical writer signs the prose impact. The merge flips the draft to current, writes the ledger, and closes the request — one transaction, every artifact reproducible from the commit. This is exactly the loop no product in Part 1 owns.

0 · The loop, end to end

#StepActorArtifact produced
1Propose. Reading the document, the writer selects a display (or a bound number in a sentence) and states the change in plain language: “split this AE table by baseline severity.” The composer captures context automatically: display slug, current iteration, E3 section, binding address.Medical writerCR-NNN record (proposed)
2Assess. The agent interprets the request, classifies its tier (§2), writes testable acceptance criteria, states its assumptions, and makes the SAP-consistency call — pre-specified or post hoc, in ARS AnalysisReasonEnum vocabulary. An out-of-scope request (needs new source data) is escalated honestly, not attempted.AgentAssessment appended to the CR (assessed)
3Draft. The agent edits analysis.yaml / display.yaml / custom.R, adds requirement rows and tests, and the pipeline regenerates: a new draft iteration vNNN+1 — spec snapshot, ard.json, rendered variants, manifest — while current.json stays untouched. A PR opens carrying the whole bundle.Agent + pipelineDraft PR: spec diff, draft iteration, ARD diff, impact report (drafted)
4Preview. The PR's pr/{N}/ site renders the document with the draft display in place, watermarked “DRAFT v{NNN+1} · CR-{NNN} · pending sign-off”. The root and dev tiers are untouched. The Reader shows a chip on the display: revision in review.CIDraft-in-document preview
5Review. The biostatistician reviews the spec diff + ARD diff (rows added, removed, changed — every affected denominator and population named). The writer reviews the binding impact report — every sentence whose rendered value changed. Both approve on the PR; neither can approve their own proposal.Biostatistician + writerRecorded approvals bound to {spec_hash, ard_hash} (in review)
6Promote. Merge is the human act that makes the draft current: current.json flips, the iteration ledger records who/why/when/commit and the CR id, the CR closes as implemented. A staleness guard re-verifies the reviewed ARD still reproduces before the flip.Human (merge)New current iteration; ledger entry; CR (implemented)

Contrast with Part 1's as-is loop: the eleven steps collapse to six; the manual translation step (a prose comment re-keyed into a tracker) becomes the agent's assessment; the writer's re-copying of numbers into in-text tables disappears entirely, because the bindings re-resolve mechanically; and the audit trail is not a reconciliation report bolted on afterwards — it is the version history.

1 · The change request is a versioned artifact

Part 1's central negative finding: in every incumbent tool the request lives in a comment thread that dies at “resolved.” Here it is a file, in the repo, with a lifecycle.

# requests/CR-012.yaml
id: CR-012
state: in_review              # derived — see §6
requested_by: "@medical-writer"
requested_at: 2026-08-14T15:02:00Z
context:                      # captured by the composer, not typed
  display: t-ae-overview
  iteration_at_request: v003
  e3_section: "12.2.1"
  binding: "t-ae-overview:any_ae:p;group=Xanomeline High Dose"
request_text: >
  Split the AE overview by baseline severity so the high-dose
  imbalance in severe events is visible in the main table.
assessment:                   # agent-written, human-checkable
  tier: A                     # P | A | N | D — verified by CI, §2
  reason: DATA DRIVEN         # ARS AnalysisReasonEnum vocabulary
  interpretation: add AESEV as a second grouping level to any_ae and by_soc_pt
  acceptance_criteria:
    - severity levels appear as nested rows under each arm column
    - subject denominators unchanged from v003
  assumptions:
    - AESEV is populated for all treatment-emergent AEs in adae
  sap_consistency: post-hoc   # triggers the E3 §9.8 note, §2
  provenance: { model: claude-fable-5, prompt_hash: "sha256:…" }
disposition:                  # filled as the loop advances
  pr: 41
  result_iteration: t-ae-overview/v004

Three properties do the work. The verbatim request is preserved — the writer's words, not the agent's paraphrase, are the record (the pattern GitHub's suggested-changes provenance uses: proposer and applier both credited). The context is captured, not typed — the composer knows which display, which iteration, which sentence the writer was looking at, which kills the “which table did they mean” ambiguity that plagues comment logs. The rationale is a merge precondition — a CR with an empty assessment cannot merge (GitBook's merge-rules pattern), which produces the change-control record regulators expect as a side effect of using the system. The iteration ledger already reserved a change-request field (D10); every display version now answers “why do you exist” with a CR id.

GitHub retains its audit log ~90 days — far short of record-retention needs. The CR record and ledger are the durable, committed export of the same facts; the platform's log is corroboration, never the system of record.

2 · The review tier is determined by the computed diff, not the request text

The industry already runs two-tier governance — a signed SAP for analysis semantics, unsigned shells for presentation (Part 1 §4). The framework makes that asymmetry mechanical, and machine-verifiable.

TierWhat changedMachine proofReview required
P — presentationdisplay.yaml only: titles, footnotes, precision, row order, variant thresholdsRegenerated ard.json byte-identical (hash proof: the numbers are untouched)Writer + statistician acknowledgement; auto-approvable by policy (open question D-CR2)
A — analysisanalysis.yaml / custom.R: new statistics, groupings, filters, populations, denominatorsARD diff non-empty; every added/removed/changed row itemizedFull biostatistician sign-off on the ARD diff; SAP-consistency call recorded; post-hoc changes auto-draft the E3 §9.8 note
N — new display / forkNew slug, or a fork of an existing display (§3)New ARD; lineage recorded (forked_from)Biostatistician sign-off + template-slot assignment; §9.8 note if post hoc
D — dataRequest needs upstream ADaM content that does not existAgent classification, honestly escalatedOutside the loop — routed to a human data ticket; the CR records the escalation

The agent claims the tier; CI verifies it. A PR labelled Tier P whose regenerated ARD hash moved fails the build — the same shape as Terraform's plan/apply discipline, where the reviewer approves the computed plan, and HCP Terraform's mandatory-vs-advisory check split: denominator and population changes block, formatting nits warn. The ITIL standard-change idea applies directly: Tier P is a pre-approved change class precisely because the system can prove nothing statistical happened.

The post-hoc rule is encoded, not remembered. EMA's reflection paper §2.3.3.3 states that after database lock, non-prespecified modifications make results post hoc, and model changes require a SAP amendment. The framework enforces this mechanically: every Tier A/N change carries an AnalysisReasonEnum value; a DATA DRIVEN change auto-drafts a generated-tier text block for E3 §9.8 (“Changes in the Planned Analyses”) — bound to the CR id, entering the same approval gate as every generated block (D8). ICH E3 reserved that section in 1995; the loop fills it as a build artifact instead of an end-of-study archaeology exercise. The CR's verbatim request, timestamps and reviewer identities are simultaneously the defense against the “changed the SAP because you saw the data” charge — the change is disclosed, attributed and signed at the moment it happens.

3 · Version or fork: the writer's intent, applied as a rule

D10 gives every display an iteration ledger. The only new question is when a change is a new version and when it is a new display.

4 · The draft lives in the document, honestly

Jeremy's requirement: the new draft display is inserted into the document for review. The three-tier Pages preview system shipped in v0.2 makes this nearly free.

5 · Dual sign-off: each role signs its own competency

Jeremy's flow ends “requests review/sign-off from the biostatistician.” The framework adds the symmetric half the closed loop makes possible — and the incumbents make impossible.

6 · The lifecycle, derived — never parallel

Decap CMS's documented failure is the cautionary tale: an editorial state machine that can disagree with the VCS approval it mirrors. Here the CR's state is computed from git and PR facts, not stored beside them.

StateDerived from
proposedCR record exists; no assessment
assessedAssessment present; no PR
draftedDraft PR open; draft iteration + preview exist
in_reviewPR review requested; approvals incomplete
implementedPR merged; current.json flipped; ledger entry carries the CR id
returnedChanges requested on the PR; loop re-enters at assess or draft
declinedPR closed unmerged, or assessment rejects with rationale — recorded, never deleted
escalatedTier D classification; pointer to the out-of-loop ticket

Every transition is a commit or a PR event; the audit trail is the version history (D9). A dashboard can render the CR queue — the open.csr equivalent of the reconciliation report, except it also knows what happened after each comment.

7 · Delivery increments

Five increments, each shippable and demonstrable on its own, ordered so the writer-facing surface lands first and the platform-dependent pieces land last.

#IncrementWhat shipsDepends on
1CR composer in the ReaderSelect a display or bound number → structured request form → CR YAML with auto-captured context, downloadable or copyable, plus a prefilled issue link. Static-site safe — no tokens, no server — the exact pattern the text editor proved (§13): the product is an artifact a human applies.Nothing new
2Agent laneA CR-labelled issue triggers the agent: assessment comment, then a draft PR — spec diff, regenerated draft iteration, ARD diff summary, binding impact report, tier claim. Runs bounded (CI) or rich (obot session) — open question D-CR4.1
3Review surfaceARD-diff renderer (compare_ard() wrapped) as a page in the PR preview + summary comment; CI tier verification (claimed tier vs computed diff); path-scoped review requirements; merge flips current.json + ledger.2
4Draft-in-documentPreview assembly renders draft iterations watermarked in place; Reader chips on displays with open CRs; binding impact report rendered in the preview; staleness guard on promotion.3
5Platform graduationThe §12 study-level review layer adopts the CR ledger: roles, real e-signature semantics, review plans. Deliberately last — the ledger is designed as the data that layer consumes, so nothing built in 1–4 is throwaway.§12 platform build

Increment 2 is where a design question from §13 gets settled by adjacency: the browser editor deliberately did not attempt the regeneration round-trip for spec edits. The agent lane is that round-trip — run server-side by CI on a branch, where R exists — which is why the composer (increment 1) never needs webR to be honest: it produces a request, not a result.

8 · Credit where due: the borrowed mechanics

MechanicBorrowed fromApplied as
Approve the computed diff, not the requestAtlantis / Terraform plan-applyBiostatistician signs the ARD diff
Staleness guard on applyAtlantis undivergedPromotion re-verifies the reviewed ARD reproduces
Mandatory vs advisory checksHCP Terraform run tasksDenominator/population changes block; formatting warns
Path-scoped required approverCODEOWNERS + ruleset required reviewer (GA 2026-02)analysis.yaml/custom.R → statistical review, non-optional
Approvals reset when the change changesGitLab patch-id approval resetApprovals bind to {spec_hash, ard_hash}
Requester ≠ approver; bot never approvesGitLab SoD settings; Copilot coding agent & reviewAgent proposes, two humans sign Tier A
Data diff summarized in the PR, detailed in the appDatafold CIARD diff comment + drill-down page in the preview
Editorial states derived from VCS factsDecap CMS editorial workflow — and its uncoupling bugCR lifecycle computed from git/PR events (§6)
Rationale as a merge preconditionGitBook merge rulesEmpty assessment blocks merge
Version bundles, previewed stacked; fork sparinglySanity Content Releases; Figma branchingLedger versions default; forks record lineage; branches short-lived
Two-tier change classesITIL standard vs normal change; the SAP/shell asymmetry itselfTier P pre-approved because provably cosmetic
Classification vocabulary for “why”CDISC ARS AnalysisReasonEnumEvery CR carries the standard's own reason code
Shells and results as one artifact typecards::mock_*()A Tier N request can start as a mock-ARD shell the statistician reviews before implementation

And one deliberate non-borrowing: no incumbent's in-app sign-off is imitated. The §12 deferral stands — status is data, surfaced in the UX; the study-level platform owns roles and e-signature when it exists. Nothing in this framework preempts it; everything in this framework feeds it.

9 · Open questions for review

#QuestionDraft position
D-CR1Where do CR records live — in-repo files, GitHub issues, or both?In-repo (requests/) as the system of record; an issue mirrors each CR as the notification/inbox surface. The repo survives platform retention limits; the issue makes the queue visible.
D-CR2May Tier P changes auto-approve once the ARD-unchanged proof passes, or does every display change see a statistician?Policy-configurable; default to a lightweight statistician acknowledgement in the demo, and demonstrate the auto-approve policy as an option — the proof is the point.
D-CR3Who retires a fork, and where is that recorded?Editorial decision by the writer, recorded as a CR of its own (Tier P — assembly change, no ARD change).
D-CR4Does the agent lane run in CI (bounded, reproducible, cheap) or as an obot-style session (richer judgment)?Start in CI with a bounded assessment step; graduate complex requests to sessions. The CR record's provenance fields don't care which produced it.
D-CR5Is the E3 §9.8 note auto-drafted for every post-hoc change, or only when the writer confirms?Always drafted (it enters the normal generated-block approval gate anyway — a human still signs it); silence is the failure mode worth designing against.