@junghanacs/entwurf 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +380 -0
  3. package/DELIVERY.md +10 -2
  4. package/README.md +43 -4
  5. package/VERIFY.md +6 -0
  6. package/docs/setup-clean-host.md +11 -1
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
  21. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  22. package/mcp/entwurf-bridge/src/index.ts +41 -41
  23. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  24. package/package.json +4 -4
  25. package/pi-extensions/entwurf-control.ts +91 -52
  26. package/pi-extensions/lib/codex-declaration.js +612 -0
  27. package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
  28. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  29. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  30. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  31. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  32. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  33. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  35. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  36. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  37. package/pi-extensions/lib/herdr-placement.ts +276 -0
  38. package/pi-extensions/lib/mux-fresh-call.ts +95 -301
  39. package/run.sh +396 -8
  40. package/scripts/check-codex-birth-hook.ts +187 -0
  41. package/scripts/check-entwurf-control-rpc.ts +138 -0
  42. package/scripts/check-entwurf-fact-provider.ts +4 -2
  43. package/scripts/check-entwurf-facts.ts +12 -6
  44. package/scripts/check-entwurf-peers-surface.ts +3 -1
  45. package/scripts/check-entwurf-self-address.ts +68 -0
  46. package/scripts/check-fresh-call-dispatch.ts +301 -0
  47. package/scripts/check-gate-qualification.ts +16 -4
  48. package/scripts/check-herdr-activation.ts +1293 -0
  49. package/scripts/check-herdr-fresh-call.ts +1250 -0
  50. package/scripts/check-herdr-placement.ts +383 -0
  51. package/scripts/check-herdr-plugin-build.ts +842 -0
  52. package/scripts/check-herdr-plugin-profile.ts +371 -0
  53. package/scripts/check-herdr-plugin.ts +523 -0
  54. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  55. package/scripts/check-herdr-sandbox.ts +456 -0
  56. package/scripts/check-herdr-supply.ts +184 -0
  57. package/scripts/check-mux-launcher-fence.ts +138 -0
  58. package/scripts/check-peer-facts.ts +331 -0
  59. package/scripts/check-release-gate-outcomes.ts +12 -0
  60. package/scripts/check-typing-call-fence.ts +296 -0
  61. package/scripts/codex-birth-doctor.sh +159 -16
  62. package/scripts/codex-birth-install.sh +178 -72
  63. package/scripts/codex-birth-uninstall.sh +162 -7
  64. package/scripts/fixtures/herdr-supply.json +29 -0
  65. package/scripts/herdr-activation.mjs +536 -0
  66. package/scripts/herdr-plugin-activate.mjs +270 -0
  67. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  68. package/scripts/herdr-runtime.mjs +1203 -0
  69. package/scripts/install-herdr-ci.sh +96 -0
  70. package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
  71. package/scripts/lib/tree-digest.ts +97 -0
  72. package/scripts/meta-bridge-install.sh +19 -2
  73. package/scripts/meta-bridge-state.py +167 -11
  74. package/scripts/mutants/codex-caller-seat.json +6 -8
  75. package/scripts/mutants/codex-native.json +195 -29
  76. package/scripts/mutants/control-socket-disconnect.json +57 -0
  77. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  78. package/scripts/mutants/herdr-activation.json +305 -0
  79. package/scripts/mutants/herdr-fresh-call.json +448 -0
  80. package/scripts/mutants/herdr-placement.json +161 -0
  81. package/scripts/mutants/herdr-plugin-build.json +160 -0
  82. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  83. package/scripts/mutants/herdr-plugin.json +133 -0
  84. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  85. package/scripts/mutants/herdr-supply.json +133 -0
  86. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  87. package/scripts/mutants/mux-fresh-call.json +104 -17
  88. package/scripts/mutants/mux-launcher-fence.json +13 -0
  89. package/scripts/mutants/omp-fresh.json +4 -4
  90. package/scripts/mutants/peer-facts.json +98 -0
  91. package/scripts/mutants/self-address.json +33 -0
  92. package/scripts/mutants/typing-call-fence.json +17 -0
  93. package/scripts/peer-facts.ts +120 -0
  94. package/scripts/register-pi-package.py +8 -0
  95. package/scripts/register-pi-provider.py +88 -12
  96. package/scripts/smoke-codex-birth.sh +281 -12
  97. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  98. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
@@ -76,6 +76,14 @@ import sys
76
76
  # `install` — semantically a no-op, byte-wise a RED `pnpm check`. sys.path[0] already
77
77
  # holds this directory when the script is run by path (how run.sh and the gates invoke
78
78
  # it); the explicit insert keeps the import true under any other invocation form.
79
+ # The sibling import below would otherwise leave `scripts/__pycache__/pi_settings_io.*.pyc`
80
+ # behind — a write into the checkout this script was merely READ from. It is git-ignored, so
81
+ # `git status --porcelain` never shows it, and on a host where the cache already exists nothing
82
+ # changes; but in the gate-qualification snapshot the file is NEW, and the tree manifest that
83
+ # certifies "the runner never wrote the tree" reads bytes, not porcelain. Measured on the #116
84
+ # M3-b3 candidate: that one path was the whole of `IMPURE: treeClean=false porcelainClean=true`.
85
+ # Same guard, same reason as codex-mcp-config.py / codex-statusline-config.py.
86
+ sys.dont_write_bytecode = True
79
87
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
80
88
  from pi_settings_io import classify_installer_root, detect_indent, dumps, unchanged # noqa: E402
81
89
 
@@ -170,11 +178,49 @@ def _now() -> str:
170
178
  return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
171
179
 
172
180
 
173
- def _classify(existing_cmd, repo_dir: str) -> str:
181
+ # #116 M3-b2 — the ONE alternative managed command, and it is DERIVED, never supplied.
182
+ # A Herdr plugin activates on a host where nothing entwurf is on PATH, so the bare bin cannot
183
+ # resolve. The plugin mode therefore names the bridge by its absolute path under the certified
184
+ # stable active runtime — and that is the whole freedom it gets: the caller passes the runtime ROOT,
185
+ # this function appends the fixed suffix, and any other string is refused. There is deliberately no
186
+ # flag or environment variable that accepts an arbitrary command or path: that would be a generic
187
+ # "write whatever you like into the operator's pi settings" authority wearing a plugin's clothes.
188
+ PLUGIN_BIN_SUFFIX = os.path.join("node_modules", ".bin", SERVER_KEY)
189
+
190
+
191
+ def stable_active_root() -> str:
192
+ """The ONE runtime address, derived from this process's XDG/HOME exactly as the runtime owner
193
+ derives it (`scripts/herdr-runtime.mjs` resolveRuntimeLayout). A gate pins the two
194
+ implementations equal; nothing here reads a caller-supplied root as authority."""
195
+ data_home = os.environ.get("XDG_DATA_HOME") or os.path.join(os.path.expanduser("~"), ".local", "share")
196
+ return os.path.join(data_home, "entwurf", "herdr-plugin", "runtime", "active")
197
+
198
+
199
+ def plugin_command_for(runtime_root: str) -> str:
200
+ # The caller may NAME the root, but it is only accepted when it IS this host's stable root. An
201
+ # absolute path was never the contract — "the stable runtime" was — and a mode that takes any
202
+ # absolute directory is a way to point every pi session at an executable of the caller's choice.
203
+ if not isinstance(runtime_root, str) or not os.path.isabs(runtime_root):
204
+ _die(2, f"register-pi-provider: --plugin-runtime must be an ABSOLUTE stable runtime root, got {runtime_root!r}")
205
+ if runtime_root != os.path.normpath(runtime_root) or runtime_root.endswith(os.sep):
206
+ _die(2, f"register-pi-provider: --plugin-runtime must be a normalised path, got {runtime_root!r}")
207
+ expected = stable_active_root()
208
+ if runtime_root != expected:
209
+ _die(2, f"register-pi-provider: --plugin-runtime must be this host's stable runtime root {expected!r}, "
210
+ f"got {runtime_root!r} — an arbitrary absolute directory is not a plugin runtime.")
211
+ return os.path.join(runtime_root, PLUGIN_BIN_SUFFIX)
212
+
213
+
214
+ def _classify(existing_cmd, repo_dir: str, managed_command: str = BARE_COMMAND) -> str:
174
215
  if existing_cmd is None:
