@junghanacs/entwurf 0.41.1 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +185 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +70 -65
  6. package/VERIFY.md +9 -9
  7. package/docs/external-mcp-host.md +1 -1
  8. package/docs/setup-clean-host.md +59 -33
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  23. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  24. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  25. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  26. package/mcp/entwurf-bridge/src/index.ts +14 -11
  27. package/native/win32-x64/entwurf_win.node +0 -0
  28. package/native/win32-x64/manifest.json +2 -2
  29. package/package.json +4 -4
  30. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  31. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  32. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  35. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  36. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  37. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  38. package/pi-extensions/lib/native-push/register.ts +17 -2
  39. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  40. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  41. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  42. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  43. package/pi-extensions/meta-bridge-omp.ts +69 -7
  44. package/pi-extensions/meta-bridge-pi-durable.ts +2 -2
  45. package/run.sh +173 -22
  46. package/scripts/check-agy-sender-identity.ts +6 -4
  47. package/scripts/check-entwurf-self-address.ts +34 -3
  48. package/scripts/check-entwurf-v2-contract.ts +24 -2
  49. package/scripts/check-entwurf-v2-production.ts +45 -31
  50. package/scripts/check-gate-qualification.ts +10 -8
  51. package/scripts/check-native-push-adapter.ts +4 -0
  52. package/scripts/check-native-push-register.ts +32 -82
  53. package/scripts/check-remote-pair-installed.ts +2 -2
  54. package/scripts/dev-bin.sh +112 -1
  55. package/scripts/lib/chain-terminus-publication.ts +58 -0
  56. package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
  57. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  58. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  59. package/scripts/mutants/chain-terminus-publication.json +17 -0
  60. package/scripts/mutants/codex-native.json +98 -0
  61. package/scripts/mutants/control-send-receipt.json +2 -2
  62. package/scripts/mutants/mux-fresh-call.json +16 -1
  63. package/scripts/mutants/native-push-paused.json +17 -0
  64. package/scripts/mutants/omp-fresh.json +13 -2
  65. package/scripts/mutants/pack-install.json +46 -0
  66. package/scripts/mutants/remote-pair-display.json +3 -3
  67. package/scripts/mutants/remote-pair-receive.json +41 -11
  68. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  69. package/scripts/mutants/remote-pair-send.json +156 -17
  70. package/scripts/mutants/remote-pair-transport.json +4 -4
  71. package/scripts/mutants/source-install.json +66 -0
  72. package/scripts/remote-pair-receive.ts +12 -12
  73. package/scripts/smoke-agy-install-state.sh +121 -0
  74. package/scripts/smoke-agy-native-push-live.ts +13 -0
  75. package/scripts/smoke-codex-fresh-live.ts +50 -27
  76. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  77. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  78. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  79. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  80. package/scripts/smoke-omp-fresh-live.ts +5 -4
  81. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  82. package/scripts/smoke-setup-verdict.sh +27 -2
@@ -461,6 +461,126 @@ export ENTWURF_AGY_STATUSLINE_TARGET="$SB/fake-status.sh"
461
461
  export ENTWURF_AGY_IMPRINT_TARGET="$SB/fake-aux.sh"
462
462
  export ENTWURF_COPILOT_STATUSLINE_TARGET="$SB/fake-aux.sh"
463
463
 
