Decision artifact2026-08-27awaiting your answersix questions

The review layer

The app finds the risks and then forgets that anyone looked. Of the ten review-workflow capabilities the platform survey scored, this portfolio has none, and the survey called that its strongest single finding. This page designs the two smallest pieces that would change how a reviewer actually works — a durable record that someone looked, and a comparison against what they saw rather than against last week — and it starts with a measurement that moves the hard problem somewhere unexpected.

Review capabilities we have: none of ten Datasets the snapshot named ps-001 has meant: four Row counts under that one name: 1,898 → 1,918 → 1,920 → 3,835 Code that would need writing to reuse the existing comparison: one argument
The finding that reorganised this page

The requirement frames the hard problem as mutable review state living beside immutable snapshots. Measuring it found the opposite difficulty: the snapshots are not immutable. The published snapshot named ps-001 was published once and then rewritten four more times, and the data underneath that single name went from 1,898 rows to 1,918, to 1,920, to 3,835. The rebuild script deletes every snapshot directory and re-issues the names starting again from one.

So a review recorded today against ps-001 would, after the next rebuild, assert that a named person approved data they never saw. That is not a storage problem. It is the reason the first recommendation on this page is about what a review points at, not about where it is kept.

THE SITUATION

Three sentences

The study app surfaces flagged sites and flagged participants at every data cut, but nothing anywhere records that a human looked at one, what they concluded, or what they did — so the same rows demand attention again at the next cut and the reasoning behind every decision lives in a meeting, an email, or nowhere. The survey of thirteen safety and monitoring platforms scored ten review-workflow capabilities and found this portfolio has none of them, while the single capability with the broadest support across those platforms is the one that tells a returning reviewer what has changed since they last looked. This page proposes the two smallest pieces of that layer, and asks six questions whose answers determine whether the record is trustworthy or merely present.

WHAT THE OTHER PLATFORMS ACTUALLY DO

Nine of thirteen, and thinner than it sounds

Only nine of the thirteen surveyed platforms touch review workflow at all. The four open-source clinical packages closest to this project — the safetyGraphics family, Roche's teal, Boehringer's DaVinci modules and Biogen's tidyCDISC — carry none of it. What they have instead is state persistence, which is a different thing: teal has named snapshots and report cards, DaVinci has a bookmark manager. Those remember a view. None of them remembers a judgment.

Among the nine that do, the depth varies enormously, and the survey is careful that a platform was scored as having a capability because its documentation describes one. Nothing was tested, and the report says plainly that vendor documentation flatters.

PlatformOf the tenWhat it actually describes doing
elluminate9A digital review plan where each objective links to the displays that satisfy it, with completion tracking; role-based review; issue management; and identification of data changes since the last review.
JReview6A per-patient "I-Reviewed" checkbox; patients with new data since the last review highlighted; colour-coding and comments on patients.
CluePoints5Issue tracking integrated across detection modules, findings and corrective actions in one place, automatic breach documentation with justification and evidence.
Oracle Empirica5Tracked versus informational alerts monitored across successive data updates, so a reviewer sees how a signal moves; plus an alerts-reviewed completeness measure per product.
Veeva CDB5Change detection feeding an automated query lifecycle. The change is consumed by automation rather than shown to a reviewer as new.
JMP Clinical3One subject selection broadcast to every report in a review, patient profiles, auto-generated narratives.
clinDataReview1Comparison across two data batches — the interim-refresh diff, and the only one of these in an open-source package.
Spotfire, Medidata1 eachBookmarked filter selections that persist between sessions; alert criteria. Medidata publishes almost nothing here.

What "since I last looked" turns out to mean

Five platforms carry this capability and the survey describes three genuinely different mechanisms plus one bare assertion. They are worth separating, because the differences are the whole design.

Something this page must not claim

The design proposed below — compare the current data against the snapshot a particular reviewer last signed, per finding — is not an observed platform behaviour. No surveyed platform is documented as doing that. It is the survey's own proposal and it is this page's proposal; it should not be attributed to JReview or to anyone else, and the requirement's description of a Veeva product reverting a changed row to incomplete describes a different Veeva product from the one the survey scored.

Where each approach breaks

THE ARCHITECTURAL PROBLEM

