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