Study Management — target acceptance criteria tracker¶
This is the single running record of what the programme must deliver and how far it has got.
It exists so that shipping thin slices early (the programme's delivery rule) never loses sight of
the target. Every wave PR updates the rows it delivers, with a link to the evidence (spec,
test name, PR, or staging capture). A row is Met only with evidence; Partial names what is
missing; Deferred names the follow-up that owns it.
Delivery rule (recorded 2026-09-02): favour feature delivery, then iterate. The rule governs how
review findings are triaged: a finding holds a PR only when it shows a regression, data-loss or
corruption risk, or a security problem; every other finding is recorded as a follow-up and the PR
ships. It does not waive the plan's CI and merge gates — focused tests, a current generated client,
lint/build/docs validation, every review thread resolved or explicitly deferred, no failed or
in-progress required check, and the final user review — which still apply to every PR. For
flag-gated slices the controlled staging proof is attached before the flag is enabled anywhere
(C7.2), not before each merge of dormant code; unflagged corrections attach preview or test evidence
to their own PR.
Scope change (recorded 2026-09-02): the Risk of Bias feature is disabled and its development is
on ice, so the Risk of Bias family is omitted from Waves 2–5. Rows that mention it are marked
Out of scope until product revives the feature. Its server-side revision field from Wave 1B
remains as dormant backend code.
Status legend: Not started · In progress (PR #) · Met (evidence) · Partial (gap) ·
Deferred (follow-up) · Out of scope (reason).
Implementation closeout (2026-09-07): Waves 1–5 are merged. Wave 1A #3052 (bf9d2f3a4), Wave 1B #3053 (348dee254), theme
tokens #3059 (945fa86a4), tracker #3068 (2270471d8), Wave 2A #3072 (a06eecadb), Wave 2B #3071
(c695e57ae), Wave 2C flag #3066 (e51bd950f), Wave 4 Searches #3075 (aa77c78fb, merged
2026-09-05), Wave 3 Library #3077 (5d7850483, merged 2026-09-07) and Wave 5 Processing #3073
(a8a254eb0, merged 2026-09-07) are confirmed merged via gh pr view. Hotfixes #3104 (b145e56b5), #3114
(bfa5d681d) and #3125 (2689894ee) repaired main between waves; the slow-spec timeout fix #3162
merged as 741ac2ed7 (verified via gh pr view 3162 --json state,mergeCommit).
The generated studyManagementProcessing flag remains off in every environment. Enabling it still
requires the security/product permission-matrix sign-off (B17), the controlled staging walkthrough
(C7.2), and an explicit per-environment release decision (C7.4). Implementation evidence recorded
below is unit/component/E2E-authored test coverage from each wave PR's own description, not a
completed staging run — a row marked Met does not imply the flag is enabled anywhere or that the
staging walkthrough has passed. PR #2990 (the pre-programme prototype) is not closed as of this
writing; C3.6 below records that as an open owner decision, not a completed step.
A. Programme target (the MVP the user must be able to do)¶
ID
Criterion
Source
Status
A1
An authorised user launches a Bulk Study Update from the Library and finds the same operation in Processing after navigation or refresh
README MVP
Met at implementation/unit level — Library half via PR #3077 (page action; strip survives dialog close, navigation and refresh); Processing destination via PR #3073 (deep-link reveal covered by its focused suite; PR open at verification time, merge SHA a8a254eb0). Rendered proof of the full Library-to-Processing round trip rides the staging walkthrough (C7.2)
A2
Every operation shows the exact raw status, truthful progress, bounded errors, initiator only when the server can prove it, and valid created/updated/completed timestamps with no year-one defaults
README MVP; D7–D9, D13
Met — contract truth PR #3052/#3053; Library surface PR #3077; Searches surface PR #3075 (aa77c78fb, shared presenter, honest bar modes, unknown-status alignment); Processing surface PR #3073 (raw enum + value, full UTC timestamps, "Not available" fallbacks; merge SHA a8a254eb0)
A3
Library, Searches and Processing link to each other and back to the owning context consistently
README MVP; D1, D3
Met — PRs #3077/#3075/#3073 (contextual "View in Processing" deep links; Processing's links back to Library/Searches from its empty state and row detail; #3073 merge SHA a8a254eb0)
A4
The experience works at 320 CSS px, at 200–400% zoom, with keyboard and screen reader
README MVP; D16, D17
Partial — Library semantics PR #3077 (dialog focus return, one throttled polite live region, ≥44px targets, forced-colors block); Searches semantics PR #3075; Processing semantics PR #3073 (semantic tables/cards, skip link, forced-colors/reduced-motion CSS; merge SHA a8a254eb0). Rendered 320px and 200–400% zoom proof for all three destinations rides the staging walkthrough (C7.2)
A5
State stays stable during loading, reconnecting, partial failure, completion and live updates; stale HTTP/SignalR payloads never regress newer state
README MVP; plan Wave 2
Met for MVP delivery/reconnect/manual refresh — server revisions PR #3052/#3053; client rejection/reconciliation PR #3072; expanded-row/focus stability through live updates PRs #3075/#3073 (merge SHA a8a254eb0); automatic refresh throughout a sustained SignalR outage is deferred to #3249
A6
Users find things intuitively placed and easy to navigate (product owner's UX mandate, 2026-09-01)
design-decision.md
Partial — entry points, navigation and automated UX contracts delivered across PRs #3077/#3075/#3073 (merge SHA a8a254eb0); the controlled staging walkthrough with the product owner remains C7.2
B. Cross-cutting design contract (applies to every UI wave)¶
ID
Criterion
Source
Status
B1
Three linked routes under one "Study management" navigation group; compact navigation uses PR #2994's adaptive primitive
D1
Met — PR #3073 (merge SHA a8a254eb0)
B2
Processing is read-only history; only Bulk PDF exposes cancel/retry; no other family ever renders cancel/retry
D2
Met — PR #3073 regression coverage confirms no unsupported family renders cancel/retry (merge SHA a8a254eb0)
B3
Contextual summaries on Library/Searches with "View in Processing"; the deep link reveals the operation even when terminal and outside the default filter, with notice + Clear + focus
Unknown status renders "Unknown status (n)", never Running
D7
Met — PR #3071 (c695e57ae) presenter; Bulk PDF's "Status unavailable" reconciled to the shared fallback by PR #3075 (aa77c78fb), closing O5
B8
Honest progress only: determinate bars need a real numerator/denominator; completion reports matched/modified, and a 0-changed run says so
D8
Met — resultSummary contract PR #3052; presentation PRs #3071/#3077/#3075/#3073
B9
Missing data renders "Not available", never fabricated
D9
Met — nullable timestamps PR #3052; presentation PRs #3071/#3077/#3075/#3073
B10
Processing grouping Needs attention → Active → Completed with counts; fixed column grid; contained horizontal scrollers
D10, D11
Met — PR #3073 (merge SHA a8a254eb0)
B11
Live updates re-sort deferred while a row is expanded; reconnecting keeps rows visible and marks them stale
D12
Met — PR #3073; expanded rows stay pinned through completion until closed (merge SHA a8a254eb0)
B12
Bounded error lists (first 10 of N, Show all)
D13
Met — PRs #3075/#3077/#3073 distinguish authoritative totals from retained samples (#3073 merge SHA a8a254eb0)
B13
Server-filtered aggregate with per-family view gates; route visibility decided by PermissionReport capabilities, never by emptiness
D15; README decision 2
Met — server gates PR #3053 (merged 348dee254); selectors PR #3071 (c695e57ae); route/empty-state capability gates PR #3073 (merge SHA a8a254eb0)
B16
Accessibility contract on every page: one polite live region per page, role="status" notices, aria-expanded on expandable rows, labelled focusable scroll regions, progressbar aria-valuetext, reduced-motion static indeterminate track
Security/product approval of the permission matrix is recorded before the flag is enabled in any environment (exposure gate; server proof from PR #3053 is evidence, not the approval)
README plan gate
Not started — blocks C7.4, never a wave PR
B14
Theme tokens: --syrf-warning-container, --syrf-warning-accent, --syrf-info-container exist and are the only new tokens; no existing --syrf-* redefined. Feature components consume only emitted --mat-sys-* / --syrf-* tokens per design-token-mapping.md: no hard-coded hex colours, no Bootstrap or private Material variables, no new fonts, and no breakpoint system competing with the shared responsive contract
design-decision.md; design-token-mapping.md
Met for the tokens (PR #3059 merged 945fa86a4); consumption restrictions checked per UI wave
B15
Feature exposure gated by generated default-off studyManagementProcessing flag with runtime-catalog entry
plan Wave ⅖
Met — PR #3066 merged e51bd950f; flag remains off in every environment
Snapshot membership reconciled by project audit.version; omitted-job race test proves a late HTTP payload cannot delete a newer SignalR-created row
Met — PR #3072 (a06eecadb)
C2.3
ProjectDetails per-family id arrays reconciled the same way
Met — PR #3072 (a06eecadb)
C2.4
Pure adapters for Search Import, Bulk Study Update, Bulk PDF: every enum value, unknown branch, invalid dates → null, no invented data, warnings separate from errors
Bulk study update is a visible page action beside Add systematic search (not in the table overflow menu)
Met (PR #3077) — LibraryBulkUpdateActionComponent renders in the Library header; the mat-menu-item entry is deleted. Proof: library-bulk-update-action.component.spec.ts, and the E2E journey opens the dialog from the page action and asserts the overflow menu no longer carries it. Confirmed in a real browser on the local E2E stack (2026-09-02): the action renders beside "Add New Systematic Search" and holds focus after the dialog closes
C3.2
Action visibility by permission; upload success/failure; refresh and re-entry show the current/latest update truthfully
Met (PR #3077) — permission gating and its absence are unit-tested; a failed direct-to-S3 PUT now calls reportBulkStudyUpdateUploadFailure (idempotent, 404/409 tolerated) so the job cannot stay "Uploading"; completion renders resultSummary, not parsed rows. The strip reads from the store, so closing the dialog, refreshing or re-entering shows the same job. Proof: update-studies.spec.ts, bulk-study-update-summary.component.spec.ts, e2e/tests/bulk-update-validation.spec.ts. The local E2E run confirmed the running-state strip renders after the dialog closes and after a refresh (chip, name, "Started by", timestamp, indeterminate bar). The completion half of that journey could not be observed locally: the S3 to Lambda to MassTransit pipeline did not reach a terminal job state, and the two pre-existing tests that share that dependency failed the same way on the same run, so it is CI evidence rather than local
C3.3
Contextual summary is the shared status strip; "View in Processing" only when the route exists and the flag is on (no dead link)
Met (PR #3077) — the strip renders the shared status chip in the Wave 2B presenter's StatusView shape and puts "View in Processing" in its trailing position, gated on selectStudyManagementProcessing. Both flag states are unit- and E2E-asserted. Wave 2B's real row, presenter and chip are consumed directly (Phase 2 complete; this PR is stacked on feat/study-mgmt-wave2b-job-rows), so the Library phrases no status of its own
C3.4
Year-one regression test; initiator availability; error disclosure; empty/summary states
Met (PR #3077) — Wave 2B's toIsoOrNull rejects year-one, unparseable, empty and impossible dates, and both the strip and the dialog render "Not available"; a missing initiator does the same; errors bound to the first 10 of N with an honest head, a "Show all" toggle and expanded state that survives live updates; no job ⇒ no strip; 0-changed, no-result and expired-upload outcomes each have their own assertion
C3.5
Keyboard dialog focus; throttled live announcements; 320px card layout; 200–400% zoom; no page-level overflow; ≥44px touch targets below 600px; forced-colors CSS block (D17 assigns it to Wave 3)
Partial (PR #3077) — dialog focus returns to the page action, one polite live region throttled to one message per 2s, ≥44px targets below 600px, forced-colors blocks on the chip and strip, and a wrapping header plus a single-column strip below 600px are all in place and unit-tested where testable. Gap: rendered 320px and 200–400% zoom overflow has not been measured in a browser; it rides the staging walkthrough
C3.6
PR #2990 closed as superseded with a link to the replacement
Awaiting owner decision — Waves 3–5 (PRs #3077/#3075/#3073) now supersede its functionality, but PR #2990 remains open as of 2026-09-07 (gh pr view 2990 confirms state: OPEN). Closing it is an explicit owner decision, not something this doc performs; see section D
processing and canonical searches routes; the legacy admin/systematic-searches URL keeps working via a compatibility redirect with a routing test; Study management navigation group; flag-off direct URL and navigation absence
Met at implementation/unit level — PR #3073 (canonical/legacy routing, flag-off behaviour, Study management nav group; covered by route/nav/E2E tests per its PR description). Merge SHA a8a254eb0
C5.2
Guard and visibility by PermissionReport capabilities for every permission combination
Met — PR #3073 (merge SHA a8a254eb0)
C5.3
Default filter, reset, result count; 250-row fixture filter/sort/Show more
Met — PR #3073 (merge SHA a8a254eb0)
C5.4
Deep-link reveal of a completed referenced operation with notice, Clear and focus
Met — PR #3073 (merge SHA a8a254eb0)
C5.5
Grouped list, deferred resort while expanded, bounded error lists, row detail with raw enum + value, full UTC timestamps (created/updated/completed, "Not available" when null) alongside any relative time, initiator, and copyable job ID
Met — PR #3073; expanded rows stay in their original position and band through live completion, then resume ordering when closed (merge SHA a8a254eb0)
C5.6
One loading boundary; empty state reads "none visible to you" with links to Library and Searches; load failure states "Your operations are unaffected" with a Retry action; reconnecting keeps rows visible and marked stale; stable focus during SignalR updates (D15, D12)
Met — PR #3073 (merge SHA a8a254eb0)
C5.7
Mobile/zoom reflow; screen-reader names and result-count announcement; colour-independent meaning; forced-colors block; app-shell skip link
Partial — semantic tables/cards, live count, focus controls, forced-colors/reduced-motion CSS and the skip link are automated in PR #3073; rendered 200–400% zoom proof remains C7.2 (merge SHA a8a254eb0)
C5.8
Processing describes itself as the history the server retains (Bulk PDF keeps 20 per search / 100 per project; other families per their retention) and never implies nothing was pruned; payload size/retention recorded with the 250-row fixture; paged history API follow-up created if needed
Met — PR #3073 adds retained-history wording (Bulk PDF's 20-per-search/100-per-project limits) and the 250-row paging fixture (merge SHA a8a254eb0)
Audit of statuses, progress units, errors, names, timestamps, permissions, entry points, routes, breakpoints, tokens, announcements, telemetry, support identifiers, user-guide copy
Met — parallel specification/architecture/quality audits across PRs #3075/#3077/#3073; intrinsic findings were fixed before merge and remaining polish is recorded in section D (#3073 merge SHA a8a254eb0)
C7.2
Controlled staging proof captured per the plan's validation strategy for each UI wave
Not started
C7.3
User guide updated for the three destinations
Met — user-guide/studies/upload-search.md (PR #3075); user-guide/studies/manage-studies.md and whats-new/whats-new-bulk-update-study.md (PR #3077); user-guide/studies/index.md and user-guide/studies/processing-activity.md (PR #3073, merge SHA a8a254eb0) — confirmed via gh pr diff --name-only on each PR
C7.4
Flag enabled per environment by explicit release decision (never by a wave PR), after B17 is recorded
O5 — unknown-status fallback alignment with Bulk PDF
Closed by PR #3075 (aa77c78fb)
Background sweep for long-expired UploadingToS3 Bulk Update jobs
Separate backend PR (PR #3052 follow-up)
CLAUDE.md pointer to this feature and the delivery rule above
After PR #2900 releases the file (single-writer rule)
Paged history API
Only if the 250-row measurement in Wave 5 shows the aggregate is not acceptable
SearchController.CalculateRob authorization gap (PR #3060, drafted, CI green)
Parked by product 2026-09-02 while RoB is on ice. Note: robToolEnabled=false only hides the UI; the endpoint stays callable by any authenticated user. Product owner to decide whether to merge #3060 as a security fix regardless of the UI freeze
PR #2990 (bulk study update job history prototype) closure
OPEN as of 2026-09-07 (verified via gh pr view 2990). Waves 3–5 supersede its functionality; closing it as superseded is an explicit owner decision this doc does not make (C3.6)
Sustained HTTP reconciliation during a persistent SignalR outage
#3249 (open) — after #3077 and #3073 merge. The MVP relies on live SignalR events, refresh/re-entry and reconnect reconciliation; during a persistent outage the open Library/Searches/Processing views can retain their last observed state until one of those reconciliations occurs
E2E runtime-feature-flag lock recovery after a killed worker (handle leak, fixture scope)
#3257 (open) — after #3077. Normal teardown releases the lock; a forcibly killed owner can leave a lock file, and later runs then fail closed after six minutes. Recovery must preserve mutual exclusion, avoid the handle/fixture-scope leak, and never delete a live worker's lock
BulkStudyUpdateJob.ApplyProgress duplicates retained parse errors at every checkpoint; UI authoritative-total wording
#3318 (open) — Project Management fix plus a UI wording pass so retained-sample counts are never read as the authoritative error total
Processing polish: mobile header semantics, scrollable diagnostics, outage warning on empty page, pinned-row position, late-arrival freeze