Skip to content

Runtime feature releases: current-code matrix (Increment A)

This is the evidence half of Increment A in the runtime feature releases plan. It records, for each requirement of the flag architecture roadmap (P1–P10) and each element of Chris's 15 October 2026 functional proposal, what main actually does today.

Baseline. syrf origin/main at e5a3155c4 (2026-10-15). cluster-gitops main at 5bdaa00d6, read through the GitHub API only. Every row was checked against the code, not against older milestones or PR descriptions. Line numbers refer to that baseline. No test was run for this matrix; "Done" means the code and its tests exist on main, not that the behaviour has been proven in a deployed fleet.

Status key. Done: the requirement is met as written. Partial: some of it exists, the gap is named. Missing: nothing on main meets it.

Summary

Status Count
Done 5
Partial 19
Missing 32
Total rows 56

The gaps that matter most for the plan:

  1. Production does not run any of this code. Production's API and PM run 104eca9, which has no runtime-flag store, controller, shared reader or migration entry point (row X.1). Everything below describes main, which production reaches only through the CD plan's catch-up promotion.
  2. On main, production ignores stored policy on both the read and the write side, and the shared reader refuses to start in production. Enabling production policy is three code changes plus a deployment mode, not one controller check (rows P10.1–P10.3).
  3. No authority envelope. The policy document has _id, revision, overrides and updatedAtUtc only: no environment identity, lineage (storeEpoch), catalog or baseline digest, or mutation ID (P3.2, P3.3).
  4. No fleet acknowledgement. Nothing records which host applied which revision, and nothing knows how many hosts there should be, so "Active" cannot be computed (P3.10).
  5. Propagation is polling only (API 30 s, shared reader 15 s). The legacy API provider keeps its last values forever on failure. The shared reader fails closed on the first failed, missing or inconsistent read; its 60 s age limit applies only if refreshes stop altogether (P3.4, P3.5).
  6. The migration entry point never checks which environment it is writing to. It takes the database name from configuration and writes. The pre-deploy Job is enabled in no environment, and previews cannot enable it in its current form (E.1, E.2).
  7. Nothing exists yet for release records, outbox, availability, discovery or protected content (rows R.1–R.10).
  8. No compatibility report or production machine identity. Staging has OpenIddict and a single-scope machine-client precedent; production is still on Auth0 with no machine client accepted by the API (S.1–S.4).

P1: generated contract and write safety

# Requirement Status Evidence on main Gap
P1.1 .NET catalog, defaults and metadata generated from the YAML schema Partial RuntimeFeatureFlagCatalog.cs is a handwritten allow-list (Flag(...)/ReloadRequired(...), lines 102–139, 273–282); a catalog test compares it with env-mapping.yaml (operating guide, "Step 2") Not generated. Owner, scopes, stale behaviour and grain are not declared anywhere
P1.2 Machine-checked consumer manifest; CI fails on a new unclassified read Done consumer-manifest.json (141 consumers), checked by pnpm run validate:generated (guide) Static evidence only, as the guide says
P1.3 Expected revision mandatory on every mutation route Done Controller RuntimeFeatureFlagsController.cs:115-116; store EnsureExpectedRevision RuntimeFeatureFlagStore.cs:451-457; group path :99-100 None
P1.4 Activation class and lifecycle exceptions declared per flag Partial RuntimeFeatureFlagActivation has immediate and pageReload only (RuntimeFeatureFlagCatalog.cs:28-32) No stale-state behaviour, allowed scopes, safe state or production eligibility

P2: shared snapshot seam

# Requirement Status Evidence on main Gap
P2.1 One immutable snapshot per process, captured once per operation, in API and PM Partial SharedRuntimeFeatureFlagReader publishes a frozen SharedRuntimeFeatureFlagSnapshot and Capture() reads it once (SharedRuntimeFeatureFlagReader.cs:16-20, 56-63); used by API FeatureGatedAttribute.cs:52-53 and PM SharedProjectStatisticsFlagSource.cs:34-46 It reads the legacy override document only. The legacy API provider still writes values into the mutable FeatureFlags singleton (RuntimeFeatureFlagProvider.cs:83-146) and is what most API consumers use
P2.2 Minimum diagnostic endpoints Done API GET /api/runtime-feature-flags/provider-status (RuntimeFeatureFlagsController.cs:65-88); PM GET /api/runtime-feature-flags/reader-status (RuntimeFeatureFlagReaderController.cs:10-16); diagnostic health check (SharedRuntimeFeatureFlagRegistration.cs:74-104) Local process only, labelled as such
P2.3 Shared evaluator (dependencies, compatibility, statistics constraints) usable by every host Missing Normalisation lives in the API store (RuntimeFeatureFlagStore.cs:337-391); the shared snapshot's Evaluate is "override or deployed default" (SharedRuntimeFeatureFlagReader.cs:19) PM cannot evaluate dependencies; a dependency-normalised value differs between API and PM

