@junghanacs/entwurf 0.41.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/AGENTS.md +2 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +344 -0
  4. package/DELIVERY.md +37 -20
  5. package/ENTWURF.json +236 -0
  6. package/README.md +75 -65
  7. package/VERIFY.md +9 -9
  8. package/docs/external-mcp-host.md +1 -1
  9. package/docs/setup-clean-host.md +59 -33
  10. package/mcp/entwurf-bridge/dist/ENTWURF.json +236 -0
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +32 -190
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/agent-guidance.js +299 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +23 -24
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +15 -11
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +29 -24
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +18 -16
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +101 -32
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  29. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +23 -45
  30. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  31. package/mcp/entwurf-bridge/src/index.ts +126 -321
  32. package/native/win32-x64/entwurf_win.node +0 -0
  33. package/native/win32-x64/manifest.json +2 -2
  34. package/package.json +5 -4
  35. package/pi-extensions/lib/agent-guidance.ts +341 -0
  36. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  37. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  38. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  39. package/pi-extensions/lib/entwurf-v2-surface.ts +27 -30
  40. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +15 -16
  41. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  42. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  43. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  44. package/pi-extensions/lib/mux-fresh-call.ts +29 -30
  45. package/pi-extensions/lib/mux-resume-call.ts +18 -19
  46. package/pi-extensions/lib/native-push/register.ts +17 -2
  47. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  48. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  49. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  50. package/pi-extensions/lib/remote-pair-send.ts +124 -34
  51. package/pi-extensions/meta-bridge-omp.ts +69 -7
  52. package/pi-extensions/meta-bridge-pi-durable.ts +23 -64
  53. package/run.sh +182 -22
  54. package/scripts/build-bridge.sh +10 -0
  55. package/scripts/check-agy-sender-identity.ts +6 -4
  56. package/scripts/check-capability-bundle-reach.ts +86 -0
  57. package/scripts/check-entwurf-self-address.ts +37 -6
  58. package/scripts/check-entwurf-v2-contract.ts +24 -2
  59. package/scripts/check-entwurf-v2-production.ts +45 -31
  60. package/scripts/check-entwurf-v2-surface.ts +94 -98
  61. package/scripts/check-gate-qualification.ts +17 -13
  62. package/scripts/check-mux-resume-call.ts +20 -3
  63. package/scripts/check-native-push-adapter.ts +4 -0
  64. package/scripts/check-native-push-register.ts +32 -82
  65. package/scripts/check-remote-pair-installed.ts +2 -2
  66. package/scripts/dev-bin.sh +112 -1
  67. package/scripts/lib/chain-terminus-publication.ts +58 -0
  68. package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
  69. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  70. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  71. package/scripts/mutants/agent-guidance.json +160 -0
  72. package/scripts/mutants/bridge-boot-resume.json +6 -8
  73. package/scripts/mutants/capability-bundle-reach.json +124 -0
  74. package/scripts/mutants/chain-terminus-publication.json +17 -0
  75. package/scripts/mutants/codex-native.json +98 -0
  76. package/scripts/mutants/control-send-receipt.json +23 -6
  77. package/scripts/mutants/mux-fresh-call.json +30 -4
  78. package/scripts/mutants/mux-resume-call.json +28 -3
  79. package/scripts/mutants/native-push-paused.json +17 -0
  80. package/scripts/mutants/omp-fresh.json +13 -2
  81. package/scripts/mutants/pack-install.json +46 -0
  82. package/scripts/mutants/pi-durable.json +17 -5
  83. package/scripts/mutants/remote-pair-display.json +3 -3
  84. package/scripts/mutants/remote-pair-receive.json +41 -11
  85. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  86. package/scripts/mutants/remote-pair-send.json +177 -19
  87. package/scripts/mutants/remote-pair-transport.json +4 -4
  88. package/scripts/mutants/self-address.json +10 -10
  89. package/scripts/mutants/source-install.json +66 -0
  90. package/scripts/mutants/v2-surface.json +50 -64
  91. package/scripts/mutants/v2-visible-resume.json +11 -0
  92. package/scripts/remote-pair-receive.ts +12 -12
  93. package/scripts/smoke-agy-install-state.sh +121 -0
  94. package/scripts/smoke-agy-native-push-live.ts +13 -0
  95. package/scripts/smoke-codex-fresh-live.ts +50 -27
  96. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  97. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  98. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  99. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  100. package/scripts/smoke-omp-fresh-live.ts +5 -4
  101. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  102. package/scripts/smoke-setup-verdict.sh +27 -2
