@junghanacs/entwurf 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/BASELINE.md +60 -5
- package/CHANGELOG.md +557 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +52 -2
- package/FAQ.md +8 -4
- package/README.md +37 -17
- package/VERIFY.md +23 -10
- package/docs/acp-backend-rail.md +5 -1
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +64 -9
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +100 -24
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +10 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +123 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +259 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js +80 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +65 -0
- package/mcp/entwurf-bridge/src/index.ts +118 -24
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/mcp/entwurf-bridge/tsconfig.build.json +17 -1
- package/package.json +5 -5
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/pi-extensions/lib/cc-retire-request.ts +553 -0
- package/pi-extensions/lib/control-retire.ts +195 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
- package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
- package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
- package/pi-extensions/lib/fresh-call-composition.ts +15 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/meta-mailbox-body.ts +13 -4
- package/pi-extensions/lib/meta-session.ts +140 -16
- package/pi-extensions/lib/mux-fresh-call.ts +7 -27
- package/pi-extensions/lib/mux-launch.ts +29 -0
- package/pi-extensions/lib/mux-resume-call.ts +33 -12
- package/pi-extensions/lib/pi-retire-request.ts +111 -0
- package/pi-extensions/lib/remote-pair-receive.ts +184 -0
- package/pi-extensions/lib/remote-pair-send.ts +325 -0
- package/pi-extensions/lib/remote-pair-transport.ts +81 -0
- package/pi-extensions/lib/resume-launch-identity.ts +104 -18
- package/pi-extensions/lib/retirement-event.ts +253 -0
- package/pi-extensions/meta-bridge-hook.ts +142 -13
- package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
- package/run.sh +122 -7
- package/scripts/agy-bridge-config.py +4 -1
- package/scripts/build-bridge.sh +14 -0
- package/scripts/check-capability-bundle-reach.ts +135 -2
- package/scripts/check-entwurf-bridge-boot.ts +2 -1
- package/scripts/check-entwurf-fact-provider.ts +1 -0
- package/scripts/check-entwurf-v2-production.ts +2 -0
- package/scripts/check-entwurf-v2-surface.ts +7 -0
- package/scripts/check-gate-qualification.ts +20 -8
- package/scripts/check-herdr-activation.ts +2 -2
- package/scripts/check-herdr-placement.ts +1 -0
- package/scripts/check-hook-launch-topology.ts +6 -15
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-meta-doctor-oracle.sh +27 -1
- package/scripts/check-meta-facts.ts +8 -3
- package/scripts/check-meta-hook-session-switch.ts +230 -13
- package/scripts/check-meta-identity-consumers.ts +1 -0
- package/scripts/check-meta-listing.ts +1 -0
- package/scripts/check-meta-manifest-schema.py +1 -1
- package/scripts/check-meta-session.ts +31 -0
- package/scripts/check-meta-v3-record.ts +55 -1
- package/scripts/check-mux-resume-call.ts +35 -9
- package/scripts/check-peer-facts.ts +1 -0
- package/scripts/check-pi-durable-contact.ts +3 -1
- package/scripts/check-pi-durable-send.ts +4 -2
- package/scripts/check-pi-mcp-bridge.ts +1 -0
- package/scripts/check-remote-pair-installed.ts +242 -0
- package/scripts/check-resume-launch-identity.ts +62 -23
- package/scripts/lib/meta-hook-bundle.ts +72 -0
- package/scripts/lib/remote-pair-installed-audit.ts +25 -0
- package/scripts/meta-bridge-claude-floor.sh +13 -9
- package/scripts/meta-bridge-doctor.sh +17 -0
- package/scripts/meta-bridge-install.sh +6 -0
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/bridge-boot-resume.json +5 -2
- package/scripts/mutants/cc-retire-pidfd.json +129 -0
- package/scripts/mutants/cc-retire-request.json +171 -0
- package/scripts/mutants/control-retire.json +105 -0
- package/scripts/mutants/control-send-receipt.json +2 -2
- package/scripts/mutants/copilot-receive.json +2 -6
- package/scripts/mutants/entwurf-v2-retire.json +476 -0
- package/scripts/mutants/fresh-call-dispatch.json +37 -0
- package/scripts/mutants/meta-hook-session-switch.json +88 -0
- package/scripts/mutants/meta-identity.json +57 -0
- package/scripts/mutants/mux-fresh-call.json +1 -1
- package/scripts/mutants/mux-resume-call.json +40 -4
- package/scripts/mutants/pi-durable.json +79 -0
- package/scripts/mutants/pi-retire-request.json +72 -0
- package/scripts/mutants/remote-pair-display.json +53 -0
- package/scripts/mutants/remote-pair-installed-audit.json +28 -0
- package/scripts/mutants/remote-pair-receive.json +126 -0
- package/scripts/mutants/remote-pair-roundtrip.json +83 -0
- package/scripts/mutants/remote-pair-send.json +194 -0
- package/scripts/mutants/remote-pair-transport.json +66 -0
- package/scripts/mutants/resume-launch-identity.json +35 -8
- package/scripts/mutants/retirement-event.json +195 -0
- package/scripts/mutants/v2-surface.json +16 -0
- package/scripts/mutants/v2-visible-resume.json +283 -13
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/remote-pair-receive.ts +73 -0
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +5 -4
- package/scripts/smoke-meta-install-state.sh +4 -4
- package/scripts/smoke-mux-lifecycle-live.ts +10 -10
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
- package/scripts/smoke-pi-provider-state.sh +3 -3
|
@@ -73,8 +73,16 @@
|
|
|
73
73
|
"claim": "V2RESUME-OBSERVE-IS-BOUNDED-WAIT",
|
|
74
74
|
"title": "the observation becomes a single immediate probe, which reports every real resume as unobserved (the socket answers ~2-4s later)",
|
|
75
75
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
76
|
-
"find": [
|
|
77
|
-
|
|
76
|
+
"find": [
|
|
77
|
+
"\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };",
|
|
78
|
+
"\t\t}",
|
|
79
|
+
"\t\tawait deps.sleep(intervalMs);"
|
|
80
|
+
],
|
|
81
|
+
"replace": [
|
|
82
|
+
"\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };",
|
|
83
|
+
"\t\t}",
|
|
84
|
+
"\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
|
|
85
|
+
],
|
|
78
86
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
79
87
|
"timeoutSeconds": 60,
|
|
80
88
|
"signature": "[QK:V2RESUME-OBSERVE-IS-BOUNDED-WAIT]",
|
|
@@ -106,8 +114,8 @@
|
|
|
106
114
|
"claim": "V2RESUME-ARGV-FROM-RECORD",
|
|
107
115
|
"title": "the launcher is handed the caller's own directory instead of the record's, so the citizen returns in the wrong project",
|
|
108
116
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
109
|
-
"find": ["\t\tconst launched = deps.launch({ cwd: identity.cwd, runtimeArgs });"],
|
|
110
|
-
"replace": ["\t\tconst launched = deps.launch({ cwd: process.cwd(), runtimeArgs });"],
|
|
117
|
+
"find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
|
|
118
|
+
"replace": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: process.cwd(), runtimeArgs });"],
|
|
111
119
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
112
120
|
"timeoutSeconds": 60,
|
|
113
121
|
"signature": "[QK:V2RESUME-ARGV-FROM-RECORD]",
|
|
@@ -117,9 +125,11 @@
|
|
|
117
125
|
"claim": "V2RESUME-RECEIPTS-SEPARATE",
|
|
118
126
|
"title": "the launch receipt absorbs the observation, so 'a window opened' and 'the citizen is back' become one claim",
|
|
119
127
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
120
|
-
"find": [
|
|
128
|
+
"find": [
|
|
129
|
+
"\t\t\tlaunch: { backend: \"pi\", targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle },"
|
|
130
|
+
],
|
|
121
131
|
"replace": [
|
|
122
|
-
"\t\t\tlaunch: { targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle, kind: observation.kind } as ResumeLaunchReceipt,"
|
|
132
|
+
"\t\t\tlaunch: { backend: \"pi\", targetGardenId, sessionFile: identity.sessionFile, handle: launched.handle, kind: observation.kind } as ResumeLaunchReceipt,"
|
|
123
133
|
],
|
|
124
134
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
125
135
|
"timeoutSeconds": 60,
|
|
@@ -156,7 +166,8 @@
|
|
|
156
166
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
157
167
|
"find": ["\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"],
|
|
158
168
|
"replace": [
|
|
159
|
-
"\t\t\t(deps as VisibleResumeDeps).launch({
|
|
169
|
+
"\t\t\t(deps as VisibleResumeDeps).launch({ backend: \"pi\", cwd: \"/tmp\", runtimeArgs: [] });",
|
|
170
|
+
"\t\t\treturn { kind: \"resume-unobserved\", socketPath, waitedMs: deps.now() - started };"
|
|
160
171
|
],
|
|
161
172
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
162
173
|
"timeoutSeconds": 60,
|
|
@@ -175,29 +186,29 @@
|
|
|
175
186
|
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
176
187
|
},
|
|
177
188
|
{
|
|
178
|
-
"claim": "V2RESUME-
|
|
189
|
+
"claim": "V2RESUME-BACKEND-UNSUPPORTED-REFUSED",
|
|
179
190
|
"title": "an out-of-domain backend is reported as an error instead of a named refusal, so a fine record reads as a broken one",
|
|
180
191
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
181
192
|
"find": ["\t\t\tif (err instanceof ResumeBackendUnsupportedError) {"],
|
|
182
193
|
"replace": ["\t\t\tif (false && err instanceof ResumeBackendUnsupportedError) {"],
|
|
183
194
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
184
195
|
"timeoutSeconds": 60,
|
|
185
|
-
"signature": "[QK:V2RESUME-
|
|
196
|
+
"signature": "[QK:V2RESUME-BACKEND-UNSUPPORTED-REFUSED]",
|
|
186
197
|
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
187
198
|
},
|
|
188
199
|
{
|
|
189
|
-
"claim": "V2RESUME-
|
|
200
|
+
"claim": "V2RESUME-BACKEND-UNSUPPORTED-BY-FIELD",
|
|
190
201
|
"title": "the backend is re-derived instead of taken from the typed error, which is the second read this rail refuses",
|
|
191
202
|
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
192
203
|
"find": [
|
|
193
|
-
"\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi
|
|
204
|
+
"\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi and Claude Code citizens can be reopened ` +"
|
|
194
205
|
],
|
|
195
206
|
"replace": [
|
|
196
|
-
"\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi
|
|
207
|
+
"\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi and Claude Code citizens can be reopened ` +"
|
|
197
208
|
],
|
|
198
209
|
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
199
210
|
"timeoutSeconds": 60,
|
|
200
|
-
"signature": "[QK:V2RESUME-
|
|
211
|
+
"signature": "[QK:V2RESUME-BACKEND-UNSUPPORTED-BY-FIELD]",
|
|
201
212
|
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
202
213
|
},
|
|
203
214
|
{
|
|
@@ -221,6 +232,265 @@
|
|
|
221
232
|
"timeoutSeconds": 60,
|
|
222
233
|
"signature": "[QK:V2RESUME-ACP-MODEL-REFUSED]",
|
|
223
234
|
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"claim": "V2RESUME-CC-DEAD-OWNER-LAUNCHES",
|
|
238
|
+
"title": "a Claude Code resume is launched as `claude --continue`, which reopens whichever session the vendor picks rather than the one the record names",
|
|
239
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
240
|
+
"find": ["\t\truntimeArgs: [\"--resume\", current.nativeSessionId],"],
|
|
241
|
+
"replace": ["\t\truntimeArgs: [\"--continue\"],"],
|
|
242
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
243
|
+
"timeoutSeconds": 60,
|
|
244
|
+
"signature": "[QK:V2RESUME-CC-DEAD-OWNER-LAUNCHES]",
|
|
245
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"claim": "V2RESUME-CC-LIVE-REFUSED",
|
|
249
|
+
"title": "a live Claude Code owner stops being refused as live, so the caller is told to wait on an inconclusive probe instead of sending to the running session",
|
|
250
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
251
|
+
"find": ["\tif (before.owner === \"live\") {"],
|
|
252
|
+
"replace": ["\tif (before.owner === (\"never\" as string)) {"],
|
|
253
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
254
|
+
"timeoutSeconds": 60,
|
|
255
|
+
"signature": "[QK:V2RESUME-CC-LIVE-REFUSED]",
|
|
256
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"claim": "V2RESUME-CC-UNCERTAIN-REFUSED",
|
|
260
|
+
"title": "an owner the shared rule cannot judge is treated as dormant, which can open a second process on a session that is still running",
|
|
261
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
262
|
+
"find": ["\tif (before.owner !== \"dead\") {"],
|
|
263
|
+
"replace": ["\tif (before.owner === (\"never\" as string)) {"],
|
|
264
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
265
|
+
"timeoutSeconds": 60,
|
|
266
|
+
"signature": "[QK:V2RESUME-CC-UNCERTAIN-REFUSED]",
|
|
267
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"claim": "V2RESUME-CC-ABSENT-REFUSED",
|
|
271
|
+
"title": "a Claude Code citizen with no receiver marker is not refused by name, so a session nothing proves dormant falls through the dormancy proof",
|
|
272
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
273
|
+
"find": ["\tif (before.kind === \"absent\") {"],
|
|
274
|
+
"replace": ["\tif (before.kind === (\"never\" as string)) {"],
|
|
275
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
276
|
+
"timeoutSeconds": 60,
|
|
277
|
+
"signature": "[QK:V2RESUME-CC-ABSENT-REFUSED]",
|
|
278
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"claim": "V2RESUME-CC-CORRUPT-REFUSED",
|
|
282
|
+
"title": "an unreadable receiver marker is not refused by name, so a marker whose owner cannot be judged falls through the dormancy proof",
|
|
283
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
284
|
+
"find": ["\tif (before.kind === \"corrupt\") {"],
|
|
285
|
+
"replace": ["\tif (before.kind === (\"never\" as string)) {"],
|
|
286
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
287
|
+
"timeoutSeconds": 60,
|
|
288
|
+
"signature": "[QK:V2RESUME-CC-CORRUPT-REFUSED]",
|
|
289
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"claim": "V2RESUME-CC-MISMATCH-REFUSED",
|
|
293
|
+
"title": "a dead marker for ANOTHER native session is accepted as this session's dormancy proof",
|
|
294
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
295
|
+
"find": ["\t\tmarker.backend !== \"claude-code\" ||", "\t\tmarker.nativeSessionId !== identity.nativeSessionId"],
|
|
296
|
+
"replace": ["\t\tmarker.backend !== \"claude-code\" ||", "\t\tfalse"],
|
|
297
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
298
|
+
"timeoutSeconds": 60,
|
|
299
|
+
"signature": "[QK:V2RESUME-CC-MISMATCH-REFUSED]",
|
|
300
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"claim": "V2RESUME-CC-OBSERVES-NEW-OWNER",
|
|
304
|
+
"title": "the observation accepts the previous owner's marker as the resumed session coming back",
|
|
305
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
306
|
+
"find": [
|
|
307
|
+
"\t\t\t(seen.marker.ownerPid !== previous.ownerPid || seen.marker.ownerStartKey !== previous.ownerStartKey) &&"
|
|
308
|
+
],
|
|
309
|
+
"replace": ["\t\t\ttrue &&"],
|
|
310
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
311
|
+
"timeoutSeconds": 60,
|
|
312
|
+
"signature": "[QK:V2RESUME-CC-OBSERVES-NEW-OWNER]",
|
|
313
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"claim": "V2RESUME-CC-RECORD-DRIFT-UNOBSERVED",
|
|
317
|
+
"title": "the observation stops checking that the record still names the same session with the same createdAt",
|
|
318
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
319
|
+
"find": ["\t\t\t\tcurrent.createdAt === identity.createdAt &&"],
|
|
320
|
+
"replace": ["\t\t\t\ttrue &&"],
|
|
321
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
322
|
+
"timeoutSeconds": 60,
|
|
323
|
+
"signature": "[QK:V2RESUME-CC-RECORD-DRIFT-UNOBSERVED]",
|
|
324
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"claim": "V2RESUME-CC-MARKER-READ-RAW",
|
|
328
|
+
"title": "the receiver marker is read through a path that follows symlinks instead of the no-follow fd reader, so a link to another citizen's marker answers for this one",
|
|
329
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
330
|
+
"find": ["\t\tbytes = readStoreRecordFile(markerPath);"],
|
|
331
|
+
"replace": ["\t\tbytes = process.getBuiltinModule(\"node:fs\").readFileSync(markerPath, \"utf8\");"],
|
|
332
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
333
|
+
"timeoutSeconds": 60,
|
|
334
|
+
"signature": "[QK:V2RESUME-CC-MARKER-READ-RAW]",
|
|
335
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"claim": "V2RESUME-PI-NEVER-READS-RECEIVER",
|
|
339
|
+
"title": "the pi path consults a Claude Code receiver marker, mixing the two backends' dormancy proofs",
|
|
340
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
341
|
+
"find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
|
|
342
|
+
"replace": [
|
|
343
|
+
"\t\tdeps.inspectReceiver(targetGardenId);",
|
|
344
|
+
"\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"
|
|
345
|
+
],
|
|
346
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
347
|
+
"timeoutSeconds": 60,
|
|
348
|
+
"signature": "[QK:V2RESUME-PI-NEVER-READS-RECEIVER]",
|
|
349
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"claim": "V2RESUME-CC-RENDER",
|
|
353
|
+
"title": "the Claude Code launch receipt stops saying it asked for claude --resume, so a reader cannot tell which session the window was asked to open",
|
|
354
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
355
|
+
"find": [
|
|
356
|
+
"\t\t\t\t`LAUNCH receipt: tmux created that window and was asked to start claude --resume on the session above.\\n` +"
|
|
357
|
+
],
|
|
358
|
+
"replace": ["\t\t\t\t`LAUNCH receipt: tmux created that window.\\n` +"],
|
|
359
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
360
|
+
"timeoutSeconds": 60,
|
|
361
|
+
"signature": "[QK:V2RESUME-CC-RENDER]",
|
|
362
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"claim": "V2RESUME-CC-OBSERVE-HELPER-BOUNDED",
|
|
366
|
+
"title": "the Claude Code observation's deadline reports the previous owner as re-armed instead of unobserved",
|
|
367
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
368
|
+
"find": ["\t\t\treturn { kind: \"resume-unobserved\", waitedMs: deps.now() - started };"],
|
|
369
|
+
"replace": [
|
|
370
|
+
"\t\t\treturn { kind: \"receiver-armed\", ownerPid: previous.ownerPid, waitedMs: deps.now() - started };"
|
|
371
|
+
],
|
|
372
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
373
|
+
"timeoutSeconds": 60,
|
|
374
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-HELPER-BOUNDED]",
|
|
375
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"claim": "V2RESUME-CC-BINDING-REREAD",
|
|
379
|
+
"title": "the binding is compared against the FIRST record read, so an arm committed between that read and the dead verdict is bound by a stale value",
|
|
380
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
381
|
+
"find": ["\tconst current = deps.resolveIdentity(targetGardenId);"],
|
|
382
|
+
"replace": ["\tconst current = identity;"],
|
|
383
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
384
|
+
"timeoutSeconds": 60,
|
|
385
|
+
"signature": "[QK:V2RESUME-CC-BINDING-REREAD]",
|
|
386
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"claim": "V2RESUME-CC-UNBOUND-LEGACY",
|
|
390
|
+
"title": "a record or marker with no binding (null) passes the binding check, so null == null reads as a match",
|
|
391
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
392
|
+
"find": ["\tif (current.attachIncarnation === null || marker.attachIncarnation === null) {"],
|
|
393
|
+
"replace": ["\tif (false) {"],
|
|
394
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
395
|
+
"timeoutSeconds": 60,
|
|
396
|
+
"signature": "[QK:V2RESUME-CC-UNBOUND-LEGACY]",
|
|
397
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"claim": "V2RESUME-CC-UNBOUND-STALE",
|
|
401
|
+
"title": "a dead marker whose binding is older than the record's still authorises a launch",
|
|
402
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
403
|
+
"find": ["\tif (current.attachIncarnation !== marker.attachIncarnation) {"],
|
|
404
|
+
"replace": ["\tif (false) {"],
|
|
405
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
406
|
+
"timeoutSeconds": 60,
|
|
407
|
+
"signature": "[QK:V2RESUME-CC-UNBOUND-STALE]",
|
|
408
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"claim": "V2RESUME-CC-UNBOUND-RECORD-MOVED",
|
|
412
|
+
"title": "a record whose createdAt changed between the two reads is still bound",
|
|
413
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
414
|
+
"find": ["\t\tcurrent.createdAt !== identity.createdAt"],
|
|
415
|
+
"replace": ["\t\tfalse"],
|
|
416
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
417
|
+
"timeoutSeconds": 60,
|
|
418
|
+
"signature": "[QK:V2RESUME-CC-UNBOUND-RECORD-MOVED]",
|
|
419
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"claim": "V2RESUME-CC-OBSERVE-PANE-JOIN",
|
|
423
|
+
"title": "the observation accepts a live owner anywhere, not only the pane this call opened",
|
|
424
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
425
|
+
"find": ["\t\t\tString(seen.marker.ownerPid) === panePid &&"],
|
|
426
|
+
"replace": [""],
|
|
427
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
428
|
+
"timeoutSeconds": 60,
|
|
429
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-PANE-JOIN]",
|
|
430
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"claim": "V2RESUME-CC-OBSERVE-BACKEND",
|
|
434
|
+
"title": "the observation accepts a live receiver marker of another backend",
|
|
435
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
436
|
+
"find": ["\t\t\tseen.marker.backend === \"claude-code\" &&"],
|
|
437
|
+
"replace": [""],
|
|
438
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
439
|
+
"timeoutSeconds": 60,
|
|
440
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-BACKEND]",
|
|
441
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"claim": "V2RESUME-CC-OBSERVE-FRESH-BINDING",
|
|
445
|
+
"title": "the observation accepts a new owner still showing the binding the dormancy proof bound",
|
|
446
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
447
|
+
"find": ["\t\t\tseen.marker.attachIncarnation !== previous.attachIncarnation"],
|
|
448
|
+
"replace": ["\t\t\ttrue"],
|
|
449
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
450
|
+
"timeoutSeconds": 60,
|
|
451
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-FRESH-BINDING]",
|
|
452
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"claim": "V2RESUME-CC-OBSERVE-RECORD-BINDING",
|
|
456
|
+
"title": "the observation stops requiring the current record to carry the marker's binding",
|
|
457
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
458
|
+
"find": ["\t\t\t\tcurrent.attachIncarnation === seen.marker.attachIncarnation"],
|
|
459
|
+
"replace": ["\t\t\t\ttrue"],
|
|
460
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
461
|
+
"timeoutSeconds": 60,
|
|
462
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-RECORD-BINDING]",
|
|
463
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"claim": "V2RESUME-CC-OBSERVE-RECORD-UNREADABLE",
|
|
467
|
+
"title": "a record defect during the observation is swallowed into the bounded wait instead of stopping it as record-unreadable",
|
|
468
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
469
|
+
"find": [
|
|
470
|
+
"\t\t\t\treturn {",
|
|
471
|
+
"\t\t\t\t\tkind: \"record-unreadable\",",
|
|
472
|
+
"\t\t\t\t\tcause: err instanceof Error ? err.message : String(err),",
|
|
473
|
+
"\t\t\t\t\twaitedMs: deps.now() - started,",
|
|
474
|
+
"\t\t\t\t};"
|
|
475
|
+
],
|
|
476
|
+
"replace": ["\t\t\t\tcurrent = identity;"],
|
|
477
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
478
|
+
"timeoutSeconds": 60,
|
|
479
|
+
"signature": "[QK:V2RESUME-CC-OBSERVE-RECORD-UNREADABLE]",
|
|
480
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"claim": "V2RESUME-CC-MARKER-BINDING-READ",
|
|
484
|
+
"title": "the receiver-marker validator drops the marker's binding, so every marker reads as unbound",
|
|
485
|
+
"subject": "pi-extensions/lib/meta-session.ts",
|
|
486
|
+
"find": [
|
|
487
|
+
"\t\tattachIncarnation: requireAttachIncarnation(raw.attachIncarnation === undefined ? null : raw.attachIncarnation),"
|
|
488
|
+
],
|
|
489
|
+
"replace": ["\t\tattachIncarnation: null,"],
|
|
490
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
491
|
+
"timeoutSeconds": 60,
|
|
492
|
+
"signature": "[QK:V2RESUME-CC-MARKER-BINDING-READ]",
|
|
493
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
224
494
|
}
|
|
225
495
|
]
|
|
226
496
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* remote-pair-receive — `entwurf remote-pair-receive --pair <absolute file>` (#107 rail 1).
|
|
4
|
+
*
|
|
5
|
+
* The receiving host's entry for one declared remote pair. The sending host runs it over one
|
|
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.
|
|
9
|
+
*
|
|
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.
|
|
13
|
+
* The sender's declaration carries the path only so the argv shape is explicit; it selects nothing.
|
|
14
|
+
*
|
|
15
|
+
* EXIT CONTRACT (stdout carries a JSON receipt line only where named):
|
|
16
|
+
* 0 dispatched, the dispatch did not report an error — receipt on stdout
|
|
17
|
+
* 4 dispatched, the dispatch reported an error (its text says what is known, e.g. a
|
|
18
|
+
* rejection or `retry-safe: false` after a possible write) — receipt
|
|
19
|
+
* 3 refused by the pair scope check, no dispatch ran — receipt
|
|
20
|
+
* 2 --pair is not this host's opt-in, the opt-in is absent/broken, or the claim is invalid; no
|
|
21
|
+
* dispatch ran — `{kind:"invalid"}` receipt;
|
|
22
|
+
* wrong argv is ALSO 2 but prints usage on stderr only, with no receipt
|
|
23
|
+
* 1 an uncaught dispatch/store throw (crash, don't warn) — no receipt; stderr carries the
|
|
24
|
+
* error. The sending host reads that as indeterminate.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
import * as path from "node:path";
|
|
29
|
+
import { runAndRenderEntwurfV2FromSurface } from "../pi-extensions/lib/entwurf-v2-surface.ts";
|
|
30
|
+
import {
|
|
31
|
+
parseRemoteClaim,
|
|
32
|
+
type RemotePairDeclaration,
|
|
33
|
+
type RemoteReceiveReceipt,
|
|
34
|
+
receiveRemoteClaim,
|
|
35
|
+
} from "../pi-extensions/lib/remote-pair-receive.ts";
|
|
36
|
+
import { readRemotePairOptIn, remotePairOptInPath } from "../pi-extensions/lib/remote-pair-send.ts";
|
|
37
|
+
|
|
38
|
+
function emit(receipt: RemoteReceiveReceipt | { kind: "invalid"; detail: string }, code: number): never {
|
|
39
|
+
process.stdout.write(`${JSON.stringify(receipt)}\n`);
|
|
40
|
+
process.exit(code);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
if (args.length !== 2 || args[0] !== "--pair") {
|
|
45
|
+
console.error("usage: entwurf remote-pair-receive --pair <this host's opt-in file> (claim JSON on stdin)");
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
const pairFile = args[1];
|
|
49
|
+
|
|
50
|
+
let decl: RemotePairDeclaration;
|
|
51
|
+
let claim: ReturnType<typeof parseRemoteClaim>;
|
|
52
|
+
try {
|
|
53
|
+
// The scope is THIS host's own opt-in, never a file the sender picks: `--pair` must name it
|
|
54
|
+
// exactly (absolute, normalized), and that one file is read for this receive.
|
|
55
|
+
const own = remotePairOptInPath();
|
|
56
|
+
if (!path.isAbsolute(pairFile) || path.normalize(pairFile) !== own) {
|
|
57
|
+
throw new Error(`--pair must be this host's opt-in file ${own} (got ${pairFile})`);
|
|
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 };
|
|
62
|
+
claim = parseRemoteClaim(readFileSync(0, "utf8"));
|
|
63
|
+
} catch (err) {
|
|
64
|
+
emit({ kind: "invalid", detail: err instanceof Error ? err.message : String(err) }, 2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const receipt = await receiveRemoteClaim(claim, decl, {
|
|
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
|
|
70
|
+
// matching claim always has a declared route back. Declared, not probed.
|
|
71
|
+
replyRoute: "declared",
|
|
72
|
+
});
|
|
73
|
+
emit(receipt, receipt.kind === "rejected" ? 3 : receipt.isError ? 4 : 0);
|
|
@@ -67,7 +67,7 @@ write_mcp_fake() { # $1 = path
|
|
|
67
67
|
while IFS= read -r line; do
|
|
68
68
|
case "$line" in
|
|
69
69
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
70
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
70
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
71
71
|
esac
|
|
72
72
|
done
|
|
73
73
|
FAKE
|
|
@@ -89,7 +89,7 @@ if [ "${REVIEW_ENV:-}" = "fail" ]; then echo configured-env-failure >&2; exit 24
|
|
|
89
89
|
while IFS= read -r line; do
|
|
90
90
|
case "$line" in
|
|
91
91
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
92
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
92
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
93
93
|
esac
|
|
94
94
|
done
|
|
95
95
|
FAKE
|
|
@@ -26,7 +26,7 @@ write_mcp_fake() {
|
|
|
26
26
|
while IFS= read -r line; do
|
|
27
27
|
case "$line" in
|
|
28
28
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
29
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
29
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
30
30
|
esac
|
|
31
31
|
done
|
|
32
32
|
FAKE
|
|
@@ -490,8 +490,8 @@ for (const name of ["entwurf", "entwurf-bridge", "entwurf-statusline"]) {
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
// The REAL subcommand, from the installed bin, under node_modules — which is the branch of
|
|
493
|
-
// start.sh that runs the prebuilt dist. Its own oracle is an EXACT set (`
|
|
494
|
-
// zero exit here is the artifact listing all
|
|
493
|
+
// start.sh that runs the prebuilt dist. Its own oracle is an EXACT set (`expected` in run.sh `validate_entwurf_bridge`, which check-bridge runs), so a
|
|
494
|
+
// zero exit here is the artifact listing all nine garden verbs and no tenth.
|
|
495
495
|
const run = spawnSync(path.join(binDir, "entwurf"), ["check-bridge"], { encoding: "utf8" });
|
|
496
496
|
const said = `${run.stdout ?? ""}${run.stderr ?? ""}`;
|
|
497
497
|
// [a-z0-9_] and not [a-z_]: the first draft of this line printed `entwurf_v` for entwurf_v2
|
|
@@ -502,7 +502,7 @@ const verbs = (said.match(/entwurf_[a-z0-9_]+/g) ?? []).filter((v, i, a) => a.in
|
|
|
502
502
|
// A SECOND, INDEPENDENT COPY OF THE SET, ON PURPOSE (A5 D1). The exit code alone used to be
|
|
503
503
|
// the oracle here, which left the printed list as decoration a parser bug could quietly
|
|
504
504
|
// corrupt — and did, see the regex note just above. The package under test carries the
|
|
505
|
-
// expectation of run.sh
|
|
505
|
+
// `validate_entwurf_bridge` expectation of run.sh in its own bytes, so reading the answer from there would let
|
|
506
506
|
// the artifact grade itself. The literal below is a copy that must AGREE with the shipped
|
|
507
507
|
// one; the two disagreeing is the signal, and a verb genuinely added upstream reddens this
|
|
508
508
|
// smoke until somebody says so here too.
|
|
@@ -513,6 +513,7 @@ const EXPECTED_VERBS = [
|
|
|
513
513
|
"entwurf_peers",
|
|
514
514
|
"entwurf_register_native",
|
|
515
515
|
"entwurf_resume_call",
|
|
516
|
+
"entwurf_retire_call",
|
|
516
517
|
"entwurf_self",
|
|
517
518
|
"entwurf_v2",
|
|
518
519
|
];
|
|
@@ -520,7 +521,7 @@ console.log(` VERBS ${verbs.join(",") || "<none>"}`);
|
|
|
520
521
|
if (run.status === 0) ok(`entwurf check-bridge exit 0 from the installed bin (${verbs.length} verbs listed)`);
|
|
521
522
|
else no(`entwurf check-bridge exit ${run.status}: ${said.trim().slice(0, 400)}`);
|
|
522
523
|
JSON.stringify(verbs) === JSON.stringify(EXPECTED_VERBS)
|
|
523
|
-
? ok(`the receipt names EXACTLY the
|
|
524
|
+
? ok(`the receipt names EXACTLY the nine garden verbs (${EXPECTED_VERBS.length}), parsed from what the installed bin answered`)
|
|
524
525
|
: no(`verb set mismatch — want ${JSON.stringify(EXPECTED_VERBS)} got ${JSON.stringify(verbs)}`);
|
|
525
526
|
process.exit(bad);
|
|
526
527
|
' "$ACTIVE" "$REGISTRY" "$PLUGIN_ID" || fail=1
|
|
@@ -46,7 +46,7 @@ cat > "$INS_BIN/claude" <<'SH'
|
|
|
46
46
|
#!/usr/bin/env bash
|
|
47
47
|
printf '%s\n' "$*" >> "$FAKE_CLAUDE_LOG"
|
|
48
48
|
case "$1${2:+ $2}" in
|
|
49
|
-
"--version") echo "2.1.
|
|
49
|
+
"--version") echo "2.1.306 (Claude Code)" ;;
|
|
50
50
|
"plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
|
|
51
51
|
"mcp get") printf '%s\n' "Scope: User config" "Status: Connected" ;;
|
|
52
52
|
*) : ;;
|
|
@@ -95,7 +95,7 @@ case "$1${2:+ $2}" in
|
|
|
95
95
|
# Keep writing well past the pipe buffer: a reader that leaves early SIGPIPEs us.
|
|
96
96
|
cat "$FAKE_MCP_TAIL"
|
|
97
97
|
exit $? ;;
|
|
98
|
-
"--version") echo "2.1.
|
|
98
|
+
"--version") echo "2.1.306 (Claude Code)" ;;
|
|
99
99
|
"plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
|
|
100
100
|
*) : ;;
|
|
101
101
|
esac
|
|
@@ -710,7 +710,7 @@ FAKE_BIN="$TMP/fake-bin"; mkdir -p "$FAKE_BIN"
|
|
|
710
710
|
cat > "$FAKE_BIN/claude" <<'SH'
|
|
711
711
|
#!/usr/bin/env bash
|
|
712
712
|
printf '%s\n' "$*" >> "$FAKE_CLAUDE_LOG"
|
|
713
|
-
[ "$1" = "--version" ] && echo "2.1.
|
|
713
|
+
[ "$1" = "--version" ] && echo "2.1.306 (Claude Code)"
|
|
714
714
|
exit 0
|
|
715
715
|
SH
|
|
716
716
|
chmod +x "$FAKE_BIN/claude"
|
|
@@ -1027,7 +1027,7 @@ if [ "${1:-} ${2:-} ${3:-}" = "plugin list --json" ]; then
|
|
|
1027
1027
|
exit 0
|
|
1028
1028
|
fi
|
|
1029
1029
|
case "$1${2:+ $2}" in
|
|
1030
|
-
"--version") echo "2.1.
|
|
1030
|
+
"--version") echo "2.1.306 (Claude Code)" ;;
|
|
1031
1031
|
"plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
|
|
1032
1032
|
"mcp get") printf '%s\n' "Scope: User config" "Status: ✔ Connected" ;;
|
|
1033
1033
|
*) : ;;
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
* re-injects a bridge extension (#29), and a resume that silently lost its history would look
|
|
35
35
|
* identical from the socket axis.
|
|
36
36
|
*
|
|
37
|
-
* Claude Code runs the same first half
|
|
38
|
-
*
|
|
39
|
-
* no window opened and no lock residue after the per-gid coordination lock is
|
|
40
|
-
*
|
|
41
|
-
* resume
|
|
37
|
+
* Claude Code runs the same first half: callback id, mailbox delivery, mailbox read receipt — and a
|
|
38
|
+
* resume of that still-LIVE sibling that is REFUSED as `target-live` (its receiver marker names a
|
|
39
|
+
* live owner), with no window opened and no lock residue after the per-gid coordination lock is
|
|
40
|
+
* released. Claude Code's DORMANT same-id resume (#147 L2) is certified deterministically; its LIVE
|
|
41
|
+
* close→resume leg is not in this smoke yet, and this cell does not stand in for it.
|
|
42
42
|
*
|
|
43
43
|
* ── Isolate the WRITES, keep the runtimes real ──
|
|
44
44
|
*
|
|
@@ -1035,20 +1035,20 @@ async function main(): Promise<void> {
|
|
|
1035
1035
|
`--- response ---\n${ccRead.text}`,
|
|
1036
1036
|
);
|
|
1037
1037
|
|
|
1038
|
-
//
|
|
1039
|
-
// the proof: it opens no window, and its per-gid coordination lock leaves no residue.
|
|
1038
|
+
// A live Claude Code sibling is not reopened: its receiver marker names a live owner. The refusal
|
|
1039
|
+
// is the proof: it opens no window, and its per-gid coordination lock leaves no residue.
|
|
1040
1040
|
const ccWindowsBefore = windowCount(cc.socket, cc.env);
|
|
1041
1041
|
const ccResume = await ccBridge.call("entwurf_resume_call", { target: ccCitizen }, RESUME_CALL_TIMEOUT_MS);
|
|
1042
1042
|
ok(
|
|
1043
|
-
"claude-code: entwurf_resume_call is REFUSED as target-
|
|
1044
|
-
ccResume.isError && ccResume.text.includes("target-
|
|
1043
|
+
"claude-code: entwurf_resume_call of the still-live sibling is REFUSED as target-live — its receiver marker names a live owner, so a second window would put two processes on one session",
|
|
1044
|
+
ccResume.isError && ccResume.text.includes("target-live"),
|
|
1045
1045
|
`--- response ---\n${ccResume.text}`,
|
|
1046
1046
|
);
|
|
1047
1047
|
ok(
|
|
1048
1048
|
"claude-code: that refusal opened no window and left no lock behind",
|
|
1049
1049
|
windowCount(cc.socket, cc.env) === ccWindowsBefore && lockResidue(cellRoots(REAL_HOME), ccCitizen).length === 0,
|
|
1050
1050
|
);
|
|
1051
|
-
receipts["claude-code/5-resume-refused-
|
|
1051
|
+
receipts["claude-code/5-resume-refused-live"] = ccResume.text;
|
|
1052
1052
|
}
|
|
1053
1053
|
|
|
1054
1054
|
await runClaudeCell();
|
|
@@ -53,7 +53,7 @@ cat > "$SB/bin/entwurf-bridge" <<'FAKE'
|
|
|
53
53
|
while IFS= read -r line; do
|
|
54
54
|
case "$line" in
|
|
55
55
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
56
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
56
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_retire_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
57
57
|
esac
|
|
58
58
|
done
|
|
59
59
|
FAKE
|