Skip to content

Temporary planning review record. The report below is reproduced verbatim as returned by the independent read-only reviewer (Plan agent, Fable model, launched 3 October 2026 about 15:00 BST at Chris's request to review in-flight programmes and their integration with the plan). Only this front matter and note were added. Line numbers refer to the package as it stood when the reviewer read it. Resolutions are in the round-2 resolution matrix.

MS review: materialised project statistics (FEAT-024) against the integrated review plan

Path abbreviations used below (all absolute): - MAIN = /home/chris/workspace/syrf/main - PKG = /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10 - STATS = MAIN/docs/features/materialized-project-statistics - GITOPS = /home/chris/workspace/cluster-gitops/syrf

Code facts were checked on main at 0f5c61073 (3 October 2026, ~14:40 BST). Live PR/issue states were read with gh at the same time.

1. Verdict

The plan treats FEAT-024 as a nearly finished service with one remaining production gate ("gate (b)") and a small catalogue extension ("new kinds inside the existing family, no new counters"). Neither holds. On main, FEAT-024 is a Study-centric projection whose only production-viable write path (the async fold) is dark in every environment, is at its production baseline protocol 4 with a provisional-fail write benchmark, has never had its pending index built anywhere, and still needs a soak, a production rollout approval and three open correctness follow-ups before any family can be served in production. What the plan asks of it (a stage-free, form/version-keyed usage family whose authoritative source is a collection that is not pmStudy; draft-only counts that no Study write can carry; an in-process "targeted refresh" that does not exist; a single receipt authority built on receipts that are only written when statistics flags are on; profile-grain screening; replacement of the hard-coded "enough = 2" that sits inside the configuration digest) amounts to a FEAT-024 programme amendment, not a contract amendment. The five changes that matter most: (1) rewrite X-STATS-b and C8 as a prerequisite chain and make the Q-31(b) path the designed pilot path, with the materialised family as a swap-in (MS-01, MS-04); (2) make the canonical engine integrate only through a FEAT-024-owned source-write seam over the Study summary projection, never writing pending entries or statistics itself, and keep the existing families exact by construction for R2a/R2b/R3a (MS-06, MS-13); (3) drop the "one receipt authority" reuse of FEAT-024 receipts and share the operation id instead (MS-05); (4) schedule two FEAT-024 changes before R2b/R3a that the plan currently assumes are free: target-aware annotation classification (digest migration) and a new-family onboarding contract with old-binary tolerance (MS-07, MS-14); (5) separate the three "N-1" mechanisms the plan conflates and keep any fold-protocol bump off R2a's critical path (MS-09, MS-15). Nothing here re-opens an owner decision; PS1–PS3, Q-31 and OPS1 stand, but their consequences are not yet costed.

2. Current-state table

Component State Evidence Known gaps
Foundation: 10 metric families (9 physical + DerivedSummary), 7 scope kinds, control rows, guards, fences, receipts, outbox, checkpoints Merged, dark by default MAIN/src/libs/project-management/SyRF.ProjectManagement.Core/Model/ProjectStatisticsAggregate/ProjectStatisticsMetricFamily.cs:10-41; …/ProjectStatisticsScopeKind.cs:11-45; STATS/STATUS.md:186-224 StageQuestionVersion scope is reserved and never written (only Read/ProjectStatisticsScopeSelector.cs and tests reference ForStageQuestionVersion); ProjectQuestion is question-only grain (ProjectStatisticsScopeKey.cs:95-103)
Transactional (same-transaction) write path Merged; measured FAIL STATS/STATUS.md:364 ("all eight cells exceed the 10% p95 gate (46% to 1,283%)", 25 commands per save) Not viable for production; superseded by the fold in intent, still the active path everywhere
Async point fold (ADR-019), slices 0–7 Merged (slice 7 docs #3962 merged 06:13 UTC) ; dark everywhere MAIN/.claude/rules/materialized-stats.md:104-151; STATS/STATUS.md:372-386; gh pr view 3962 = MERGED 5cca5e490 Fold flag materializedProjectStatisticsFold absent from every GitOps values file (default false); no project in fold mode; pending index IX_Study_PendingStatistics not built in any environment (STATS/STATUS.md:353)
Fold protocol and N-1 window Protocol 4 = production baseline P0; window at P0 is {4}; stamp advance built, StampAdvanceAllowed unset MAIN/src/libs/project-management/SyRF.ProjectManagement.Core/Services/ProjectStatistics/Fold/ProjectStatisticsFoldProtocol.cs:60,66,95-102; STATS/phase2c-staging-proof-runbook.md:2308-2350 Breaking bumps need reset+backfill per project; stamp advance is an operator procedure per bump
Production fold enable Refused in code …/Fold/ProjectStatisticsFoldAdministration.cs:240-244,257-260 (syrftest and unknown DB names refused with FoldCoverageIncomplete "until gate (b) passes and the production rollout is separately approved"); commit 89d295734 Lifting it is "part of the separately approved production rollout, never a feature PR" (materialized-stats.md:135-138)
Gate (b) (write gate: <10% or ≤2 ms p95, zero statistics-caused failures at ½/5/10 reviewers) PROVISIONAL FAIL on a loaded host; idle rerun on Bramble pending STATS/screening-write-benchmark.md:453-470 (14/16 cells miss latency; +2 ms uncontended, +7–21 ms contended; zero statistics-caused conflicts); STATS/STATUS.md:393-404 Likely lever is the pre-write durable-mode re-read, a design-owner safety decision (screening-write-benchmark.md:629-648)
Soak gate Open Issues #3510 (open, updated 06:11 UTC 3 Oct), #3952 (open) ; soak moved to the isolated e2e stack on Bramble by the 30 Sept decision (STATS/STATUS.md:367) 7 days / 10,000 reads / 1,000 mutations not started
Staging deployment Writes, Serving, ProjectScreening on; allowlist = project …0102 ("Ready for Annotation"); consumers ProjectOverview/Pages/SignalR/Exports/History on; StageOverview off; scheduled repair on GITOPS/environments/staging/api/values.yaml:35,110-136; GITOPS/environments/staging/project-management/values.yaml:39,43,85-111 Runtime overrides (revision 72) to be cleared, UNVERIFIED (STATS/STATUS.md:169-174)
Production deployment No statistics keys at all; chart defaults (all false) Grep of GITOPS/environments/production/** finds no materializedProjectStatistics*/ProjectStatistics* key; MAIN/src/charts/syrf-common/env-mapping.yaml:1627-1691 defaults "false" Production images were older restore builds on 22 Sept (STATS/STATUS.md:166); current production image state UNVERIFIED
Preview environments No statistics keys Grep of GITOPS/environments/preview/preview.values.yaml finds none Pilots on preview (Q-07) have no FEAT-024 at all
Per-project admission Static deployment allowlist on both hosts; durable eligibility (#3524) planned, not built …/Lifecycle/ProjectStatisticsAllowlist.cs:16-30; STATS/project-rollout-administration.md:34-39,66-76 "A cache TTL … is not that barrier"; eligibility must participate in source admission in-transaction
Consumers (Project Overview screening totals, Stage Overview pie/bundle, reviewer progress stores, Screening Info, question counts and assignment locks, search counts, four history charts) Merged behind independent flags; route SignalStores (#3776–#3795) STATS/STATUS.md:226-281; STATS/statistics-reference.md:277-291 Each needs activation, parity and browser proof; ReviewController.GetFullStats still runs the broad aggregate
Reviewer screening drift guard Guard merged (#3956, 05:53 UTC); admin re-persist tool open gh pr view 3956 MERGED b0c18e033; issue #3960 open Blocks MembershipScreening/ReviewerScreening in production
Annotation writer flag coupling Open defect (GitHub closed #3840, code unfixed) …/Families/Annotation/ProjectAnnotationStatisticsWriter.cs:76-79 (Families.All(IsFamilyServingRequested)); STATS/STATUS.md:20-24 Enable QuestionAnswers/ReviewerAnnotation only with both annotation flags on
Receipts and receipt maintenance Merged; maintenance default off; receipts written only when a statistics operation is prepared …/Model/ProjectStatisticsAggregate/ProjectStatisticsSourceOperationReceipt.cs:20-60; ProjectStatisticsReceiptRetirement.cs:14-15 (two supported namespaces); MAIN/src/services/api/SyRF.API.Endpoint/Services/SubmitAnnotationSessionService.cs:161-176 Fold receipts have ObservedAtUtc = fold time; overflowed/quarantined saves have no receipt (statistics-reference.md:209-216)
Open FEAT-024 PRs None gh pr list --state open --search "statistics OR stats OR fold OR FEAT-024" returns no FEAT-024 PR; #3591/#3766/#3767 (listed open in STATUS) are MERGED STATS/STATUS.md:43-47,386 stale on those four
Other open follow-ups #3953 (stamp-advance), #3849 (fence gaps), #3845 (parity calculators for non-screening families), #3846, #3910, #3876, #3254, #3474, #3506 (families inherit ProjectScreening version constants), #3360 (PM host ignores runtime flag toggles), #3255, #3364, #3194 (benchmarks not in CI), #3086 gh issue view on each, all OPEN #3506 and #3845 bear directly on adding families for the plan

3. Findings

ID Severity Location Finding Evidence Recommended change
MS-01 Major PKG/integrated-plan.md:739 (§5.11 X-STATS-b), :922-924 (§6.4), :939 (W3); PKG/contracts.md:351-352 (C8 "Production") "FEAT-024 production readiness for the usage family (gate (b))" names one gate for a chain of at least seven: gate (b) idle-host pass; soak (#3510/#3952); production pending-index build in an approved window; separately approved production rollout that lifts the in-code refusal; a production allowlist/eligibility mechanism (today a static deployment list); the usage family itself (does not exist: new enum value, scope kinds, calculator, backfill route, flag, consumer manifest, fleet dispatch, parity calculator); and that family's own staging proof. W3 schedules "R2c ships (production per Q-31)" right after R2a, so by the plan's own windows the Q-31(b) path will be the first production path, not an exception. ProjectStatisticsFoldAdministration.cs:240-260; STATS/statistics-reference.md:253-259 ("Before production" column per family); STATS/STATUS.md:393-419; STATS/project-rollout-administration.md:60-76 Rewrite X-STATS-b as a numbered prerequisite list with owner evidence per step (X-STATS-b1…b7). State in R2c and §6.4 that the (b) path (authoritative counting and identity enumeration at the protected boundary) is the designed first pilot path and the materialised family is a swap-in behind the same interface. Add X-STATS-a for staging/preview (MS-10).
MS-02 Major PKG/domain-model.md:62 ("no new counters"), :147-149 ("new kinds inside the existing ProjectStatistics family"), :166; PKG/contracts.md:336-340 (C8 usage family) The usage family's authoritative source is pmFormSession/pmAnnotationHead, not pmStudy. Every FEAT-024 family today is rebuilt from Project and Study, parity-audited against a Study pipeline, bound to Study.Version as source revision, and keyed by a scope key with fixed components (StageId, InvestigatorId, QuestionId, QuestionVersionId, SystematicSearchId). FormId/FormVersionId/ProfileId/ProfileVersionId are new key components, new metric keys and a new family: this is a technical-plan amendment ("canonical sources"), not "new kinds". STATS/README.md:114 ("can always be rebuilt from Project and Study"); ProjectStatisticsScopeKey.cs:84-103,139-152; ProjectStatisticsSourceOperationReceipt.cs:28,42 (expectedSourceRevision); IProjectStatisticsFamilyScopeCalculator under …/Services/ProjectStatistics/Lifecycle/ Replace "no new counters" with: "new families (FormVersionUsage, QuestionVersionAnswers; later ProfileVersionDecisions) with new scope kinds 7–9 and key components, authoritative over the canonical collections in the same pinned snapshot, delivered by a FEAT-024 technical-plan amendment at F2 (forms) and F5 (profiles)". Record that the catalogue/source version for these families is independent of ProjectScreening's constants (#3506 must be fixed first).
MS-03 Major PKG/contracts.md:336-339 (sessions by category incl. draft-only); PKG/open-questions-and-assumptions.md:123 (E21 "drafts stored outside Study; autosave must not bump the Study version") FEAT-024's point maintenance is Study-centric: a change is counted only when a Study write carries a pending entry (fold) or a classified delta (transactional). A draft-only session never writes Study, so a draft_only count cannot be point-maintained; it can only be Stale-on-event or computed. The plan assigns it to the materialised family anyway. materialized-stats.md:110-119 ("Hot paths never write statistics… a fold-path save writes its own Study"); STATS/statistics-reference.md:144-158 Define the usage family over explicit versions only (completed, saved_incomplete, by form version, deduplicated across stages). Count draft_only authoritatively from pmSessionDraft (indexed by base form version per E21) inside the publish fence; record both in the FormPublishOperation manifest with their basis. State this in C8 and E3.
MS-04 Major PKG/open-questions-and-assumptions.md:190 (A-08 "targeted refresh of the materialized rows… never an ad-hoc substitute"), :105 (E3 "targeted refresh, pause recovery"); PKG/contracts.md:343-348; PKG/acceptance-criteria.md:192 (AC-R2c-04) No in-process, project-admin-callable "targeted refresh" exists. Republishing a Stale scope is POST api/admin/project-statistics/{id}/{family}/backfill (BatchAdminProjects/operator identity only, synchronous despite 202, may answer 409 Contended/RetryExhausted/PendingNotDrained) or the hourly default-off repair. Conversely, FEAT-024's reader already returns a pinned authoritative value for a Stale scope in the same snapshot (coherent fallback), which is exactly what PS2 permits ("actively update the specific relevant statistics there and then"); A-08 forbids it. Also, FEAT-024 has no "pause": the definition-rewrite fence makes every read answer 503 for the whole project until released Stale, which is a read fence, not a reviewer pause. STATS/STATUS.md:344; STATS/phase2c-staging-proof-runbook.md:417-537; …/Families/QuestionAnswers/IProjectStatisticsDefinitionRewriteFence.cs:31-43,79-91; STATS/statistics-reference.md:109-118 E3 to specify: (i) a FEAT-024 service API "scoped rebuild at a pinned snapshot" (reusing ProjectStatisticsRebuildService.PublishAsync, the sanctioned retry unit) callable by the publication command under the fence; (ii) "current" for PS2/PS3 = a FEAT-024 read at the fence whose result is Materialized-Fresh or pinned-Authoritative, with the read's revision identity (projection revision, source revision, digest) recorded in the frozen manifest; (iii) the reviewer "pause" is the canonical engine's project/form-scoped write fence (the same primitive adoption uses), not a FEAT-024 facility. Reword A-08 accordingly.
MS-05 Major PKG/contracts.md:116-117 (E35 "reuse FEAT-024's source-operation receipts… never two idempotency systems"); PKG/domain-model.md:147; PKG/open-questions-and-assumptions.md:137 FEAT-024 receipts are a statistics-protocol artefact: BeginOperationAsync returns null unless Writes, the family flags and the allowlist admit the project (so in production today no receipt is ever written); the receipt binds ExpectedSourceRevision = Study.Version; retirement supports only two namespaces; on the fold path ObservedAtUtc is the fold time and overflowed or quarantined saves have no receipt. Canonical command idempotency cannot depend on statistics flags, on Study versions of aggregates that are not Study, or on a worker. SubmitAnnotationSessionService.cs:161-176; ProjectAnnotationStatisticsWriter.cs:76-79,95-98; ProjectStatisticsReceiptRetirement.cs:14-15; STATS/statistics-reference.md:209-216,189-197 Replace E35 with: the canonical command receipt (keyed commandId + payload digest, always on) is the authority for canonical commands; FEAT-024's source-operation receipt remains the statistics-protocol receipt and reuses the canonical commandId as its operation id and the same digest, so one id has at most one statistics operation. Add the digest rule to C1: a retry with the same commandId and digest returns the original receipt; a different payload under the same id is refused as a typed conflict (mirrors DigestMismatch).
MS-06 Major PKG/contracts.md:118-121 (C1 Study coupling "carries FEAT-024 pending entries under the fold protocol"); PKG/integrated-plan.md:406-408; PKG/domain-model.md:174-181 (§5) The plan has the engine write pending entries. Which path applies depends on flags and the project's FoldMode: all off (production today) means nothing; writes on and fold off means the transactional coordinator (snapshot transaction, guards, receipts); fold on means a whole-pending-set append under the N-1 rules. The fold is dark and provisional; its vocabulary is pinned by command-budget tests. An engine that writes entries itself couples R2a to a protocol that may still change if gate (b) fails. materialized-stats.md:116-122,139-145; …/Fold/ProjectScreeningFoldSave.cs, ProjectAnnotationFoldSave.cs; STATS/phase0-mutation-ownership-matrix.md:47-50 C1/E20: the engine never writes statistics or pending entries. It writes Study only through a FEAT-024-owned source-write seam (the writer/fold-save pair today) extended with a "projection-only" shape that takes the before/after Study summary projection and emits whatever the active path needs (nothing, a classified delta, or an entry/intent). Correctness floor for every path: every family a commit can affect is at least staled (intent) when statistics are on. Give V2-19's table a "statistics half" column with these three cases.
MS-07 Major PKG/acceptance-criteria.md:221 (AC-R3a-06 "the hard-coded 'enough = 2' is replaced by targets"); PKG/integrated-plan.md:516; PKG/contracts.md:325-326 (C7 digest row) "Enough = 2" is not only StudyStats.cs: the materialised classifier mirrors it as AnnotationThresholds.MinimumNumberSessions = 2, and that constant is an input of the shared configuration digest (annotation.v1:mns=). Replacing it with the effective target is a catalogue version bump for SA/MSA/DR/RA and a digest-format change, which the rules say "requires an explicit compatibility/reconciliation plan for established controls, current rows and retained checkpoints before serving is re-enabled". Today only ReviewerAnnotation is staled on a target change. The plan treats it as an R3a side effect. MAIN/src/libs/project-management/SyRF.ProjectManagement.Mongo.Data/StudyStats.cs:353-355; …/Families/Annotation/AnnotationThresholds.cs:16,28; …/ProjectStatisticsConfigurationDigest.cs:14-16; …/Families/Annotation/ProjectStageConfigurationChange.cs:42-53; materialized-stats.md:40-50 Make "target-aware annotation classification" a named FEAT-024 change (recommendation R2 in §4) with its own reconciliation plan (forced rebuild of allowlisted projects, checkpoint identity kept), landed before R2b/R3a and referenced by AC-R3a-06. Until then AC-R3a-06 cannot pass for materialised consumers.
MS-08 Major PKG/integrated-plan.md:537-549 (R3b), :525 (R3a); PKG/contracts.md:107 (kind policies), :325 (C7 tallies); F5 exit evidence :805 ProjectScreening/MembershipScreening/ReviewerScreening are legacy pipelines over ScreeningInfo.Screenings (one decision per reviewer per project) and persisted InclusionInfo; the fold's reviewer transition needs the persisted statuses and a membership digest. R3a's single default profile can be projected into that shape exactly. R3b's several profiles cannot ("legacy single-ScreeningInfo can't represent canonical decisions, so no flattening", the plan's own migration row). F5 lists profile-version usage (C8) but not profile-grain screening families, so R3b would silently break the three screening families on canonical projects. STATS/reviewer-screening-rebuild.md:22-37,83-101; STATS/statistics-reference.md:178-179; PKG/migration-adoption-rollback.md:145; research …/screening-specialised-annotation-research.md:1155-1160 ("do not serve existing project-wide projections as though they already have profile grain") R3a: state that the Study projection writes the default profile's decision into legacy ScreeningInfo/InclusionInfo so PS/MS/RS stay exact (an AC-R3a criterion with the parity audit). R3b/F5: add "profile-grain screening families (ProjectProfile, MembershipProfile scope kinds) as a FEAT-024 scope amendment, or PS/MS/RS declared unsupported for multi-profile projects (served live)" to the F5 exit evidence. Ask Chris which (Q3 below).
MS-09 Major PKG/integrated-plan.md:283-284 (R0 "the same N-1 rule FEAT-024 follows"); PKG/contracts.md:344-345 (C8 "Derivation changes are breaking changes within the N-1 window"); PKG/domain-model.md:46-48 Three distinct mechanisms are merged into one "N-1 rule": (a) BSON extra-element tolerance for value-object maps (#3145/#3512), which is what R0 needs; (b) the fold protocol window (owner decision h), which governs only pending-entry derivation from P0 and needs a stamp advance per bump; © per-family catalogue/source versions and the configuration digest, which govern rows, guards and checkpoints and today are coupled to ProjectScreening's constants (#3506). A "derivation change" in a family's counters is ©, not (b). materialized-stats.md:37-39,129-134; ProjectStatisticsFoldProtocol.cs:28-48; gh issue view 3506 OPEN In C16 and C8, name the three mechanisms and which plan change triggers which: R0 floor = (a); new transition kinds for canonical commits = (b), one bump (MS-15); new families/dimensions/target-aware classification = ©, per-family versions after #3506.
MS-10 Major PKG/acceptance-criteria.md:432-460 (§6 pilots: seeded projects incl. "Ready for Annotation", preview and staging); PKG/integrated-plan.md:95-99, :737-739 The FEAT-024 staging pilot is project …0102 "Ready for Annotation", the only allowlisted project, with Writes/Serving/Screening on. It is also one of the plan's named pilot seed projects. A canonical writer in that project meets live statistics (fences answering 503, the screening writer's transactional path, the parity audit). Preview environments carry no FEAT-024 keys, so an R2c pilot on preview cannot satisfy PS1 at all; and enabling the usage family on staging is itself a FEAT-024 decision (STATUS keeps every non-screening family off). GITOPS/environments/staging/api/values.yaml:35; STATS/STATUS.md:164,544-551; preview grep (no keys); MAIN/src/services/project-management/SyRF.ProjectManagement.Endpoint/Seeding/DatabaseSeeder.cs:561-664 Add X-STATS-a (staging): usage family on and pilot projects allowlisted on both hosts, by FEAT-024 decision. Exempt preview pilots from PS1 (they use the (b) path) or require per-PR #preview-config. Keep project 102 out of R2a–R3a pilots until a "canonical commit on an allowlisted project with statistics on" fixture passes (MS-24 test list).
MS-11 Major PKG/acceptance-criteria.md:350 (AC-P1-07 "gains source type without a new counter"); PKG/contracts.md:475-477 (C12 identification part); PKG/integrated-plan.md:689 (P1) A source-type dimension is new metric keys (counters) in the SearchPopulation family and therefore a catalogue/source-version change; retained checkpoints never gain it ("missing history is explicitly unavailable"). SearchPopulation counts SystematicSearch.NumberOfStudies (reference-file metadata), enumerates through search-side ProjectIds links, and has no notion of withdrawal (amendment J). PRISMA box 2/11 must count Citations at a frozen watermark. If R5b reads FEAT-024 rows, "regenerating a frozen report gives identical numbers" (AC-R5b-02) depends on a disposable projection. materialized-stats.md:194-211; STATS/README.md:133-136; STATS/statistics-reference.md:577-597 C12/R5b: PRISMA snapshots are computed from authoritative records (Citations, ExternalStepRecords, ScreeningOutcomes) at the report watermark and stored frozen; FEAT-024 rows are never a report input. Reword AC-P1-07 to "the SearchPopulation family gains source-type metric keys under a new family source version; retained history stays unlabelled; withdrawn searches are excluded by the family's enumeration rule".
MS-12 Major PKG/contracts.md:444-446 (C11 ordering: per-project commit sequence allocated inside each canonical transaction); PKG/domain-model.md:143; E25 PKG/open-questions-and-assumptions.md:127; PKG/acceptance-criteria.md:172 (AC-R2a-19) A per-project counter document written in every Save/Complete serialises all of a project's canonical commits on one document. FEAT-024 measured the identical pattern (Project.StatisticsAdmissionToken written on every eligibility save): "the Project token serializes every save", counted as a cost driver, and deferred as an eligibility redesign. The plan's AC-R2a-19 budget (1.2× today's p95) is to be met on the same hot path. STATS/reviewer-screening-rebuild.md:44-53; STATS/screening-write-benchmark.md:474-476; STATS/async-point-fold-design.md:1899-1902 (deferred item 2) Decide E25 at F1 on benchmark evidence using FEAT-024's harness cells (½/5/10 reviewers, same/different Study). Default to a design without a per-project hot document: per-Study ordering (Study.Version, revision commit time) plus the C11 watermark (at least one maximum transaction lifetime in the past) already gives identical exports at the same watermark; if a project-wide order is still wanted, allocate it outside the transaction or as a server-time stamp.
MS-13 Minor PKG/open-questions-and-assumptions.md:122 (E20); PKG/contracts.md:108 (Study summary projection) The projection's write rules are unstated and FEAT-024 has strict ones: every whole-Study write is version-guarded; the whole PendingStatistics set is recomputed from the before-image on an isolated copy (never a bare $push, never from the shared cached instance); StatisticsFoldSequence, BulkUpdateLock, SlotReservations and PendingStatistics are opaque persistence fields that must round-trip. E20 also does not name the SessionTally fields legacy readers and the allocation programme depend on (NumberOfCompletedCandidateSessions, ReconciliationStarted/Completed, reservation counts; "every stage session or slot reservation implies a SessionTally for that stage"). materialized-stats.md:114-119; MAIN/.claude/rules/repository-cache.md; MAIN/src/libs/project-management/SyRF.ProjectManagement.Core/Model/StudyAggregate/Study.cs:142-171,242; MAIN/docs/features/proportional-study-allocation/performance-validation.md:316-324 E20 to list: the projection fields (per bound stage: the SessionTally shape; per profile: current outcome; readiness flags; the ReconciliationTask-derived ReconciliationStarted/Completed booleans per bound stage), the write rule (isolated read, version-guarded replace through the statistics-aware repository overloads, opaque fields preserved), and the allocation tally invariant.
MS-14 Minor PKG/contracts.md:336-340 (new scope kinds "append-only ordinals"); PKG/integrated-plan.md:275-299 (R0 tolerant maps) Appending enum ordinals is not enough for rolling deploys: ProjectStatisticsScopeKey.Compose throws on an unknown scope kind; FamilyFlagKey returns null for an unknown family; reset "stales every materialized family"; the daily producer, fleet dispatch, repair, drift check and admin router enumerate families. An older binary meeting a new family's rows, guards or outbox slots must ignore them, and that has no test today (the #3145/#3512 tolerance covers fields, not kinds). ProjectStatisticsScopeKey.cs:152; …/Wiring/ProjectStatisticsFlagMap.cs:64-102; STATS/async-point-fold-design.md:1233-1240; STATS/STATUS.md:203-205 FEAT-024 to publish a "new family onboarding contract" (registration checklist plus a rolling-deploy test: an N-1 binary reading a project with rows/guards/slots of an unknown family serves its known families and ignores the rest) before F2 (recommendation R3).
MS-15 Minor PKG/contracts.md:118-121; PKG/integrated-plan.md:452-461 (R2b claims), :502-504 (R3a decisions) Each release that adds a transition kind (form-keyed session versions, form-keyed reservation claims, profile-keyed decisions) would be its own fold-protocol bump with an operator stamp-advance procedure, and while the stamp is N-1 an N binary may only write N-1 constructs (intents). STATS/async-point-fold-design.md:1281-1282,1301-1331; STATS/phase2c-staging-proof-runbook.md:2315-2342 Plan one bump (protocol 5) declared at F3 with every canonical transition kind, shipped dark after gate (b); until then canonical commits carry invalidation intents only (allowed by the N-1 rules and exact enough for pilots, which are served live for those families). Keep any bump off R2a's critical path.
MS-16 Minor PKG/acceptance-criteria.md:52 (AC-ALL-04); PKG/migration-adoption-rollback.md:166-180 (§6) The rollback rehearsal does not include FEAT-024's order: fold-disable and wait for Disabled, close the project narrow gate (two-stage with quarantine), close the fleet gate, flags off via GitOps on both hosts, allowlist guard, then images; a rollback past an advanced stamp needs reset twice around guard removal; the PM host ignores runtime flag toggles (#3360). STATS/phase2c-staging-proof-runbook.md:1124-1182,2368-2402; STATS/async-point-fold-design.md:1730-1731,1742 Add to §6 and AC-ALL-04: when a release changed a statistics writer, family or protocol, the rehearsal follows the FEAT-024 rollback order and records the fold mode and stamp before and after.
MS-17 Minor PKG/migration-adoption-rollback.md:112-131 (§4 protocol), :106 (statistics row); PKG/acceptance-criteria.md:394 (AC-R6-04) Adoption shadow/cutover is a bulk mutation of Study and must raise FEAT-024's staged operation fence for every family (as bulk Study update does) so nothing is served Fresh over a half-migrated population, and must rebuild under the new family source version after cutover. The parity audit exists for ProjectScreening only; other families are verified by manual exactness checks (#3845 open), so "statistics parity" in AC-R6-04 is not automatable today. STATS/phase0-mutation-ownership-matrix.md:69 (2.16 bulk update fence); STATS/statistics-reference.md:491-495 ("No reviewer-family parity audit"); gh issue view 3845 OPEN Add fence (step 3–5) and post-cutover reset/rebuild (step 6) to the protocol; make per-family parity audits (#3845) a G-ADOPT prerequisite or label AC-R6-04's statistics parity as manual.
MS-18 Minor PKG/contracts.md:345-346 (C8 ProjectQuestion authorization); PKG/source-status-inventory.md:369 (live question locks become legacy-only) The QuestionAnswers family is keyed by question id, authorised through QuestionExists on Project.AnnotationQuestions, and its consumer drives the designer's edit locks and assignment warnings. For canonical projects questions live in pmQuestionDefinition and versions replace locks, so the family's purpose changes (publication impact per question version) and its authorization source changes. materialized-stats.md:162-165; STATS/question-answer-backfill.md:54-129; STATS/statistics-reference.md:521-558 C8/C17: the canonical designer reads the QuestionVersionAnswers family (or authoritative counts) and never the legacy locks; QuestionExists resolves canonical definitions for canonical projects.
MS-19 Minor PKG/contracts.md:290 (C6 admission extends ReviewEligibilityPolicy); PKG/integrated-plan.md:740 (X-ELIG) With reviewEligibilityPolicy on, every eligibility admission writes Project.StatisticsAdmissionToken to serialise against settings; FEAT-024 recorded it as a source-level contention cost and deferred its redesign as "an eligibility design change, outside FEAT-024". The plan's StageSettings versions are the natural replacement (a per-stage settings revision checked in the Study filter). STATS/async-point-fold-design.md:1899-1902; STATS/screening-write-benchmark.md:474-476 Assign fold deferred item 2 to L4/C6 at F3: admission checks the bound StageSettings version instead of writing the Project.
MS-20 Minor PKG/integrated-plan.md:663-671 (R5c "computed from canonical revisions"); PKG/contracts.md:353-355 Agreement over all canonical revisions of a large project (10,000 studies × 200 questions × 3 reviewers) has no stated performance budget or caching design; AC-ALL-09 only bounds regression of touched endpoints. FEAT-024's exclusion of kappa stands and should not be reopened by convenience. STATS/README.md:215-231; PKG/acceptance-criteria.md:57 R5c: a separate 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; add an absolute budget to AC-R5c.
MS-21 Minor PKG/contracts.md:577-579 (C17 DTOs); PKG/ui-coverage-comparison.md:89 Overview data is mid-migration to route-owned SignalStores over FEAT-024 query services (StageOverviewStatisticsQuery, ProjectReviewerProgressQuery, ReviewerProgressQuery). New "gate status, sufficiency, work status, batch frontier" DTOs built beside them create a second owner of overview numbers. STATS/STATUS.md:254-281; …/Families/Annotation/StageOverviewStatisticsQuery.cs, ProjectReviewerProgressQuery.cs C17: the new fields extend the existing statistics query services and consumer flags (one read per route), with form/step fields added to the same DTOs; no parallel overview endpoint.
MS-22 Note PKG/contracts.md:433-457 (C11), :487-490 (C12 report part) FEAT-024 history is daily observed counters that are never reconstructed ("historical unavailability is explicit and never substituted"); as-of exports reconstruct from revisions. Nothing in C11/C12 forbids using FEAT-024 checkpoints as as-of or PRISMA evidence, and nothing says what the daily producer records for canonical projects. STATS/README.md:133-136,203; STATS/derived-summaries.md:33-37 Add one rule to C11 and C12: FEAT-024 history is never an input to as-of exports or report snapshots; for canonical projects the daily observation records the new families under their captured catalogue version.
MS-23 Note PKG/integrated-plan.md:978 ("#3955 merged; #3956 open"), :19-24; PKG/source-status-inventory.md:296-297 Since the plan's 05:48 BST snapshot, #3956 (05:53 UTC) and #3962 (slice 7, 06:13 UTC) merged; no FEAT-024 PR is open; STATS/STATUS.md itself still lists #3591/#3766/#3767 as open and slice 7 as "in review" although all are merged. gh pr view 3956 3962 3591 3766 3767 Refresh §8 and the inventory row before G0; ask the FEAT-024 owner to correct STATUS lines 43-47 and 386.
MS-24 Minor PKG/integrated-plan.md:286-291 (R0 admission service); PKG/domain-model.md:141-142; C16 FEAT-024 plans its own durable per-project eligibility (#3524) with a stronger requirement than an admission record: eligibility "must participate in source admission/publication, including the source-only branch" inside the transaction, because a periodically refreshed list lets an unlisted operation commit after enrolment and strand the projection. Two per-project admission records with different consistency rules will coexist. STATS/project-rollout-administration.md:34-39,60-76; …/Lifecycle/ProjectStatisticsAllowlist.cs C16: ProjectAdmission is never the statistics allowlist; #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 in-transaction by FEAT-024's own gates.
# Change Rationale Timing Compatibility / migration Risk PR slicing Owner
R1 Extract a single source-write participant seam from ProjectScreeningStatisticsWriter/ProjectAnnotationStatisticsWriter and ProjectScreeningFoldSave/ProjectAnnotationFoldSave, add a "projection-only Study write" shape (input: before/after Study summary projection and the affected families; output: nothing, a classified delta, or an entry/intent per the active path) MS-06; keeps "hot paths never write statistics" and the pinned command budgets regardless of which path is live Before F1 (the engine builds against it) Pure refactor; command-budget tests (FoldSaveCommandBudgetTests, ProjectStatisticsFoldCommandBudgetTests) must stay byte-identical Low 1 PR refactor, 1 PR adding the projection-only shape with intents FEAT-024
R2 Target-aware annotation classification: replace AnnotationThresholds.MinimumNumberSessions = 2 and StudyStats.cs:355 with the stage's effective SessionCountTarget; bump the annotation catalogue version (annotation.v2) and move mns out of the digest into per-stage definition metadata (already captured in StageStatisticsDefinitionSnapshot) MS-07; closes the existing TODO (PR #2331) and the AC-R3a-06 dependency Before R2b/R3a build; after gate (b) rerun so the benchmark baseline is stable Digest-format change: explicit reconciliation plan (forced rebuild of allowlisted projects, retained checkpoints keep their identity); ProjectStageConfigurationChange.AffectedFamilies extends to SA/MSA/DR Medium (staging pilot reset+backfill; history identity) PR a: calculator + classifier + parity tests; PR b: digest/version migration + runbook step FEAT-024
R3 New-family onboarding contract and rolling-deploy tolerance test (unknown family rows/guards/outbox slots ignored by an N-1 binary; FamilyFlagKey null refuses instead of throwing; Compose on an unknown kind falls back) plus fixing #3506 (per-family version constants) MS-14, MS-09© Before F2 Additive; tests only, plus the #3506 constant split Low 1 PR FEAT-024
R4 Scope-kind and key extension: FormVersion = 7, QuestionVersion = 8 at F2; ProfileVersion = 9, ProjectProfile = 10, MembershipProfile = 11 at F5; new key components (FormId, FormVersionId, ProfileId, ProfileVersionId) with tolerant class maps MS-02, MS-08 F2 (forms), F5 (profiles) Class maps already ignore extra elements (#3145); append-only ordinals; selector/authorization filter updated Low 1 PR per freeze FEAT-024 with L2/L3
R5 Usage family (FormVersionUsage, QuestionVersionAnswers) over canonical collections in the same pinned snapshot: calculator, backfill/rebuild routes, parity calculator, flag, consumer manifest, fleet dispatch, repair/drift inclusion, and the scoped rebuild at a pinned snapshot service API for the publication command MS-02, MS-03, MS-04; technical-plan amendment "canonical sources" After F2; dark; staging enable for pilot projects (X-STATS-a) New family under its own catalogue/source version; explicit versions only (drafts counted authoritatively) Medium 3–4 PRs (amendment doc + ADR; family + routes; parity + fleet; service API) FEAT-024 + L7
R6 Protocol 5: all canonical transition kinds (form-keyed session version, form-keyed reservation claim, profile-keyed decision) in one additive bump with IntroducedAt = 5, N-1 harness cases, stamp-advance procedure MS-15 After F3 and only after gate (b) passes; never on R2a's critical path; until then intents only Additive from P0; stamp advance after both rollouts; StampAdvanceAllowed handling per runbook Medium 1 PR per kind group, one bump FEAT-024
R7 Close the production-blocking follow-ups the plan's X-STATS-b implicitly needs: #3840 flag coupling, #3960 drift re-persist tool, #3845 parity calculators, gate (b) idle rerun, soak #3952/#3510 MS-01; these gate any production family, usage family included Now (already open) None Low–medium Existing issues FEAT-024
R8 Build durable eligibility #3524 only after C16 freezes, aligned to the admission service's record shape and audit, with in-transaction participation kept MS-24 After F1 Explicit migration from the static allowlist (no silent union) per project-rollout-administration.md:60-64 Medium 2 PRs FEAT-024 + L0
R9 Hand fold deferred item 2 (eligibility Project token) to the StageSettings design: admission checks the bound settings version in the Study filter MS-19 F3 Eligibility programme change (paused); behind reviewEligibilityPolicy Medium 1 PR in the eligibility programme L4 with eligibility owner
R10 Add a canonical-commit arm to ProjectScreeningWriteBenchmark (cells ½/5/10 reviewers, same/different Study, eligibility off/on) and run it for AC-R2a-19 and the E25 decision MS-12 M0/F1 None Low 1 PR L1 using FEAT-024's harness

What should not be built further until the contracts freeze: point maintenance for QuestionAnswers at question grain (fold deferred item 6; the grain becomes question version at F2); any writer to the reserved StageQuestionVersion scope; profile grain in the screening families before F5; #3524 before C16; any further per-release fold-protocol bump planning before gate (b) is decided.

New read models, by placement: FEAT-024 families only for bounded, catalogue-defined, parity-auditable counts needed on hot pages (form-version usage, question-version answers, profile-grain screening, a task-based DomainReconciliation at R4a); derived summaries (not persisted) for gate status, sufficiency and batch frontier; authoritative indexed counts for drafts, queues (my concerns, assigned work, changes awaiting approval) and publication manifests; a separate rebuildable derived store for agreement (R5c); frozen snapshots computed from authoritative records for PRISMA (R5b); and the allocation programme keeps reading SessionTally through the Study projection.

5. Changes to the plan

  • PKG/integrated-plan.md §5.11 X-STATS-b: replace the single row with the prerequisite chain in MS-01 (gate (b) idle pass; soak; production pending index; production rollout approval lifting the in-code refusal; production eligibility/allowlist for pilots; usage family built and proven on staging). Add X-STATS-a (staging: usage family on, pilot projects allowlisted on both hosts) as an R2c staging-pilot prerequisite and state that preview pilots use the (b) path. §6.4: say plainly that R2c's first production pilots are expected to run under Q-31(b). §8 FEAT-024 row: refresh states (no open PRs; slice 7 merged) and list R1–R6 above as the joins at F1/F2/F3/F5.
  • PKG/contracts.md C1 (Study coupling, receipt authority): replace "carries FEAT-024 pending entries under the fold protocol" with the seam rule (MS-06) and replace E35 with the shared-operation-id rule and the digest qualification (MS-05). C7: add the target-aware classification change (R2) as a named amendment and the SessionTally/reconciliation-boolean projection fields (MS-13). C8: usage family over explicit versions only, drafts counted authoritatively (MS-03); "current" defined as a FEAT-024 read at the fence with recorded identity, plus the scoped-rebuild service API (MS-04); the three N-1 mechanisms named (MS-09); canonical designer counts (MS-18). C11: E25 decided at F1 on benchmark evidence, default non-serialising (MS-12); FEAT-024 history never an as-of input (MS-22). C12: PRISMA counts from authoritative records only; SearchPopulation extension is a family source-version change (MS-11). C16: ProjectAdmission is not the statistics allowlist; #3524 after C16 (MS-24). C17: overview fields extend the existing statistics queries (MS-21).
  • PKG/domain-model.md §2 and §3.6: replace "no new counters"/"new kinds inside the existing family" with "new families and scope kinds over canonical sources by FEAT-024 amendment" (MS-02). §5: add the statistics-half column with the three cases (MS-06). §4 ProjectStatistics row: add R3b profile-grain families and the R2 classification change.
  • PKG/open-questions-and-assumptions.md: reword A-08 (MS-04); E3 to include the scoped-rebuild API and the fence/pause ownership; E20 to list the projection fields and write rules (MS-13); E25 as a measured decision (MS-12); add an engineering item for the new-family onboarding contract and the protocol-5 batching (MS-14, MS-15); add fold deferred item 2 as an L4 item (MS-19).
  • PKG/acceptance-criteria.md: AC-R3a-06 to reference R2 and its reconciliation plan (MS-07); AC-P1-07 reworded (MS-11); AC-ALL-04 to include the FEAT-024 rollback order when statistics were touched (MS-16); AC-R6-04 to say which families have an automated parity audit (MS-17); AC-R2c-04 to define "refreshed" as the fence read (MS-04); new criteria listed in §7 below.
  • PKG/migration-adoption-rollback.md §4: fence during shadow/cutover and rebuild after (MS-17); §5 R2a–R3b rows: state which statistics path applies to canonical projects (served live for new semantics until the families exist).
  • PKG/source-status-inventory.md fact 15 and §4: refresh PR states; note the digest coupling of "enough = 2" and the static allowlist/#3524 status.

6. Questions for Chris

  1. Publish-gate "currentness" for the first pilots. May the publication command treat a FEAT-024 read at the fence whose scope is Stale and therefore answered by the pinned authoritative calculation (same snapshot, same authorization, identity recorded in the manifest) as "current statistics" under PS2/PS3, rather than blocking until an administrator backfill republishes the rows? Recommendation: yes; it is what PS2's "actively update the specific relevant statistics there and then" allows, and it keeps R2c independent of the admin-only backfill route.
  2. Pilot projects versus the FEAT-024 staging pilot. Project "Ready for Annotation" (…0102) is both the only FEAT-024 staging pilot and a named plan pilot. Keep it out of the R2a–R3a pilots until the seam (R1) is proven on an allowlisted project in the e2e stack, or make it the deliberate integration pilot? Recommendation: keep it out until the fixture in §7 passes, then use it deliberately for R2c's X-STATS-a proof.
  3. Multi-profile screening statistics (R3b). Should FEAT-024 add profile-grain screening families at F5 (new scope kinds, catalogue bump, their own proof), or should the three screening families be declared unsupported for multi-profile projects and served live until a later FEAT-024 slice? Recommendation: served live for R3b pilots; add the families at F5 as a FEAT-024 scope amendment so R3a's exactness (default profile projected into the legacy shape) is not spent on R3b's timeline.
  4. Agreement statistics storage. R5c is outside FEAT-024 (its README excludes kappa). May R5c keep its own rebuildable derived store with a watermark (not a FEAT-024 family, not catalogue-coupled)? Recommendation: yes, with an absolute performance budget in AC-R5c.
  5. Latency budget alignment. FEAT-024's write gate is "under 10% or at most +2 ms p95 and zero statistics-caused failures"; AC-R2a-19 allows 1.2× today's p95 for Save/Complete. Should the canonical engine be held to the same per-cell rule (measured with the same harness, R10), or is 1.2× acceptable for the first pilot? Recommendation: keep 1.2× for the pilot but adopt the zero-statistics-caused-failure rule now and measure with the same cells, so the two programmes report one number.
  6. Preview pilots and PS1. Preview environments carry no FEAT-024 configuration. Exempt preview pilots from PS1 (the (b) path) or require per-PR preview configuration of the usage family? Recommendation: exempt preview; staging is the PS1 proving ground.

7. Coverage gaps

  • Missing acceptance criteria and tests the plan should add: (a) parity: a canonical commit on an allowlisted project with Writes/Serving/family on leaves every affected family either exact or Stale, never Fresh-and-wrong (per family, both paths); (b) fence: a Save/Complete during an open definition-rewrite or inclusion-recalculation fence is refused or deferred exactly as legacy saves are (typed 503), and a publication under the fence never commits a Study write before the fence is admitted; © rebuild: after adoption cutover and after R2 (target-aware classification), backfill republishes every family of the pilot project with ScopesStale = 0 and the parity audit matches; (d) N-1: an N-1 binary reading a project with usage-family rows/guards serves its known families (R3's test), and a mixed fleet keeps the stamp at N-1 with canonical commits carrying intents only; (e) benchmark: the canonical-commit arm (R10) at ½/5/10 reviewers with the commit-sequence design under test, and the fold arm's per-cell equalities asserted for the pilot's save shapes; (f) drafts: the draft-only count at the fence equals the pmSessionDraft enumeration at the same snapshot, never a materialised row; (g) rollback: the FEAT-024 rollback order rehearsed with canonical data present when a release bumped the protocol.
  • Not verified: live production image versions and the production MongoDB server version (the fold design needs 4.4+; STATS/STATUS.md:744-745 says the operator must confirm); whether staging runtime overrides (revision 72) were cleared; the staging pending-index state beyond STATUS's statement; any database content (no MCP reads were made).
  • Not read in full: STATS/technical-plan.md (224 KB) and STATS/async-point-fold-design.md (148 KB) were read in targeted sections only; STATS/phase0-calculation-consumer-catalogue.md (D12 and the consumer inventory) and STATS/historical-charts-rollout.md were not read; web consumer code was not inspected beyond STATUS's inventory; the eligibility programme's open PRs (#3746, #3741) were not re-read for statistics seams.
  • Round-2 verifier V2-19 (transaction table versus FEAT-024) is not yet resolved in the package (no round-2-resolution-matrix.md exists); MS-06 adds the path-dependent precision it needs rather than repeating it.

Critical Files for Implementation

  • /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/contracts.md
  • /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/integrated-plan.md
  • /home/chris/workspace/syrf/main/src/libs/project-management/SyRF.ProjectManagement.Core/Services/ProjectStatistics/Fold/ProjectStatisticsFoldAdministration.cs
  • /home/chris/workspace/syrf/main/src/libs/project-management/SyRF.ProjectManagement.Core/Services/ProjectStatistics/ProjectStatisticsConfigurationDigest.cs
  • /home/chris/workspace/syrf/main/docs/features/materialized-project-statistics/STATUS.md