@junghanacs/entwurf 0.20.1 → 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.
Files changed (111) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -3
  3. package/CHANGELOG.md +473 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +332 -60
  6. package/README.md +100 -22
  7. package/VERIFY.md +93 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +64 -33
  10. package/docs/setup-clean-host.md +151 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +130 -114
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +519 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +189 -19
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  29. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  31. package/mcp/entwurf-bridge/src/index.ts +154 -116
  32. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  33. package/package.json +13 -4
  34. package/pi-extensions/entwurf-control.ts +71 -19
  35. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  36. package/pi-extensions/lib/codex-fresh-preflight.ts +580 -0
  37. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  38. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  39. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  40. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  41. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  42. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  43. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  44. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  45. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  46. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  47. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  48. package/pi-extensions/lib/mux-fresh-call.ts +233 -29
  49. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  50. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  51. package/pi-extensions/lib/native-push/register.ts +7 -9
  52. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  53. package/run.sh +342 -28
  54. package/scripts/check-agy-sender-identity.ts +1 -1
  55. package/scripts/check-codex-app-server-launch.ts +445 -0
  56. package/scripts/check-codex-birth-hook.ts +264 -0
  57. package/scripts/check-codex-bridge-identity.ts +179 -0
  58. package/scripts/check-codex-native-push.ts +386 -0
  59. package/scripts/check-codex-sender-identity.ts +495 -0
  60. package/scripts/check-compaction-send-guard.ts +130 -0
  61. package/scripts/check-copilot-receive-arm.ts +4 -1
  62. package/scripts/check-entwurf-fact-provider.ts +38 -0
  63. package/scripts/check-entwurf-peers-surface.ts +13 -1
  64. package/scripts/check-entwurf-self-address.ts +15 -16
  65. package/scripts/check-entwurf-v2-contract.ts +4 -3
  66. package/scripts/check-entwurf-v2-decider.ts +7 -5
  67. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  68. package/scripts/check-entwurf-v2-production.ts +245 -12
  69. package/scripts/check-entwurf-v2-runner.ts +1 -1
  70. package/scripts/check-entwurf-v2-send.ts +26 -7
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +8 -3
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-mux-launch-tmux.ts +345 -4
  75. package/scripts/check-native-push-adapter.ts +20 -16
  76. package/scripts/check-native-push-register.ts +5 -1
  77. package/scripts/check-release-gate-outcomes.ts +47 -1
  78. package/scripts/check-setup-qualification.sh +3 -1
  79. package/scripts/codex-app-server-launch.sh +275 -0
  80. package/scripts/codex-birth-doctor.sh +276 -0
  81. package/scripts/codex-birth-install.sh +414 -0
  82. package/scripts/codex-birth-uninstall.sh +170 -0
  83. package/scripts/codex-mcp-config.py +435 -0
  84. package/scripts/codex-socket-path.ts +33 -0
  85. package/scripts/codex-statusline-config.py +434 -0
  86. package/scripts/codex-terminal-title-config.py +500 -0
  87. package/scripts/codex_toml_io.py +653 -0
  88. package/scripts/lib/codex-fresh-live-protocol.ts +121 -0
  89. package/scripts/lib/codex-fresh-source-receipts.ts +426 -0
  90. package/scripts/lib/launch-receipt-windows.ts +46 -0
  91. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  92. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  93. package/scripts/mutants/codex-app-server-launch.json +157 -0
  94. package/scripts/mutants/codex-caller-seat.json +336 -0
  95. package/scripts/mutants/codex-native.json +838 -0
  96. package/scripts/mutants/compaction-send-guard.json +103 -0
  97. package/scripts/mutants/entwurf-peers.json +19 -0
  98. package/scripts/mutants/mux-fresh-call.json +93 -10
  99. package/scripts/mutants/omp-fresh.json +6 -4
  100. package/scripts/mutants/release-gate.json +13 -0
  101. package/scripts/mutants/v2-surface.json +75 -1
  102. package/scripts/raw-async-delivery/README.md +2 -1
  103. package/scripts/raw-codex-measure/README.md +114 -46
  104. package/scripts/smoke-agy-native-push-live.ts +3 -1
  105. package/scripts/smoke-codex-birth.sh +347 -0
  106. package/scripts/smoke-codex-config-state.sh +700 -0
  107. package/scripts/smoke-codex-fresh-live.ts +1426 -0
  108. package/scripts/smoke-codex-native-push-live.ts +75 -0
  109. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  110. package/scripts/smoke-setup-verdict.sh +125 -10
  111. package/scripts/tsconfig.json +1 -0
