@junghanacs/entwurf 0.13.0 → 0.14.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 (193) hide show
  1. package/AGENTS.md +52 -17
  2. package/BASELINE.md +60 -206
  3. package/CHANGELOG.md +64 -0
  4. package/CONTRIBUTING.md +14 -10
  5. package/DELIVERY.md +118 -261
  6. package/README.md +91 -431
  7. package/VERIFY.md +74 -95
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +236 -0
  12. package/docs/external-mcp-host.md +131 -0
  13. package/docs/fresh-cut-policy.md +99 -0
  14. package/docs/setup-clean-host.md +124 -328
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  38. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  39. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  40. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  41. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  42. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  43. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  44. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  45. package/mcp/entwurf-bridge/src/index.ts +176 -67
  46. package/mcp/entwurf-bridge/start.sh +2 -2
  47. package/mcp/entwurf-bridge/test.sh +23 -9
  48. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  49. package/package.json +25 -11
  50. package/pi-extensions/entwurf-control.ts +220 -71
  51. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  52. package/pi-extensions/lib/acp/augment.ts +42 -3
  53. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  54. package/pi-extensions/lib/acp/backend.ts +366 -38
  55. package/pi-extensions/lib/acp/config.ts +1 -1
  56. package/pi-extensions/lib/acp/engraving.ts +43 -1
  57. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  58. package/pi-extensions/lib/acp/models.ts +4 -4
  59. package/pi-extensions/lib/acp/overlay.ts +11 -9
  60. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  61. package/pi-extensions/lib/entwurf-core.ts +15 -15
  62. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  63. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  64. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  65. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  66. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  67. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  68. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  69. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  70. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  71. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  72. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  73. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  74. package/pi-extensions/lib/meta-session.ts +93 -5
  75. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  76. package/pi-extensions/lib/mux-launch.ts +267 -0
  77. package/pi-extensions/lib/mux-placement.ts +387 -0
  78. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  79. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  80. package/pi-extensions/lib/session-id.js +8 -5
  81. package/pi-extensions/lib/socket-discovery.ts +3 -3
  82. package/prompts/engraving.md +13 -5
  83. package/run.sh +688 -282
  84. package/scripts/agy-bridge-config.py +5 -1
  85. package/scripts/check-acp-backend-preflight.ts +1 -1
  86. package/scripts/check-acp-carrier-augment.ts +246 -10
  87. package/scripts/check-acp-cortex.ts +5 -5
  88. package/scripts/check-acp-overlay.ts +13 -3
  89. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  90. package/scripts/check-acp-stop-reason.ts +342 -0
  91. package/scripts/check-acp-stream-hooks.ts +504 -0
  92. package/scripts/check-elapsed.sh +25 -0
  93. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  94. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  95. package/scripts/check-entwurf-control-rpc.ts +4 -3
  96. package/scripts/check-entwurf-resume-args.ts +72 -70
  97. package/scripts/check-entwurf-session-identity.ts +14 -10
  98. package/scripts/check-entwurf-v2-contract.ts +34 -59
  99. package/scripts/check-entwurf-v2-decider.ts +17 -177
  100. package/scripts/check-entwurf-v2-lock.ts +5 -2
  101. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  102. package/scripts/check-entwurf-v2-production.ts +2 -91
  103. package/scripts/check-entwurf-v2-release.ts +10 -105
  104. package/scripts/check-entwurf-v2-runner.ts +4 -85
  105. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  106. package/scripts/check-entwurf-v2-send.ts +0 -28
  107. package/scripts/check-entwurf-v2-surface.ts +198 -128
  108. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  109. package/scripts/check-fresh-cut-gate.sh +1 -1
  110. package/scripts/check-gate-qualification.ts +101 -7
  111. package/scripts/check-install-container.sh +10 -2
  112. package/scripts/check-install-surface.ts +1 -1
  113. package/scripts/check-keyset-overlap.py +1 -1
  114. package/scripts/check-meta-facts.ts +249 -0
  115. package/scripts/check-meta-identity-consumers.ts +1 -1
  116. package/scripts/check-meta-session.ts +169 -0
  117. package/scripts/check-mux-launch-tmux.ts +316 -0
  118. package/scripts/check-mux-launch.ts +288 -0
  119. package/scripts/check-mux-launcher-fence.ts +264 -0
  120. package/scripts/check-mux-parent-artifact.ts +195 -0
  121. package/scripts/check-mux-placement-tmux.ts +322 -0
  122. package/scripts/check-mux-placement.ts +323 -0
  123. package/scripts/check-mux-resume-call.ts +282 -0
  124. package/scripts/check-probe-cli-shim.ts +25 -22
  125. package/scripts/check-probe-ordering.ts +121 -81
  126. package/scripts/check-release-gate-outcomes.ts +452 -0
  127. package/scripts/check-resume-launch-identity.ts +244 -0
  128. package/scripts/check-socket-discovery.ts +1 -1
  129. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  130. package/scripts/inventory-verification-surface.ts +349 -0
  131. package/scripts/lib/claude-launcher-fence.ts +322 -0
  132. package/scripts/lib/live-skip.ts +33 -0
  133. package/scripts/lib/mutation-qualify.ts +109 -3
  134. package/scripts/lib/probe-acp-turn.ts +33 -12
  135. package/scripts/lib/step-outcome.sh +88 -0
  136. package/scripts/meta-bridge-doctor.sh +6 -8
  137. package/scripts/meta-facts.ts +60 -0
  138. package/scripts/mutants/acp-augment.json +106 -0
  139. package/scripts/mutants/acp-cortex.json +2 -2
  140. package/scripts/mutants/acp-overlay.json +17 -0
  141. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  142. package/scripts/mutants/acp-stop-reason.json +80 -0
  143. package/scripts/mutants/acp-stream-hooks.json +158 -0
  144. package/scripts/mutants/bridge-boot-resume.json +45 -0
  145. package/scripts/mutants/meta-facts.json +50 -0
  146. package/scripts/mutants/meta-identity.json +36 -0
  147. package/scripts/mutants/mux-boundary.json +196 -0
  148. package/scripts/mutants/mux-fresh-call.json +185 -0
  149. package/scripts/mutants/mux-launcher-fence.json +123 -0
  150. package/scripts/mutants/mux-parent-artifact.json +39 -0
  151. package/scripts/mutants/mux-resume-call.json +148 -0
  152. package/scripts/mutants/probe-ordering.json +6 -1021
  153. package/scripts/mutants/release-gate.json +140 -0
  154. package/scripts/mutants/resume-args.json +76 -0
  155. package/scripts/mutants/resume-launch-identity.json +96 -0
  156. package/scripts/mutants/v2-surface.json +74 -23
  157. package/scripts/mutants/v2-visible-resume.json +215 -0
  158. package/scripts/new-session-id.ts +9 -4
  159. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  160. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  161. package/scripts/smoke-acp-cortex-live.ts +8 -14
  162. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  163. package/scripts/smoke-acp-mcp-live.ts +2 -2
  164. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  165. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  166. package/scripts/smoke-acp-overlay-live.ts +2 -2
  167. package/scripts/smoke-acp-provider-live.ts +2 -2
  168. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  169. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  170. package/scripts/smoke-acp-skill-live.ts +2 -2
  171. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  172. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  173. package/scripts/smoke-agy-native-push-live.ts +10 -20
  174. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  175. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  176. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
  177. package/scripts/smoke-meta-honesty.sh +1 -1
  178. package/scripts/smoke-meta-install-state.sh +4 -0
  179. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  180. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  181. package/scripts/smoke-pi-attach.ts +1 -1
  182. package/scripts/smoke-user-scope-citizen.sh +1 -1
  183. package/scripts/tsconfig.json +1 -0
  184. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  185. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  186. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  187. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  188. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  189. package/scripts/check-acp-sdk-surface.ts +0 -236
  190. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  191. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  192. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  193. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * check-entwurf-v2-release — deterministic gate for the PURE release-policy reducer