The state is not actually mutable, and the snapshots are not actually immutable

The requirement names the central tension: review state is mutable and per-person, while the whole data architecture rests on immutable published snapshots that can be re-run and reproduced. Both halves of that sentence turn out to need correcting, and correcting them is what makes the design small.

Nothing mutable has to be stored

A review is not a state. It is an event: at a moment, a named person asserted something about a named finding as that finding stood in a particular set of data. An event is a fact, and facts do not change. What people want to see is a status — reviewed, escalated, outstanding — and a status is not a stored thing either. It is a fold over the events, joined against whatever the data says now, computed when the page renders.

That leaves two immutable inputs and one derived view, which is precisely the shape the pipeline already has. Nothing in the design ever writes a field twice, and nothing ever needs a lock, a merge or a last-writer-wins rule.

But the snapshots are not immutable, and that is measured

The published site holds three snapshots under the names ps-001, ps-002 and ps-003, and the app addresses history by those names. Reading the publish history of the first one shows it was published once and rewritten four times afterwards. Hashing the results file at each of those five commits shows four genuinely different datasets under the one name.

Commit to the published branchRows in the results fileSame data as before?
Publish ps-001 (cut-1)1,898
Pin one package to a tagged version1,898yes
Republish from the unified raw layer1,918no
Republish with real tolerance limits and a real denominator1,920no
Republish with the safety overview and participant metrics3,835no

The rebuild script makes this structural rather than accidental: it deletes every snapshot directory on the published branch, deletes the index that lists them, and re-issues the names from one. A review recorded against a snapshot name therefore has no defence at all. After a rebuild it points at whatever now holds that name, and the page would show a person's name and a date beside data they have never seen.

This is the cost the review layer imposes, and it is not optional

The published site says of itself that every number on it comes from the snapshot tree, and the design record describes a snapshot as an immutable static tree that is the only data interface. Nothing currently checks either sentence, and nothing can: no part of the pipeline, the published tree, the charts or the site hashes anything. The only cryptographic identifiers anywhere in the system pin the package versions, which identify the code and say nothing about the data.

A review layer is the first feature that makes those sentences load-bearing, because it is the first feature where a person's name is attached to a claim about specific data. It does not create the weakness. It is what makes the weakness visible.

What a review should point at instead

A finding needs two identifiers and they do different jobs.

Note what this excludes. The requirement proposes putting the flag into the key. It should not be there: across the three published snapshots, 310 findings carry two different flag values and 13 carry three, so keying on the flag detaches a reviewer's conclusion at precisely the moment the flag flips, which is the one moment they care about. The flag is something the reviewer saw, not something that names the thing they looked at.

The same correction collapses the requirement's second key. It proposes a separate identity for participant-level safety findings, but those already publish through the identical seven columns, with the participant identifier sitting in the group field. There is one key, not two, and it already covers 1,930 published participant findings alongside the site and country ones.

WHAT COUNTS AS CHANGED

The trap that makes this feature worthless if it is built the obvious way

The obvious implementation hashes the whole result row and reopens the finding when the hash moves. On this data that reverts essentially every finding to unreviewed at essentially every snapshot, because a site that is still enrolling has a different denominator every time the pipeline runs. The feature would then mark everything as changed, a reviewer would learn within two cuts to ignore it, and the capability would be worse than not having it.

The mechanism the requirement admires — a changed row reverting its listing to incomplete — works in the product it comes from because that product watches source data records, where a change means somebody corrected a value. A risk-indicator row is a derived aggregate that moves by construction. Copying the mechanism across that difference without noticing it is the failure mode here.

So materiality has to be declared rather than computed, and it belongs next to the metric's threshold, because deciding what counts as a meaningful move is the same kind of judgment as deciding where the threshold sits. Three tiers are enough.

TierWhat movedWhat should happen
The verdictThe flag, in either directionAlways reopens. Not configurable — this is the finding changing its mind.
The magnitudeThe score or the rate, by more than a tolerance the metric declaresReopens by default, and the metric may set the tolerance or opt out.
The substrateThe numerator, the denominator, the participants underneathNoted on the row, never reopens by default. This is the one that moves every single cut.
A defect in the comparison that already ships, found by writing this

