@junghanacs/entwurf 0.41.1 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/BASELINE.md +7 -5
- package/CHANGELOG.md +344 -0
- package/DELIVERY.md +37 -20
- package/ENTWURF.json +236 -0
- package/README.md +75 -65
- package/VERIFY.md +9 -9
- package/docs/external-mcp-host.md +1 -1
- package/docs/setup-clean-host.md +59 -33
- package/mcp/entwurf-bridge/dist/ENTWURF.json +236 -0
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +32 -190
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/agent-guidance.js +299 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +23 -24
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +15 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +29 -24
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +18 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +101 -32
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +23 -45
- package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
- package/mcp/entwurf-bridge/src/index.ts +126 -321
- package/native/win32-x64/entwurf_win.node +0 -0
- package/native/win32-x64/manifest.json +2 -2
- package/package.json +5 -4
- package/pi-extensions/lib/agent-guidance.ts +341 -0
- package/pi-extensions/lib/entwurf-self-address.ts +13 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
- package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +27 -30
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +15 -16
- package/pi-extensions/lib/fresh-call-composition.ts +21 -10
- package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
- package/pi-extensions/lib/meta-sender-identity.ts +4 -0
- package/pi-extensions/lib/mux-fresh-call.ts +29 -30
- package/pi-extensions/lib/mux-resume-call.ts +18 -19
- package/pi-extensions/lib/native-push/register.ts +17 -2
- package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
- package/pi-extensions/lib/remote-pair-receive.ts +27 -19
- package/pi-extensions/lib/remote-pair-send.ts +124 -34
- package/pi-extensions/meta-bridge-omp.ts +69 -7
- package/pi-extensions/meta-bridge-pi-durable.ts +23 -64
- package/run.sh +182 -22
- package/scripts/build-bridge.sh +10 -0
- package/scripts/check-agy-sender-identity.ts +6 -4
- package/scripts/check-capability-bundle-reach.ts +86 -0
- package/scripts/check-entwurf-self-address.ts +37 -6
- package/scripts/check-entwurf-v2-contract.ts +24 -2
- package/scripts/check-entwurf-v2-production.ts +45 -31
- package/scripts/check-entwurf-v2-surface.ts +94 -98
- package/scripts/check-gate-qualification.ts +17 -13
- package/scripts/check-mux-resume-call.ts +20 -3
- package/scripts/check-native-push-adapter.ts +4 -0
- package/scripts/check-native-push-register.ts +32 -82
- package/scripts/check-remote-pair-installed.ts +2 -2
- package/scripts/dev-bin.sh +112 -1
- package/scripts/lib/chain-terminus-publication.ts +58 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
- package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
- package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
- package/scripts/mutants/agent-guidance.json +160 -0
- package/scripts/mutants/bridge-boot-resume.json +6 -8
- package/scripts/mutants/capability-bundle-reach.json +124 -0
- package/scripts/mutants/chain-terminus-publication.json +17 -0
- package/scripts/mutants/codex-native.json +98 -0
- package/scripts/mutants/control-send-receipt.json +23 -6
- package/scripts/mutants/mux-fresh-call.json +30 -4
- package/scripts/mutants/mux-resume-call.json +28 -3
- package/scripts/mutants/native-push-paused.json +17 -0
- package/scripts/mutants/omp-fresh.json +13 -2
- package/scripts/mutants/pack-install.json +46 -0
- package/scripts/mutants/pi-durable.json +17 -5
- package/scripts/mutants/remote-pair-display.json +3 -3
- package/scripts/mutants/remote-pair-receive.json +41 -11
- package/scripts/mutants/remote-pair-roundtrip.json +67 -7
- package/scripts/mutants/remote-pair-send.json +177 -19
- package/scripts/mutants/remote-pair-transport.json +4 -4
- package/scripts/mutants/self-address.json +10 -10
- package/scripts/mutants/source-install.json +66 -0
- package/scripts/mutants/v2-surface.json +50 -64
- package/scripts/mutants/v2-visible-resume.json +11 -0
- package/scripts/remote-pair-receive.ts +12 -12
- package/scripts/smoke-agy-install-state.sh +121 -0
- package/scripts/smoke-agy-native-push-live.ts +13 -0
- package/scripts/smoke-codex-fresh-live.ts +50 -27
- package/scripts/smoke-entwurf-chain-live.ts +7 -9
- package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
- package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
- package/scripts/smoke-mux-lifecycle-live.ts +140 -8
- package/scripts/smoke-omp-fresh-live.ts +5 -4
- package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
- package/scripts/smoke-setup-verdict.sh +27 -2
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
{
|
|
6
6
|
"claim": "V2SURF-RESUME-HINT-SCOPE",
|
|
7
7
|
"title": "the dormant capability hint returns to a Pi-only automatic-resume invitation",
|
|
8
|
-
"subject": "
|
|
8
|
+
"subject": "ENTWURF.json",
|
|
9
9
|
"find": [
|
|
10
|
-
"
|
|
11
|
-
"\t\t\t\t\"it submits no prompt. This capability hint is not permission to resume automatically.\""
|
|
10
|
+
"Only on an explicit operator request, use entwurf_resume_call {target} for Pi or Claude Code; it submits no prompt. This capability hint is not permission to resume automatically."
|
|
12
11
|
],
|
|
13
12
|
"replace": [
|
|
14
|
-
"
|
|
13
|
+
"Reopen the citizen with entwurf_resume_call {target} (pi targets only; it runs no turn), then dispatch again."
|
|
15
14
|
],
|
|
16
15
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
17
16
|
"timeoutSeconds": 60,
|
|
@@ -21,12 +20,11 @@
|
|
|
21
20
|
{
|
|
22
21
|
"claim": "V2SURF-MCP-LOCK-DOMAIN",
|
|
23
22
|
"title": "MCP long description reverts to the false \"socket paths take a per-target lock\" claim",
|
|
24
|
-
"subject": "
|
|
23
|
+
"subject": "ENTWURF.json",
|
|
25
24
|
"find": [
|
|
26
|
-
"
|
|
27
|
-
"\t\t\"mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode \" +"
|
|
25
|
+
"LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode "
|
|
28
26
|
],
|
|
29
|
-
"replace": ["
|
|
27
|
+
"replace": ["LOCK POLICY: socket paths take a per-target lock; the mailbox path does not. mode "],
|
|
30
28
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
31
29
|
"timeoutSeconds": 60,
|
|
32
30
|
"signature": "[QK:V2SURF-MCP-LOCK-DOMAIN]",
|
|
@@ -57,15 +55,9 @@
|
|
|
57
55
|
{
|
|
58
56
|
"claim": "V2SURF-MODE-SCOPE",
|
|
59
57
|
"title": "the bridge mode param (the one every caller reads, pi included since #125) reverts to \"Delivery mode for a live send\" (false for native-push) (#120 P2: the description was rewritten to stop claiming an interrupt, so the mutation now strips the rail scope from the corrected sentence)",
|
|
60
|
-
"subject": "
|
|
61
|
-
"find": [
|
|
62
|
-
|
|
63
|
-
"\t\t\t\t\t\"The mailbox and native-push plans carry no mode, so it has no effect on those rails.\","
|
|
64
|
-
],
|
|
65
|
-
"replace": [
|
|
66
|
-
"\t\t\t\t\"Delivery mode for a live send: steer (ask the steering queue) or follow_up (ask the follow-up queue). \" +",
|
|
67
|
-
"\t\t\t\t\t\"Both queues are volatile process memory.\","
|
|
68
|
-
],
|
|
58
|
+
"subject": "ENTWURF.json",
|
|
59
|
+
"find": ["Injection style for a CONTROL-SOCKET send only:"],
|
|
60
|
+
"replace": ["Delivery mode for a live send:"],
|
|
69
61
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
70
62
|
"timeoutSeconds": 60,
|
|
71
63
|
"signature": "[QK:V2SURF-MODE-SCOPE]",
|
|
@@ -74,19 +66,11 @@
|
|
|
74
66
|
{
|
|
75
67
|
"claim": "V2SURF-INBOX-SCOPE-HONESTY",
|
|
76
68
|
"title": "entwurf_inbox_read reverts to the false \"drains your own inbox\" access claim (SCOPE paragraph gone)",
|
|
77
|
-
"subject": "
|
|
69
|
+
"subject": "ENTWURF.json",
|
|
78
70
|
"find": [
|
|
79
|
-
"
|
|
80
|
-
"\t\t\"SCOPE — read this literally: the garden id is CALLER-SUPPLIED and is NOT verified against \" +",
|
|
81
|
-
"\t\t\"your own identity (a host with no garden record of its own can call this too). So passing \" +",
|
|
82
|
-
"\t\t\"another citizen's garden id drains THEIR mail and stamps THEIR receipt, and they will never \" +",
|
|
83
|
-
"\t\t\"see those messages. Pass only the id from your own doorbell notice or your own meta-record; \" +",
|
|
84
|
-
"\t\t\"use entwurf_self if you need to confirm which id that is. \" +"
|
|
85
|
-
],
|
|
86
|
-
"replace": [
|
|
87
|
-
"\t\t\"doorbell is only a wake attempt, not a read. An empty inbox mutates nothing. \" +",
|
|
88
|
-
"\t\t\"This tool drains your own meta-bridge inbox and stamps the read receipt on your meta-record. \" +"
|
|
71
|
+
"SCOPE — read this literally: the garden id is CALLER-SUPPLIED and is NOT verified against your own identity"
|
|
89
72
|
],
|
|
73
|
+
"replace": ["This tool drains your own meta-bridge inbox and stamps the read receipt on your meta-record"],
|
|
90
74
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
91
75
|
"timeoutSeconds": 60,
|
|
92
76
|
"signature": "[QK:V2SURF-INBOX-SCOPE-HONESTY]",
|
|
@@ -95,15 +79,12 @@
|
|
|
95
79
|
{
|
|
96
80
|
"claim": "V2SURF-DORMANT-HONESTY",
|
|
97
81
|
"title": "MCP long description advertises owned-outcome as a selectable dormant verb again and drops the reason a dormant target actually returns",
|
|
98
|
-
"subject": "
|
|
82
|
+
"subject": "ENTWURF.json",
|
|
99
83
|
"find": [
|
|
100
|
-
"
|
|
101
|
-
"\t\t\"— same receiver rule as the mailbox, no active drainer means no delivery. \" +",
|
|
102
|
-
"\t\t\"The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child \" +",
|
|
103
|
-
"\t\t\"and was withdrawn under the visible-first rule — re-open the session yourself, then dispatch again. \" +"
|
|
84
|
+
"A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
|
|
104
85
|
],
|
|
105
86
|
"replace": [
|
|
106
|
-
"
|
|
87
|
+
"owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY and is never auto-converted. "
|
|
107
88
|
],
|
|
108
89
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
109
90
|
"timeoutSeconds": 60,
|
|
@@ -113,45 +94,35 @@
|
|
|
113
94
|
{
|
|
114
95
|
"claim": "V2SURF-INDETERMINATE-NO-START",
|
|
115
96
|
"title": "the indeterminate hint reverts to a socket that \"answered\" and drops the no-start facts — overclaiming a reply that a permission-refused or timed-out probe never got",
|
|
116
|
-
"subject": "
|
|
97
|
+
"subject": "ENTWURF.json",
|
|
117
98
|
"find": [
|
|
118
|
-
"
|
|
119
|
-
"\t\t\t// The socket probe did not settle, so liveness is UNKNOWN — an unestablished probe",
|
|
120
|
-
"\t\t\t// is not a measured death, and dispatching into it could double-deliver. Say",
|
|
121
|
-
"\t\t\t// \"the PROBE was inconclusive\", never \"the socket answered inconclusively\":",
|
|
122
|
-
"\t\t\t// `indeterminate` also covers a probe that got no answer at all, was refused by",
|
|
123
|
-
"\t\t\t// permissions, or timed out, and \"answered\" claims a reply that may never have",
|
|
124
|
-
"\t\t\t// existed. The wire id is FROZEN and still spells \"-no-spawn\"; it names the rule",
|
|
125
|
-
"\t\t\t// (never dispatch into an indeterminate target), not a capability that still",
|
|
126
|
-
"\t\t\t// exists. So the hint's job is to say plainly what did NOT happen: nothing",
|
|
127
|
-
"\t\t\t// delivered, nothing started.",
|
|
128
|
-
"\t\t\treturn (",
|
|
129
|
-
"\t\t\t\t\"the control-socket probe was inconclusive, so the target's liveness is UNKNOWN — this is not a \" +",
|
|
130
|
-
"\t\t\t\t\"measured death. NOTHING was delivered and NO process was started (the reason id keeps its frozen \" +",
|
|
131
|
-
"\t\t\t\t\"'-no-spawn' wire spelling from an era when one could be; entwurf_v2 starts nothing on any rail). \" +",
|
|
132
|
-
"\t\t\t\t\"Re-run entwurf_peers to re-probe; if it stays indeterminate, check for a stale socket file left at \" +",
|
|
133
|
-
"\t\t\t\t\"that garden id by a session that died without cleaning up.\"",
|
|
134
|
-
"\t\t\t);"
|
|
135
|
-
],
|
|
136
|
-
"replace": [
|
|
137
|
-
"\t\tcase \"indeterminate-no-spawn\":",
|
|
138
|
-
"\t\t\treturn \"the target's control socket answered inconclusively; retry.\";"
|
|
99
|
+
"the control-socket probe was inconclusive, so the target's liveness is UNKNOWN — this is not a measured death. NOTHING was delivered and NO process was started (the reason id keeps its frozen '-no-spawn' wire spelling from an era when one could be; entwurf_v2 starts nothing on any rail). Re-run entwurf_peers to re-probe; if it stays indeterminate, check for a stale socket file left at that garden id by a session that died without cleaning up."
|
|
139
100
|
],
|
|
101
|
+
"replace": ["the target's control socket answered inconclusively; retry."],
|
|
140
102
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
141
103
|
"timeoutSeconds": 60,
|
|
142
104
|
"signature": "[QK:V2SURF-INDETERMINATE-NO-START]",
|
|
143
105
|
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
144
106
|
},
|
|
107
|
+
{
|
|
108
|
+
"claim": "V2SURF-REJECT-HINTS-FROM-ROOT",
|
|
109
|
+
"title": "the surface stops returning the Root hint for one declared reason, so a native-push caller loses its next step while the Root text itself still looks perfect",
|
|
110
|
+
"subject": "pi-extensions/lib/entwurf-v2-surface.ts",
|
|
111
|
+
"find": ["\t\t\treturn ENTWURF_REJECT_HINTS[\"native-push-probe-indeterminate\"];"],
|
|
112
|
+
"replace": ["\t\t\treturn undefined;"],
|
|
113
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
114
|
+
"timeoutSeconds": 60,
|
|
115
|
+
"signature": "[QK:V2SURF-REJECT-HINTS-FROM-ROOT]",
|
|
116
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
117
|
+
},
|
|
145
118
|
{
|
|
146
119
|
"claim": "V2SURF-PEERS-DEAD-ROW",
|
|
147
120
|
"title": "entwurf_peers drops the dead-row sentence, so a dormant citizen is listed with nothing saying it is unreachable",
|
|
148
|
-
"subject": "
|
|
121
|
+
"subject": "ENTWURF.json",
|
|
149
122
|
"find": [
|
|
150
|
-
"
|
|
151
|
-
"\t\t\"currently unreachable by any verb, so listing it grants no action — appearing here is not \" +",
|
|
152
|
-
"\t\t\"an invitation to dispatch. In particular `unsupported` does NOT mean mailbox: it means this backend has no \" +"
|
|
123
|
+
"row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt. In particular"
|
|
153
124
|
],
|
|
154
|
-
"replace": ["
|
|
125
|
+
"replace": ["row. In particular"],
|
|
155
126
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
156
127
|
"timeoutSeconds": 60,
|
|
157
128
|
"signature": "[QK:V2SURF-PEERS-DEAD-ROW]",
|
|
@@ -160,12 +131,12 @@
|
|
|
160
131
|
{
|
|
161
132
|
"claim": "V2SURF-DESC-FITS-HOST-CAP",
|
|
162
133
|
"title": "the v2 tool description grows back past the host's 2048-char cap — the INTENT contract is silently cut off before the model that must choose an intent ever reads it",
|
|
163
|
-
"subject": "
|
|
134
|
+
"subject": "ENTWURF.json",
|
|
164
135
|
"find": [
|
|
165
|
-
"
|
|
136
|
+
"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id "
|
|
166
137
|
],
|
|
167
138
|
"replace": [
|
|
168
|
-
"
|
|
139
|
+
"CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING "
|
|
169
140
|
],
|
|
170
141
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
171
142
|
"timeoutSeconds": 60,
|
|
@@ -223,6 +194,21 @@
|
|
|
223
194
|
"timeoutSeconds": 120,
|
|
224
195
|
"signature": "[QK:NATIVE-PUSH-RETRY-BYTE-IDENTITY]",
|
|
225
196
|
"signatureSource": "pi-extensions/lib/entwurf-v2-native-push.test.ts"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"claim": "V2SURF-RECORD-LESS-SOCKET-HINT-FROM-ROOT",
|
|
200
|
+
"title": "the record-less-socket hint returns a hand-kept copy of its text, one byte off the Root segments, instead of composing them with the owner's command: every phrase the 2C cell checks still reads, so only the whole-string comparison against Root can see the drift",
|
|
201
|
+
"subject": "pi-extensions/lib/entwurf-v2-surface.ts",
|
|
202
|
+
"find": [
|
|
203
|
+
"\t\t\treturn `${hint.beforeFreshCutPrescription}${FRESH_CUT_PRESCRIPTION}${hint.afterFreshCutPrescription}`;"
|
|
204
|
+
],
|
|
205
|
+
"replace": [
|
|
206
|
+
"\t\t\treturn `a control socket exists at this garden id but NO meta-record claims it — the record is the sole address authority, so a bare socket is not an addressable citizen. If a pre-record-era resident owns that socket, restart it under the current runtime (session_start births its record); if the store predates the current generation, quiesce and archive it with ${FRESH_CUT_PRESCRIPTION}. A stale/forged socket should be removed.`;"
|
|
207
|
+
],
|
|
208
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
209
|
+
"timeoutSeconds": 60,
|
|
210
|
+
"signature": "[QK:V2SURF-RECORD-LESS-SOCKET-HINT-FROM-ROOT]",
|
|
211
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
226
212
|
}
|
|
227
213
|
]
|
|
228
214
|
}
|
|
@@ -491,6 +491,17 @@
|
|
|
491
491
|
"timeoutSeconds": 60,
|
|
492
492
|
"signature": "[QK:V2RESUME-CC-MARKER-BINDING-READ]",
|
|
493
493
|
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"claim": "V2RESUME-REJECT-HINTS-FROM-ROOT",
|
|
497
|
+
"title": "one static rejection hint is re-authored as a code literal instead of the root ENTWURF.json text, so the reader stays exact while the rendered rejection no longer carries it",
|
|
498
|
+
"subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
|
|
499
|
+
"find": ["\t\"target-locked\": ENTWURF_RESUME_REJECT_HINTS[\"target-locked\"],"],
|
|
500
|
+
"replace": ["\t\"target-locked\": \"another resume or dispatch holds this citizen's lock\","],
|
|
501
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
|
|
502
|
+
"timeoutSeconds": 60,
|
|
503
|
+
"signature": "[QK:V2RESUME-REJECT-HINTS-FROM-ROOT]",
|
|
504
|
+
"signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
|
|
494
505
|
}
|
|
495
506
|
]
|
|
496
507
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* remote-pair-receive — `entwurf remote-pair-receive --pair <absolute file>` (#107 rail 1).
|
|
3
|
+
* remote-pair-receive — `entwurf remote-pair-receive --pair <absolute file>` (#107 rail 1, #149).
|
|
4
4
|
*
|
|
5
|
-
* The receiving host's entry for
|
|
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,
|
|
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
|
|
12
|
-
* this host accepts and, because
|
|
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
|
|
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
|
|
60
|
-
if (
|
|
61
|
-
|
|
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,
|
|
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
|
|
69
|
+
// The scope file IS this host's own opt-in, and the matched route names the sender's host: a
|
|
70
70
|
// matching claim always has a declared route back. Declared, not probed.
|
|
71
71
|
replyRoute: "declared",
|
|
72
72
|
});
|
|
@@ -461,6 +461,126 @@ export ENTWURF_AGY_STATUSLINE_TARGET="$SB/fake-status.sh"
|
|
|
461
461
|
export ENTWURF_AGY_IMPRINT_TARGET="$SB/fake-aux.sh"
|
|
462
462
|
export ENTWURF_COPILOT_STATUSLINE_TARGET="$SB/fake-aux.sh"
|
|
463
463
|
|
|
464
|
+
# Hermetic PATH for every dev-bin cell: the host's own entwurf entrypoints (e.g. a real
|
|
465
|
+
# ~/.local/bin/entwurf into some checkout) and its real pnpm/npm global bin dirs must not leak into
|
|
466
|
+
# the exclusivity verdict. Drop every host PATH dir that carries a package bin name, and put fixture
|
|
467
|
+
# package managers first; FAKE_PM_MODE picks their answer. Restored after the dev-bin cells.
|
|
468
|
+
DEVBIN_ORIG_PATH="$PATH"
|
|
469
|
+
PACKAGE_BINS="$(python3 -c 'import json,sys; print(" ".join(json.load(open(sys.argv[1]))["bin"]))' "$REPO_DIR/package.json")"
|
|
470
|
+
HERMETIC_PATH=""
|
|
471
|
+
IFS=':' read -r -a _dirs <<<"$PATH"
|
|
472
|
+
for _d in "${_dirs[@]}"; do
|
|
473
|
+
[ -n "$_d" ] || continue
|
|
474
|
+
for _b in $PACKAGE_BINS; do { [ -e "$_d/$_b" ] || [ -L "$_d/$_b" ]; } && continue 2; done
|
|
475
|
+
HERMETIC_PATH="${HERMETIC_PATH:+$HERMETIC_PATH:}$_d"
|
|
476
|
+
done
|
|
477
|
+
unset _dirs _d _b
|
|
478
|
+
FAKE_PM="$SB/fake-pm"; PM_ROOT="$SB/pm"; mkdir -p "$FAKE_PM"
|
|
479
|
+
for _pm in pnpm npm; do
|
|
480
|
+
case "$_pm" in pnpm) _q="bin -g"; _a="$PM_ROOT/pnpm-bin" ;; npm) _q="prefix -g"; _a="$PM_ROOT/npm-prefix" ;; esac
|
|
481
|
+
cat > "$FAKE_PM/$_pm" <<SH
|
|
482
|
+
#!/usr/bin/env bash
|
|
483
|
+
[ "\$*" = "$_q" ] || { echo "fixture $_pm answers only: $_q" >&2; exit 64; }
|
|
484
|
+
case "\${FAKE_PM_MODE:-ok}" in
|
|
485
|
+
ok) [ "$_pm" = npm ] || case ":\$PATH:" in *":$_a:"*) ;; *) echo "[ERROR] The configured global bin directory \"$_a\" is not in PATH" >&2; exit 1 ;; esac
|
|
486
|
+
echo "$_a" ;;
|
|
487
|
+
fail) echo "ERR_FIXTURE_$_pm boom" >&2; exit 1 ;;
|
|
488
|
+
hang) exec sleep 30 ;;
|
|
489
|
+
esac
|
|
490
|
+
SH
|
|
491
|
+
chmod +x "$FAKE_PM/$_pm"
|
|
492
|
+
done
|
|
493
|
+
unset _pm _q _a
|
|
494
|
+
mkdir -p "$PM_ROOT/pnpm-bin" # an existing, empty pnpm global bin dir
|
|
495
|
+
export PATH="$FAKE_PM:$HERMETIC_PATH:$PM_ROOT/pnpm-bin" # pnpm 11 answers `bin -g` only when that dir is on PATH
|
|
496
|
+
want "dev-bin hermetic PATH: no package bin name resolves, and pnpm/npm are the fixtures" \
|
|
497
|
+
"! command -v entwurf >/dev/null && [ \"\$(command -v pnpm)\" = '$FAKE_PM/pnpm' ] && [ \"\$(command -v npm)\" = '$FAKE_PM/npm' ]"
|
|
498
|
+
|
|
499
|
+
# J-X: developer-install exclusivity (dev-bin.sh exclusive). A certified PATH winner alone let an
|
|
500
|
+
# old global shim LATER in PATH stay selectable by a shell with another PATH order (non-interactive
|
|
501
|
+
# SSH picked it). Every cell links by NAME so an operator-set mutant elsewhere cannot reach here.
|
|
502
|
+
bash "$DEVBIN" expose entwurf >/dev/null 2>&1
|
|
503
|
+
bash "$DEVBIN" expose entwurf-bridge >/dev/null 2>&1
|
|
504
|
+
SAME_DIR="$SB/same-root"; mkdir -p "$SAME_DIR"
|
|
505
|
+
ln -s "$SB/fake-entwurf.sh" "$SAME_DIR/entwurf" # a second link, same target
|
|
506
|
+
ln -s "$DLINK" "$SAME_DIR/entwurf-bridge" # a chain through our link, same target
|
|
507
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$DBIN_DIR:$SAME_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
508
|
+
want "dev-bin exclusive: duplicate PATH entries and links resolving to our target are the same install [QK:SOURCE-SAME-TARGET-PASSES]" \
|
|
509
|
+
"[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'no other root under the 6 package bin names'"
|
|
510
|
+
want "dev-bin exclusive: an existing-but-empty package-manager global bin is checked and clean, and the PASS names its limit" \
|
|
511
|
+
"printf '%s' \"\$OUT\" | grep -q 'pnpm global bin $PM_ROOT/pnpm-bin' && printf '%s' \"\$OUT\" | grep -q 'another shell.s PATH or another package manager is not covered'"
|
|
512
|
+
TOOLS_ONLY="$SB/tools-only"; mkdir -p "$TOOLS_ONLY"
|
|
513
|
+
for _t in bash python3 dirname env; do ln -s "$(command -v "$_t")" "$TOOLS_ONLY/$_t"; done; unset _t
|
|
514
|
+
set +e; OUT="$(PATH="$TOOLS_ONLY:$DBIN_DIR" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
515
|
+
want "dev-bin exclusive: an absent package manager is a named not-checked line, not a failure" \
|
|
516
|
+
"[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'not checked: pnpm not on PATH' && printf '%s' \"\$OUT\" | grep -q 'not checked: npm not on PATH'"
|
|
517
|
+
rm -rf "$SAME_DIR"
|
|
518
|
+
|
|
519
|
+
OTHER_DIR="$SB/old-global/bin"; mkdir -p "$OTHER_DIR"
|
|
520
|
+
printf '#!/bin/sh\nexec /old/global/entwurf "$@"\n' > "$OTHER_DIR/entwurf"; chmod +x "$OTHER_DIR/entwurf"
|
|
521
|
+
want "dev-bin control: with an old shim LATER in PATH the winner is still ours, so verify alone passes" \
|
|
522
|
+
"PATH='$DBIN_DIR:$OTHER_DIR':\"\$PATH\" bash '$DEVBIN' verify entwurf >/dev/null 2>&1"
|
|
523
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
524
|
+
want "dev-bin exclusive: an old root LATER in PATH is named and refused, not hidden behind our winner [QK:SOURCE-LATER-ROOT-FAILS]" \
|
|
525
|
+
"[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF 'another entwurf root is still selectable: $OTHER_DIR/entwurf'"
|
|
526
|
+
want "dev-bin exclusive: the refusal removes nothing and says so" \
|
|
527
|
+
"[ \"\$(sed -n 2p '$OTHER_DIR/entwurf')\" = 'exec /old/global/entwurf \"\$@\"' ] && [ -L '$OLINK' ] && printf '%s' \"\$OUT\" | grep -q 'nothing was removed'"
|
|
528
|
+
mkdir -p "$SB/other-checkout/mcp/entwurf-bridge"; printf '#!/bin/sh\n' > "$SB/other-checkout/mcp/entwurf-bridge/start.sh"
|
|
529
|
+
rm -f "$OTHER_DIR/entwurf"; ln -s "$SB/other-checkout/mcp/entwurf-bridge/start.sh" "$OTHER_DIR/entwurf-bridge"
|
|
530
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
531
|
+
want "dev-bin exclusive: a helper name from another checkout is refused like the operator" \
|
|
532
|
+
"[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF '$OTHER_DIR/entwurf-bridge -> $SB/other-checkout/mcp/entwurf-bridge/start.sh'"
|
|
533
|
+
rm -rf "$SB/old-global"
|
|
534
|
+
|
|
535
|
+
mkdir -p "$PM_ROOT/npm-prefix/bin"; printf '#!/bin/sh\n' > "$SB/other-checkout/run.sh"
|
|
536
|
+
ln -s "$SB/other-checkout/run.sh" "$PM_ROOT/npm-prefix/bin/entwurf" # npm's <prefix>/bin, OFF this PATH
|
|
537
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
538
|
+
want "dev-bin exclusive: another root in npm's global bin dir is refused although that dir is not on this PATH [QK:SOURCE-GLOBAL-BIN-OTHER-ROOT]" \
|
|
539
|
+
"[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF '$PM_ROOT/npm-prefix/bin/entwurf -> $SB/other-checkout/run.sh' && printf '%s' \"\$OUT\" | grep -qx ' seen via: npm global bin'"
|
|
540
|
+
want "dev-bin exclusive: the removal hint is conditional on the package manager, never asserted" \
|
|
541
|
+
"printf '%s' \"\$OUT\" | grep -qF 'if a global @junghanacs/entwurf installed it: npm uninstall -g @junghanacs/entwurf' && printf '%s' \"\$OUT\" | grep -q 'otherwise remove it through whoever owns it'"
|
|
542
|
+
rm -rf "$PM_ROOT/npm-prefix"
|
|
543
|
+
|
|
544
|
+
set +e; OUT="$(FAKE_PM_MODE=fail PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
545
|
+
want "dev-bin exclusive: a package manager that cannot answer is refused, not read as clean [QK:SOURCE-GLOBAL-QUERY-REFUSED]" \
|
|
546
|
+
"[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect a package manager — \`pnpm bin -g\` exited 1: ERR_FIXTURE_pnpm boom'"
|
|
547
|
+
set +e; OUT="$(FAKE_PM_MODE=hang ENTWURF_DEV_BIN_PM_TIMEOUT=1 PATH="$DBIN_DIR:$PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
548
|
+
want "dev-bin exclusive: a package manager that hangs is bounded and refused" \
|
|
549
|
+
"[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -q 'did not answer within 1s'"
|
|
550
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$FAKE_PM:$HERMETIC_PATH" bash "$DEVBIN" exclusive 2>&1)"; RC=$?; set -e
|
|
551
|
+
want "dev-bin exclusive: pnpm refusing because its global bin dir is off PATH is an inspect failure, not a clean dir" \
|
|
552
|
+
"[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'is not in PATH'"
|
|
553
|
+
|
|
554
|
+
# The package manifest is read by the verdict step itself: an unreadable or wrong-shaped one is an
|
|
555
|
+
# inspect failure, never "0 names, no other root". Driven on a copy of THIS dev-bin.sh beside a
|
|
556
|
+
# sandbox package.json, so the repo's own manifest is never touched.
|
|
557
|
+
MANI="$SB/manifest-copy"; mkdir -p "$MANI/scripts"; cp "$DEVBIN" "$MANI/scripts/dev-bin.sh"
|
|
558
|
+
cp "$REPO_DIR/package.json" "$MANI/package.json"
|
|
559
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
|
|
560
|
+
want "dev-bin exclusive control: the copied script with the real manifest checks all 6 names and passes" \
|
|
561
|
+
"[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'no other root under the 6 package bin names'"
|
|
562
|
+
printf 'not json{' > "$MANI/package.json"
|
|
563
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
|
|
564
|
+
want "dev-bin exclusive: an unreadable package manifest is refused, never read as zero names [QK:SOURCE-MANIFEST-UNREADABLE-REFUSED]" \
|
|
565
|
+
"[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect the package manifest $MANI/package.json' && ! printf '%s' \"\$OUT\" | grep -q '0 package bin names'"
|
|
566
|
+
for _shape in '{"name":"x"}' '{"bin":"./run.sh"}' '{"bin":{}}' '{"bin":{"entwurf-bridge":"./x"}}'; do
|
|
567
|
+
printf '%s' "$_shape" > "$MANI/package.json"
|
|
568
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$PATH" bash "$MANI/scripts/dev-bin.sh" exclusive 2>&1)"; RC=$?; set -e
|
|
569
|
+
want "dev-bin exclusive: manifest $_shape is refused as uninspectable" \
|
|
570
|
+
"[ '$RC' -eq 6 ] && printf '%s' \"\$OUT\" | grep -qF 'could not inspect the package manifest'"
|
|
571
|
+
done
|
|
572
|
+
unset _shape; rm -rf "$MANI"
|
|
573
|
+
|
|
574
|
+
OTHER_DIR="$SB/old-global/bin"; mkdir -p "$OTHER_DIR"
|
|
575
|
+
printf '#!/bin/sh\nexec /old/global/entwurf "$@"\n' > "$OTHER_DIR/entwurf"; chmod +x "$OTHER_DIR/entwurf"
|
|
576
|
+
set +e; OUT="$(PATH="$DBIN_DIR:$OTHER_DIR:$PATH" bash "$REPO_DIR/run.sh" expose-dev-bin 2>&1)"; RC=$?; set -e
|
|
577
|
+
want "source setup's bins step fails when another root is still selectable [QK:SOURCE-EXCLUSIVE-WIRED]" \
|
|
578
|
+
"[ '$RC' -eq 5 ] && printf '%s' \"\$OUT\" | grep -qF 'another entwurf root is still selectable: $OTHER_DIR/entwurf'"
|
|
579
|
+
rm -rf "$SB/old-global" "$SB/other-checkout" "$TOOLS_ONLY"
|
|
580
|
+
bash "$DEVBIN" remove >/dev/null 2>&1 # the helper links expose-dev-bin just made
|
|
581
|
+
bash "$DEVBIN" remove entwurf >/dev/null 2>&1 # by NAME, like the expose above: the
|
|
582
|
+
bash "$DEVBIN" remove entwurf-bridge >/dev/null 2>&1 # managed list itself may be under test
|
|
583
|
+
|
|
464
584
|
# J-0: the setup wrapper calls no-arg expose. This is the cross-contract cell the #82 regression
|
|
465
585
|
# lacked: an npm consumer gets `entwurf` from package bin linking, and source setup must expose the
|
|
466
586
|
# same operator command without a second global package install.
|
|
@@ -578,6 +698,7 @@ ok "dev-bin migrate: foreign legacy refused (linkPath basename != entwurf-bridge
|
|
|
578
698
|
rm -f "$LEGACY" "$DLINK" "$DSTATE"
|
|
579
699
|
bash "$DEVBIN" remove >/dev/null 2>&1
|
|
580
700
|
unset ENTWURF_DEV_BIN_DIR ENTWURF_OPERATOR_TARGET ENTWURF_BRIDGE_TARGET ENTWURF_AGY_STATUSLINE_TARGET
|
|
701
|
+
export PATH="$DEVBIN_ORIG_PATH"
|
|
581
702
|
unset ENTWURF_AGY_IMPRINT_TARGET ENTWURF_COPILOT_STATUSLINE_TARGET
|
|
582
703
|
|
|
583
704
|
# ── K: permission grant — the OTHER half of a usable bridge ───────────────────
|
|
@@ -48,6 +48,7 @@ import * as os from "node:os";
|
|
|
48
48
|
import * as path from "node:path";
|
|
49
49
|
import { fileURLToPath } from "node:url";
|
|
50
50
|
import type { SenderEnvelope } from "../pi-extensions/lib/entwurf-control-rpc.ts";
|
|
51
|
+
import { nativePushPaused } from "../pi-extensions/lib/entwurf-v2-contract.ts";
|
|
51
52
|
import { makeProductionEntwurfV2Deps } from "../pi-extensions/lib/entwurf-v2-production.ts";
|
|
52
53
|
import type { EntwurfV2RunResult } from "../pi-extensions/lib/entwurf-v2-runner.ts";
|
|
53
54
|
import { runEntwurfV2 } from "../pi-extensions/lib/entwurf-v2-runner.ts";
|
|
@@ -79,6 +80,18 @@ function smokeSender(gardenId: string, cwd: string): SenderEnvelope {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
async function main(): Promise<void> {
|
|
83
|
+
// #152: agy native-push is PAUSED by product policy, so this positive LIVE acceptance cannot
|
|
84
|
+
// run — every cell below would drive the paused rail. It exits NON-ZERO by name (neither a PASS
|
|
85
|
+
// nor the protocol SKIP), and touches no agy process, port or conversation. The pause itself is
|
|
86
|
+
// proven deterministically beside the behavior (pi-extensions/lib/native-push/paused.test.ts).
|
|
87
|
+
if (nativePushPaused("antigravity")) {
|
|
88
|
+
console.error(
|
|
89
|
+
"[entwurf:paused] smoke-agy-native-push-live — agy native-push is paused (#152: its language " +
|
|
90
|
+
"server refused entwurf's measured external calls for a missing CSRF token). This LIVE acceptance does not run while " +
|
|
91
|
+
"the pause holds; it is not a PASS.",
|
|
92
|
+
);
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
82
95
|
if (process.env.LIVE !== "1") {
|
|
83
96
|
skipLive(
|
|
84
97
|
"smoke-agy-native-push-live",
|
|
@@ -75,12 +75,14 @@ import {
|
|
|
75
75
|
} from "../pi-extensions/lib/native-push/codex-ws-client.ts";
|
|
76
76
|
import {
|
|
77
77
|
buildCodexInstruction,
|
|
78
|
+
buildInitialPiBootstrapTask,
|
|
78
79
|
buildInitialPiPhaseOne,
|
|
79
80
|
buildInitialPiRelay,
|
|
80
81
|
runCleanupStages,
|
|
81
82
|
selectReport,
|
|
82
83
|
} from "./lib/codex-fresh-live-protocol.ts";
|
|
83
84
|
import {
|
|
85
|
+
assertBootstrapOrder,
|
|
84
86
|
assertExactSourceToolCalls,
|
|
85
87
|
assertSourceCallScopes,
|
|
86
88
|
codexCallbackSenders,
|
|
@@ -978,11 +980,7 @@ async function run(): Promise<void> {
|
|
|
978
980
|
backend: "pi",
|
|
979
981
|
model: piModel,
|
|
980
982
|
cwd: scratch,
|
|
981
|
-
task:
|
|
982
|
-
"Session-info already supplies device and time. Do not recheck time, cwd, or identity with shell or other tools. " +
|
|
983
|
-
"Your FIRST tool call must be mcp__entwurf_bridge__entwurf_callback with no arguments, and nothing before it. " +
|
|
984
|
-
`After your required callback receipt, end the turn and wait passively for one addressed message containing ${initialPiWaitToken}. ` +
|
|
985
|
-
"That message is the only authority to open the Codex sibling. Do not call shell, sleep, terminal, or any tool to wait.",
|
|
983
|
+
task: buildInitialPiBootstrapTask(initialPiWaitToken),
|
|
986
984
|
});
|
|
987
985
|
receipts["2-initial-pi-launch"] = initialPiLaunch.text;
|
|
988
986
|
ok(
|
|
@@ -1440,25 +1438,33 @@ async function run(): Promise<void> {
|
|
|
1440
1438
|
const observed = await readCodexThread(codexSocketPath, codexThreadId, true);
|
|
1441
1439
|
return codexThreadIsTerminal(observed) ? observed : null;
|
|
1442
1440
|
});
|
|
1441
|
+
// THE BOOTSTRAP ORDER, THEN THE ROLES — two leaves, composed. `[GLG 직접, 2026-10-24]` a fresh
|
|
1442
|
+
// sibling may check its caller with the read-only listing the framing offers, then calls back,
|
|
1443
|
+
// then works. `assertBootstrapOrder` admits only that prefix (successful exact `entwurf_peers`
|
|
1444
|
+
// checks), requires the exact callback once and successfully, and hands the rest to the
|
|
1445
|
+
// unchanged exact-once role audit — so `bash true` before the callback (the r3 RED) is still
|
|
1446
|
+
// refused, by the order leaf now rather than by a count. On Pi the receipts carry transcript
|
|
1447
|
+
// ordinals, so this also proves every peers RESULT was recorded before the callback was called,
|
|
1448
|
+
// and the callback RESULT before every task call.
|
|
1449
|
+
//
|
|
1450
|
+
// The callback is the ZERO-ARGUMENT eighth verb and `{}` is compared byte-exactly: a target, a
|
|
1451
|
+
// message or a `wants_reply` typed here is a parameter the verb does not have. Nothing is
|
|
1452
|
+
// weakened by the nonce not being in THIS expectation: the correlation is proven earlier and from
|
|
1453
|
+
// the delivered body — `the initial Pi callback carries the exact launch nonce` reads the
|
|
1454
|
+
// fixture's own mailbox, requires exactly one exact match and refuses duplicates, and the two
|
|
1455
|
+
// cells beside it bind that callback's sender envelope to a real pi V3 citizen.
|
|
1456
|
+
const initialPiSources = piSourceToolReceipts(readInitialPiEntries());
|
|
1457
|
+
const initialPiTaskCalls = assertBootstrapOrder(
|
|
1458
|
+
initialPiSources,
|
|
1459
|
+
// The guided task asked this Pi to take the offered check, so a zero-check PASS is not positive evidence.
|
|
1460
|
+
{ peersArguments: {}, callback: { toolName: "entwurf_callback", arguments: {} }, minPeersPrefix: 1 },
|
|
1461
|
+
"initial Pi bootstrap order",
|
|
1462
|
+
);
|
|
1463
|
+
// The verified prefix count: the order leaf admitted exactly these many leading peers checks.
|
|
1464
|
+
const piPeersPrefix = initialPiSources.length - initialPiTaskCalls.length - 1;
|
|
1443
1465
|
assertExactSourceToolCalls(
|
|
1444
|
-
|
|
1466
|
+
initialPiTaskCalls,
|
|
1445
1467
|
[
|
|
1446
|
-
// 0.24.0 moved a pi sibling's first action to the ZERO-ARGUMENT eighth verb, and this
|
|
1447
|
-
// smoke's own launch receipt says so in the same run: "its first action is the
|
|
1448
|
-
// zero-argument callback verb (codex is the one arm that still sends the nonce as
|
|
1449
|
-
// arguments)". The Codex audit below therefore keeps its `entwurf_v2` shape, and only
|
|
1450
|
-
// this one moves. `{}` is matched by deep-strict equality, so it pins the verb's actual
|
|
1451
|
-
// contract — a target or message typed here would be a second address axis and the verb
|
|
1452
|
-
// refuses it by name.
|
|
1453
|
-
//
|
|
1454
|
-
// Nothing is weakened by dropping the nonce from THIS expectation: the correlation is
|
|
1455
|
-
// proven earlier and from the delivered body, which is where a nonce can actually be
|
|
1456
|
-
// corrupted — `the initial Pi callback carries the exact launch nonce` (:970-973) reads
|
|
1457
|
-
// the fixture's own mailbox, requires exactly one exact match and refuses duplicates,
|
|
1458
|
-
// and the two cells beside it bind that callback's sender envelope to a real pi V3
|
|
1459
|
-
// citizen. This audit's job is the different one its name says: exactly these calls,
|
|
1460
|
-
// once each, and nothing else.
|
|
1461
|
-
{ toolName: "entwurf_callback", arguments: {} },
|
|
1462
1468
|
{
|
|
1463
1469
|
toolName: "entwurf_fresh_call",
|
|
1464
1470
|
arguments: {
|
|
@@ -1480,10 +1486,18 @@ async function run(): Promise<void> {
|
|
|
1480
1486
|
],
|
|
1481
1487
|
"initial Pi final exact-once audit",
|
|
1482
1488
|
);
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1489
|
+
// Codex: the same composition over the Entwurf MCP calls `thread/read` reports (this smoke's
|
|
1490
|
+
// Codex scope — no vendor-native action taxonomy). Its callback is `entwurf_v2` to the initial Pi
|
|
1491
|
+
// with the nonce, so `wants_reply` keeps its schema default there. `thread/read` gives item order
|
|
1492
|
+
// and a status at audit time only: on Codex this proves CALL ORDER and completion-at-audit; that a
|
|
1493
|
+
// peers result preceded the callback call, and that the callback result preceded the next call,
|
|
1494
|
+
// are both UNVERIFIED.
|
|
1495
|
+
const codexFinalSources = codexSourceToolReceipts(codexThread);
|
|
1496
|
+
const codexTaskCalls = assertBootstrapOrder(
|
|
1497
|
+
codexFinalSources,
|
|
1498
|
+
{
|
|
1499
|
+
peersArguments: {},
|
|
1500
|
+
callback: {
|
|
1487
1501
|
toolName: "entwurf_v2",
|
|
1488
1502
|
arguments: {
|
|
1489
1503
|
target: initialPiGid,
|
|
@@ -1492,6 +1506,13 @@ async function run(): Promise<void> {
|
|
|
1492
1506
|
message: codexNonce,
|
|
1493
1507
|
},
|
|
1494
1508
|
},
|
|
1509
|
+
},
|
|
1510
|
+
"Codex bootstrap order",
|
|
1511
|
+
);
|
|
1512
|
+
const codexPeersPrefix = codexFinalSources.length - codexTaskCalls.length - 1;
|
|
1513
|
+
assertExactSourceToolCalls(
|
|
1514
|
+
codexTaskCalls,
|
|
1515
|
+
[
|
|
1495
1516
|
{
|
|
1496
1517
|
toolName: "entwurf_fresh_call",
|
|
1497
1518
|
arguments: { backend: "pi", model: piModel, task: outboundPiTask },
|
|
@@ -1508,7 +1529,9 @@ async function run(): Promise<void> {
|
|
|
1508
1529
|
],
|
|
1509
1530
|
"Codex final exact-once audit",
|
|
1510
1531
|
);
|
|
1511
|
-
receipts["16-source-final-audit"] =
|
|
1532
|
+
receipts["16-source-final-audit"] =
|
|
1533
|
+
`initial-pi=bootstrap order (peers prefix=${piPeersPrefix} → callback, results-before-next-call by transcript ordinal) + 2/2 task roles exact\n` +
|
|
1534
|
+
`codex=bootstrap order (peers prefix=${codexPeersPrefix} → callback, call order only) + 2/2 task roles exact`;
|
|
1512
1535
|
}
|
|
1513
1536
|
|
|
1514
1537
|
let failure: unknown = null;
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
writeMetaSenderMarker,
|
|
65
65
|
} from "../pi-extensions/lib/meta-session.ts";
|
|
66
66
|
import { terminateChild } from "./lib/acp-child-cleanup.ts";
|
|
67
|
+
import { awaitTerminusPublication } from "./lib/chain-terminus-publication.ts";
|
|
67
68
|
import { skipLive } from "./lib/live-skip.ts";
|
|
68
69
|
|
|
69
70
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -390,14 +391,11 @@ async function main(): Promise<void> {
|
|
|
390
391
|
|
|
391
392
|
// ── the chain runs on its own from here; wait for the terminus ───────────
|
|
392
393
|
const boxDir = path.join(mailboxDir, gidD);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
);
|
|
399
|
-
if (msgs.length > 0) break;
|
|
400
|
-
await sleep(POLL_MS);
|
|
394
|
+
// Body AND doorbell, inside the same deadline: enqueue publishes `.msg` first and
|
|
395
|
+
// `inbox.signal` last, so a body alone is a publication still in flight (P5 2026-10-24).
|
|
396
|
+
const { msgs, signalled } = await awaitTerminusPublication(boxDir, Date.now() + CHAIN_TIMEOUT_MS, POLL_MS);
|
|
397
|
+
if (msgs.length > 0 && !signalled) {
|
|
398
|
+
console.error(`[smoke-entwurf-chain-live] terminus at timeout: ${msgs.length} body file(s) but no inbox.signal`);
|
|
401
399
|
}
|
|
402
400
|
if (msgs.length === 0) {
|
|
403
401
|
let ownerAlive = false;
|
|
@@ -423,7 +421,7 @@ async function main(): Promise<void> {
|
|
|
423
421
|
console.error(`[smoke-entwurf-chain-live] C stream tail:\n${streamC.slice(-2000)}`);
|
|
424
422
|
}
|
|
425
423
|
ok(`the chain reached the mailbox terminus (${msgs.length} .msg)`, msgs.length === 1);
|
|
426
|
-
ok("the terminus doorbell was poked",
|
|
424
|
+
ok("the terminus doorbell was poked", signalled);
|
|
427
425
|
|
|
428
426
|
const body = await fsp.readFile(path.join(boxDir, msgs[0] as string), "utf8");
|
|
429
427
|
|