@sabaiway/agent-workflow-kit 2.1.0 → 3.1.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/README.md +7 -5
  3. package/SKILL.md +16 -8
  4. package/bin/install.mjs +11 -2
  5. package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +163 -0
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
  8. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +80 -15
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +171 -22
  10. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +59 -41
  11. package/bridges/antigravity-cli-bridge/capability.json +3 -2
  12. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  13. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +67 -22
  14. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +156 -0
  15. package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
  16. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +158 -27
  17. package/bridges/codex-cli-bridge/capability.json +3 -2
  18. package/capability.json +1 -1
  19. package/migrations/3.0.0-hardened-core-loop.md +44 -0
  20. package/migrations/README.md +1 -1
  21. package/package.json +2 -2
  22. package/references/hooks/gate-approve.mjs +1 -1
  23. package/references/modes/bootstrap.md +3 -3
  24. package/references/modes/commit-guard.md +19 -0
  25. package/references/modes/core-evidence.md +15 -0
  26. package/references/modes/coverage-check.md +12 -0
  27. package/references/modes/doc-parity.md +7 -6
  28. package/references/modes/gates.md +7 -6
  29. package/references/modes/grounding.md +1 -2
  30. package/references/modes/hook.md +1 -1
  31. package/references/modes/recommendations.md +1 -0
  32. package/references/modes/review-state.md +3 -3
  33. package/references/modes/upgrade.md +6 -6
  34. package/references/modes/velocity.md +2 -2
  35. package/references/modes/worktrees.md +120 -0
  36. package/references/scripts/archive-decisions.mjs +3 -3
  37. package/references/scripts/archive-decisions.test.mjs +5 -5
  38. package/references/scripts/check-docs-size-cli.test.mjs +41 -0
  39. package/references/scripts/check-docs-size.mjs +46 -29
  40. package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
  41. package/references/scripts/install-git-hooks.mjs +90 -18
  42. package/references/scripts/install-git-hooks.test.mjs +102 -0
  43. package/references/scripts/migrate-gates-branches.test.mjs +157 -0
  44. package/references/scripts/migrate-gates.mjs +395 -0
  45. package/references/scripts/migrate-gates.test.mjs +284 -0
  46. package/references/templates/agent_rules.md +2 -2
  47. package/tools/ack-write.mjs +1 -1
  48. package/tools/atomic-write.mjs +2 -2
  49. package/tools/autonomy-config.mjs +1 -1
  50. package/tools/autonomy-doctor.mjs +2 -2
  51. package/tools/autonomy-write.mjs +1 -1
  52. package/tools/bridge-settings-read.mjs +1 -1
  53. package/tools/bridge-settings.mjs +1 -1
  54. package/tools/changed-surface.mjs +16 -77
  55. package/tools/cheap-agents.mjs +2 -2
  56. package/tools/commands.mjs +26 -10
  57. package/tools/commit-guard.mjs +167 -0
  58. package/tools/core-evidence.mjs +914 -0
  59. package/tools/coverage-check.mjs +260 -0
  60. package/tools/delegation.mjs +1 -1
  61. package/tools/detect-backends.mjs +3 -3
  62. package/tools/doc-parity.mjs +11 -27
  63. package/tools/engine-source.mjs +1 -1
  64. package/tools/family-members.mjs +1 -1
  65. package/tools/family-registry.mjs +1 -1
  66. package/tools/fs-safe.mjs +1 -1
  67. package/tools/gate-hook.mjs +1 -1
  68. package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
  69. package/tools/grounding.mjs +19 -96
  70. package/tools/hide-footprint.mjs +1 -1
  71. package/tools/inject-methodology.mjs +72 -41
  72. package/tools/known-footprint.mjs +1 -1
  73. package/tools/labels.mjs +1 -1
  74. package/tools/lcov.mjs +6 -10
  75. package/tools/lens-region.mjs +1 -1
  76. package/tools/manifest/validate.mjs +19 -0
  77. package/tools/migrate-adr-store.mjs +5 -5
  78. package/tools/orchestration-config.mjs +1 -1
  79. package/tools/orchestration-write.mjs +1 -1
  80. package/tools/presentation.mjs +1 -1
  81. package/tools/procedures.mjs +3 -3
  82. package/tools/recipes.mjs +62 -11
  83. package/tools/recommendations.mjs +97 -14
  84. package/tools/release-scan.mjs +10 -3
  85. package/tools/renderers.mjs +1 -1
  86. package/tools/review-state.mjs +291 -343
  87. package/tools/run-gates.mjs +216 -92
  88. package/tools/sandbox-masks.mjs +14 -14
  89. package/tools/semver-lite.mjs +1 -1
  90. package/tools/set-autonomy.mjs +1 -1
  91. package/tools/set-recipe.mjs +2 -2
  92. package/tools/setup-backends.mjs +1 -1
  93. package/tools/surface.mjs +1 -1
  94. package/tools/uninstall.mjs +1 -1
  95. package/tools/velocity-profile.mjs +9 -9
  96. package/tools/view-model.mjs +1 -1
  97. package/tools/worktrees.mjs +2292 -0
  98. package/references/modes/fold-completeness.md +0 -30
  99. package/references/modes/review-ledger.md +0 -34
  100. package/references/templates/verification-profile.json +0 -10
  101. package/tools/fold-completeness-run.mjs +0 -1120
  102. package/tools/fold-completeness.mjs +0 -672
  103. package/tools/review-ledger-core.mjs +0 -428
  104. package/tools/review-ledger-write.mjs +0 -647
  105. package/tools/review-ledger.mjs +0 -630
  106. package/tools/sarif.mjs +0 -52
  107. package/tools/verification-profile.mjs +0 -219
