@junghanacs/entwurf 0.12.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 +240 -0
- package/BASELINE.md +227 -0
- package/CHANGELOG.md +1210 -0
- package/CONTRIBUTING.md +63 -0
- package/DELIVERY.md +209 -0
- package/LICENSE +21 -0
- package/README.md +504 -0
- package/VERIFY.md +260 -0
- package/demo/README.md +188 -0
- package/demo/demo-baseline.sh +156 -0
- package/demo/demo.sh +183 -0
- package/docs/assets/entwurf-demo.gif +0 -0
- package/docs/assets/entwurf-doomemacs.gif +0 -0
- package/docs/assets/entwurf-entwurf.gif +0 -0
- package/docs/assets/entwurf-hero.jpg +0 -0
- package/docs/setup-clean-host.md +305 -0
- package/mcp/entwurf-bridge/src/index.ts +513 -0
- package/mcp/entwurf-bridge/start.sh +25 -0
- package/mcp/entwurf-bridge/test.sh +54 -0
- package/mcp/tsconfig.json +29 -0
- package/package.json +130 -0
- package/pi/entwurf-capabilities.json +9 -0
- package/pi/entwurf-targets.json +20 -0
- package/pi/meta-bridge/.claude-plugin/marketplace.json +12 -0
- package/pi/meta-bridge/entwurf-meta-receive/.claude-plugin/plugin.json +5 -0
- package/pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json +50 -0
- package/pi/meta-bridge/entwurf-meta-receive/scripts/doorbell.sh +54 -0
- package/pi/settings.reference.json +43 -0
- package/pi/skill-plugin-example/.claude-plugin/plugin.json +4 -0
- package/pi/skill-plugin-example/skills/hello/SKILL.md +18 -0
- package/pi-extensions/acp-provider.ts +63 -0
- package/pi-extensions/entwurf-control.ts +1692 -0
- package/pi-extensions/lib/acp/acp-client.ts +90 -0
- package/pi-extensions/lib/acp/augment.ts +238 -0
- package/pi-extensions/lib/acp/backend-adapter.ts +319 -0
- package/pi-extensions/lib/acp/backend.ts +873 -0
- package/pi-extensions/lib/acp/config.ts +552 -0
- package/pi-extensions/lib/acp/context.ts +177 -0
- package/pi-extensions/lib/acp/engraving.ts +123 -0
- package/pi-extensions/lib/acp/event-mapper.ts +339 -0
- package/pi-extensions/lib/acp/models.ts +102 -0
- package/pi-extensions/lib/acp/overlay.ts +220 -0
- package/pi-extensions/lib/acp/prompts/engraving.md +1 -0
- package/pi-extensions/lib/acp/session-store.ts +418 -0
- package/pi-extensions/lib/acp/tool-surface.ts +184 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +208 -0
- package/pi-extensions/lib/entwurf-core.ts +2033 -0
- package/pi-extensions/lib/entwurf-deliverability.ts +123 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +158 -0
- package/pi-extensions/lib/entwurf-facts.ts +251 -0
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +100 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +154 -0
- package/pi-extensions/lib/entwurf-preflight.ts +247 -0
- package/pi-extensions/lib/entwurf-resume-args.ts +88 -0
- package/pi-extensions/lib/entwurf-self-address.ts +117 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +427 -0
- package/pi-extensions/lib/entwurf-v2-decider.ts +441 -0
- package/pi-extensions/lib/entwurf-v2-lock.ts +412 -0
- package/pi-extensions/lib/entwurf-v2-mailbox.ts +87 -0
- package/pi-extensions/lib/entwurf-v2-production.ts +326 -0
- package/pi-extensions/lib/entwurf-v2-release.ts +149 -0
- package/pi-extensions/lib/entwurf-v2-resume-marker.ts +34 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +196 -0
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +166 -0
- package/pi-extensions/lib/entwurf-v2-send.ts +271 -0
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +337 -0
- package/pi-extensions/lib/entwurf-v2-spawn.ts +323 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +205 -0
- package/pi-extensions/lib/meta-mailbox-body.ts +80 -0
- package/pi-extensions/lib/meta-session.ts +1951 -0
- package/pi-extensions/lib/project-trust-handler.ts +154 -0
- package/pi-extensions/lib/session-id.js +57 -0
- package/pi-extensions/lib/socket-discovery.ts +346 -0
- package/pi-extensions/lib/socket-probe.ts +92 -0
- package/pi-extensions/meta-bridge-hook.ts +250 -0
- package/pi-extensions/model-lock.ts +235 -0
- package/prompts/engraving.md +27 -0
- package/protocol.js +31 -0
- package/run.sh +2832 -0
- package/scripts/check-acp-backend-preflight.ts +130 -0
- package/scripts/check-acp-carrier-augment.ts +297 -0
- package/scripts/check-acp-config.ts +322 -0
- package/scripts/check-acp-event-mapper.ts +253 -0
- package/scripts/check-acp-overlay.ts +154 -0
- package/scripts/check-acp-prompt-builder.ts +191 -0
- package/scripts/check-acp-provider-surface.ts +159 -0
- package/scripts/check-acp-sdk-surface.ts +211 -0
- package/scripts/check-acp-session-reuse.ts +756 -0
- package/scripts/check-acp-session-store.ts +387 -0
- package/scripts/check-acp-tool-surface.ts +159 -0
- package/scripts/check-entwurf-bridge-boot.ts +199 -0
- package/scripts/check-entwurf-capabilities.ts +123 -0
- package/scripts/check-entwurf-control-rpc.ts +187 -0
- package/scripts/check-entwurf-deliverability.ts +158 -0
- package/scripts/check-entwurf-fact-provider.ts +316 -0
- package/scripts/check-entwurf-facts.ts +358 -0
- package/scripts/check-entwurf-mailbox-guard.ts +264 -0
- package/scripts/check-entwurf-peers-surface.ts +235 -0
- package/scripts/check-entwurf-resume-args.ts +149 -0
- package/scripts/check-entwurf-self-address.ts +209 -0
- package/scripts/check-entwurf-session-identity.ts +703 -0
- package/scripts/check-entwurf-v2-contract.ts +536 -0
- package/scripts/check-entwurf-v2-decider.ts +795 -0
- package/scripts/check-entwurf-v2-lock.ts +368 -0
- package/scripts/check-entwurf-v2-mailbox.ts +228 -0
- package/scripts/check-entwurf-v2-matrix.ts +437 -0
- package/scripts/check-entwurf-v2-production.ts +406 -0
- package/scripts/check-entwurf-v2-release.ts +245 -0
- package/scripts/check-entwurf-v2-runner.ts +452 -0
- package/scripts/check-entwurf-v2-send-fallback.ts +371 -0
- package/scripts/check-entwurf-v2-send.ts +454 -0
- package/scripts/check-entwurf-v2-spawn-production.ts +452 -0
- package/scripts/check-entwurf-v2-spawn.ts +398 -0
- package/scripts/check-entwurf-v2-surface.ts +352 -0
- package/scripts/check-keyset-overlap.py +120 -0
- package/scripts/check-mailbox-receipt-state.ts +170 -0
- package/scripts/check-meta-capability-source.ts +112 -0
- package/scripts/check-meta-dual-consumers.ts +154 -0
- package/scripts/check-meta-dual-read.ts +158 -0
- package/scripts/check-meta-listing.ts +138 -0
- package/scripts/check-meta-mailbox-state-write.ts +135 -0
- package/scripts/check-meta-migration.ts +212 -0
- package/scripts/check-meta-receiver-marker.ts +185 -0
- package/scripts/check-meta-record-v2.ts +191 -0
- package/scripts/check-meta-session.ts +673 -0
- package/scripts/check-model-lock.ts +408 -0
- package/scripts/check-package-source-routing.ts +253 -0
- package/scripts/check-pi-preflight.ts +304 -0
- package/scripts/check-project-trust-handler.ts +265 -0
- package/scripts/check-shell-quote.ts +121 -0
- package/scripts/check-socket-discovery.ts +428 -0
- package/scripts/check-socket-probe.ts +106 -0
- package/scripts/fixtures/probe-mcp-server.ts +33 -0
- package/scripts/gnew-rpc-drive.ts +211 -0
- package/scripts/lib/acp-child-cleanup.ts +116 -0
- package/scripts/meta-bridge-doctor.sh +315 -0
- package/scripts/meta-bridge-hook-log.sh +26 -0
- package/scripts/meta-bridge-install.sh +135 -0
- package/scripts/meta-bridge-prune.ts +199 -0
- package/scripts/meta-bridge-state.py +549 -0
- package/scripts/meta-bridge-statusline.sh +192 -0
- package/scripts/meta-bridge-store-doctor.ts +64 -0
- package/scripts/meta-bridge-uninstall.sh +39 -0
- package/scripts/new-session-id.ts +25 -0
- package/scripts/postinstall-chmod.cjs +58 -0
- package/scripts/raw-async-delivery/README.md +258 -0
- package/scripts/raw-async-delivery/cc-enqueue-addressed.sh +35 -0
- package/scripts/raw-async-delivery/cc-mailbox-rewake.sh +38 -0
- package/scripts/raw-async-delivery/cc-watch-filechanged.sh +19 -0
- package/scripts/raw-async-delivery/cc-watch-sessionstart.sh +15 -0
- package/scripts/raw-async-delivery/codex-local-appserver.sh +39 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/.claude-plugin/plugin.json +5 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/hooks/hooks.json +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-filechanged.sh +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-sessionstart.sh +27 -0
- package/scripts/raw-async-delivery/raw-agy-send.sh +29 -0
- package/scripts/raw-async-delivery/raw-claude-enqueue.sh +30 -0
- package/scripts/raw-async-delivery/raw-codex-ws-turn-start.py +164 -0
- package/scripts/raw-async-delivery/repro-addressed-routing.sh +96 -0
- package/scripts/raw-async-delivery/repro-plugin-idle-wake.sh +104 -0
- package/scripts/resolve-acp-bridge.ts +25 -0
- package/scripts/smoke-acp-bundled-mcp-live.ts +292 -0
- package/scripts/smoke-acp-carrier-augment-live.ts +192 -0
- package/scripts/smoke-acp-mcp-live.ts +129 -0
- package/scripts/smoke-acp-memory-containment-live.ts +389 -0
- package/scripts/smoke-acp-overlay-live.ts +314 -0
- package/scripts/smoke-acp-provider-live.ts +162 -0
- package/scripts/smoke-acp-raw-turn-live.ts +261 -0
- package/scripts/smoke-acp-session-reuse-live.ts +172 -0
- package/scripts/smoke-acp-skill-live.ts +144 -0
- package/scripts/smoke-acp-socket-citizen-live.ts +168 -0
- package/scripts/smoke-claude-native-resume-live.sh +198 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +398 -0
- package/scripts/smoke-entwurf-v2-spawn-live.ts +175 -0
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +428 -0
- package/scripts/smoke-meta-async-drift.sh +171 -0
- package/scripts/smoke-meta-honesty.sh +147 -0
- package/scripts/smoke-meta-install-state.sh +403 -0
- package/scripts/smoke-meta-keyset-guard.sh +111 -0
- package/scripts/smoke-meta-prune.sh +174 -0
- package/scripts/smoke-resident-garden-guard.sh +433 -0
- package/scripts/smoke-session-id-name.ts +187 -0
- package/scripts/tsconfig.json +34 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check-entwurf-v2-matrix — the 5d-5 (a) REACHABILITY + LOCK SSOT TABLE.
|
|
3
|
+
*
|
|
4
|
+
* GPT Q5 design: a deterministic gate (no API, no real pi) that fixes the 5d-5
|
|
5
|
+
* operating claim — "target kind × resulting transport × lock acquire→release×1" —
|
|
6
|
+
* as ONE readable table, so a release note can say "the matrix is closed" without a
|
|
7
|
+
* reader grepping the 12 scattered check-entwurf-v2-* gates.
|
|
8
|
+
*
|
|
9
|
+
* This is a THIN coverage gate, NOT a re-implementation of the decider. It drives
|
|
10
|
+
* the REAL `decideDispatch` over minimal injected fakes (the same shape
|
|
11
|
+
* check-entwurf-v2-decider uses) and asserts, per row, the (transport, lock class,
|
|
12
|
+
* reject reason) the decider actually produces. A final COVERAGE pass then FAILS if
|
|
13
|
+
* any transport, lock class, or pre-probe reject is missing from the table — so a
|
|
14
|
+
* future decider change that silently drops a reachability cell cannot pass.
|
|
15
|
+
*
|
|
16
|
+
* Sender-surface parity (MCP entwurf_v2 vs pi-native) is NOT re-run here: both ride
|
|
17
|
+
* the same `runAndRenderEntwurfV2FromSurface` (proven once in check-entwurf-v2-surface),
|
|
18
|
+
* and the matrix is keyed on the decider both surfaces share. Adding a second surface
|
|
19
|
+
* sweep would duplicate that proof — Q-a: "전수 실행을 두 surface로 중복하지 말 것".
|
|
20
|
+
*
|
|
21
|
+
* Lock classes (the "acquire→release×1" axis made machine-checkable):
|
|
22
|
+
* none — no lock acquired at all (pre-probe reject, or the lock-free
|
|
23
|
+
* unsupported/meta-mailbox path): acquire 0, release 0.
|
|
24
|
+
* held — in-domain execute keeps the claim for 5c re-resolve: acquire 1,
|
|
25
|
+
* release 0, decision.lock non-null.
|
|
26
|
+
* mailbox-null — unsupported execute → meta-mailbox, deliberately lock-free (?7):
|
|
27
|
+
* acquire 0, release 0, decision.lock === null.
|
|
28
|
+
* released — in-domain reject after the probe: the held lock is released
|
|
29
|
+
* exactly once: acquire 1, release 1.
|
|
30
|
+
* acquire-fail — target-locked: the acquire itself failed, nothing to release:
|
|
31
|
+
* acquire 1, release 0.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import assert from "node:assert/strict";
|
|
35
|
+
import type { PreflightOutcome } from "../pi-extensions/lib/entwurf-preflight.ts";
|
|
36
|
+
import {
|
|
37
|
+
type EntwurfIntent,
|
|
38
|
+
type EntwurfV2Transport,
|
|
39
|
+
isLivenessSupported,
|
|
40
|
+
} from "../pi-extensions/lib/entwurf-v2-contract.ts";
|
|
41
|
+
import {
|
|
42
|
+
type DispatchDeciderDeps,
|
|
43
|
+
type DispatchDecision,
|
|
44
|
+
decideDispatch,
|
|
45
|
+
type ExecutionPlan,
|
|
46
|
+
type TargetResolution,
|
|
47
|
+
} from "../pi-extensions/lib/entwurf-v2-decider.ts";
|
|
48
|
+
import type { AcquireLockResult, LockClaim } from "../pi-extensions/lib/entwurf-v2-lock.ts";
|
|
49
|
+
import type { MetaBackendV2, MetaIdentity } from "../pi-extensions/lib/meta-session.ts";
|
|
50
|
+
import { controlSocketPath, type TargetSocketInspection } from "../pi-extensions/lib/socket-discovery.ts";
|
|
51
|
+
import type { SocketLiveness } from "../pi-extensions/lib/socket-probe.ts";
|
|
52
|
+
|
|
53
|
+
let passed = 0;
|
|
54
|
+
function ok(label: string, cond: boolean): void {
|
|
55
|
+
assert.ok(cond, label);
|
|
56
|
+
console.log(` ok ${label}`);
|
|
57
|
+
passed++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const GID = "20260612T100000-aaaaaa";
|
|
61
|
+
const CWD = "/home/junghan/repos/gh/entwurf";
|
|
62
|
+
|
|
63
|
+
function identity(backend: MetaBackendV2): MetaIdentity {
|
|
64
|
+
return {
|
|
65
|
+
schemaVersion: 2,
|
|
66
|
+
gardenId: GID,
|
|
67
|
+
backend,
|
|
68
|
+
nativeSessionId: `native-${GID}`,
|
|
69
|
+
cwd: CWD,
|
|
70
|
+
model: null,
|
|
71
|
+
transcriptPath: null,
|
|
72
|
+
parentGardenId: null,
|
|
73
|
+
isEntwurf: false,
|
|
74
|
+
createdAt: "2026-06-12T01:00:00.000Z",
|
|
75
|
+
recordUpdatedAt: "2026-06-12T01:00:00.000Z",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function lockClaim(gardenId: string): LockClaim {
|
|
80
|
+
return {
|
|
81
|
+
gardenId,
|
|
82
|
+
pid: 4242,
|
|
83
|
+
hostname: "test-host",
|
|
84
|
+
createdAt: "2026-06-12T01:00:00.000Z",
|
|
85
|
+
nonce: "deadbeefcafef00d",
|
|
86
|
+
owner: "entwurf_v2",
|
|
87
|
+
lockPath: `/fake/locks/${gardenId}.lock`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const APPROVE: PreflightOutcome = {
|
|
92
|
+
kind: "approve",
|
|
93
|
+
reason: "saved-true",
|
|
94
|
+
launchArgs: ["--approve"],
|
|
95
|
+
trustStoreDecision: true,
|
|
96
|
+
trustStoreInherited: false,
|
|
97
|
+
hasTrustInputs: true,
|
|
98
|
+
canonicalCwd: CWD,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ── injected fakes with call tracking (decider-gate shape, minimal copy) ─────
|
|
102
|
+
interface ScenarioOpts {
|
|
103
|
+
resolution: TargetResolution;
|
|
104
|
+
lock?: "ok" | "conflict";
|
|
105
|
+
inspection?: TargetSocketInspection;
|
|
106
|
+
probe?: SocketLiveness;
|
|
107
|
+
preflight?: PreflightOutcome;
|
|
108
|
+
mailboxDeliverable?: boolean;
|
|
109
|
+
}
|
|
110
|
+
interface Tracked {
|
|
111
|
+
deps: DispatchDeciderDeps;
|
|
112
|
+
acquireCalls: string[];
|
|
113
|
+
releaseCalls: LockClaim[];
|
|
114
|
+
inspectCalls: string[];
|
|
115
|
+
probeCalls: string[];
|
|
116
|
+
mailboxCalls: MetaIdentity[];
|
|
117
|
+
}
|
|
118
|
+
function mkDeps(opts: ScenarioOpts): Tracked {
|
|
119
|
+
const acquireCalls: string[] = [];
|
|
120
|
+
const releaseCalls: LockClaim[] = [];
|
|
121
|
+
const inspectCalls: string[] = [];
|
|
122
|
+
const probeCalls: string[] = [];
|
|
123
|
+
const mailboxCalls: MetaIdentity[] = [];
|
|
124
|
+
const deps: DispatchDeciderDeps = {
|
|
125
|
+
resolveTarget: () => opts.resolution,
|
|
126
|
+
acquireLock: (gardenId: string): AcquireLockResult => {
|
|
127
|
+
acquireCalls.push(gardenId);
|
|
128
|
+
if (opts.lock === "conflict") {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
conflict: {
|
|
132
|
+
reason: "target-locked",
|
|
133
|
+
lockPath: `/fake/locks/${gardenId}.lock`,
|
|
134
|
+
holder: lockClaim(gardenId),
|
|
135
|
+
detail: "held by a live dispatcher",
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, claim: lockClaim(gardenId) };
|
|
140
|
+
},
|
|
141
|
+
releaseLock: (claim: LockClaim) => {
|
|
142
|
+
releaseCalls.push(claim);
|
|
143
|
+
},
|
|
144
|
+
inspectSocket: async (gardenId: string): Promise<TargetSocketInspection> => {
|
|
145
|
+
inspectCalls.push(gardenId);
|
|
146
|
+
return opts.inspection ?? { kind: "absent", socketPath: controlSocketPath(gardenId, "/fake/ctl") };
|
|
147
|
+
},
|
|
148
|
+
probeSocket: async (socketPath: string): Promise<SocketLiveness> => {
|
|
149
|
+
probeCalls.push(socketPath);
|
|
150
|
+
return opts.probe ?? "dead";
|
|
151
|
+
},
|
|
152
|
+
preflightForCwd: () => opts.preflight ?? APPROVE,
|
|
153
|
+
mailboxDeliverabilityFor: (id: MetaIdentity) => {
|
|
154
|
+
mailboxCalls.push(id);
|
|
155
|
+
return { deliverable: opts.mailboxDeliverable ?? false, reason: "fake-deliverability" };
|
|
156
|
+
},
|
|
157
|
+
mailboxDir: "/fake/mailbox",
|
|
158
|
+
sessionsDir: "/fake/sessions",
|
|
159
|
+
};
|
|
160
|
+
return { deps, acquireCalls, releaseCalls, inspectCalls, probeCalls, mailboxCalls };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type LockClass = "none" | "held" | "mailbox-null" | "released" | "acquire-fail";
|
|
164
|
+
|
|
165
|
+
type Expect =
|
|
166
|
+
| { decision: "execute"; transport: EntwurfV2Transport; lock: LockClass }
|
|
167
|
+
| { decision: "reject"; reason: string; lock: LockClass; diagnostic?: boolean };
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Which IO seams the decider is ALLOWED to touch — the "어느 축을 만지면 안 되는지"
|
|
171
|
+
* axis (GPT D4-a review). A row name says "no inspect"; reachOf() + runRow make the
|
|
172
|
+
* machine enforce it instead of trusting the prose. DERIVED from the scenario, never
|
|
173
|
+
* hand-set, so a new row cannot drift from its declared target kind:
|
|
174
|
+
* pre-probe — rejected before any socket work: inspect 0, probe 0, mailbox 0
|
|
175
|
+
* (bad-target, pre-probe conflict, target-locked = lock fails first).
|
|
176
|
+
* unsupported — lock-free mailbox branch: the deliverability seam is consulted
|
|
177
|
+
* exactly once, NO socket inspect/probe: inspect 0, probe 0, mailbox 1.
|
|
178
|
+
* in-domain — locked socket branch: inspect ≥1, mailbox 0 (probe count varies by
|
|
179
|
+
* inspection kind, so it is not asserted here).
|
|
180
|
+
*/
|
|
181
|
+
type Reaches = "pre-probe" | "unsupported" | "in-domain";
|
|
182
|
+
|
|
183
|
+
function reachOf(s: ScenarioOpts): Reaches {
|
|
184
|
+
const id = s.resolution.identity;
|
|
185
|
+
if (!id) return "pre-probe"; // bad-target — no citizen
|
|
186
|
+
if (s.resolution.preProbeAddressConflict) return "pre-probe";
|
|
187
|
+
if (s.lock === "conflict") return "pre-probe"; // target-locked: lock fails before the socket
|
|
188
|
+
return isLivenessSupported(id.backend) ? "in-domain" : "unsupported";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface Row {
|
|
192
|
+
name: string;
|
|
193
|
+
targetKind: string;
|
|
194
|
+
intent: EntwurfIntent;
|
|
195
|
+
scenario: ScenarioOpts;
|
|
196
|
+
expect: Expect;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const present = (i: TargetSocketInspection) => i;
|
|
200
|
+
|
|
201
|
+
// ── THE TABLE — every reachability + lock cell the 5d-5 claim covers ─────────
|
|
202
|
+
const ROWS: Row[] = [
|
|
203
|
+
{
|
|
204
|
+
name: "bad-target",
|
|
205
|
+
targetKind: "no citizen",
|
|
206
|
+
intent: "owned-outcome",
|
|
207
|
+
scenario: { resolution: { identity: null, preProbeAddressConflict: false } },
|
|
208
|
+
expect: { decision: "reject", reason: "bad-target", lock: "none" },
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "address-conflict (pre-probe)",
|
|
212
|
+
targetKind: "quarantined gid/socket",
|
|
213
|
+
intent: "fire-and-forget",
|
|
214
|
+
scenario: { resolution: { identity: identity("claude-code"), preProbeAddressConflict: true } },
|
|
215
|
+
expect: { decision: "reject", reason: "target-address-conflict", lock: "none" },
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "target-locked",
|
|
219
|
+
targetKind: "pi, lock held by another",
|
|
220
|
+
intent: "fire-and-forget",
|
|
221
|
+
scenario: { resolution: { identity: identity("pi"), preProbeAddressConflict: false }, lock: "conflict" },
|
|
222
|
+
expect: { decision: "reject", reason: "target-locked", lock: "acquire-fail", diagnostic: true },
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "unsupported self-fetch active → meta-mailbox",
|
|
226
|
+
targetKind: "claude-code, active receiver",
|
|
227
|
+
intent: "fire-and-forget",
|
|
228
|
+
scenario: {
|
|
229
|
+
resolution: { identity: identity("claude-code"), preProbeAddressConflict: false },
|
|
230
|
+
mailboxDeliverable: true,
|
|
231
|
+
},
|
|
232
|
+
expect: { decision: "execute", transport: "meta-mailbox", lock: "mailbox-null" },
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "unsupported self-fetch inactive → reject",
|
|
236
|
+
targetKind: "claude-code, inactive receiver",
|
|
237
|
+
intent: "fire-and-forget",
|
|
238
|
+
scenario: {
|
|
239
|
+
resolution: { identity: identity("claude-code"), preProbeAddressConflict: false },
|
|
240
|
+
mailboxDeliverable: false,
|
|
241
|
+
},
|
|
242
|
+
expect: { decision: "reject", reason: "mailbox-undeliverable", lock: "none" },
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "unsupported owned-outcome → reject",
|
|
246
|
+
targetKind: "claude-code, owned intent",
|
|
247
|
+
intent: "owned-outcome",
|
|
248
|
+
scenario: {
|
|
249
|
+
resolution: { identity: identity("claude-code"), preProbeAddressConflict: false },
|
|
250
|
+
mailboxDeliverable: true,
|
|
251
|
+
},
|
|
252
|
+
expect: { decision: "reject", reason: "backend-liveness-unsupported", lock: "none" },
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "in-domain live ff → control-socket",
|
|
256
|
+
targetKind: "pi, socket alive",
|
|
257
|
+
intent: "fire-and-forget",
|
|
258
|
+
scenario: {
|
|
259
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
260
|
+
lock: "ok",
|
|
261
|
+
inspection: present({ kind: "socket-file", socketPath: "/fake/ctl/s.sock" }),
|
|
262
|
+
probe: "alive",
|
|
263
|
+
},
|
|
264
|
+
expect: { decision: "execute", transport: "control-socket", lock: "held" },
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "in-domain dormant owned → spawn-bg",
|
|
268
|
+
targetKind: "pi, socket absent (dormant)",
|
|
269
|
+
intent: "owned-outcome",
|
|
270
|
+
scenario: {
|
|
271
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
272
|
+
lock: "ok",
|
|
273
|
+
inspection: present({ kind: "absent", socketPath: "/fake/ctl/expected.sock" }),
|
|
274
|
+
probe: "dead",
|
|
275
|
+
preflight: APPROVE,
|
|
276
|
+
},
|
|
277
|
+
expect: { decision: "execute", transport: "spawn-bg", lock: "held" },
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "in-domain live owned → reject (no autosend)",
|
|
281
|
+
targetKind: "pi, socket alive, owned intent",
|
|
282
|
+
intent: "owned-outcome",
|
|
283
|
+
scenario: {
|
|
284
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
285
|
+
lock: "ok",
|
|
286
|
+
inspection: present({ kind: "socket-file", socketPath: "/fake/ctl/s.sock" }),
|
|
287
|
+
probe: "alive",
|
|
288
|
+
},
|
|
289
|
+
expect: { decision: "reject", reason: "owned-live-no-autosend", lock: "released" },
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "in-domain ff dormant → reject",
|
|
293
|
+
targetKind: "pi, confirmed dormant, ff intent",
|
|
294
|
+
intent: "fire-and-forget",
|
|
295
|
+
scenario: {
|
|
296
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
297
|
+
lock: "ok",
|
|
298
|
+
inspection: present({ kind: "absent", socketPath: "/fake/ctl/s.sock" }),
|
|
299
|
+
probe: "dead",
|
|
300
|
+
},
|
|
301
|
+
expect: { decision: "reject", reason: "dormant-fire-forget-unsupported", lock: "released" },
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "in-domain indeterminate → reject",
|
|
305
|
+
targetKind: "pi, socket indeterminate (EACCES)",
|
|
306
|
+
intent: "owned-outcome",
|
|
307
|
+
scenario: {
|
|
308
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
309
|
+
lock: "ok",
|
|
310
|
+
inspection: present({ kind: "indeterminate", socketPath: "/fake/ctl/s.sock", error: "EACCES" }),
|
|
311
|
+
},
|
|
312
|
+
expect: { decision: "reject", reason: "indeterminate-no-spawn", lock: "released" },
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "in-domain address-conflict under lock → reject (released)",
|
|
316
|
+
targetKind: "pi, socket symlink address-conflict",
|
|
317
|
+
intent: "fire-and-forget",
|
|
318
|
+
scenario: {
|
|
319
|
+
resolution: { identity: identity("pi"), preProbeAddressConflict: false },
|
|
320
|
+
lock: "ok",
|
|
321
|
+
inspection: present({ kind: "address-conflict", socketPath: "/fake/ctl/s.sock", reason: "symlink" }),
|
|
322
|
+
},
|
|
323
|
+
expect: { decision: "reject", reason: "target-address-conflict", lock: "released" },
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
function planTransport(d: DispatchDecision): EntwurfV2Transport | null {
|
|
328
|
+
return d.kind === "execute" ? (d.plan as ExecutionPlan).transport : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Verify a row against the REAL decideDispatch + the tracked lock calls.
|
|
332
|
+
async function runRow(row: Row): Promise<void> {
|
|
333
|
+
const t = mkDeps(row.scenario);
|
|
334
|
+
const d = await decideDispatch({ target: GID, intent: row.intent, message: "hi" }, t.deps);
|
|
335
|
+
const acq = t.acquireCalls.length;
|
|
336
|
+
const rel = t.releaseCalls.length;
|
|
337
|
+
|
|
338
|
+
// reach axis (GPT D4-a): the decider must touch ONLY the seams this target kind
|
|
339
|
+
// allows — make the "no inspect / no mailbox" prose machine-checked.
|
|
340
|
+
const reaches = reachOf(row.scenario);
|
|
341
|
+
const insp = t.inspectCalls.length;
|
|
342
|
+
const prb = t.probeCalls.length;
|
|
343
|
+
const mbx = t.mailboxCalls.length;
|
|
344
|
+
if (reaches === "pre-probe") {
|
|
345
|
+
ok(`${row.name}: pre-probe reach — no inspect, no probe, no mailbox`, insp === 0 && prb === 0 && mbx === 0);
|
|
346
|
+
} else if (reaches === "unsupported") {
|
|
347
|
+
ok(
|
|
348
|
+
`${row.name}: unsupported reach — deliverability seam ×1, no socket inspect/probe`,
|
|
349
|
+
mbx === 1 && insp === 0 && prb === 0,
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
ok(`${row.name}: in-domain reach — socket inspected, deliverability seam untouched`, insp >= 1 && mbx === 0);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (row.expect.decision === "execute") {
|
|
356
|
+
ok(`${row.name}: execute`, d.kind === "execute");
|
|
357
|
+
ok(`${row.name}: transport=${row.expect.transport}`, planTransport(d) === row.expect.transport);
|
|
358
|
+
const lockNull = d.kind === "execute" && d.lock === null;
|
|
359
|
+
if (row.expect.lock === "held") {
|
|
360
|
+
ok(`${row.name}: lock held (acquire 1, release 0, non-null)`, acq === 1 && rel === 0 && !lockNull);
|
|
361
|
+
} else {
|
|
362
|
+
// mailbox-null
|
|
363
|
+
ok(`${row.name}: lock-free meta-mailbox (acquire 0, lock null)`, acq === 0 && rel === 0 && lockNull);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
ok(`${row.name}: reject`, d.kind === "reject");
|
|
367
|
+
ok(`${row.name}: reason=${row.expect.reason}`, d.kind === "reject" && d.receipt.reason === row.expect.reason);
|
|
368
|
+
ok(`${row.name}: no plan field`, !("plan" in d));
|
|
369
|
+
switch (row.expect.lock) {
|
|
370
|
+
case "none":
|
|
371
|
+
ok(`${row.name}: no lock (acquire 0, release 0)`, acq === 0 && rel === 0);
|
|
372
|
+
break;
|
|
373
|
+
case "acquire-fail":
|
|
374
|
+
ok(`${row.name}: acquire failed (acquire 1, release 0)`, acq === 1 && rel === 0);
|
|
375
|
+
break;
|
|
376
|
+
case "released":
|
|
377
|
+
ok(`${row.name}: lock acquired then released ×1 (acquire 1, release 1)`, acq === 1 && rel === 1);
|
|
378
|
+
break;
|
|
379
|
+
default:
|
|
380
|
+
assert.fail(`${row.name}: reject row cannot expect lock=${row.expect.lock}`);
|
|
381
|
+
}
|
|
382
|
+
if (row.expect.diagnostic) {
|
|
383
|
+
ok(
|
|
384
|
+
`${row.name}: carries target-locked diagnostic`,
|
|
385
|
+
d.kind === "reject" && d.diagnostic?.kind === "target-locked",
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
ok(`${row.name}: no diagnostic`, d.kind === "reject" && d.diagnostic === undefined);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function main(): Promise<void> {
|
|
394
|
+
console.log("[check-entwurf-v2-matrix] 5d-5 (a) reachability + lock SSOT table\n");
|
|
395
|
+
for (const row of ROWS) {
|
|
396
|
+
console.log(`── ${row.name} [${row.targetKind}]`);
|
|
397
|
+
await runRow(row);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── COVERAGE: the table must span every transport + lock class + pre-probe ──
|
|
401
|
+
// reject, or the "matrix is closed" claim is a lie. A dropped decider cell makes
|
|
402
|
+
// one of these sets shrink → fail (not a silent green).
|
|
403
|
+
console.log("\n── coverage (table completeness)");
|
|
404
|
+
const transports = new Set<string>();
|
|
405
|
+
const lockClasses = new Set<LockClass>();
|
|
406
|
+
const rejectReasons = new Set<string>();
|
|
407
|
+
for (const row of ROWS) {
|
|
408
|
+
lockClasses.add(row.expect.lock);
|
|
409
|
+
if (row.expect.decision === "execute") transports.add(row.expect.transport);
|
|
410
|
+
else rejectReasons.add(row.expect.reason);
|
|
411
|
+
}
|
|
412
|
+
for (const tr of ["control-socket", "meta-mailbox", "spawn-bg"]) {
|
|
413
|
+
ok(`coverage: transport "${tr}" exercised`, transports.has(tr));
|
|
414
|
+
}
|
|
415
|
+
for (const lc of ["none", "held", "mailbox-null", "released", "acquire-fail"] as LockClass[]) {
|
|
416
|
+
ok(`coverage: lock class "${lc}" exercised`, lockClasses.has(lc));
|
|
417
|
+
}
|
|
418
|
+
for (const rr of [
|
|
419
|
+
"bad-target",
|
|
420
|
+
"target-address-conflict",
|
|
421
|
+
"target-locked",
|
|
422
|
+
"mailbox-undeliverable",
|
|
423
|
+
"backend-liveness-unsupported",
|
|
424
|
+
"owned-live-no-autosend",
|
|
425
|
+
"dormant-fire-forget-unsupported",
|
|
426
|
+
"indeterminate-no-spawn",
|
|
427
|
+
]) {
|
|
428
|
+
ok(`coverage: reject reason "${rr}" in table`, rejectReasons.has(rr));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(`\n[check-entwurf-v2-matrix] ${passed} assertions ok`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
main().catch((err) => {
|
|
435
|
+
console.error(err);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
});
|