@manhquy/flow-skill 0.5.0 → 0.6.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/README.md +4 -4
- package/package.json +1 -1
- package/skills/flow/SKILL.md +12 -5
- package/skills/flow/_templates/02-scope.md +9 -0
- package/skills/flow/_templates/03-prd.md +11 -0
- package/skills/flow/_templates/05-contract.md +8 -0
- package/skills/flow/_templates/card.md +4 -0
- package/skills/flow/eval/fixtures/converge/conv-01/cards/C-001.md +21 -0
- package/skills/flow/eval/fixtures/converge/conv-01/flow/03-prd.md +7 -0
- package/skills/flow/eval/fixtures/converge/conv-01/src/app.py +3 -0
- package/skills/flow/eval/fixtures/converge/gap-01/cards/C-001.md +21 -0
- package/skills/flow/eval/fixtures/converge/gap-01/cards/C-002.md +21 -0
- package/skills/flow/eval/fixtures/converge/gap-01/flow/03-prd.md +8 -0
- package/skills/flow/eval/fixtures/converge/gap-01/flow/05-contract.md +7 -0
- package/skills/flow/eval/fixtures/converge/gap-01/src/app.py +11 -0
- package/skills/flow/eval/fixtures/converge/manifest.tsv +3 -0
- package/skills/flow/law/CLAUDE.md +7 -0
- package/skills/flow/references/artifact-lifecycle.md +58 -0
- package/skills/flow/references/clarify.md +191 -0
- package/skills/flow/references/command-dispatch.md +3 -1
- package/skills/flow/references/concierge.md +2 -0
- package/skills/flow/references/converge.md +109 -0
- package/skills/flow/references/flow-catalog.tsv +2 -0
- package/skills/flow/references/gate-rules.md +32 -0
- package/skills/flow/references/mode-work.md +11 -1
- package/skills/flow/references/stage-state-machine.md +9 -1
- package/skills/flow/runner/attestations.sh +249 -138
- package/skills/flow/runner/flow.sh +425 -5
- package/skills-manifest.json +14 -2
|
@@ -217,17 +217,15 @@ _att_subject_lock_name() { # kind subject_id
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
# ---------- cleanliness ----------
|
|
220
|
-
#
|
|
220
|
+
# Allowed dirt: .flow/**, flow/.lock{,.d/**}, cards/C-*.md with projection==HEAD.
|
|
221
221
|
_att_git_clean_for_attest() {
|
|
222
222
|
local porcelain
|
|
223
223
|
porcelain="$(git -C "$ROOT" status --porcelain=v1 --untracked-files=all 2>/dev/null || echo FAIL)"
|
|
224
224
|
[ "$porcelain" = "FAIL" ] && return 1
|
|
225
225
|
[ -z "$porcelain" ] && return 0
|
|
226
|
-
# Allow only changes under .flow/
|
|
227
226
|
local line path
|
|
228
227
|
while IFS= read -r line; do
|
|
229
228
|
[ -z "$line" ] && continue
|
|
230
|
-
# format: XY PATH or XY ORIG -> PATH
|
|
231
229
|
path="${line#?? }"
|
|
232
230
|
case "$path" in
|
|
233
231
|
*" -> "*) path="${path##* -> }" ;;
|
|
@@ -235,6 +233,11 @@ _att_git_clean_for_attest() {
|
|
|
235
233
|
path="${path#\"}"; path="${path%\"}"
|
|
236
234
|
case "$path" in
|
|
237
235
|
.flow|.flow/*) continue ;;
|
|
236
|
+
flow/.lock|flow/.lock.d|flow/.lock.d/*) continue ;;
|
|
237
|
+
cards/C-*.md)
|
|
238
|
+
_att_card_projection_matches_head "$path" || return 1
|
|
239
|
+
continue
|
|
240
|
+
;;
|
|
238
241
|
*) return 1 ;;
|
|
239
242
|
esac
|
|
240
243
|
done <<EOF
|
|
@@ -242,6 +245,23 @@ $porcelain
|
|
|
242
245
|
EOF
|
|
243
246
|
return 0
|
|
244
247
|
}
|
|
248
|
+
_att_card_projection_matches_head() {
|
|
249
|
+
local rel="$1" tmp now old
|
|
250
|
+
[ -f "$ROOT/$rel" ] || return 1
|
|
251
|
+
git -C "$ROOT" cat-file -e "HEAD:${rel}" 2>/dev/null || return 1
|
|
252
|
+
tmp="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-proj.$$")"
|
|
253
|
+
if ! git -C "$ROOT" show "HEAD:${rel}" >"$tmp" 2>/dev/null; then
|
|
254
|
+
rm -f "$tmp"; return 1
|
|
255
|
+
fi
|
|
256
|
+
now="$(_att_card_contract_projection "$ROOT/$rel")"
|
|
257
|
+
old="$(_att_card_contract_projection "$tmp")"
|
|
258
|
+
rm -f "$tmp"
|
|
259
|
+
[ "$now" = "$old" ]
|
|
260
|
+
}
|
|
261
|
+
_att_require_clean_for_consume() {
|
|
262
|
+
_att_git_ok || return 0
|
|
263
|
+
_att_git_clean_for_attest
|
|
264
|
+
}
|
|
245
265
|
|
|
246
266
|
# ---------- card field parsing ----------
|
|
247
267
|
_att_field_count() { # $1 file $2 field
|
|
@@ -414,11 +434,13 @@ _att_parse_kv_file() { # $1 path — sets _ATT_KV_<key> via eval-safe names only
|
|
|
414
434
|
}
|
|
415
435
|
|
|
416
436
|
_att_owner_fingerprint() { # $1 owner path relative, $2 revision
|
|
417
|
-
local rel="$1" rev="$2" cmd_path oracle_path mode blob o_mode o_blob body fmt oid
|
|
437
|
+
local rel="$1" rev="$2" cmd_path oracle_path mode blob o_mode o_blob body fmt oid owner_blob owner_mode
|
|
418
438
|
cmd_path="${_ATT_KV_command#repo:}"
|
|
439
|
+
owner_blob="$(git -C "$ROOT" rev-parse "${rev}:${rel}" 2>/dev/null | tr -d '\r\n')" || return 1
|
|
440
|
+
owner_mode="$(git -C "$ROOT" ls-tree "$rev" -- "$rel" 2>/dev/null | awk '{print $1}' | head -1)"
|
|
419
441
|
blob="$(git -C "$ROOT" rev-parse "${rev}:${cmd_path}" 2>/dev/null | tr -d '\r\n')" || return 1
|
|
420
442
|
mode="$(git -C "$ROOT" ls-tree "$rev" -- "$cmd_path" 2>/dev/null | awk '{print $1}' | head -1)"
|
|
421
|
-
body="owner=${rel}"$'\n'"cmd=${cmd_path}:${mode}:${blob}"$'\n'
|
|
443
|
+
body="owner=${rel}:${owner_mode}:${owner_blob}"$'\n'"cmd=${cmd_path}:${mode}:${blob}"$'\n'
|
|
422
444
|
if [ "${_ATT_KV_revision_oracle}" != "none" ] && [ -n "${_ATT_KV_revision_oracle}" ]; then
|
|
423
445
|
oracle_path="${_ATT_KV_revision_oracle#repo:}"
|
|
424
446
|
o_blob="$(git -C "$ROOT" rev-parse "${rev}:${oracle_path}" 2>/dev/null | tr -d '\r\n')" || return 1
|
|
@@ -431,6 +453,53 @@ _att_owner_fingerprint() { # $1 owner path relative, $2 revision
|
|
|
431
453
|
printf 'git-%s:%s' "$fmt" "$oid"
|
|
432
454
|
}
|
|
433
455
|
|
|
456
|
+
# ---------- committed-blob execution ----------
|
|
457
|
+
_att_path_mode_at_rev() { git -C "$ROOT" ls-tree "$1" -- "$2" 2>/dev/null | awk '{print $1}' | head -1; }
|
|
458
|
+
_att_blob_at_rev() { git -C "$ROOT" rev-parse "${1}:${2}" 2>/dev/null | tr -d '\r\n'; }
|
|
459
|
+
_att_blob_on_disk() {
|
|
460
|
+
local f="$ROOT/$1"
|
|
461
|
+
[ -f "$f" ] || return 1
|
|
462
|
+
[ ! -L "$f" ] || return 1
|
|
463
|
+
git -C "$ROOT" hash-object -- "$f" 2>/dev/null | tr -d '\r\n'
|
|
464
|
+
}
|
|
465
|
+
_att_assert_committed_regular() {
|
|
466
|
+
local rev="$1" rel="$2" mode blob disk
|
|
467
|
+
mode="$(_att_path_mode_at_rev "$rev" "$rel")"
|
|
468
|
+
case "$mode" in 100644|100755) : ;; *) return 1 ;; esac
|
|
469
|
+
blob="$(_att_blob_at_rev "$rev" "$rel")" || return 1
|
|
470
|
+
[ -n "$blob" ] || return 1
|
|
471
|
+
disk="$(_att_blob_on_disk "$rel")" || return 1
|
|
472
|
+
[ "$disk" = "$blob" ] || return 1
|
|
473
|
+
_ATT_BLOB_OID="$blob"
|
|
474
|
+
return 0
|
|
475
|
+
}
|
|
476
|
+
_att_materialize_blob_exec() {
|
|
477
|
+
local rev="$1" rel="$2" mode blob tmp
|
|
478
|
+
mode="$(_att_path_mode_at_rev "$rev" "$rel")"
|
|
479
|
+
case "$mode" in 100644|100755) : ;; *) return 1 ;; esac
|
|
480
|
+
blob="$(_att_blob_at_rev "$rev" "$rel")" || return 1
|
|
481
|
+
tmp="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-exec.$$.$RANDOM")"
|
|
482
|
+
if ! git -C "$ROOT" cat-file blob "$blob" >"$tmp" 2>/dev/null; then
|
|
483
|
+
rm -f "$tmp"; return 1
|
|
484
|
+
fi
|
|
485
|
+
chmod 700 "$tmp" 2>/dev/null || chmod +x "$tmp" 2>/dev/null || true
|
|
486
|
+
printf '%s' "$tmp"
|
|
487
|
+
return 0
|
|
488
|
+
}
|
|
489
|
+
_att_load_owner_at_rev() {
|
|
490
|
+
local rev="$1" rel="$2" cmd_rel
|
|
491
|
+
_att_assert_committed_regular "$rev" "$rel" || return 1
|
|
492
|
+
_att_parse_kv_file "$ROOT/$rel" || return 1
|
|
493
|
+
case "${_ATT_KV_command:-}" in repo:*) : ;; *) return 1 ;; esac
|
|
494
|
+
cmd_rel="${_ATT_KV_command#repo:}"
|
|
495
|
+
_att_assert_committed_regular "$rev" "$cmd_rel" || return 1
|
|
496
|
+
if [ "${_ATT_KV_revision_oracle:-none}" != "none" ] && [ -n "${_ATT_KV_revision_oracle:-}" ]; then
|
|
497
|
+
case "$_ATT_KV_revision_oracle" in repo:*) : ;; *) return 1 ;; esac
|
|
498
|
+
_att_assert_committed_regular "$rev" "${_ATT_KV_revision_oracle#repo:}" || return 1
|
|
499
|
+
fi
|
|
500
|
+
return 0
|
|
501
|
+
}
|
|
502
|
+
|
|
434
503
|
# ---------- projections / fingerprints ----------
|
|
435
504
|
_att_card_contract_projection() { # $1 card file
|
|
436
505
|
local f="$1"
|
|
@@ -465,17 +534,20 @@ _att_card_semantic_fingerprint() { # $1 card file $2 base $3 head -> prints fp;
|
|
|
465
534
|
head="$(_att_full_commit "$head")" || return 1
|
|
466
535
|
tree="$(git -C "$ROOT" rev-parse "${head}^{tree}" 2>/dev/null | tr -d '\r\n')" || return 1
|
|
467
536
|
proj="$(_att_card_contract_projection "$file")"
|
|
468
|
-
# changed paths base..head excluding card path
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
537
|
+
# changed paths base..head excluding card path.
|
|
538
|
+
# Bash 3.2 + set -u: avoid `read st p rest` (unbound rest) and case R*|C* inside
|
|
539
|
+
# command substitution (macOS /bin/bash syntax error). Use name-only only.
|
|
540
|
+
path_manifest="$(
|
|
541
|
+
git -C "$ROOT" diff-tree -r --name-only "$base" "$head" 2>/dev/null \
|
|
542
|
+
| while IFS= read -r p || [ -n "$p" ]; do
|
|
543
|
+
p="$(printf '%s' "$p" | tr -d '\r')"
|
|
544
|
+
[ -z "$p" ] && continue
|
|
545
|
+
[ "$p" = "cards/${id}.md" ] && continue
|
|
546
|
+
mode_oid="$(git -C "$ROOT" ls-tree "$head" -- "$p" 2>/dev/null | awk '{print $1" "$3}')"
|
|
547
|
+
[ -n "$mode_oid" ] || continue
|
|
548
|
+
printf '%s %s\n' "$p" "$mode_oid"
|
|
549
|
+
done | sort
|
|
550
|
+
)"
|
|
479
551
|
body="base=${base}"$'\n'"head=${head}"$'\n'"tree=${tree}"$'\n'"proj<<"$'\n'"${proj}"$'\n'">>"$'\n'"paths<<"$'\n'"${path_manifest}"$'\n'">>"$'\n'
|
|
480
552
|
fmt="$(_att_object_format)"
|
|
481
553
|
oid="$(printf '%s' "$body" | git -C "$ROOT" hash-object --stdin 2>/dev/null | tr -d '\r\n')"
|
|
@@ -611,116 +683,135 @@ revision_oracle_fingerprint: ${_ATT_W_revision_oracle_fingerprint}
|
|
|
611
683
|
}
|
|
612
684
|
|
|
613
685
|
# ---------- process supervisor ----------
|
|
614
|
-
#
|
|
686
|
+
# Prefer setsid (Linux). macOS often lacks setsid: fall back to best-effort kill of
|
|
687
|
+
# the launched PID (not a full session). Live-auto residual is documented for that case.
|
|
688
|
+
_att_has_setsid() { command -v setsid >/dev/null 2>&1; }
|
|
689
|
+
|
|
615
690
|
_att_supervisor_capable() {
|
|
616
691
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-unsupported" ]; then return 1; fi
|
|
617
692
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-ok" ]; then return 0; fi
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
693
|
+
if [ "${_ATT_SUP_CAP_CACHED:-}" = "1" ]; then
|
|
694
|
+
[ "${_ATT_SUP_CAP_OK:-0}" = "1" ]; return $?
|
|
695
|
+
fi
|
|
696
|
+
_ATT_SUP_CAP_CACHED=1
|
|
697
|
+
_ATT_SUP_CAP_OK=0
|
|
698
|
+
local outf pid pgid
|
|
699
|
+
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-cap.$$")"
|
|
700
|
+
: > "$outf"
|
|
701
|
+
if _att_has_setsid; then
|
|
702
|
+
( cd "${ROOT:-/tmp}" || exit 127; exec setsid sleep 30 ) >"$outf" 2>&1 &
|
|
703
|
+
else
|
|
704
|
+
( cd "${ROOT:-/tmp}" || exit 127; exec sleep 30 ) >"$outf" 2>&1 &
|
|
705
|
+
fi
|
|
706
|
+
pid=$!
|
|
707
|
+
sleep 0.15 2>/dev/null || true
|
|
708
|
+
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
709
|
+
[ -n "$pgid" ] || pgid="$pid"
|
|
710
|
+
if _att_has_setsid; then
|
|
711
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
712
|
+
else
|
|
713
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
714
|
+
fi
|
|
715
|
+
sleep 0.25 2>/dev/null || true
|
|
716
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
717
|
+
if _att_has_setsid; then
|
|
718
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
719
|
+
else
|
|
720
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
721
|
+
fi
|
|
722
|
+
sleep 0.1 2>/dev/null || true
|
|
723
|
+
fi
|
|
724
|
+
wait "$pid" 2>/dev/null || true
|
|
725
|
+
rm -f "$outf" 2>/dev/null || true
|
|
726
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
727
|
+
return 1
|
|
728
|
+
fi
|
|
729
|
+
_ATT_SUP_CAP_OK=1
|
|
621
730
|
return 0
|
|
622
731
|
}
|
|
623
732
|
|
|
624
|
-
# Run argv array: _att_run_supervised cmd args...
|
|
625
|
-
# Sets: _ATT_RUN_RC (exit|timeout|output-cap|signal|spawn-error), _ATT_RUN_CODE (numeric),
|
|
626
|
-
# _ATT_RUN_OUT (bounded stdout), _ATT_RUN_MS
|
|
627
733
|
_att_run_supervised() {
|
|
628
|
-
|
|
629
|
-
local
|
|
630
|
-
local
|
|
631
|
-
local comb="${FLOW_ATTEST_COMBINED_CAP:-98304}"
|
|
632
|
-
local grace_s="${FLOW_ATTEST_GRACE_S:-2}"
|
|
633
|
-
local outf errf start end rc=0 pid="" pgid_file
|
|
734
|
+
local timeout_s="${FLOW_ATTEST_TIMEOUT_S:-30}" cap="${FLOW_ATTEST_OUT_CAP:-65536}"
|
|
735
|
+
local comb="${FLOW_ATTEST_COMBINED_CAP:-98304}" grace_s="${FLOW_ATTEST_GRACE_S:-2}"
|
|
736
|
+
local outf errf start end rc=0 pid="" pgid="" i use_setsid=0
|
|
634
737
|
_ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127; _ATT_RUN_OUT=""; _ATT_RUN_MS=0
|
|
635
|
-
if ! _att_supervisor_capable; then
|
|
636
|
-
_ATT_RUN_RC="spawn-error"; return 1
|
|
637
|
-
fi
|
|
738
|
+
if ! _att_supervisor_capable; then _ATT_RUN_RC="spawn-error"; return 1; fi
|
|
638
739
|
case "$timeout_s" in ''|*[!0-9]*|0) timeout_s=30 ;; esac
|
|
639
740
|
case "$cap" in ''|*[!0-9]*|0) cap=65536 ;; esac
|
|
640
741
|
case "$comb" in ''|*[!0-9]*|0) comb=98304 ;; esac
|
|
742
|
+
case "$grace_s" in ''|*[!0-9]*) grace_s=2 ;; esac
|
|
743
|
+
_att_has_setsid && use_setsid=1
|
|
641
744
|
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-out.$$")"
|
|
642
745
|
errf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-err.$$")"
|
|
643
|
-
|
|
644
|
-
: > "$outf"; : > "$errf"; : > "$pgid_file"
|
|
746
|
+
: > "$outf"; : > "$errf"
|
|
645
747
|
start="$(_att_now_s)"
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
(
|
|
649
|
-
cd "$ROOT" || exit 127
|
|
650
|
-
setsid "$@" >"$outf" 2>"$errf"
|
|
651
|
-
) &
|
|
652
|
-
pid=$!
|
|
748
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
749
|
+
( cd "$ROOT" || exit 127; exec setsid "$@" ) >"$outf" 2>"$errf" &
|
|
653
750
|
else
|
|
654
|
-
(
|
|
655
|
-
cd "$ROOT" || exit 127
|
|
656
|
-
set -m
|
|
657
|
-
"$@" >"$outf" 2>"$errf"
|
|
658
|
-
) &
|
|
659
|
-
pid=$!
|
|
751
|
+
( cd "$ROOT" || exit 127; exec "$@" ) >"$outf" 2>"$errf" &
|
|
660
752
|
fi
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if command -v pkill >/dev/null 2>&1; then
|
|
668
|
-
pkill -"$sig" -P "$pid" 2>/dev/null || true
|
|
753
|
+
pid=$!
|
|
754
|
+
i=0
|
|
755
|
+
while [ "$i" -lt 20 ]; do
|
|
756
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
757
|
+
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
758
|
+
[ -n "$pgid" ] && break
|
|
669
759
|
fi
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
760
|
+
i=$((i+1)); sleep 0.05 2>/dev/null || true
|
|
761
|
+
done
|
|
762
|
+
[ -n "$pgid" ] || pgid="$pid"
|
|
763
|
+
( sleep "$timeout_s" 2>/dev/null
|
|
674
764
|
if kill -0 "$pid" 2>/dev/null; then
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
765
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
766
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
767
|
+
sleep "$grace_s" 2>/dev/null
|
|
768
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
769
|
+
else
|
|
770
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
771
|
+
sleep "$grace_s" 2>/dev/null
|
|
772
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
773
|
+
fi
|
|
678
774
|
echo timeout > "${outf}.to"
|
|
679
|
-
fi
|
|
680
|
-
) &
|
|
775
|
+
fi ) &
|
|
681
776
|
local wd=$!
|
|
682
|
-
|
|
683
|
-
(
|
|
684
|
-
while kill -0 "$pid" 2>/dev/null; do
|
|
777
|
+
( while kill -0 "$pid" 2>/dev/null; do
|
|
685
778
|
local os es
|
|
686
779
|
os="$(wc -c < "$outf" 2>/dev/null | tr -d ' ')"
|
|
687
780
|
es="$(wc -c < "$errf" 2>/dev/null | tr -d ' ')"
|
|
688
781
|
if [ "${os:-0}" -gt "$cap" ] || [ "${es:-0}" -gt "$cap" ] || [ $(( ${os:-0} + ${es:-0} )) -gt "$comb" ]; then
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
782
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
783
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
784
|
+
sleep 1
|
|
785
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
786
|
+
else
|
|
787
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
788
|
+
sleep 1
|
|
789
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
790
|
+
fi
|
|
791
|
+
echo cap > "${outf}.cap"; break
|
|
694
792
|
fi
|
|
695
793
|
sleep 0.1 2>/dev/null || sleep 1
|
|
696
|
-
done
|
|
697
|
-
) &
|
|
794
|
+
done ) &
|
|
698
795
|
local capw=$!
|
|
699
796
|
wait "$pid" 2>/dev/null; rc=$?
|
|
700
797
|
kill "$wd" 2>/dev/null; wait "$wd" 2>/dev/null
|
|
701
798
|
kill "$capw" 2>/dev/null; wait "$capw" 2>/dev/null
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
[ "$_ATT_RUN_MS" -lt 0 ] && _ATT_RUN_MS=0
|
|
705
|
-
if [ -f "${outf}.cap" ]; then
|
|
706
|
-
_ATT_RUN_RC="output-cap"; _ATT_RUN_CODE=1
|
|
707
|
-
rm -f "${outf}.cap"
|
|
708
|
-
elif [ -f "${outf}.to" ]; then
|
|
709
|
-
_ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124
|
|
710
|
-
rm -f "${outf}.to"
|
|
711
|
-
elif [ "$rc" -eq 137 ] || [ "$rc" -eq 143 ]; then
|
|
712
|
-
if [ $((end - start)) -ge "$timeout_s" ]; then
|
|
713
|
-
_ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124
|
|
714
|
-
else
|
|
715
|
-
_ATT_RUN_RC="signal"; _ATT_RUN_CODE="$rc"
|
|
716
|
-
fi
|
|
717
|
-
elif [ "$rc" -eq 127 ]; then
|
|
718
|
-
_ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127
|
|
799
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
800
|
+
kill -KILL -"$pgid" 2>/dev/null || true
|
|
719
801
|
else
|
|
720
|
-
|
|
802
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
721
803
|
fi
|
|
804
|
+
end="$(_att_now_s)"
|
|
805
|
+
_ATT_RUN_MS=$(( (end - start) * 1000 )); [ "$_ATT_RUN_MS" -lt 0 ] && _ATT_RUN_MS=0
|
|
806
|
+
if [ -f "${outf}.cap" ]; then _ATT_RUN_RC="output-cap"; _ATT_RUN_CODE=1; rm -f "${outf}.cap"
|
|
807
|
+
elif [ -f "${outf}.to" ]; then _ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124; rm -f "${outf}.to"
|
|
808
|
+
elif [ "$rc" -eq 137 ] || [ "$rc" -eq 143 ]; then
|
|
809
|
+
if [ $((end - start)) -ge "$timeout_s" ]; then _ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124
|
|
810
|
+
else _ATT_RUN_RC="signal"; _ATT_RUN_CODE="$rc"; fi
|
|
811
|
+
elif [ "$rc" -eq 127 ]; then _ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127
|
|
812
|
+
else _ATT_RUN_RC="exit"; _ATT_RUN_CODE="$rc"; fi
|
|
722
813
|
_ATT_RUN_OUT="$(head -c "$cap" "$outf" 2>/dev/null | tr -d '\000')"
|
|
723
|
-
rm -f "$outf" "$errf"
|
|
814
|
+
rm -f "$outf" "$errf" 2>/dev/null || true
|
|
724
815
|
return 0
|
|
725
816
|
}
|
|
726
817
|
|
|
@@ -769,6 +860,7 @@ _att_receipt_status() { # $1 path -> current|missing|stale|invalid|red|blocked-a
|
|
|
769
860
|
if [ -n "$headfp" ] && [ "$headfp" != "$_ATT_R_subject_fingerprint" ]; then
|
|
770
861
|
echo "stale"; return 1
|
|
771
862
|
fi
|
|
863
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
772
864
|
;;
|
|
773
865
|
semantic_gate:card)
|
|
774
866
|
local file; file="$(resolve_card_file "$_ATT_R_subject_id")"
|
|
@@ -799,21 +891,24 @@ _att_receipt_status() { # $1 path -> current|missing|stale|invalid|red|blocked-a
|
|
|
799
891
|
echo "stale"; return 1
|
|
800
892
|
fi
|
|
801
893
|
fi
|
|
894
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
802
895
|
;;
|
|
803
896
|
live_verify:card)
|
|
804
897
|
[ "$_ATT_R_verdict" = "pass" ] || { echo "red"; return 1; }
|
|
805
|
-
local head file proj cur_fp
|
|
898
|
+
local head file proj cur_fp ofp_now owner_rel
|
|
806
899
|
head="$(_att_full_commit HEAD)"
|
|
807
900
|
file="$(resolve_card_file "$_ATT_R_subject_id")"
|
|
808
901
|
[ -f "$file" ] || { echo "stale"; return 1; }
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
fi
|
|
813
|
-
|
|
902
|
+
[ -n "$_ATT_R_subject_revision" ] || { echo "stale"; return 1; }
|
|
903
|
+
[ "$_ATT_R_subject_revision" = "$head" ] || { echo "stale"; return 1; }
|
|
904
|
+
case "${_ATT_R_owner_ref:-}" in repo:*) owner_rel="${_ATT_R_owner_ref#repo:}" ;; *) echo "stale"; return 1 ;; esac
|
|
905
|
+
if ! _att_load_owner_at_rev "$head" "$owner_rel"; then echo "stale"; return 1; fi
|
|
906
|
+
ofp_now="$(_att_owner_fingerprint "$owner_rel" "$head")" || { echo "stale"; return 1; }
|
|
907
|
+
[ "$ofp_now" = "${_ATT_R_owner_fingerprint}" ] || { echo "stale"; return 1; }
|
|
814
908
|
proj="$(awk '/^## Verify/{p=1} /^## Done-evidence/{p=1} /^## Evidence/{p=0} /^## /{if($0!~/^## (Verify|Done-evidence)/)p=0} p{print}' "$file" | tr -d '\r')"
|
|
815
|
-
cur_fp="$(_att_hash_text "live|${
|
|
909
|
+
cur_fp="$(_att_hash_text "live|${head}|${_ATT_R_target_id}|${ofp_now}|${proj}")"
|
|
816
910
|
[ "$cur_fp" = "$_ATT_R_subject_fingerprint" ] || { echo "stale"; return 1; }
|
|
911
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
817
912
|
;;
|
|
818
913
|
esac
|
|
819
914
|
case "$_ATT_R_verdict" in
|
|
@@ -991,28 +1086,26 @@ _att_mint_semantic_stage() {
|
|
|
991
1086
|
_att_is_stage "$stage" || { echo "FAIL: invalid stage '$stage'"; return 2; }
|
|
992
1087
|
_att_git_ok || { echo "FAIL: attest requires a Git repository"; return 2; }
|
|
993
1088
|
full="$(_att_full_commit "$rev")" || { echo "FAIL: bad revision"; return 2; }
|
|
994
|
-
_att_git_clean_for_attest || { echo "FAIL: dirty worktree (only .flow/
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
_att_parse_kv_file "$owner_abs" || { echo "FAIL: invalid owner manifest"; return 2; }
|
|
1089
|
+
_att_git_clean_for_attest || { echo "FAIL: dirty worktree (only .flow/, locks, status/Evidence allowed)"; return 1; }
|
|
1090
|
+
if ! _att_load_owner_at_rev "$full" "$owner_rel"; then
|
|
1091
|
+
echo "FAIL: owner/command must be committed regular files matching disk at revision (blob equality)"
|
|
1092
|
+
return 1
|
|
1093
|
+
fi
|
|
1000
1094
|
[ "$_ATT_KV_kind" = "semantic_gate" ] || { echo "FAIL: owner kind must be semantic_gate"; return 2; }
|
|
1001
1095
|
[ "$_ATT_KV_subject_id" = "$stage" ] || { echo "FAIL: owner subject_id mismatch"; return 2; }
|
|
1002
1096
|
[ "$_ATT_KV_target_id" = "none" ] && [ "$_ATT_KV_revision_oracle" = "none" ] || { echo "FAIL: semantic owner must have target/oracle none"; return 2; }
|
|
1003
|
-
case "$_ATT_KV_command" in repo:*) : ;; *) echo "FAIL: command must be repo:..."; return 2 ;; esac
|
|
1004
1097
|
local cmd_rel="${_ATT_KV_command#repo:}"
|
|
1005
|
-
[ -f "$ROOT/$cmd_rel" ] || { echo "FAIL: command not found"; return 2; }
|
|
1006
|
-
git -C "$ROOT" cat-file -e "${full}:${cmd_rel}" 2>/dev/null || { echo "FAIL: command not committed"; return 1; }
|
|
1007
1098
|
if ! _att_stage_fingerprint "$stage" "$full" >/dev/null; then echo "FAIL: stage fingerprint"; return 1; fi
|
|
1008
1099
|
fp="$_ATT_LAST_FP"
|
|
1009
1100
|
ofp="$(_att_owner_fingerprint "$owner_rel" "$full")" || { echo "FAIL: owner fingerprint"; return 1; }
|
|
1010
1101
|
|
|
1011
|
-
local lname; lname="$(_att_subject_lock_name semantic_gate "$stage")"
|
|
1102
|
+
local lname execf; lname="$(_att_subject_lock_name semantic_gate "$stage")"
|
|
1012
1103
|
_att_lock_acquire "$lname" || return 1
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1104
|
+
execf="$(_att_materialize_blob_exec "$full" "$cmd_rel")" || {
|
|
1105
|
+
_att_lock_release "$lname"; echo "FAIL: could not materialize committed producer"; return 1
|
|
1106
|
+
}
|
|
1107
|
+
_att_run_supervised "$execf" --subject-id "$stage" --subject-revision "$full" --subject-fingerprint "$fp"
|
|
1108
|
+
rm -f "$execf" 2>/dev/null || true
|
|
1016
1109
|
if [ "$_ATT_RUN_RC" != "exit" ] || [ "$_ATT_RUN_CODE" -ne 0 ]; then
|
|
1017
1110
|
_ATT_W_kind=semantic_gate; _ATT_W_subject_type=stage; _ATT_W_subject_id="$stage"
|
|
1018
1111
|
_ATT_W_subject_fingerprint="$fp"; _ATT_W_verdict=fail
|
|
@@ -1067,20 +1160,27 @@ _att_mint_semantic_card() {
|
|
|
1067
1160
|
_att_git_clean_for_attest || { echo "FAIL: dirty worktree"; return 1; }
|
|
1068
1161
|
full_base="$(_att_full_commit "$base")" || { echo "FAIL: bad base"; return 2; }
|
|
1069
1162
|
full_head="$(_att_full_commit "$rev")" || { echo "FAIL: bad revision"; return 2; }
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1163
|
+
if [ "$full_base" = "$full_head" ]; then
|
|
1164
|
+
echo "FAIL: card semantic requires non-empty base..revision (base must differ from --revision)"
|
|
1165
|
+
return 2
|
|
1166
|
+
fi
|
|
1167
|
+
if ! _att_load_owner_at_rev "$full_head" "$owner_rel"; then
|
|
1168
|
+
echo "FAIL: owner/command must be committed regular files matching disk at revision"
|
|
1169
|
+
return 1
|
|
1170
|
+
fi
|
|
1074
1171
|
[ "$_ATT_KV_kind" = "semantic_gate" ] || { echo "FAIL: owner kind"; return 2; }
|
|
1075
1172
|
[ "$_ATT_KV_subject_id" = "$id" ] || { echo "FAIL: subject_id mismatch"; return 2; }
|
|
1076
1173
|
local cmd_rel="${_ATT_KV_command#repo:}"
|
|
1077
1174
|
if ! _att_card_semantic_fingerprint "$file" "$full_base" "$full_head" >/dev/null; then echo "FAIL: fingerprint"; return 1; fi
|
|
1078
1175
|
fp="$_ATT_LAST_FP"
|
|
1079
1176
|
ofp="$(_att_owner_fingerprint "$owner_rel" "$full_head")" || { echo "FAIL: owner fp"; return 1; }
|
|
1080
|
-
local lname; lname="$(_att_subject_lock_name semantic_gate "$id")"
|
|
1177
|
+
local lname execf; lname="$(_att_subject_lock_name semantic_gate "$id")"
|
|
1081
1178
|
_att_lock_acquire "$lname" || return 1
|
|
1082
|
-
|
|
1083
|
-
|
|
1179
|
+
execf="$(_att_materialize_blob_exec "$full_head" "$cmd_rel")" || {
|
|
1180
|
+
_att_lock_release "$lname"; echo "FAIL: could not materialize committed producer"; return 1
|
|
1181
|
+
}
|
|
1182
|
+
_att_run_supervised "$execf" --subject-id "$id" --subject-revision "$full_head" --subject-fingerprint "$fp"
|
|
1183
|
+
rm -f "$execf" 2>/dev/null || true
|
|
1084
1184
|
local verdict=fail eref=none
|
|
1085
1185
|
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1086
1186
|
local sch sfp crit high
|
|
@@ -1109,16 +1209,17 @@ _att_mint_semantic_card() {
|
|
|
1109
1209
|
|
|
1110
1210
|
_att_mint_live() {
|
|
1111
1211
|
local id="$1" rev="$2" owner_rel="$3"
|
|
1112
|
-
local file full
|
|
1212
|
+
local file full cmd_rel oracle_rel target ofp cfp lname ap
|
|
1113
1213
|
id="$(_att_norm_card_id "$id")" || { echo "FAIL: bad card"; return 2; }
|
|
1114
1214
|
file="$(resolve_card_file "$id")"; [ -f "$file" ] || { echo "FAIL: card missing"; return 2; }
|
|
1115
1215
|
_att_git_ok || { echo "FAIL: git required"; return 2; }
|
|
1116
1216
|
if ! _att_supervisor_capable; then echo "FAIL: process supervisor unsupported on this platform"; return 1; fi
|
|
1217
|
+
_att_git_clean_for_attest || { echo "FAIL: dirty worktree (live mint requires clean subject)"; return 1; }
|
|
1117
1218
|
full="$(_att_full_commit "$rev")" || { echo "FAIL: bad revision"; return 2; }
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1219
|
+
if ! _att_load_owner_at_rev "$full" "$owner_rel"; then
|
|
1220
|
+
echo "FAIL: owner/command/oracle must be committed regular files matching disk at revision"
|
|
1221
|
+
return 1
|
|
1222
|
+
fi
|
|
1122
1223
|
[ "$_ATT_KV_kind" = "live_verify" ] || { echo "FAIL: owner kind must be live_verify"; return 2; }
|
|
1123
1224
|
[ "$_ATT_KV_subject_id" = "$id" ] || { echo "FAIL: subject mismatch"; return 2; }
|
|
1124
1225
|
target="$_ATT_KV_target_id"
|
|
@@ -1126,9 +1227,13 @@ _att_mint_live() {
|
|
|
1126
1227
|
case "$_ATT_KV_revision_oracle" in repo:*) : ;; *) echo "FAIL: live needs revision_oracle"; return 2 ;; esac
|
|
1127
1228
|
cmd_rel="${_ATT_KV_command#repo:}"; oracle_rel="${_ATT_KV_revision_oracle#repo:}"
|
|
1128
1229
|
ofp="$(_att_owner_fingerprint "$owner_rel" "$full")" || { echo "FAIL: owner fp"; return 1; }
|
|
1129
|
-
local
|
|
1130
|
-
|
|
1131
|
-
|
|
1230
|
+
local o_blob o_mode o_fp cmd_blob cmd_mode
|
|
1231
|
+
o_blob="$(_att_blob_at_rev "$full" "$oracle_rel")"
|
|
1232
|
+
o_mode="$(_att_path_mode_at_rev "$full" "$oracle_rel")"
|
|
1233
|
+
o_fp="$(_att_hash_text "oracle:${oracle_rel}:${o_mode}:${o_blob}")"
|
|
1234
|
+
cmd_blob="$(_att_blob_at_rev "$full" "$cmd_rel")"
|
|
1235
|
+
cmd_mode="$(_att_path_mode_at_rev "$full" "$cmd_rel")"
|
|
1236
|
+
cfp="$(_att_hash_text "argv:${cmd_rel}:${cmd_mode}:${cmd_blob}|${id}|${full}|${target}")"
|
|
1132
1237
|
local proj fp
|
|
1133
1238
|
proj="$(awk '/^## Verify/{p=1} /^## Done-evidence/{p=1} /^## Evidence/{p=0} /^## /{if($0!~/^## (Verify|Done-evidence)/)p=0} p{print}' "$file" | tr -d '\r')"
|
|
1134
1239
|
fp="$(_att_hash_text "live|${full}|${target}|${ofp}|${proj}")"
|
|
@@ -1137,20 +1242,26 @@ _att_mint_live() {
|
|
|
1137
1242
|
ap="$(_att_attempt_path "$id")"
|
|
1138
1243
|
mkdir -p "$(dirname "$ap")" 2>/dev/null || true
|
|
1139
1244
|
printf 'pid=%s\nstart=%s\nhost=%s\n' "$$" "$(_att_now_s)" "$(uname -n 2>/dev/null || echo host)" > "$ap"
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1245
|
+
local oracle_exec cmd_exec oracle_oid verdict=fail rcode
|
|
1246
|
+
oracle_exec="$(_att_materialize_blob_exec "$full" "$oracle_rel")" || {
|
|
1247
|
+
rm -f "$ap"; _att_lock_release "$lname"; echo "FAIL: materialize oracle"; return 1
|
|
1248
|
+
}
|
|
1249
|
+
_att_run_supervised "$oracle_exec" --subject-id "$id" --subject-revision "$full" --target-id "$target"
|
|
1250
|
+
rm -f "$oracle_exec" 2>/dev/null || true
|
|
1145
1251
|
rcode="$(_att_result_code_str)"
|
|
1146
1252
|
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1147
1253
|
oracle_oid="$(printf '%s' "$_ATT_RUN_OUT" | tr -d '\r\n[:space:]')"
|
|
1148
1254
|
if [ "$oracle_oid" = "$full" ]; then
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1255
|
+
cmd_exec="$(_att_materialize_blob_exec "$full" "$cmd_rel")" || true
|
|
1256
|
+
if [ -n "${cmd_exec:-}" ]; then
|
|
1257
|
+
_att_run_supervised "$cmd_exec" --subject-id "$id" --subject-revision "$full" --subject-fingerprint "$fp"
|
|
1258
|
+
rm -f "$cmd_exec" 2>/dev/null || true
|
|
1259
|
+
rcode="$(_att_result_code_str)"
|
|
1260
|
+
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1261
|
+
verdict=pass
|
|
1262
|
+
fi
|
|
1263
|
+
else
|
|
1264
|
+
rcode="spawn-error"
|
|
1154
1265
|
fi
|
|
1155
1266
|
else
|
|
1156
1267
|
rcode="exit:1"
|