@sabaiway/agent-workflow-kit 14.20.0 → 14.21.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 (41) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +38 -50
  5. package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
  8. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
  10. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
  11. package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
  12. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
  13. package/bridges/antigravity-cli-bridge/capability.json +14 -7
  14. package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
  15. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
  16. package/bridges/codex-cli-bridge/SKILL.md +51 -49
  17. package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
  18. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
  19. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
  20. package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
  21. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
  22. package/bridges/codex-cli-bridge/capability.json +22 -8
  23. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
  24. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
  25. package/bridges/codex-cli-bridge/setup/README.md +3 -3
  26. package/capability.json +1 -1
  27. package/package.json +1 -1
  28. package/references/modes/bridge-settings.md +10 -7
  29. package/references/modes/recipes.md +1 -1
  30. package/references/modes/review-state.md +1 -1
  31. package/tools/bridge-catalog.mjs +114 -0
  32. package/tools/bridge-posture.mjs +11 -2
  33. package/tools/bridge-settings-read.mjs +41 -5
  34. package/tools/bridge-settings.mjs +111 -23
  35. package/tools/detect-backends.mjs +6 -6
  36. package/tools/family-registry.mjs +3 -3
  37. package/tools/manifest/schema.md +1 -1
  38. package/tools/manifest/validate.mjs +3 -2
  39. package/tools/procedures.mjs +3 -3
  40. package/tools/recipes.mjs +2 -2
  41. package/tools/review-state.mjs +1 -1
@@ -20,10 +20,11 @@
20
20
  # (never silently truncated). Optionally (CODEX_REVIEW_SCHEMA=1) the findings come
21
21
  # back as a validated JSON object, with a raw-text fallback.
22
22
  #
23
- # Auth/policy: subscription-only, identical to codex-exec.sh. Quality-first: the
24
- # review runs on the PINNED model at the pinned effort (advisory findings still bear on
25
- # what ships, so the same pin-integrity guard applies; CODEX_PROBE=1 relaxes it for
26
- # a throwaway probe only).
23
+ # Auth/policy: subscription-only, identical to codex-exec.sh. The review runs on the host
24
+ # posture — the built-in default gpt-6.1-sol at effort high unless the user set another with
25
+ # `/agent-workflow-kit bridge-settings` (advisory findings still bear on what ships, so the same
26
+ # one-off refusal applies; CODEX_PROBE=1 is the one route to a one-off, and a probe receipt never
27
+ # attests).
27
28
  #
28
29
  # Best-effort read hygiene: the review runs under a temporary HOME / XDG_* with an
29
30
  # ABSOLUTE CODEX_HOME so codex auth + history still resolve, but its default config
@@ -39,7 +40,7 @@ set -euo pipefail
39
40
  case "${1:-}" in
40
41
  --help|-h)
41
42
  cat <<'HELP'