175
216
  return "absent"
176
- if existing_cmd == BARE_COMMAND:
217
+ if existing_cmd == managed_command:
177
218
  return "managed-current"
219
+ # The OTHER shape we own is adoptable, so bare↔plugin is a normalisation rather than a
220
+ # takeover. In DEFAULT mode this is exactly the historical set (the bare bin is
221
+ # managed-current above and never reaches here), so default bytes and verdicts are unchanged.
222
+ if managed_command != BARE_COMMAND and existing_cmd == BARE_COMMAND:
223
+ return "managed-legacy"
178
224
  if isinstance(existing_cmd, str) and (
179
225
  existing_cmd == f"{repo_dir}/mcp/{SERVER_KEY}/start.sh"
180
226
  or existing_cmd.endswith(f"/entwurf/mcp/{SERVER_KEY}/start.sh")
@@ -210,7 +256,8 @@ def _load_state_file(state_path: str) -> dict | None:
210
256
 
211
257
 
212
258
  def cmd_install(settings_path: str, repo_dir: str, scope: str, state_path: str, takeover: bool = False,
213
- preflight: bool = False) -> None:
259
+ preflight: bool = False, plugin_runtime: str | None = None) -> None:
260
+ managed_command = plugin_command_for(plugin_runtime) if plugin_runtime else BARE_COMMAND
214
261
  if os.path.islink(settings_path):
215
262
  target = os.readlink(settings_path)