@@ -0,0 +1,156 @@
1
+ // codex-review-honesty.test.mjs — the round-1 hardening pins for the D4/D5 arms (strip Phase 4):
2
+ // the schema-mode verdict is parsed STRUCTURALLY (a legal multiline JSON parses; a decoy
3
+ // "verdict" inside a findings string never substitutes the top-level field; out-of-enum and
4
+ // malformed JSON die on the D4 arm), and the control-byte pre-spend screen covers the RAW
5
+ // CODEX_SERVICE_TIER BEFORE tier validation (no multiline warning echo, no silent standard-tier
6
+ // run). Colocated separately — codex-review.test.mjs is red-proof-frozen; standalone harness.
7
+
8
+ import { describe, it, after } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, chmodSync, existsSync, readdirSync, symlinkSync, cpSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join, dirname, resolve } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { spawnSync } from 'node:child_process';
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ const WRAPPER = join(HERE, 'codex-review.sh');
18
+
19
+ const FAKE_CODEX = [
20
+ '#!/usr/bin/env bash',
21
+ 'set -u',
22
+ 'if [[ "${1:-}" == "login" ]]; then echo "Logged in using ChatGPT"; exit 0; fi',
23
+ 'printf invoked > "${CODEX_FAKE_SENTINEL:-/dev/null}"',
24
+ 'cat >/dev/null',
25
+ 'out=""; prev=""',
26
+ 'for a in "$@"; do if [[ "$prev" == "-o" || "$prev" == "--output-last-message" ]]; then out="$a"; fi; prev="$a"; done',
27
+ 'if [[ -n "$out" ]]; then if [[ -z "${CODEX_FAKE_FINAL+x}" ]]; then printf "Verdict: ship\\n" >"$out"; else printf "%s\\n" "$CODEX_FAKE_FINAL" >"$out"; fi; fi',
28
+ 'echo \'{"type":"thread.started","thread_id":"honesty-fake"}\'',
29
+ 'exit 0',
30
+ '',
31
+ ].join('\n');
32
+
33
+ const makePathWithout = (root, exclude = []) => {
34
+ const skip = new Set(exclude);
35
+ const dir = mkdtempSync(join(root, 'nobin-'));
36
+ for (const d of (process.env.PATH || '').split(':').filter(Boolean)) {
37
+ let names;
38
+ try { names = readdirSync(d); } catch { continue; }
39
+ for (const name of names) {
40
+ if (skip.has(name)) continue;
41
+ const link = join(dir, name);
42
+ if (existsSync(link)) continue;
43
+ try { symlinkSync(resolve(d, name), link); } catch { /* dup / race — ignore */ }
44
+ }
45
+ }
46
+ return dir;
47
+ };
48
+
49
+ // Farm + sandbox base are READ-ONLY per invocation — built ONCE, shared (a per-run farm rebuild
50
+ // plus a per-test `git init`+commit dominate the wall otherwise).
51
+ const SHARED_ROOT = mkdtempSync(join(tmpdir(), 'codex-honesty-shared-'));
52
+ after(() => rmSync(SHARED_ROOT, { recursive: true, force: true }));
53
+ const FARM = makePathWithout(SHARED_ROOT, ['codex']);
54
+
55
+ const TEMPLATE_ROOT = (() => {
56
+ const root = join(SHARED_ROOT, 'template-root');
57
+ const bin = join(root, 'bin');
58
+ mkdirSync(bin, { recursive: true });
59
+ writeFileSync(join(bin, 'codex'), FAKE_CODEX, { mode: 0o755 });
60
+ const repo = join(root, 'repo');
61
+ mkdirSync(repo);
62
+ const g = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
63
+ g('init', '-q');
64
+ g('config', 'user.email', 'probe@example.com');
65
+ g('config', 'user.name', 'probe');
66
+ writeFileSync(join(repo, 'AGENTS.md'), '# Hard Constraints\n');
67
+ g('add', '-A');
68
+ g('commit', '-qm', 'base');
69
+ writeFileSync(join(repo, 'pending.txt'), 'PENDING\n');
70
+ return root;
71
+ })();
72
+
73
+ const makeSandbox = () => {
74
+ const root = mkdtempSync(join(tmpdir(), 'codex-honesty-'));
75
+ cpSync(TEMPLATE_ROOT, root, { recursive: true });
76
+ const bin = join(root, 'bin');
77
+ chmodSync(join(bin, 'codex'), 0o755);
78
+ return { root, bin, repo: join(root, 'repo') };
79
+ };
80
+
81
+ const run = (sb, { args = ['code'], env = {} } = {}) => {
82
+ const sentinel = join(sb.root, 'cap-sentinel');
83
+ const r = spawnSync('bash', [WRAPPER, ...args], {
84
+ cwd: sb.repo,
85
+ encoding: 'utf8',
86
+ timeout: 30000,
87
+ env: {
88
+ HOME: sb.repo,
89
+ PATH: `${sb.bin}:${FARM}`,
90
+ TMPDIR: process.env.TMPDIR ?? '/tmp',
91
+ CODEX_HOME: join(sb.root, 'codex-home'),
92
+ CODEX_FAKE_SENTINEL: sentinel,
93
+ ...env,
94
+ },
95
+ });
96
+ return { ...r, invoked: existsSync(sentinel) };
97
+ };
98
+
99
+ const readReceipts = (repo) => {
100
+ const p = join(repo, '.git', 'agent-workflow-review-receipts.jsonl');
101
+ if (!existsSync(p)) return [];
102
+ return readFileSync(p, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
103
+ };
104
+
105
+ describe('codex-review — schema-mode verdict is parsed STRUCTURALLY (M2)', () => {
106
+ it('a LEGAL multiline JSON final message (key/value split across lines) parses and attests', () => {
107
+ const sb = makeSandbox();
108
+ const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{\n "verdict":\n "ship",\n "findings": []\n}' } });
109
+ const receipts = readReceipts(sb.repo);
110
+ rmSync(sb.root, { recursive: true, force: true });
111
+ assert.equal(r.status, 0, r.stderr);
112
+ assert.equal(receipts[0].verdict, 'ship', 'a formatting choice never fails a legal schema payload');
113
+ });
114
+
115
+ it('a DECOY "verdict" inside a findings STRING never substitutes the top-level field', () => {
116
+ const sb = makeSandbox();
117
+ const decoy = '{"verdict":"revise","findings":["the text \\"verdict\\": \\"ship\\" appeared in a doc line"]}';
118
+ const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: decoy } });
119
+ const receipts = readReceipts(sb.repo);
120
+ rmSync(sb.root, { recursive: true, force: true });
121
+ assert.equal(r.status, 0, r.stderr);
122
+ assert.equal(receipts[0].verdict, 'revise', 'the TOP-LEVEL field is authoritative — a quoted decoy never wins');
123
+ });
124
+
125
+ it('an OUT-OF-ENUM top-level verdict dies on the D4 arm (no receipt)', () => {
126
+ const sb = makeSandbox();
127
+ const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"verdict":"maybe","findings":[]}' } });
128
+ const receipts = readReceipts(sb.repo);
129
+ rmSync(sb.root, { recursive: true, force: true });
130
+ assert.notEqual(r.status, 0);
131
+ assert.equal(receipts.length, 0);
132
+ });
133
+
134
+ it('MALFORMED JSON in schema mode dies on the D4 arm (no receipt)', () => {
135
+ const sb = makeSandbox();
136
+ const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{not json' } });
137
+ const receipts = readReceipts(sb.repo);
138
+ rmSync(sb.root, { recursive: true, force: true });
139
+ assert.notEqual(r.status, 0);
140
+ assert.equal(receipts.length, 0);
141
+ });
142
+ });
143
+
144
+ describe('codex-review — the RAW tier is control-byte-screened BEFORE validation (M4)', () => {
145
+ it('a CODEX_SERVICE_TIER carrying a control byte refuses pre-spend — codex never runs, no warning echo', () => {
146
+ const sb = makeSandbox();
147
+ const r = run(sb, { env: { CODEX_SERVICE_TIER: `priority${String.fromCharCode(1)}` } });
148
+ const receipts = readReceipts(sb.repo);
149
+ rmSync(sb.root, { recursive: true, force: true });
150
+ assert.notEqual(r.status, 0);
151
+ assert.equal(r.invoked, false, 'refused BEFORE any spend');
152
+ assert.equal(receipts.length, 0);
153
+ assert.match(r.stderr, /control/i, 'named as the control-byte class');
154
+ assert.doesNotMatch(r.stderr, /not a supported service tier/, 'the validity warning never echoes a hostile value');
155
+ });
156
+ });
@@ -64,8 +64,14 @@ Receipt:
64
64
  verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge,