P3: usable propagation slice

# Requirement Status Evidence on main Gap
P3.1 State and audit committed atomically Done WithTransactionAsync around ReplaceOneAsync + InsertOneAsync (RuntimeFeatureFlagStore.cs:88-92, 112-115, 182-186) Write and read concern are not explicit at the call site; staging's cluster address carries no w=majority option (cluster-gitops staging.values.yaml:17)
P3.2 Authority envelope: environment ID, storeEpoch, catalog, baseline and policy digests Missing BuildState writes _id, revision, overrides, updatedAtUtc (RuntimeFeatureFlagStore.cs:489-496). The shared reader rejects any document with storeEpoch (SharedRuntimeFeatureFlagReader.cs:112) Whole envelope, plus the reviewed legacy-to-lineage migration (README §3)
P3.3 Mutation ID resolves a lost response without a second mutation Missing RuntimeFeatureFlagMutation has value, revision, reason and dependency fields only (RuntimeFeatureFlagModels.cs:57-62). The admin UI refreshes instead of replaying (guide) Mutation ID, unique index, receipt lookup
P3.4 Change-stream watch with race-free bootstrap Missing Flags poll only: API PeriodicTimer(30 s) (RuntimeFeatureFlagProvider.cs:172), shared reader 15 s (SharedRuntimeFeatureFlagRegistration.cs:69). Change-stream code exists for other collections: MongoContext.GetCachedCollectionChangeStream (MongoContext.cs:180-228) and the realtime ChangeFeedSource.cs:69-77 Not wired to policy. See the freshness assessment for whether it is needed
P3.5 Periodic reconciliation; a stale observation fails closed for risky admission Partial Shared reader: 15 s poll, 5 s read deadline; a single failed, missing or equal-revision/different-digest read sets Unavailable or Inconsistent at once, and Evaluate returns no decision for any state but Current (SharedRuntimeFeatureFlagReader.cs:65-69, 84-104); MaximumAge 60 s marks an observation Stale only when refreshes stop (:45, 56-63) Legacy API provider logs and keeps the last values indefinitely (RuntimeFeatureFlagProvider.cs:177-199). No per-flag stale behaviour
P3.6 Revision regression and equal-revision/different-digest detected Partial Shared reader marks Inconsistent (SharedRuntimeFeatureFlagReader.cs:90-95) Legacy provider accepts an equal revision with any content (RuntimeFeatureFlagProvider.cs:56); so does the browser (runtime-feature-flags.service.ts:111)
P3.7 Deployment-controlled provider mode with parity-checked rollback Partial RuntimeFeatureFlags:SharedReaderMode = Disabled/Shadow/Membership/StatisticsServing (SharedRuntimeFeatureFlagRegistration.cs:24-40) README proposed ProviderMode=Legacy|Shadow|Shared; the name differs. Rollback parity is a manual runbook step (guide), not a check
P3.8 Browser bootstrap seeds the service with the same envelope Missing main.ts:284-291 applies the snapshot; the service's counter starts at -1 (runtime-feature-flags.service.ts:74) A slower first poll can apply an older revision than bootstrap did
P3.9 Browser recovery and boundaries: reconnect fetch, revision as a decimal string, context generation, safe fallback Partial Five-minute poll plus SignalR hint, 3 s timeout, two retries (runtime-feature-flags.service.ts:32, 86-99) No reconnect subscription; revision is a JSON number (:21); no context generation; if the first fetch fails the browser keeps deployed defaults (:99-103, main.ts:292-301), which is permissive once production can stop a default-On flag
P3.10 Fleet acknowledgement against an expected-host inventory Missing The guide says there is no fleet registry (manage-feature-flags.md, provider-status paragraph). syrf_metadata records instances and versions (DatabaseMetadataWriter.cs:160-168), not applied policy Host acknowledgement store, expected inventory, Active/Degraded computation
P3.11 Each API pod notifies its own SignalR clients after it applies a revision Missing Only the writing pod broadcasts (RuntimeFeatureFlagsController.cs:142-143, 149-160); there is no backplane Browsers on other pods wait for the five-minute poll

P4: authoritative baseline import

# Requirement Status Evidence on main Gap
P4.1 Reviewed import of an accepted baseline; pods never reseed Missing Deployed values come from configuration at startup (RuntimeFeatureFlagCatalog.cs:262-271, store constructor :67) Baseline bundle, digest, import command
P4.2 Clearing an override previews the baseline it reveals Partial The admin page shows a Deployed column and a Default action (guide) No side-effect-free preview; the revealed baseline is per-pod configuration

