@sabaiway/agent-workflow-kit 14.22.0 → 14.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/SKILL.md +1 -1
  3. package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
  4. package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
  6. package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
  7. package/bridges/antigravity-cli-bridge/capability.json +3 -3
  8. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
  9. package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
  10. package/bridges/codex-cli-bridge/SKILL.md +44 -24
  11. package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
  12. package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
  13. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
  14. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
  15. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
  16. package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
  17. package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
  18. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
  19. package/bridges/codex-cli-bridge/capability.json +10 -10
  20. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
  21. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
  22. package/bridges/codex-cli-bridge/setup/README.md +8 -5
  23. package/capability.json +1 -1
  24. package/package.json +1 -1
  25. package/references/modes/recommendations.md +1 -1
  26. package/references/modes/velocity.md +1 -1
  27. package/tools/detect-backends.mjs +3 -3
  28. package/tools/manifest/schema.md +2 -2
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env bash
2
- # Read-only ADVISORY review BY the OpenAI Codex CLI. Runs under the read-only
3
- # sandbox, so codex structurally CANNOT edit/create/delete files or write to git
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 / skill-scan roots point at a throwaway dir instead of the real $HOME.
32
- # This trims roaming noise; it is NOT a security boundary (absolute paths anywhere
33
- # on disk remain readable under read-only — read-scoping is a prompt concern).
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 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
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 field is banner-only — never a receipt field). Quote the
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="4.0.0" # aw-version-anchor
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
- --sandbox read-only
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
- it('passes the clean-capture flags and read-only sandbox to codex', async () => {
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": "4.0.0",
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, 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=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 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",
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 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",
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)", "enforcement": "enforced", "condition": "it cannot nest inside another sandbox — the FS turns read-only; 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", "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 is banner-only)", "enforcement": "enforced", "source": "bin/codex-exec.sh" },
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 — codex cannot edit, create or delete a file", "enforcement": "enforced", "source": "bin/codex-review.sh" },
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 — codex cannot edit, create or delete a file", "enforcement": "enforced", "source": "bin/codex-review.sh" },
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 `source=` field (`model:<s>,effort:<s>`, each `default`, `setting` or
42
- `environment`) and the `timeout=` field are **banner-only** (`timeout=` is exactly the duration handed
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 network-off `workspace-write`, and you
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` scrub,
98
- `--ignore-user-config`, the host posture's model/effort) and **restates the full posture via `-c`** —
99
- because `codex exec resume` resets the sandbox/approval/network posture and rejects `-s`/`--add-dir`/`-C`,
100
- the wrapper passes `-c sandbox_mode=workspace-write -c approval_policy=never -c
101
- sandbox_workspace_write.network_access=false` explicitly. A resume takes no passthrough flags and an
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 has network OFF), then codex is
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
  ```