@junghanacs/entwurf 0.41.1 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +185 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +70 -65
  6. package/VERIFY.md +9 -9
  7. package/docs/external-mcp-host.md +1 -1
  8. package/docs/setup-clean-host.md +59 -33
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  23. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  24. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  25. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  26. package/mcp/entwurf-bridge/src/index.ts +14 -11
  27. package/native/win32-x64/entwurf_win.node +0 -0
  28. package/native/win32-x64/manifest.json +2 -2
  29. package/package.json +4 -4
  30. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  31. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  32. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  35. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  36. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  37. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  38. package/pi-extensions/lib/native-push/register.ts +17 -2
  39. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  40. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  41. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  42. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  43. package/pi-extensions/meta-bridge-omp.ts +69 -7
  44. package/pi-extensions/meta-bridge-pi-durable.ts +2 -2
  45. package/run.sh +173 -22
  46. package/scripts/check-agy-sender-identity.ts +6 -4
  47. package/scripts/check-entwurf-self-address.ts +34 -3
  48. package/scripts/check-entwurf-v2-contract.ts +24 -2
  49. package/scripts/check-entwurf-v2-production.ts +45 -31
  50. package/scripts/check-gate-qualification.ts +10 -8
  51. package/scripts/check-native-push-adapter.ts +4 -0
  52. package/scripts/check-native-push-register.ts +32 -82
  53. package/scripts/check-remote-pair-installed.ts +2 -2
  54. package/scripts/dev-bin.sh +112 -1
  55. package/scripts/lib/chain-terminus-publication.ts +58 -0
  56. package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
  57. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  58. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  59. package/scripts/mutants/chain-terminus-publication.json +17 -0
  60. package/scripts/mutants/codex-native.json +98 -0
  61. package/scripts/mutants/control-send-receipt.json +2 -2
  62. package/scripts/mutants/mux-fresh-call.json +16 -1
  63. package/scripts/mutants/native-push-paused.json +17 -0
  64. package/scripts/mutants/omp-fresh.json +13 -2
  65. package/scripts/mutants/pack-install.json +46 -0
  66. package/scripts/mutants/remote-pair-display.json +3 -3
  67. package/scripts/mutants/remote-pair-receive.json +41 -11
  68. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  69. package/scripts/mutants/remote-pair-send.json +156 -17
  70. package/scripts/mutants/remote-pair-transport.json +4 -4
  71. package/scripts/mutants/source-install.json +66 -0
  72. package/scripts/remote-pair-receive.ts +12 -12
  73. package/scripts/smoke-agy-install-state.sh +121 -0
  74. package/scripts/smoke-agy-native-push-live.ts +13 -0
  75. package/scripts/smoke-codex-fresh-live.ts +50 -27
  76. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  77. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  78. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  79. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  80. package/scripts/smoke-omp-fresh-live.ts +5 -4
  81. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  82. package/scripts/smoke-setup-verdict.sh +27 -2
@@ -6,9 +6,9 @@
6
6
  "claim": "REMOTE-ROUNDTRIP-A-TO-B",
7
7
  "title": "the bridge never reads the opt-in, so a declared remote target falls through to the local decider as bad-target",
8
8
  "subject": "mcp/entwurf-bridge/src/index.ts",
9
- "find": ["\t\t\t\t\troute: readRemotePairOptIn(remotePairOptInPath()),"],
10
- "replace": ["\t\t\t\t\troute: null,"],
11
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
9
+ "find": ["\t\t\t\t\toptIn: readRemotePairOptIn(remotePairOptInPath()),"],
10
+ "replace": ["\t\t\t\t\toptIn: null,"],
11
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
12
12
  "timeoutSeconds": 600,
13
13
  "signature": "[QK:REMOTE-ROUNDTRIP-A-TO-B]",
14
14
  "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
@@ -25,7 +25,7 @@
25
25
  "replace": [
26
26
  "\t\t\t\t\ttransport: { run: async () => ({ exitCode: 1, signal: null, timedOut: false, stdout: \"\", stderr: \"\" }) },"
27
27
  ],