464
+ # Hermetic PATH for every dev-bin cell: the host's own entwurf entrypoints (e.g. a real
465
+ # ~/.local/bin/entwurf into some checkout) and its real pnpm/npm global bin dirs must not leak into
466
+ # the exclusivity verdict. Drop every host PATH dir that carries a package bin name, and put fixture
467
+ # package managers first; FAKE_PM_MODE picks their answer. Restored after the dev-bin cells.
468
+ DEVBIN_ORIG_PATH="$PATH"
469
+ PACKAGE_BINS="$(python3 -c 'import json,sys; print(" ".join(json.load(open(sys.argv[1]))["bin"]))' "$REPO_DIR/package.json")"
470
+ HERMETIC_PATH=""
471
+ IFS=':' read -r -a _dirs <<<"$PATH"
472
+ for _d in "${_dirs[@]}"; do
473
+ [ -n "$_d" ] || continue
474
+ for _b in $PACKAGE_BINS; do { [ -e "$_d/$_b" ] || [ -L "$_d/$_b" ]; } && continue 2; done
475
+ HERMETIC_PATH="${HERMETIC_PATH:+$HERMETIC_PATH:}$_d"
476
+ done
477
+ unset _dirs _d _b
478
+ FAKE_PM="$SB/fake-pm"; PM_ROOT="$SB/pm"; mkdir -p "$FAKE_PM"
479
+ for _pm in pnpm npm; do
480
+ case "$_pm" in pnpm) _q="bin -g"; _a="$PM_ROOT/pnpm-bin" ;; npm) _q="prefix -g"; _a="$PM_ROOT/npm-prefix" ;; esac
481
+ cat > "$FAKE_PM/$_pm" <<SH
482
+ #!/usr/bin/env bash
483
+ [ "\$*" = "$_q" ] || { echo "fixture $_pm answers only: $_q" >&2; exit 64; }
484
+ case "\${FAKE_PM_MODE:-ok}" in
485
+ ok) [ "$_pm" = npm ] || case ":\$PATH:" in *":$_a:"*) ;; *) echo "[ERROR] The configured global bin directory \"$_a\" is not in PATH" >&2; exit 1 ;; esac
486
+ echo "$_a" ;;
487
+ fail) echo "ERR_FIXTURE_$_pm boom" >&2; exit 1 ;;
488
+ hang) exec sleep 30 ;;
489
+ esac
490
+ SH
491
+ chmod +x "$FAKE_PM/$_pm"
492
+ done
493
+ unset _pm _q _a
494
+ mkdir -p "$PM_ROOT/pnpm-bin" # an existing, empty pnpm global bin dir
495
+ export PATH="$FAKE_PM:$HERMETIC_PATH:$PM_ROOT/pnpm-bin" # pnpm 11 answers `bin -g` only when that dir is on PATH
496
+ want "dev-bin hermetic PATH: no package bin name resolves, and pnpm/npm are the fixtures" \
497
+ "! command -v entwurf >/dev/null && [ \"\$(command -v pnpm)\" = '$FAKE_PM/pnpm' ] && [ \"\$(command -v npm)\" = '$FAKE_PM/npm' ]"
498
+
499
+ # J-X: developer-install exclusivity (dev-bin.sh exclusive). A certified PATH winner alone let an
500
+ # old global shim LATER in PATH stay selectable by a shell with another PATH order (non-interactive
501
+ # SSH picked it). Every cell links by NAME so an operator-set mutant elsewhere cannot reach here.
502
+ bash "$DEVBIN" expose entwurf >/dev/null 2>&1
503
+ bash "$DEVBIN" expose entwurf-bridge >/dev/null 2>&1
504
+ SAME_DIR="$SB/same-root"; mkdir -p "$SAME_DIR"
505
+ ln -s "$SB/fake-entwurf.sh" "$SAME_DIR/entwurf" # a second link, same target
506
+ ln -s "$DLINK" "$SAME_DIR/entwurf-bridge" # a chain through our link, same target
507
+ set +e; OUT="$(PATH="$DBIN_DIR:$DBIN_DIR:$SAME_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
508
+ want "dev-bin exclusive: duplicate PATH entries and links resolving to our target are the same install [QK:SOURCE-SAME-TARGET-PASSES]" \
509
+ "[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'no other root under the 6 package bin names'"
510
+ want "dev-bin exclusive: an existing-but-empty package-manager global bin is checked and clean, and the PASS names its limit" \
511
+ "printf '%s' \"\$OUT\" | grep -q 'pnpm global bin $PM_ROOT/pnpm-bin' && printf '%s' \"\$OUT\" | grep -q 'another shell.s PATH or another package manager is not covered'"
512
+ TOOLS_ONLY="$SB/tools-only"; mkdir -p "$TOOLS_ONLY"
513
+ for _t in bash python3 dirname env; do ln -s "$(command -v "$_t")" "$TOOLS_ONLY/$_t"; done; unset _t
514
+ set +e; OUT="$(PATH="$TOOLS_ONLY:$DBIN_DIR" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
515
+ want "dev-bin exclusive: an absent package manager is a named not-checked line, not a failure" \
516
+ "[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'not checked: pnpm not on PATH' && printf '%s' \"\$OUT\" | grep -q 'not checked: npm not on PATH'"
517
+ rm -rf "$SAME_DIR"
518
+
519
+ OTHER_DIR="$SB/old-global/bin"; mkdir -p "$OTHER_DIR"
520
+ printf '#!/bin/sh\nexec /old/global/entwurf "$@"\n' > "$OTHER_DIR/entwurf"; chmod +x "$OTHER_DIR/entwurf"
521
+ want "dev-bin control: with an old shim LATER in PATH the winner is still ours, so verify alone passes" \
522
+ "PATH='$DBIN_DIR:$OTHER_DIR':\"\$PATH\" bash '$DEVBIN' verify entwurf >/dev/null 2>&1"
523
+ set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
524
+ want "dev-bin exclusive: an old root LATER in PATH is named and refused, not hidden behind our winner [QK:SOURCE-LATER-ROOT-FAILS]" \
525
+ "[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF 'another entwurf root is still selectable: $OTHER_DIR/entwurf'"
526
+ want "dev-bin exclusive: the refusal removes nothing and says so" \
527
+ "[ \"\$(sed -n 2p '$OTHER_DIR/entwurf')\" = 'exec /old/global/entwurf \"\$@\"' ] && [ -L '$OLINK' ] && printf '%s' \"\$OUT\" | grep -q 'nothing was removed'"
528
+ mkdir -p "$SB/other-checkout/mcp/entwurf-bridge"; printf '#!/bin/sh\n' > "$SB/other-checkout/mcp/entwurf-bridge/start.sh"
529
+ rm -f "$OTHER_DIR/entwurf"; ln -s "$SB/other-checkout/mcp/entwurf-bridge/start.sh" "$OTHER_DIR/entwurf-bridge"
530
+ set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
531
+ want "dev-bin exclusive: a helper name from another checkout is refused like the operator" \
532
+ "[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF '$OTHER_DIR/entwurf-bridge -> $SB/other-checkout/mcp/entwurf-bridge/start.sh'"
533
+ rm -rf "$SB/old-global"
534
+
535
+ mkdir -p "$PM_ROOT/npm-prefix/bin"; printf '#!/bin/sh\n' > "$SB/other-checkout/run.sh"
536
+ ln -s "$SB/other-checkout/run.sh" "$PM_ROOT/npm-prefix/bin/entwurf" # npm's <prefix>/bin, OFF this PATH
537
+ set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
538
+ want "dev-bin exclusive: another root in npm's global bin dir is refused although that dir is not on this PATH [QK:SOURCE-GLOBAL-BIN-OTHER-ROOT]" \
539
+ "[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF '$PM_ROOT/npm-prefix/bin/entwurf -> $SB/other-checkout/run.sh' && printf '%s' \"\$OUT\" | grep -qx ' seen via: npm global bin'"
540
+ want "dev-bin exclusive: the removal hint is conditional on the package manager, never asserted" \
541
+ "printf '%s' \"\$OUT\" | grep -qF 'if a global @junghanacs/entwurf installed it: npm uninstall -g @junghanacs/entwurf' && printf '%s' \"\$OUT\" | grep -q 'otherwise remove it through whoever owns it'"
542
+ rm -rf "$PM_ROOT/npm-prefix"
543
+
544
+ set +e; OUT="$(FAKE_PM_MODE=fail PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
545
+ want "dev-bin exclusive: a package manager that cannot answer is refused, not read as clean [QK:SOURCE-GLOBAL-QUERY-REFUSED]" \
546
+ "[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect a package manager — \`pnpm bin -g\` exited 1: ERR_FIXTURE_pnpm boom'"
547
+ set +e; OUT="$(FAKE_PM_MODE=hang ENTWURF_DEV_BIN_PM_TIMEOUT=1 PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
548
+ want "dev-bin exclusive: a package manager that hangs is bounded and refused" \
549
+ "[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -q 'did not answer within 1s'"
550
+ set +e; OUT="$(PATH="$DBIN_DIR:$FAKE_PM:$HERMETIC_PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
551
+ want "dev-bin exclusive: pnpm refusing because its global bin dir is off PATH is an inspect failure, not a clean dir" \
552
+ "[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'is not in PATH'"
553
+
554
+ # The package manifest is read by the verdict step itself: an unreadable or wrong-shaped one is an
555
+ # inspect failure, never "0 names, no other root". Driven on a copy of THIS dev-bin.sh beside a
556
+ # sandbox package.json, so the repo's own manifest is never touched.
557
+ MANI="$SB/manifest-copy"; mkdir -p "$MANI/scripts"; cp "$DEVBIN" "$MANI/scripts/dev-bin.sh"
558
+ cp "$REPO_DIR/package.json" "$MANI/package.json"
559
+ set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
560
+ want "dev-bin exclusive control: the copied script with the real manifest checks all 6 names and passes" \
561
+ "[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'no other root under the 6 package bin names'"
562
+ printf 'not json{' > "$MANI/package.json"
563
+ set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
564
+ want "dev-bin exclusive: an unreadable package manifest is refused, never read as zero names [QK:SOURCE-MANIFEST-UNREADABLE-REFUSED]" \
565
+ "[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect the package manifest $MANI/package.json' && ! printf '%s' \"\$OUT\" | grep -q '0 package bin names'"
566
+ for _shape in '{"name":"x"}' '{"bin":"./run.sh"}' '{"bin":{}}' '{"bin":{"entwurf-bridge":"./x"}}'; do
567
+ printf '%s' "$_shape" > "$MANI/package.json"
568
+ set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
569
+ want "dev-bin exclusive: manifest $_shape is refused as uninspectable" \
570
+ "[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect the package manifest'"
571
+ done
572
+ unset _shape; rm -rf "$MANI"
573
+
574
+ OTHER_DIR="$SB/old-global/bin"; mkdir -p "$OTHER_DIR"
575
+ printf '#!/bin/sh\nexec /old/global/entwurf "$@"\n' > "$OTHER_DIR/entwurf"; chmod +x "$OTHER_DIR/entwurf"
576
+ set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$REPO_DIR/run.sh" expose-dev-bin 2>&1)"; RC=$?; set -e
577
+ want "source setup's bins step fails when another root is still selectable [QK:SOURCE-EXCLUSIVE-WIRED]" \
578
+ "[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF 'another entwurf root is still selectable: $OTHER_DIR/entwurf'"
579
+ rm -rf "$SB/old-global" "$SB/other-checkout" "$TOOLS_ONLY"
580
+ bash "$DEVBIN" remove >/dev/null 2>&1 # the helper links expose-dev-bin just made
581
+ bash "$DEVBIN" remove entwurf >/dev/null 2>&1 # by NAME, like the expose above: the
582
+ bash "$DEVBIN" remove entwurf-bridge >/dev/null 2>&1 # managed list itself may be under test
583
+
464
584
  # J-0: the setup wrapper calls no-arg expose. This is the cross-contract cell the #82 regression
