d1ade5653b
A verified audit of all ~90 plan documents (2026-08-01) found ~30 .tasks.json
trackers and several plan headers still reporting 'pending'/'draft' for work
fully merged to main. Sync them so future audits don't re-litigate closed work:
- Flip ~380 stale task statuses to completed across March/May/June/July
trackers (audit-log series, milestones M5-M10, playwright waves, stillpending,
LocalDb, ClusterClient->gRPC DoD rows, and more), each verified against
code/git evidence before flipping.
- Annotate obsolete-not-done rows: ClusterClient CLI transport (never built,
HTTP shipped), TreeView Areas/Instances pages (replaced by Topology),
template-tree drag-drop (dropped for M9 menu reorder), otopcua item C
(premise superseded by #17).
- Flip stale headers: aggregated-live-alarm + kpi-rollups 'Draft not executed'
-> Delivered 2026-07-10; otopcua cutover-scope SCOPING -> DECIDED;
scadabridge-rename -> Implemented; LocalDb phase1/2 status strings ->
merged 28ca04d7.
- Fix doc drift: T9/T10 'deferred' -> shipped as SMS (Teams dropped); waitfor
sandbox follow-up shipped; followups #52/#53/#54/#162/#207 resolved; purge
TODO closed by PendingDeploymentPurgeActor; live-gate pre-existing failures
#28/#29/#31 fixed; auto-down boot-alone residual superseded by self-first
seeds; supersession banners on keep-oldest SBR + ClusterClient-era designs;
requirements-traceability 'Pending' clarified as frozen plan-generation
status.
Deliberately left pending (genuinely open, tracked in the pending-work list):
opcua-tag-browser task 19 (live smoke), ipsen tasks 7-8 (vd03 verification),
selfform task 7 (vd03 overlay, user-held), live-gate observation 1
(external-system delete orphan bug), otopcua item A + maxDepth calibration.
195 lines
9.3 KiB
JSON
195 lines
9.3 KiB
JSON
{
|
|
"planPath": "docs/plans/2026-07-19-localdb-adoption-phase1.md",
|
|
"execution": {
|
|
"mode": "parallel-waves",
|
|
"implementerModel": "opus",
|
|
"isolation": "worktree",
|
|
"branch": "feat/localdb-phase1",
|
|
"note": "Dispatch every unblocked task concurrently (waves in the plan header). Parallel implementers MUST use worktree isolation - concurrent git in one worktree races destructively."
|
|
},
|
|
"amendments": [
|
|
{
|
|
"date": "2026-07-19",
|
|
"by": "execution review",
|
|
"change": "Task 5 split into 5a (id 5, writer) and 5b (id 15, read path). Code recon found the integer site_events.id load-bearing in the keyset cursor (EventLogQueryService:70,141,153), the storage-cap purge (EventLogPurgeService:164), and long-typed DTOs crossing the site<->central Akka boundary (EventLogEntry.Id, both ContinuationTokens) - none of which were in the original Task 5 file set. 5b is high-risk. Task 10 gains a blockedBy on 15."
|
|
},
|
|
{
|
|
"date": "2026-07-19",
|
|
"by": "execution",
|
|
"change": "Task 2 in isolation left the production writer broken (site_events.id NOT NULL, nothing minting it) - 40 tests red. Per the plan's own 'one PR-sized push' guidance, 5a's GUID minting and all of 5b landed together with Task 2 in commit 727fa48c. Task 5a remains OPEN for its other half: the ILocalDb.CreateConnection() swap, which still needs Task 3."
|
|
}
|
|
],
|
|
"prerequisites": [
|
|
{
|
|
"commit": "4715f2f9",
|
|
"what": "gRPC 2.71.0 -> 2.76.0 + Protobuf 3.34.1. Forced by LocalDb.Replication 0.1.0's nuspec floor; restore fails NU1605 below it. Directory.Packages.props had deferred this bump to 'its own reviewed commit' - honored."
|
|
},
|
|
{
|
|
"commit": "f056b67e",
|
|
"what": "AngleSharp GHSA-pgww-w46g-26qg scoped suppression in CentralUI.Tests (supersedes the wrong pin in ecf6b628). Pre-existing: the solution build was RED on clean main, making every task's verification gate meaningless. No patched-and-working version exists upstream - 1.5.x breaks bunit at runtime, and bunit 2.7.2 still resolves vulnerable 1.4.0."
|
|
}
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": 1,
|
|
"subject": "Task 1: Add LocalDb packages to the build",
|
|
"status": "completed",
|
|
"commit": "7370b331"
|
|
},
|
|
{
|
|
"id": 2,
|
|
"subject": "Task 2: Shared schema helpers (tracking + events, GUID PK)",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
1
|
|
],
|
|
"commit": "727fa48c"
|
|
},
|
|
{
|
|
"id": 3,
|
|
"subject": "Task 3: Register LocalDb in the site composition root",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
2
|
|
],
|
|
"commit": "2977e6c4",
|
|
"note": "LocalDb:Path added to 10 configs (8 site nodes incl. docker-env2 site-x, dev template, wonder prod sample) - it is REQUIRED via ValidateOnStart, so any site config missing it fails startup."
|
|
},
|
|
{
|
|
"id": 4,
|
|
"subject": "Task 4: Rewire OperationTrackingStore onto ILocalDb connections",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
3
|
|
],
|
|
"commit": "0b5e9b44",
|
|
"note": "Ctor takes ILocalDb, not IOptions<OperationTrackingOptions>; both ad-hoc reader paths switched too and their OpenAsync calls removed (CreateConnection returns an OPEN connection). Three DI fixtures needed LocalDb:Path added because resolving IOperationTrackingStore now forces ILocalDb construction."
|
|
},
|
|
{
|
|
"id": 5,
|
|
"subject": "Task 5a: Rewire SiteEventLogger onto ILocalDb + mint GUID ids",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
3
|
|
],
|
|
"commit": "727fa48c + 0d8a80aa",
|
|
"note": "GUID minting landed early with Task 2 (727fa48c) to avoid committing a writer that violated site_events.id NOT NULL. The ILocalDb.CreateConnection() swap is 0d8a80aa. connectionStringOverride DELETED, not preserved: site_events is replicated, so a raw connection lacks zb_hlc_next() and fails closed."
|
|
},
|
|
{
|
|
"id": 15,
|
|
"subject": "Task 5b: Migrate the event-log read path off integer ids",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
5
|
|
],
|
|
"commit": "727fa48c"
|
|
},
|
|
{
|
|
"id": 6,
|
|
"subject": "Task 6: One-time legacy data migrator",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
4,
|
|
5
|
|
],
|
|
"commit": "98b94771",
|
|
"note": "Runs AFTER RegisterReplicated (pinned by an oplog assertion). Deterministic mig-{NodeName}-{legacyId} event ids, not fresh GUIDs, so a crash between commit and rename cannot duplicate. DEVIATION: sync transaction on a CreateConnection() connection instead of BeginTransactionAsync - OnReady is a synchronous Action<ILocalDb>."
|
|
},
|
|
{
|
|
"id": 7,
|
|
"subject": "Task 7: Replication registration + sync endpoint (default OFF)",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
3
|
|
],
|
|
"commit": "a560e9ea",
|
|
"note": "DEVIATION: AddZbLocalDbReplication moved from Program.cs (where the plan put it) into SiteServiceRegistration, because the composition-root tests build that graph and not Program.cs - in Program.cs a missing registration would still show green. MapZbLocalDbSync stays in Program.cs (needs the WebApplication). Endpoint is UNAUTHENTICATED until Task 8; no config in this repo sets a peer."
|
|
},
|
|
{
|
|
"id": 8,
|
|
"subject": "Task 8: Bearer auth interceptor for the sync endpoint",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
7
|
|
],
|
|
"commit": "59c69519",
|
|
"note": "Fail-closed: no configured ApiKey rejects ALL sync streams. All four handler shapes gated, not just unary (the real RPC is a duplex stream). DEVIATION: Grpc.Core.Testing does not exist on grpc-dotnet; hand-rolled FakeServerCallContext instead of adding the retired native package."
|
|
},
|
|
{
|
|
"id": 9,
|
|
"subject": "Task 9: Surface ISyncStatus as site health signal",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
7
|
|
],
|
|
"commit": "59c69519",
|
|
"note": "Additive init properties on SiteHealthReport so the Akka DTO ctor is untouched. Both fields nullable: null = no data, false/0 = a real reading (which is the healthy default-OFF state). Collector stores one tuple, read once, so a torn read cannot pair fresh Connected with stale backlog."
|
|
},
|
|
{
|
|
"id": 10,
|
|
"subject": "Task 10: Two-node in-process convergence test",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
6,
|
|
8,
|
|
15
|
|
],
|
|
"commit": "3c395d79",
|
|
"note": "5 scenarios over real loopback gRPC + the real interceptor, initialized via SiteLocalDbSetup.OnReady. Verified NON-vacuous: mismatching the two ApiKeys turns all 5 red with 2m30s of timeouts."
|
|
},
|
|
{
|
|
"id": 11,
|
|
"subject": "Task 11: Docker rig enablement (site-a pair)",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
8
|
|
],
|
|
"commit": "(with Task 11 commit)",
|
|
"note": "site-a replicated, site-b/site-c deliberately not, so default-OFF is proven side-by-side on one rig."
|
|
},
|
|
{
|
|
"id": 12,
|
|
"subject": "Task 12: Live gate on the docker rig",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
9,
|
|
10,
|
|
11
|
|
],
|
|
"commit": "95c108f1",
|
|
"note": "PASS. Found one REAL defect: ZbTelemetryOptions.Meters is a silent allowlist, so localdb_* was absent from /metrics - the plan's 'flows through automatically' claim was wrong. Fixed + pinned. Evidence: identical row_version/HLC/originating node_id across the pair, 0 dead letters, oplog drained, site failover + rejoin catch-up, localdb_* scraped, site-b inert. NOTE: docker/failover-drill.sh targets CENTRAL nodes and does NOT exercise the site-pair claim; the site flip was run directly."
|
|
},
|
|
{
|
|
"id": 13,
|
|
"subject": "Task 13: Documentation truth pass",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
12
|
|
],
|
|
"commit": "(docs commit)",
|
|
"note": "Both CLAUDE.md files + the false 'starts a fresh log' XML doc + the two now-migration-only option properties."
|
|
},
|
|
{
|
|
"id": 14,
|
|
"subject": "Task 14: Phase 2 gate - plan the bespoke-replicator migration",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
12
|
|
],
|
|
"commit": "(gate commit)",
|
|
"note": "Gate document only, per the plan - Phase 2 gets its own implementation plan. docs/plans/2026-07-19-localdb-phase2-gate.md. KEY FINDING: the rig SOAK that Phase 2's oplog-cap tuning needs has NOT been run; the live gate proved correctness, not steady-state behaviour."
|
|
}
|
|
],
|
|
"knownFlakes": [
|
|
{
|
|
"test": "SiteRuntime.Tests InstanceActorChildAttributeRaceTests.ChildActors_AreSeededFromAnIsolatedCopy_NotTheLiveAttributesDictionary",
|
|
"note": "Intermittent ActorNotFoundException under full-suite load; passes in isolation and in most full runs. Pre-existing, unrelated to LocalDb. Worth its own issue."
|
|
},
|
|
{
|
|
"test": "AuditLog.Tests ParentExecutionIdCorrelationTests.InboundRoutedRun_AllRoutedRows_CarryInboundExecutionId_AsParentExecutionId",
|
|
"note": "Cold-MSSQL-fixture timing: ~91s and AwaitAssert-times-out on a cold fixture, ~1s and passes warm. Baselined against a stashed tree to confirm it is not a LocalDb regression."
|
|
}
|
|
],
|
|
"lastUpdated": "2026-07-19",
|
|
"phase1Status": "COMPLETE - all 15 tasks done, live gate PASS. MERGED to main as 28ca04d7 ('LocalDb adoption Phase 1 + 2: consolidate the site database, delete the bespoke replicators (#23)') - Phase 1 and Phase 2 landed together in that one merge."
|
|
} |