28
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
28
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
29
29
  "timeoutSeconds": 600,
30
30
  "signature": "[QK:REMOTE-ROUNDTRIP-REVERSE-DISPATCH]",
31
31
  "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
@@ -40,7 +40,7 @@
40
40
  "subject": "mcp/entwurf-bridge/src/index.ts",
41
41
  "find": ["\t\t\t\t\trunLocal: (params, opts) => runAndRenderEntwurfV2FromSurface(params, opts),"],
42
42
  "replace": ["\t\t\t\t\trunLocal: async () => ({ isError: false, text: \"local skipped\" }),"],
43
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
43
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
44
44
  "timeoutSeconds": 600,
45
45
  "signature": "[QK:REMOTE-ROUNDTRIP-OPT-IN-ABSENT-OFF]",
46
46
  "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
@@ -55,7 +55,7 @@
55
55
  "subject": "pi-extensions/lib/remote-pair-send.ts",
56
56
  "find": ["refused by the other host's pair: ${o.reason} (${o.detail})"],
57
57
  "replace": ["failed"],
58
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
58
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
59
59
  "timeoutSeconds": 600,
60
60
  "signature": "[QK:REMOTE-ROUNDTRIP-OTHER-HOST-REFUSES]",
61
61
  "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
@@ -70,7 +70,7 @@
70
70
  "subject": "mcp/entwurf-bridge/src/index.ts",
71
71
  "find": ["\t\t\t\t\t\tcontrolSocketDirs: [CONTROL_SOCKET_DIR, ENTWURF_DIR],"],
72
72
  "replace": ["\t\t\t\t\t\tcontrolSocketDirs: [ENTWURF_DIR],"],
73
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
73
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
74
74
  "timeoutSeconds": 600,
75
75
  "signature": "[QK:REMOTE-ROUNDTRIP-LOCAL-TRACE-BOTH-SOCKET-DIRS]",
76
76
  "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
@@ -78,6 +78,66 @@
78
78
  "argv": ["bash", "scripts/build-bridge.sh"],
79
79
  "output": "mcp/entwurf-bridge/dist"
80
80
  }
