Screening-only Project Overview cutover¶
The Phase 5.1 boundary is one project-screening endpoint and one independently reversible Project Overview consumer. Stage and membership consumers continue to use their existing contracts.
GET /api/projects/{projectId}/screening-stats returns ProjectScreeningStats. The endpoint requires
project view authorization and ViewScreeningProgressGraph, including explicitly authorized public nonmembers before consulting
either statistics source. Its dedicated materializedProjectStatisticsProjectOverview flag defaults
to false and requires the Pages kill switch, global serving and the screening family in the runtime catalogue. The
existing adapter also requires writes, serving, the family and the explicit project allowlist, and
the reader enforces durable freshness and compatibility within its snapshot.
The authoritative delegate is lazy. A Fresh materialized response invokes no study aggregation.
Every authoritative response, including a disabled-consumer request, opens a pinned read-only Mongo
snapshot. It reads the current Project without the repository aggregate cache, rechecks Project.View and ViewScreeningProgressGraph permissions (including explicitly authorized public nonmembers), and reads global/project controls before running the facets
in that same session. Inclusion and definition rewrite fences, the legacy inclusion-job flag and
durable-mode disagreement return HTTP 503 with a typed reason; revoked permissions return 403.
An unpinnable snapshot also returns 503. No refusal executes screening facets. These admission reads
are required source-consistency work even while the consumer flag is off; performance comparisons
must include them. A reader refusal never authorizes use of the request's cached Project settings.
Fallback executes the original screening totals and screening-tally facets only, with the original
mapper and formulas. The original broad FullStats query remains unchanged and is the independent
parity oracle. This endpoint does not substitute one field into a previously computed broad response.
Read-request benchmarks must compare identical project corpora, report aggregate commands/facets, p50/p95, and distinguish Fresh, fallback and disabled-consumer requests. Existing reviewer-statistics requests and broad legacy SignalR server aggregation remain separate costs. The Overview refresh bound also adds periodic requests; whole-page acceptance must include those costs rather than claiming all page aggregation has disappeared. No staging or production flag activation is authorized by this implementation or its local tests.
Deployment and runtime activation boundary¶
The project-management mutation host does not read the API runtime override store (#3360). Activation therefore requires consistent static write/family configuration across every API and mutation-host replica, durable mode admission, backfill and parity evidence. This consumer does not solve runtime maintenance propagation or authorize a live activation. API-only write/family overrides are rejected, and previously saved overrides are ignored in favor of the immutable deployed values. Runtime serving can stop reads, but cannot activate beyond deployed serving readiness.
materializedProjectStatisticsPages is a master kill switch for both this dedicated consumer and
existing full-stats substitution (#3361). Turning it off restores authoritative-only full-stats and
removes the screening-only Overview consumer, even when its dedicated flag remains on. The dedicated
flag can independently roll back Overview. The screening-only endpoint remains callable while either
consumer flag is off and then uses its guarded authoritative query.
The browser's project guard waits for its initial runtime flag snapshot before deciding whether to request FullStats. Transient failures retry twice at 250ms intervals; each attempt is limited to three seconds. A sustained outage retains deployed defaults after that bounded wait so project navigation remains available. This degraded path may request legacy FullStats and must not be counted as a successful cutover in performance evidence. Ordinary successful cold-start runtime activation skips the broad request, including when the deployed browser consumer default is false.
Overview behavior and rollback¶
With the dedicated flag enabled, Project Overview shows total studies, sufficiently screened studies
and remaining studies to members admitted by the existing project route who have project-view and
graph permission. The API also supports explicitly authorized public nonmembers, but the existing
browser route rejects ProjectMemberStatus.NotMember before Overview mounts; this slice preserves
that route boundary. Public Overview navigation is a separate, permission-scoped follow-up
#3461, including checks that other project routes
remain membership-only. Its existing reviewer screening and annotation
progress continues to load through the reviewer endpoint. Overview no longer requests broad FullStats when both page gates are on. This is the bounded latency path for #3311; the existing full-stats endpoint retains its equality-gated shadow mode and still aggregates.
A child-route guard loads that unchanged contract when users navigate to existing screening, stage or
other project consumers, including after first entering through Overview. Turning the flag off removes
the new surface, cancels its requests, and restores legacy statistics loading on the current Overview.
The separate component never marks membership/stage statistics loaded and does not accept unsolicited broad response updates as a newer screening snapshot. It requests the current screening answer on entry and every 30 seconds; requests time out after 15 seconds, failures hide prior counts and retry on the next interval. Changing project or leaving the view cancels its pending subscription and refresh timer. This is bounded polling pending the separately planned SignalR invalidation consumer, not a replacement for that protocol. A hidden browser tab may throttle timers.
Local correctness proof¶
The real Mongo replica-set suite compares all screening-only results to the original full-facet oracle, including empty corpora and threshold/ratio boundary profiles. Endpoint tests cover lazy materialized reads, fallback, disabled-consumer behavior, nonmembers and active members without graph permission. Tests exercise Pages/dedicated-flag combinations, consumer rollback and initial runtime snapshot readiness. HTTP tests prove the parity admin route returns403 for nonadministrators without querying either existing or absent projects. Browser tests exercise real router navigation from Overview to legacy screening/stage consumers, flag-off loading, preserved reviewer requests, rendering, retry and subscription cancellation. Angular template compilation also checks the complete application. Reproducible before/after performance evidence is a separate artifact; these correctness tests do not assert timing improvement on a shared host.
Reproduce the focused checks from the worktree root:
dotnet test src/libs/project-management/SyRF.ProjectManagement.Mongo.Data.Tests/SyRF.ProjectManagement.Mongo.Data.Tests.csproj --filter 'FullyQualifiedName~ProjectScreeningQueryAdapterParityTests|FullyQualifiedName~ProjectStatisticsAuthorizationContextSourceTests'
dotnet test src/services/api/SyRF.API.Endpoint.Tests/SyRF.API.Endpoint.Tests.csproj --filter 'FullyQualifiedName~ProjectScreeningEndpointTests|FullyQualifiedName~RuntimeFeatureFlag'
From src/services/web, run:
pnpm exec ngc -p tsconfig.build.json --noEmit
pnpm exec ng test --watch=false --include='src/app/project/project-overview/project-screening-totals.component.spec.ts' --include='src/app/core/services/project/project-statistics-route.guard.spec.ts' --include='src/app/core/services/project/project-guard.service.spec.ts' --include='src/app/core/services/runtime-feature-flags.service.spec.ts'
Rollout also requires #3371: its administrative mode-transition surface opens the durable fleet and project narrow gates after backfill. Until that prerequisite is merged and its runbook completed, keep this consumer disabled; do not automatically admit a project. Parity requires writes and screening-family maintenance enabled, but global serving may remain off. The authoritative aggregation receives the caller cancellation token through the terminal Mongo query.
Runtime maintenance protection¶
API runtime overrides cannot change materializedProjectStatisticsWrites or any metric-family
maintenance gate. The provider captures immutable deployed values at construction and reapplies them
to both its effective lookup and the shared FeatureFlags singleton on every refresh, including
partial snapshots. Stored overrides are normalized to the same deployed values; attempts to mutate
these gates or automatically enable them through a consumer dependency are refused. This keeps API
source writers consistent with the project-management host, which reads static deployment configuration.
Runtime serving may be stopped without stopping maintenance, and unrelated partial refreshes preserve that stop. Runtime activation cannot exceed deployed serving readiness. Existing page, Overview, SignalR and export consumer switches retain their runtime behavior. Changing maintenance requires coordinated deployment values across mutation hosts; no new rollout flag or live activation is added.
Regression coverage includes every maintenance gate in both directions, saved legacy overrides, mutable-singleton/configuration isolation and serving kill-switch persistence. See review 5190938570.