@junghanacs/entwurf 0.23.0 → 0.24.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 +3 -1
- package/BASELINE.md +2 -0
- package/CHANGELOG.md +307 -0
- package/FAQ.md +47 -0
- package/README.md +5 -3
- package/VERIFY.md +43 -2
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +97 -18
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +33 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +7 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +41 -1
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/package.json +15 -13
- package/pi-extensions/entwurf-control.ts +56 -0
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +20 -10
- package/pi-extensions/lib/acp/context.ts +30 -10
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/acp/session-store.ts +2 -2
- package/pi-extensions/lib/acp/tool-surface.ts +2 -1
- package/pi-extensions/lib/callback-env.ts +78 -0
- package/pi-extensions/lib/compaction-send-guard.ts +4 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
- package/pi-extensions/lib/fresh-call-composition.ts +56 -22
- package/pi-extensions/lib/herdr-fresh-call.ts +12 -4
- package/pi-extensions/lib/herdr-placement.ts +13 -4
- package/pi-extensions/lib/mux-fresh-call.ts +23 -8
- package/pi-extensions/meta-bridge-omp.ts +35 -48
- package/run.sh +327 -246
- package/scripts/check-acp-backend-preflight.ts +117 -9
- package/scripts/check-acp-carrier-augment.ts +6 -4
- package/scripts/check-acp-event-mapper.ts +17 -6
- package/scripts/check-acp-prompt-builder.ts +17 -9
- package/scripts/check-acp-prompt-lifecycle.ts +23 -6
- package/scripts/check-acp-session-reuse.ts +64 -51
- package/scripts/check-acp-session-store.ts +31 -8
- package/scripts/check-acp-stop-reason.ts +20 -2
- package/scripts/check-acp-stream-hooks.ts +22 -5
- package/scripts/check-acp-usage-accounting.ts +42 -18
- package/scripts/check-entwurf-bridge-boot.ts +16 -1
- package/scripts/check-fresh-call-dispatch.ts +4 -5
- package/scripts/check-gate-qualification.ts +45 -5
- package/scripts/check-herdr-activation.ts +73 -0
- package/scripts/check-herdr-fresh-call.ts +22 -6
- package/scripts/check-herdr-sandbox.ts +14 -2
- package/scripts/check-install-container.sh +1 -1
- package/scripts/check-pi-launch.ts +361 -0
- package/scripts/check-release-gate-outcomes.ts +7 -0
- package/scripts/inventory-verification-surface.ts +86 -10
- package/scripts/lib/mutation-qualify.ts +1 -1
- package/scripts/mutants/acp-backend-preflight.json +20 -0
- package/scripts/mutants/bridge-boot-resume.json +26 -0
- package/scripts/mutants/herdr-activation.json +16 -0
- package/scripts/mutants/herdr-fresh-call.json +11 -0
- package/scripts/mutants/herdr-plugin-build.json +1 -1
- package/scripts/mutants/mux-fresh-call.json +21 -8
- package/scripts/mutants/omp-fresh.json +5 -9
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-launch.json +113 -0
- package/scripts/mutants/resume-args.json +6 -6
- package/scripts/mutants/v2-surface.json +2 -2
- package/scripts/mutants/v2-visible-resume.json +17 -17
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +23 -5
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +56 -28
- package/scripts/smoke-herdr-raw-install-live.sh +877 -0
- package/scripts/smoke-omp-fresh-live.ts +4 -2
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-provider-state.sh +2 -2
- package/scripts/tsconfig.json +5 -0
- package/scripts/check-entwurf-facts.ts +0 -391
- package/scripts/check-entwurf-resume-args.ts +0 -122
- package/scripts/check-entwurf-v2-decider.ts +0 -657
- package/scripts/check-entwurf-v2-lock.ts +0 -371
- package/scripts/check-entwurf-v2-mailbox.ts +0 -261
- package/scripts/check-entwurf-v2-matrix.ts +0 -401
- package/scripts/check-entwurf-v2-native-push.ts +0 -221
- package/scripts/check-entwurf-v2-release.ts +0 -151
- package/scripts/check-entwurf-v2-runner.ts +0 -448
- package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
- package/scripts/check-entwurf-v2-send.ts +0 -458
- package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
- package/scripts/check-socket-discovery.ts +0 -401
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* check-entwurf-v2-send-fallback — deterministic gate for the 5c-2b same-lock
|
|
3
|
-
* re-resolve resolver (`resolveDeadControlSendFallback`). Proves the dead-control-send
|
|
4
|
-
* fallback routing over injected fakes, with NO filesystem, AND that the resolver never
|
|
5
|
-
* releases / never spawns / never mis-routes off the held gid:
|
|
6
|
-
*
|
|
7
|
-
* 1. mis-wire: plan/lock gid mismatch → throws BEFORE any IO (no resolveTarget call).
|
|
8
|
-
* 2. bad-target (identity null) → reject, NO inspect/probe.
|
|
9
|
-
* 3. preProbeAddressConflict → reject, NO inspect/probe.
|
|
10
|
-
* 4. unsupported backend + seam deliverable → meta-mailbox plan, NO inspect/probe;
|
|
11
|
-
* message/wantsReply preserved, same target gid.
|
|
12
|
-
* 5. unsupported backend + seam undeliverable → reject, NO inspect/probe. A self-fetch
|
|
13
|
-
* citizen with an INACTIVE receiver is refused here too (SE-2 2d-3 active-receiver gate).
|
|
14
|
-
* 6. in-domain pi + socket-file + probe alive → control-socket plan with the INSPECTED
|
|
15
|
-
* socketPath; message/mode/wantsReply preserved, same target gid.
|
|
16
|
-
* 7. in-domain pi + absent (ENOENT → dead) → reject (dormant-fire-forget-unsupported);
|
|
17
|
-
* NEVER a mailbox.
|
|
18
|
-
* 8. in-domain pi + probe indeterminate → reject (indeterminate-no-spawn); no mailbox.
|
|
19
|
-
* 9. in-domain pi + inspect indeterminate → reject; no probe connect, no mailbox.
|
|
20
|
-
* 10. address-conflict (symlink) → reject (target-address-conflict).
|
|
21
|
-
* 11. probe throw / inspect throw → PROPAGATE (resolver does not catch; the 5c-2a hand
|
|
22
|
-
* owns failed+release). The resolver NEVER calls a release seam (it has none).
|
|
23
|
-
* 12. every execute plan targets plan.targetGardenId === lock.gardenId, and is one of
|
|
24
|
-
* control-socket / meta-mailbox only.
|
|
25
|
-
*
|
|
26
|
-
* No real IO — fakes count inspect/probe calls so "short-circuits before probing" is
|
|
27
|
-
* asserted structurally.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import assert from "node:assert/strict";
|
|
31
|
-
import type { TargetResolution } from "../pi-extensions/lib/entwurf-v2-decider.ts";
|
|
32
|
-
import type { LockClaim } from "../pi-extensions/lib/entwurf-v2-lock.ts";
|
|
33
|
-
import type { ControlSocketPlan } from "../pi-extensions/lib/entwurf-v2-send.ts";
|
|
34
|
-
import {
|
|
35
|
-
type DeadFallbackDeps,
|
|
36
|
-
resolveDeadControlSendFallback,
|
|
37
|
-
} from "../pi-extensions/lib/entwurf-v2-send-fallback.ts";
|
|
38
|
-
import type { MetaCitizenBackend, MetaIdentity } from "../pi-extensions/lib/meta-session.ts";
|
|
39
|
-
import type { TargetSocketInspection } from "../pi-extensions/lib/socket-discovery.ts";
|
|
40
|
-
import type { SocketLiveness } from "../pi-extensions/lib/socket-probe.ts";
|
|
41
|
-
|
|
42
|
-
let passed = 0;
|
|
43
|
-
function ok(label: string, cond: boolean): void {
|
|
44
|
-
assert.ok(cond, label);
|
|
45
|
-
console.log(` ok ${label}`);
|
|
46
|
-
passed++;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const GID = "20260612T100000-aaaaaa";
|
|
50
|
-
const WRONG_GID = "20260612T999999-bbbbbb";
|
|
51
|
-
const CWD = "/home/junghan/repos/gh/entwurf";
|
|
52
|
-
|
|
53
|
-
function lockClaim(gardenId = GID): LockClaim {
|
|
54
|
-
return {
|
|
55
|
-
gardenId,
|
|
56
|
-
pid: 4242,
|
|
57
|
-
hostname: "test-host",
|
|
58
|
-
createdAt: "2026-06-12T01:00:00.000Z",
|
|
59
|
-
nonce: "deadbeefcafef00d",
|
|
60
|
-
owner: "entwurf_v2",
|
|
61
|
-
lockPath: `/fake/locks/${gardenId}.lock`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function identity(backend: MetaCitizenBackend): MetaIdentity {
|
|
66
|
-
return {
|
|
67
|
-
schemaVersion: 3,
|
|
68
|
-
gardenId: GID,
|
|
69
|
-
backend,
|
|
70
|
-
nativeSessionId: `native-${GID}`,
|
|
71
|
-
cwd: CWD,
|
|
72
|
-
model: null,
|
|
73
|
-
transcriptPath: null,
|
|
74
|
-
createdAt: "2026-06-12T01:00:00.000Z",
|
|
75
|
-
recordUpdatedAt: "2026-06-12T01:00:00.000Z",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const CONTROL_PLAN = {
|
|
80
|
-
transport: "control-socket",
|
|
81
|
-
action: "send",
|
|
82
|
-
targetGardenId: GID,
|
|
83
|
-
socketPath: "/fake/ctl/orig.sock",
|
|
84
|
-
mode: "follow_up",
|
|
85
|
-
wantsReply: true,
|
|
86
|
-
message: "hello",
|
|
87
|
-
} as const satisfies ControlSocketPlan;
|
|
88
|
-
|
|
89
|
-
interface Trace {
|
|
90
|
-
inspectCalls: number;
|
|
91
|
-
probeCalls: number;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
interface FakeSpec {
|
|
95
|
-
resolution: TargetResolution;
|
|
96
|
-
/** SE-2 2d-3: the verdict the injected mailboxDeliverabilityFor seam returns on the
|
|
97
|
-
* unsupported path. Default false (fail-closed) — the resolver trusts the seam's
|
|
98
|
-
* active-receiver judgement, never wake-mode alone. */
|
|
99
|
-
mailboxDeliverable?: boolean;
|
|
100
|
-
inspection?: TargetSocketInspection | { throw: unknown };
|
|
101
|
-
probe?: SocketLiveness | { throw: unknown };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function makeDeps(spec: FakeSpec): { deps: DeadFallbackDeps; trace: Trace } {
|
|
105
|
-
const trace: Trace = { inspectCalls: 0, probeCalls: 0 };
|
|
106
|
-
const deps: DeadFallbackDeps = {
|
|
107
|
-
async resolveTarget() {
|
|
108
|
-
return spec.resolution;
|
|
109
|
-
},
|
|
110
|
-
async inspectSocket(_gid) {
|
|
111
|
-
trace.inspectCalls++;
|
|
112
|
-
if (!spec.inspection) throw new Error("test: inspectSocket called but no inspection spec");
|
|
113
|
-
if ("throw" in spec.inspection) throw spec.inspection.throw;
|
|
114
|
-
return spec.inspection;
|
|
115
|
-
},
|
|
116
|
-
async probeSocket(_path) {
|
|
117
|
-
trace.probeCalls++;
|
|
118
|
-
if (spec.probe === undefined) throw new Error("test: probeSocket called but no probe spec");
|
|
119
|
-
if (typeof spec.probe === "object" && "throw" in spec.probe) throw spec.probe.throw;
|
|
120
|
-
return spec.probe;
|
|
121
|
-
},
|
|
122
|
-
mailboxDeliverabilityFor: () => ({ deliverable: spec.mailboxDeliverable ?? false, reason: "fake-deliverability" }),
|
|
123
|
-
};
|
|
124
|
-
return { deps, trace };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function present(identityArg: MetaIdentity): TargetResolution {
|
|
128
|
-
return { identity: identityArg, preProbeAddressConflict: false };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function rejects(fn: () => Promise<unknown>): Promise<unknown> {
|
|
132
|
-
try {
|
|
133
|
-
await fn();
|
|
134
|
-
return Symbol("did-not-throw");
|
|
135
|
-
} catch (err) {
|
|
136
|
-
return err;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function main(): Promise<void> {
|
|
141
|
-
// ── 1: mis-wire → throws before any IO ───────────────────────────────────
|
|
142
|
-
{
|
|
143
|
-
const { deps, trace } = makeDeps({ resolution: present(identity("pi")) });
|
|
144
|
-
const err = await rejects(() => resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(WRONG_GID), deps));
|
|
145
|
-
ok("mis-wire (plan/lock gid mismatch) → throws", err instanceof Error);
|
|
146
|
-
ok("mis-wire → no inspect/probe (fail-loud before IO)", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── 2: bad-target → reject, no inspect/probe ──────────────────────────────
|
|
150
|
-
{
|
|
151
|
-
const { deps, trace } = makeDeps({ resolution: { identity: null, preProbeAddressConflict: false } });
|
|
152
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
153
|
-
ok("bad-target → reject", r.kind === "reject" && r.reason === "bad-target");
|
|
154
|
-
ok("bad-target → no inspect/probe", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ── 3: preProbeAddressConflict → reject, no inspect/probe ──────────────────
|
|
158
|
-
{
|
|
159
|
-
const { deps, trace } = makeDeps({
|
|
160
|
-
resolution: { identity: identity("pi"), preProbeAddressConflict: true },
|
|
161
|
-
});
|
|
162
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
163
|
-
ok("preProbeAddressConflict → reject", r.kind === "reject" && r.reason === "target-address-conflict");
|
|
164
|
-
ok("preProbeAddressConflict → no inspect/probe", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ── 4: unsupported + deliverable → meta-mailbox plan, no inspect/probe ─────
|
|
168
|
-
// Deliverability is the seam's verdict (active receiver), not wake-mode alone.
|
|
169
|
-
{
|
|
170
|
-
const { deps, trace } = makeDeps({ resolution: present(identity("claude-code")), mailboxDeliverable: true });
|
|
171
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
172
|
-
ok("unsupported + deliverable → execute", r.kind === "execute");
|
|
173
|
-
ok(
|
|
174
|
-
"unsupported + deliverable → meta-mailbox plan, same target, msg preserved",
|
|
175
|
-
r.kind === "execute" &&
|
|
176
|
-
r.plan.transport === "meta-mailbox" &&
|
|
177
|
-
r.plan.targetGardenId === GID &&
|
|
178
|
-
r.plan.message === "hello" &&
|
|
179
|
-
r.plan.wantsReply === true,
|
|
180
|
-
);
|
|
181
|
-
ok("unsupported → no inspect/probe (mailbox mini-table)", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── 5: unsupported + seam undeliverable → reject, no inspect/probe ─────────
|
|
185
|
-
{
|
|
186
|
-
const { deps, trace } = makeDeps({ resolution: present(identity("codex")), mailboxDeliverable: false });
|
|
187
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
188
|
-
ok("unsupported + undeliverable → reject (mailbox-undeliverable)", r.kind === "reject");
|
|
189
|
-
ok("unsupported + undeliverable → no inspect/probe", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ── 5b: SE-2 2d-3 — a self-fetch CITIZEN with an INACTIVE receiver (seam false) →
|
|
193
|
-
// reject, NO inspect/probe. The dead-fallback honours the same active-receiver gate as
|
|
194
|
-
// the decider, so a re-resolve cannot smuggle a reply into a terminated session's mailbox.
|
|
195
|
-
{
|
|
196
|
-
const { deps, trace } = makeDeps({ resolution: present(identity("claude-code")), mailboxDeliverable: false });
|
|
197
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
198
|
-
ok("self-fetch citizen + inactive receiver → reject (SE-2 2d-3)", r.kind === "reject");
|
|
199
|
-
ok("self-fetch citizen + inactive → no inspect/probe", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ── 6: in-domain pi + socket-file + probe alive → control-socket retry ─────
|
|
203
|
-
{
|
|
204
|
-
const { deps, trace } = makeDeps({
|
|
205
|
-
resolution: present(identity("pi")),
|
|
206
|
-
inspection: { kind: "socket-file", socketPath: "/fake/ctl/fresh.sock" },
|
|
207
|
-
probe: "alive",
|
|
208
|
-
});
|
|
209
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
210
|
-
ok("pi + alive → execute control-socket", r.kind === "execute" && r.plan.transport === "control-socket");
|
|
211
|
-
ok(
|
|
212
|
-
"pi + alive → uses the INSPECTED socketPath, preserves mode/msg/wantsReply, same target",
|
|
213
|
-
r.kind === "execute" &&
|
|
214
|
-
r.plan.transport === "control-socket" &&
|
|
215
|
-
r.plan.socketPath === "/fake/ctl/fresh.sock" &&
|
|
216
|
-
r.plan.mode === "follow_up" &&
|
|
217
|
-
r.plan.message === "hello" &&
|
|
218
|
-
r.plan.wantsReply === true &&
|
|
219
|
-
r.plan.targetGardenId === GID,
|
|
220
|
-
);
|
|
221
|
-
ok("pi + alive → inspected once, probed once", trace.inspectCalls === 1 && trace.probeCalls === 1);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ── 7: in-domain pi + absent (dead) → reject, NEVER a mailbox enqueue ─────
|
|
225
|
-
{
|
|
226
|
-
const { deps, trace } = makeDeps({
|
|
227
|
-
resolution: present(identity("pi")),
|
|
228
|
-
inspection: { kind: "absent", socketPath: "/fake/ctl/gone.sock" },
|
|
229
|
-
});
|
|
230
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
231
|
-
ok("pi + dead → reject (dormant-fire-forget-unsupported)", r.kind === "reject");
|
|
232
|
-
ok(
|
|
233
|
-
"pi + dead → reject reason is the fire-and-forget dormant cell",
|
|
234
|
-
r.kind === "reject" && r.reason === "dormant-fire-forget-unsupported",
|
|
235
|
-
);
|
|
236
|
-
ok("pi + dead → never probes (absent short-circuits)", trace.probeCalls === 0);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ── 8: in-domain pi + probe indeterminate → reject, no mailbox ────────────
|
|
240
|
-
{
|
|
241
|
-
const { deps } = makeDeps({
|
|
242
|
-
resolution: present(identity("pi")),
|
|
243
|
-
inspection: { kind: "socket-file", socketPath: "/fake/ctl/stall.sock" },
|
|
244
|
-
probe: "indeterminate",
|
|
245
|
-
});
|
|
246
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
247
|
-
ok("pi + probe indeterminate → reject (indeterminate-no-spawn)", r.kind === "reject");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ── 9: in-domain pi + inspect indeterminate → reject, no probe ────────────
|
|
251
|
-
{
|
|
252
|
-
const { deps, trace } = makeDeps({
|
|
253
|
-
resolution: present(identity("pi")),
|
|
254
|
-
inspection: { kind: "indeterminate", socketPath: "/fake/ctl/x.sock", error: "EACCES" },
|
|
255
|
-
});
|
|
256
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
257
|
-
ok("pi + inspect indeterminate → reject", r.kind === "reject");
|
|
258
|
-
ok("pi + inspect indeterminate → never connects (no probe)", trace.probeCalls === 0);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ── 9b: #50 C4 — a record that VANISHED mid-dispatch while its socket remains
|
|
262
|
-
// re-resolves to the honest `record-less-socket` reject (a diagnostic
|
|
263
|
-
// state), never a retry into a socket the record no longer authorizes, and never
|
|
264
|
-
// the `bad-target` "absent" lie. Pre-probe: the fallback must NOT re-enter the
|
|
265
|
-
// in-domain inspect/probe path for it (inspectCalls === 0). A plain vanished
|
|
266
|
-
// target (no socket) stays bad-target — the two must not blur. ─────────────────
|
|
267
|
-
const recordLess: TargetResolution = { identity: null, preProbeAddressConflict: false, recordLessSocket: true };
|
|
268
|
-
{
|
|
269
|
-
const { deps, trace } = makeDeps({
|
|
270
|
-
resolution: recordLess,
|
|
271
|
-
inspection: { kind: "socket-file", socketPath: "/fake/ctl/fresh.sock" },
|
|
272
|
-
probe: "alive",
|
|
273
|
-
});
|
|
274
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
275
|
-
ok(
|
|
276
|
-
"recordLess re-resolve → reject record-less-socket (never a retry, never bad-target)",
|
|
277
|
-
r.kind === "reject" && r.reason === "record-less-socket",
|
|
278
|
-
);
|
|
279
|
-
ok(
|
|
280
|
-
"recordLess re-resolve → NO in-domain inspect/probe (pre-probe short-circuit)",
|
|
281
|
-
trace.inspectCalls === 0 && trace.probeCalls === 0,
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
{
|
|
285
|
-
const { deps, trace } = makeDeps({
|
|
286
|
-
resolution: { identity: null, preProbeAddressConflict: false, recordLessSocket: false },
|
|
287
|
-
inspection: { kind: "absent", socketPath: "/fake/ctl/gone.sock" },
|
|
288
|
-
});
|
|
289
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
290
|
-
ok("vanished target (no socket) → reject bad-target", r.kind === "reject" && r.reason === "bad-target");
|
|
291
|
-
ok("vanished target → no inspect/probe", trace.inspectCalls === 0 && trace.probeCalls === 0);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ── 10: address-conflict (symlink) → reject ───────────────────────────────
|
|
295
|
-
{
|
|
296
|
-
const { deps } = makeDeps({
|
|
297
|
-
resolution: present(identity("pi")),
|
|
298
|
-
inspection: { kind: "address-conflict", socketPath: "/fake/ctl/link.sock", reason: "symlink" },
|
|
299
|
-
});
|
|
300
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
301
|
-
ok(
|
|
302
|
-
"address-conflict → reject (target-address-conflict)",
|
|
303
|
-
r.kind === "reject" && r.reason === "target-address-conflict",
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ── 11: probe throw / inspect throw → PROPAGATE (resolver never catches) ───
|
|
308
|
-
{
|
|
309
|
-
const probeBoom = new Error("probe boom");
|
|
310
|
-
const { deps: d1 } = makeDeps({
|
|
311
|
-
resolution: present(identity("pi")),
|
|
312
|
-
inspection: { kind: "socket-file", socketPath: "/fake/ctl/p.sock" },
|
|
313
|
-
probe: { throw: probeBoom },
|
|
314
|
-
});
|
|
315
|
-
const e1 = await rejects(() => resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), d1));
|
|
316
|
-
ok("probe throw → propagates (hand owns failed+release)", e1 === probeBoom);
|
|
317
|
-
|
|
318
|
-
const inspectBoom = new Error("inspect boom");
|
|
319
|
-
const { deps: d2 } = makeDeps({
|
|
320
|
-
resolution: present(identity("pi")),
|
|
321
|
-
inspection: { throw: inspectBoom },
|
|
322
|
-
});
|
|
323
|
-
const e2 = await rejects(() => resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), d2));
|
|
324
|
-
ok("inspect throw → propagates", e2 === inspectBoom);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ── 12: every execute plan targets the held gid ───────────────────────────
|
|
328
|
-
{
|
|
329
|
-
const cases: FakeSpec[] = [
|
|
330
|
-
{ resolution: present(identity("claude-code")), mailboxDeliverable: true },
|
|
331
|
-
{
|
|
332
|
-
resolution: present(identity("pi")),
|
|
333
|
-
inspection: { kind: "socket-file", socketPath: "/fake/ctl/a.sock" },
|
|
334
|
-
probe: "alive",
|
|
335
|
-
},
|
|
336
|
-
];
|
|
337
|
-
let allSameTargetNonSpawn = true;
|
|
338
|
-
for (const s of cases) {
|
|
339
|
-
const { deps } = makeDeps(s);
|
|
340
|
-
const r = await resolveDeadControlSendFallback(CONTROL_PLAN, lockClaim(), deps);
|
|
341
|
-
if (r.kind !== "execute") allSameTargetNonSpawn = false;
|
|
342
|
-
else if (r.plan.targetGardenId !== GID) allSameTargetNonSpawn = false;
|
|
343
|
-
}
|
|
344
|
-
ok("every execute plan: same target gid (a SEND fallback never re-targets)", allSameTargetNonSpawn);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
console.log(`\n[check-entwurf-v2-send-fallback] ${passed} assertions ok`);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
await main();
|