81
+ },
82
+ {
83
+ "claim": "REMOTE-ROUNDTRIP-MULTI-INDEPENDENT",
84
+ "title": "the receiving entry reads only the first route of its file, so a second citizen paired with this host is refused as an undeclared sender",
85
+ "subject": "scripts/remote-pair-receive.ts",
86
+ "find": ["\tdecls = optIn.routes.map("],
87
+ "replace": ["\tdecls = optIn.routes.slice(0, 1).map("],
88
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
89
+ "timeoutSeconds": 600,
90
+ "signature": "[QK:REMOTE-ROUNDTRIP-MULTI-INDEPENDENT]",
91
+ "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
92
+ "build": {
93
+ "argv": ["bash", "scripts/build-bridge.sh"],
94
+ "output": "mcp/entwurf-bridge/dist"
95
+ }
96
+ },
97
+ {
98
+ "claim": "REMOTE-ROUNDTRIP-MULTI-REVERSE",
99
+ "title": "the receiving side recognizes only the first route's remote as a sender, so a claim from a second host lands on the wrong route or is refused",
100
+ "subject": "pi-extensions/lib/remote-pair-receive.ts",
101
+ "find": ["\tconst fromRoutes = decls.filter((d) => d.remoteGardenId === claim.from);"],
102
+ "replace": ["\tconst fromRoutes = decls.filter((d) => d.remoteGardenId === decls[0].remoteGardenId);"],
103
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
104
+ "timeoutSeconds": 600,
105
+ "signature": "[QK:REMOTE-ROUNDTRIP-MULTI-REVERSE]",
106
+ "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
107
+ "build": {
108
+ "argv": ["bash", "scripts/build-bridge.sh"],
109
+ "output": "mcp/entwurf-bridge/dist"
110
+ }
111
+ },
112
+ {
113
+ "claim": "REMOTE-ROUNDTRIP-MULTI-UNDECLARED-SENDER-ZERO-SSH",
114
+ "title": "a sender with no route to a declared remote borrows another sender's route, so ssh runs and the other host is asked to accept a pair nobody declared",
115
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
116
+ "find": ["? declared.find((r) => r.localGardenId === sender.sessionId) :"],
117
+ "replace": ["? (declared.find((r) => r.localGardenId === sender.sessionId) ?? declared[0]) :"],
118
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
119
+ "timeoutSeconds": 600,
120
+ "signature": "[QK:REMOTE-ROUNDTRIP-MULTI-UNDECLARED-SENDER-ZERO-SSH]",
121
+ "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
122
+ "build": {
123
+ "argv": ["bash", "scripts/build-bridge.sh"],
124
+ "output": "mcp/entwurf-bridge/dist"
125
+ }
126
+ },
127
+ {
128
+ "claim": "REMOTE-ROUNDTRIP-MULTI-CROSS-CLAIM-REFUSED",
129
+ "title": "the target is matched against every route rather than the sender's own, so a peer that declares an extra pair on its side widens this host's",
130
+ "subject": "pi-extensions/lib/remote-pair-receive.ts",
131
+ "find": ["\tconst decl = fromRoutes.find((d) => d.localGardenId === claim.target);"],
132
+ "replace": ["\tconst decl = decls.find((d) => d.localGardenId === claim.target);"],
133
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
134
+ "timeoutSeconds": 600,
135
+ "signature": "[QK:REMOTE-ROUNDTRIP-MULTI-CROSS-CLAIM-REFUSED]",
136
+ "signatureSource": "pi-extensions/lib/remote-pair-roundtrip.test.ts",
137
+ "build": {
138
+ "argv": ["bash", "scripts/build-bridge.sh"],
139
+ "output": "mcp/entwurf-bridge/dist"
140
+ }
81
141
  }
82
142
  ]
83
143
  }
@@ -6,9 +6,9 @@
6
6
  "claim": "REMOTE-ROUTE-UNDECLARED-LOCAL-UNCHANGED",
7
7
  "title": "a declared route captures every target, so a gid the declaration never named is sent to the other host instead of the local decider",
8
8
  "subject": "pi-extensions/lib/remote-pair-send.ts",
9
- "find": ["\tif (route === null || params.target !== route.remoteGardenId) {"],
10
- "replace": ["\tif (route === null) {"],
11
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
9
+ "find": ["\tconst declared = (opts.optIn?.routes ?? []).filter((r) => r.remoteGardenId === params.target);"],
10
+ "replace": ["\tconst declared = opts.optIn?.routes ?? [];"],
11
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
12
12
  "timeoutSeconds": 600,
13
13
  "signature": "[QK:REMOTE-ROUTE-UNDECLARED-LOCAL-UNCHANGED]",
14
14
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -21,9 +21,9 @@
21
21
  "claim": "REMOTE-ROUTE-LOCAL-CONFLICT-FAILS-CLOSED",
22
22
  "title": "a declared remote gid that this host also answers to is sent away anyway, so two hosts' citizens share one address and the sender silently picks the remote",
23
23
  "subject": "pi-extensions/lib/remote-pair-send.ts",
24
- "find": ["\tif (await opts.localTrace(route.remoteGardenId)) {"],
24
+ "find": ["\tif (await opts.localTrace(params.target)) {"],
25
25
  "replace": ["\tif (Boolean(0)) {"],
26
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
26
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
27
27
  "timeoutSeconds": 600,
28
28
  "signature": "[QK:REMOTE-ROUTE-LOCAL-CONFLICT-FAILS-CLOSED]",
29
29
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -36,9 +36,9 @@
36
36
  "claim": "REMOTE-ROUTE-SENDER-MUST-BE-DECLARED",
