@junghanacs/entwurf 0.12.0 → 0.12.1
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/CHANGELOG.md +21 -0
- package/README.md +88 -28
- package/docs/setup-clean-host.md +117 -219
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +454 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +111 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +1683 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +121 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +155 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +119 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +160 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +63 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +81 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +290 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +254 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +365 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +64 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +218 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +108 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-resume-marker.js +33 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +116 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +184 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +237 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +216 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +164 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +66 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +1502 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +50 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +259 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-probe.js +81 -0
- package/mcp/entwurf-bridge/dist/protocol.js +29 -0
- package/mcp/entwurf-bridge/start.sh +49 -7
- package/mcp/entwurf-bridge/test.sh +12 -3
- package/mcp/entwurf-bridge/tsconfig.build.json +42 -0
- package/package.json +29 -9
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +101 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +10 -78
- package/pi-extensions/lib/entwurf-v2-decider.ts +6 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +26 -4
- package/run.sh +140 -15
- package/scripts/check-entwurf-bridge-pi-free.ts +146 -0
- package/scripts/check-entwurf-v2-contract.ts +6 -4
- package/scripts/smoke-acp-bundled-mcp-live.ts +13 -2
- package/scripts/smoke-acp-carrier-augment-live.ts +35 -19
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entwurf-v2-contract-schema — the pi-side TypeBox REPRESENTATION of the frozen
|
|
3
|
+
* `entwurf_v2` contract. This module exists ONLY to carry the pi-ai TypeBox
|
|
4
|
+
* schema builders (`StringEnum`, `Type`) out of the pi-free core
|
|
5
|
+
* (`entwurf-v2-contract.ts`); the constants/types/decision logic live there.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ pi LANE ONLY — the MCP bridge (`mcp/entwurf-bridge/src/index.ts`) MUST NOT
|
|
8
|
+
* import this module. It value-imports `@earendil-works/pi-ai`, so reaching it
|
|
9
|
+
* from the bridge boot closure would re-couple the harness-neutral meta-bridge to
|
|
10
|
+
* pi (the `check-entwurf-bridge-pi-free` gate fails if it does). The bridge needs
|
|
11
|
+
* the contract CONSTANTS + `resolveDispatch` (pi-free core), never these schemas.
|
|
12
|
+
* Consumers: the pi MCP-tool param surface and `check-entwurf-v2-contract`.
|
|
13
|
+
*
|
|
14
|
+
* StringEnum (typebox 1.x) inside Type.Object (typebox 0.34) — the same mix the
|
|
15
|
+
* existing entwurf tools use. The logic types in the core are hand-written unions,
|
|
16
|
+
* NOT `Static<>` inferences, so the 0.34/1.x widening caveat does not touch them;
|
|
17
|
+
* `check-entwurf-v2-contract` keeps these schemas and the core types in lockstep.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
21
|
+
import {
|
|
22
|
+
ENTWURF_INTENTS,
|
|
23
|
+
ENTWURF_V2_ACTIONS,
|
|
24
|
+
ENTWURF_V2_MODES,
|
|
25
|
+
ENTWURF_V2_OWNERSHIPS,
|
|
26
|
+
ENTWURF_V2_REJECT_REASONS,
|
|
27
|
+
ENTWURF_V2_TRANSPORTS,
|
|
28
|
+
FACT_LIVENESSES,
|
|
29
|
+
} from "./entwurf-v2-contract.ts";
|
|
30
|
+
import { SESSION_ID_RE } from "./session-id.js";
|
|
31
|
+
|
|
32
|
+
export const EntwurfV2InputSchema = Type.Object(
|
|
33
|
+
{
|
|
34
|
+
// R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
|
|
35
|
+
// a malformed/typo gid fails the schema (→ bad-target) and can never reach a
|
|
36
|
+
// spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
|
|
37
|
+
target: Type.String({
|
|
38
|
+
pattern: SESSION_ID_RE.source,
|
|
39
|
+
description:
|
|
40
|
+
"garden-id of an EXISTING citizen (pattern-enforced). spawn-new is out of v2 scope (legacy entwurf keeps it); a malformed/typo gid is bad-target.",
|
|
41
|
+
}),
|
|
42
|
+
intent: StringEnum(ENTWURF_INTENTS, {
|
|
43
|
+
description:
|
|
44
|
+
"caller's declared outcome contract (F1): fire-and-forget = ack only, owned-outcome = caller owns completion.",
|
|
45
|
+
}),
|
|
46
|
+
mode: Type.Optional(
|
|
47
|
+
StringEnum(ENTWURF_V2_MODES, {
|
|
48
|
+
description:
|
|
49
|
+
"delivery mode (steer = interrupt current turn, follow_up = queue) — NOT the ownership axis (F1) nor liveness routing. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
wantsReply: Type.Optional(
|
|
53
|
+
Type.Boolean({
|
|
54
|
+
description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
// `additionalProperties: false` — a frozen contract input is exact; an unknown
|
|
58
|
+
// key is a caller error, not silently ignored.
|
|
59
|
+
},
|
|
60
|
+
{ additionalProperties: false },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Receipt = a DISCRIMINATED union on `ok` (R3/F6) — NOT one flat object with
|
|
64
|
+
// optionals. Each branch is EXACT (`additionalProperties: false`): without it,
|
|
65
|
+
// JSON Schema's default admits extra keys, so an illegal receipt like
|
|
66
|
+
// {ok:true, ..., reason:"bad-target"} would validate against the success branch.
|
|
67
|
+
// With it, success carries action/transport/ownership and rejects a stray reason;
|
|
68
|
+
// reject carries reason and rejects any allow facet — the branches are mutually
|
|
69
|
+
// exclusive at the schema level, not merely by declared-property convention.
|
|
70
|
+
export const EntwurfV2ReceiptSuccessSchema = Type.Object(
|
|
71
|
+
{
|
|
72
|
+
ok: Type.Literal(true),
|
|
73
|
+
action: StringEnum(ENTWURF_V2_ACTIONS),
|
|
74
|
+
transport: StringEnum(ENTWURF_V2_TRANSPORTS),
|
|
75
|
+
ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
|
|
76
|
+
observedLiveness: StringEnum(FACT_LIVENESSES, {
|
|
77
|
+
description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
{ additionalProperties: false },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export const EntwurfV2ReceiptRejectSchema = Type.Object(
|
|
84
|
+
{
|
|
85
|
+
ok: Type.Literal(false),
|
|
86
|
+
reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
|
|
87
|
+
// ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
|
|
88
|
+
// key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
|
|
89
|
+
// and a real FactLiveness otherwise. Optional would lose the "key always
|
|
90
|
+
// present, value may be null" shape and weaken the discriminated union; the
|
|
91
|
+
// reason-dependent null/non-null rule is enforced semantically (the gate's
|
|
92
|
+
// rejectObservedLivenessWellFormed fixture), not by this blanket union.
|
|
93
|
+
observedLiveness: Type.Union([StringEnum(FACT_LIVENESSES), Type.Null()], {
|
|
94
|
+
description:
|
|
95
|
+
"the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
{ additionalProperties: false },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
export const EntwurfV2ReceiptSchema = Type.Union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* entwurf-v2-contract — the FROZEN contract surface for the unified `entwurf_v2`
|
|
3
|
-
* verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE
|
|
3
|
+
* verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE pi-FREE core: the
|
|
4
4
|
* intent×liveness decision table + the reject taxonomy + a pure resolver.
|
|
5
5
|
* NO runtime dispatch, NO spawn/send, NO I/O — step 5 wires this to transports.
|
|
6
|
+
* The pi-ai TypeBox REPRESENTATION of this contract lives in the separate
|
|
7
|
+
* `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
|
|
8
|
+
* harness-neutral MCP bridge reaches at boot — carries no pi dependency.
|
|
6
9
|
*
|
|
7
10
|
* Why a frozen contract BEFORE the fact-provider (step 4): with the legacy
|
|
8
11
|
* 3-verb surface (`entwurf`/`entwurf_resume`/`entwurf_send`) still live, building
|
|
@@ -41,8 +44,6 @@
|
|
|
41
44
|
* not prose.
|
|
42
45
|
*/
|
|
43
46
|
|
|
44
|
-
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
45
|
-
import { SESSION_ID_RE } from "./session-id.js";
|
|
46
47
|
import type { SocketLiveness } from "./socket-probe.ts";
|
|
47
48
|
|
|
48
49
|
// ── Caller-declared intent (F1) ────────────────────────────────────────────
|
|
@@ -350,78 +351,9 @@ export function resolveDispatch(
|
|
|
350
351
|
};
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
// ── TypeBox schemas
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
// R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
|
|
361
|
-
// a malformed/typo gid fails the schema (→ bad-target) and can never reach a
|
|
362
|
-
// spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
|
|
363
|
-
target: Type.String({
|
|
364
|
-
pattern: SESSION_ID_RE.source,
|
|
365
|
-
description:
|
|
366
|
-
"garden-id of an EXISTING citizen (pattern-enforced). spawn-new is out of v2 scope (legacy entwurf keeps it); a malformed/typo gid is bad-target.",
|
|
367
|
-
}),
|
|
368
|
-
intent: StringEnum(ENTWURF_INTENTS, {
|
|
369
|
-
description:
|
|
370
|
-
"caller's declared outcome contract (F1): fire-and-forget = ack only, owned-outcome = caller owns completion.",
|
|
371
|
-
}),
|
|
372
|
-
mode: Type.Optional(
|
|
373
|
-
StringEnum(ENTWURF_V2_MODES, {
|
|
374
|
-
description:
|
|
375
|
-
"delivery mode (steer = interrupt current turn, follow_up = queue) — NOT the ownership axis (F1) nor liveness routing. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
|
|
376
|
-
}),
|
|
377
|
-
),
|
|
378
|
-
wantsReply: Type.Optional(
|
|
379
|
-
Type.Boolean({
|
|
380
|
-
description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
|
|
381
|
-
}),
|
|
382
|
-
),
|
|
383
|
-
// `additionalProperties: false` — a frozen contract input is exact; an unknown
|
|
384
|
-
// key is a caller error, not silently ignored.
|
|
385
|
-
},
|
|
386
|
-
{ additionalProperties: false },
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
// Receipt = a DISCRIMINATED union on `ok` (R3/F6) — NOT one flat object with
|
|
390
|
-
// optionals. Each branch is EXACT (`additionalProperties: false`): without it,
|
|
391
|
-
// JSON Schema's default admits extra keys, so an illegal receipt like
|
|
392
|
-
// {ok:true, ..., reason:"bad-target"} would validate against the success branch.
|
|
393
|
-
// With it, success carries action/transport/ownership and rejects a stray reason;
|
|
394
|
-
// reject carries reason and rejects any allow facet — the branches are mutually
|
|
395
|
-
// exclusive at the schema level, not merely by declared-property convention.
|
|
396
|
-
export const EntwurfV2ReceiptSuccessSchema = Type.Object(
|
|
397
|
-
{
|
|
398
|
-
ok: Type.Literal(true),
|
|
399
|
-
action: StringEnum(ENTWURF_V2_ACTIONS),
|
|
400
|
-
transport: StringEnum(ENTWURF_V2_TRANSPORTS),
|
|
401
|
-
ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
|
|
402
|
-
observedLiveness: StringEnum(FACT_LIVENESSES, {
|
|
403
|
-
description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
|
|
404
|
-
}),
|
|
405
|
-
},
|
|
406
|
-
{ additionalProperties: false },
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
export const EntwurfV2ReceiptRejectSchema = Type.Object(
|
|
410
|
-
{
|
|
411
|
-
ok: Type.Literal(false),
|
|
412
|
-
reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
|
|
413
|
-
// ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
|
|
414
|
-
// key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
|
|
415
|
-
// and a real FactLiveness otherwise. Optional would lose the "key always
|
|
416
|
-
// present, value may be null" shape and weaken the discriminated union; the
|
|
417
|
-
// reason-dependent null/non-null rule is enforced semantically (the gate's
|
|
418
|
-
// rejectObservedLivenessWellFormed fixture), not by this blanket union.
|
|
419
|
-
observedLiveness: Type.Union([StringEnum(FACT_LIVENESSES), Type.Null()], {
|
|
420
|
-
description:
|
|
421
|
-
"the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
|
|
422
|
-
}),
|
|
423
|
-
},
|
|
424
|
-
{ additionalProperties: false },
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
export const EntwurfV2ReceiptSchema = Type.Union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
|
|
354
|
+
// ── TypeBox schemas ────────────────────────────────────────────────────────
|
|
355
|
+
// MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi-ai TypeBox
|
|
356
|
+
// builders (StringEnum/Type) are a pi-lane dependency, so they cannot live in
|
|
357
|
+
// this pi-free core — the MCP bridge reaches this module at boot and must stay
|
|
358
|
+
// harness-neutral (check-entwurf-bridge-pi-free). The schemas import the
|
|
359
|
+
// constants/types above; pi-side consumers import the schemas from there.
|
|
@@ -195,7 +195,11 @@ export interface DispatchDeciderDeps {
|
|
|
195
195
|
releaseLock: (claim: LockClaim) => unknown;
|
|
196
196
|
inspectSocket: (gardenId: string) => Promise<TargetSocketInspection>;
|
|
197
197
|
probeSocket: (socketPath: string) => Promise<SocketLiveness>;
|
|
198
|
-
|
|
198
|
+
// MaybePromise (0.12.1 B-2): production lazy-imports the pi-coding-agent-backed
|
|
199
|
+
// preflight via `await import()` so the harness-neutral bridge boots pi-free;
|
|
200
|
+
// only the owned-outcome resume branch (below) awaits it. Sync test fakes that
|
|
201
|
+
// return a plain PreflightOutcome still satisfy this.
|
|
202
|
+
preflightForCwd: (cwd: string) => PreflightOutcome | Promise<PreflightOutcome>;
|
|
199
203
|
/**
|
|
200
204
|
* SE-2 slice 2d-3: the REQUIRED mailbox-deliverability seam (no default). The decider
|
|
201
205
|
* does NOT judge deliverability itself — it asks this injected fn, which combines the
|
|
@@ -392,7 +396,7 @@ async function decideInDomain(
|
|
|
392
396
|
// 1B: preflight runs ONLY here (the sole branch that launches a child into a
|
|
393
397
|
// target cwd). deny → nonce-owned release → untrusted-fail-fast, with the
|
|
394
398
|
// honest measured liveness (dormant = the `dead` we just probed).
|
|
395
|
-
const outcome = deps.preflightForCwd(resume.cwd);
|
|
399
|
+
const outcome = await deps.preflightForCwd(resume.cwd);
|
|
396
400
|
if (outcome.kind === "deny") {
|
|
397
401
|
return rejectAfterRelease(makeRejectReceipt("untrusted-fail-fast", liveness));
|
|
398
402
|
}
|
|
@@ -39,7 +39,12 @@ import {
|
|
|
39
39
|
receiverMarkerMatchesIdentity,
|
|
40
40
|
} from "./entwurf-deliverability.ts";
|
|
41
41
|
import { isNonPiGardenIdSocketConflict } from "./entwurf-facts.ts";
|
|
42
|
-
|
|
42
|
+
// 0.12.1 B-2: TYPE-ONLY import — `entwurf-preflight.ts` value-imports
|
|
43
|
+
// `@earendil-works/pi-coding-agent` (ProjectTrustStore), so a static value-import
|
|
44
|
+
// here would re-couple the harness-neutral MCP bridge to pi at boot
|
|
45
|
+
// (check-entwurf-bridge-pi-free). The real preflight is reached ONLY via the lazy
|
|
46
|
+
// `await import()` in `lazyProductionPreflight`, on the owned-outcome resume branch.
|
|
47
|
+
import type { PreflightInput, PreflightOutcome } from "./entwurf-preflight.ts";
|
|
43
48
|
import { isLivenessSupported } from "./entwurf-v2-contract.ts";
|
|
44
49
|
import {
|
|
45
50
|
type DispatchDeciderDeps,
|
|
@@ -109,7 +114,10 @@ export interface ProductionEntwurfV2Seams {
|
|
|
109
114
|
releaseLock: (claim: LockClaim, deps: { dir?: string }) => unknown;
|
|
110
115
|
inspectSocket: (gid: string, dir: string) => Promise<TargetSocketInspection>;
|
|
111
116
|
probeSocket: (socketPath: string) => Promise<SocketLiveness>;
|
|
112
|
-
|
|
117
|
+
// MaybePromise (0.12.1 B-2): the production default is the lazy wrapper, which
|
|
118
|
+
// `await import()`s the pi-coding-agent-backed preflight only on a resume verdict.
|
|
119
|
+
// A deterministic gate may still inject a sync spy returning a plain PreflightOutcome.
|
|
120
|
+
preflight: (input: PreflightInput) => PreflightOutcome | Promise<PreflightOutcome>;
|
|
113
121
|
classifyConnect: (code: string | undefined) => "dead" | "indeterminate";
|
|
114
122
|
sendRpc: (socketPath: string, command: RpcCommand, options?: RpcClientOptions) => Promise<{ response: RpcResponse }>;
|
|
115
123
|
enqueue: (opts: EnqueueMetaMessageOptions) => EnqueueMetaMessageResult;
|
|
@@ -138,6 +146,20 @@ export interface ProductionEntwurfV2Opts {
|
|
|
138
146
|
seams?: Partial<ProductionEntwurfV2Seams>;
|
|
139
147
|
}
|
|
140
148
|
|
|
149
|
+
/**
|
|
150
|
+
* 0.12.1 B-2: the production `preflight` seam default. preflight value-imports
|
|
151
|
+
* `@earendil-works/pi-coding-agent` (ProjectTrustStore), so importing it eagerly
|
|
152
|
+
* would pull pi into the harness-neutral MCP bridge's boot closure. This wrapper
|
|
153
|
+
* defers that to a lazy `await import()` reached ONLY on the owned-outcome resume
|
|
154
|
+
* branch (the decider awaits it). peers/self/list/mailbox-deliver therefore boot
|
|
155
|
+
* with no pi package present; a pi-less environment that DOES hit a spawn-bg resume
|
|
156
|
+
* surfaces an honest module-not-found at that point rather than failing boot.
|
|
157
|
+
*/
|
|
158
|
+
async function lazyProductionPreflight(input: PreflightInput): Promise<PreflightOutcome> {
|
|
159
|
+
const { preflight } = await import("./entwurf-preflight.ts");
|
|
160
|
+
return preflight(input);
|
|
161
|
+
}
|
|
162
|
+
|
|
141
163
|
/** Map a record-side socket inspection to the singleton (socketGids, symlinkedGids) the
|
|
142
164
|
* `isNonPiGardenIdSocketConflict` predicate consumes. `indeterminate` fails LOUD (QB2): an
|
|
143
165
|
* unprovable conflict must NOT be folded to "no conflict" — that would silently allow an
|
|
@@ -185,7 +207,7 @@ export function makeProductionEntwurfV2Deps(opts: ProductionEntwurfV2Opts): Entw
|
|
|
185
207
|
releaseLock: s.releaseLock ?? realReleaseLock,
|
|
186
208
|
inspectSocket: s.inspectSocket ?? inspectTargetControlSocket,
|
|
187
209
|
probeSocket: s.probeSocket ?? probeSocketLiveness,
|
|
188
|
-
preflight: s.preflight ??
|
|
210
|
+
preflight: s.preflight ?? lazyProductionPreflight,
|
|
189
211
|
classifyConnect: s.classifyConnect ?? classifyConnectError,
|
|
190
212
|
sendRpc: s.sendRpc ?? realSendRpc,
|
|
191
213
|
enqueue: s.enqueue ?? enqueueMetaMessage,
|
|
@@ -265,7 +287,7 @@ export function makeProductionEntwurfV2Deps(opts: ProductionEntwurfV2Opts): Entw
|
|
|
265
287
|
releaseLock: release,
|
|
266
288
|
inspectSocket,
|
|
267
289
|
probeSocket,
|
|
268
|
-
preflightForCwd: (cwd: string): PreflightOutcome =>
|
|
290
|
+
preflightForCwd: (cwd: string): PreflightOutcome | Promise<PreflightOutcome> =>
|
|
269
291
|
io.preflight({ cwd, agentDir: opts.agentDir, prefixRoots: opts.prefixRoots }),
|
|
270
292
|
mailboxDeliverabilityFor,
|
|
271
293
|
mailboxDir,
|
package/run.sh
CHANGED
|
@@ -13,7 +13,16 @@
|
|
|
13
13
|
#
|
|
14
14
|
set -euo pipefail
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
17
|
+
while [ -L "$SOURCE" ]; do
|
|
18
|
+
DIR="$(cd -P -- "$(dirname -- "$SOURCE")" && pwd)"
|
|
19
|
+
TARGET="$(readlink "$SOURCE")"
|
|
20
|
+
case "$TARGET" in
|
|
21
|
+
/*) SOURCE="$TARGET" ;;
|
|
22
|
+
*) SOURCE="$DIR/$TARGET" ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
REPO_DIR=$(cd -P -- "$(dirname -- "$SOURCE")" && pwd)
|
|
17
26
|
PROJECT_DIR_DEFAULT=$(pwd)
|
|
18
27
|
TARGET_PROJECT_DIR=${2:-$PROJECT_DIR_DEFAULT}
|
|
19
28
|
# npm publish identity. Scoped 2026-05-18 — bare `entwurf` was not on npm
|
|
@@ -36,6 +45,7 @@ Usage:
|
|
|
36
45
|
./run.sh release-gate [project-dir] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm check) + the v2-native live gates (v2 matrix/spawn-resume-live, check-bridge, retargeted smoke-session-id-name, RGG) + the ACP plugin acceptance floor (11 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). LIVE-gated MUST steps HONEST-SKIP when LIVE!=1 (a CUT needs LIVE=1, SKIP=0). --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
|
|
37
46
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
|
|
38
47
|
./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b, IN pnpm check): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2 registered/schema; tools/list only, no auth/side-effect
|
|
48
|
+
./run.sh check-entwurf-bridge-pi-free # deterministic gate (0.12.1 A, IN pnpm check): static — bridge index eager value-import closure must carry no @earendil-works/pi-* (type-only + dynamic import excluded); proves the meta-bridge boots pi-free
|
|
39
49
|
./run.sh check-model-lock # deterministic unit test for pi-extensions/model-lock.ts (4-quadrant + edge cases, no API)
|
|
40
50
|
./run.sh check-shell-quote # POSIX-safety gate for shellQuote (remote SSH arg quoting in entwurf paths) — source parity + behavior matrix, no SSH
|
|
41
51
|
./run.sh check-entwurf-session-identity # deterministic gate for locked garden session identity & name grammar (sessionId/buildSessionName/parse/collision), no API
|
|
@@ -785,6 +795,17 @@ check_entwurf_bridge_boot() {
|
|
|
785
795
|
(cd "$REPO_DIR" && node --experimental-strip-types scripts/check-entwurf-bridge-boot.ts)
|
|
786
796
|
}
|
|
787
797
|
|
|
798
|
+
check_entwurf_bridge_pi_free() {
|
|
799
|
+
# 0.12.1 A-gate (static half): the entwurf-bridge MCP server must boot WITHOUT any
|
|
800
|
+
# pi package. entwurf is a harness-neutral npm package; pi is one optional adapter
|
|
801
|
+
# lane, not a boot dependency. Walks the EAGER static value-import closure of
|
|
802
|
+
# mcp/entwurf-bridge/src/index.ts and fails if any reachable module statically
|
|
803
|
+
# value-imports @earendil-works/pi-*. Type-only imports and dynamic `await import()`
|
|
804
|
+
# (the intended lazy preflight boundary) are excluded — the runtime boot smoke is the
|
|
805
|
+
# final authority that peers/self/list/mailbox-deliver come up pi-free.
|
|
806
|
+
(cd "$REPO_DIR" && node --experimental-strip-types scripts/check-entwurf-bridge-pi-free.ts)
|
|
807
|
+
}
|
|
808
|
+
|
|
788
809
|
check_entwurf_v2_production() {
|
|
789
810
|
# Deterministic gate for 0.11 Stage 0 step 5d-2b: makeProductionEntwurfV2Deps — the ctx-free
|
|
790
811
|
# PRODUCTION assembly of runEntwurfV2's deps. Proves the wiring over fake leaf-IO spies (no
|
|
@@ -1650,7 +1671,11 @@ check_pack() {
|
|
|
1650
1671
|
section "pack invariants (dry-run)"
|
|
1651
1672
|
|
|
1652
1673
|
local json
|
|
1653
|
-
|
|
1674
|
+
# --silent so the `prepack` build (pnpm --silent run build-bridge → tsc) and
|
|
1675
|
+
# npm's own lifecycle banner stay off stdout; otherwise they pollute the --json
|
|
1676
|
+
# payload this parses. prepack runs on dry-run too, which is how dist lands in
|
|
1677
|
+
# this gate's file list.
|
|
1678
|
+
json=$(cd "$REPO_DIR" && npm pack --dry-run --json --silent 2>/dev/null) || {
|
|
1654
1679
|
fail "[check-pack] npm pack --dry-run failed"
|
|
1655
1680
|
return 1
|
|
1656
1681
|
}
|
|
@@ -1701,6 +1726,10 @@ check_pack() {
|
|
|
1701
1726
|
"pi-extensions/entwurf-control.ts"
|
|
1702
1727
|
"pi-extensions/model-lock.ts" "pi-extensions/lib/entwurf-core.ts"
|
|
1703
1728
|
"mcp/entwurf-bridge/src/index.ts"
|
|
1729
|
+
# 0.12.1 C — the prepack-built node_modules-safe boot artifact. start.sh runs
|
|
1730
|
+
# this dist JS when present (the .ts source can't strip-types under
|
|
1731
|
+
# node_modules). prepack runs on `npm pack --dry-run`, so it is in this gate.
|
|
1732
|
+
"mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js"
|
|
1704
1733
|
"scripts/postinstall-chmod.cjs"
|
|
1705
1734
|
"pi/entwurf-capabilities.json"
|
|
1706
1735
|
"pi/entwurf-targets.json"
|
|
@@ -1796,6 +1825,15 @@ check_pack_install() {
|
|
|
1796
1825
|
tgz_path="${REPO_DIR}/${tgz_name}"
|
|
1797
1826
|
rm -f "$tgz_path"
|
|
1798
1827
|
|
|
1828
|
+
# 0.12.1 C — stale-dist guard. `tsc` emit does NOT prune orphaned files from
|
|
1829
|
+
# outDir, and `files: ["mcp/"]` would carry any leftover dist file into the
|
|
1830
|
+
# tarball. build-bridge therefore `rm -rf`s dist before emit. Prove it: plant a
|
|
1831
|
+
# sentinel in dist, then assert the pack's prepack (build-bridge) wiped it so it
|
|
1832
|
+
# never reaches the tarball. Without the clean step this sentinel ships.
|
|
1833
|
+
local stale_probe="${REPO_DIR}/mcp/entwurf-bridge/dist/__stale_probe__.js"
|
|
1834
|
+
mkdir -p "$(dirname "$stale_probe")"
|
|
1835
|
+
printf 'module.exports = "stale";\n' > "$stale_probe"
|
|
1836
|
+
|
|
1799
1837
|
echo "[check-pack-install] npm pack -> ${tgz_name}"
|
|
1800
1838
|
(cd "$REPO_DIR" && npm pack --dry-run=false 2>&1 | tail -1) || {
|
|
1801
1839
|
fail "[check-pack-install] npm pack failed"
|
|
@@ -1814,6 +1852,15 @@ check_pack_install() {
|
|
|
1814
1852
|
local tar_files pass=1 f pat
|
|
1815
1853
|
tar_files=$(tar -tf "$tgz_path" | sed 's|^package/||' | grep -v '/$' || true)
|
|
1816
1854
|
|
|
1855
|
+
# 0.12.1 C — the planted stale sentinel must NOT have survived into the tarball.
|
|
1856
|
+
# If it did, build-bridge's `rm -rf dist` clean step regressed and stale/orphan
|
|
1857
|
+
# emit can ship. (See the plant just before npm pack above.)
|
|
1858
|
+
if grep -qxF "mcp/entwurf-bridge/dist/__stale_probe__.js" <<<"$tar_files"; then
|
|
1859
|
+
rm -f "$tgz_path"
|
|
1860
|
+
fail "[check-pack-install] stale dist file shipped — build-bridge did not clean dist before emit (orphan-emit publish risk)"
|
|
1861
|
+
return 1
|
|
1862
|
+
fi
|
|
1863
|
+
|
|
1817
1864
|
# Required tarball contents. The old 0.11.0 ACP root files (index.ts,
|
|
1818
1865
|
# acp-bridge.ts, event-mapper.ts, engraving.ts, pi-context-augment.ts,
|
|
1819
1866
|
# pi-extensions/entwurf.ts) were removed on v2-only and are GONE — keeping them
|
|
@@ -1830,6 +1877,8 @@ check_pack_install() {
|
|
|
1830
1877
|
"pi-extensions/entwurf-control.ts"
|
|
1831
1878
|
"pi-extensions/model-lock.ts" "pi-extensions/lib/entwurf-core.ts"
|
|
1832
1879
|
"mcp/entwurf-bridge/src/index.ts"
|
|
1880
|
+
# 0.12.1 C — prepack-built node_modules-safe boot artifact (see check-pack).
|
|
1881
|
+
"mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js"
|
|
1833
1882
|
"scripts/postinstall-chmod.cjs"
|
|
1834
1883
|
"pi/entwurf-capabilities.json"
|
|
1835
1884
|
"pi/entwurf-targets.json"
|
|
@@ -1959,26 +2008,25 @@ check_pack_install() {
|
|
|
1959
2008
|
done
|
|
1960
2009
|
echo "[check-pack-install] pi loader smoke pass (entwurf registered, claude-sonnet-4-6 + claude-opus-4-8 anchor)"
|
|
1961
2010
|
|
|
1962
|
-
# npm-managed
|
|
1963
|
-
# `
|
|
1964
|
-
#
|
|
1965
|
-
#
|
|
1966
|
-
#
|
|
1967
|
-
#
|
|
1968
|
-
#
|
|
1969
|
-
#
|
|
1970
|
-
#
|
|
1971
|
-
# Prove `run.sh install` actually writes settings from the hoisted layout. HOME is
|
|
2011
|
+
# npm-managed neutral install regression — the README's PRIMARY install path is
|
|
2012
|
+
# now `npm install @junghanacs/entwurf` (NOT `pi install npm:...`). This layout
|
|
2013
|
+
# lands the package under node_modules, hoists runtime deps to the sibling
|
|
2014
|
+
# node_modules, has no package-local node_modules, and the pi peer trio must be
|
|
2015
|
+
# absent because pi is an optional adapter lane. The old cwd-relative
|
|
2016
|
+
# preflight_dep_integrity rejected every hoisted-dep npm install; 0.12.0 then
|
|
2017
|
+
# additionally died because start.sh tried strip-types under node_modules.
|
|
2018
|
+
# Prove `entwurf`/`entwurf-bridge` bins exist, `run.sh install` writes settings
|
|
2019
|
+
# from the hoisted layout, and the installed bridge boots from dist. HOME is
|
|
1972
2020
|
# redirected to a throwaway dir so ensure_agent_dir_symlinks operates on a temp
|
|
1973
|
-
# ~/.pi/agent and never touches
|
|
2021
|
+
# ~/.pi/agent and never touches the operator's real targets link.
|
|
1974
2022
|
if ! command -v npm >/dev/null 2>&1; then
|
|
1975
2023
|
fail "[check-pack-install] npm not on PATH — cannot run npm-managed install regression"
|
|
1976
2024
|
return 1
|
|
1977
2025
|
fi
|
|
1978
2026
|
local npmroot="$npm_tmp/npmroot" npmhome="$npm_tmp/npmhome" npmproj="$npm_tmp/npmproj" npm_log npm_pkg wire_log
|
|
1979
2027
|
mkdir -p "$npmroot" "$npmhome" "$npmproj"
|
|
1980
|
-
npm_log=$(cd "$npmroot" && npm install "$tgz_path" --
|
|
1981
|
-
fail "[check-pack-install] npm-managed install failed:"
|
|
2028
|
+
npm_log=$(cd "$npmroot" && npm install "$tgz_path" --no-audit --no-fund 2>&1) || {
|
|
2029
|
+
fail "[check-pack-install] npm-managed neutral install failed:"
|
|
1982
2030
|
echo "$npm_log" | tail -10 | sed 's/^/ /' >&2
|
|
1983
2031
|
return 1
|
|
1984
2032
|
}
|
|
@@ -1987,6 +2035,11 @@ check_pack_install() {
|
|
|
1987
2035
|
fail "[check-pack-install] npm-managed layout missing executable run.sh (postinstall-chmod did not run?) at $npm_pkg"
|
|
1988
2036
|
return 1
|
|
1989
2037
|
fi
|
|
2038
|
+
if [ ! -x "$npmroot/node_modules/.bin/entwurf" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-bridge" ]; then
|
|
2039
|
+
fail "[check-pack-install] npm-managed neutral install missing package bins (entwurf / entwurf-bridge)"
|
|
2040
|
+
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
2041
|
+
return 1
|
|
2042
|
+
fi
|
|
1990
2043
|
wire_log=$(HOME="$npmhome" "$npm_pkg/run.sh" install "$npmproj" 2>&1) || {
|
|
1991
2044
|
fail "[check-pack-install] npm-managed run.sh install failed (preflight rejected hoisted deps?):"
|
|
1992
2045
|
echo "$wire_log" | tail -15 | sed 's/^/ /' >&2
|
|
@@ -2004,6 +2057,75 @@ check_pack_install() {
|
|
|
2004
2057
|
fi
|
|
2005
2058
|
echo "[check-pack-install] npm-managed install regression pass (hoisted-dep run.sh install wrote settings)"
|
|
2006
2059
|
|
|
2060
|
+
# 0.12.1 C — installed bridge BOOT regression. This is the test whose absence
|
|
2061
|
+
# let the 0.12.0 install bug ship: the README's bridge launcher was
|
|
2062
|
+
# `node --experimental-strip-types src/index.ts`, which Node REFUSES under
|
|
2063
|
+
# node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). Every gate above
|
|
2064
|
+
# wired settings or probed shape but never BOOTED the bridge from its installed
|
|
2065
|
+
# node_modules home, so the dead-on-arrival MCP server passed publish. Here we
|
|
2066
|
+
# boot the installed start.sh and assert it answers MCP tools/list with the v2
|
|
2067
|
+
# surface. Two proofs in one: (1) the prepack dist JS boots under node_modules
|
|
2068
|
+
# with plain node — a strip-types fallback would crash with the exact error
|
|
2069
|
+
# above, so a parseable tools/list IS proof the dist path was taken; (2) the
|
|
2070
|
+
# boot is pi-free — the @earendil-works peer trio is optional and must NOT be
|
|
2071
|
+
# installed by the neutral npm path, so the eager closure stands up with pi absent.
|
|
2072
|
+
local installed_start="$npmroot/node_modules/.bin/entwurf-bridge"
|
|
2073
|
+
local installed_dist="$npm_pkg/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js"
|
|
2074
|
+
if [ ! -f "$installed_dist" ]; then
|
|
2075
|
+
fail "[check-pack-install] installed bridge missing prebuilt dist (prepack did not emit into the tarball?): $installed_dist"
|
|
2076
|
+
return 1
|
|
2077
|
+
fi
|
|
2078
|
+
if [ -d "$npmroot/node_modules/@earendil-works" ]; then
|
|
2079
|
+
fail "[check-pack-install] @earendil-works present in npm-managed node_modules — pi-free boot proof is void (neutral npm install should not install optional pi peers)"
|
|
2080
|
+
return 1
|
|
2081
|
+
fi
|
|
2082
|
+
local boot_out
|
|
2083
|
+
if ! boot_out=$(START_SH="$installed_start" node --input-type=module <<'JS'
|
|
2084
|
+
import { spawn } from 'node:child_process';
|
|
2085
|
+
const start = process.env.START_SH;
|
|
2086
|
+
// Sanitize the child env so the pi-free proof cannot be masked by a leaked
|
|
2087
|
+
// module-resolution path: if NODE_PATH (or a stray pi env) pointed at a tree
|
|
2088
|
+
// holding @earendil-works, a statically pi-importing eager graph could resolve
|
|
2089
|
+
// and boot anyway, turning this gate falsely green. Strip it so "boots with
|
|
2090
|
+
// @earendil absent" stays an honest adversarial proof.
|
|
2091
|
+
const env = { ...process.env };
|
|
2092
|
+
delete env.NODE_PATH;
|
|
2093
|
+
const child = spawn(start, { stdio: ['pipe', 'pipe', 'pipe'], env });
|
|
2094
|
+
let stdout = '', stderr = '', done = false;
|
|
2095
|
+
const timer = setTimeout(() => {
|
|
2096
|
+
child.kill('SIGKILL');
|
|
2097
|
+
console.error('installed bridge boot timeout');
|
|
2098
|
+
if (stderr.trim()) console.error(stderr.trim());
|
|
2099
|
+
process.exit(1);
|
|
2100
|
+
}, 5000);
|
|
2101
|
+
function finish(trimmed) {
|
|
2102
|
+
if (done) return;
|
|
2103
|
+
done = true;
|
|
2104
|
+
clearTimeout(timer);
|
|
2105
|
+
let msg;
|
|
2106
|
+
try { msg = JSON.parse(trimmed); }
|
|
2107
|
+
catch { console.error('unparseable tools/list:', trimmed.slice(0, 300)); if (stderr.trim()) console.error(stderr.trim()); process.exit(1); }
|
|
2108
|
+
const names = (msg?.result?.tools ?? []).map((t) => t?.name).sort();
|
|
2109
|
+
for (const need of ['entwurf_v2', 'entwurf_peers', 'entwurf_self', 'entwurf_inbox_read']) {
|
|
2110
|
+
if (!names.includes(need)) { console.error('missing MCP tool from installed boot:', need, '— got', names.join(',')); process.exit(1); }
|
|
2111
|
+
}
|
|
2112
|
+
console.log(names.join(','));
|
|
2113
|
+
child.kill('SIGTERM');
|
|
2114
|
+
process.exit(0);
|
|
2115
|
+
}
|
|
2116
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); const t = stdout.trim(); if (t) finish(t); });
|
|
2117
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
2118
|
+
child.on('error', (e) => { clearTimeout(timer); console.error('installed bridge spawn error:', String(e)); process.exit(1); });
|
|
2119
|
+
child.on('close', () => { if (done) return; clearTimeout(timer); if (stderr.trim()) console.error(stderr.trim()); console.error('installed bridge closed with empty tools/list'); process.exit(1); });
|
|
2120
|
+
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n');
|
|
2121
|
+
JS
|
|
2122
|
+
); then
|
|
2123
|
+
fail "[check-pack-install] installed bridge boot FAILED — the npm-installed MCP server does not answer tools/list (the 0.12.0 strip-types-under-node_modules regression):"
|
|
2124
|
+
echo "$boot_out" | tail -15 | sed 's/^/ /' >&2
|
|
2125
|
+
return 1
|
|
2126
|
+
fi
|
|
2127
|
+
echo "[check-pack-install] installed bridge boot pass (dist boots under node_modules, pi-free: $boot_out)"
|
|
2128
|
+
|
|
2007
2129
|
ok "[check-pack-install] publish install smoke pass"
|
|
2008
2130
|
return 0
|
|
2009
2131
|
}
|
|
@@ -2560,6 +2682,9 @@ case "$cmd" in
|
|
|
2560
2682
|
check-entwurf-bridge-boot)
|
|
2561
2683
|
check_entwurf_bridge_boot
|
|
2562
2684
|
;;
|
|
2685
|
+
check-entwurf-bridge-pi-free)
|
|
2686
|
+
check_entwurf_bridge_pi_free
|
|
2687
|
+
;;
|
|
2563
2688
|
check-entwurf-v2-spawn)
|
|
2564
2689
|
check_entwurf_v2_spawn
|
|
2565
2690
|
;;
|