42
- codex-review — read-only ADVISORY review by the OpenAI Codex CLI (subscription-only; the PINNED model at the pinned effort).
43
+ codex-review — read-only ADVISORY review by the OpenAI Codex CLI (subscription-only; the host posture's model and effort).
43
44
 
44
45
  Usage:
45
46
  codex-review plan <plan-file> [--nonce <n>]
@@ -91,15 +92,17 @@ Receipt:
91
92
 
92
93
  Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
93
94
  ${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
94
- CODEX_SERVICE_TIER — service tier: 'priority' (Fast — "2x speed, increased usage", the codex catalog's own words for gpt-6-astra, cache fetched 2026-09-05; it states no credit-rate figure); a consented SPEND knob, default off (standard tier)
95
+ CODEX_SERVICE_TIER — service tier: 'priority' (Fast — "2x speed, increased usage", the codex catalog's own words for gpt-6.1-sol, cache fetched 2026-10-03; it states no credit-rate figure); a consented SPEND knob, default off (standard tier)
95
96
  CODEX_HARD_TIMEOUT — hard wall-clock cap, integer seconds 1..86400 (built-in default 1800)
96
97
  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
97
98
 
98
99
  Notes:
99
- the review posture banner appends a banner-only timeout=<duration> field — exactly the duration
100
- handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary
101
- exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer
102
- happen), and the field never enters the receipt posture or the D5 banner↔receipt parity
100
+ the review posture banner appends a banner-only source=model:<s>,effort:<s> field — where each
101
+ value came from: default, setting or environment — then a banner-only timeout=<duration> field —
102
+ exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no
103
+ timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped
104
+ review run can no longer happen), and neither field enters the receipt posture or the D5
105
+ banner↔receipt parity
103
106
  quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated
104
107
  posture; a prose re-type drifts
105
108
 
@@ -107,7 +110,7 @@ Honesty + posture (D4/D5):
107
110
  a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or
108
111
  missing output included — exits 4 with NO receipt: a FAILED review to RE-RUN, never a fatal
109
112
  session error. One stderr banner line states the ACTUAL run posture (review posture: model=…
110
- effort=… tier=… timeout=…) and the receipt records the same posture {model, effort, tier} (tier
113
+ effort=… tier=… source=… timeout=…) and the receipt records the same posture {model, effort, tier} (tier
111
114
  null on the standard tier; the timeout field is banner-only — never a receipt field). Quote the
112
115
  posture banner verbatim when labeling this dispatch. A posture value carrying control bytes
113
116
  refuses pre-spend in every mode.
@@ -121,6 +124,8 @@ esac
121
124
 
122
125
  # This wrapper's applied settings-file subset (see the shared reader block below).
123
126
  AW_SETTINGS_APPLIED="CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES"
127
+ DEFAULT_CODEX_MODEL="gpt-6.1-sol"
128
+ DEFAULT_CODEX_EFFORT="high"
124
129
 
125
130
  # --- Bridge settings file (host-level, kit-independent) — byte-identical across the four wrappers ---
126
131
  # ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE lines,
@@ -141,7 +146,7 @@ aw_settings_file() {
141
146
  printf '%s/agent-workflow/bridge-settings.conf' "${XDG_CONFIG_HOME:-$HOME/.config}"
142
147
  }
143
148
  aw_settings_known() {
144
- case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR AGY_REVIEW_MAX_TOTAL_BYTES " in
149
+ case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR AGY_REVIEW_MAX_TOTAL_BYTES CODEX_MODEL CODEX_EFFORT AGY_MODEL " in
145
150
  *" $1 "*) return 0 ;;
146
151
  *) return 1 ;;
147
152
  esac
@@ -226,6 +231,37 @@ aw_apply_settings() {
226
231
  done
227
232
  return 0
228
233
  }
234
+ aw_read_posture() {
235
+ local key="$1" default="$2" empty="$3" file line value notify=1
236
+ AW_HOST_VALUE="$default"
237
+ AW_HOST_SOURCE="default"
238
+ file="$(aw_settings_file)"
239
+ [[ -n "${AW_SETTINGS_NOTIFIED:-}" ]] && notify=0
240
+ if [[ -f "$file" && -r "$file" ]]; then
241
+ line="$(grep "^${key}=" "$file" 2>/dev/null || true)"
242
+ if [[ -n "$line" ]]; then
243
+ value="${line##*$'\n'}"
244
+ value="${value#*=}"
245
+ if [[ -n "$value" && "$value" != *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
246
+ AW_HOST_VALUE="$value"
247
+ AW_HOST_SOURCE="setting"
248
+ elif (( notify )); then
249
+ echo "warning: $key in bridge settings file '$file' is empty or carries a control byte — using the built-in default '$default'." >&2
250
+ fi
251
+ fi
252
+ fi
253
+ AW_RUN_VALUE="$AW_HOST_VALUE"
254
+ AW_RUN_SOURCE="$AW_HOST_SOURCE"
255
+ if [[ -n "${!key+x}" ]]; then
256
+ AW_RUN_VALUE="${!key:-$empty}"
257
+ AW_RUN_SOURCE="environment"
258
+ fi
259
+ return 0
260
+ }
261
+ aw_read_posture CODEX_MODEL "$DEFAULT_CODEX_MODEL" "$DEFAULT_CODEX_MODEL"
262
+ HOST_CODEX_MODEL="$AW_HOST_VALUE"; CODEX_MODEL="$AW_RUN_VALUE"; CODEX_MODEL_SOURCE="$AW_RUN_SOURCE"
263
+ aw_read_posture CODEX_EFFORT "$DEFAULT_CODEX_EFFORT" "$DEFAULT_CODEX_EFFORT"
264
+ HOST_CODEX_EFFORT="$AW_HOST_VALUE"; CODEX_EFFORT="$AW_RUN_VALUE"; CODEX_EFFORT_SOURCE="$AW_RUN_SOURCE"
229
265
  aw_apply_settings
230
266
 
231
267
  # --- Effective-timeout resolver (D5 banner honesty; AD-061) --------------------
@@ -279,15 +315,11 @@ aw_resolve_timeout_bin() {
279
315
  printf '%s' "$bin"
280
316
  }
281
317
 
282
- DEFAULT_CODEX_MODEL="gpt-6-astra"
283
- DEFAULT_CODEX_EFFORT="high"
284
318
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
285
319
  # version (drift-guarded by codex-review.test.mjs against capability.json).
286
320
  AW_RECEIPT_BACKEND="codex"
287
- AW_BRIDGE_VERSION="3.8.1" # aw-version-anchor
288
- CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
289
- CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
290
- # Generous hard cap for a slow review at the pinned effort (subscription latency varies).
321
+ AW_BRIDGE_VERSION="4.0.0" # aw-version-anchor
322
+ # Generous hard cap for a slow review at the host posture's effort (subscription latency varies).
291
323
  CODEX_HARD_TIMEOUT="${CODEX_HARD_TIMEOUT:-1800}"
292
324
  # Above this assembled-payload size (bytes), the diff goes via a git-dir-local temp
293
325
  # file instead of inline — never truncated.
@@ -301,8 +333,8 @@ AW_REVIEW_MAP_BUDGET_BYTES=1500000
301
333
  # Codex service tier (quality-neutral speed knob): default EMPTY ⇒ no
302
334
  # service_tier flag (standard tier) — enabling Fast is a consented per-host SPEND act, never a
303
335
  # silent default. The only server-catalog tier id on this subscription is 'priority' (catalog
304
- # display name "Fast": "2x speed, increased usage" in the catalog's own words for gpt-6-astra, cache
305
- # fetched 2026-09-05; it states no credit-rate figure; quality-neutral — same model). codex itself
336
+ # display name "Fast": "2x speed, increased usage" in the catalog's own words for gpt-6.1-sol, cache
337
+ # fetched 2026-10-03; it states no credit-rate figure; quality-neutral — same model). codex itself
306
338
  # accepts ANY -c service_tier string silently (probe-verified), so
307
339
  # the wrapper validates the effective value: an unsupported one warns and runs on the standard
308
340
  # tier — a typo can never silently masquerade as Fast.
@@ -353,29 +385,15 @@ if [[ -z "$timeout_bin" ]]; then
353
385
  exit 127
354
386
  fi
355
387
  aw_timeout_banner="$(aw_timeout_label "$timeout_bin" "$CODEX_HARD_TIMEOUT")"
356
- echo "review posture: model=$CODEX_MODEL effort=$CODEX_EFFORT tier=${CODEX_SERVICE_TIER:-standard} timeout=$aw_timeout_banner" >&2
388
+ echo "review posture: model=$CODEX_MODEL effort=$CODEX_EFFORT tier=${CODEX_SERVICE_TIER:-standard} source=model:$CODEX_MODEL_SOURCE,effort:$CODEX_EFFORT_SOURCE timeout=$aw_timeout_banner" >&2
357
389
 
358
- # --- Quality-first guard: refuse any non-pinned model/effort ---------------
359
- # A relaxed run is RECORDED, not just warned about: the receipt carries probe:true so the kit's
390
+ # A probe run is RECORDED, not just warned about: the receipt carries probe:true so the kit's
360
391
  # review-state gate rejects it — "do NOT use this as a real review" becomes a mechanism, not a plea.
361
392
  REVIEW_PROBE=false
362
393
  if [[ "${CODEX_PROBE:-}" == "1" ]]; then
363
394
  REVIEW_PROBE=true
364
395
  echo "warning: CODEX_PROBE=1 — THROWAWAY PROBE MODE. Quality guards relaxed; do NOT use this run's" >&2
365
396
  echo " output as a real review (model='$CODEX_MODEL' effort='$CODEX_EFFORT')." >&2
366
- else
367
- if [[ "$CODEX_MODEL" != "$DEFAULT_CODEX_MODEL" ]]; then
368
- echo "error: CODEX_MODEL='$CODEX_MODEL' is not the pinned model '$DEFAULT_CODEX_MODEL'." >&2
369
- echo " A delegated review must run on the pinned model at the pinned effort (quality-first)." >&2
370
- echo " For a throwaway probe whose result is effort-independent, set CODEX_PROBE=1." >&2
371
- exit 2
372
- fi
373
- if [[ "$CODEX_EFFORT" != "$DEFAULT_CODEX_EFFORT" ]]; then
374
- echo "error: CODEX_EFFORT='$CODEX_EFFORT' is not the pinned effort '$DEFAULT_CODEX_EFFORT'." >&2
375
- echo " A delegated review must run at the pinned reasoning effort (quality-first)." >&2
376
- echo " For a throwaway probe whose result is effort-independent, set CODEX_PROBE=1." >&2
377
- exit 2
378
- fi
379
397
  fi
380
398
 
381
399
  # --- Subscription-only guard (see codex-exec.sh) -----------------------------
@@ -412,6 +430,86 @@ if [[ ! -f AGENTS.md ]]; then
412
430
  exit 2
413
431
  fi
414
432
 
433
+ # --- The effective model checked against the installed CLI's catalog, then the one-off rule ---
434
+ # Both refuse BEFORE any spend; the read runs with the review's CODEX_HOME and the built-in provider.
435
+ catalog_env=(env CODEX_HOME="$real_codex_home")
436
+ CODEX_CATALOG_TIMEOUT_S=15
437
+ aw_codex_remedy() {
438
+ echo " remedy: set these lines in $(aw_settings_file):" >&2
439
+ echo " CODEX_MODEL=$1" >&2
440
+ echo " CODEX_EFFORT=$2" >&2
441
+ echo " and unset CODEX_MODEL and CODEX_EFFORT in the environment." >&2
442
+ }
443
+ aw_codex_catalog_check() {
444
+ local raw="" rc=0 verdict="" kind="" offered="" model="" effort="" catalog_js=""
445
+ if [[ -n "$timeout_bin" ]]; then
446
+ raw="$("${catalog_env[@]}" "$timeout_bin" --kill-after=5s "$CODEX_CATALOG_TIMEOUT_S" codex debug models -c model_provider=openai </dev/null 2>/dev/null)" || rc=$?
447
+ else
448
+ raw="$("${catalog_env[@]}" codex debug models -c model_provider=openai </dev/null 2>/dev/null)" || rc=$?
449
+ fi
450
+ if (( rc == 124 || rc == 137 )); then
451
+ verdict="unreadable"$'\t'"it exceeded ${CODEX_CATALOG_TIMEOUT_S}s"
452
+ elif (( rc != 0 )); then
453
+ verdict="unreadable"$'\t'"codex debug models exited $rc"
454
+ elif ! command -v node >/dev/null 2>&1; then
455
+ verdict="unreadable"$'\t'"no node to parse it"
456
+ else
457
+ IFS= read -r -d '' catalog_js <<'CATALOG_JS' || true
458
+ const [model, effort, defaultModel, defaultEffort] = process.argv.slice(1);
459
+ const answer = (...fields) => { process.stdout.write(fields.join("\t")); process.exit(0); };
460
+ const CONTROL = /[\u0000-\u001f\u007f]/;
461
+ let doc;
462
+ try { doc = JSON.parse(require("node:fs").readFileSync(0, "utf8")); } catch { answer("unreadable", "its output is not JSON"); }
463
+ if (doc === null || typeof doc !== "object" || !Array.isArray(doc.models)) answer("unreadable", "it carries no models[]");
464
+ const offered = doc.models.filter((m) => m !== null && typeof m === "object" && m.visibility === "list");
465
+ if (offered.some((m) => typeof m.slug !== "string" || m.slug === "" || CONTROL.test(m.slug))) answer("unreadable", "an offered entry carries no usable slug");
466
+ if (offered.length === 0) answer("unreadable", "it offers no model");
467
+ const effortsOf = (m) => (Array.isArray(m.supported_reasoning_levels)
468
+ && m.supported_reasoning_levels.every((l) => l !== null && typeof l === "object" && typeof l.effort === "string" && !CONTROL.test(l.effort))
469
+ ? m.supported_reasoning_levels.map((l) => l.effort) : null);
470
+ const rank = (m) => (typeof m.priority === "number" ? m.priority : Number.MAX_SAFE_INTEGER);
471
+ const refuse = (kind, list) => {
472
+ const builtIn = offered.find((m) => m.slug === defaultModel);
473
+ if (builtIn && (effortsOf(builtIn) ?? []).includes(defaultEffort)) answer(kind, list, defaultModel, defaultEffort);
474
+ const first = [...offered].sort((a, b) => rank(a) - rank(b))[0];
475
+ const level = first.default_reasoning_level;
476
+ if (typeof level !== "string" || level === "" || CONTROL.test(level)) answer("unreadable", "the entry for " + first.slug + " carries no default_reasoning_level");
477
+ answer(kind, list, first.slug, level);
478
+ };
479
+ const entry = offered.find((m) => m.slug === model);
480
+ if (entry === undefined) refuse("model", offered.map((m) => m.slug).join(", "));
481
+ const efforts = effortsOf(entry);
482
+ if (efforts === null) answer("unreadable", "the entry for " + model + " carries no supported_reasoning_levels");
483
+ if (!efforts.includes(effort)) refuse("effort", efforts.join(", "));
484
+ answer("ok");
485
+ CATALOG_JS
486
+ verdict="$(printf '%s' "$raw" | NODE_OPTIONS= node -e "$catalog_js" -- "$CODEX_MODEL" "$CODEX_EFFORT" "$DEFAULT_CODEX_MODEL" "$DEFAULT_CODEX_EFFORT" 2>/dev/null)" || verdict="unreadable"$'\t'"its parser failed"
487
+ fi
488
+ IFS=$'\t' read -r kind offered model effort <<<"$verdict" || true
489
+ case "$kind" in
490
+ ok) return 0 ;;
491
+ model)
492
+ echo "error: CODEX_MODEL='$CODEX_MODEL' ($CODEX_MODEL_SOURCE) is not a model the installed codex offers — refusing before any run is spent." >&2
493
+ echo " offered: $offered" >&2 ;;
494
+ effort)
495
+ echo "error: CODEX_EFFORT='$CODEX_EFFORT' ($CODEX_EFFORT_SOURCE) is not an effort the installed codex offers for $CODEX_MODEL — refusing before any run is spent." >&2
496
+ echo " offered: $offered" >&2 ;;
497
+ *)
498
+ echo "warning: the codex model catalog is unreadable (${offered:-no answer}) — the run proceeds unchecked; codex itself refuses a model it does not serve." >&2
499
+ return 0 ;;
500
+ esac
501
+ aw_codex_remedy "$model" "$effort"
502
+ exit 2
503
+ }
504
+ aw_codex_catalog_check
505
+ if [[ "$CODEX_MODEL" != "$HOST_CODEX_MODEL" || "$CODEX_EFFORT" != "$HOST_CODEX_EFFORT" ]]; then
506
+ if [[ "${CODEX_PROBE:-}" != "1" ]]; then
507
+ echo "error: model=$CODEX_MODEL effort=$CODEX_EFFORT is a one-off — the host posture is model=$HOST_CODEX_MODEL effort=$HOST_CODEX_EFFORT, and only a probe (CODEX_PROBE=1) runs a one-off; refusing before any run is spent." >&2
508
+ aw_codex_remedy "$CODEX_MODEL" "$CODEX_EFFORT"
509
+ exit 2
510
+ fi
511
+ fi
512
+
415
513
  # Register the cleanup trap up front (before allocating any temp path), so a
