Commit Graph

13 Commits

Author SHA1 Message Date
Joseph Doherty 7621b48925 docs(known-issues): cached-telemetry drain hot-loops on a missing tracking snapshot
An audit row whose tracking snapshot cannot be resolved is skipped and left
Pending, on the reasoning that central reconciliation will pick it up. Nothing
removes it from the local drain queue, so the next tick re-reads it, fails
identically, and logs again — forever. Measured on the rig at ~2,800
warnings/minute, sustained across both a process restart and a container
restart, until the audit database itself was discarded.

The code comment already names the cause ("the tracking store was reset"), so
this is an anticipated input, not an exotic one. Two production triggers need
no operator error: the tracking retention window elapsing before the drain
catches up (long central outage, large backlog), or restoring one store
independently of the other. The two stores are easy to desync because they live
in different places — audit in auditlog.db, which on the docker rig is INSIDE
the container, and tracking in the bind-mounted LocalDb database.

Skipping the row is right; leaving it Pending with no other state change is
not. There is no attempt counter, no backoff, no terminal state, and one
Warning per row per pass. Suggested fixes ranked by effort, the cheapest being
to rate-limit the warning the way MaintenanceBackgroundService already does for
oplog caps.

No data loss — the audit rows are intact and still reach central by the
reconciliation path. Found while cleaning the rig after the Phase 2 live gate.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-20 05:59:35 -04:00
Joseph Doherty 7b5a5a6f34 docs(localdb): phase 2 truth pass across both repos
Normative first: Component-StoreAndForward.md:83 specified the whole chunked,
ack-confirmed SfBufferSnapshotChunk resync protocol, which Phase 2 deleted. It
is rewritten rather than removed — the failure modes it reasoned about still
exist, they are just bounded differently — and it now states the
duplicate-delivery bound explicitly: a message can be delivered twice only when
the OLD primary delivered it and the status change had not yet replicated when
the gate flipped. One flush interval plus the in-flight ack, and unlike the old
model it does NOT grow with backlog depth or with how long a node was absent.
The N1 directional-authority and N5 orphan-row hazards are recorded as
structurally gone, not merely unguarded.

Frame-size known-issue amended: its 2026-06-26 resolution replaced the
intra-site hop with notify-and-fetch; Phase 2 then deleted notify-and-fetch
itself, so the 128 KB Akka frame constraint no longer applies to that hop in
any form. Successor ceiling recorded (4 MB gRPC cap via MaxBatchSize, which
batches by ROW COUNT), including that the failure mode differs — an oversized
gRPC message is rejected, not silently dropped.

Deployment docs gain the two operational constraints that have no home in code:
a site pair must be stopped and started TOGETHER (the SfBufferSnapshot compat
handler that made a mixed-version pair converge went with the replicator, and a
mixed pair now diverges silently), and a node offline beyond TombstoneRetention
can resurrect deleted rows on rejoin.

Both CLAUDE.md files corrected — each still said Phase 2 was NOT started.

Definition of done closed: build 0 warnings, all 10 suites green (3509 tests,
0 failures, 0 skips). Two DoD items needed amending rather than ticking: the
stale-symbol grep still matches 4 lines, all deliberate comment prose recording
what was deleted (a literal zero would delete the explanations that stop the
old design coming back), and the live gate has 10 evidence items, not the 9 the
checklist claimed.

Deletes the phase2 resume-state scratch doc, which said to delete it on landing.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-20 05:37:48 -04:00
Joseph Doherty 8652eab98e fix(localdb): root-cause the soak disk-I/O failure + ship the hardening follow-ups
The Phase 2 soak's "LocalDb fails under load" blocker is NOT a product defect.
Root cause: host-side (macOS) sqlite3 reads of the live, bind-mounted WAL
databases. POSIX advisory locks do not propagate across the virtiofs boundary,
so the host reader believes it is the only connection, checkpoints on close and
resets the WAL to 0 bytes under the container. The container's still-mapped
WAL index then references frames that no longer exist and every subsequent
statement fails SQLITE_IOERR_SHORT_READ (522) -> primary code 10, permanently
until the process reopens the database.

Reproduced on demand both on the rig (one sqlite3 SELECT reset a 4.6 MiB WAL and
produced the first error one second later) and in a minimal python:3.12-alpine
repro with no LocalDb or .NET involved. Refuted the converse: a freshly-reopened
node sustained the full soak load 10+ minutes with zero errors.