216
263
  _die(3, f"register-pi-provider: refusing to adopt {settings_path} — it is a symlink to {target} "
@@ -254,7 +301,7 @@ def cmd_install(settings_path: str, repo_dir: str, scope: str, state_path: str,
254
301
  _prune_legacy(servers, repo_dir) # independent of entwurf-bridge ownership
255
302
  existing = servers.get(SERVER_KEY)
256
303
  existing_cmd = existing.get("command") if isinstance(existing, dict) else existing
257
- ownership = _classify(existing_cmd, repo_dir)
304
+ ownership = _classify(existing_cmd, repo_dir, managed_command)
258
305
 
259
306
  if preflight:
260
307
  # Read-only half of the atomic user-scope operation: the ownership decision
@@ -282,8 +329,8 @@ def cmd_install(settings_path: str, repo_dir: str, scope: str, state_path: str,
282
329
  sys.stdout.write(f"install: no change — {settings_path} left untouched (bytes and mtime stable)\n")
283
330
  return
284
331
 
285
- # absent / managed-current / managed-legacy → normalize to the bare stable bin.
286
- newval = {"command": BARE_COMMAND}
332
+ # absent / managed-current / managed-legacy → normalize to the managed command for this mode.
333
+ newval = {"command": managed_command}
287
334
  # preserve non-empty custom args if the operator set them; else default [].
288
335
  if isinstance(existing, dict) and existing.get("args") not in (None, []):
289
336
  newval["args"] = existing["args"]
@@ -292,7 +339,8 @@ def cmd_install(settings_path: str, repo_dir: str, scope: str, state_path: str,
292
339
  servers[SERVER_KEY] = newval
293
340
  wrote = _persist(settings_path, before, data, raw)
294
341
  sys.stdout.write(
295
- f"install: {ownership} → entwurfProvider.mcpServers.{SERVER_KEY} = {BARE_COMMAND} (bare stable bin)\n"
342
+ f"install: {ownership} → entwurfProvider.mcpServers.{SERVER_KEY} = {managed_command} "
343
+ f"({'stable plugin runtime' if plugin_runtime else 'bare stable bin'})\n"
296
344
  )
297
345
  # The desired value AND the legacy prune both already held: nothing to say to the
298
346
  # file. Reported so an operator (and the gate) can tell "already correct" from
@@ -317,7 +365,7 @@ def cmd_install(settings_path: str, repo_dir: str, scope: str, state_path: str,
317
365
  "managedSettingsPath": os.path.abspath(settings_path),
318
366
  "scope": "user",
319
367
  "key": f"entwurfProvider.mcpServers.{SERVER_KEY}",
320
- "command": BARE_COMMAND,
368
+ "command": managed_command,
321
369
  "ownership": ownership, # absent | managed-current | managed-legacy
322
370
  "installerRoot": repo_dir, # #86 C2: the root whose inverse may remove this key
323
371
  "preimage": existing, # raw prior value (audit only; NOT restored)
@@ -387,8 +435,23 @@ def cmd_remove(settings_path: str, repo_dir: str, scope: str, state_path: str, o
387
435
  before = _parse_settings(managed, raw) # dies 4 on corrupt — preflight and writer alike
388
436
  data = _parse_settings(managed, raw)
389
437
  provider, servers = _provider_servers(data, create=False)
438
+ # #116 M3-b2 — DRIFT IS NOT OURS TO DELETE. The user-scope inverse is admitted by the
439
+ # ownership record, not by the command string, so without this it would remove whatever now
440
+ # sits at our key. The state records the exact command we wrote (bare bin, or the absolute
441
+ # bridge under a stable plugin runtime); if the live value is PRESENT and different, someone
442
+ # replaced our key after we installed it and the honest move is to refuse by name rather
443
+ # than delete their override. An ABSENT key stays an idempotent no-op.
444
+ recorded_cmd = state.get("command")
445
+ live = servers.get(SERVER_KEY) if isinstance(servers, dict) else None
446
+ live_cmd = live.get("command") if isinstance(live, dict) else live
447
+ if isinstance(recorded_cmd, str) and live_cmd is not None and live_cmd != recorded_cmd:
448
+ _die(6, f"register-pi-provider: the user-scope {SERVER_KEY} command drifted since install — "
449
+ f"we wrote {recorded_cmd!r}, {managed} now holds {live_cmd!r}. That is somebody's override, "
450
+ "not our key; zero settings bytes written and the ownership state is left intact.")
390
451
  if preflight:
391
- sys.stdout.write("preflight: remove ok (owner verified; managed target bound and parseable)\n")
452
+ sys.stdout.write(
453
+ f"preflight: remove ok (owner verified; managed target bound and parseable; command={recorded_cmd!r})\n"
454
+ )
392
455
  return
393
456
  if os.path.exists(managed):
394
457
  # honest inverse: absent/managed-* → remove OUR key (a legacy repo path is NOT
@@ -442,6 +505,7 @@ def cmd_remove(settings_path: str, repo_dir: str, scope: str, state_path: str, o
442
505
 
443
506
 
444
507
  def _parse(argv: list):
508
+ plugin_runtime = None
445
509
  # positional: settings_path repo_dir ; flags: --scope <s> [--state <p>] [--takeover] [--orphan-cleanup] [--preflight]
446
510
  pos, scope, state_path = [], None, ""
447
511
  takeover, orphan, preflight = False, False, False
@@ -460,17 +524,24 @@ def _parse(argv: list):
460
524
  orphan = True
461
525
  elif a == "--preflight":
462
526
  preflight = True
527
+ elif a == "--plugin-runtime":
528
+ i += 1
529
+ # A flag with no value must NOT fall through to bare mode: the caller asked for the
530
+ # plugin runtime and would otherwise be told, silently, that it got it.
531
+ if i >= len(argv) or not argv[i]:
532
+ _die(5, "register-pi-provider.py: --plugin-runtime requires a value")
533
+ plugin_runtime = argv[i]
463
534
  else:
464
535
  pos.append(a)
465
536
  i += 1
466
- return pos, scope, state_path, takeover, orphan, preflight
537
+ return pos, scope, state_path, takeover, orphan, preflight, plugin_runtime
467
538
 
468
539
 
469
540
  def main(argv: list) -> None:
470
541
  if len(argv) < 2:
471
542
  _die(5, "usage: register-pi-provider.py <install|remove> <settings_path> <repo_dir> --scope <user|project> [--state <path>]")
472
543
  sub = argv[1]
473
- pos, scope, state_path, takeover, orphan, preflight = _parse(argv[2:])
544
+ pos, scope, state_path, takeover, orphan, preflight, plugin_runtime = _parse(argv[2:])
474
545
  if sub not in ("install", "remove"):
475
546
  _die(5, f"register-pi-provider.py: unknown subcommand {sub!r}")
476
547
  if len(pos) != 2:
@@ -486,8 +557,13 @@ def main(argv: list) -> None:
486
557
  _die(5, "register-pi-provider.py: --takeover is an install action")
487
558
  if orphan and sub != "remove":
488
559
  _die(5, "register-pi-provider.py: --orphan-cleanup is a remove action")
560
+ # The plugin mode is an INSTALL-side USER-scope decision only. `remove` needs no mode: it
561
+ # recognises every shape we own through the same classifier, so an inverse never has to be told
562
+ # which one it is undoing — and a mode flag on remove would be a way to aim a deletion.
563
+ if plugin_runtime is not None and (sub != "install" or scope != "user"):
564
+ _die(5, "register-pi-provider.py: --plugin-runtime is a user-scope install action")
489
565
  if sub == "install":
490
- cmd_install(settings_path, repo_dir, scope, state_path, takeover, preflight)
566
+ cmd_install(settings_path, repo_dir, scope, state_path, takeover, preflight, plugin_runtime)
491
567
  else:
492
568
  cmd_remove(settings_path, repo_dir, scope, state_path, orphan, preflight)
493
569
 
@@ -7,6 +7,10 @@
7
7
  # 1. the published declaration is the EXACT trust identity (matcher-less, type/command/
8
8
  # timeout, quoted fixed launcher path) — because that identity is what an operator
9
9
  # approves once, and any drift in it costs a second approval;
10
+ # 1b. entwurf owns ONLY that declaration inside a file it SHARES (#117): a neighbouring
11
+ # integration's `SessionStart` group in either ordering leaves install/doctor green, is
12
+ # reported as foreign, is never certified, survives install and inverse byte-for-byte, and
13
+ # moves no verdict of ours when it is edited;
10
14
  # 2. config.toml is neither read nor written, so the vendor's [hooks.state] and entwurf's
11
15
  # own two user atoms survive install AND inverse byte-for-byte;
12
16
  # 3. a foreign or edited hooks.json is a ZERO-WRITE refusal, never an adoption — this unit
@@ -76,9 +80,13 @@ want "the launcher is published and executable" "[ -f '$LAUNCHER' ] && [ -x '$LA
76
80
  want "the closure is published whole" "[ -f '$HELPER/meta-bridge-hook-codex.ts' ] && [ -f '$HELPER/lib/meta-session.ts' ] && [ -f '$HELPER/lib/native-push/codex-ws-client.ts' ] && [ -f '$HELPER/lib/session-id.js' ] && [ -f '$HELPER/entwurf-capabilities.json' ]"
77
81
  want "the state is a regular non-symlink file" "[ -f '$STATE' ] && [ ! -L '$STATE' ]"
78
82
  want "the state is bound to the fixed paths and finished publishing" \
79
- "node -e 'const s=require(process.argv[1]); if(s.schema!==\"codex-birth-install-state/v1\")throw 0; if(s.status!==\"installed\")throw 0; if(s.hooksFile!==process.argv[2])throw 0; if(s.helperDir!==process.argv[3])throw 0;' '$STATE' '$HOOKS' '$HELPER'"
83
+ "node -e 'const s=require(process.argv[1]); if(s.schema!==\"codex-birth-install-state/v2\")throw 0; if(s.status!==\"installed\")throw 0; if(s.hooksFile!==process.argv[2])throw 0; if(s.helperDir!==process.argv[3])throw 0;' '$STATE' '$HOOKS' '$HELPER'"
84
+ # The receipt is a DECLARATION digest, not a file digest. A state carrying a whole-file
85
+ # `hooksSha256` would be claiming authority over bytes a neighbour owns — the #117 defect itself.
86
+ want "[QK:CODEX-BIRTH-STATE-CERTIFIES-DECLARATION] the state records a declaration receipt and NO whole-file digest" \
87
+ "node -e 'const s=require(process.argv[1]); const hex=v=>/^[0-9a-f]{64}\$/.test(v); if(\"hooksSha256\" in s)throw new Error(\"the state still claims the whole file\"); if(s.declaration.event!==\"SessionStart\")throw 0; if(typeof s.declaration.command!==\"string\")throw 0; if(!hex(s.declaration.sha256))throw 0;' '$STATE'"
80
88
  want "the state records a digest for every published byte" \
81
- "node -e 'const s=require(process.argv[1]); const hex=v=>/^[0-9a-f]{64}\$/.test(v); if(!hex(s.hooksSha256))throw 0; if(s.helperFiles.length!==6)throw 0; for(const f of s.helperFiles){if(!hex(f.sha256))throw 0;}' '$STATE'"
89
+ "node -e 'const s=require(process.argv[1]); const hex=v=>/^[0-9a-f]{64}\$/.test(v); if(s.helperFiles.length!==6)throw 0; for(const f of s.helperFiles){if(!hex(f.sha256))throw 0;}' '$STATE'"
82
90
 
83
91
  # The trust identity, asserted field by field. This is the cell that costs a second operator
84
92
  # approval if it ever drifts, so it is checked literally rather than by shape.
@@ -153,7 +161,7 @@ MT1="$(stat -c %Y "$HOOKS")"; sleep 1.1
153
161
  "$INSTALL" >"$SB/out" 2>&1 || die "reinstall failed: $(cat "$SB/out")"
154
162
  MT2="$(stat -c %Y "$HOOKS")"
155
163
  want "reinstall does not rewrite the declaration" "[ '$MT1' = '$MT2' ]"
156
- want "reinstall adopts its own published declaration by digest" "grep -q 'adopting the hooks.json this unit published' '$SB/out'"
164
+ want "reinstall recognises its own declaration by normalized digest and rewrites nothing" "grep -q 'NOT REWRITTEN, not one byte' '$SB/out'"
157
165
  cmp -s "$SB/config.before.toml" "$CFG" && ok "reinstall still left config.toml byte-identical" || die "reinstall modified config.toml"
158
166
 
159
167
  ###############################################################################
@@ -198,31 +206,180 @@ ok "the unit axis is green again after the absent member was republished"
198
206
  # refusals — a foreign file, an edited file, a symlink; each ZERO-WRITE
199
207
  ###############################################################################
200
208
  "$UNINSTALL" >/dev/null 2>&1 || die "clean uninstall before the refusal cells failed"
201
- printf '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"/opt/operator/own-hook.sh","timeout":10}]}]}}\n' > "$HOOKS"
202
- cp "$HOOKS" "$SB/foreign-hooks.json"
203
- refuses "[QK:CODEX-BIRTH-FOREIGN-HOOKS-REFUSED] a foreign hooks.json is refused, not adopted" "already exists and this host has NO entwurf ownership state" "$INSTALL"
204
- cmp -s "$SB/foreign-hooks.json" "$HOOKS" && ok "the foreign declaration survives the refusal byte-for-byte" || die "the foreign hooks.json was overwritten"
205
- want "the foreign refusal wrote no state and no helper" "[ ! -e '$STATE' ] && [ ! -e '$HELPER' ]"
209
+
210
+ ###############################################################################
211
+ # COEXISTENCE (#117) the neighbour is joined, never adopted and never refused
212
+ #
213
+ # Until 0.22.0 this unit owned hooks.json WHOLE: a file it had not written was a zero-write
214
+ # refusal, and its whole-file digest went red the moment anyone else appended. That is exactly
215
+ # what Herdr's official Codex integration does, and the vendor keeps running both declarations
216
+ # because `[source]` discovery.rs:664-665 keys trust per `<path>:<event>:<group>:<handler>`.
217
+ # These cells are the new contract: one group is ours, everything else is foreign bytes we read
218
+ # and never write.
219
+ ###############################################################################
220
+ FOREIGN_GROUP=' {
221
+ "hooks": [
222
+ {
223
+ "command": "bash '"'"'/sandbox/.codex/herdr-agent-state.sh'"'"' session",
224
+ "timeout": 10,
225
+ "type": "command"
226
+ }
227
+ ]
228
+ }'
229
+ neighbour_survives() { # <label>
230
+ printf '%s' "$FOREIGN_GROUP" > "$SB/foreign-block.txt"
231
+ grep -qF -f "$SB/foreign-block.txt" "$HOOKS" && ok "$1" || die "$1 (the foreign group's bytes changed)"
232
+ }
233
+ foreign_file() { # writes a hooks.json declaring ONLY the neighbour, in its own formatting AND
234
+ # its own mode — both are things this unit must carry over rather than normalise.
235
+ printf '{\n "hooks": {\n "SessionStart": [\n%s\n ]\n }\n}\n' "$FOREIGN_GROUP" > "$HOOKS"
236
+ chmod 0600 "$HOOKS"
237
+ }
238
+
239
+ # ORDER A — the neighbour is there first and entwurf joins it.
240
+ foreign_file
241
+ cp "$HOOKS" "$SB/foreign-only.json"
242
+ want "[QK:CODEX-BIRTH-JOINS-FOREIGN-FILE] a foreign hooks.json is JOINED by text splice, never adopted and never refused" \
243
+ "'$INSTALL' >'$SB/out' 2>&1 && grep -q 'appended entwurf' '$SB/out' && grep -q 'byte-for-byte' '$SB/out'"
244
+ neighbour_survives "the neighbour's declaration survives the install byte-for-byte"
245
+ want "[QK:CODEX-BIRTH-SHARED-FILE-MODE-KEPT] the shared file keeps the mode it was found with — a file we only add a group to is not one whose permissions are ours to reset" \
246
+ "[ \"\$(stat -c %a '$HOOKS')\" = '0600' ] || [ \"\$(stat -c %a '$HOOKS')\" = '600' ]"
247
+ want "entwurf's declaration was APPENDED, so the neighbour keeps index 0 and the trust receipt it already has" \
248
+ "node -e 'const s=JSON.parse(require(\"node:fs\").readFileSync(process.argv[1],\"utf8\")); const g=s.hooks.SessionStart; if(g.length!==2)throw new Error(\"groups \"+g.length); if(g[0].hooks[0].command.includes(\"entwurf\"))throw new Error(\"entwurf took index 0\"); if(g[1].hooks[0].command!==\"'\''\"+process.argv[2]+\"'\''\")throw new Error(\"ours is not at index 1\");' '$HOOKS' '$LAUNCHER'"
249
+ want "[QK:CODEX-BIRTH-DOCTOR-COEXISTS] the doctor certifies entwurf's own declaration at its MEASURED index and stays green beside a neighbour" \
250
+ "'$DOCTOR' --unit-only >'$SB/out' 2>&1 && grep -q \"entwurf's declaration is certified at .* group 1 handler 0\" '$SB/out'"
251
+ want "[QK:CODEX-BIRTH-DOCTOR-REPORTS-FOREIGN] the neighbour is reported as present-but-foreign, in its own section, and certified by nothing" \
252
+ "grep -q 'FOREIGN (what else declares' '$SB/out' && grep -q 'SessionStart group 0: bash' '$SB/out' && grep -q 'present-but-foreign' '$SB/out' && grep -q 'never overwritten, never absorbed' '$SB/out'"
253
+
254
+ # THE VENDOR RECEIPT IS READ AT OUR MEASURED INDEX, and this is the cell that would have caught
255
+ # the #117 false green: with the neighbour at index 0, a constant `:0:0` reads THEIR approval and
256
+ # reports a birth hook the vendor was never asked to run.
257
+ trust_receipt "$HOOKS:session_start:1:0"
258
+ want "[QK:CODEX-BIRTH-TRUST-INDEX-MEASURED] the full doctor reads the vendor receipt at entwurf's MEASURED index (1:0 here), never at the constant :0:0" \
259
+ "'$DOCTOR' >'$SB/out' 2>&1"
260
+ trust_receipt "$HOOKS:session_start:0:0"
261
+ "$DOCTOR" >"$SB/out" 2>&1 && die "the doctor read the NEIGHBOUR's receipt at :0:0 as entwurf's own approval"
262
+ want "a receipt at the neighbour's index is named as somebody else's approval, and the key looked for is ours" \
263
+ "grep -q \"NONE at this unit's key $HOOKS:session_start:1:0\" '$SB/out'"
264
+ cp "$SB/config.before.toml" "$CFG"
265
+
266
+ # A neighbour EDITED after our install moves nothing of ours — it is not ours to certify.
267
+ python3 -c 'import json,sys
268
+ p=sys.argv[1]; d=json.load(open(p))
269
+ d["hooks"]["SessionStart"][0]["hooks"][0]["timeout"]=99
270
+ open(p,"w").write(json.dumps(d,indent=2))' "$HOOKS"
271
+ want "[QK:CODEX-BIRTH-FOREIGN-EDIT-NEUTRAL] a neighbour's declaration edited after our install is still reported and still certified by nothing — our verdict is unmoved" \
272
+ "'$DOCTOR' --unit-only >'$SB/out' 2>&1 && grep -q \"entwurf's declaration is certified\" '$SB/out' && grep -q 'SessionStart group 0: bash' '$SB/out'"
273
+ "$INSTALL" >"$SB/out" 2>&1 || die "reinstall beside an edited neighbour failed: $(cat "$SB/out")"
274
+ want "a reinstall beside an edited neighbour does not rewrite hooks.json at all" "grep -q 'NOT REWRITTEN, not one byte' '$SB/out'"
275
+
276
+ # The inverse takes our group out and leaves the neighbour's bytes exactly where they were.
277
+ cp "$HOOKS" "$SB/before-inverse.json"
278
+ # The exit status rides INSIDE the claim: a splice aimed at the wrong group is caught by the
279
+ # post-condition and comes back as DRIFT, which is a nonzero exit — and a separate `|| die` above
280
+ # would swallow that with a line that cannot carry this claim's token.
281
+ INVERSE_RC=0
282
+ "$UNINSTALL" >"$SB/out" 2>&1 || INVERSE_RC=$?
283
+ want "[QK:CODEX-BIRTH-INVERSE-KEEPS-FOREIGN] the inverse removes ONLY entwurf's group, by splice, and keeps the shared file — a splice that landed anywhere else would be refused by its own post-condition and reported as drift instead" \
284
+ "[ \"$INVERSE_RC\" -eq 0 ] && [ -f '$HOOKS' ] && grep -q 'by text splice' '$SB/out' && ! grep -q 'codex-birth-launch.sh' '$HOOKS'"
285
+ want "the inverse left the neighbour as the file's only declaration" \
286
+ "node -e 'const s=JSON.parse(require(\"node:fs\").readFileSync(process.argv[1],\"utf8\")); if(s.hooks.SessionStart.length!==1)throw 0; if(!s.hooks.SessionStart[0].hooks[0].command.includes(\"herdr\"))throw 0;' '$HOOKS'"
287
+ want "the inverse removed entwurf's own description and left no entwurf prose behind" "! grep -q 'entwurf codex-birth' '$HOOKS'"
288
+ # The neighbour was re-indented by its own editor above; what must survive the SPLICE is every
289
+ # byte of the group as it stood immediately before the inverse ran.
290
+ node -e '
291
+ const fs = require("node:fs");
292
+ const before = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).hooks.SessionStart.filter((g) => !JSON.stringify(g).includes("codex-birth-launch"));
293
+ const after = JSON.parse(fs.readFileSync(process.argv[2], "utf8")).hooks.SessionStart;
294
+ if (JSON.stringify(before) !== JSON.stringify(after)) throw new Error("foreign groups changed across the inverse");
295
+ ' "$SB/before-inverse.json" "$HOOKS" && ok "[QK:CODEX-BIRTH-INVERSE-FOREIGN-EXACT] every foreign group is value-identical across the inverse" || die "the inverse changed a foreign group"
206
296
  rm -f "$HOOKS"
207
297
 
298
+ # ORDER B — entwurf is there first and the neighbour appends afterwards, re-serializing the
299
+ # WHOLE document on its way past (measured: Herdr does exactly this, oracle 2026-09-17).
300
+ "$INSTALL" >/dev/null 2>&1 || die "install before the order-B cell failed"
301
+ python3 -c 'import json,sys
302
+ p=sys.argv[1]; d=json.load(open(p))
303
+ g=d["hooks"]["SessionStart"][0]["hooks"][0]
304
+ d["hooks"]["SessionStart"][0]["hooks"][0]={"command":g["command"],"timeout":g["timeout"],"type":g["type"]}
305
+ d["hooks"]["SessionStart"].append({"hooks":[{"command":"bash \x27/sandbox/.codex/herdr-agent-state.sh\x27 session","timeout":10,"type":"command"}]})
306
+ open(p,"w").write(json.dumps(d,indent=2))' "$HOOKS"
307
+ BEFORE_SHA="$(sha256sum "$HOOKS" | cut -d" " -f1)"
308
+ want "[QK:CODEX-BIRTH-NORMALIZED-DIGEST] the certification is blind to key order and indentation a neighbour imposed — bytes changed, our declaration did not" \
309
+ "'$DOCTOR' --unit-only >'$SB/out' 2>&1 && grep -q \"entwurf's declaration is certified at .* group 0 handler 0\" '$SB/out' && grep -q 'NORMALIZED digest, not by file bytes' '$SB/out'"
310
+ "$INSTALL" >"$SB/out" 2>&1 || die "reinstall after a neighbour re-serialized the file failed: $(cat "$SB/out")"
311
+ want "that reinstall rewrote NOT ONE BYTE of the shared file" \
312
+ "grep -q 'NOT REWRITTEN, not one byte' '$SB/out' && [ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = '$BEFORE_SHA' ]"
313
+
314
+ # OUR OWN declaration edited is still a named refusal — the narrowing is about WHOSE bytes, not
315
+ # about being lenient with ours. SHAPE catches it first: `timeout` is part of the identity the
316
+ # operator approved, so a value the installer never writes is not a digest question at all.
317
+ python3 -c 'import json,sys
318
+ p=sys.argv[1]; d=json.load(open(p))
319
+ d["hooks"]["SessionStart"][0]["hooks"][0]["timeout"]=31
320
+ open(p,"w").write(json.dumps(d,indent=2))' "$HOOKS"
321
+ refuses "[QK:CODEX-BIRTH-OWN-DECLARATION-DRIFT] an edit to entwurf's OWN handler is refused by name" "declaration-shape-drifted" "$INSTALL"
322
+ "$DOCTOR" --unit-only >"$SB/out" 2>&1 && die "the doctor was green over an edited entwurf declaration"
323
+ want "the doctor names our own drifted declaration and what about it drifted" "grep -q 'declaration is not certifiable' '$SB/out' && grep -q 'timeout must be 30' '$SB/out'"
324
+ "$UNINSTALL" >"$SB/out" 2>&1 && die "the inverse removed an edited entwurf declaration"
325
+ want "the inverse leaves our own drifted declaration in place" "grep -q 'DRIFT' '$SB/out' && grep -q 'declaration-shape-drifted' '$SB/out' && [ -f '$HOOKS' ]"
326
+
327
+ # SHAPE and DIGEST are two different authorities and only one of them is the recorded receipt.
328
+ # Shape asks "is this the declaration these fixed paths produce"; the digest asks "is this the
329
+ # declaration the STATE recorded". Tampering with the receipt alone is the only way to separate
330
+ # them, and it must be red: a receipt anyone can retune certifies nothing.
331
+ rm -rf "$HOOKS" "$UNIT_ROOT"
332
+ "$INSTALL" >/dev/null 2>&1 || die "install before the receipt-binding cell failed"
333
+ python3 -c 'import json,sys
334
+ p=sys.argv[1]; s=json.load(open(p))
335
+ s["declaration"]["sha256"]="0"*64
336
+ open(p,"w").write(json.dumps(s,indent=2))' "$STATE"
337
+ want "[QK:CODEX-BIRTH-DECLARATION-RECEIPT-BINDS] the live declaration is compared to the RECORDED normalized digest, and a receipt that no longer names it is red" \
338
+ "! '$DOCTOR' --unit-only >'$SB/out' 2>&1 && grep -q 'declaration was EDITED after install' '$SB/out' && grep -q 'live normalized' '$SB/out' && grep -q 'recorded 0000' '$SB/out'"
339
+ "$INSTALL" >/dev/null 2>&1 || die "install should re-record the receipt over a tampered one"
340
+ "$DOCTOR" --unit-only >/dev/null 2>&1 || die "the unit axis should be green once the receipt is republished"
341
+ ok "install re-records the declaration receipt, and the unit axis is green again"
342
+
343
+ # ...and a SECOND copy of our declaration is a refusal too: the vendor would run the birth hook
344
+ # twice per session, and only one of those positions can carry the operator's receipt.
345
+ python3 -c 'import json,sys
346
+ p=sys.argv[1]; d=json.load(open(p)); g=d["hooks"]["SessionStart"]
347
+ g[0]["hooks"][0]["timeout"]=30
348
+ g.append(json.loads(json.dumps(g[0])))
349
+ open(p,"w").write(json.dumps(d,indent=2))' "$HOOKS"
350
+ refuses "[QK:CODEX-BIRTH-DECLARATION-DUPLICATED] our declaration present twice is refused by name, never first-match accepted" "declaration-duplicated" "$INSTALL"
351
+ "$DOCTOR" --unit-only >"$SB/out" 2>&1 && die "the doctor accepted a duplicated entwurf declaration"
352
+ want "the doctor names the duplication and both positions" "grep -q 'declaration-duplicated' '$SB/out' && grep -qE 'group [0-9]+ handler [0-9]+, group [0-9]+ handler [0-9]+' '$SB/out'"
353
+ rm -rf "$HOOKS" "$UNIT_ROOT"
354
+
355
+
208
356
  ln -s "$SB/foreign-hooks.json" "$HOOKS"
209
357
  refuses "a symlinked hooks.json is refused" "is a SYMLINK" "$INSTALL"
210
358
  want "the symlink refusal wrote no state" "[ ! -e '$STATE' ] && [ -L '$HOOKS' ]"
211
359
  rm -f "$HOOKS"
212
360
 
213
361
  "$INSTALL" >/dev/null 2>&1 || die "install before the edited-declaration cell failed"
362
+ # WHITESPACE IS NOT DRIFT ANY MORE, and that is a contract change worth pinning: a normalized
363
+ # digest is blind to formatting precisely so a neighbour's re-serialize cannot fake an edit.
214
364
  printf '\n' >> "$HOOKS"
365
+ want "[QK:CODEX-BIRTH-WHITESPACE-NOT-DRIFT] reformatting alone is not drift — the digest is over the declaration, not the bytes" \
366
+ "'$DOCTOR' --unit-only >/dev/null 2>&1"
367
+ # An edit to a FIELD of our handler still is.
368
+ python3 -c 'import json,sys
369
+ p=sys.argv[1]; d=json.load(open(p))
370
+ d["hooks"]["SessionStart"][0]["hooks"][0]["timeout"]=7
371
+ open(p,"w").write(json.dumps(d,indent=2))' "$HOOKS"
215
372
  cp "$HOOKS" "$SB/edited-hooks.json"
216
- refuses "an edited declaration is refused instead of republished" "was edited after install" "$INSTALL"
373
+ refuses "an edited declaration is refused instead of republished" "declaration-shape-drifted" "$INSTALL"
217
374
  cmp -s "$SB/edited-hooks.json" "$HOOKS" && ok "the edited declaration survives the refusal byte-for-byte" || die "the edited hooks.json was overwritten"
218
375
  "$DOCTOR" --unit-only >"$SB/out" 2>&1 && die "the unit axis should be red while the declaration is edited"
219
- want "the doctor names the edited declaration" "grep -q 'was EDITED after install' '$SB/out'"
376
+ want "the doctor names the edited declaration" "grep -q 'declaration is not certifiable' '$SB/out'"
220
377
 
221
378
  ###############################################################################
222
379
  # inverse — exact removal, and a refusal that leaves drift in place
223
380
  ###############################################################################
224
381
  "$UNINSTALL" >"$SB/out" 2>&1 && die "the inverse should refuse while the declaration is drifted"
225
- want "the inverse refuses the drifted declaration" "grep -q 'DRIFT: the declaration was edited after install' '$SB/out'"
382
+ want "the inverse refuses the drifted declaration" "grep -q 'DRIFT' '$SB/out' && grep -q 'declaration-shape-drifted' '$SB/out'"
226
383
  want "the drifted declaration is LEFT IN PLACE" "[ -f '$HOOKS' ]"
227
384
  want "the inverse keeps the state so a later run can still license the path" "[ -f '$STATE' ]"
228
385
  cmp -s "$SB/edited-hooks.json" "$HOOKS" && ok "the drifted declaration is byte-identical after the refused inverse" || die "the refused inverse changed the drifted file"
@@ -234,7 +391,7 @@ printf '# operator edit\n' >> "$LAUNCHER"
234
391
  "$UNINSTALL" >"$SB/out" 2>&1 && die "the inverse should refuse while the launcher is drifted"
235
392
  want "the inverse refuses a drifted helper member by digest" "grep -q 'DRIFT: helper member was edited after install' '$SB/out'"
236
393
  want "the drifted launcher is LEFT IN PLACE" "[ -f '$LAUNCHER' ]"
237
- want "the declaration it already removed is gone, and the state remains" "[ ! -e '$HOOKS' ] && [ -f '$STATE' ]"
394
+ want "[QK:CODEX-BIRTH-INVERSE-SOLE-OWNER] the declaration it already removed is GONE — the FILE arm deletes the file it was the sole declaration in — and the state remains, because a drifted helper is still owed a licence" "[ ! -e '$HOOKS' ] && [ -f '$STATE' ]"
238
395
 
239
396
  rm -f "$LAUNCHER"
240
397
  "$INSTALL" >/dev/null 2>&1 || die "reinstall before the exact-inverse cell failed"
@@ -249,6 +406,11 @@ cmp -s "$SB/config.before.toml" "$CFG" && ok "the full install/inverse cycle lef
249
406
  # empty unit on the strength of somebody else's approval — measured exactly that way once.
250
407
  "$DOCTOR" --unit-only >"$SB/out" 2>&1 && die "[QK:CODEX-BIRTH-DOCTOR-ABSENT-IS-RED] the unit axis was green with NOTHING installed"
251
408
  want "an absent unit is named as red, not as an informational note" "grep -q 'no Codex birth unit is installed here' '$SB/out'"
409
+ # ...and with nothing installed the FOREIGN axis says NOT READ, never "none": the UNIT axis never
410
+ # opened that file, and "none" would be this doctor asserting something about the operator's
411
+ # hooks.json it never looked at.
412
+ want "[QK:CODEX-BIRTH-FOREIGN-UNREAD-NOT-NONE] an unscanned FOREIGN axis reports NOT READ rather than claiming there are no neighbours" \
413
+ "grep -q 'NOT READ' '$SB/out' && ! grep -q 'none — entwurf is the only' '$SB/out'"
252
414
  trust_receipt "$HOOKS:session_start:0:0"
253
415
  "$DOCTOR" >"$SB/out" 2>&1 && die "a stale vendor receipt made an ABSENT unit green"
254
416
  want "a stale receipt cannot carry an absent unit" "grep -q 'no Codex birth unit is installed here' '$SB/out'"
@@ -314,6 +476,29 @@ python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); s["status"]="publis
314
476
  "$INSTALL" >/dev/null 2>&1 || die "an interrupted `publishing` state should be finishable, not refused"
315
477
  want "the finished publish records installed" "grep -q '\"status\": \"installed\"' '$STATE'"
316
478
 
479
+ # A v1 ownership receipt recorded a digest of the WHOLE hooks.json. Reading it leniently would
480
+ # certify bytes a neighbour owns, so every reader refuses it by name — and the installer is the
481
+ # one forward path, superseding it without removing anything or rewriting a foreign byte.
482
+ rm -rf "$HOOKS" "$UNIT_ROOT"
483
+ "$INSTALL" >/dev/null 2>&1 || die "install before the v1-supersede cell failed"
484
+ python3 -c 'import json,sys
485
+ p=sys.argv[1]; s=json.load(open(p))
486
+ s["schema"]="codex-birth-install-state/v1"
487
+ s["hooksSha256"]="1"*64
488
+ del s["declaration"]
489
+ open(p,"w").write(json.dumps(s,indent=2))' "$STATE"
490
+ want "[QK:CODEX-BIRTH-STATE-V1-REFUSED] the doctor refuses a v1 receipt by name and names the one forward path" \
491
+ "! '$DOCTOR' --unit-only >'$SB/out' 2>&1 && grep -q 'codex-birth-install-state/v1' '$SB/out' && grep -q 'WHOLE hooks.json' '$SB/out' && grep -q 'install-codex-birth' '$SB/out'"
492
+ "$UNINSTALL" >"$SB/out" 2>&1 && die "the inverse removed things on the word of a v1 receipt"
493
+ want "the inverse refuses a v1 receipt and removes NOTHING" \
494
+ "grep -q 'codex-birth-install-state/v2' '$SB/out' && [ -f '$HOOKS' ] && [ -f '$LAUNCHER' ] && [ -f '$STATE' ]"
495
+ HOOKS_BEFORE_V1="$(sha256sum "$HOOKS" | cut -d' ' -f1)"
496
+ "$INSTALL" >"$SB/out" 2>&1 || die "the installer should supersede a v1 receipt: $(cat "$SB/out")"
497
+ want "the installer supersedes v1 forward, says so, and rewrites not one byte of hooks.json" \
498
+ "grep -q 'superseding a v1 ownership receipt' '$SB/out' && grep -q 'NOT REWRITTEN, not one byte' '$SB/out' && [ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = \"$HOOKS_BEFORE_V1\" ]"
499
+ "$DOCTOR" --unit-only >/dev/null 2>&1 || die "the unit axis should be green once the receipt is v2"
500
+ ok "the unit axis is green once the receipt has been superseded to v2"
501
+
317
502
  # The directory holding the receipt carries the receipt's authority. Children untouched: what
318
503
  # changed is only who may replace the inventory every digest is compared against.
319
504
  chmod 0777 "$UNIT_ROOT"
@@ -330,6 +515,90 @@ chmod 0755 "$UNIT_ROOT"
330
515
  ok "the unit axis is green again once the root is no longer writable"
331
516
  "$UNINSTALL" >/dev/null 2>&1 || die "cleanup uninstall after the parent-authority cell failed"
332
517
 
518
+ # ── the FILE verdict has to EARN its delete (sol B2, 2026-09-18) ─────────────────────────────
519
+ # The counterexample, derived from the source rather than imagined: a hooks.json that existed
520
+ # BEFORE this unit, carrying a foreign top-level `description` and an EMPTY SessionStart array.
521
+ # Our install appends one group, so afterwards there is no foreign GROUP, one event, and one
522
+ # declaration — which is exactly what the old FILE test asked. It deleted the whole file, and the
523
+ # neighbour's description with it, while the receipt had recorded `hooksExistedBefore: true` the
524
+ # entire time and nothing read it.
525
+ "$UNINSTALL" >/dev/null 2>&1 || true
526
+ printf '{\n "description": "a neighbour wrote this",\n "note": "and this",\n "hooks": {\n "SessionStart": []\n }\n}\n' > "$HOOKS"
527
+ chmod 0600 "$HOOKS"
528
+ PRE_EXISTING_SHA="$(sha256sum "$HOOKS" | cut -d' ' -f1)"
529
+ "$INSTALL" >/dev/null 2>&1 || die "install into a pre-existing empty-declaration file failed"
530
+ "$UNINSTALL" >"$SB/out" 2>&1 || die "uninstall after that install failed: $(cat "$SB/out")"
531
+ want "[QK:CODEX-BIRTH-INVERSE-KEEPS-PRE-EXISTING-FILE] a file that existed BEFORE this unit survives the inverse whole — our group comes out by splice and the neighbour's top-level bytes come back byte-for-byte, because hooksExistedBefore is a removal authority and not a decoration" \
532
+ "[ -f '$HOOKS' ] && [ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = \"$PRE_EXISTING_SHA\" ] && grep -q 'by text splice' '$SB/out'"
533
+ # And the delete arm still exists for the case it was written for: a file this unit created,
534
+ # holding nothing but its own description and its own group.
535
+ rm -f "$HOOKS"
536
+ "$INSTALL" >/dev/null 2>&1 || die "install into a host with no hooks.json failed"
537
+ "$UNINSTALL" >"$SB/out" 2>&1 || die "uninstall of a file we created failed: $(cat "$SB/out")"
538
+ want "a hooks.json this unit CREATED, holding only its own description and group, is still removed whole — the narrowing above added proofs to that delete, it did not retire it" \
539
+ "[ ! -e '$HOOKS' ] && grep -q 'entwurf was its only declaration' '$SB/out'"
540
+
541
+ # ── the SHARED file's own ownership, decided ONCE for four surfaces (sol B3, 2026-09-18) ─────
542
+ # The split this closes: install and uninstall asked only symlink-and-regular, the doctor asked
543
+ # NOTHING about who owned hooks.json, and only the fresh-call preflight refused a foreign uid or a
544
+ # group-writable mode. So a host could install clean, read GREEN, and have every Codex launch
545
+ # refuse it as `codex-birth-unit-missing` — three surfaces saying yes about bytes the fourth said
546
+ # no about. All four now decide with `classifyOwnedPath` in the shared declaration leaf.
547
+ #
548
+ # We still never chmod what we share. The cells below prove the mode we found is the mode left
549
+ # behind: an unsafe shared file is a zero-write REFUSAL, not a file we normalise on the way past.
550
+ "$INSTALL" >/dev/null 2>&1 || die "install before the shared-ownership cells failed"
551
+ HOOKS_BEFORE_UNSAFE="$(sha256sum "$HOOKS" | cut -d' ' -f1)"
552
+ chmod 0666 "$HOOKS"
553
+ refuses "[QK:CODEX-BIRTH-SHARED-FILE-OWNERSHIP] install refuses a hooks.json anyone else can rewrite — a receipt cannot bind bytes somebody else controls" \
554
+ "group/world-writable" "$INSTALL"
555
+ want "the refused install neither repaired the mode nor wrote a byte" \
556
+ "[ \"\$(stat -c %a '$HOOKS')\" = '666' ] && [ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = \"$HOOKS_BEFORE_UNSAFE\" ]"
557
+ # ONE assertion, because the exit code and the wording are one claim: a doctor that stayed green
558
+ # would fail a separate negative line first, and that line could not carry the token (a manifest
559
+ # may name its QK exactly once per gate source), so the mutant would die unattributable.
560
+ DOCTOR_RC=0
561
+ "$DOCTOR" --unit-only >"$SB/out" 2>&1 || DOCTOR_RC=$?
562
+ want "[QK:CODEX-BIRTH-DOCTOR-SHARED-OWNERSHIP] the doctor asks the same ownership question BEFORE it measures any digest — a digest taken in a file somebody else can rewrite certifies nothing, and a green here would contradict the launcher" \
563
+ "[ \"$DOCTOR_RC\" -ne 0 ] && grep -q 'group/world-writable' '$SB/out' && grep -q 'every Codex fresh call refuses this host' '$SB/out'"
564
+ "$UNINSTALL" >"$SB/out" 2>&1 || true # drift is a nonzero exit; the WORDS and the bytes are the claim
565
+ want "[QK:CODEX-BIRTH-INVERSE-SHARED-OWNERSHIP] the inverse asks it too and leaves an unsafe shared file exactly as found, counted as DRIFT — it used to rewrite a file it could not bind" \
566
+ "grep -q 'DRIFT: the declaration file writable-by-others' '$SB/out' && [ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = \"$HOOKS_BEFORE_UNSAFE\" ]"
567
+ chmod 0600 "$HOOKS"
568
+ # That inverse removed the closure it COULD account for and left the shared file alone, so the
569
+ # way back to green is an install — which is itself the claim: the refusal was about this file's
570
+ # ownership and nothing else, and it lifts the moment the ownership does.
571
+ "$INSTALL" >/dev/null 2>&1 || die "install should succeed again once the shared file is no longer writable by others"
572
+ "$DOCTOR" --unit-only >/dev/null 2>&1 || die "the unit axis should be green again once the shared file is no longer writable by others"
573
+ ok "all four surfaces agree again once the shared file is safe"
574
+ ln -sf "$SB/foreign-only.json" "$SB/hooks-link.json"
575
+ refuses "a symlinked hooks.json is refused with its OWN word — the repair is not a mode change, it is a different file" \
576
+ "SYMLINK" env CODEX_HOME="$CODEX_HOME" HOOKS_LINK=1 bash -c "cp '$HOOKS' '$SB/hooks-real.json'; rm '$HOOKS'; ln -s '$SB/hooks-real.json' '$HOOKS'; '$INSTALL'"
577
+ rm -f "$HOOKS"; cp "$SB/hooks-real.json" "$HOOKS"; chmod 0600 "$HOOKS"
578
+ "$UNINSTALL" >/dev/null 2>&1 || die "cleanup uninstall after the shared-ownership cells failed"
579
+
580
+ # ── the ORPHAN verdict: our bytes with no receipt behind them (glm #2) ────────────────────────
581
+ # `[glm 감사 2026-09-18]` this branch had no assertion and no mutant at all. It is the half-removed
582
+ # state — a declaration commanding OUR launcher, or a closure directory, with the receipt that
583
+ # licenses removing them gone — and the doctor must go RED rather than fall through to the
584
+ # "nothing is installed here" arm, which reads as a clean host and invites a reinstall over bytes
585
+ # the inverse will then refuse.
586
+ "$INSTALL" >/dev/null 2>&1 || die "install before the orphan cell failed"
587
+ ORPHAN_HOOKS_SHA="$(sha256sum "$HOOKS" | cut -d' ' -f1)"
588
+ mv "$STATE" "$SB/state-parked.json"
589
+ # ONE assertion, because red-for-the-wrong-reason is the failure mode here: with the orphan test
590
+ # removed the doctor still exits nonzero, down the "nothing is installed here" arm, and a cell that
591
+ # only checked the exit code would call that a kill. The WORDS are the claim.
592
+ ORPHAN_RC=0
593
+ "$DOCTOR" --unit-only >"$SB/out" 2>&1 || ORPHAN_RC=$?
594
+ want "[QK:CODEX-BIRTH-DOCTOR-ORPHAN-RED] a declaration or closure with no receipt behind it is RED and NAMED as not provably ours — never the clean-host arm, which reads as nothing installed and invites a reinstall over bytes the inverse will then refuse" \
595
+ "[ \"$ORPHAN_RC\" -ne 0 ] && grep -q 'not provably ours' '$SB/out' && ! grep -q 'no Codex birth unit is installed here' '$SB/out'"
596
+ want "the orphan doctor is READ-ONLY — it names those bytes and repairs nothing" \
597
+ "[ \"\$(sha256sum '$HOOKS' | cut -d' ' -f1)\" = \"$ORPHAN_HOOKS_SHA\" ] && [ ! -e '$STATE' ]"
598
+ mv "$SB/state-parked.json" "$STATE"
599
+ "$DOCTOR" --unit-only >/dev/null 2>&1 || die "the unit axis should be green again once the receipt is back"
600
+ "$UNINSTALL" >/dev/null 2>&1 || die "cleanup uninstall after the orphan cell failed"
601
+
333
602
  # A relative root would make "the fixed launcher path" depend on the caller's cwd — and that
334
603
  # path is exactly what the vendor keys its trust receipt to.
335
604
  ( cd "$SB" && CODEX_HOME="relative/.codex" "$INSTALL" >"$SB/out" 2>&1 ) && die "[QK:CODEX-BIRTH-REFUSES-RELATIVE-ROOT] a relative CODEX_HOME was accepted"