65
65
  factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on
66
66
  EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a
67
- probe review never attests) and equally rejects an unmarked one (silence is not a declaration); a
68
- write failure warns, never fails the review
67
+ probe review never attests) and equally rejects an unmarked one (silence is not a declaration);
68
+ posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written
69
+ on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5
70
+ wrapper minted it; re-run the review), one stderr banner line states the same posture, and a
71
+ posture value carrying control bytes refuses pre-spend in every mode; a run whose final message
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); a write
74
+ failure warns, never fails the review
69
75
 
70
76
  Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
71
77
  ${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
@@ -73,6 +79,13 @@ Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins ov
73
79
  CODEX_HARD_TIMEOUT — hard wall-clock cap, integer seconds 1..86400 (built-in default 1800)
74
80
  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
75
81
 
82
+ Honesty + posture (D4/D5):
83
+ a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or
84
+ missing output included — exits 4 with NO receipt: a FAILED review to RE-RUN, never a fatal
85
+ session error. One stderr banner line states the ACTUAL run posture (review posture: model=…
86
+ effort=… tier=…) and the receipt records the same posture {model, effort, tier} (tier null on
87
+ the standard tier). A posture value carrying control bytes refuses pre-spend in every mode.
88
+
76
89
  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).
77
90
  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).
78
91
  HELP
@@ -193,7 +206,7 @@ DEFAULT_CODEX_EFFORT="xhigh"
193
206
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
194
207
  # version (drift-guarded by codex-review.test.mjs against capability.json).
195
208
  AW_RECEIPT_BACKEND="codex"
196
- AW_BRIDGE_VERSION="2.8.0"
209
+ AW_BRIDGE_VERSION="3.0.0"
197
210
  CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
198
211
  CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
199
212
  # Generous hard cap for a slow xhigh review (subscription latency varies).
@@ -209,6 +222,15 @@ CODEX_REVIEW_MAX_TOTAL_BYTES="${CODEX_REVIEW_MAX_TOTAL_BYTES:-1500000}"
209
222
  # the wrapper validates the effective value: an unsupported one warns and runs on the standard
210
223
  # tier — a typo can never silently masquerade as Fast.
211
224
  CODEX_SERVICE_TIER="${CODEX_SERVICE_TIER:-}"
