@junghanacs/entwurf 0.21.0 → 0.22.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/AGENTS.md +2 -2
- package/BASELINE.md +3 -1
- package/CHANGELOG.md +278 -0
- package/DELIVERY.md +156 -26
- package/README.md +64 -13
- package/VERIFY.md +67 -11
- package/docs/external-mcp-host.md +16 -6
- package/docs/setup-clean-host.md +63 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +40 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +194 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +160 -25
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
- package/mcp/entwurf-bridge/src/index.ts +50 -16
- package/mcp/entwurf-bridge/tsconfig.build.json +9 -0
- package/package.json +2 -2
- package/pi-extensions/entwurf-control.ts +4 -4
- package/pi-extensions/lib/codex-caller-seat.ts +204 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +218 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
- package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
- package/pi-extensions/lib/mux-fresh-call.ts +196 -37
- package/run.sh +127 -3
- package/scripts/check-codex-app-server-launch.ts +445 -0
- package/scripts/check-entwurf-v2-production.ts +42 -1
- package/scripts/check-entwurf-v2-send.ts +26 -7
- package/scripts/check-gate-qualification.ts +4 -2
- package/scripts/check-mux-launch-tmux.ts +331 -35
- package/scripts/codex-app-server-launch.sh +275 -0
- package/scripts/codex-socket-path.ts +33 -0
- package/scripts/codex-terminal-title-config.py +500 -0
- package/scripts/codex_toml_io.py +121 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +11 -3
- package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
- package/scripts/mutants/codex-app-server-launch.json +157 -0
- package/scripts/mutants/codex-caller-seat.json +336 -0
- package/scripts/mutants/codex-native.json +3 -3
- package/scripts/mutants/mux-fresh-call.json +86 -14
- package/scripts/mutants/v2-surface.json +22 -0
- package/scripts/smoke-codex-config-state.sh +192 -3
- package/scripts/smoke-codex-fresh-live.ts +277 -37
- package/scripts/smoke-entwurf-chain-live.ts +50 -0
- package/scripts/smoke-setup-verdict.sh +13 -11
package/AGENTS.md
CHANGED
|
@@ -21,7 +21,7 @@ Ask: does this follow capability rather than a surface name; record backend asym
|
|
|
21
21
|
- **pi adapter:** attaches its native session to a record, hosts the record-keyed control socket, and exposes pi’s native tools.
|
|
22
22
|
- **Native bridges:** register existing sessions without owning transcript or auth. Claude Code and Copilot are self-fetch; Antigravity and Codex are native-push. Codex remains native rather than ACP so its vendor tools, delegation, and work context remain intact. Admission/evidence details belong in [DELIVERY.md](./DELIVERY.md) and [VERIFY.md](./VERIFY.md).
|
|
23
23
|
- **ACP plugin:** one `entwurf` provider under an isolated overlay. The host pi session is already a record-backed citizen; ACP creates neither a second citizen nor a socket layer.
|
|
24
|
-
- **mux:** launch-only placement and fixed-runtime launch. It never imports into delivery. Fresh identity arrives only through its callback envelope, never a lookup; same-id visible resume keeps identity/liveness/locking on the v2 side of the injected seam. Fresh call accepts only the narrow model, literal absolute cwd, and existing caller-server seat inputs; it creates no session and does not infer seat from cwd. Codex alone has
|
|
24
|
+
- **mux:** launch-only placement and fixed-runtime launch. It never imports into delivery. Fresh identity arrives only through its callback envelope, never a lookup; same-id visible resume keeps identity/liveness/locking on the v2 side of the injected seam. Fresh call accepts only the narrow model, literal absolute cwd, and existing caller-server seat inputs; it creates no session and does not infer seat from cwd. A Codex CALLER alone has an omitted-placement rule: it opens beside its own TUI pane, matched by thread-id in that pane title; 0/2+ matches reject; explicit placement still wins. Do not turn either fresh call or resume into a generic driver, command/env carrier, or second creation API. Exact module/argv/import ownership: [docs/mux-launch-rail.md](./docs/mux-launch-rail.md).
|
|
25
25
|
- **Delivery:** `entwurf_v2` addresses an existing garden id through a live control socket, deliverable self-fetch mailbox, or probe-alive native push. It never starts a process. Fresh creation and visible same-id resume are separate lifecycle verbs.
|
|
26
26
|
|
|
27
27
|
## Hard Rules
|
|
@@ -41,7 +41,7 @@ Ask: does this follow capability rather than a surface name; record backend asym
|
|
|
41
41
|
13. **Doctors report runtime and ownership independently.** Working runtime does not prove ownership; broken ownership does not erase runtime observation. Required failure on either axis is red.
|
|
42
42
|
14. **Native-hook ownership is structural.** Use shipped exec launchers and provenance, shared plausible-owner-pid policy, no shell fallback or ancestry guess. Version floors belong to their package source and are enforced there; hook contract/docs own the changing detail.
|
|
43
43
|
15. **Crash, don’t warn.** Invalid config/path/model/store state throws. Empty catches are only bounded environment probes; diagnostics go to stderr.
|
|
44
|
-
16. **mux launches, never delivers.** A tmux handle is an ephemeral view, not an address or liveness receipt; screen text/keystrokes are not delivery evidence. Codex's
|
|
44
|
+
16. **mux launches, never delivers.** A tmux handle is an ephemeral view, not an address or liveness receipt; screen text/keystrokes are not delivery evidence. A Codex caller's seat is the pane whose title carries its own thread-id, a placement input only; it is never an address, and never a pane nobody named. Cross-window adjacency requires the documented observed-coordinate proof, never pane guessing.
|
|
45
45
|
17. **Entwurf installs itself; setup composes.** Installation supplies Entwurf bytes and development fixtures, never a harness binary, subscription, credential, or login. Setup detects existing harness capability; absence is explicit skip and detected incompleteness is non-green. Portability evidence and rail/runtime support remain separate.
|
|
46
46
|
|
|
47
47
|
History and incident detail belong in [CHANGELOG.md](./CHANGELOG.md), issue threads, [BASELINE.md](./BASELINE.md), or source-adjacent comments. When prose and behavior disagree, repair the owning source or document.
|
package/BASELINE.md
CHANGED
|
@@ -34,7 +34,9 @@ not **UNSUPPORTED** — the deliberate never, which today is native Windows only
|
|
|
34
34
|
| Maintainer NixOS installed package | **certified** for `0.12.8-repair.1` | 2026-07-25 registry install → doctor exit 0 (HISTORY) |
|
|
35
35
|
| Secondary Ubuntu installed package | **certified** for `0.12.8-repair.1` | 2026-07-25 same artifact, isolated agent dir → doctor exit 0 (HISTORY) |
|
|
36
36
|
| macOS Claude meta-bridge | NOT CERTIFIED — pending physical host | No physical-Mac doctor yet; a CI runner has no Claude login. Physical-host rental remains unscheduled; no cut has carried it. |
|
|
37
|
-
| Native Codex Linux
|
|
37
|
+
| Native Codex Linux caller-DIRECTORY axis (current; #95 lane C) | **certified** | 2026-09-16, Codex 0.153.4: 65-assertion LIVE, exit 0, at `b3e07dc` with a clean worktree. Same host and topology as the lane B row below (app-server `737636` in A `$30/@41/%45`; initial Pi `20260916T154902-556eb5` `$2/@58`, Codex `20260916T155037-10ddaa` `$2/@59`, Codex-opened Pi `20260916T155102-9add8a` `$2/@61`, `seat-source=codex-title-anchor`). **hop 1 (cwd requested):** pane `#{pane_current_path}` = rollout `session_meta.cwd` = record cwd = requested scratch = `/tmp/entwurf-codex-fresh-live-db65N2`, against app-server cwd `/home/junghan/repos/gh/entwurf` read from `/proc/737636/cwd`. **hop 2 (cwd NOT requested):** the outbound Pi's pane, its own birth-written record and the Codex caller's record all read that same scratch, and its receipt names the caller-record rule. Artifact `.probe-artifacts/codex-fresh-live-w4yJBw/`; stdout `.probe-artifacts/lane-c-live-20260916T154900.log` sha256 `712050e7a8cf03ece98e7f34029ae98a92e9b2a7fd96aff87d0c1e6ba2ee9af2`. Cleanup reclaimed `@58`/`@59`/`@61` with no failure; records and transcripts preserved; app-server and `$30` untouched. 65 and 56 are different contracts — lane C added the cwd axis on top of lane B. Long floors (qualification body, frozen `check:full`) NOT run at this SHA; GLG decides host vs CI. |
|
|
38
|
+
| Native Codex Linux caller-seat topology (current; #95 lane B) | **certified** | 2026-09-16, Codex 0.153.4: 56-assertion LIVE, exit 0. App-server `737636` alone in session A `$30/@41/%45`; initial Pi `20260916T144205-e876ff` `$2/@46`, omitted-placement Codex `20260916T144230-d050d7` `$2/@47`, Codex-opened Pi `$2/@48`. A ≠ S is the decisive half: the app-server env names A, so S can only have come from the caller's pane title — receipt `seat-source=codex-title-anchor`. Artifact `.probe-artifacts/codex-fresh-live-nYcGC1/run-stdout.log` sha256 `ed60c2bd…`; the 43-assertion hop-1 run that measured the D1 retirement is kept at `codex-fresh-live-4aFCDD/run-stdout.log` sha256 `8392a603…`. Cleanup reclaimed `@46`/`@47`/`@48` with no failure; app-server and `$30` untouched. |
|
|
39
|
+
| Native Codex Linux home topology (PAST CONTRACT — #95 D1 retired the fixed home 2026-09-16) | **certified** for 0.21.0 as shipped | 2026-09-12, Codex 0.153.4: 57-assertion explicit-home LIVE. Initial Pi `$150/@397`; operator app-server `$158/@390/%390`; omitted-placement Codex `$158/@398`; Codex-opened Pi `$158/@399`; exact callbacks and addressed delivery both ways. Receipt and digest: `DELIVERY.md`. Deterministic closure of that axis: qualification 460/460 across 43 lanes with origin/snapshot purity green, then frozen full floor exit 0 in 506s — both pre-amendment. **Observer-amended acceptance, 2026-09-12:** standalone LIVE 48 assertions exit 0, source audit `initial-pi=3/3`/`codex=3/3` completed exact, initial Pi `$150/@431`, Codex `$158/@432`, Codex-opened Pi `$158/@433`, all three reclaimed by their own source receipts; artifact `.probe-artifacts/codex-fresh-live-fZccoK/`. The 48 and 57 counts are different contracts, not a regression. **Release acceptance at that same fingerprint:** `check:full` exit 0 502s, LIVE release gate `--cut` MUST 24/0/0 · BEHAVIOR 1/0/0 · `cut: OK`, `check-gate-qualification` 475/475 across 43 lanes with origin purity green, and the aggregate's Codex leg repeating the 48-assertion acceptance (artifact `codex-fresh-live-2oId4C`). Unrestricted attached-TUI placement remains unsupported and unclaimed. |
|
|
38
40
|
| Darwin install fence (meta/copilot/omp-bridge + omp-receive) | portable (Linux or Darwin) | Four installers accept Darwin; python3/node/harness presence holds the seat, not the platform name. A Darwin install is not a rail receipt. |
|
|
39
41
|
| Doctor fail-closed on unreadable environ | Linux measured; Darwin unmeasured | Copilot launch-flag and omp identity-carrier: missing `/proc/<pid>/environ` as predicate INPUT is `UNVERIFIABLE` (non-green), not a benign note. omp distinguishes `pgrep` exit 1 (absent) from exit 2 (enumeration failed); copilot distinguishes ENOENT/ESRCH (gone) from other errno (read denied). |
|
|
40
42
|
| setup on uncertified platform with harness present | named non-green | All 13 harness units ask `harness_rail_certified_platform()`; uncertified → FAIL whose wording is not "install failed". Harness-absent Darwin setup (the CI cell) can still be computed green. |
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,284 @@ All notable changes to this project will be documented here. Format follows [Kee
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.22.0 - 2026-09-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **A Codex sibling opened into a directory the vendor has no answer for now SAYS SO
|
|
12
|
+
(`codex-launch-cwd-undecided`).** `entwurf_fresh_call` reads the launch directory against the
|
|
13
|
+
operator's Codex config and prints what it saw — and then opens the window anyway. **It is a
|
|
14
|
+
diagnostic, not a gate, and that is the product decision (GLG, 2026-09-17):** the vendor's
|
|
15
|
+
consent screen is self-repairing for the human this rail exists to put a window in front of, one
|
|
16
|
+
answer teaches the vendor that directory for good, and a refusal would replace that with "no
|
|
17
|
+
window, go run codex yourself, then call again" while having to be right about a decision this
|
|
18
|
+
process cannot fully see. The UNATTENDED case is answered where it belongs —
|
|
19
|
+
`smoke-codex-fresh-live` asserts the same leaf up front, so a release gate with nobody at the
|
|
20
|
+
keyboard reads a named precondition instead of a callback timeout. `[source rust-v0.153.4]` a `--remote` startup — which this
|
|
21
|
+
argv always is — runs `check_directory_trust` on the `-C` value (`tui/src/lib.rs:1699-1725`), and
|
|
22
|
+
nothing on that path consults the approval or sandbox policy
|
|
23
|
+
(`tui/src/onboarding/directory_trust.rs:33-130`): the
|
|
24
|
+
`--dangerously-bypass-approvals-and-sandbox` token this launcher already carries turns off
|
|
25
|
+
approvals, never folder consent. A DIRECT decision is keyed to the exact directory and nothing else — for
|
|
26
|
+
`ProjectTrustHost::Remote` the lookup is `vec![cwd_key]` (`tui/src/config_update.rs:290-296`), with
|
|
27
|
+
no project-root marker, no git root and no parent inheritance. **The question is whether the
|
|
28
|
+
directory has been ANSWERED, not whether it is trusted:** a saved `untrusted` folder is
|
|
29
|
+
explicitly skipped on a remote target (`onboarding/directory_trust.rs:94-96`), so that turn
|
|
30
|
+
starts too and is not noted at all. A cwd inside an explicitly `untrusted` project gets its OWN
|
|
31
|
+
note (`codex-launch-cwd-untrusted-ancestor`) because the vendor answers that one with an error
|
|
32
|
+
rather than a screen — `pass the repository root explicitly with --cd`
|
|
33
|
+
(`config_update.rs:357-371`) — so its repair names a different directory, not a prompt.
|
|
34
|
+
|
|
35
|
+
**The leaf is deliberately NARROWER than the vendor's own judgment and says so rather than
|
|
36
|
+
claiming equivalence.** It reads the operator's `config.toml`; the vendor reads an effective
|
|
37
|
+
config — system, managed and cloud layers merged around that file — through its app-server, and
|
|
38
|
+
an enabled PROJECT LAYER consents on a directory's behalf with no entry at all
|
|
39
|
+
(`config_update.rs:346-354`). That gap is affordable precisely because nothing here refuses:
|
|
40
|
+
everything the leaf cannot see stays silent, and a note that turns out to be unnecessary costs a
|
|
41
|
+
line of stderr. **`[측정 2026-09-16]` this is the
|
|
42
|
+
failure it retires, not a hypothetical:** four `smoke-codex-fresh-live` runs, one scratch directory
|
|
43
|
+
each; the two whose directory carried a trust entry (`…-db65N2`, `…-pBXxOJ`, both answered by a
|
|
44
|
+
human at the keyboard) passed, and the two unattended release-gate runs (`…-2zznHl`, `…-kSsoAn`)
|
|
45
|
+
died with no rollout written after 21:28 at all — the sibling had opened on a consent screen and
|
|
46
|
+
started no turn, so there was no birth, no callback, and nothing to address. The caller then waits
|
|
47
|
+
out its entire timeout on a window that is merely waiting to be answered, which reads as a delivery
|
|
48
|
+
defect and is a folder question. The directory asked about is read back off codex's own `-C` token
|
|
49
|
+
rather than recomputed, so the consent question cannot drift from the thread's real start
|
|
50
|
+
directory, and the inherited default stays resolved in exactly one place.
|
|
51
|
+
|
|
52
|
+
- **A Codex caller opens its sibling beside its own pane (#95 lane B).** Until now the seat a
|
|
53
|
+
Codex citizen got came from the app-server's inherited `TMUX`/`TMUX_PANE`, so a sibling opened
|
|
54
|
+
from Codex landed wherever the operator happened to have started the app-server rather than
|
|
55
|
+
beside the TUI the operator was looking at. `[측정 2026-09-16, codex-cli 0.153.4]` with
|
|
56
|
+
`thread-id` in `[tui].terminal_title` the TUI writes its thread UUID into the OSC 0 title and
|
|
57
|
+
tmux answers with it as `#{pane_title}` — truncated to 32 chars, i.e. 29 graphemes plus `...`
|
|
58
|
+
(`status_surfaces.rs:892-894`, `:1027-1043`; unchanged at `rust-v0.154.0`). So when the
|
|
59
|
+
reconciled sender of a fresh call is a record-backed `codex` citizen and `placement` is
|
|
60
|
+
omitted, that request's own `_meta.threadId` selects the pane whose title carries the token and
|
|
61
|
+
its `$session` becomes the target. **No vendor patch, no new carrier: one config item.**
|
|
62
|
+
- **`entwurf install-codex-terminal-title` / `uninstall-` / `doctor-`** — a fourth Codex
|
|
63
|
+
ownership atom, owning only `thread-id` membership within `tui.terminal_title`. A different key
|
|
64
|
+
and a different axis from `install-codex-statusline`: `status_line` is what a human reads
|
|
65
|
+
inside the TUI, `terminal_title` is what the multiplexer reports back. The seeded list is
|
|
66
|
+
`["activity", "project-name", "thread-id"]` — `activity` leads because the herdr Codex detector
|
|
67
|
+
keys on the spinner and `Action Required` prefix — and an existing operator list is APPENDED to,
|
|
68
|
+
never reordered. `setup` composes it on a detected Codex host.
|
|
69
|
+
- **The seat now follows the CALLER, never the backend being opened, on both fresh-call surfaces.**
|
|
70
|
+
An explicit `placement.tmuxSession` always wins; then a Codex CALLER's own pane
|
|
71
|
+
(`codex-title-anchor`); then the caller's own session. The anchored receipt reports its SOURCE and
|
|
72
|
+
no session name, because a session was observed rather than a name requested.
|
|
73
|
+
- **`entwurf codex-app-server` — the one command that starts the operator's Codex app-server.**
|
|
74
|
+
Every Codex rail needs a server the operator starts themselves, and the command was
|
|
75
|
+
`codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"`:
|
|
76
|
+
correct, in three documents, and in the text of a refusal — which meant most operators met it
|
|
77
|
+
only AFTER something had already failed. This verb owns that SPELLING and nothing else. It
|
|
78
|
+
`exec`s the vendor in the terminal it was typed in (cwd, pid, tty and exit status are the
|
|
79
|
+
server's; Ctrl-C is the operator's), creates the control directory, forwards the rest of the
|
|
80
|
+
argv byte-identical, and strips `PI_SESSION_ID`/`PI_AGENT_ID` so the server every bridge child
|
|
81
|
+
inherits from cannot carry someone else's identity. The address comes from the SAME leaf
|
|
82
|
+
delivery, preflight and the LIVE smoke read: the launcher ASKS that resolver through the new
|
|
83
|
+
internal `entwurf codex-socket-path` rather than re-deriving the path, so there is exactly one
|
|
84
|
+
spelling of this address instead of two that agree on the inputs somebody thought of.
|
|
85
|
+
Refusals are named and happen before the exec: a live socket (naming the pid whose `/proc`
|
|
86
|
+
cmdline actually carries that socket, and saying plainly when the host holds none — never an
|
|
87
|
+
inferred owner), a resolved address that is not absolute or carries a control character
|
|
88
|
+
(refused before anything is created: a relative socket is a different file for every process
|
|
89
|
+
that resolves it, and the bridge that will look for it is the app-server's MCP child with its
|
|
90
|
+
own cwd), an unidentifiable path, a second
|
|
91
|
+
`--listen`, a recursive launch, a socket classification nobody wrote, no `codex` on PATH. A dead
|
|
92
|
+
socket file is reported and launched over. **It is a managed spelling, not a managed
|
|
93
|
+
lifecycle**: no supervisor, no restart, no daemon, no pid file, and `entwurf_fresh_call` still
|
|
94
|
+
REJECTS a missing app-server rather than starting one. The tmux seat this server gives caller-seat lookups is reported as a fact, not
|
|
95
|
+
enforced as a requirement. Typing the raw vendor command remains equivalent.
|
|
96
|
+
|
|
97
|
+
### Verification
|
|
98
|
+
|
|
99
|
+
- **Release acceptance for this cut.** `[LIVE 2026-09-17, oracle]` `LIVE=1 ./run.sh release-gate
|
|
100
|
+
/tmp/entwurf-release-gate-0.22.0.royX2L --cut` → **`MUST: PASS=24 FAIL=0 SKIP=0`**, **`BEHAVIOR:
|
|
101
|
+
PASS=1 FAIL=0 SKIP=0`**, **`cut: OK`**, exit 0, 18:47–20:02 KST; log preserved at
|
|
102
|
+
`<scratch>/release-gate.log`. Inside it: `check:full` exit 0 in 521s, `check-gate-qualification`
|
|
103
|
+
**520/520 killed**, `smoke-entwurf-chain-live` 24 assertions, `smoke-codex-fresh-live` **66
|
|
104
|
+
assertions** — one more than lane C's 65, because the launch-directory precondition this release
|
|
105
|
+
adds is asserted before anything launches. Gate env `ENTWURF_CODEX_FRESH_MODEL=gpt-5.6-luna`,
|
|
106
|
+
`ENTWURF_CODEX_FRESH_PI_MODEL=openai-codex/gpt-5.6-luna`, `ENTWURF_CODEX_APP_SERVER_PID` naming an
|
|
107
|
+
operator-started app-server in its own tmux session. Exact-SHA CI at the pre-release commit:
|
|
108
|
+
run [`35202801617`](https://github.com/junghan0611/entwurf/actions/runs/35202801617)
|
|
109
|
+
`event=workflow_dispatch` @ `f5d6e10`, all four required jobs success and the `check` job's
|
|
110
|
+
qualification BODY step success.
|
|
111
|
+
- **The host's Codex hook declaration was measured with the herdr SessionStart group temporarily
|
|
112
|
+
removed, and that is a debt this release does not pay.** `~/.codex/hooks.json` on `oracle` carries
|
|
113
|
+
a second SessionStart group installed by herdr, so `doctor-codex-birth` reports the declaration as
|
|
114
|
+
edited after install and `smoke-codex-fresh-live` cannot run. For this cut the file was moved to
|
|
115
|
+
`hooks.json.herdr-bak` and re-published by `install-codex-birth`; the vendor's trust receipt for
|
|
116
|
+
`…/hooks.json:session_start:0:0` survived that re-publication, measured by a real Codex TUI
|
|
117
|
+
starting with no consent screen. The herdr group is restored after the cut and the doctor goes RED
|
|
118
|
+
again on purpose. **#117 owns the real repair** — entwurf requires a whole-file digest where the
|
|
119
|
+
unit it actually owns is one group.
|
|
120
|
+
- **Two gate defects found by the release gate itself, both repaired in test/gate code with zero
|
|
121
|
+
production bytes.** `check-gate-qualification` scored **518/520** on the first prepared commit and
|
|
122
|
+
both survivors were launch-directory claims whose assertion could not see the defect its mutant
|
|
123
|
+
plants. `CODEX-LAUNCH-CWD-ANCESTOR-PLAIN-PATHS-ONLY` guards two predicates — the cwd side and the
|
|
124
|
+
key side — and every non-plain cell put the encoded character in the KEY, so the key-side filter
|
|
125
|
+
answered alone and deleting the cwd-side guard changed nothing; cells that vary the CWD against a
|
|
126
|
+
plain untrusted key now close it. `FRESHCALL-CODEX-LAUNCH-CWD-NOTES-NEVER-REFUSES` asserted the
|
|
127
|
+
`indexOf("-C")` lookup but not the value read at it, so a note that asked about the REQUESTED cwd
|
|
128
|
+
instead of codex's own `-C` token passed; the three statements are now pinned contiguously.
|
|
129
|
+
Neither mutant was stale — each still matched its production line exactly once, which is what made
|
|
130
|
+
the hole an assertion hole rather than drift.
|
|
131
|
+
- **`smoke-entwurf-chain-live` went red because a model read the operator's policy correctly, and
|
|
132
|
+
the repair moved the authority rather than the wording.** The native Claude Code hop declined to
|
|
133
|
+
forward, citing the operator's global AGENTS.md; B, C and D were all born and addressable and the
|
|
134
|
+
terminus simply received nothing. A first repair put the provenance in the payload and the hop
|
|
135
|
+
named those sentences as the tell — content that vouches for itself is the shape of an injection,
|
|
136
|
+
so no sentence inside the payload can settle it. The payload is therefore byte-identical to
|
|
137
|
+
before, and the fact now rides the disposable world the turn starts in: the gate mints that
|
|
138
|
+
directory, every `ENTWURF_META_*_DIR` resolves inside it, and it exists because the operator ran
|
|
139
|
+
`release-gate`, so an `AGENTS.md`/`CLAUDE.md` written there is a standing operator instruction of
|
|
140
|
+
exactly the kind the operator's own global policy defers to for directory-scoped behaviour. **No
|
|
141
|
+
negative instruction was added**: a "do not check your policy" line would buy the same green by
|
|
142
|
+
asking the model to stop reading, and would keep passing after the chain broke. Accepted on the
|
|
143
|
+
host default model with no model pin.
|
|
144
|
+
|
|
145
|
+
- **The Codex LIVE card now measures WHERE a sibling starts, and the acceptance is recorded.**
|
|
146
|
+
`[LIVE 2026-09-16, Codex 0.153.4, 65 assertions, exit 0]` hop 1 requests a cwd and joins four
|
|
147
|
+
authorities that cannot borrow from each other — the pane's `#{pane_current_path}`, the vendor
|
|
148
|
+
rollout's `session_meta.cwd`, the Entwurf record and the requested scratch; hop 2 requests NEITHER
|
|
149
|
+
placement NOR cwd, so its directory can only come from the Codex caller's own record. Both are
|
|
150
|
+
asserted different from the app-server's live `/proc/<pid>/cwd`, which is the failure mode staying
|
|
151
|
+
visible rather than coincidentally equal. 65 and 56 are different contracts: lane C added the cwd
|
|
152
|
+
axis on top of lane B's seat axis. Receipts and digests in `DELIVERY.md` / `BASELINE.md`.
|
|
153
|
+
- **Caller-seat LIVE accepted on Linux, 2026-09-16 (Codex 0.153.4): 56 assertions, exit 0.** The
|
|
154
|
+
operator-owned app-server stayed alone in session A (`$30`/`@41`/`%45`, pid `737636`) while the
|
|
155
|
+
initial Pi, the omitted-placement Codex it opened, and that Codex's own outbound Pi all landed in a
|
|
156
|
+
different session S (`$2`: `@46`, `@47`, `@48`). A ≠ S is what makes the run decisive: the
|
|
157
|
+
app-server's inherited `TMUX` names A, so the pre-#95 environment fallback would have put the
|
|
158
|
+
outbound Pi there. Its receipt instead records `seat-source=codex-title-anchor`. `DELIVERY.md` owns
|
|
159
|
+
the coordinates, garden ids and digests, including the 43-assertion run kept as the D1 measurement.
|
|
160
|
+
- **`check-codex-app-server-launch` — 29 assertions, hermetic, plus 9 mutants all killed by
|
|
161
|
+
their own claim.** A fake vendor on a sandbox PATH under the real name reports the argv, parent
|
|
162
|
+
and environment it was handed; nothing is asserted by reading the launcher's source.
|
|
163
|
+
`check-pack-install` proves the INSTALLED verb reaches a vendor at the address the INSTALLED
|
|
164
|
+
compiled resolver computes — a file-list assertion cannot tell a shipped script from one that
|
|
165
|
+
resolves its siblings wrongly under `node_modules`.
|
|
166
|
+
- **Independent review caught a false success this gate had certified, and the repair removed the
|
|
167
|
+
class rather than the instance.** The launcher's first version re-derived the socket path in
|
|
168
|
+
bash and the gate compared the two spellings over four ASCII-normal inputs they agreed on.
|
|
169
|
+
`[측정 2026-09-16]` they disagree on `CODEX_HOME=$'\ufeff'` — JS `trim` strips U+FEFF, a POSIX
|
|
170
|
+
`[:space:]` trim does not, so the managed launch would have started a server at
|
|
171
|
+
`<BOM>/app-server-control/app-server-control.sock` while delivery looked at `$HOME/.codex` —
|
|
172
|
+
and on `path.join`'s normalization of a trailing slash or a `..` segment. Widening the matrix
|
|
173
|
+
would only have added the inputs somebody thought of, so the second spelling was deleted: the
|
|
174
|
+
launcher asks, the gate's hostile cells (BOM, trailing slash, `..`) keep it asking, and the
|
|
175
|
+
mutant that used to drop `CODEX_HOME` now re-derives the path in bash instead. The same review
|
|
176
|
+
found the `/proc` holder assertion over-determined (it accepted the fallback text OR a pid, so
|
|
177
|
+
deleting the scan passed) and the socket classifier not fail-closed on an unrecognised reading.
|
|
178
|
+
A second review pass then found that asking one authority for the address does not make the
|
|
179
|
+
ANSWER safe: the resolver returns `CODEX_HOME` faithfully, so
|
|
180
|
+
`CODEX_HOME=$'relative\ncontrol-home'` had the launcher create that directory under its caller's
|
|
181
|
+
cwd and bind a relative socket — a different file for every process that resolves it, while the
|
|
182
|
+
gate still passed. This launcher is the only Codex surface that CREATES and binds rather than
|
|
183
|
+
reads, so it now refuses a non-absolute or control-character address before the first write.
|
|
184
|
+
Every one of those is a discriminating cell with a mutant of its own; the lane is 9.
|
|
185
|
+
- **Two gate defects surfaced on the way and were fixed rather than worked around.** A mutant whose
|
|
186
|
+
`find` no longer matched had been silently untested since a rename — a stale mutant is a claim
|
|
187
|
+
nobody is checking, so `check-gate-qualification` scores it red. And the LIVE source-call oracle
|
|
188
|
+
compared tool arguments with `isDeepStrictEqual`, so a model that omitted the optional `wants_reply`
|
|
189
|
+
rather than passing it explicitly read as drift; it now normalizes that one key to its schema
|
|
190
|
+
default. Both are recorded in `VERIFY.md`'s affected-set rules so the inner loop stops missing them.
|
|
191
|
+
|
|
192
|
+
### Changed
|
|
193
|
+
|
|
194
|
+
- **`smoke-codex-fresh-live` launches in ONE stable directory instead of a fresh `mkdtemp` per
|
|
195
|
+
run.** Per-run random scratch made every run an undecided folder — a trust prompt per run, an
|
|
196
|
+
entry per run in the operator's `~/.codex/config.toml`, and unattended failure whenever nobody was
|
|
197
|
+
there to answer. The path is now one stable `os.tmpdir()`-rooted directory, answered `Trust`
|
|
198
|
+
once; the smoke PRINTS it as `launch-cwd <dir>` on every run and asserts that consent by name
|
|
199
|
+
BEFORE it launches anything, so a missing answer reads as `codex-launch-cwd-undecided` — with the
|
|
200
|
+
exact `codex -C <dir>` repair command — at the top of the run rather than as a callback timeout
|
|
201
|
+
twenty assertions later. The directory is printed rather than documented as a literal because
|
|
202
|
+
`os.tmpdir()` is a host fact: a doc spelling it `$TMPDIR/...` would send an operator with no
|
|
203
|
+
`TMPDIR` to `/entwurf-codex-fresh-live` in the filesystem root, which is a different directory
|
|
204
|
+
the vendor consents to separately. Lane C is unaffected: its only requirement of that directory is that it differ
|
|
205
|
+
from the app-server's own, which the assertion still proves.
|
|
206
|
+
- **The Codex live-spend default comes down to `gpt-5.6-luna`.** The `entwurf-dev` skill's Codex
|
|
207
|
+
backend default and the `smoke-codex-fresh-live` recommendation in `VERIFY.md` move off
|
|
208
|
+
`gpt-5.6-sol`. **This is the Codex axis alone** — OMP's fresh default is deliberately unchanged at
|
|
209
|
+
`openai-codex/gpt-5.6-sol` (`scripts/smoke-omp-fresh-live.ts:14`), because that is the model its
|
|
210
|
+
two-stage bootstrap callback was actually measured on and moving it would invalidate that
|
|
211
|
+
receipt. A cost decision, not a gate change: no step moved tier, and the acceptance receipt for
|
|
212
|
+
the Codex cell must be the run that actually happened on the new default rather than a `sol` run
|
|
213
|
+
inherited from before.
|
|
214
|
+
|
|
215
|
+
- **The fixed `codex` tmux home is RETIRED (#95 D1).** 0.21.0 shipped it three days earlier: an
|
|
216
|
+
omitted-placement Codex TARGET selected an already-existing session named exactly `codex`, where
|
|
217
|
+
the operator was asked to seat the app-server and every Codex TUI, and the launch receipt labelled
|
|
218
|
+
that source `Codex home`. GLG retired it the moment the caller-pane anchor stood up — *"무조건
|
|
219
|
+
은퇴야 … 코덱스의 거처를 만들어주자는 말은 그냥 기술이 안돼서 무마한 개념이야. 제대로 가야지."* The
|
|
220
|
+
room was a workaround for a mapping that did not exist yet, and keeping it after the anchor landed
|
|
221
|
+
would have left Codex alone answering "where does a sibling open?" differently from every other
|
|
222
|
+
backend. `CODEX_HOME_TMUX_SESSION`, the `codex-home` receipt source, its renderer branch, its
|
|
223
|
+
deterministic cells and its mutant are gone; an omitted seat is the caller's own session for every
|
|
224
|
+
backend, and the operator seats the app-server wherever they like — Entwurf still never creates,
|
|
225
|
+
moves or supervises it. The 2026-09-12 home acceptance stays recorded in `DELIVERY.md` and
|
|
226
|
+
`docs/setup-clean-host.md` as history, not as a live contract. The reshaped LIVE card now REQUIRES
|
|
227
|
+
the app-server to sit in a different session from the Pi/Codex pair: that separation is what tells
|
|
228
|
+
the caller-pane anchor apart from the app-server's inherited environment.
|
|
229
|
+
|
|
230
|
+
### Fixed
|
|
231
|
+
|
|
232
|
+
- **An in-band refusal now reaches the sender with the reason the RECEIVER gave it
|
|
233
|
+
(`d49e6cb`, `0c8a42c`).** A completed control-socket RPC that came back `success:false` was
|
|
234
|
+
finalized as `rejected` with no reason at all, so every named receiver refusal — including the
|
|
235
|
+
0.21.0 compaction guard's own `compacting` / `busy` — arrived at the caller as a reasonless
|
|
236
|
+
rejection indistinguishable from "no live route". The send hand now carries that error verbatim
|
|
237
|
+
when the receiver supplied one, and invents nothing when it did not: receiver evidence is not
|
|
238
|
+
reclassified into the resolver's taxonomy (`dormant-fire-forget-unsupported`,
|
|
239
|
+
`mailbox-undeliverable`, …), and the two remain distinguishable at the surface. The gap was one
|
|
240
|
+
hop wide and entirely invisible to the unit lane — the production `sendOverSocket` closure was
|
|
241
|
+
what dropped `response.error` before `driveSend` could see it — so the guard that holds it is a
|
|
242
|
+
production-wiring assertion with its own mutant (`V2PROD-INBAND-ERROR-WIRED`), not another test
|
|
243
|
+
of the pure function.
|
|
244
|
+
- **A Codex sibling's THREAD now opens where its pane is (#95 lane C).** The seat was right and the
|
|
245
|
+
directory was not: `[측정 2026-09-16]` a pi → Codex → Claude Code chain recorded the app-server's
|
|
246
|
+
`~/repos/gh/entwurf` for all three citizens while the panes sat in `~/repos/gh/agent-config`.
|
|
247
|
+
`[source rust-v0.153.4]` an explicit `--remote <endpoint>` resolves to `AppServerTarget::Remote`
|
|
248
|
+
(`codex-rs/tui/src/lib.rs:875-876`), and a Remote target takes its new thread's cwd from
|
|
249
|
+
`remote_cwd_override` alone (`app_server_session.rs:2022-2033`) — absent, the app-server opens the
|
|
250
|
+
thread in its OWN directory, and the birth hook then records that vendor-supplied cwd honestly.
|
|
251
|
+
The codex argv now always carries that override as `-C <dir>`, the flag the shared interactive
|
|
252
|
+
options already expose beside `--dangerously-bypass-approvals-and-sandbox`
|
|
253
|
+
(`utils/cli/src/shared_options.rs:53-68`), preserved for a remote target at
|
|
254
|
+
`startup_orchestration.rs:191-194`. **One flag, no vendor patch, no new layer.**
|
|
255
|
+
- **A Codex CALLER that names no cwd opens its sibling in its OWN directory.** This bridge runs as
|
|
256
|
+
the operator-owned app-server's MCP child, so the directory its process reports is the
|
|
257
|
+
app-server's rather than the caller's — an omitted `cwd` used to put every sibling of every Codex
|
|
258
|
+
caller in the app-server's repo, whatever backend was being opened. The surface now hands the
|
|
259
|
+
composition that citizen's RECORD cwd under the same condition that already carries its thread
|
|
260
|
+
id, and it is consulted only when the call requested none. One value, two carriers: tmux `-c`
|
|
261
|
+
places the pane, codex `-C` places the thread. Every other caller's argv is byte-identical, and
|
|
262
|
+
the receipt names which rule chose the directory (`requested` / the Codex caller's own record
|
|
263
|
+
directory) instead of calling a caller-record path "requested".
|
|
264
|
+
- **Three named refusals replace a silent wrong seat.** Zero matching panes is
|
|
265
|
+
`codex-caller-seat-unresolved` (the TUI is on another tmux server, its config carries no
|
|
266
|
+
`thread-id`, or that server has `allow-set-title off`, which `[측정]` replaces every pane title
|
|
267
|
+
with the hostname); two or more is `codex-caller-seat-ambiguous`; and a Codex caller whose own
|
|
268
|
+
`[tui].terminal_title` lacks `thread-id` is refused pre-mutation as `codex-caller-title-missing`
|
|
269
|
+
with the installer as its repair. None of them falls back to another session, and none opens a
|
|
270
|
+
window. That caller-side check runs only when the anchor is actually consulted — an explicit
|
|
271
|
+
seat never reads a title.
|
|
272
|
+
- **A pane title is a placement input and nothing else (Hard Rule 16).** Any process in any pane
|
|
273
|
+
can emit the same OSC string, so the leaf's answer may only reach a `-t` target; identity,
|
|
274
|
+
delivery and liveness keep the record + `_meta` join they already had, and the first-turn
|
|
275
|
+
framing still carries the caller's garden id rather than the thread id a title matched. The leaf
|
|
276
|
+
(`pi-extensions/lib/codex-caller-seat.ts`) imports nothing at all, which is how that boundary is
|
|
277
|
+
held by the code rather than by a comment.
|
|
278
|
+
- **The title match is over tokens, not ` | ` segments, and the count is over panes.**
|
|
279
|
+
`title_setup.rs:183-193` joins the `activity` item to its neighbour with a plain space rather
|
|
280
|
+
than ` | `, so on a host whose operator list ends in `activity` a working TUI renders
|
|
281
|
+
`<spinner> <thread-id>` inside one segment and a segment-equality rule would lose the seat
|
|
282
|
+
exactly while the caller is busy. Counting panes rather than tokens keeps a single pane that
|
|
283
|
+
shows the same thread twice (`thread-title` beside `thread-id`) from reading as an ambiguity.
|
|
284
|
+
|
|
7
285
|
## 0.21.0 - 2026-09-13
|
|
8
286
|
|
|
9
287
|
### Added
|
package/DELIVERY.md
CHANGED
|
@@ -88,7 +88,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
|
|
|
88
88
|
| **pi native Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. |
|
|
89
89
|
| **Claude Code interactive `>=2.1.217`** | shipped; Linux certified | D6; D7/D8 partial | Per-session mailbox + exec-form `FileChanged`/`asyncRewake`. B2 proved idle wake and same-session continuity on one NixOS host. |
|
|
90
90
|
| **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
|
|
91
|
-
| **Codex CLI app-server citizen** | supported in 0.21.0;
|
|
91
|
+
| **Codex CLI app-server citizen** | supported in 0.21.0; caller-seat topology ACCEPTED on Linux at 0.153.4, 2026-09-16 (56 assertions, exit 0), and the caller-DIRECTORY axis ACCEPTED the same day on the same host (#95 lane C, 65 assertions, exit 0; the 2026-09-12 explicit-home acceptance is history — #95 D1 retired that room) | D6 accepted; D7 partial; D8 unproven | Vendor-trusted user-scope `SessionStart` birth plus strict per-request metadata join; no shared-pid sender marker. `thread/loaded/list` probes the operator-owned app-server UDS and one-shot `codex queue` delivers with no retry. The operator owns the app-server and seats it wherever they like; Entwurf creates, moves and supervises none of it. Since #95 lane B a Codex CALLER with no explicit placement opens its sibling beside its OWN pane, located by the `thread-id` its `[tui].terminal_title` writes into the pane title; 0 or 2+ matching panes refuse with no fallback, and the title is a placement input only — never an address, liveness or delivery fact. #95 D1 (2026-09-16) retired the fixed `codex` home that omitted-placement Codex TARGETS used to select, so an omitted seat is the caller's own session for every backend. Exact 0.153.4 source still bounds placing a sibling beside a TUI whose thread nobody named as unsupported; the anchor resolves only the pane showing the caller's own thread. |
|
|
92
92
|
| **Codex embedded TUI** | deferred | D0 partial | At the 2026-09-08 Codex 0.153.4 measurement, standalone mode had no `watchPaths`/`FileChanged`/`asyncRewake` analogue or supported idle receive route. This is dated vendor evidence, not a claim that the current candidate lacks records or fresh. |
|
|
93
93
|
| **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` → `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately — it announces the inbox instead of injecting the body. |
|
|
94
94
|
| **Copilot CLI garden citizen** | shipped in 0.15.0; send + receive + visible fresh accepted on one host | D6; D7 partial; D3 pending; D8 unproven | Birth, garden id, MCP hand and record-backed sender identity are accepted; the RECEIVER is an installed first-party extension that binds to the V3 record, writes a receiver marker owned by the WATCHER pid, and rings a doorbell the model drains with `entwurf_inbox_read`. `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail: armed → delivered, unarmed/stale → the honest `mailbox-undeliverable` refusal. **D6 is the owned-invocation LIVE acceptance of 2026-08-23** — garden `20260823T181316-d9f6ba`, native `20fe30c8-b2bc-4600-91a0-8a409131be51`, CLI 1.0.80: receive log `joined`→`armed`→`doorbell fresh=1`→`rang`, mailbox `lastEnqueuedAt 09:23:41.235Z` / `lastReadAt 09:23:56.480Z`, and a model reply on the same record/native/gid chain. **Visible fresh (step 9 clause 7) is a separate LIVE, 2026-08-25** — launch window `@89`/`%89` nonce `mux-fresh-call-690529ae99f99faa2252aefb`; exact-callback garden `20260825T085721-f68be0`; one `entwurf_v2` → `meta-mailbox → enqueued`; same garden `lastReadAt 2026-08-24T23:57:47.784Z` plus same-gid reply; GLG saw footer garden id and a healthy multi-turn window. Those rows stay unmerged. D7 is PARTIAL: reply and read receipt were observed, the completion taxonomy and long-haul operation were not. D3 (second-session isolation of an owned invocation) is PENDING — observed once, decisive log lost to scratch cleanup. Evidence level L4: one host. Launch through the owned invocation `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one process; `doctor-copilot-receive` reads live CLI environments because a session launched without it is silently inert. Visible fresh is operator-metered and is not a release-gate MUST. |
|
|
@@ -152,23 +152,55 @@ operator starts app-server in existing exact tmux session `codex`
|
|
|
152
152
|
The standalone embedded TUI remains outside this rail: the 2026-09-08 measurement
|
|
153
153
|
found no idle receive route equivalent to the app-server. `turn/steer` is active-turn
|
|
154
154
|
steering, not idle wake. Entwurf does not start, stop, supervise, or health-loop the
|
|
155
|
-
app-server. The operator starts it
|
|
155
|
+
app-server. The operator starts it in a tmux session **of their own choosing**:
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
|
-
# Run from a pane in the operator-owned tmux session
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
|
|
158
|
+
# Run from a pane in the operator-owned tmux session that will hold the app-server.
|
|
159
|
+
# For the LIVE acceptance that session must NOT be the one the Pi/Codex pair runs in.
|
|
160
|
+
entwurf codex-app-server
|
|
162
161
|
```
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
That verb owns the SPELLING of one vendor command and nothing else — it `exec`s
|
|
164
|
+
`codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"`
|
|
165
|
+
in the terminal it was typed in, after creating the control directory that address lives in.
|
|
166
|
+
It resolves the socket through the same leaf every other Codex surface reads, so the address a
|
|
167
|
+
consumer gets cannot drift from the one delivery looks for. There is no supervisor, no restart,
|
|
168
|
+
no daemon and no pid file: Ctrl-C is the operator's. A live socket, an unidentifiable one, or a
|
|
169
|
+
second `--listen` are named refusals; a dead socket file is reported and launched over. Passing
|
|
170
|
+
the raw vendor command by hand remains equivalent and is not deprecated.
|
|
171
|
+
|
|
172
|
+
#95 D1 (GLG, 2026-09-16) retired the requirement that this session be named `codex`, and with it the
|
|
173
|
+
rule that an omitted-placement Codex TARGET selected it. The operator still owns the app-server and
|
|
174
|
+
still chooses its room; Entwurf neither creates nor supervises it, and a missing app-server rejects.
|
|
175
|
+
|
|
176
|
+
Since #95 lane B the outbound direction no longer rides the app-server's inherited
|
|
177
|
+
`TMUX`/`TMUX_PANE`: a Codex CALLER with no explicit placement opens its sibling beside its own
|
|
178
|
+
pane, found by matching the caller's `_meta.threadId` against the `thread-id` the TUI writes
|
|
179
|
+
into that pane's title. Zero matching panes (`codex-caller-seat-unresolved`), two or more
|
|
180
|
+
(`codex-caller-seat-ambiguous`) and a caller whose `[tui].terminal_title` lacks `thread-id`
|
|
181
|
+
(`codex-caller-title-missing`) are all named refusals with no fallback seat. An attached TUI
|
|
182
|
+
still does not lend its pane to the server, and N arbitrary clients whose threads nobody named
|
|
183
|
+
still have no adjacency claim: the anchor resolves only the pane displaying the thread the
|
|
184
|
+
caller itself put on the request.
|
|
185
|
+
|
|
186
|
+
The seat order, for all five backends, and it follows the CALLER rather than what is being
|
|
187
|
+
opened: explicit `placement` wins; then a Codex CALLER's own pane (`codex-title-anchor`, no
|
|
188
|
+
session name on the receipt because a session was observed rather than requested); then the
|
|
189
|
+
caller's own session.
|
|
190
|
+
|
|
191
|
+
The DIRECTORY follows the caller the same way (#95 lane C, 2026-09-16): requested `cwd` wins;
|
|
192
|
+
then a Codex CALLER's own RECORD cwd, because this bridge is the app-server's MCP child and its
|
|
193
|
+
process directory is the app-server's rather than that caller's; then the launching process's
|
|
194
|
+
own, which is what tmux gives a window opened with no `-c`. Codex alone also carries that one
|
|
195
|
+
value into its argv as `-C <dir>`, and omitting it is a wrong answer rather than a neutral one:
|
|
196
|
+
an explicit `--remote` target takes its new thread's cwd from that override ALONE
|
|
197
|
+
(`codex-rs/tui/src/app_server_session.rs:2022-2033` at rust-v0.153.4), so without it the THREAD
|
|
198
|
+
opens in the app-server's repo while its pane sits elsewhere — measured on 2026-09-16 as three
|
|
199
|
+
citizens of one chain recording a directory none of them was in. The receipt names which rule
|
|
200
|
+
chose the directory (`requested` / the Codex caller's own record directory) and invents nothing
|
|
201
|
+
for the inherited case.
|
|
202
|
+
|
|
203
|
+
Four ownership atoms remain separate:
|
|
172
204
|
|
|
173
205
|
1. `entwurf install-codex-birth` owns `$CODEX_HOME/hooks.json` — one whole file, ours or
|
|
174
206
|
absent — plus its launcher and import closure under `$XDG_DATA_HOME/entwurf/codex-birth`.
|
|
@@ -181,8 +213,17 @@ Three ownership atoms remain separate:
|
|
|
181
213
|
keeps custom roots and the app-server seat intact without storing their values.
|
|
182
214
|
3. `entwurf install-codex-statusline` owns only `thread-title` within
|
|
183
215
|
`tui.status_line`. Birth calls `thread/name/set`, so the visible title is the garden id.
|
|
184
|
-
|
|
185
|
-
|
|
216
|
+
4. `entwurf install-codex-terminal-title` owns only `thread-id` within `tui.terminal_title`
|
|
217
|
+
— a different key and a different axis from atom 3: `status_line` is what a human reads
|
|
218
|
+
inside the TUI, `terminal_title` is what the multiplexer reports back as `#{pane_title}`
|
|
219
|
+
and is the only value a caller seat can be matched against. The seeded list is
|
|
220
|
+
`["activity", "project-name", "thread-id"]` (`activity` leads because the herdr Codex
|
|
221
|
+
detector keys on the spinner/action-required prefix) and an existing operator list is
|
|
222
|
+
appended to, never reordered. A tmux server with `allow-set-title off` replaces every pane
|
|
223
|
+
title with the hostname, so the seat refuses there even with the atom installed — that is
|
|
224
|
+
a repair condition the refusal names, not an inference this rail makes.
|
|
225
|
+
|
|
226
|
+
All four have state-backed doctors and inverses. Symlinked or foreign config is a
|
|
186
227
|
named refusal, not an adoption. Entwurf never installs Codex or its credentials.
|
|
187
228
|
|
|
188
229
|
Sender identity is request-scoped. The bridge requires the complete Codex metadata
|
|
@@ -211,17 +252,44 @@ name its caller and the callback was uncorrelatable.
|
|
|
211
252
|
Visible fresh runs:
|
|
212
253
|
|
|
213
254
|
```text
|
|
214
|
-
|
|
255
|
+
operator-owned tmux session A (any name — the operator seats it)
|
|
215
256
|
operator-owned app-server + supported Codex TUIs
|
|
216
|
-
codex --remote unix://<default socket> --model <explicit model>
|
|
257
|
+
codex --remote unix://<default socket> -C <launch directory> --model <explicit model>
|
|
217
258
|
--dangerously-bypass-approvals-and-sandbox <callback-first prompt>
|
|
218
259
|
```
|
|
219
260
|
|
|
220
|
-
With `placement` omitted
|
|
221
|
-
to
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
261
|
+
With `placement` omitted the seat follows the CALLER: a Codex citizen's own TUI pane, resolved from
|
|
262
|
+
the `thread-id` in that pane's title to a native `$id` before mutation, with 0 or 2+ matches refused
|
|
263
|
+
and no fallback; every other caller keeps caller-session default placement. An explicit seat remains
|
|
264
|
+
an expert override. The preflight must certify the state-backed birth closure digests, vendor trust
|
|
265
|
+
receipt, exact MCP/env boundary, `thread-title`, `terminal_title`, app-server socket, and a narrow LOCAL
|
|
266
|
+
read of the LAUNCH DIRECTORY's folder consent before tmux mutation — a guard on the cases the user
|
|
267
|
+
config decides, not a certification of the vendor's verdict. That last axis is the vendor's, not ours:
|
|
268
|
+
`[source rust-v0.153.4]` a `--remote` startup always runs `check_directory_trust` on the `-C` value
|
|
269
|
+
(`tui/src/lib.rs:1699-1725`) and nothing on that path reads the approval or sandbox policy
|
|
270
|
+
(`tui/src/onboarding/directory_trust.rs:33-130`), so
|
|
271
|
+
`--dangerously-bypass-approvals-and-sandbox` does not cover it — approvals and folder consent are
|
|
272
|
+
two different gates. A DIRECT decision is keyed to the exact directory: for `ProjectTrustHost::Remote` that lookup is
|
|
273
|
+
`vec![cwd_key]` alone (`tui/src/config_update.rs:290-296`), with no project-root marker, git root or
|
|
274
|
+
parent inheritance. An UNDECIDED directory renders a consent screen, and a TUI waiting on one has started no turn —
|
|
275
|
+
no rollout, no birth, no callback. Entwurf NOTES that as `codex-launch-cwd-undecided` and opens the
|
|
276
|
+
window anyway: the screen is self-repairing for the human this rail exists to put a window in front
|
|
277
|
+
of, and one answer teaches the vendor that directory for good. The unattended case is answered
|
|
278
|
+
where it belongs — `smoke-codex-fresh-live` asserts the same leaf up front, so a gate with nobody
|
|
279
|
+
at the keyboard reads a named precondition instead of a callback timeout. A directory the operator deliberately answered `untrusted`
|
|
280
|
+
is NOT refused: on a remote target the vendor skips that screen
|
|
281
|
+
(`onboarding/directory_trust.rs:94-96`; `uses_remote_workspace()` is `matches!(self, Self::Remote
|
|
282
|
+
{ .. })` at `tui/src/lib.rs:307-309`), so the turn starts and refusing it would be entwurf
|
|
283
|
+
inventing a policy the vendor does not have. A cwd INSIDE an explicitly `untrusted` project is a
|
|
284
|
+
third answer with its own reason, `codex-launch-cwd-untrusted-ancestor`: there the vendor returns
|
|
285
|
+
`pass the repository root explicitly with --cd` (`config_update.rs:357-371`) rather than a screen,
|
|
286
|
+
so answering a prompt at the child would only reproduce that error.
|
|
287
|
+
|
|
288
|
+
**The preflight leaf is narrower than the vendor's decision and does not claim otherwise.** It
|
|
289
|
+
reads one TOML file while the vendor reads a layered config through its app-server, where an
|
|
290
|
+
enabled project layer can consent with no entry at all (`config_update.rs:346-354`). Every case the
|
|
291
|
+
leaf cannot see resolves to "proceed", so it may fail to catch a hang but never refuses a launch
|
|
292
|
+
the vendor would have run. The callback spelling is `mcp__entwurf_bridge__entwurf_v2`; the new garden id
|
|
225
293
|
comes only from its sender envelope. There is no Codex resume, watcher, session/app-server creator,
|
|
226
294
|
or lifecycle supervisor.
|
|
227
295
|
|
|
@@ -246,7 +314,8 @@ preserved all three records/transcripts, and left operator app-server PID `15516
|
|
|
246
314
|
|
|
247
315
|
That receipt established the mechanism of the now-selected deployment, but its initial Pi also sat
|
|
248
316
|
in the app-server session and predates the fixed-home default. The stronger explicit-home acceptance
|
|
249
|
-
passed on 2026-09-12
|
|
317
|
+
passed on 2026-09-12, under the `codex` home topology #95 D1 later retired. Receipt
|
|
318
|
+
`.probe-artifacts/20260912T140745-codex-home-live-green.log`
|
|
250
319
|
(SHA-256 `09e79bd1b62962f8a11d647ee456a71b11965b6792c7972d97ac4992119c91d7`)
|
|
251
320
|
records 57 assertions and exit 0. The operator app-server stayed at `$158/@390/%390`; real initial Pi
|
|
252
321
|
`20260912T140748-355654` opened outside the home at `$150/@397`, omitted-placement Codex
|
|
@@ -298,7 +367,8 @@ omitted-placement Codex `20260913T065151-4b6078` (thread `01a0979a-edc4-7570-a29
|
|
|
298
367
|
`$158/@448`, Codex-opened Pi `20260913T065213-0811e6` at `$158/@449`, against the same operator-owned
|
|
299
368
|
app-server at `$158/@390/%390`. Artifact `.probe-artifacts/codex-fresh-live-2oId4C/` (`run-manifest.json`
|
|
300
369
|
sha256 `2cff6f08…`). Note the fixture seat differs from the standalone run: the aggregate builds its own
|
|
301
|
-
tmux session (`$187`), while the Codex legs
|
|
370
|
+
tmux session (`$187`), while the Codex legs resolved to the exact `codex` home that was the contract
|
|
371
|
+
at the time. That gate reported
|
|
302
372
|
MUST PASS=24 FAIL=0 SKIP=0 and `cut: OK`. Keep the three Codex acceptances apart — 57-assertion
|
|
303
373
|
pre-amendment first admission, 48-assertion standalone amended (`fZccoK`), and this 48-assertion
|
|
304
374
|
aggregate repeat (`2oId4C`).
|
|
@@ -308,6 +378,66 @@ as an observation rather than a defect: a callback nonce arrived one character s
|
|
|
308
378
|
instruction carried the wrong payload. Joined source receipts showed target, rail, and delivery were all
|
|
309
379
|
correct, so both oracles refused for the right reason and nothing in the product was changed.
|
|
310
380
|
|
|
381
|
+
**Caller-seat acceptance (#95 lane B), 2026-09-16, Codex 0.153.4 — 56 assertions, exit 0.** This is the
|
|
382
|
+
receipt for the topology that replaced the fixed home: `ENTWURF_CODEX_FRESH_MODEL=gpt-5.6-sol`,
|
|
383
|
+
`ENTWURF_CODEX_FRESH_PI_MODEL=openai-codex/gpt-5.6-luna`, final source audit `codex=3/3 completed exact`.
|
|
384
|
+
On one tmux server (`36728`) the operator-owned app-server `737636` stayed in ITS OWN session
|
|
385
|
+
A = `$30`/`@41`/`%45`, while all three smoke citizens opened in S = `$2`: initial Pi
|
|
386
|
+
`20260916T144205-e876ff` at `@46`, omitted-placement Codex `20260916T144230-d050d7` at `@47`, and the
|
|
387
|
+
Codex-opened outbound Pi at `@48`. **A ≠ S is what makes the run decisive rather than merely green:**
|
|
388
|
+
the app-server's inherited `TMUX` names A, so the pre-#95 environment fallback would have put that
|
|
389
|
+
outbound Pi there. It landed in S, and its receipt names the rule that chose the seat —
|
|
390
|
+
`seat: $2 (the Codex caller's own pane, found by its thread-id terminal title — an OBSERVED session,
|
|
391
|
+
not a requested name)`, recorded as `seat-source=codex-title-anchor`. Artifact
|
|
392
|
+
`.probe-artifacts/codex-fresh-live-nYcGC1/`, stdout preserved at `run-stdout.log`
|
|
393
|
+
(SHA-256 `ed60c2bdb46c13c51bf1d2dcf147b88c3c9f368a86a2bf17e972d3ecfde5fde9`). Cleanup reclaimed exactly
|
|
394
|
+
`@46`, `@47`, `@48` with no CLEANUP FAILURE, preserved every born record and transcript, and left the
|
|
395
|
+
app-server and `$30` untouched.
|
|
396
|
+
|
|
397
|
+
**Caller-directory acceptance (#95 lane C), 2026-09-16, Codex 0.153.4 — 65 assertions, exit 0.** Same
|
|
398
|
+
host, same models, same four-coordinate topology (app-server `737636` in A = `$30`/`@41`/`%45`; initial
|
|
399
|
+
Pi `20260916T154902-556eb5` at `$2`/`@58`, omitted-placement Codex `20260916T155037-10ddaa` at `$2`/`@59`,
|
|
400
|
+
Codex-opened outbound Pi `20260916T155102-9add8a` at `$2`/`@61` with `seat-source=codex-title-anchor`),
|
|
401
|
+
final source audit `initial-pi=3/3` and `codex=3/3 completed exact`. **65 and 56 are different
|
|
402
|
+
contracts, not a regression:** lane C added the cwd axis (seven assertions) and the pane-directory
|
|
403
|
+
reader it needs, on top of everything lane B already asserted.
|
|
404
|
+
|
|
405
|
+
The two decisive receipts, both measured against the app-server's own live directory
|
|
406
|
+
`/home/junghan/repos/gh/entwurf` (read from `/proc/737636/cwd`):
|
|
407
|
+
|
|
408
|
+
- **hop 1, cwd REQUESTED** (`8b-codex-thread-cwd`): the fresh Codex's pane `#{pane_current_path}`, the
|
|
409
|
+
vendor's own rollout `session_meta.cwd`, its Entwurf record and the requested scratch are ONE
|
|
410
|
+
directory — `/tmp/entwurf-codex-fresh-live-db65N2` — and it is not the app-server's. Four authorities
|
|
411
|
+
that cannot borrow from each other; before `-C` the rollout carried the app-server's path while the
|
|
412
|
+
pane sat in the scratch.
|
|
413
|
+
- **hop 2, cwd NOT REQUESTED** (`13b-outbound-pi-cwd`): the leg named neither placement nor cwd, and the
|
|
414
|
+
outbound Pi still opened in `/tmp/entwurf-codex-fresh-live-db65N2` — its pane, its own birth-written
|
|
415
|
+
record, and the Codex caller's record all agree. Its launch receipt names the rule rather than
|
|
416
|
+
borrowing the other one's noun: `cwd: /tmp/entwurf-codex-fresh-live-db65N2 (the Codex caller's own
|
|
417
|
+
record directory, used because no cwd was requested — not an observation)`, while both requested legs
|
|
418
|
+
still read `requested start directory`.
|
|
419
|
+
|
|
420
|
+
Artifact `.probe-artifacts/codex-fresh-live-w4yJBw/` (run manifest, mailbox observations/selections and
|
|
421
|
+
a 13-file pre-cleanup snapshot including all three records, both Pi transcripts and the Codex rollout);
|
|
422
|
+
stdout preserved at `.probe-artifacts/lane-c-live-20260916T154900.log`
|
|
423
|
+
(SHA-256 `712050e7a8cf03ece98e7f34029ae98a92e9b2a7fd96aff87d0c1e6ba2ee9af2`). Cleanup reclaimed exactly
|
|
424
|
+
`@58`, `@59`, `@61` with no CLEANUP FAILURE, removed its own scratch and fixture record, preserved every
|
|
425
|
+
born citizen record and transcript, and left the app-server and `$30` untouched.
|
|
426
|
+
|
|
427
|
+
Two earlier attempts that day are kept as their own receipts, because each stopped at a different
|
|
428
|
+
truth. The FIRST stopped at 26 assertions, before any window opened: `codex-birth-unit-missing`, because
|
|
429
|
+
`~/.codex/hooks.json` carried a second `SessionStart` entry added after install, so the declaration no
|
|
430
|
+
longer matched its recorded digest. The unit was not broken — entwurf's own atom and all six helper
|
|
431
|
+
digests were intact — but this rail owns that file whole, and the preflight refused rather than run
|
|
432
|
+
against bytes it could not vouch for. The SECOND stopped at 43 assertions with hop 1 already green, and
|
|
433
|
+
its log is kept as the D1 measurement: `.probe-artifacts/codex-fresh-live-4aFCDD/run-stdout.log`
|
|
434
|
+
(SHA-256 `8392a603dcfe7e89549942d55e515f97724398fb261bd8873d25806f71086afe`) carries
|
|
435
|
+
`omitted placement opened fresh Codex in the caller's own session S, away from the app-server's A` —
|
|
436
|
+
the retirement of the fixed home, observed on a real host. It then failed on the gate's own defect
|
|
437
|
+
rather than the product's: the source-call oracle compared arguments with `isDeepStrictEqual`, so a
|
|
438
|
+
model that omitted the optional `wants_reply` instead of passing it explicitly read as drift. That
|
|
439
|
+
oracle now normalizes exactly that key to its schema default and nothing else.
|
|
440
|
+
|
|
311
441
|
Unrestricted attached-TUI parity (**B**) is explicitly outside this support claim. The exact vendor
|
|
312
442
|
checkout `rust-v0.153.4` at `3d2ee51ca2d5db578f328aa75e20aa22c0197c9a` found no public
|
|
313
443
|
request→attached-TUI-seat carrier: process-local `ConnectionId` does not cross into thread/core/MCP/hook
|
|
@@ -317,7 +447,7 @@ behaviour remain invalid; explicit `placement.tmuxSession` is an operator-named
|
|
|
317
447
|
an inferred seat. The gate strips ambient `PI_SESSION_ID`/`PI_AGENT_ID`; a fixture may preserve receipts
|
|
318
448
|
but cannot substitute for the initial visible record-backed Pi turn.
|
|
319
449
|
|
|
320
|
-
Run the clause-7 invocation from a tmux session
|
|
450
|
+
Run the clause-7 invocation from a tmux session OTHER than the app-server's own; the explicit app-server PID must belong to the operator-owned app-server, and that session's NAME is not a requirement — #95 D1 retired the fixed `codex` home on 2026-09-16, so A ≠ S is the precondition and no particular name is. Both models are explicit, and the launch directory must already be answered in this Codex (`VERIFY.md` owns the derivation and the one-time `Trust`):
|
|
321
451
|
|
|
322
452
|
```bash
|
|
323
453
|
LIVE=1 \
|