37
37
  "title": "any present envelope may use the route, so an anonymous caller or another citizen on this host speaks to the other host as the declared sender",
38
38
  "subject": "pi-extensions/lib/remote-pair-send.ts",
39
- "find": ["\tif (!sender || sender.origin === \"external-mcp\" || sender.sessionId !== route.localGardenId) {"],
40
- "replace": ["\tif (!sender || Boolean(0)) {"],
41
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
39
+ "find": ["\t\tsender && sender.origin !== \"external-mcp\""],
40
+ "replace": ["\t\tsender"],
41
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
42
42
  "timeoutSeconds": 600,
43
43
  "signature": "[QK:REMOTE-ROUTE-SENDER-MUST-BE-DECLARED]",
44
44
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -53,7 +53,7 @@
53
53
  "subject": "pi-extensions/lib/remote-pair-send.ts",
54
54
  "find": ["\t\t\"BatchMode=yes\","],
55
55
  "replace": ["\t\t\"BatchMode=no\","],
56
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
56
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
57
57
  "timeoutSeconds": 600,
58
58
  "signature": "[QK:REMOTE-ROUTE-EXACT-ARGV-ONCE]",
59
59
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -70,7 +70,7 @@
70
70
  "replace": [
71
71
  "\tawait opts.transport.run(remoteReceiveArgv(route), JSON.stringify(claim), 1);\n\tconst result = await opts.transport.run("
72
72
  ],
73
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
73
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
74
74
  "timeoutSeconds": 600,
75
75
  "signature": "[QK:REMOTE-ROUTE-NO-RETRY]",
76
76
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -87,7 +87,7 @@
87
87
  "\tif (r.exitCode === 0 && keys === \"isError,kind,text\" && o.kind === \"dispatched\" && o.isError === false) {"
88
88
  ],
89
89
  "replace": ["\tif (keys === \"isError,kind,text\" && o.kind === \"dispatched\" && o.isError === false) {"],
90
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
90
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
91
91
  "timeoutSeconds": 600,
92
92
  "signature": "[QK:REMOTE-RECEIPT-EXIT-AND-KIND-AGREE]",
93
93
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -102,7 +102,7 @@
102
102
  "subject": "pi-extensions/lib/remote-pair-send.ts",
103
103
  "find": ["const REMOTE_PATH = /^\\/[A-Za-z0-9._/-]+$/;"],
104
104
  "replace": ["const REMOTE_PATH = /^\\/.+$/;"],
105
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
105
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
106
106
  "timeoutSeconds": 600,
107
107
  "signature": "[QK:REMOTE-ROUTE-DECL-NO-QUOTING-NEEDED]",
108
108
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -117,7 +117,7 @@
117
117
  "subject": "pi-extensions/lib/remote-pair-send.ts",
118
118
  "find": ["\t\t\tif (inspection.kind !== \"absent\") return true;"],
119
119
  "replace": ["\t\t\tif (false) return true;"],
120
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
120
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
121
121
  "timeoutSeconds": 600,
122
122
  "signature": "[QK:REMOTE-ROUTE-LOCAL-TRACE-ANY-STATE]",
123
123
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -132,7 +132,7 @@
132
132
  "subject": "pi-extensions/lib/remote-pair-send.ts",
133
133
  "find": ["read/answer unknown until a reply arrives; paths below are on the other host)"],
134
134
  "replace": ["read/answer unknown until a reply arrives)"],
135
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
135
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
136
136
  "timeoutSeconds": 600,
137
137
  "signature": "[QK:REMOTE-ROUTE-RECEIPT-PATHS-OTHER-HOST]",
138
138
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -147,7 +147,7 @@
147
147
  "subject": "pi-extensions/lib/remote-pair-send.ts",
148
148
  "find": ["\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;"],
149
149
  "replace": ["\t\tif (false) return null;"],
150
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
150
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
151
151
  "timeoutSeconds": 600,