@@ -0,0 +1,157 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "codex-app-server-launch",
4
+ "mutants": [
5
+ {
6
+ "claim": "CODEX-APP-SERVER-ADDRESS-MATCHES-PRODUCT-LEAF",
7
+ "title": "re-deriving the control-socket path in bash instead of asking the product resolver reintroduces the divergence this launcher was repaired for: a CODEX_HOME that JS trims away and a POSIX trim keeps, or a path.join normalization, starts a server at an address delivery and preflight never look at",
8
+ "subject": "scripts/codex-app-server-launch.sh",
9
+ "find": [
10
+ "if ! SOCK=\"$(bash \"$REPO_DIR/run.sh\" codex-socket-path)\"; then",
11
+ "\tfail \"could not resolve the Codex control-socket path.",
12
+ " '$REPO_DIR/run.sh codex-socket-path' failed; that leaf is the only spelling of this address,",
13
+ " and this launcher will not invent a second one. Its output above says why.\"",
14
+ "fi",
15
+ "[ -n \"$SOCK\" ] \\",
16
+ "\t|| fail \"the Codex control-socket resolver returned an empty path \u2014 neither CODEX_HOME nor",
17
+ " HOME names a usable Codex home. Set CODEX_HOME.\""
18
+ ],
19
+ "replace": ["SOCK=\"${CODEX_HOME:-$HOME/.codex}/app-server-control/app-server-control.sock\""],
20
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
21
+ "timeoutSeconds": 300,
22
+ "signature": "[QK:CODEX-APP-SERVER-ADDRESS-MATCHES-PRODUCT-LEAF]",
23
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
24
+ },
25
+ {
26
+ "claim": "CODEX-APP-SERVER-EXECS-NOT-FORKS",
27
+ "title": "forking the vendor instead of becoming it leaves run.sh between the operator and their server: Ctrl-C hits the wrapper, and the process the operator believes they stopped is not the one that dies",
28
+ "subject": "scripts/codex-app-server-launch.sh",
29
+ "find": ["exec \"$codex_bin\" app-server --listen \"unix://$SOCK\" \"$@\""],
30
+ "replace": ["\"$codex_bin\" app-server --listen \"unix://$SOCK\" \"$@\""],
31
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
32
+ "timeoutSeconds": 300,
33
+ "signature": "[QK:CODEX-APP-SERVER-EXECS-NOT-FORKS]",
34
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
35
+ },
36
+ {
37
+ "claim": "CODEX-APP-SERVER-REFUSES-LIVE-SOCKET",
38
+ "title": "reading a LIVE control socket as a stale leftover launches a second server over the endpoint every existing citizen record already points at \u2014 the running one keeps its threads while its address answers somebody else",
39
+ "subject": "scripts/codex-app-server-launch.sh",
40
+ "find": ["\tprint(\"live\")"],
41
+ "replace": ["\tprint(\"stale\")"],
42
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
43
+ "timeoutSeconds": 300,
44
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-LIVE-SOCKET]",
45
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
46
+ },
47
+ {
48
+ "claim": "CODEX-APP-SERVER-REFUSES-INDETERMINATE-SOCKET",
49
+ "title": "reading an unidentifiable path (symlink, foreign owner, not a socket) as an absent one lets the launch clobber something it could not identify \u2014 the same classification the delivery rail refuses on, silently downgraded at the one place that would overwrite it",
50
+ "subject": "scripts/codex-app-server-launch.sh",
51
+ "find": ["\tprint(\"indeterminate\\tthe path is a symlink\")"],
52
+ "replace": ["\tprint(\"absent\")"],
53
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
54
+ "timeoutSeconds": 300,
55
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-INDETERMINATE-SOCKET]",
56
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
57
+ },
58
+ {
59
+ "claim": "CODEX-APP-SERVER-REFUSES-SECOND-LISTEN",
60
+ "title": "appending the injected address beside an operator's own --listen hands the vendor two listen addresses; one wins silently, and the losing one is the address every record was written against",
61
+ "subject": "scripts/codex-app-server-launch.sh",
62
+ "find": [
63
+ "for tok in \"$@\"; do",
64
+ "\tif [ \"${tok%%=*}\" = \"--listen\" ]; then",
65
+ "\t\tfail \"codex-app-server-listen-override: this verb exists to spell ONE --listen address",
66
+ " (unix://$SOCK), and you passed your own. Run the vendor directly if you mean a",
67
+ " different address: $VENDOR_CMD app-server --listen <your-address>\"",
68
+ "\tfi",
69
+ "done"
70
+ ],
71
+ "replace": ["# the operator may spell their own --listen beside ours"],
72
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
73
+ "timeoutSeconds": 300,
74
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-SECOND-LISTEN]",
75
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
76
+ },
77
+ {
78
+ "claim": "CODEX-APP-SERVER-READS-PROC-HOLDER",
79
+ "title": "dropping the /proc cmdline scan makes every already-listening refusal say nobody could be found \u2014 the operator is told to stop a server the message cannot name, and the refusal reads the same whether the host was searched or not",
80
+ "subject": "scripts/codex-app-server-launch.sh",
81
+ "find": [
82
+ "\t\towner=\"\"",
83
+ "\t\tif [ -d /proc ]; then",
84
+ "\t\t\tfor cl in /proc/[0-9]*/cmdline; do",
85
+ "\t\t\t\t[ -r \"$cl\" ] || continue",
86
+ "\t\t\t\tif tr '\\0' ' ' < \"$cl\" 2>/dev/null | grep -qF \"$SOCK\"; then",
87
+ "\t\t\t\t\towner=\"${owner} pid $(basename \"$(dirname \"$cl\")\"): $(tr '\\0' ' ' < \"$cl\")",
88
+ "\"",
89
+ "\t\t\t\tfi",
90
+ "\t\t\tdone",
91
+ "\t\tfi"
92
+ ],
93
+ "replace": ["\t\towner=\"\""],
94
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
95
+ "timeoutSeconds": 300,
96
+ "signature": "[QK:CODEX-APP-SERVER-READS-PROC-HOLDER]",
97
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
98
+ },
99
+ {
100
+ "claim": "CODEX-APP-SERVER-REFUSES-UNRECOGNISED-PROBE",
101
+ "title": "letting an unrecognised socket classification fall through to the exec turns the one reading nobody understands into a launch \u2014 every branch above it exists to decide whether this launch would clobber a running server",
102
+ "subject": "scripts/codex-app-server-launch.sh",
103
+ "find": [
104
+ "\t*)",
105
+ "\t\t# Hard Rule 15. Every branch above is a decision about whether this launch would",
106
+ "\t\t# CLOBBER a running server, so an unrecognised classification is the one case where",
107
+ "\t\t# proceeding is unsafe \u2014 and a probe that exits 0 while printing something nobody wrote",
108
+ "\t\t# would otherwise fall straight through to the exec.",
109
+ "\t\tfail \"codex-app-server-socket-probe-unrecognised: the socket classifier answered",
110
+ " '$probe_status', which is not one of live/stale/indeterminate/absent. Refusing to launch on a",
111
+ " reading nothing understands.\"",
112
+ "\t\t;;"
113
+ ],
114
+ "replace": ["\tunreachable-classification)", "\t\t:", "\t\t;;"],
115
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
116
+ "timeoutSeconds": 300,
117
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-UNRECOGNISED-PROBE]",
118
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
119
+ },
120
+ {
121
+ "claim": "CODEX-APP-SERVER-REFUSES-RELATIVE-SOCKET",
122
+ "title": "accepting a relative resolved address makes the launcher create a directory under whatever cwd it was typed in and bind a socket whose meaning differs per process \u2014 the bridge that looks for it is the app-server's MCP child with its own working directory, so the server and its finder end up at two different files",
123
+ "subject": "scripts/codex-app-server-launch.sh",
124
+ "find": [
125
+ "case \"$SOCK\" in",
126
+ "\t/*) ;;",
127
+ "\t*) fail \"codex-app-server-socket-path-not-absolute: the resolver returned",
128
+ " '$SOCK'",
129
+ " A relative control-socket address resolves to a different file for every process that reads it,",
130
+ " and the bridge that looks for this socket runs with its own working directory. Set CODEX_HOME to",
131
+ " an absolute path.\" ;;",
132
+ "esac"
133
+ ],
134
+ "replace": ["# any address the resolver returns is good enough to create and bind"],
135
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
136
+ "timeoutSeconds": 300,
137
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-RELATIVE-SOCKET]",
138
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
139
+ },
140
+ {
141
+ "claim": "CODEX-APP-SERVER-REFUSES-CONTROL-CHAR-SOCKET",
142
+ "title": "accepting a control-character-bearing address creates and binds a path no operator can name back \u2014 the address appears in receipts, refusals and /proc scans as a broken line, and this is the only surface that would bring it into existence",
143
+ "subject": "scripts/codex-app-server-launch.sh",
144
+ "find": [
145
+ "case \"$SOCK\" in",
146
+ "\t*[[:cntrl:]]*) fail \"codex-app-server-socket-path-untrusted: the resolved control-socket address",
147
+ " carries a control character (CODEX_HOME almost certainly does). Refusing to create or bind it.\" ;;",
148
+ "esac"
149
+ ],
150
+ "replace": ["# control characters in an address are somebody else's problem"],
151
+ "gate": ["bash", "run.sh", "check-codex-app-server-launch"],
152
+ "timeoutSeconds": 300,
153
+ "signature": "[QK:CODEX-APP-SERVER-REFUSES-CONTROL-CHAR-SOCKET]",
154
+ "signatureSource": "scripts/check-codex-app-server-launch.ts"
155
+ }
156
+ ]
157
+ }
@@ -0,0 +1,336 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "codex-caller-seat",
4
+ "mutants": [
5
+ {
6
+ "claim": "CODEX-SEAT-ANCHOR-PREFIX-LENGTH",
7
+ "title": "the anchor keeps one char too many before the ellipsis — the token is 33 chars, no live Codex pane title ever equals it, and every Codex caller silently loses its seat while the leaf reports the ordinary 'your TUI is not here' refusal",
8
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
9
+ "find": ["\treturn `${chars.slice(0, maxChars - 3).join(\"\")}...`;"],
10
+ "replace": ["\treturn `${chars.slice(0, maxChars - 2).join(\"\")}...`;"],
11
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:CODEX-SEAT-ANCHOR-PREFIX-LENGTH]",
14
+ "signatureSource": "test/codex-caller-seat.test.ts"
15
+ },
16
+ {
17
+ "claim": "CODEX-SEAT-ANCHOR-ELLIPSIS",
18
+ "title": "the anchor drops the vendor's `...` suffix, so it is a 29-char prefix the title never carries on its own — same silent total loss of the seat, dressed as a normal refusal",
19
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
20
+ "find": ["\treturn `${chars.slice(0, maxChars - 3).join(\"\")}...`;"],
21
+ "replace": ["\treturn chars.slice(0, maxChars - 3).join(\"\");"],
22
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:CODEX-SEAT-ANCHOR-ELLIPSIS]",
25
+ "signatureSource": "test/codex-caller-seat.test.ts"
26
+ },
27
+ {
28
+ "claim": "CODEX-SEAT-TOKEN-NOT-SEGMENT",
29
+ "title": "the title is split ONLY on ` | `, so a segment must equal the anchor whole. `title_setup.rs:183-193` joins the `activity` item to its neighbour with a plain space, so on a host whose operator list ends in `activity` a WORKING TUI renders `<spinner> <anchor>` as one segment and the caller's seat vanishes exactly while it is busy — the state every fresh call is made in. Proof is in two halves: this mutant dies on the token assertion in the fast lane, and the behavioural oracle independent of that source is the activity-adjacent pane cell in check-mux-launch-tmux (a real `select-pane -T` title, resolved to its real session id)",
30
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
31
+ "find": [
32
+ "\tfor (const segment of title.split(TITLE_SEGMENT_SEPARATOR)) {\n\t\tfor (const token of segment.split(TITLE_TOKEN_SEPARATOR)) {\n\t\t\tif (anchors.includes(token)) return true;\n\t\t}\n\t}"
33
+ ],
34
+ "replace": [
35
+ "\tfor (const segment of title.split(TITLE_SEGMENT_SEPARATOR)) {\n\t\tif (anchors.includes(segment)) return true;\n\t}"
36
+ ],
37
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
38
+ "timeoutSeconds": 120,
39
+ "signature": "[QK:CODEX-SEAT-TOKEN-NOT-SEGMENT]",
40
+ "signatureSource": "test/codex-caller-seat.test.ts"
41
+ },
42
+ {
43
+ "claim": "CODEX-SEAT-EXACT-TOKEN",
44
+ "title": "a token that merely CONTAINS the anchor matches, so a pane whose title quotes this thread id inside a longer word — a log line, a path, another agent's status text — is read as the caller's own TUI and a sibling is seated in a stranger's window",
45
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
46
+ "find": ["\t\t\tif (anchors.includes(token)) return true;"],
47
+ "replace": ["\t\t\tif (anchors.some((anchor) => token.includes(anchor))) return true;"],
48
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
49
+ "timeoutSeconds": 120,
50
+ "signature": "[QK:CODEX-SEAT-EXACT-TOKEN]",
51
+ "signatureSource": "test/codex-caller-seat.test.ts"
52
+ },
53
+ {
54
+ "claim": "CODEX-SEAT-AMBIGUOUS-REJECTS",
55
+ "title": "two panes naming one thread take the FIRST match instead of refusing — tmux's listing order decides where the operator's sibling opens, and the receipt names that guess as if it were an observation",
56
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
57
+ "find": ["\tif (matches.length > 1) return { ok: false, reason: \"codex-caller-seat-ambiguous\" };"],
58
+ "replace": [""],
59
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
60
+ "timeoutSeconds": 120,
61
+ "signature": "[QK:CODEX-SEAT-AMBIGUOUS-REJECTS]",
62
+ "signatureSource": "test/codex-caller-seat.test.ts"
63
+ },
64
+ {
65
+ "claim": "CODEX-SEAT-UNRESOLVED-NO-FALLBACK",
66
+ "title": "no matching pane stops being a refusal — the leaf returns an `ok` seat built from nothing, and the composition above it seats a sibling against an undefined session",
67
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
68
+ "find": ["\tif (matches.length === 0) return { ok: false, reason: \"codex-caller-seat-unresolved\" };"],
69
+ "replace": [""],
70
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
71
+ "timeoutSeconds": 120,
72
+ "signature": "[QK:CODEX-SEAT-UNRESOLVED-NO-FALLBACK]",
73
+ "signatureSource": "test/codex-caller-seat.test.ts"
74
+ },
75
+ {
76
+ "claim": "CODEX-SEAT-COUNTS-PANES",
77
+ "title": "the count moves from panes to TOKENS, so one pane whose title carries both `thread-title` (the full id, for an unnamed thread) and `thread-id` (the truncated one) refuses as ambiguous — two views of the same thread in the same window become a reason not to open anything",
78
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
79
+ "find": ["\t\tif (titleNamesThread(pane.title, anchors)) {"],
80
+ "replace": ["\t\tfor (const anchor of anchors) if (titleNamesThread(pane.title, [anchor])) {"],
81
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
82
+ "timeoutSeconds": 120,
83
+ "signature": "[QK:CODEX-SEAT-COUNTS-PANES]",
84
+ "signatureSource": "test/codex-caller-seat.test.ts"
85
+ },
86
+ {
87
+ "claim": "CODEX-SEAT-UNREADABLE-LINE-RAISES",
88
+ "title": "an unreadable pane line is skipped instead of raised — the listing is the whole evidence base for `exactly one`, so the dropped line can be the second match that should have refused, and a silent one-match answer replaces a named ambiguity",
89
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
90
+ "find": [
91
+ "\t\tif (pane === null) {\n\t\t\tthrow new Error(`codex-caller-seat: tmux printed a pane line this leaf cannot read: ${JSON.stringify(line)}`);\n\t\t}"
92
+ ],
93
+ "replace": ["\t\tif (pane === null) continue;"],
94
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
95
+ "timeoutSeconds": 120,
96
+ "signature": "[QK:CODEX-SEAT-UNREADABLE-LINE-RAISES]",
97
+ "signatureSource": "test/codex-caller-seat.test.ts"
98
+ },
99
+ {
100
+ "claim": "CODEX-SEAT-IMPORTS-NOTHING",
101
+ "title": "the leaf acquires an import, so the module that reads a FORGEABLE pane title is no longer structurally unable to reach identity, delivery or a record (Hard Rule 16). The fence is what makes 'a title is only a placement input' a property of the code rather than a promise in a comment",
102
+ "subject": "pi-extensions/lib/codex-caller-seat.ts",
103
+ "find": ["export type CodexCallerSeatRejectReason ="],
104
+ "replace": [
105
+ "import { resolveTmuxSessionId } from \"./resolve-tmux-session.ts\";\n\nexport type CodexCallerSeatRejectReason ="
106
+ ],
107
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
108
+ "timeoutSeconds": 120,
109
+ "signature": "[QK:CODEX-SEAT-IMPORTS-NOTHING]",
110
+ "signatureSource": "test/codex-caller-seat.test.ts"
111
+ },
112
+ {
113
+ "claim": "CODEX-SEAT-FOLLOWS-CALLER-NOT-BACKEND",
114
+ "title": "the anchor branch is gated on the BACKEND BEING OPENED instead of on who is calling — the exact confusion #95 D1 retired the `codex` home to remove. A Codex citizen opening a PI sibling then loses its own pane and lands wherever the app-server happens to sit, while a pi citizen opening a CODEX sibling walks into an anchor lookup it has no thread id for",
115
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
116
+ "find": ["\t} else if (params.placement === undefined && params.callerNativeSessionId !== undefined) {"],
117
+ "replace": ["\t} else if (params.placement === undefined && params.backend === \"codex\") {"],
118
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
119
+ "timeoutSeconds": 120,
120
+ "signature": "[QK:CODEX-SEAT-FOLLOWS-CALLER-NOT-BACKEND]",
121
+ "signatureSource": "test/mux-fresh-call.test.ts"
122
+ },
123
+ {
124
+ "claim": "CODEX-SEAT-PRECEDENCE-ANCHOR-OVER-EXPLICIT",
125
+ "title": "the anchor stops re-reading the explicit placement, so an expert override is silently overridden by the caller's own pane — the operator names a project seat and the sibling opens somewhere else, with a receipt that says `requested`",
126
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
127
+ "find": ["\t} else if (params.placement === undefined && params.callerNativeSessionId !== undefined) {"],
128
+ "replace": ["\t}\n\tif (params.callerNativeSessionId !== undefined) {"],
129
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
130
+ "timeoutSeconds": 120,
131
+ "signature": "[QK:CODEX-SEAT-PRECEDENCE-ANCHOR-OVER-EXPLICIT]",
132
+ "signatureSource": "test/mux-fresh-call.test.ts"
133
+ },
134
+ {
135
+ "claim": "CODEX-SEAT-COMPOSITION-REJECT-IS-REAL",
136
+ "title": "an unresolvable or ambiguous anchor stops being a refusal and falls through to the caller's own session — the sibling opens, the launch looks successful, and the operator's Codex never sees the window it asked for. Proof is in two halves: this mutant dies on the source-text branch assert in the fast lane, and the behavioural oracle independent of that source is the caller-seat composition cell in check-mux-launch-tmux (a named refusal with a byte-identical list-windows -a before and after)",
137
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
138
+ "find": ["\t\tif (!anchor.ok) return { ok: false, reason: anchor.reason };"],
139
+ "replace": ["\t\tif (!anchor.ok) return { ok: false, reason: \"caller-identity-unavailable\" };"],
140
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
141
+ "timeoutSeconds": 120,
142
+ "signature": "[QK:CODEX-SEAT-COMPOSITION-REJECT-IS-REAL]",
143
+ "signatureSource": "test/mux-fresh-call.test.ts"
144
+ },
145
+ {
146
+ "claim": "CODEX-SEAT-RECEIPT-INVENTS-A-NAME",
147
+ "title": "the anchored receipt reports a session NAME the caller never asked for — `tmuxSession` becomes the observed `$id`, so a receipt field whose whole contract is `what was REQUESTED` starts carrying an observation, and every reader that echoes it back as a seat name addresses a session by a string tmux may not resolve",
148
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
149
+ "find": ["\t\t\t\t\t? { tmuxSessionSource: \"codex-title-anchor\" as const }"],
150
+ "replace": ["\t\t\t\t\t? { tmuxSession: targetSessionId, tmuxSessionSource: \"codex-title-anchor\" as const }"],
151
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
152
+ "timeoutSeconds": 120,
153
+ "signature": "[QK:CODEX-SEAT-RECEIPT-INVENTS-A-NAME]",
154
+ "signatureSource": "test/mux-fresh-call.test.ts"
155
+ },
156
+ {
157
+ "claim": "CODEX-SEAT-TITLE-BECOMES-DELIVERY-TARGET",
158
+ "title": "the caller's own garden address is replaced by the thread id the PANE TITLE matched, so a forgeable title decides who the fresh sibling is told to call back to. The sibling's first message would then be delivered against an identity nobody vouched for — the exact Hard Rule 16 crossing this lane is built to make impossible: the title is a placement input and may never become an address",
159
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
160
+ "find": ["\t\t\tcallerGardenId: params.callerGardenId,\n\t\t\tnonce,\n\t\t}),"],
161
+ "replace": [
162
+ "\t\t\tcallerGardenId: params.callerNativeSessionId ?? params.callerGardenId,\n\t\t\tnonce,\n\t\t}),"
163
+ ],
164
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
165
+ "timeoutSeconds": 120,
166
+ "signature": "[QK:CODEX-SEAT-TITLE-BECOMES-DELIVERY-TARGET]",
167
+ "signatureSource": "test/mux-fresh-call.test.ts"
168
+ },
169
+ {
170
+ "claim": "CODEX-CALLER-PREFLIGHT-TITLE",
171
+ "title": "the caller-side capability check reads `status_line` instead of `terminal_title`, so a host whose visible identity is installed passes a seat check it never satisfied — the fresh call then reaches tmux, finds no titled pane, and reports `unresolved` as if the operator's TUI were missing rather than their config",
172
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
173
+ "find": ["\tconst terminalTitle = (tui as Record<string, unknown>).terminal_title;"],
174
+ "replace": ["\tconst terminalTitle = (tui as Record<string, unknown>).status_line;"],
175
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
176
+ "timeoutSeconds": 120,
177
+ "signature": "[QK:CODEX-CALLER-PREFLIGHT-TITLE]",
178
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
179
+ },
180
+ {
181
+ "claim": "CODEX-CALLER-PREFLIGHT-NOT-STATUS-LINE",
182
+ "title": "the caller axis accepts `thread-title` as well as `thread-id`, so the status-line atom silently satisfies a check it has nothing to do with — `status_line` is read inside the TUI and never reaches `#{pane_title}`, so the seat is still unfindable and the operator is told their config is fine",
183
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
184
+ "find": ["\treturn !Array.isArray(terminalTitle) || !terminalTitle.includes(\"thread-id\");"],
185
+ "replace": [
186
+ "\treturn !Array.isArray(terminalTitle) || !(terminalTitle.includes(\"thread-id\") || terminalTitle.includes(\"thread-title\"));"
187
+ ],
188
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
189
+ "timeoutSeconds": 120,
190
+ "signature": "[QK:CODEX-CALLER-PREFLIGHT-NOT-STATUS-LINE]",
191
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
192
+ },
193
+ {
194
+ "claim": "CODEX-CALLER-PREFLIGHT-SHAPE",
195
+ "title": "a `terminal_title` that is not an array of items is read as satisfying the caller axis, so an operator whose value is a string (or whose config does not parse) is told the seat is configured and only finds out when the fresh call cannot place a window",
196
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
197
+ "find": ["\tif (config === null || callerTitleMissing(config)) return \"codex-caller-title-missing\";"],
198
+ "replace": ["\tif (config !== null && callerTitleMissing(config)) return \"codex-caller-title-missing\";"],
199
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
200
+ "timeoutSeconds": 120,
201
+ "signature": "[QK:CODEX-CALLER-PREFLIGHT-SHAPE]",
202
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
203
+ },
204
+ {
205
+ "claim": "CODEX-CALLER-PREFLIGHT-SURFACE-WIRED",
206
+ "title": "the bridge stops running the caller-side preflight, so a Codex caller with no `thread-id` in its terminal title gets the generic `unresolved` seat refusal instead of the installer that repairs it — a capability gap reported as an operator's missing TUI",
207
+ "subject": "mcp/entwurf-bridge/src/index.ts",
208
+ "find": [
209
+ "\t\t\t\ttargetMissing === null && callerNativeSessionId !== undefined && placement === undefined\n\t\t\t\t\t? codexCallerFreshPreflight(process.env)\n\t\t\t\t\t: null;"
210
+ ],
211
+ "replace": ["\t\t\t\tnull;"],
212
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
213
+ "timeoutSeconds": 120,
214
+ "signature": "[QK:CODEX-CALLER-PREFLIGHT-SURFACE-WIRED]",
215
+ "signatureSource": "test/fresh-call-surfaces.contract.test.ts"
216
+ },
217
+ {
218
+ "claim": "CODEX-TT-OPERATOR-ITEM-UNOWNED",
219
+ "title": "the pre-repair shape returns: an operator-authored `thread-id` gets a receipt written over it, so entwurf claims an edit it never made. The doctor then answers `owned`, and its `drift` red — whose repair reads \"reinstall in place\" — starts pointing at bytes that were always the operator's. The same receipt is what made the inverse dishonest: parked over their item, a reinstall after they hand-removed it splices ours back in while the receipt still says `already-present`, and the take-back then reports \"nothing to take back\" and leaves our bytes in their config for good",
220
+ "subject": "scripts/codex-terminal-title-config.py",
221
+ "find": [
222
+ " if prior is None:\n # The operator wrote it. Nothing is written ANYWHERE — not the config, not a\n # receipt — so the doctor reads this host as `unowned`, which is what it is.\n sys.stdout.write(f\"already-present {os.path.abspath(config_path)}\\n\")\n return\n # Ours, already installed. The strongest idempotence is to leave both files exactly\n # as they are, carrying the ORIGINAL receipt rather than re-deriving one.\n mode = prior[\"detectMode\"]\n new_lines = lines\n preimage = prior[\"terminalTitlePreimage\"]\n postimage_span = prior[\"postimageSpan\"]\n snippet = prior[\"snippet\"]\n"
223
+ ],
224
+ "replace": [
225
+ " mode = \"already-present\"\n new_lines = lines\n preimage = span_text(lines, key_span[0], key_span[1] + 1)\n postimage_span = preimage\n snippet = None\n"
226
+ ],
227
+ "gate": ["bash", "run.sh", "smoke-codex-config-state"],
228
+ "timeoutSeconds": 300,
229
+ "signature": "[QK:CODEX-TT-OPERATOR-ITEM-UNOWNED]",
230
+ "signatureSource": "scripts/smoke-codex-config-state.sh"
231
+ },
232
+ {
233
+ "claim": "CODEX-LAUNCH-CWD-TRUST",
234
+ "title": "the launch directory's folder consent is never asked, so an unconsented directory is launched anyway — the sibling opens on the vendor's trust screen, starts no turn, writes no rollout and never calls back, and the caller waits out its whole timeout on a window that is merely waiting to be answered. This is the 2026-09-16 failure verbatim: the two unattended release-gate runs died here while the two a human sat through passed",
235
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
236
+ "find": ["\tif (levelOf(launchCwd) !== null) return null;"],
237
+ "replace": ["\tif (true) return null;"],
238
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
239
+ "timeoutSeconds": 120,
240
+ "signature": "[QK:CODEX-LAUNCH-CWD-TRUST]",
241
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
242
+ },
243
+ {
244
+ "claim": "CODEX-LAUNCH-CWD-DECIDED-NOT-TRUSTED",
245
+ "title": "the axis narrows back to `trusted` only, so a directory the operator deliberately answered `untrusted` is refused even though the vendor skips its consent screen on this remote rail and the turn would have started — entwurf inventing a policy the vendor does not have, and a launch that works refused for a reason the operator cannot repair without reversing their own security decision",
246
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
247
+ "find": ["\t\treturn level === \"trusted\" || level === \"untrusted\" ? level : null;"],
248
+ "replace": ["\t\treturn level === \"trusted\" ? level : null;"],
249
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
250
+ "timeoutSeconds": 120,
251
+ "signature": "[QK:CODEX-LAUNCH-CWD-DECIDED-NOT-TRUSTED]",
252
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
253
+ },
254
+ {
255
+ "claim": "CODEX-LAUNCH-CWD-EXACT-KEY",
256
+ "title": "the consent lookup walks up to a parent entry, so a trusted `/tmp` is read as consent for an undecided `/tmp/<scratch>` — the preflight passes a launch the vendor still stops, which is the exact shape measured on 2026-09-16 and the reason this rail's key is the literal cwd (`ProjectTrustHost::Remote` looks up `vec![cwd_key]` alone, with no root marker, git root or parent)",
257
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
258
+ "find": ["\tif (levelOf(launchCwd) !== null) return null;"],
259
+ "replace": [
260
+ "\tif (Object.keys(table).some((key) => levelOf(key) === \"trusted\" && (launchCwd === key || launchCwd.startsWith(`${key}/`)))) return null;"
261
+ ],
262
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
263
+ "timeoutSeconds": 120,
264
+ "signature": "[QK:CODEX-LAUNCH-CWD-EXACT-KEY]",
265
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
266
+ },
267
+ {
268
+ "claim": "CODEX-LAUNCH-CWD-NO-EVIDENCE-PROCEEDS",
269
+ "title": "an unreadable user config becomes a refusal, so absence is treated as evidence: the vendor loads an empty user table and merges system, managed and cloud layers around it, any of which can carry the decision that starts the turn. Every host without a user config.toml — and every managed installation — would be refused a sibling the vendor was going to open, with a repair that fixes nothing",
270
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
271
+ "find": ["\tif (config === null) return null;"],
272
+ "replace": ["\tif (config === null) return \"codex-launch-cwd-undecided\";"],
273
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
274
+ "timeoutSeconds": 120,
275
+ "signature": "[QK:CODEX-LAUNCH-CWD-NO-EVIDENCE-PROCEEDS]",
276
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
277
+ },
278
+ {
279
+ "claim": "CODEX-LAUNCH-CWD-UNTRUSTED-ANCESTOR",
280
+ "title": "a cwd inside an explicitly untrusted project collapses into the generic undecided reason, so the operator is told to answer a consent prompt at a directory the vendor never prompts for — on this rail it refuses the startup outright with `pass the repository root explicitly with --cd`, and the suggested repair reproduces that same error instead of fixing it",
281
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
282
+ "find": ["\t\t\treturn \"codex-launch-cwd-untrusted-ancestor\";"],
283
+ "replace": ["\t\t\treturn \"codex-launch-cwd-undecided\";"],
284
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
285
+ "timeoutSeconds": 120,
286
+ "signature": "[QK:CODEX-LAUNCH-CWD-UNTRUSTED-ANCESTOR]",
287
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
288
+ },
289
+ {
290
+ "claim": "CODEX-LAUNCH-CWD-LAYER-NOT-REFUSED",
291
+ "title": "the project-layer escape narrows from EXISTENCE to safe ownership, so a `.codex` the vendor would admit a layer from — its admission consults no owner, mode or symlink safety, and it preserves even an unknown or untrusted layer as a disabled one — goes unseen here and the leaf synthesises an `untrusted-ancestor` refusal whose vendor precondition (`project_layers.is_empty()`) does not hold. That is a FALSE REFUSAL: the operator loses a sibling that would have started, and is handed a repair naming a directory that was never the problem",
292
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
293
+ "find": ["\t\tif (candidate !== codexHome && fs.existsSync(candidate)) return null;"],
294
+ "replace": [
295
+ "\t\tif (candidate !== codexHome && isSafeOwnedDir(candidate, process.getuid?.() ?? -1)) return null;"
296
+ ],
297
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
298
+ "timeoutSeconds": 120,
299
+ "signature": "[QK:CODEX-LAUNCH-CWD-LAYER-NOT-REFUSED]",
300
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
301
+ },
302
+ {
303
+ "claim": "CODEX-LAUNCH-CWD-HOME-NOT-A-LAYER",
304
+ "title": "the operator's own codex home counts as a project layer, so every directory under $HOME answers `null` and the axis is silently dead on a real host — the gate stays green while the failure it exists to catch walks straight through it",
305
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
306
+ "find": ["\t\tif (candidate !== codexHome && fs.existsSync(candidate)) return null;"],
307
+ "replace": ["\t\tif (fs.existsSync(candidate)) return null;"],
308
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
309
+ "timeoutSeconds": 120,
310
+ "signature": "[QK:CODEX-LAUNCH-CWD-HOME-NOT-A-LAYER]",
311
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
312
+ },
313
+ {
314
+ "claim": "CODEX-LAUNCH-CWD-ANCESTOR-PLAIN-PATHS-ONLY",
315
+ "title": "the ancestor match is asserted for paths this leaf cannot compare the way the vendor does — the vendor compares path URIs segment-wise and fails closed on encoded separators, this compares strings — so an encoded or dot-segmented key can produce a confident `untrusted-ancestor` refusal, complete with a repair naming a specific other directory, for a launch the vendor would have run",
316
+ "subject": "pi-extensions/lib/codex-fresh-preflight.ts",
317
+ "find": ["\tif (plainPosixPath(launchCwd)) {"],
318
+ "replace": ["\tif (true) {"],
319
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
320
+ "timeoutSeconds": 120,
321
+ "signature": "[QK:CODEX-LAUNCH-CWD-ANCESTOR-PLAIN-PATHS-ONLY]",
322
+ "signatureSource": "test/codex-fresh-preflight.test.ts"
323
+ },
324
+ {
325
+ "claim": "FRESHCALL-CODEX-LAUNCH-CWD-NOTES-NEVER-REFUSES",
326
+ "title": "the note names the REQUESTED directory instead of the token codex receives, so a launch that named none — the case where the pane inherits this process's directory and `-C` names it — reports the empty string, and the operator is pointed at a directory the thread never starts in",
327
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
328
+ "find": ["\t\tconst launchCwd = backendArgs[at + 1] ?? \"\";"],
329
+ "replace": ["\t\tconst launchCwd = cwd ?? \"\";"],
330
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
331
+ "timeoutSeconds": 120,
332
+ "signature": "[QK:FRESHCALL-CODEX-LAUNCH-CWD-NOTES-NEVER-REFUSES]",
333
+ "signatureSource": "test/mux-fresh-call.test.ts"
334
+ }
335
+ ]
336
+ }