P5 and P5a: consumers

# Requirement Status Evidence on main Gap
P5.1 Migrate one host and flag family at a time with captured reads Partial API disableMembership route gate (FeatureGatedAttribute.cs:52-53); PM statistics serving and four consumer selectors (SharedProjectStatisticsFlagSource.cs:34-46) Every other consumer is on the legacy provider or startup FeatureFlags. README §13 lists only the membership slice; the PM StatisticsServing mode is newer
P5.2 signalRActive backend/browser lifecycle audit Missing Still special-cased: pinned to deployment while reviewer tracking is on (RuntimeFeatureFlagModels.cs:151, 163-168) Audit and classification
P5.3 Grouped controls (not in the roadmap) Done RuntimeFeatureFlagGroups.cs; atomic group mutation RuntimeFeatureFlagStore.cs:95-118 Ahead of the roadmap; keep
P5a.1 "Use precomputed statistics" operable at runtime in API and PM Partial StatisticsServing mode in PM; group statistics-consumers-v2 (guide) Non-production only; stops reads but cannot start deployment-disabled serving
P5a.2 "Calculate and maintain statistics" as a coordinated runtime transition Missing Maintenance gates pinned to deployment (RuntimeFeatureFlagProvider.cs:71-74, ProjectStatisticsRuntimeActivation.cs) Whole requirement; stays its own track

P6: audit and administration depth

# Requirement Status Evidence on main Gap
P6.1 Audit history read API and UI Missing Audit is written (RuntimeFeatureFlagStore.cs:72, 184-186) and never read by any endpoint Paginated, authorised history
P6.2 Side-effect-free preview with a plan digest Partial The 422 dependency plan and exact confirmed set (RuntimeFeatureFlagStore.cs:238-253) No preview endpoint, no digest binding revision, catalog and actor
P6.3 Restore as a forward revision from history Partial Default/clear exists No restore-from-history
P6.4 Real-actor authorisation under impersonation; both identities audited Partial Audit records real and impersonated identities (RuntimeFeatureFlagsController.cs:162-174, RuntimeFeatureFlagStore.cs:516-521). But the authorisation decision uses currentUser.GetUserId(), which returns the impersonated user (CurrentUserService.cs:26-31), and under support impersonation the claim groups are empty (:48-50). Authorisation is the target's authority, safe today only because impersonation cannot escalate Decide which identity is authorised for production policy; plan RT-13 asserts it per impersonation path
P6.5 Separate capabilities: change policy, emergency stop, inspect targeting Partial One capability, ApplicationCapability.ManageRuntimeFeatureFlags, mapped to Administrator (ApplicationAuthorityPolicy.cs:42, 85) Split capabilities
P6.6 Emergency stop distinct from Off Missing The UI offers On, Off and Default only (guide) Stop semantics, safe state, separate permission

P7–P10

# Requirement Status Evidence on main Gap
P7.1 Project-scoped allow/deny Missing The guide: "This MVP deliberately does not include percentage or audience targeting" Whole phase
P8.1 User, investigator and combined targeting; beta consent Missing Same Whole phase
P9.1 Stable basis-point cohorts Missing Same Whole phase
P10.1 Production mutation permitted under an explicit policy Missing IsMutationAllowed allow-lists non-production environment names (RuntimeFeatureFlagStore.cs:467-487); the controller returns 403 (RuntimeFeatureFlagsController.cs:109-110) Deployment-controlled production mode (see ADR-025)
P10.2 Production reads honour stored policy Missing BuildEffectiveValues ignores overrides when mutation is not allowed (RuntimeFeatureFlagStore.cs:352); descriptors hide them (:199-202) The proposal mentions only the mutation refusal; the read side must change too
P10.3 Shared reader runs in production Missing Enabled modes throw unless the environment is in a non-production list (SharedRuntimeFeatureFlagRegistration.cs:31-36) Same production mode switch

Proposal elements

