Commit Graph

2663 Commits

Author SHA1 Message Date
Joseph Doherty f47ceb55b1 docs: inbound API key store — shared SQL Server provider on central pairs (spec, runbook + central-pairs procedure, DBA DDL copy, topology, env inventory, CLAUDE.md, register row 108 closed with rig proof, row 109 Secrets backoff follow-up, Delmia spec §8)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 23:31:21 -04:00
Joseph Doherty a83cc8613d chore(docker): both rigs' centrals on the shared SQL Server inbound key store; delete sync-inbound-api-keys.sh (superseded); verify-delmia-notifier.sh preflight is now the proof that both nodes see a key minted on one — drill green with no file copy, takeover 28 s
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 23:31:21 -04:00
Joseph Doherty d6fd8c400c feat(host): StartupValidator warns when a clustered central keeps per-node inbound API keys; rejects an unknown ApiKeyStore:Provider at boot; Program.cs logs CollectWarnings once Serilog is up
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 23:26:34 -04:00
Joseph Doherty 06be0b9e64 feat(host): inbound API key store provider — SqlServer shares one zbauth row set across the central pair; one-shot SQLite import at boot (file renamed .imported, one retry on a concurrent-peer PK race, failure never blocks boot)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 23:26:03 -04:00
Joseph Doherty afe59a5518 chore(host): pin ZB.MOM.WW.Auth 0.3.0 + ApiKeys.SqlServer; InboundApiKeyStoreOptions (Provider Sqlite|SqlServer, ReadProvider fails the boot on an unknown value)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 22:46:11 -04:00
Joseph Doherty 08405c1ca1 chore(gitignore): fix worktree ignore typo (.claire/ -> .claude/worktrees/)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 14:02:35 -04:00
Joseph Doherty 081e57f166 docs: Delmia rig stand-in + notifier verification results (A-G incl. active-central kill/takeover 27 s, rejoin); register rows 107 (prod DelmiaRecv path) + 108 (per-node inbound API key store breaks central failover); CLAUDE.md bullet; plan + task file
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:55:22 -04:00
Joseph Doherty f4363e0b4b fix(delmia-notifier)+chore(docker): review follow-ups — trim the env key like the file key, log which source supplied the key (never the key), hoist the not-configured message; README warns never to commit a filled-in appsettings.json; design-doc body lines updated; seed-delmia-api.sh no longer hides CLI errors and notes key names are not unique
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:52:56 -04:00
Joseph Doherty 83f7bbfb50 test(docker): verify-delmia-notifier.sh — notifier end-to-end on the rig incl. standby relay, active-central kill/takeover and rejoin; sync-inbound-api-keys.sh mirrors the per-node key store
First run (docker stop) found two things: SIGTERM is a graceful CoordinatedShutdown handover (takeover 0 s), so the drill now uses docker kill; and inbound API keys are a PER-NODE SQLite store (/app/data/inbound-api-keys.sqlite on each central's own volume, written by whichever node serves the management call), so the key minted through the active node did not exist on the survivor and every post-takeover call answered 401 KeyNotFound. The sync script is a rig-only workaround; the verify script's preflight refuses to start unless both nodes list the key. Second run: A/B/E/F YES, C/G NO, D 503 in the takeover window, takeover 27 s, victim rejoined as standby.

Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:52:56 -04:00
Joseph Doherty 27eb598987 chore(docker): seed-delmia-api.sh — DelmiaRecipeDownload inbound method (prod script) + scoped delmia-notifier API key
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:43:21 -04:00
Joseph Doherty 5466cab920 chore(docker): seed-delmia-sim.sh — in-memory SimMachine + composed DelmiaReceiver stand-in on site-a with a ValueChange simulator playing the Galaxy side
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:43:21 -04:00
Joseph Doherty 8adb09a083 feat(delmia-notifier): optional ScadaBridge:ApiKey in appsettings.json as a fallback to SCADABRIDGE_API_KEY (env still wins); README/env inventory/design row updated
The 2026-06-26 design kept the key env-only. The file is now an explicit fallback: SCADABRIDGE_API_KEY (non-blank) -> ScadaBridge:ApiKey (non-blank, trimmed) -> not configured. Env-only stays the recommended production posture; the failure message names both sources.

Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:43:21 -04:00
Joseph Doherty 7dc30085be docs(api-specs): capture the as-deployed DelmiaRecipeDownload contract from prod vd03 (method, site handshake, keys, observed calls); link from the legacy dnc spec
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
2026-09-02 12:43:21 -04:00
Joseph Doherty afb5180ce0 docs+test(site-runtime): Lever 1 final-review polish — document that the Good reset does not release an in-flight request and that the guard is inert once interval ≥ ceiling; FlappingGood asserts the retry landed during the burst instead of timing Thread.Sleep; NativeAlarmActor (same unguarded amplifier) + cancel-race residue recorded as follow-ups
Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 15:10:21 -04:00
Joseph Doherty c52f14afca docs(plans): Lever 1 live gate PASS on the docker rig (one request per 45 s ceiling, backoff 5→9.4→20.4 s, Good within ~95 s of restore); plan tasks 1-8 completed
Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 15:06:22 -04:00
Joseph Doherty 0589486f4e fix(site-runtime): ConnectionQualityChanged(Good) only resets the tag-subscribe backoff while it is stretched — a flapping connection can no longer defer the retry it was meant to hasten
Review finding on ab08ecd3: each Good re-armed the watchdog from 'now', so Goods arriving faster than the floor starved the retry. Pinned by FlappingGood_DoesNotDeferTheRetry (12 Goods over 1.2 s; naive re-arm measured 1.43 s, guarded ~0.3 s).

Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 14:57:30 -04:00
Joseph Doherty 150c3f60d8 docs: tag-subscribe in-flight guard + backoff (Lever 1) — spec, CLAUDE.md, design-doc status 2026-08-28 14:53:23 -04:00
Joseph Doherty ab08ecd3d3 feat(site-runtime): ConnectionQualityChanged(Good) resets tag-subscribe backoff to the floor (Lever 1); lock in superseded-reply drop
Task 4 review fix-ups: RepeatedFailures gaps[1] lower bound 600→500 (two compounding jitter draws bottom out at 512 ms); SubscribeToDcl also clears _tagPathToAttributes and its prune is documented as defensive (PreStart is the only caller).
2026-08-28 14:50:04 -04:00
Joseph Doherty b6b401c879 feat(site-runtime): tag-subscribe failure reply frees the in-flight slot, backs off (×2 ±20%, capped) and Success resets to floor (Lever 1)
Also from the Task 3 review: FailedSubscribeResponse_IsRetried guards the failure path again (large ceiling); HandleSubscribeTagsResponse reads state instead of get-or-create; SubscribeToDcl prunes stale retry state on re-entry; doc comments refreshed.

Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 14:36:22 -04:00
Joseph Doherty 029d39fca2 feat(site-runtime): tag-subscribe in-flight guard — one outstanding request per connection until TagSubscribeInFlightCeilingMs (Lever 1)
FailedSubscribeResponse_IsRetried and LostSubscribeResponse_IsRetried_AndSuccessStopsRetrying now pin the in-flight ceiling to the retry interval: a lost reply is re-sent only once the outstanding request is presumed lost, by design.

Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 14:24:35 -04:00
Joseph Doherty 2f471dd7a1 feat(site-runtime): pure NextTagSubscribeInterval backoff step (double, ±20% jitter, clamp) 2026-08-28 14:20:19 -04:00
Joseph Doherty 6f7c6ad877 feat(site-runtime): TagSubscribeRetryMaxIntervalMs + TagSubscribeInFlightCeilingMs options (Lever 1, validated) 2026-08-28 14:19:50 -04:00
Joseph Doherty e39cca093b docs(plans): Lever 1 implementation plan — tag-subscribe in-flight guard + exponential backoff in InstanceActor (8 tasks, TDD)
Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 14:18:01 -04:00
Joseph Doherty dce52ad087 docs(plans): MxGateway subscribe-storm prevention design — reviewed against both repos; Lever 3 re-scoped to the gateway's per-session command_timeout (no per-invoke override exists), da0bfe2b attributed to the ScadaBridge DCL, line refs refreshed
Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 14:13:02 -04:00
Joseph Doherty a9b23556eb feat(inbound-api): standby central node relays POST /api/* to the active peer instead of 503
A caller addressing a standby central node directly (or hitting Traefik inside its
/health/active probe window after a failover) used to get 503 STANDBY_NODE. The
standby now relays the request verbatim to the active peer over HTTP
(IStandbyRequestForwarder / HttpStandbyRequestForwarder) and replays the response;
method scripts and Route.To() still execute only on the active node.

- ScadaBridge:InboundApi:StandbyForwardPeerEndpoints (static list of the OTHER
  central node's HTTP base URL; empty = old 503 behaviour) + StandbyForwardTimeout
  (60s), both startup-validated.
- Peers tried in order: peer 503 stamped X-ScadaBridge-Standby, connection failure
  or timeout → next peer; all fail → 503 ACTIVE_NODE_UNREACHABLE. A 503 without
  the marker is the active node's own answer and is replayed.
- One-hop loop guard via X-ScadaBridge-Forwarded; 413 body cap now runs BEFORE
  the active-node check so a standby never buffers/relays an oversized body.
- AuditWriteMiddleware skips its own ApiInbound row for a relayed request so a
  call is audited once, on the node that executed it.
- Rig + env2 central nodes configured; spec (Component-InboundAPI.md → Standby
  forwarding), Component-Host.md options row, CLAUDE.md updated.
- 20 new tests (filter decision + relay mechanics + validator).

Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
2026-08-28 13:34:20 -04:00
Joseph Doherty f07ba5cff5 docs(deployment): QA vq03/vq04 + prod vd03 upgraded to afb27e77 (template picker) — binary-only, no migrations; all six health endpoints Healthy
Claude-Session: https://claude.ai/code/session_01Rs5tk49eMrNcwmi8FAohHX
2026-08-21 08:34:49 -04:00
Joseph Doherty afb27e7780 docs: CLAUDE.md — template picker on Create Instance (RevealKeys, ?templateId, Deployer-gated New Instance…) + the Single-mode SelectedKey/SelectedKeys trap
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 08:24:05 -04:00
Joseph Doherty 7a77d38abf test(playwright): key the Configure bulk-Apply click by data-test (strict-mode collision with the grouped 'Apply to n' button)
The grouped Connection Bindings panel (77f4af0d) added a per-group
'Apply to n' button, so InstanceConfigureTests' Name = "Apply" role
locator started matching two elements and failing Playwright's strict
mode. Adds data-test="binding-bulk-apply" alongside the existing
binding-bulk-select / binding-group-apply keys and clicks that instead.

Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 08:22:03 -04:00
Joseph Doherty d6a31f1cd0 docs(plans): template picker — live rig gate PASS (New Instance…, ?templateId prefill, picker reveal/search/cancel, Disable verb) + task status
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 08:07:22 -04:00
Joseph Doherty 4bb77cf0f7 docs(centralui): template picker on Create Instance, New Instance… from the Templates tree
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 08:01:37 -04:00
Joseph Doherty 1790f67ebd test(centralui): pin Topology Disable/Delete confirm verbs (register row 28)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 07:45:46 -04:00
Joseph Doherty 7c2582d105 feat(centralui): Templates tree context menu — "New Instance…" (Deployer-gated)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 07:44:15 -04:00
Joseph Doherty 5b4a27b23e feat(centralui): Create Instance picks its template from the folder tree (+ ?templateId preselect)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 07:42:14 -04:00
Joseph Doherty 4bd2651925 feat(centralui): TemplatePickerDialog — folder-tree template picker hosted by IDialogService
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 07:08:37 -04:00
Joseph Doherty 8df6bcb6e0 feat(centralui): TemplateFolderTree.RevealKeys — expand to a preselected node
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:55:03 -04:00
Joseph Doherty 9261fa09fa feat(centralui): TemplatePathFormatter — folder-path label for template pickers
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:53:40 -04:00
Joseph Doherty 22468c1548 docs(plans): template picker design + 8-task plan — Create Instance picks from the Templates folder tree (TemplatePickerDialog, ?templateId preselect), Templates tree gains Deployer-gated 'New Instance…', Topology Disable confirm verb pinned (already fixed by 0db253b5)
Claude-Session: https://claude.ai/code/session_01Rs5tk49eMrNcwmi8FAohHX
2026-08-21 06:51:27 -04:00
Joseph Doherty a3e2df325c docs(deployment): QA vq03/vq04 + prod vd03 upgraded to 691a2f2f (create-time bindings) — binary-only, no migrations; healthy; outgoing-process stop-timeout FTL noted
Claude-Session: https://claude.ai/code/session_01Rs5tk49eMrNcwmi8FAohHX
2026-08-21 06:36:24 -04:00
Joseph Doherty 691a2f2f80 docs(plans): create-time bindings — live rig gate PASS (Object-node pick, prefill, 30/30 bound, deploy-gate A/B) + task status
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:20:50 -04:00
Joseph Doherty a26edffd4c docs: create-time connection bindings (Central UI, DeploymentManager preview flatten, CLI cross-ref)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:14:55 -04:00
Joseph Doherty 80b9ab2d57 feat(centralui): Create saves bindings (upsert) and lands on Configure with a created/bound/bindError notice
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:12:53 -04:00
Joseph Doherty 74c7bd2fa8 feat(centralui): Create Instance — Connection Bindings card (preview flatten, per-group connection/Object/Browse, Bind-all prefill)
Tasks 4 and 5 of the plan land together: the card's state fields are write-only
until the markup consumes them, and the project treats unused-field warnings as
errors, so neither task builds on its own.

Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:11:53 -04:00
Joseph Doherty ab6e3432bd feat(centralui): NodeBrowserDialog.SelectableNodeClasses — opt-in Object node selection (default unchanged)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:07:12 -04:00
Joseph Doherty 8d17af37ac feat(centralui): CreateBindingPlanner — prefill/tail-strip/binding-list logic for create-time bindings
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:05:41 -04:00
Joseph Doherty 6257c9e20e feat(deploymentmanager): IFlatteningPipeline.PreviewAsync — flatten a template+site without an instance row (create-page bindings)
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
2026-08-21 06:04:13 -04:00
Joseph Doherty a7301bb638 docs(plans): create-time connection bindings — design (inline card on Create Instance, object-node Browse, land on Configure) + 9-task plan
Claude-Session: https://claude.ai/code/session_01Rs5tk49eMrNcwmi8FAohHX
2026-08-21 05:59:00 -04:00
Joseph Doherty 8beee522a3 docs(deployment): prod vd03 hotfix da0bfe2b (binary-only swap via WinRM, app.bak.20260820T170309) — healthy, boot-time advise retries match the a24fa38e boot
Claude-Session: https://claude.ai/code/session_01Rs5tk49eMrNcwmi8FAohHX
2026-08-20 17:08:27 -04:00
Joseph Doherty 50f7439e33 docs(deployment): QA hotfix da0bfe2b — MxGateway graceful-stream-end disconnect; incident root cause (dashboard session close 15:29:03) 2026-08-20 16:57:45 -04:00
Joseph Doherty da0bfe2be1 fix(dcl): MxGateway adapter treats a graceful end of the event stream as a disconnect — a dashboard 'close session' (or graceful gateway shutdown) completes the stream with OK, leaving the adapter Connected on a dead session: values frozen at last GOOD quality, every write 'Session … was not found' until restart (QA vq03 Z28061 MoveIn, 2026-08-20). Pinned: signals once while connected, silent for superseded loops / own teardown 2026-08-20 16:51:13 -04:00
Joseph Doherty f5a74db3de docs(deployment): record prod upgrade of wonder-app-vd03 to a24fa38e (via WinRM from vq04) — 6 EF migrations, AuditLog:SiteWriter path, System32 auditlog.db relocated; Z28061 270 rows / 8 groups 2026-08-20 16:40:00 -04:00