@sabaiway/agent-workflow-kit 14.22.0 → 14.24.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 +74 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
- package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
- package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
- package/bridges/antigravity-cli-bridge/capability.json +3 -3
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
- package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
- package/bridges/codex-cli-bridge/SKILL.md +44 -24
- package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
- package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
- package/bridges/codex-cli-bridge/capability.json +10 -10
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
- package/bridges/codex-cli-bridge/setup/README.md +8 -5
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +5 -4
- package/references/modes/velocity.md +1 -1
- package/tools/ack-write.mjs +1 -1
- package/tools/bridge-sandbox-recipe.mjs +40 -0
- package/tools/detect-backends.mjs +3 -3
- package/tools/manifest/schema.md +9 -9
- package/tools/manifest/validate.mjs +5 -5
- package/tools/recommendations.mjs +17 -46
- package/tools/velocity-profile.mjs +64 -14
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Read-only ADVISORY review BY the OpenAI Codex CLI. Runs
|
|
3
|
-
#
|
|
2
|
+
# Read-only ADVISORY review BY the OpenAI Codex CLI. Runs read-only — on Linux with
|
|
3
|
+
# codex-cli 0.160.0 or newer, off a codex version and profile digest recorded as
|
|
4
|
+
# refused, under the permissions profile `jev` with no write entry
|
|
5
|
+
# (network to api.typesafe.ai and docs.typesafe.ai only), elsewhere under --sandbox
|
|
6
|
+
# read-only — so codex structurally CANNOT edit/create/delete files or write to git
|
|
4
7
|
# — it can only read and emit findings. The orchestrator reads those findings and
|
|
5
8
|
# decides what to act on; codex never applies them itself.
|
|
6
9
|
#
|
|
@@ -28,9 +31,10 @@
|
|
|
28
31
|
#
|
|
29
32
|
# Best-effort read hygiene: the review runs under a temporary HOME / XDG_* with an
|
|
30
33
|
# ABSOLUTE CODEX_HOME so codex auth + history still resolve, but its default config
|
|
31
|
-
# / cache
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
+
# / cache roots point at a throwaway dir instead of the real $HOME; the user's real
|
|
35
|
+
# skill root ($HOME/.agents/skills) is linked into it when it exists, so a review can
|
|
36
|
+
# load a skill. This trims roaming noise; it is NOT a security boundary (absolute paths
|
|
37
|
+
# anywhere on disk remain readable under read-only — read-scoping is a prompt concern).
|
|
34
38
|
set -euo pipefail
|
|
35
39
|
|
|
36
40
|
# --- --help / -h (pre-preflight: no codex, no login, no git tree needed) -------
|
|
@@ -97,12 +101,16 @@ Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins ov
|
|
|
97
101
|
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
|
|
98
102
|
|
|
99
103
|
Notes:
|
|
100
|
-
the review posture banner
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
banner
|
|
104
|
+
the review posture banner states what the run reaches in a jev= field:
|
|
105
|
+
jev=api.typesafe.ai,docs.typesafe.ai when the run carries the read-only permissions profile `jev`
|
|
106
|
+
(Linux, codex-cli 0.160.0 or newer, a version and profile digest not recorded as refused), else
|
|
107
|
+
jev=unreachable (<reason>) under --sandbox read-only; a run whose codex refuses the profile exits
|
|
108
|
+
72 with no receipt; then a banner-only source=model:<s>,effort:<s> field — where each value came
|
|
109
|
+
from: default, setting or environment — then a banner-only timeout=<duration> field — exactly the
|
|
110
|
+
duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout
|
|
111
|
+
binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no
|
|
112
|
+
longer happen), and none of the three fields enters the receipt posture or the D5 banner↔receipt
|
|
113
|
+
parity
|
|
106
114
|
quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated
|
|
107
115
|
posture; a prose re-type drifts
|
|
108
116
|
|
|
@@ -110,8 +118,8 @@ Honesty + posture (D4/D5):
|
|
|
110
118
|
a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or
|
|
111
119
|
missing output included — exits 4 with NO receipt: a FAILED review to RE-RUN, never a fatal
|
|
112
120
|
session error. One stderr banner line states the ACTUAL run posture (review posture: model=…
|
|
113
|
-
effort=… tier=… source=… timeout=…) and the receipt records the same posture {model, effort, tier} (tier
|
|
114
|
-
null on the standard tier; the timeout
|
|
121
|
+
effort=… tier=… jev=… source=… timeout=…) and the receipt records the same posture {model, effort, tier} (tier
|
|
122
|
+
null on the standard tier; the jev= and timeout= fields are banner-only — never a receipt field). Quote the
|
|
115
123
|
posture banner verbatim when labeling this dispatch. A posture value carrying control bytes
|
|
116
124
|
refuses pre-spend in every mode.
|
|
117
125
|
|
|
@@ -126,6 +134,9 @@ esac
|
|
|
126
134
|
AW_SETTINGS_APPLIED="CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES"
|
|
127
135
|
DEFAULT_CODEX_MODEL="gpt-6.1-sol"
|
|
128
136
|
DEFAULT_CODEX_EFFORT="high"
|
|
137
|
+
# This wrapper's filesystem part of the permissions profile `jev` (the shared block below): the whole disk read
|
|
138
|
+
# and no write entry at all (P14: the tree, .git and /tmp refused writes).
|
|
139
|
+
AW_PROFILE_FILESYSTEM='{":root"="read"}'
|
|
129
140
|
|
|
130
141
|
# --- Bridge settings file (host-level, kit-independent) — byte-identical across the four wrappers ---
|
|
131
142
|
# ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE lines,
|
|
@@ -314,11 +325,24 @@ aw_resolve_timeout_bin() {
|
|
|
314
325
|
[[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
|
|
315
326
|
printf '%s' "$bin"
|
|
316
327
|
}
|
|
328
|
+
aw_scrub_billing_keys() {
|
|
329
|
+
# The one key rule (spec jev-every-run): OPENAI_BASE_URL and every *_API_KEY are unset, so a stray key can never
|
|
330
|
+
# switch a run to paid billing; TYPESAFE_API_KEY is neither read nor set, so it reaches the CLI exactly as given.
|
|
331
|
+
local name
|
|
332
|
+
unset OPENAI_BASE_URL 2>/dev/null || true
|
|
333
|
+
for name in $(compgen -v); do
|
|
334
|
+
case "$name" in
|
|
335
|
+
TYPESAFE_API_KEY) ;;
|
|
336
|
+
*_API_KEY) unset "$name" 2>/dev/null || true ;;
|
|
337
|
+
esac
|
|
338
|
+
done
|
|
339
|
+
return 0
|
|
340
|
+
}
|
|
317
341
|
|
|
318
342
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
319
343
|
# version (drift-guarded by codex-review.test.mjs against capability.json).
|
|
320
344
|
AW_RECEIPT_BACKEND="codex"
|
|
321
|
-
AW_BRIDGE_VERSION="
|
|
345
|
+
AW_BRIDGE_VERSION="5.0.0" # aw-version-anchor
|
|
322
346
|
# Generous hard cap for a slow review at the host posture's effort (subscription latency varies).
|
|
323
347
|
CODEX_HARD_TIMEOUT="${CODEX_HARD_TIMEOUT:-1800}"
|
|
324
348
|
# Above this assembled-payload size (bytes), the diff goes via a git-dir-local temp
|
|
@@ -367,6 +391,9 @@ if [[ -n "$CODEX_SERVICE_TIER" ]]; then
|
|
|
367
391
|
fi
|
|
368
392
|
CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
|
|
369
393
|
|
|
394
|
+
# --- Subscription-only guard (see codex-exec.sh): the shared key rule, before the first spawn of codex ---
|
|
395
|
+
aw_scrub_billing_keys
|
|
396
|
+
|
|
370
397
|
# --- D5 banner (one line, the ACTUAL run posture) -------------------------------------------
|
|
371
398
|
# The control-byte screen already ran above (BEFORE tier validation); the banner states the
|
|
372
399
|
# EFFECTIVE posture (post tier validation) and the receipt records the same fields — except
|
|
@@ -384,8 +411,174 @@ if [[ -z "$timeout_bin" ]]; then
|
|
|
384
411
|
echo " on macOS: brew install coreutils for gtimeout), then re-run." >&2
|
|
385
412
|
exit 127
|
|
386
413
|
fi
|
|
414
|
+
|
|
415
|
+
# --- The permissions profile `jev` (spec jev-every-run) — byte-identical in codex-exec.sh and codex-review.sh ---
|
|
416
|
+
# On Linux with codex-cli 0.160.0 or newer a run carries the profile `jev` by four -c overrides, with neither
|
|
417
|
+
# --sandbox nor sandbox_mode beside them: its filesystem part is this wrapper's AW_PROFILE_FILESYSTEM, and its
|
|
418
|
+
# network reaches api.typesafe.ai and docs.typesafe.ai and no other host (a literal of this bridge, which never
|
|
419
|
+
# imports the kit). Elsewhere, and on a codex version and profile digest recorded as refused, the run keeps
|
|
420
|
+
# today's flags and its banner says jev=unreachable (<reason>). Each run, a resume included, chooses afresh.
|
|
421
|
+
AW_PROFILE_FLOOR="0.160.0"
|
|
422
|
+
AW_PROFILE_HOSTS="api.typesafe.ai,docs.typesafe.ai"
|
|
423
|
+
AW_PROFILE_FLAGS=(-c default_permissions=jev -c permissions.jev.network.enabled=true
|
|
424
|
+
-c 'permissions.jev.network.domains={"api.typesafe.ai"="allow","docs.typesafe.ai"="allow"}'
|
|
425
|
+
-c "permissions.jev.filesystem=$AW_PROFILE_FILESYSTEM")
|
|
426
|
+
# The version on the first line of `codex --version` ("codex-cli X.Y.Z[-pre]"); nothing when unreadable.
|
|
427
|
+
aw_codex_version() { # $1 = the timeout binary or ""
|
|
428
|
+
local raw="" line="" re='^codex-cli[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)[[:space:]]*$'
|
|
429
|
+
if [[ -n "$1" ]]; then
|
|
430
|
+
raw="$("$1" --kill-after=5s 15 codex --version </dev/null 2>/dev/null)" || raw=""
|
|
431
|
+
else
|
|
432
|
+
raw="$(codex --version </dev/null 2>/dev/null)" || raw=""
|
|
433
|
+
fi
|
|
434
|
+
line="${raw%%$'\n'*}"
|
|
435
|
+
line="${line%$'\r'}"
|
|
436
|
+
if [[ "$line" =~ $re ]]; then printf '%s' "${BASH_REMATCH[1]}"; fi
|
|
437
|
+
return 0
|
|
438
|
+
}
|
|
439
|
+
# 0 when version $1 is at or above $2, compared as major.minor.patch; a prerelease sorts below its release.
|
|
440
|
+
aw_version_at_least() {
|
|
441
|
+
local core="${1%%-*}" have=() want=() i
|
|
442
|
+
IFS=. read -r -a have <<<"$core"
|
|
443
|
+
IFS=. read -r -a want <<<"$2"
|
|
444
|
+
for i in 0 1 2; do
|
|
445
|
+
if (( 10#${have[i]} != 10#${want[i]} )); then
|
|
446
|
+
(( 10#${have[i]} > 10#${want[i]} ))
|
|
447
|
+
return
|
|
448
|
+
fi
|
|
449
|
+
done
|
|
450
|
+
[[ "$1" == "$core" ]]
|
|
451
|
+
}
|
|
452
|
+
aw_profile_state_file() {
|
|
453
|
+
printf '%s/agent-workflow/codex-jev-profile-refused' "${XDG_STATE_HOME:-$HOME/.local/state}"
|
|
454
|
+
}
|
|
455
|
+
# sha256 of this run's profile overrides, one per line; nothing without a sha256 tool.
|
|
456
|
+
aw_profile_digest() {
|
|
457
|
+
local sum=""
|
|
458
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
459
|
+
sum="$(printf '%s\n' "${AW_PROFILE_FLAGS[@]}" | sha256sum 2>/dev/null)" || sum=""
|
|
460
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
461
|
+
sum="$(printf '%s\n' "${AW_PROFILE_FLAGS[@]}" | shasum -a 256 2>/dev/null)" || sum=""
|
|
462
|
+
fi
|
|
463
|
+
printf '%s' "${sum%% *}"
|
|
464
|
+
}
|
|
465
|
+
# 0 when the state file $1 lists "<version $2> <digest $3>"; an unreadable file reads as empty.
|
|
466
|
+
aw_profile_refused() {
|
|
467
|
+
local line=""
|
|
468
|
+
[[ -n "$3" && -f "$1" && -r "$1" ]] || return 1
|
|
469
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
470
|
+
if [[ "${line%$'\r'}" == "$2 $3" ]]; then return 0; fi
|
|
471
|
+
done <"$1"
|
|
472
|
+
return 1
|
|
473
|
+
}
|
|
474
|
+
# Appends "<version $2> <digest $3>" to the state file $1, its directory made 0700; one stderr line when it cannot.
|
|
475
|
+
aw_profile_record() {
|
|
476
|
+
local dir="${1%/*}"
|
|
477
|
+
if [[ -n "$3" ]] && { ( umask 077 && mkdir -p -- "$dir" ) && chmod 700 -- "$dir" && printf '%s %s\n' "$2" "$3" >>"$1"; } 2>/dev/null; then
|
|
478
|
+
return 0
|
|
479
|
+
fi
|
|
480
|
+
echo "warning: could not append the refused profile to $1 — until it can be written, every run on this codex version spends one run on the refusal." >&2
|
|
481
|
+
return 1
|
|
482
|
+
}
|
|
483
|
+
# Chooses this run's posture: profile_on (1 or 0), profile_reason, and the banner's jev= value in profile_label.
|
|
484
|
+
aw_profile_choose() { # $1 = the timeout binary or "", $2 = the repository's temp root or ""
|
|
485
|
+
local os=""
|
|
486
|
+
profile_on=0
|
|
487
|
+
profile_reason=""
|
|
488
|
+
profile_version="$(aw_codex_version "$1")"
|
|
489
|
+
profile_digest="$(aw_profile_digest)"
|
|
490
|
+
os="$(uname -s 2>/dev/null)" || os=""
|
|
491
|
+
if [[ "$os" != "Linux" ]]; then
|
|
492
|
+
profile_reason="not linux (${os:-unreadable})"
|
|
493
|
+
elif [[ -z "$profile_version" ]]; then
|
|
494
|
+
profile_reason="codex version unreadable"
|
|
495
|
+
elif ! aw_version_at_least "$profile_version" "$AW_PROFILE_FLOOR"; then
|
|
496
|
+
profile_reason="codex $profile_version below $AW_PROFILE_FLOOR"
|
|
497
|
+
elif [[ -n "$2" ]]; then
|
|
498
|
+
profile_reason="repository under $2"
|
|
499
|
+
elif aw_profile_refused "$(aw_profile_state_file)" "$profile_version" "$profile_digest"; then
|
|
500
|
+
profile_reason="profile not recognized by codex $profile_version, recorded in $(aw_profile_state_file)"
|
|
501
|
+
else
|
|
502
|
+
profile_on=1
|
|
503
|
+
fi
|
|
504
|
+
if (( profile_on )); then profile_label="$AW_PROFILE_HOSTS"; else profile_label="unreachable ($profile_reason)"; fi
|
|
505
|
+
profile_label="${profile_label//[$'\x01'-$'\x1f'$'\x7f']/?}"
|
|
506
|
+
return 0
|
|
507
|
+
}
|
|
508
|
+
# The loud failure, judged on ONE attempt's trace: 0 on a match, its kind in profile_refusal (1 = a P1/P13-shaped
|
|
509
|
+
# error item before the first turn.started, 2 = `bwrap: execvp` in a command item whose failure is proven) and
|
|
510
|
+
# the matched text in profile_refusal_text. The same strings in a successful command or the final message never fire.
|
|
511
|
+
aw_profile_scan() { # $1 = the trace
|
|
512
|
+
local line="" started=0
|
|
513
|
+
local item_re='^\{"type":"item\.completed","item":\{("id":"[^"]*",)?"type":"error","message":"((Permissions profile `jev` |Configured filesystem path `[^`"]*` is not recognized )[^"\\]*)"'
|
|
514
|
+
profile_refusal=0
|
|
515
|
+
profile_refusal_text=""
|
|
516
|
+
[[ -r "$1" ]] || return 1
|
|
517
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
518
|
+
if [[ "$line" == '{"type":"turn.started"'* ]]; then started=1; fi
|
|
519
|
+
if (( ! started )) && [[ "$line" =~ $item_re ]]; then
|
|
520
|
+
profile_refusal=1
|
|
521
|
+
profile_refusal_text="${BASH_REMATCH[2]}"
|
|
522
|
+
return 0
|
|
523
|
+
fi
|
|
524
|
+
if aw_ns_item_evidence "$line" && [[ "$AW_NS_OUTPUT" == *'bwrap: execvp'* ]]; then
|
|
525
|
+
profile_refusal=2
|
|
526
|
+
profile_refusal_text="${AW_NS_OUTPUT%%\\n*}"
|
|
527
|
+
return 0
|
|
528
|
+
fi
|
|
529
|
+
done <"$1"
|
|
530
|
+
return 1
|
|
531
|
+
}
|
|
532
|
+
# Prints the matched refusal (no "update codex" remedy: none is known) and records a refused profile key.
|
|
533
|
+
aw_profile_refusal_report() {
|
|
534
|
+
local file=""
|
|
535
|
+
echo "error: codex refused this run's permissions profile \`jev\` (exit 72): $profile_refusal_text" >&2
|
|
536
|
+
if (( profile_refusal == 1 )); then
|
|
537
|
+
file="$(aw_profile_state_file)"
|
|
538
|
+
if aw_profile_record "$file" "$profile_version" "$profile_digest"; then
|
|
539
|
+
echo " recorded in $file: the next run on codex $profile_version with this profile runs today's flags (jev=unreachable)." >&2
|
|
540
|
+
fi
|
|
541
|
+
fi
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# codex-exec.sh's command-item walk, byte-identical (its comments state the shape and the measured slice costs):
|
|
546
|
+
# 0 when a trace line is a command_execution item with a PROVEN failure, its aggregated_output in AW_NS_OUTPUT.
|
|
547
|
+
aw_ns_is_string_content() {
|
|
548
|
+
if grep -qE '(^|[^\\])(\\\\)*"' <<<"$1"; then return 1; fi
|
|
549
|
+
return 0
|
|
550
|
+
}
|
|
551
|
+
aw_ns_item_evidence() {
|
|
552
|
+
local LC_ALL=C
|
|
553
|
+
local line="$1" d1='","aggregated_output":"' d2='","exit_code":' rest tail cmd agg code after off
|
|
554
|
+
case "$line" in '{'*) ;; *) return 1 ;; esac
|
|
555
|
+
rest="${line#*'"type":"command_execution","command":"'}"
|
|
556
|
+
if [[ "$rest" == "$line" ]]; then return 1; fi
|
|
557
|
+
tail="${rest#*"$d1"}"
|
|
558
|
+
if [[ "$tail" == "$rest" ]]; then return 1; fi
|
|
559
|
+
cmd="${rest:0:$(( ${#rest} - ${#tail} - ${#d1} ))}"
|
|
560
|
+
aw_ns_is_string_content "$cmd" || return 1
|
|
561
|
+
while IFS=: read -r off _; do break; done < <(grep -aboF -- "$d2" <<<"$tail")
|
|
562
|
+
if [[ -z "$off" ]]; then return 1; fi
|
|
563
|
+
agg="${tail:0:off}"
|
|
564
|
+
after="${tail:$(( off + ${#d2} ))}"
|
|
565
|
+
aw_ns_is_string_content "$agg" || return 1
|
|
566
|
+
code="${after%%,*}"
|
|
567
|
+
if [[ "$code" =~ ^-?[0-9]+$ && "$code" != "0" ]]; then
|
|
568
|
+
:
|
|
569
|
+
elif [[ "${after#"$code",}" == '"status":"failed"'* ]]; then
|
|
570
|
+
:
|
|
571
|
+
else
|
|
572
|
+
return 1
|
|
573
|
+
fi
|
|
574
|
+
AW_NS_OUTPUT="$agg"
|
|
575
|
+
return 0
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
# A review has no temp-root rule: its profile writes nothing, so a .git under a temp root stays read-only (P14).
|
|
579
|
+
aw_profile_choose "$timeout_bin" ""
|
|
387
580
|
aw_timeout_banner="$(aw_timeout_label "$timeout_bin" "$CODEX_HARD_TIMEOUT")"
|
|
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
|
|
581
|
+
echo "review posture: model=$CODEX_MODEL effort=$CODEX_EFFORT tier=${CODEX_SERVICE_TIER:-standard} jev=$profile_label source=model:$CODEX_MODEL_SOURCE,effort:$CODEX_EFFORT_SOURCE timeout=$aw_timeout_banner" >&2
|
|
389
582
|
|
|
390
583
|
# A probe run is RECORDED, not just warned about: the receipt carries probe:true so the kit's
|
|
391
584
|
# review-state gate rejects it — "do NOT use this as a real review" becomes a mechanism, not a plea.
|
|
@@ -396,12 +589,6 @@ if [[ "${CODEX_PROBE:-}" == "1" ]]; then
|
|
|
396
589
|
echo " output as a real review (model='$CODEX_MODEL' effort='$CODEX_EFFORT')." >&2
|
|
397
590
|
fi
|
|
398
591
|
|
|
399
|
-
# --- Subscription-only guard (see codex-exec.sh) -----------------------------
|
|
400
|
-
unset OPENAI_API_KEY CODEX_API_KEY OPENAI_BASE_URL 2>/dev/null || true
|
|
401
|
-
while IFS= read -r _api_key_var; do
|
|
402
|
-
unset "$_api_key_var" 2>/dev/null || true
|
|
403
|
-
done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
|
|
404
|
-
|
|
405
592
|
# Resolve the real CODEX_HOME (where the cached ChatGPT login lives) to an ABSOLUTE
|
|
406
593
|
# path BEFORE the env fence repoints HOME — auth must keep resolving.
|
|
407
594
|
real_codex_home="${CODEX_HOME:-$HOME/.codex}"
|
|
@@ -999,6 +1186,19 @@ REVIEW_ARTIFACT=""
|
|
|
999
1186
|
REVIEW_FINGERPRINT=""
|
|
1000
1187
|
REVIEW_ARTIFACT_PATH=""
|
|
1001
1188
|
|
|
1189
|
+
# The user's real Codex skill root, resolved before the fence home repoints HOME (spec jev-every-run): when it
|
|
1190
|
+
# exists, the fence home links .agents/skills to it on a profile and a fallback run alike, read-only in both, and
|
|
1191
|
+
# the fence directives admit reading it; on a profile run they also name the two reachable hosts.
|
|
1192
|
+
skills_root="$(builtin cd -P -- "$HOME/.agents/skills" 2>/dev/null && builtin pwd -P)" || skills_root=""
|
|
1193
|
+
fence_skills=""
|
|
1194
|
+
if [[ -n "$skills_root" ]]; then
|
|
1195
|
+
fence_skills=" A skill under ~/.agents/skills (linked into this run's home) may be read when the review needs one."
|
|
1196
|
+
fi
|
|
1197
|
+
fence_hosts=""
|
|
1198
|
+
if (( profile_on )); then
|
|
1199
|
+
fence_hosts=" The hosts api.typesafe.ai and docs.typesafe.ai are reachable; every other host is refused, and still no file may be edited, created or deleted."
|
|
1200
|
+
fi
|
|
1201
|
+
|
|
1002
1202
|
case "$mode" in
|
|
1003
1203
|
plan)
|
|
1004
1204
|
target="${1:-}"
|
|
@@ -1015,7 +1215,7 @@ case "$mode" in
|
|
|
1015
1215
|
# Plan-mode receipt identity: the artifact-file sha256 (informational-only for the tree checker).
|
|
1016
1216
|
REVIEW_ARTIFACT="plan"
|
|
1017
1217
|
REVIEW_FINGERPRINT="$(sha256_stdin <"$target" || true)"
|
|
1018
|
-
fence_plan="Do not read files outside this git working tree; the plan above plus the in-repo code it references are your whole surface
|
|
1218
|
+
fence_plan="Do not read files outside this git working tree; the plan above plus the in-repo code it references are your whole surface.${fence_skills}${fence_hosts}"
|
|
1019
1219
|
directive="You are REVIEWING an implementation plan — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file, and do NOT rewrite the plan. Obey the project's Hard Constraints from its root AGENTS.md (already merged into your context). Read the plan below and the relevant repository code it references. ${fence_plan} ${OUTPUT_FORMAT_PLAN} Cover: correctness risks, missing or mis-ordered steps, ambiguities a cold executor would trip on, violated project Hard Constraints, scope creep, and missing verification/gates."
|
|
1020
1220
|
prompt="${directive}"$'\n\nPLAN:\n'"$(cat -- "$target")"
|
|
1021
1221
|
;;
|
|
@@ -1041,12 +1241,12 @@ case "$mode" in
|
|
|
1041
1241
|
payload_bytes="$(wc -c <"$review_diff_file")"
|
|
1042
1242
|
base_directive="You are REVIEWING the uncommitted working-tree changes of a git repository — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file and do NOT run any git write command. The COMPLETE change set (repo file map, git status, staged + unstaged diffs, and untracked file contents — binaries noted but skipped, symlinks shown as targets) has been assembled FOR you; you do NOT need to run git yourself, though you MAY read other in-repo files for surrounding context. Obey the project's Hard Constraints from its root AGENTS.md (already merged into your context)."
|
|
1043
1243
|
if [[ "$payload_bytes" -gt "$CODEX_REVIEW_MAX_TOTAL_BYTES" ]]; then
|
|
1044
|
-
fence_code="Do not read files outside this git working tree, with ONE exception — the precomputed-diff file at ${review_diff_file}; read it IN FULL before reviewing. That file plus the in-repo code are your whole surface
|
|
1244
|
+
fence_code="Do not read files outside this git working tree, with ONE exception — the precomputed-diff file at ${review_diff_file}; read it IN FULL before reviewing. That file plus the in-repo code are your whole surface.${fence_skills}${fence_hosts}"
|
|
1045
1245
|
directive="${base_directive} ${fence_code} ${OUTPUT_FORMAT_CODE}"
|
|
1046
1246
|
if [[ $# -gt 0 ]]; then directive="${directive} Extra focus: $*"; fi
|
|
1047
1247
|
prompt="$directive"
|
|
1048
1248
|
else
|
|
1049
|
-
fence_code="Do not read files outside this git working tree; the assembled change set below plus the in-repo code are your whole surface
|
|
1249
|
+
fence_code="Do not read files outside this git working tree; the assembled change set below plus the in-repo code are your whole surface.${fence_skills}${fence_hosts}"
|
|
1050
1250
|
directive="${base_directive} ${fence_code} ${OUTPUT_FORMAT_CODE}"
|
|
1051
1251
|
if [[ $# -gt 0 ]]; then directive="${directive} Extra focus: $*"; fi
|
|
1052
1252
|
prompt="${directive}"$'\n\nASSEMBLED CHANGE SET:\n'"$(cat -- "$review_diff_file")"
|
|
@@ -1066,14 +1266,23 @@ fence_home="$(mktemp -d)"
|
|
|
1066
1266
|
out="$(mktemp)"
|
|
1067
1267
|
trace="$(mktemp)"
|
|
1068
1268
|
mkdir -p "$fence_home/.config" "$fence_home/.cache" "$fence_home/.local/share"
|
|
1269
|
+
if [[ -n "$skills_root" ]]; then
|
|
1270
|
+
mkdir -p "$fence_home/.agents"
|
|
1271
|
+
ln -s "$skills_root" "$fence_home/.agents/skills"
|
|
1272
|
+
fi
|
|
1069
1273
|
|
|
1070
1274
|
# --- Optional structured findings (CODEX_REVIEW_SCHEMA=1) ---------------------
|
|
1071
1275
|
# A FLEXIBLE schema (optional evidence, free-text notes) so codex can almost always
|
|
1072
1276
|
# conform (a live probe confirmed --output-schema CONSTRAINS the output rather than
|
|
1073
1277
|
# failing); a raw-text retry covers the rare validation/run failure. Default OFF.
|
|
1278
|
+
if (( profile_on )); then
|
|
1279
|
+
posture_flags=("${AW_PROFILE_FLAGS[@]}")
|
|
1280
|
+
else
|
|
1281
|
+
posture_flags=(--sandbox read-only)
|
|
1282
|
+
fi
|
|
1074
1283
|
codex_flags=(codex exec
|
|
1075
1284
|
--ignore-user-config
|
|
1076
|
-
|
|
1285
|
+
"${posture_flags[@]}"
|
|
1077
1286
|
-c approval_policy="never"
|
|
1078
1287
|
-c model_reasoning_effort="$CODEX_EFFORT"
|
|
1079
1288
|
-c hide_agent_reasoning=true
|
|
@@ -1126,13 +1335,18 @@ fence_env=(env
|
|
|
1126
1335
|
# guaranteed non-empty there (the fail-closed preflight) — the run reuses the exact binary the
|
|
1127
1336
|
# banner reported.
|
|
1128
1337
|
|
|
1129
|
-
# Run codex with the current ${codex_cmd[@]}; capture rc, stream → $trace.
|
|
1338
|
+
# Run codex with the current ${codex_cmd[@]}; capture rc, stream → $trace. Each attempt's own trace is scanned
|
|
1339
|
+
# for the loud failure (spec jev-every-run) before a retry and before any output: a match exits 72, no receipt.
|
|
1130
1340
|
invoke_codex() {
|
|
1131
1341
|
set +e
|
|
1132
1342
|
printf '%s' "$prompt" \
|
|
1133
1343
|
| "${fence_env[@]}" "$timeout_bin" --kill-after=15s "$CODEX_HARD_TIMEOUT" "${codex_cmd[@]}" >"$trace" 2>&1
|
|
1134
1344
|
rc=$?
|
|
1135
1345
|
set -e
|
|
1346
|
+
if (( profile_on )) && aw_profile_scan "$trace"; then
|
|
1347
|
+
aw_profile_refusal_report
|
|
1348
|
+
exit 72
|
|
1349
|
+
fi
|
|
1136
1350
|
}
|
|
1137
1351
|
|
|
1138
1352
|
codex_cmd=("${codex_flags[@]}" -)
|
|
@@ -19,6 +19,7 @@ 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:-}" == "--version" ]]; then echo "codex-cli ${CODEX_FAKE_VERSION:-0.160.0}"; exit 0; fi',
|
|
22
23
|
'if [[ "${1:-}" == "debug" ]]; then [[ -z "${CODEX_FAKE_NO_CATALOG:-}" ]] || exit 1; cat <<EOF',
|
|
23
24
|
'{"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
25
|
'EOF',
|
|
@@ -184,14 +185,15 @@ describe('codex-review.sh — clean output + session capture (1.2)', { concurren
|
|
|
184
185
|
assert.doesNotMatch(r.stdout, /thread\.started/);
|
|
185
186
|
});
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
// The fallback (spec jev-every-run: codex below 0.160.0); the review profile is codex-review-jev.test.mjs's.
|
|
189
|
+
it('passes the clean-capture flags and, on a fallback run, the read-only sandbox to codex', async () => {
|
|
188
190
|
const sb = makeSandbox();
|
|
189
|
-
const r = await run(sb);
|
|
191
|
+
const r = await run(sb, { env: { CODEX_FAKE_VERSION: '0.159.0' } });
|
|
190
192
|
rmSync(sb.root, { recursive: true, force: true });
|
|
191
|
-
for (const f of [/(^|\n)-o(\n|$)/, /(^|\n)--json(\n|$)/, /hide_agent_reasoning=true/,
|
|
192
|
-
/(^|\n)read-only(\n|$)/]) {
|
|
193
|
+
for (const f of [/(^|\n)-o(\n|$)/, /(^|\n)--json(\n|$)/, /hide_agent_reasoning=true/, /(^|\n)--sandbox\nread-only(\n|$)/]) {
|
|
193
194
|
assert.match(r.argv, f, `expected ${f} among codex argv`);
|
|
194
195
|
}
|
|
196
|
+
assert.match(r.stderr, /^review posture: .* jev=unreachable \(.*\) source=/m);
|
|
195
197
|
});
|
|
196
198
|
|
|
197
199
|
it('surfaces the session id on STDERR only — never the shared resume sidecar', async () => {
|
|
@@ -267,9 +269,7 @@ describe('codex-review.sh — best-effort env read-fence (1.5)', { concurrency:
|
|
|
267
269
|
describe('codex-review.sh — subscription / config isolation (invariant)', { concurrency: 2 }, () => {
|
|
268
270
|
it('clears every *_API_KEY + OPENAI_BASE_URL and passes --ignore-user-config', async () => {
|
|
269
271
|
const sb = makeSandbox();
|
|
270
|
-
const r = await run(sb, { env: {
|
|
271
|
-
OPENAI_API_KEY: 'sk-x', OPENAI_BASE_URL: 'http://evil.example', FOO_API_KEY: 'bar',
|
|
272
|
-
} });
|
|
272
|
+
const r = await run(sb, { env: { OPENAI_API_KEY: 'sk-x', OPENAI_BASE_URL: 'http://evil.example', FOO_API_KEY: 'bar' } });
|
|
273
273
|
rmSync(sb.root, { recursive: true, force: true });
|
|
274
274
|
assert.equal(r.status, 0, r.stderr);
|
|
275
275
|
assert.match(r.capEnv, /^OPENAI_API_KEY=<unset>$/m);
|
|
@@ -1634,7 +1634,7 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
|
|
|
1634
1634
|
const receipts = readReceipts(sb.repo);
|
|
1635
1635
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1636
1636
|
assert.equal(r.status, 0, r.stderr);
|
|
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');
|
|
1637
|
+
assert.match(r.stderr, /review posture: model=gpt-6\.1-sol effort=high tier=standard jev=(api\.typesafe\.ai,docs\.typesafe\.ai|unreachable \(.*\)) source=model:default,effort:default/, 'the banner states the actual run posture');
|
|
1638
1638
|
assert.deepEqual(receipts[0].posture, { model: 'gpt-6.1-sol', effort: 'high', tier: null }, 'banner ↔ receipt parity (standard tier = null)');
|
|
1639
1639
|
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
|
|
1640
1640
|
});
|
|
@@ -1677,7 +1677,7 @@ describe('codex-review.sh — dispatch-posture labeling (D5)', { concurrency: 2
|
|
|
1677
1677
|
const receipts = readReceipts(sb.repo);
|
|
1678
1678
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1679
1679
|
assert.equal(r.status, 0, r.stderr);
|
|
1680
|
-
assert.match(r.stderr, /^review posture: model=gpt-6\.1-sol effort=high tier=standard source=model:default,effort:default timeout=1800s$/m);
|
|
1680
|
+
assert.match(r.stderr, /^review posture: model=gpt-6\.1-sol effort=high tier=standard jev=(api\.typesafe\.ai,docs\.typesafe\.ai|unreachable \(.*\)) source=model:default,effort:default timeout=1800s$/m);
|
|
1681
1681
|
assert.deepEqual(Object.keys(receipts[0].posture), ['model', 'effort', 'tier'], 'timeout never enters the receipt posture');
|
|
1682
1682
|
});
|
|
1683
1683
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "codex-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "5.0.0",
|
|
7
7
|
"posture": { "model": "gpt-6.1-sol", "effort": "high", "tier": null },
|
|
8
8
|
"provides": ["execute", "review"],
|
|
9
9
|
"roles": {
|
|
@@ -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> source=… timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend,
|
|
32
|
+
"exec posture banner: ONE stderr line before dispatch states the ACTUAL run posture — exec posture: model=… effort=… tier=… sandbox=jev-profile|workspace-write session=fresh|resume:<id> jev=… source=… timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend, control bytes in a model, effort, tier or timeout value refuse pre-spend, and a jev= reason shows a control byte as ?",
|
|
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
|
|
34
|
+
"the exec posture banner states what the run reaches in a jev= field: jev=api.typesafe.ai,docs.typesafe.ai when the run carries the permissions profile `jev` (Linux, codex-cli 0.160.0 or newer, a repository outside every temp root, a version and profile digest not recorded as refused), else jev=unreachable (<reason>) on today's workspace-write flags with network off; a run whose codex refuses the profile exits 72 with no success receipt and, for a refused profile key, records its version and digest when the state file can be written, so the next run on them takes today's flags; then 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: none of the three 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
|
|
54
|
+
"the review posture banner states what the run reaches in a jev= field: jev=api.typesafe.ai,docs.typesafe.ai when the run carries the read-only permissions profile `jev` (Linux, codex-cli 0.160.0 or newer, a version and profile digest not recorded as refused), else jev=unreachable (<reason>) under --sandbox read-only; a run whose codex refuses the profile exits 72 with no receipt; then 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 none of the three fields 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
|
}
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
{ "slot": "<extra codex flags...>", "required": false, "description": "extra codex flags, filtered by the guarded passthrough tiers" }
|
|
82
82
|
],
|
|
83
83
|
"guardrails": [
|
|
84
|
-
{ "value": "runs under codex's OWN OS sandbox (workspace-write
|
|
85
|
-
{ "value": "the guarded passthrough blocks model / sandbox / approval overrides", "enforcement": "enforced", "source": "bin/codex-exec.sh" },
|
|
84
|
+
{ "value": "runs under codex's OWN OS sandbox: the permissions profile `jev` (workspace writes, network to api.typesafe.ai and docs.typesafe.ai only), else workspace-write with network off", "enforcement": "enforced", "condition": "the profile needs Linux, codex-cli 0.160.0+, a repo outside every temp root and no recorded refusal; it cannot nest inside another sandbox — route it outside on the OBSERVED failure", "source": "capability.json roles.execute.contract.notes" },
|
|
85
|
+
{ "value": "the guarded passthrough blocks model / sandbox / approval overrides", "enforcement": "enforced", "condition": "under the permissions profile `jev` even CODEX_PROBE=1 refuses --add-dir, -C, --cd, --enable and --disable", "source": "bin/codex-exec.sh" },
|
|
86
86
|
{ "value": "hard wall-clock cap CODEX_HARD_TIMEOUT (built-in default 3600s)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped, and a NONCED run refuses pre-spend", "source": "capability.json settings.CODEX_HARD_TIMEOUT" },
|
|
87
|
-
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=fresh|resume:<id>; timeout
|
|
87
|
+
{ "value": "ONE exec posture stderr banner line states the ACTUAL run posture before dispatch (session=fresh|resume:<id>; jev= names the reachable hosts or why none; jev= and timeout= are banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" },
|
|
88
88
|
{ "value": "a NONCED run reserves its nonce before any spend and publishes a fail-closed terminal receipt at exit", "enforcement": "enforced", "condition": "nonce-supplied runs only — a nonce-less invocation writes no artifact at all", "source": "capability.json roles.execute.contract.receipt" }
|
|
89
89
|
],
|
|
90
90
|
"customHooks": ["CODEX_PROBE"]
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
{ "slot": "[--nonce <n>]", "required": false, "description": "the flow dispatch nonce — the plain-argument lane onto the AW_REVIEW_NONCE seam (round-open prints the value; flag and a non-empty env must agree)" }
|
|
141
141
|
],
|
|
142
142
|
"guardrails": [
|
|
143
|
-
{ "value": "read-only sandbox
|
|
143
|
+
{ "value": "read-only — the permissions profile `jev` with no write entry, else --sandbox read-only: codex cannot edit, create or delete a file", "enforcement": "enforced", "condition": "the profile runs on Linux with codex-cli 0.160.0 or newer, off a codex version and profile digest recorded as refused, and reaches api.typesafe.ai and docs.typesafe.ai only", "source": "bin/codex-review.sh" },
|
|
144
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
|
],
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
{ "slot": "[extra focus...]", "required": false, "description": "extra focus words appended to the review directive" }
|
|
164
164
|
],
|
|
165
165
|
"guardrails": [
|
|
166
|
-
{ "value": "read-only sandbox
|
|
166
|
+
{ "value": "read-only — the permissions profile `jev` with no write entry, else --sandbox read-only: codex cannot edit, create or delete a file", "enforcement": "enforced", "condition": "the profile runs on Linux with codex-cli 0.160.0 or newer, off a codex version and profile digest recorded as refused, and reaches api.typesafe.ai and docs.typesafe.ai only", "source": "bin/codex-review.sh" },
|
|
167
167
|
{ "value": "an oversized change set rides via a git-dir temp file, never truncated", "enforcement": "enforced", "source": "bin/codex-review.sh" },
|
|
168
168
|
{ "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" }
|
|
169
169
|
],
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
"effect": "codex-review code: above this assembled-payload size (bytes) the diff rides via a git-dir temp file instead of inline — never truncated."
|
|
229
229
|
}
|
|
230
230
|
],
|
|
231
|
-
"networkHosts": ["chatgpt.com", "*.chatgpt.com"],
|
|
231
|
+
"networkHosts": ["chatgpt.com", "*.chatgpt.com", "api.typesafe.ai", "docs.typesafe.ai"],
|
|
232
232
|
"writableDirs": [{ "env": "CODEX_HOME", "default": "~/.codex" }],
|
|
233
233
|
"detect": {
|
|
234
234
|
"installed": {
|
|
@@ -34,20 +34,23 @@ the one route to a one-off — never use its output as real work; its review rec
|
|
|
34
34
|
## Posture banners — quote them verbatim
|
|
35
35
|
|
|
36
36
|
Every dispatch states its ACTUAL posture on ONE stderr line: `codex-exec` emits
|
|
37
|
-
`exec posture: model=… effort=… tier=… sandbox=workspace-write session=fresh|resume:<id> source=… timeout=…`
|
|
37
|
+
`exec posture: model=… effort=… tier=… sandbox=jev-profile|workspace-write session=fresh|resume:<id> jev=… source=… timeout=…`
|
|
38
38
|
(fresh and resume alike, only after the resume id is resolved and validated), `codex-review` emits
|
|
39
|
-
`review posture: model=… effort=… tier=… source=… timeout=…`. When you label a dispatch for a user or a
|
|
39
|
+
`review posture: model=… effort=… tier=… jev=… source=… timeout=…`. When you label a dispatch for a user or a
|
|
40
40
|
record, **quote the posture banner verbatim** — the banner is the machine-stated posture; a prose
|
|
41
|
-
re-type drifts. The `
|
|
42
|
-
|
|
41
|
+
re-type drifts. The `jev=` field says what the run reaches: `jev=api.typesafe.ai,docs.typesafe.ai` under
|
|
42
|
+
the permissions profile `jev`, else `jev=unreachable (<reason>)` on today's flags (`codex-exec`:
|
|
43
|
+
`sandbox=workspace-write`, network off; `codex-review`: `--sandbox read-only`); a run whose codex refuses
|
|
44
|
+
the profile exits 72. The `jev=`, `source=` (`model:<s>,effort:<s>`, each `default`, `setting` or
|
|
45
|
+
`environment`) and `timeout=` fields are **banner-only** (`timeout=` is exactly the duration handed
|
|
43
46
|
to `timeout(1)`; on exec `uncapped` when no capping binary is on PATH, while `codex-review` fails
|
|
44
47
|
CLOSED pre-spend there) — informational, never part of a receipt or the banner↔receipt parity.
|
|
45
48
|
|
|
46
49
|
## Exec vs review
|
|
47
50
|
|
|
48
51
|
Use **`codex-exec`** when there is a concrete plan or focused instruction to implement, the project
|
|
49
|
-
declares Hard Constraints + gates in `AGENTS.md`, the work fits
|
|
50
|
-
can review the resulting diff.
|
|
52
|
+
declares Hard Constraints + gates in `AGENTS.md`, the work fits `workspace-write` with no network beyond
|
|
53
|
+
`api.typesafe.ai` and `docs.typesafe.ai`, and you can review the resulting diff.
|
|
51
54
|
|
|
52
55
|
Use **`codex-review plan`** for a cold second opinion on a plan before executing it (risks, missing or
|
|
53
56
|
mis-ordered steps, scope creep, missing gates).
|
|
@@ -94,11 +97,12 @@ codex-exec --resume <session-id> <file|-> # explicit session id
|
|
|
94
97
|
```
|
|
95
98
|
|
|
96
99
|
Resume continues the SAME codex session, so you avoid re-sending the original context. It runs through
|
|
97
|
-
the wrapper, which **re-establishes every invariant** (subscription-only `*_API_KEY`
|
|
98
|
-
`--ignore-user-config`, the host posture's model/effort) and **restates the
|
|
99
|
-
because `codex exec resume` resets the sandbox/approval/network posture and
|
|
100
|
-
the wrapper passes `-c
|
|
101
|
-
|
|
100
|
+
the wrapper, which **re-establishes every invariant** (the subscription-only key rule — every `*_API_KEY`
|
|
101
|
+
but `TYPESAFE_API_KEY` unset — `--ignore-user-config`, the host posture's model/effort) and **restates the
|
|
102
|
+
full posture via `-c`** — because `codex exec resume` resets the sandbox/approval/network posture and
|
|
103
|
+
rejects `-s`/`--add-dir`/`-C`, the wrapper passes `-c approval_policy=never` and the posture it chooses
|
|
104
|
+
afresh for this run: the profile `jev`'s four overrides, or `-c sandbox_mode=workspace-write -c
|
|
105
|
+
sandbox_workspace_write.network_access=false`. A resume takes no passthrough flags and an
|
|
102
106
|
empty resumed instruction is rejected. Prefer resume over re-dispatching a fresh run when you are
|
|
103
107
|
iterating on the same task; start a fresh `codex-exec` when the task changes.
|
|
104
108
|
|
|
@@ -113,8 +117,8 @@ iterating on the same task; start a fresh `codex-exec` when the task changes.
|
|
|
113
117
|
boundary must be a real executable; the real git path is baked into the shim, not exposed as an env
|
|
114
118
|
var). `git config` is read-only too (blocked on a write flag or a `<name> <value>` set form). The
|
|
115
119
|
orchestrator commits after review.
|
|
116
|
-
- **New dependencies / network installs** are done by hand (exec
|
|
117
|
-
re-dispatched.
|
|
120
|
+
- **New dependencies / network installs** are done by hand (exec reaches at most `api.typesafe.ai` and
|
|
121
|
+
`docs.typesafe.ai`), then codex is re-dispatched.
|
|
118
122
|
- **A hung run** is killed at `CODEX_HARD_TIMEOUT` (exec 3600s / review 1800s) and reported (exit
|
|
119
123
|
124/137); raise it for a known-healthy slow run.
|
|
120
124
|
|
|
@@ -124,7 +128,7 @@ iterating on the same task; start a fresh `codex-exec` when the task changes.
|
|
|
124
128
|
Implement the change below from the current project root.
|
|
125
129
|
Obey root AGENTS.md (already in your context), especially its Hard Constraints and declared gates.
|
|
126
130
|
Do not run git write commands. Do not commit.
|
|
127
|
-
If a dependency install, network call, missing gate set, or out-of-repo write is needed, STOP and report.
|
|
131
|
+
If a dependency install, a network call to a host the execution contract does not name as reachable, a missing gate set, or an out-of-repo write is needed, STOP and report.
|
|
128
132
|
|
|
129
133
|
<the focused instruction + relevant paths>
|
|
130
134
|
```
|