@consiliency/agent-board-cli 1.27.0 → 1.28.0
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/claude-marketplace/plugins/agent-board/.claude-plugin/plugin.json +2 -2
- package/dist/assets.json +2 -2
- package/dist/cli.js +2 -1
- package/dist/doctor.d.ts +61 -0
- package/dist/doctor.js +177 -36
- package/dist/keyslot_status.d.ts +117 -0
- package/dist/keyslot_status.js +242 -0
- package/dist/setup.js +59 -13
- package/dist/setup_storage.d.ts +35 -10
- package/dist/setup_storage.js +97 -16
- package/keyslot/lib.sh +793 -37
- package/keyslot/status.sh +115 -14
- package/package.json +1 -1
package/keyslot/status.sh
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# status.sh --harness <h> [--slot <s>] [--json]
|
|
3
|
-
# chained and loaded? (IF-0-KEYSEAL-3,
|
|
2
|
+
# status.sh --harness <h> [--slot <s>] [--json] [--scan-cache fresh|use|only] [--scan-timeout <s>]:
|
|
3
|
+
# what does slot <s> hold, measured, and is it chained and loaded? (IF-0-KEYSEAL-3,
|
|
4
|
+
# IF-0-KEYSEAL-6). <s> defaults to <h>.
|
|
5
|
+
#
|
|
6
|
+
# SCAN CACHE (IF-552-2, #552): check 6's result is cached per slot under
|
|
7
|
+
# <state root>/agent-board/keyslot-scan/<slot>.json, valid for 24 h and only for the same seam,
|
|
8
|
+
# uid, machine, slot, mode, exemption, pin, blob, scope and scanner. `fresh` (the default, a hand
|
|
9
|
+
# run) never reads it and scans; `use` reads it and scans on a miss; `only` never scans, and a
|
|
10
|
+
# miss is exit 2 `scan_not_cached`. A match is never cached: it writes a tombstone. The whole
|
|
11
|
+
# scan stage runs under one `timeout` (--scan-timeout, default 1800 s); a scan that gives up is
|
|
12
|
+
# exit 2 `scan_timed_out` and is never cached. Progress goes to stderr; stdout stays JSON.
|
|
4
13
|
#
|
|
5
14
|
# MODE, from provenance only, never from what provision was asked (A1):
|
|
6
15
|
# sealed <s>.cred + <s>.pub, the pin derives from a PIPED decrypt of the blob, no
|
|
@@ -34,6 +43,8 @@
|
|
|
34
43
|
# legacy_loader_present flat-layout loader files (pre-A1) are present, or the installed
|
|
35
44
|
# template still runs them
|
|
36
45
|
# nothing_committed, undeterminable
|
|
46
|
+
# scan_not_cached --scan-cache only, and no valid cache (the scan is not judged)
|
|
47
|
+
# scan_timed_out the scan stage hit --scan-timeout (the scan is not judged)
|
|
37
48
|
# The remedy for the three loader causes is provision.sh --refresh-loader, which never re-keys.
|
|
38
49
|
# The JSON carries hashes only: never the pin, the reference or any key byte.
|
|
39
50
|
set -euo pipefail
|
|
@@ -43,21 +54,30 @@ here=${BASH_SOURCE[0]%/*}
|
|
|
43
54
|
# shellcheck source=lib.sh
|
|
44
55
|
. "$here/lib.sh"
|
|
45
56
|
|
|
46
|
-
harness= slot= json=no
|
|
57
|
+
harness= slot= json=no scan_cache=fresh scan_timeout=$KEYSEAL_SCAN_TIMEOUT_S
|
|
58
|
+
usage="usage: status.sh --harness <h> [--slot <s>] [--json] [--scan-cache fresh|use|only] [--scan-timeout <s>]"
|
|
47
59
|
while (($#)); do
|
|
48
60
|
case $1 in
|
|
49
61
|
--harness) harness=${2-}; shift 2 || keyseal_die "usage: --harness <h>" ;;
|
|
50
62
|
--slot) slot=${2-}; shift 2 || keyseal_die "usage: --slot <s>" ;;
|
|
51
63
|
--json) json=yes; shift ;;
|
|
52
|
-
|
|
64
|
+
--scan-cache) scan_cache=${2-}; shift 2 || keyseal_die "$usage" ;;
|
|
65
|
+
--scan-timeout) scan_timeout=${2-}; shift 2 || keyseal_die "$usage" ;;
|
|
66
|
+
*) keyseal_die "$usage" ;;
|
|
53
67
|
esac
|
|
54
68
|
done
|
|
55
69
|
[[ $harness =~ $KEYSEAL_SLOT_RE ]] || keyseal_die "bad_harness"
|
|
56
70
|
slot=${slot:-$harness}
|
|
57
71
|
[[ $slot =~ $KEYSEAL_SLOT_RE ]] || keyseal_die "bad_slot"
|
|
58
|
-
|
|
72
|
+
[[ $scan_cache == fresh || $scan_cache == use || $scan_cache == only ]] || keyseal_die "bad_scan_cache: $usage"
|
|
73
|
+
[[ $scan_timeout =~ ^[1-9][0-9]{0,6}$ ]] || keyseal_die "bad_scan_timeout: $usage"
|
|
74
|
+
[[ -n ${XDG_RUNTIME_DIR-} ]] || keyseal_die "runtime_dir_unset: XDG_RUNTIME_DIR is unset, so the slot's agent socket cannot be located; run from a login session or export XDG_RUNTIME_DIR=/run/user/$(id -u)"
|
|
59
75
|
: "${HOME:?status: HOME unset}"
|
|
60
76
|
keyseal_resolve_bins || exit 1
|
|
77
|
+
# The scan's cleanup (IF-552-2): the ticker, the stage group and every temp file, on any exit.
|
|
78
|
+
trap keyseal_scan_cleanup EXIT
|
|
79
|
+
trap 'exit 130' INT
|
|
80
|
+
trap 'exit 143' TERM
|
|
61
81
|
|
|
62
82
|
K=$(keyseal_key_dir)
|
|
63
83
|
cred=$K/$slot.cred pub=$K/$slot.pub key=$K/$slot.key optin=$K/$slot.unsealed-optin
|
|
@@ -66,6 +86,7 @@ load_unit=$KEYSEAL_LOAD_UNIT@$slot.service
|
|
|
66
86
|
declare -A ok detail
|
|
67
87
|
exit2=()
|
|
68
88
|
has() { [[ -e $1 || -L $1 ]]; }
|
|
89
|
+
str_or_null() { if [[ -n $1 ]]; then keyseal_json_str "$1"; else printf null; fi; }
|
|
69
90
|
|
|
70
91
|
probe_out=$("$here/probe.sh" --json 2>/dev/null) && probe_rc=0 || probe_rc=$?
|
|
71
92
|
outcome=error
|
|
@@ -170,20 +191,105 @@ case $mode in
|
|
|
170
191
|
;;
|
|
171
192
|
esac
|
|
172
193
|
|
|
173
|
-
|
|
194
|
+
pin_sha=null blob_sha=null
|
|
195
|
+
[[ -n $pin ]] && pin_sha=$(keyseal_json_str "$(keyseal_sha256_str "$pin")")
|
|
196
|
+
[[ -f $cred ]] && blob_sha=$(keyseal_json_str "$(keyseal_sha256_file "$cred")")
|
|
197
|
+
machine_id=$(keyseal_machine_id) || machine_id=
|
|
198
|
+
|
|
199
|
+
# 6 scan: the unsealed slot's own key file is the one exemption. IF-552-2: the inputs a cached
|
|
200
|
+
# result is valid for, the cache read (under its lock), then a scan unless a valid cache serves.
|
|
174
201
|
exempt=
|
|
175
202
|
[[ $mode == unsealed ]] && exempt=$key
|
|
176
|
-
|
|
177
|
-
|
|
203
|
+
stale_temp=$(keyseal_scan_stale_temp)
|
|
204
|
+
keyseal_scan_roots
|
|
205
|
+
KEYSEAL_INPUT=(
|
|
206
|
+
[seam]=$(keyseal_seam_json)
|
|
207
|
+
[uid]=$(keyseal_uid)
|
|
208
|
+
[machine_id]=$(str_or_null "$machine_id")
|
|
209
|
+
[slot]=$(keyseal_json_str "$slot")
|
|
210
|
+
[mode]=$(keyseal_json_str "$mode")
|
|
211
|
+
[exempt]=$(if [[ -n $exempt ]]; then keyseal_json_str "$(keyseal_sha256_str "$exempt")"; else printf null; fi)
|
|
212
|
+
[pin_sha256]=$pin_sha
|
|
213
|
+
[blob_sha256]=$blob_sha
|
|
214
|
+
[scope_sha256]=$(keyseal_json_str "$(keyseal_sha256_str "$(keyseal_scan_scope_json)")")
|
|
215
|
+
[scanner_sha256]=$(str_or_null "$(keyseal_files_sha "$here" lib.sh status.sh || true)")
|
|
216
|
+
)
|
|
217
|
+
keyseal_cache_read "$slot" "$scan_cache"
|
|
218
|
+
cache_read="$KEYSEAL_CACHE_STATE:$KEYSEAL_CACHE_REASON"
|
|
219
|
+
cache_last=null
|
|
220
|
+
[[ -n $KEYSEAL_CACHE_LAST ]] && cache_last=$(keyseal_json_str "$(keyseal_iso "$KEYSEAL_CACHE_LAST")")
|
|
221
|
+
scan_source=none scan_scanned_at=null scan_age=null scan_duration=null scan_core=
|
|
222
|
+
scan_judged=yes scan_timed_out=no
|
|
223
|
+
if [[ $KEYSEAL_CACHE_STATE == hit ]]; then
|
|
224
|
+
scan_source=cached scan_core=$KEYSEAL_CACHE_SCAN
|
|
225
|
+
scan_now=$(keyseal_now) || scan_now=$KEYSEAL_CACHE_SCANNED_AT
|
|
226
|
+
scan_scanned_at=$(keyseal_json_str "$(keyseal_iso "$KEYSEAL_CACHE_SCANNED_AT")")
|
|
227
|
+
scan_age=$((scan_now - KEYSEAL_CACHE_SCANNED_AT)) scan_duration=$KEYSEAL_CACHE_DURATION
|
|
228
|
+
elif [[ $scan_cache == only ]]; then
|
|
229
|
+
scan_judged=no
|
|
230
|
+
detail[scan]="not judged: no valid scan cache ($KEYSEAL_CACHE_STATE:$KEYSEAL_CACHE_REASON)"
|
|
231
|
+
exit2+=(scan_not_cached)
|
|
232
|
+
else
|
|
233
|
+
# scanned_at is the scan's START: a file created after it may be missed.
|
|
234
|
+
scan_start=$(keyseal_now) || scan_start=0
|
|
235
|
+
printf 'status: scan: starting (%s:%s)\n' "$KEYSEAL_CACHE_STATE" "$KEYSEAL_CACHE_REASON" >&2
|
|
236
|
+
keyseal_scan_run "$pin" "$exempt" "$scan_timeout"
|
|
237
|
+
scan_end=$(keyseal_now) || scan_end=$scan_start
|
|
238
|
+
scan_duration=$((scan_end - scan_start))
|
|
239
|
+
((scan_duration >= 0)) || scan_duration=0
|
|
240
|
+
case $KEYSEAL_SCAN_OUTCOME in
|
|
241
|
+
ok)
|
|
242
|
+
scan_source=fresh scan_core=$(keyseal_scan_core_json)
|
|
243
|
+
scan_scanned_at=$(keyseal_json_str "$(keyseal_iso "$scan_start")") scan_age=$scan_duration
|
|
244
|
+
;;
|
|
245
|
+
timed_out)
|
|
246
|
+
scan_judged=no scan_timed_out=yes
|
|
247
|
+
detail[scan]="not judged: the scan gave up after $scan_timeout s (scan_timed_out)"
|
|
248
|
+
exit2+=(scan_timed_out)
|
|
249
|
+
KEYSEAL_CACHE_STATE=not_written KEYSEAL_CACHE_REASON=timed_out
|
|
250
|
+
;;
|
|
251
|
+
*)
|
|
252
|
+
detail[scan]="the scan did not run to completion"
|
|
253
|
+
KEYSEAL_CACHE_STATE=not_written KEYSEAL_CACHE_REASON=incomplete
|
|
254
|
+
;;
|
|
255
|
+
esac
|
|
256
|
+
fi
|
|
257
|
+
if [[ -n $scan_core ]]; then
|
|
258
|
+
if [[ $scan_core == *'"plaintext_matches":[]'* ]]; then ok[scan]=true detail[scan]=""
|
|
178
259
|
else ok[scan]=false detail[scan]="plaintext copy of the slot key found"
|
|
179
260
|
fi
|
|
180
261
|
else
|
|
181
|
-
|
|
262
|
+
ok[scan]=false
|
|
263
|
+
fi
|
|
264
|
+
# A match is never cached: it writes a tombstone. Only a clean, completed, fresh scan commits.
|
|
265
|
+
if [[ $scan_source == fresh ]]; then
|
|
266
|
+
if [[ ${ok[scan]} == true ]]; then keyseal_cache_commit_clean "$slot" "$scan_start" "$scan_duration" "$scan_core"
|
|
267
|
+
else keyseal_cache_commit_tombstone "$slot" "$harness"
|
|
268
|
+
fi
|
|
269
|
+
fi
|
|
270
|
+
if [[ $scan_source == fresh ]]; then
|
|
271
|
+
n_matches=0 n_candidates=0
|
|
272
|
+
[[ $scan_core =~ \"candidates\":([0-9]+) ]] && n_candidates=${BASH_REMATCH[1]}
|
|
273
|
+
[[ ${ok[scan]} == true ]] || n_matches=$(printf '%s' "$scan_core" | keyseal_node count-matches 2>/dev/null || printf '?')
|
|
274
|
+
printf 'status: scan: done in %s s (%s folders, %s candidates, %s matches)\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "$n_candidates" "$n_matches" >&2
|
|
275
|
+
elif [[ $scan_source == none && $scan_judged == yes || $scan_timed_out == yes ]]; then
|
|
276
|
+
printf 'status: scan: done in %s s (%s folders): %s\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "${detail[scan]}" >&2
|
|
277
|
+
fi
|
|
278
|
+
scan_meta="\"source\":\"$scan_source\",\"scanned_at\":$scan_scanned_at,\"age_s\":$scan_age,\"duration_s\":$scan_duration,\"inputs\":$(keyseal_cache_inputs_json),\"stale_temp\":$stale_temp,\"cache\":{\"mode\":\"$scan_cache\",\"state\":\"$KEYSEAL_CACHE_STATE\",\"reason\":$(str_or_null "$KEYSEAL_CACHE_REASON"),\"read\":$(keyseal_json_str "$cache_read"),\"last_scanned_at\":$cache_last}"
|
|
279
|
+
# A scan that was not judged still names the scope it would have scanned.
|
|
280
|
+
if [[ -n $scan_core ]]; then scan=$(keyseal_scan_with_headers "${scan_core%\}},$scan_meta}")
|
|
281
|
+
else scan="{\"roots\":$(keyseal_json_list ${KEYSEAL_ROOTS[@]+"${KEYSEAL_ROOTS[@]}"}),\"roots_skipped\":$KEYSEAL_ROOTS_SKIPPED_JSON,$scan_meta}"
|
|
182
282
|
fi
|
|
183
283
|
|
|
184
284
|
# Nothing committed is 2 by the provenance table: there is no unit, agent or chain to judge.
|
|
185
285
|
hard=(committed chain units agent scan)
|
|
186
286
|
[[ ${exit2[*]} == *nothing_committed* ]] && hard=(committed scan)
|
|
287
|
+
# A scan that was not judged (no valid cache under `only`, or the deadline) is 2, never 1.
|
|
288
|
+
if [[ $scan_judged == no ]]; then
|
|
289
|
+
judged=()
|
|
290
|
+
for c in "${hard[@]}"; do [[ $c == scan ]] || judged+=("$c"); done
|
|
291
|
+
hard=("${judged[@]}")
|
|
292
|
+
fi
|
|
187
293
|
# committed, chain and scan are always hard. A loader that could not run, or an agent it left
|
|
188
294
|
# EMPTY, is the consequence of a sealed slot's capability loss or an undeterminable blob, so it
|
|
189
295
|
# is 2 while one is present (review round 1, Opus B2). A wrong or extra key stays 1.
|
|
@@ -219,17 +325,12 @@ rc=0
|
|
|
219
325
|
for c in "${hard[@]}"; do [[ ${ok[$c]} == true ]] || rc=1; done
|
|
220
326
|
if ((rc == 0 && ${#exit2[@]})); then rc=2; fi
|
|
221
327
|
|
|
222
|
-
pin_sha=null blob_sha=null
|
|
223
|
-
[[ -n $pin ]] && pin_sha=$(keyseal_json_str "$(keyseal_sha256_str "$pin")")
|
|
224
|
-
[[ -f $cred ]] && blob_sha=$(keyseal_json_str "$(keyseal_sha256_file "$cred")")
|
|
225
328
|
boot_id=$(</proc/sys/kernel/random/boot_id)
|
|
226
|
-
machine_id=$(keyseal_machine_id) || machine_id=
|
|
227
329
|
bool() { if [[ $1 == yes ]]; then printf true; else printf false; fi; }
|
|
228
330
|
reasons=
|
|
229
331
|
for r in "${exit2[@]}"; do reasons+="${reasons:+,}$(keyseal_json_str "$r")"; done
|
|
230
332
|
outdated=no
|
|
231
333
|
[[ ${exit2[*]} == *loader_outdated* ]] && outdated=yes
|
|
232
|
-
str_or_null() { if [[ -n $1 ]]; then keyseal_json_str "$1"; else printf null; fi; }
|
|
233
334
|
|
|
234
335
|
if [[ $json == yes ]]; then
|
|
235
336
|
checks=
|