@indigoai-us/hq-cli 5.345.113 → 5.345.115
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.
- package/CHANGELOG.md +24 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +5 -1
- package/dist/commands/bot-jobs.d.ts +1 -11
- package/dist/commands/bot-jobs.js +13 -34
- package/dist/commands/capacity.js +2 -0
- package/dist/commands/core-audit-log-native.js +1 -1
- package/dist/commands/core-checkpoint-native.d.ts +27 -0
- package/dist/commands/core-checkpoint-native.js +85 -2
- package/dist/commands/core-dm-bind-native.js +1 -1
- package/dist/commands/core-git-pack-extension-check-native.js +1 -1
- package/dist/commands/core-job-native-utils.d.ts +2 -2
- package/dist/commands/core-job-native-utils.js +3 -11
- package/dist/commands/core-job-notify-native.js +1 -2
- package/dist/commands/core-job-probe-native.d.ts +2 -3
- package/dist/commands/core-job-probe-native.js +14 -22
- package/dist/commands/core-job-remediate-native.js +1 -2
- package/dist/commands/core-job-run-native.js +58 -54
- package/dist/commands/core-jobs-native.d.ts +1 -3
- package/dist/commands/core-jobs-native.js +16 -271
- package/dist/commands/core-jobs-schedule.d.ts +7 -0
- package/dist/commands/core-jobs-schedule.js +243 -0
- package/dist/commands/core-native-utilities.d.ts +2 -3
- package/dist/commands/core-native-utilities.js +2 -19
- package/dist/commands/core-pin-native.js +1 -1
- package/dist/commands/core-refresh-vault-access-native.js +1 -1
- package/dist/commands/core-resolve-company-command.js +2 -17
- package/dist/commands/core-work-mesh-bind-trusted-native.js +1 -1
- package/dist/commands/core.js +1 -17
- package/dist/commands/lanes.js +1 -0
- package/dist/fast-scaffold.d.ts +1 -1
- package/dist/fast-scaffold.js +0 -38
- package/dist/lib/capacity/lane-login.js +73 -6
- package/dist/lib/capacity/route.d.ts +1 -1
- package/dist/lib/capacity/route.js +5 -1
- package/dist/lib/capacity/sign-in.d.ts +12 -0
- package/dist/lib/capacity/sign-in.js +65 -16
- package/dist/lib/daemon/catalog.d.ts +0 -2
- package/dist/lib/daemon/catalog.js +3 -6
- package/dist/lib/daemon/run.js +0 -7
- package/dist/utils/resident-package-preload.js +2 -7
- package/package.json +1 -1
- package/assets/scaffold/core/scripts/audit-log.sh +0 -384
- package/assets/scaffold/core/scripts/git-pack-extension-check.sh +0 -164
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +0 -590
- package/assets/scaffold/core/scripts/hq-job-notify.sh +0 -389
- package/assets/scaffold/core/scripts/hq-job-probe.sh +0 -865
- package/assets/scaffold/core/scripts/hq-job-remediate.sh +0 -359
- package/assets/scaffold/core/scripts/hq-job-run.sh +0 -1025
- package/assets/scaffold/core/scripts/hq-pin.sh +0 -118
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +0 -310
- package/assets/scaffold/core/scripts/jobs-validate.sh +0 -482
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +0 -836
- package/assets/scaffold/core/scripts/refresh-vault-access.sh +0 -188
- package/assets/scaffold/core/scripts/resolve-company.sh +0 -331
- package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +0 -184
- package/dist/commands/core-job-post-exit-assets.d.ts +0 -8
- package/dist/commands/core-job-post-exit-assets.js +0 -37
- package/dist/commands/core-native-jobs-flag.d.ts +0 -4
- package/dist/commands/core-native-jobs-flag.js +0 -7
- package/dist/commands/core-native-utilities-flag.d.ts +0 -4
- package/dist/commands/core-native-utilities-flag.js +0 -7
|
@@ -1,865 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# hq-core: public
|
|
3
|
-
# hq-job-probe.sh — Outpost requirements probe → hq-pro jobs status (US-008).
|
|
4
|
-
#
|
|
5
|
-
# On the Outpost, deterministically checks a job's requirements checklist and
|
|
6
|
-
# POSTs readiness to POST /outpost/internal/jobs-status. Never rewrites job YAML.
|
|
7
|
-
#
|
|
8
|
-
# Usage:
|
|
9
|
-
# core/scripts/hq-job-probe.sh [options] <job.yaml|job-id>
|
|
10
|
-
# core/scripts/hq-job-probe.sh [options] --all
|
|
11
|
-
#
|
|
12
|
-
# Options:
|
|
13
|
-
# --hq-root DIR HQ tree root (default: HQ_ROOT or repo root above core/)
|
|
14
|
-
# --dry-run Run checks + print JSON; skip ingest
|
|
15
|
-
# --no-ingest Alias of --dry-run
|
|
16
|
-
# --json Force JSON on stdout (default)
|
|
17
|
-
# -h, --help Show this help
|
|
18
|
-
#
|
|
19
|
-
# Checks (per requirements / job fields):
|
|
20
|
-
# - runtime CLI auth: claude → ~/.claude/.credentials.json; codex → ~/.codex/auth.json
|
|
21
|
-
# - requirements.secrets[] visible via `hq secrets list` (names only; never values)
|
|
22
|
-
# - optional cwd exists (HQ-root-relative or absolute)
|
|
23
|
-
# - company path present when requirements.company / company scope set
|
|
24
|
-
#
|
|
25
|
-
# Ingest auth (box identity — same trust model as disk-status / relay-status):
|
|
26
|
-
# Header: x-outpost-instance-token
|
|
27
|
-
# Body: userId + jobId + kind=probe + readiness + probed_at + next_actions + checks
|
|
28
|
-
# Identity from (first match):
|
|
29
|
-
# 1. OUTPOST_USER_ID + OUTPOST_INSTANCE_TOKEN (+ optional OUTPOST_ID)
|
|
30
|
-
# 2. /etc/outpost/codex-identity.env (OUTPOST_USER_ID / OUTPOST_INSTANCE_TOKEN)
|
|
31
|
-
# 3. USER_ID= / INSTANCE_TOKEN= / OUTPOST_ID= lines in /usr/local/bin/outpost-runner.sh
|
|
32
|
-
# API base: HQ_PRO_API_URL → HQ_API_URL → HQ_VAULT_API_URL → https://hqapi.hq.computer
|
|
33
|
-
#
|
|
34
|
-
# StatusIngestError (unreachable / 5xx / retryable body):
|
|
35
|
-
# Bounded backoff retries; writes ~/.hq/jobs/probes/{id}/last-attempt.json;
|
|
36
|
-
# never reports readiness=ready solely because ingest failed; surfaces
|
|
37
|
-
# next_action "status ingest failed — retry probe".
|
|
38
|
-
#
|
|
39
|
-
# Env (tests / overrides):
|
|
40
|
-
# HQ_ROOT, HOME, HQ_PRO_API_URL, OUTPOST_USER_ID, OUTPOST_INSTANCE_TOKEN, OUTPOST_ID
|
|
41
|
-
# HQ_JOB_PROBE_CLAUDE_CRED, HQ_JOB_PROBE_CODEX_AUTH
|
|
42
|
-
# HQ_JOB_PROBE_STATE_DIR, HQ_JOB_PROBE_MAX_ATTEMPTS, HQ_JOB_PROBE_BACKOFF_BASE_SEC
|
|
43
|
-
# HQ_JOB_PROBE_SLEEP (set to ":" to no-op sleep), HQ_JOB_PROBE_NOW (ISO timestamp)
|
|
44
|
-
# HQ_JOB_PROBE_CURL (curl binary override), HQ_JOB_PROBE_RUNNER (outpost-runner path)
|
|
45
|
-
#
|
|
46
|
-
# Exit: 0 ready+ingested (or dry-run ready); 1 blocked; 2 usage/config; 3 StatusIngestError
|
|
47
|
-
|
|
48
|
-
set -euo pipefail
|
|
49
|
-
|
|
50
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
51
|
-
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
52
|
-
# shellcheck disable=SC1091
|
|
53
|
-
. "$SCRIPT_DIR/hook-lib.sh"
|
|
54
|
-
AUTH_HEADER_FILE=""
|
|
55
|
-
|
|
56
|
-
cleanup_auth_header_file() {
|
|
57
|
-
if [ -n "${AUTH_HEADER_FILE:-}" ]; then
|
|
58
|
-
rm -f -- "$AUTH_HEADER_FILE" 2>/dev/null || true
|
|
59
|
-
AUTH_HEADER_FILE=""
|
|
60
|
-
fi
|
|
61
|
-
}
|
|
62
|
-
trap cleanup_auth_header_file EXIT
|
|
63
|
-
|
|
64
|
-
DRY_RUN=0
|
|
65
|
-
PROBE_ALL=0
|
|
66
|
-
TARGET=""
|
|
67
|
-
|
|
68
|
-
usage() {
|
|
69
|
-
sed -n '3,48p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
die() {
|
|
73
|
-
echo "hq-job-probe: $*" >&2
|
|
74
|
-
exit 2
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
log() {
|
|
78
|
-
echo "hq-job-probe: $*" >&2
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
while [ $# -gt 0 ]; do
|
|
82
|
-
case "$1" in
|
|
83
|
-
--hq-root)
|
|
84
|
-
HQ_ROOT="${2:-}"
|
|
85
|
-
[ -n "$HQ_ROOT" ] || die "--hq-root requires a directory"
|
|
86
|
-
shift 2
|
|
87
|
-
;;
|
|
88
|
-
--dry-run|--no-ingest)
|
|
89
|
-
DRY_RUN=1
|
|
90
|
-
shift
|
|
91
|
-
;;
|
|
92
|
-
--all)
|
|
93
|
-
PROBE_ALL=1
|
|
94
|
-
shift
|
|
95
|
-
;;
|
|
96
|
-
--json)
|
|
97
|
-
shift
|
|
98
|
-
;;
|
|
99
|
-
-h|--help)
|
|
100
|
-
usage
|
|
101
|
-
exit 0
|
|
102
|
-
;;
|
|
103
|
-
--)
|
|
104
|
-
shift
|
|
105
|
-
break
|
|
106
|
-
;;
|
|
107
|
-
-*)
|
|
108
|
-
die "unknown option: $1"
|
|
109
|
-
;;
|
|
110
|
-
*)
|
|
111
|
-
if [ -n "$TARGET" ]; then
|
|
112
|
-
die "unexpected argument: $1"
|
|
113
|
-
fi
|
|
114
|
-
TARGET="$1"
|
|
115
|
-
shift
|
|
116
|
-
;;
|
|
117
|
-
esac
|
|
118
|
-
done
|
|
119
|
-
|
|
120
|
-
command -v yq >/dev/null 2>&1 || die "yq is required (mikefarah/yq)"
|
|
121
|
-
command -v jq >/dev/null 2>&1 || die "jq is required"
|
|
122
|
-
|
|
123
|
-
# jq.exe on MSYS may use CRT text mode unless --binary is supported.
|
|
124
|
-
case "${OSTYPE:-}:${MSYSTEM:-}" in
|
|
125
|
-
msys*:*|*:MINGW*)
|
|
126
|
-
# Probe --binary once per script run, in the top-level shell. Most jq
|
|
127
|
-
# calls run inside $(...) subshells, so a cache filled lazily inside jq()
|
|
128
|
-
# would be lost and the probe would repeat on every call.
|
|
129
|
-
if command jq --binary -n 'null' >/dev/null 2>&1; then
|
|
130
|
-
HQ_JQ_BINARY_SUPPORTED=1
|
|
131
|
-
else
|
|
132
|
-
HQ_JQ_BINARY_SUPPORTED=0
|
|
133
|
-
fi
|
|
134
|
-
jq() {
|
|
135
|
-
local stdout_file stderr_file status
|
|
136
|
-
stdout_file="$(mktemp)" || return $?
|
|
137
|
-
stderr_file="$(mktemp)" || { status=$?; rm -f "$stdout_file"; return "$status"; }
|
|
138
|
-
if [[ "$HQ_JQ_BINARY_SUPPORTED" == 1 ]]; then
|
|
139
|
-
if command jq --binary "$@" >"$stdout_file" 2>"$stderr_file"; then status=0; else status=$?; fi
|
|
140
|
-
command cat "$stdout_file"
|
|
141
|
-
command cat "$stderr_file" >&2
|
|
142
|
-
else
|
|
143
|
-
if command jq "$@" >"$stdout_file" 2>"$stderr_file"; then status=0; else status=$?; fi
|
|
144
|
-
command sed -b 's/\r$//' "$stdout_file"
|
|
145
|
-
command sed -b 's/\r$//' "$stderr_file" >&2
|
|
146
|
-
fi
|
|
147
|
-
rm -f "$stdout_file" "$stderr_file"
|
|
148
|
-
return "$status"
|
|
149
|
-
}
|
|
150
|
-
;;
|
|
151
|
-
esac
|
|
152
|
-
|
|
153
|
-
iso_now() {
|
|
154
|
-
if [ -n "${HQ_JOB_PROBE_NOW:-}" ]; then
|
|
155
|
-
printf '%s' "$HQ_JOB_PROBE_NOW"
|
|
156
|
-
return
|
|
157
|
-
fi
|
|
158
|
-
date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|
|
159
|
-
|| date -u +"%Y-%m-%dT%H:%M:%S+00:00"
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
probe_sleep() {
|
|
163
|
-
local secs="${1:-1}"
|
|
164
|
-
# HQ_JOB_PROBE_SLEEP=':'|true → no-op for tests. Never eval arbitrary env (review CRITICAL 4).
|
|
165
|
-
if [ "${HQ_JOB_PROBE_SLEEP:-}" = ":" ] || [ "${HQ_JOB_PROBE_SLEEP:-}" = "true" ]; then
|
|
166
|
-
return 0
|
|
167
|
-
fi
|
|
168
|
-
if [[ "$secs" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
|
|
169
|
-
sleep "$secs" 2>/dev/null || true
|
|
170
|
-
fi
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
api_base() {
|
|
174
|
-
local base
|
|
175
|
-
base="${HQ_PRO_API_URL:-${HQ_API_URL:-${HQ_VAULT_API_URL:-https://hqapi.hq.computer}}}"
|
|
176
|
-
base="${base%/}"
|
|
177
|
-
printf '%s' "$base"
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
# Load box identity into OUTPOST_USER_ID / OUTPOST_INSTANCE_TOKEN / OUTPOST_ID.
|
|
181
|
-
resolve_box_identity() {
|
|
182
|
-
local xtrace_was_set=0
|
|
183
|
-
case "$-" in
|
|
184
|
-
*x*) xtrace_was_set=1; set +x ;;
|
|
185
|
-
esac
|
|
186
|
-
|
|
187
|
-
if [ -n "${OUTPOST_USER_ID:-}" ] && [ -n "${OUTPOST_INSTANCE_TOKEN:-}" ]; then
|
|
188
|
-
[ "$xtrace_was_set" -eq 0 ] || set -x
|
|
189
|
-
return 0
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
local envf="/etc/outpost/codex-identity.env"
|
|
193
|
-
if [ -f "$envf" ]; then
|
|
194
|
-
# shellcheck disable=SC1090
|
|
195
|
-
. "$envf" 2>/dev/null || true
|
|
196
|
-
if [ -n "${OUTPOST_USER_ID:-}" ] && [ -n "${OUTPOST_INSTANCE_TOKEN:-}" ]; then
|
|
197
|
-
[ "$xtrace_was_set" -eq 0 ] || set -x
|
|
198
|
-
return 0
|
|
199
|
-
fi
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
local runner="${HQ_JOB_PROBE_RUNNER:-/usr/local/bin/outpost-runner.sh}"
|
|
203
|
-
if [ -f "$runner" ]; then
|
|
204
|
-
OUTPOST_USER_ID="${OUTPOST_USER_ID:-$(sed -n 's/^USER_ID="\(.*\)"$/\1/p' "$runner" 2>/dev/null | head -1)}"
|
|
205
|
-
OUTPOST_INSTANCE_TOKEN="${OUTPOST_INSTANCE_TOKEN:-$(sed -n 's/^INSTANCE_TOKEN="\(.*\)"$/\1/p' "$runner" 2>/dev/null | head -1)}"
|
|
206
|
-
OUTPOST_ID="${OUTPOST_ID:-$(sed -n 's/^OUTPOST_ID="\(.*\)"$/\1/p' "$runner" 2>/dev/null | head -1)}"
|
|
207
|
-
fi
|
|
208
|
-
|
|
209
|
-
if [ -n "${OUTPOST_USER_ID:-}" ] && [ -n "${OUTPOST_INSTANCE_TOKEN:-}" ]; then
|
|
210
|
-
[ "$xtrace_was_set" -eq 0 ] || set -x
|
|
211
|
-
return 0
|
|
212
|
-
fi
|
|
213
|
-
[ "$xtrace_was_set" -eq 0 ] || set -x
|
|
214
|
-
return 1
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
create_auth_header_file() {
|
|
218
|
-
local xtrace_was_set=0 write_rc=0
|
|
219
|
-
AUTH_HEADER_FILE="$(mktemp "${TMPDIR:-/tmp}/hq-job-probe-auth-header.XXXXXX")"
|
|
220
|
-
chmod 600 "$AUTH_HEADER_FILE"
|
|
221
|
-
case "$-" in
|
|
222
|
-
*x*) xtrace_was_set=1; set +x ;;
|
|
223
|
-
esac
|
|
224
|
-
if printf 'x-outpost-instance-token: %s\n' "$OUTPOST_INSTANCE_TOKEN" >"$AUTH_HEADER_FILE"; then
|
|
225
|
-
write_rc=0
|
|
226
|
-
else
|
|
227
|
-
write_rc=$?
|
|
228
|
-
fi
|
|
229
|
-
[ "$xtrace_was_set" -eq 0 ] || set -x
|
|
230
|
-
return "$write_rc"
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
find_job_file_by_id() {
|
|
234
|
-
local id="$1" f found=""
|
|
235
|
-
while IFS= read -r f; do
|
|
236
|
-
[ -n "$f" ] || continue
|
|
237
|
-
if [ "$(yq -r '.id // ""' "$f" 2>/dev/null)" = "$id" ]; then
|
|
238
|
-
if [ -n "$found" ]; then
|
|
239
|
-
die "duplicate job id '$id' in registry: $found and $f"
|
|
240
|
-
fi
|
|
241
|
-
found="$f"
|
|
242
|
-
fi
|
|
243
|
-
done < <(
|
|
244
|
-
{
|
|
245
|
-
find "$HQ_ROOT/personal/jobs" \( -name '*.yaml' -o -name '*.yml' \) -type f 2>/dev/null || true
|
|
246
|
-
find "$HQ_ROOT/companies" -path '*/jobs/*' \( -name '*.yaml' -o -name '*.yml' \) -type f 2>/dev/null || true
|
|
247
|
-
} | sort
|
|
248
|
-
)
|
|
249
|
-
[ -n "$found" ] || return 1
|
|
250
|
-
printf '%s' "$found"
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
collect_all_job_files() {
|
|
254
|
-
{
|
|
255
|
-
find "$HQ_ROOT/personal/jobs" \( -name '*.yaml' -o -name '*.yml' \) -type f 2>/dev/null || true
|
|
256
|
-
find "$HQ_ROOT/companies" -path '*/jobs/*' \( -name '*.yaml' -o -name '*.yml' \) -type f 2>/dev/null || true
|
|
257
|
-
} | sort
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
claude_cred_path() {
|
|
261
|
-
printf '%s' "${HQ_JOB_PROBE_CLAUDE_CRED:-${HOME}/.claude/.credentials.json}"
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
codex_auth_path() {
|
|
265
|
-
printf '%s' "${HQ_JOB_PROBE_CODEX_AUTH:-${HOME}/.codex/auth.json}"
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
check_runtime_auth() {
|
|
269
|
-
# prints ok|missing|no_cli ; uses global RUNTIME
|
|
270
|
-
local runtime="$1"
|
|
271
|
-
case "$runtime" in
|
|
272
|
-
claude)
|
|
273
|
-
if ! command -v claude >/dev/null 2>&1; then
|
|
274
|
-
printf 'no_cli'
|
|
275
|
-
return 1
|
|
276
|
-
fi
|
|
277
|
-
if [ -s "$(claude_cred_path)" ]; then
|
|
278
|
-
printf 'ok'
|
|
279
|
-
return 0
|
|
280
|
-
fi
|
|
281
|
-
printf 'missing'
|
|
282
|
-
return 1
|
|
283
|
-
;;
|
|
284
|
-
codex)
|
|
285
|
-
if ! command -v codex >/dev/null 2>&1; then
|
|
286
|
-
printf 'no_cli'
|
|
287
|
-
return 1
|
|
288
|
-
fi
|
|
289
|
-
if [ -s "$(codex_auth_path)" ]; then
|
|
290
|
-
printf 'ok'
|
|
291
|
-
return 0
|
|
292
|
-
fi
|
|
293
|
-
printf 'missing'
|
|
294
|
-
return 1
|
|
295
|
-
;;
|
|
296
|
-
*)
|
|
297
|
-
printf 'unknown_runtime'
|
|
298
|
-
return 1
|
|
299
|
-
;;
|
|
300
|
-
esac
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
# List visible secret NAMES only (never values). stdout: one name per line.
|
|
304
|
-
list_secret_names() {
|
|
305
|
-
local company="${1:-}"
|
|
306
|
-
local out rc=0
|
|
307
|
-
if [ -n "$company" ]; then
|
|
308
|
-
out="$(hq secrets list --company "$company" 2>/dev/null)" || rc=$?
|
|
309
|
-
else
|
|
310
|
-
out="$(hq secrets list --personal 2>/dev/null)" || rc=$?
|
|
311
|
-
fi
|
|
312
|
-
if [ "$rc" -ne 0 ]; then
|
|
313
|
-
return "$rc"
|
|
314
|
-
fi
|
|
315
|
-
# Skip banner / header rows; emit first whitespace-delimited field when it
|
|
316
|
-
# looks like a vault key name (UPPER_SNAKE / alnum._:-).
|
|
317
|
-
printf '%s\n' "$out" | awk '
|
|
318
|
-
BEGIN { ignore = 1 }
|
|
319
|
-
/^NAME([[:space:]]|$)/ { ignore = 0; next }
|
|
320
|
-
ignore { next }
|
|
321
|
-
/^[[:space:]]*$/ { next }
|
|
322
|
-
/^Secrets / { next }
|
|
323
|
-
{
|
|
324
|
-
name = $1
|
|
325
|
-
if (name ~ /^[A-Za-z][A-Za-z0-9._:\/-]{0,127}$/) print name
|
|
326
|
-
}
|
|
327
|
-
'
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
secret_name_visible() {
|
|
331
|
-
local needle="$1"
|
|
332
|
-
local names_file="$2"
|
|
333
|
-
grep -Fxq -- "$needle" "$names_file" 2>/dev/null
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
state_dir_for_job() {
|
|
337
|
-
local id="$1"
|
|
338
|
-
local base="${HQ_JOB_PROBE_STATE_DIR:-${HOME}/.hq/jobs/probes}"
|
|
339
|
-
printf '%s/%s' "$base" "$id"
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
write_attempt_marker() {
|
|
343
|
-
local id="$1" payload="$2"
|
|
344
|
-
local dir
|
|
345
|
-
dir="$(state_dir_for_job "$id")"
|
|
346
|
-
mkdir -p "$dir"
|
|
347
|
-
printf '%s\n' "$payload" >"$dir/last-attempt.json"
|
|
348
|
-
# Never chmod secrets; marker is non-secret forensics.
|
|
349
|
-
chmod 600 "$dir/last-attempt.json" 2>/dev/null || true
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
# Box-local readiness cache for reconciler (US-004). Written only after a
|
|
353
|
-
# successful ingest (or dry-run) so timers never arm on a failed ingest.
|
|
354
|
-
write_status_cache() {
|
|
355
|
-
local id="$1" readiness="$2" source="${3:-probe-ingest}"
|
|
356
|
-
local base="${HQ_JOB_STATUS_CACHE_DIR:-${HOME}/.hq/jobs/status-cache}"
|
|
357
|
-
mkdir -p "$base"
|
|
358
|
-
jq -nc \
|
|
359
|
-
--arg job_id "$id" \
|
|
360
|
-
--arg readiness "$readiness" \
|
|
361
|
-
--arg updated_at "$(iso_now)" \
|
|
362
|
-
--arg source "$source" \
|
|
363
|
-
'{job_id:$job_id,readiness:$readiness,updated_at:$updated_at,source:$source}' \
|
|
364
|
-
>"$base/${id}.json"
|
|
365
|
-
chmod 600 "$base/${id}.json" 2>/dev/null || true
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
# POST probe ingest with bounded backoff. Sets INGEST_HTTP, INGEST_BODY,
|
|
369
|
-
# and INGEST_RETRYABLE.
|
|
370
|
-
ingest_probe() {
|
|
371
|
-
local payload="$1"
|
|
372
|
-
local max_attempts base_sec attempt delay http body curl_bin url tmp_body tmp_hdr
|
|
373
|
-
max_attempts="${HQ_JOB_PROBE_MAX_ATTEMPTS:-3}"
|
|
374
|
-
base_sec="${HQ_JOB_PROBE_BACKOFF_BASE_SEC:-1}"
|
|
375
|
-
curl_bin="${HQ_JOB_PROBE_CURL:-curl}"
|
|
376
|
-
url="$(api_base)/outpost/internal/jobs-status"
|
|
377
|
-
|
|
378
|
-
INGEST_HTTP=0
|
|
379
|
-
INGEST_BODY=""
|
|
380
|
-
INGEST_RETRYABLE=0
|
|
381
|
-
|
|
382
|
-
if ! command -v "$curl_bin" >/dev/null 2>&1 && [ "$curl_bin" = "curl" ]; then
|
|
383
|
-
if ! command -v curl >/dev/null 2>&1; then
|
|
384
|
-
INGEST_BODY='{"error":true,"code":"StatusIngestError","step":"config","message":"curl not found","retryable":true}'
|
|
385
|
-
INGEST_RETRYABLE=1
|
|
386
|
-
return 1
|
|
387
|
-
fi
|
|
388
|
-
fi
|
|
389
|
-
|
|
390
|
-
attempt=1
|
|
391
|
-
while [ "$attempt" -le "$max_attempts" ]; do
|
|
392
|
-
tmp_body="$(mktemp "${TMPDIR:-/tmp}/hq-job-probe-body.XXXXXX")"
|
|
393
|
-
tmp_hdr="$(mktemp "${TMPDIR:-/tmp}/hq-job-probe-hdr.XXXXXX")"
|
|
394
|
-
create_auth_header_file
|
|
395
|
-
http=0
|
|
396
|
-
set +e
|
|
397
|
-
"$curl_bin" -sS -m 20 -X POST "$url" \
|
|
398
|
-
-H "content-type: application/json" \
|
|
399
|
-
-H "@${AUTH_HEADER_FILE}" \
|
|
400
|
-
--data "$payload" \
|
|
401
|
-
-D "$tmp_hdr" \
|
|
402
|
-
-o "$tmp_body"
|
|
403
|
-
local curl_rc=$?
|
|
404
|
-
set -e
|
|
405
|
-
if [ "$curl_rc" -eq 0 ]; then
|
|
406
|
-
http="$(awk 'BEGIN{c=0} /^HTTP\//{c=$2} END{print c+0}' "$tmp_hdr" 2>/dev/null || echo 0)"
|
|
407
|
-
body="$(cat "$tmp_body" 2>/dev/null || true)"
|
|
408
|
-
else
|
|
409
|
-
http=0
|
|
410
|
-
body="$(printf '{"error":true,"code":"StatusIngestError","step":"lookup","message":"curl failed rc=%s (API unreachable)","retryable":true}' "$curl_rc")"
|
|
411
|
-
fi
|
|
412
|
-
rm -f "$tmp_body" "$tmp_hdr" "$AUTH_HEADER_FILE"
|
|
413
|
-
AUTH_HEADER_FILE=""
|
|
414
|
-
|
|
415
|
-
INGEST_HTTP="$http"
|
|
416
|
-
INGEST_BODY="$body"
|
|
417
|
-
|
|
418
|
-
if [ "$http" -ge 200 ] && [ "$http" -lt 300 ]; then
|
|
419
|
-
INGEST_RETRYABLE=0
|
|
420
|
-
return 0
|
|
421
|
-
fi
|
|
422
|
-
|
|
423
|
-
# Retry on transport failure, 5xx, or explicit retryable StatusIngestError.
|
|
424
|
-
local retry=0
|
|
425
|
-
if [ "$http" -eq 0 ] || [ "$http" -ge 500 ]; then
|
|
426
|
-
retry=1
|
|
427
|
-
elif printf '%s' "$body" | jq -e '.retryable == true' >/dev/null 2>&1; then
|
|
428
|
-
retry=1
|
|
429
|
-
elif printf '%s' "$body" | jq -e '.code == "StatusIngestError" and (.step == "persist" or .step == "lookup" or .step == "config")' >/dev/null 2>&1; then
|
|
430
|
-
retry=1
|
|
431
|
-
fi
|
|
432
|
-
INGEST_RETRYABLE="$retry"
|
|
433
|
-
|
|
434
|
-
if [ "$retry" -ne 1 ] || [ "$attempt" -ge "$max_attempts" ]; then
|
|
435
|
-
break
|
|
436
|
-
fi
|
|
437
|
-
delay=$((base_sec * (1 << (attempt - 1))))
|
|
438
|
-
log "ingest attempt $attempt/$max_attempts failed (http=$http); backoff ${delay}s"
|
|
439
|
-
probe_sleep "$delay"
|
|
440
|
-
attempt=$((attempt + 1))
|
|
441
|
-
done
|
|
442
|
-
|
|
443
|
-
return 1
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
probe_one_file() {
|
|
447
|
-
local job_file="$1"
|
|
448
|
-
[ -f "$job_file" ] || die "job file not found: $job_file"
|
|
449
|
-
|
|
450
|
-
local id name runtime req_runtime company cwd_rel
|
|
451
|
-
id="$(yq -r '.id // ""' "$job_file")"
|
|
452
|
-
name="$(yq -r '.name // ""' "$job_file")"
|
|
453
|
-
runtime="$(yq -r '.runtime // ""' "$job_file")"
|
|
454
|
-
req_runtime="$(yq -r '.requirements.runtime // ""' "$job_file")"
|
|
455
|
-
company="$(yq -r '.requirements.company // ""' "$job_file")"
|
|
456
|
-
cwd_rel="$(yq -r '.requirements.cwd // .cwd // ""' "$job_file")"
|
|
457
|
-
|
|
458
|
-
[ -n "$id" ] || die "job missing id: $job_file"
|
|
459
|
-
if [ -n "$req_runtime" ]; then
|
|
460
|
-
runtime="$req_runtime"
|
|
461
|
-
fi
|
|
462
|
-
[ -n "$runtime" ] || die "job missing runtime: $job_file"
|
|
463
|
-
|
|
464
|
-
local probed_at event_id
|
|
465
|
-
probed_at="$(iso_now)"
|
|
466
|
-
event_id="probe-${probed_at}-${id}"
|
|
467
|
-
|
|
468
|
-
# bash 3.2 + set -u: keep action/missing lists as newline strings, not arrays
|
|
469
|
-
local next_actions_nl=""
|
|
470
|
-
local auth_status secrets_status cwd_status company_status
|
|
471
|
-
local blocked=0
|
|
472
|
-
local secrets_missing_json="[]"
|
|
473
|
-
local names_tmp
|
|
474
|
-
local _action_line
|
|
475
|
-
|
|
476
|
-
# --- runtime auth ---
|
|
477
|
-
auth_status="$(check_runtime_auth "$runtime" || true)"
|
|
478
|
-
case "$auth_status" in
|
|
479
|
-
ok) ;;
|
|
480
|
-
no_cli)
|
|
481
|
-
blocked=1
|
|
482
|
-
_action_line="install ${runtime} CLI on Outpost and re-login"
|
|
483
|
-
next_actions_nl="${next_actions_nl:+${next_actions_nl}$'\n'}${_action_line}"
|
|
484
|
-
;;
|
|
485
|
-
missing)
|
|
486
|
-
blocked=1
|
|
487
|
-
_action_line="device re-login for ${runtime} on Outpost (cached credentials missing)"
|
|
488
|
-
next_actions_nl="${next_actions_nl:+${next_actions_nl}$'\n'}${_action_line}"
|
|
489
|
-
;;
|
|
490
|
-
*)
|
|
491
|
-
blocked=1
|
|
492
|
-
_action_line="unsupported runtime '${runtime}' — fix job requirements.runtime"
|
|
493
|
-
next_actions_nl="${next_actions_nl:+${next_actions_nl}$'\n'}${_action_line}"
|
|
494
|
-
auth_status="unknown_runtime"
|
|
495
|
-
;;
|
|
496
|
-
esac
|
|
497
|
-
|
|
498
|
-
# --- secrets (names only via hq secrets list) ---
|
|
499
|
-
secrets_status="ok"
|
|
500
|
-
names_tmp="$(mktemp "${TMPDIR:-/tmp}/hq-job-probe-secrets.XXXXXX")"
|
|
501
|
-
local list_rc=0
|
|
502
|
-
list_secret_names "$company" >"$names_tmp" || list_rc=$?
|
|
503
|
-
if [ "$list_rc" -ne 0 ]; then
|
|
504
|
-
blocked=1
|
|
505
|
-
secrets_status="list_failed"
|
|
506
|
-
if [ -n "$company" ]; then
|
|
507
|
-
_action_line="hq secrets list --company ${company} failed on Outpost — check hq login / vault access"
|
|
508
|
-
else
|
|
509
|
-
_action_line="hq secrets list --personal failed on Outpost — check hq login / vault access"
|
|
510
|
-
fi
|
|
511
|
-
next_actions_nl="${next_actions_nl:+${next_actions_nl}$'\n'}${_action_line}"
|
|
512
|
-
else
|
|
513
|
-
local missing_names_nl=""
|
|
514
|
-
local secret_count
|
|
515
|
-
secret_count="$(yq -r '.requirements.secrets // [] | length' "$job_file")"
|
|
516
|
-
if [ "$secret_count" != "null" ] && [ "${secret_count:-0}" -gt 0 ] 2>/dev/null; then
|
|
517
|
-
local i secret_name
|
|
518
|
-
i=0
|
|
519
|
-
while [ "$i" -lt "$secret_count" ]; do
|
|
520
|
-
secret_name="$(yq -r ".requirements.secrets[$i]" "$job_file")"
|
|
521
|
-
i=$((i + 1))
|
|
522
|
-
[ -n "$secret_name" ] && [ "$secret_name" != "null" ] || continue
|
|
523
|
-
if ! secret_name_visible "$secret_name" "$names_tmp"; then
|
|
524
|
-
if [ -z "$missing_names_nl" ]; then
|
|
525
|
-
missing_names_nl="$secret_name"
|
|
526
|
-
else
|
|
527
|
-
missing_names_nl="${missing_names_nl}"$'\n'"${secret_name}"
|
|
528
|
-
fi
|
|
529
|
-
fi
|
|
530
|
-
done
|
|
531
|
-
fi
|
|
532
|
-
if [ -n "$missing_names_nl" ]; then
|
|
533
|
-
blocked=1
|
|
534
|
-
secrets_status="missing"
|
|
535
|
-
local sn
|
|
536
|
-
while IFS= read -r sn; do
|
|
537
|
-
[ -n "$sn" ] || continue
|
|
538
|
-
_action_line="hq secrets share ${sn} (grant Outpost owner visibility; names only — never paste values)"
|
|
539
|
-
next_actions_nl="${next_actions_nl:+${next_actions_nl}$'\n'}${_action_line}"
|
|
540
|
-
done <<EOF
|
|
541
|
-
$missing_names_nl
|
|
542
|
-
EOF
|
|
543
|
-
secrets_missing_json="$(printf '%s\n' "$missing_names_nl" | jq -R . | jq -s -c .)"
|
|
544
|
-
fi
|
|
545
|
-
fi
|
|
546
|
-
rm -f "$names_tmp"
|
|
547
|
-
|
|
548
|
-
# --- cwd (HQ-root-relative only; no absolute / no .. escape) ---
|
|
549
|
-
cwd_status="ok"
|
|
550
|
-
if [ -n "$cwd_rel" ] && [ "$cwd_rel" != "null" ]; then
|
|
551
|
-
local cwd_abs cwd_part cwd_bad=0
|
|
552
|
-
case "$cwd_rel" in
|
|
553
|
-
/*)
|
|
554
|
-
blocked=1
|
|
555
|
-
cwd_status="invalid"
|
|
556
|
-
next_actions_nl="${next_actions_nl:+$next_actions_nl$'\n'}fix cwd to HQ-root-relative path (absolute not allowed)"
|
|
557
|
-
cwd_bad=1
|
|
558
|
-
;;
|
|
559
|
-
esac
|
|
560
|
-
if [ "$cwd_bad" -eq 0 ]; then
|
|
561
|
-
local IFS='/'
|
|
562
|
-
# shellcheck disable=SC2086
|
|
563
|
-
for cwd_part in $cwd_rel; do
|
|
564
|
-
if [ "$cwd_part" = ".." ]; then
|
|
565
|
-
blocked=1
|
|
566
|
-
cwd_status="invalid"
|
|
567
|
-
next_actions_nl="${next_actions_nl:+$next_actions_nl$'\n'}fix cwd — remove '..' path components"
|
|
568
|
-
cwd_bad=1
|
|
569
|
-
break
|
|
570
|
-
fi
|
|
571
|
-
done
|
|
572
|
-
unset IFS
|
|
573
|
-
fi
|
|
574
|
-
if [ "$cwd_bad" -eq 0 ]; then
|
|
575
|
-
# Portable resolve: GNU realpath -m, BSD realpath (existing paths), else hq_normpath.
|
|
576
|
-
local hq_canon=""
|
|
577
|
-
hq_canon="$(realpath -m "$HQ_ROOT" 2>/dev/null || realpath "$HQ_ROOT" 2>/dev/null || true)"
|
|
578
|
-
cwd_abs="$(realpath -m "$HQ_ROOT/$cwd_rel" 2>/dev/null || true)"
|
|
579
|
-
if [ -z "$cwd_abs" ] && [ -d "$HQ_ROOT/$cwd_rel" ]; then
|
|
580
|
-
cwd_abs="$(realpath "$HQ_ROOT/$cwd_rel" 2>/dev/null || true)"
|
|
581
|
-
fi
|
|
582
|
-
if [ -z "$hq_canon" ] || [ -z "$cwd_abs" ]; then
|
|
583
|
-
hq_canon="$(hq_normpath "$HQ_ROOT")"
|
|
584
|
-
cwd_abs="$(hq_normpath "$HQ_ROOT/$cwd_rel")"
|
|
585
|
-
fi
|
|
586
|
-
case "$cwd_abs" in
|
|
587
|
-
"$hq_canon"|"$hq_canon"/*) ;;
|
|
588
|
-
*)
|
|
589
|
-
blocked=1
|
|
590
|
-
cwd_status="invalid"
|
|
591
|
-
next_actions_nl="${next_actions_nl:+$next_actions_nl$'\n'}fix cwd — must resolve under HQ root"
|
|
592
|
-
cwd_bad=1
|
|
593
|
-
;;
|
|
594
|
-
esac
|
|
595
|
-
fi
|
|
596
|
-
if [ "$cwd_bad" -eq 0 ]; then
|
|
597
|
-
if [ ! -d "$cwd_abs" ]; then
|
|
598
|
-
blocked=1
|
|
599
|
-
cwd_status="missing"
|
|
600
|
-
next_actions_nl="${next_actions_nl:+$next_actions_nl$'\n'}create cwd on Outpost HQ tree: ${cwd_rel}"
|
|
601
|
-
fi
|
|
602
|
-
fi
|
|
603
|
-
else
|
|
604
|
-
cwd_status="skipped"
|
|
605
|
-
fi
|
|
606
|
-
|
|
607
|
-
# --- company path ---
|
|
608
|
-
company_status="ok"
|
|
609
|
-
if [ -n "$company" ] && [ "$company" != "null" ]; then
|
|
610
|
-
if [ ! -d "$HQ_ROOT/companies/$company" ]; then
|
|
611
|
-
blocked=1
|
|
612
|
-
company_status="missing"
|
|
613
|
-
next_actions+=("company path missing on Outpost: companies/${company} — sync or join company")
|
|
614
|
-
fi
|
|
615
|
-
else
|
|
616
|
-
company_status="skipped"
|
|
617
|
-
fi
|
|
618
|
-
|
|
619
|
-
local readiness="ready"
|
|
620
|
-
if [ "$blocked" -eq 1 ]; then
|
|
621
|
-
readiness="blocked"
|
|
622
|
-
fi
|
|
623
|
-
|
|
624
|
-
local actions_json
|
|
625
|
-
if [ -z "$next_actions_nl" ]; then
|
|
626
|
-
actions_json='[]'
|
|
627
|
-
else
|
|
628
|
-
actions_json="$(printf '%s\n' "$next_actions_nl" | awk 'NF && !seen[$0]++' | jq -R . | jq -s -c .)"
|
|
629
|
-
fi
|
|
630
|
-
|
|
631
|
-
local checks_json
|
|
632
|
-
checks_json="$(jq -nc \
|
|
633
|
-
--arg runtime "$runtime" \
|
|
634
|
-
--arg auth "$auth_status" \
|
|
635
|
-
--arg secrets "$secrets_status" \
|
|
636
|
-
--argjson missing "$secrets_missing_json" \
|
|
637
|
-
--arg cwd "$cwd_status" \
|
|
638
|
-
--arg company "$company_status" \
|
|
639
|
-
--arg company_slug "$company" \
|
|
640
|
-
'{
|
|
641
|
-
runtime: $runtime,
|
|
642
|
-
auth: $auth,
|
|
643
|
-
secrets: {status: $secrets, missing: $missing},
|
|
644
|
-
cwd: $cwd,
|
|
645
|
-
company: (if $company_slug == "" then {status: $company} else {status: $company, slug: $company_slug} end)
|
|
646
|
-
}')"
|
|
647
|
-
|
|
648
|
-
local user_id="${OUTPOST_USER_ID:-}"
|
|
649
|
-
local outpost_id="${OUTPOST_ID:-}"
|
|
650
|
-
|
|
651
|
-
# Build ingest payload (snake_case fields accepted by US-009 handler).
|
|
652
|
-
local payload
|
|
653
|
-
payload="$(jq -nc \
|
|
654
|
-
--arg userId "$user_id" \
|
|
655
|
-
--arg jobId "$id" \
|
|
656
|
-
--arg readiness "$readiness" \
|
|
657
|
-
--arg probed_at "$probed_at" \
|
|
658
|
-
--arg event_id "$event_id" \
|
|
659
|
-
--arg outpostId "$outpost_id" \
|
|
660
|
-
--argjson next_actions "$actions_json" \
|
|
661
|
-
--argjson checks "$checks_json" \
|
|
662
|
-
'{
|
|
663
|
-
userId: $userId,
|
|
664
|
-
jobId: $jobId,
|
|
665
|
-
kind: "probe",
|
|
666
|
-
readiness: $readiness,
|
|
667
|
-
probed_at: $probed_at,
|
|
668
|
-
event_id: $event_id,
|
|
669
|
-
next_actions: $next_actions,
|
|
670
|
-
checks: $checks
|
|
671
|
-
} + (if $outpostId != "" then {outpostId: $outpostId} else {} end)')"
|
|
672
|
-
|
|
673
|
-
local result_ingest="skipped"
|
|
674
|
-
local result_http=0
|
|
675
|
-
local ingest_error=0
|
|
676
|
-
|
|
677
|
-
if [ "$DRY_RUN" -eq 1 ]; then
|
|
678
|
-
result_ingest="dry_run"
|
|
679
|
-
write_status_cache "$id" "$readiness" "probe-dry-run"
|
|
680
|
-
else
|
|
681
|
-
if ! resolve_box_identity; then
|
|
682
|
-
# Cannot authenticate to hq-pro — treat as StatusIngestError path.
|
|
683
|
-
ingest_error=1
|
|
684
|
-
result_ingest="StatusIngestError"
|
|
685
|
-
local marker
|
|
686
|
-
marker="$(jq -nc \
|
|
687
|
-
--arg job_id "$id" \
|
|
688
|
-
--arg probed_at "$probed_at" \
|
|
689
|
-
--arg local_readiness "$readiness" \
|
|
690
|
-
--arg message "missing Outpost identity (OUTPOST_USER_ID / OUTPOST_INSTANCE_TOKEN)" \
|
|
691
|
-
--argjson checks "$checks_json" \
|
|
692
|
-
--argjson next_actions "$(jq -nc --argjson a "$actions_json" '$a + ["status ingest failed — retry probe"]')" \
|
|
693
|
-
'{
|
|
694
|
-
job_id: $job_id,
|
|
695
|
-
probed_at: $probed_at,
|
|
696
|
-
local_readiness: $local_readiness,
|
|
697
|
-
ingest: {ok: false, code: "StatusIngestError", step: "config", message: $message, retryable: true},
|
|
698
|
-
reported_readiness: null,
|
|
699
|
-
next_actions: $next_actions,
|
|
700
|
-
checks: $checks
|
|
701
|
-
}')"
|
|
702
|
-
write_attempt_marker "$id" "$marker"
|
|
703
|
-
# Never claim ready when ingest cannot run.
|
|
704
|
-
readiness="unknown"
|
|
705
|
-
actions_json="$(jq -nc --argjson a "$actions_json" '
|
|
706
|
-
($a + ["status ingest failed — retry probe"]) as $x
|
|
707
|
-
| reduce $x[] as $i ([]; if index($i) then . else . + [$i] end)
|
|
708
|
-
')"
|
|
709
|
-
else
|
|
710
|
-
# Refresh payload with resolved userId
|
|
711
|
-
payload="$(jq -nc \
|
|
712
|
-
--arg userId "$OUTPOST_USER_ID" \
|
|
713
|
-
--arg jobId "$id" \
|
|
714
|
-
--arg readiness "$readiness" \
|
|
715
|
-
--arg probed_at "$probed_at" \
|
|
716
|
-
--arg event_id "$event_id" \
|
|
717
|
-
--arg outpostId "${OUTPOST_ID:-}" \
|
|
718
|
-
--argjson next_actions "$actions_json" \
|
|
719
|
-
--argjson checks "$checks_json" \
|
|
720
|
-
'{
|
|
721
|
-
userId: $userId,
|
|
722
|
-
jobId: $jobId,
|
|
723
|
-
kind: "probe",
|
|
724
|
-
readiness: $readiness,
|
|
725
|
-
probed_at: $probed_at,
|
|
726
|
-
event_id: $event_id,
|
|
727
|
-
next_actions: $next_actions,
|
|
728
|
-
checks: $checks
|
|
729
|
-
} + (if $outpostId != "" then {outpostId: $outpostId} else {} end)')"
|
|
730
|
-
|
|
731
|
-
local intended_readiness="$readiness"
|
|
732
|
-
if ingest_probe "$payload"; then
|
|
733
|
-
result_ingest="ok"
|
|
734
|
-
result_http="$INGEST_HTTP"
|
|
735
|
-
local marker_ok
|
|
736
|
-
marker_ok="$(jq -nc \
|
|
737
|
-
--arg job_id "$id" \
|
|
738
|
-
--arg probed_at "$probed_at" \
|
|
739
|
-
--arg local_readiness "$intended_readiness" \
|
|
740
|
-
--argjson http "$INGEST_HTTP" \
|
|
741
|
-
--argjson checks "$checks_json" \
|
|
742
|
-
--argjson next_actions "$actions_json" \
|
|
743
|
-
'{
|
|
744
|
-
job_id: $job_id,
|
|
745
|
-
probed_at: $probed_at,
|
|
746
|
-
local_readiness: $local_readiness,
|
|
747
|
-
ingest: {ok: true, http: $http},
|
|
748
|
-
reported_readiness: $local_readiness,
|
|
749
|
-
next_actions: $next_actions,
|
|
750
|
-
checks: $checks
|
|
751
|
-
}')"
|
|
752
|
-
write_attempt_marker "$id" "$marker_ok"
|
|
753
|
-
write_status_cache "$id" "$intended_readiness" "probe-ingest"
|
|
754
|
-
else
|
|
755
|
-
ingest_error=1
|
|
756
|
-
result_ingest="StatusIngestError"
|
|
757
|
-
result_http="$INGEST_HTTP"
|
|
758
|
-
# Leave prior API readiness unchanged (we did not successfully write).
|
|
759
|
-
# Never report ready solely because ingest failed.
|
|
760
|
-
local fail_actions body_raw
|
|
761
|
-
fail_actions="$(jq -nc --argjson a "$actions_json" '$a + ["status ingest failed — retry probe"]')"
|
|
762
|
-
body_raw="${INGEST_BODY:-}"
|
|
763
|
-
[ -n "$body_raw" ] || body_raw="{}"
|
|
764
|
-
# Prefer --arg + fromjson so nested JSON cannot break ${:-{}} brace parsing.
|
|
765
|
-
local marker_fail
|
|
766
|
-
marker_fail="$(jq -nc \
|
|
767
|
-
--arg job_id "$id" \
|
|
768
|
-
--arg probed_at "$probed_at" \
|
|
769
|
-
--arg local_readiness "$intended_readiness" \
|
|
770
|
-
--argjson http "$INGEST_HTTP" \
|
|
771
|
-
--arg body "$body_raw" \
|
|
772
|
-
--argjson checks "$checks_json" \
|
|
773
|
-
--argjson next_actions "$fail_actions" \
|
|
774
|
-
--argjson retryable "${INGEST_RETRYABLE:-1}" \
|
|
775
|
-
'{
|
|
776
|
-
job_id: $job_id,
|
|
777
|
-
probed_at: $probed_at,
|
|
778
|
-
local_readiness: $local_readiness,
|
|
779
|
-
ingest: {
|
|
780
|
-
ok: false,
|
|
781
|
-
code: "StatusIngestError",
|
|
782
|
-
http: $http,
|
|
783
|
-
retryable: ($retryable == 1),
|
|
784
|
-
body: (try ($body | fromjson) catch {raw: $body})
|
|
785
|
-
},
|
|
786
|
-
reported_readiness: null,
|
|
787
|
-
next_actions: $next_actions,
|
|
788
|
-
checks: $checks
|
|
789
|
-
}')"
|
|
790
|
-
write_attempt_marker "$id" "$marker_fail"
|
|
791
|
-
readiness="unknown"
|
|
792
|
-
actions_json="$fail_actions"
|
|
793
|
-
fi
|
|
794
|
-
fi
|
|
795
|
-
fi
|
|
796
|
-
|
|
797
|
-
local result
|
|
798
|
-
result="$(jq -nc \
|
|
799
|
-
--arg job_id "$id" \
|
|
800
|
-
--arg name "$name" \
|
|
801
|
-
--arg job_file "$job_file" \
|
|
802
|
-
--arg readiness "$readiness" \
|
|
803
|
-
--arg probed_at "$probed_at" \
|
|
804
|
-
--arg ingest "$result_ingest" \
|
|
805
|
-
--argjson http "$result_http" \
|
|
806
|
-
--argjson checks "$checks_json" \
|
|
807
|
-
--argjson next_actions "$actions_json" \
|
|
808
|
-
--argjson dry_run "$DRY_RUN" \
|
|
809
|
-
'{
|
|
810
|
-
job_id: $job_id,
|
|
811
|
-
name: $name,
|
|
812
|
-
job_file: $job_file,
|
|
813
|
-
readiness: $readiness,
|
|
814
|
-
probed_at: $probed_at,
|
|
815
|
-
ingest: $ingest,
|
|
816
|
-
http: $http,
|
|
817
|
-
dry_run: ($dry_run == 1),
|
|
818
|
-
checks: $checks,
|
|
819
|
-
next_actions: $next_actions
|
|
820
|
-
}')"
|
|
821
|
-
|
|
822
|
-
printf '%s\n' "$result"
|
|
823
|
-
|
|
824
|
-
if [ "$ingest_error" -eq 1 ]; then
|
|
825
|
-
return 3
|
|
826
|
-
fi
|
|
827
|
-
if [ "$blocked" -eq 1 ]; then
|
|
828
|
-
return 1
|
|
829
|
-
fi
|
|
830
|
-
return 0
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
# --- main ---
|
|
834
|
-
|
|
835
|
-
if [ "$PROBE_ALL" -eq 1 ]; then
|
|
836
|
-
[ -z "$TARGET" ] || die "--all does not take a job argument"
|
|
837
|
-
overall=0
|
|
838
|
-
any=0
|
|
839
|
-
while IFS= read -r f; do
|
|
840
|
-
[ -n "$f" ] || continue
|
|
841
|
-
any=1
|
|
842
|
-
rc=0
|
|
843
|
-
probe_one_file "$f" || rc=$?
|
|
844
|
-
if [ "$rc" -gt "$overall" ]; then
|
|
845
|
-
overall="$rc"
|
|
846
|
-
fi
|
|
847
|
-
done < <(collect_all_job_files)
|
|
848
|
-
[ "$any" -eq 1 ] || die "no job YAML found under personal/jobs or companies/*/jobs in $HQ_ROOT"
|
|
849
|
-
exit "$overall"
|
|
850
|
-
fi
|
|
851
|
-
|
|
852
|
-
[ -n "$TARGET" ] || die "usage: hq-job-probe.sh [--dry-run] <job.yaml|job-id> | --all"
|
|
853
|
-
|
|
854
|
-
JOB_FILE=""
|
|
855
|
-
if [ -f "$TARGET" ]; then
|
|
856
|
-
JOB_FILE="$TARGET"
|
|
857
|
-
elif [ -f "$HQ_ROOT/$TARGET" ]; then
|
|
858
|
-
JOB_FILE="$HQ_ROOT/$TARGET"
|
|
859
|
-
else
|
|
860
|
-
JOB_FILE="$(find_job_file_by_id "$TARGET")" || die "job id not found in registry: $TARGET"
|
|
861
|
-
fi
|
|
862
|
-
|
|
863
|
-
rc=0
|
|
864
|
-
probe_one_file "$JOB_FILE" || rc=$?
|
|
865
|
-
exit "$rc"
|