The original brief's isolation was confounded - both nodes had been poisoned by
the same sampling pass, and a poisoned standby looks healthy only because it
issues almost no statements. Corrections annotated in place.

Hardening shipped alongside:
- SqliteErrorCodes.Describe: log SQLite primary AND extended codes at the
  LocalDb-adjacent catch sites (the missing extended code is what made the
  original diagnosis so slow).
- SiteAuditTelemetryActor: stop touching ActorContext across an await
  (NotSupportedException), with regression coverage.
- infra/reseed.sh: apply the MSSQL init scripts explicitly. The official
  mssql/server image does not implement /docker-entrypoint-initdb.d, so a fresh
  volume hung the reseed forever; compose mounts annotated as informational.

Deliberately NOT done: detect-and-reopen self-heal in SqliteLocalDb. It defends
only against external interference, which is now prevented at the source, and
same-kernel production readers see the locks correctly.

Build 0 warnings; SiteAuditTelemetryActorTests 9/9, SiteEventLogging 70/70.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-20 02:01:00 -04:00
Joseph Doherty e9e11d635e docs(known-issues): root-cause brief for the LocalDb disk-I/O-error-under-load defect
Standalone handoff for a follow-up investigation. Records the symptom, a full
reproduction (including the two rig-tooling blockers and the CachedCall-vs-Call
detail needed to generate load at all), the isolation evidence, a code map of
the LocalDb connection model, four ranked hypotheses, and acceptance criteria.