152
152
  "signature": "[QK:REMOTE-PAIR-OPT-IN-ABSENT-IS-OFF]",
153
153
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -162,7 +162,7 @@
162
162
  "subject": "pi-extensions/lib/remote-pair-send.ts",
163
163
  "find": ["\tif (!st.isFile()) {\n\t\tthrow new RemoteRouteDeclarationError(`remote pair opt-in"],
164
164
  "replace": ["\tif (false) {\n\t\tthrow new RemoteRouteDeclarationError(`remote pair opt-in"],
165
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
165
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
166
166
  "timeoutSeconds": 600,
167
167
  "signature": "[QK:REMOTE-PAIR-OPT-IN-BROKEN-REFUSED]",
168
168
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -181,7 +181,7 @@
181
181
  "replace": [
182
182
  "\t\t\t\ttext: `entwurf_v2 remote ${via} → the other host did not deliver (paths below are on the other host)\\n${o.text}`,"
183
183
  ],
184
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
184
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
185
185
  "timeoutSeconds": 600,
186
186
  "signature": "[QK:REMOTE-REPORTED-ERROR-NO-DELIVERY-INFERENCE]",
187
187
  "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
@@ -189,6 +189,145 @@
189
189
  "argv": ["bash", "scripts/build-bridge.sh"],
190
190
  "output": "mcp/entwurf-bridge/dist"
191
191
  }