# Element Status Evidence on main Gap
E.1 Environment binding verified before any migration write Missing MigrationEntryPoint.RunAsync reads ConnectionStrings:MongoConnection:DatabaseName and opens it (MigrationEntryPoint.cs:124-131); RuntimeEnvironment is used only for metadata (:138-141). syrf_metadata.meta.environment is a best-effort SetOnInsert from any pod start (DatabaseMetadataWriter.cs:239-246) Binding record and pre-write check (plan §6.3)
E.2 ADR-023 migrations can provision the release stores Partial Runner, lock file and PreSync Job template exist; latest PM migration is 0008 (PmMigrations.cs:115-119, migrations.lock.json). Only CreateIndexMigration and UnsetFieldsMigration kinds exist The Job is enabled in no environment (no migrations.enabled in cluster-gitops; pre-deploy plan M22): staging needs pre-deploy R1, production its catch-up enablement, previews the DatabaseLifecycle switch to up (the template refuses migrations.enabled with preview.waitForDatabase, _migrations-job.tpl:39). A second catalogue in the same database would see the first one's syrf_migrations records as unknown (MigrationRunner.cs:83, 151). Expected binding values cannot come through env:, which the Job strips of SYRF_MIGRATION_* (_migrations-job.tpl:51-55)
E.3 Global policy identity separate from release ID Missing Only a global revision exists Release ID
R.1 Feature definition with stable identity, separate from flag keys Missing Closest: RuntimeFeatureFlagGroup(Key, Name, Description, Keys) (RuntimeFeatureFlagGroups.cs:6) Feature catalog
R.2 Release intent, record and append-only events Missing None Whole store
R.3 Durable outbox for announcements Missing Pattern exists: pmActivityClaimRevocationOutbox (MongoDB reference) Release outbox
R.4 Availability response per trusted context Missing Anonymous GET /api/runtime-feature-flags returns global values only (RuntimeFeatureFlagsController.cs:59-63) Context-aware endpoint
R.5 Discovery preference (seen/dismissed) Missing Closest: ReviewerWorkspaceSettings.FirstRunHintSeen (ReviewerWorkspaceSettings.cs:54, 67), scoped to the review workspace Store and endpoint
R.6 What's New driven by confirmed milestones Partial Static whats-new.json, shipped with the web image; date is a hand-typed string such as "18th August 2026" (whats-new.json:6) Dates follow web deployment, not activation. The feed how-to still describes a latest/past shape; the feed uses updates
R.7 Guide shows availability and milestone dates Partial Public Jekyll site; dates appear in page titles (user-guide/whats-new/whats-new-bulk-pdf-upload.md, front matter) No authenticated or availability-aware view
R.8 Protected (private pilot) content, assets and search Missing user-guide/ is a public static site Protected surface
R.9 Declared editorial timezone for date-only headings Missing Storage and scheduling convention is UTC (Quartz schedules set TimeZoneId = "UTC", e.g. ProjectStatisticsMaintenanceSchedule.cs:21; statistics charts render UTC, observed-history.ts:689). No declared display zone for date-only headings; feed dates carry no zone Decision OD-9 (UTC or Europe/London for display)
R.10 Staging rehearsals cannot create production records Missing No release records exist; staging and production databases are separate (cluster-gitops staging.values.yaml:13-17, production.values.yaml:19-20) Binding and environment checks on every write
C.1 CD plan G2 production render comparison Missing Planned only; .github/promotion/ does not exist on main Built by the CD plan; made conditional by this plan
X.1 Production runs the runtime-flag mechanism described above Missing cluster-gitops production/api/config.yaml pins 9.44.1-restore.104eca9 (chart api-v9.44.0) and PM 11.43.1-restore.104eca9; git grep -il 'RuntimeFeatureFlag\|MigrationEntryPoint' 104eca9 -- src returns nothing The CD plan's catch-up promotion; every production row above describes main
S.1 Environment compatibility report through a scoped automation view Missing Only the anonymous snapshot exists (RuntimeFeatureFlagsController.cs:59-63); the statistics staging preflight script combines that snapshot, /health/live versions and cluster-gitops files itself (statistics STATUS) Report contract, revision vector, endpoint (plan §14)
S.2 Environment-bound machine identity with a read-only scope Partial Staging: OpenIddict client-credentials (Identity.Endpoint/Program.cs:142); the single-scope syrf-statistics-evidence client (StatisticsParityEvidenceClient.cs:4-23) and the statistics:operate credential, accepted on specific routes (API Program.cs:481-485). Production: Identity disabled (cluster-gitops production/identity/config.yaml) and no OpenIddict validation configured, so only the Auth0 JWT scheme authenticates there; it would authenticate an Auth0 machine token for the API audience, but no authorisation path for a machine principal exists in production, and the tenant's grants are not visible from the repository Production automation identity and its authorisation path (plan OD-21)
S.3 Workload observations from a scoped collector Partial syrf_metadata instance records (DatabaseMetadataWriter.cs:160-168); staging-only read-only Kubernetes identity syrf-stats-preflight (#3806) Collector publication route and production identity
S.4 Deployment compatibility constraint enforced on policy writes Missing None Constraint record and write-path check (plan §14.5)

Superseded assumptions

The full list, with what replaces each, is in plan §3.