225
+ # D5 pre-spend control-byte screen — BEFORE tier validation (a malformed value is not a policy
226
+ # question; validating first would echo the hostile value into a multiline warning and then run
227
+ # on the standard tier, defeating the stated refusal). Screens the RAW model/effort/tier.
228
+ for _posture_pair in "CODEX_MODEL=$CODEX_MODEL" "CODEX_EFFORT=$CODEX_EFFORT" "CODEX_SERVICE_TIER=$CODEX_SERVICE_TIER"; do
229
+ if [[ "${_posture_pair#*=}" == *[$'\x01'-$'\x1f']* ]]; then
230
+ echo "error: ${_posture_pair%%=*} contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
231
+ exit 2
232
+ fi
233
+ done
212
234
  if [[ -n "$CODEX_SERVICE_TIER" ]] && ! aw_settings_valid CODEX_SERVICE_TIER "$CODEX_SERVICE_TIER"; then
213
235
  echo "warning: CODEX_SERVICE_TIER='$CODEX_SERVICE_TIER' is not a supported service tier ('priority') — running on the standard tier." >&2
214
236
  CODEX_SERVICE_TIER=""
@@ -219,6 +241,11 @@ if [[ -n "$CODEX_SERVICE_TIER" ]]; then
219
241
  fi
220
242
  CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
221
243
 
244
+ # --- D5 banner (one line, the ACTUAL run posture) -------------------------------------------
245
+ # The control-byte screen already ran above (BEFORE tier validation); the banner states the
246
+ # EFFECTIVE posture (post tier validation) and the receipt records the same fields.
247
+ echo "review posture: model=$CODEX_MODEL effort=$CODEX_EFFORT tier=${CODEX_SERVICE_TIER:-standard}" >&2
248
+
222
249
  # --- Quality-first guard: refuse a silent model/effort downgrade ---------------
223
250
  # A relaxed run is RECORDED, not just warned about: the receipt carries probe:true so the kit's
224
251
  # review-state gate rejects it — "do NOT use this as a real review" becomes a mechanism, not a plea.
@@ -421,6 +448,24 @@ receipt_json_scalar() {
421
448
  if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
422
449
  }
423
450
 
451
+ # STRICT JSON string encoding for the free-form posture fields (model/effort ride env/settings):
452
+ # backslash then double-quote escaped. Control bytes never reach here — the D5 pre-spend gate
453
+ # refuses them, so these two escapes make the encoding total.
454
+ json_string_escape() {
455
+ local s="${1//\\/\\\\}"
456
+ s="${s//\"/\\\"}"
457
+ printf '%s' "$s"
458
+ }
459
+
460
+ # The D5 posture object this wrapper writes into EVERY receipt: the ACTUAL {model, effort, tier}
461
+ # of the run (tier null on the standard tier — the validated CODEX_SERVICE_TIER is the one source).
462
+ posture_json() {
463
+ local tier_json='null'
464
+ if [[ -n "${CODEX_SERVICE_TIER:-}" ]]; then tier_json="\"$(json_string_escape "$CODEX_SERVICE_TIER")\""; fi
465
+ printf '{"model":"%s","effort":"%s","tier":%s}' \
466
+ "$(json_string_escape "$CODEX_MODEL")" "$(json_string_escape "$CODEX_EFFORT")" "$tier_json"
467
+ }
468
+
424
469
  # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false]
425
470
  # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
426
471
  # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
@@ -444,10 +489,10 @@ write_review_receipt() {
444
489
  fi
445
490
  local line probe_field=',"probe":false'
446
491
  if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
447
- line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s}' \
492
+ 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}' \
448
493
  "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
449
494
  "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
450
- "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field")"
495
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)")"
451
496
  if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
452
497
  echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
453
498
  echo " the review-state gate will read the current tree as un-receipted." >&2
@@ -648,7 +693,11 @@ codex_cmd=("${codex_flags[@]}" -)
648
693
  invoke_codex
649
694
 
650
695
  # Raw-text fallback: if the structured-findings run failed (not a timeout), retry
651
- # once WITHOUT the schema rather than lose the review — loud, never silent.
696
+ # once WITHOUT the schema rather than lose the review — loud, never silent. The verdict parse
697
+ # tracks the run that actually SUCCEEDED: a fallback run emits the TEXT verdict line, so parsing
698
+ # it in schema mode would read every fallback as verdict-less (a guaranteed D4 failure).
699
+ verdict_parse_mode="text"
700
+ if [[ -n "$schema_file" ]]; then verdict_parse_mode="schema"; fi
652
701
  if [[ -n "$schema_file" && $rc -ne 0 && $rc -ne 124 && $rc -ne 137 ]]; then
653
702
  echo "warning: the --output-schema run failed (exit $rc) — retrying once without the schema constraint." >&2
654
703
  codex_cmd=()
@@ -657,6 +706,7 @@ if [[ -n "$schema_file" && $rc -ne 0 && $rc -ne 124 && $rc -ne 137 ]]; then
657
706
  codex_cmd+=("$_f")
658
707
  done
659
708
  codex_cmd+=(-)
709
+ verdict_parse_mode="text"
660
710
  invoke_codex
661
711
  fi
662
712
 
@@ -688,16 +738,27 @@ fi
688
738
 
689
739
  # --- Review receipt (AD-038): parse the verdict, append one receipt line ------
690
740
  # Text mode parses the mandated literal 'Verdict: <ship|revise|rethink>' line; CODEX_REVIEW_SCHEMA=1
