@manhquy/flow-skill 0.5.0 → 0.5.1
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 +1 -1
- package/skills/flow/runner/attestations.sh +196 -128
- package/skills/flow/runner/flow.sh +5 -0
- package/skills-manifest.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Install the **flow** skill into coding agents with one command.
|
|
|
5
5
|
| | |
|
|
6
6
|
|---|---|
|
|
7
7
|
| **Package** | [`@manhquy/flow-skill`](https://www.npmjs.com/package/@manhquy/flow-skill) (installer CLI) |
|
|
8
|
-
| **Current GA** | **0.5.
|
|
8
|
+
| **Current GA** | **0.5.1** — ships skill product **v0.28.1** |
|
|
9
9
|
| **Platforms** | macOS · Linux · Windows (same Node path) |
|
|
10
10
|
| **Requirement** | Node.js **≥ 22.14** |
|
|
11
11
|
| **Source** | [github.com/manhquydev/flow-skill](https://github.com/manhquydev/flow-skill) |
|
|
@@ -89,7 +89,7 @@ npx @manhquy/flow-skill@latest --yes --all --dry-run --json
|
|
|
89
89
|
|----|--------|
|
|
90
90
|
| `npx @manhquy/flow-skill@latest` | Bare `npx @manhquy/flow-skill` (stale npx cache) |
|
|
91
91
|
| **Run** the CLI to copy the skill | `npm i` alone (package only; no skill files in agent homes) |
|
|
92
|
-
| Pin installer `@0.5.
|
|
92
|
+
| Pin installer `@0.5.1` if needed | Pin npm `@0.28.1` (skill version ≠ package version) |
|
|
93
93
|
| Prefer `@latest` | `@rc` (retired / behind) |
|
|
94
94
|
|
|
95
95
|
**Two version axes:** `package.json` version = installer; `SKILL.md` metadata = skill product. Both appear in `--help` and in the `plan` JSONL event (`version` + `skillVersion`).
|
|
@@ -137,7 +137,7 @@ rm -rf <project>/.claude/skills/flow
|
|
|
137
137
|
|---------|-----|
|
|
138
138
|
| Agent has no `/flow` after `npm i` | Run `npx @manhquy/flow-skill@latest` (install must **execute**) |
|
|
139
139
|
| Old skill after reinstall | Always use `@latest`; avoid bare package name |
|
|
140
|
-
| `No matching version found` for `@0.28.
|
|
140
|
+
| `No matching version found` for `@0.28.1` | That is the **skill** version. Use `@latest` or `@0.5.x` |
|
|
141
141
|
| Node too old | Install Node ≥ 22.14 (`nvm install 22`, etc.) |
|
|
142
142
|
| Windows `EBUSY` / `EPERM` | Close the agent holding files under the skill dir; re-run |
|
|
143
143
|
| Stale install lock | Delete `<parent>/.flow-skill.installing.lock` if reclaim fails |
|
|
@@ -147,7 +147,7 @@ rm -rf <project>/.claude/skills/flow
|
|
|
147
147
|
## JSONL (`--json`)
|
|
148
148
|
|
|
149
149
|
```jsonl
|
|
150
|
-
{"event":"plan","version":"0.5.
|
|
150
|
+
{"event":"plan","version":"0.5.1","skillVersion":"0.28.1","dryRun":false,"scope":"global","targets":["claude"]}
|
|
151
151
|
{"event":"install:start","target":"claude","dests":["~/.claude/skills/flow"]}
|
|
152
152
|
{"event":"install:done","target":"claude","dests":["~/.claude/skills/flow"],"result":"success","error":null,"warnings":[]}
|
|
153
153
|
{"event":"summary","success":true,"total":1,"attempted":1,"installed":1,"failed":0,"skipped":0,"aborted":false}
|
package/package.json
CHANGED
package/skills/flow/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ keywords: [flow, buildflow, gate, build, ship, scope, prd, contract, card, deplo
|
|
|
8
8
|
license: MIT
|
|
9
9
|
metadata:
|
|
10
10
|
author: flow-skill
|
|
11
|
-
version: "0.28.
|
|
11
|
+
version: "0.28.1"
|
|
12
12
|
attribution: "Methodology from ai20k-build-phase/buildflow (Tony, arealisticdreamer.com); durable-layer ancestry and improve-ritual spirit from repository-harness (protocol v1 EOL — flow-owned fork); agent patterns from claudekit-engineer, BMAD-METHOD. v0.22 concierge from BMAD bmad-help; forge-idea from bmad-forge-idea (MIT, BMad Code LLC)."
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -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"
|
|
@@ -611,116 +680,95 @@ revision_oracle_fingerprint: ${_ATT_W_revision_oracle_fingerprint}
|
|
|
611
680
|
}
|
|
612
681
|
|
|
613
682
|
# ---------- process supervisor ----------
|
|
614
|
-
# Probe: can we create a process group and kill it?
|
|
615
683
|
_att_supervisor_capable() {
|
|
616
684
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-unsupported" ]; then return 1; fi
|
|
617
685
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-ok" ]; then return 0; fi
|
|
618
|
-
|
|
619
|
-
if
|
|
620
|
-
|
|
686
|
+
command -v setsid >/dev/null 2>&1 || return 1
|
|
687
|
+
if [ "${_ATT_SUP_CAP_CACHED:-}" = "1" ]; then
|
|
688
|
+
[ "${_ATT_SUP_CAP_OK:-0}" = "1" ]; return $?
|
|
689
|
+
fi
|
|
690
|
+
_ATT_SUP_CAP_CACHED=1; _ATT_SUP_CAP_OK=0
|
|
691
|
+
local outf pid pgid
|
|
692
|
+
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-cap.$$")"
|
|
693
|
+
: > "$outf"
|
|
694
|
+
( cd "${ROOT:-/tmp}" || exit 127; exec setsid sleep 30 ) >"$outf" 2>&1 &
|
|
695
|
+
pid=$!
|
|
696
|
+
sleep 0.15 2>/dev/null || true
|
|
697
|
+
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
698
|
+
[ -n "$pgid" ] || pgid="$pid"
|
|
699
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
700
|
+
sleep 0.25 2>/dev/null || true
|
|
701
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
702
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
703
|
+
sleep 0.1 2>/dev/null || true
|
|
704
|
+
fi
|
|
705
|
+
wait "$pid" 2>/dev/null || true
|
|
706
|
+
rm -f "$outf" 2>/dev/null || true
|
|
707
|
+
kill -0 "$pid" 2>/dev/null && return 1
|
|
708
|
+
_ATT_SUP_CAP_OK=1
|
|
621
709
|
return 0
|
|
622
710
|
}
|
|
623
|
-
|
|
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
711
|
_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
|
|
712
|
+
local timeout_s="${FLOW_ATTEST_TIMEOUT_S:-30}" cap="${FLOW_ATTEST_OUT_CAP:-65536}"
|
|
713
|
+
local comb="${FLOW_ATTEST_COMBINED_CAP:-98304}" grace_s="${FLOW_ATTEST_GRACE_S:-2}"
|
|
714
|
+
local outf errf start end rc=0 pid="" pgid="" i
|
|
634
715
|
_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
|
|
716
|
+
if ! _att_supervisor_capable; then _ATT_RUN_RC="spawn-error"; return 1; fi
|
|
638
717
|
case "$timeout_s" in ''|*[!0-9]*|0) timeout_s=30 ;; esac
|
|
639
718
|
case "$cap" in ''|*[!0-9]*|0) cap=65536 ;; esac
|
|
640
719
|
case "$comb" in ''|*[!0-9]*|0) comb=98304 ;; esac
|
|
720
|
+
case "$grace_s" in ''|*[!0-9]*) grace_s=2 ;; esac
|
|
641
721
|
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-out.$$")"
|
|
642
722
|
errf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-err.$$")"
|
|
643
|
-
|
|
644
|
-
: > "$outf"; : > "$errf"; : > "$pgid_file"
|
|
723
|
+
: > "$outf"; : > "$errf"
|
|
645
724
|
start="$(_att_now_s)"
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
else
|
|
654
|
-
(
|
|
655
|
-
cd "$ROOT" || exit 127
|
|
656
|
-
set -m
|
|
657
|
-
"$@" >"$outf" 2>"$errf"
|
|
658
|
-
) &
|
|
659
|
-
pid=$!
|
|
660
|
-
fi
|
|
661
|
-
# Best-effort record of process group for the wrapper
|
|
662
|
-
printf '%s\n' "$pid" > "$pgid_file"
|
|
663
|
-
_att_kill_tree() {
|
|
664
|
-
local sig="$1"
|
|
665
|
-
kill -"$sig" -"$pid" 2>/dev/null || kill -"$sig" "$pid" 2>/dev/null || true
|
|
666
|
-
# also try children via pkill when available
|
|
667
|
-
if command -v pkill >/dev/null 2>&1; then
|
|
668
|
-
pkill -"$sig" -P "$pid" 2>/dev/null || true
|
|
725
|
+
( cd "$ROOT" || exit 127; exec setsid "$@" ) >"$outf" 2>"$errf" &
|
|
726
|
+
pid=$!
|
|
727
|
+
i=0
|
|
728
|
+
while [ "$i" -lt 20 ]; do
|
|
729
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
730
|
+
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
731
|
+
[ -n "$pgid" ] && break
|
|
669
732
|
fi
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
733
|
+
i=$((i+1)); sleep 0.05 2>/dev/null || true
|
|
734
|
+
done
|
|
735
|
+
[ -n "$pgid" ] || pgid="$pid"
|
|
736
|
+
( sleep "$timeout_s" 2>/dev/null
|
|
674
737
|
if kill -0 "$pid" 2>/dev/null; then
|
|
675
|
-
|
|
738
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
676
739
|
sleep "$grace_s" 2>/dev/null
|
|
677
|
-
|
|
740
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
678
741
|
echo timeout > "${outf}.to"
|
|
679
|
-
fi
|
|
680
|
-
) &
|
|
742
|
+
fi ) &
|
|
681
743
|
local wd=$!
|
|
682
|
-
|
|
683
|
-
(
|
|
684
|
-
while kill -0 "$pid" 2>/dev/null; do
|
|
744
|
+
( while kill -0 "$pid" 2>/dev/null; do
|
|
685
745
|
local os es
|
|
686
746
|
os="$(wc -c < "$outf" 2>/dev/null | tr -d ' ')"
|
|
687
747
|
es="$(wc -c < "$errf" 2>/dev/null | tr -d ' ')"
|
|
688
748
|
if [ "${os:-0}" -gt "$cap" ] || [ "${es:-0}" -gt "$cap" ] || [ $(( ${os:-0} + ${es:-0} )) -gt "$comb" ]; then
|
|
689
|
-
|
|
749
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
690
750
|
sleep 1
|
|
691
|
-
|
|
692
|
-
echo cap > "${outf}.cap"
|
|
693
|
-
break
|
|
751
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
752
|
+
echo cap > "${outf}.cap"; break
|
|
694
753
|
fi
|
|
695
754
|
sleep 0.1 2>/dev/null || sleep 1
|
|
696
|
-
done
|
|
697
|
-
) &
|
|
755
|
+
done ) &
|
|
698
756
|
local capw=$!
|
|
699
757
|
wait "$pid" 2>/dev/null; rc=$?
|
|
700
758
|
kill "$wd" 2>/dev/null; wait "$wd" 2>/dev/null
|
|
701
759
|
kill "$capw" 2>/dev/null; wait "$capw" 2>/dev/null
|
|
760
|
+
kill -KILL -"$pgid" 2>/dev/null || true
|
|
702
761
|
end="$(_att_now_s)"
|
|
703
|
-
_ATT_RUN_MS=$(( (end - start) * 1000 ))
|
|
704
|
-
[ "$
|
|
705
|
-
|
|
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"
|
|
762
|
+
_ATT_RUN_MS=$(( (end - start) * 1000 )); [ "$_ATT_RUN_MS" -lt 0 ] && _ATT_RUN_MS=0
|
|
763
|
+
if [ -f "${outf}.cap" ]; then _ATT_RUN_RC="output-cap"; _ATT_RUN_CODE=1; rm -f "${outf}.cap"
|
|
764
|
+
elif [ -f "${outf}.to" ]; then _ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124; rm -f "${outf}.to"
|
|
711
765
|
elif [ "$rc" -eq 137 ] || [ "$rc" -eq 143 ]; then
|
|
712
|
-
if [ $((end - start)) -ge "$timeout_s" ]; then
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
fi
|
|
717
|
-
elif [ "$rc" -eq 127 ]; then
|
|
718
|
-
_ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127
|
|
719
|
-
else
|
|
720
|
-
_ATT_RUN_RC="exit"; _ATT_RUN_CODE="$rc"
|
|
721
|
-
fi
|
|
766
|
+
if [ $((end - start)) -ge "$timeout_s" ]; then _ATT_RUN_RC="timeout"; _ATT_RUN_CODE=124
|
|
767
|
+
else _ATT_RUN_RC="signal"; _ATT_RUN_CODE="$rc"; fi
|
|
768
|
+
elif [ "$rc" -eq 127 ]; then _ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127
|
|
769
|
+
else _ATT_RUN_RC="exit"; _ATT_RUN_CODE="$rc"; fi
|
|
722
770
|
_ATT_RUN_OUT="$(head -c "$cap" "$outf" 2>/dev/null | tr -d '\000')"
|
|
723
|
-
rm -f "$outf" "$errf"
|
|
771
|
+
rm -f "$outf" "$errf" 2>/dev/null || true
|
|
724
772
|
return 0
|
|
725
773
|
}
|
|
726
774
|
|
|
@@ -769,6 +817,7 @@ _att_receipt_status() { # $1 path -> current|missing|stale|invalid|red|blocked-a
|
|
|
769
817
|
if [ -n "$headfp" ] && [ "$headfp" != "$_ATT_R_subject_fingerprint" ]; then
|
|
770
818
|
echo "stale"; return 1
|
|
771
819
|
fi
|
|
820
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
772
821
|
;;
|
|
773
822
|
semantic_gate:card)
|
|
774
823
|
local file; file="$(resolve_card_file "$_ATT_R_subject_id")"
|
|
@@ -799,21 +848,24 @@ _att_receipt_status() { # $1 path -> current|missing|stale|invalid|red|blocked-a
|
|
|
799
848
|
echo "stale"; return 1
|
|
800
849
|
fi
|
|
801
850
|
fi
|
|
851
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
802
852
|
;;
|
|
803
853
|
live_verify:card)
|
|
804
854
|
[ "$_ATT_R_verdict" = "pass" ] || { echo "red"; return 1; }
|
|
805
|
-
local head file proj cur_fp
|
|
855
|
+
local head file proj cur_fp ofp_now owner_rel
|
|
806
856
|
head="$(_att_full_commit HEAD)"
|
|
807
857
|
file="$(resolve_card_file "$_ATT_R_subject_id")"
|
|
808
858
|
[ -f "$file" ] || { echo "stale"; return 1; }
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
fi
|
|
813
|
-
|
|
859
|
+
[ -n "$_ATT_R_subject_revision" ] || { echo "stale"; return 1; }
|
|
860
|
+
[ "$_ATT_R_subject_revision" = "$head" ] || { echo "stale"; return 1; }
|
|
861
|
+
case "${_ATT_R_owner_ref:-}" in repo:*) owner_rel="${_ATT_R_owner_ref#repo:}" ;; *) echo "stale"; return 1 ;; esac
|
|
862
|
+
if ! _att_load_owner_at_rev "$head" "$owner_rel"; then echo "stale"; return 1; fi
|
|
863
|
+
ofp_now="$(_att_owner_fingerprint "$owner_rel" "$head")" || { echo "stale"; return 1; }
|
|
864
|
+
[ "$ofp_now" = "${_ATT_R_owner_fingerprint}" ] || { echo "stale"; return 1; }
|
|
814
865
|
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|${
|
|
866
|
+
cur_fp="$(_att_hash_text "live|${head}|${_ATT_R_target_id}|${ofp_now}|${proj}")"
|
|
816
867
|
[ "$cur_fp" = "$_ATT_R_subject_fingerprint" ] || { echo "stale"; return 1; }
|
|
868
|
+
if ! _att_require_clean_for_consume; then echo "stale"; return 1; fi
|
|
817
869
|
;;
|
|
818
870
|
esac
|
|
819
871
|
case "$_ATT_R_verdict" in
|
|
@@ -991,28 +1043,26 @@ _att_mint_semantic_stage() {
|
|
|
991
1043
|
_att_is_stage "$stage" || { echo "FAIL: invalid stage '$stage'"; return 2; }
|
|
992
1044
|
_att_git_ok || { echo "FAIL: attest requires a Git repository"; return 2; }
|
|
993
1045
|
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; }
|
|
1046
|
+
_att_git_clean_for_attest || { echo "FAIL: dirty worktree (only .flow/, locks, status/Evidence allowed)"; return 1; }
|
|
1047
|
+
if ! _att_load_owner_at_rev "$full" "$owner_rel"; then
|
|
1048
|
+
echo "FAIL: owner/command must be committed regular files matching disk at revision (blob equality)"
|
|
1049
|
+
return 1
|
|
1050
|
+
fi
|
|
1000
1051
|
[ "$_ATT_KV_kind" = "semantic_gate" ] || { echo "FAIL: owner kind must be semantic_gate"; return 2; }
|
|
1001
1052
|
[ "$_ATT_KV_subject_id" = "$stage" ] || { echo "FAIL: owner subject_id mismatch"; return 2; }
|
|
1002
1053
|
[ "$_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
1054
|
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
1055
|
if ! _att_stage_fingerprint "$stage" "$full" >/dev/null; then echo "FAIL: stage fingerprint"; return 1; fi
|
|
1008
1056
|
fp="$_ATT_LAST_FP"
|
|
1009
1057
|
ofp="$(_att_owner_fingerprint "$owner_rel" "$full")" || { echo "FAIL: owner fingerprint"; return 1; }
|
|
1010
1058
|
|
|
1011
|
-
local lname; lname="$(_att_subject_lock_name semantic_gate "$stage")"
|
|
1059
|
+
local lname execf; lname="$(_att_subject_lock_name semantic_gate "$stage")"
|
|
1012
1060
|
_att_lock_acquire "$lname" || return 1
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1061
|
+
execf="$(_att_materialize_blob_exec "$full" "$cmd_rel")" || {
|
|
1062
|
+
_att_lock_release "$lname"; echo "FAIL: could not materialize committed producer"; return 1
|
|
1063
|
+
}
|
|
1064
|
+
_att_run_supervised "$execf" --subject-id "$stage" --subject-revision "$full" --subject-fingerprint "$fp"
|
|
1065
|
+
rm -f "$execf" 2>/dev/null || true
|
|
1016
1066
|
if [ "$_ATT_RUN_RC" != "exit" ] || [ "$_ATT_RUN_CODE" -ne 0 ]; then
|
|
1017
1067
|
_ATT_W_kind=semantic_gate; _ATT_W_subject_type=stage; _ATT_W_subject_id="$stage"
|
|
1018
1068
|
_ATT_W_subject_fingerprint="$fp"; _ATT_W_verdict=fail
|
|
@@ -1067,20 +1117,27 @@ _att_mint_semantic_card() {
|
|
|
1067
1117
|
_att_git_clean_for_attest || { echo "FAIL: dirty worktree"; return 1; }
|
|
1068
1118
|
full_base="$(_att_full_commit "$base")" || { echo "FAIL: bad base"; return 2; }
|
|
1069
1119
|
full_head="$(_att_full_commit "$rev")" || { echo "FAIL: bad revision"; return 2; }
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1120
|
+
if [ "$full_base" = "$full_head" ]; then
|
|
1121
|
+
echo "FAIL: card semantic requires non-empty base..revision (base must differ from --revision)"
|
|
1122
|
+
return 2
|
|
1123
|
+
fi
|
|
1124
|
+
if ! _att_load_owner_at_rev "$full_head" "$owner_rel"; then
|
|
1125
|
+
echo "FAIL: owner/command must be committed regular files matching disk at revision"
|
|
1126
|
+
return 1
|
|
1127
|
+
fi
|
|
1074
1128
|
[ "$_ATT_KV_kind" = "semantic_gate" ] || { echo "FAIL: owner kind"; return 2; }
|
|
1075
1129
|
[ "$_ATT_KV_subject_id" = "$id" ] || { echo "FAIL: subject_id mismatch"; return 2; }
|
|
1076
1130
|
local cmd_rel="${_ATT_KV_command#repo:}"
|
|
1077
1131
|
if ! _att_card_semantic_fingerprint "$file" "$full_base" "$full_head" >/dev/null; then echo "FAIL: fingerprint"; return 1; fi
|
|
1078
1132
|
fp="$_ATT_LAST_FP"
|
|
1079
1133
|
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")"
|
|
1134
|
+
local lname execf; lname="$(_att_subject_lock_name semantic_gate "$id")"
|
|
1081
1135
|
_att_lock_acquire "$lname" || return 1
|
|
1082
|
-
|
|
1083
|
-
|
|
1136
|
+
execf="$(_att_materialize_blob_exec "$full_head" "$cmd_rel")" || {
|
|
1137
|
+
_att_lock_release "$lname"; echo "FAIL: could not materialize committed producer"; return 1
|
|
1138
|
+
}
|
|
1139
|
+
_att_run_supervised "$execf" --subject-id "$id" --subject-revision "$full_head" --subject-fingerprint "$fp"
|
|
1140
|
+
rm -f "$execf" 2>/dev/null || true
|
|
1084
1141
|
local verdict=fail eref=none
|
|
1085
1142
|
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1086
1143
|
local sch sfp crit high
|
|
@@ -1109,16 +1166,17 @@ _att_mint_semantic_card() {
|
|
|
1109
1166
|
|
|
1110
1167
|
_att_mint_live() {
|
|
1111
1168
|
local id="$1" rev="$2" owner_rel="$3"
|
|
1112
|
-
local file full
|
|
1169
|
+
local file full cmd_rel oracle_rel target ofp cfp lname ap
|
|
1113
1170
|
id="$(_att_norm_card_id "$id")" || { echo "FAIL: bad card"; return 2; }
|
|
1114
1171
|
file="$(resolve_card_file "$id")"; [ -f "$file" ] || { echo "FAIL: card missing"; return 2; }
|
|
1115
1172
|
_att_git_ok || { echo "FAIL: git required"; return 2; }
|
|
1116
1173
|
if ! _att_supervisor_capable; then echo "FAIL: process supervisor unsupported on this platform"; return 1; fi
|
|
1174
|
+
_att_git_clean_for_attest || { echo "FAIL: dirty worktree (live mint requires clean subject)"; return 1; }
|
|
1117
1175
|
full="$(_att_full_commit "$rev")" || { echo "FAIL: bad revision"; return 2; }
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1176
|
+
if ! _att_load_owner_at_rev "$full" "$owner_rel"; then
|
|
1177
|
+
echo "FAIL: owner/command/oracle must be committed regular files matching disk at revision"
|
|
1178
|
+
return 1
|
|
1179
|
+
fi
|
|
1122
1180
|
[ "$_ATT_KV_kind" = "live_verify" ] || { echo "FAIL: owner kind must be live_verify"; return 2; }
|
|
1123
1181
|
[ "$_ATT_KV_subject_id" = "$id" ] || { echo "FAIL: subject mismatch"; return 2; }
|
|
1124
1182
|
target="$_ATT_KV_target_id"
|
|
@@ -1126,9 +1184,13 @@ _att_mint_live() {
|
|
|
1126
1184
|
case "$_ATT_KV_revision_oracle" in repo:*) : ;; *) echo "FAIL: live needs revision_oracle"; return 2 ;; esac
|
|
1127
1185
|
cmd_rel="${_ATT_KV_command#repo:}"; oracle_rel="${_ATT_KV_revision_oracle#repo:}"
|
|
1128
1186
|
ofp="$(_att_owner_fingerprint "$owner_rel" "$full")" || { echo "FAIL: owner fp"; return 1; }
|
|
1129
|
-
local
|
|
1130
|
-
|
|
1131
|
-
|
|
1187
|
+
local o_blob o_mode o_fp cmd_blob cmd_mode
|
|
1188
|
+
o_blob="$(_att_blob_at_rev "$full" "$oracle_rel")"
|
|
1189
|
+
o_mode="$(_att_path_mode_at_rev "$full" "$oracle_rel")"
|
|
1190
|
+
o_fp="$(_att_hash_text "oracle:${oracle_rel}:${o_mode}:${o_blob}")"
|
|
1191
|
+
cmd_blob="$(_att_blob_at_rev "$full" "$cmd_rel")"
|
|
1192
|
+
cmd_mode="$(_att_path_mode_at_rev "$full" "$cmd_rel")"
|
|
1193
|
+
cfp="$(_att_hash_text "argv:${cmd_rel}:${cmd_mode}:${cmd_blob}|${id}|${full}|${target}")"
|
|
1132
1194
|
local proj fp
|
|
1133
1195
|
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
1196
|
fp="$(_att_hash_text "live|${full}|${target}|${ofp}|${proj}")"
|
|
@@ -1137,20 +1199,26 @@ _att_mint_live() {
|
|
|
1137
1199
|
ap="$(_att_attempt_path "$id")"
|
|
1138
1200
|
mkdir -p "$(dirname "$ap")" 2>/dev/null || true
|
|
1139
1201
|
printf 'pid=%s\nstart=%s\nhost=%s\n' "$$" "$(_att_now_s)" "$(uname -n 2>/dev/null || echo host)" > "$ap"
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1202
|
+
local oracle_exec cmd_exec oracle_oid verdict=fail rcode
|
|
1203
|
+
oracle_exec="$(_att_materialize_blob_exec "$full" "$oracle_rel")" || {
|
|
1204
|
+
rm -f "$ap"; _att_lock_release "$lname"; echo "FAIL: materialize oracle"; return 1
|
|
1205
|
+
}
|
|
1206
|
+
_att_run_supervised "$oracle_exec" --subject-id "$id" --subject-revision "$full" --target-id "$target"
|
|
1207
|
+
rm -f "$oracle_exec" 2>/dev/null || true
|
|
1145
1208
|
rcode="$(_att_result_code_str)"
|
|
1146
1209
|
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1147
1210
|
oracle_oid="$(printf '%s' "$_ATT_RUN_OUT" | tr -d '\r\n[:space:]')"
|
|
1148
1211
|
if [ "$oracle_oid" = "$full" ]; then
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1212
|
+
cmd_exec="$(_att_materialize_blob_exec "$full" "$cmd_rel")" || true
|
|
1213
|
+
if [ -n "${cmd_exec:-}" ]; then
|
|
1214
|
+
_att_run_supervised "$cmd_exec" --subject-id "$id" --subject-revision "$full" --subject-fingerprint "$fp"
|
|
1215
|
+
rm -f "$cmd_exec" 2>/dev/null || true
|
|
1216
|
+
rcode="$(_att_result_code_str)"
|
|
1217
|
+
if [ "$_ATT_RUN_RC" = "exit" ] && [ "$_ATT_RUN_CODE" -eq 0 ]; then
|
|
1218
|
+
verdict=pass
|
|
1219
|
+
fi
|
|
1220
|
+
else
|
|
1221
|
+
rcode="spawn-error"
|
|
1154
1222
|
fi
|
|
1155
1223
|
else
|
|
1156
1224
|
rcode="exit:1"
|
|
@@ -1814,6 +1814,11 @@ cmd_auto() {
|
|
|
1814
1814
|
echo " mint: flow attest semantic --stage 05-contract --revision HEAD --owner <committed-manifest>"
|
|
1815
1815
|
_att_auto_lock_release; return 1
|
|
1816
1816
|
fi
|
|
1817
|
+
# Cleanliness last among preflight diagnostics so risk/receipt messages still print.
|
|
1818
|
+
if ! _att_require_clean_for_consume; then
|
|
1819
|
+
echo "FAIL: dirty worktree — auto activation requires clean tree (except .flow/, locks, status/Evidence)."
|
|
1820
|
+
_att_auto_lock_release; return 1
|
|
1821
|
+
fi
|
|
1817
1822
|
local cfp; cfp="$_ATT_R_subject_fingerprint"
|
|
1818
1823
|
local rfp; rfp="$(_att_risk_fingerprint)"
|
|
1819
1824
|
local by; by="$(_att_sanitize_label "${FLOW_SESSION_ID:-session}")"
|