@@ -5,13 +5,12 @@
5
5
  {
6
6
  "claim": "V2SURF-RESUME-HINT-SCOPE",
7
7
  "title": "the dormant capability hint returns to a Pi-only automatic-resume invitation",
8
- "subject": "pi-extensions/lib/entwurf-v2-surface.ts",
8
+ "subject": "ENTWURF.json",
9
9
  "find": [
10
- "\t\t\t\t\"Only on an explicit operator request, use entwurf_resume_call {target} for Pi or Claude Code; \" +",
11
- "\t\t\t\t\"it submits no prompt. This capability hint is not permission to resume automatically.\""
10
+ "Only on an explicit operator request, use entwurf_resume_call {target} for Pi or Claude Code; it submits no prompt. This capability hint is not permission to resume automatically."
12
11
  ],
13
12
  "replace": [
14
- "\t\t\t\t\"Reopen the citizen with entwurf_resume_call {target} (pi targets only; it runs no turn), then dispatch again.\""
13
+ "Reopen the citizen with entwurf_resume_call {target} (pi targets only; it runs no turn), then dispatch again."
15
14
  ],
16
15
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
17
16
  "timeoutSeconds": 60,
@@ -21,12 +20,11 @@
21
20
  {
22
21
  "claim": "V2SURF-MCP-LOCK-DOMAIN",
23
22
  "title": "MCP long description reverts to the false \"socket paths take a per-target lock\" claim",
24
- "subject": "mcp/entwurf-bridge/src/index.ts",
23
+ "subject": "ENTWURF.json",
25
24
  "find": [
26
- "\t\t\"LOCK: taken for a control-socket-DOMAIN dispatch. The \" +",
27
- "\t\t\"mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode \" +"
25
+ "LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode "
28
26
  ],
29
- "replace": ["\t\t\"LOCK POLICY: socket paths take a per-target lock; the mailbox path does not. mode \" +"],
27
+ "replace": ["LOCK POLICY: socket paths take a per-target lock; the mailbox path does not. mode "],
30
28
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
31
29
  "timeoutSeconds": 60,
32
30
  "signature": "[QK:V2SURF-MCP-LOCK-DOMAIN]",
@@ -57,15 +55,9 @@
57
55
  {
58
56
  "claim": "V2SURF-MODE-SCOPE",
59
57
  "title": "the bridge mode param (the one every caller reads, pi included since #125) reverts to \"Delivery mode for a live send\" (false for native-push) (#120 P2: the description was rewritten to stop claiming an interrupt, so the mutation now strips the rail scope from the corrected sentence)",
60
- "subject": "mcp/entwurf-bridge/src/index.ts",
61
- "find": [
62
- "\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. \" +",
63
- "\t\t\t\t\t\"The mailbox and native-push plans carry no mode, so it has no effect on those rails.\","
64
- ],
65
- "replace": [
66
- "\t\t\t\t\"Delivery mode for a live send: steer (ask the steering queue) or follow_up (ask the follow-up queue). \" +",
67
- "\t\t\t\t\t\"Both queues are volatile process memory.\","
68
- ],
58
+ "subject": "ENTWURF.json",
59
+ "find": ["Injection style for a CONTROL-SOCKET send only:"],
60
+ "replace": ["Delivery mode for a live send:"],
69
61
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
70
62
  "timeoutSeconds": 60,
71
63
  "signature": "[QK:V2SURF-MODE-SCOPE]",
@@ -74,19 +66,11 @@
74
66
  {
75
67
  "claim": "V2SURF-INBOX-SCOPE-HONESTY",
76
68
  "title": "entwurf_inbox_read reverts to the false \"drains your own inbox\" access claim (SCOPE paragraph gone)",
77
- "subject": "mcp/entwurf-bridge/src/index.ts",
69
+ "subject": "ENTWURF.json",
78
70
  "find": [
79
- "\t\t\"doorbell is only a wake attempt, not a read. An empty inbox mutates nothing. \" +",
80
- "\t\t\"SCOPE — read this literally: the garden id is CALLER-SUPPLIED and is NOT verified against \" +",
81
- "\t\t\"your own identity (a host with no garden record of its own can call this too). So passing \" +",
82
- "\t\t\"another citizen's garden id drains THEIR mail and stamps THEIR receipt, and they will never \" +",
83
- "\t\t\"see those messages. Pass only the id from your own doorbell notice or your own meta-record; \" +",
84
- "\t\t\"use entwurf_self if you need to confirm which id that is. \" +"
85
- ],
86
- "replace": [
87
- "\t\t\"doorbell is only a wake attempt, not a read. An empty inbox mutates nothing. \" +",
88
- "\t\t\"This tool drains your own meta-bridge inbox and stamps the read receipt on your meta-record. \" +"
71
+ "SCOPE — read this literally: the garden id is CALLER-SUPPLIED and is NOT verified against your own identity"
89
72
  ],
73
+ "replace": ["This tool drains your own meta-bridge inbox and stamps the read receipt on your meta-record"],
90
74
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
91
75
  "timeoutSeconds": 60,
92
76
  "signature": "[QK:V2SURF-INBOX-SCOPE-HONESTY]",
@@ -95,15 +79,12 @@
95
79
  {
96
80
  "claim": "V2SURF-DORMANT-HONESTY",
97
81
  "title": "MCP long description advertises owned-outcome as a selectable dormant verb again and drops the reason a dormant target actually returns",
98
- "subject": "mcp/entwurf-bridge/src/index.ts",
82
+ "subject": "ENTWURF.json",
99
83
  "find": [
100
- "\t\t\"DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported \" +",
101
- "\t\t\"— same receiver rule as the mailbox, no active drainer means no delivery. \" +",
102
- "\t\t\"The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child \" +",
103
- "\t\t\"and was withdrawn under the visible-first rule — re-open the session yourself, then dispatch again. \" +"
84
+ "A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
104
85
  ],
105
86
  "replace": [
106
- "\t\t\"owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY and is never auto-converted. \" +"
87
+ "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY and is never auto-converted. "
107
88
  ],
108
89
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
109
90
  "timeoutSeconds": 60,
@@ -113,45 +94,35 @@
113
94
  {
114
95
  "claim": "V2SURF-INDETERMINATE-NO-START",
115
96
  "title": "the indeterminate hint reverts to a socket that \"answered\" and drops the no-start facts — overclaiming a reply that a permission-refused or timed-out probe never got",
116
- "subject": "pi-extensions/lib/entwurf-v2-surface.ts",
97
+ "subject": "ENTWURF.json",
117
98
  "find": [
118
- "\t\tcase \"indeterminate-no-spawn\":",
119
- "\t\t\t// The socket probe did not settle, so liveness is UNKNOWN — an unestablished probe",
120
- "\t\t\t// is not a measured death, and dispatching into it could double-deliver. Say",
121
- "\t\t\t// \"the PROBE was inconclusive\", never \"the socket answered inconclusively\":",
122
- "\t\t\t// `indeterminate` also covers a probe that got no answer at all, was refused by",
123
- "\t\t\t// permissions, or timed out, and \"answered\" claims a reply that may never have",
124
- "\t\t\t// existed. The wire id is FROZEN and still spells \"-no-spawn\"; it names the rule",
125
- "\t\t\t// (never dispatch into an indeterminate target), not a capability that still",
126
- "\t\t\t// exists. So the hint's job is to say plainly what did NOT happen: nothing",
127
- "\t\t\t// delivered, nothing started.",
128
- "\t\t\treturn (",
129
- "\t\t\t\t\"the control-socket probe was inconclusive, so the target's liveness is UNKNOWN — this is not a \" +",
130
- "\t\t\t\t\"measured death. NOTHING was delivered and NO process was started (the reason id keeps its frozen \" +",
131
- "\t\t\t\t\"'-no-spawn' wire spelling from an era when one could be; entwurf_v2 starts nothing on any rail). \" +",
132
- "\t\t\t\t\"Re-run entwurf_peers to re-probe; if it stays indeterminate, check for a stale socket file left at \" +",
133
- "\t\t\t\t\"that garden id by a session that died without cleaning up.\"",
134
- "\t\t\t);"
135
- ],
136
- "replace": [
137
- "\t\tcase \"indeterminate-no-spawn\":",
138
- "\t\t\treturn \"the target's control socket answered inconclusively; retry.\";"
99
+ "the control-socket probe was inconclusive, so the target's liveness is UNKNOWN — this is not a measured death. NOTHING was delivered and NO process was started (the reason id keeps its frozen '-no-spawn' wire spelling from an era when one could be; entwurf_v2 starts nothing on any rail). Re-run entwurf_peers to re-probe; if it stays indeterminate, check for a stale socket file left at that garden id by a session that died without cleaning up."
139
100
  ],
101
+ "replace": ["the target's control socket answered inconclusively; retry."],
140
102
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
141
103
  "timeoutSeconds": 60,
142
104
  "signature": "[QK:V2SURF-INDETERMINATE-NO-START]",
143
105
  "signatureSource": "scripts/check-entwurf-v2-surface.ts"
144
106
  },
107
+ {
108
+ "claim": "V2SURF-REJECT-HINTS-FROM-ROOT",
109
+ "title": "the surface stops returning the Root hint for one declared reason, so a native-push caller loses its next step while the Root text itself still looks perfect",
110
+ "subject": "pi-extensions/lib/entwurf-v2-surface.ts",
111
+ "find": ["\t\t\treturn ENTWURF_REJECT_HINTS[\"native-push-probe-indeterminate\"];"],
112
+ "replace": ["\t\t\treturn undefined;"],
113
+ "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
114
+ "timeoutSeconds": 60,
115
+ "signature": "[QK:V2SURF-REJECT-HINTS-FROM-ROOT]",
116
+ "signatureSource": "scripts/check-entwurf-v2-surface.ts"
117
+ },
145
118
  {
146
119
  "claim": "V2SURF-PEERS-DEAD-ROW",
147
120
  "title": "entwurf_peers drops the dead-row sentence, so a dormant citizen is listed with nothing saying it is unreachable",
148
- "subject": "mcp/entwurf-bridge/src/index.ts",
121
+ "subject": "ENTWURF.json",
149
122
  "find": [
150
- "\t\t\"row. A `dead` row is a REPORTED FACT and nothing more: that citizen is dormant and is \" +",
151
- "\t\t\"currently unreachable by any verb, so listing it grants no action — appearing here is not \" +",
152
- "\t\t\"an invitation to dispatch. In particular `unsupported` does NOT mean mailbox: it means this backend has no \" +"
123
+ "row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt. In particular"
153
124
  ],
154
- "replace": ["\t\t\"row. In particular `unsupported` does NOT mean mailbox: it means this backend has no \" +"],
125
+ "replace": ["row. In particular"],
155
126
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
156
127
  "timeoutSeconds": 60,
157
128
  "signature": "[QK:V2SURF-PEERS-DEAD-ROW]",
@@ -160,12 +131,12 @@
160
131
  {
161
132
  "claim": "V2SURF-DESC-FITS-HOST-CAP",
162
133
  "title": "the v2 tool description grows back past the host's 2048-char cap — the INTENT contract is silently cut off before the model that must choose an intent ever reads it",
163
- "subject": "mcp/entwurf-bridge/src/index.ts",
134
+ "subject": "ENTWURF.json",
164
135
  "find": [
165
- "\t\"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id \" +"
136
+ "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id "
166
137
  ],
167
138
  "replace": [
168
- "\t\"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +\n\t\t\"PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING \" +"
139
+ "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING "
169
140
  ],
170
141
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
171
142
  "timeoutSeconds": 60,
@@ -223,6 +194,21 @@
223
194
  "timeoutSeconds": 120,
224
195
  "signature": "[QK:NATIVE-PUSH-RETRY-BYTE-IDENTITY]",
225
196
  "signatureSource": "pi-extensions/lib/entwurf-v2-native-push.test.ts"
197
+ },
198
+ {
199
+ "claim": "V2SURF-RECORD-LESS-SOCKET-HINT-FROM-ROOT",
200
+ "title": "the record-less-socket hint returns a hand-kept copy of its text, one byte off the Root segments, instead of composing them with the owner's command: every phrase the 2C cell checks still reads, so only the whole-string comparison against Root can see the drift",
201
+ "subject": "pi-extensions/lib/entwurf-v2-surface.ts",
202
+ "find": [
203
+ "\t\t\treturn `${hint.beforeFreshCutPrescription}${FRESH_CUT_PRESCRIPTION}${hint.afterFreshCutPrescription}`;"
204
+ ],
205
+ "replace": [
206
+ "\t\t\treturn `a control socket exists at this garden id but NO meta-record claims it — the record is the sole address authority, so a bare socket is not an addressable citizen. If a pre-record-era resident owns that socket, restart it under the current runtime (session_start births its record); if the store predates the current generation, quiesce and archive it with ${FRESH_CUT_PRESCRIPTION}. A stale/forged socket should be removed.`;"
207
+ ],
208
+ "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
209
+ "timeoutSeconds": 60,
210
+ "signature": "[QK:V2SURF-RECORD-LESS-SOCKET-HINT-FROM-ROOT]",
211
+ "signatureSource": "scripts/check-entwurf-v2-surface.ts"
226
212
  }
227
213
  ]
228
214
  }
@@ -491,6 +491,17 @@
491
491
  "timeoutSeconds": 60,
492
492
  "signature": "[QK:V2RESUME-CC-MARKER-BINDING-READ]",
493
493
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
494
+ },
495
+ {
496
+ "claim": "V2RESUME-REJECT-HINTS-FROM-ROOT",
497
+ "title": "one static rejection hint is re-authored as a code literal instead of the root ENTWURF.json text, so the reader stays exact while the rendered rejection no longer carries it",
498
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
499
+ "find": ["\t\"target-locked\": ENTWURF_RESUME_REJECT_HINTS[\"target-locked\"],"],
500
+ "replace": ["\t\"target-locked\": \"another resume or dispatch holds this citizen's lock\","],
501
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
502
+ "timeoutSeconds": 60,
503
+ "signature": "[QK:V2RESUME-REJECT-HINTS-FROM-ROOT]",
504
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
494
505
  }
495
506
  ]
496
507
  }
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * remote-pair-receive — `entwurf remote-pair-receive --pair <absolute file>` (#107 rail 1).
3
+ * remote-pair-receive — `entwurf remote-pair-receive --pair <absolute file>` (#107 rail 1, #149).
4
4
  *
5
- * The receiving host's entry for one declared remote pair. The sending host runs it over one
5
+ * The receiving host's entry for its declared remote pairs. The sending host runs it over one
6
6
  * SSH call and writes one JSON claim to stdin. This entry reads the operator-named pair
7
- * declaration once, applies the pair scope check, and hands a matching claim to the existing
8
- * v2 dispatch. The scope and trust model are in pi-extensions/lib/remote-pair-receive.ts.
7
+ * declaration once, selects the one route whose two ends are the claim's, and hands a matching
8
+ * claim to the existing v2 dispatch. The scope and trust model are in pi-extensions/lib/remote-pair-receive.ts.
9
9
  *
10
10
  * Scope: `--pair` must name THIS host's own opt-in file (`remotePairOptInPath()`), absolute and
11
- * normalized-equal; any other path — even a valid declaration — is exit 2. That one file is the pair
12
- * this host accepts and, because it names the sender's host as its route, the declared route back.
11
+ * normalized-equal; any other path — even a valid declaration — is exit 2. That one file holds the
12
+ * pairs this host accepts and, because each route names the sender's host, the declared route back.
13
13
  * The sender's declaration carries the path only so the argv shape is explicit; it selects nothing.
14
14
  *
15
15
  * EXIT CONTRACT (stdout carries a JSON receipt line only where named):
@@ -47,7 +47,7 @@ if (args.length !== 2 || args[0] !== "--pair") {
47
47
  }
48
48
  const pairFile = args[1];
49
49
 
50
- let decl: RemotePairDeclaration;
50
+ let decls: RemotePairDeclaration[];
51
51
  let claim: ReturnType<typeof parseRemoteClaim>;
52
52
  try {
53
53
  // The scope is THIS host's own opt-in, never a file the sender picks: `--pair` must name it
@@ -56,17 +56,17 @@ try {
56
56
  if (!path.isAbsolute(pairFile) || path.normalize(pairFile) !== own) {
57
57
  throw new Error(`--pair must be this host's opt-in file ${own} (got ${pairFile})`);
58
58
  }
59
- const route = readRemotePairOptIn(own);
60
- if (route === null) throw new Error(`this host has no remote pair opt-in (${own} is absent)`);
61
- decl = { remoteGardenId: route.remoteGardenId, ssh: route.ssh, localGardenId: route.localGardenId };
59
+ const optIn = readRemotePairOptIn(own);
60
+ if (optIn === null) throw new Error(`this host has no remote pair opt-in (${own} is absent)`);
61
+ decls = optIn.routes.map((r) => ({ remoteGardenId: r.remoteGardenId, ssh: r.ssh, localGardenId: r.localGardenId }));
62
62
  claim = parseRemoteClaim(readFileSync(0, "utf8"));
63
63
  } catch (err) {
64
64
  emit({ kind: "invalid", detail: err instanceof Error ? err.message : String(err) }, 2);
65
65
  }
66
66
 
67
- const receipt = await receiveRemoteClaim(claim, decl, {
67
+ const receipt = await receiveRemoteClaim(claim, decls, {
68
68
  dispatch: (params, sender) => runAndRenderEntwurfV2FromSurface(params, { senderProvider: () => sender }),
69
- // The scope file IS this host's own opt-in, and it names the sender's host as its route: a
69
+ // The scope file IS this host's own opt-in, and the matched route names the sender's host: a
70
70
  // matching claim always has a declared route back. Declared, not probed.
71
71
  replyRoute: "declared",
72
72
  });
@@ -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