192
+ },
193
+ {
194
+ "claim": "REMOTE-ROUTES-V2-EXACT-SHAPE",
195
+ "title": "an empty routes array is read as a declaration with no route, so a file meant to switch the pair off silently keeps the bridge reading an opt-in instead of being refused",
196
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
197
+ "find": ["\tif (!Array.isArray(obj.routes) || obj.routes.length === 0) {"],
198
+ "replace": ["\tif (!Array.isArray(obj.routes)) {"],
199
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
200
+ "timeoutSeconds": 600,
201
+ "signature": "[QK:REMOTE-ROUTES-V2-EXACT-SHAPE]",
202
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
203
+ "build": {
204
+ "argv": ["bash", "scripts/build-bridge.sh"],
205
+ "output": "mcp/entwurf-bridge/dist"
206
+ }
207
+ },
208
+ {
209
+ "claim": "REMOTE-ROUTES-LEGACY-NAMED-REFUSAL",
210
+ "title": "the versionless single-pair file is quietly read as one route, so a host keeps a pre-#149 declaration through the hard cut with no named transition",
211
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
212
+ "find": ["\tif (!Object.hasOwn(obj, \"version\")) {"],
213
+ "replace": [
214
+ "\tif (!Object.hasOwn(obj, \"version\")) return { routes: [parseRoute(obj, what)] };\n\tif (Boolean(0)) {"
215
+ ],
216
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
217
+ "timeoutSeconds": 600,
218
+ "signature": "[QK:REMOTE-ROUTES-LEGACY-NAMED-REFUSAL]",
219
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
220
+ "build": {
221
+ "argv": ["bash", "scripts/build-bridge.sh"],
222
+ "output": "mcp/entwurf-bridge/dist"
223
+ }
224
+ },
225
+ {
226
+ "claim": "REMOTE-ROUTES-LEGAL-REPEATS-ACCEPTED",
227
+ "title": "one alias may carry only one route, so a host with two citizens on the same other host cannot declare both pairs",
228
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
229
+ "find": ["\t\tif (file !== undefined && file !== r.remotePairFile) {"],
230
+ "replace": ["\t\tif (file !== undefined) {"],
231
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
232
+ "timeoutSeconds": 600,
233
+ "signature": "[QK:REMOTE-ROUTES-LEGAL-REPEATS-ACCEPTED]",
234
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
235
+ "build": {
236
+ "argv": ["bash", "scripts/build-bridge.sh"],
237
+ "output": "mcp/entwurf-bridge/dist"
238
+ }
239
+ },
240
+ {
241
+ "claim": "REMOTE-ROUTES-DUPLICATE-PAIR-REFUSED",
242
+ "title": "the same exact pair may be declared twice, so the file carries two entries for one route and the reader keeps whichever it meets first",
243
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
244
+ "find": ["\t\tif (pairs.has(pair)) {"],
245
+ "replace": ["\t\tif (Boolean(0)) {"],
246
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
247
+ "timeoutSeconds": 600,
248
+ "signature": "[QK:REMOTE-ROUTES-DUPLICATE-PAIR-REFUSED]",
249
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
250
+ "build": {
251
+ "argv": ["bash", "scripts/build-bridge.sh"],
252
+ "output": "mcp/entwurf-bridge/dist"
253
+ }
254
+ },
255
+ {
256
+ "claim": "REMOTE-ROUTES-LOCAL-REMOTE-OVERLAP-REFUSED",
257
+ "title": "a gid may be local in one route and remote in another, so one citizen is claimed to live on this host and on another at once",
258
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
259
+ "find": ["\t\tif (locals.has(r.remoteGardenId)) {"],
260
+ "replace": ["\t\tif (Boolean(0)) {"],
261
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
262
+ "timeoutSeconds": 600,
263
+ "signature": "[QK:REMOTE-ROUTES-LOCAL-REMOTE-OVERLAP-REFUSED]",
264
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
265
+ "build": {
266
+ "argv": ["bash", "scripts/build-bridge.sh"],
267
+ "output": "mcp/entwurf-bridge/dist"
268
+ }
269
+ },
270
+ {
271
+ "claim": "REMOTE-ROUTES-REMOTE-HOST-CONFLICT-REFUSED",
272
+ "title": "one remote gid may be reached through two aliases, so which host a message to it goes to depends on which sender sends it",
273
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
274
+ "find": ["\t\tif (host && (host.ssh !== r.ssh || host.remotePairFile !== r.remotePairFile)) {"],
275
+ "replace": ["\t\tif (host && host.remotePairFile !== r.remotePairFile) {"],
276
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
277
+ "timeoutSeconds": 600,
278
+ "signature": "[QK:REMOTE-ROUTES-REMOTE-HOST-CONFLICT-REFUSED]",
279
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
280
+ "build": {
281
+ "argv": ["bash", "scripts/build-bridge.sh"],
282
+ "output": "mcp/entwurf-bridge/dist"
283
+ }
284
+ },
285
+ {
286
+ "claim": "REMOTE-ROUTES-ALIAS-FILE-CONFLICT-REFUSED",
287
+ "title": "one alias may name two remote files, though the host behind it accepts only its one canonical file, so one of those routes can never be received",
288
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
289
+ "find": ["\t\tif (file !== undefined && file !== r.remotePairFile) {"],
290
+ "replace": ["\t\tif (Boolean(0)) {"],
291
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
292
+ "timeoutSeconds": 600,
293
+ "signature": "[QK:REMOTE-ROUTES-ALIAS-FILE-CONFLICT-REFUSED]",
294
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
295
+ "build": {
296
+ "argv": ["bash", "scripts/build-bridge.sh"],
297
+ "output": "mcp/entwurf-bridge/dist"
298
+ }
299
+ },
300
+ {
301
+ "claim": "REMOTE-ROUTE-SELECT-EXACT-PAIR",
302
+ "title": "the route is chosen by target alone, so a citizen paired with another remote speaks over a route declared for a different sender",
303
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
304
+ "find": ["? declared.find((r) => r.localGardenId === sender.sessionId) :"],
305
+ "replace": ["? declared.find(() => true) :"],
306
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
307
+ "timeoutSeconds": 600,
308
+ "signature": "[QK:REMOTE-ROUTE-SELECT-EXACT-PAIR]",
309
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
310
+ "build": {
311
+ "argv": ["bash", "scripts/build-bridge.sh"],
312
+ "output": "mcp/entwurf-bridge/dist"
313
+ }
314
+ },
315
+ {
316
+ "claim": "REMOTE-ROUTE-NO-LOCAL-FALLBACK",
317
+ "title": "a declared remote target with no route for this sender falls through to the local path, so a gid the operator said lives elsewhere is resolved on this host",
318
+ "subject": "pi-extensions/lib/remote-pair-send.ts",
319
+ "find": ["\tif (!sender || !route) {"],
320
+ "replace": [
321
+ "\tif (!sender || !route) return opts.runLocal(params, { senderProvider: opts.senderProvider });\n\tif (!sender || !route) {"
322
+ ],
323
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
324
+ "timeoutSeconds": 600,
325
+ "signature": "[QK:REMOTE-ROUTE-NO-LOCAL-FALLBACK]",
326
+ "signatureSource": "pi-extensions/lib/remote-pair-send.test.ts",
327
+ "build": {
328
+ "argv": ["bash", "scripts/build-bridge.sh"],
329
+ "output": "mcp/entwurf-bridge/dist"
330
+ }
192
331
  }
