@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,434 @@
1
+ #!/usr/bin/env python3
2
+ """codex-statusline-config — stateful `[tui].status_line` adapter; stdlib only.
3
+
4
+ One reversible atom in `$CODEX_HOME/config.toml`: ensure the TUI's closed-enum
5
+ status line INCLUDES `thread-title` — the only carrier that can hold a visible
6
+ garden id (measured S1b-B in scripts/raw-codex-measure/README.md: an explicitly
7
+ set name survives auto-titling in both orderings). The atom owns exactly the
8
+ `thread-title` MEMBERSHIP, never the list: operator items, their order and
9
+ their bytes survive untouched (a text insert right after the opening bracket,
10
+ not a re-serialization), and the inverse removes exactly the snippet that was
11
+ added. `status_line` is snake_case in TOML — the vendor's own key on this host.
12
+
13
+ Refuses, with the offending name in the message: a symlinked config (someone
14
+ else's SSOT), malformed TOML, a foreign install-state, a state retargeted at
15
+ another config path, and an explicit contrary operator value by name — a
16
+ `status_line` that is not an array of items is the operator's decision, not
17
+ drift to overwrite.
18
+
19
+ The doctor judges EFFECTIVE thread-title from the config alone, independent of
20
+ the state receipt (the operator may set it themselves — that is green, not
21
+ drift). Its subject is that REQUIRED visible-identity axis, so a present item is
22
+ the only green and every other effective token is red; the state never rescues
23
+ one, it only annotates ownership and adds `drift` when our receipt binds the
24
+ file whose item is gone.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import sys
32
+ sys.dont_write_bytecode = True
33
+
34
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
35
+ from codex_toml_io import ( # noqa: E402
36
+ append_block,
37
+ assert_blast_radius,
38
+ atomic_write,
39
+ atomic_write_state,
40
+ deep_get,
41
+ die,
42
+ find_direct_key,
43
+ find_table_family,
44
+ now,
45
+ parse_toml,
46
+ read_text,
47
+ render_file,
48
+ render_string_array,
49
+ scan_lines,
50
+ span_text,
51
+ span_value,
52
+ splice,
53
+ )
54
+
55
+ STATE_SCHEMA_VERSION = 1
56
+ ATOM = "codex-statusline"
57
+ TUI_PATH = ("tui",)
58
+ STATUSLINE_KEY = "status_line"
59
+ REQUIRED_ITEM = "thread-title"
60
+ STATE_REQUIRED = (
61
+ "managedConfigPath",
62
+ "atom",
63
+ "detectMode",
64
+ "configExistedBefore",
65
+ "tuiTableExisted",
66
+ "statusLineExisted",
67
+ "statusLinePreimage",
68
+ "postimageSpan",
69
+ "snippet",
70
+ "installedAt",
71
+ )
72
+
73
+ # detectMode → what the inverse takes back:
74
+ # created-new the whole file (we made it; only if nothing else remains)
75
+ # appended-table the [tui] block we appended (only if it is still only ours)
76
+ # inserted-key the `status_line = [...]` key lines we inserted
77
+ # merged-item the exact `"thread-title", ` snippet spliced after `[`
78
+ # already-present nothing — the operator had it before we ever ran
79
+
80
+
81
+ def load_state(state_path: str) -> dict | None:
82
+ raw = read_text(state_path, "install-state")
83
+ if raw is None:
84
+ return None
85
+ if not raw.strip():
86
+ die(4, f"codex-statusline: install-state {state_path} is empty — refusing to guess")
87
+ try:
88
+ state = json.loads(raw)
89
+ except ValueError as error:
90
+ die(4, f"codex-statusline: install-state {state_path} is not valid JSON: {error}")
91
+ if not isinstance(state, dict):
92
+ die(4, f"codex-statusline: install-state {state_path} top-level must be a JSON object")
93
+ return state
94
+
95
+
96
+ def checked_state(state_path: str) -> dict:
97
+ state = load_state(state_path)
98
+ if state is None:
99
+ die(2, f"codex-statusline: no install-state at {state_path} — nothing to undo")
100
+ if state.get("schemaVersion") != STATE_SCHEMA_VERSION:
101
+ die(4, f"codex-statusline: install-state {state_path} is not schemaVersion {STATE_SCHEMA_VERSION} — refusing")
102
+ atom = state.get("atom")
103
+ if atom != ATOM:
104
+ die(4, f"codex-statusline: install-state {state_path} belongs to atom {atom!r}, not {ATOM!r} — refusing to touch it")
105
+ if any(key not in state for key in STATE_REQUIRED):
106
+ die(4, f"codex-statusline: install-state {state_path} has an unsupported shape — refusing")
107
+ path = state.get("managedConfigPath")
108
+ if not isinstance(path, str) or not os.path.isabs(path):
109
+ die(4, f"codex-statusline: install-state {state_path} has no absolute managedConfigPath")
110
+ return state
111
+
112
+
113
+ def prior_state(state_path: str, config_path: str) -> dict | None:
114
+ state = load_state(state_path)
115
+ if state is None:
116
+ return None
117
+ if state.get("schemaVersion") != STATE_SCHEMA_VERSION or state.get("atom") != ATOM:
118
+ checked_state(state_path) # the honest, named refusal
119
+ if state.get("managedConfigPath") != os.path.abspath(config_path):
120
+ die(
121
+ 3,
122
+ f"codex-statusline: install-state {state_path} is bound to {state.get('managedConfigPath')}, "
123
+ f"not {os.path.abspath(config_path)} — refusing to retarget an install onto another config",
124
+ )
125
+ return checked_state(state_path)
126
+
127
+
128
+ def classify(parsed: dict) -> tuple[str, object]:
129
+ """`(tui-shape, status_line-value)`; tui-shape is `table`, `refuse:<name>`,
130
+ or `not-line-shaped` only in combination with the span search."""
131
+ tui = deep_get(parsed, TUI_PATH)
132
+ if tui is None:
133
+ return "absent", None
134
+ if not isinstance(tui, dict):
135
+ return "refuse:tui", tui
136
+ value = tui.get(STATUSLINE_KEY)
137
+ if value is None:
138
+ return "no-key", None
139
+ if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
140
+ return "refuse:status_line", value
141
+ return "has-key", value
142
+
143
+
144
+ def install(config_path: str, state_path: str) -> None:
145
+ if os.path.islink(config_path):
146
+ die(3, f"codex-statusline: refusing to adopt {config_path} — symlink to {os.readlink(config_path)} (someone else's SSOT)")
147
+ if os.path.exists(config_path) and not os.path.isfile(config_path):
148
+ die(4, f"codex-statusline: refusing: {config_path} exists and is not a regular file")
149
+
150
+ text = read_text(config_path, "config")
151
+ parsed = parse_toml(text if text is not None else "", f"config {config_path}") if text is not None else {}
152
+ shape, value = classify(parsed)
153
+ if shape.startswith("refuse:"):
154
+ name = shape.split(":", 1)[1]
155
+ die(
156
+ 3,
157
+ f"codex-statusline: refusing: {'.'.join(TUI_PATH)}{'.' + STATUSLINE_KEY if name == STATUSLINE_KEY else ''} in {config_path} "
158
+ f"is explicitly a {type(value).__name__}, not {'an array of status-line items' if name == STATUSLINE_KEY else 'a table'}. "
159
+ "That is the operator's decision, not drift — entwurf will not overwrite it by name.",
160
+ )
161
+
162
+ lines = (text if text is not None else "").split("\n")
163
+ infos = scan_lines(lines)
164
+ tui_family = find_table_family(lines, infos, TUI_PATH)
165
+ key_span = find_direct_key(lines, infos, tui_family[0], tui_family[1], STATUSLINE_KEY) if tui_family is not None else None
166
+ if shape in ("has-key", "no-key") and tui_family is None:
167
+ die(
168
+ 4,
169
+ f"codex-statusline: [tui] in {config_path} exists but not as a literal table block "
170
+ "(inline/dotted form) — this writer owns only line-shaped tables and refuses to touch it",
171
+ )
172
+ if shape == "has-key" and key_span is None:
173
+ die(
174
+ 4,
175
+ f"codex-statusline: `tui.{STATUSLINE_KEY}` in {config_path} exists but not as a literal `key = value` line "
176
+ "(dotted/inline form) — this writer owns only line-shaped keys and refuses to touch it",
177
+ )
178
+
179
+ canonical_line = f"{STATUSLINE_KEY} = {render_string_array([REQUIRED_ITEM])}"
180
+ prior = prior_state(state_path, config_path)
181
+
182
+ if shape == "has-key":
183
+ if REQUIRED_ITEM in value:
184
+ mode = "already-present"
185
+ new_lines = lines # untouched: the strongest idempotence is no write at all
186
+ preimage = span_text(lines, key_span[0], key_span[1] + 1)
187
+ postimage_span = preimage
188
+ snippet = None
189
+ else:
190
+ mode = "merged-item"
191
+ preimage = span_text(lines, key_span[0], key_span[1] + 1)
192
+ snippet = f'"{REQUIRED_ITEM}", ' if value else f'"{REQUIRED_ITEM}"'
193
+ old_span = preimage
194
+ bracket = old_span.index("[", old_span.index("="))
195
+ new_span = old_span[: bracket + 1] + snippet + old_span[bracket + 1:]
196
+ # The scanner's own receipt: the merged span must parse to exactly
197
+ # the old list with our item prepended, or we refuse to splice it.
198
+ merged_value = span_value(new_span, "merged status_line").get(STATUSLINE_KEY)
199
+ if merged_value != [REQUIRED_ITEM] + list(value):
200
+ die(7, "codex-statusline: internal refusal: merged status_line span does not re-parse to old items + thread-title")
201
+ postimage_span = new_span
202
+ new_lines = splice(lines, key_span[0], key_span[1] + 1, new_span.split("\n"))
203
+ elif shape == "no-key":
204
+ mode = "inserted-key"
205
+ preimage = None
206
+ postimage_span = canonical_line
207
+ snippet = None
208
+ new_lines = lines[: tui_family[0] + 1] + [canonical_line] + lines[tui_family[0] + 1:]
209
+ elif shape == "absent":
210
+ block = [f"[{TUI_PATH[0]}]", canonical_line]
211
+ if text is None:
212
+ mode = "created-new"
213
+ new_lines = list(block)
214
+ else:
215
+ mode = "appended-table"
216
+ new_lines = append_block(lines, block)
217
+ preimage = None
218
+ postimage_span = canonical_line
219
+ snippet = None
220
+ else: # pragma: no cover — classify() exhausted above
221
+ die(7, f"codex-statusline: internal: unhandled shape {shape!r}")
222
+
223
+ if prior is None:
224
+ state = {
225
+ "schemaVersion": STATE_SCHEMA_VERSION,
226
+ "managedConfigPath": os.path.abspath(config_path),
227
+ "atom": ATOM,
228
+ "detectMode": mode,
229
+ "configExistedBefore": text is not None,
230
+ "tuiTableExisted": shape in ("has-key", "no-key"),
231
+ "statusLineExisted": shape == "has-key",
232
+ # The FIRST preimage, captured once; a reinstall never re-captures.
233
+ "statusLinePreimage": preimage,
234
+ "postimageSpan": postimage_span,
235
+ "snippet": snippet,
236
+ "installedAt": now(),
237
+ }
238
+ else:
239
+ state = prior
240
+
241
+ candidate = render_file(new_lines)
242
+ _, after = assert_blast_radius(text, candidate, TUI_PATH + (STATUSLINE_KEY,), f"config {config_path}")
243
+ effective = deep_get(after, TUI_PATH + (STATUSLINE_KEY,))
244
+ if shape == "has-key":
245
+ expected = list(value) if mode == "already-present" else [REQUIRED_ITEM] + list(value)
246
+ else:
247
+ expected = [REQUIRED_ITEM]
248
+ if effective != expected:
249
+ die(7, f"codex-statusline: internal refusal: candidate status_line is not the operator's items plus thread-title")
250
+
251
+ # The receipt is durable BEFORE the operator's config is ever touched: it
252
+ # already carries the exact preimage/snippet of the bytes about to be
253
+ # replaced (or a prior receipt's, on a re-run). A crash or write failure
254
+ # between here and the config write below leaves the operator's config
255
+ # untouched and the receipt correct — a retry resumes from it instead of
256
+ # mistaking our own canonical postimage for a fresh operator preimage.
257
+ atomic_write_state(state_path, state)
258
+ if candidate != (text if text is not None else ""):
259
+ atomic_write(config_path, candidate, prefix=".codex-sl-")
260
+ sys.stdout.write(f"{mode} {os.path.abspath(config_path)}\n")
261
+
262
+
263
+ def _current_key_span(config_path: str, text: str):
264
+ """Locate the CURRENT status_line key span (fresh bytes, never cached) and
265
+ its parsed value; `not-line-shaped` is refused here too."""
266
+ parsed = parse_toml(text, f"config {config_path}")
267
+ shape, value = classify(parsed)
268
+ if shape.startswith("refuse:"):
269
+ die(4, f"codex-statusline: {config_path} now carries an explicit contrary value under `tui`/`{STATUSLINE_KEY}` — refusing to guess; fix it yourself")
270
+ lines = text.split("\n")
271
+ infos = scan_lines(lines)
272
+ tui_family = find_table_family(lines, infos, TUI_PATH)
273
+ key_span = find_direct_key(lines, infos, tui_family[0], tui_family[1], STATUSLINE_KEY) if tui_family is not None else None
274
+ return parsed, shape, value, lines, infos, tui_family, key_span
275
+
276
+
277
+ def uninstall(state_path: str) -> None:
278
+ state = checked_state(state_path)
279
+ config_path = state["managedConfigPath"]
280
+ mode = state["detectMode"]
281
+ if os.path.islink(config_path):
282
+ die(3, f"codex-statusline: refusing to uninstall — {config_path} became a symlink (someone else's SSOT)")
283
+
284
+ text = read_text(config_path, "config")
285
+ if text is None:
286
+ os.remove(state_path)
287
+ sys.stdout.write(f"uninstalled {config_path} (config already absent)\n")
288
+ return
289
+ parsed, shape, value, lines, infos, tui_family, key_span = _current_key_span(config_path, text)
290
+
291
+ if mode == "already-present":
292
+ os.remove(state_path) # nothing was taken; nothing is given back
293
+ sys.stdout.write(f"uninstalled {config_path} (already present before install; nothing to take back)\n")
294
+ return
295
+
296
+ if key_span is None:
297
+ if shape == "has-key" and REQUIRED_ITEM in value:
298
+ # `tui`/`status_line` is gone by NAME but the path still holds
299
+ # thread-title — reshaped into inline/dotted TOML. This writer
300
+ # cannot span-edit that shape; treating it as absent would
301
+ # silently drop the receipt over an item we never actually took
302
+ # back. Refuse and keep the receipt so the operator can repair it.
303
+ die(
304
+ 3,
305
+ f"codex-statusline: refusing to uninstall — `tui.{STATUSLINE_KEY}` in {config_path} exists but not "
306
+ "as a literal `key = value` line (dotted/inline form) — this writer owns only line-shaped keys "
307
+ "and refuses to guess; the receipt is retained",
308
+ )
309
+ # else: thread-title is genuinely absent — our membership was taken
310
+ # back by hand, or vanished along with the key/table itself.
311
+ os.remove(state_path)
312
+ sys.stdout.write(f"uninstalled {config_path} (status_line already absent)\n")
313
+ return
314
+
315
+ current_span = span_text(lines, key_span[0], key_span[1] + 1)
316
+ current_value = value if shape == "has-key" else None
317
+
318
+ if mode == "merged-item":
319
+ if current_span != state["postimageSpan"]:
320
+ if isinstance(current_value, list) and REQUIRED_ITEM not in current_value:
321
+ os.remove(state_path) # operator removed our item themselves
322
+ sys.stdout.write(f"uninstalled {config_path} (thread-title already removed)\n")
323
+ return
324
+ die(
325
+ 6,
326
+ f"codex-statusline: refusing: `tui.{STATUSLINE_KEY}` in {config_path} changed since install "
327
+ "(operator edits present). Remove 'thread-title' yourself if you want it gone.",
328
+ )
329
+ snippet = state["snippet"]
330
+ if not isinstance(snippet, str) or snippet not in current_span:
331
+ die(7, "codex-statusline: internal refusal: recorded snippet is not in the recorded span")
332
+ restored = current_span.replace(snippet, "", 1)
333
+ restored_value = span_value(restored, "restored status_line").get(STATUSLINE_KEY)
334
+ preimage = state["statusLinePreimage"]
335
+ preimage_value = span_value(preimage, "preimage status_line").get(STATUSLINE_KEY) if preimage else None
336
+ if restored_value != preimage_value:
337
+ die(7, "codex-statusline: internal refusal: snippet removal does not reproduce the preimage value")
338
+ candidate_lines = splice(lines, key_span[0], key_span[1] + 1, restored.split("\n"))
339
+ elif mode in ("inserted-key", "appended-table", "created-new"):
340
+ if current_span != state["postimageSpan"]:
341
+ if isinstance(current_value, list) and REQUIRED_ITEM not in current_value:
342
+ os.remove(state_path)
343
+ sys.stdout.write(f"uninstalled {config_path} (thread-title already removed)\n")
344
+ return
345
+ die(
346
+ 6,
347
+ f"codex-statusline: refusing: `tui.{STATUSLINE_KEY}` in {config_path} changed since install "
348
+ "(operator edits present). Remove it yourself if you want it gone.",
349
+ )
350
+ candidate_lines = splice(lines, key_span[0], key_span[1] + 1, [])
351
+ # A [tui] block that WE appended and that now holds nothing else goes
352
+ # away whole; an operator's [tui] header always stays.
353
+ if not state["tuiTableExisted"] and tui_family is not None:
354
+ rescanned = scan_lines(candidate_lines)
355
+ family = find_table_family(candidate_lines, rescanned, TUI_PATH)
356
+ if family is not None:
357
+ family_body = span_text(candidate_lines, family[0] + 1, family[1])
358
+ if not [line for line in family_body.split("\n") if line.strip()]:
359
+ candidate_lines = splice(candidate_lines, family[0], family[1], [])
360
+ else:
361
+ die(4, f"codex-statusline: install-state {state_path} has unknown detectMode {mode!r}")
362
+
363
+ candidate = render_file(candidate_lines)
364
+ assert_blast_radius(text, candidate, TUI_PATH + (STATUSLINE_KEY,), f"config {config_path}")
365
+ if state["detectMode"] == "created-new" and state["configExistedBefore"] is False and not [l for l in candidate_lines if l.strip()]:
366
+ os.remove(config_path)
367
+ os.remove(state_path)
368
+ sys.stdout.write(f"uninstalled {config_path} (file removed)\n")
369
+ return
370
+ atomic_write(config_path, candidate, prefix=".codex-sl-")
371
+ os.remove(state_path) # the receipt is deleted LAST
372
+ sys.stdout.write(f"uninstalled {config_path}\n")
373
+
374
+
375
+ def effective_token(config_path: str) -> str:
376
+ """The EFFECTIVE verdict, judged from the config alone — never the state."""
377
+ if os.path.islink(config_path):
378
+ return "symlink"
379
+ text = read_text(config_path, "config")
380
+ if text is None or not text.strip():
381
+ return "file-absent"
382
+ try:
383
+ parsed = parse_toml(text, f"config {config_path}")
384
+ except SystemExit:
385
+ return "invalid-toml"
386
+ shape, value = classify(parsed)
387
+ if shape in ("absent", "no-key"):
388
+ return "status-line-absent"
389
+ if shape.startswith("refuse:"):
390
+ return "malformed-value"
391
+ return "thread-title-present" if REQUIRED_ITEM in value else "thread-title-absent"
392
+
393
+
394
+ def doctor_static(config_path: str, state_path: str) -> None:
395
+ """One line, two axes: `<effective> <ownership>[ drift]`. The effective
396
+ token is computed without ever reading the receipt; ownership annotates and
397
+ never rescues. GREEN (exit 0) is exactly `thread-title-present`, owned or
398
+ unowned — an unowned file that already carries the item is green, and every
399
+ other effective token is RED because this doctor's subject is the REQUIRED
400
+ visible-identity axis, not our bookkeeping. Ownership still distinguishes
401
+ the two reds an operator repairs differently: `drift` means our receipt
402
+ binds this file and our item is gone (repair in place), while a bare
403
+ `<effective> unowned` means the axis was never established here."""
404
+ effective = effective_token(config_path)
405
+ state = load_state(state_path)
406
+ owned = (
407
+ state is not None
408
+ and state.get("schemaVersion") == STATE_SCHEMA_VERSION
409
+ and state.get("atom") == ATOM
410
+ and state.get("managedConfigPath") == os.path.abspath(config_path)
411
+ )
412
+ ownership = "owned" if owned else "unowned"
413
+ if effective != "thread-title-present":
414
+ sys.stdout.write(f"{effective} {ownership}{' drift' if owned else ''}\n")
415
+ raise SystemExit(1)
416
+ sys.stdout.write(f"{effective} {ownership}\n")
417
+
418
+
419
+ def main(argv: list[str]) -> None:
420
+ if len(argv) < 2:
421
+ die(5, "usage: codex-statusline-config.py <install config state|uninstall state|doctor-static config state>")
422
+ verb = argv[1]
423
+ if verb == "install" and len(argv) == 4:
424
+ install(argv[2], argv[3])
425
+ elif verb == "uninstall" and len(argv) == 3:
426
+ uninstall(argv[2])
427
+ elif verb == "doctor-static" and len(argv) == 4:
428
+ doctor_static(argv[2], argv[3])
429
+ else:
430
+ die(5, "usage: codex-statusline-config.py <install config state|uninstall state|doctor-static config state>")
431
+
432
+
433
+ if __name__ == "__main__":
434
+ main(sys.argv)