@sabaiway/agent-workflow-kit 5.1.0 → 5.3.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/CHANGELOG.md +95 -0
- package/SKILL.md +13 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +14 -3
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +220 -30
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +264 -8
- package/bridges/antigravity-cli-bridge/bin/agy.sh +12 -2
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +18 -0
- package/bridges/antigravity-cli-bridge/capability.json +19 -13
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +17 -7
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +156 -36
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +228 -4
- package/bridges/codex-cli-bridge/bin/codex-review.sh +205 -34
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +276 -5
- package/bridges/codex-cli-bridge/capability.json +10 -7
- package/bridges/codex-cli-bridge/references/driving-codex.md +7 -5
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +26 -12
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/flow-writer.md +37 -0
- package/references/modes/gates.md +4 -4
- package/references/modes/procedures.md +4 -2
- package/references/modes/receipt-deadline.md +16 -0
- package/references/modes/review-state.md +1 -1
- package/references/modes/set-flow.md +22 -0
- package/references/scripts/archive-decisions.mjs +340 -15
- package/references/scripts/archive-decisions.test.mjs +522 -2
- package/tools/cheap-agents.mjs +8 -2
- package/tools/commands.mjs +24 -2
- package/tools/commit-guard.mjs +44 -9
- package/tools/core-evidence.mjs +25 -22
- package/tools/detect-backends.mjs +33 -11
- package/tools/dispatch-record.mjs +926 -0
- package/tools/doc-parity.mjs +21 -6
- package/tools/flow-check.mjs +842 -0
- package/tools/flow-record.mjs +795 -0
- package/tools/flow-store-read.mjs +114 -0
- package/tools/flow-store.mjs +1178 -0
- package/tools/flow-writer.mjs +1265 -0
- package/tools/fs-read-nofollow.mjs +128 -0
- package/tools/gates-declaration.mjs +184 -0
- package/tools/gates-init.mjs +59 -17
- package/tools/orchestration-config.mjs +87 -10
- package/tools/orchestration-write.mjs +3 -3
- package/tools/plan-files.mjs +35 -0
- package/tools/procedures.mjs +75 -11
- package/tools/receipt-deadline.mjs +242 -0
- package/tools/recipes.mjs +21 -0
- package/tools/repo-lex.mjs +22 -0
- package/tools/review-state.mjs +240 -80
- package/tools/run-gates.mjs +361 -139
- package/tools/set-flow.mjs +465 -0
- package/tools/velocity-profile.mjs +8 -2
|
@@ -42,8 +42,8 @@ case "${1:-}" in
|
|
|
42
42
|
codex-review — read-only ADVISORY review by the OpenAI Codex CLI (subscription-only; frontier model at max effort).
|
|
43
43
|
|
|
44
44
|
Usage:
|
|
45
|
-
codex-review plan <plan-file>
|
|
46
|
-
codex-review code [extra focus...]
|
|
45
|
+
codex-review plan <plan-file> [--nonce <n>]
|
|
46
|
+
codex-review code [--nonce <n>] [extra focus...]
|
|
47
47
|
|
|
48
48
|
Grounding:
|
|
49
49
|
automatic — the wrapper precomputes the full working-tree change set (repo map,
|
|
@@ -70,7 +70,17 @@ Receipt:
|
|
|
70
70
|
wrapper minted it; re-run the review), one stderr banner line states the same posture, and a
|
|
71
71
|
posture value carrying control bytes refuses pre-spend in every mode; a run whose final message
|
|
72
72
|
carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or missing output included —
|
|
73
|
-
exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error);
|
|
73
|
+
exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the
|
|
74
|
+
dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument
|
|
75
|
+
equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with
|
|
76
|
+
different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything
|
|
77
|
+
else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce,
|
|
78
|
+
fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json;
|
|
79
|
+
atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse
|
|
80
|
+
loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append,
|
|
81
|
+
so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less
|
|
82
|
+
invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field
|
|
83
|
+
still changes with each bridge release); a write
|
|
74
84
|
failure warns, never fails the review
|
|
75
85
|
|
|
76
86
|
Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
|
|
@@ -80,9 +90,10 @@ Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins ov
|
|
|
80
90
|
CODEX_REVIEW_MAX_TOTAL_BYTES — inline-payload cap, integer bytes 1..100000000 (default 1500000); above it the diff rides via a git-dir temp file
|
|
81
91
|
|
|
82
92
|
Notes:
|
|
83
|
-
the review posture banner appends a banner-only timeout=<duration
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
the review posture banner appends a banner-only timeout=<duration> field — exactly the duration
|
|
94
|
+
handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary
|
|
95
|
+
exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer
|
|
96
|
+
happen), and the field never enters the receipt posture or the D5 banner↔receipt parity
|
|
86
97
|
quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated
|
|
87
98
|
posture; a prose re-type drifts
|
|
88
99
|
|
|
@@ -95,7 +106,7 @@ Honesty + posture (D4/D5):
|
|
|
95
106
|
posture banner verbatim when labeling this dispatch. A posture value carrying control bytes
|
|
96
107
|
refuses pre-spend in every mode.
|
|
97
108
|
|
|
98
|
-
Environment: CODEX_REVIEW_SCHEMA=1 (structured JSON findings), CODEX_HARD_TIMEOUT (seconds, default 1800), CODEX_PROBE=1 (throwaway probe only), AW_REVIEW_RECEIPTS (receipt file override).
|
|
109
|
+
Environment: CODEX_REVIEW_SCHEMA=1 (structured JSON findings), CODEX_HARD_TIMEOUT (seconds, default 1800), CODEX_PROBE=1 (throwaway probe only), AW_REVIEW_RECEIPTS (receipt file override), AW_REVIEW_NONCE (dispatch nonce — mints the finding manifest beside the receipt; the --nonce <n> flag is its plain-argument equivalent).
|
|
99
110
|
Requires at run time: the codex CLI on PATH, a ChatGPT-subscription login, a git work tree with a root AGENTS.md (--help needs none of these).
|
|
100
111
|
HELP
|
|
101
112
|
exit 0
|
|
@@ -213,8 +224,9 @@ aw_apply_settings
|
|
|
213
224
|
|
|
214
225
|
# --- Effective-timeout resolver (D5 banner honesty; AD-061) --------------------
|
|
215
226
|
# ONE rule, both bridges: the posture banner prints EXACTLY the duration handed to timeout(1) —
|
|
216
|
-
# an integer-seconds value rendered with the `s` suffix, a duration string verbatim
|
|
217
|
-
#
|
|
227
|
+
# an integer-seconds value rendered with the `s` suffix, a duration string verbatim; without a
|
|
228
|
+
# capping binary the EXEC wrappers print `timeout=uncapped` and run, while the REVIEW wrappers
|
|
229
|
+
# refuse pre-spend (fail-closed preflight) — never a fabricated number.
|
|
218
230
|
# The EFFECTIVE value (env included — closing the aw_settings_valid env bypass) is validated by
|
|
219
231
|
# the same per-key rule as the settings file, plus a 7-digit integer-part bound (overflow); an
|
|
220
232
|
# invalid value warns + falls back to the built-in default — a typo never silently masquerades
|
|
@@ -266,7 +278,7 @@ DEFAULT_CODEX_EFFORT="xhigh"
|
|
|
266
278
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
267
279
|
# version (drift-guarded by codex-review.test.mjs against capability.json).
|
|
268
280
|
AW_RECEIPT_BACKEND="codex"
|
|
269
|
-
AW_BRIDGE_VERSION="3.
|
|
281
|
+
AW_BRIDGE_VERSION="3.4.0"
|
|
270
282
|
CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
|
|
271
283
|
CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
|
|
272
284
|
# Generous hard cap for a slow xhigh review (subscription latency varies).
|
|
@@ -297,6 +309,14 @@ for _posture_pair in "CODEX_MODEL=$CODEX_MODEL" "CODEX_EFFORT=$CODEX_EFFORT" "CO
|
|
|
297
309
|
exit 2
|
|
298
310
|
fi
|
|
299
311
|
done
|
|
312
|
+
# The dispatch nonce seam (flow-orchestration Decision 2/P5): validated pre-spend under the SAFE
|
|
313
|
+
# grammar — a nonce that would escape the derived manifest name refuses before any CLI run. The
|
|
314
|
+
# bracket expression ENUMERATES the ASCII set (no ranges): a range like A-Z is locale-collation-
|
|
315
|
+
# dependent and could admit a non-ASCII nonce the kit's JS reader then refuses.
|
|
316
|
+
if [[ -n "${AW_REVIEW_NONCE:-}" && ! "${AW_REVIEW_NONCE}" =~ ^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-]{1,64}$ ]]; then
|
|
317
|
+
echo "error: AW_REVIEW_NONCE fails the safe nonce grammar ([A-Za-z0-9._-]{1,64}) — the derived manifest name would be unsafe; fix the nonce and re-run." >&2
|
|
318
|
+
exit 2
|
|
319
|
+
fi
|
|
300
320
|
CODEX_HARD_TIMEOUT="$(aw_effective_timeout CODEX_HARD_TIMEOUT 1800)"
|
|
301
321
|
if [[ -n "$CODEX_SERVICE_TIER" ]] && ! aw_settings_valid CODEX_SERVICE_TIER "$CODEX_SERVICE_TIER"; then
|
|
302
322
|
echo "warning: CODEX_SERVICE_TIER='$CODEX_SERVICE_TIER' is not a supported service tier ('priority') — running on the standard tier." >&2
|
|
@@ -312,11 +332,19 @@ CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
|
|
|
312
332
|
# The control-byte screen already ran above (BEFORE tier validation); the banner states the
|
|
313
333
|
# EFFECTIVE posture (post tier validation) and the receipt records the same fields — except
|
|
314
334
|
# `timeout`, a BANNER-ONLY informational field (AD-061): it prints exactly the duration handed
|
|
315
|
-
# to timeout(1)
|
|
335
|
+
# to timeout(1) (the fail-closed preflight below guarantees a capping binary) and never enters
|
|
336
|
+
# the receipt.
|
|
316
337
|
# aw_resolve_timeout_bin: builtin type -P (an exported function can shadow neither `timeout` nor
|
|
317
338
|
# `type` itself), normalized to an ABSOLUTE path fail-closed; the dispatch below reuses this SAME
|
|
318
|
-
# resolved path (banner and run never make independent conclusions).
|
|
339
|
+
# resolved path (banner and run never make independent conclusions). The preflight fails CLOSED
|
|
340
|
+
# without a capping binary (flow-orchestration #26): an uncapped review run is refused pre-spend.
|
|
319
341
|
timeout_bin="$(aw_resolve_timeout_bin)"
|
|
342
|
+
if [[ -z "$timeout_bin" ]]; then
|
|
343
|
+
echo "error: no 'timeout'/'gtimeout' binary on PATH — the hard-timeout preflight fails CLOSED:" >&2
|
|
344
|
+
echo " an uncapped review run is refused before any CLI spend. Install coreutils (timeout;" >&2
|
|
345
|
+
echo " on macOS: brew install coreutils for gtimeout), then re-run." >&2
|
|
346
|
+
exit 127
|
|
347
|
+
fi
|
|
320
348
|
aw_timeout_banner="$(aw_timeout_label "$timeout_bin" "$CODEX_HARD_TIMEOUT")"
|
|
321
349
|
echo "review posture: model=$CODEX_MODEL effort=$CODEX_EFFORT tier=${CODEX_SERVICE_TIER:-standard} timeout=$aw_timeout_banner" >&2
|
|
322
350
|
|
|
@@ -540,7 +568,104 @@ posture_json() {
|
|
|
540
568
|
"$(json_string_escape "$CODEX_MODEL")" "$(json_string_escape "$CODEX_EFFORT")" "$tier_json"
|
|
541
569
|
}
|
|
542
570
|
|
|
543
|
-
#
|
|
571
|
+
# write_finding_manifest <receipts-path> <fingerprint|""> <findings-file> — the wrapper-minted
|
|
572
|
+
# finding MANIFEST (flow-orchestration Decision 2 / P5 / P24-25), minted ONLY when the dispatch
|
|
573
|
+
# nonce seam AW_REVIEW_NONCE is supplied: {schema, backend, nonce, fingerprint, findings} lands
|
|
574
|
+
# beside the receipts file under the {backend, nonce}-derived name. The write is ATOMIC (temp +
|
|
575
|
+
# hard-link publish) and NO-CLOBBER: a byte-identical re-write is an idempotent no-op, different
|
|
576
|
+
# bytes refuse loudly. Returns non-zero on ANY failure — the caller then EXCLUDES the receipt
|
|
577
|
+
# append, so a nonce-supplied dispatch can never land a receipt without its readable manifest.
|
|
578
|
+
# A nonce-less invocation returns 0 untouched (no nonce field, no manifest — the receipt
|
|
579
|
+
# otherwise proceeds unchanged).
|
|
580
|
+
write_finding_manifest() {
|
|
581
|
+
local receipts="$1" fingerprint="$2" findings_file="$3" nonce="${AW_REVIEW_NONCE:-}"
|
|
582
|
+
[[ -n "$nonce" ]] || return 0
|
|
583
|
+
if [[ ! "$nonce" =~ ^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-]{1,64}$ ]]; then
|
|
584
|
+
echo "error: AW_REVIEW_NONCE fails the safe nonce grammar ([A-Za-z0-9._-]{1,64}) — the derived manifest name would be unsafe; NO manifest was written." >&2
|
|
585
|
+
return 1
|
|
586
|
+
fi
|
|
587
|
+
if [[ -z "$findings_file" || ! -f "$findings_file" ]]; then
|
|
588
|
+
echo "error: no captured findings file to mint the manifest from — NO manifest was written." >&2
|
|
589
|
+
return 1
|
|
590
|
+
fi
|
|
591
|
+
local manifest mint_rc
|
|
592
|
+
manifest="$(dirname -- "$receipts")/agent-workflow-finding-manifest-${AW_RECEIPT_BACKEND}-${nonce}.json"
|
|
593
|
+
# The WHOLE mint core rides ONE node script (a family floor; the verdict parse already leans on
|
|
594
|
+
# it): FATAL UTF-8 compose (BOM kept — invalid bytes refuse rather than silently mutate the
|
|
595
|
+
# digest domain), an UNPREDICTABLE sibling temp opened with "wx" (O_CREAT|O_EXCL — a planted
|
|
596
|
+
# node at the name refuses, and ONLY a temp we provably created is ever unlinked), a hard-link
|
|
597
|
+
# publish (atomic no-clobber), and an EEXIST loser judged through ONE O_NOFOLLOW|O_NONBLOCK
|
|
598
|
+
# descriptor whose fstat must say REGULAR before the same-fd byte compare — no TOCTOU window,
|
|
599
|
+
# no symlink read-through, no FIFO hang. Exit: 0 minted or byte-identical no-op; 3 different
|
|
600
|
+
# bytes or a non-regular/symlink manifest; 5 minted-but-temp-left (SUCCESS with the orphan path
|
|
601
|
+
# on stdout — the caller warns loudly, never silently); 1 unreadable/non-UTF-8 findings or an
|
|
602
|
+
# fs failure.
|
|
603
|
+
local mint_out
|
|
604
|
+
mint_out="$( umask 077; node -e '
|
|
605
|
+
const fs = require("node:fs");
|
|
606
|
+
const { join, dirname, basename } = require("node:path");
|
|
607
|
+
const { randomBytes } = require("node:crypto");
|
|
608
|
+
const [file, backend, nonce, fingerprint, manifest] = process.argv.slice(1);
|
|
609
|
+
let code = 1;
|
|
610
|
+
let tmp = null;
|
|
611
|
+
try {
|
|
612
|
+
const findings = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(fs.readFileSync(file));
|
|
613
|
+
const bytes = Buffer.from(`${JSON.stringify({ schema: 1, backend, nonce, fingerprint: fingerprint || null, findings })}\n`);
|
|
614
|
+
const candidate = join(dirname(manifest), `.${basename(manifest)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
|
|
615
|
+
const fd = fs.openSync(candidate, "wx", 0o600);
|
|
616
|
+
tmp = candidate;
|
|
617
|
+
try {
|
|
618
|
+
fs.writeFileSync(fd, bytes);
|
|
619
|
+
} finally {
|
|
620
|
+
fs.closeSync(fd);
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
fs.linkSync(tmp, manifest);
|
|
624
|
+
code = 0;
|
|
625
|
+
} catch (err) {
|
|
626
|
+
if (err && err.code === "EEXIST") {
|
|
627
|
+
try {
|
|
628
|
+
const mfd = fs.openSync(manifest, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
629
|
+
try {
|
|
630
|
+
code = !fs.fstatSync(mfd).isFile() ? 3 : fs.readFileSync(mfd).equals(bytes) ? 0 : 3;
|
|
631
|
+
} finally {
|
|
632
|
+
fs.closeSync(mfd);
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
code = 3;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
code = 1;
|
|
641
|
+
}
|
|
642
|
+
if (tmp !== null) { try { fs.unlinkSync(tmp); } catch { if (code === 0) code = 5; process.stdout.write(tmp); } }
|
|
643
|
+
process.exit(code);
|
|
644
|
+
' "$findings_file" "$AW_RECEIPT_BACKEND" "$nonce" "$fingerprint" "$manifest" 2>/dev/null )"
|
|
645
|
+
mint_rc=$?
|
|
646
|
+
if [[ $mint_rc -eq 5 ]]; then
|
|
647
|
+
echo "warning: the finding manifest was minted, but its temporary sibling could not be removed —" >&2
|
|
648
|
+
echo " orphan left at: ${mint_out} — remove it by hand (the manifest and the receipt are intact)." >&2
|
|
649
|
+
return 0
|
|
650
|
+
fi
|
|
651
|
+
if [[ $mint_rc -eq 3 ]]; then
|
|
652
|
+
echo "error: the finding manifest $manifest already exists with DIFFERENT bytes or is not a regular file — no-clobber refuses loudly (one dispatch identity, one manifest)." >&2
|
|
653
|
+
if [[ -n "$mint_out" ]]; then
|
|
654
|
+
echo " orphan left at: ${mint_out} — its temporary sibling could not be removed either; remove it by hand." >&2
|
|
655
|
+
fi
|
|
656
|
+
return 1
|
|
657
|
+
fi
|
|
658
|
+
if [[ $mint_rc -ne 0 ]]; then
|
|
659
|
+
echo "error: could not compose or write the finding manifest (unreadable or non-UTF-8 findings, or an fs failure) — NO manifest was written." >&2
|
|
660
|
+
if [[ -n "$mint_out" ]]; then
|
|
661
|
+
echo " orphan left at: ${mint_out} — its temporary sibling could not be removed either; remove it by hand." >&2
|
|
662
|
+
fi
|
|
663
|
+
return 1
|
|
664
|
+
fi
|
|
665
|
+
return 0
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
# write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false] [delivery|""] [findings-file]
|
|
544
669
|
# Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
|
|
545
670
|
# to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
|
|
546
671
|
# git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
|
|
@@ -550,8 +675,10 @@ posture_json() {
|
|
|
550
675
|
# marker is written ALWAYS, true or false: the receipt SELF-DECLARES, so the gate reads the fact
|
|
551
676
|
# itself instead of inferring it from this wrapper's version (which bumps in a different release
|
|
552
677
|
# phase). Silence is not a declaration — an unmarked receipt is untrustworthy and the gate rejects it.
|
|
678
|
+
# The 9th argument feeds the finding-manifest mint: on a nonce-supplied dispatch the manifest is
|
|
679
|
+
# minted FIRST (atomic, no-clobber, ORDERED) and a failed mint EXCLUDES the receipt append.
|
|
553
680
|
write_review_receipt() {
|
|
554
|
-
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}" delivery="${8:-}"
|
|
681
|
+
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}" delivery="${8:-}" findings_file="${9:-}"
|
|
555
682
|
local receipts="${AW_REVIEW_RECEIPTS:-}"
|
|
556
683
|
if [[ -z "$receipts" ]]; then
|
|
557
684
|
local receipt_git_dir
|
|
@@ -561,13 +688,24 @@ write_review_receipt() {
|
|
|
561
688
|
fi
|
|
562
689
|
receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
|
|
563
690
|
fi
|
|
564
|
-
|
|
691
|
+
if ! write_finding_manifest "$receipts" "$fingerprint" "$findings_file"; then
|
|
692
|
+
echo "warning: the finding manifest could not be minted — the receipt append is EXCLUDED (a" >&2
|
|
693
|
+
echo " nonce-supplied dispatch never lands a receipt without its readable manifest);" >&2
|
|
694
|
+
echo " the review itself succeeded — re-run it to mint the pair." >&2
|
|
695
|
+
return 0
|
|
696
|
+
fi
|
|
697
|
+
# A nonce-SUPPLIED dispatch stamps its nonce into the receipt too (the flow round-land matcher
|
|
698
|
+
# requires exact {backend, nonce} equality — dispatch identity end-to-end); the nonce is
|
|
699
|
+
# grammar-safe by the pre-spend check, and a nonce-less receipt stays BYTE-EXACT (the frozen
|
|
700
|
+
# compatibility floor).
|
|
701
|
+
local line probe_field=',"probe":false' delivery_field="" nonce_field=""
|
|
565
702
|
if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
|
|
566
703
|
if [[ -n "$delivery" ]]; then delivery_field=",\"delivery\":\"$delivery\""; fi
|
|
567
|
-
|
|
704
|
+
if [[ -n "${AW_REVIEW_NONCE:-}" ]]; then nonce_field=",\"nonce\":\"${AW_REVIEW_NONCE}\""; fi
|
|
705
|
+
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"posture":%s%s%s}' \
|
|
568
706
|
"$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
|
|
569
707
|
"$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
|
|
570
|
-
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)" "$delivery_field")"
|
|
708
|
+
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)" "$delivery_field" "$nonce_field")"
|
|
571
709
|
if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
|
|
572
710
|
echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
|
|
573
711
|
echo " the review-state gate will read the current tree as un-receipted." >&2
|
|
@@ -655,6 +793,48 @@ assemble_code_diff() {
|
|
|
655
793
|
mode="${1:-}"
|
|
656
794
|
shift || true
|
|
657
795
|
|
|
796
|
+
# --nonce <v> — the plain-argument lane onto the EXISTING AW_REVIEW_NONCE seam (flow
|
|
797
|
+
# FLOW-NONCE-DISPATCH-LANE): a host whose dispatch policy has no env-prefix lane passes the
|
|
798
|
+
# round-open nonce as an argument instead. Stripped for EVERY mode before mode handling
|
|
799
|
+
# (plan-mode consult dispatches carry nonces too); the flag and a non-empty env value must
|
|
800
|
+
# AGREE — two disagreeing sources would mint an ambiguous dispatch identity (fail closed).
|
|
801
|
+
# Grammar screen identical to the env screen above (enumerated ASCII — locale-independent),
|
|
802
|
+
# pre-spend. The NEXT argument is taken unconditionally (only end-of-args refuses here): the
|
|
803
|
+
# closed grammar is the single validity door, so a grammar-valid leading-dash value is legal —
|
|
804
|
+
# and presence rides its own flag, so an EMPTY value still hits the grammar screen and a
|
|
805
|
+
# duplicate after it still refuses.
|
|
806
|
+
nonce_flag=""
|
|
807
|
+
nonce_flag_set=0
|
|
808
|
+
scan_args=()
|
|
809
|
+
while [[ $# -gt 0 ]]; do
|
|
810
|
+
case "$1" in
|
|
811
|
+
--nonce)
|
|
812
|
+
if [[ $# -lt 2 ]]; then
|
|
813
|
+
echo "error: --nonce needs a value; got '<end of args>'." >&2
|
|
814
|
+
exit 2
|
|
815
|
+
fi
|
|
816
|
+
if [[ "$nonce_flag_set" == "1" ]]; then
|
|
817
|
+
echo "error: duplicate --nonce — one dispatch carries one nonce." >&2
|
|
818
|
+
exit 2
|
|
819
|
+
fi
|
|
820
|
+
nonce_flag="$2"; nonce_flag_set=1; shift 2 ;;
|
|
821
|
+
*)
|
|
822
|
+
scan_args+=("$1"); shift ;;
|
|
823
|
+
esac
|
|
824
|
+
done
|
|
825
|
+
if [[ "$nonce_flag_set" == "1" ]]; then
|
|
826
|
+
if [[ ! "$nonce_flag" =~ ^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-]{1,64}$ ]]; then
|
|
827
|
+
echo "error: --nonce fails the safe nonce grammar ([A-Za-z0-9._-]{1,64}) — the derived manifest name would be unsafe; fix the nonce and re-run." >&2
|
|
828
|
+
exit 2
|
|
829
|
+
fi
|
|
830
|
+
if [[ -n "${AW_REVIEW_NONCE:-}" && "${AW_REVIEW_NONCE}" != "$nonce_flag" ]]; then
|
|
831
|
+
echo "error: --nonce disagrees with the AW_REVIEW_NONCE environment value — one dispatch carries one nonce; drop one source and re-run." >&2
|
|
832
|
+
exit 2
|
|
833
|
+
fi
|
|
834
|
+
AW_REVIEW_NONCE="$nonce_flag"
|
|
835
|
+
fi
|
|
836
|
+
set -- "${scan_args[@]+"${scan_args[@]}"}"
|
|
837
|
+
|
|
658
838
|
REVIEW_ARTIFACT=""
|
|
659
839
|
REVIEW_FINGERPRINT=""
|
|
660
840
|
|
|
@@ -711,7 +891,7 @@ case "$mode" in
|
|
|
711
891
|
fi
|
|
712
892
|
;;
|
|
713
893
|
*)
|
|
714
|
-
echo "usage: $0 plan <plan-file> | code [extra focus...]" >&2
|
|
894
|
+
echo "usage: $0 plan <plan-file> [--nonce <n>] | code [--nonce <n>] [extra focus...]" >&2
|
|
715
895
|
exit 2
|
|
716
896
|
;;
|
|
717
897
|
esac
|
|
@@ -780,25 +960,16 @@ fence_env=(env
|
|
|
780
960
|
CODEX_HOME="$real_codex_home")
|
|
781
961
|
|
|
782
962
|
# --- Hard wall-clock cap via timeout(1) (gtimeout on macOS) -------------------
|
|
783
|
-
# $timeout_bin was resolved ONCE (absolute path, type -P) at the banner emit above
|
|
784
|
-
# reuses the exact binary the
|
|
785
|
-
|
|
786
|
-
echo "warning: no 'timeout'/'gtimeout' on PATH — running codex WITHOUT a hard wall-clock cap" >&2
|
|
787
|
-
echo " (install coreutils to enable CODEX_HARD_TIMEOUT=$CODEX_HARD_TIMEOUT)." >&2
|
|
788
|
-
fi
|
|
963
|
+
# $timeout_bin was resolved ONCE (absolute path, type -P) at the banner emit above and is
|
|
964
|
+
# guaranteed non-empty there (the fail-closed preflight) — the run reuses the exact binary the
|
|
965
|
+
# banner reported.
|
|
789
966
|
|
|
790
967
|
# Run codex with the current ${codex_cmd[@]}; capture rc, stream → $trace.
|
|
791
968
|
invoke_codex() {
|
|
792
969
|
set +e
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
rc=$?
|
|
797
|
-
else
|
|
798
|
-
printf '%s' "$prompt" \
|
|
799
|
-
| "${fence_env[@]}" "${codex_cmd[@]}" >"$trace" 2>&1
|
|
800
|
-
rc=$?
|
|
801
|
-
fi
|
|
970
|
+
printf '%s' "$prompt" \
|
|
971
|
+
| "${fence_env[@]}" "$timeout_bin" --kill-after=15s "$CODEX_HARD_TIMEOUT" "${codex_cmd[@]}" >"$trace" 2>&1
|
|
972
|
+
rc=$?
|
|
802
973
|
set -e
|
|
803
974
|
}
|
|
804
975
|
|
|
@@ -875,4 +1046,4 @@ fi
|
|
|
875
1046
|
# codex is grounded by construction (AGENTS.md auto-merge + the precomputed change set): grounded
|
|
876
1047
|
# true, factsHash null (native grounding — no separate facts payload exists). Every codex run is a
|
|
877
1048
|
# full fresh run (one-shot, no resume) → fresh:true. $REVIEW_PROBE marks a guards-relaxed run.
|
|
878
|
-
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" true "" "$REVIEW_PROBE"
|
|
1049
|
+
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" true "" "$REVIEW_PROBE" "" "$out"
|