193
332
  ]
194
333
  }
@@ -8,7 +8,7 @@
8
8
  "subject": "pi-extensions/lib/remote-pair-transport.ts",
9
9
  "find": ["{ shell: false, stdio: [\"pipe\", \"pipe\", \"pipe\"] }"],
10
10
  "replace": ["{ shell: true, stdio: [\"pipe\", \"pipe\", \"pipe\"] }"],
11
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
11
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
12
12
  "timeoutSeconds": 600,
13
13
  "signature": "[QK:REMOTE-TRANSPORT-NO-SHELL-SEPARATE-STREAMS]",
14
14
  "signatureSource": "pi-extensions/lib/remote-pair-transport.test.ts",
@@ -23,7 +23,7 @@
23
23
  "subject": "pi-extensions/lib/remote-pair-transport.ts",
24
24
  "find": ["\t\t\t\t\tchild.kill(\"SIGKILL\");"],
25
25
  "replace": ["\t\t\t\t\tvoid 0;"],
26
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
26
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
27
27
  "timeoutSeconds": 600,
28
28
  "signature": "[QK:REMOTE-TRANSPORT-TIMEOUT-REAPS-OWN-CHILD]",
29
29
  "signatureSource": "pi-extensions/lib/remote-pair-transport.test.ts",
@@ -38,7 +38,7 @@
38
38
  "subject": "pi-extensions/lib/remote-pair-transport.ts",
39
39
  "find": ["\t\t\t\t\texitCode: spawnError ? null : code,"],
40
40
  "replace": ["\t\t\t\t\texitCode: spawnError ? 0 : code,"],
41
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
41
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
42
42
  "timeoutSeconds": 600,
43
43
  "signature": "[QK:REMOTE-TRANSPORT-SPAWN-FAILURE-INDETERMINATE]",
44
44
  "signatureSource": "pi-extensions/lib/remote-pair-transport.test.ts",
@@ -53,7 +53,7 @@
53
53
  "subject": "pi-extensions/lib/remote-pair-transport.ts",
54
54
  "find": ["\t\t\t\t\tfinalTimer = setTimeout(() => {"],
55
55
  "replace": ["\t\t\t\t\tfinalTimer = setTimeout(() => {\n\t\t\t\t\t\treturn;"],
56
- "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
56
+ "gate": ["bash", "run.sh", "check-remote-pair-beside"],
57
57
  "timeoutSeconds": 600,
58
58
  "signature": "[QK:REMOTE-TRANSPORT-HELD-PIPES-STILL-SETTLE]",
59
59
  "signatureSource": "pi-extensions/lib/remote-pair-transport.test.ts",
@@ -27,6 +27,72 @@
27
27
  "timeoutSeconds": 300,
28
28
  "signature": "[QK:SOURCE-OPERATOR-FAILS-LOUD]",
29
29
  "signatureSource": "scripts/smoke-agy-install-state.sh"