416
514
  # failure mid-assembly cannot leak the diff temp file, the schema, or the fence.
417
515
  fence_home=""
@@ -19,6 +19,10 @@ const FAKE_CODEX = [
19
19
  '#!/usr/bin/env bash',
20
20
  'set -u',
21
21
  'if [[ "${1:-}" == "login" ]]; then echo "${CODEX_FAKE_LOGIN:-Logged in using ChatGPT}"; exit 0; fi',
22
+ 'if [[ "${1:-}" == "debug" ]]; then [[ -z "${CODEX_FAKE_NO_CATALOG:-}" ]] || exit 1; cat <<EOF',
23
+ '{"models":[{"slug":"gpt-6.1-sol","priority":1,"visibility":"list","default_reasoning_level":"low","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"}]},{"slug":"gpt-6-astra","priority":2,"visibility":"list","default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"medium"},{"effort":"high"}]}]}',
24
+ 'EOF',
25
+ 'exit 0; fi',
22
26
  ': "${CODEX_FAKE_ARGV:=/dev/null}"',
23
27
  ': "${CODEX_FAKE_ENV:=/dev/null}"',
24
28
  ': "${CODEX_FAKE_STDIN:=/dev/null}"',
@@ -160,25 +164,8 @@ const run = ({ repo, bin }, { args = ['code'], env = {}, path, cwd } = {}) => ne
160
164
  // run() IS that twin now, so the twin is one name pointing at it — two spawn paths could only drift.
161
165
  const runAsync = run;
162
166
 
163
- describe('codex-review.sh — quality-first model/effort guard (1.1)', { concurrency: 2 }, () => {
164
- it('refuses a non-default CODEX_MODEL', async () => {
165
- const sb = makeSandbox();
166
- const r = await run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
167
- rmSync(sb.root, { recursive: true, force: true });
168
- assert.notEqual(r.status, 0);
169
- assert.match(r.stderr, /not the pinned model/);
170
- assert.equal(r.capStdin, '', 'codex must not be invoked when the guard fires');
171
- });
172
-
173
- it('refuses a non-default CODEX_EFFORT', async () => {
174
- const sb = makeSandbox();
175
- const r = await run(sb, { env: { CODEX_EFFORT: 'xhigh' } });
176
- rmSync(sb.root, { recursive: true, force: true });
177
- assert.notEqual(r.status, 0);
178
- assert.match(r.stderr, /not the pinned effort/);
179
- });
180
-
181
- it('CODEX_PROBE=1 relaxes the guard and warns', async () => {
167
+ describe('codex-review.sh — the probe one-off (1.1)', { concurrency: 2 }, () => {
168
+ it('CODEX_PROBE=1 runs a one-off effort and warns', async () => {
182
169
  const sb = makeSandbox();
183
170
  const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_EFFORT: 'low' } });
184
171
  rmSync(sb.root, { recursive: true, force: true });
@@ -890,27 +877,28 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
890
877
  }
891
878
  });
892
879
 
893
- it('CODEX_PROBE really relaxes the guard on EVERY review parent the catalog claims (behavioural)', async () => {
880
+ it('a one-off is refused on EVERY review parent and CODEX_PROBE=1 runs it (behavioural)', async () => {
894
881
  // The catalog CLAIMS these modes are modified by the hook; prove it per parent rather than
895
- // trusting a source scan: an off-pin model is exit 2 normally, exit 0 under probe.
882
+ // trusting a source scan: a one-off model is exit 2 normally, exit 0 under probe.
896
883
  const hook = catalog.find((e) => e.key === 'CODEX_PROBE');
897
884
  const drive = { 'review.plan': ['plan', 'plan.md'], 'review.code': ['code'] };
898
885
  const reviewParents = hook.parents.filter((p) => reviewPrimaries.some((r) => r.key === p));
899
- assert.ok(reviewParents.length > 0, 'CODEX_PROBE must claim at least one review parent');
886
+ assert.deepEqual(reviewParents, ['review.plan', 'review.code'], 'CODEX_PROBE claims every review parent');
900
887
  for (const parent of reviewParents) {
901
888
  assert.ok(drive[parent], `no behavioural drive for claimed parent "${parent}" — add one`);
902
889
  // The exit code alone is weak evidence: pin the real DISPATCH too (capStdin is non-empty only
903
890
  // when codex was actually invoked), so a run that dies early can never pass the probe-on branch.
904
891
  const guarded = makeSandbox();
905
- const off = await run(guarded, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model' } });
892
+ const off = await run(guarded, { args: drive[parent], env: { CODEX_MODEL: 'gpt-6-astra' } });
906
893
  rmSync(guarded.root, { recursive: true, force: true });
907
- assert.equal(off.status, 2, `${parent}: the quality guard must refuse an off-pin model without the hook`);
908
- assert.equal(off.capStdin, '', `${parent}: the guard must refuse BEFORE spending a run`);
894
+ assert.equal(off.status, 2, `${parent}: a one-off must be refused without the hook`);
895
+ assert.match(off.stderr, /one-off/, `${parent}: the refusal names the one-off`);
896
+ assert.equal(off.capStdin, '', `${parent}: the one-off must refuse BEFORE spending a run`);
909
897
 
910
898
  const probed = makeSandbox();
911
- const on = await run(probed, { args: drive[parent], env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
899
+ const on = await run(probed, { args: drive[parent], env: { CODEX_MODEL: 'gpt-6-astra', CODEX_PROBE: '1' } });
912
900
  rmSync(probed.root, { recursive: true, force: true });
913
- assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really relax the guard — the catalog claims it does`);
901
+ assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really run the one-off — the catalog claims it does`);
914
902
  assert.notEqual(on.capStdin, '', `${parent}: CODEX_PROBE=1 must really reach codex, not merely exit 0`);
915
903
  }
916
904
  });
@@ -958,8 +946,8 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
958
946
  for (const field of ['durationS', 'blocking', 'artifactPath']) assert.ok(REVIEW_CONTRACT.receipt.includes(`${field} = `), `the manifest receipt contract declares ${field}`);
959
947
  });
960
948
 
961
- // The probe marker (BRIDGE-MODES-CATALOG, D3): a CODEX_PROBE=1 review runs with the
962
- // pinned-model/max-effort guard OFF, so its receipt must be distinguishable — the kit's
949
+ // The probe marker (BRIDGE-MODES-CATALOG, D3): a CODEX_PROBE=1 review is the one route to a
950
+ // one-off model or effort, so its receipt must be distinguishable — the kit's
963
951
  // review-state gate rejects a probe-marked receipt. EVERY receipt carries the marker (true or
964
952
  // false): it self-declares, so the gate reads the fact rather than inferring it from a version
965
953
  // string that bumps in a different release phase. Silence is not a declaration.
@@ -983,13 +971,13 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
983
971
  assert.equal(receipts[0].probe, false, 'silence is not a declaration — the gate rejects an unmarked receipt');
984
972
  });
985
973
 
986
- it('the marker tracks the RELAXED GUARD, not the model — a probe on an off-pinned model still marks', async () => {
974
+ it('the marker tracks the probe flag, not the model — a probe one-off still marks', async () => {
987
975
  const sb = makeSandbox();
988
- const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5-mini', CODEX_EFFORT: 'low', CODEX_FAKE_FINAL: 'Verdict: ship' } });
976
+ const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-6-astra', CODEX_EFFORT: 'medium', CODEX_FAKE_FINAL: 'Verdict: ship' } });
989
977
  const receipts = readReceipts(sb.repo);
990
978
  rmSync(sb.root, { recursive: true, force: true });
991
979
  assert.equal(r.status, 0, r.stderr);
992
- assert.equal(receipts[0].probe, true, 'exactly the runs the guard let through unpinned are marked');
980
+ assert.equal(receipts[0].probe, true, 'exactly the probe runs are marked');
993
981
  });
994
982
 
995
983
  it('the code-mode fingerprint tracks the uncommitted state (same tree → same hash; edit → different)', async () => {
@@ -1561,7 +1549,7 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
1561
1549
  const help = runHelp('--help').stdout;
1562
1550
  const section = helpSection(help, SETTINGS_HEADER);
1563
1551
  const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
1564
- const want = (MANIFEST.settings ?? []).filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1552
+ const want = (MANIFEST.settings ?? []).filter((s) => s.kind !== 'posture' && s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1565
1553
  assert.ok(want.length > 0, 'the manifest must declare settings for this wrapper');
1566
1554
  setEq(got, want, 'help Settings keys ⟷ manifest settings.appliesTo');
1567
1555
  assert.ok(section.some((l) => l.includes('agent-workflow/bridge-settings.conf')), 'the section names the settings file');
@@ -1579,7 +1567,7 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
1579
1567
  it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', async () => {
1580
1568
  const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
1581
1569
  assert.ok(m, 'AW_SETTINGS_APPLIED not found');
1582
- const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1570
+ const want = ALL_SETTINGS.filter((s) => s.kind !== 'posture' && s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
1583
1571
  assert.ok(want.length > 0);
1584
1572
  setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
1585
1573
  });
@@ -1588,8 +1576,8 @@ describe('codex-review.sh — settings surface ⟷ manifest (D6, manifest-pinned
1588
1576
  const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
1589
1577
  assert.ok(body, 'aw_settings_valid not found');
1590
1578
  const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
1591
- setEq(armKeys, ALL_SETTINGS.map((s) => s.key), 'validation arms ⟷ manifest keys');
1592
- for (const s of ALL_SETTINGS) {
1579
+ setEq(armKeys, ALL_SETTINGS.filter((s) => s.kind !== 'posture').map((s) => s.key), 'validation arms ⟷ manifest keys (a posture key is read by aw_read_posture, never validated here)');
1580
+ for (const s of ALL_SETTINGS.filter((x) => x.kind !== 'posture')) {
1593
1581
  const arm = body[0].match(new RegExp(`^ ${s.key}\\) (.*) ;;$`, 'm'));
1594
1582
  assert.ok(arm, `no validation arm for ${s.key}`);
1595
1583
  if (s.kind === 'enum') for (const v of s.values) assert.ok(arm[1].includes(`"${v}"`), `${s.key}: enum value '${v}' not pinned`);
@@ -1646,8 +1634,8 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
1646
1634
  const receipts = readReceipts(sb.repo);
1647
1635
  rmSync(sb.root, { recursive: true, force: true });
1648
1636
  assert.equal(r.status, 0, r.stderr);
1649
- assert.match(r.stderr, /review posture: model=gpt-6-astra effort=high tier=standard/, 'the banner states the actual run posture');
1650
- assert.deepEqual(receipts[0].posture, { model: 'gpt-6-astra', effort: 'high', tier: null }, 'banner ↔ receipt parity (standard tier = null)');
1637
+ assert.match(r.stderr, /review posture: model=gpt-6\.1-sol effort=high tier=standard source=model:default,effort:default/, 'the banner states the actual run posture');
1638
+ assert.deepEqual(receipts[0].posture, { model: 'gpt-6.1-sol', effort: 'high', tier: null }, 'banner ↔ receipt parity (standard tier = null)');
1651
1639
  assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
1652
1640
  });
1653
1641
 
@@ -1657,24 +1645,24 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
1657
1645
  const receipts = readReceipts(sb.repo);
1658
1646
  rmSync(sb.root, { recursive: true, force: true });
1659
1647
  assert.equal(r.status, 0, r.stderr);
1660
- assert.match(r.stderr, /review posture: model=gpt-6-astra effort=high tier=priority/);
1648
+ assert.match(r.stderr, /review posture: model=gpt-6\.1-sol effort=high tier=priority/);
1661
1649
  assert.equal(receipts[0].posture.tier, 'priority');
1662
1650
  });
1663
1651
 
1664
1652
  it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded (probe lane)', async () => {
1665
1653
  const hostile = 'we"ird \\ mo"del';
1666
1654
  const sb = makeSandbox();
1667
- const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: hostile } });
1655
+ const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: hostile, CODEX_FAKE_NO_CATALOG: '1' } });
1668
1656
  const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