465
585
  # lacked: an npm consumer gets `entwurf` from package bin linking, and source setup must expose the
466
586
  # same operator command without a second global package install.
@@ -578,6 +698,7 @@ ok "dev-bin migrate: foreign legacy refused (linkPath basename != entwurf-bridge
578
698
  rm -f "$LEGACY" "$DLINK" "$DSTATE"
579
699
  bash "$DEVBIN" remove >/dev/null 2>&1
580
700
  unset ENTWURF_DEV_BIN_DIR ENTWURF_OPERATOR_TARGET ENTWURF_BRIDGE_TARGET ENTWURF_AGY_STATUSLINE_TARGET
701
+ export PATH="$DEVBIN_ORIG_PATH"
581
702
  unset ENTWURF_AGY_IMPRINT_TARGET ENTWURF_COPILOT_STATUSLINE_TARGET
582
703
 
583
704
  # ── K: permission grant — the OTHER half of a usable bridge ───────────────────
@@ -48,6 +48,7 @@ import * as os from "node:os";
48
48
  import * as path from "node:path";
49
49
  import { fileURLToPath } from "node:url";
50
50
  import type { SenderEnvelope } from "../pi-extensions/lib/entwurf-control-rpc.ts";
51
+ import { nativePushPaused } from "../pi-extensions/lib/entwurf-v2-contract.ts";
51
52
  import { makeProductionEntwurfV2Deps } from "../pi-extensions/lib/entwurf-v2-production.ts";
