diff --git a/docker/README.md b/docker/README.md index 6531856d..dff7d0fd 100644 --- a/docker/README.md +++ b/docker/README.md @@ -99,7 +99,7 @@ docker/ ├── seed-live-probe.sh # Seed one deployable live-data instance on site-a (stream traffic) ├── seed-delmia-sim.sh # Seed the in-memory Delmia machine stand-in (DelmiaSim01) on site-a ├── seed-delmia-api.sh # Seed the DelmiaRecipeDownload inbound method + a scoped API key (token printed once) -├── verify-delmia-notifier.sh # DelmiaNotifier end-to-end vs the rig: standby relay, active-central crash/takeover, rejoin +├── verify-delmia-notifier.sh # DelmiaNotifier end-to-end vs the rig: standby relay, active-central crash/takeover, rejoin; step H = Flow Debugger live run + replay (CLI flow-debug) ├── teardown.sh # Stop and remove containers ├── central-node-a/ │ ├── appsettings.Central.json # Central node A configuration diff --git a/docker/central-node-a/appsettings.Central.json b/docker/central-node-a/appsettings.Central.json index b96c4ee3..b144142a 100644 --- a/docker/central-node-a/appsettings.Central.json +++ b/docker/central-node-a/appsettings.Central.json @@ -104,5 +104,10 @@ "Logging": { "MinimumLevel": "Information" } + }, + "AuditLog": { + "PerChannelRetentionDays": { + "ScriptTrace": 30 + } } -} \ No newline at end of file +} diff --git a/docker/central-node-b/appsettings.Central.json b/docker/central-node-b/appsettings.Central.json index 85da9d39..dc0522f8 100644 --- a/docker/central-node-b/appsettings.Central.json +++ b/docker/central-node-b/appsettings.Central.json @@ -104,5 +104,10 @@ "Logging": { "MinimumLevel": "Information" } + }, + "AuditLog": { + "PerChannelRetentionDays": { + "ScriptTrace": 30 + } } -} \ No newline at end of file +} diff --git a/docker/verify-delmia-notifier.sh b/docker/verify-delmia-notifier.sh index cc656780..6d21ec72 100755 --- a/docker/verify-delmia-notifier.sh +++ b/docker/verify-delmia-notifier.sh @@ -29,6 +29,8 @@ set -euo pipefail # E active KILLED, after survivor /health/active=200 -> YES (first URL refused -> failover) # F victim restarted and rejoined (memberCount=2) -> YES # G reject path (DownloadPath *.bad) -> NO + "Simulated reject" +# H Flow Debugger live run + replay -> YES (flow-debug run-method returns the traced +# site steps inline; flow-debug replay rebuilds the same chain from the central audit store) : "${DELMIA_API_KEY:?export DELMIA_API_KEY=sbk_... first}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" @@ -91,4 +93,42 @@ echo " victim ready + memberCount=2 on survivor after $(( $(date +%s) - T1 )) notify F YES echo "== G reject path"; notify G NO -d 'C:\recipes\x.bad' || true + +# H — Flow Debugger (design 2026-09-05). A Live run executes the real method on the active node +# with tracing on, so the site's ScriptRun / AttributeWrite / AttributeWait rows come back inline +# in the same reply; Replay reads the same chain back from the central AuditLog, which the site +# feeds through the 5 s telemetry push — hence the bounded retry. Both must show the full kind set. +echo "== H Flow Debugger live run + replay" +ACT=$(active) +H_ARGS=(--url "$(url_of "$ACT")" --username "${SCADABRIDGE_USERNAME:-multi-role}" --password "${SCADABRIDGE_PASSWORD:-password}" --format json) +h_check() { # stdin = FlowDebugRunResult JSON; prints a one-line verdict, exits 1 when incomplete or kinds missing + python3 -c ' +import json, sys +r = json.load(sys.stdin) +kinds = sorted(json.loads(e["detailsJson"]).get("kind", "?") for e in r.get("events", [])) +need = ["AttributeWait", "AttributeWrite", "InboundRequest", "RouteCall", "ScriptRun"] +missing = [k for k in need if k not in kinds] +ok = r.get("completed") is True and not missing +print(("YES" if ok else "NO") + " root=" + str(r.get("rootExecutionId")) + " events=" + str(len(kinds)) + " kinds=" + ",".join(kinds) + + (" missing=" + ",".join(missing) if missing else "") + (" error=" + str(r.get("errorCode")) if r.get("errorCode") else "")) +sys.exit(0 if ok else 1)' +} +METHOD_ID=$("$CLI" api-method list "${H_ARGS[@]}" | python3 -c 'import json,sys; ms=json.load(sys.stdin); print(next(m["id"] for m in ms if m["name"]=="DelmiaRecipeDownload"))') +H_PARAMS=$(python3 -c 'import json,sys; print(json.dumps({"MachineCode":sys.argv[1],"DownloadPath":"C:\\recipes\\wo1.nc","WorkOrderNumber":"WO-H","PartNumber":"PN-1","JobStepNumber":"0100","Username":"drill"}))' "$MACHINE") +set +e +H_RUN=$("$CLI" flow-debug run-method --method-id "$METHOD_ID" --params "$H_PARAMS" "${H_ARGS[@]}") +H_RC=$?; set -e +[ "$H_RC" -eq 0 ] || { echo "FAIL: flow-debug run-method exited $H_RC: $H_RUN"; exit 1; } +H_LIVE=$(echo "$H_RUN" | h_check) || { echo "FAIL: live run incomplete: $H_LIVE"; exit 1; } +echo " live: $H_LIVE" +H_ROOT=$(echo "$H_RUN" | python3 -c 'import json,sys; print(json.load(sys.stdin)["rootExecutionId"])') +H_REPLAY="" +for i in $(seq 1 20); do + set +e; H_REPLAY=$("$CLI" flow-debug replay --execution-id "$H_ROOT" "${H_ARGS[@]}" | h_check); H_RRC=$?; set -e + [ "$H_RRC" -eq 0 ] && break + sleep 2 +done +[ "${H_RRC:-1}" -eq 0 ] || { echo "FAIL: replay never showed the full chain after 40 s: $H_REPLAY"; exit 1; } +echo " replay: $H_REPLAY" +echo "H live+replay -> YES" echo "== done. Notifier log: $RUN_DIR/delmia-notifier.log"