Commit Graph

1580 Commits

Author SHA1 Message Date
Joseph Doherty 66d5a67e4f feat(central-ui): Flow Debugger drill-ins from Audit Log + Execution Chain; TraceAllCalls checkbox on the method form
'Open in Flow Debugger' (data-test open-flow-debugger) beside 'View execution chain' in
AuditEventDetail (rendered only with an ExecutionId) and beside 'View this execution in
the Audit Log' on the Execution Chain page — both target
/deployment/flow-debugger?executionId=… which replays on first render. ApiMethodForm gains a
'Trace all calls (Flow Debugger)' form-check under Timeout, loaded from and saved to
ApiMethod.TraceAllCalls on both the update and create paths, with the ~7-rows-per-call
guidance as help text.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:33:37 -04:00
Joseph Doherty 254118a467 feat(central-ui): Flow Debugger page (predict, live run, replay, step detail) + nav entry
/deployment/flow-debugger (RequireDeployment): entry toggle (inbound method | value-change
trigger), method picker or Site → Instance → Attribute pickers (site-scope filtered), Live |
Replay mode, inputs form generated from the method's ParameterDefinitions via
InboundApiSchema (values coerced by declared type into the request JsonElement), status
strip (state chip, run #, start, total, steps/boundaries/writes, root ExecutionId link to
the Execution Chain page), the FlowGraphView with −/Fit/+ zoom, and FlowStepDetail (tabs
Input · Output · Writes · Timing, 'Where this ran' column, Open in Debug View when the
instance is known, Audit rows (n) → /audit/log?executionId=). State machine Idle →
Predicted → Running → Completed | Failed | TimedOut; STANDBY_NODE renders as an inline
warning alert with the server message verbatim; ?methodId= / ?instanceId=&attribute= /
?executionId= drill-ins (the last replays on first render); last entry + inputs persisted
per method in localStorage; 'site not traced' label when a run's route call came back with
no child execution and no Site-lane steps. NavMenu: Deployment → Flow Debugger.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:31:55 -04:00
Joseph Doherty ba15a38327 feat(central-ui): FlowGraphView SVG swimlane renderer
Dependency-free inline SVG: three fixed 110px lanes (Central / Site / Tags, sublabelled
from the steps' site/instance), columns from a topological pass over the graph edges with
same-lane collision bumping so independent siblings never overlap, 150x78 nodes on a
130+col*170 grid, orthogonal connectors with arrow markers (accent for boundary crossings,
dashed for external actors, light for predicted edges). Renders a real FlowGraph, a
PredictedFlow (dashed nodes, input-dependent finer-dashed), or both — predicted nodes
matched to real steps by kind in order, the leftovers appended greyed as ghosts. Selected
node = accent 2px on --active-bg; failed/timed-out/not-traced outcomes styled; each node a
role=button tabindex=0 <g> with click + Enter/Space selection; svg role=img with a summary
aria-label. Layout is a pure static ComputeLayout, every coordinate InvariantCulture.
Razor reserves the <text> tag, so SVG text elements are emitted through a small
HTML-encoding MarkupString helper.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:25:54 -04:00
Joseph Doherty f9b8a2cf8f feat(central-ui): FlowDebugService — management-command bridge + predicted graph
IFlowDebugService/FlowDebugService (scoped, registered beside IDataConnectionMoveService).
Run/replay go to the per-node ManagementActor through ManagementActorHolder with the Blazor
principal projected to AuthenticatedUser, exactly as DataConnectionMoveService does; the
FlowDebugRunResult payload is deserialised (camelCase) and fed to FlowGraphBuilder, while
ManagementError/Unauthorized/holder-null/transport faults collapse into FlowDebugOutcome
with the server's error code verbatim (STANDBY_NODE reaches the page untouched). Prediction
is DB-only: PredictMethodAsync hands FlowShapePredictor a memoised resolver that maps a
route target → Instance.UniqueName → template parent chain (nearest definition wins, cycle
guarded) → TemplateScript.Code; PredictValueChangeAsync roots at the write and appends each
script whose value-change trigger names the attribute (same attributeName/attribute keys
the site's TriggerRouting parser reads).

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:20:22 -04:00
Joseph Doherty b73d6b5e2e feat(script-analysis): FlowShapePredictor — predicted flow graph from script syntax
Pure Roslyn syntax walk (SourceCodeKind.Script, no semantic model, DB-free) that turns a
method or site script into PredictedNode/PredictedEdge chains in source order: Route.To(x)
.Call/SetAttributes/WaitForAttribute, Scripts.Call, Attributes.WriteBatchAndWaitAsync/
WaitAsync/WaitForAsync/SetAsync, Attributes[x] = v, ExternalSystem/Database/Notify. String
literals resolve; anything else is InputDependent with the expression text as title. An
if-return guard becomes a note on the node that follows it (condition visited first, so a
guard whose condition IS the traced call — Delmia's WriteBatchAndWaitAsync — attributes
correctly). Routed/nested script bodies come from a caller-supplied resolver, recursion
capped by maxDepth; parse errors and any walker fault degrade to request+response with a
'parse error' note. Predicted kinds are exactly the traced kinds so the page can overlay by
kind + title. Deviation from the plan's node count: no separate central 'method script'
node — the real graph records none, so the InboundRequest node stands for it.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:15:42 -04:00
Joseph Doherty 0fb7e16ced feat(cli): flow-debug run-method|run-value-change|replay
New top-level command group for the Flow Debugger (design 2026-09-05 §7.1). run-method /
run-value-change post the FlowDebugRun* management commands with a 2-minute client wait;
replay posts FlowDebugReplayCommand. A table projection unwraps the FlowDebugRunResult
envelope to one row per step (occurred | kind | lane | target | durationMs | status |
error) using the Commons FlowStepProjector's kind/lane resolution, with a trailing note
row for an incomplete run (error code verbatim, e.g. STANDBY_NODE) or a truncated event
list. --params must be a JSON object (INVALID_PARAMS otherwise). README section added;
command-tree test bumped to 18 groups.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:11:42 -04:00
Joseph Doherty d11dcb77c5 feat(management): FlowDebug run-method / run-value-change / replay commands (Deployer, active-node gated, inline trace)
Flow Debugger (design 2026-09-05 §7.1): FlowDebugRunMethodCommand / FlowDebugRunValueChangeCommand /
FlowDebugReplayCommand on the per-node ManagementActor (Deployer role; the two run commands
are long-running), all delegating to FlowDebugRunner. RunMethod refuses on a standby with
STANDBY_NODE (ManagementCommandException now carries an optional Code that replaces
COMMAND_FAILED), validates parameters through the real ParameterValidator/SchemaRefResolver
(made public), synthesizes the inbound row (actor = operator, Extra.source = flow-debugger)
and runs the method through InboundScriptExecutor with a trace-sink RouteHelper so the reply
carries the RouteCall row + the site's inline events. RunValueChange does a traced routed
write via IInstanceRouter, then polls IPullTraceEventsClient (interface moved to Commons so
ManagementService can consume it) until a ScriptRun parented to the root settles, else
NO_TRIGGER_WITHIN_WINDOW. Replay reads IAuditLogRepository.GetTraceEventsAsync.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-06 00:04:18 -04:00
Joseph Doherty 9300bc1a4f feat(audit): GetTraceEventsAsync returns the ScriptTrace chain for Replay
Flow Debugger (design 2026-09-05 §7.1): IAuditLogRepository.GetTraceEventsAsync walks any
entry id to its root and down the Edges/Chain CTE exactly as GetExecutionTreeAsync, then
returns the chain's ScriptTrace rows plus the ApiInbound.InboundRequest row via the canonical
row mapper, ordered (OccurredAtUtc, EventId). Test doubles implement the new member with a
NotSupported stub.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:59:08 -04:00
Joseph Doherty 981deed0fd feat(inbound-api): executor threads the trace flag; endpoint honours ApiMethod.TraceAllCalls
Flow Debugger (design 2026-09-05 §5.2): InboundScriptExecutor.ExecuteAsync gains a trailing
trace parameter that builds the script's Route helper WithTrace(trace); the inbound endpoint
passes method.TraceAllCalls so a flagged integration method is traced on every real call.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:59:08 -04:00
Joseph Doherty 3f582a278c feat(inbound-api): RouteHelper writes an always-on RouteCall audit row, propagates Trace, exposes an inline trace sink
Flow Debugger (design 2026-09-05 §5.1/§5.2/§7.1): RouteTarget.Call emits one
ScriptTrace.RouteCall central-direct-write row per routed call (ExecutionId = the inbound
parent, Extra.childExecutionId from the reply, best-effort, never throws into the script)
whether or not the run is traced. WithTrace(bool) stamps Trace on RouteToCallRequest and
RouteToSetAttributesRequest; WithTraceSink hands the row + the reply's inline site events to a
callback so a Live run renders without touching the store. DI supplies ICentralAuditWriter /
INodeIdentityProvider as optional ctor args; new RouteHelper(locator, router) still compiles.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:59:08 -04:00
Joseph Doherty 7e61552d82 feat(wire): attribute updates carry source_execution_id/trace; PullTraceEvents RPC + site read-by-execution-ids
Flow Debugger (design 2026-09-05 §5.3/§5.4): AttributeValueUpdate fields 7/8 stamped by
StreamRelayActor and parsed by SiteStreamGrpcClient (older frames => null/false). New
PullTraceEvents unary RPC on SiteStreamService (same PSK-gated prefix as PullAuditEvents):
a point read of the site audit SQLite by ExecutionId OR ParentExecutionId
(ISiteAuditQueue.ReadByExecutionIdsAsync, json_extract on the codec's camelCase keys, new
IX_audit_event_occurred index, 64-id / 2000-row caps) that moves no cursor and flips no
ForwardState. Central IPullTraceEventsClient/GrpcPullTraceEventsClient mirror the audit pull
client (endpoint resolution, NodeB failover, fault => empty) and register beside it.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:52:50 -04:00
Joseph Doherty f15d0db0ad feat(wire): route_to_call trace flag + child_execution_id + trace_events; route_to_set_attributes trace (additive proto + mapper)
Flow Debugger (design 2026-09-05 §5.2/§5.4): site_command.proto gains trace=7 on
RouteToCallRequestDto, child_execution_id=6 + repeated sitestream.AuditEventDto trace_events=7
on RouteToCallResponseDto, trace=6 on RouteToSetAttributesRequestDto (imports
Protos/sitestream.proto the way central_control.proto does). Mapper round-trips them; an
empty trace_events list maps to null so an untraced/older site reads as 'not traced'.
Vendored C# regenerated via docker/regen-proto.sh (csproj untouched); golden samples extended.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:52:50 -04:00
Joseph Doherty 2c86adb782 feat(site-runtime): route handlers forward Trace and return child ExecutionId + trace events
Flow Debugger (design 2026-09-05 §5.2/§5.4): RouteInboundApiCall passes Trace on the
ScriptCallRequest and maps ScriptCallResult.ExecutionId/TraceEvents onto
RouteToCallResponse; RouteInboundApiSetAttributes passes Trace on SetStaticAttributeCommand.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:43:33 -04:00
Joseph Doherty 01be39019e feat(site-runtime): InstanceActor trace arm stamps the device echo of a traced write; Trace rides on published changes
Flow Debugger (design 2026-09-05 §5.3): a traced SetStaticAttributeCommand on a data-sourced
attribute records (attribute → writer, expiry) for TraceArmWindowSeconds (new option,
default 30, validated > 0); the next DCL echo for that attribute is published with the
writer's SourceExecutionId + Trace whatever its value, and the arm is consumed once. Static
writes publish Trace directly. Untraced writes never arm (pinned).

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:43:33 -04:00
Joseph Doherty eca1434160 feat(site-runtime): traced runs emit ScriptRun rows, return ExecutionId + trace events; traced value-change runs inherit the parent
Flow Debugger (design 2026-09-05 §5.1/§5.4): ScriptRunLauncher emits one ScriptRun row per
traced run on every exit path (OccurredAtUtc = run start), replies with the run's
ExecutionId (always) and the collector snapshot (traced only). ScriptActor forwards
ScriptCallRequest.Trace and, for a value-change trigger fired by a traced stamped change,
parents the run to the writer with trigger detail in Extra; every other trigger stays a root.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:43:33 -04:00
Joseph Doherty 93489cd7a2 feat(site-runtime): ScriptRuntimeContext emits ScriptTrace AttributeWrite/AttributeWait rows and inherits the Trace flag
Flow Debugger (design 2026-09-05 §5.1/§5.2): the context is the single funnel for
SetAttribute / WriteBatchAndWait / WaitInternal, so it emits the write/wait rows (collector
first, then the best-effort IAuditWriter whose redactor applies the payload cap), stamps
Trace on SetStaticAttributeCommand and ScriptCallRequest, and hands the flag + sink to the
inline CallShared child context. Untraced contexts emit nothing (pinned).

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:43:32 -04:00
Joseph Doherty 23157d2bcf feat(inbound-api): ApiMethod.TraceAllCalls (entity, migration, mgmt command, CLI --trace-all-calls)
Flow Debugger (design 2026-09-05 §5.2): bit NOT NULL DEFAULT 0 column + EF migration
20260906034113 with its docs/plans/sql production twin. CreateApiMethodCommand gains
TraceAllCalls=false; UpdateApiMethodCommand gains bool? TraceAllCalls (null = unchanged so an
older CLI never resets it). Management result echoes the flag; CLI create/update expose
--trace-all-calls; README carries the ~7-rows-per-call warning.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:42:52 -04:00
Joseph Doherty febb268d89 feat(site-runtime): FlowTraceCollector
Per-run inline sink for ScriptTrace audit events (Flow Debugger design 2026-09-05 §5.4):
cap 500, Truncated flag, lock-based; the audit rows stay complete beyond the cap.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:42:39 -04:00
Joseph Doherty ec7b5b2ea3 feat(commons): FlowStep/FlowGraph projection + FlowGraphBuilder for the Flow Debugger
Central-side projection of ScriptTrace/ApiInbound audit events (design §7.2): lanes
Central/Site/Tags, in-execution chaining, spawner→child edges (RouteCall by
childExecutionId), inferred write→satisfied-wait echo edge, synthesized Response node.
ScriptTraceExtra is the camelCase Extra payload shared by every emitter.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:41:50 -04:00
Joseph Doherty 24e595d6d9 feat(commons): additive Trace flag + ChildExecutionId/TraceEvents on route/call/write/change records
Flow Debugger (design 2026-09-05 §5.2/§5.4): Trace rides beside ParentExecutionId on
RouteToCallRequest/RouteToSetAttributesRequest/ScriptCallRequest/SetStaticAttributeCommand/
AttributeValueChanged; RouteToCallResponse and ScriptCallResult carry the site run's
ExecutionId and its ScriptTrace AuditEvents inline. All trailing optional — zero call-site edits.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:41:50 -04:00
Joseph Doherty 352e0bfa37 feat(audit): ScriptTrace channel + RouteCall/ScriptRun/AttributeWrite/AttributeWait kinds
Flow Debugger (design 2026-09-05 §5.1): trace steps are audit rows. New
AuditChannel.ScriptTrace appended after Cluster and four kinds appended after
ManualFailover so stored ordinals never move; lock-in tests + alog.md §4 updated.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:41:50 -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 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 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 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 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 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 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 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 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 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 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 546250096d fix(centralui): grouped bindings review fixes — Apply with the header at none/mixed keeps each member's connection (never unbinds; upsert Save could not persist it), object inference runs over effective references (bare-default members make an overridden group mixed, default-only prefixes infer), bindings to a deleted connection count as unbound, 'n changed · unsaved' diffs against last-saved state; bUnit behaviour tests for fallback banner, grouping and mixed-group Apply+Save 2026-08-20 16:09:34 -04:00
Joseph Doherty 77f4af0d64 feat(centralui): grouped Connection Bindings panel — rows sourced from the flattened config (composed module members were invisible; flatten failure falls back to direct attributes with a banner), per-module connection + Object apply, unbound/mixed pills, show-only-unbound filter, Test Bindings covers composed rows, per-row Browse retained 2026-08-20 15:59:51 -04:00
Joseph Doherty 02a374259b feat(cli): instance bind-module — rebind one composed module's members to a new object path via GetInstance → SetConnectionBindings upsert; --dry-run 2026-08-20 15:55:09 -04:00
Joseph Doherty 2fb1c8ee14 feat(centralui): BindingGroupBuilder.ApplyObject — per-module bulk rewrite to <object>.<member>, blank clears 2026-08-20 15:54:47 -04:00
Joseph Doherty 5da7003e88 feat(centralui): BindingGroupBuilder — group flattened binding rows by module, infer the shared MxAccess object prefix, count unbound 2026-08-20 15:53:59 -04:00