Not root-caused. Highest-value next step identified: capture the SQLite EXTENDED
result code (logs only carry the generic primary code 10), and run the load with
LocalDb:Path off the bind mount to partition environmental vs library causes.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-20 01:11:08 -04:00
Joseph Doherty d9f5fbb664 docs(known-issues): mark deploy-config frame-size bug RESOLVED via notify-and-fetch
The 128 KB Akka frame-size deploy trap is fixed and merged. Record the
resolution at the top of the writeup (notify-and-fetch on both the
central->site deploy hop and the site active->standby replication hop;
AskTimeout classification fix; startup reconciliation + topology perf fix),
link the design + plan docs, and note the live-smoke validation. The
diagnosis is retained as historical record.
2026-06-26 23:09:24 -04:00
Joseph Doherty 8a78e759c0 docs: former-api-specs (MES + DNC/Delmia) + inbound compile-error known issue
- former-api-specs/mes: Alarm-API, MoveIn-MoveOut-API, API-key authgaps (from ~/Desktop/mesapi)
- former-api-specs/dnc: Delmia-Integration-API — Delmia document service + WW recipe-download notify (from ~/Desktop/delmiaintegration)
- known-issues: inbound API compile error not client-visible; no api-method validate
2026-06-26 04:13:19 -04:00
Joseph Doherty 1f261263b2 fix(centralui): surface inherited compositions in the templates tree (followup #9)
The templates tree rendered a derived/composed member (e.g. LeftReactorSide,
derived from ReactorSide) as a flat leaf, omitting compositions it inherits
from its base (e.g. LeakTest composed onto ReactorSide). BuildCompositionLeavesFor
recursed only over a template's OWN composition rows; an inherited composition
row lives on the ancestor, and TemplateComposition has no IsInherited placeholder
(unlike attributes/alarms/scripts/native-sources), so the child's own Compositions
was empty. Same 'derived templates don't surface inherited members' family as
followups #1/#2, but for compositions. Deploy/flatten was always correct
(TemplateResolver.ResolveAllMembers walks the chain) — display-only.

Fix:
- BuildCompositionLeavesFor now renders the EFFECTIVE composition set (own +
  inherited) via EffectiveCompositionsFor, which walks the inheritance chain
  (leaf->root, child wins on InstanceName), mirroring the resolver.
- Inherited slots are flagged (TemplateTreeNode.IsInherited), badged 'inherited'
  in the label, and their context menu offers only 'Open composed template'
  (Rename/Delete edit the ancestor's slot, so suppressed on inherited nodes).
- The same inherited row can appear under several derived members (LeakTest under
  both LeftReactorSide and RightReactorSide), so composition nodes use a
  path-qualified KeyOverride to keep TreeView selection/expansion keys unique;
  recursion is cycle-guarded.

Tests: +1 bUnit (TemplatesPageTests.Renders_InheritedComposition_UnderDerivedComposedMember);
CentralUI suite 867 green; full solution builds 0/0.

Docs: Component-CentralUI.md (effective composition set in tree); known-issues
tracker #9 recorded + resolved.

Note: CentralUI change — shows on wonder-app-vd03 only after that host is redeployed.
2026-06-24 19:29:48 -04:00
Joseph Doherty 7747f25c9e docs(known-issues): mark central-report singleton-hang fix as committed (was stale 'pending commit')
The HOST-021 fix (AkkaHostedService.GetOrCreateActorSystem + AddSingleton<ActorSystem>
in Program.cs/SiteServiceRegistration.cs) is committed and live on main; the tracker
still read 'pending commit' / 'Fix (pending, task #48)'. Status corrected.
2026-06-24 19:05:42 -04:00
Joseph Doherty cdd65beb6c feat(cli+templateengine+deploymanager): resolve follow-ups #4/#5/#6/#8 — CLI ergonomics + structured deploy validation error
Closes the four remaining items in the 2026-06-24 template-inheritance/CLI
follow-up tracker.

#4 — CLI `instance set-bindings` can now set DataSourceReferenceOverride.
  `--bindings` accepts an optional 3rd element per entry:
  [attributeName, dataConnectionId, dataSourceReferenceOverride]. A string
  sets the override; a JSON null or an omitted 3rd element leaves it unset
  (template default). TryParseBindings accepts 2- or 3-element entries and
  rejects a non-string/non-null 3rd element or 4+ elements with a clean
  error. Previously the CLI sent the override as null and silently wiped any
  existing one (only a raw POST /management could set it).

#5 — `template update` is partial, not full-replace (fixed server-side so all
  clients benefit). UpdateTemplateAsync now uses leave-unchanged semantics:
  a null description keeps the stored value (pass "" to clear); a null
  parentTemplateId keeps the existing parent. Parent stays immutable — a
  non-null differing value is still rejected — but omitting --parent-id is
  now a no-op instead of failing every derived-template update.

#6 — compact `template list`/`get` table output + `--detail`. Table output is
  now id/name/description/parent/derived + member counts (#attrs/#alarms/
  #scripts/#comps/#nativeAlarms) via TemplateTableProjection, fed through a
  new optional tableProjector seam on CommandHelpers. `--detail` restores the
  full dump. JSON output is left untouched (always full) so machine consumers
  are unaffected — the projector only runs on the table path.

#8 — structured deploy-time validation error. New ValidationResult.SummarizeErrors()
  (Commons) returns a grouped, capped summary: leading total count, one line
  per ValidationCategory, and a per-module rollup (canonical name up to its
  last dot) with counts + "... and N more module(s)" caps. DeploymentService
  uses it for the "Pre-deployment validation failed" message and logs the full
  per-entry list via LogWarning. Replaces the flat semicolon-joined dump that
  became a wall of text for instances with 50-194 unbound attributes.

Tests: +8 Commons (SummarizeErrors), +8 CLI (4 binding 3-element / 4 table
projection), +2 net TemplateEngine (partial-update). Affected suites green:
Commons 587, CLI 341, TemplateEngine 447, DeploymentManager 101,
ManagementService 230, CentralUI 866; full solution builds 0/0.

Docs: Component-DeploymentManager.md "Validation Error Reporting"; CLI README
(set-bindings 3-element form, template update leave-unchanged, list/get
--detail); UpdateTemplateCommand doc; known-issues tracker #4/#5/#6/#8 resolved
(all 8 items now closed).
2026-06-24 18:27:42 -04:00
Joseph Doherty 2b5949320c feat(templateengine+centralui): resolve follow-ups #1/#2 — inherited-member propagation & resync
Derived templates store IsInherited placeholder rows mirroring inherited
members, but a base member added/changed/removed AFTER a child was derived
never reached the child — leaving the editor's editable tabs incomplete (#1)
and stored rows drifted from the resolved set (#2).

Fix (one order-independent reconcile, two entry points):
- Auto-propagation: every attribute/alarm/script add/update/delete now
  reconciles the template's derived subtree (TemplateService.ReconcileDescendantsAsync),
  hooked into all member-mutating paths incl. native-alarm-source CRUD in the
  ManagementActor.
- Resync: ResyncInheritedMembersAsync repairs a template + its subtree on
  demand — materialize missing placeholders, re-sync drifted ones, remove
  orphans, across attributes/alarms/scripts/native sources. Exposed as
  management ResyncInheritedMembersCommand (Designer-gated, audited) → CLI
  `template resync-members` → a Resync button on the editor's staleness banner.

Reconcile drives off TemplateInheritanceResolver (same precedence + HiLo merge
as deploy), only ever touches IsInherited placeholders (never an authored
override), and matches the staleness comparison keys so the banner clears.
BuildDerivedTemplate now also materializes native-source placeholders at
compose time (previously omitted → any inherited native source was perpetually
stale).

Tests: +8 TemplateServiceTests (materialize / drift-update / orphan-remove /
override-untouched / base-cascade / multi-type / direct-propagate / end-to-end
add) + 1 ManagementService test fix (native-source add resolves TemplateService).
Affected suites green: TemplateEngine 446, ManagementService 230, CentralUI 866,
CLI 333, Transport 127, ConfigurationDatabase 307; full solution builds 0/0.

Docs: Component-TemplateEngine.md "Inherited-Member Propagation & Resync";
CLI README `template resync-members`; known-issues tracker #1/#2 resolved.
2026-06-24 15:51:26 -04:00
Joseph Doherty b3f6833b36 fix(templateengine+centralui): resolve follow-ups #3 (derived-template collisions) and #7 (sandbox batch/wait surface)
#3 — CollisionDetector counted a derived template's IsInherited placeholder
rows as a distinct origin from the parent members the inheritance walk
re-adds, reporting a spurious "Naming collision" for every inherited row and
blocking any attribute/composition add to a derived template. CollectDirectMembers
now skips IsInherited rows on the direct-template and inherited-parent walks;
it keeps them for the composed-module walk, where placeholders are the sole
representation of a derived module's inherited members (that walk does not
climb the composed template's parent chain).

#7 — SandboxAttributeAccessor (Central UI Test-Run host) omitted
WriteBatchAndWaitAsync / WaitAsync / WaitForAsync, so the editor false-flagged
valid instance scripts with CS1061 even though `template validate` and the
deploy gate accept them. Added the five overloads mirroring the runtime
AttributeAccessor; they throw a labelled ScriptSandboxException if run in
Test Run (the central sandbox has no device-batch / event-waiter transport).

Tests: +3 CollisionDetector unit + 1 end-to-end TemplateService (derived add
now succeeds); +2 ScriptAnalysisService diagnose-clean. Each new test verified
to fail without its fix with the exact user-facing symptom. Full suites green
(TemplateEngine.Tests 438, CentralUI.Tests 866).

Docs: Component-TemplateEngine.md (inherited-placeholder collision rule),
Component-ScriptAnalysis.md (third sandbox surface + its compile-clean guard),
known-issues tracker #3/#7 marked resolved and the minor note promoted to #8.
2026-06-24 15:03:27 -04:00
Joseph Doherty 1a647cf1c4 docs(known-issues): track template-inheritance UI gaps + CLI/validation footguns
Records 7 issues found during the 2026-06-24 CvdReactor live-ops session:
- template editor omits inherited-but-unmaterialized base attrs (MoveInType etc.)
- derived templates carry incomplete/stale IsInherited row sets
- collision detector blocks adding attrs/compositions to derived templates
- CLI instance set-bindings can't set DataSourceReferenceOverride
- CLI template update full-replaces description (nulls it if omitted)
- CLI template list/get table dumps every attribute
- Central UI script editor false-flags WriteBatchAndWaitAsync/WaitAsync/WaitForAsync
  (sandbox compile surface out of sync with runtime + deploy gate)
2026-06-24 12:16:38 -04:00
Joseph Doherty d33617d65d fix(host): register ActorSystem as DI singleton so health-probe scopes don't dispose it (HOST-021)
Per-probe health-check child scopes were disposing the AddTransient-bridged
ActorSystem (IDisposable), terminating the live cluster node ~4s after boot and
leaving every singleton-proxy Ask to hang the full 30s QueryTimeout — the central
report pages (/notifications, /site-calls, /monitoring/health) loaded in ~30s.
Bridge it as a singleton via a new lazy AkkaHostedService.GetOrCreateActorSystem()
so child-scope disposal never touches it. Verified: 0 post-startup terminates,
healthy active/standby, report pages ~0.05s, Playwright 68 passed / 0 failed.
2026-06-05 08:26:09 -04:00