1669
1657
  rmSync(sb.root, { recursive: true, force: true });
1670
1658
  assert.equal(r.status, 0, r.stderr);
1671
1659
  assert.equal(receipts[0].posture.model, hostile, 'the exact bytes round-trip through strict encoding');
1672
- assert.equal(receipts[0].probe, true, 'an off-pinned model runs only on the probe lane');
1660
+ assert.equal(receipts[0].probe, true, 'a one-off model runs only on the probe lane');
1673
1661
  });
1674
1662
 
1675
- it('a posture value carrying CONTROL BYTES refuses pre-spend, BEFORE the pinned-model guard', async () => {
1663
+ it('a posture value carrying CONTROL BYTES refuses pre-spend, BEFORE the catalog check', async () => {
1676
1664
  const sb = makeSandbox();
1677
- const r = await run(sb, { env: { CODEX_MODEL: `gpt-6-astra${String.fromCharCode(1)}` } });
1665
+ const r = await run(sb, { env: { CODEX_MODEL: `gpt-6.1-sol${String.fromCharCode(1)}` } });
1678
1666
  const receipts = readReceipts(sb.repo);
1679
1667
  rmSync(sb.root, { recursive: true, force: true });
1680
1668
  assert.notEqual(r.status, 0);
@@ -1689,7 +1677,7 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
1689
1677
  const receipts = readReceipts(sb.repo);
1690
1678
  rmSync(sb.root, { recursive: true, force: true });
1691
1679
  assert.equal(r.status, 0, r.stderr);
1692
- assert.match(r.stderr, /^review posture: model=gpt-6-astra effort=high tier=standard timeout=1800s$/m);
1680
+ assert.match(r.stderr, /^review posture: model=gpt-6\.1-sol effort=high tier=standard source=model:default,effort:default timeout=1800s$/m);
1693
1681
  assert.deepEqual(Object.keys(receipts[0].posture), ['model', 'effort', 'tier'], 'timeout never enters the receipt posture');
1694
1682
  });
1695
1683
 
@@ -1715,7 +1703,7 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
1715
1703
 
1716
1704
  it('a DEL (0x7f) byte in a banner field refuses pre-spend like the C0 range', async () => {
1717
1705
  const sb = makeSandbox();
1718
- const r = await run(sb, { env: { CODEX_MODEL: `gpt-6-astra${String.fromCharCode(127)}` } });
1706
+ const r = await run(sb, { env: { CODEX_MODEL: `gpt-6.1-sol${String.fromCharCode(127)}` } });
1719
1707
  const receipts = readReceipts(sb.repo);
1720
1708
  rmSync(sb.root, { recursive: true, force: true });
1721
1709
  assert.notEqual(r.status, 0);
@@ -3,8 +3,8 @@
3
3
  "schema": 1,
4
4
  "name": "codex-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "3.8.1",
7
- "posture": { "model": "gpt-6-astra", "effort": "high", "tier": null },
6
+ "version": "4.0.0",
7
+ "posture": { "model": "gpt-6.1-sol", "effort": "high", "tier": null },
8
8
  "provides": ["execute", "review"],
9
9
  "roles": {
10
10
  "execute": {
@@ -21,7 +21,7 @@
21
21
  "codex-exec --resume-last [--nonce <n>] <plan-file|->",
22
22
  "codex-exec --resume <session-id> [--nonce <n>] <plan-file|->"
23
23
  ],
24
- "receipt": "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, no node.",
24
+ "receipt": "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, and it needs no node.",
25
25
  "passthrough": {
26
26
  "policy": "guarded",
27
27
  "blocked": ["-c*", "--config*", "-s*", "--sandbox*", "--dangerously-bypass-approvals-and-sandbox", "--dangerously-bypass-hook-trust", "--full-auto", "--oss", "--local-provider*", "-p*", "--profile*", "-m*", "--model*", "-o*", "--output-last-message*", "--json*", "--color*", "--output-schema*", "--ephemeral*"],
@@ -29,9 +29,9 @@
29
29
  },
30
30
  "notes": [
31
31
  "nested-sandbox limit: codex-exec ships its OWN OS sandbox (bwrap workspace-write) and cannot run nested inside a harness sandbox (the FS turns read-only) — route it OUTSIDE the harness sandbox (excludedCommands / a per-run consented bypass) on the OBSERVED bwrap/EPERM failure, never a preemptive blanket",
32
- "exec posture banner: ONE stderr line before dispatch states the ACTUAL run posture — exec posture: model=… effort=… tier=… sandbox=workspace-write session=fresh|resume:<id> timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend, and control bytes in any banner field refuse pre-spend",
32
+ "exec posture banner: ONE stderr line before dispatch states the ACTUAL run posture — exec posture: model=… effort=… tier=… sandbox=workspace-write session=fresh|resume:<id> source=… timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend, and control bytes in any banner field refuse pre-spend",
33
33
  "threat model: the sidecar byte and grammar screens detect corrupted input under a trusted parent environment. A hostile parent environment — including exported shell functions or PATH substitution of core/backend commands — is outside the threat model and can substitute the backend itself. Targeted shadow-proof resolution protects banner/dispatch honesty from accidental shadowing; it is not an environment security boundary",
34
- "the exec posture banner appends a banner-only timeout=<duration|uncapped> field — exactly the duration handed to timeout(1), uncapped when no timeout/gtimeout binary caps the run; INFORMATIONAL only: it is never persisted in a receipt or session sidecar",
34
+ "the exec posture banner appends a banner-only source=model:<s>,effort:<s> field — where each value came from: default, setting or environment — then a banner-only timeout=<duration|uncapped> field — exactly the duration handed to timeout(1), uncapped when no timeout/gtimeout binary caps the run; INFORMATIONAL only: neither is persisted in a receipt or session sidecar",
35
35
  "quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts",
36
36
  "every-run nested-sandbox scan (DUAL policy, deliberately two different rules): the scan runs on EVERY completed run, not only a failed one, because a run that SURVIVES the nested-sandbox failure exits 0 with an ungrounded answer and nothing said so. Failed run (rc != 0): the existing loose whole-trace combination rule prints the recovery hint. Successful run (rc == 0): a warning fires ONLY on precise per-item evidence — both a sandbox-mechanism token AND a permission/read-only failure token inside the aggregated_output of ONE command_execution item whose failure is PROVEN (a nonzero exit_code, or the serialized status \"failed\"); a null exit_code is never failure by itself, tokens split across two items never fire, and a successful command's output never fires. The answer is printed FIRST on stdout, then the warning on stderr. HONEST RESIDUAL: the exit status does NOT change on that lane (a distinct nonzero exit would give a heuristic scan DENY polarity, refusing real work whenever the scan over-warns), so an orchestrator keying on exit status alone can still bank an ungrounded answer — the stderr warning is the signal"
37
37
  ]
@@ -51,7 +51,7 @@
51
51
  "continue": [],
52
52
  "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; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of [blocker] and [major] lines (schema mode: findings with either severity; a payload whose findings cannot be counted fails the run, no receipt); artifactPath = the normalized realpath on plan receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; 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); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
53
53
  "notes": [
54
- "the review posture banner appends a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity",
54
+ "the review posture banner appends a banner-only source=model:<s>,effort:<s> field — where each value came from: default, setting or environment — then a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and neither field enters the receipt posture or the D5 banner↔receipt parity",
55
55
  "quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts"
56
56
  ]
57
57
  }
@@ -141,7 +141,7 @@
141
141
  ],
142
142
  "guardrails": [
143
143
  { "value": "read-only sandbox — codex cannot edit, create or delete a file", "enforcement": "enforced", "source": "bin/codex-review.sh" },
144
- { "value": "runs on the pinned model at the pinned effort", "enforcement": "enforced", "condition": "unless CODEX_PROBE=1 relaxes the guard for a throwaway probe", "source": "bin/codex-review.sh" },
144
+ { "value": "runs on the host posture's model and effort", "enforcement": "enforced", "condition": "a one-off is refused pre-spend unless CODEX_PROBE=1 marks a throwaway probe", "source": "bin/codex-review.sh" },
145
145
  { "value": "a successful review appends one receipt line the review-state gate reads", "enforcement": "enforced", "condition": "a receipt write failure warns and the review still succeeds — the tree then reads un-receipted", "source": "capability.json roles.review.contract.receipt" }
146
146
  ],
147
147
  "customHooks": ["CODEX_PROBE"]
@@ -187,13 +187,27 @@
187
187
  }
188
188
  ],
189
189
  "settings": [
190
+ {
191
+ "key": "CODEX_MODEL",
192
+ "kind": "posture",
193
+ "default": "gpt-6.1-sol",
194
+ "appliesTo": ["codex-exec", "codex-review"],
195
+ "effect": "the codex model every run uses (the host posture); a set value is checked against the installed codex's catalog first. /agent-workflow-kit bridge-settings shows the models offered."
196
+ },
197
+ {
198
+ "key": "CODEX_EFFORT",
199
+ "kind": "posture",
200
+ "default": "high",
201
+ "appliesTo": ["codex-exec", "codex-review"],
202
+ "effect": "the codex reasoning effort every run uses (the host posture); a set value is checked against the efforts the model offers first. /agent-workflow-kit bridge-settings shows them."
203
+ },
190
204
  {
191
205
  "key": "CODEX_SERVICE_TIER",
192
206
  "kind": "enum",
193
207
  "values": ["priority"],
194
208
  "default": null,
195
209
  "appliesTo": ["codex-exec", "codex-review"],
196
- "effect": "codex service tier; 'priority' (catalog display name Fast) = \"2x speed, increased usage\" in the codex catalog's own words for gpt-6-astra (cache fetched 2026-09-05); it states no credit-rate figure — quality-neutral (same model). Unset/empty ⇒ no service_tier flag (standard tier). SPEND KNOB: enabling it is a consented per-host act, never a default. codex itself accepts any -c service_tier string silently, so the wrapper validates the value."
210
+ "effect": "codex service tier; 'priority' (catalog display name Fast) = \"2x speed, increased usage\" in the codex catalog's own words for gpt-6.1-sol (cache fetched 2026-10-03); it states no credit-rate figure — quality-neutral (same model). Unset/empty ⇒ no service_tier flag (standard tier). SPEND KNOB: enabling it is a consented per-host act, never a default. codex itself accepts any -c service_tier string silently, so the wrapper validates the value."
197
211
  },
198
212
  {
199
213
  "key": "CODEX_HARD_TIMEOUT",