52
53
  import type { EntwurfV2RunResult } from "../pi-extensions/lib/entwurf-v2-runner.ts";
53
54
  import { runEntwurfV2 } from "../pi-extensions/lib/entwurf-v2-runner.ts";
@@ -79,6 +80,18 @@ function smokeSender(gardenId: string, cwd: string): SenderEnvelope {
79
80
  }
80
81
 
81
82
  async function main(): Promise<void> {
83
+ // #152: agy native-push is PAUSED by product policy, so this positive LIVE acceptance cannot
84
+ // run — every cell below would drive the paused rail. It exits NON-ZERO by name (neither a PASS
85
+ // nor the protocol SKIP), and touches no agy process, port or conversation. The pause itself is
86
+ // proven deterministically beside the behavior (pi-extensions/lib/native-push/paused.test.ts).
87
+ if (nativePushPaused("antigravity")) {
88
+ console.error(
89
+ "[entwurf:paused] smoke-agy-native-push-live — agy native-push is paused (#152: its language " +
90
+ "server refused entwurf's measured external calls for a missing CSRF token). This LIVE acceptance does not run while " +
91
+ "the pause holds; it is not a PASS.",
92
+ );
93
+ process.exit(2);
94
+ }
82
95
  if (process.env.LIVE !== "1") {
83
96
  skipLive(
84
97
  "smoke-agy-native-push-live",
@@ -75,12 +75,14 @@ import {
75
75
  } from "../pi-extensions/lib/native-push/codex-ws-client.ts";
76
76
  import {
77
77
  buildCodexInstruction,
78
+ buildInitialPiBootstrapTask,
78
79
  buildInitialPiPhaseOne,
79
80
  buildInitialPiRelay,
80
81
  runCleanupStages,
81
82
  selectReport,
82
83
  } from "./lib/codex-fresh-live-protocol.ts";
83
84
  import {
85
+ assertBootstrapOrder,
84
86
  assertExactSourceToolCalls,
85
87
  assertSourceCallScopes,
86
88
  codexCallbackSenders,
@@ -978,11 +980,7 @@ async function run(): Promise<void> {
978
980
  backend: "pi",
979
981
  model: piModel,
980
982
  cwd: scratch,
981
- task:
982
- "Session-info already supplies device and time. Do not recheck time, cwd, or identity with shell or other tools. " +
983
- "Your FIRST tool call must be mcp__entwurf_bridge__entwurf_callback with no arguments, and nothing before it. " +
984
- `After your required callback receipt, end the turn and wait passively for one addressed message containing ${initialPiWaitToken}. ` +
985
- "That message is the only authority to open the Codex sibling. Do not call shell, sleep, terminal, or any tool to wait.",
983
+ task: buildInitialPiBootstrapTask(initialPiWaitToken),
986
984
  });
987
985
  receipts["2-initial-pi-launch"] = initialPiLaunch.text;
988
986
  ok(
@@ -1440,25 +1438,33 @@ async function run(): Promise<void> {
1440
1438
  const observed = await readCodexThread(codexSocketPath, codexThreadId, true);
1441
1439
  return codexThreadIsTerminal(observed) ? observed : null;
1442
1440
  });
1441
+ // THE BOOTSTRAP ORDER, THEN THE ROLES — two leaves, composed. `[GLG 직접, 2026-10-24]` a fresh
1442
+ // sibling may check its caller with the read-only listing the framing offers, then calls back,
1443
+ // then works. `assertBootstrapOrder` admits only that prefix (successful exact `entwurf_peers`
1444
+ // checks), requires the exact callback once and successfully, and hands the rest to the
1445
+ // unchanged exact-once role audit — so `bash true` before the callback (the r3 RED) is still
1446
+ // refused, by the order leaf now rather than by a count. On Pi the receipts carry transcript
1447
+ // ordinals, so this also proves every peers RESULT was recorded before the callback was called,
1448
+ // and the callback RESULT before every task call.
1449
+ //
1450
+ // The callback is the ZERO-ARGUMENT eighth verb and `{}` is compared byte-exactly: a target, a
1451
+ // message or a `wants_reply` typed here is a parameter the verb does not have. Nothing is
1452
+ // weakened by the nonce not being in THIS expectation: the correlation is proven earlier and from
1453
+ // the delivered body — `the initial Pi callback carries the exact launch nonce` reads the
1454
+ // fixture's own mailbox, requires exactly one exact match and refuses duplicates, and the two
1455
+ // cells beside it bind that callback's sender envelope to a real pi V3 citizen.
1456
+ const initialPiSources = piSourceToolReceipts(readInitialPiEntries());
1457
+ const initialPiTaskCalls = assertBootstrapOrder(
1458
+ initialPiSources,
1459
+ // The guided task asked this Pi to take the offered check, so a zero-check PASS is not positive evidence.
1460
+ { peersArguments: {}, callback: { toolName: "entwurf_callback", arguments: {} }, minPeersPrefix: 1 },
1461
+ "initial Pi bootstrap order",
1462
+ );
1463
+ // The verified prefix count: the order leaf admitted exactly these many leading peers checks.
1464
+ const piPeersPrefix = initialPiSources.length - initialPiTaskCalls.length - 1;
1443
1465
  assertExactSourceToolCalls(
1444
- piSourceToolReceipts(readInitialPiEntries()),
1466
+ initialPiTaskCalls,
1445
1467
  [
1446
- // 0.24.0 moved a pi sibling's first action to the ZERO-ARGUMENT eighth verb, and this
1447
- // smoke's own launch receipt says so in the same run: "its first action is the
1448
- // zero-argument callback verb (codex is the one arm that still sends the nonce as
1449
- // arguments)". The Codex audit below therefore keeps its `entwurf_v2` shape, and only
1450
- // this one moves. `{}` is matched by deep-strict equality, so it pins the verb's actual
1451
- // contract — a target or message typed here would be a second address axis and the verb
1452
- // refuses it by name.
1453
- //
1454
- // Nothing is weakened by dropping the nonce from THIS expectation: the correlation is
1455
- // proven earlier and from the delivered body, which is where a nonce can actually be
1456
- // corrupted — `the initial Pi callback carries the exact launch nonce` (:970-973) reads
1457
- // the fixture's own mailbox, requires exactly one exact match and refuses duplicates,
1458
- // and the two cells beside it bind that callback's sender envelope to a real pi V3
1459
- // citizen. This audit's job is the different one its name says: exactly these calls,
1460
- // once each, and nothing else.
1461
- { toolName: "entwurf_callback", arguments: {} },
1462
1468
  {
1463
1469
  toolName: "entwurf_fresh_call",
1464
1470
  arguments: {
@@ -1480,10 +1486,18 @@ async function run(): Promise<void> {
1480
1486
  ],
1481
1487
  "initial Pi final exact-once audit",
1482
1488
  );
1483
- assertExactSourceToolCalls(
1484
- codexSourceToolReceipts(codexThread),
1485
- [
1486
- {
1489
+ // Codex: the same composition over the Entwurf MCP calls `thread/read` reports (this smoke's
1490
+ // Codex scope — no vendor-native action taxonomy). Its callback is `entwurf_v2` to the initial Pi
1491
+ // with the nonce, so `wants_reply` keeps its schema default there. `thread/read` gives item order
1492
+ // and a status at audit time only: on Codex this proves CALL ORDER and completion-at-audit; that a
1493
+ // peers result preceded the callback call, and that the callback result preceded the next call,
1494
+ // are both UNVERIFIED.
1495
+ const codexFinalSources = codexSourceToolReceipts(codexThread);
1496
+ const codexTaskCalls = assertBootstrapOrder(
1497
+ codexFinalSources,
1498
+ {
1499
+ peersArguments: {},
1500
+ callback: {
1487
1501
  toolName: "entwurf_v2",
1488
1502
  arguments: {
1489
1503
  target: initialPiGid,
@@ -1492,6 +1506,13 @@ async function run(): Promise<void> {
1492
1506
  message: codexNonce,
1493
1507
  },
1494
1508
  },
1509
+ },
1510
+ "Codex bootstrap order",
1511
+ );
1512
+ const codexPeersPrefix = codexFinalSources.length - codexTaskCalls.length - 1;
1513
+ assertExactSourceToolCalls(
1514
+ codexTaskCalls,
1515
+ [
1495
1516
  {
1496
1517
  toolName: "entwurf_fresh_call",
1497
1518
  arguments: { backend: "pi", model: piModel, task: outboundPiTask },
@@ -1508,7 +1529,9 @@ async function run(): Promise<void> {
1508
1529
  ],
1509
1530
  "Codex final exact-once audit",
1510
1531
  );
1511
- receipts["16-source-final-audit"] = "initial-pi=3/3 completed exact\ncodex=3/3 completed exact";
1532
+ receipts["16-source-final-audit"] =
1533
+ `initial-pi=bootstrap order (peers prefix=${piPeersPrefix} → callback, results-before-next-call by transcript ordinal) + 2/2 task roles exact\n` +
1534
+ `codex=bootstrap order (peers prefix=${codexPeersPrefix} → callback, call order only) + 2/2 task roles exact`;
1512
1535
  }
1513
1536
 
1514
1537
  let failure: unknown = null;
@@ -64,6 +64,7 @@ import {
64
64
  writeMetaSenderMarker,
65
65
  } from "../pi-extensions/lib/meta-session.ts";
66
66
  import { terminateChild } from "./lib/acp-child-cleanup.ts";
67
+ import { awaitTerminusPublication } from "./lib/chain-terminus-publication.ts";
67
68
  import { skipLive } from "./lib/live-skip.ts";
68
69
 
69
70
  const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -390,14 +391,11 @@ async function main(): Promise<void> {
390
391
 
391
392
  // ── the chain runs on its own from here; wait for the terminus ───────────
392
393
  const boxDir = path.join(mailboxDir, gidD);
393
- const until = Date.now() + CHAIN_TIMEOUT_MS;
394
- let msgs: string[] = [];
395
- while (Date.now() < until) {
396
- msgs = (await fsp.readdir(boxDir).catch(() => [] as string[])).filter(
397
- (f) => f.endsWith(".msg") || f.endsWith(".msg.delivered"),
398
- );
399
- if (msgs.length > 0) break;
400
- await sleep(POLL_MS);
394
+ // Body AND doorbell, inside the same deadline: enqueue publishes `.msg` first and
395
+ // `inbox.signal` last, so a body alone is a publication still in flight (P5 2026-10-24).
396
+ const { msgs, signalled } = await awaitTerminusPublication(boxDir, Date.now() + CHAIN_TIMEOUT_MS, POLL_MS);
397
+ if (msgs.length > 0 && !signalled) {
398
+ console.error(`[smoke-entwurf-chain-live] terminus at timeout: ${msgs.length} body file(s) but no inbox.signal`);
401
399
  }
402
400
  if (msgs.length === 0) {
403
401
  let ownerAlive = false;
@@ -423,7 +421,7 @@ async function main(): Promise<void> {
423
421
  console.error(`[smoke-entwurf-chain-live] C stream tail:\n${streamC.slice(-2000)}`);
424
422
  }
425
423
  ok(`the chain reached the mailbox terminus (${msgs.length} .msg)`, msgs.length === 1);
426
- ok("the terminus doorbell was poked", existsSync(path.join(boxDir, "inbox.signal")));
424
+ ok("the terminus doorbell was poked", signalled);
427
425
 
428
426
  const body = await fsp.readFile(path.join(boxDir, msgs[0] as string), "utf8");
429
427
 
@@ -45,7 +45,7 @@
45
45
  * fenced `XDG_CACHE_HOME`, joined to a session by its EXACT `sessionId` — plus
46
46
  * entwurf's own mailbox artifact and hook journal. It proves the public call, the
47
47
  * identity join, the delivered callback, the caller's continuation, the child's
48
- * FIRST action, and the codex named reject. It does NOT prove the receipt text or
48
+ * callback before any work (a read-only check may precede it), and the codex named reject. It does NOT prove the receipt text or
49
49
  * the final answer content: those have no source artifact on this axis and are
50
50
  * left to GLG's manual validation rather than substituted with a screen read.
51
51
  *
@@ -853,7 +853,7 @@ async function main(): Promise<void> {
853
853
  );
854
854
  }
855
855
 
856
- // ── the child's FIRST action was the callback ───────────────────────────────
856
+ // ── the child called back before its work ───────────────────────────────────
857
857
  const childNative = String(joined?.nativeSessionId ?? childRow?.sessionValue ?? "");
858
858
  if (cell.childKind === "pi") {
859
859
  const childRecords = transcriptRecords(String(childRow?.sessionValue ?? ""));
@@ -901,8 +901,8 @@ async function main(): Promise<void> {
901
901
  // BOTH. `[측정 2026-09-17, oracle, LIVE]` this cell used to require `order[0] ===
902
902
  // "entwurf_v2"`, and a Sonnet 5 child failed it by calling `entwurf_peers` first and
903
903
  // the callback second. That is not a violation: the framing this rail now sends
904
- // OFFERS exactly that corroboration ("you can corroborate the caller first if you
905
- // want to"), so the old oracle contradicted our own prompt and would have forbidden
904
+ // OFFERS exactly that corroboration ("you may corroborate the caller before that
905
+ // call"), so the old oracle contradicted our own prompt and would have forbidden
906
906
  // the behaviour we asked for. What still must hold is everything the claim was
907
907
  // actually about — the callback lands before any work, it completes, and neither the
908
908
  // callback verb nor the delivery verb failed or timed out anywhere in the log.