The site already computes a snapshot-to-snapshot comparison and renders it as a "since the last snapshot" section. It compares flag levels and nothing else, and it skips any finding whose flag level did not change.

That is fine as far as it goes, but the statistical bounds are recomputed at every snapshot from that snapshot's own results — the flagging is relative to peers. On the bundled study the boundary curve for one indicator moved by more than half its range across three cuts. A site can therefore go from amber to red with no change whatever in its own numbers, purely because the other sites improved. The comparison reports that the flag moved; it cannot say whether this site got worse or the field got better, and those two findings call for opposite actions. Whatever is built here should name which, and today nothing can.

WHAT TO ACTUALLY BUILD

Two pieces, and the first one is nearly free

Piece one · the comparison already exists

Compare against what this reviewer last saw, not against last week

The function that compares two snapshots takes the two sets of rows as its first two arguments. Today the page hands it the previous snapshot. Handing it instead the results of the snapshot a particular reviewer last signed turns an impersonal weekly diff into a personal one, and the function itself needs no change at all.

The watermark this needs is not a session, a cookie or a page view. It is the digest recorded on that reviewer's most recent entry. That makes it durable, auditable, portable between machines, and computable with no login and no server — and it means a reviewer who was away for a month sees one month of change rather than one week of it.

This matters beyond convenience: the roadmap item that records what you personally have looked at is explicitly local-only and says the public static site cannot do that even in principle, and should not, because per-visitor tracking has no place on a public page. A watermark taken from a signed record rather than from a visit is how the same capability arrives on the static side without any tracking at all.

Piece two · the record

An entry that says who, what, why, and what they did about it

One append-only entry per finding reviewed, carrying the four-part key, the digest of what was on screen, the state, a reason from a short controlled list, free text, and the action taken or recommended. Those fields are not invented: the regulator's own question-and-answer on risk-based monitoring asks for the date, the individuals involved, a summary of the data reviewed, a description of the deficiency and a description of the actions taken, to be taken, or recommended, and the entry maps onto that field for field.

The controlled reason list is the part that earns its keep. Free text alone cannot be counted; a short vocabulary lets the study say that most dismissals of one indicator were the same known site practice, which is itself a finding about the study. Requiring a reason before a dismissal is the mechanism that makes a dismissal worth reading, and it is proven — it is what the code-scanning workflow this project already lives inside requires.

Four states are enough to start: outstanding, which is the default and is recorded nowhere; acknowledged, meaning seen and no action needed; escalated, meaning seen and action required; and resolved, meaning the action happened or the finding cleared.

Suppression should not be a separate switch

One surveyed platform offers a flag that hides a finding from future refreshes. This design should decline it. Acknowledged already means do not show me this again unless it changes, because the digest is what brings it back. Adding a second way to hide a row creates a second thing to audit and a second way for a finding to disappear quietly.

Writing it, with no server and no password

The site is static by contract, stated in the requirements, in the publishing notes and in the design record, and it is served from a branch verbatim with no build step. That rules out a form that posts somewhere. It does not rule out writing, because the hosting platform will accept a fully composed file through a link.

This was executed rather than assumed. Opening a prefilled new-file link against the demo study repository opens the platform's own editor with the path and the entry already filled in, under whatever account the reviewer is already signed into. Nothing was committed. No credential is ever handled by the page, and there is no authentication code to write.

Then the failure direction, by binary search on the same link:

Entry content carried in the linkWhat the platform did
2,000 charactersAccepted, content intact
8,000 charactersAccepted, content intact
8,250 charactersRefused — request too long
16,000 and 32,000 charactersRefused
64,000 charactersConnection dropped before any reply

A realistic entry carrying the full regulatory field set is 400 characters, which becomes 580 once encoded into a link, so one hand-off carries about thirteen findings. That is a real bound and the design should state what happens above it rather than discover it in front of a user: the page splits a longer session into a second hand-off. It is also the reason the record is one file per review session rather than one file per finding, which has a second benefit — two reviewers working at once never touch the same file, so a conflict between them is not merely unlikely but structurally impossible.

Where the file goes, and why not the obvious place

The study repository has a source branch and a published branch, and the published one is arranged by a script. That script replaces exactly three named directories and copies four named files; it never enumerates the published root to remove anything unknown. A review folder kept at that root would therefore survive every publish — while the rebuild deletes every snapshot directory beside it.

