25 KiB
Architecture Review Fix Plans — Master Tracker
Created: 2026-07-08
Source: archreview/00-OVERALL.md and domain reports 01–08
Plans: 8 implementation plans, 192 tasks (191 net of one cross-plan duplicate — see Dedupe Rulings)
Every plan follows the TDD bite-sized-task format and ships a co-located .tasks.json manifest. Execute a plan with the superpowers-extended-cc:executing-plans skill (pass the plan path); it reads/updates the .tasks.json. When a plan's status changes, update the table below — this file is the single source of truth for initiative progress.
Status Board
| Plan | Domain | Tasks | Done | Status | Findings coverage |
|---|---|---|---|---|---|
| PLAN-01 | Cluster, Host & Failover | 23 | 23 | ✅ Complete (all 23 tasks; merged to main ec47cb56) |
28 findings: 20 fixed, 3 → other plans, 3 accepted, 2 roadmap-deferred |
| PLAN-02 | Communication & S&F | 24 | 24 | ✅ Complete (all 24 tasks; merged to main) |
28 findings: 25 fixed, 1 → plan 01, 1 deferred (serializer swap), 1 won't-fix |
| PLAN-03 | Site Runtime & DCL | 26 | 26 | ✅ Complete (all 26 tasks; merged to main c7a0c922) |
28 actionable: 25 fixed, 3 deferred with rationale |
| PLAN-04 | Data & Audit Backbone | 26 | 25 | 🟨 In progress (only T26 final build+test sweep left; on branch worktree-archreview-wave2-plan04 — Batches 1–3 merged to main, Batches 4–8 + T24 unmerged) |
27 items: 22 fixed, 4 deferred, 1 won't-fix (incl. NodeB reconcile from report 08) |
| PLAN-05 | Templates, Deployment & Transport | 25 | 1 | 🟨 In progress (P0: T1) | 33 line-items: 27 fixed, 6 deferred with rationale |
| PLAN-06 | Edge Integrations | 24 | 0 | ⬜ Not started | 24 findings: 21 fixed, 3 deferred/won't-fix |
| PLAN-07 | UI, Management & Security | 33 | 3 | 🟨 In progress (P0: T1, T2, T3) | 24 items: 20 fixed, 3 partial (deferrals documented), 1 won't-fix |
| PLAN-08 | Conventions & Tests | 11 | 1 | 🟨 In progress (P0: T1) | 13 covered, 4 owned by other plans, ~20 triaged into deferred-work register |
Status values: ⬜ Not started · 🟨 In progress · ✅ Complete · ⏸ Blocked
Progress: P0 (Wave 0) complete — 10 of 192 tasks done (2026-07-08): PLAN-01 T1/T3/T4, PLAN-02 T1/T2, PLAN-05 T1, PLAN-07 T1/T2/T3, PLAN-08 T1. Merged to main (3f8e18c6). One follow-up surfaced during execution — see the active-node-crash SBR gap in the deferred registry.
Wave 1 — PLAN-02 COMPLETE (2026-07-08): all 24 tasks delivered and merged to main (ce239f6f). Store-and-Forward: active-node delivery gate (Func<bool> seam), bounded/short-circuiting retry sweep, upsert-based standby applies + anti-entropy peer resync, WAL+busy_timeout, epoch-ms due-check, ordered single-reader audit-observer channel; Notify.Send enqueue-only (deferToSweep, maxRetries:0, corrupt-payload parks not discards); gRPC channel keying + DropOldest debug-stream eviction counting + 5s application heartbeat; oldest-parked-age health field. Docker cluster rebuilt on merged image (Healthy, 5 singletons, corrected active-node semantics) and 3 sites seeded. Initiative total: 52 of 192 (sum of the Done column above).
Wave 1 — PLAN-01 COMPLETE (2026-07-08): all 23 tasks delivered on worktree branch worktree-archreview-wave1-plan01 (merged to main ec47cb56 2026-07-09; branch since removed). Beyond the P0/early-Wave-1 spine (SBR downing + two-node rig T1/T3/T4; oldest-member active-node unification via ClusterActivityEvaluator T5–T8; CentralSingletonRegistrar drains T2/T9/T10; health-report acking T11–T12), Wave 1 also landed: metrics-staleness + status-transition state machine and its dashboard surface (T13/T14), deleted-site eviction from the aggregator (T18), seed-vs-metrics-port validator + AllowSingleNodeCluster + docker stop_grace_period (T15–T17), rate-limited dead-letter warnings (T19), the down-if-alone process-exit recovery loop (T20), the failover-drill.sh SIGKILL drill (T21), and the REQ-HOST-6 + Traefik/Host doc reconciliation with unused-Akka.Hosting-package removal (T22/T23). PLAN-01 now 23 / 23. Initiative total: 30 of 192. Each task TDD + pathspec-committed; full solution builds clean (0 warnings). Three deploy-artifact sub-edits (deploy/wonder-app-vd03/ appsettings + install.ps1, Tasks 16/20/23) were deferred because that production artifact is not tracked in this repo — logged in the deferred register below.
Wave 1 — PLAN-03 COMPLETE (2026-07-09): all 26 tasks delivered and merged to main (c7a0c922). Site Runtime & DCL hardening across nine TDD batches: reconnect/backoff leak fixes and the DeployCompileValidator compile gate, DCL adapter lifecycle + alarm-filter overwrite warning (S10) + observed adapter dispose on stop (S9), site-node cert reconcile (CertStoreActor trusted-store export → DeploymentManagerActor MemberUp-driven additive push), replicated config-fetch retry (ConfigFetchRetryCount), invariant-culture conditional-trigger fallback (C6), bounded/DropOldest event-channel comment fix (C2), and the SiteRuntime/DCL requirements-doc sync. Three low-severity items deferred with rationale (see PLAN-03 coverage table). Full CentralUI/SiteRuntime/DCL suites green. Initiative total: 78 of 192 (sum of the Done column above). Two out-of-band fixes also landed post-merge: PLAN-07's SecuredWrites IBrowseService bUnit-harness gap (resolved 2026-07-09, 9049690b) — a pre-existing failure surfaced during PLAN-03's full-suite run, not a plan task.
Wave 2 — PLAN-04 IN PROGRESS (2026-07-09): 24 of 26 tasks delivered on worktree branch worktree-archreview-wave2-plan04 (Batches 1–3 fast-forwarded to main+origin; Batches 4–8 still on the branch). Site SQLite retention chain + partition-switch hardening + purger role-grants/index migrations (T1–T9), backlog KPI + SiteCalls freshness upsert + EF EnableRetryOnFailure (T10–T12), operator-identity audit on Retry/Discard relays + reconciliation composite-keyset cursor/abandoned-row audit/NodeB failover (T13–T18), time-sliced KpiSample purge + single-query NotificationOutbox KPI + SiteCalls OPTION (RECOMPILE) (T19–T21), short-first-tick purge timers + data-layer cleanups + bounded parallel notification delivery (T22, T23, T25), and the design-doc reconciliation pass (T24). Remaining: T26 (full dotnet build/dotnet test sweep). Initiative total: 103 of 192 (sum of the Done column).
Parallelization Map — Incomplete Tasks (2026-07-09)
Covers only the five plans with open tasks: PLAN-04 (2 open), PLAN-05 (24), PLAN-06 (24), PLAN-07 (30), PLAN-08 (10). PLAN-01/02/03 are complete — nothing to parallelize there. "Ready" = every blockedBy entry is already completed in the plan's .tasks.json, so the task can start now. Ready sets are recomputed below from the live manifests; re-derive after each merge.
Cross-plan serialization mutexes (a task holding one must be the ONLY task touching that surface, anywhere)
| Shared surface | Tasks that touch it (P0n = PLAN-0n) | Rule |
|---|---|---|
ManagementService/ManagementActor.cs |
P05 T13; P06 T2, T3, T11, T20, T22; P07 T5, T7, T8, T9, T10, T11, T12, T15, T16, T17, T22, T23, T28, T30, T31 | Global single-writer. At most one ManagementActor task in flight across ALL plans. PLAN-07 already serializes its 15 internally — extend the same mutex to P05 T13 and the five P06 tasks. |
EF migration model-snapshot (ConfigurationDatabase/Migrations/* + *ModelSnapshot) |
P06 T8 (ready), T19 | Never two migration tasks concurrently. Build first, never --no-build (empty-scaffold gotcha). Only P06 T8 is ready now. |
Host/Program.cs |
P06 T7; P08 T4 | Single-writer — don't run P06 T7 and P08 T4 at the same time. |
DeploymentManager/ArtifactDeploymentService.cs |
P06 T10; P07 T4 | Single-writer (P06 T10 not yet ready; P07 T4 is). |
SiteRuntime/Persistence/SiteStorageService.cs |
P06 T10; P08 T6 | Single-writer (neither ready yet). |
NotificationOutbox/NotificationOutboxActor.cs |
P06 T18, T21 | Intra-PLAN-06 (PLAN-04's delivery work is done and no longer touches it). |
CentralUI/.../Monitoring/AlarmSummary.razor |
P07 T29, T32 | Intra-PLAN-07 — serialize. |
Docs (low-risk, coordinate — pathspec-commit + last-writer reconcile): CLAUDE.md (P05 T25, P06 T18, P07 T15/T31/T33, P08 T10), Component-ManagementService.md, Component-InboundAPI.md, Component-ExternalSystemGateway.md, Component-NotificationService.md |
multiple | Not a hard blocker; do not co-edit the same file in the same wall-clock window. |
Dedupe reminder: P07 T33 is a skip — CLI.Tests is already in the slnx via P08 T1 (done). Mark it skipped-duplicate in PLAN-07-…tasks.json; do not execute it.
Ready set + concurrent lanes per plan
- PLAN-04 — T24 done (2026-07-09); only T26 (final
dotnet build/dotnet testsweep, infra up) remains. No intra-plan parallelism, no cross-plan file conflict. - PLAN-05 — ready: {2, 7, 9, 13, 14, 15, 16, 21, 22}. Lanes: (a)
BundleImporter.csmutex — T2 ↔ T14 (one at a time); (b) global ManagementActor lane — T13 (also editsFlatteningService.cs, so not concurrent with T9); (c) free-concurrent, file-disjoint — T7, T9, T15, T16, T21, T22 (Export / FlatteningService / TemplateEngine-Validation / DeploymentManager-pipeline / ScriptAnalysis / LineDiffer). Up to ~6 concurrent implementers once the two mutex lanes are accounted for. - PLAN-06 — ready: {1, 6, 7, 8, 17, 23}. Lanes: T8 is the sole migration in flight; T7 holds the
Host/Program.csmutex (coordinate with P08 T4); T1, T6, T17, T23 are free-concurrent (InboundScriptExecutor / InboundApiEndpointFilter / SmsAdapter / DelmiaNotifier docs). The ExternalSystem (T9→…), Notification (T18/T19→…) and inbound-contract (T2→T3→T4→T5) chains stay blocked. - PLAN-07 — ready non-ManagementActor (mutually concurrent, file-disjoint): {4, 6, 14, 19, 21, 26, 29 | 32} (ArtifactDeploymentService / ConfigSecretScrubber / SecuredWriteRepository / ManagementEndpoints+options / Commons Browse-Cert message records / Security LoginThrottle / CentralUI PollGate — T29 and T32 share
AlarmSummary.razor, so serialize those two). Ready ManagementActor tasks (→ the ONE global Lane M): {8, 9, 10, 12, 28, 30, 31} — start with T10 (it unblocks T11, T13, T22, T23). T33 = skip (dedupe). - PLAN-08 — ready: {2, 3, 4, 5, 7, 8, 9, 11} — each is a new per-component validator file or an isolated test/doc, so mutually parallel, with one caveat: T4 shares
Host/Program.cswith P06 T7 (sequence them). This is the initiative's single highest-parallelism slice (independent validator files). T6, T10 stay blocked.
Recommended concurrent execution
Run up to five plan-lane executor sessions concurrently (one each for PLAN-04/05/06/07/08 — PLAN-01/02/03 are done), with these coordination rules layered on top:
- One ManagementActor task at a time, initiative-wide — route P05 T13 and every ready P07 ManagementActor task ({8,9,10,12,28,30,31}) through a single serialized "Lane M"; start with P07 T10.
- One migration at a time — only P06 T8 is a ready migration; hold the snapshot until it lands.
Host/Program.csmutex — P06 T7 ⟂ P08 T4.- Inside each session, dispatch that plan's ready, file-disjoint tasks as concurrent implementers per the lanes above; use pathspec-scoped commits and verify each landed on HEAD (known repo failure mode — see Execution hygiene).
- Highest-value immediate fan-out: PLAN-08 T2/T3/T5/T7/T8/T9/T11 (independent validator + test + doc files) and PLAN-07's non-ManagementActor octet can run as many concurrent implementers today; the throughput ceiling is Lane M (ManagementActor) and the single migration lane.
P0 — Do These First (regardless of plan order)
✅ All six delivered 2026-07-08 on branch archreview-p0 (worktree). TDD throughout; full solution builds clean; touched suites green (Host, Communication 244, Security 163, Transport 127+63, IntegrationTests cluster rig). One finding surfaced — see the active-node-crash SBR gap in the deferred registry below.
- ✅ PLAN-01 Task 1 — enable the SBR downing provider (the system-wide Critical; one line + parsed-config test). Provider verified active via the live two-node rig.
- ✅ PLAN-01 Tasks 3–4 — the two-node kill-test rig (
TwoNodeClusterFixture); integration-proves Task 1 and later PLAN-02 work. Task 4 rewritten to the achievable keep-oldest guarantee (younger-crash → SBR downs it → oldest keeps singleton); oldest-crash gap registered as follow-up. - ✅ PLAN-07 Tasks 1–3 —
DisableLoginGuard+ scrubdeploy/wonder-app-vd03(Critical; unauthenticated prod surface). Guard + Host wiring committed;deploy/is gitignored so its scrub is on-disk-only (not version-controlled), effective on next redeploy. - ✅ PLAN-02 Tasks 1–2 — ClusterClient generation-suffixed names (Critical crash-loop dropping routing to all sites).
- ✅ PLAN-05 Task 1 — bundle import consumes
BaseTemplateName(Critical silent data loss). - ✅ PLAN-08 Task 1 — CLI.Tests into the slnx (protects every subsequent plan's test runs). 341 CLI tests now run at the solution level.
Cross-Plan Dependency Map
Hard-ish sequencing (execute earlier):
- PLAN-01 Tasks 5–7 → PLAN-02 Tasks 3–4. PLAN-02's standby S&F delivery gate is a
Func<bool>seam with marked swap points; it works standalone but its final wiring consumes PLAN-01'sClusterActivityEvaluator/SelfIsPrimary(singleton-host semantics). - PLAN-01 Tasks 3–4 → PLAN-02 integration proof. The two-node rig is how PLAN-02's Critical fixes get integration-verified.
- PLAN-04 Task 13 (additive
RequestedBymessage change) before any PLAN-07 sender refactors touching the same messages.
Soft seams (either order works, coordinate):
- PLAN-05 Task 14 (
IScriptArtifactChangeBus) ↔ PLAN-06 Task 3 (InboundScriptExecutor.InvalidateMethod). PLAN-06 Task 1's per-request revision check is the designed correctness fallback, so there is no hard sequencing; when both land, wire the bus to the invalidate seam. - PLAN-03's
DeployCompileValidatorshould adopt PLAN-05's compile-cache abstraction once it exists (later refactor, not a blocker). - PLAN-03 Task 8's stuck-thread watchdog pattern is reusable by PLAN-06 Task 4 (inbound orphan counter).
EF Core migrations — serialize globally. Migration-adding tasks exist in PLAN-04 (Tasks 6, 8), PLAN-06 (Tasks 8, 19), and possibly PLAN-07 (secured-write expiry). All share one model snapshot: never run two migration tasks concurrently, and always build first (never --no-build on dotnet ef migrations add — known empty-scaffold gotcha).
Shared-file merge surfaces (don't run these plans' overlapping tasks concurrently):
| File / area | Plans touching it |
|---|---|
ManagementActor.cs |
PLAN-07 (15 tasks, serialized within plan), PLAN-05 (management-path publish), PLAN-06 (method-update paths) |
deploy/wonder-app-vd03/appsettings.Central.json |
PLAN-07 (auth), PLAN-01 (Cluster/Node sections) |
NotificationOutboxActor.cs |
PLAN-04, PLAN-06 (SMS retry policy) |
StoreAndForwardService.cs / site tracking store |
PLAN-02 (primary owner), PLAN-04 Task 15 (keyset query) |
Host/Program.cs + service registration |
PLAN-01, PLAN-08 (options validation) |
SiteHealthReport (Commons record, additive fields) |
PLAN-03 Task 6, PLAN-04 health-event fields |
Transport BundleImporter.cs |
PLAN-05 (primary, serialized chain), PLAN-07 (import idempotency note) |
Dedupe Rulings (one owner per finding)
| Finding | Owner | Others must skip |
|---|---|---|
| CLI.Tests into slnx | PLAN-08 Task 1 (also retires CLAUDE.md gotcha + memory note) | PLAN-07 Task 33 — skip as duplicate; mark skipped-duplicate in its tasks.json |
DisableLogin: true artifact |
PLAN-07 Tasks 1–3 | PLAN-01 references only |
| AuditLog reconciliation NodeB failover | PLAN-04 Task 18 | PLAN-08 pointer only |
SmsConfiguration.MaxRetryCount unused |
PLAN-06 Task 18 | PLAN-08 pointer only |
Transport AreaName: null |
PLAN-05 Task 7 | PLAN-08 pointer only |
| Role-casing normalization | PLAN-07 Task 10 chain | PLAN-08 pointer only |
| Two-node failover rig | PLAN-01 Tasks 3–4 | PLAN-08 Task 8 is a documented placeholder; PLAN-02 consumes |
| Script-artifact invalidation contract | PLAN-05 Task 14 (design + publisher) | PLAN-06 implements the inbound consumer |
Recommended Execution Waves
- Wave 0 (P0 list above). Independent files; all six items can run in parallel. ~1 focused session.
- Wave 1 — stability spine. Rest of PLAN-01 (active-node semantics, drains); PLAN-02 Tasks 3–9 (standby gate, gRPC channel keying, sweep bounding); PLAN-03 Tasks 1–4 (reconnect leak, compile gate); PLAN-04 Tasks 1–5 (purge chains); PLAN-05 import chain Tasks 2–6; PLAN-06 Tasks 1–4 + 8; PLAN-07 Tasks 4–9 (deploy scoping, secret elision).
- Wave 2 — remainder. All other tasks per intra-plan
blockedByedges; migration tasks serialized globally; docs-update tasks land with their code tasks (repo rule: doc + code + tests together). - Wave 3 — closeout. PLAN-08 Task 11 (deferred-work register), each plan's doc-sweep task, then a full
dotnet build ZB.MOM.WW.ScadaBridge.slnx+dotnet test(which now includes CLI.Tests) +bash docker/deploy.sh+ the two-node drill from PLAN-01.
Execution hygiene: work in a dedicated worktree (not the primary checkout); one plan per executor session where possible; for parallel implementers use pathspec-scoped commits and verify every commit landed on HEAD afterward (both are known failure modes in this repo's history). Re-verify a finding's line numbers before editing — reports and plans cite the code as of 2026-07-08.
Deferred / Won't-Fix Registry (initiative-level)
Consolidated in PLAN-08 Task 11 → docs/plans/2026-07-08-deferred-work-register.md (triages the 23-item inventory from report 08 plus each plan's coverage-table deferrals: serializer swap, hash-chain/Parquet, aggregated live alarm stream, central cert-trust persistence, runtime script sandbox, clustered-GUID benchmark, TLS profile, Dockerfile HEALTHCHECK, etc.). Nothing from the review is silently dropped: every report finding is either a task, an ownership pointer, or a registered deferral with rationale.
Follow-up discovered during P0 execution (2026-07-08)
- Active(oldest)-node hard crash is NOT covered by two-node
keep-oldest— total central outage until restart. PLAN-01 Task 1'sdowning-provider-classfix is verified active (a hard-crashed node now transitionsUnreachable → Down → Removed, impossible under the pre-fix NoDowning default). But while building PLAN-01 Task 4's behavioral proof, the live two-node rig showed (Akka 1.5.62) thatkeep-oldestdowns the partition without the oldest: crashing the younger node is survivable (oldest keeps its singletons), but crashing the oldest node makes the younger survivor down itself (Down~8s, terminated ~10s) → the whole central cluster is lost until the crashed node restarts.down-if-alone = ondoes not help on a hard crash (the alone-oldest is dead and cannot down itself). Because ScadaBridge treats oldest = active node (PLAN-01 Task 5ClusterActivityEvaluator), this means a crash of the active central node does not fail over to the standby — the scenario operators care most about. Task 4's test was rewritten to prove the achievable guarantee (younger-crash → SBR downs it → oldest keeps singleton) and documents this asymmetry in its XML doc. Remedy is a topology/strategy decision (deferred, owner: user): e.g.keep-majority+ a 3rd/lighthouse seed node,static-quorum, or an explicit accepted-risk note. Not fixed in P0 to avoid silently changing the production SBR strategy.
Deferred during Wave 1 PLAN-01 execution (2026-07-08)
deploy/wonder-app-vd03/overlay edits not applied — that production deploy artifact is not tracked in this repo. PLAN-01 Tasks 16, 20, and 23 each specified a small edit to the on-host deployment overlay (appsettings.Central.json: addAllowSingleNodeCluster: true+ drop the phantom seed, addNodeName: central-a;install.ps1: addsc.exe failureservice-recovery actions for the down-if-alone restart contract). Only the deployment-record doc (docs/deployment/deployment-records/2026-06-27-wonder-app-vd03.md) is version-controlled; thedeploy/wonder-app-vd03/artifact directory itself is not present in the repo (production config kept out of source control). The code/validator/doc halves of all three tasks shipped and are what make the features usable (AllowSingleNodeClusterflag + validator, theIHostApplicationLifetimeprocess-exit watchdog, the oldest-member Traefik/Host doc reconciliation + TLS roadmap note). Owner: whoever maintains the on-host artifact — apply the three overlay edits out-of-band onwonder-app-vd03(see the referenced task steps); withoutNodeNameevery audit row from that deployment stamps a NULLSourceNode, and without the service-recovery actions a self-downed node will not auto-restart.
Plan deviations during Wave 1 PLAN-03 execution (2026-07-09)
- Task 15 (S6) — deploy-success reporting redesigned: readiness handshake, not swallow-only. The plan's Task 15 relied on the
Terminatedsignal beating the persistence result, so a stale persistenceSuccesscould simply be swallowed once the init-failure path had already repliedFailed. In practice the store commits FIRST (the optimisticStoreDeployedConfigAsynccompletes before the child's asyncPreStartthrows), so the swallow-only guard still reportedSuccessfor an actor that then died — the exact S6 bug. Fix as shipped makes the join deterministic:InstanceActor.PreStartnow sends a newInstanceActorInitializedreadiness message to the DeploymentManager as its final init step, and deploymentSuccessrequires both the persistence commit and that signal (InFlightDeploy.Persisted && Initialized); an actor that dies during init never signals readiness, so theTerminatedfallback fails the deployment and rolls back the optimistic state. A second, related bug was fixed at the same time: the optimisticStore…and the rollbackRemove…were independentTask.Runs with no ordering guarantee, so the persisted-row rollback is now deferred until the store commits (_initFailedPendingRowRemoval) and can never race ahead of it. New public surface:Messages/InstanceActorInitialized.cs(additive). Follow-up owner: PLAN-03 Task 26 — the design-doc updates must describe the two-signal deploy-success join + the readiness message (alongside Task 4's already-flagged synchronous-compile-gate deviation). No further code work outstanding; this is a doc-capture follow-up.
Pre-existing failure discovered during Wave 1 PLAN-03 execution — owner PLAN-07 (2026-07-09)
- ✅ RESOLVED 2026-07-09 — 3
CentralUI.Tests / SecuredWritesTestsrender tests fixed by registering a stubIBrowseServicein the bUnit harness; suite now 7/7 green. Original report retained below for history.PLAN-03's final full-solutionIBrowseServicenot registered in the page's bUnit harness (PLAN-07 / Secured Writes, NOT PLAN-03).dotnet testsurfaced 3 failures inSecuredWritesTests(Operator_SeesSubmitForm,Operator_Submit_CallsServiceWithEnteredValues,Verifier_OwnSubmission_HasApproveAndRejectDisabled), allInvalidOperationException: Cannot provide a value for property 'BrowseService' on type 'NodeBrowserDialog' — no registered service of type 'IBrowseService'. Root cause: the Secured Writes page (Components/Pages/Operations/SecuredWrites.razor) rendersComponents/Dialogs/NodeBrowserDialog.razor, which@injectsIBrowseService; the test fixture (SecuredWritesTests) registers_service/_siteRepo/_dialog/auth but not a fakeIBrowseService, so rendering the child dialog throws. Confirmed pre-existing and out of PLAN-03 scope — the failing files were last touched by commits outside the PLAN-03 session (1a7e7351page/test,9cff87fedialog), and PLAN-03 never touches Secured Writes / CentralUI. Merged tomain(c7a0c922) with this red present because it is not a regression from this branch. Owner: PLAN-07 — register a stubIBrowseServicein theSecuredWritesTestsServicescollection beforeRenderPage()(or in the shared bUnit fixture). See PLAN-07 "Post-plan known issues".