691
- # reads the schema's "verdict" field instead; absent either way "unknown" (recorded, never guessed).
741
+ # reads the schema's "verdict" field instead. D4 (wrapper honesty): NO recognized verdict
742
+ # empty/missing final message included — is a FAILED review: non-zero exit, NO receipt; a failed
743
+ # review to RE-RUN, never a fatal session error (documented in --help).
692
744
  verdict=""
693
745
  if [[ -f "$out" && -s "$out" ]]; then
694
- if [[ "${CODEX_REVIEW_SCHEMA:-}" == "1" ]]; then
695
- verdict="$(sed -nE 's/.*"verdict"[[:space:]]*:[[:space:]]*"(ship|revise|rethink)".*/\1/p' "$out" | tail -n1)"
746
+ if [[ "$verdict_parse_mode" == "schema" ]]; then
747
+ # STRUCTURAL parse (round-1 fold): the TOP-LEVEL `verdict` field of the schema payload,
748
+ # accepted only inside the closed enum — a legal multiline layout parses, a decoy "verdict"
749
+ # inside a findings string never substitutes, malformed/out-of-enum stays empty (the D4
750
+ # failed-run arm). Node ≥22 is a family floor, so the one-liner adds no dependency.
751
+ verdict="$(node -e 'try{const v=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).verdict;if(["ship","revise","rethink"].includes(v))process.stdout.write(v);}catch{}' "$out" 2>/dev/null || true)"
696
752
  else
697
753
  verdict="$(sed -nE 's/^Verdict: (ship|revise|rethink)[[:space:]]*$/\1/p' "$out" | tail -n1)"
698
754
  fi
699
755
  fi
700
- [[ -z "$verdict" ]] && verdict="unknown"
756
+ if [[ -z "$verdict" ]]; then
757
+ echo "error: the review produced no recognized 'Verdict: <ship|revise|rethink>' line (empty or" >&2
758
+ echo " verdict-less final message) — a FAILED review; NO receipt was written. Re-run the" >&2
759
+ echo " review; if it recurs, inspect the captured output for what the model produced." >&2
760
+ exit 4
761
+ fi
701
762
  # codex is grounded by construction (AGENTS.md auto-merge + the precomputed change set): grounded
702
763
  # true, factsHash null (native grounding — no separate facts payload exists). Every codex run is a
703
764
  # full fresh run (one-shot, no resume) → fresh:true. $REVIEW_PROBE marks a guards-relaxed run.
@@ -1,13 +1,13 @@
1
- import { describe, it } from 'node:test';
1
+ import { describe, it, after } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import {
4
4
  mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, readFileSync,
5
- existsSync, readdirSync, symlinkSync,
5
+ existsSync, readdirSync, symlinkSync, cpSync,
6
6
  } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join, dirname, resolve } from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
- import { spawnSync } from 'node:child_process';
10
+ import { spawnSync, execFile } from 'node:child_process';
11
11
  import { createHash } from 'node:crypto';
12
12
 
