Skip to content

Programme integration and strategic changes

Temporary planning document; planning only. This page authorises no code, flag change, migration, deployment, GitHub write or message to anyone. Existing programmes keep ownership of their code and PRs (OPS1, OWNER; integrated plan §7 rule 4). Everything here that is not tied to an owner decision ID is PROPOSAL. Questions cite the Batch D IDs in open questions; this page mints none.

1. Purpose, status and refresh time

Chris asked on 3 October that "study allocation, study pool partitioning, active reviewer tracking, materialised stats and notification service which are at various degrees of completeness are also reviewed, integration with plan taken into account and any changes to those implementations are strategically suggested and thought through". This page does that. For each programme it gives: the current state with evidence; how the plan integrates with it (touch points, conflicts, double ownership, missing contracts, unstated assumptions); the changes recommended to the existing implementation, each with rationale, timing against the plan's releases and freeze gates, compatibility, risk, PR slicing and owner; what should not be built further yet; and what the plan adopts from the programme. It also covers progressive batches, the paused eligibility programme, authorization, the architecture review (#3961) and the smaller prerequisites, and it replaces the plan's external-joins table (integrated plan §5.11).

It resolves every finding in the round-2 programme reviews AP, RT, MS and NS, plus DS-02 and DS-04, PH-02, PH-04, PH-09, PH-14, PH-15 and PH-20 and DC-05 and DC-21. The per-finding outcome is in the resolution record.

Companion documents own the mechanics and are linked, not repeated: consistency model (transactions, Study.CanonicalSummary, the command ledger, ownership markers, durable effects, the FEAT-024 source-write seam mechanics), versioning model, domain model (aggregates, claim value object, enrolment record) and delivery operating model (gates, critical path, F1a/F1b/F1c). Where this page says "F1a", it means the engine-contract freeze of that split; before the split lands, read it as F1.

Precedence: the owner ledger, then decision register §1.11 to §1.14, then the round-2 adjudications, then this page's proposals.

Decision recorded after the brief: Chris decided D1-01 on 3 October: keep #3964, port the active-member check and its tests from #3969 into it, and close #3969 (OWNER; the ledger owner records it in register §1.11). It has been carried out: #3964 merged at 20:27 BST (merge commit 85e6facf7) and #3969 is closed (refresh row "Re-check 20:35 BST" below). Later that evening Chris approved D1-02 to D1-09 as recommended (register §1.13), so no D1 question is open. Late that evening he set the date for activating #3987's families (D1-03, register §1.14): from 5 October 2026, staging first, then production the following week. The production date is a target: production enablement keeps its own approval (D1-04) and waits for X-STATS-b1 to b7 (§7.2). Still to come: the stack owner's agreement to restack #3965 onto #3944 (D1-09, A-33). The live-state refresh rows below were not re-read for this update.

Refresh (live state used throughout):