30
+ },
31
+ {
32
+ "claim": "SOURCE-SAME-TARGET-PASSES",
33
+ "title": "the exclusivity check compares the unresolved entry path again, so a duplicate PATH entry or a second link to this very checkout reads as another root and blocks a correct developer install",
34
+ "subject": "scripts/dev-bin.sh",
35
+ "find": [" if os.path.lexists(path) and os.path.realpath(path) != target:"],
36
+ "replace": [" if os.path.lexists(path) and os.path.abspath(path) != target:"],
37
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
38
+ "timeoutSeconds": 300,
39
+ "signature": "[QK:SOURCE-SAME-TARGET-PASSES]",
40
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
41
+ },
42
+ {
43
+ "claim": "SOURCE-LATER-ROOT-FAILS",
44
+ "title": "the exclusivity check reads only the head of PATH again, so an old global shim later in PATH stays selectable by a shell with another PATH order while setup reports a developer install",
45
+ "subject": "scripts/dev-bin.sh",
46
+ "find": ["for d in os.environ.get(\"PATH\", \"\").split(\":\"):"],
47
+ "replace": ["for d in os.environ.get(\"PATH\", \"\").split(\":\")[:1]:"],
48
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
49
+ "timeoutSeconds": 300,
50
+ "signature": "[QK:SOURCE-LATER-ROOT-FAILS]",
51
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
52
+ },
53
+ {
54
+ "claim": "SOURCE-GLOBAL-BIN-OTHER-ROOT",
55
+ "title": "the package manager's reported global bin dir is queried but never searched, so another root there passes whenever that dir is absent from this PATH",
56
+ "subject": "scripts/dev-bin.sh",
57
+ "find": [" add(to_bin(value), label)\n"],
58
+ "replace": [" pass\n"],
59
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
60
+ "timeoutSeconds": 300,
61
+ "signature": "[QK:SOURCE-GLOBAL-BIN-OTHER-ROOT]",
62
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
63
+ },
64
+ {
65
+ "claim": "SOURCE-GLOBAL-QUERY-REFUSED",
66
+ "title": "a package manager that fails or hangs is read as not installed again, so an uninspectable global bin dir yields a clean developer-install verdict",
67
+ "subject": "scripts/dev-bin.sh",
68
+ "find": [" errors.append(value)\n"],
69
+ "replace": [" skipped.append(value)\n"],
70
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
71
+ "timeoutSeconds": 300,
72
+ "signature": "[QK:SOURCE-GLOBAL-QUERY-REFUSED]",
73
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
74
+ },
75
+ {
76
+ "claim": "SOURCE-EXCLUSIVE-WIRED",
77
+ "title": "source setup's bins step drops the exclusivity verdict again and returns its pre-repair success, so another selectable root is reported as a developer install",
78
+ "subject": "run.sh",
79
+ "find": [" [ \"$rc\" -eq 0 ] && return \"$ex_rc\"\n"],
80
+ "replace": [" [ \"$rc\" -eq 0 ] && return 0\n"],
81
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
82
+ "timeoutSeconds": 300,
83
+ "signature": "[QK:SOURCE-EXCLUSIVE-WIRED]",
84
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
85
+ },
86
+ {
87
+ "claim": "SOURCE-MANIFEST-UNREADABLE-REFUSED",
88
+ "title": "an unreadable or wrong-shaped package manifest is read as zero bin names again, so the exclusivity check finds no other root by checking nothing and source setup reports a developer install",
89
+ "subject": "scripts/dev-bin.sh",
90
+ "find": [" sys.exit(6) # an unreadable manifest is never \"0 names, no other root\"\n"],
91
+ "replace": [" bins = {} # regressed: an unreadable manifest read as zero names\n"],
92
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
93
+ "timeoutSeconds": 300,
94
+ "signature": "[QK:SOURCE-MANIFEST-UNREADABLE-REFUSED]",
95
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
30
96
  }
31
97
  ]
32
98
  }
@@ -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
  });