Feature flag administration delivery slices¶
Delivered administration controls¶
Ordinary On, Off and Default actions submit in one click. Each row offers an optional comment, retained after failure and cleared after success. Automatic audit reasons, real-administrator authorization, revision checks, exact dependency confirmation and production read-only policy remain. Acceptance: ordinary clicks send one request without a dialog; dependency cancellation writes nothing; stale revisions never silently retry. No new flag is needed for this bounded improvement to the existing administration page.
Preview verification exposed a separate timeout in the consolidated version job: run 35765939187 exhausted its 20-minute budget after four of nine sequential service calculations, before Web. The job now has a bounded 60-minute budget, pinned by positive and adjacent negative contract checks. Version calculation, artifact validation and TLS readiness remain mandatory.
Prior work and evidence¶
Inspected on Juniper, 2026-09-22, against the origin/main baseline of this PR. PR #3527 remains an open, documentation-only shared-provider proposal; this document supplements rather than replaces its architecture review gates. Its owner task has completed, with no implementation. Grouped rows (#3472), generated display names (#3488), and statistics pilot operations (#3523) have merged. The reviewed local Claude handovers for AF2 and flag retirement do not establish a newer generic-provider implementation. No Claude sessions were restarted or modified, and no existing worktree was repurposed.
Current source anchors:
- Catalog and dependency definitions
- Statistics deployment constraints
- API startup and 30-second refresh
- Browser snapshot and reconnect handling
- Environment mappings and service routing
The retirement audit below verified selected production values on 2026-09-22. Fleet convergence was not measured. Schema routing does not prove a service reads runtime overrides. Saved policy is not proof of application in every consumer. Existing API refresh can fail, and browser fallback polling is five minutes; neither promises immediate fleet-wide activation.
Ordered follow-ups and acceptance¶
- Expose truthful management and consumer metadata. Generate consumer names, scope, activation requirements and runtime/deployment constraints from a single catalog. Distinguish intended/declaration routing from verified consumers. Show deployment-only controls read-only with a reason; never offer a working-looking maintenance toggle that the server rejects. Preserve server validation as authority. Verify catalog parity, special-case statistics serving, and mixed-version clients.
- Delivered grouped controls. The reviewer workspace group applies
annotationFormV2,stageReviewRedesign,stageReviewDockview, andintegratedPdfViewertogether; PDF activation requires reload and form eligibility still applies. The screening statistics group (statistics-consumers-v2) controls Serving, Pages, ProjectOverview, screening history and SignalR statistics refreshes. It requires deployed writes, serving and screening. The exports gate has no implemented export/report consumer and is excluded. Maintenance/family gates remain deployment-managed. The previousstatistics-screening-v1andstatistics-consumers-v1groups remain accepted for old clients but are no longer advertised; their membership is unchanged. Stage Overview, stage history and reviewer history remain individual until their maintenance families are deployed. Individual controls remain available. The API advertises exact group membership with the snapshot. The UI shows configured and effective On/Off/Mixed values and refuses a group with missing members. A group request requires the current revision and commits every planned override plus one audit record in the existing Mongo transaction. Additional dependencies require exact confirmation; deployment-only requirements remain blocking. A revision conflict reloads without retry. An uncertain network/server failure refreshes state and warns that the outcome is unknown, rather than claiming nothing happened or issuing compensating writes. - Shared runtime authority after #3527 approval. Implement the proposal's minimal API/PM vertical slice with immutable snapshots, accepted defaults identity and recovery. Verify two instances of each participating service, cold start, reconnect, missed events, invalid resume state and stale/out-of-order responses. Expose saved/applied/unknown states and useful failures. Decide staleness and operation boundaries before migrating flags; do not silently change production policy. Start with an audited runtime-safe consumer, not statistics or a durable lifecycle gate.
- Statistics runtime domain transitions. Build on the existing pilot UI and its follow-ups #3524–#3526. Separate “Calculate and maintain”, “Use precomputed statistics” and “Build initial statistics”. Maintenance can remain on while reads are off. Preserve project enrollment, Freshness, epochs, compatibility and source-only invalidation. Initial build is an explicit operation with progress/failure, never an implied effect of enabling a switch. Test restart and partial host failure before enabling runtime maintenance. A generic group toggle cannot bypass these domain transitions.
- Other groups and retirement decisions. Consider Risk of Bias tool plus its AI-test control, and statistics consumer presets. Keep diagnostics/telemetry independent: similar category names do not establish a common lifecycle. Add a change-history view and clear environment identity before richer targeting. Staging/preview presets must never imply production promotion or alter preview seeding defaults.
Cross-service architecture proposal (not implemented)¶
The grouped endpoint improves persistence coherence; it does not solve fleet propagation.
The existing singleton override document and append-only audit collection remain the write
source. PUT api/runtime-feature-flags/groups/{key} uses the same real-admin check,
non-production policy, dependency evaluator and compatibility checks as individual edits.
A group advances one revision in the same transaction as its audit entry, including the
exact changed keys and group identity. Group IDs are immutable versioned contracts;
changing membership requires a new ID, preventing mixed-version replicas from applying
a different member set from the one advertised to the administrator. Transactions prevent partially persisted groups.
Mongo transaction retries still enforce the caller's expected revision. Revision broadcasts
are best-effort after commit; a notification failure does not undo a committed group.
The shared-provider proposal in #3527 should be implemented as follows after architecture approval:
- Authority and persistence: keep one environment policy with accepted catalog/defaults identity and monotonic revision. API is the authorized writer; API, PM and other migrated hosts evaluate immutable snapshots through a shared provider. GitOps remains defaults and the production authority until an explicit policy decision changes that. Persist accepted defaults identity so rolling versions cannot reinterpret the same override differently.
- Propagation and consistency: publish invalidations only after commit. Consumers fetch a complete policy and atomically replace their local snapshot, never mix individual flag updates. A Mongo watch plus periodic reconciliation must recover missed invalidations. Each operation captures one policy identity at its admission boundary. Diagnostics report saved revision and each known consumer's applied revision, version and observation time; absence or stale reporting is Unknown, not convergence. Do not promise simultaneous cross-service activation; durable operations need domain-specific fencing/epochs.
- Recovery: establish a watch/read boundary without a bootstrap gap; reject older revisions; retain the last accepted snapshot on transient refresh failure and report staleness. Define reviewed per-gate freshness limits before activation. On invalid resume tokens, incompatible catalogs or restart without an acceptable snapshot, reconcile from authoritative state and fail closed for affected gated operations. Reconnect requires a full fetch. Persisting an override must never be undone by a restart or permissive fallback.
- Statistics: runtime maintenance requires a coordinated domain transition over every mutation host, durable epochs, source-only invalidation and readiness checks. Generic flag persistence is insufficient. Read usage, maintenance and initial build stay distinct; project enrollment and Freshness remain mandatory independently of global group state.
- Rollout: first shadow-read and compare API/PM evaluations in non-production; then enable one audited runtime-safe consumer. Prove two instances per host, disconnect/reconnect, cold start, lost events, stale writers and unknown commits. Expand only after those tests and useful consumer diagnostics pass. Roll back through a new revision; never restore old documents over newer edits. Production rollout and flag retirement require separate reviewed decisions.
Current implementation limitations: API refreshes on a 30-second timer, browsers fetch on notification/reconnect and poll every five minutes, and PM does not consume the API override provider. Statistics maintenance remains deployment-owned. The admin group states are the API's effective snapshot, not an all-host acknowledgement or evidence that a project is ready.
Deployment-only and split-consumer inventory¶
This covers the Boolean FeatureFlags mappings plus the separately catalogued session limit. Operational configuration outside FeatureFlags (transport topology, notifier publication, cleanup authority, storage and identity writer floors) remains deployment configuration; being Boolean is not evidence that runtime mutation is safe.
| Control | Current boundary and reason | Runtime path |
|---|---|---|
DeletionLifecycle |
API/PM durable deletion protocol; absent from runtime catalog | Review coordinated lifecycle transition; retain deployment control now |
BulkStudyUpdateRequireCurrentExecutionVersion |
PM execution-version compatibility floor; absent from catalog | Keep compatibility floor deployment-managed pending protocol review |
BulkStudyUpdateAtomicApply |
ADR-020: API stamps new bulk-update jobs with execution version 2 (all-or-nothing); absent from catalog because it changes job semantics mid-flight | Deployment-managed permanently; PM runs any admitted version 2 job whatever its value |
BatchRiskOfBiasAtomicApply |
Application-authority M5b: PM saves batch risk-of-bias results all or nothing and recovers interrupted saves; absent from catalog because it changes job semantics mid-flight | Deployment-managed permanently; turn off only with no all-or-nothing save in flight, since recovery runs only while it is on |
IdentityClaimRecovery |
Identity admission/login/claim flow uses host options; absent from catalog | Separate identity-host design and authorization review |
ActiveReviewerTrackingEnabled |
API/PM and browser mapping, absent from runtime catalog; reviewer slot lifecycle | Audit reservation/release semantics before shared-provider migration |
PM BulkPdfUpload and SignalRActive |
Separate PM mappings; API/browser override does not establish PM runtime propagation | Audit PM consumers and operation boundaries; migrate through shared provider |
materializedProjectStatisticsWrites |
API/PM maintenance hosts use deployed values | Required coordinated runtime domain transition, not a permanent exception |
Seven statistics family gates: Screening, MembershipScreening, Annotation, MembershipAnnotation, QuestionAnswers, SearchPopulation, DerivedSummaries (all prefixed materializedProjectStatistics) |
Maintenance gates pinned to deployment by ProjectStatisticsRuntimeActivation |
Migrate with maintenance, preserving durable state and readiness |
materializedProjectStatisticsServing |
Runtime can stop reads, but cannot enable deployment-disabled serving | Coordinate maintenance/readiness and project scope first |
maxInProgressSessions |
Catalogued API setting; shared backend setting is not proof of a common runtime authority | Audit capacity consumers; do not migrate only to demonstrate propagation |
signalRActive, devMode, errorTracking, apmEnabled, logRocketEnabled,
integratedPdfViewer and zonelessChangeDetection are browser reload-bound runtime
flags, not environment-only flags. Statistics Pages, SignalR, Exports, ProjectOverview
and StageOverview consumer controls remain subject to serving and domain readiness.
Retirement investigation — 2026-09-22¶
The initial audit was read-only. This PR subsequently retires the three settings-availability switches described below; no production writes or secret reads were made. Code inspected at main 129551b89bab9b2ff3f0437d5d5a9cb9c8a9d9d7 and #3553 worktree. GitOps main observed d36ef23470b98e090b563ab3783046258e2212bb. Paths below are repository-relative and line numbers refer to inspected main.
Findings and proposed order¶
| Flag | Evidence and current semantics | Recommendation / minimum removal |
|---|---|---|
newStageOverview |
Production public app configuration and running Deployment template both true. GitOps syrf/environments/production/web/values.yaml:67. Enabled since 2025-12-05 commit 0f3307d542939373885267a321fcb3779ff3070a. Sole nongenerated behavior consumer is src/services/web/src/app/project/project-nav/project-nav.component.ts:579: hides/shows Stage Overview link, not an alternate implementation or route guard. |
Strong first retirement candidate: keep enabled behavior. Remove nav selector and boolean predicate; retain normal permissions/routing. Remove declaration/catalog/config/fixtures and regenerate. No stage-overview implementation deletion. Validate stage active/inactive navigation, direct links and normal access controls. |
newScreeningOverview |
Same observed production true and 2025-12-05 enablement; GitOps web values:66. Sole behavior consumer project-nav:532 combines flag with viewSearches permission. |
Strong first retirement candidate: keep enabled behavior. Remove flag operand, preserve viewSearches permission; do not replace it with broader import/search capability. Keep Screening Overview implementation and its independent materialized-data flag. Validate authorized/unauthorized nav. |
newProgressIndicators |
Same observed production true and historical enablement; GitOps web values:65. stage-review.component.html:603,615,808,822 renders the same app-review-progress component either way; true wraps it in a button opening launchProgressDialog. Redesigned workspace explicitly does not depend on this flag (stage-review.component.ts:936). |
Strong retirement candidate: keep current clickable progress behavior. Collapse duplicated conditional markup into true branch; remove observable/import/selectors. Preserve progress components and live statistics gating. This is no longer a wholesale old/new indicator switch. Validate screening and annotation dialog entry plus redesigned workspace. |
newStageSettings |
Same observed production true since 2025-12-05; GitOps web values:68. The flag gated required annotators and advanced annotation controls on the stage settings page. | Retired in this PR at the owner's request. The controls are always rendered for permitted stage designers; form validation, proportional-allocation lock, annotation-mode constraints, advanced exclusion controls and saved option values remain unchanged. No backend migration or mass option enablement. |
screeningSettingsConfigurable |
The screening settings route and its stage-settings link already use project-design authorization, not this flag. The flag only revealed an unbound “Screening Type” placeholder dropdown beside the link. | Retired in this PR. Keep the real settings route/link available under existing permissions and remove the unbound placeholder; preserve each project's screening configuration. |
annotationSettingsConfigurable |
Generated configuration and catalog entry existed, but no live UI or backend consumer read the switch. Actual advanced annotation visibility used newStageSettings. |
Retired in this PR. Remove the dead switch and preserve the existing annotation settings form and stored values. |
quantitativeDataExportEnabled |
Additional candidate: public production app config true, same GitOps enablement commit; GitOps web:69. project-nav.component.ts:659 only conditionally adds Quantitative export navigation, with exportData permission independently checked. |
Next small retirement candidate, subject to confirming current export workflow acceptance. Keep link/route/component and permission. No reason found to delete export feature. |
newQuestionManagement |
Observed production false; schema default false. project-nav:714 only hides nav item. project-admin.routes.ts:61–69 matches route with projectDesignGuardFn, not this flag. Current design.store.ts:925–951 dispatches real save and awaits result; delete/copy connected in question-node.component.ts:156,188. Recent implementation commit 00561fe0b10334988d9f112d6d71954b4ae3a55c (2026-08-24). docs/features/question-management/README.md:526 says existing implementation must be starting point, architecture:37 expects revival of dormant QM v2. |
Retain pending product decision; NOT proven abandoned. README inventory claims save/delete/preview stubs but current code contradicts at least save/delete/copy claims: do not use that stale inventory to justify deletion. First reconcile current implementation against roadmap and decide revival vs abandonment. If explicitly abandoned later, remove nav/route/lazy subtree and v2-only backend code only after shared-consumer analysis; do not remove active legacy designer/AF2/extensibility infrastructure. Flag off presently does not establish route inaccessibility. |
showUpdateInclusionInfo |
Production true, but features.selectors.ts:106–110 also requires admin options. screening-settings.component.html:163 exposes Update Inclusion Info and Update & Replace Inclusion Info maintenance actions. | Retain as operational control unless deliberately redesigned. Long-term enablement alone insufficient: this controls maintenance operations rather than completed visual rollout. Preserve admin checks. |
schemaV0MultiOptionConditionalParentAnswers |
Already retired in application main by 6f4b809233 (2026-08-06); absent from schema/catalog. ADR-011:26–30 keeps API compatibility response always true, removes application/Helm flag. GitOps production.values.yaml:14 and live API/PM Deployment env still contain true; live images are 9.44.1-restore.104eca9 and 11.43.1-restore.104eca9. |
Do not duplicate application retirement or blindly clean deployed configuration. Track stale GitOps key cleanup separately after verifying deployed writer releases reached retirement and permanent compatibility floor. ADR-011:74–86 explains old writers are unsafe once hybrid data exists; returning flag cannot make an old writer safe. Current production env indicates configuration/version lag, not proof cleanup is safe. |
| Materialized statistics flags | Current API catalog explicitly separates writes, serving, families and consumer gates. Maintenance hosts do not yet read API runtime overrides. Data readiness/epochs and rollout remain active safety concerns. | Retain. Grouping consumers is UI simplification, not reason to delete correctness/operational gates. |
| AF2/reviewer redesign/integrated PDF | src/services/web/CLAUDE.md explicitly requires separate rollout decision and stability interval before legacy removal; host eligibility fallbacks persist. | Retain. No production rollout evidence satisfying retirement contract. |
| Proportional allocation | docs/features/proportional-study-allocation/STATUS.md:54 calls older PR #2450 abandoned, but current programme supersedes it. | Do not confuse abandoned PR with abandoned feature. Keep current flag/programme. |
Production evidence¶
Read-only public response https://syrf.org.uk/assets/data/appConfig.env.json returned on 2026-09-22:
{"newProgressIndicators":"true","newQuestionManagement":"false","newScreeningOverview":"true","newStageOverview":"true","newStageSettings":"true","quantitativeDataExportEnabled":"true","showUpdateInclusionInfo":"true"}
Read-only kubectl get deployment -n syrf-production -o json, filtered to these nonsecret names, independently showed the same five requested values in syrf-web; deployed image ghcr.io/camaradesuk/syrf-web:7.39.0. No shell was executed in pods. API runtime catalog URL currently returns404 in production, so no runtime-snapshot assertion is made. Public web response establishes actually served static values rather than GitOps intention alone.
GitOps historical enablement: 2025-12-05 production flag configuration. Current values: production web, production shared values. GitHub file history retained these four settings since initial enablement; no intervening disabled configuration was found in returned history. This proves deployed/long-standing configuration, not absence of incidents or a formally accepted rollback retirement.
Common removal mechanics / acceptance¶
Application declaration is src/charts/syrf-common/env-mapping.yaml (requested flags at1200,1266,1277,1299,1310); generated defaults currently false even where live production is true. Remove schema entries then run generator; never hand-edit generated outputs. Update RuntimeFeatureFlagCatalog.cs:89,97–101, handwritten features.selectors.ts, default/local/E2E configuration, test mocks and relevant behavior tests. The persisted runtime override store must tolerate/remove obsolete override keys; confirm before release rather than assuming deleting UI removes stored state. Existing audit entries should remain historical records.
Coordinate GitOps obsolete-value cleanup after application deployment compatibility, preserving old rollback releases until their intended retirement. Keep feature functionality, permissions, independent stats consumers and domain validation. The separate navigation and progress candidates remain ordered follow-ups; avoid bundling QM abandonment or cross-service architecture.
Limits and decisions still needed¶
No business owner declaration or incident/rollback acceptance for the four flags was located, and no authenticated production UI mutation was attempted. Strong candidates can be presented as a concrete keep-enabled cleanup proposal; deployed true is not blanket approval to remove safety controls. No evidence establishes an abandoned flagged feature among the five requested. Current QM docs need correction, not automatic code deletion. No Claude session files were modified or reviewed by this subtask; the parallel #3527 work is addressing its Claude review.
The three settings-availability flags are retired here at the owner's explicit request. Other retirement recommendations remain proposals. This PR makes no production configuration or persisted-option writes.