@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.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +380 -0
- package/DELIVERY.md +10 -2
- package/README.md +43 -4
- package/VERIFY.md +6 -0
- package/docs/setup-clean-host.md +11 -1
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
- package/mcp/entwurf-bridge/src/index.ts +41 -41
- package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
- package/package.json +4 -4
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/codex-declaration.js +612 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
- package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
- package/pi-extensions/lib/entwurf-facts.ts +14 -1
- package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
- package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
- package/pi-extensions/lib/entwurf-self-address.ts +33 -0
- package/pi-extensions/lib/fresh-call-composition.ts +493 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
- package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
- package/pi-extensions/lib/herdr-placement.ts +276 -0
- package/pi-extensions/lib/mux-fresh-call.ts +95 -301
- package/run.sh +396 -8
- package/scripts/check-codex-birth-hook.ts +187 -0
- package/scripts/check-entwurf-control-rpc.ts +138 -0
- package/scripts/check-entwurf-fact-provider.ts +4 -2
- package/scripts/check-entwurf-facts.ts +12 -6
- package/scripts/check-entwurf-peers-surface.ts +3 -1
- package/scripts/check-entwurf-self-address.ts +68 -0
- package/scripts/check-fresh-call-dispatch.ts +301 -0
- package/scripts/check-gate-qualification.ts +16 -4
- package/scripts/check-herdr-activation.ts +1293 -0
- package/scripts/check-herdr-fresh-call.ts +1250 -0
- package/scripts/check-herdr-placement.ts +383 -0
- package/scripts/check-herdr-plugin-build.ts +842 -0
- package/scripts/check-herdr-plugin-profile.ts +371 -0
- package/scripts/check-herdr-plugin.ts +523 -0
- package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
- package/scripts/check-herdr-sandbox.ts +456 -0
- package/scripts/check-herdr-supply.ts +184 -0
- package/scripts/check-mux-launcher-fence.ts +138 -0
- package/scripts/check-peer-facts.ts +331 -0
- package/scripts/check-release-gate-outcomes.ts +12 -0
- package/scripts/check-typing-call-fence.ts +296 -0
- package/scripts/codex-birth-doctor.sh +159 -16
- package/scripts/codex-birth-install.sh +178 -72
- package/scripts/codex-birth-uninstall.sh +162 -7
- package/scripts/fixtures/herdr-supply.json +29 -0
- package/scripts/herdr-activation.mjs +536 -0
- package/scripts/herdr-plugin-activate.mjs +270 -0
- package/scripts/herdr-plugin-deactivate.mjs +193 -0
- package/scripts/herdr-runtime.mjs +1203 -0
- package/scripts/install-herdr-ci.sh +96 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
- package/scripts/lib/tree-digest.ts +97 -0
- package/scripts/meta-bridge-install.sh +19 -2
- package/scripts/meta-bridge-state.py +167 -11
- package/scripts/mutants/codex-caller-seat.json +6 -8
- package/scripts/mutants/codex-native.json +195 -29
- package/scripts/mutants/control-socket-disconnect.json +57 -0
- package/scripts/mutants/fresh-call-dispatch.json +154 -0
- package/scripts/mutants/herdr-activation.json +305 -0
- package/scripts/mutants/herdr-fresh-call.json +448 -0
- package/scripts/mutants/herdr-placement.json +161 -0
- package/scripts/mutants/herdr-plugin-build.json +160 -0
- package/scripts/mutants/herdr-plugin-profile.json +202 -0
- package/scripts/mutants/herdr-plugin.json +133 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
- package/scripts/mutants/herdr-supply.json +133 -0
- package/scripts/mutants/meta-hook-session-switch.json +2 -2
- package/scripts/mutants/mux-fresh-call.json +104 -17
- package/scripts/mutants/mux-launcher-fence.json +13 -0
- package/scripts/mutants/omp-fresh.json +4 -4
- package/scripts/mutants/peer-facts.json +98 -0
- package/scripts/mutants/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/peer-facts.ts +120 -0
- package/scripts/register-pi-package.py +8 -0
- package/scripts/register-pi-provider.py +88 -12
- package/scripts/smoke-codex-birth.sh +281 -12
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- 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
|
-
|
|
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 ==
|
|
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
|
|
286
|
-
newval = {"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} = {
|
|
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":
|
|
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(
|
|
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/
|
|
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(
|
|
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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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" "
|
|
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 '
|
|
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
|
|
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
|
|
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"
|