@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.
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +38 -50
- package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
- package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
- package/bridges/antigravity-cli-bridge/capability.json +14 -7
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
- package/bridges/codex-cli-bridge/SKILL.md +51 -49
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
- package/bridges/codex-cli-bridge/capability.json +22 -8
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
- package/bridges/codex-cli-bridge/setup/README.md +3 -3
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/bridge-settings.md +10 -7
- package/references/modes/recipes.md +1 -1
- package/references/modes/review-state.md +1 -1
- package/tools/bridge-catalog.mjs +114 -0
- package/tools/bridge-posture.mjs +11 -2
- package/tools/bridge-settings-read.mjs +41 -5
- package/tools/bridge-settings.mjs +111 -23
- package/tools/detect-backends.mjs +6 -6
- package/tools/family-registry.mjs +3 -3
- package/tools/manifest/schema.md +1 -1
- package/tools/manifest/validate.mjs +3 -2
- package/tools/procedures.mjs +3 -3
- package/tools/recipes.mjs +2 -2
- 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.
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# a
|
|
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
|
|
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-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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="
|
|
288
|
-
|
|
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-
|
|
305
|
-
# fetched 2026-
|
|
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
|
-
#
|
|
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 —
|
|
164
|
-
it('
|
|
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('
|
|
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:
|
|
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.
|
|
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: '
|
|
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}:
|
|
908
|
-
assert.
|
|
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: '
|
|
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
|
|
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
|
|
962
|
-
//
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
1650
|
-
assert.deepEqual(receipts[0].posture, { model: 'gpt-6-
|
|
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-
|
|
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, '
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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": "
|
|
7
|
-
"posture": { "model": "gpt-6-
|
|
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:
|
|
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
|
|
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
|
|
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-
|
|
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",
|