amsd-pipeline-shared 0.5.23 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -244,3 +244,4 @@ d4992178558476af4c4c228e6aa63b2d scripts/lib/mint-and-spec.sh
244
244
  fac1b26443bb52ba45ba072f4d654920 scripts/lib/story-guards.sh
245
245
  ada237373615a26d924226184ed7614b scripts/lib/prd-change-review.sh
246
246
  c2dfd2381db0242fdbc487a948fd7feb scripts/lib/agent-si.js
247
+ 03bde875977ab741c54c3b38a236a496 scripts/pre-run-reset.sh
@@ -165,6 +165,7 @@ scripts/lib/content-retry.js
165
165
  scripts/lib/control-plane.sh
166
166
  scripts/lib/cost-emitter.js
167
167
  scripts/lib/cost-record.sh
168
+ scripts/lib/dashboard-target.sh
168
169
  scripts/lib/doctor/agent.js
169
170
  scripts/lib/doctor/classify.js
170
171
  scripts/lib/doctor/cli.js
@@ -254,6 +255,7 @@ scripts/lib/transcript-tool-calls.js
254
255
  scripts/lib/worktree-rungs.sh
255
256
  scripts/llm-handler.sh
256
257
  scripts/mint-agents-step.js
258
+ scripts/pre-run-reset.sh
257
259
  scripts/preflight-check.sh
258
260
  scripts/preflight-prd-integrity.sh