3
3
  * (0.11 Stage 0 step 5c-1). Proves the Fable-3 "release-after-observation" timing as
4
- * a pure state machine BEFORE any spawn/send IO exists:
4
+ * a pure state machine BEFORE any send IO exists:
5
5
  *
6
6
  * 1. decideReleasePolicy maps each transport correctly and ENFORCES the lock
7
7
  * invariants (meta-mailbox ⇒ null else throw; in-domain ⇒ non-null AND
@@ -9,11 +9,6 @@
9
9
  * 2. no-lock policy NEVER releases — on any event.
10
10
  * 3. control-socket holds before send-final, releases EXACTLY ONCE on send-final
11
11
  * (every terminal outcome), holds after.
12
- * 4. spawn-bg: spawn-started ALONE does NOT release (the load-bearing rule).
13
- * 5. spawn-bg: first socket-alive releases.
14
- * 6. spawn-bg: first child-exited (any code: 0 / non-zero / null-signal) releases.
15
- * 7. spawn-bg: socket↔exit race is idempotent — one release regardless of order.
16
- * 8. spawn-bg: spawn-start-failed finalizes (no child to watch) with one release.
17
12
  * 9. single-release: after a release, no later event releases again.
18
13
  *
19
14
  * No IO — the reducer is pure; the gate folds event sequences and checks the
@@ -70,19 +65,6 @@ const CONTROL_PLAN: ExecutionPlan = {
70
65
  wantsReply: false,
71
66
  message: "m",
72
67
  };
73
- const SPAWN_PLAN: ExecutionPlan = {
74
- transport: "spawn-bg",
75
- action: "resume",
76
- targetGardenId: GID,
77
- sessionId: GID,
78
- cwd: "/home/junghan/repos/gh/entwurf",
79
- prompt: "p",
80
- wantsReply: false,
81
- launchArgs: ["--approve"],
82
- expectedSocketPath: "/fake/ctl/s.sock",
83
- observeTimeoutMs: 30_000,
84
- releaseWhen: "socket-alive-or-child-exited",
85
- };
86
68
 
87
69
  // Fold a sequence of events; return the shouldRelease flag emitted per event.
88
70
  function fold(policy: ReleasePolicy, events: ReleaseEvent[]): boolean[] {
@@ -111,10 +93,6 @@ ok(
111
93
  "policy: control-socket + lock → release-after-send-final",
112
94
  decideReleasePolicy(CONTROL_PLAN, lockClaim()).kind === "release-after-send-final",
113
95
  );
114
- ok(
115
- "policy: spawn-bg + lock → release-after-spawn-observation",
116
- decideReleasePolicy(SPAWN_PLAN, lockClaim()).kind === "release-after-spawn-observation",
117
- );
118
96
  ok(
119
97
  "policy: meta-mailbox + lock → throws (?7 violated)",
120
98
  throws(() => decideReleasePolicy(MAILBOX_PLAN, lockClaim())),
@@ -123,10 +101,6 @@ ok(
123
101
  "policy: control-socket + null → throws (must hold lock)",
124
102
  throws(() => decideReleasePolicy(CONTROL_PLAN, null)),
125
103
  );
126
- ok(
127
- "policy: spawn-bg + null → throws (must hold lock)",
128
- throws(() => decideReleasePolicy(SPAWN_PLAN, null)),
129
- );
130
104
  // in-domain lock whose gardenId ≠ plan target = mis-paired plan/lock (same grade as
131
105
  // a null lock — a later release would free a DIFFERENT gid). Fail loud.
132
106
  const WRONG_GID = "20260612T999999-bbbbbb";
@@ -134,21 +108,11 @@ ok(
134
108
  "policy: control-socket + mismatched lock gid → throws",
135
109
  throws(() => decideReleasePolicy(CONTROL_PLAN, { ...lockClaim(), gardenId: WRONG_GID })),
136
110
  );
137
- ok(
138
- "policy: spawn-bg + mismatched lock gid → throws",
139
- throws(() => decideReleasePolicy(SPAWN_PLAN, { ...lockClaim(), gardenId: WRONG_GID })),
140
- );
141
111
 
142
112
  // ── 2: no-lock NEVER releases ────────────────────────────────────────────────
143
113
  {
144
114
  const policy: ReleasePolicy = { kind: "no-lock" };
145
- const flags = fold(policy, [
146
- { kind: "mailbox-enqueued" },
147
- { kind: "send-final", outcome: "sent" },
148
- { kind: "socket-alive" },
149
- { kind: "child-exited", code: 0 },
150
- { kind: "spawn-started", pid: 1 },
151
- ]);
115
+ const flags = fold(policy, [{ kind: "mailbox-enqueued" }, { kind: "send-final", outcome: "sent" }]);
152
116
  ok(
153
117
  "no-lock: never releases on any event",
154
118
  flags.every((f) => f === false),
@@ -158,8 +122,6 @@ ok(
158
122
  // ── 3: control-socket holds before send-final, releases once, holds after ────
159
123
  {
160
124
  const policy: ReleasePolicy = { kind: "release-after-send-final" };
161
- ok("control: spawn-started before final → hold", fold(policy, [{ kind: "spawn-started", pid: 1 }])[0] === false);
162
- ok("control: socket-alive before final → hold", fold(policy, [{ kind: "socket-alive" }])[0] === false);
163
125
  for (const outcome of ["sent", "fallback-sent", "rejected", "failed"] as const) {
164
126
  const flags = fold(policy, [
165
127
  { kind: "send-final", outcome },
@@ -169,76 +131,19 @@ ok(
169
131
  }
170
132
  }
171
133
 
172
- // ── 4: spawn-bg — spawn-started ALONE does NOT release (load-bearing) ─────────
173
- {
174
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
175
- ok("spawn: spawn-started alone → NO release", fold(policy, [{ kind: "spawn-started", pid: 99 }])[0] === false);
176
- ok(
177
- "spawn: spawn-started then socket-alive → release only on observation",
178
- (() => {
179
- const f = fold(policy, [{ kind: "spawn-started", pid: 99 }, { kind: "socket-alive" }]);
180
- return f[0] === false && f[1] === true;
181
- })(),
182
- );
183
- }
184
-
185
- // ── 5: spawn-bg — first socket-alive releases ────────────────────────────────
186
- {
187
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
188
- ok("spawn: socket-alive releases", fold(policy, [{ kind: "socket-alive" }])[0] === true);
189
- }
190
-
191
- // ── 6: spawn-bg — first child-exited (any code) releases ─────────────────────
192
- {
193
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
194
- for (const code of [0, 1, 137, null] as const) {
195
- ok(`spawn: child-exited(code=${code}) releases`, fold(policy, [{ kind: "child-exited", code }])[0] === true);
196
- }
197
- }
198
-
199
- // ── 7: spawn-bg — socket↔exit race idempotent (one release, either order) ────
200
- {
201
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
202
- const socketThenExit = fold(policy, [{ kind: "socket-alive" }, { kind: "child-exited", code: 0 }]);
203
- ok(
204
- "spawn: socket-alive then child-exited → release only on first",
205
- socketThenExit[0] === true && socketThenExit[1] === false,
206
- );
207
- const exitThenSocket = fold(policy, [{ kind: "child-exited", code: 1 }, { kind: "socket-alive" }]);
208
- ok(
209
- "spawn: child-exited then socket-alive → release only on first",
210
- exitThenSocket[0] === true && exitThenSocket[1] === false,
211
- );
212
- }
213
-
214
- // ── 8: spawn-bg — spawn-start-failed finalizes (no child to watch) ───────────
215
- {
216
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
217
- ok(
218
- "spawn: spawn-start-failed releases (frees the gid, no child)",
219
- fold(policy, [{ kind: "spawn-start-failed", error: "ENOENT" }])[0] === true,
220
- );
221
- const startedThenFailedImpossibleButSafe = fold(policy, [
222
- { kind: "spawn-started", pid: 1 },
223
- { kind: "spawn-start-failed", error: "late" },
224
- ]);
225
- ok(
226
- "spawn: started(hold) then start-failed(release) — still one release",
227
- startedThenFailedImpossibleButSafe[0] === false && startedThenFailedImpossibleButSafe[1] === true,
228
- );
229
- }
230
-
231
134
  // ── 9: single-release — after release, no later event releases again ─────────
135
+ // Restated on the control-socket policy: the spawn observation events this used to fold
136
+ // went with their transport, but the at-most-once guarantee is the reducer's own and must
137
+ // still be proven against repeated terminal events.
232
138
  {
233
- const policy: ReleasePolicy = { kind: "release-after-spawn-observation" };
139
+ const policy: ReleasePolicy = { kind: "release-after-send-final" };
234
140
  const flags = fold(policy, [
235
- { kind: "socket-alive" },
236
- { kind: "child-exited", code: 0 },
237
- { kind: "child-exited", code: 1 },
238
- { kind: "socket-alive" },
141
+ { kind: "send-final", outcome: "sent" },
142
+ { kind: "send-final", outcome: "fallback-sent" },
143
+ { kind: "send-final", outcome: "failed" },
239
144
  ]);
240
145
  ok(
241
- "single-release: exactly one release across many triggers",
146
+ "single-release: exactly one release across many terminal events",
242
147
  flags.filter((f) => f).length === 1 && flags[0] === true,
243
148
  );
244
149
  }
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * check-entwurf-v2-runner — deterministic gate for the 5d-1 execute-router
3
3
  * (`executeDispatch`). It proves the DECISION→hand routing + the outcome-rich result
4
- * mapping over injected fake hands, with NO socket/spawn/timer:
4
+ * mapping over injected fake hands, with NO socket or timer:
5
5
  *
6
6
  * 1. reject decision → kind "rejected", receipt+diagnostic carried, NO hand called.
7
7
  * 2. control-socket execute → sendControl(plan, lock) called with the SAME plan + lock;
8
8
  * result outcome "sent" → executed{control-socket, outcome:"sent"}.
9
- * 3. spawn-bg execute → resumeSpawnBg(plan, lock); a `socket-alive` result rides
10
- * executed; a `lock-retained` result ALSO rides executed (fail-closed, not a failure).
11
9
  * 4. meta-mailbox execute → sendMailbox(plan, null) with the NULL lock (?7); success
12
10
  * → executed{meta-mailbox, success:true}.
13
11
  * 5. N3 — control rejected with rejectReason → executed{outcome:"rejected", rejectReason}
@@ -16,7 +14,7 @@
16
14
  * finalizedOutcome + releaseFailed:true + retrySafe:false.
17
15
  * 7. control hand throws a PLAIN transport error → execution-failed, retrySafe:false,
18
16
  * NO finalizedOutcome / releaseFailed (a failed send, lock already released).
19
- * 8. spawn / mailbox hand throws → execution-failed, retrySafe:false.
17
+ * 8. the mailbox hand throws → execution-failed, retrySafe:false.
20
18
  * 9. mailbox hand returns {success:false} → CONTRACT VIOLATION (a mailbox has no
21
19
  * in-band reject) → fail loud → execution-failed, NOT a silent success.
22
20
  * (exactly ONE hand runs per execute — the other two are never called — is asserted
@@ -26,7 +24,7 @@
26
24
  * over a FAKE `decide` (not the real decider — that is check-entwurf-v2-decider's job):
27
25
  * 10. decide called EXACTLY once, with the SAME input.
28
26
  * 11. reject decision → no executor hand called, the rejected result returned.
29
- * 12. execute decision → the matching executor hand ran (control/spawn/mailbox).
27
+ * 12. execute decision → the matching executor hand ran (control/mailbox).
30
28
  * 13. decide THROWS → propagates (no decision = no receipt to wrap).
31
29
  * 14. executeDispatch result returned VERBATIM (passthrough, no re-wrapping).
32
30
  *
@@ -57,7 +55,6 @@ import type {
57
55
  RpcSendResult,
58
56
  } from "../pi-extensions/lib/entwurf-v2-send.ts";
59
57
  import { SendDeliveredReleaseFailedError } from "../pi-extensions/lib/entwurf-v2-send.ts";
60
- import type { SpawnBgPlan, SpawnBgResumeResult } from "../pi-extensions/lib/entwurf-v2-spawn.ts";
61
58
 
62
59
  let passed = 0;
63
60
  function ok(label: string, cond: boolean): void {
@@ -89,19 +86,6 @@ const CONTROL_PLAN: ControlSocketPlan = {
89
86
  wantsReply: false,
90
87
  message: "m",
91
88
  };
92
- const SPAWN_PLAN: SpawnBgPlan = {
93
- transport: "spawn-bg",
94
- action: "resume",
95
- targetGardenId: GID,
96
- sessionId: GID,
97
- cwd: "/home/junghan/repos/gh/entwurf",
98
- prompt: "p",
99
- wantsReply: false,
100
- launchArgs: [],
101
- expectedSocketPath: "/fake/ctl/s.sock",
102
- observeTimeoutMs: 30_000,
103
- releaseWhen: "socket-alive-or-child-exited",
104
- };
105
89
  const MAILBOX_PLAN: MetaMailboxPlan = {
106
90
  transport: "meta-mailbox",
107
91
  action: "send",
@@ -138,14 +122,12 @@ function executeDecision(plan: ExecutionPlan, lock: LockClaim | null): Extract<D
138
122
  interface Trace {
139
123
  calls: string[];
140
124
  controlArgs?: { plan: ControlSocketPlan; lock: LockClaim | null };
141
- spawnArgs?: { plan: SpawnBgPlan; lock: LockClaim | null };
142
125
  mailboxArgs?: { plan: MetaMailboxPlan; lock: LockClaim | null };
143
126
  nativePushArgs?: { plan: NativePushPlan; lock: LockClaim | null };
144
127
  }
145
128
 
146
129
  interface FakeSpec {
147
130
  control?: { result: ControlSocketSendResult } | { throw: unknown };
148
- spawn?: { result: SpawnBgResumeResult } | { throw: unknown };
149
131
  mailbox?: { result: RpcSendResult } | { throw: unknown };
150
132
  nativePush?: { result: NativePushSendResult } | { throw: unknown };
151
133
  }
@@ -160,13 +142,6 @@ function makeDeps(spec: FakeSpec): { deps: DispatchExecutorDeps; trace: Trace }
160
142
  if ("throw" in spec.control) throw spec.control.throw;
161
143
  return spec.control.result;
162
144
  },
163
- async resumeSpawnBg(plan, lock) {
164
- trace.calls.push("resumeSpawnBg");
165
- trace.spawnArgs = { plan, lock };
166
- if (!spec.spawn) throw new Error("test: resumeSpawnBg called but no spec");
167
- if ("throw" in spec.spawn) throw spec.spawn.throw;
168
- return spec.spawn.result;
169
- },
170
145
  async sendMailbox(plan, lock) {
171
146
  trace.calls.push("sendMailbox");
172
147
  trace.mailboxArgs = { plan, lock };
@@ -224,41 +199,6 @@ async function main(): Promise<void> {
224
199
  ok("2: success receipt carried", res.kind === "executed" && res.receipt === SUCCESS_RECEIPT);
225
200
  }
226
201
 
227
- // ── 3: spawn-bg execute → socket-alive AND lock-retained both ride executed ─
228
- {
229
- const lock = lockClaim();
230
- const alive = makeDeps({ spawn: { result: { kind: "socket-alive", released: true, pid: 7 } } });
231
- const r1 = await executeDispatch(executeDecision(SPAWN_PLAN, lock), alive.deps);
232
- ok("3: spawn → only resumeSpawnBg ran", alive.trace.calls.length === 1 && alive.trace.calls[0] === "resumeSpawnBg");
233
- ok(
234
- "3: spawn plan + lock reach the hand",
235
- alive.trace.spawnArgs?.plan === SPAWN_PLAN && alive.trace.spawnArgs?.lock === lock,
236
- );
237
- ok(
238
- "3: socket-alive → executed{spawn-bg, socket-alive}",
239
- r1.kind === "executed" && r1.outcome.transport === "spawn-bg" && r1.outcome.result.kind === "socket-alive",
240
- );
241
-
242
- const retainedResult: SpawnBgResumeResult = {
243
- kind: "lock-retained",
244
- released: false,
245
- reason: "observe-failed",
246
- diagnostic: {
247
- targetGardenId: GID,
248
- lockPath: "/fake/locks/x.lock",
249
- expectedSocketPath: "/fake/ctl/s.sock",
250
- observeTimeoutMs: 30_000,
251
- killGraceMs: 5_000,
252
- },
253
- };
254
- const retained = makeDeps({ spawn: { result: retainedResult } });
255
- const r2 = await executeDispatch(executeDecision(SPAWN_PLAN, lock), retained.deps);
256
- ok(
257
- "3: lock-retained rides executed (fail-closed, not a failure)",
258
- r2.kind === "executed" && r2.outcome.transport === "spawn-bg" && r2.outcome.result.kind === "lock-retained",
259
- );
260
- }
261
-
262
202
  // ── 4: meta-mailbox execute → sendMailbox(plan, null), success ────────────
263
203
  {
264
204
  const { deps, trace } = makeDeps({ mailbox: { result: { success: true } } });
@@ -355,15 +295,8 @@ async function main(): Promise<void> {
355
295
  );
356
296
  }
357
297
 
358
- // ── 8: spawn / mailbox hand throw → execution-failed ──────────────────────
298
+ // ── 8: the mailbox hand throws → execution-failed ─────────────────────────
359
299
  {
360
- const s = makeDeps({ spawn: { throw: new Error("spawn boom") } });
361
- const rs = await executeDispatch(executeDecision(SPAWN_PLAN, lockClaim()), s.deps);
362
- ok(
363
- "8: spawn throw → execution-failed",
364
- rs.kind === "execution-failed" && rs.transport === "spawn-bg" && rs.retrySafe === false,
365
- );
366
-
367
300
  const m = makeDeps({ mailbox: { throw: new Error("enqueue boom") } });
368
301
  const rm = await executeDispatch(executeDecision(MAILBOX_PLAN, null), m.deps);
369
302
  ok(
@@ -444,20 +377,6 @@ async function main(): Promise<void> {
444
377
  rc.kind === "executed" &&
445
378
  rc.outcome.transport === "control-socket",
446
379
  );
447
-
448
- const spw = makeRunDeps(
449
- { decision: executeDecision(SPAWN_PLAN, lock) },
450
- { spawn: { result: { kind: "socket-alive", released: true, pid: 7 } } },
451
- );
452
- const rsp = await runEntwurfV2(DISPATCH_INPUT, spw.deps);
453
- ok(
454
- "12: spawn execute → resumeSpawnBg ran, executed{spawn-bg}",
455
- spw.trace.calls.length === 1 &&
456
- spw.trace.calls[0] === "resumeSpawnBg" &&
457
- rsp.kind === "executed" &&
458
- rsp.outcome.transport === "spawn-bg",
459
- );
460
-
461
380
  const mbx = makeRunDeps(
462
381
  { decision: executeDecision(MAILBOX_PLAN, null) },
463
382
  { mailbox: { result: { success: true } } },
@@ -14,14 +14,14 @@
14
14
  * 6. in-domain pi + socket-file + probe alive → control-socket plan with the INSPECTED
15
15
  * socketPath; message/mode/wantsReply preserved, same target gid.
16
16
  * 7. in-domain pi + absent (ENOENT → dead) → reject (dormant-fire-forget-unsupported);
17
- * NEVER a spawn-bg plan, NEVER a mailbox.
17
+ * NEVER a mailbox.
18
18
  * 8. in-domain pi + probe indeterminate → reject (indeterminate-no-spawn); no mailbox.
19
19
  * 9. in-domain pi + inspect indeterminate → reject; no probe connect, no mailbox.
20
20
  * 10. address-conflict (symlink) → reject (target-address-conflict).
21
21
  * 11. probe throw / inspect throw → PROPAGATE (resolver does not catch; the 5c-2a hand
22
22
  * owns failed+release). The resolver NEVER calls a release seam (it has none).
23
23
  * 12. every execute plan targets plan.targetGardenId === lock.gardenId, and is one of
24
- * control-socket / meta-mailbox — never spawn-bg.
24
+ * control-socket / meta-mailbox only.
25
25
  *
26
26
  * No real IO — fakes count inspect/probe calls so "short-circuits before probing" is
27
27
  * asserted structurally.
@@ -221,7 +221,7 @@ async function main(): Promise<void> {
221
221
  ok("pi + alive → inspected once, probed once", trace.inspectCalls === 1 && trace.probeCalls === 1);
222
222
  }
223
223
 
224
- // ── 7: in-domain pi + absent (dead) → reject, NEVER spawn-bg/mailbox ───────
224
+ // ── 7: in-domain pi + absent (dead) → reject, NEVER a mailbox enqueue ─────
225
225
  {
226
226
  const { deps, trace } = makeDeps({
227
227
  resolution: present(identity("pi")),
@@ -324,7 +324,7 @@ async function main(): Promise<void> {
324
324
  ok("inspect throw → propagates", e2 === inspectBoom);
325
325
  }
326
326
 
327
- // ── 12: every execute plan targets the held gid, never spawn-bg ───────────
327
+ // ── 12: every execute plan targets the held gid ───────────────────────────
328
328
  {
329
329
  const cases: FakeSpec[] = [
330
330
  { resolution: present(identity("claude-code")), mailboxDeliverable: true },
@@ -340,9 +340,8 @@ async function main(): Promise<void> {
340
340
  const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
341
341
  if (r.kind !== "execute") allSameTargetNonSpawn = false;
342
342
  else if (r.plan.targetGardenId !== GID) allSameTargetNonSpawn = false;
343
- else if (r.plan.transport === "spawn-bg") allSameTargetNonSpawn = false;
344
343
  }
345
- ok("every execute plan: same target gid, never spawn-bg", allSameTargetNonSpawn);
344
+ ok("every execute plan: same target gid (a SEND fallback never re-targets)", allSameTargetNonSpawn);
346
345
  }
347
346
 
348
347
  console.log(`\n[check-entwurf-v2-send-fallback] ${passed} assertions ok`);
@@ -23,8 +23,6 @@
23
23
  * 10. masking guard: on a `failed` send the ORIGINAL send error is rethrown even if
24
24
  * releaseLock itself throws (a release failure must not mask the send failure).
25
25
  * 11. lock invariants: null lock / mismatched-gid lock → throws (decideReleasePolicy).
26
- * 12. contract: a re-resolve into a spawn-bg plan for a SEND → throws AND still
27
- * releases the held lock ×1 (lock-leak backstop, not a leak).
28
26
  * 13. lock-leak backstop: any UNEXPECTED dep throw after the lock is held
29
27
  * (classifyConnect) still releases ×1 and rethrows the original error.
30
28
  * 14. mis-route: a re-resolve returning a DIFFERENT-target control plan fails loud
@@ -95,20 +93,6 @@ const MAILBOX_PLAN = {
95
93
  message: "m",
96
94
  } as const satisfies Extract<ExecutionPlan, { transport: "meta-mailbox" }>;
97
95
 
98
- const SPAWN_PLAN = {
99
- transport: "spawn-bg",
100
- action: "resume",
101
- targetGardenId: GID,
102
- sessionId: GID,
103
- cwd: "/home/junghan/repos/gh/entwurf",
104
- prompt: "p",
105
- wantsReply: false,
106
- launchArgs: [],
107
- expectedSocketPath: "/fake/ctl/s.sock",
108
- observeTimeoutMs: 30_000,
109
- releaseWhen: "socket-alive-or-child-exited",
110
- } as const satisfies Extract<ExecutionPlan, { transport: "spawn-bg" }>;
111
-
112
96
  // A connect-time error with a real `.code` (drives the F3 classifier the way a node
113
97
  // net error would). dead = ECONNREFUSED/ENOENT; everything else = indeterminate.
114
98
  function connError(code: string): NodeJS.ErrnoException {
@@ -351,18 +335,6 @@ async function main(): Promise<void> {
351
335
  ok("mismatched-gid lock → throws (mis-paired plan/lock)", mismatchErr instanceof Error);
352
336
  }
353
337
 
354
- // ── 12: contract — re-resolve into a spawn-bg plan for a SEND → throws,
355
- // AND the held lock is STILL released (lock-leak backstop) ───────────
356
- {
357
- const { deps, trace } = makeDeps({
358
- firstSend: { throwCode: "ECONNREFUSED" },
359
- deadFallback: { kind: "execute", plan: SPAWN_PLAN },
360
- });
361
- const err = await rejects(() => executeControlSocketSend(CONTROL_PLAN, lockClaim(), deps));
362
- ok("re-resolve → spawn-bg for a send → throws (contract violation)", err instanceof Error);
363
- ok("re-resolve → spawn-bg → lock STILL released ×1 (no leak)", trace.releases.length === 1);
364
- }
365
-
366
338
  // ── 13: lock-leak backstop — an UNEXPECTED dep throw (classifyConnect) after
367
339
  // the lock is held still releases ×1 and rethrows the original error ──
368
340
  {