Source Read at State
GitHub PRs and issues (gh pr view, gh issue view, gh pr list) 14:18–14:30 UTC (15:18–15:30 BST), 3 October Per section
main 14:18 UTC de3e98c59. Since the reviewers' 0f5c61073: #3967 (invitation token matching, 886089c5d), #3971 (production promotion blocked on main integration tests), #3970, #3968. None touches allocation, tracking, eligibility, batches or statistics code; #3967 changes invitation-token matching, which #3938's invitation capture also touches (conflict not checked)
cluster-gitops 14:20 UTC The local checkout 7c377aa4 (2 October) is 55 commits behind its remote-tracking origin/main ea972fd6 (13:37 UTC, 3 October). Environment values here come from ea972fd6 (read with git diff, no fetch). The round-2 reviewers read 7c377aa4. One material difference: staging now pins materializedProjectStatisticsFold: true on API and PM, with SYRF__ProjectStatistics__Fold__AllowPartialCoverage on the API (§7.1)
Local worktrees (read-only) 15:20 BST #3964: three commits not yet pushed (ce83b232a, 8daad48ca, 1688b4f94). #3965: four commits not yet pushed (4f19b5672…b30ba5d2e) plus uncommitted edits in progress. #3969: one pushed WIP commit. Stack top ae3c6749d
Re-check 19:25 BST main eb93caffa (one commit since de3e98c59: auth-migration E2E live rows, #3994; outside every programme here). #3965 now has eight commits not yet pushed (local head 8432e3aeb; the fix round adds all-or-nothing thread creation, a content-free "questioned" marker for every reconciler, stable reviewer labels and typed reply refusals; the reconciler self-review check is still same-stage only). #3964 unchanged (three local commits). #3969 still open. #3961 updated at 18:13 UTC (production-approval controls; a new frontend bug-fix plan, §10). Every other PR state above unchanged
Re-check 20:35 BST #3964 merged at 20:27 BST (19:27 UTC), merge commit 85e6facf7, after an approving Claude review on head 16788f9 and green checks; its worktree and branches were removed. #3969 closed (19:27 UTC) with a comment pointing to #3964 (D1-01 carried out). #3965 open and ready: ten commits pushed, head 986b1cdc2, base codex/checked-pdf-proposals-and-explicit-administrator-a-r6bdl2 (#3947's branch); the tenth commit refuses a reconciler who reviewed the study in any stage (gh pr view, gh api). It waits for the stack (D1-09). Present-tense statements about #3964, #3965 and #3969 on this page use this row

Limits: the drafter of this page made no database reads and no runtime-override reads, and ran no tests or benchmarks. The orchestrating session made two read-only queries on 3 October: a count of stored owner-reserved grants (ChangeOwner, AssignPermissions, Delete) in production and staging, which found none; and a count of legacy reconciled sessions in production, which timed out on an unindexed scan and was not retried (to run off-peak before F4). Claims that could not be checked are marked UNVERIFIED where they appear and listed in the final report.

2. Summary

2.1 Summary table

Programme State (15:18 BST; #3964, #3965 and #3969 at 20:35 BST) Joins (external joins and gates) Top recommended changes Batch D
Proportional allocation and pool partitioning (FEAT-025) Merged, dark. Flag set only in preview pr-3327. Reviewer validity (Chris, 22 September) not met: blocked on #3251. STATUS stale. Never accepted by a human (#3269, #3327) X-AUTH-RESOLVER (#3251) → R3a per-reviewer preview, AL1; F-A; AL1 placed after allocation Phase 2 Membership-facts seam (E64); refuse allocation on canonical stages until AL1 (E65); RA5 scoped admission (E66); one reservation migration (E68) D3-13a, D3-13c, D3-13d
Progressive shared batches (#3936, #3939) Plan open and mergeable; implementation open, conflicting, 63 files; nothing on main X-BATCH (rewritten) ← X-ELIG; X-BATCH → R3c Slice #3939; evidence seam; pool-entry-based membership; durable opening that emits pool-entry events; read-only status; performance gate (E67) D3-13b, D3-13e, D3-13f
Review eligibility (paused) S1a–S4-A and the claim-revoked event merged, dark; #3746 conflicting; #3742 empty draft; #3741 draft. Flag reaches the API host only X-ELIG enumerated (S4-B, S4-C, S6a, S6b, fixed-two correction, Project-token redesign, flag delivery to PM) → R3a production and X-BATCH R3a absorbs S6b; D8 mapping; StageSettings version replaces the Project token; flags to PM with an agreement check (E75) D3-09
Active reviewer tracking, claims and presence Merged; off in every deployed environment; on in the E2E stack; reconciliation excluded; M15 transition has no caller; #3876 deferred X-RECLAIM → R4a in every environment; X-CLAIMS → R2b and R3a production claims; claim contract v2 at F1a Claim contract v2 and one migration (E68); production claims route (E69); R0 and R2a adapters (E70); E2E in both modes; orphan backstop D2-07, D2-08, D3-16, D3-17, D3-18, D3-19, D3-20
Materialised statistics (FEAT-024) Slices 0–7 merged, dark; gate (b) failed on latency on an idle host (3 October, #3510); soak open; staging fold flag now pinned on; production refused in code; no FEAT-024 PR open X-STATS-a → R2c staging pilot; X-STATS-b1…b7 → R2c production; X-STATS-c (target-aware classification) → R2b pilots on allowlisted projects, R3a Source-write seam (R1); target-aware classification at all three fixed-two sites (E71); canonical-sources amendment (E72); protocol 5 once, after gate (b); #3524 after C16 D1-03 (decided), D1-08 (decided), D3-10a–d, D3-11
Notification service Eight stacked PRs plus #3965, none merged; #3932 conflicting; stack E2E never run in CI; #3965's Q-10 code pushed and ready for review, stacked on #3947 X-NOTIF (defined as stack steps 1–5 merged) → notices in R1c, R2c, R3c, R4a, R4b; G-NOTIF per environment and kind family Tolerant preferences before #3942 merges; C15 v2 (E73); enablement controls (E74); merge order with #3965 restacked D1-09 (decided), D3-01, D3-07, D3-21–D3-25
Authorization (#3335) Active; M5b merged; WP1d, WP9, WP11, WP-M1/M2 open; ownership fix #3964 merged (D1-01) X-AUTH-SCHEMA, X-AUTH-ENFORCE, X-AUTH-WP9 → R1b/R1c; X-AUTH-RESOLVER → R3a, AL1 Out-of-request resolver on the single evaluator; presence, study-issue and PDF-correction capabilities in the C10 catalogue D1-01 (decided)
Architecture review (#3961) Draft docs PR; issues #3972–#3990 open; Phase 0 fixes #3967, #3968, #3970, #3971 merged X-ARCH-a (#3985, #3973) → F1a; X-ARCH-b (#3986) → R0; X-ARCH-c (#3975) → R0 and G-NOTIF; X-ARCH-d (#3979, #3980) → R3a One joint roadmap; one writer per collection for canonical collections D1-02 (decided), D1-03 (decided)
Other prerequisites Deletion lifecycle (ADR-014 uncommitted), search import timeouts, flag overhaul P7, job classification, AF2 admission slices, AF2 pdf-tools, FEAT-023 X-DEL, X-IMPORT, X-PDFTOOLS, X-AF2, X-SHELL Per §11 D1-07 (decided), D3-01, D3-12

2.2 Cross-programme integration principles

These five rules are what make the per-programme changes below coherent rather than piecemeal. All are PROPOSAL.

  1. One membership-facts seam answers "who holds what on this study". Eligibility pools, the allocation saved-work exemption and slot rule, typed admission, the capacity guards, batch completion and FEAT-024 classification all read this reviewer's facts from embedded Study fields today. Once canonical sessions, claims and decisions live outside Study, every one of them would answer wrongly. IReviewMembershipFacts (E64) is extracted now with an embedded-Study provider and no behaviour change; the Study.CanonicalSummary provider follows at R0/R2a (shape in the consistency model). The eligibility truth table becomes its conformance suite.
  2. One claim contract. Capacity claims (form slot, profile slot, requested review) and editor exclusion (task, query) share one typed contract (E68), used by tracking, eligibility, allocation and reconciliation, with one reservation migration.
  3. One per-project admission record. R0's admission service (the enrolment record, named CanonicalEnrolment in the domain model) is the single per-project mechanism for canonical scopes, per-project notification admission, tracking pilots, AF2, shell and eligibility per-project admission, and the flag overhaul's P7 targeting. FEAT-024's durable eligibility (#3524) shares its record shape and audit but stays separate, because it must take part in source admission inside the transaction (MS-24).
  4. One durable-effects pattern. Batch openings, claim revocations, notification fan-out, publication phase 2 and adoption record a durable intent in the source transaction and a leased idempotent worker expands it (C19 class b in the consistency model). No frontier or state is recomputed lazily on a read; no in-memory outbox.
  5. One fence primitive, owned by the engine. Publication drain, stage completion and adoption cutover use the engine's scoped fence. FEAT-024's definition-rewrite fence stays a statistics read fence, never a reviewer pause (MS-04).

A sixth rule follows from DS-04 and Q-25: production enablement is per project, not per environment. Each programme's pilot activation reads the same admission record, so a pilot can start, stop and roll back without a fleet-wide flag change (D1-07).

3. Proportional allocation and pool partitioning

FEAT-025, owned by the allocation programme (docs/features/proportional-study-allocation/).

3.1 Current state

Component State Evidence Gaps
Allocation MVP Merged, dark (CODE-MAIN). #2991 (2 September), #3211, #3212, #3213, #3228, #3267, #3329, #3604, #3611; regime identity and provenance #3603 merged 24 September; tie-break performance 7f8b9f4c1 on 3 October gh pr view (all MERGED); git log Admin progress budget (warm p95 ≤ 2,000 ms at 100k studies / 50 reviewers) not met; memoisation (STATUS item 8) not started; regime-evolution Phases 2–5 unstarted (regime-evolution-plan.md, Draft)
Flag proportionalStudyAllocation Default false. Delivered to the API host only (src/charts/syrf-common/env-mapping.yaml, block at :1150 with services: [api], entry :1254). Set only in preview pr-3327 (API, PM and web values); unset in staging and production cluster-gitops ea972fd6 PM never receives the value through Helm
Reviewer validity (Chris, 22 September) Not met. Only active membership is checked at save; the effective-permission check is blocked on an out-of-request resolver STATUS.md:14-23; #3251 OPEN (updated 22 September) Blocks AL1 and the plan's "Who is offered what"
Human acceptance None. #3327 (preview flag vehicle, no code) OPEN, CONFLICTING, last updated 8 September; preview checklist all pending; #3269 OPEN gh pr view 3327; gh issue view 3269 AL1 would build on an unaccepted base
Status document Stale: STATUS.md:71 still says #3603 is "pending review/merge"; last updated 24 September; #3048 is closed (unmerged), not paused STATUS.md:7, 71; gh Refresh before G0
Pool partitioning Merged, inert with the flag off. StudyWorkloadShareBucket.FromStudyId (FNV-1a mod 10,000) persisted as Study.WorkloadShareBucket with index ProjectId_1_WorkloadShareBucket_1; the plan walks 10,000 buckets per request Study.cs:536-553; StageWorkloadSharePlan.cs:39-57 (per AP) Plan memoisation not started
Pools and selection Project-wide predicates minus excluded and own-session filters; random selection is $sample after $match; no StudyLifecycleStatus; FEAT-008's rand field not built ReviewEligibilityPoolFilters.cs:63-115; StudyRepository.cs:763, 801 (per AP); docs/features/stage-filtering/README.md:199-237 No selection budget
Targets Stage.SessionCountTarget falls back to the project screening threshold; enabling allocation materialises the inherited value and locks target and mode edits Stage.cs:177, 360-377 (per AP) Screening threshold doubles as annotation target; no decoupling in the plan's adoption mapping
Legacy partitions Unfinished PartitionSet model and an unflagged study-partitions admin route PartitionSet.cs; project-admin.routes.ts:53-55 (per AP) FEAT-026 warns not to expose them as batches
Open issues #3251, #3252, #3264, #3269, #3321, #3745 (Phase 3: leftover claims under reallocation), #2042 gh issue view (all OPEN) —

3.2 Integration with the plan

Per-reviewer membership projection (AP-01, Corrected). Every allocation and admission read takes this reviewer's membership from embedded data: own ordinary session and status, own claim per activity, own screening decision, and which other reviewers already hold work (for the D8 slot rule) (ReviewEligibilityPoolFilters.cs:77-84, 117-123, 140-150; StageWorkloadShareEligibility.cs:73-84; AllocationClaimSlot.cs:29-39; ActivityReservationAdmission.cs:41-58; StageReviewService.cs:539-541, 676-681; per AP; the orchestrator re-checked the pool-filter reads). E20's "bounded per-bound-stage tallies and flags" would therefore re-offer completed studies, deny an out-of-bucket resume with 404 and undercount slots for every canonical session. Correction: E20 is a per-form, per-reviewer membership projection (per form, one member per reviewer: {state: placeHeld, savedIncomplete, completed or withdrawn; standing: qualifying, needsUpdating, pinnedOlderCounted, pinnedOlderNotCounted or notApplicable; versionSeq; claimActivities; admittingRegimeId; routeStageId}; per profile the reviewer's current decision; derived tallies), carried by Study.CanonicalSummary (brief §1.3; shape and write rules in the consistency model §3.3). draft_only is never written to Study: readers take it from pmFormSession/pmSessionDraft, and it appears on Study only as placeHeld when a Study-writing command recorded it. It is bounded by the SF4 rule (every qualifying candidate), not by the target. The programme-side change is the seam E64. A parity fixture (every truth-table row answers identically from the projection and from embedded data) becomes part of AC-M0-04's C7 identity sign-off.

Allocation on canonical stages (AP-06, D3-13a). The regime evaluator reads stage.SessionCountTarget and ConfigureWorkloadShares requires ReviewMode.Annotation and StudySelectionMode.Annotation (Stage.cs:53-68, 150-191; StageAllocationRegimeEvaluator.cs:97-104, per AP). A canonical stage has neither: the target is form-owned (SF2), the mode is replaced by StageSettings, and the #3732 override is legacy-only. A form-version publication could change the target under a published regime. Recommended (D3-13a): refuse allocation on every canonical stage until AL1: ConfigureWorkloadShares refuses a canonical stage, and R0 refuses to admit a stage whose allocation is enabled unless AL1 is live. A-09 is widened to cover single-stage canonical forms (open questions A-09).

X-AUTH-RESOLVER (AP-02, Adopted). "Who is offered what" (AC-R3a-09) evaluates admission for other reviewers, but the admission service and pool scope take app groups from the current user and fail closed otherwise (ReviewEligibilityPoolScope.cs:55-60, 79-82; StageReviewService.cs:160-166, per AP). That is exactly #3251, open since 5 September. New join X-AUTH-RESOLVER: owner the authorization programme (#3335); the single ProjectAuthorityEvaluator is the natural home (UNVERIFIED that it can evaluate out of request); needed by AC-R3a-09's per-reviewer rows, allocation reviewer validity, allocation Phase 2 and AL1. Until it lands, R3a's Monitor shows pool-level counts by refusal reason with no per-reviewer rows (D3-13d). Evaluating with the administrator's own groups is the "empty-claims approximation" Chris ruled out for allocation.

RA5 scoped admission (AP-03, D3-13c). Under allocation each bucket holds exactly target reviewers and EnforceAnnotationTarget refuses a further place; the pure policy returns AllocationNotAssigned, AtCapacity or AnnotationTargetMet for exactly the requested reviewer (ReviewEligibilityPolicy.cs:279-292, per AP). So RA5 cannot work on allocated or enforced-target stages. Recommended (D3-13c): the AdditionalReviewRequest command writes a requestedReview claim on Study (claim contract v2, E68): single use, expiring, audited; it lets exactly that reviewer past bucket membership, the pool's at-target filter and the capacity guard; it never changes the target; it is recorded as provenance on the resulting session and counted outside allocation progress (E66). It is the same mechanism RT-13 needs (capacity versus target, D3-17).

AL1 placement (AP-10, Adopted). The allocation roadmap has Phases 1–5 (provenance; authority, revalidation and repair; reallocation and protected claims; materialisation; switchover). AL1 sits after Phase 2 (it needs the resolver for reviewer validity on a form-scoped roster) and before Phase 3 (#3745). A form-keyed regime is a regime schema v2 (form binding, target source) with an image floor one release ahead and an updated StageAllocationRegimeCompatibilityCheck (README.md:194-215, the existing v1 floor). F-A's exit evidence adds the allocation owner's signed phase mapping. AL1 also needs #3269's legacy-stage checklist completed on preview or staging (AP-23) and X-AUTH-RESOLVER.

Regime and batch plan by reference (AP-11, Adopted). Following brief §1.12, a canonical StageSettingsVersion carries allocationRegimeId and batchPlanId; regime and plan records stay in their own collections. Embedded Stage.WorkloadShares, the regime pointer on the embedded Stage and #3939's Stage.ProgressiveBatches are legacy-only. PROPOSAL at F3.

Adoption rows (AP-13, Adopted). Migration §3 gains: stage target (override or inherited) → form target, materialised; project agreement threshold → the compatibility profile's collective rule; an enabled legacy regime → a frozen legacy regime record, allocation disabled on adoption unless AL1 is live. Criterion AC-R6-14.

Read APIs on canonical stages (AP-15, Adopted). workload-shares/my-studies, /progress and the editor count "own ordinary saved sessions" from embedded data and gate on ReviewMode.Annotation (delivery-plan.md:39-61, per AP). Until AL1 they refuse canonical stages with a typed reason, and the stage overview shows "Allocation is not yet available for shared forms". From AL1 they read the membership projection (AC-AL1-04..08).

Selection budget and sampling (AP-17, Adopted). Selection is $sample after $match; R3a adds route filters and #3939 adds an $in of up to 10,000 study IDs per Next. FEAT-008 proposed a rand-field range scan and a 400 ms p95 target (stage-filtering/README.md:199-237, verified). AC-R3a-26: selection p95 ≤ 400 ms at 100,000 studies (PROPOSAL, FEAT-008's figure), measured on Bramble; the sampling strategy is decided at F3 from that benchmark and is part of X-BATCH's performance gate.

Legacy PartitionSet retirement (AP-20, Adopted). The first L16 PR removes the study-partitions route, or flags it off; the PartitionSet field stays readable; the inventory records it as "retire".

Flag double read (AP-22, Adopted). The controller passes the runtime-overridable RuntimeFeatureFlags value as applyWorkloadShares, while the default TryAdmitActivityReviewAsync overload reads the process FeatureFlags.ProportionalStudyAllocation (ReviewController.cs:500, 626, 753, 790, 1173, 1443, 1609; StudyRepository.ActivityReservations.cs:19-21, per AP). A runtime override can disagree with admission; with #3975 (overrides reset on each resolution) the disagreement is not hypothetical. Fix in E75: one evaluation per request, passed into admission, with a test.

Stale STATUS (AP-21, Corrected). The inventory rows are refreshed (source-status inventory §2, §3 and §7). The allocation owner is asked for a STATUS refresh before G0 (change A6).

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
A1 Extract IReviewMembershipFacts; re-point pool predicates, StageWorkloadShareEligibility, AllocationClaimSlot, ActivityReservationAdmission, the own-place checks and the capacity pipelines at it; embedded-Study provider first (E64) AP-01, RT-05, RT-06: canonical facts must feed the same policies without a fork Before F1a freezes E20; no behaviour change Nothing persisted; the truth table runs row by row against both providers Low; covered by ReviewEligibilityPoolPredicateTests PR 1: Core seam, embedded provider, truth-table parity. PR 2 (R0/R2a): CanonicalSummary provider Eligibility owner (seam); L1 (provider)
A2 Refuse ConfigureWorkloadShares on canonical stages; R0 refuses to admit a stage with an enabled regime (E65 part 1) AP-06, D3-13a: the evaluator has no valid inputs on a canonical stage Ships with R0's admission service, before R2a writes No data change Low One small PR alongside the R0 admission service Allocation owner with L0
A3 AL1 adapter: regime schema v2 (form binding, target source), floor one release ahead, evaluator reads the form target with an equality check, publication of a different target refused while a regime exists, read APIs and editor on the membership projection, D8 slot rule on form-keyed claims (E65 part 2) AP-06, AP-10, AP-15, AP-16 After allocation Phase 2 and X-AUTH-RESOLVER, before Phase 3; F-A, then AL1 Regime schema bump ⇒ image floor one release ahead; pmStageAllocationRegime keeps unknown elements Medium (floor, start-up check) PR A: schema v2 and floor. PR B: evaluator on form target. PR C: editor and read APIs Allocation owner with L7
A4 requestedReview claim honoured by pool filters, allocation, typed admission and capacity guards (E66) AP-03, RT-13: RA5 otherwise fails on allocated or enforced-target stages Design at F4; build in R4a Additive claim kind and session provenance, behind R4a's flags Low to medium One L6 PR touching ActivityReservationAdmission and the policy facts (AdditionalReviewAdmitted) L6 with eligibility and allocation owners
A5 Out-of-request authority resolver (#3251) on the authorization programme's evaluator AP-02: blocks reviewer validity, AL1 and per-reviewer preview X-AUTH-RESOLVER: before AL1 and before R3a's per-reviewer rows Read-only evaluation Medium (semantics across identity providers) Authorization programme PR; allocation Phase 2 PR consumes it Authorization owner; allocation owner
A6 Refresh STATUS (#3603 merged, #3048 closed, 7f8b9f4c1), and record that the flag reaches the API host only AP-21, AP-08 Before G0 None Low One docs PR Allocation owner
A7 Retire the study-partitions route (keep PartitionSet readable) AP-20 First L16 PR Route removal only Low One web PR L16
A8 One flag evaluation per request, passed into admission (E75) AP-22 Before any environment enables eligibility with allocation Code and a test Low One eligibility-programme PR Eligibility owner
A9 Run #3269's legacy-stage acceptance on preview or staging AP-23: AL1 builds on an unaccepted base Before F-A None Low Acceptance run, no code (refresh or replace #3327's preview vehicle) Allocation owner; L17

3.4 Do not build further until

  • Allocation Phases 3–5 (reallocation, protected claims, materialisation, switchover) against embedded Study: until E20 (membership projection) and claim contract v2 freeze at F1a and X-BATCH freezes at F3 (AP-01, AP-07). Building them now means a second rewrite.
  • Materialised per-reviewer allocation counters (the evidenced next step for #3252's admin read): until F1a decides whether they are a FEAT-024 family or a read of the membership projection. They must never become a third counter store (OPS1: "no competing counters").
  • Any allocation exposure on canonical stages before AL1 (D3-13a).

3.5 What the plan adopts

  • The regime pattern (immutable record, transition log, stage pointer, start-up compatibility check) as the template for StageSettingsVersion and batch plans; #3603's project-revision fence as the StageSettings publication fence.
  • The review-eligibility truth table as the conformance format for C6 and for E64 (also PH-04).
  • Deterministic bucket assignment (FNV-1a mod 10,000 on study ID) as AL1's allocation primitive: no per-study write when a plan changes.
  • Chris's reviewer-validity requirement (22 September) applied to AL1's form-scoped roster.

4. Progressive shared review batches

FEAT-026, owned by the batch programme: plan #3936, implementation #3939.

4.1 Current state

Component State Evidence Gaps
#3936 plan OPEN, MERGEABLE (blocked on review), head a799b538e, 6 files +401/−11, updated 2 October 17:21 UTC (DOC-DRAFT) gh pr view 3936; gh pr diff 3936 Records behaviour it attributes to Chris and three open decisions (denominator, late arrivals, reconciliation)
#3939 implementation OPEN, CONFLICTING, head 62e8101eb, 63 files +3,233/−60, updated 2 October 19:21 UTC (CODE-PR) gh pr view 3939; gh pr diff 3939 Adds Stage.ProgressiveBatches (embedded), three collections (plan, membership, access), and a PM-only flag block progressiveReviewBatchBackendFlags delivering progressiveReviewBatches and reviewEligibilityPolicy; ConfigureAsync refuses unless both flags are on
Completion definition Legacy-keyed: stage tally NumberOfCompletedCandidateSessions against stage.SessionCountTarget, ReviewMode and the project threshold; every project study enrolled (Filters.InProject) #3939 ProgressiveBatchConfiguration.cs, ProgressiveReviewBatches.PrepareAsync Diverges from SF2/R3b for shared forms; later stages' denominators include studies that cannot enter them
Shared opening Recomputed lazily in RefreshAsync on every Next and status read; personal grant is a $max on the access row; nothing emitted, audited or notified. #3936 required a CAS opening plus an outbox #3939 ProgressiveReviewBatches.cs; #3936 technical plan "Concurrency, lifecycle and migration" Amendment A's pool-entry event has nothing to attach to
Performance "Arrival detection still scans current project study IDs with an indexed anti-join; production-scale performance qualification remains a rollout requirement." The 2,500-batch test checks an index plan only #3939 README diff Unqualified
Denominator decision #3939's README states "Chris confirmed this disposition" (excluded studies stay in the denominator and count as finished; restored work returns to its original membership) #3939 README diff, verified Not in the ledger or the register (grep, verified)

4.2 Integration with the plan

The plan's join was "X-BATCH: #3939 merged or completion definition extracted" → R3c, and AC-R3c-05 required readiness "to give the same result as #3939's for the same data". Under SF2 (one form-owned target, one contribution across stages) and R3b (per-profile outcomes) that criterion is unsatisfiable for shared forms (AP-04). X-BATCH is rewritten (Corrected) as six entry criteria, which hold before R3c reuses batch readiness and before any environment enables progressiveReviewBatches:

  1. Evidence seam (AP-04). Completion becomes a pure decision over IStudyObligationEvidence facts (E67). A legacy provider reproduces #3939's current definition; a canonical provider reads the form-unique membership projection (E64) and, from R3b, per-profile outcomes. AC-R3c-05 is reworded: "uses the same definition (seam) and the shared fixtures; results differ only where SF2 or R3b semantics differ, and those cases are listed".
  2. Pool-entry-based denominator (AP-04). Membership and denominators are defined over studies that entered the stage's pool (amendment A's StudyEnteredPool event in StudyPoolLedger), with late entrants appended as separately shuffled cohorts (#3936's proposal), instead of Filters.InProject. From R3a, route filters decide who can enter.
  3. Durable opening transition (AP-05). A shared opening is a compare-and-swap on the plan (plan ID, shared ordinal, revision) and a personal grant a compare-and-swap on the access row. Each writes a pool-entry event (or its legacy capture, E26) and a durable intent for audit and notices (C19 class b) in the same transaction. Transaction rows "batch opened" and "personal batch grant" go to the consistency model.
  4. Status reads never mutate (AP-05). GetStatusAsync reads; openings happen only in commands (a completion hook or an evaluator triggered by evidence change).
  5. Performance gate (AP-05, AP-17). Comparable to #3252: Next p95 at 100,000 studies and 2,500 batches with documented keys examined, bounded opening-evaluation cost, and AC-R3a-26's selection budget, on Bramble, before any environment enables the flag.
  6. X-ELIG first (AP-19). Batches require reviewEligibilityPolicy; the legacy flag-off Next path has no batch integration. Never enable batches in an environment before X-ELIG holds there, with both flags delivered to both hosts (E75).

PRISMA pool entry (D3-13e). Recommended: a study "enters screening" at its first release to anyone; shared openings and personal grants are recorded as separate pool-entry sources, so early-stopped and batched reviews report honestly (amendment A).

Denominator (AP-12, D3-13b). Chris is asked to record #3939's denominator disposition in the ledger (the ledger owner assigns the ID) together with "restored scope returns to its original membership". Until then #3939's README text is PROPOSAL.

Slicing (D3-13f). Recommended: merge #3939 in slices: PR 1 pure completion and progression decisions behind the evidence seam, with truth tables; PR 2 persistence with durable opening and grant transitions emitting pool-entry events; PR 3 Next integration and direct-access checks, held until the performance gate; PR 4 settings UI to Material 3 (UI1). Never activate before X-ELIG in the same environment.

Double ownership of settings (AP-11). For canonical stages the StageSettingsVersion carries batchPlanId (§3.2). #3939's rule "disable the stage before changing batches" maps onto R3c's Completed/Reopen lifecycle, not onto a disabled stage.

Supersession rows (AP-18, Adopted). The inventory's §6 gains FEAT-026's "Do not add a stage closure concept" (superseded by LC1/RX2) and FEAT-008's Filter Set model (decide at F3 whether the Filter Set schema is the route representation; keep the same-profile $elemMatch simplifier as a correctness rule for screeningOutcomes[] filters).

Batches and FEAT-024. #3936 allows materialised rows only "if their contract matches exact batch scope and configured targets". FEAT-024's annotation families classify with a fixed two, so batches use authoritative indexed completion (as #3939 does) until X-STATS-c (§7).

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
B1 Split #3939 into the four slices above AP-04, AP-05, AP-11, AP-19; D3-13f Now, before X-BATCH freezes at F3. PR 1–2 may merge dark once reworked; PR 3 waits for B5 Three collections are additive; Stage.ProgressiveBatches legacy-only Medium: the PR conflicts and its frontier is O(N) per Next Four PRs Batch owner
B2 IStudyObligationEvidence seam with a legacy provider (E67) AP-04 PR 1; canonical provider in R3c None Low Part of PR 1 Batch owner; L4 supplies the canonical provider
B3 CAS opening and personal grant writing pool-entry events and durable intents; status read-only AP-05 PR 2; before any enablement New fields on plan and access rows; intents per C19 Medium PR 2 Batch owner with L12 (event shape frozen at F3)
B4 Pool-entry-based membership with late cohorts; route-aware enrolment from R3a AP-04 PR 2 (legacy pool = project minus excluded); canonical from R3a Membership rows additive; adoption re-bases membership on pool-entry events Medium PR 2 Batch owner
B5 Performance qualification on Bramble AP-05, AP-17 Before PR 3 merges and before any enablement None Low Benchmark PR Batch owner; L17
B6 Record the denominator decision AP-12, D3-13b Before PR 2 merges (opening depends on it) None Low Ledger update Chris; ledger owner
B7 Deliver both flags to both hosts through one block, replacing #3939's partial PM block AP-08, E75 With X-ELIG Generator run only Low Eligibility-programme PR Eligibility owner; batch owner reviews

4.4 Do not build further until

  • 3939's selection integration (Next, direct access, lists) against embedded Study: until X-BATCH

    freezes at F3 and B5 passes.
  • Any reconciliation batching mode (#3936 open decision 3): until R4a's task model (one task per study × form, RE4) exists.

4.5 What the plan adopts

  • The behaviour #3936 records as agreed with Chris (DOC-DRAFT; not yet in the ledger): random, stable, shared membership with a persisted seed; configurable size and threshold; automatic personal advancement that never opens a shared batch; return to earlier work when it becomes eligible; the two saved-session policies.
  • Monotonic access, append-only cohorts, and "a batch grants scheduling access, never permission" (consistent with C10).
  • The copy rule "reviewer exhaustion is never 'Stage complete'" in the copy deck (UX strategy).
  • ProgressiveBatchCompletion as R3c's readiness definition, through the seam.

5. Review eligibility policy

Owned by the eligibility programme; paused (see 5.1).

5.1 Current state

Component State Evidence Gaps
Merged slices Dark behind reviewEligibilityPolicy: settings and claims #3579, S1a #3646, S1b #3659, S2 #3691, S2-C #3695, S3 #3719, S4-A #3732, claim-revoked event with durable intent #3736 (24–25 September) gh pr view (all MERGED) —
Open slices #3746 S6a (server-contract residue, flag-off identity): OPEN, CONFLICTING, updated 1 October. #3742 S4-B (D7 migration tooling): draft, 0 files. #3741 S5 audit: draft, 3 files. S4-C (settings API and editor) and S6b (browser consumption) have no PR gh pr view; review-eligibility-policy.md:36; #3746 body ("The web doesn't consume them yet (S6b)") —
Flag Default false; unset everywhere; delivered to the API host only (env-mapping.yaml:1150, entry :1243) Verified Core code that branches on it runs in both hosts; #3939 had to add a PM block. Whether any PM-hosted writer branches on it today (for example #3695's fences) is UNVERIFIED
S5 audit (branch, DOC-DRAFT) The browser never reads eligibility; #3551's veto remains; two revocation causes; no E2E or staging acceptance; fixed-two "not done" (D3a.2); D8(2c) deferred to #3745 AP §2, citing the audit branch —
Hotspot With the flag on, every review write also writes Project.StatisticsAdmissionToken ("the same real project-token write the reviewer save uses"); measured with statistics off: 199 of 500 submissions exhausted at five reviewers on different studies, 700 of 1,000 at ten StudyRepository.ActivityReviewWrites.cs:87-91 (verified); STATS screening-write-benchmark.md:549-554 (per DC-05) R3a would inherit it
Pause "Paused 25 September until the statistics work finishes" is recorded only in session memory; the policy document (updated 25 September) does not say so; #3746 had activity on 1 October UNVERIFIED in the repository; A-31 —

5.2 Integration with the plan

X-ELIG enumerated (AP-09, Corrected). X-ELIG listed the flag, the reservation migration and the D7 tool. It now lists each slice with an owner and a "merged or extracted" criterion:

Item What Criterion
S4-B Audited D7 migration tooling (dry-run plan, CAS apply, rollback, audit), targeting claim contract v2's key (E68) Merged and rehearsed, or replaced by an authorised count-only check showing nothing to migrate (A-32)
S4-C Grouped-configuration settings API and editor Merged, or absorbed by R3a's stage designer for canonical stages (legacy stages keep today's editor)
S6a Server-contract residue and flag-off identity (#3746) Rebased and merged
S6b Browser consumption of the eligibility response (removes #3551's veto) Absorbed by R3a as part of AC-R3a-03 if the programme is still paused at F3 (D3-09)
Fixed-two correction S5 row D3a.2; FEAT-024-owned for the statistics families X-STATS-c (E71)
Project-token redesign Admission never writes a per-project document per save (DC-05, MS-19) Agreed with the eligibility owner at F3; AC-ALL-26 (C18-T02) with eligibility on
Flag delivery Both flags reach API and PM with a cross-host agreement check (AP-08) E75 merged

D8 mapping (PH-04, D3-09). Eligibility D8 (Chris, 24 September) has five sub-decisions; none was mapped. Recommended mapping, put to Chris in D3-09:

D8 sub-decision Mapping in the step model
(1) Removing own annotation on a disabled stage or after a mode change is allowed "until annotation versioning replaces deletion" Legacy scopes unchanged. Canonical sessions are never deleted: the action becomes withdrawal or audited draft discard (C5); D8(1)'s own sunset clause applies
(2) Leftover claims outside the reviewer's allocation are released with a warning Carried into claim contract v2: the release is typed admission's job; under reallocation (#3745, allocation Phase 3) claims count as protected work. AL1 inherits it
(3) A disabled stage cannot be opened Holds for the stage route. A shared session stays reachable through another active bound stage (SF1); the refusal is per route, not per session
(4) Hidden excluded saved work mirrors HideExcludedStudiesFromReviewers Applies per route; EW1 (saved-work completion after collective exclusion) governs canonical stages, with the hide setting presentation-only
(5) An administrator may reconcile before readiness, with a warning Carried into R4a as a non-blocking ReconciliationNotReady warning on the task; readiness still gates the reconciliation pool

The truth table (generated, with Mongo-seeded row tests) is extended with step and route columns rather than replaced by a separate C6 table (PH-04).

Flags delivered API-only (AP-08, Adopted). reviewEligibilityPolicy and proportionalStudyAllocation sit in the API-only block. Enabling one host but not the other is the split-brain hazard FEAT-024 guards against with its durable reviewer-mode epoch. E75: move both to a block delivered to API and PM, add a cross-host agreement check (durable mode, as for tracking), inventory PM-hosted branches, and record in the inventory that both are API-only today.

Project-token hotspot (DC-05, MS-19, Corrected). R3a's admission "extends ReviewEligibilityPolicy", whose save path writes the Project token per review write. Canonical admission must never write a per-project document per save. Default design (PROPOSAL, chosen at F3 with evidence): admission checks the bound StageSettingsVersion (its ID and revision) in the Study write filter, and a settings change publishes a new version under the engine's scoped fence with a drain, so in-flight admissions either see the old version and commit before the fence or retry against the new one. FEAT-024's fold deferred item 2 (async-point-fold-design.md:1899-1902) is handed to L4/C6 at F3. The write gate (AC-ALL-26, C18-T02) runs with eligibility on.

What R3a absorbs (D3-09). If the programme is still paused at F3: S6b's browser consumption (as part of AC-R3a-03), and S4-B, S4-C and S6a only as far as R3a needs them for canonical stages. If it resumes, those slices return to it and R3a's scope shrinks (A-31). Either way the eligibility owner's C6 checklist passes at F3, run by a fresh-context agent; Chris rules on exceptions.

One reservation migration (AP-07, Adopted). E18 re-keys claims to study + form (+ profile) + reviewer; X-ELIG already needed a migration of untyped reservations. Two migrations would touch the hub, the consumers and the FEAT-024 fold twice. One migration (S4-B as the vehicle) targets claim contract v2's final key with stage provenance (E68); see §6.2 for its consumer inventory. RT-23 adds that production probably holds no untyped reservations at all, because claims exist only with tracking on (A-32); an authorised count-only check per environment replaces a migration rehearsal where the count is zero, and eligibility is switched on before tracking.

RA5 (AP-03). The pure policy's facts gain AdditionalReviewAdmitted (E66, §3.2).

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
L1 Membership-facts seam (A1, E64) AP-01 Before F1a None Low As A1 Eligibility owner
L2 Deliver both flags to both hosts; cross-host agreement check; inventory PM branches; one evaluation per request (E75) AP-08, AP-22 Before any environment enables eligibility Helm mapping and generator only; no data Low One PR (replaces #3939's partial block) Eligibility owner
L3 S4-B targets claim contract v2's key; count-only check first AP-07, RT-23, A-32 Key frozen at F1a; executed with X-ELIG Migrates untyped → typed and stage → form keys once; FEAT-024 reservation fold kinds bump the protocol (IntroducedAt) Medium (hub, consumers, fold) S4-B; FEAT-024 protocol change separately Eligibility owner; FEAT-024 owner; presence owner
L4 Replace the Project token with a StageSettings-version check for canonical admission DC-05, MS-19 Design at F3; build with R3a Behind the flags; legacy path unchanged until migrated Medium One PR in R3a (or the eligibility programme if resumed) L4 with eligibility owner
L5 Extend the truth table with step and route columns; encode the D8 mapping PH-04, D3-09 F3 Generated table and row tests Low One PR L4 with eligibility owner
L6 Rebase #3746 (S6a) or close it in favour of R3a AP-09 Before F3 — Low Owner's call Eligibility owner

5.4 Do not build further until

  • S4-B's migration of legacy untyped reservations: until a count shows any exist (RT §4 hold list) and claim contract v2's key is frozen.
  • New ReviewActivity values on v1 typed pages: until claim contract v2 freezes.

5.5 What the plan adopts

The facts → policy → decision pattern with stable reason codes; the generated truth table and its Mongo-seeded row tests; typed refusals with bounded server-authored details (#3746); the claim-revocation outbox and targeted event (#3736) for every revocation; guarded settings saves with "Apply anyway" (#3579) as the D6 conflict flow.

6. Active reviewer tracking, claims and presence

Owned by the presence owner (named at G0; the tracking PRs so far — #2467, #3008, #3014, #3719 — came from Chris's sessions).

6.1 Current state

Fact Evidence (verified unless marked)
The effective mode is ActiveReviewerTrackingAvailable = ActiveReviewerTrackingEnabled && SignalRActive src/libs/kernel/SyRF.SharedKernel/Settings/FeatureFlags.cs:35-36
Claims, capacity guards and typed admission exist only when tracking is effective. With it off, no claim is created, annotation and screening saves are unguarded and EnforceAnnotationTarget does nothing RT-02 (StageReviewService.cs:220-221, 331-335, 631-634; SubmitAnnotationSessionService.cs:321-337; StudyRepository.cs:2056-2073, 2523-2525)
Off in every deployed environment (unset in production, staging and preview) cluster-gitops ea972fd6: no activeReviewerTrackingEnabled key; appsettings default false (API appsettings.json:33, PM :35)
On in the E2E stack appsettings.e2etest.json (API :130, PM :83)
Delivered to API and PM env-mapping.yaml:1593 (services: [api, project-management])
Reconciliation is excluded at every layer: it reserves nothing and resolves no capacity mode StageReviewService.cs:67-70, 153-156; presence policy turns off in reconciliation mode (stage-review-presence-policy.ts:17, per RT)
Turning it on is a FEAT-024 durable reviewer-mode transition (capacity writes fail closed with a typed 503 on disagreement); AdvanceModeEpochAsync has no production caller (M15) .claude/rules/materialized-stats.md "Durable reviewer-mode epoch"; ProjectStatisticsWriteEpochLifecycleService.cs:370 (only tests call it)
Every surface is keyed by stage: reservation natural key (InvestigatorId, StageId), the two-activity page, connections, the unique current-presence index, hub signatures, DTOs, scheduled commands (held in Quartz up to 2 h) RT §2 and RT-11
PM ignores runtime flag toggles #3360 OPEN
Per-stage tracking was deferred from the fold MVP by Chris on 1 October #3876 OPEN (body: "Decision: Chris, 2026-10-01: defer from the MVP and do it as a follow-up")
Production over-allocation report still open; synthetic coverage only #2446 OPEN; #2565 CLOSED; epic #2470 OPEN
Design documents are stale: the feature page's narrative still describes ActiveReviewSession; the redesign document is Draft (April) docs/features/signalr-active-reviewer-tracking.md:102-160; review-session-model-redesign.md front matter

6.2 Integration with the plan

X-RECLAIM replaces X-TRACK (RT-01, Blocker, Corrected). RA1 (OWNER): "Normal pool work uses existing active-work tracking to prevent two reconcilers simultaneously editing the same study." Tracking gives reconciliation no protection at all, yet the plan accepted "tracking enabled" as an R4a production route and had no criterion testing editor exclusion. X-RECLAIM is a reconciliation-task editor claim (CAS plus lease on the task, atomic "Start reconciling", assignment and release hooks for RA3/RA4) that works with tracking off. Owner: L6 with the presence owner; frozen in C9/C7 at F4; required for R4a in every environment. E6 is reworded from "if tracking stays off" to "always". Criteria AC-R4a-36 to 39. It is the RA1 mechanism; "tracking enabled" is no longer a sufficient R4a prerequisite. The editor claim is the taskEditor kind of the claim contract (query review reuses it as queryEditor, AC-R4b-09).

Q-25's tracking line was wrong (RT-02, Corrected; D3-16). Q-25 said tracking is "not needed for slot-reservation claims". The reverse holds: claims exist only with tracking on. So R2b's claim re-keying, R3a's reservation admission and every capacity promise do nothing in production. Q-25 keeps its decision; its tracking cell gets a correction note (open questions, Q-25 correction), and the production claims route goes back to Chris as D3-16. Recommended (a): reshape #3876 into a binding-scope tracking setting and enable it per admitted pilot project (and for legacy projects that opt in, addressing #2446).

X-CLAIMS (RT-02, RT-03, Adopted). New production prerequisite for R2b claim behaviour, R3a's reservation admission and any capacity promise. Jointly owned by the presence owner and the FEAT-024 owner. Evidence: (a) the route D3-16 chooses is built: the binding-scope setting enabled for the pilot, or the M15 transition wired and rehearsed on staging; (b) API and PM switched together in one static configuration change (#3360); © load and failover runs AC-T-03 to AC-T-07; (d) the orphan backstop AC-T-06; (e) the affected E2E flows pass in both tracking modes (AC-ALL-23). Staging cannot rehearse a fleet-wide switch today because FEAT-024 writes are on there; whether the global control row exists in staging is UNVERIFIED.

Claim contract v2 at F1a (RT-11, Adopted; E68). A v1 typed page holds one screening and one annotation claim, but R3a stages can hold several form and profile steps, and a claim shared by two stage tabs must live until the last tab closes. The contract (value object in the domain model):

  • A claim is {kind, scopeId, routeStage, routeStep, reservedAt, allocationRegimeId, leaseExpiry, holders}; kind ∈ formSlot, profileSlot, requestedReview, taskEditor, queryEditor; unique per (study, kind, scope, reviewer); released when the last page using it ends.
  • Capacity claims (formSlot, profileSlot, requestedReview) live on Study, so the atomic guard still works; editor claims live on their own aggregates (ReconciliationTask, QueryWorkItem).
  • Claims key on form identity, never form version.
  • Versioning: new hub methods (for example JoinReviewContext) rather than new parameters until MinUiVersion moves (NotificationHub.cs:109); additive DTO fields; new command contracts, with the old handlers kept for at least the suspension grace (2 h) plus the idle timeout; presence index migration by create, read both, drop. Legacy v0/v1 pages stay for legacy scopes.
  • One reservation migration with the eligibility programme's S4-B (AP-07). Consumers of the (InvestigatorId, StageId) key, each with its cutover: pool predicates, the D8 slot rule (AllocationClaimSlot), typed admission, Study.GetSlotReservation, the FEAT-024 reservation fold kinds (protocol bump with IntroducedAt), pmReviewerPresence's unique index, the hub join, the idle, suspension and liveness consumers and their scheduled commands, and claim-revocation intents.

R0 floor and inventory (RT-07, RT-08, Corrected). SessionTallies are recomputed on every load (stored values ignored) and the claim pipeline recomputes the allocated total as candidates plus reservations (ExtractionInfo.cs:31-80; StudyRepository.cs:2913-2916, 3217-3227, per RT and DC-02). A binary that does not merge canonical counts writes them away on any whole-Study save or claim. R0 therefore ships reader logic, not only tolerant maps: the tally getter and the claim pipeline merge CanonicalSummary's form-keyed counts and per-reviewer markers, inert until a canonical writer exists (mechanics: consistency model); AC-R0-09. The R0 writer and reader inventory gains tracking's Study writers (hub join, leave, dirty/clean, disconnect and prior-study release; PM idle, suspension and liveness consumers; the claim pipelines and typed admission; the direct-navigation claim; the screened-reservation release; the guarded settings save's "Apply anyway"; the reservation restore on session deletion) and readers (the presence snapshot; FEAT-024's availability calculators), each with a route, refuse or adapt decision. AC-R0-02 gets one test per writer, including "a stage-keyed claim on a canonical form is refused or translated".

R2a is not claim-free (RT-05, Corrected). A-21's basis ("keeps R2a free of claim, tally and allocation joins") is rewritten. R2a keeps claims stage-keyed (one stage per form) but must change: own-place detection (through E64, so a returning reviewer with a canonical session is not treated as new); claim release on the first explicit Save or Complete, inside the canonical transaction; presence FormSessionId (additive; legacy field kept); and "dirty" meaning C5's draft-changes flag, not AF2's pristine state. At F1a the presence owner's checklist for the R2a adapter passes, run by a fresh-context agent; Chris rules on exceptions. AC-R2a-35 to 37 and AC-R2a-06.

Draft holds the place? (RT-09, D2-07). RT recommended "yes until Save or Complete, discard, admin release or 14 days"; PH-03 said "never". The adjudicated middle ground (brief §1.8, D2-07): a draft keeps the place while the reviewer is active, under today's idle and disconnect timers counting draft activity; when they lapse the place is released but the draft is kept; the reviewer may still Complete it as an extra contribution (SF4's target is a minimum) unless an optional capacity cap applies (D3-17), in which case they are told honestly and may keep or discard the draft. Release paths read whether a draft exists in the same snapshot; autosave never writes Study.

Draft lease on connection identity (RT-10, Adopted; D2-08). The lease is held by a stable client tab ID (sessionStorage) recorded on both the draft and ReviewSessionConnection, renewed by a REST heartbeat (the bulk-PDF lease pattern, BulkPdfUploadController.cs:368-375) or the hub heartbeat when tracked. It works with tracking off. Other tabs are read-only, with an explicit "Take over editing" that ends the old lease (D2-08). Draft mechanics (etag, write sequence, conflict copy) are in the consistency model.

Capacity versus target (RT-13, D3-17). SF4 makes the form target a minimum; today one number is both minimum and cap. Recommended (D3-17): an optional capacity cap (stage or route policy), off by default; when on it defaults to the form target, never limits requested extra reviews, never evicts existing work. Sessions that hold a place: a draft-backed claim while active (D2-07), saved incomplete, completed, Needs updating; withdrawal frees the place. The requestedReview claim lets exactly the requested reviewer past the pool and capacity filters (AP-03, E66).

Shared-form tracking settings (RT-12, D3-18, extends Q-28). EnforceAnnotationTarget, IdleSessionTimeoutMinutes, MaxInProgress and #3876's proposed setting have no rule for shared forms. Recommended (D3-18): the most restrictive bound stage sets the cap and the idle timeout; the stage in use sets the in-progress limit, counting a shared session once; the form is tracked if any bound stage is. #3876 is shaped as a binding-scope setting, not a per-stage one. AC-R2b-09, AC-R2b-10.

Dependent steps (D3-19). Recommended: no place is held on a dependent form while the reviewer is still screening; the claim is taken at Include; if refused, the reviewer sees "Enough reviewers" for that step and keeps their screening decision (matches D6's "acquire only currently eligible activities"). AC-R3a-25.

Presence disclosure (RT-14, D3-20). Presence snapshots carry the investigator ID of every reservation holder to every member who can view studies, whatever the stage's blinding (StudyReviewPresenceSnapshot.cs:86-96, per RT). Realtime presence is added to C10's channel list (Adopted). Recommended disclosure rule (D3-20): reviewers see counts and their own place; names only for holders of the Monitor capability; never across reconciliation blinding (BL1). AC-T-08.

E2E in both modes (RT-04, Adopted). E2E runs tracked; production runs untracked; "existing specs pass with flags off" proves the wrong behaviour. AC-ALL-01/02 state the tracking mode, and the affected review-flow specs run in both modes as a Playwright project matrix for R2a, R2b, R3a and R4a (AC-ALL-23).

Orphan backstop (RT-24, Adopted). A lost scheduled removal (Quartz has lost its RabbitMQ connection after a pod restart before) leaks a claim indefinitely, and claims made dirty and left when the flag is turned off count again when it is turned back on. Before X-CLAIMS: claims carry an absolute leaseExpiry that guards treat as free once passed, or a bounded sweep runs behind its own flag. AC-T-06.

Completion, revocation and target reduction (RT-18, RT-19, RT-20, Adopted). Stage completion withdraws that stage's claim references through the claim-revocation outbox (#3736); the claim survives if another bound stage still uses it; the client keeps its unsaved changes (AC-R3c-14). Publishing a lower target, or switching enforcement on, goes through D6's conflict flow ("Apply anyway"), revoking the most recently acquired claims first (AC-R2c-19). Revoking a grant releases temporary claims through the outbox; drafts are kept (AC-R1c-10).

Retention (RT-21, Adopted). pmReviewerPresence ("preserved indefinitely") and pmReviewSessionConnection join E32 with retention rules, and join adoption manifests if they become exposure evidence (retention text in open questions E32).

Stale documents (RT-27, Adopted). Before F1a the presence owner lands a docs-only PR (T1).

Other tracking rules. Transaction rows for claims and presence (RT-15) go to the consistency model: the first explicit Save/Complete also releases the claim and closes and opens presence; claim, release and expiry write the Study claim set plus the FEAT-024 part or fold entry; task claim and release write the claim plus assignment state; the pinned command-budget tests (FoldSaveCommandBudgetTests, ProjectStatisticsFoldCommandBudgetTests) are named for update. AC-R2b-03 is rewritten (RT-16): two tabs through stages A and B hold one claim; closing either keeps it; closing both releases it once. Q-20's publish-pause warning needs a form-scoped "who has form F open now", which needs presence keyed by form and tracking on; the minimal version drops the warning (RT-17). The copy deck gains the slot vocabulary ("review slot", "released", "offline", "enough reviewers") and the draft/claim rule (RT-22, UX strategy). No notice per claim; one workload notice per reviewer per plan change; revocation events keyed by claim scope (RT-25, C15). Exposure reports travel in the draft, Save and Complete REST payloads, never over the presence hub, which is off in production (RT-26, C3).

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
T1 Update the delivered contract: claims only when tracked, typed claims, reconciliation excluded, FEAT-024 mode coupling, stage-keyed surfaces; mark the redesign Implemented; retire the April planning documents RT-27; Q-25's error came from stale documents Now, before G0 None Low One docs PR Presence owner
T2 Give M15 an owner: expose the reviewer-mode transition (AdvanceModeEpochAsync, the epoch batch, the 5-minute grace) through an admin route, with a runbook and staging rehearsal RT-03: no environment with FEAT-024 writes can turn tracking on today Only if D3-16 chooses (b); before any tracked pilot on staging Fleet-wide invalidation then family rebuild; static configuration on both hosts (#3360) Medium (a) route and batch wiring; (b) runbook and rehearsal FEAT-024 owner with presence owner
T3 Reshape #3876 into a binding-scope tracking setting (effective per stage-settings binding; tracked if any bound stage is) RT-03, RT-12: per-project pilots without a fleet-wide transition; works for shared forms Design at F1a with E68; build before X-CLAIMS (D3-16 a) Default keeps today's behaviour; a change is an ordinary scoped statistics invalidation Medium (a) setting and read paths; (b) scoped invalidation; © settings UI Presence and FEAT-024 owners
T4 R0 floor: tally getter and claim pipeline merge canonical counts and markers; claim and presence writers check canonical ownership (E70) RT-07, RT-08 R0, before R2a writes Inert until a canonical writer exists; tests interleave R0 and R2a writes Medium (hot path) (a) getter merge; (b) pipeline; © ownership checks in tracking writers L0/L1 with presence owner
T5 R2a adapter: own-place detection via E64; claim release on first explicit Save in the canonical transaction; presence FormSessionId; dirty = draft-changes flag; draft-aware release per D2-07 (E70) RT-05, RT-09 R2a Behind R2a flags; additive presence field Medium (a) server release and own-place; (b) presence link; © client dirty semantics (L5 order) Presence owner with L1/L5
T6 Claim contract v2: versioned hub methods, DTOs, commands; presence index migration (E68) RT-11 Freeze at F1a; build against fakes in W1; ship with R2b New hub method beside the old ones; additive DTOs; old handlers kept ≥ 2 h + idle timeout; presence index create–read-both–drop High (a) domain claim set and Study class map; (b) canonical claim pipeline and guard; © hub and DTOs; (d) commands and consumers; (e) presence and connection keys plus index; (f) web store Presence owner
T7 Draft lease on connections: stable tab ID, REST heartbeat, take-over (E70) RT-10; must work untracked Contract at F1a; build in R2a New fields on ReviewSessionConnection (already tolerant of extra elements) Medium (a) tab-ID plumbing; (b) REST lease endpoints; © take-over UI L1/L5 with presence owner
T8 Reconciliation task editor claim (CAS plus lease), atomic "Start reconciling", assignment and release hooks; requestedReview claim on Study RT-01, RT-13 Contract at F4; build in R4a New aggregate fields; the reconcile host joins presence through a new method Medium (a) task claim and lease; (b) atomic start; © assignment interplay; (d) requested-review claim; (e) host states L6 with presence owner
T9 Orphan-claim backstop plus a load and failover run on Bramble (E69) RT-24; scale unknown Before X-CLAIMS None Low to medium (a) lease expiry or sweep behind its own flag; (b) benchmark Presence owner
T10 Shape presence payloads by the disclosure rule (counts plus the recipient's own claim) RT-14, D3-20 Before any tracked pilot with blinding, and before task claims reach presence Additive; the client still finds its own claim Low One PR Presence owner with authorization owner
T11 E2E in both tracking modes (E69) RT-04 Before R2a acceptance (W2) None Low Playwright project matrix L17

6.4 Do not build further until

Claim contract v2 freezes at F1a: #3876 as a per-stage setting; any new ReviewActivity value on v1 pages; the admin presence dashboard (#2470's admin-visibility item, redesign phase 6) on stage-keyed presence; analytics on ReviewerPresence.AnnotationSessionId; in-place hub signature changes; the migration of untyped reservations in #3742 until a count shows any exist.

6.5 What the plan adopts

ADR-008's absolute server timestamps and shared REST/hub access result for every new timer (RA3 expiry, draft retention, leases); generation tokens and baselines that reject stale scheduled deliveries; AccessDenialReason extended with append-only values (step locked, stage completed, task held, place held by draft) rather than a parallel enum; the claim-revocation outbox (#3736) for every revocation; the version-guarded reservation save with its statistics part (ReservationChangeSave, #3738) for every claim write; the fail-closed pattern for fleet-wide modes; the suspended-sessions handover's "no false positives" criteria (docs/planning/session-capacity-suspended-sessions-handover.md:291-299).

7. Materialised statistics

FEAT-024, owned by the statistics programme (docs/features/materialized-project-statistics/).

7.1 Current state

Component State Evidence Gaps
Foundation: 10 metric families, 7 scope kinds, control rows, guards, fences, receipts, outbox, checkpoints Merged, dark ProjectStatisticsMetricFamily.cs:10-41; ProjectStatisticsScopeKind.cs:11-45 (per MS) StageQuestionVersion scope reserved, never written; ProjectQuestion is question-only grain
Transactional write path Merged; measured FAIL (all eight cells over the 10% p95 gate, 46%–1,283%; 25 commands per save) STATUS.md:364 (verified) Not viable for production; still the active path wherever writes are on and fold is off
Async point fold, slices 0–7 Merged (slice 7 docs #3962 at 06:13 UTC; #3956 at 05:53 UTC) gh pr view 3962 3956 (MERGED) STATUS still lists slice 7 "In review" (STATUS.md:386)
Fold protocol and N-1 window Protocol 4 = production baseline P0; stamp advance built; StampAdvanceAllowed unset ProjectStatisticsFoldProtocol.cs (per MS); rules file Each breaking bump needs reset and backfill per project
Gate (b) Fail on latency. Provisional fail on a loaded host (14 of 16 cells missed latency); the idle-host rerun on Bramble (3 October, 22:38–23:01 UTC, c45648aeb) also failed on latency in every contended cell, with zero statistics-caused or fold-caused conflicts (#3510) STATUS.md:393-404 (verified) Likely lever is the pre-write durable-mode re-read (design owner's call)
Production No statistics keys in production values; fold enable refused in code for syrftest and unknown databases cluster-gitops ea972fd6; ProjectStatisticsFoldAdministration.cs:71, 241 (verified) Lifting it is "a separately approved production rollout, never a feature PR"
Staging (new since the review) Writes, Serving, ProjectScreening on; allowlist project …0102. materializedProjectStatisticsFold: true now pinned on API and PM ("staging pilot only (Chris, 2026-10-01) … a project still needs an explicit admin enable"); SYRF__ProjectStatistics__Fold__AllowPartialCoverage: "true" on the API; a statistics operator client (#3908) in staging Identity cluster-gitops ea972fd6 (staging/api/values.yaml, staging/project-management/values.yaml, staging/identity/values.yaml) STATUS on main still says "the fold flag is off in every environment … no project has fold mode on" (STATUS.md:372-374). Whether project 0102 is in fold mode, and whether the pending index is built in staging, is UNVERIFIED (no database read)
Preview No statistics keys preview/preview.values.yaml Preview pilots have no FEAT-024 at all
Per-project admission Static allowlist on both hosts; durable eligibility #3524 planned, not built ProjectStatisticsAllowlist.cs:16-30 (per MS); #3524 OPEN Eligibility must take part in source admission inside the transaction
Soak Open: #3510 and #3952 (isolated e2e stack on Bramble, per the 30 September decision) gh issue view (OPEN, updated 3 October) Not started
Correctness follow-ups #3840 OPEN again (flag coupling unfixed in code); #3960 (drift re-persist); #3845 (parity calculators); #3849; #3846; #3953; #3910 gh issue view (all OPEN) #3840 blocks QuestionAnswers/ReviewerAnnotation unless both annotation flags are on; #3960 blocks Membership/ReviewerScreening in production
Version constants Backfill families inherit ProjectScreening's version constants #3506 OPEN Must be fixed before new families
Fixed two AnnotationThresholds.MinimumNumberSessions = 2 is an input of the shared configuration digest (annotation.v1:mns=); also StudyStats.cs:353-355 and StudyRepository.GetSessionFilter (StudyRepository.cs:1665, 1712, 1725, #3979) Verified at all three sites A target-aware change is a digest-format change
Open FEAT-024 PRs None gh pr list (15:25 BST) —

7.2 Integration with the plan

X-STATS-b is a chain, and Q-31(b) is the designed first pilot path (MS-01, Corrected). "FEAT-024 production readiness for the usage family (gate (b))" names one gate for a chain of seven. By the plan's own windows (W3: "R2c ships (production per Q-31)") the Q-31(b) path, authoritative counting and identity enumeration at the protected boundary, will be the first production path, so it is designed as the first pilot path, not an exception; the materialised family is a swap-in behind the same interface. This applies Chris's Q-31 decision (OWNER: materialised family as the target, (b) for named pilots if gate (b) is not reached when R2c is otherwise ready); it changes the planning expectation, not the decision.

Step Prerequisite Owner Evidence
X-STATS-b1 Gate (b) passes on an idle host (Bramble rerun) FEAT-024 owner Benchmark record
X-STATS-b2 Soak #3510 via #3952, with #3840, #3960 and #3845 closed for the families in use FEAT-024 owner Soak evidence
X-STATS-b3 Production pending index built in an approved window (operator route) FEAT-024 owner; operator Runbook record
X-STATS-b4 Production rollout separately approved, lifting the in-code refusal; D1-03 decided "activate" (3 October), with production targeted for the week after the 5 October 2026 staging activation; the target does not replace this approval or the earlier steps Chris Approval record
X-STATS-b5 Production per-project eligibility: #3524 built after C16 freezes, or the static allowlist on both hosts for named pilots FEAT-024 owner with L0 Configuration record
X-STATS-b6 Usage family built (E72): new families, scope kinds, onboarding contract, #3506 FEAT-024 owner with L7 Merged code and parity calculator
X-STATS-b7 Usage family proven on staging under X-STATS-a (parity, fence and rebuild fixtures, C8-T07, AC-R2c-24, AC-R2b-13 and AC-R2c-25) FEAT-024 owner with L7 Staging evidence

X-STATS-a (MS-10, Adopted; D3-10b, D3-10d). For an R2c staging pilot to satisfy PS1, the usage family must be on in staging and the pilot projects allowlisted on both hosts: a FEAT-024 decision, because STATUS keeps every non-screening family off there. Preview carries no FEAT-024 keys, so preview pilots are exempt from PS1 and use the (b) path (D3-10d). Project 0102 is both FEAT-024's only staging pilot and a named plan pilot seed; with the fold flag now pinned on in staging it may be in fold mode (UNVERIFIED). It stays out of R2a–R3a pilots until the "canonical commit on an allowlisted project with statistics on" fixture (C8-T07) passes in the e2e stack, then becomes the deliberate X-STATS-a integration pilot (D3-10b).

Source-write seam (MS-06, Corrected). The engine never writes statistics or pending entries. It writes Study only through a FEAT-024-owned source-write seam with a projection-only shape (input: before and after CanonicalSummary plus the affected families; output: nothing, a classified delta, or an entry/intent, per the project's active path). Correctness floor on every path: every family a commit can affect is at least staled when statistics are on. Mechanics: consistency model. This keeps R2a off a protocol that may still change if gate (b) fails.

New families and scope kinds by technical-plan amendment (MS-02, Corrected). The plan's "no new counters" and "new kinds inside the existing family" are replaced. The usage family's authority is pmFormSession and the head collection, not pmStudy; every FEAT-024 family today is rebuilt from Project and Study and bound to Study.Version. So: new families (FormVersionUsage, QuestionVersionAnswers; later ProfileVersionDecisions) with new scope kinds and key components (FormId, FormVersionId, ProfileId, ProfileVersionId), authoritative over the canonical collections in the same pinned snapshot, delivered by a FEAT-024 technical-plan amendment ("canonical sources") at F2 (forms) and F5 (profiles). Their catalogue and source versions are independent of ProjectScreening's constants, so #3506 is fixed first (E72).

Drafts counted authoritatively (MS-03, Corrected). A draft-only session never writes Study, so no FEAT-024 path can point-maintain a draft_only count. The usage family covers explicit versions only (completed and saved-incomplete, by form version, deduplicated across stages). draft_only is counted authoritatively from the draft collection (indexed by base form version, E21) inside the publish fence; the publication manifest records both counts with their basis (AC-R2c-25).

"Current" at the fence and a scoped rebuild API (MS-04, Corrected; D3-10a). No in-process, project-admin-callable "targeted refresh" exists: republishing a Stale scope is the admin backfill route (operator identity, synchronous, may answer 409) or the hourly default-off repair. FEAT-024's reader already answers a Stale scope with a pinned authoritative value in the same snapshot, which is what PS2 permits ("actively update the specific relevant statistics there and then"). So: (i) FEAT-024 exposes a service API "scoped rebuild at a pinned snapshot" (reusing ProjectStatisticsRebuildService.PublishAsync, the sanctioned retry unit) callable by the publication command under the fence; (ii) recommended (D3-10a): "current" for PS2/PS3 is a FEAT-024 read at the fence whose result is Materialised-Fresh or pinned-Authoritative, with the read's identity (projection revision, source revision, digest) recorded in the frozen manifest; (iii) the reviewer "pause" is the engine's scoped write fence, not a FEAT-024 facility (FEAT-024's definition-rewrite fence makes reads answer 503 for the whole project). A-08 is reworded (open questions A-08).

Receipts (MS-05, Corrected). FEAT-024 receipts are written only when statistics flags and the allowlist admit the project (so none in production today), bind Study.Version, and on the fold path carry the fold time and are missing for overflowed saves. Canonical idempotency cannot depend on them. E35 is replaced by the canonical command ledger (brief §1.4, owned by the consistency model); FEAT-024's source-operation receipt stays the statistics-protocol receipt and reuses the canonical CommandId as its OperationId, with the same digest, so one command has at most one statistics operation.

Target-aware classification (MS-07, AP-14, Corrected; X-STATS-c). "Enough = 2" lives at three sites (verified): StudyStats.cs:353-355 (a TODO since #2331), FEAT-024's AnnotationThresholds.MinimumNumberSessions (digest input annotation.v1:mns=; ProjectStatisticsConfigurationDigest.cs:16), and the study-library session filter (StudyRepository.cs:1665, 1712, 1725, #3979). Replacing it is a catalogue version bump for the stage, membership-stage, domain-reconciliation and reviewer annotation families and a digest-format change, which the rules say needs "an explicit compatibility/reconciliation plan for established controls, current rows and retained checkpoints before serving is re-enabled". It is a named FEAT-024-owned change with its own issue (E71), landed before R2b pilots on allowlisted projects and before R3a; AC-R3a-06 references it. Until then AC-R3a-06 cannot pass for materialised consumers.

Profile grain (MS-08, D3-10c). ProjectScreening, MembershipScreening and ReviewerScreening read legacy ScreeningInfo (one decision per reviewer per project). R3a's single default profile can be projected into that shape exactly: R3a states that the Study projection writes the default profile's decision into legacy ScreeningInfo/InclusionInfo, so the three families stay exact, verified by the parity audit (AC-R3a-34). R3b's several profiles cannot. Recommended (D3-10c): multi-profile screening statistics are served live for R3b pilots; profile-grain families (ProjectProfile and MembershipProfile scope kinds) arrive at F5 as a FEAT-024 scope amendment.

Three N-1 mechanisms (MS-09, Corrected). C8 and C16 name them separately: (a) BSON extra-element tolerance for value-object maps (#3145, #3512), which R0 needs; (b) the fold protocol window (protocol bumps, stamp advance), which governs pending-entry derivation only; © per-family catalogue and source versions plus the configuration digest, which govern rows, guards and checkpoints (coupled to ProjectScreening's constants until #3506). R0's floor is (a); new transition kinds for canonical commits are (b); new families, dimensions and target-aware classification are ©.

PRISMA never from FEAT-024 rows (MS-11, Corrected). A source-type dimension is new SearchPopulation metric keys (a catalogue and source-version change); retained checkpoints never gain it; SearchPopulation counts SystematicSearch.NumberOfStudies and has no withdrawal notion. PRISMA snapshots are computed from authoritative records (Citations, external step records, ScreeningOutcomes) at the report watermark and stored frozen. AC-P1-07 is reworded (acceptance criteria §4.23).

Per-project commit sequence rejected (MS-12, Corrected). A per-project counter written in every canonical commit serialises a project's commits on one document; FEAT-024 measured exactly that pattern with the Project token. Brief §1.2 deletes ProjectCommitSequence from interactive commits (per-aggregate versions plus HLC; consistency model). The programme side is R10: a canonical-commit arm in FEAT-024's write benchmark (½/5/10 reviewers, same and different Study, eligibility off and on), which also measures D1-08's gate.

Onboarding contract and protocol 5 (MS-14, MS-15, Adopted). Appending enum ordinals is not enough for rolling deploys: ProjectStatisticsScopeKey.Compose throws on an unknown scope kind, and the daily producer, fleet dispatch, repair, drift check and admin router enumerate families. FEAT-024 publishes a new-family onboarding contract with a rolling-deploy test before F2 (R3). Transition kinds for canonical commits (form-keyed session versions, form-keyed claims, profile-keyed decisions) go into one additive bump, protocol 5, declared at F3 and shipped dark only after gate (b); until then canonical commits carry invalidation intents only (allowed by the N-1 rules; exact enough for pilots, which are served live for those families). No bump sits on R2a's critical path (R6).

Rollback order and adoption fences (MS-16, MS-17, Adopted). When a release changed a statistics writer, family or protocol, the rollback rehearsal (AC-ALL-04) follows FEAT-024's order: fold-disable and wait for Disabled; close the project gate (two-stage with quarantine); close the fleet gate; flags off through GitOps on both hosts; allowlist guard; then images; a rollback past an advanced stamp needs reset twice around guard removal; record fold mode and stamp before and after. Adoption shadow and cutover raise FEAT-024's staged operation fence for every family (as bulk update does) and reset and rebuild under the new family source version after cutover. Per-family parity audits (#3845) are a G-ADOPT prerequisite, or AC-R6-04 labels statistics parity as manual for the families without one.

QuestionAnswers and the canonical designer (MS-18, Adopted). For canonical projects the designer reads QuestionVersionAnswers (or authoritative counts), never the legacy locks; FEAT-024's QuestionExists resolves canonical definitions for canonical projects.

Agreement in its own store (MS-20, D3-11). Recommended: R5c keeps a rebuildable derived store keyed by (project, form version, method version) with a watermark and the independent/informed split, computed by a bounded background job; never a FEAT-024 family; AC-R5c gains an absolute budget. FEAT-024's exclusion of kappa stands.

Overview DTOs (MS-21, Adopted). New gate, sufficiency, work-status and batch-frontier fields extend the existing statistics query services and consumer flags (StageOverviewStatisticsQuery, ProjectReviewerProgressQuery, ReviewerProgressQuery), one read per route; no parallel overview endpoint (C17).

History is never an as-of input (MS-22, Adopted). FEAT-024 history is daily observed counters that are never reconstructed; C11 and C12 gain the rule; for canonical projects the daily observation records the new families under their captured catalogue version.

#3524 after C16 (MS-24, Adopted). R0's admission record is never the statistics allowlist. Durable eligibility (#3524) stays FEAT-024-owned and is built after C16 freezes, so both records share one admission-service shape and audit, with statistics eligibility read inside the transaction by FEAT-024's own gates (R8).

No transactional point mode for admitted projects (DC-21, Corrected). In transactional (non-fold) point mode every canonical transaction would also write six per-project statistics documents and the Project token (screening-write-overhead-diagnosis.md:128-141, per DC). Admitted projects run FEAT-024 in fold mode or with statistics writes off; R0's admission service refuses the combination "admitted and allowlisted with writes on but not in fold mode".

Activate or freeze (#3987, D1-03). The architecture review asks whether ProjectStatistics (about 56% of PM Core, 36 collections, about 25 flags) is activated on a date or frozen. Decided (D1-03, Chris, 3 October, as recommended): activate the families this plan uses. Chris set the date late that evening (register §1.14): from 5 October 2026, staging first, then production the following week; the production date is a target, and production enablement keeps its own approval (D1-04) and waits for X-STATS-b1 to b7. A freeze, which was not chosen, would have made Q-31(b) the GA path for publication evidence (authoritative counting at the protected boundary), shrunk E72 to the authoritative counting service, dropped X-STATS-b and served statistics in R2–R5 live. So Q-31(b) is not extended to GA, and X-STATS-b1 to b7 stay on the GA path.

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
R1 Extract a single source-write participant seam from the screening and annotation statistics writers and fold saves, with a projection-only Study write shape MS-06 Before F1a (the engine builds against it) Pure refactor; command-budget tests stay byte-identical Low Refactor PR; projection-only shape with intents FEAT-024 owner
R2 Target-aware annotation classification at all three sites; catalogue bump (annotation.v2); mns moved out of the digest into per-stage definition metadata (E71) MS-07, AP-14, #3979 Before R2b pilots on allowlisted projects and R3a; after the gate (b) rerun so the benchmark baseline is stable Digest-format change with a reconciliation plan (forced rebuild of allowlisted projects; retained checkpoints keep identity); ProjectStageConfigurationChange.AffectedFamilies extended Medium (staging pilot reset and backfill; history identity) PR a: calculator, classifier, parity tests. PR b: digest and version migration with runbook step. #3979 separately (study-library filter) FEAT-024 owner; architecture-review owner for #3979
R3 New-family onboarding contract and rolling-deploy test; fix #3506 MS-14, MS-09© Before F2 Additive; tests, plus the constant split Low One PR FEAT-024 owner
R4 Scope kinds and key components: form version and question version at F2; profile version, project profile, membership profile at F5 MS-02, MS-08 F2, F5 Class maps already ignore extra elements; append-only ordinals Low One PR per freeze FEAT-024 owner with L2/L3
R5 Usage families over canonical collections in the same pinned snapshot, with backfill and rebuild routes, parity calculator, flag, consumer manifest, fleet dispatch, repair and drift inclusion, and the scoped-rebuild-at-pinned-snapshot service API (E72) MS-02, MS-03, MS-04 After F2; dark; staging enable for pilots under X-STATS-a New families under their own catalogue and source versions; explicit versions only Medium 3–4 PRs (amendment and ADR; family and routes; parity and fleet; service API) FEAT-024 owner with L7
R6 Protocol 5: every canonical transition kind in one additive bump with IntroducedAt = 5, N-1 harness cases and the stamp-advance procedure MS-15 After F3 and only after gate (b); until then intents only Additive from P0; stamp advance after both rollouts Medium One PR per kind group, one bump FEAT-024 owner
R7 Close the production-blocking follow-ups: #3840, #3960, #3845, a passing gate (b) after the pre-write fix, the soak MS-01 Now (already open) None Low to medium Existing issues FEAT-024 owner
R8 Durable eligibility #3524 after C16 freezes, aligned to the admission record shape and audit, keeping in-transaction participation MS-24 After F1a Explicit migration from the static allowlist (no silent union) Medium 2 PRs FEAT-024 owner with L0
R9 Hand fold deferred item 2 (eligibility Project token) to the StageSettings design MS-19, DC-05 F3 Eligibility-programme change behind its flag Medium One PR (L4) L4 with eligibility owner
R10 Canonical-commit arm in ProjectScreeningWriteBenchmark MS-12, D1-08 M0/F1a None Low One PR L1 using FEAT-024's harness
R11 Correct STATUS: slice 7 merged; #3591, #3766, #3767 merged; staging fold flag pinned on MS-23 and the refresh Before G0 None Low One docs PR FEAT-024 owner

7.4 Read-model placement

Read model Placement Why Release
Form-version usage, question-version answers New FEAT-024 families (E72) Bounded, catalogue-defined, parity-auditable, needed on the publication page F2 / R2c (served authoritatively under Q-31(b) until X-STATS-b)
Profile-version decisions; profile-grain screening FEAT-024 scope amendment at F5, or served live (D3-10c) Legacy screening families cannot represent several profiles F5 / R3b
Task-based domain reconciliation counts FEAT-024 family change at R4a Today's DomainReconciliation counts two booleans per stage R4a
Existing screening and annotation families on canonical projects Kept exact through the Study projection (default profile written into legacy ScreeningInfo; tallies merged) Avoids spending R3a on R3b's timeline R2a–R3a
Gate status, sufficiency, batch frontier Derived summaries, not persisted, extending existing statistics queries Configuration-dependent, cheap to derive, one read per route R3a–R3c
Draft-only counts Authoritative indexed count on the draft collection, inside the fence No Study write exists to maintain them R2c
Queues (my concerns, assigned work, requested reviews, changes awaiting approval) Authoritative indexed counts over the feature aggregates, computed at read time Group routing must reach people granted after an event R3c–R4b
Publication impact manifests Authoritative enumeration of identities in the fenced snapshot Counts never decide identities R2c
Agreement Separate rebuildable derived store with a watermark (D3-11) FEAT-024 excludes kappa; heavy, method-versioned R5c
PRISMA Frozen snapshots from authoritative records at the report watermark Reports must regenerate identically R5b
Allocation progress Allocation keeps reading SessionTally through the Study projection; per-reviewer counters only as decided at F1a (§3.4) No third counter store AL1

7.5 Do not build further until

Point maintenance for QuestionAnswers at question grain (fold deferred item 6): until F2 fixes the question-version grain. Any writer to the reserved StageQuestionVersion scope. Profile grain in the screening families: until F5. #3524: until C16 freezes. Any further per-release fold-protocol bump planning: until gate (b) is decided.

7.6 What the plan adopts

"Hot paths never write statistics" and the whole-pending-set rule as engine rules; the pinned command-budget tests as the model for the canonical commit's budget tests; the rebuild publication as the sanctioned retry unit; fail-closed quarantine of unknown entry kinds; the transaction-admission pattern (snapshot read, captured flags, unknown-commit-result retry) as input to C18; the benchmark harness and its cells for D1-08; the soak on the isolated e2e stack, never on staging with real accounts.

8. Notification service

Owned by the notification programme (the "SyRF Notifications and Study Issues" conversation, delegated to the Juniper thread; plan lane L14 only specifies contracts).

8.1 Current state

PR State at 15:18 BST (#3965 at 20:35 BST) Head Notes
#3932 inbox and export capture OPEN, CONFLICTING; base main 2ddd96fb0; 51 files +2,257/−188 Conflicts only in generated files (per NS); no human approval; inbox reads gated by the capture flag
#3938 import and invitation OPEN, mergeable into #3932's branch 180f0d1bb; 22 files +693/−23 Opted-in users get duplicate invitation email (NS-25); #3967 since changed invitation-token matching on main
#3941 access and workload OPEN, mergeable in stack eb72c886d; 26 files +915/−67 Random SourceIds; legacy ACL and stage targets; wraps ProjectController.UpdateProject, which #3964 also edits
#3942 preferences and immediate email OPEN, mergeable in stack 75f89b33d; 48 files +1,877/−109 Exact-set category validation; preferences unflagged; no unsubscribe, no halt
#3943 daily digests OPEN, mergeable in stack d75fd81b9; 23 files +1,010/−30 Titles only, ungrouped, at most 100 a day
#3944 reconciliation conversations OPEN, mergeable in stack 59ab32e7b; 52 files +2,222/−24 Reads legacy sessions; keyed by stage
#3945 study issues OPEN, mergeable in stack 25b364da3; 49 files +2,256/−28 Writes Study bibliographic fields (version-guarded, lock-aware; verified StudyIssueRepository.cs:69-87); recipients are Administrator-group members
#3947 checked PDFs OPEN, mergeable in stack ae3c6749d; 40 files +3,009/−16 Writes PdfRelativePath (verified CheckedPdfProposalRepository.cs:125-139); adds native tools to the API image; needs a clamd scanner and an isolation review
#3965 private conversations OPEN, ready for review (20:35 BST): ten commits pushed, base = #3947's branch (codex/checked-pdf-proposals-and-explicit-administrator-a-r6bdl2); waits for the stack (D1-09). Contents: reconciliationConversations flag depending on notificationInbox; one-to-one threads; completed non-reconciliation sessions only; read-only context; a fix round (all-or-nothing thread creation, a content-free "questioned" marker for every reconciler, stable reviewer labels, typed reply refusals; docs); and a tenth commit refusing a reconciler who reviewed the study in any stage. (At 15:18 BST GitHub showed a draft with 0 files; at 19:25 BST eight commits were local.) 986b1cdc2 Completed-only candidacy already excludes RA5 requested reviewers until they return their review (answers NS's Q-N9; recorded in the register, no question). The reconciler self-review refusal is now stage-independent: any non-reconciliation annotation session of the reconciler on the study, in any stage and any status, refuses them (stricter than NS-05's legacy rule, which needs overlapping questions; commit 986b1cdc2 message). Threads are still keyed by stage and read ExtractionInfo.Sessions
Checks and reviews Stack E2E jobs skipped (no run:e2e-* label); latest-head Claude reviews were short delta reviews; product CI runs only while a slice is temporarily retargeted to main — Per NS §2 and the stack's technical-plan.md "CI validation for stacked slices"
Flags and workers notificationInbox, notificationEmail, studyAttention (requires inbox), environment-wide, API and web only; change-stream watcher, email worker and digest worker start in every API replica regardless of flags; accepted email continues after the flag goes off — Per NS; the stack plan says durable workers "must finish accepted obligations even if admission/UI is disabled" (technical-plan.md:64-65)
Follow-up tracker #3950 OPEN (13 items) — Covers only an observer-load item among those the plan assumed (NS-22)

8.2 Integration with the plan

C15 v2 (NS-01, NS-03, NS-12, Corrected/Adopted). C15 required capture "in the same transaction as the domain change" and the notifications page forbade any outbox. Several of the plan's own events have no such transaction (publication phase 2, outdated-flag fan-out, time-driven expiry and LC1 reminders, adoption notices), and the stack's extension model (one kind = one email category, a central resolver switch, a typed field per source type, five inbox writers) was built for eight fixed kinds while the plan needs about twenty more. C15 v2 is frozen at F1b (contract) with kinds per feature; its full text is C15 in contracts. Summary:

  • A kind registry (kind, email category, scope legacy/canonical/both, admission flag, inline recipient limit, resolver), registered through DI by the owning feature; a registry test fails the build when a kind lacks a category, resolver, label or disclosure fixtures.
  • A generic Source sub-document; one capture service for all writers; server-provided action label, context lines, typed availability and workflow state.
  • Two capture modes: inline (active source transaction, bounded recipients) and recorded fan-out (a durable intent, NotificationFanOut, in the source transaction; a leased idempotent expander writes rows in batches). Time-driven notices are domain transitions run by a scheduler that marks the aggregate and captures inline. This is C19 class (b) for notifications; "no outbox" becomes "no second notification store; durable intents are part of C15; in-memory outboxes stay forbidden" (the stack's own principle).
  • Deterministic identity: SourceId = SHA-256(kind, source type, source ID, occurrence key); row ID = SHA-256(SourceId, recipient); occurrence key from durable identity (command ID, aggregate version, transition, publish operation ID); upsert with $setOnInsert, never InsertOne. "As reviewAccessGranted already does" is deleted: that kind uses Guid.NewGuid().
  • A disclosure-policy hook per channel (inbox, email, digest) after every resolver (NS §4.2).

Preferences tolerance before #3942 merges (NS-02, Adopted). Exact-set validation makes every new kind a breaking change: in a rolling deploy or rollback, whichever side has a different category list rejects every preference save with 400, including opt-out, and the preferences API is unflagged. Before #3942 merges: unknown keys kept and ignored, missing keys mean off, the client sends back keys it does not render, a kind→email-category map (the eight existing kinds map to themselves; the review-workflow kinds map to at most six new categories, notifications integration §3), and a version-skew test matrix (C15-T06). If #3942 merges unchanged, the tolerant validator must still deploy one release before the first new category.

Enablement controls and G-NOTIF (NS-04, Adopted; D3-21). Today enablement cannot be scoped or fully reversed: flags are environment-wide; turning email off does not pause accepted mail; turning the inbox off 404s links already emailed; the email flag declares no dependency on the inbox; staging and preview flags can be overridden at runtime by any SyRF administrator. Before any enablement outside the e2e stack and Mailpit: per-project notification admission (an R0 enrolment scope, or an interim registry until R0 exists) checked at capture for every kind; an operator delivery halt that pauses without cancelling; inbox reads available whenever saved items exist; the declared email→inbox dependency (E74). G-NOTIF is a gate per environment and kind family that Chris approves, recorded in the flag audit comment (D3-21: pilot projects first, platform-wide after pilot exit; staging and preview overrides only with his approval per release, email kept in Mailpit). Because runtime overrides are currently reset on resolution (#3975), G-NOTIF evidence never relies on a runtime override (X-ARCH-c).

Merge order and #3965 (NS-05, NS-17; D1-09). Order approved by Chris on 3 October, as recommended:

  1. The ownership fix, #3964 (D1-01 decided; #3969's active-member check and tests ported). It edits ProjectController.UpdateProject, which #3941 wraps. Done: merged at 20:27 BST on 3 October (85e6facf7).
  2. 3932: regenerate the generated files on current main; full checks on the head retargeted to

    main; a run:e2e-full run; human approval.
  3. 3938.

  4. 3941: rebased on step 0, with a test that a refused owner-reserved grant captures nothing.

  5. 3942, with the tolerant preferences.

  6. 3943.

  7. 3944, then #3965 immediately after (restacked onto #3944).

  8. 3945.

  9. 3947 last (native PDF tools isolation, or moved out of the API image).

Restacking #3965 is the stack owner's call (D1-09): if declined, #3965 stays on #3947 and lands in the same merge train before any environment enables conversations. X-NOTIF is met when steps 1–5 are merged with flags off; R1c's access notices also need step 3; R4a's conversation work needs step 6. #3965's ten pushed commits (head 986b1cdc2) implement the Q-10 list (own flag with declared dependency; one-to-one; completed sessions only; a content-free "questioned" marker; read-only link; stable labels) and, since the tenth commit, a cross-stage refusal for legacy scopes: a reconciler with any non-reconciliation annotation session on the study, in any stage, is refused (stricter than the overlap-only legacy rule proposed here). From R4a the rule becomes: refuse any session on that study × form. That rule is an R4a precondition at the latest (AC-R4a-21).

Exposure (NS-06, Adopted). C3 gains the exposure kind "questioned in reconciliation" (session, thread, time); every later version of that reviewer's session on that study × form is informed; R5c, C11 manifests and R6 mapping consume it; #3965 stores the record so it can be looked up by session. Fixture: a questioned reviewer saves a new version and agreement classifies it as informed (AC-R5c-07).

Queues surfaced without notifications (NS-07, Adopted; D3-07). A passive per-project queue does not "alert the project admin" (LC1) or let a raiser "receive" an outcome (QY6) for someone who never opens that project. C17 gains a flag-independent surface: a global navigation badge with counts computed at read time over the four queues; a cross-project "My work" tab beside "Pending Projects"; a project-overview banner for admins with pending LC1 requests (placement per D3-07 and the UX strategy). Acceptance: with every notification flag off, a user reaches an assignment in an unopened project from the project index in one step (AC-R3c-11, AC-R4a-47).

Study writers in the stack (NS-08, Corrected). Study-issue acceptance rewrites Title, Abstract, Year, DOI or Url, and checked-PDF approval rewrites the PDF path (both verified, CODE-PR). Both join the M0 inventory (AC-M0-03). For admitted projects after P2, an accepted bibliographic correction appends a correction event, re-runs DOI/PMID matching and never edits Citations; a PDF approval records a P1 retrieval event. Owner: L12 with the study-attention owner.

#3941 alignment (NS-09, Corrected). #3941 decides effective Review access through the legacy ACL path, ignores Reconcile grants, and computes workload from stage.WorkloadShares and stage.SessionCountTarget. Changes: read effective grants through the active decision path with a parity test, and add Reconcile grants; for canonical forms derive workload notices from the form target and the AL1 plan, and suppress the stage-based capture. AC-R1c-04 becomes conditional on X-NOTIF (step 3) with a parity test; dashed X-NOTIF edges go to R1c, R2c, R3c, R4a and R4b.

UI1 for stack screens (NS-10, D3-01). "Retain the stack's UI" becomes: the inbox and preferences pass UI-1 to UI-8 before testers see them, and the conversation, issue and PDF screens before production; a Material 3 inbox redesign PR lands before R2c (D3-01).

Flood controls (NS-13, Adopted). Before R2c: mark-all-read; project and kind filters; a context line from the resolver; "hide unavailable"; an unread count that excludes resolved items (D3-23); digests grouped by project and kind; jittered refetch and per-request authority memoisation (#3950 item 1); a recipient threshold for recorded fan-out (PROPOSAL: 200); older publication notices for the same form marked superseded.

Q-20 narrowed (NS-14, PROPOSAL). Candidate answers belong to their reviewer, so outdated flags on a session are normally its owner's own doing, and the ledger already requires an in-form alert. No notice for self-caused flags; otherwise one in-app notice per recipient per cause when the cause is a support on-behalf-of write, an adoption remap or a shared-gold revision (open questions Q-20). A publication writes no revision under D2-01, so it never causes this flag; its effects reach reviewers through the "form version published" notice.

Catalogue rows and taxonomy (NS-15, Adopted). Added: a phase-2 completed or stalled notice for the publishing admin; canonical workload changes; R6 cutover, pilot admission, removal and read-only (U28); a kind for Reconcile grants. Publication is one kind whose per-recipient effect is worked out at read time (two kinds would breach AC-R2c-07's "at most one notice"). Live banners use the existing per-user SignalR groups, not C15. Taxonomy in notifications integration §3.

Live on merge (NS-16, Adopted). Merging changes production even with flags off: unflagged preference endpoints, five web routes guarded only by sign-in, isolated reads and a join-response fix on project endpoints, three hosted services in every replica, native PDF tools in the API image. The owner is asked to add route flag guards (keeping opt-out reachable while the account has pending mail) and to make the workers idle when no flag is on and nothing is pending (E74).

Ownership transfers (NS-19, Adopted). The notification programme keeps capture, inbox, email and digests. StudyConversation moves to L6 at R4a (rebound to ReconciliationTask; until then conversations refuse canonical scopes through R0's ownership record, NS-18). Study issues and checked PDFs move to the Study Management and PDF programmes. #3947 is listed once, under the PDF programme.

Capabilities (NS-20, Adopted). C10's catalogue gains "receive and resolve study issues" and "approve PDF corrections" at F1b; recipients expand by capability before R1c ships (today they are Administrator-group members).

Retention (NS-21, Adopted). E32 covers preferences, the delivery ledger, digests (which hold notification IDs), conversation and issue posts (free text) and PDF bytes, not just inbox items; retention never orphans ledger or digest references; post text follows the annotation erasure rule.

#3950 coverage (NS-22, Corrected). #3950 does not track retention, fan-out limits, unsubscribe, the email flag dependency, a halt or per-project admission. These are marked untracked; the owner is asked to open issues (no GitHub write here).

Issues versus queries (NS-24, Adopted). After R4b, the issue form in canonical projects sends answer disputes to "Raise a query"; C9 says study issues never carry answer concerns.

Duplicate invitation email (NS-25, Follow-up). Remove projectInvitation from the email categories, or skip notification mail when invitation mail was sent: a stack backlog item.

Unknown-kind rollback (NS-26, Adopted). Older binaries mark an unknown kind's ledger row Suppressed (terminal). "An unknown kind leaves the ledger row ready" ships one deploy before the first new kind; the rollback section notes it.

Other. #3944 is removed from the Study projection's readers; conversations refuse canonical scopes until R4a (NS-18, Corrected). Each operation in the transaction table gets a capture mode: Save/Complete inline and bounded (task holder, requesting reconciler); gold publication and query resolution inline to raisers; publication recorded fan-out keyed by the publish operation; benchmark with capture on (NS-23, consistency model). C15-T01 to T11 apply to every release that adds a kind (NS-11).

Numbering follows NS §4.

# Change Rationale Timing Compatibility and migration Risk PR slicing Owner
N1 Tolerant preference validation; client sends back unknown keys; kind→category map; declare notificationEmail → notificationInbox (E74) NS-02, NS-04 Before #3942 merges (at the latest one deploy before the first new category) None: existing kinds map to themselves Low; invalid modes and time zones still rejected Amend #3942 Notification programme
N2 Restack #3965 onto #3944; review it (pushed and ready, ten commits; the legacy cross-stage reconciler refusal is in the tenth) NS-05, NS-06; D1-09 Before #3945 merges and before studyAttention or reconciliationConversations is enabled anywhere Generated flag files regenerate; #3945 and #3947 rebase mechanically; no data yet Low to medium Retarget #3965; merge straight after #3944 Notification programme; L6 reviews
N3 Delivery halt (pause, not cancel); inbox reads independent of capture; per-project notification admission (E74) NS-04, D3-21 Halt and read gate before any enablement with real users; admission before any production pilot with notices New data with safe defaults Medium N3a: halt and read gate. N3b: interim admission registry, later an R0 enrolment scope Notification programme with L0
N4 Kind registry, Source sub-document, single capture service, server labels, unknown-kind behaviour (E73) NS-03, NS-12, NS-26 ADR in W0, frozen at F1b; landed after the base merges and before the first review-workflow kind Additive fields; existing kinds keep typed fields (no data migration; Entity extra elements keep older binaries tolerant) Medium; mitigated by the existing physical tests N4a: registry with existing kinds as handlers. N4b: capture service (bulk upsert, recipient cap). N4c: client uses server labels Notification programme; L14 writes the specification
N5 Recorded fan-out: durable intent plus leased expander (E73) NS-01 Before R2c and before R1c bulk group edits New collection; older binaries ignore intents, which wait until roll-forward Medium One PR with intent, worker and resume tests Notification programme; L2 consumes
N6 Disclosure-policy hook with channel rules and one alias source NS-03, NS-11 Interface at F1b with C10 (default keeps today's behaviour); BL1 and VS1 rules before R4a and R4b kinds Code only Low Hook and fixture harness L8 with the notification programme
N7 #3941 alignment: active decision path, Reconcile grants, canonical workload suppression NS-09 Grants before R1c; workload before R2b and AL1 Code; parity tests Medium One PR Notification programme with authorization and allocation owners
N8 Material 3 inbox and preferences; grouped digests; flood controls; resolved and superseded state NS-10, NS-13; D3-01, D3-23 Screens before testers see them; flood controls before R2c Additive ResolvedAtUtc; new endpoints Low to medium N8a: inbox redesign. N8b: grouped digest. N8c: refetch jitter and memoisation Notification programme; L16 design review
N9 Register the #3945 and #3947 Study writers; P1 and P2 hooks NS-08 Inventory at M0; hooks before P1 and P2 Code only Low One PR per hook L12 with the study-attention owner
N10 Conversations refuse canonical scopes; rebind to the task at R4a; stable aliases; L6 takes ownership NS-05, NS-18, NS-19 Refusal before R2a pilots; rebind at F4/R4a R6 manifests remap references (already planned) Medium R0 refusal check; R4a rebind L0; L6
N11 Merge #3947 last; move native PDF tools out of the API image (for example into the PDF Agent) or finish the isolation review first NS-05, NS-16 Before #3947 merges Image change only Medium Amend #3947 PDF and study-attention owner
N12 Retention and erasure after the E32 decision; an ADR "Saved notification capture and delivery"; MongoDB reference entries for the new collections NS-21, NS-17 E32 at F1a; implementation before production enablement A TTL index applies to existing rows; the digest processor already tolerates missing items Low One PR plus the ADR Notification programme

8.4 Do not build further until

  • New notification kinds beyond the eight existing ones: until C15 v2 is frozen (F1b) and N4 lands.
  • Email for any new category outside Mailpit: until N1, N3 and G-NOTIF.
  • Conversation features that assume stage keys (handover, open-task-only threads): until R4a rebinds them to ReconciliationTask.

8.5 What the plan adopts

The saved inbox row as the durable obligation; read state never resolving a workflow; generic payloads shaped by the resolver under fresh authority; deterministic SHA-256 identities (StudyConversation, StudyIssue) as the model for every kind; the per-item delivery ledger with leases and ambiguous-send handling; "durable workers finish accepted obligations"; Mailpit and the hermetic e2e stack as the only places test mail goes.

9. Authorization programme

Owned by the authorization programme (#3335, OPEN; handover plan handover/2026-09-08-authorization-3335/PLAN.md).

Joins (existing, unchanged): X-AUTH-SCHEMA (gate G-D: membership schema 1 applied and verified in production, after WP-M1/WP-M2) and X-AUTH-ENFORCE (M6 staged cutover, gate G-C, or parity tests) for R1c; X-AUTH-WP9 (explanations, after WP3) for R1b's explanations and R1c.

New join X-AUTH-RESOLVER (#3251; AP-02). An out-of-request, cross-provider authority resolver on the single ProjectAuthorityEvaluator, needed by AL1, allocation reviewer validity and Phase 2, and R3a's per-reviewer "Who is offered what". Until it lands R3a ships pool-level counts (D3-13d).

Catalogue additions (C10, at F1b): realtime presence as a disclosure channel (RT-14, D3-20); "view who is reviewing" folded into Monitor; "receive and resolve study issues" and "approve PDF corrections" (NS-20); the claim kinds' admin actions (release a task editor claim, override an assignment) mapped to existing grants in E6.

Ownership fix (D1-01, decided). Chris chose #3964: the controller compares the caller with the persisted owner, so a stored ChangeOwner grant cannot bypass it, and owner-reserved activities are refused by the permission endpoints with an aggregate backstop. #3969's active-member check and its tests are ported into #3964, and #3969 is closed by its owner. State at 20:35 BST: #3964 merged at 20:27 BST (merge commit 85e6facf7) after an approving Claude review on head 16788f9 and green checks, and its worktree and branches were removed; #3969 is closed with a comment pointing to

3964. (At 15:20 BST #3964's three commits were local and #3969 had one pushed WIP commit.) Step 0

of the notification merge order (§8.2) is therefore done, and R1b's security-fix entry criterion is met.

Recommended change for the authorization programme: deliver #3251 through the evaluator (A5); schedule WP11's split (dialog over existing groups first) as the plan already proposed; add the catalogue entries above to its coverage test (#3882) when they land.

10. Architecture review programme

The architecture review is #3961 (draft docs PR; head 809ae76d7 at the 19:25 BST re-check, updated 18:13 UTC), with issues #3972–#3990, run by another session. Its premise ("the highest-leverage work is to make the existing rules enforceable and to remove surface area, not to add features") competes with this plan for the same approver, agents, CI and files, and several items change foundations that F1a freezes (DS-02, Corrected: the programme was missing from plan §8 and §10). Its synthesis still lists #3964 versus #3969 as open; D1-01 has since decided it (#3964 merged, #3969 closed).

Finding (issue) Why it matters to this plan Joint sequencing (D1-02, approved 3 October)
Persistence unsound under concurrency: singleton unit of work, shared mutable 2-second cache, version bumped in memory before the write (Audit.cs:28-34, per DS-02), upserts that resurrect deleted documents, direct $set writes without a version bump (#3985) Canonical commits rely on version CAS and isolated reads (C18). Verified examples of version-less direct writes on pmStudy: the question-delete cascade and the inclusion recalculation (StudyRepository.cs:1306-1319, 1620-1650; no Audit update in InclusionInfoFiltersAndUpdates) X-ARCH-a: #3985 is an F1a prerequisite, or canonical repositories use isolated reads and non-upsert saves enforced by an architecture test (A-34)
Fire-and-forget domain events (#3973) C19's in-process IDomainEvent is allowed only for loss-tolerant effects, and only after events are awaited X-ARCH-a: #3973 before F1a
Lamar last-registration-wins; runtime flag provider re-created per resolve, resetting overrides (#3975) Admission and evidence must not depend on runtime overrides; AP-22's double read becomes real X-ARCH-c: #3975 before R0 relies on runtime overrides and before any G-NOTIF evidence
MassTransit v8 open-source support ends December 2026 (#3986); global retry and outbox (#3984) R0 adds ownership guards to PM consumers; tracking's idle, suspension and liveness consumers and scheduled commands are MassTransit/Quartz paths X-ARCH-b: the #3986 decision before R0 guards PM consumers; new consumers follow it
ProjectStatistics activate or freeze (#3987) Decides X-STATS-b and E72's size D1-03 decided 3 October: activate from 5 October 2026, staging first, production the following week as a target that keeps its own approval and waits for X-STATS-b1 to b7 (register §1.14)
One v0/v1 schema migration (#3988) System-question snapshots (E24) depend on SystemQuestionVersion variants Fold into E24, or defer until after R2a (D1-02)
Web state convergence; god files stage-review.component.ts, the AF2 store (#3989) L5's serialisation point Run as L5 seam slices in the L5 order (plan §7)
Study library filter hard-codes two reviewers (#3979) One of the three fixed-two sites X-ARCH-d: before R3a, alongside E71
ValueObject equality ignores base fields; agreement threshold lost in JSON (#3980) R3a's compatibility profile reproduces the project threshold X-ARCH-d: before R3a
Modular monolith with one writer per collection; job state out of Project Canonical collections should have one writer host each; Project is already a hot document Adopted for canonical collections: one writer host per collection, declared in C16
Frontend bug-fix plan (docs/planning/frontend-bug-fix-plan-2026-10.md in #3961, added 19:12 BST): PR-3 SignalR groups not re-subscribed after automatic reconnect (#3976); PR-7 a bounded wait on AF2 saves; PR-9 stage-review effects (review-effects.ts), pending that plan's decision D2 PR-3 touches the client that carries presence and #3932's InboxChanged hint; whether presence rejoin is affected is UNVERIFIED (its store has its own reconnect logic). PR-7 and PR-9 touch R2a's AF2 persistence and the reviewer workspace, L5's serialisation point PR-3 before any tracked pilot (checked by AC-T-01) and before G-NOTIF; PR-7 agreed with the AF2 owner before R2a's drafts adapter; PR-9, if done, lands before L5's R2a changes or joins the L5 order

The plan adds the programme to §8 with these joins and its platform risks to §10. Chris approved D1-02 as recommended on 3 October (register §1.13):

3961 Phase 0 continues; #3985 and #3973 are F1a prerequisites; #3986 decided before R0; #3988

folded into E24 or after R2a; #3989 as L5 seam slices.

11. Other programmes and prerequisites

Deletion lifecycle, ADR-014 versus amendment J (PH-02, D3-12). ADR-014 (in the uncommitted .worktrees/bulk-pdf-deletion-lifecycle worktree; product decisions of 12 August) gives project and search deletion a 24-hour grace period and then physically deletes Project, Search and Study documents, leaving content-free tombstones; the deletionLifecycle flag on main is that design's flag. Amendment J (approved 3 October) and QD1 forbid erasing identification history and published evidence, and the canonical collections are outside ADR-014's deletion scope. Recommended (D3-12): withdrawing a search hides its Studies but keeps Citations and canonical evidence; deleting a whole project keeps ADR-014's removal with a tombstone. X-DEL is redefined: ADR-014's manifest and tombstone model covers the canonical collections for whole-project deletion, and search withdrawal never physically removes identification history. The register's §2 records the conflict; Q-33 is re-framed as "which governs for admitted projects".

Search import robustness, X-IMPORT (PH-09, Adopted). P1 adds Citation capture and P2 identifier matching inside search import, whose parse job still has a 5-minute timeout (ReferenceFileParseJobConsumer.cs:88, verified); the May incident showed imports above about 3,000 studies fault and duplicate-event saga crashes. #2612 is a docs-only plan ("add staged search import plan", 1 file, OPEN since 1 September), not an implementation. X-IMPORT (prerequisite of P1): staged import as #2612 plans, or an equivalent, implemented; a sized timeout with heartbeat and a stall watchdog; idempotent saga creation; P2's pending-dedup status aligned with staged-pending status. Acceptance for 5,000- and 50,000-record imports (AC-P1-19). Owner: Study Management (import) with L12.

Flag overhaul P7 versus R0 admission (PH-14, Adopted). The flag overhaul plans per-project targeting (P7) and says domain enrolment stays separate from flags. R0's admission record is that domain enrolment; AF2 production pilots route through P7 targeting keyed to it, so only one per-project mechanism is built. At F1a the flag programme owner's checklist passes, run by a fresh-context agent; Chris rules on exceptions (E75).

Authority-transition job classification (PH-15, Adopted). The approved authority transition classifies every background job family and quarantines jobs it cannot admit, and new consumers need broker permission rules. New families (publication phase 2, adoption backfills, retroactive dedup, expiry, lifecycle transitions, outcome recomputation, notification fan-out expansion, batch-opening evaluation) get an M5/P9 classification and broker rules in C16 and in each release ADR.

AF2 per-project admission slices (DS-04, Adopted; D1-07). Q-25 approved per-project routes for AF2, the redesigned shell and eligibility, but the plan listed them as other programmes' joins with no slice. Plan-owned slices reading R0's admission record: AF2 per-project admission (R0 or R2a; the AF2 gate is a pure-function input, annotation-form-v2-eligibility.ts:59-60, per DS); stage-review shell per-project admission (R2a); eligibility per-project admission (R3a). Every release gets a "production enablement" step with its own evidence, including turning R1a's editor on by default. Environment-wide enablement of these flags stays a GA prerequisite. Production opt-in pilots run before GA under D1-07 (approved 3 October): after staging acceptance, R0's production soak and AF2 per-project admission.

AF2 pdf-tools, X-PDFTOOLS (PH-20, Adopted). AF2 still needs v1's pdf-tools subsystem to mark, move and delete graph regions and to render cropped regions; that migration is "separate, unscheduled work", and it also blocks Phase 4 PR 9's cleanup (src/services/web/CLAUDE.md:361, verified). Canonical extraction (O1) runs on AF2 only and R4c depends on it. New join X-PDFTOOLS → O1 and R4c, owned by the AF2 programme; if the AF2 owner prefers, O1's scope states the gap explicitly (graph digitisation stays on AF1 for legacy projects) instead.

FEAT-023 Material 3 (D3-01). UI1 applies before FEAT-023's cutover: M3 roles over current components, routes registered for the cutover baselines; the FEAT-023 light cutover sequenced before R2a's reviewer UI if possible; the stack's inbox and preferences screens meet UI1 before testers see them, and its conversation, issue and PDF screens before production.

Bulk PDF and study attention ownership (NS-19). Checked PDFs (#3947) and study issues (#3945) move to the PDF and Study Management programmes; P1 records PDF approval as a retrieval event; bulk PDF finalisation stays in the writer inventory.

12. Revised external joins

Replaces integrated plan §5.11. Status at 15:18 BST.

Join Needed by Owner Evidence Status
X-AF2: AF2 production readiness, or the plan-owned AF2 per-project admission slice reading R0's admission record (Q-25, DS-04) Any production pilot of R2–R4 reviewer UI AF2 programme (readiness); L5 with L0 (slice) Agreed per-flag decision; slice merged with tests Not met: AF2 on in staging only
X-SHELL: stageReviewRedesign / stageReviewDockview readiness, or the shell per-project admission slice R2a onwards where the new UI extends the shell Stage-review programme; L5 (slice) Per-flag decision; slice merged Not met: off everywhere
X-STATS-a: usage family on in staging and pilot projects allowlisted on both hosts R2c staging pilot under PS1 FEAT-024 owner Staging configuration and parity record Not met: family not built; fold flag now pinned on in staging
X-STATS-b1…b7 (§7.2): idle gate (b); soak; production pending index; production rollout approval; production eligibility; usage family built; usage family staging proof R2c production publication from the materialised family; until then Q-31(b) FEAT-024 owner; Chris (b4) Per step b1 failed on latency (idle-host rerun, 3 October, #3510); b2 open; b3–b7 not started
X-STATS-c: target-aware annotation classification (E71) R2b pilots on allowlisted projects; R3a (AC-R3a-06) FEAT-024 owner (+ #3979) Merged with reconciliation plan executed Not started; no issue tracks it
X-ELIG: S4-B, S4-C, S6a, S6b, fixed-two correction, Project-token redesign, flag delivery to PM (§5.2) R3a production admission; X-BATCH activation Eligibility programme (paused); R3a absorbs per D3-09 Each item merged or extracted; count-only reservation check; AC-ALL-26 (C18-T02) with eligibility on Not met; programme paused (A-31)
X-CLAIMS: production claims route per D3-16; static switch on both hosts; load, failover and orphan backstop; E2E both modes R2b claim behaviour, R3a reservation admission and any capacity promise, in production Presence owner with FEAT-024 owner AC-T-03 to AC-T-07; AC-T-09 (route built); AC-ALL-23 (both tracking modes) Not met: tracking off everywhere; M15 unowned; #3876 deferred
X-RECLAIM: reconciliation-task editor claim working untracked (RA1) R4a in every environment L6 with presence owner (internal to the plan) AC-R4a-36 to 39 Designed here; frozen at F4
X-AUTH-SCHEMA: membership schema 1 in production (G-D) R1c Authorization programme Production migration evidence Not met
X-AUTH-ENFORCE: M6 staged cutover (G-C), or parity tests R1c Authorization programme Cutover evidence or parity tests Not met
X-AUTH-WP9: explanation endpoints and surfaces (after WP3) Explanations in R1b; R1c Authorization programme Explanation equals enforcement decision Not met
X-AUTH-RESOLVER: out-of-request authority resolver (#3251) AL1; allocation reviewer validity and Phase 2; R3a per-reviewer preview Authorization programme with allocation owner Resolver merged; validity check on save and activation Not met: #3251 open since 5 September
X-BATCH: evidence seam, pool-entry membership, durable opening with pool-entry events, read-only status, performance gate, X-ELIG first (§4.2) R3c readiness reuse; any batch enablement Batch programme Merged slices; benchmark; event fixtures Not met: #3939 conflicting
X-NOTIF: stack steps 1–5 (#3932, #3938, #3941, #3942, #3943) merged with flags off Notices in R1c (also step 3), R2c, R3c, R4a (also step 6 for conversations), R4b; never a release dependency (feature queues) Notification programme Merged code; run:e2e-full on retargeted heads Not met: nothing merged
G-NOTIF (gate): per environment and kind family Any notification enablement outside e2e and Mailpit Chris Approval recorded in the flag audit; N1, N3 delivered —
X-DEL: ADR-014 covers canonical collections for project deletion; withdrawal keeps identification history (D3-12) P1 search withdrawal Deletion-lifecycle programme ADR-014 committed and amended Not met: ADR-014 uncommitted
X-IMPORT: staged import (as #2612 plans) implemented; sized timeout with heartbeat and watchdog; idempotent sagas P1, P2 Study Management (import) with L12 AC-P1-19 Not met: #2612 is a plan document
X-AF2-PR9: AF2 Phase 4 PR 9 R4c AF2 programme Merged code Not met
X-PDFTOOLS: v1 pdf-tools migrated to AF2's data-source seam, or O1's scope states the gap O1, R4c AF2 programme Merged code, or O1 scope note Not met: unscheduled
X-ARCH-a: #3985 non-upsert saves and version bump on direct writes; #3973 awaited events F1a Architecture-review programme Merged with architecture tests Not met
X-ARCH-b: MassTransit-after-v8 decision (#3986) R0 (PM consumer guards) Architecture-review programme; Chris ADR Not met
X-ARCH-c: runtime flag provider fix (#3975) R0 admission; G-NOTIF evidence Architecture-review programme Merged with singleton-identity test Not met
X-ARCH-d: #3979 (fixed-two filter) and #3980 (ValueObject equality, threshold serialisation) R3a Architecture-review programme Merged Not met

13. Programme risks

Risk Mitigation
Claims and capacity guards do nothing in production (tracking off everywhere), so R2b/R3a capacity promises cannot be tested there X-CLAIMS; D3-16 (per-pilot binding-scope tracking recommended); E2E both modes
Switching tracking on breaks saves under FEAT-024 writes (typed 503 on mode disagreement) Binding-scope setting (T3) or the owned M15 transition (T2); static configuration on both hosts
Two reconcilers edit one study (no editor exclusion today) X-RECLAIM in every environment
Membership facts go wrong once canonical data leaves Study (re-offered studies, 404 resumes, slot miscounts) E20 as a per-reviewer projection; E64 seam with truth-table parity; R0 floor reader logic
Two reservation migrations touch hub, consumers and fold twice One migration (E68) with S4-B; count-only check first
A flag enabled on one host only (eligibility, allocation) E75: delivery to both hosts plus an agreement check
Batches activate with an O(N) lazy frontier and no durable opening X-BATCH criteria; performance gate before any enablement
Owner decisions recorded only in PR bodies (#3939's denominator) D3-13b; ledger precedence; PR text treated as PROPOSAL
FEAT-024's production chain is longer than the plan assumed Q-31(b) designed as the first pilot path; X-STATS-b chain with owners
Fixed two in the configuration digest makes target-aware counting a migration X-STATS-c with a reconciliation plan before R2b/R3a
The staging fold flag is now on, and project 0102 is both FEAT-024's pilot and a plan pilot seed D3-10b: keep 0102 out until C8-T07 passes; FEAT-024 STATUS corrected (R11)
Notification category skew blocks opt-out during deploys or rollbacks Tolerant preferences before #3942 merges (N1); C15-T06
Email continues after the flag is turned off; environment-wide flags; runtime overrides by any admin Delivery halt, per-project admission, inbox reads independent of capture (N3); G-NOTIF; D3-21
Exposure from reconciliation questions leaks into agreement as "independent" C3 exposure kind; AC-R4a-35, AC-R5c-07
Inbox floods from publications and outdated flags Flood controls before R2c (N8); recorded fan-out with a threshold
Nine stacked PRs with generated-file conflicts and no CI E2E merge badly Merge protocol (§8.2); run:e2e-full; ADR
#3961's roadmap changes foundations F1a freezes and competes for capacity; MassTransit v8 support ends December 2026 D1-02 joint sequencing; X-ARCH joins
Runtime flag overrides are silently reset (#3975) X-ARCH-c; no evidence relies on runtime overrides
Deletion physically removes identification history (ADR-014) D3-12; X-DEL redefined
Imports time out once P1/P2 add work inside the job X-IMPORT with 5,000- and 50,000-record criteria
Presence payloads disclose reviewer identities across blinding T10; D3-20
Orphaned claims hold capacity indefinitely Backstop before X-CLAIMS (T9)

14. Decisions, engineering items and assumptions

14.1 Batch D decisions this page depends on

ID What it decides here Recommendation used
D1-01 #3964 versus #3969 Decided by Chris (3 October): keep #3964, port #3969's active-member check and tests, close #3969. Carried out: #3964 merged (85e6facf7), #3969 closed
D1-02 Precedence and sequencing with #3961 Decided by Chris (3 October), as recommended: #3961 Phase 0 continues; X-ARCH-a before F1a; X-ARCH-b before R0; #3988 into E24 or after R2a; #3989 as L5 seam slices
D1-03 ProjectStatistics activate or freeze (#3987); GA counting Decided by Chris (3 October), as recommended: activate the families this plan uses; not freeze, so Q-31(b) is not extended to GA. Date (late on 3 October, register §1.14): from 5 October 2026, staging first, then production the following week as a target; production enablement keeps its own approval and waits for X-STATS-b1 to b7
D1-07 Production opt-in pilots before GA Decided by Chris (3 October), as recommended: yes, after staging acceptance, R0 soak and per-project admission slices
D1-08 Write-path gate shape Decided by Chris (3 October), as recommended: ADR-019 gate (b) shape; R10 measures it; the start thresholds apply now and F1a confirms them from M0 evidence
D1-09 Notification merge order and #3965 placement Decided by Chris (3 October), as recommended: as §8.2; restacking #3965 onto #3944 still needs the stack owner's agreement (A-33)
D2-07 Does a draft hold the place Middle ground (§6.2)
D2-08 Two tabs Read-only second tab with "Take over editing"
D3-01 UI1 timing for stack screens Inbox and preferences before testers; others before production
D3-07 "My work" placement Project-level in R3c/R4a; global after GA; the badge is flag-independent
D3-09 Eligibility absorption and D8 mapping R3a absorbs S6b (and others only if still paused); D8 as §5.2
D3-10a–d Fence read as current; project 0102; multi-profile served live; preview exempt from PS1 Yes to all
D3-11 Agreement store Own rebuildable store
D3-12 Deletion versus history Withdrawal keeps history; project deletion keeps ADR-014 with tombstone
D3-13a–f Allocation and batches Yes to all (§3.2, §4.2)
D3-16 Production claims route (a) binding-scope #3876 per admitted pilot
D3-17 Optional capacity cap Yes
D3-18 Shared-form tracking settings As §6.2
D3-19 No claim on a dependent form while screening Yes
D3-20 Presence disclosure Counts and own place; names for Monitor holders; never across BL1
D3-21 Notification enablement Pilot projects first; G-NOTIF per environment and kind family; halt before production email
D3-22 Emails name the project Yes, project name only
D3-23 Auto-resolve related notices Yes
D3-24 Per-project email mute Yes, before production email
D3-25 Conversations as audit record Yes

14.2 Engineering items E64–E75

ID Contract Lane / contract Gate
E64 Membership-facts seam. Extract IReviewMembershipFacts, behind which the pool predicates, StageWorkloadShareEligibility, AllocationClaimSlot, ActivityReservationAdmission, the own-place checks and the capacity pipelines read per-reviewer facts (own session state per form, claim kinds held, own decision per profile, other reviewers holding a place). Embedded-Study provider first with truth-table parity and no behaviour change; CanonicalSummary provider at R0/R2a (shape in the consistency model) Eligibility owner, L1, L7 / C6, C7 F1a (seam), R0 (floor reads it)
E65 Allocation on canonical stages. Refusal guard (no shares on canonical stages; R0 refuses a stage with an enabled regime) until AL1; AL1 adapter: regime schema v2 (form binding, target source), floor one release ahead, compatibility check, read APIs and editor on the membership projection, D8 slot rule on form-keyed claims, publication of a different target refused while a regime exists L7, allocation owner / C7 R0 (guard); F-A, AL1 (adapter)
E66 Requested-review admission and capacity cap. The requestedReview claim written by the AdditionalReviewRequest command (single use, expiring, audited), honoured by pool filters, allocation, typed admission and capacity guards for that reviewer only; target unchanged; counted outside allocation progress; the optional capacity cap (D3-17); the set of sessions that hold a place L6, L7 / C7, C9 F4 (design), R4a (build)
E67 Progressive batches for canonical stages. IStudyObligationEvidence with legacy and canonical providers; pool-entry-based membership with late cohorts; CAS opening and personal grant writing pool-entry events and durable intents; read-only status; performance gate (Next p95 at 100,000 studies and 2,500 batches; AC-R3a-26, AC-R3c-17) L7, batch owner, L12 / C7, C12 F3 (contract), X-BATCH (evidence)
E68 Claim contract v2 and one reservation-key migration. Typed claims (§6.2) unique per (study, kind, scope, reviewer), released when the last page ends; capacity claims on Study, editor claims on their aggregates; keyed by form identity; versioned hub methods, additive DTOs, new commands with old handlers kept ≥ suspension grace + idle timeout; presence index create–read-both–drop; S4-B targets the final key with stage provenance; the consumer inventory in §6.2 L7 with presence, eligibility and FEAT-024 owners / C7 F1a (contract), R2b (ship), X-ELIG (migration)
E69 Production claims route (X-CLAIMS). Per D3-16, the binding-scope tracking setting enabled per admitted pilot, or the M15 transition with an admin route rehearsed on staging; API and PM switched together statically; orphan-claim backstop (absolute lease expiry or bounded sweep behind its own flag); load and failover on Bramble; E2E in both tracking modes Presence owner, FEAT-024 owner, L17 / C7, C16 Before R2b/R3a production claims
E70 Tracking adapters. R0: tally getter and claim pipeline merge canonical counts and markers; tracking writers and readers in the inventory with tests. R2a: own-place detection via E64; claim release on first explicit Save/Complete in the canonical transaction; presence FormSessionId; dirty = draft-changes flag; draft-aware release (D2-07); draft lease on a stable tab ID that works untracked (D2-08) Presence owner with L0, L1, L5 / C5, C7, C16 R0, R2a
E71 Target-aware annotation classification. Replace the fixed two at StudyStats.cs:353-355, AnnotationThresholds.MinimumNumberSessions (digest input) and StudyRepository.GetSessionFilter (#3979) with the effective target; catalogue bump and digest migration with a reconciliation plan (forced rebuild of allowlisted projects; checkpoints keep identity); ProjectStageConfigurationChange.AffectedFamilies extended FEAT-024 owner; architecture-review owner (#3979) / C7 Before R2b pilots on allowlisted projects; before R3a (X-STATS-c)
E72 FEAT-024 canonical-sources amendment. Technical-plan amendment and ADR: families over canonical collections in the same pinned snapshot (FormVersionUsage, QuestionVersionAnswers at F2; profile families at F5 per D3-10c); scope kinds and key components with tolerant maps; onboarding contract with an N-1 test; #3506; usage over explicit versions with drafts counted authoritatively; the scoped-rebuild-at-pinned-snapshot service API; fence-read identity in the manifest; protocol 5 batched after gate (b) FEAT-024 owner with L2, L7 / C8 F2, F5
E73 C15 v2 in the notification stack. Kind registry via DI; Source sub-document; one capture service (bulk upsert, $setOnInsert); deterministic SourceId and row ID; inline and recorded fan-out (NotificationFanOut plus leased expander); server label, context, availability and state; disclosure hook per channel; unknown kinds leave the ledger row ready one deploy ahead; registry test Notification programme; L14 (spec), L8 (hook) / C15, C19 ADR in W0; frozen at F1b; landed after the base merges, before the first review-workflow kind
E74 Notification enablement controls (G-NOTIF evidence). Tolerant preferences and kind→category map before #3942 merges; declared email→inbox dependency; operator delivery halt; inbox reads independent of capture admission; per-project notification admission (interim registry, then R0 enrolment scope); idle workers; route guards with opt-out reachable; flood controls; retention per E32 Notification programme with L0 / C15, C16 Before any enablement outside e2e and Mailpit; flood controls before R2c
E75 Flag delivery and evaluation consistency. reviewEligibilityPolicy and proportionalStudyAllocation delivered to PM (one block replacing #3939's partial one) with a cross-host agreement check before either is enabled; PM-hosted branches inventoried; one flag evaluation per request passed into admission (AP-22) with a test; no gate or evidence relies on runtime overrides until #3975 is fixed; R0 admission is the domain enrolment that P7 per-project targeting keys to L0 with eligibility, allocation and flag-overhaul owners / C6, C16 Before X-ELIG enablement; F1a (P7 alignment)

14.3 Assumptions A-31–A-34

ID Assumption Basis Cost if wrong
A-31 The review-eligibility programme stays paused through F3 Session memory ("paused 25 September until the statistics work finishes"); not recorded in the repository; #3746 had activity on 1 October If it resumes, X-ELIG slices return to it and R3a's absorbed scope (D3-09) shrinks; if it stays paused, R3a absorbs S6b (and S4-B, S4-C, S6a as needed) and grows
A-32 No legacy untyped slot reservations exist in production, because claims are created only with tracking on, which no deployed environment has enabled RT-23 code reading; count UNVERIFIED S4-B must migrate them before eligibility is enabled; an authorised count-only check per environment settles it
A-33 The notification stack owner accepts C15 v2 and lands it after the base merges and before the first review-workflow kind, and restacks #3965 onto #3944 (D1-09) NS §4; the owner has not been consulted Each review-workflow kind edits shared notification files; conversations wait for #3947's isolation review
A-34 The architecture-review programme lands #3985 and #3973 before F1a #3961 Phase 0/1; D1-02 F1a waits, or canonical repositories carry their own isolated-read, non-upsert and awaited-dispatch discipline enforced by an architecture test, and R0's floor also covers the version-less direct writers (StudyRepository.cs:1306-1319, 1620-1650)

Resolution record

Finding Category Where Note
AP-01 Corrected §3.2; §2.2; E64; contracts C7, open questions E20 note E20 is a per-form, per-reviewer membership projection carried by CanonicalSummary (consistency model); seam and truth-table parity in AC-M0-04
AP-02 Adopted §3.2; §9; §12 X-AUTH-RESOLVER New join; D3-13d for the interim pool-level Monitor
AP-03 Question §3.2; E66 D3-13c; requestedReview claim design ready
AP-04 Corrected §4.2; E67; contracts C7, AC-R3c-05 X-BATCH rewritten; evidence seam; pool-entry denominator
AP-05 Corrected §4.2; E67; contracts C7 Durable CAS opening with pool-entry events; status read-only; performance gate
AP-06 Question §3.2; E65; contracts C7, open questions A-09 D3-13a; refusal guard recommended
AP-07 Adopted §5.2; §6.2; E68 One migration with S4-B; consumer inventory
AP-08 Adopted §5.2; E75; inventory §5 Flags to both hosts with an agreement check
AP-09 Corrected §5.2; §12 X-ELIG X-ELIG enumerated; absorption per D3-09
AP-10 Adopted §3.2; E65 AL1 after allocation Phase 2, before Phase 3; regime schema v2 and floor
AP-11 Adopted §3.2; §4.2 Settings reference regime and plan by ID (PROPOSAL at F3)
AP-12 Question §4.2 D3-13b; #3939's text stays PROPOSAL until recorded
AP-13 Adopted §3.2; AC-R6-14 Adoption rows for target, threshold and enabled regimes
AP-14 Corrected §7.2; E71; §12 X-STATS-c FEAT-024-owned prerequisite with reconciliation plan
AP-15 Adopted §3.2; E65 Read APIs refuse canonical stages until AL1
AP-16 Adopted AC-AL1-04..08 —
AP-17 Adopted §3.2; AC-R3a-26 Selection p95 400 ms PROPOSAL; sampling at F3
AP-18 Adopted §4.2; inventory §6 Two supersession rows
AP-19 Corrected §4.2; §12 X-ELIG before X-BATCH activation
AP-20 Adopted §3.2; change A7 Route retired in the first L16 PR
AP-21 Corrected §3.1; inventory §2, §3, §7 Refreshed; owner asked to update STATUS (A6)
AP-22 Adopted §3.2; E75 One evaluation per request, with a test
AP-23 Adopted §3.2; change A9 #3269 checklist before F-A
RT-01 Corrected §6.2; §12 X-RECLAIM; contracts C7, open questions E6, AC-R4a-36 to 39 X-TRACK replaced; E6 "always"
RT-02 Corrected §6.2; open questions Q-25 note Q-25 premise corrected; route is D3-16
RT-03 Adopted §6.2; §12 X-CLAIMS; E69 Joint ownership with FEAT-024
RT-04 Adopted §6.2; T11; AC-ALL-23 E2E in both modes
RT-05 Corrected §6.2; E70; open questions A-21, AC-R2a-35 to 37 and AC-R2a-06 R2a adapter
RT-06 Corrected §3.2; §6.2; contracts C7 Projection keyed by form with per-reviewer markers
RT-07 Corrected §6.2; E70; AC-R0-09 Floor reader logic (mechanics in consistency model)
RT-08 Corrected §6.2; contracts C16 Tracking writers and readers in the inventory
RT-09 Question §6.2 D2-07 middle ground
RT-10 Adopted §6.2; T7; E70 Lease on stable tab ID; take-over per D2-08
RT-11 Adopted §6.2; E68; contracts C7 Claim contract v2 at F1a
RT-12 Question §6.2; open questions Q-28 D3-18
RT-13 Question §6.2; E66 D3-17 (cap) with D3-13c (requested review)
RT-14 Question §6.2; §9 D3-20; C10 channel listing adopted
RT-15 Adopted §6.2 Transaction rows supplied to the consistency model
RT-16 Corrected §6.2; AC-R2b-03 Rewritten
RT-17 Adopted §6.2; open questions Q-20 Dependency recorded; minimal version drops the warning
RT-18 Adopted §6.2; AC-R3c-14 Completion withdraws claims through the outbox
RT-19 Adopted §6.2; AC-R2c-19 Target reduction through D6's flow
RT-20 Adopted §6.2; AC-R1c-10 Revocation releases claims
RT-21 Adopted §6.2; open questions E32 Presence and connection retention
RT-22 Adopted §6.2 Slot vocabulary in the copy deck (UX strategy)
RT-23 Adopted §5.2; A-32; AC-R3a-24 Count-only check; eligibility before tracking
RT-24 Adopted §6.2; T9; AC-T-06 Backstop before X-CLAIMS
RT-25 Adopted §6.2; contracts C15 No per-claim notices; scope-keyed revocation events
RT-26 Adopted §6.2 Exposure in REST payloads only
RT-27 Adopted §6.3 T1 Docs PR before F1a
MS-01 Corrected §7.2; §12 X-STATS-b chain; Q-31(b) designed first pilot path
MS-02 Corrected §7.2; E72; contracts C8 New families and scope kinds by amendment
MS-03 Corrected §7.2; contracts C8, AC-R2c-25 Drafts counted authoritatively
MS-04 Corrected §7.2; contracts C8, open questions A-08 Fence read and scoped rebuild API; D3-10a confirms the reading
MS-05 Corrected §7.2 Command ledger (consistency model); FEAT-024 reuses CommandId
MS-06 Corrected §7.2; R1 Source-write seam (mechanics in consistency model)
MS-07 Corrected §7.2; E71 Named FEAT-024 change; three sites
MS-08 Question §7.2 D3-10c; R3a default profile projected exactly
MS-09 Corrected §7.2; contracts C8, C16 Three mechanisms named
MS-10 Adopted §7.2; §12 X-STATS-a D3-10b, D3-10d; staging fold flag noted
MS-11 Corrected §7.2; contracts C8, AC-P1-07 PRISMA from authoritative records only
MS-12 Corrected §7.2; R10 Per-project sequence removed (brief §1.2; consistency model)
MS-13 Corrected §3.2; §6.2 Projection fields and write rules in the consistency model
MS-14 Adopted §7.2; R3 Onboarding contract before F2
MS-15 Adopted §7.2; R6 Protocol 5 once, after gate (b)
MS-16 Adopted §7.2; AC-ALL-04 © FEAT-024 rollback order in AC-ALL-04 ©
MS-17 Adopted §7.2 Adoption fences and rebuild; #3845 or manual label
MS-18 Adopted §7.2; contracts C8 Canonical designer reads question-version answers
MS-19 Adopted §5.2; R9 StageSettings version replaces the Project token
MS-20 Question §7.2 D3-11
MS-21 Adopted §7.2 Overview fields extend existing queries
MS-22 Adopted §7.2; contracts C8 History never an as-of input
MS-23 Corrected §7.1; R11; inventory §3, §7 Refreshed; staging fold flag pinned on (new)
MS-24 Adopted §7.2; R8; contracts C16 #3524 after C16; admission record is not the allowlist
NS-01 Corrected §8.2; E73; contracts C15 Two capture modes; "no outbox" wording replaced
NS-02 Adopted §8.2; N1; E74 Before #3942 merges
NS-03 Adopted §8.2; N4; E73 Kind registry and single capture service
NS-04 Adopted §8.2; N3; E74; §12 G-NOTIF D3-21 sets the enablement policy
NS-05 Question; answered 3 October §8.2; N2 D1-09; #3965's ten pushed commits noted; the legacy cross-stage refusal is in the tenth; the R4a rule is outstanding
NS-06 Adopted §8.2; AC-R5c-07 C3 exposure kind
NS-07 Adopted §8.2; AC-R3c-11, AC-R3c-12, AC-R4a-47 D3-07 for placement
NS-08 Corrected §8.2; N9; contracts C16 Two stack Study writers added
NS-09 Corrected §8.2; N7; AC-R1c-04 X-NOTIF for R1c; dashed edges
NS-10 Question §8.2; N8 D3-01
NS-11 Adopted §8.2; acceptance criteria §7.5 C15-T01 to T11 —
NS-12 Corrected §8.2; contracts C15 Deterministic SourceId; wrong precedent removed
NS-13 Adopted §8.2; N8 Before R2c
NS-14 Adopted §8.2; open questions Q-20 PROPOSAL narrowing
NS-15 Adopted §8.2; notifications §3 Rows and taxonomy
NS-16 Adopted §8.2; E74 Live-on-merge list
NS-17 Adopted §8.2; N12 Merge protocol; X-NOTIF definition; ADR
NS-18 Corrected §8.2; N10 #3944 out of the projection's readers
NS-19 Adopted §8.2; §11 Ownership transfers
NS-20 Adopted §8.2; §9 Capabilities in C10
NS-21 Adopted §8.2; open questions E32 Stores beyond the inbox
NS-22 Corrected §8.2; notifications §1.2, §6 Items marked untracked
NS-23 Adopted §8.2 Capture modes per operation (consistency model)
NS-24 Adopted §8.2 Issues never carry answer disputes after R4b
NS-25 Follow-up §8.2 Stack backlog: remove projectInvitation from email categories
NS-26 Adopted §8.2; E73 Unknown kind leaves the row ready, one deploy ahead
DS-02 Corrected §10; §12 X-ARCH-a–d; plan §8, §10 Programme added; sequencing via D1-02, D1-03; D1-01 decided
DS-04 Adopted §11; §12 X-AF2, X-SHELL Plan-owned per-project admission slices; D1-07
PH-02 Question §11; §12 X-DEL D3-12
PH-04 Question §5.2 D3-09 (D8 mapping); truth table extended
PH-09 Adopted §11; §12 X-IMPORT #2612 verified as a plan document only
PH-14 Adopted §11; E75 R0 admission is P7's domain enrolment
PH-15 Adopted §11; contracts C16 M5/P9 classification and broker rules
PH-20 Adopted §11; §12 X-PDFTOOLS Or O1 states the gap
DC-05 Corrected §5.2; R9; §12 X-ELIG No per-project document per admission; StageSettings-version default
DC-21 Corrected §7.2; contracts C8, C16 Admission refuses transactional point mode