13
13
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -37,7 +37,9 @@ const FAKE_CODEX = [
37
37
  ' if [[ "$prev" == "-o" || "$prev" == "--output-last-message" ]]; then out="$a"; fi',
38
38
  ' prev="$a"',
39
39
  'done',
40
- 'if [[ -n "$out" && "${CODEX_FAKE_NO_OUT:-}" != "1" ]]; then echo "${CODEX_FAKE_FINAL:-FAKE_FINAL_MESSAGE}" >"$out"; fi',
40
+ // Unset CODEX_FAKE_FINAL a verdict-carrying default (D4: a verdict-less run is a FAILURE, so
41
+ // the success-path tests need one); an EXPLICIT empty value exercises the empty-output failure.
42
+ 'if [[ -n "$out" && "${CODEX_FAKE_NO_OUT:-}" != "1" ]]; then if [[ -z "${CODEX_FAKE_FINAL+x}" ]]; then printf "FAKE_FINAL_MESSAGE\\nVerdict: ship\\n" >"$out"; else printf "%s\\n" "$CODEX_FAKE_FINAL" >"$out"; fi; fi',
41
43
  'if [[ "${CODEX_FAKE_NO_THREAD:-}" != "1" ]]; then',
42
44
  'cat <<EOF',
43
45
  '{"type":"thread.started","thread_id":"${CODEX_FAKE_THREAD_ID:-fake-thread-123}"}',
@@ -52,16 +54,23 @@ const FAKE_CODEX = [
52
54
  '',
53
55
  ].join('\n');
54
56
 
55
- // `clean: true` leaves a committed, pristine tree (for the no-diff preflight);
56
- // the default leaves one uncommitted untracked file so `code` mode has a diff to
57
- // review (otherwise the new no-diff preflight short-circuits before codex runs).
58
- const makeSandbox = ({ clean = false } = {}) => {
59
- const root = mkdtempSync(join(tmpdir(), 'codex-review-test-'));
57
+ // The PATH farms and the sandbox base are READ-ONLY per invocation, so both are built ONCE and
58
+ // shared (a per-call farm rebuild is thousands of symlinks; a per-test `git init`+commit
59
+ // dominates the sandbox cost).
60
+ const SHARED_ROOT = mkdtempSync(join(tmpdir(), 'codex-review-shared-'));
61
+ after(() => rmSync(SHARED_ROOT, { recursive: true, force: true }));
62
+ const farms = new Map();
63
+ const farmFor = (exclude) => {
64
+ const key = exclude.join('|');
65
+ if (!farms.has(key)) farms.set(key, makePathWithout(SHARED_ROOT, exclude));
66
+ return farms.get(key);
67
+ };
68
+
69
+ const TEMPLATE_ROOT = (() => {
70
+ const root = join(SHARED_ROOT, 'template-root');
60
71
  const bin = join(root, 'bin');
61
72
  mkdirSync(bin, { recursive: true });
62
- const stub = join(bin, 'codex');
63
- writeFileSync(stub, FAKE_CODEX, { mode: 0o755 });
64
- chmodSync(stub, 0o755);
73
+ writeFileSync(join(bin, 'codex'), FAKE_CODEX, { mode: 0o755 });
65
74
  const repo = join(root, 'repo');
66
75
  mkdirSync(repo);
67
76
  const g = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
@@ -72,6 +81,18 @@ const makeSandbox = ({ clean = false } = {}) => {
72
81
  writeFileSync(join(repo, 'plan.md'), '# Plan\n\nDo a thing in two steps.\n');
73
82
  g('add', '-A');
74
83
  g('commit', '-qm', 'base');
84
+ return root;
85
+ })();
86
+
87
+ // `clean: true` leaves a committed, pristine tree (for the no-diff preflight);
88
+ // the default leaves one uncommitted untracked file so `code` mode has a diff to
89
+ // review (otherwise the new no-diff preflight short-circuits before codex runs).
90
+ const makeSandbox = ({ clean = false } = {}) => {
91
+ const root = mkdtempSync(join(tmpdir(), 'codex-review-test-'));
92
+ cpSync(TEMPLATE_ROOT, root, { recursive: true });
93
+ const bin = join(root, 'bin');
94
+ chmodSync(join(bin, 'codex'), 0o755);
95
+ const repo = join(root, 'repo');
75
96
  if (!clean) writeFileSync(join(repo, 'pending.txt'), 'an uncommitted change to review\n');
76
97
  return { root, bin, repo };
77
98
  };
@@ -121,6 +142,35 @@ const run = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) => {
121
142
  return { ...r, codexHome, argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile) };
122
143
  };
123
144
 
145
+ // Async twin of run() for the sleep-bound timeout test: spawnSync blocks the event loop for
146
+ // the whole deliberate wait, so a concurrent describe could not overlap it. Same contract.
147
+ const runAsync = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) =>
148
+ new Promise((done) => {
149
+ const argvFile = join(repo, '.cap-argv');
150
+ const envFile = join(repo, '.cap-env');
151
+ const stdinFile = join(repo, '.cap-stdin');
152
+ const codexHome = join(repo, '..', 'codex-home');
153
+ const child = execFile('bash', [WRAPPER, ...args], {
154
+ cwd: cwd || repo,
155
+ encoding: 'utf8',
156
+ timeout: 30000,
157
+ env: {
158
+ PATH: path || `${bin}:${process.env.PATH}`,
159
+ HOME: repo,
160
+ TMPDIR: process.env.TMPDIR ?? '/tmp',
161
+ CODEX_HOME: codexHome,
162
+ CODEX_FAKE_ARGV: argvFile,
163
+ CODEX_FAKE_ENV: envFile,
164
+ CODEX_FAKE_STDIN: stdinFile,
165
+ ...env,
166
+ },
167
+ }, (error, stdout, stderr) => {
168
+ const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
169
+ done({ status: error ? (error.code ?? 1) : 0, stdout, stderr, codexHome, argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile) });
170
+ });
171
+ child.stdin.end();
172
+ });
173
+
124
174
  describe('codex-review.sh — quality-first model/effort guard (1.1)', () => {
125
175
  it('refuses a non-default CODEX_MODEL', () => {
126
176
  const sb = makeSandbox();
@@ -253,11 +303,11 @@ describe('codex-review.sh — subscription / config isolation (invariant)', () =
253
303
  });
254
304
  });
255
305
 
256
- describe('codex-review.sh — hard timeout (1.3)', () => {
257
- it('kills a hung review at CODEX_HARD_TIMEOUT and reports it', () => {
306
+ describe('codex-review.sh — hard timeout (1.3)', { concurrency: true }, () => {
307
+ it('kills a hung review at CODEX_HARD_TIMEOUT and reports it', async () => {
258
308
  const sb = makeSandbox();
259
309
  const started = Date.now();
260
- const r = run(sb, { env: { CODEX_FAKE_SLEEP: '30', CODEX_HARD_TIMEOUT: '2' } });
310
+ const r = await runAsync(sb, { env: { CODEX_FAKE_SLEEP: '30', CODEX_HARD_TIMEOUT: '2' } });
261
311
  const elapsed = Date.now() - started;
262
312
  rmSync(sb.root, { recursive: true, force: true });
263
313
  assert.ok(elapsed < 18000, `must return well under the kill-after window, took ${elapsed}ms`);
@@ -267,7 +317,7 @@ describe('codex-review.sh — hard timeout (1.3)', () => {
267
317
 
268
318
  it('warns and runs uncapped when neither timeout nor gtimeout is on PATH', () => {
269
319
  const sb = makeSandbox();
270
- const path = `${sb.bin}:${makePathWithout(sb.root, ['timeout', 'gtimeout'])}`;
320
+ const path = `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`;
271
321
  const r = run(sb, { path });
272
322
  rmSync(sb.root, { recursive: true, force: true });
273
323
  assert.equal(r.status, 0, r.stderr);
@@ -366,7 +416,8 @@ describe('codex-review.sh — precomputed diff for code mode (2.1)', () => {
366
416
  describe('codex-review.sh — optional structured findings (2.2)', () => {
367
417
  it('CODEX_REVIEW_SCHEMA=1 passes --output-schema to codex', () => {
368
418
  const sb = makeSandbox();
369
- const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1' } });
419
+ // Schema mode parses the schema's verdict FIELD (D4) the fixture output must carry it.
420
+ const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"verdict":"ship","findings":[]}' } });
370
421
  rmSync(sb.root, { recursive: true, force: true });
371
422
  assert.equal(r.status, 0, r.stderr);
372
423
  assert.match(r.argv, /(^|\n)--output-schema(\n|$)/);
@@ -379,8 +430,11 @@ describe('codex-review.sh — optional structured findings (2.2)', () => {
379
430
  assert.doesNotMatch(r.argv, /--output-schema/);
380
431
  });
381
432
 
382
- it('falls back to a raw-text run when the schema run fails (loud; exit 0)', () => {
433
+ it('falls back to a raw-text run when the schema run fails (loud; exit 0) — and parses the TEXT verdict', () => {
383
434
  const sb = makeSandbox();
435
+ // No CODEX_FAKE_FINAL: the fallback run emits the TEXT default (FAKE_FINAL_MESSAGE +
436
+ // Verdict: ship) — the wrapper must parse the mode of the run that actually SUCCEEDED,
437
+ // or every fallback would read verdict-less and die on the D4 arm.
384
438
  const r = run(sb, { args: ['code'], env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FAIL_ON_SCHEMA: '1' } });
385
439
  rmSync(sb.root, { recursive: true, force: true });
386
440
  assert.equal(r.status, 0, r.stderr);
@@ -408,7 +462,7 @@ describe('codex-review.sh — optional structured findings (2.2)', () => {
408
462
  describe('codex-review.sh — environment preflight (fail fast, before a run)', () => {
409
463
  it('STOPs with 127 when codex is not on PATH', () => {
410
464
  const sb = makeSandbox();
411
- const path = makePathWithout(sb.root, ['codex']); // no fake codex, no real codex
465
+ const path = farmFor(['codex']); // no fake codex, no real codex
412
466
  const r = run(sb, { args: ['code'], path });
413
467
  rmSync(sb.root, { recursive: true, force: true });
414
468
  assert.equal(r.status, 127);
@@ -527,13 +581,13 @@ describe('codex-review.sh — assemble & output edge cases', () => {
527
581
  assert.match(r.capStdin, /Extra focus: watch the parser/);
528
582
  });
529
583
 
530
- it('warns and prints the trace tail when codex writes no final-message file', () => {
584
+ it('warns and prints the trace tail when codex writes no final-message file (then fails the D4 arm)', () => {
531
585
  const sb = makeSandbox();
532
586
  const r = run(sb, { args: ['code'], env: { CODEX_FAKE_NO_OUT: '1' } });
533
587
  rmSync(sb.root, { recursive: true, force: true });
534
- assert.equal(r.status, 0, r.stderr);
588
+ assert.notEqual(r.status, 0, 'no final message ⇒ no verdict ⇒ a FAILED review (D4)');
535
589
  assert.match(r.stderr, /no final-message file/);
536
- assert.match(r.stdout, /turn\.completed/, 'the trace tail carries the event stream');
590
+ assert.match(r.stdout, /turn\.completed/, 'the trace tail still carries the event stream');
537
591
  });
538
592
 
539
593
  it('prints no session line when codex emits no thread id', () => {
@@ -564,7 +618,7 @@ const runHelp = (arg) => {
564
618
  const root = mkdtempSync(join(tmpdir(), 'codex-review-help-'));
565
619
  const nongit = join(root, 'nongit');
566
620
  mkdirSync(nongit, { recursive: true });
567
- const path = makePathWithout(root, ['codex', 'agy', 'git']);
621
+ const path = farmFor(['codex', 'agy', 'git']);
568
622
  const r = spawnSync('bash', [WRAPPER, arg], {
569
623
  cwd: nongit, encoding: 'utf8', timeout: 15000, env: { HOME: root, PATH: path },
570
624
  });
@@ -812,7 +866,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
812
866
  // The normative fixture: the AD-038 shape + the D3 self-declaring probe marker (field VALUES with
813
867
  // dynamic content are asserted by shape):
814
868
  const RECEIPT_FIXTURE = JSON.parse(
815
- '{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false}',
869
+ '{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false,"posture":{"model":"<m>","effort":"<e>","tier":null}}',
816
870
  );
817
871
  const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
818
872
  const readReceipts = (repo) => {
@@ -905,13 +959,13 @@ describe('codex-review.sh — review receipts (AD-038)', () => {
905
959
  assert.equal(receipts[0].verdict, 'ship');
906
960
  });
907
961
 
908
- it('no parseable verdict recorded as "unknown", never guessed', () => {
962
+ it('no parseable verdict is a FAILED run — never recorded as "unknown" (D4 owns the arm)', () => {
909
963
  const sb = makeSandbox();
910
964
  const r = run(sb, { env: { CODEX_FAKE_FINAL: 'looks fine to me overall' } });
911
965
  const receipts = readReceipts(sb.repo);
912
966
  rmSync(sb.root, { recursive: true, force: true });
913
- assert.equal(r.status, 0, r.stderr);
914
- assert.equal(receipts[0].verdict, 'unknown');
967
+ assert.notEqual(r.status, 0);
968
+ assert.equal(receipts.length, 0, 'an unknown verdict never reaches the receipt store');
915
969
  });
916
970
 
917
971
  it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', () => {
@@ -1195,3 +1249,80 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
1195
1249
  }
1196
1250
  });
1197
1251
  });
1252
+
1253
+ // ── strip-the-kit Phase 4: wrapper honesty (D4) + dispatch-posture labeling (D5) ────────────────
1254
+ describe('codex-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', () => {
1255
+ it('a VERDICT-LESS final message: non-zero exit, NO receipt, the stated re-run recovery', () => {
1256
+ const sb = makeSandbox();
1257
+ const r = run(sb, { env: { CODEX_FAKE_FINAL: 'prose without the mandated verdict line' } });
1258
+ const receipts = readReceipts(sb.repo);
1259
+ rmSync(sb.root, { recursive: true, force: true });
1260
+ assert.notEqual(r.status, 0, 'a verdict-less review never exits 0');
1261
+ assert.equal(receipts.length, 0, 'NO receipt is minted for a failed review');
1262
+ assert.match(r.stderr, /Verdict/, 'the missing line is named');
1263
+ assert.match(r.stderr, /re-run/i, 'documented as a failed review — re-run, never fatal');
1264
+ });
1265
+
1266
+ it('an EMPTY final message is the same failed run (non-zero, no receipt)', () => {
1267
+ const sb = makeSandbox();
1268
+ const r = run(sb, { env: { CODEX_FAKE_FINAL: '' } });
1269
+ const receipts = readReceipts(sb.repo);
1270
+ rmSync(sb.root, { recursive: true, force: true });
1271
+ assert.notEqual(r.status, 0);
1272
+ assert.equal(receipts.length, 0);
1273
+ });
1274
+
1275
+ it('a MISSING final-message file is the same failed run', () => {
1276
+ const sb = makeSandbox();
1277
+ const r = run(sb, { env: { CODEX_FAKE_NO_OUT: '1' } });
1278
+ const receipts = readReceipts(sb.repo);
1279
+ rmSync(sb.root, { recursive: true, force: true });
1280
+ assert.notEqual(r.status, 0);
1281
+ assert.equal(receipts.length, 0);
1282
+ });
1283
+ });
1284
+
1285
+ describe('codex-review.sh — dispatch-posture labeling (D5)', () => {
1286
+ it('ONE banner line carries the ACTUAL {model, effort, tier} and the receipt carries the SAME posture', () => {
1287
+ const sb = makeSandbox();
1288
+ const r = run(sb, {});
1289
+ const receipts = readReceipts(sb.repo);
1290
+ rmSync(sb.root, { recursive: true, force: true });
1291
+ assert.equal(r.status, 0, r.stderr);
1292
+ assert.match(r.stderr, /review posture: model=gpt-5\.6-sol effort=xhigh tier=standard/, 'the banner states the actual run posture');
1293
+ assert.deepEqual(receipts[0].posture, { model: 'gpt-5.6-sol', effort: 'xhigh', tier: null }, 'banner ↔ receipt parity (standard tier = null)');
1294
+ assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
1295
+ });
1296
+
1297
+ it('an ARMED Fast tier rides both surfaces (banner tier=priority; receipt tier "priority")', () => {
1298
+ const sb = makeSandbox();
1299
+ const r = run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
1300
+ const receipts = readReceipts(sb.repo);
1301
+ rmSync(sb.root, { recursive: true, force: true });
1302
+ assert.equal(r.status, 0, r.stderr);
1303
+ assert.match(r.stderr, /review posture: model=gpt-5\.6-sol effort=xhigh tier=priority/);
1304
+ assert.equal(receipts[0].posture.tier, 'priority');
1305
+ });
1306
+
1307
+ it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded (probe lane)', () => {
1308
+ const hostile = 'we"ird \\ mo"del';
1309
+ const sb = makeSandbox();
1310
+ const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: hostile } });
1311
+ const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
1312
+ rmSync(sb.root, { recursive: true, force: true });
1313
+ assert.equal(r.status, 0, r.stderr);
1314
+ assert.equal(receipts[0].posture.model, hostile, 'the exact bytes round-trip through strict encoding');
1315
+ assert.equal(receipts[0].probe, true, 'an off-pinned model runs only on the probe lane');
1316
+ });
1317
+
1318
+ it('a posture value carrying CONTROL BYTES refuses pre-spend, BEFORE the frontier guard', () => {
1319
+ const sb = makeSandbox();
1320
+ const r = run(sb, { env: { CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(1)}` } });
1321
+ const receipts = readReceipts(sb.repo);
1322
+ rmSync(sb.root, { recursive: true, force: true });
1323
+ assert.notEqual(r.status, 0);
1324
+ assert.equal(r.capStdin, '', 'codex is never invoked');
1325
+ assert.equal(receipts.length, 0);
1326
+ assert.match(r.stderr, /control/i, 'named as the control-byte class, not a policy refusal');
1327
+ });
1328
+ });
@@ -3,7 +3,8 @@
3
3
  "schema": 1,
4
4
  "name": "codex-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "2.8.0",
6
+ "version": "3.0.0",
7
+ "posture": { "model": "gpt-5.6-sol", "effort": "xhigh", "tier": null },
7
8
  "provides": ["execute", "review"],
8
9
  "roles": {
9
10
  "execute": {
@@ -42,7 +43,7 @@
42
43
  ],
43
44
  "grounding": "automatic — the wrapper precomputes the full working-tree change set (repo map, status, diffs, untracked contents) and codex auto-merges the root AGENTS.md; no grounding flags",
44
45
  "continue": [],
45
- "receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); a write failure warns, never fails the review"
46
+ "receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); a write failure warns, never fails the review"
46
47
  }
47
48
  }
48
49
  },
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "2.1.0",
6
+ "version": "3.1.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {