Skip to content

Milestone Validation: M005

This is Phase 5's Nyquist contract. Evidence names command/script version, timestamp, environment, status, and bounded results in #2466 or a linked run. Exact secret/version/export/callback identifiers remain in a restricted manifest; local E2E never substitutes for the live harness.

Re-baseline — 2026-08-27

This document was last maintained on 2026-08-10 (e7c985fe4), before any of the native-parity PRs merged. As found on 2026-08-25 it carried 0 of 58 ticked boxes and an Identity baseline of "313 passing" — both had stopped being true, so the document reported settled work as outstanding and could not be used to tell real remaining risk from bookkeeping.

It is re-baselined here against current SyRF main through #2905 (14755a8b2). The final stacked Identity suite total below is the actually observed post-stack result, not a carried-forward historical count.

Rules applied:

  • Where this document and the code disagreed, the code won, and the correction says so plainly instead of quietly restating history.
  • Every ticked box names its evidence — a file:line, or a command whose output was actually observed. A tick without evidence is worse than an unticked box, because it converts an open question into a false assurance.
  • not verifiable from code is a permitted answer and is used wherever the criterion is about deployed state, an operator action, or a human process. Several exit checks are exactly that, and saying so is the honest answer.
  • Re-baselining moved bookkeeping, not risk. Nothing below claims rehearsal readiness, recommends activation, or softens a gate; the reconciliation additionally added outstanding items that no box previously carried (see "Items still open").
Mark Meaning
[x] Evidenced by the named file:line, merge SHA or observed command output.
[ ] Outstanding. The suffix states why: OPEN (a known gap, with its issue where one exists), live evidence outstanding, not verifiable from code, or not started.
[~] Withdrawn. An earlier revision listed this as outstanding; it was disproved against the code and is retired in place — kept rather than deleted so the correction stays visible — and where the withdrawal leaves real work behind, that work carries its own [ ] box. Withdrawn items are not counted as outstanding.

The isolated synthetic rehearsal cannot be stood up today. The blocking items, each evidenced below, are: the external test-mailbox resource the live contract requires has unverified availability (operator-supplied at run time, so its absence from both repositories proves nothing — an operator check is what is outstanding); the required staging google-oauth Secret is absent and the sole operator has not yet confirmed the exact staging callback while retaining every Auth0 callback; and the S08 Task 1 render-and-audit of the operator-owned staging resources has not been performed. The read-only ProjectManagement connection is no longer a missing declarative blocker: cluster-gitops@e25ca2ed declares the least-privilege Atlas user and the exact operator-generated Secret contract. Read-only inspection found both the Atlas resource Ready and the namespace-local Secret present with its expected key contract; S08 must still prove the eventual pod mount/use. The separate required ProjectManagementProvisioning BaseUrl/audience and dedicated syrf-identity-provisioner client/Secret are not declared in staging GitOps, and no corresponding ESO-generated Secret exists. Task 1 must add and render that least-privilege contract without reusing syrf-api (IdentityHostOptions.cs:59-67,127-142; .chart/values.yaml:50-57). The unexecuted live harness is deliberately not in that list, because slices/S07/S07-PLAN.md:108 requires it to run only after S08 deploys, so it cannot also be a prerequisite of standing the environment up. Its assertions being code-resident rather than live is the R08 gap, tracked in its own box below. The isolated-rehearsal rollback is excluded from the setup list for the same sequencing reason, but remains a distinct post-S08 S30/R10 checkpoint: it reverts only the isolated Argo application/revision and synthetic resources. That is separate from S09's later Auth0 provider rollback on ordinary staging Web/API (slices/S09/S09-PLAN.md:12), and both rollback checkpoints remain explicit below. The absent Redis/Valkey manifest in cluster-gitops sits alongside them as the rehearsal's own session dependency (RH-3) and as the S09 gate — but it is not an S08 blocker: S08 is Identity-only and provisions no session store.

Baseline

Signal Baseline Required direction
Production issuer Auth0 endpoints at signin.syrf.org.uk OpenIddict at identity.syrf.org.uk after S13
Production Web authProvider=auth0 BFF/Auth0 after S11; BFF/OpenIddict after S13
Identity GitOps cluster-gitops@e25ca2ed declares disabled staging Identity 1.24.0 / SyRF 8053842 (syrf/environments/staging/identity/config.yaml, service.enabled: false). The ApplicationSet selector excludes it; read-only inspection on 2026-08-27 found no Identity Argo Application, workload, Service, Ingress or DNS record. No production Identity directory exists. The pin predates #2905, so S08 needs a later immutable release containing 14755a8b2 Staging/production Synced/Healthy
Identity tests 1,789 passing, 0 failed at 14755a8b2dotnet test src/services/identity/identity.slnf1,112 (Endpoint.Tests) + 677 (Migration.Tests). The former "313" and 1,565 baselines predate the final parity stack and are retired. ≥1,789 plus all new tests
Package audit Identity-scoped: dotnet list src/services/identity/identity.slnf package --vulnerable --include-transitive reports SyRF.Identity.Endpoint, SyRF.Identity.Migration, SyRF.Identity.Shared and SyRF.SharedKernel clean; SharpCompress and Snappier do not appear, and the only advisory is SSH.NET 2024.2.0 (High, GHSA-q939-rpr3-3284) via Testcontainers 4.3.0 in the two Identity test projects. Repo-wide the picture is worse, and it is the one that matters for a shipped image: dotnet list syrf.sln package --vulnerable --include-transitive reports Snappier 1.0.0 (High, GHSA-pggp-6c3x-2xmx) and SharpCompress 0.30.1 (Moderate) reaching ten non-test projects eachSyRF.API.Endpoint, SyRF.Mongo.Common, SyRF.ProjectManagement.{Application,Core,Endpoint,Messages,Mongo.Data}, SyRF.Quartz, SyRF.S3FileSavedNotifier.Endpoint, SyRF.WebHostConfig.Common — plus Microsoft.OpenApi 2.0.0 (High) in SyRF.Quartz. Tracked as #2907. Neither reported vulnerable, and no vulnerable package in a shipped project
DataProtection Shared encrypted Mongo ring implemented (SyRF.Identity.Shared/IdentityDataProtectionExtensions.cs:27,61,68) Shared encrypted Mongo ring
BFF sessions Disabled; no production Redis, and no Redis/Valkey manifest or operator exists in cluster-gitops (verified 2026-08-27 at e25ca2ed) Redis + provider/issuer/generation
Campaign control Durable compare-and-swap Mongo state (SyRF.Identity.Migration/CampaignRunStore.cs); eleven GitOps operations, default off (.chart/values.yaml:161-172) Secret-safe durable GitOps operations

Requirement Validation Matrix

complete · outstanding · code-resident evidence present, live/manual evidence outstanding.

R08 and R17 were previously inverted against this legend and are corrected here. R08 was although all ten S07-contract flows are asserted in checked-in harness code and only the live run is missing — which is the definition of . R17 was although six of its acceptance clauses are open in code, which the legend's explicitly excludes. Symbols now follow the legend rather than intuition about which requirement feels closer to done.

Requirement Automated evidence Live/manual evidence Status
M005-R01 Identity suite, real Mongo campaign test, package audit Test-only advisory reported rather than suppressed; no unexplained suppression
M005-R02 Chart→host startup; schema/block/checksum two-pass generation Rendered key-name inventory
M005-R03 Issuer/forwarding; S02 basic readiness; S03 ring contributor Ingress discovery and dependency state
M005-R04 Two-token CLI/Endpoint process test Token A before and token B after Argo rollout; backup restore
M005-R05 Explicit provider/custom-domain/selector/session tests Separate S09 rollback and S27 reapply
M005-R06 CSRF, log/redaction, telemetry/query/worktree/status tests Exact 24-hour aggregate snapshots; Identity endpoint PII-placement sentinel proof
M005-R07 Cohort/readiness/input-resolver/campaign state tests Secret-mounted canary and durable stop/resume
M005-R08 live-smoke/Playwright/status compile/tests Full staging topology
M005-R09 Reviewed SyRF chart/package plus cluster-gitops Argo/Kubernetes/operator/ExternalSecret render and preflight Authorised staging version transfer and dark launch
M005-R10 Post-S08 rehearsal matrix/teardown plus config/old-cookie checks Distinct rehearsal teardown, switch, rollback and reapply revisions/syncs
M005-R11 Production render/key-ring/restore checks Authorised versions and Argo two-token rollout
M005-R12 Pre-created exact baseline query plus restricted Auth0-client → exact enabled GCP version authorisation ExternalSecret target/remoteRef proof and exact 24-hour BFF/Auth0 hold
M005-R13 Status inspection for every GitOps operation Canary approval, stop quiescence, reconciliation, 100/95/90 plus the Google-only cohort held to the all-enabled floor (four denominators, three declared floors — see the readiness exit check); post-migration S12 backup restore
M005-R14 Atomic config/smoke/threshold scripts KEEP or completed fresh-generation rollback
M005-R15 Bounded builds/tests/BSON/generation/promotions Four reviews; S19 rollback; S28 reapply; production then secrets
M005-R16 Evidence/classification/preflight validators Export, per-version/callback/tenant manual receipts
M005-R17 Mapping, confirmation/profile, external-lifecycle and migration integration/E2E suites Code work is outstanding, not only live evidence: the acceptance clauses below list six open code-level gaps, so this is not a case of "only a staging run remains". The live matrix itself belongs to the post-S08 rehearsal

Re-baseline notes on the partially-evidenced rows:

  • R01 — the suite is green (1,789/1,789) and the deployed Identity image closure reports no vulnerable package. SharpCompress/Snappier are clean within that closure only; repo-wide they are not, which is #2907. That does not reopen R01 or block the Identity image itself. S08 now also changes the deployed Project Management receiver configuration, however, so the Project Management portion of #2907 is an explicit S08/G0 affected-service prerequisite rather than being deferred to S10–S13. The audit also reports SSH.NET 2024.2.0 (High) transitively through Testcontainers 4.3.0 in both Identity test projects. It is test-only, absent from the runtime image, and reported rather than suppressed: a repository-wide search finds no NuGetAuditSuppress entry. That explicit explanation satisfies R01's no-unexplained-suppression gate; the later affected-service reassessment of #2907 remains separate.
  • R08 — the harness is checked in (e2e/tests/auth-migration-live.spec.ts, scripts/auth-migration/live-smoke.sh) and asserts all ten flows the S07 contract requires (slices/S07/S07-PLAN.md:101-102); the per-flow mapping is under the live-matrix item below. It is for one reason only: it has never been executed against a deployed topology, so every assertion is code-resident and none is live evidence. Coverage is not the gap — which is precisely why the legend's fits and the earlier did not. Its operator-invoked design is not counted against it either — see the live-matrix item below.
  • R17 — all five PRs merged (SHAs under the exit check below), and the fail-closed mapping, admission and migration-preservation halves are evidenced. It is not complete: the emailed single-use sign-in link factor, auth_time/amr, the link-failure 500, the ported notification templates, and the shared userinfo projection are all still open, and the five clauses below do not yet have their required automated acceptance evidence. The eight application flows of the rehearsal's full live matrix are not part of that list: each already has an in-repo acceptance suite, and running them against a deployed topology is the rehearsal's own step 6, filed under the rehearsal prerequisites below. The separate isolated-topology rollback is likewise a post-S08 rehearsal checkpoint, not an R17 acceptance clause. The concurrent-unlink race is not on that list. An earlier revision of this note listed it; #2839 was closed as not reproducible on 2026-08-26, because the Mongo user store persists login removal through a ConcurrencyStamp compare-and-swap and the losing racer therefore fails — the evidence is in sub-item (a) of the external-lifecycle exit check below. See "R17 native-parity acceptance clauses".

Application Readiness Exit Checks (S01–S07, S26, S29, M005-R17)

  • ≥313 Identity tests plus new real-store/startup/DataProtection/readiness/security/campaign tests pass; no vulnerable package is reported in the dependency closure of the Identity artefacts S08 deploys. — dotnet test src/services/identity/identity.slnfFailed: 0, Passed: 1,112 and Failed: 0, Passed: 677 (1,789 total) at 14755a8b2. For the advisory half, the scope that matters is the deployed image closure: the image builds SyRF.Identity.Endpoint and SyRF.Identity.Migration over SyRF.Identity.Shared and SyRF.SharedKernel and nothing else (src/services/identity/SyRF.Identity.Endpoint/Dockerfile:17-20,32-38; the two csproj files reference only those), and all four report no vulnerable package. The criterion was reworded on 2026-08-25, and the honest reason is that the first round's audit was wrong, not that the reword changed nothing. It originally read "SharpCompress/Snappier are not reported vulnerable" — an unscoped, repo-wide claim — and the first round ticked it having audited only identity.slnf. At its original breadth that evidence did not support the tick: repo-wide, both packages are reported vulnerable (next box). An earlier revision of this line said the reword "narrows nothing that was previously proven"; that was not defensible and is withdrawn. This tick is a fresh pass at the corrected scope, not continuity of the original claim. The one advisory inside identity.slnf is SSH.NET 2024.2.0 (High) in the two Identity test projects via Testcontainers. It is absent from the image and explicitly reported, and a repository-wide search finds no NuGetAuditSuppress entry; R01's no-unexplained-suppression evidence is therefore satisfied.
  • OPEN (new, found 2026-08-25 — corrected 2026-08-27): the package audit is not clean repo-wide, and the shipped exposure is Snappier, not SSH.NET. dotnet list syrf.sln package --vulnerable --include-transitive reports Snappier 1.0.0 (High, GHSA-pggp-6c3x-2xmx) in ten non-test projects, including SyRF.API.Endpoint, SyRF.ProjectManagement.Core and SyRF.Mongo.Common; SharpCompress 0.30.1 (Moderate) in the same ten; and Microsoft.OpenApi 2.0.0 (High) in SyRF.Quartz. All of those ship. SSH.NET (High) is test-only apart from the shared SyRF.Testing.Common library. This corrects an earlier statement in this document's own re-baseline, which audited only identity.slnf and therefore reported SharpCompress and Snappier as clean; scoped to Identity they are, repo-wide they are not. Tracked as #2907. This does not reopen M005-R01 because the Identity image closure is clean. It does gate the Project Management portion of S08/G0, however: Task 1 changes the receiver chart/values and therefore rolls the affected Project Management deployment. A focused exact-artifact audit/remediation must close the Project Management Snappier/SharpCompress exposure before that change can merge or sync. Later slices separately reassess every other affected artifact they deploy. The test-only SSH.NET 2024.2.0 advisory is reported rather than suppressed, and the repository has no NuGetAuditSuppress entry, so no unexplained suppression remains and R01 itself is complete.
  • S02 adds SYRF__, exact consumed keys, real rendered-host startup, explicit issuer/trusted forwarding, process liveness, and Mongo/seed/key/SES readiness without requiring the not-yet-built ring. — SyRF.Identity.Endpoint/Program.cs:36 (AddEnvironmentVariables("SYRF__")), :148-150 (explicit issuer), :441-455 (forwarded headers restricted to configured proxies), :329 ("self", tag live), Health/IdentityReadinessHealthCheck.cs:113-151 (Mongo, ProjectManagement, DataProtection ring, OpenIddict client seed, SES); SyRF.Identity.Endpoint.Tests/IdentityConfigurationStartupTests.cs:37,318,344,405,429 (rendered-chart environment starts the real non-Development host; missing consumed key, disabled SES, colliding client ids and non-origin CORS all fail closed).
  • S03 alone adds encrypted-ring readiness. Endpoint and CLI share application name, Mongo repository, certificate, and token-provider purpose. — SyRF.Identity.Shared/IdentityDataProtectionExtensions.cs:27,61 (FixedApplicationName for both hosts), :68 (ProtectKeysWithCertificate); ring probe at Health/IdentityReadinessHealthCheck.cs:86-109; contract test SyRF.Identity.Migration.Tests/IdentityDataProtectionContractTests.cs.
  • The integration test generates two tokens before redemption: Endpoint process A redeems A, exits, and process B redeems valid B after restart; wrong ring/application/certificate fails. — SyRF.Identity.Migration.Tests/DataProtectionCrossProcessTests.cs:104 CampaignTokens_SurviveEndpointRestart_AndRejectReplayOrMismatchedProtection: asserts exactly two links emitted before any redemption (:123), process A redeems the first (:134), a different key ring (:144), a different ring certificate (:154) and a different application name (:167) are each refused, and restarted process B redeems the second (:173).
  • Canonical env mapping changes include JSON schema, generated blocks, and .generated-checksums.json; first/second generator passes are identical. — standing gate rather than a one-time state: src/charts/syrf-common/env-mapping.schema.json, ./.generated-checksums.json, enforced per PR by pnpm run validate:generated (package.json:24) in the validate-generated-code job (.github/workflows/pr-tests.yml:176,209).
  • S05 is serialized behind S01/S03/S04; no S01/S05 campaign or S04/S05 API shared-file execution is parallel; telemetry consumes S04's explicit provider. — not verifiable from code: this is a slice execution-ordering claim about how the work was run, not a property of the tree. The telemetry half is evidenced (src/services/api/SyRF.API.Endpoint/Auth/BffAuthTelemetry.cs, BffProviderConfigurationTests.cs).
  • CSRF requires exact effective scheme+IDN host+effective port after trusted forwarding. — src/services/api/SyRF.API.Endpoint/Auth/ValidateSameOriginAttribute.cs:103-138 (scheme, normalised host and effective port compared as one tuple, default port substituted explicitly), :155-186 (IdnMapping().GetAscii normalisation, non-DNS names refused); tests SyRF.API.Endpoint.Tests/ValidateSameOriginAttributeTests.cs.
  • Public evidence, GitOps diffs, rendered manifests, evidence JSON, help and argv contain no email, stable user/Auth0/project/job/run ID, cookie/session/nonce, token/reset link, authorization/connection/private-key material, or sensitive source/export/secret path; existing evidence-scanner behavior is unchanged. — live evidence outstanding: the mechanism exists and is tested (scripts/auth-migration/check-redacted-evidence.sh, scripts/auth-migration/tests/scripts.bats, SyRF.Identity.Migration.Tests/OperationDeclarationTests.cs), but no public evidence artefact has been produced yet, so the property is asserted by mechanism and not yet demonstrated over real output.
  • Identity endpoint administration, forgot-password and email-transport identifiers appear only as exact Pii* structured property values—never in rendered messages, message templates, ordinary properties/scopes, or exception text/data—and credentials remain wholly absent, proven by sentinel capture tests. — SyRF.Identity.Endpoint.Tests/IdentitySensitiveLogTests.cs:86,111,143,164,190,220,239,269,301,356 (sentinel capture across every administration branch, including AdminPasswordReset_KnownEmail_LogsTaggedUserIdButNeverResetToken and AdminReadEndpoints_EmitNoLogEventsInAnyBranch).
  • Deployed Identity console output, the collector, and the ingest pipeline preserve and match nested Properties.Pii* fields; application formatter tests alone do not prove downstream redaction, hashing, or routing. — not verifiable from code, and this document already says so: it is a property of the deployed collector and ingest pipeline. Unchanged.
  • Readiness emits exactly the privileged, enabled active-90-day, all-enabled and Google-only denominators from authoritative activity/privilege/lock/password/Google inputs. — SyRF.Identity.Migration/Commands/ReadinessCommand.cs:126,244-263,334-353 (three metrics built from project-privilege, enabled, lockout, password and Google inputs); tests SyRF.Identity.Migration.Tests/ReadinessCommandTests.cs. Now four denominators, though still three declared floors: PR #2837 added a Google-only metric measured by the provider route for that cohort (:347-355), gated at :413 against the existing all-enabled floor rather than a new configurable one (Models/ReadinessThresholds.cs:16,18-19 still parses exactly three; .chart/values.yaml:196 renders [100, 95, 90]) and a GoogleWithoutSignInRoute reason count for enabled Google-only accounts carrying no Google login at all (:367,389). Schema 3 introduced that denominator; #2925 bumps the public evidence to schema 4 because provider-route readiness is now explicitly independent from provider/local email verification, which remains the separate admission gate (Models/ReadinessModels.cs). The three-percentage wording in M005-R13, G3 and the S12 checks below predates it and now understates the gate.
  • Production import/verify/readiness/campaign/status/control inputs use one resolver; no credentials, exports, identifiers, allowlists, or source paths are accepted on argv. — SyRF.Identity.Migration/Configuration/SecretInputResolver.cs:146; tests SecretInputResolverTests.cs, OperationDeclarationTests.cs, ProductionOperationCommandTests.cs.
  • Canary membership is secret-mounted; durable Mongo state implements approve, stop-before-claim/send, stopped, resume generation, unknown disposition, completion, and zero-send rerun. — SyRF.Identity.Migration/CampaignRunStore.cs, CampaignSender.cs; .chart/values.yaml:161-172 (operation selector, default empty), :250-267 (downward-API and per-operation secret mounts); tests SyRF.Identity.Migration.Tests/CampaignControlTests.cs (approve/stop/stopped/resume/complete/rerun transitions).
  • Worktree guard accepts normal .git/worktrees and bare .bare/worktrees, including physical repo/.worktrees/*, while rejecting main checkouts. — scripts/auth-migration/assert-worktree.sh; tests scripts/auth-migration/tests/scripts.bats.
  • Checked-in telemetry, redaction, identity-job-status.sh, and live harness are executable and read-only toward clusters except GitOps/Argo reconciliation. — all five scripts are mode 755 (ls -l scripts/auth-migration/); the only cluster verbs are kubectl get (identity-job-status.sh:89, live-smoke.sh:258) and kubectl port-forward (live-smoke.sh:274) — no apply, patch, delete or create appears in any of them.
  • All focused native-parity PRs A–D plus the resolved A2 native-endpoint companion in the behaviour-parity gate are merged; the suite records each registration, password, external, migration, token, userinfo and BFF-resolution outcome. — merged: A #2735 (a6fcc4987), A2 #2803 (52f031087), B #2806 (0b448ee92), C #2808 (a4d73b297), D #2837 (4b65b43c8), with follow-ups #2838 (b9e753b3c), #2855 (b558611a3), #2854 (0eafb3472), registration non-enumeration #2901 (3ea3b800c), operation-bound step-up #2902 (f8755a2e1) and deletion-time revocation #2905 (14755a8b2). Outcome coverage: AccountClaimingTests.cs and RegistrationDisclosureTests.cs (registration/claiming), AdmissionPageTests.cs/IdentityAdmissionMiddlewareTests.cs (password/middle step), ExternalLoginPageTests.cs/ExternalIdentityLifecycleServiceTests.cs/StepUpProofServiceTests.cs (external), MigrationParityMatrixTests.cs/MigrationImportParityIntegrationTests.cs (migration), AuthorizationControllerTests.cs (token/userinfo), SyRF.API.Endpoint.Tests/BffAuthControllerTests.cs (BFF resolution). Merged is not the same as complete — the named acceptance clauses still open are listed under "R17 native-parity acceptance clauses".
  • Token issuance, /connect/userinfo, BffAuthController and ApplicationService.ResolveInvestigatorIdAsync all require one verified immutable Identity-to-Investigator mapping; missing or ambiguous user_id fails closed and an Identity sub is never treated as an InvestigatorId. — Controllers/AuthorizationController.cs:108 (authorize), :250 (token/refresh), :339-349 (userinfo — :339 is the null-user/missing-SyrfUserId guard and :344-349 adds the account-version match and the admission evaluation; both are load-bearing for the fail-closed guarantee); SyRF.ProjectManagement.Core/Services/ApplicationService.cs:111-140 — an unparseable claim and a GUID-shaped sub without a user_id claim both throw InvestigatorMappingRequiredException, and the legacy Auth0Id lookup is unreachable for GUID subjects; SyRF.API.Endpoint/Auth/BffAuthController.cs:274-281 returns 401 on that exception. Also Services/UserClaimsService.cs:21-25 as defence in depth. The parity matrix's current-state rows now record this closure; userinfo remains a second, divergent claim producer (see its own clause below).
  • The approved email-verification admission policy, resend/profile-completion flow and per-source imported verification provenance are proven without locking out the existing population. — EVIDENCED (#2925). ExternalLogin.cshtml.cs resolves linked accounts only by provider key, reconciles trusted provider metadata, and reaches ExternalLoginSignInAsync without changing EmailConfirmed; the provider-derived confirmation service and DI registration no longer exist. ExternalLoginPageTests.OnGetCallbackAsync_VerifiedLinkedProvider_PreservesLocalConfirmationAndUsesProviderKey proves that a verified Google assertion leaves local confirmation false and performs no email lookup, account/link creation or mapping mutation. Admission.cshtml explains and offers the separate SyRF verification email, covered by AuthPageMarkupTests. ReadinessCohort.IsGoogleSignInReady now measures a linked provider key plus no lockout independently of provider/local verification, with all four verification combinations, missing-link and lockout cases covered in ReadinessCommandTests. Existing resend/profile/provenance evidence remains at Services/VerificationEmailSender.cs, Pages/Account/Admission.cshtml.cs, SyRF.Identity.Migration/InvestigatorReconciler.cs and UserImporter.cs.
  • Auth0 blocked and Investigator Deactivated states remain independently preserved as indefinite native lockouts in import, verify, rerun and readiness fixtures. — SyRF.Identity.Migration/UserImporter.cs:210-213 (Auth0Blocked and InvestigatorDeactivated recorded separately in the ledger), :589-596 (either state sets LockoutEnd to DateTimeOffset.MaxValue); Commands/ReadinessCommand.cs:237-243,281-287,380-384; fixtures in UserImporterTests.cs, ReadinessCommandTests.cs, MigrationParityMatrixTests.cs.
  • External create/link/unlink/recovery tests prove recent step-up, provider-key uniqueness, non-enumerating errors, local return URLs, last-recovery protection, security notifications and session/token invalidation; email alone never links accounts. — EVIDENCED. #2902 binds five-minute step-up proof to the exact link/unlink operation (StepUpProofService.cs:94-120; StepUpRequestService.cs:58-104); the unique provider-key index is at IdentityUserIndexInitializer.cs:82-90; return URLs are local-only (ExternalLogin.cshtml.cs:184); notifications fire (IdentityEmailService.cs:88-108); and no email-driven AddLogin path exists. The earlier concurrent-last-method concern is withdrawn: the Mongo user store persists through a concurrency-stamp compare-and-swap, so the losing unlink racer fails (#2839 closed as not reproducible). #2905 (14755a8b2) closes the remaining deletion gap: administrative and self-service deletion rotate the security stamp and revoke tokens, authorizations and BFF sessions before account removal, fail closed on revocation/delete ambiguity, revoke credentials orphaned by an already-removed account, and preserve the permanent Investigator mapping tombstone.

Items still open (reconciled 2026-08-27)

These carried no checkbox before, and none is closed by this re-baseline. They are split by which gate they actually belong to: conflating them would inflate the apparent R17 gap and hide which wave the work routes to.

R17 native-parity acceptance clauses

  • Step-up evidence is not observable downstream. auth_time and amr exist only as unused OIDC constant declarations (src/libs/kernel/SyRF.SharedKernel/Constants.cs:121 declares AuthenticationTime = "auth_time", :103 declares AuthenticationMethod = "amr"); no Identity code sets either claim, and a repo-wide search finds no other occurrence. #2902 implements account/security-stamp/five-minute freshness and exact operation targeting (StepUpProofService.cs:94-120; StepUpRequestService.cs:58-104), but that proof is path-scoped and invisible to API-side or downstream consumers. This is not a re-decision. The authoritative parity gate requires the still-missing downstream claims, so the settled requirement is to issue auth_time and amr without weakening the implemented freshness contract. What is genuinely absent is an owner and an issue number, not a choice.
  • The emailed single-use sign-in link factor does not exist. Pages/Account/Manage/StepUp.cshtml.cs:97-218,341-343 implements password, passkey and MFA only; PR #2838 recorded the emailed sign-in link and its step-up plumbing (D6) as out of scope. The design pack's emailed-link waiting and landing screens therefore have no page.
  • Account deletion revokes sessions and tokens before removing the account. #2905 (14755a8b2) covers both AdminApiController.DeleteUser and AccountApiController.DeleteProfile, including orphaned credentials, fail-closed revocation/deletion errors, security-stamp rotation, BFF-session invalidation and mapping-tombstone preservation. This is the same closure recorded in the external-lifecycle exit check above.
  • The step-up proof is bound to the operation it authorises. #2902 (f8755a2e1) closed #2842 and the cross-site GET half of #2852: only antiforgery-protected link/unlink handlers issue the data-protected request, the proof is account/security-stamp/five-minute bound, and validation compares the exact link provider or unlink provider-plus-key before mutation (StepUpRequestService.cs:18-37,58-104; StepUpProofService.cs:40-46,94-120; ExternalLogins.cshtml.cs:120-132,135-227,230-305). This is operation targeting only; downstream auth_time/amr remains a separate open clause above.
  • A failed link surfaces as a 500 that also burns the step-up proof (#2835). Services/ExternalIdentityLifecycleService.cs:603,660,701 throw; Pages/Account/Manage/ExternalLogins.cshtml.cs contains no catch at all.
  • Link/unlink notifications are ad-hoc inline bodies, not ported templates, and do not carry the support address. Services/IdentityEmailService.cs:94,105 say "contact SyRF support"; helpdesk@syrf.org.uk appears only in the page footer model (Pages/Shared/AuthLayoutModels.cs:32), never in an email.
  • /connect/userinfo is a second, divergent claim producer. Controllers/AuthorizationController.cs:333-400 allocates and populates its own dictionary (:352 allocation, :359-384 population, of which only :377,381 are the namespaced SyRF claims) instead of using Services/UserClaimsService.cs:19-85, so the two projections must be changed twice or they drift.

Non-R17 tracked application work

  • claimPending has no user-facing surface. The flag reaches the Angular store (src/services/web/src/app/core/auth/auth.reducer.ts:21,86) but no template reads it. claimPending appears nowhere in the authoritative parity gate and must not hold R17/S08; it remains tracked here as later application UX work.

Environment, rehearsal and later-phase prerequisites

These are not R17 parity gaps. They block standing up the isolated rehearsal environment (S08), are performed at that rehearsal, or belong to a later phase, and are listed here only because no other checkbox in this document carried them.

  • [~] Withdrawn — "no email sink exists" was inferred from the wrong artefact. An earlier revision of this document reasoned from Services/IdentityEmailService.cs:26 (SES) and :140 (NoOpEmailService) being the only IIdentityEmailService implementations to the conclusion that no emailed-link flow could be exercised end to end. That does not follow: under the reviewed contract the sink is environment infrastructure, not an Identity service implementation. The live contract uses an external HTTPS test-mailbox adapter (slices/S07/S07-PLAN.md:105 — "obtains/reset-verifies a synthetic password through the configured test mailbox adapter"), and the harness implements exactly that: e2e/tests/auth-migration-live.helpers.ts:88-95 requires a non-local HTTPS AUTH_SMOKE_MAILBOX_ENDPOINT plus a private token file, and :293-317 snapshots the mailbox before the request, polls it for a new link, and asserts a real password-reset link is delivered and issuer-matched. S08 in turn asserts SES synthetic delivery through the existing transport (slices/S08/S08-PLAN.md:116-124). So the emailed reset flow is exercisable end to end, and treating the two service classes as proof of a missing prerequisite would invite an unnecessary mail server into the application chart. This is the same error class as the withdrawn ExternalSecret item: inferring a SyRF-side gap from an absent SyRF-side artefact when the contract places the resource elsewhere. The real work this withdrawal leaves behind is the next box.
  • The external test-mailbox resource's availability is unverified. The harness requires an operator-supplied non-local HTTPS endpoint and bearer token (AUTH_SMOKE_MAILBOX_ENDPOINT, AUTH_SMOKE_MAILBOX_TOKEN_FILE/_FDe2e/tests/auth-migration-live.helpers.ts:88-95, scripts/auth-migration/live-smoke.sh:105-111). An earlier revision of this box declared it "not provisioned"; that was not supportable and is corrected here. The only evidence offered was that AUTH_SMOKE appears nowhere in either repository — but the same box records that these values are supplied at run time rather than by a chart or GitOps manifest, so the search can only ever return nothing, whether or not a mailbox exists. It proves the absence of declarative wiring, which is the designed state, and says nothing about the resource. This is the same error class as the two withdrawn items above: inferring a SyRF-side gap from an absent SyRF-side artefact when the contract places the resource elsewhere. What is actually outstanding is an operator confirmation that a mailbox endpoint and token exist and are reachable from the rehearsal environment; until someone checks, treat availability as unknown rather than as a confirmed blocker. It remains an environment resource to be confirmed or stood up, not application code to be written. This is the live successor to the withdrawn item above; it is counted here and the withdrawn item is not.
  • The live harness has never been executed against a deployed topology. This is the whole of the R08 gap, and it is about evidence, not coverage. It is not an S08 prerequisite: slices/S07/S07-PLAN.md:108 says to run it "only after S08 deploy" and S07's done condition at :110 asks for a checked-in harness rather than live results, so S08 supplies the environment this box waits on and cannot also be gated on it. Against the S07 contract the harness is complete. slices/S07/S07-PLAN.md:101-102 names ten required flows, and all ten are asserted in the checked-in suite: (1) password sign-in — auth-migration-live.spec.ts:18,26,45; (2) Google sign-in — :35 with /api/auth/me 200 at :37; (3) emailed password reset — :44-47 end to end via the mailbox adapter; (4) /api/auth/meauth-migration-live.helpers.ts verifyAuthenticatedSurfaces asserts 200; (5) admin/account pages — both load and render the account nav, with /api/investigators 200 proving the administrator claim; (6) representative protected API — asserted < 400; (7) refresh and logout — spec.ts:21-22,38,48; (8) SignalR negotiate and re-negotiate after reload — both asserted 200; (9) cross-replica acceptance — one cookie replayed to two explicitly selected ready replicas, asserted 200/200 (scripts/auth-migration/live-smoke.sh:255-289); (10) old-cookie rejection after a generation change — asserted 401/401 (:292-307). Counted by assertion rather than by test( block — an earlier revision said "three tests", which materially understated it, and that figure was repeated downstream. What remains is running it. Every assertion above is code-resident; none has been produced against a deployed environment, which is why R08 is rather than complete and this box stays open. The harness being operator-invoked rather than wired into a workflow is not a gap: the suite carries designated account passwords, a mailbox token and live cluster access (live-smoke.sh:258,274), so operator invocation is the deliberate and correct design, and recording its absence as a blocker would create pressure to wire those credentials into an ordinary repository workflow. Flows outside the S07 contract are not counted against this gate; the full live-matrix obligation is recorded separately below in this Environment/rehearsal section.
  • [~] Withdrawn — the Identity chart is not missing an ExternalSecret template or an isolated-namespace values profile. An earlier revision of this document listed "the Identity chart has no ExternalSecret template and no isolated-namespace values profile" as a SyRF-side prerequisite. That is wrong under the reviewed contract: slices/S08/S08-PLAN.md:94-103 assigns environment values and generated credentials to cluster-gitops and External Secrets Operator, cert-manager to the Issuer/Certificates and Atlas to the database user, leaving SyRF the chart/package only — and those resources already exist (cluster-gitops@e25ca2ed plugins/local/extra-secrets-staging/values.yaml targets the staging Identity secret contracts, with resources/identity-staging-infrastructure.yaml alongside syrf/environments/staging/identity/{config,values}.yaml). Adding chart templates for them would duplicate operator-owned resources. The real work this withdrawal leaves behind is the next box, which carries it.
  • The S08 Task 1 render-and-audit has not been performed. slices/S08/S08-PLAN.md:94-103 requires both the Identity caller chart and Project Management receiver chart to be audited against the exact operator-owned cluster-gitops/ESO resources they consume — plugins/local/extra-secrets-staging/values.yaml, resources/identity-staging-infrastructure.yaml, syrf/environments/staging/identity/{config,values}.yaml, and syrf/environments/staging/project-management/values.yaml at cluster-gitops@e25ca2ed — so every key is proven to be produced. This includes the currently absent caller ProjectManagementProvisioning block targeting the Project Management origin, the receiver identityService block targeting the Identity issuer with the same Project Management audience, and the shared dedicated ESO-generated syrf-identity-provisioner Secret reference; syrf-api must not gain investigators:provision. Not started, and it is a genuine S08 blocker: without it Identity fails startup or Project Management leaves introspection disabled. This is the live successor to the withdrawn item above; it is counted here and the withdrawn item is not.
  • campaign-final has no CLI action. CampaignRunStore.CompleteRunAsync is called only from tests (SyRF.Identity.Migration.Tests/CampaignControlTests.cs), and .chart/values.yaml:167-171 records that the operation needs a new migration CLI action. The durable campaign sequence cannot be completed by catalogue entry alone. Blocks S12, not the rehearsal.
  • The declared read-only ProjectManagement contract still needs eventual-pod mount/use proof. Identity correctly fails startup outside Development without the connection (SyRF.Identity.Endpoint.Tests/IdentityConfigurationStartupTests.cs:366), but this is no longer a missing staging declaration or backing-resource gap: cluster-gitops@e25ca2ed defines the least-privilege Atlas read user on syrf_staging, its password Secret reference, and the operator-generated connection Secret consumed by staging Identity; read-only inspection found the Atlas resource Ready and the namespace-local Secret present with its expected key contract. S08 must prove the eventual pod mount/use; no value enters evidence.
  • The S30 full live matrix — eight application flows plus isolated-topology rollback beyond the S07 harness contract — has not been run. The application flows are, in the rehearsal's step 6 order, email confirmation and resend, forced password reset as an admission gate, passkeys, optional MFA, external login/link/unlink and step-up, profile completion, token claims, and API/BFF/Swagger authorization; the ninth checkpoint is rollback of the isolated Argo application/revision and synthetic resources. (actions-openiddict-mapping.md:698-700 lists twelve flows; password, sessions and SignalR are inside the S07 harness contract, while isolated rollback remains here.) This isolated rollback is distinct from S09's later Auth0 provider rollback on ordinary staging Web/API and does not become an S08 setup prerequisite. The matrix is a rehearsal obligation, not an R17 one, and an earlier revision of this document wrongly counted it against R17. The authoritative parity gate stages the work explicitly: actions-openiddict-mapping.md:674-681 makes the rehearsal serial after A–D and A2 are merged and all acceptance suites are green, and :683-700 defines the isolated synthetic rehearsal as a separate SyRF plus cluster-gitops change after the Identity-only S08 dark launch; slices/S30/S30-PLAN.md now owns its provision, live matrix and teardown. R17's own requirement is the in-repo integration/E2E suites, and each of the eight application flows has one: confirmation/resend (VerificationEmailSenderTests.cs, AdmissionPageTests.cs, RegisterModelTests.cs), forced reset (IdentityAdmissionServiceTests.cs, ResetPasswordPageTests.cs, ChangePasswordPageTests.cs), passkeys (PasskeyManagementPageTests.cs 15 tests, MongoPasskeyUserStoreTests.cs 5), MFA (StepUpPageTests.cs 26 tests, LoginWithTwoFactorPageTests.cs), external link/unlink and step-up (ExternalIdentityLifecycleServiceTests.cs 55, ExternalLoginsPageTests.cs 25, StepUpProofServiceTests.cs), profile completion (AdmissionPageTests.cs, AccountApiControllerTests.cs), token claims (UserClaimsServiceTests.cs 9 tests, AuthorizationControllerTests.cs 43 — the namespaced user_id, groups/roles, names, picture and email projections are asserted by name at AuthorizationControllerTests.cs:133,171,210,561-566), and API/BFF/Swagger authorization (OpenIddictClientSeederTests.cs:183, IdentityConfigurationStartupTests.cs, AuthPageSmokeTests.cs, plus API-side DirectBearerSchemeSelectorTests.cs and BffSessionTokenValidatorTests.cs). The rollback checkpoint instead needs the live isolated topology and is also carried by the explicit rehearsal teardown/rollback exit check below. Account claiming is deliberately not in this list. An earlier revision of this box put it here in place of token claims, which reversed a distinction the parity gate keeps consistently: bare "claims" at :116 is the Token claims row, while the separate feature is spelled "Account claiming" at :409-452 and :750-752. It also cannot be exercised at this rehearsal by construction — step 4 (:694-695) requires synthetic identities and forbids copying production/Auth0 user data, and account claiming exists only for the 2,624 pre-existing loginless Investigator records. Its R17 coverage is unaffected and stays evidenced above (AccountClaimingTests.cs, 37 tests). So the outstanding work is live execution at the rehearsal stage, not missing acceptance coverage — counting it against R17 would put a post-S08 obligation onto a pre-S08 gate. This box is a move, not a new item: it changes which gate it is filed under and changes no count. Note this is a coverage question about the live matrix only; the R17 acceptance clauses that are genuinely unmet in code remain listed above and are unaffected.

Terminology correction: the preceding historical phrase "Identity-only S08" means only that Identity receives no default application traffic. S08 now also rolls the audited Project Management receiver configuration; its #2907 gate is therefore part of S08, and S30 additionally depends on S08A.

Staging and Isolated-Rehearsal Exit Checks (S08, S08A, S30, S09, S27)

Not started. As of 2026-08-27 cluster-gitops@e25ca2ed declares immutable staging Identity 1.24.0 / SyRF 8053842 in syrf/environments/staging/identity/config.yaml with service.enabled: false. The ApplicationSet selector excludes disabled services. Read-only cluster and DNS inspection found no identity-staging Argo Application, Identity workload, Service, Ingress or DNS record; staging Web/API remain on Auth0 and no staging or production BFF value enables BffAuth. The operator-owned ProjectManagement read-only Atlas user and expected generated Secret are Ready by read-only inspection; the eventual pod mount/use proof remains part of S08. The required staging GitOps also lacks the ProjectManagementProvisioning BaseUrl/audience and dedicated syrf-identity-provisioner Secret reference required by current main, and no corresponding ESO-generated Secret exists. The required staging google-oauth Secret is absent and the external callback state remains unverified. Every box below is outstanding; repository state proves declarations, while the live checks require the authorised S08 execution and the later isolated rehearsal stages named on their individual rows.

  • S08 extends only the established SyRF Identity/Project Management chart packages and cluster-gitops staging Identity/Project Management/extra-secrets paths; cert-manager, Atlas and ExternalSecrets remain operator-owned and no Terraform or manual cloud resource is introduced. The two renders prove Identity calls the Project Management origin, Project Management introspects at the Identity issuer, both sides agree on the Project Management audience and dedicated ESO-generated syrf-identity-provisioner Secret reference, only that client receives investigators:provision, and syrf-api is neither reused nor broadened.
  • S08 is Identity-only and does not provision or require Redis: staging Web/API remain on Auth0 and no BFF session traffic is switched.
  • S08A establishes the missing Redis/Valkey dependency through focused SyRF package and cluster-gitops PRs: pinned Kubernetes-native chart/operator, separate ordinary-staging and isolated-rehearsal instances/credentials/key spaces/network policies, redacted render/live evidence, and a separately reviewed inverse while BFF remains disabled. S30 and S09 explicitly depend on S08A; Terraform/manual cloud is not a fallback. — re-verified 2026-08-27 at cluster-gitops@e25ca2ed: a case-insensitive search for redis or valkey across application YAML returns no workload/operator path, so S08A has not started.
  • Before G2, restricted evidence proves the production confidential Auth0 BFF client is the approved source for one enabled syrf-auth0-bff-production version; extra-secrets-production maps only auth0-bff/clientSecret to that record, and no value/version identifier enters GitOps or public evidence.
  • Restricted staging inventory maps every rendered remoteRef to an approved source owner and enabled version; an authorised operator confirms all exact rows before GitOps references them.
  • Google has the staging Identity callback and retains every Auth0 callback; S08 does not alter production callbacks. — Current evidence: the required staging google-oauth Secret is absent, and repository/cluster state cannot prove the external callback. The sole operator must provision/approve the credential through the established secret channel and confirm/add exactly https://identity.staging.syrf.org.uk/signin-google while retaining all Auth0 callbacks.
  • Dark launch changes no Web/API auth values.
  • Tokens A/B are generated before a GitOps rollout-nonce merge; A redeems before and B after Argo-managed replacement through another replica. No direct pod restart/delete/patch command occurs.
  • S30's actual-ingress live matrix proves trusted and untrusted X-Forwarded-Proto/X-Forwarded-Host cases and the resulting discovery, OAuth callback and emailed-link URLs; unit/host forwarding tests do not substitute for this live boundary. S08/R09 verifies the normal ingress issuer/endpoints/TLS only and cannot be declared to have run the hostile matrix.
  • Full staging live harness passes. — not started. The harness asserts all ten flows the S07 contract requires (slices/S07/S07-PLAN.md:101-102), so coverage is not the obstacle; what is missing is an environment to run it against, which is S08's job, plus operator confirmation of the external test-mailbox resource the emailed-reset assertion needs.
  • S30 follows the S08 Identity dark launch, its audited Project Management receiver prerequisite and S08A, then proves rollback by removing only its own Argo application/revision and synthetic-only resources through reviewed GitOps teardown, with the retained S08 Identity service, Auth0, staging Web/API, real users and live data unchanged; the absence of residual rehearsal Application/workload/route/DNS objects is recorded. This distinct isolated-topology rollback is a rehearsal/R10 exit, not an M005-R09 prerequisite or a substitute for S09's later Auth0 provider rollback.
  • S09 ends on a separately synced Auth0 rollback; only after approval does S27 separately merge/sync OpenIddict reapply. Revisions, timestamps, and generations are distinct.

Production Preparation and Cutover (S10–S13)

Not started, and gated behind the staging checks above. No production Identity directory exists in cluster-gitops@e25ca2ed. None of these is verifiable from this repository.

  • S10 adds the production Identity callback under its separate operator approval and still retains every Auth0 callback.
  • Restricted production inventory/source transfer is authorised and populated before Identity refs; staging-only material is not reused.
  • Production dark launch preserves direct Auth0/BFF-disabled/Auth0 administration.
  • Production uses the same two-pre-rollout-token plus GitOps-only rollout proof and isolated backup restore.
  • Exact 24-hour baseline/rollback queries exist before BFF/Auth0 rollout; the BFF/Auth0 window holds ≥99% for exactly 24 hours and records the authoritative 5xx/callback-p95 baseline for later windows.
  • S12 executes and inspects distinct Argo-managed import, verify, and import-rerun Jobs; rerun has zero writes.
  • S12 executes/inspects campaign-canary, canary-approve, bounded batch, durable stop, stopped status, resume, final, and idempotent-rerun operations; rerun has zero sends. — the campaign-final half additionally has no CLI action yet (see above).
  • S12 separately executes/inspects reconciliation and readiness; unknown/bounce/support dispositions pass, and all four denominators clear their floors: privileged 100%, active-90-day 95%, all-enabled 90%, and Google-only against the same all-enabled 90% floor (ReadinessCommand.cs:413). ReadinessThresholds still declares exactly three configurable floors (Models/ReadinessThresholds.cs:16,18-19), which the chart renders as [100, 95, 90] (.chart/values.yaml:196); the fourth denominator reuses one rather than adding another.
  • A fresh post-migration Identity backup is restored into an isolated verification database with matching aggregate counts/checksums for IdentityUsers (including RequiresPasswordReset), OpenIddict stores, MigrationLog delivery records and DataProtectionKeys.
  • No user/run/canary identifiers, credentials, secret names, or source/export paths appear in GitOps, argv, logs, diffs, or public evidence.
  • S13 changes BFF provider/authority/client/generation and IdentityService together; campaign does not run there.
  • Cutover thresholds pass or the prepared fresh-generation Auth0 inverse completes.

Observation and Cleanup (S14–S21, S23, S23A, S24, S25, S28)

Not started, and unmergeable before G4. These are plan-shape and cleanup contracts; the boxes record intent for slices that have not been executed.

  • For every 10–14-path plan, the executor freezes an exact touched-file manifest before editing and stops for a split if actual scope would exceed its declared manifest. S15 Task 1 may touch only its 12 named files; S24 must expand its broad directories to an exact under-15-file manifest before implementation.
  • Four reviews occur at/after days 7/14/21/28 and span ≥28 days; kill-switch code/config/secrets/callback/tenant remain intact through G4.
  • S15 has 12 declared paths; S23A has 7; S23 has 15; S17 has 7; S24 has 8; S25 has 5. Every plan remains at or below the 15-path blocker limit.
  • Angular tests run via pnpm exec ng test --no-watch (Vitest under Angular's builder), not direct vitest run and not Jest --runInBand.
  • Core, AppServices, ApplicationService, events, controllers, handlers, DTOs, seeds, serializers, and tests use provider-neutral names while persisted BSON remains Auth0Id.
  • NSwag runs from src/services/web; intentional generated changes use first-pass/second-pass diff comparison, not git diff --exit-code.
  • S19 ends on a separately synced prior-release rollback; S28 separately approves/syncs cleanup reapply and ends staging on cleanup before S20.
  • S20 production cleanup is intentionally unflagged and healthy before S21.
  • S21 removes only zero-consumer GitOps/Kubernetes refs and freezes a complete per-external-item S22 classification without mutating external versions/callback/tenant.

Final External Cleanup (S22)

Not started. Every item here is an authorised manual external action and is not verifiable from code by construction.

  • Final Auth0 users/config/connections/log/billing export is encrypted, checksummed, owned, and retained under policy.
  • Restricted schema-versioned classification has one row for export, every exact obsolete GCP Auth0 version, Google Auth0 callback, Auth0 apps/connections/tenant/subscription, and retained migration/BSON provenance.
  • Each GCP version is explicitly retain, disable, or destroy; destruction has explicit retention authorisation, and read-only verification matches the decision.
  • Google Auth0 callback is removed only after export/manual approval; exactly staging and production Identity callbacks remain.
  • Authorised Auth0 owner manually disables/cancels under #2442; no script/API mutates Auth0.
  • Public evidence contains classification counts/hash and receipts, not exact identifiers, values, or paths.
  • Post-action full production smoke proves no runtime dependency.

Worktree and Runnable-Command Contract

Pre-S07 commands use an inline physical */.worktrees/<name>/top-level assertion; S07 and later direct commands invoke assert-worktree.sh, while checked-in wrappers self-guard. The script compares canonical physical root/top-level/git-dir/common-dir and requires both physical .worktrees placement and linked <common-dir>/worktrees/* metadata for normal and bare layouts. Cross-repository roots are explicit. Angular tests use pnpm --dir ... exec ng test --no-watch. NSwag runs under src/services/web. Rollback/reapply are never joined in one chain.

Evidence Redaction Rules

Allowed in access-controlled application logs: aggregate counts, bounded provider/operation/outcome/environment/service tags, and user/email/subject identifiers only as separately attached Pii* structured properties. Sensitive values are forbidden in rendered messages, message templates, ordinary properties/scopes, and exception text/data. Credentials—including cookies, sessions, nonces, reset tokens/links, authorization data, connection strings, private keys, and secrets—are forbidden from logs even under Pii*. Public evidence, argv, GitOps diffs, rendered manifests, and evidence JSON remain strictly identifier- and credential-free under the unchanged evidence scanner; raw exports/OIDC bodies and sensitive secret/export/source paths remain restricted. Synthetic labels are allowed only when they cannot map to a real account.

Verdict

  • pass: M005-R01–R17 validated, every S22 classification row reconciled, manual receipts recorded, and no threshold breach remains.
  • needs-attention: OpenIddict is stable but observation, cleanup, external classification/action, or manual shutdown remains incomplete.
  • fail: provider/session/ring isolation, durable control, authoritative readiness, thresholds, rollback, secret population, redaction, or external-action ordering fails.

Any failure creates a #2466-linked gap slice; evidence is never edited to conceal a failed gate.

Verdict as at 2026-08-27: pending. The re-baseline closed no requirement. It records which halves of R01–R07 and R17 are evidenced in code and leaves G0 explicitly un-passed. The tally moved from 58 boxes, none ticked, to 73 boxes: 15 ticked with evidence and 58 outstanding — fifteen items that no box previously carried were added, all of them open. (That figure is the first round's; the current tally is stated below.)

A second reconciliation round on 2026-08-26 corrected what three of those boxes say. Two recorded blockers that do not exist were withdrawn — the concurrent-unlink race, refuted by the store's ConcurrencyStamp compare-and-swap and closed as #2839 (not reproducible) on 2026-08-26, and the "missing" Identity ExternalSecret template, which is operator-owned and already present in cluster-gitops — and the live-harness item now names its missing flow coverage rather than its deliberate operator-invoked design. The step-up-proof binding item (#2842, with the cross-site GET half of #2852, in flight as #2902) took the withdrawn race's place among the R17 acceptance clauses.

Two withdrawals, one [~] — the counting basis, stated once so the tally can be reconstructed. [~] marks a whole box that has been retired. The concurrent-unlink race was never a box of its own: it was sub-item (a) inside the external-lifecycle exit check, which stays [ ] because sub-item (b) is still unmet, so its withdrawal is recorded as bold inline text there and cannot show as a [~] without retiring a box that is still open. Round 2 therefore withdrew two claims while adding one [~] (ExternalSecret); round 3 added the second (the email sink). A reader counting [~] markers sees 1 after round 2 and 2 after round 3, which is why the tally below reads withdrawn 1 → 2 — it counts boxes, and the prose above counts claims. Both are correct on their own basis; only the basis was previously unstated.

The second round also retired the ExternalSecret item, which had worn [ ] and been counted as outstanding while the live work replacing it — the S08 Task 1 render-and-audit — carried no box at all. Two errors of opposite sign had held the outstanding count steady; separating them moved the total from 73 to 74.

A third round on 2026-08-26 reconciled the live-harness numbers, which did not add up, and corrected two boxes that drew the wrong conclusion from true facts.

Round-three tally as at 2026-08-26: 76 boxes — 15 ticked with evidence, 59 outstanding, and 2 withdrawn. Every move is named:

  • The "six of thirteen named areas" figure was wrong and is gone. No list of thirteen areas was ever defined, here or in the harness; "six covered, seven unasserted" sat above an absence list naming nine, which totals fifteen. The denominator was never derived. It is replaced by two explicit, auditable lists at different gates: the ten flows slices/S07/S07-PLAN.md:101-102 actually requires, each mapped to the assertion that covers it — all ten are asserted — and the rehearsal's eight application flows plus a ninth isolated-topology rollback checkpoint beyond that contract. The isolated rollback is distinct from S09's later Auth0 provider rollback.
  • +1 box: those nine rehearsal checkpoints now have their own [ ] under the rehearsal prerequisites. They were previously miscounted against the S07 harness gate, which does not require them; counting them there falsely left R08/S08 blocked on coverage the contract never asked for. A first correction re-filed them under the R17 acceptance clauses, and that was wrong too: actions-openiddict-mapping.md:674-681 makes green acceptance suites a precondition of the rehearsal, and :683-705 makes the full matrix the rehearsal's own step 6 after the S08 Identity dark launch, receiver audit and S08A session-store substrate, so counting them against R17 put a post-S08 obligation onto a pre-S08 gate. The eight application flows were checked for a split and do not split. Each already has an in-repo acceptance suite (named on the box), so none is an R17 coverage gap. The ninth checkpoint is isolated-topology rollback and requires the live rehearsal resources. None is exercisable end to end without a deployed topology, because the containerised local E2E stack runs no Identity service (e2e/docker-compose.yml defines mongodb, rabbitmq, mock-oidc, localstack and sqlserver only) and the live config refuses local targets by construction (e2e/playwright.auth-migration.config.ts:17-21). All eight are therefore rehearsal-stage. Neither move changes the count.
  • +1 box, and one moves to [~]: "no email sink exists" was inferred from the absence of a third IIdentityEmailService implementation, but the sink is environment infrastructure, not application code — the live contract uses an external test-mailbox adapter and S08 uses SES. Withdrawn. Confirming that mailbox resource is genuinely outstanding and takes its own [ ].
  • No box changed state for R01, but two now agree with each other. The ticked box no longer claims the reword "narrows nothing previously proven" — the first round's audit was wrong, and the tick is a fresh pass at the corrected scope. The open box no longer claims the repo-wide advisories leave R01 unsatisfied — they are real and tracked as #2907. They do not gate the Identity artifact itself, but S08's Project Management receiver rollout is now explicitly gated on that affected artifact's #2907 audit/remediation; later slices re-assess their own deployed artifacts.

Net: outstanding 58 → 59, withdrawn 1 → 2, total 74 → 76. The count rose because work that was real but uncounted now has boxes, and a claim that was false left the outstanding column. A phantom blocker on a gate document is as damaging as a missed one: it stalls the gate and teaches readers that the list is noise — and a denominator nobody can reconstruct does both at once.

A fourth reconciliation on 2026-08-27 moved the #2902 operation-target binding box from outstanding to evidenced, refreshed the disabled GitOps declaration and live Auth0/Identity observations, and split two previously implicit live obligations into explicit boxes: S30 actual-ingress forwarded-header/URL generation and the isolated-rehearsal GitOps teardown. It also moved claimPending under a non-R17 heading; that is a classification change, not a checkbox-state change. The ProjectManagement row now distinguishes its Ready Atlas/namespace-Secret backing contract from the still-outstanding eventual-pod mount/use proof.

After #2905 merged, a fifth reconciliation closed the deletion-time revocation gap in both places where the same requirement was intentionally tracked: the external-lifecycle exit check and its standalone R17 acceptance clause. The checkpoint-copy line was then consolidated into the same #2836 checkbox as the provider-confirmation behaviour; they are one focused slice, not two gaps.

Current tally as at 2026-08-27: 77 boxes — 19 ticked with evidence, 56 outstanding, and 2 withdrawn. From round four, two existing boxes moved from [ ] to [x] on #2905 (14755a8b2), and one duplicated [ ] was removed without changing the underlying #2836 obligation. #2925 then completed that single consolidated obligation without adding or removing a box.