@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
|
@@ -9,8 +9,9 @@ wrapper commands are `codex-exec` and `codex-review`, backed by `bin/codex-exec.
|
|
|
9
9
|
|
|
10
10
|
| Level | Can write? | Network? | Wrapper that uses it |
|
|
11
11
|
|---|---|---|---|
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
12
|
+
| permissions profile `jev` | exec: the repository, `/tmp` and `$TMPDIR`; review: nothing | `api.typesafe.ai` and `docs.typesafe.ai` only | both wrappers on Linux with codex-cli 0.160.0 or newer (conditions below) |
|
|
13
|
+
| `read-only` | no | no | `codex-review`, the fallback (codex only reads + emits findings) |
|
|
14
|
+
| `workspace-write` | repository, `/tmp` and `$TMPDIR` | off (forced off) | `codex-exec`, the fallback (codex edits the repo) |
|
|
14
15
|
| `danger-full-access` | anywhere | yes | never used by this skill |
|
|
15
16
|
|
|
16
17
|
**A sandbox bounds WRITES, not READS.** Even `read-only` can read any file on disk (it is what kept a
|
|
@@ -20,7 +21,7 @@ a **prompt + env** concern, not a sandbox one — see the review read-fence belo
|
|
|
20
21
|
|
|
21
22
|
## Flags the wrappers always pass
|
|
22
23
|
|
|
23
|
-
`codex-exec
|
|
24
|
+
`codex-exec`, on today's flags:
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
codex exec --ignore-user-config \
|
|
@@ -32,10 +33,23 @@ codex exec --ignore-user-config \
|
|
|
32
33
|
--color never -o "$out" --json -m "$CODEX_MODEL" -
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
36
|
+
On Linux with codex-cli 0.160.0 or newer, from a repository outside every temp root and off a codex
|
|
37
|
+
version and profile digest recorded as refused, the first two
|
|
38
|
+
posture flags (`--sandbox workspace-write` and the `network_access=false` override) give way to the
|
|
39
|
+
permissions profile `jev` — four overrides, never beside `--sandbox` or `sandbox_mode` (codex refuses
|
|
40
|
+
both together):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
-c default_permissions=jev -c permissions.jev.network.enabled=true \
|
|
44
|
+
-c 'permissions.jev.network.domains={"api.typesafe.ai"="allow","docs.typesafe.ai"="allow"}' \
|
|
45
|
+
-c 'permissions.jev.filesystem={":root"="read",":tmpdir"="write",":slash_tmp"="write",":workspace_roots"={"."="write"}}'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`codex-review` is the same minus the write/network posture, plus (optionally) `--output-schema`: on
|
|
49
|
+
today's flags `--sandbox read-only`, which in v0.142.3 also grants **no network** (the
|
|
50
|
+
`sandbox_workspace_write.*` config, `network_access` included, applies **only** to `workspace-write`);
|
|
51
|
+
under the profile `jev` the same four overrides with `permissions.jev.filesystem={":root"="read"}` —
|
|
52
|
+
no write entry at all, so a review still writes nothing. The banner's `jev=` field names the posture.
|
|
39
53
|
|
|
40
54
|
### Clean output capture
|
|
41
55
|
|
|
@@ -90,16 +104,30 @@ Note that `--skip-git-repo-check` (tier 2) only relaxes **codex's own** git-repo
|
|
|
90
104
|
preflight still **requires a git work tree and a root `AGENTS.md`** and STOPs first if either is
|
|
91
105
|
missing — passing it does not let `codex-exec` run outside a repo.
|
|
92
106
|
|
|
93
|
-
## Network
|
|
94
|
-
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
## Network: two hosts at most
|
|
108
|
+
|
|
109
|
+
A run reaches `api.typesafe.ai` and `docs.typesafe.ai` under the permissions profile `jev` and no other
|
|
110
|
+
host; on today's flags `codex-exec` keeps the network off. Either way **new dependencies and any other
|
|
111
|
+
network step are installed by a human**, not by codex: if a task needs a new package, codex must STOP
|
|
112
|
+
and report it; the orchestrator installs it, then re-dispatches. The profile runs on Linux with
|
|
113
|
+
codex-cli 0.160.0 or newer, off a codex version and profile digest recorded as refused and, for
|
|
114
|
+
`codex-exec`, from a repository whose root and git common dir sit outside `/tmp` and `$TMPDIR` (there
|
|
115
|
+
the profile would leave `.git` writable); every other run keeps today's flags and its banner says
|
|
116
|
+
`jev=unreachable (<reason>)`.
|
|
117
|
+
|
|
118
|
+
**The loud failure.** A run whose codex refuses the profile — a "Permissions profile `jev` …" or
|
|
119
|
+
"Configured filesystem path … is not recognized …" error item before the turn starts, or `bwrap: execvp`
|
|
120
|
+
in a command item whose failure is proven — exits **72** with the matched message and no success
|
|
121
|
+
receipt (a nonced exec run records `transport-failure`; a review writes no receipt). The error-item case
|
|
122
|
+
appends `<codex version> <sha256 of the profile overrides>` to
|
|
123
|
+
`${XDG_STATE_HOME:-~/.local/state}/agent-workflow/codex-jev-profile-refused`, so the next run on that
|
|
124
|
+
version and digest takes today's flags before any spend; if that file cannot be written the wrapper
|
|
125
|
+
says so on stderr and the next run tries the profile again.
|
|
98
126
|
|
|
99
127
|
## Escalation & approvals
|
|
100
128
|
|
|
101
129
|
There is **no TTY** in `codex exec`, so `approval_policy=never`: codex never pauses for an interactive
|
|
102
|
-
approval. Any action that would need escalation (network, writes outside the repo, an ambiguous
|
|
130
|
+
approval. Any action that would need escalation (network beyond the run's posture, writes outside the repo, an ambiguous
|
|
103
131
|
decision) is **refused and reported**, and the orchestrator handles it by hand.
|
|
104
132
|
|
|
105
133
|
## Commit prohibition — enforced by a git-write shim
|
|
@@ -142,10 +170,13 @@ non-conforming text); a raw-text retry covers a rare validation/run failure. Def
|
|
|
142
170
|
### Review read-fence (best-effort env hygiene)
|
|
143
171
|
|
|
144
172
|
`codex-review` runs under a throwaway `HOME` + `XDG_CONFIG_HOME`/`XDG_CACHE_HOME`/`XDG_DATA_HOME` with an
|
|
145
|
-
**absolute `CODEX_HOME`** so auth + history still resolve while codex's default config/cache
|
|
146
|
-
|
|
173
|
+
**absolute `CODEX_HOME`** so auth + history still resolve while codex's default config/cache roots point
|
|
174
|
+
at an empty dir (trims the roaming noise). When the user's real skill root `$HOME/.agents/skills`
|
|
175
|
+
exists, the throwaway home gets `.agents/skills` as a link to it (read-only on every posture), so a
|
|
176
|
+
review can load a skill, and the fence directives admit reading it. This is **env hygiene, NOT a security
|
|
147
177
|
boundary** — absolute paths anywhere on disk remain readable under `read-only`; the real read-scoping is
|
|
148
|
-
the prompt fence ("do not read outside the working tree, except the precomputed-diff temp file"
|
|
178
|
+
the prompt fence ("do not read outside the working tree, except the precomputed-diff temp file" and a
|
|
179
|
+
linked skill).
|
|
149
180
|
|
|
150
181
|
## `resume` — resets posture, restated via `-c`
|
|
151
182
|
|
|
@@ -157,7 +188,8 @@ the sandbox/approval/network posture, and — probed on codex-cli 0.147.0 — ac
|
|
|
157
188
|
run starts, so the failure is loud and costs no quota; the wrapper's own test pins the accepted set
|
|
158
189
|
(`RESUME_ACCEPTED_FLAGS`) precisely because the hermetic fake CLI accepts any argv and cannot answer
|
|
159
190
|
this question. The `codex-exec --resume`/`--resume-last` entrypoint handles the reset: it restates
|
|
160
|
-
the entire policy via `-c` (`
|
|
191
|
+
the entire policy via `-c` (`approval_policy=never` and the posture it chooses afresh for this run —
|
|
192
|
+
the profile `jev`'s four overrides, or `sandbox_mode=workspace-write` with
|
|
161
193
|
`sandbox_workspace_write.network_access=false`) plus the host posture's `-m`/effort and
|
|
162
194
|
`--ignore-user-config`, reads the session id from the sidecar (or an argument), and applies the same
|
|
163
195
|
EVIDENCE posture as a fresh run — `-o` for the final message, `--json` into the trace. Only a *raw*
|
|
@@ -175,8 +207,9 @@ no-op — while `codex-review` **refuses pre-spend** (the fail-closed hard-timeo
|
|
|
175
207
|
|
|
176
208
|
Both wrappers, before invoking codex:
|
|
177
209
|
|
|
178
|
-
- **unset** `
|
|
179
|
-
stray key can't switch to paid api-key billing;
|
|
210
|
+
- **unset** `OPENAI_BASE_URL` and every `*_API_KEY` except `TYPESAFE_API_KEY` (`OPENAI_API_KEY` and
|
|
211
|
+
`CODEX_API_KEY` included), so a stray key can't switch to paid api-key billing; `TYPESAFE_API_KEY`
|
|
212
|
+
passes as given (empty stays empty, unset stays unset) and changes no flag;
|
|
180
213
|
- pass **`--ignore-user-config`** so a personal `~/.codex/config.toml` cannot change behaviour. Auth
|
|
181
214
|
still works: codex reads the cached login from `CODEX_HOME` (`~/.codex`) regardless of that flag;
|
|
182
215
|
- preflight `codex login status` and refuse unless it contains `Logged in using ChatGPT`;
|
|
@@ -203,7 +236,8 @@ exec` over the precomputed diff instead, keeping every invariant intact.
|
|
|
203
236
|
| `-c key=value` | override a config value (dotted, TOML-parsed) — how policy is set deterministically |
|
|
204
237
|
| `--sandbox <mode>` | `read-only` \| `workspace-write` \| `danger-full-access` (this skill uses the first two) |
|
|
205
238
|
| `-c approval_policy=never` | never pause for interactive approval (required: exec has no TTY) |
|
|
206
|
-
| `-c sandbox_workspace_write.network_access=false` | network
|
|
239
|
+
| `-c sandbox_workspace_write.network_access=false` | network off under workspace-write (exec on today's flags) |
|
|
240
|
+
| `-c default_permissions=jev` + `-c permissions.jev.*` | the permissions profile `jev`: its filesystem part and a network limited to the listed domains (probed on 0.160.0; never beside `--sandbox`/`sandbox_mode`) |
|
|
207
241
|
| `-c hide_agent_reasoning=true` / `-c model_reasoning_summary=none` | drop chain-of-thought from output (reasoning still runs) |
|
|
208
242
|
| `-o, --output-last-message <f>` | write ONLY the final message (clean capture) |
|
|
209
243
|
| `--json` | structured event stream (`thread.started` ⇒ session id) |
|
|
@@ -230,5 +264,10 @@ exec` over the precomputed diff instead, keeping every invariant intact.
|
|
|
230
264
|
from the target project root.
|
|
231
265
|
- **`exceeded the hard cap`** (exit 124/137): the run hit `CODEX_HARD_TIMEOUT` — raise it for a
|
|
232
266
|
known-healthy slow run, or narrow the task, then re-dispatch.
|
|
233
|
-
- **codex wants to install a dependency**: it can't (
|
|
234
|
-
re-dispatch.
|
|
267
|
+
- **codex wants to install a dependency**: it can't (exec reaches at most `api.typesafe.ai` and
|
|
268
|
+
`docs.typesafe.ai`) — install it by hand, then re-dispatch.
|
|
269
|
+
- **exit 72**: codex refused the permissions profile `jev` (the message is on stderr). A refused profile
|
|
270
|
+
key, codex's error item before the first turn, is recorded for that wrapper's profile digest when the
|
|
271
|
+
state file can be written (otherwise the wrapper prints one warning naming the file), so that wrapper's
|
|
272
|
+
next run on that codex version takes today's flags — re-dispatch; a `bwrap: execvp` failure records
|
|
273
|
+
nothing and recurs on every run until its cause is fixed.
|
|
@@ -28,9 +28,10 @@ codex login status # expect: Logged in using ChatGPT
|
|
|
28
28
|
|
|
29
29
|
This caches credentials under `CODEX_HOME` (`~/.codex`, e.g. `~/.codex/auth.json`). That directory is
|
|
30
30
|
**personal** — never copy, commit, package, print, or share it. This skill needs **no API keys** and
|
|
31
|
-
must not be configured with api-key billing; both wrappers unset
|
|
32
|
-
`
|
|
33
|
-
pay-as-you-go and a personal
|
|
31
|
+
must not be configured with api-key billing; both wrappers unset `OPENAI_BASE_URL` and every
|
|
32
|
+
`*_API_KEY` except `TYPESAFE_API_KEY` (the one key a run passes, as given) and pass
|
|
33
|
+
`--ignore-user-config`, so billing can never silently fall back to pay-as-you-go and a personal
|
|
34
|
+
`~/.codex/config.toml` can never change behaviour.
|
|
34
35
|
|
|
35
36
|
## 3. Put the wrappers on `PATH`
|
|
36
37
|
|
|
@@ -69,8 +70,10 @@ if it reports a missing git work tree or root `AGENTS.md`, run it from a project
|
|
|
69
70
|
## Notes
|
|
70
71
|
|
|
71
72
|
- The wrappers are **subscription-only** by design and will not use api-key billing.
|
|
72
|
-
- `codex-exec` runs
|
|
73
|
-
|
|
73
|
+
- `codex-exec` runs **workspace-write**; `codex-review` runs **read-only**. On Linux with codex-cli
|
|
74
|
+
0.160.0 or newer, off a codex version and profile digest recorded as refused and, for `codex-exec`, from
|
|
75
|
+
a repository outside every temp root, both carry the permissions profile `jev`, whose network reaches `api.typesafe.ai`
|
|
76
|
+
and `docs.typesafe.ai` and no other host; elsewhere the network stays off. They also run on the host posture — `CODEX_MODEL` / `CODEX_EFFORT` from the
|
|
74
77
|
bridge settings file, else `gpt-6.1-sol` / `high` — checked against the installed CLI's catalog,
|
|
75
78
|
and refuse a one-off model or effort unless `CODEX_PROBE=1` (see `../SKILL.md`), enforce a hard
|
|
76
79
|
timeout, capture only codex's final message, and block codex from writing git via a shim — see
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.24.0",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -24,7 +24,8 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
|
|
|
24
24
|
|
|
25
25
|
**Per-item posture notes (the consent moment — surface BEFORE running or handing over the apply):**
|
|
26
26
|
|
|
27
|
-
- `
|
|
27
|
+
- `bridge-tier` — the rendered line IS the mutation: `velocity-profile --apply --bridge-tier` writes the project `.claude/settings.json` on that one run. It seeds, for each placed bridge only, the review wrapper's `code`-mode allow rule (a wrapper then runs UNATTENDED: it reads any repo file it is pointed at and sends the assembled payload to its subscription backend), the quoted grounding pre-step rule when `agy-review` is placed, the wrapper names in `sandbox.excludedCommands`, and the declared hosts of the placed bridges (each bundled manifest's `networkHosts`) in `sandbox.network.allowedDomains`. Where the host honours the settings sandbox keys, every sandboxed command of the project can then reach those hosts, not only the wrappers, and the `--autonomy` preview reports them as a network weakening. The writable state dirs (`writableDirs`) stay hand-apply (the sandbox lanes below). The item fires while an allow rule, an exclusion or a declared host of a placed bridge is missing, so a project wired before the tier seeded hosts is offered the one re-run.
|
|
28
|
+
- `sandbox-lane` — surface this note TOGETHER with the sandbox-lanes ladder below (the ladder IS the practical half of the note — inline, never a pointer). Pure DISCOVERABILITY: it surfaces the manifest-declared observed session-sandbox recipe (egress hosts ∪ resolved writable state dirs — `networkHosts` ∪ `writableDirs` of the wired bridges' `capability.json`, the single documentation source) and converges on a NEUTRAL fingerprint acknowledgement recorded by the consent-gated **ack writer** into the family-owned `docs/ai/acks.json` (`sandboxLaneAck`; a changed recipe re-fires the item). The store is family-owned so no host settings validator guards it (AD-055 relocated the ack off the Claude Code settings schema, which rejected the unknown key); the legacy `"agentWorkflow": { "sandboxLaneAck": … }` settings-scope key is still READ for one deprecation window (until the next kit MAJOR). It never claims the settings security keys take effect on any host class; the tier seeds `sandbox.network.allowedDomains` with the placed bridges' declared hosts only through the consented `--bridge-tier` (AD-172, which reverses the bridge council of 2026-07-11 for those hosts alone; the egress they widen for every sandboxed command is stated at that consent), and `sandbox.filesystem.allowWrite` stays unseeded (that council: a write allowance on CLI state dirs would expose credential dirs). Posture history: an IDE-managed session sandbox was live-observed (2026-07-11/12) ignoring hand-applied settings security keys in BOTH scopes, and codex needs a writable HOME (EROFS `~/.codex` in-sandbox); whether a session's sandbox honors the settings keys is runtime-unknowable from the advisor (a denial-only signal) — which is exactly why the item states only detectable facts and no zero-prompt promise on any host class.
|
|
28
29
|
|
|
29
30
|
- `read-lane` — enabling the opt-in read-only compound lane auto-approves *compounds* (and singles) of the seeded read-only core that carry ZERO shell metaprogramming: an UNATTENDED trust extension, bounded by the audited read-only core (never a command outside it; prompt-bypass only, never a sandbox bypass) and applied regardless of which of those core commands you seeded as individual settings rules. It is a PROJECT-PERSISTENT declaration in `docs/ai/lanes.json` — every future session, subagents' Bash too where the host fires hooks on subagent Bash, and (committed) every checkout. The apply depends on state: when the lane is OFF, it is the `gate-hook --read-lane` preview (whose own currency check refuses a stale hook — a pre-1.48 hook never reads `lanes.json`); when the placed hook is STALE (an enabled lane over an old hook) or MISSING, the item instead surfaces a **delete-to-reseed** / re-place recovery (a destructive `rm` + `--apply`, an attention item — never the safe preview). Risk profile: a bounded read-only trust-posture extension — no write/exec exposure beyond the audited core.
|
|
30
31
|
- `worktrees-dir` — on a settings-native host that honors the key, the HAND-APPLY line widens the OS-sandbox WRITE surface to the whole worktrees parent dir: every sibling path under it (other repositories included) becomes agent-writable, and the widening persists for every later session. A harness-managed host may ignore that project setting; grant the narrow parent through host/session controls or use the provision terminal fallback instead. When that scope is wider than you want, narrow it FIRST: create a dedicated dir yourself (outside the agent's write surface), point `docs/ai/worktrees.json` `parentDir` at it, then re-run recommendations — the item re-renders with the narrowed dir. The kit never writes sandbox filesystem allowances itself; the line is always yours to paste. **Convergence** is two-path: a declared `sandbox.filesystem.allowWrite` entry covering the probed dir (either settings scope — `~` and `~/…` resolve against home, and coverage is path-segment-aware, so a grant on a sibling or on a child never counts), or — for a host that ignores that key — the neutral dir-bound acknowledgement recorded by this item's consent-gated apply one-liner (`ack-write --lane worktrees-dir` — a dry-run preview that prints the exact `--apply`; recorded as `worktreesDirAck` in the family-owned `docs/ai/acks.json`, never a security key), while the grant advice itself rides the `recipe:` line as the labeled FIRST step; against a trusted host NO the apply stays the HAND-APPLY grant advice and no ack is offered. Neither is proof of write CAPABILITY: the provision preflight's real create+delete probe stays the runtime truth, and the fingerprint is bound to the **resolved probe dir**, so the item re-fires only when that resolved dir changes (two absent `parentDir` values sharing an existing ancestor resolve to the same dir and keep the same ack). Risk profile: a real write-surface widening where honored — scope it deliberately.
|
|
@@ -44,10 +45,10 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
|
|
|
44
45
|
|
|
45
46
|
**Sandbox lanes (what to DO with the `sandbox-lane` recipe, per host class):**
|
|
46
47
|
|
|
47
|
-
- **Settings-native sandbox** (the harness reads the `.claude/settings.json` sandbox keys):
|
|
48
|
-
- **Harness-managed sandbox** (an IDE/session-imposed sandbox that ignores the settings keys) — a NARROWEST-SCOPE ladder: (1) prefer wrapper/command-SCOPED sandbox rules where the harness offers them (only the review wrappers gain the egress + state-dir writes); (2) a SESSION-scoped allowance (the manifest hosts + the resolved state dirs for the whole session) is an INFORMED WIDENING — it carries the same blast-radius class the settings security keys were rejected for (every sandboxed command in the session gains that egress and those writable credential dirs), acceptable only as the maintainer's deliberate choice for a review-heavy session. Its **paste-ready hand-apply shape** (the kit
|
|
48
|
+
- **Settings-native sandbox** (the harness reads the `.claude/settings.json` sandbox keys): where the harness honours the `--bridge-tier` wiring's `excludedCommands`, the wrappers run OUTSIDE the sandbox and never consult `allowedDomains`/`allowWrite` — nothing beyond the tier. A harness may not honour that entry (observed 2026-10-06: Claude Code 2.1.291 ran an agent's plain `codex-review` inside its sandbox with the project entry present, and that run still reached `chatgpt.com`, which only `allowedDomains` admitted); there a recipe host outside `allowedDomains` is refused by the harness proxy or held for a network prompt (a refusal reads `Tunnel connection failed: 403` or `CONNECT tunnel failed, response 403` in the run). The lane for the hosts is the `--bridge-tier` re-run (the `bridge-tier` item offers it while a declared host of a placed bridge is missing): it seeds the placed bridges' declared hosts into `sandbox.network.allowedDomains`, and the agent never hand-adds them. For a wrapper the harness keeps sandboxed the hand-apply `allowWrite` step for the writable state dirs stays: the `filesystem.allowWrite` half of the shape in (2) below, filled from this recipe — the agent does not add it and the kit never writes it. Record the ack (the ack-write one-liner) once the lane is confirmed.
|
|
49
|
+
- **Harness-managed sandbox** (an IDE/session-imposed sandbox that ignores the settings keys) — a NARROWEST-SCOPE ladder: (1) prefer wrapper/command-SCOPED sandbox rules where the harness offers them (only the review wrappers gain the egress + state-dir writes); (2) a SESSION-scoped allowance (the manifest hosts + the resolved state dirs for the whole session) is an INFORMED WIDENING — it carries the same blast-radius class the settings security keys were rejected for (every sandboxed command in the session gains that egress and those writable credential dirs), acceptable only as the maintainer's deliberate choice for a review-heavy session. Its **paste-ready hand-apply shape** (the kit writes `allowedDomains` only via `--bridge-tier`, and never `allowWrite`) is, in `.claude/settings.json`, `"sandbox": { "network": { "allowedDomains": ["<egress host>", …] }, "filesystem": { "allowWrite": ["<writable state dir>", …] } }` — fill `<egress host>` / `<writable state dir>` from THIS recipe (the wired bridges' `capability.json` `networkHosts` ∪ resolved `writableDirs`); one consent then lands durably in config. (3) Else the per-run consented bypass. Record the ack (the ack-write one-liner) once a lane is chosen — choosing the bypass, or consciously declining, also counts.
|
|
49
50
|
- Either way the recipe is **observed-minimal, honestly incomplete** — a blocked host names itself at run time; extend by hand and re-ack (the fingerprint moves with the recipe, not with your extensions).
|
|
50
51
|
|
|
51
|
-
**Invariants:** read-only (never writes, never commits, never runs a subscription CLI) · `--cwd` required · present-even-when-empty · verdict-first from frozen templates · probe failures degrade to stated skip lines · apply one-liners are cwd-independent (absolute tool paths + a pinned `--cwd`; the sandbox-provision item pins via a `cd <root> &&` prefix — the doctor reads its cwd; the ONE exception is the `set-autonomy` item, a conversational skill invocation explicitly labeled *run IN the target project*) ·
|
|
52
|
+
**Invariants:** read-only (never writes, never commits, never runs a subscription CLI) · `--cwd` required · present-even-when-empty · verdict-first from frozen templates · probe failures degrade to stated skip lines · apply one-liners are cwd-independent (absolute tool paths + a pinned `--cwd`; the sandbox-provision item pins via a `cd <root> &&` prefix — the doctor reads its cwd; the ONE exception is the `set-autonomy` item, a conversational skill invocation explicitly labeled *run IN the target project*) · `sandbox.filesystem.allowWrite` stays hand-apply territory, and `sandbox.network.allowedDomains` is seeded only by the consented `velocity-profile --bridge-tier` · the sandbox-lane convergence is a neutral acknowledgement recorded into `docs/ai/acks.json` by the consent-gated ack writer (the legacy settings-scope key read for one deprecation window), never a security key · the `worktrees-dir` item converges on a declared `allowWrite` entry covering its probed dir, or on its own `worktreesDirAck` lane in that same store (recorded by its apply one-liner, never by the `recipe:` line).
|
|
52
53
|
|
|
53
54
|
**Exit codes:** `0` report rendered (items or the empty state); `1` error (e.g. `--cwd` is not a directory); `2` usage.
|
|
@@ -50,7 +50,7 @@ Honesty notes: tier entries get **NO PreToolUse-hook residual coverage — with
|
|
|
50
50
|
|
|
51
51
|
**The boundary of that qualifier, stated so it is not guessed.** It covers claims about `sandbox.*` keys — the block whose honouring was observed to vary by host. It deliberately does NOT cover `permissions.*` semantics (an allow rule auto-approving a command, `local > project` precedence for a non-sandbox key): that is the harness's own documented permission model, the layer this whole mode rests on, and hedging it would say nothing true. **Stated residual:** which sentences count as settings-derived runtime claims is decided by review, not by a checker — deciding whether a sentence asserts a runtime effect is a question about prose meaning, and this family deliberately has no mechanism that judges that (the same limit that retired the byte-scanning deny rung). A new claim-bearing line therefore has to carry the qualifier by authorship; the acceptance criterion is that every line stating what a `sandbox.*` key DOES carries it, and the tests pin the surfaces that exist today. What does NOT change is the CLASSIFICATION: which red-line a key would weaken, and that a proven bridge-tier exclusion is a note rather than a weakening, are properties of the declaration and stay flat statements.
|
|
52
52
|
|
|
53
|
-
**The `--bridge-tier` (own opt-in, AD-044).** Seeds what a promptless council review run needs —
|
|
53
|
+
**The `--bridge-tier` (own opt-in, AD-044).** Seeds what a promptless council review run needs — three surfaces: `permissions.allow` prefix rules, the wrapper names in `sandbox.excludedCommands` (where the host honors the settings sandbox keys the harness runs an excluded command OUTSIDE the sandbox — the wrappers need network — so a plain allowlisted invocation triggers no sandbox-bypass approval; where it does not, the wrapper starts sandboxed and simply prompts or fails loudly — fail-safe, never a silent widening), and the placed bridges' declared hosts in `sandbox.network.allowedDomains` (below). All three land in the **project** `.claude/settings.json` — the file this writer owns; an exclusion placed only in `settings.local.json` was live-observed NOT to route the command outside the sandbox (2026-07-11: the wrapper then starts sandboxed and dies on a read-only HOME + a network prompt), so hand-wiring the local file is not a working substitute for this tier. **Honesty note:** a session whose sandbox is imposed by the harness runtime itself (e.g. an IDE-managed session sandbox) may ignore settings-level exclusions entirely — there the wrappers need the session-level sandbox config (or a per-run consented bypass); the tier's seeded posture is correct for a settings-native sandbox that honours the exclusion and simply prompts again elsewhere (fail-safe, never a silent widening). A harness that reads the settings sandbox keys may still drop this exclusion (observed 2026-10-06: Claude Code 2.1.291 ran an agent's plain `codex-review` inside its sandbox with the project entry present); its lane is the settings-native one in `${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`. Each bridge's observed egress hosts are declared in its `capability.json` `networkHosts` — the single documentation source (observed-minimal — a blocked host names itself at run time; read the manifests, this doc deliberately retypes no host list). The tier ALSO seeds the placed bridges' declared hosts — the `networkHosts` of every bundled manifest whose review wrapper is placed — into the project's `sandbox.network.allowedDomains` (AD-172, which reverses the bridge council of 2026-07-11 for those hosts only): where the host honors the settings sandbox keys, a wrapper a harness keeps sandboxed despite the exclusion is no longer refused them, and every sandboxed command of the project can then reach those hosts, not only the wrappers — that widening is part of this consent, and the `--autonomy` preview reports it as a network weakening. `sandbox.filesystem.allowWrite` stays unseeded: a write allowance on CLI state dirs (`~/.codex`, `~/.gemini/…`) would make credential dirs writable to every sandboxed command, so the writable state dirs stay hand-apply. Per-bridge picture under a harness-managed sandbox (live-observed 2026-07-11/12): an IDE-managed session sandbox ignores hand-applied `sandbox.network.allowedDomains` / `sandbox.filesystem.allowWrite` in BOTH settings scopes — its own per-host network consents govern egress; the durable zero-prompt lanes there are the session/host sandbox config (hosts from `networkHosts` + the CLI state-dir writes) or the per-run consented bypass. codex additionally needs a writable HOME (EROFS `~/.codex`); note the apex-vs-wildcard nuance — an apex domain is NOT covered by its `*.`-wildcard form, so the manifests carry both forms where observed (the blocked host names itself at run time). The upgrade Recommendations advisor surfaces exactly this recipe (hosts ∪ resolved `writableDirs`) as the `sandbox-lane` discoverability item — a consent-gated **ack writer** converging on a neutral fingerprint acknowledgement recorded into the family-owned `docs/ai/acks.json` (the writable state dirs stay hand-apply; the hosts are the tier's), with the posture notes at the consent moment (`${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`). Membership is the FROZEN review-wrapper constant, **never** the execution/probe wrappers (`codex-exec`, `agy-run` keep their human prompt — delegated execution is not covered by this consent; codex-exec's nested-sandbox recovery is the canon's observed-failure lane, not a preemptive tier seed), and only the **`code` review mode** — a `plan`/`diff` invocation takes a file argument that can point OUTSIDE the repo, so those modes keep their prompt; each wrapper entry derives ONLY when its bridge is **placed on PATH** (an absent bridge is a stated skip). The seeded byte-forms (this list IS the documented-invocation source for the bridge tier):
|
|
54
54
|
|
|
55
55
|
- `Bash(codex-review code:*)` — the code-mode prefix, args wildcard; plus `codex-review` in `sandbox.excludedCommands`
|
|
56
56
|
- `Bash(agy-review code:*)` — the code-mode prefix, args wildcard; plus `agy-review` in `sandbox.excludedCommands`
|
package/tools/ack-write.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// ANSWERED renders THIS tool's PREVIEW one-liner; the preview prints the exact `--apply` command;
|
|
5
5
|
// the agent runs `--apply` only after the mode doc's §3 informed-consent confirmation. It records a
|
|
6
6
|
// NEUTRAL fingerprint acknowledgement — never a security key; the kit never writes sandbox
|
|
7
|
-
//
|
|
7
|
+
// filesystem allowances, and allowedDomains only through velocity-profile --bridge-tier.
|
|
8
8
|
//
|
|
9
9
|
// The wording is per-LANE or genuinely neutral, never "recipe": the sandbox lane acknowledges a
|
|
10
10
|
// session-sandbox recipe, but the coverage-domain lane acknowledges a census FACT and the
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// bridge-sandbox-recipe.mjs — the one reader of the bundled bridges' declared sandbox surfaces
|
|
2
|
+
// (contract: bridges/velocity-profile). The bridge tier seeds the hosts it returns and the
|
|
3
|
+
// Recommendations sandbox-lane recipe renders hosts and dirs from it, so the two cannot drift.
|
|
4
|
+
//
|
|
5
|
+
// Dependency-free, Node >= 22. No side effects on import.
|
|
6
|
+
|
|
7
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { DEFAULT_BUNDLE_ROOT } from './bridge-settings-read.mjs';
|
|
10
|
+
|
|
11
|
+
// The manifest-declared session-sandbox recipe surfaces of every BUNDLED bridge whose review
|
|
12
|
+
// wrapper is in the wired set — networkHosts ∪ writableDirs, derived from the manifests (the
|
|
13
|
+
// single documentation source), never hardcoded here.
|
|
14
|
+
export const bundledSandboxRecipe = (placedWrappers, deps) => {
|
|
15
|
+
const readFile = deps.readFile ?? readFileSync;
|
|
16
|
+
const readDir = deps.readdir ?? readdirSync;
|
|
17
|
+
const bundleRoot = deps.bundleRoot ?? DEFAULT_BUNDLE_ROOT;
|
|
18
|
+
const hosts = [];
|
|
19
|
+
const dirEntries = [];
|
|
20
|
+
for (const dir of readDir(bundleRoot)) {
|
|
21
|
+
// An unreadable/unparsable bundled manifest must NOT thin the recipe silently — a partial
|
|
22
|
+
// recipe rendered as complete is worse than none. The throw reaches the caller: a stated skip
|
|
23
|
+
// in the advisor, a STOP with zero writes in the bridge tier.
|
|
24
|
+
let manifest;
|
|
25
|
+
try {
|
|
26
|
+
manifest = JSON.parse(readFile(join(bundleRoot, dir, 'capability.json'), 'utf8'));
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// ENOTDIR = the entry is a stray regular file (.DS_Store, a README), not a bridge bundle.
|
|
29
|
+
if (err?.code === 'ENOTDIR') continue;
|
|
30
|
+
throw new Error(`bundled manifest unreadable: ${join(dir, 'capability.json')} — ${err?.message ?? err}`);
|
|
31
|
+
}
|
|
32
|
+
const reviewCmd = manifest?.roles?.review?.cmd;
|
|
33
|
+
if (!reviewCmd || !placedWrappers.includes(reviewCmd)) continue;
|
|
34
|
+
if (Array.isArray(manifest.networkHosts)) {
|
|
35
|
+
for (const h of manifest.networkHosts) if (!hosts.includes(h)) hosts.push(h);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(manifest.writableDirs)) dirEntries.push(...manifest.writableDirs);
|
|
38
|
+
}
|
|
39
|
+
return { hosts, dirEntries };
|
|
40
|
+
};
|
|
@@ -83,9 +83,9 @@ const RAW_BACKENDS = [
|
|
|
83
83
|
},
|
|
84
84
|
notes: [
|
|
85
85
|
'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',
|
|
86
|
-
'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,
|
|
86
|
+
'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 ?',
|
|
87
87
|
'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',
|
|
88
|
-
|
|
88
|
+
"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",
|
|
89
89
|
'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
|
|
90
90
|
'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',
|
|
91
91
|
],
|
|
@@ -99,7 +99,7 @@ const RAW_BACKENDS = [
|
|
|
99
99
|
continue: [],
|
|
100
100
|
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',
|
|
101
101
|
notes: [
|
|
102
|
-
'the review posture banner
|
|
102
|
+
'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',
|
|
103
103
|
'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
|
|
104
104
|
],
|
|
105
105
|
},
|
package/tools/manifest/schema.md
CHANGED
|
@@ -99,18 +99,18 @@ drift-guarded set-equal to this block by the bridge `bin/*.test.mjs` suites (hel
|
|
|
99
99
|
|
|
100
100
|
`networkHosts` declares the hosts the backend CLI is **observed** to contact (synthetic examples:
|
|
101
101
|
`*.api.backend.example`, `accounts.backend.example`; the real observed lists live in each bridge's
|
|
102
|
-
`capability.json`) — the **single
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
`capability.json`) — the **single source** of the hosts `velocity-profile --bridge-tier` seeds
|
|
103
|
+
into the project's `sandbox.network.allowedDomains` for every placed bridge, and of a hand-applied
|
|
104
|
+
session sandbox allowlist. Rules:
|
|
105
105
|
|
|
106
106
|
- Each entry is a bare dotted hostname or a `*.family` wildcard — never a scheme/path/port.
|
|
107
107
|
Entries must be unique. A malformed list **fails** `--strict` (the entries are pasted verbatim
|
|
108
108
|
into allowlist lines by the Recommendations advisor).
|
|
109
|
-
- **The
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
- **The `--bridge-tier` seed is the primary lane** (AD-172): beside the wrappers' exclusion it
|
|
110
|
+
merges the placed bridges' hosts into the project's `sandbox.network.allowedDomains`, so a
|
|
111
|
+
settings-native harness that drops the exclusion still admits them; that pre-allow widens egress
|
|
112
|
+
for **every** sandboxed command, which the tier's consent states. The **hand-apply** fallback
|
|
113
|
+
stays for a harness-managed sandbox that ignores the settings keys.
|
|
114
114
|
- Observed-minimal, honestly incomplete: a blocked host names itself at run time — extend the
|
|
115
115
|
hand-applied list by hand; the manifest list records what was actually observed.
|
|
116
116
|
|
|
@@ -132,7 +132,7 @@ declared path is only the **default under no override**. Rules:
|
|
|
132
132
|
relative path. The advisor anchors those to the **target project root** (its pinned `--cwd`),
|
|
133
133
|
matching what a wrapper invoked from the project root resolves — the documented dispatch form
|
|
134
134
|
(the wrapper itself anchors to its invocation `$PWD`); else the `default` applies.
|
|
135
|
-
-
|
|
135
|
+
- Unlike `networkHosts`, this is a **documentation source** only: the kit never seeds
|
|
136
136
|
`sandbox.filesystem.allowWrite` or any other filesystem allowance — the entries feed the
|
|
137
137
|
sandbox-lane discoverability item (session/host sandbox config is hand-applied territory).
|
|
138
138
|
|
|
@@ -638,10 +638,10 @@ export const validateManifest = (skillDir) => {
|
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
// `networkHosts` (AD-044 Plan 4, consult-locked): the backend CLI's OBSERVED egress host
|
|
641
|
-
// families
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
// Recommendations advisor renders it verbatim, so a bad entry would corrupt a
|
|
641
|
+
// families. `velocity-profile --bridge-tier` seeds them into sandbox.network.allowedDomains for
|
|
642
|
+
// placed bridges (AD-172; the pre-allow widens egress for EVERY sandboxed command, stated at that
|
|
643
|
+
// consent), and a malformed list FAILS --strict like `settings`: the tier writes it and the
|
|
644
|
+
// Recommendations advisor renders it verbatim, so a bad entry would corrupt a seeded line.
|
|
645
645
|
const networkHosts = manifest.networkHosts;
|
|
646
646
|
if (networkHosts != null) {
|
|
647
647
|
if (!Array.isArray(networkHosts) || networkHosts.length === 0) {
|
|
@@ -661,7 +661,7 @@ export const validateManifest = (skillDir) => {
|
|
|
661
661
|
|
|
662
662
|
// `writableDirs` (REC-UX-REWORK, D6): the backend CLI's writable state-dir declarations —
|
|
663
663
|
// {env, default} entries the Recommendations advisor RESOLVES at run time (a NON-EMPTY env
|
|
664
|
-
// value wins, else the default) and renders into the sandbox-lane recipe.
|
|
664
|
+
// value wins, else the default) and renders into the sandbox-lane recipe. Unlike `networkHosts`
|
|
665
665
|
// this is a DOCUMENTATION source (the kit never seeds filesystem allowances), and a malformed
|
|
666
666
|
// list FAILS --strict for the same reason: the resolved dir is rendered into a hand-applied line.
|
|
667
667
|
const writableDirs = manifest.writableDirs;
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
// below) and VERDICT-FIRST (D1): every non-optimal state opens with ONE composed verdict line.
|
|
20
20
|
// Registry strings are frozen tool DATA, fact-true, one line under the shape cap (D2); posture/
|
|
21
21
|
// risk prose lives in the mode doc at the consent moment (D3). A probe failure is a stated
|
|
22
|
-
// skipped-item line — never a crash, never a fabricated item.
|
|
23
|
-
// sandbox.network.allowedDomains
|
|
24
|
-
//
|
|
22
|
+
// skipped-item line — never a crash, never a fabricated item. sandbox.filesystem.allowWrite stays
|
|
23
|
+
// HAND-APPLY territory; sandbox.network.allowedDomains is seeded only by the consented
|
|
24
|
+
// `velocity-profile --bridge-tier` (AD-172); the sandbox-lane item's convergence is a NEUTRAL
|
|
25
25
|
// fingerprint-bound acknowledgement recorded by the consent-gated ack writer into the family-owned
|
|
26
26
|
// docs/ai/acks.json (AD-055 relocated it off the host settings schema), never a security key (D4).
|
|
27
27
|
//
|
|
@@ -79,7 +79,7 @@ import { CHECKER_CLAIM, INITIAL_ADOPTION_REASON, SOURCE_SIZE_GATE_ID, classifySo
|
|
|
79
79
|
import { resolveDeclaredDir, dirCovers, isResolvableDeclaredEntry } from './declared-paths.mjs';
|
|
80
80
|
import { resolveGitHooksPath } from './commit-guard.mjs';
|
|
81
81
|
import { loadConfig } from './orchestration-config.mjs';
|
|
82
|
-
import {
|
|
82
|
+
import { bundledSandboxRecipe } from './bridge-sandbox-recipe.mjs';
|
|
83
83
|
import { assertContainedRealPath } from './fs-safe.mjs';
|
|
84
84
|
import { loadWorktreesConfig, resolveProbeDir } from './worktrees.mjs';
|
|
85
85
|
import { preflightCheapAgents, EXECUTOR_VEHICLE } from './cheap-agents.mjs';
|
|
@@ -252,7 +252,7 @@ export const composeVerdict = ({ attention, optional, skipped }) => {
|
|
|
252
252
|
export const WHATS = Object.freeze({
|
|
253
253
|
'velocity-core': 'routine read-only commands still prompt — {n} audited read-only allowlist entr(ies) not seeded',
|
|
254
254
|
'kit-tools-tier': "the kit's own read-only tools still prompt — {n} kit-tools tier entr(ies) not seeded",
|
|
255
|
-
'bridge-tier': '
|
|
255
|
+
'bridge-tier': 'bridge-wrappers tier incomplete — {n} entr(ies) missing (allow rules, sandbox exclusions or declared hosts of placed bridges)',
|
|
256
256
|
'autonomy-policy': 'no {path} — the computed defaults apply implicitly (red-lines ask/deny; every activity floors at prompt)',
|
|
257
257
|
'autonomy-render': 'the declared autonomy policy is not rendered into .claude/settings.json — drift: {drift}',
|
|
258
258
|
'sandbox-provision': 'the OS sandbox is unavailable: {reason}',
|
|
@@ -415,7 +415,7 @@ export const OPT_IN_CAPABILITIES = Object.freeze([
|
|
|
415
415
|
{ id: 'executor-vehicle', mode: 'agents', advisorKey: 'executor-vehicle' },
|
|
416
416
|
// Exempt, not un-audited. `acceptEdits` auto-applies Edit/Write and auto-runs mkdir/touch/mv/cp:
|
|
417
417
|
// a TRUST-POSTURE change. The kit never nudges a user toward weakening their approval posture (the
|
|
418
|
-
// same doctrine that keeps sandbox
|
|
418
|
+
// same doctrine that keeps sandbox filesystem allowances HAND-APPLY); velocity presents the
|
|
419
419
|
// full honest posture at its own consent moment, where the user is already deciding.
|
|
420
420
|
{
|
|
421
421
|
id: 'accept-edits',
|
|
@@ -465,8 +465,8 @@ const probeVelocityItems = ({ root, deps, add, skip }) => {
|
|
|
465
465
|
skip('kit-tools-tier', err);
|
|
466
466
|
}
|
|
467
467
|
try {
|
|
468
|
-
const bt = planVelocityProfile(preflight, { bridgeTier: true, findWrapper: deps.findWrapper });
|
|
469
|
-
const delta = bt.bridgeToAdd.length + bt.excludedToAdd.length;
|
|
468
|
+
const bt = planVelocityProfile(preflight, { bridgeTier: true, findWrapper: deps.findWrapper, bundleRoot: deps.bundleRoot, readdir: deps.readdir, readFile: deps.readFile });
|
|
469
|
+
const delta = bt.bridgeToAdd.length + bt.excludedToAdd.length + bt.hostsToAdd.length;
|
|
470
470
|
if (delta > 0) {
|
|
471
471
|
add('bridge-tier', fillTemplate(WHATS['bridge-tier'], { n: delta }), applyLine(' --bridge-tier'));
|
|
472
472
|
}
|
|
@@ -1157,37 +1157,6 @@ const probeMasksItem = ({ root, deps, add, skip }) => {
|
|
|
1157
1157
|
// knob is retired in the wrappers (recognized, arms nothing) and an oversized code review is now a
|
|
1158
1158
|
// chunked feed with a per-part delivery proof, so there is nothing left to offer.
|
|
1159
1159
|
|
|
1160
|
-
// The manifest-declared session-sandbox recipe surfaces of every BUNDLED bridge whose review
|
|
1161
|
-
// wrapper is in the wired set — networkHosts ∪ writableDirs, derived from the manifests (the
|
|
1162
|
-
// single documentation source), never hardcoded here.
|
|
1163
|
-
const bundledSandboxRecipe = (placedWrappers, deps) => {
|
|
1164
|
-
const readFile = deps.readFile ?? readFileSync;
|
|
1165
|
-
const readDir = deps.readdir ?? readdirSync;
|
|
1166
|
-
const bundleRoot = deps.bundleRoot ?? DEFAULT_BUNDLE_ROOT;
|
|
1167
|
-
const hosts = [];
|
|
1168
|
-
const dirEntries = [];
|
|
1169
|
-
for (const dir of readDir(bundleRoot)) {
|
|
1170
|
-
// An unreadable/unparsable bundled manifest must NOT thin the recipe silently — a partial
|
|
1171
|
-
// recipe rendered as complete is worse than no item. The throw reaches the probe's catch and
|
|
1172
|
-
// becomes a stated skip.
|
|
1173
|
-
let manifest;
|
|
1174
|
-
try {
|
|
1175
|
-
manifest = JSON.parse(readFile(join(bundleRoot, dir, 'capability.json'), 'utf8'));
|
|
1176
|
-
} catch (err) {
|
|
1177
|
-
// ENOTDIR = the entry is a stray regular file (.DS_Store, a README), not a bridge bundle.
|
|
1178
|
-
if (err?.code === 'ENOTDIR') continue;
|
|
1179
|
-
throw new Error(`bundled manifest unreadable: ${join(dir, 'capability.json')} — ${err?.message ?? err}`);
|
|
1180
|
-
}
|
|
1181
|
-
const reviewCmd = manifest?.roles?.review?.cmd;
|
|
1182
|
-
if (!reviewCmd || !placedWrappers.includes(reviewCmd)) continue;
|
|
1183
|
-
if (Array.isArray(manifest.networkHosts)) {
|
|
1184
|
-
for (const h of manifest.networkHosts) if (!hosts.includes(h)) hosts.push(h);
|
|
1185
|
-
}
|
|
1186
|
-
if (Array.isArray(manifest.writableDirs)) dirEntries.push(...manifest.writableDirs);
|
|
1187
|
-
}
|
|
1188
|
-
return { hosts, dirEntries };
|
|
1189
|
-
};
|
|
1190
|
-
|
|
1191
1160
|
// D6 resolution, mirroring the wrappers' byte-semantics (`${VAR:-default}` + the exact case-arms:
|
|
1192
1161
|
// `~` / `~/…` / `/…` ride as-given; EVERY other form — including `~user/…`, which the wrappers
|
|
1193
1162
|
// never resolve as a home path — anchors like a relative path). The advisor anchors to the TARGET
|
|
@@ -1322,7 +1291,7 @@ const readReadLaneToggle = (root, deps) => {
|
|
|
1322
1291
|
// D3: the risk-marked keys — every key here has a per-item posture note in the mode doc, surfaced
|
|
1323
1292
|
// at the consent moment; the static contract test asserts EXACT bidirectional coverage
|
|
1324
1293
|
// (risk-marked keys == mode-doc note keys — a dropped note goes red, not silent).
|
|
1325
|
-
export const RISK_NOTED_KEYS = Object.freeze(['sandbox-lane', 'read-lane', 'worktrees-dir', 'adr-store-migration', 'gates-inert', 'source-size', 'gate-hook', 'mcp-channel', 'spec-adoption', 'enforcement', 'profile-gap', 'jev-connect', 'jev-skill']);
|
|
1294
|
+
export const RISK_NOTED_KEYS = Object.freeze(['bridge-tier', 'sandbox-lane', 'read-lane', 'worktrees-dir', 'adr-store-migration', 'gates-inert', 'source-size', 'gate-hook', 'mcp-channel', 'spec-adoption', 'enforcement', 'profile-gap', 'jev-connect', 'jev-skill']);
|
|
1326
1295
|
|
|
1327
1296
|
// The feature-spec layer's adoption state (contract: kit/spec-adoption). The canon lets a plan cite
|
|
1328
1297
|
// zero governing specs while a project adopts the layer, and nothing ever said whether adoption had
|
|
@@ -1362,9 +1331,11 @@ const probeSandboxLane = ({ root, deps, add, skip }) => {
|
|
|
1362
1331
|
const sandbox = settings.data?.sandbox;
|
|
1363
1332
|
const excluded = Array.isArray(sandbox?.excludedCommands) ? sandbox.excludedCommands : [];
|
|
1364
1333
|
const probePlaced = deps.findWrapper ?? ((cmd) => findOnPath(cmd, deps).state === 'present');
|
|
1365
|
-
// Wired = the
|
|
1366
|
-
// scope) — surfacing the recipe
|
|
1367
|
-
// bridge-tier item (codex terminal).
|
|
1334
|
+
// Wired = the two-surface proof the tier wrote before AD-172 (the wrapper in the PROJECT
|
|
1335
|
+
// excludedCommands + its code-mode allow rule, either scope) — surfacing the recipe before those
|
|
1336
|
+
// exist would front-run the bridge-tier item (codex terminal). The hosts surface (AD-172) is
|
|
1337
|
+
// deliberately not required: the recipe also carries the writable dirs no tier seeds, so it may
|
|
1338
|
+
// render beside a bridge-tier item offering the hosts re-run. Byte-form from the tier's own constants.
|
|
1368
1339
|
const allowRules = [
|
|
1369
1340
|
...(Array.isArray(settings.data?.permissions?.allow) ? settings.data.permissions.allow : []),
|
|
1370
1341
|
...(Array.isArray(localSettings.data?.permissions?.allow) ? localSettings.data.permissions.allow : []),
|
|
@@ -1372,7 +1343,7 @@ const probeSandboxLane = ({ root, deps, add, skip }) => {
|
|
|
1372
1343
|
const wired = BRIDGE_REVIEW_WRAPPERS.filter(
|
|
1373
1344
|
(w) => excluded.includes(w) && probePlaced(w) && allowRules.includes(`Bash(${w} ${BRIDGE_REVIEW_MODE}:*)`),
|
|
1374
1345
|
);
|
|
1375
|
-
if (wired.length === 0) return; // the tier
|
|
1346
|
+
if (wired.length === 0) return; // the tier's rules and exclusions are not wired — the bridge-tier item covers first
|
|
1376
1347
|
const { hosts, dirEntries } = bundledSandboxRecipe(wired, deps);
|
|
1377
1348
|
const env = deps.getenv ?? process.env;
|
|
1378
1349
|
const home = deps.home ?? homedir();
|
|
@@ -1797,8 +1768,8 @@ section renders present-even-when-empty ("${RECOMMENDATIONS_EMPTY_LINE}"); a pro
|
|
|
1797
1768
|
stated skipped-item line. Apply lines are cwd-independent (absolute tool paths, a pinned --cwd;
|
|
1798
1769
|
the doctor item pins via a cd prefix; the ONE exception is the set-autonomy item — a
|
|
1799
1770
|
conversational skill invocation labeled "run IN the target project") and preserve each writer's
|
|
1800
|
-
own consent semantics; the
|
|
1801
|
-
|
|
1771
|
+
own consent semantics; sandbox filesystem allowances stay HAND-APPLY territory, the placed bridges' declared hosts reach sandbox.network.allowedDomains only through the
|
|
1772
|
+
consented velocity-profile --bridge-tier, and the sandbox-lane convergence is a neutral fingerprint acknowledgement recorded by
|
|
1802
1773
|
the consent-gated ack writer into docs/ai/acks.json (never a security key).
|
|
1803
1774
|
|
|
1804
1775
|
Read-only: never writes, never commits, never runs a subscription CLI. Exit codes: 0 report
|