259
261
  scripts/preflight-static.sh
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bash
2
+ # THE DASHBOARDS SERVE A RUN — pre-run-reset.sh's Step 1, lifted into a function so every pipeline points them the same way (operator,
3
+ # 2026-10-22: launch checklist item 12, "Dashboards all up and running, controlled by eleventy", in scope for the Claude workflows).
4
+ # The lines below are pre-run-reset.sh's Step 1 as released (2.0.80), unchanged: the active-PRD and active-output-dir pointers, the compose
5
+ # override mounting the run's PRD directory at /prd-dir and its LOG_DIR at /logs-dir, nginx's alias, and agent-monitor recreated.
6
+ # point_dashboards_at_run <prd_file> <log_dir>
7
+ # What pre-run-reset.sh sets before its Step 1 is taken from the caller when set, else derived from this install as pre-run-reset.sh derives it.
8
+ point_dashboards_at_run() {
9
+ local PRD_FILE="${1:?prd file}" LOG_DIR="${2:?log dir}"
10
+ local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ local REPO_ROOT="${REPO_ROOT:-$(cd "$_here/../../.." && pwd)}"
12
+ local PRD_PRESENT=1; [ -f "$PRD_FILE" ] || PRD_PRESENT=0
13
+ if [ -z "${OBS_PROJECT:-}" ]; then
14
+ local _identity_lib="$REPO_ROOT/orchestrations-installer/lib/isolated-compose-identity.sh"
15
+ [ -f "$_identity_lib" ] && . "$_identity_lib" || { echo "[dashboards] missing $_identity_lib — cannot resolve the observability project name" >&2; return 1; }
16
+ local OBS_PROJECT; OBS_PROJECT="$(isolated_project_name "$REPO_ROOT" obs)"
17
+ fi
18
+ local _STATE_FILE="${PIPELINE_SERVICES_STATE_FILE:-$REPO_ROOT/.pipeline-services-state.env}"
19
+ if [ -z "${OBS_DASHBOARD_PORT:-}" ] && [ -f "$_STATE_FILE" ]; then set -a; . "$_STATE_FILE"; set +a; fi
20
+ local COMPOSE_BASE="${COMPOSE_BASE:-$REPO_ROOT/docker-compose.observability.yml}"
21
+ local COMPOSE_OVERRIDE="${COMPOSE_OVERRIDE:-$REPO_ROOT/docker-compose.observability.override.yml}"
22
+ local DASHBOARD_STATE_DIR="${DASHBOARD_STATE_DIR:-$REPO_ROOT/orchestrations/dashboards}"
23
+ declare -F info >/dev/null 2>&1 || info() { echo "[dashboards] $*" >&2; }
24
+ declare -F success >/dev/null 2>&1 || success() { echo "[dashboards] ✓ $*" >&2; }
25
+ # ── Step 1: Wire agent-monitor to serve the live PRD ─────────────────────────
26
+ # Generate a compose override that mounts the PRD's PARENT DIRECTORY (not the
27
+ # file itself) at /prd-dir inside the nginx container, and patch nginx.conf's
28
+ # alias to the correct basename. nginx.conf routes GET /prd.json to that path.
29
+ #
30
+ # Directory mount, not file mount — deliberately. A single-FILE bind mount is
31
+ # fixed to the inode present at mount time; since the orchestration pipeline
32
+ # always writes the PRD via atomic tmp+rename (jq ... > tmp && mv tmp file /
33
+ # fs.renameSync — correct, corruption-safe, used throughout this codebase),
34
+ # every write creates a NEW inode at that path, silently orphaning a file
35
+ # mount the moment the PRD is rewritten after this script runs (found live
36
+ # 2026-07-13: the dashboard had been serving a frozen, stale PRD snapshot
37
+ # indefinitely). A directory mount doesn't have this problem — renames INSIDE
38
+ # the mounted directory are visible immediately, no container restart needed
39
+ # for subsequent PRD writes during the run.
40
+ # THE LOGS MOUNT DOES NOT DEPEND ON THE PRD. This whole block used to be skipped when the PRD did
41
+ # not exist yet, taking /logs-dir down with it — but LOG_DIR is this run's own log directory and is
42
+ # knowable with or without a PRD. The consequence was a chain: a fresh install has no prd.json (it
43
+ # is REGENERATED by ingest, and no longer committed — see .gitignore), so the dashboard mount was
44
+ # skipped, so nginx had no /logs-dir, so pre-flight's own reachability check failed
45
+ # ("nginx /logs/healing-events.jsonl not reachable") and refused the launch — on an install where
46
+ # nothing was actually wrong.
47
+ #
48
+ # /prd-dir is the only part that genuinely needs the file, so that is the only part now conditional.
49
+ # THE PRD'S DIRECTORY IS KNOWABLE EVEN WHEN THE FILE IS NOT THERE YET, and mounting it is the whole
50
+ # reason this is a DIRECTORY mount rather than a file mount: a file appearing inside a mounted
51
+ # directory is visible immediately, with no container restart.
52
+ #
53
+ # Conditioning the /prd-dir mount on the file already existing was half a fix. On a fresh install
54
+ # the PRD does not exist when this runs — ingest writes it minutes LATER — so the mount was skipped,
55
+ # nothing re-mounted afterwards, and nginx served 404 for /prd.json for the whole run. The dashboard
56
+ # showed "data offline" against a perfectly healthy run (live 2026-09-04, pipeline-tests-13).
57
+ PRD_DIR="$(dirname "$PRD_FILE")"
58
+ PRD_BASENAME="$(basename "$PRD_FILE")"
59
+ # The directory itself must exist to be mounted; docker would otherwise create it as root.
60
+ mkdir -p "$PRD_DIR" 2>/dev/null || true
61
+
62
+ if [ "$PRD_PRESENT" = "0" ]; then
63
+ info "Configuring agent-monitor to serve $PRD_BASENAME (written by ingest during this run) and this run's logs..."
64
+ else
65
+ info "Configuring agent-monitor to serve: $(basename "$PRD_FILE")..."
66
+
67
+ # Ensure the PRD file is world-readable so nginx (non-root) can serve it
68
+ chmod o+r "$PRD_FILE"
69
+
70
+ # build/snapshot.js (the Eleventy watcher — a separate Node process, no access
71
+ # to the Docker mounts above) needs its own way to know which PRD is active.
72
+ # It reads this pointer file directly; falls back to the old dead
73
+ # orchestrations/prd.json symlink when absent so an unrun/older setup doesn't
74
+ # break. Truncate-write (>), not atomic rename — this file itself must never
75
+ # be subject to the inode-staleness bug it exists to work around.
76
+ echo -n "$PRD_FILE" > "$DASHBOARD_STATE_DIR/.active-prd-path"
77
+
78
+ # Same pointer-file pattern for the project output directory: snapshot.js
79
+ # (the Eleventy watcher) reads EPAM_PROJECT_OUTPUT_DIR from its process env,
80
+ # but since pre-run-reset.sh is invoked as a subprocess (not sourced), the
81
+ # export above does not propagate to the parent shell or to the Eleventy
82
+ # watcher started later by run-agent-orchestration.sh. Writing the path here
83
+ # lets snapshot.js read it without requiring the env var in the process chain.
84
+ echo -n "$LOG_DIR" > "$DASHBOARD_STATE_DIR/.active-output-dir"
85
+ fi # end: the PRD-only half (pointer files, chmod, nginx alias below)
86
+
87
+ # /logs-dir gets the same directory-mount treatment as /prd-dir, and for the
88
+ # identical reason: step-status.json/agent-status.json/agent-activity.jsonl/
89
+ # phase-cost.jsonl are all rewritten via atomic tmp+rename or repeated append
90
+ # during a run, and for external-project runs LOG_DIR is a directory the
91
+ # dashboard has otherwise never been told about at all.
92
+ #
93
+ # WRITTEN WHETHER OR NOT A PRD EXISTS YET — the /prd-dir line is the only part that needs one.
94
+ {
95
+ echo "services:"
96
+ echo " agent-monitor:"
97
+ echo " volumes:"
98
+ echo " - ${PRD_DIR}:/prd-dir:ro"
99
+ echo " - ${LOG_DIR}:/logs-dir:ro"
100
+ } > "$COMPOSE_OVERRIDE"
101
+
102
+ # nginx.conf's /prd.json alias basename must match whatever PRD was passed —
103
+ # patch it in place (truncate-write via sed -i, not atomic rename, so this
104
+ # single-file mount for nginx.conf itself is not subject to the same bug).
105
+ # Pointed whether or not the file exists yet: the basename is known from --prd, and the alias must
106
+ # already be correct when ingest writes the file into the mounted directory mid-run.
107
+ sed -i "s|alias /prd-dir/[^;]*;|alias /prd-dir/${PRD_BASENAME};|" \
108
+ "$REPO_ROOT/orchestrations/dashboards/nginx.conf"
109
+
110
+ # --force-recreate, not just `up -d`: nginx.conf itself is STILL a single-
111
+ # FILE bind mount (the sed -i just above rewrites it via a new inode, same as
112
+ # every other atomic-write case this session). Plain `up -d` only recreates a
113
+ # container when the COMPOSE CONFIG changed — it does not notice an existing
114
+ # bind-mounted file's inode changing underneath it, so without
115
+ # --force-recreate the container keeps serving the nginx.conf it started
116
+ # with, silently ignoring this run's PRD-basename patch (found live
117
+ # 2026-07-13 verifying this exact fix — even the fix's own docs file wasn't
118
+ # safe from the bug it documents).
119
+ # THE COMPOSE THAT MATCHES THE INSTALL, resolved once.
120
+ #
121
+ # This shelled out to `docker compose` literally. A podman install therefore never remounted
122
+ # agent-monitor, so the run's own log dir was never served and the pre-flight gate
123
+ # "nginx /logs/healing-events.jsonl not reachable" failed on every podman run -- after the
124
+ # installer's health check had already reported the box "ready", because that check probes host
125
+ # ports and cannot see a mount that was never made.
126
+ #
127
+ # container_compose() already resolves the runtime and knows podman needs PODMAN_COMPOSE_PROVIDER
128
+ # and a real runtime dir, and the installer lib ships inside every install. Sourced, never
129
+ # reimplemented: a second copy of the resolution is how this drifted from install.sh in the first
130
+ # place.
131
+ _obs_runtime_lib() {
132
+ local _c
133
+ for _c in "${INSTALLER_LIB:-}" \
134
+ "$(dirname "${BASH_SOURCE[0]}")/../../orchestrations-installer/lib/container-runtime.sh" \
135
+ "${REPO_ROOT:-}/orchestrations-installer/lib/container-runtime.sh"; do
136
+ [ -n "$_c" ] && [ -f "$_c" ] && { printf '%s' "$_c"; return 0; }
137
+ done
138
+ return 1
139
+ }
140
+ _obs_runtime_name() {
141
+ local _l; _l="$(_obs_runtime_lib)" || { printf 'none'; return 1; }
142
+ # shellcheck disable=SC1090
143
+ . "$_l" 2>/dev/null || { printf 'none'; return 1; }
144
+ container_runtime 2>/dev/null || printf 'none'
145
+ }
146
+ _obs_have_runtime() {
147
+ local _n; _n="$(_obs_runtime_name)"
148
+ [ -n "$_n" ] && [ "$_n" != "none" ]
149
+ }
150
+ _obs_compose() {
151
+ local _l; _l="$(_obs_runtime_lib)" || return 1
152
+ # shellcheck disable=SC1090
153
+ . "$_l" 2>/dev/null || return 1
154
+ container_compose "$@"
155
+ }
156
+
157
+ # THE DASHBOARD RESTART IS THE ONLY SLOW STEP, AND A TEST DOES NOT NEED IT.
158
+ #
159
+ # `docker compose up --force-recreate` costs ~3 seconds. Everything else this script does — the
160
+ # roster restore, the PRD restore, clearing generated artefacts and prompts — is file work that
161
+ # takes milliseconds. A test that exercises the RESET therefore paid three seconds per case for a
162
+ # container it never looks at: two test files spent 23 of the 24 seconds my tests take, purely
163
+ # here, and that is the difference between a suite that gets run and one that does not.
164
+ #
165
+ # Skipped only when asked. A run never sets this, so the live path is unchanged.
166
+ # REPLACING THE CONTAINER, NOT RESTARTING IT.
167
+ #
168
+ # --force-recreate is honoured by docker compose and IGNORED by podman-compose, which restarts the
169
+ # existing container instead. A restarted container keeps the volumes it was created with, so the
170
+ # override this script just wrote -- carrying THIS run's /prd-dir and /logs-dir -- was reported as
171
+ # applied and had no effect. Live 2026-09-08, pipeline-tests-41: override written 08:28,
172
+ # agent-monitor created 08:23 at install time and merely started at 08:28, mounts holding neither
173
+ # /logs-dir nor /prd-dir, and the run then died at a pre-flight check for a /logs path nginx could
174
+ # not serve.
175
+ #
176
+ # The same class this file already records for nginx.conf ("without --force-recreate the container
177
+ # keeps serving the nginx.conf it started with") -- one runtime further down. Removing the service
178
+ # first makes the replacement explicit and true on both runtimes; --force-recreate stays for
179
+ # docker, where it is meaningful and cheap.
180
+ _obs_replace_agent_monitor() {
181
+ EPAM_OBS_SUBNET="${OBS_SUBNET:-}" \
182
+ EPAM_OBS_CLICKHOUSE_PORT="${OBS_CLICKHOUSE_PORT:-}" \
183
+ EPAM_OBS_LANGFUSE_PORT="${OBS_LANGFUSE_PORT:-}" \
184
+ EPAM_OBS_DASHBOARD_PORT="${OBS_DASHBOARD_PORT:-}" \
185
+ EPAM_OBS_GRAFANA_PORT="${OBS_GRAFANA_PORT:-}" \
186
+ EPAM_PRD_DIR="$PRD_DIR" EPAM_LOG_DIR="$LOG_DIR" \
187
+ _obs_compose -f "$COMPOSE_BASE" -f "$COMPOSE_OVERRIDE" -p "$OBS_PROJECT" \
188
+ rm -sf agent-monitor >/dev/null 2>&1 || true
189
+ EPAM_OBS_SUBNET="${OBS_SUBNET:-}" \
190
+ EPAM_OBS_CLICKHOUSE_PORT="${OBS_CLICKHOUSE_PORT:-}" \
191
+ EPAM_OBS_LANGFUSE_PORT="${OBS_LANGFUSE_PORT:-}" \
192
+ EPAM_OBS_DASHBOARD_PORT="${OBS_DASHBOARD_PORT:-}" \
193
+ EPAM_OBS_GRAFANA_PORT="${OBS_GRAFANA_PORT:-}" \
194
+ EPAM_PRD_DIR="$PRD_DIR" EPAM_LOG_DIR="$LOG_DIR" \
195
+ _obs_compose -f "$COMPOSE_BASE" -f "$COMPOSE_OVERRIDE" -p "$OBS_PROJECT" \
196
+ up -d --force-recreate agent-monitor 2>/dev/null
197
+ }
198
+
199
+ if [ "${EPAM_SKIP_CONTAINER_RESTART:-0}" = "1" ]; then
200
+ info " Container restart skipped (EPAM_SKIP_CONTAINER_RESTART=1)"
201
+
202
+ elif _obs_replace_agent_monitor; then
203
+ if [ "$PRD_PRESENT" = "1" ]; then
204
+ success "agent-monitor restarted → /prd-dir = $PRD_DIR (serving $PRD_BASENAME), /logs-dir = $LOG_DIR"
205
+ else
206
+ success "agent-monitor restarted → /prd-dir = $PRD_DIR (serving $PRD_BASENAME once ingest writes it), /logs-dir = $LOG_DIR"
207
+ fi
208
+ elif ! _obs_have_runtime; then
209
+ info " no container runtime found — skipping container restart"
210
+ else
211
+ # NOT "docker is missing". The runtime IS here and the remount FAILED, which is a different
212
+ # fact and a different fix. The old text blamed an absent docker for every outcome, and on
213
+ # 2026-09-08 that sent the reader hunting containers "stuck in Created" while the real cause
214
+ # was this call naming a runtime the box does not use.
215
+ info " agent-monitor remount FAILED with $(_obs_runtime_name) — this run's /logs-dir and /prd-dir are NOT mounted;"
216
+ info " the pre-flight check 'nginx /logs/healing-events.jsonl not reachable' will fail because of this"
217
+ fi
218
+ return 0
219
+ }