@junghanacs/entwurf 0.22.0 → 0.23.1
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 +4 -1
- package/BASELINE.md +1 -0
- package/CHANGELOG.md +514 -0
- package/DELIVERY.md +10 -2
- package/README.md +43 -4
- package/VERIFY.md +47 -0
- package/docs/acp-backend-rail.md +96 -17
- 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/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
- 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/entwurf-v2-contract.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
- 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 +9 -8
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +4 -4
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/acp/tool-surface.ts +2 -1
- 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/entwurf-v2-contract.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
- 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 +588 -204
- package/scripts/check-acp-usage-accounting.ts +9 -9
- 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-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 +17 -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-pi-launch.ts +358 -0
- package/scripts/check-release-gate-outcomes.ts +19 -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/inventory-verification-surface.ts +86 -10
- 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/pi-launch.json +113 -0
- package/scripts/mutants/resume-args.json +6 -6
- package/scripts/mutants/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/mutants/v2-surface.json +2 -2
- package/scripts/mutants/v2-visible-resume.json +17 -17
- 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-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-codex-birth.sh +281 -12
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
- package/scripts/smoke-herdr-raw-install-live.sh +849 -0
- package/scripts/tsconfig.json +5 -0
- package/scripts/check-entwurf-facts.ts +0 -385
- package/scripts/check-entwurf-resume-args.ts +0 -122
- package/scripts/check-entwurf-v2-decider.ts +0 -657
- package/scripts/check-entwurf-v2-lock.ts +0 -371
- package/scripts/check-entwurf-v2-mailbox.ts +0 -261
- package/scripts/check-entwurf-v2-matrix.ts +0 -401
- package/scripts/check-entwurf-v2-native-push.ts +0 -221
- package/scripts/check-entwurf-v2-release.ts +0 -151
- package/scripts/check-entwurf-v2-runner.ts +0 -448
- package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
- package/scripts/check-entwurf-v2-send.ts +0 -458
- package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
- package/scripts/check-socket-discovery.ts +0 -401
|
@@ -21,6 +21,9 @@ MARKETPLACE = "meta-bridge-local"
|
|
|
21
21
|
PLUGIN_REF = f"{PLUGIN}@{MARKETPLACE}"
|
|
22
22
|
STATE_VERSION = 1
|
|
23
23
|
OWNER = "entwurf meta-bridge"
|
|
24
|
+
# The only suffix an assembled marketplace path may end in — the inverse acts on that address, and
|
|
25
|
+
# the doctor already refuses anything else, so the preflight uses the SAME shape (not a looser one).
|
|
26
|
+
ASSEMBLED_SUFFIX = "/entwurf/meta-bridge/.assembled"
|
|
24
27
|
|
|
25
28
|
PERMISSION_ALLOW = [
|
|
26
29
|
"Bash",
|
|
@@ -304,6 +307,44 @@ def is_installed_package(repo: Path) -> bool:
|
|
|
304
307
|
return parts[-3:] == ("node_modules", "@junghanacs", "entwurf")
|
|
305
308
|
|
|
306
309
|
|
|
310
|
+
# #116 M3-b2 — the explicit plugin mode. A Herdr plugin activates on a host where nothing entwurf is
|
|
311
|
+
# on PATH, so the bare bin shim cannot resolve. In that mode the commands are DERIVED from the
|
|
312
|
+
# certified stable active runtime root: the caller passes the root, these helpers append the fixed
|
|
313
|
+
# suffixes, and nothing accepts an arbitrary command. The default installed branch below is
|
|
314
|
+
# untouched — mode is an explicit input, never a path heuristic, because a stable-runtime install
|
|
315
|
+
# lives at `.../active/node_modules/@junghanacs/entwurf` and already satisfies every layout test.
|
|
316
|
+
PLUGIN_RUNTIME: Path | None = None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def stable_active_root() -> Path:
|
|
320
|
+
"""The ONE runtime address, derived from this process's XDG/HOME exactly as the runtime owner
|
|
321
|
+
derives it (`scripts/herdr-runtime.mjs` resolveRuntimeLayout). A gate pins the two equal."""
|
|
322
|
+
data_home = os.environ.get("XDG_DATA_HOME") or os.path.join(os.path.expanduser("~"), ".local", "share")
|
|
323
|
+
return Path(data_home) / "entwurf" / "herdr-plugin" / "runtime" / "active"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def set_plugin_runtime(root: Path | None) -> None:
|
|
327
|
+
global PLUGIN_RUNTIME
|
|
328
|
+
if root is None:
|
|
329
|
+
PLUGIN_RUNTIME = None
|
|
330
|
+
return
|
|
331
|
+
if not root.is_absolute():
|
|
332
|
+
die(f"--plugin-runtime must be an ABSOLUTE stable runtime root, got {root}")
|
|
333
|
+
normalised = Path(os.path.normpath(str(root)))
|
|
334
|
+
# Named, not chosen: the caller may say which root, and it is accepted only when it IS this
|
|
335
|
+
# host's stable one. Any absolute directory would let a mode flag point Claude's MCP command at
|
|
336
|
+
# an executable of the caller's choosing.
|
|
337
|
+
expected = stable_active_root()
|
|
338
|
+
if normalised != expected:
|
|
339
|
+
die(f"--plugin-runtime must be this host's stable runtime root {expected}, got {normalised}")
|
|
340
|
+
PLUGIN_RUNTIME = normalised
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def plugin_bin(name: str) -> str:
|
|
344
|
+
assert PLUGIN_RUNTIME is not None
|
|
345
|
+
return str(PLUGIN_RUNTIME / "node_modules" / ".bin" / name)
|
|
346
|
+
|
|
347
|
+
|
|
307
348
|
def desired_mcp(repo: Path) -> dict[str, Any]:
|
|
308
349
|
env = {
|
|
309
350
|
# #50 C4: anonymous sends are refused by the BRIDGE DEFAULT now — the
|
|
@@ -313,6 +354,8 @@ def desired_mcp(repo: Path) -> dict[str, Any]:
|
|
|
313
354
|
# session always has an authoritative garden-id sender.
|
|
314
355
|
"ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/claude-code",
|
|
315
356
|
}
|
|
357
|
+
if PLUGIN_RUNTIME is not None:
|
|
358
|
+
return {"type": "stdio", "command": plugin_bin("entwurf-bridge"), "args": [], "env": env}
|
|
316
359
|
if is_installed_package(repo):
|
|
317
360
|
# Installed package: wire the STABLE `entwurf-bridge` bin shim that npm/pnpm
|
|
318
361
|
# place on PATH. Baking repo/mcp/entwurf-bridge/start.sh would embed the pnpm
|
|
@@ -333,6 +376,8 @@ def desired_mcp(repo: Path) -> dict[str, Any]:
|
|
|
333
376
|
|
|
334
377
|
|
|
335
378
|
def desired_statusline(repo: Path) -> dict[str, Any]:
|
|
379
|
+
if PLUGIN_RUNTIME is not None:
|
|
380
|
+
return {"type": "command", "command": plugin_bin("entwurf-statusline")}
|
|
336
381
|
if is_installed_package(repo):
|
|
337
382
|
# Installed package: mirror the MCP stable-bin pattern. The statusLine is
|
|
338
383
|
# executed at render time, so a bare bin shim lets npm/pnpm update the
|
|
@@ -342,20 +387,23 @@ def desired_statusline(repo: Path) -> dict[str, Any]:
|
|
|
342
387
|
return {"type": "command", "command": str((repo / "scripts" / "meta-bridge-statusline.sh").resolve())}
|
|
343
388
|
|
|
344
389
|
|
|
345
|
-
def
|
|
346
|
-
existing = load_state(required=False)
|
|
347
|
-
state = existing if existing is not None else init_state(repo, asm)
|
|
348
|
-
state["updatedAt"] = iso_now()
|
|
349
|
-
state["repo"] = str(repo.resolve())
|
|
350
|
-
state["assembledMarketplacePath"] = str(asm.resolve())
|
|
351
|
-
|
|
390
|
+
def read_live_documents() -> tuple[dict[str, Any], dict[str, Any]]:
|
|
352
391
|
settings = read_json(settings_path(), {})
|
|
353
392
|
root = read_json(claude_root_config_path(), {})
|
|
354
393
|
if not isinstance(settings, dict):
|
|
355
394
|
die(f"{settings_path()} root must be a JSON object")
|
|
356
395
|
if not isinstance(root, dict):
|
|
357
396
|
die(f"{claude_root_config_path()} root must be a JSON object")
|
|
397
|
+
return settings, root
|
|
398
|
+
|
|
358
399
|
|
|
400
|
+
def plan_snapshots(state: dict[str, Any], repo: Path, asm: Path, settings: dict[str, Any], root: dict[str, Any]) -> None:
|
|
401
|
+
"""Every snapshot decision `prepare` makes, in one place.
|
|
402
|
+
|
|
403
|
+
#116 M3-b2 amendment. `preflight-install` runs this against DEEP COPIES so the forward promise
|
|
404
|
+
is the real plan and not a JSON-object smoke test: a preflight that only checked the documents
|
|
405
|
+
parse would go green and then let `prepare` die on an array-type mismatch it never looked at.
|
|
406
|
+
"""
|
|
359
407
|
snapshot_value(
|
|
360
408
|
state,
|
|
361
409
|
"settings",
|
|
@@ -397,6 +445,15 @@ def prepare(repo: Path, asm: Path) -> None:
|
|
|
397
445
|
legacy_absent_if_equal=desired_mcp(repo),
|
|
398
446
|
)
|
|
399
447
|
|
|
448
|
+
|
|
449
|
+
def prepare(repo: Path, asm: Path) -> None:
|
|
450
|
+
existing = load_state(required=False)
|
|
451
|
+
state = existing if existing is not None else init_state(repo, asm)
|
|
452
|
+
state["updatedAt"] = iso_now()
|
|
453
|
+
state["repo"] = str(repo.resolve())
|
|
454
|
+
state["assembledMarketplacePath"] = str(asm.resolve())
|
|
455
|
+
settings, root = read_live_documents()
|
|
456
|
+
plan_snapshots(state, repo, asm, settings, root)
|
|
400
457
|
write_json(state_path(), state, mode=0o600)
|
|
401
458
|
print(f"[meta-bridge-state] prepared {state_path()}")
|
|
402
459
|
|
|
@@ -446,10 +503,41 @@ def apply(repo: Path, asm: Path) -> None:
|
|
|
446
503
|
print("[meta-bridge-state] applied managed keyset (settings.json + user MCP)")
|
|
447
504
|
|
|
448
505
|
|
|
449
|
-
def
|
|
506
|
+
def certify_entry(obj: dict[str, Any], entry: dict[str, Any], where: str = "state entry") -> str:
|
|
507
|
+
"""Prove this entry COULD be restored, and write nothing.
|
|
508
|
+
|
|
509
|
+
#116 M3-b2 amendment. This is the read-only twin of `restore_entry`, and it exists as one
|
|
510
|
+
function rather than two because the first cut of the uninstall preflight re-implemented the
|
|
511
|
+
checks and knew only ONE of the three entry shapes: it demanded `original` on every entry, while
|
|
512
|
+
`snapshot_array_items` writes `{originalExisted, added}`. Every real Claude activation therefore
|
|
513
|
+
failed its own preflight. A preflight that re-derives the restore rules will drift from them
|
|
514
|
+
again the next time a kind is added — so `restore_entry` calls THIS, and so does the preflight.
|
|
515
|
+
"""
|
|
450
516
|
path = entry.get("path")
|
|
451
517
|
if not isinstance(path, list) or not all(isinstance(p, str) for p in path):
|
|
452
|
-
die("bad state entry path")
|
|
518
|
+
die(f"{where}: bad state entry path")
|
|
519
|
+
kind = entry.get("kind")
|
|
520
|
+
if kind in ("map-entry", "scalar"):
|
|
521
|
+
original = entry.get("original")
|
|
522
|
+
if not isinstance(original, dict) or "existed" not in original:
|
|
523
|
+
die(f"{where}: bad scalar/map original in state")
|
|
524
|
+
return kind
|
|
525
|
+
if kind == "array-items":
|
|
526
|
+
if "originalExisted" not in entry:
|
|
527
|
+
die(f"{where}: array-items entry records no originalExisted")
|
|
528
|
+
if not isinstance(entry.get("added", []), list):
|
|
529
|
+
die(f"{where}: bad array added list in state")
|
|
530
|
+
existed, value = get_nested(obj, path)
|
|
531
|
+
if existed and not isinstance(value, list):
|
|
532
|
+
die(f"{where}: {'.'.join(path)} exists but is not an array; restoration is not applicable")
|
|
533
|
+
return kind
|
|
534
|
+
die(f"{where}: unknown state entry kind {kind!r}")
|
|
535
|
+
raise AssertionError("unreachable")
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def restore_entry(obj: dict[str, Any], entry: dict[str, Any]) -> None:
|
|
539
|
+
certify_entry(obj, entry)
|
|
540
|
+
path = entry.get("path")
|
|
453
541
|
kind = entry.get("kind")
|
|
454
542
|
if kind in ("map-entry", "scalar"):
|
|
455
543
|
original = entry.get("original")
|
|
@@ -525,8 +613,71 @@ def relinquish_retired_scalar(
|
|
|
525
613
|
|
|
526
614
|
|
|
527
615
|
def preflight_uninstall() -> None:
|
|
528
|
-
|
|
529
|
-
|
|
616
|
+
"""Prove the ENTIRE restoration plan is applicable, writing nothing.
|
|
617
|
+
|
|
618
|
+
#116 M3-b2. This used to be `load_state(required=True)` and a print — it proved the ledger
|
|
619
|
+
existed and nothing else. An aggregate deactivate that calls it and then claims "every inverse
|
|
620
|
+
was preflighted" would be asserting a check that had not run, and a teardown that starts
|
|
621
|
+
mutating on that promise is how a Claude failure strands a Pi half. Every entry is certified
|
|
622
|
+
through the SAME function `restore_entry` uses, so the two cannot drift.
|
|
623
|
+
"""
|
|
624
|
+
state = load_state(required=True)
|
|
625
|
+
assert state is not None
|
|
626
|
+
owner = state.get("owner")
|
|
627
|
+
if owner != OWNER:
|
|
628
|
+
die(f"{state_path()} is not ours: owner={owner!r} (expected {OWNER!r})")
|
|
629
|
+
settings, root = read_live_documents()
|
|
630
|
+
certified = 0
|
|
631
|
+
for path_key, doc in (("settings", settings), ("claudeRoot", root)):
|
|
632
|
+
entries = state.get("files", {}).get(path_key, {}).get("keys", {})
|
|
633
|
+
if not isinstance(entries, dict):
|
|
634
|
+
die(f"{state_path()} has no {path_key} key map")
|
|
635
|
+
for name, entry in entries.items():
|
|
636
|
+
if not isinstance(entry, dict):
|
|
637
|
+
die(f"{state_path()} {path_key} entry {name!r} is not a record")
|
|
638
|
+
certify_entry(doc, entry, f"{path_key} entry {name!r}")
|
|
639
|
+
certified += 1
|
|
640
|
+
certify_assembled_path(state.get("assembledMarketplacePath"))
|
|
641
|
+
print(
|
|
642
|
+
f"[meta-bridge-state] uninstall preflight ok ({state_path()}): owner={owner}, "
|
|
643
|
+
f"settings+claudeRoot parse, {certified} restore entries applicable, "
|
|
644
|
+
f"assembled={state.get('assembledMarketplacePath')}"
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def certify_assembled_path(recorded: Any) -> None:
|
|
649
|
+
"""The recorded marketplace path is an address the inverse acts on, so its SHAPE is checked."""
|
|
650
|
+
if not isinstance(recorded, str) or not recorded:
|
|
651
|
+
die(f"{state_path()} records no assembledMarketplacePath")
|
|
652
|
+
if not os.path.isabs(recorded):
|
|
653
|
+
die(f"{state_path()} assembledMarketplacePath is not absolute: {recorded}")
|
|
654
|
+
if not recorded.endswith(ASSEMBLED_SUFFIX):
|
|
655
|
+
die(f"{state_path()} assembledMarketplacePath does not end in {ASSEMBLED_SUFFIX}: {recorded}")
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def preflight_install(repo: Path, asm: Path) -> None:
|
|
659
|
+
"""The forward half of the same promise: run the REAL plan on copies, write nothing.
|
|
660
|
+
|
|
661
|
+
An aggregate activation has to be able to say "nothing will fail once I start" before it starts.
|
|
662
|
+
A preflight that only checked the documents parse would go green and then let `prepare` die on
|
|
663
|
+
an array-type mismatch it never looked at, so this walks `plan_snapshots` — the very function
|
|
664
|
+
`prepare` calls — against deep copies and certifies every entry it produces.
|
|
665
|
+
"""
|
|
666
|
+
settings, root = read_live_documents()
|
|
667
|
+
existing = load_state(required=False)
|
|
668
|
+
state = copy.deepcopy(existing) if existing is not None else init_state(repo, asm)
|
|
669
|
+
plan_snapshots(state, repo, asm, copy.deepcopy(settings), copy.deepcopy(root))
|
|
670
|
+
planned = 0
|
|
671
|
+
for path_key, doc in (("settings", settings), ("claudeRoot", root)):
|
|
672
|
+
for name, entry in state["files"][path_key]["keys"].items():
|
|
673
|
+
certify_entry(doc, entry, f"planned {path_key} entry {name!r}")
|
|
674
|
+
planned += 1
|
|
675
|
+
certify_assembled_path(str(asm.resolve()))
|
|
676
|
+
mode = "plugin-runtime" if PLUGIN_RUNTIME is not None else ("installed" if is_installed_package(repo) else "clone")
|
|
677
|
+
print(
|
|
678
|
+
f"[meta-bridge-state] install preflight ok (mode={mode}, {planned} planned entries certified, "
|
|
679
|
+
f"mcp={desired_mcp(repo)['command']}, statusline={desired_statusline(repo)['command']}, asm={asm})"
|
|
680
|
+
)
|
|
530
681
|
|
|
531
682
|
|
|
532
683
|
def uninstall() -> None:
|
|
@@ -667,6 +818,7 @@ def main() -> int:
|
|
|
667
818
|
choices=[
|
|
668
819
|
"prepare",
|
|
669
820
|
"apply",
|
|
821
|
+
"preflight-install",
|
|
670
822
|
"preflight-uninstall",
|
|
671
823
|
"uninstall",
|
|
672
824
|
"assembled-path",
|
|
@@ -678,6 +830,7 @@ def main() -> int:
|
|
|
678
830
|
)
|
|
679
831
|
parser.add_argument("--repo", default=Path(__file__).resolve().parents[1], type=Path)
|
|
680
832
|
parser.add_argument("--asm", default=None, type=Path)
|
|
833
|
+
parser.add_argument("--plugin-runtime", default=None, type=Path)
|
|
681
834
|
args = parser.parse_args()
|
|
682
835
|
repo = args.repo.resolve()
|
|
683
836
|
# The live artifact always lives under the XDG data dir — dev clone and
|
|
@@ -688,10 +841,13 @@ def main() -> int:
|
|
|
688
841
|
default_asm = xdg_data / "entwurf" / "meta-bridge" / ".assembled"
|
|
689
842
|
asm = (args.asm or default_asm).resolve()
|
|
690
843
|
try:
|
|
844
|
+
set_plugin_runtime(args.plugin_runtime)
|
|
691
845
|
if args.command == "prepare":
|
|
692
846
|
prepare(repo, asm)
|
|
693
847
|
elif args.command == "apply":
|
|
694
848
|
apply(repo, asm)
|
|
849
|
+
elif args.command == "preflight-install":
|
|
850
|
+
preflight_install(repo, asm)
|
|
695
851
|
elif args.command == "preflight-uninstall":
|
|
696
852
|
preflight_uninstall()
|
|
697
853
|
elif args.command == "uninstall":
|
|
@@ -157,10 +157,8 @@
|
|
|
157
157
|
"claim": "CODEX-SEAT-TITLE-BECOMES-DELIVERY-TARGET",
|
|
158
158
|
"title": "the caller's own garden address is replaced by the thread id the PANE TITLE matched, so a forgeable title decides who the fresh sibling is told to call back to. The sibling's first message would then be delivered against an identity nobody vouched for — the exact Hard Rule 16 crossing this lane is built to make impossible: the title is a placement input and may never become an address",
|
|
159
159
|
"subject": "pi-extensions/lib/mux-fresh-call.ts",
|
|
160
|
-
"find": ["\t\t\tcallerGardenId
|
|
161
|
-
"replace": [
|
|
162
|
-
"\t\t\tcallerGardenId: params.callerNativeSessionId ?? params.callerGardenId,\n\t\t\tnonce,\n\t\t}),"
|
|
163
|
-
],
|
|
160
|
+
"find": ["\t\t\tcallerGardenId,\n\t\t\tnonce,\n\t\t}),"],
|
|
161
|
+
"replace": ["\t\t\tcallerGardenId: params.callerNativeSessionId ?? callerGardenId,\n\t\t\tnonce,\n\t\t}),"],
|
|
164
162
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
165
163
|
"timeoutSeconds": 120,
|
|
166
164
|
"signature": "[QK:CODEX-SEAT-TITLE-BECOMES-DELIVERY-TARGET]",
|
|
@@ -203,12 +201,12 @@
|
|
|
203
201
|
},
|
|
204
202
|
{
|
|
205
203
|
"claim": "CODEX-CALLER-PREFLIGHT-SURFACE-WIRED",
|
|
206
|
-
"title": "the
|
|
207
|
-
"subject": "
|
|
204
|
+
"title": "the shared dispatcher stops running the caller-side preflight, so a Codex caller with no `thread-id` in its terminal title gets the generic `unresolved` seat refusal instead of the capability repair — and the MCP surface, its only supplier, cannot tell the two apart",
|
|
205
|
+
"subject": "pi-extensions/lib/fresh-call-dispatch.ts",
|
|
208
206
|
"find": [
|
|
209
|
-
"\t\
|
|
207
|
+
"\t\ttargetMissing === null && request.callerNativeSessionId !== undefined && request.placement === undefined\n\t\t\t? codexCallerFreshPreflight(env)\n\t\t\t: null;"
|
|
210
208
|
],
|
|
211
|
-
"replace": ["\t\
|
|
209
|
+
"replace": ["\t\tnull;"],
|
|
212
210
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
213
211
|
"timeoutSeconds": 120,
|
|
214
212
|
"signature": "[QK:CODEX-CALLER-PREFLIGHT-SURFACE-WIRED]",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
{
|
|
47
47
|
"claim": "FRESHCALL-CODEX-ARGV",
|
|
48
48
|
"title": "Codex fresh launch drops the remote app-server attachment flag and opens an unrelated local session",
|
|
49
|
-
"subject": "pi-extensions/lib/
|
|
49
|
+
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
50
50
|
"find": ["\t\t\t\t\"--remote\","],
|
|
51
51
|
"replace": ["\t\t\t\t\"--resume\","],
|
|
52
52
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
"claim": "FRESHCALL-CODEX-MCP-ENV-ORDER",
|
|
159
|
-
"title": "fresh preflight accepts the managed Codex MCP env_vars in any order, as long as the set matches
|
|
159
|
+
"title": "fresh preflight accepts the managed Codex MCP env_vars in any order, as long as the set matches \u2014 a reordered vendor-visible env manifest passes as exact",
|
|
160
160
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
161
161
|
"find": ["\t\tenvVars.some((name, index) => name !== CODEX_MCP_ENV_VARS[index])"],
|
|
162
162
|
"replace": [
|
|
@@ -171,8 +171,8 @@
|
|
|
171
171
|
"claim": "FRESHCALL-CODEX-HOOKS-SAFE",
|
|
172
172
|
"title": "fresh preflight stops checking the operator-owned hooks.json for unsafe ownership/permissions/symlinks, so a world-writable or symlinked hooks.json is treated as a safe published birth unit",
|
|
173
173
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
174
|
-
"find": ["\tif (!isSafeOwnedFile(paths.hooksFile, expectedUid)) return
|
|
175
|
-
"replace": ["\tif (false) return
|
|
174
|
+
"find": ["\tif (!isSafeOwnedFile(paths.hooksFile, expectedUid)) return null;"],
|
|
175
|
+
"replace": ["\tif (false) return null;"],
|
|
176
176
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
177
177
|
"timeoutSeconds": 120,
|
|
178
178
|
"signature": "[QK:FRESHCALL-CODEX-HOOKS-SAFE]",
|
|
@@ -217,14 +217,14 @@
|
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
"claim": "FRESHCALL-CODEX-HOOK-KEYS",
|
|
220
|
-
"title": "
|
|
221
|
-
"subject": "pi-extensions/lib/codex-
|
|
222
|
-
"find": ["\
|
|
223
|
-
"replace": ["\
|
|
224
|
-
"gate": ["bash", "run.sh", "check-
|
|
225
|
-
"timeoutSeconds":
|
|
220
|
+
"title": "the declaration leaf stops NAMING the extra handler key, so an async or extra-key hook \u2014 still refused by the normalized digest \u2014 arrives as an anonymous mismatch instead of a diagnosis that quotes the key. This claim owns the diagnosis, never the acceptance",
|
|
221
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
222
|
+
"find": ["\tif (handlerKeys.join(\",\") !== \"command,timeout,type\") {"],
|
|
223
|
+
"replace": ["\tif (false) {"],
|
|
224
|
+
"gate": ["bash", "run.sh", "check-codex-birth-hook"],
|
|
225
|
+
"timeoutSeconds": 180,
|
|
226
226
|
"signature": "[QK:FRESHCALL-CODEX-HOOK-KEYS]",
|
|
227
|
-
"signatureSource": "
|
|
227
|
+
"signatureSource": "scripts/check-codex-birth-hook.ts"
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
"claim": "CODEX-TOML-STATE-BEFORE-CONFIG",
|
|
@@ -311,14 +311,14 @@
|
|
|
311
311
|
"signatureSource": "scripts/smoke-codex-config-state.sh"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
|
-
"claim": "CODEX-BIRTH-FOREIGN-
|
|
315
|
-
"title": "the
|
|
314
|
+
"claim": "CODEX-BIRTH-JOINS-FOREIGN-FILE",
|
|
315
|
+
"title": "the birth unit refuses a hooks.json it did not write instead of joining it, so a host carrying a neighbouring integration can never be installed into",
|
|
316
316
|
"subject": "scripts/codex-birth-install.sh",
|
|
317
|
-
"find": ["
|
|
318
|
-
"replace": ["
|
|
317
|
+
"find": [" if (sel.code !== \"declaration-absent\") throw new Error(sel.code + \": \" + sel.detail);"],
|
|
318
|
+
"replace": [" throw new Error(\"hooks.json already exists and no ownership state vouches for it\");"],
|
|
319
319
|
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
320
320
|
"timeoutSeconds": 120,
|
|
321
|
-
"signature": "[QK:CODEX-BIRTH-FOREIGN-
|
|
321
|
+
"signature": "[QK:CODEX-BIRTH-JOINS-FOREIGN-FILE]",
|
|
322
322
|
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
323
323
|
},
|
|
324
324
|
{
|
|
@@ -380,10 +380,14 @@
|
|
|
380
380
|
},
|
|
381
381
|
{
|
|
382
382
|
"claim": "FRESHCALL-CODEX-PREMUTATION-MCP",
|
|
383
|
-
"title": "the MCP surface
|
|
383
|
+
"title": "the MCP public surface relabels a Codex request as pi at the shared dispatcher seam, so the one central capability preflight is bypassed and placement runs first",
|
|
384
384
|
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
385
|
-
"find": [
|
|
386
|
-
|
|
385
|
+
"find": [
|
|
386
|
+
"\t\t\tconst dispatched = await dispatchFreshCall({\n\t\t\t\tbackend,\n\t\t\t\tmodel,\n\t\t\t\ttask,\n\t\t\t\tcwd,\n\t\t\t\tplacement,\n\t\t\t\tcallerGardenId,\n\t\t\t\tcallerNativeSessionId,\n\t\t\t\tcallerCwd,\n\t\t\t});"
|
|
387
|
+
],
|
|
388
|
+
"replace": [
|
|
389
|
+
"\t\t\tconst dispatched = await dispatchFreshCall({\n\t\t\t\tbackend: \"pi\",\n\t\t\t\tmodel,\n\t\t\t\ttask,\n\t\t\t\tcwd,\n\t\t\t\tplacement,\n\t\t\t\tcallerGardenId,\n\t\t\t\tcallerNativeSessionId,\n\t\t\t\tcallerCwd,\n\t\t\t});"
|
|
390
|
+
],
|
|
387
391
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
388
392
|
"timeoutSeconds": 180,
|
|
389
393
|
"signature": "[QK:FRESHCALL-CODEX-PREMUTATION-MCP]",
|
|
@@ -391,10 +395,10 @@
|
|
|
391
395
|
},
|
|
392
396
|
{
|
|
393
397
|
"claim": "FRESHCALL-CODEX-PREMUTATION-PI",
|
|
394
|
-
"title": "the native pi public surface
|
|
398
|
+
"title": "the native pi public surface relabels a Codex request as pi at the shared dispatcher seam, bypassing the same central preflight from the other public door",
|
|
395
399
|
"subject": "pi-extensions/entwurf-control.ts",
|
|
396
|
-
"find": ["\t\t\t\t\
|
|
397
|
-
"replace": ["\t\t\t\t\
|
|
400
|
+
"find": ["\t\t\t\t\tbackend: params.backend,"],
|
|
401
|
+
"replace": ["\t\t\t\t\tbackend: \"pi\","],
|
|
398
402
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
399
403
|
"timeoutSeconds": 180,
|
|
400
404
|
"signature": "[QK:FRESHCALL-CODEX-PREMUTATION-PI]",
|
|
@@ -445,9 +449,7 @@
|
|
|
445
449
|
"claim": "FRESHCALL-CODEX-TRUST-RECEIPT",
|
|
446
450
|
"title": "fresh preflight opens a Codex sibling whose birth hook the vendor never agreed to run",
|
|
447
451
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
448
|
-
"find": [
|
|
449
|
-
"\tif (config === null || trustReceiptMissing(config, paths.hooksFile)) return \"codex-birth-trust-missing\";"
|
|
450
|
-
],
|
|
452
|
+
"find": ["\tif (config === null || trustReceiptMissing(config, key)) return \"codex-birth-trust-missing\";"],
|
|
451
453
|
"replace": ["\tif (config === null) return \"codex-birth-trust-missing\";"],
|
|
452
454
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
453
455
|
"timeoutSeconds": 180,
|
|
@@ -458,8 +460,10 @@
|
|
|
458
460
|
"claim": "FRESHCALL-CODEX-STATE-DIGEST",
|
|
459
461
|
"title": "fresh preflight accepts a birth closure whose bytes no longer match the digests its own ownership state recorded",
|
|
460
462
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
461
|
-
"find": [
|
|
462
|
-
|
|
463
|
+
"find": [
|
|
464
|
+
"\t\tif (typeof member.sha256 !== \"string\" || digest(path.join(paths.helperDir, rel)) !== member.sha256) return null;"
|
|
465
|
+
],
|
|
466
|
+
"replace": ["\t\tif (typeof member.sha256 !== \"string\") return null; // mutant: existence is enough"],
|
|
463
467
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
464
468
|
"timeoutSeconds": 180,
|
|
465
469
|
"signature": "[QK:FRESHCALL-CODEX-STATE-DIGEST]",
|
|
@@ -518,7 +522,7 @@
|
|
|
518
522
|
"title": "fresh preflight compares only the digests a state happens to name, so a short inventory hides an unchecked closure member",
|
|
519
523
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
520
524
|
"find": [
|
|
521
|
-
"\tif (named.length !== expected.length) return
|
|
525
|
+
"\tif (named.length !== expected.length) return null;\n\tif (expected.some((name) => !named.includes(name))) return null;\n\tif (named.some((name, index) => named.indexOf(name) !== index)) return null;"
|
|
522
526
|
],
|
|
523
527
|
"replace": ["\t// mutant: judge only the members a state happens to name"],
|
|
524
528
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
@@ -554,7 +558,7 @@
|
|
|
554
558
|
"claim": "FRESHCALL-CODEX-STATE-PARENT-AUTHORITY",
|
|
555
559
|
"title": "fresh preflight trusts a digest inventory sitting in a directory anyone on the host can rewrite",
|
|
556
560
|
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
557
|
-
"find": ["\tif (!isSafeOwnedDir(path.dirname(paths.stateFile), expectedUid)) return
|
|
561
|
+
"find": ["\tif (!isSafeOwnedDir(path.dirname(paths.stateFile), expectedUid)) return null;"],
|
|
558
562
|
"replace": ["\t// mutant: the directory holding the receipt is nobody's business"],
|
|
559
563
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
560
564
|
"timeoutSeconds": 180,
|
|
@@ -833,6 +837,168 @@
|
|
|
833
837
|
"timeoutSeconds": 180,
|
|
834
838
|
"signature": "[QK:CODEX-LIVE-RECOVERY-SOURCE-ONLY]",
|
|
835
839
|
"signatureSource": "test/codex-fresh-live-protocol.test.ts"
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
"claim": "CODEX-DECL-CANONICAL-KEY-ORDER",
|
|
843
|
+
"title": "the declaration digest is taken over unsorted keys, so a neighbour re-serializing the document reads as an edited entwurf declaration",
|
|
844
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
845
|
+
"find": ["\tconst keys = Object.keys(value).sort();"],
|
|
846
|
+
"replace": ["\tconst keys = Object.keys(value);"],
|
|
847
|
+
"gate": ["bash", "run.sh", "check-codex-birth-hook"],
|
|
848
|
+
"timeoutSeconds": 180,
|
|
849
|
+
"signature": "[QK:CODEX-DECL-CANONICAL-KEY-ORDER]",
|
|
850
|
+
"signatureSource": "scripts/check-codex-birth-hook.ts"
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
"claim": "CODEX-DECL-SELECT-BY-COMMAND",
|
|
854
|
+
"title": "entwurf's declaration is selected by POSITION instead of by its launcher command, so a neighbour at index 0 is certified as ours",
|
|
855
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
856
|
+
"find": [
|
|
857
|
+
"\t\t\tif (handler !== null && typeof handler === \"object\" && !Array.isArray(handler) && handler.command === want) {"
|
|
858
|
+
],
|
|
859
|
+
"replace": [
|
|
860
|
+
"\t\t\tif (handler !== null && typeof handler === \"object\" && !Array.isArray(handler) && h === 0) {"
|
|
861
|
+
],
|
|
862
|
+
"gate": ["bash", "run.sh", "check-codex-birth-hook"],
|
|
863
|
+
"timeoutSeconds": 180,
|
|
864
|
+
"signature": "[QK:CODEX-DECL-SELECT-BY-COMMAND]",
|
|
865
|
+
"signatureSource": "scripts/check-codex-birth-hook.ts"
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
"claim": "CODEX-DECL-SPLICE-KEEPS-FOREIGN-BYTES",
|
|
869
|
+
"title": "a certified splice returns a RE-SERIALIZED document instead of the spliced text, rewriting every byte a neighbouring integration owns",
|
|
870
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
871
|
+
"find": ["\treturn spliced;"],
|
|
872
|
+
"replace": ["\treturn JSON.stringify(reparsed, null, 2);"],
|
|
873
|
+
"gate": ["bash", "run.sh", "check-codex-birth-hook"],
|
|
874
|
+
"timeoutSeconds": 180,
|
|
875
|
+
"signature": "[QK:CODEX-DECL-SPLICE-KEEPS-FOREIGN-BYTES]",
|
|
876
|
+
"signatureSource": "scripts/check-codex-birth-hook.ts"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
"claim": "CODEX-DECL-SPLICE-CERTIFIED",
|
|
880
|
+
"title": "the span splice is written out on the span reader's word alone, with no re-parse proving it landed where the caller intended",
|
|
881
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
882
|
+
"find": ["\tif (canonicalJson(reparsed) !== canonicalJson(expected)) {"],
|
|
883
|
+
"replace": ["\tif (false) {"],
|
|
884
|
+
"gate": ["bash", "run.sh", "check-codex-birth-hook"],
|
|
885
|
+
"timeoutSeconds": 180,
|
|
886
|
+
"signature": "[QK:CODEX-DECL-SPLICE-CERTIFIED]",
|
|
887
|
+
"signatureSource": "scripts/check-codex-birth-hook.ts"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
"claim": "CODEX-DECLARATION-DUPLICATED",
|
|
891
|
+
"title": "a duplicated entwurf declaration is first-match accepted, so the vendor runs the birth hook twice and only one position carries the receipt",
|
|
892
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
893
|
+
"find": ["\tif (mine.length > 1) {"],
|
|
894
|
+
"replace": ["\tif (mine.length > 2) {"],
|
|
895
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
896
|
+
"timeoutSeconds": 180,
|
|
897
|
+
"signature": "[QK:CODEX-DECLARATION-DUPLICATED]",
|
|
898
|
+
"signatureSource": "test/codex-fresh-preflight.test.ts"
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
"claim": "CODEX-DECLARATION-TRUST-INDEX",
|
|
902
|
+
"title": "fresh preflight reads the vendor trust receipt at the constant :0:0, so a neighbour's approval at index 0 admits a birth hook the vendor never agreed to run",
|
|
903
|
+
"subject": "pi-extensions/lib/codex-fresh-preflight.ts",
|
|
904
|
+
"find": ["\tconst key = trustReceiptKey(paths.hooksFile, declaration.groupIndex, declaration.handlerIndex);"],
|
|
905
|
+
"replace": ["\tconst key = trustReceiptKey(paths.hooksFile, 0, 0);"],
|
|
906
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
907
|
+
"timeoutSeconds": 180,
|
|
908
|
+
"signature": "[QK:CODEX-DECLARATION-TRUST-INDEX]",
|
|
909
|
+
"signatureSource": "test/codex-fresh-preflight.test.ts"
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"claim": "CODEX-BIRTH-TRUST-INDEX-MEASURED",
|
|
913
|
+
"title": "the doctor reads the vendor trust receipt at the constant :0:0, reporting a neighbour's approval as entwurf's own",
|
|
914
|
+
"subject": "scripts/codex-birth-doctor.sh",
|
|
915
|
+
"find": [" TRUST_KEY=\"$HOOKS_FILE:session_start:$DECL_GROUP_INDEX:$DECL_HANDLER_INDEX\""],
|
|
916
|
+
"replace": [" TRUST_KEY=\"$HOOKS_FILE:session_start:0:0\""],
|
|
917
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
918
|
+
"timeoutSeconds": 180,
|
|
919
|
+
"signature": "[QK:CODEX-BIRTH-TRUST-INDEX-MEASURED]",
|
|
920
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"claim": "CODEX-BIRTH-SHARED-FILE-OWNERSHIP",
|
|
924
|
+
"title": "the shared ownership predicate stops seeing a group/world-writable file, so all four surfaces write into and certify bytes somebody else can rewrite",
|
|
925
|
+
"subject": "pi-extensions/lib/codex-declaration.js",
|
|
926
|
+
"find": ["\tif ((stat.mode & 0o022) !== 0) return \"writable-by-others\";"],
|
|
927
|
+
"replace": [""],
|
|
928
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
929
|
+
"timeoutSeconds": 300,
|
|
930
|
+
"signature": "[QK:CODEX-BIRTH-SHARED-FILE-OWNERSHIP]",
|
|
931
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
"claim": "CODEX-BIRTH-DOCTOR-SHARED-OWNERSHIP",
|
|
935
|
+
"title": "the doctor stops asking who owns the shared file and goes straight to the digest, so it reports green on a host every launch refuses",
|
|
936
|
+
"subject": "scripts/codex-birth-doctor.sh",
|
|
937
|
+
"find": [" if (owned !== \"ok\") {"],
|
|
938
|
+
"replace": [" if (false) {"],
|
|
939
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
940
|
+
"timeoutSeconds": 300,
|
|
941
|
+
"signature": "[QK:CODEX-BIRTH-DOCTOR-SHARED-OWNERSHIP]",
|
|
942
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"claim": "CODEX-BIRTH-INVERSE-SHARED-OWNERSHIP",
|
|
946
|
+
"title": "the inverse stops asking and rewrites a shared file it cannot bind, instead of leaving it as found and counting the drift",
|
|
947
|
+
"subject": "scripts/codex-birth-uninstall.sh",
|
|
948
|
+
"find": [" if [ \"$owned\" != \"ok\" ]; then"],
|
|
949
|
+
"replace": [" if false; then"],
|
|
950
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
951
|
+
"timeoutSeconds": 300,
|
|
952
|
+
"signature": "[QK:CODEX-BIRTH-INVERSE-SHARED-OWNERSHIP]",
|
|
953
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"claim": "CODEX-BIRTH-INVERSE-KEEPS-PRE-EXISTING-FILE",
|
|
957
|
+
"title": "the FILE verdict goes back to asking only whether our group was the last declaration, so a hooks.json that existed before this unit is deleted with its owners top-level bytes",
|
|
958
|
+
"subject": "scripts/codex-birth-uninstall.sh",
|
|
959
|
+
"find": [
|
|
960
|
+
" if (createdByUs === \"yes\" && soleDeclaration && topLevel === \"description,hooks\" && ourDescription) {"
|
|
961
|
+
],
|
|
962
|
+
"replace": [" if (soleDeclaration) {"],
|
|
963
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
964
|
+
"timeoutSeconds": 300,
|
|
965
|
+
"signature": "[QK:CODEX-BIRTH-INVERSE-KEEPS-PRE-EXISTING-FILE]",
|
|
966
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
"claim": "CODEX-BIRTH-DOCTOR-ORPHAN-RED",
|
|
970
|
+
"title": "the doctor stops looking for our own bytes when the receipt is gone, so a half-removed unit reads as a clean host and invites a reinstall over bytes the inverse will refuse",
|
|
971
|
+
"subject": "scripts/codex-birth-doctor.sh",
|
|
972
|
+
"find": [" if [ \"$ORPHAN_DECLARATION\" = \"1\" ] || [ -e \"$HELPER_DIR\" ]; then"],
|
|
973
|
+
"replace": [" if false; then"],
|
|
974
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
975
|
+
"timeoutSeconds": 300,
|
|
976
|
+
"signature": "[QK:CODEX-BIRTH-DOCTOR-ORPHAN-RED]",
|
|
977
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
"claim": "CODEX-BIRTH-INVERSE-KEEPS-FOREIGN",
|
|
981
|
+
"title": "the text splice takes a fixed group index instead of the one the selector measured, so the inverse reaches for a neighbours declaration and the post-condition is the only thing standing between that and a written file",
|
|
982
|
+
"subject": "scripts/codex-birth-uninstall.sh",
|
|
983
|
+
"find": [" let spliced = m.certifySplice(m.removeSessionStartGroup(text, sel.groupIndex), want);"],
|
|
984
|
+
"replace": [" let spliced = m.certifySplice(m.removeSessionStartGroup(text, 0), want);"],
|
|
985
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
986
|
+
"timeoutSeconds": 300,
|
|
987
|
+
"signature": "[QK:CODEX-BIRTH-INVERSE-KEEPS-FOREIGN]",
|
|
988
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
"claim": "CODEX-BIRTH-INVERSE-SOLE-OWNER",
|
|
992
|
+
"title": "the FILE arm drops the target from its own removal, so a hooks.json this unit created and was the sole declaration in survives an uninstall that reports it gone",
|
|
993
|
+
"subject": "scripts/codex-birth-uninstall.sh",
|
|
994
|
+
"find": [
|
|
995
|
+
" FILE) rm -f -- \"$tmp\" \"$target\"; note \"removed $target (entwurf was its only declaration)\" ;;"
|
|
996
|
+
],
|
|
997
|
+
"replace": [" FILE) rm -f -- \"$tmp\"; note \"removed $target (entwurf was its only declaration)\" ;;"],
|
|
998
|
+
"gate": ["bash", "run.sh", "smoke-codex-birth"],
|
|
999
|
+
"timeoutSeconds": 300,
|
|
1000
|
+
"signature": "[QK:CODEX-BIRTH-INVERSE-SOLE-OWNER]",
|
|
1001
|
+
"signatureSource": "scripts/smoke-codex-birth.sh"
|
|
836
1002
|
}
|
|
837
1003
|
]
|
|
838
1004
|
}
|