That makes the published branch a survivable address but a bad home, for four reasons that all point the same way: everything on it is world-readable, the pipeline pushes to it without rebasing so a reviewer's commit landing at the wrong moment breaks the next publish, that branch is the one branch policy reserves for your approval so no reviewer could write to it unattended anyway, and it holds generated output rather than authored material.

The review record is authored. It belongs on the source branch with the other authored things, where changes arrive as pull requests on the ordinary lane, and the publish step should carry a derived projection of it onto the site the same way it carries everything else. That resolves the visibility question, the race, and the policy collision in one move, and it costs one thing worth naming: a review recorded on Tuesday does not appear on the site until the pipeline next runs, which today is Monday.

WHAT IT COSTS

Named honestly, including what it does to the provenance claim

What should wait, on the record

Of the ten capabilities, this design proposes two and deliberately defers the rest. A review plan with objectives linked to displays is the rarest thing in the survey and the closest analogue to this project's own traceability, but it needs the record to exist first. Role-based assignment needs roles, which needs accounts. An audit trail with electronic signatures is a gate on adoption rather than a display and is declined above. Participant-level alerting needs a notion of a refresh that this design supplies and should be designed after it. Raising a query back into the data-capture system is out of scope and should stay out. Annotation should never be built at all as a separate feature — an entry with a note is the annotation, and building both would be two records of the same thing. Issue tracking with a documented resolution comes almost free: an escalation opens an issue in the same study repository, which is the mechanism the study-level repository work already proposes for change requests and sign-offs.

THE QUESTIONS

Six, each with a recommendation

RL1 · where the record lives

Where does a reviewer's record live, and who is allowed to write it?

This is the fork everything else follows from. The record can be authored material in the study repository, or generated material on the published branch, or it can leave the repository entirely for a database, which would end the static-hosting contract.

  • Authored on the source branch, projected to the site when the pipeline publishes. Costs a delay between recording and seeing, currently up to a week. Forecloses nothing; the projection can be made more frequent later.
  • Written straight to the published branch. Immediate, and it does survive the publisher. Costs world-readability for every field, a race against the pipeline's own push, and a collision with the policy that reserves that branch for your approval.
  • Kept as issues rather than files. Gets comments, assignment and notifications free. Costs the audit trail: an issue body can be edited afterwards and the edit leaves no event in the ordinary history.
  • An external store. Ends the no-server contract, which is stated in four places and is the reason the whole thing can be forked and hosted by anyone.
Recommendation: authored on the source branch, projected on publish. It puts review on the ordinary merge lane, keeps the published branch generated, and lets the projection decide what becomes public. Issues stay in the design but as the escalation path, not as the record.
RL2 · what a review points at

What does a review attach to, when a snapshot name has already meant four different datasets?

A review has to name the data it was written against, or it cannot be checked later. The published names are currently reused.

  • A digest of the results the reviewer saw, published alongside each snapshot. Costs a few lines in the publisher. A rebuild producing the same data keeps the review attached; a rebuild producing different data correctly detaches it.
  • The snapshot name, plus a promise to stop rewriting them. Costs nothing today and relies on a discipline that has already failed five times on the only study we have.
  • The data-cut date carried inside the results. Costs correctness: two of the three published snapshots carry the same date, so it cannot tell them apart.
Recommendation: the digest. It is the only option that makes a false record impossible rather than merely discouraged, and it makes the site's own immutability claim checkable as a side effect.
RL3 · what brings a finding back

What kind of change should reopen a finding someone has already signed off?

Too sensitive and everything is permanently unreviewed; too blunt and a signature quietly outlives the thing it was about.

  • Flag changes reopen; magnitude beyond a declared tolerance reopens; everything underneath is noted only. Costs a tolerance decision per metric, made once, beside the threshold.
  • Any change to any published value reopens. Costs the feature: an enrolling site changes its denominator every cut, so this marks everything as changed forever.
  • Only flag changes reopen. Simplest, and misses a site that doubled its rate while staying inside the boundary.
Recommendation: the three tiers. And whichever is chosen, the page should name the field that moved rather than showing a bare changed marker, so the reviewer can tell in one glance whether it was worth their attention.
RL4 · what becomes public

Which parts of a review may appear on a world-readable page?

The demo study is a public repository served verbatim, so anything the site carries is published rather than stored. A clinical judgment about a named participant is not obviously something to publish, and blinding is already treated elsewhere as a property enforced where the data is written out.

  • Publish the fact and the controlled reason; keep free text in the repository unless the study opts in. Costs a reader the reasoning behind a dismissal on the public page, and keeps it one click away for anyone with repository access.
  • Publish everything. Costs a permanent, indexed, public record of clinical reasoning about identified participants, on a study whose blinding status the app does not currently reason about.
  • Publish nothing; the record is repository-only. Costs the whole point — the reviewer never sees their own state on the page they work in.
Recommendation: publish the fact and the reason, hold the free text behind a per-study switch that defaults to off. This is the question on this page with real downside risk, and the default should be the safe one.
RL5 · who is a reviewer

Who counts as a reviewer in the first version, when the site has no login?

The write path borrows the hosting platform's identity, so attribution is genuine and free, but it only exists for people with an account there.

  • Account holders only, said in writing. Costs the medical monitor who has no such account, which may be most of the eventual audience. Forecloses nothing later.
  • Allow an entry to name someone other than whoever committed it. Costs the one thing this design has that the surveyed platforms mostly do not — attribution that is mechanically true rather than asserted.
  • Wait for a proper identity story. Costs the whole layer for a version or two.
Recommendation: account holders only, stated plainly, plus a publish check that refuses an entry whose named author is not the person who committed it. A check that can fail is what separates this from a text field.
RL6 · the number at the top

Which number belongs at the top — how much has been reviewed, or what is still outstanding?

One surveyed platform makes a reviewed-completeness percentage its headline tile. A different tradition scopes the count to the period under review so that it shrinks to zero when the work is done.

  • Outstanding findings that are new or changed since you last signed. Bounded, reaches zero honestly, and rises only when the data moves. Costs nothing extra — it falls out of the watermark.
  • Percentage reviewed. Familiar to anyone from that platform. Costs meaning: it reaches a hundred by dismissing everything, and it makes the act the goal.
  • Both. Costs the reader's attention on the one number that should drive behaviour.
Recommendation: the outstanding count, scoped to what changed since that reviewer last signed. A number a person can drive to zero by doing the work, and that cannot be inflated by doing nothing.
WHAT UNBLOCKS

What starts on each answer

If you answerWhat startsWhat it costs
RL2 alone — key to a digestPublishing a results digest with each snapshot, which is independently worth having and needs no review layer to justify itA few lines in the study's publisher, plus a decision about whether the digest is asserted or checked
RL1, RL2, RL5The record: an entry schema, the compose-and-hand-off write path, and the publish check that makes attribution trueThe bulk of the build, and a per-study decision about repository access
RL3 and RL6The personal watermark and the outstanding count, reusing the comparison that already shipsA tolerance per metric, decided once beside each threshold
RL4The projection onto the published site, and with it the first honest answer to what a public study site may say about a participantA per-study switch, and a revision to the provenance chip so two sources are distinguishable
Defer the whole thingNothing here. The gap analysis's strongest finding stays open, and the requirement stays in the backlogNothing immediately — except that the snapshot-name weakness measured on this page remains unaddressed, and it is a defect in the provenance story whether or not a review layer is ever built

This page is also the answer to the fifth question on the clinical-priorities artifact, which asked whether the review layer becomes first-release scope or is deferred in writing. The recommendation here is the narrow middle: the digest and the record are worth building, the remaining eight capabilities are deferred in writing above, and nothing in this design gates a clinical release.

CLAIMS FOUND WRONG

Three things previously written that this page contradicts

METHOD

What was measured here rather than relayed

Requirement: the action log, on the roadmap — #139. Answers: the fifth question on the clinical-priorities artifact, D0026. Sources: the platform gap analysis, survey of thirteen platforms; the dashboard design that filed this requirement, app dashboard design. Adjacent: study-level repositories #131, blinding as a published property #144, what you personally last looked at #205, and the repeated-snapshot problem in the demo study demo-301#8.

Drafted by 👯🤖 Claude Code (Claude Opus 5, worker W0139). Not reviewed by @jwildfire before publication.