@junghanacs/entwurf 0.12.9 → 0.13.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 +135 -248
- package/BASELINE.md +46 -7
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +4 -2
- package/DELIVERY.md +5 -4
- package/README.md +46 -18
- package/VERIFY.md +10 -6
- package/demo/demo.sh +7 -1
- package/docs/setup-clean-host.md +10 -4
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +159 -66
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +148 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +26 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +10 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +15 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +13 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +31 -41
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +8 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +32 -25
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +5 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +9 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +17 -20
- package/mcp/entwurf-bridge/src/index.ts +190 -82
- package/package.json +7 -3
- package/pi/settings.reference.json +1 -1
- package/pi-extensions/acp-provider.ts +20 -10
- package/pi-extensions/entwurf-control.ts +69 -27
- package/pi-extensions/lib/acp/augment.ts +60 -2
- package/pi-extensions/lib/acp/backend-adapter.ts +183 -8
- package/pi-extensions/lib/acp/backend.ts +5 -1
- package/pi-extensions/lib/acp/config.ts +19 -5
- package/pi-extensions/lib/acp/engraving.ts +3 -1
- package/pi-extensions/lib/acp/event-mapper.ts +10 -3
- package/pi-extensions/lib/acp/models.ts +69 -7
- package/pi-extensions/lib/acp/overlay.ts +234 -3
- package/pi-extensions/lib/acp/tool-surface.ts +12 -4
- package/pi-extensions/lib/entwurf-core.ts +26 -9
- package/pi-extensions/lib/entwurf-deliverability.ts +10 -6
- package/pi-extensions/lib/entwurf-fact-provider.ts +15 -17
- package/pi-extensions/lib/entwurf-facts.ts +13 -13
- package/pi-extensions/lib/entwurf-resume-args.ts +40 -46
- package/pi-extensions/lib/entwurf-self-address.ts +8 -9
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +7 -2
- package/pi-extensions/lib/entwurf-v2-contract.ts +32 -25
- package/pi-extensions/lib/entwurf-v2-decider.ts +8 -8
- package/pi-extensions/lib/entwurf-v2-lock.ts +5 -4
- package/pi-extensions/lib/entwurf-v2-production.ts +14 -11
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +3 -1
- package/pi-extensions/lib/meta-session.ts +17 -20
- package/run.sh +200 -62
- package/scripts/agy-bridge-config.py +249 -49
- package/scripts/agy-bridge.sh +59 -14
- package/scripts/check-acp-carrier-augment.ts +34 -2
- package/scripts/check-acp-cortex.ts +668 -0
- package/scripts/check-acp-provider-surface.ts +50 -6
- package/scripts/check-acp-sdk-surface.ts +22 -11
- package/scripts/check-acp-session-reuse.ts +64 -1
- package/scripts/check-agy-permission-matrix.py +655 -0
- package/scripts/check-entwurf-deliverability.ts +9 -6
- package/scripts/check-entwurf-fact-provider.ts +7 -7
- package/scripts/check-entwurf-facts.ts +12 -12
- package/scripts/check-entwurf-resume-args.ts +22 -60
- package/scripts/check-entwurf-self-address.ts +187 -4
- package/scripts/check-entwurf-v2-contract.ts +2 -2
- package/scripts/check-entwurf-v2-production.ts +5 -5
- package/scripts/check-entwurf-v2-spawn-production.ts +6 -2
- package/scripts/check-entwurf-v2-surface.ts +327 -14
- package/scripts/check-gate-qualification.ts +787 -0
- package/scripts/check-meta-identity-consumers.ts +1 -1
- package/scripts/check-probe-cli-shim.ts +879 -0
- package/scripts/check-probe-ordering.ts +2450 -0
- package/scripts/check-shell-quote.ts +5 -4
- package/scripts/fixtures/probe-cli-shim +20 -0
- package/scripts/fixtures/probe-mcp-server.ts +168 -12
- package/scripts/lib/mutation-qualify.ts +794 -0
- package/scripts/lib/probe-acp-turn.ts +207 -0
- package/scripts/lib/probe-cli-shim.ts +464 -0
- package/scripts/lib/probe-cli-target.ts +165 -0
- package/scripts/lib/probe-event-log.ts +383 -0
- package/scripts/lib/probe-verdict.ts +1213 -0
- package/scripts/mutants/acp-augment.json +30 -0
- package/scripts/mutants/acp-cortex.json +196 -0
- package/scripts/mutants/agy-permission.json +144 -0
- package/scripts/mutants/meta-identity.json +17 -0
- package/scripts/mutants/probe-ordering.json +1032 -0
- package/scripts/mutants/self-address.json +59 -0
- package/scripts/mutants/v2-surface.json +87 -0
- package/scripts/smoke-acp-cortex-live.ts +392 -0
- package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-v2-send-live.ts +33 -18
- package/scripts/smoke-agy-install-state.sh +205 -20
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -2
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +0 -100
- package/scripts/check-entwurf-mailbox-guard.ts +0 -266
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* entwurf-resume-args — the SINGLE source of truth for the `pi` argv a resume spawn is
|
|
3
|
-
* launched with (0.11 Stage 0 step 5c-3b).
|
|
4
|
-
*
|
|
5
|
-
* shape — the one place the `--no-extensions` / `--entwurf-control` decision lives.
|
|
3
|
+
* launched with (0.11 Stage 0 step 5c-3b). One place owns the
|
|
4
|
+
* `--no-extensions` / `--entwurf-control` decision so the launch shape cannot drift.
|
|
6
5
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* is the early-exit/failure observation. `plan.launchArgs` (`--approve` or empty, from
|
|
19
|
-
* the decider's preflight) rides along as pi flags before the prompt.
|
|
6
|
+
* The shipped posture (A1): a RESIDENT, addressable garden citizen. The resume turn runs via
|
|
7
|
+
* `-p <prompt>` (the prompt-as-turn authority), and the child is launched WITH
|
|
8
|
+
* `--entwurf-control` and WITHOUT `--no-extensions` — the keep-alive is the GOAL here: the
|
|
9
|
+
* resumed session stands its control socket up and stays addressable. The 5c-3a watcher's
|
|
10
|
+
* `socket-alive` is exactly that "resumed citizen is up" observation (→ release the per-gid
|
|
11
|
+
* lock, child lives on); `child-exited` is the early-exit/failure observation.
|
|
12
|
+
* `plan.launchArgs` (`--approve` or empty, from the decider's preflight) rides along as pi
|
|
13
|
+
* flags before the prompt.
|
|
14
|
+
*
|
|
15
|
+
* A second `legacy` one-shot posture (`--no-extensions`, no control socket, so `pi -p` could
|
|
16
|
+
* exit) lived here until 2026-07-27 and was removed with its launcher and the v1 verbs.
|
|
20
17
|
*
|
|
21
18
|
* Provider/model identity is the caller's existing authority (readSessionIdentity /
|
|
22
19
|
* getEntwurfExplicitExtensions) — this builder only LAYS OUT argv, it never resolves
|
|
23
|
-
* identity. `explicitExtensionArgs` is preserved verbatim
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* identity. `explicitExtensionArgs` is preserved verbatim: a recorded `provider=entwurf`
|
|
21
|
+
* resume needs the bridge re-injected to resolve the provider, and dropping it would
|
|
22
|
+
* re-introduce the "Unknown provider" footgun (#29). (A future slice may dedup against
|
|
23
|
+
* settings-loaded extensions; not here.)
|
|
27
24
|
*
|
|
28
|
-
* This module is import-free on purpose: the
|
|
29
|
-
*
|
|
30
|
-
* the gate import the same source — a self-contained string builder keeps both configs happy.
|
|
25
|
+
* This module is import-free on purpose: the v2 adapter and the gate import the same source,
|
|
26
|
+
* and a self-contained string builder keeps both tsconfigs happy.
|
|
31
27
|
*/
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
/** The one shipped launch posture: a resident citizen (`--entwurf-control`, extensions
|
|
30
|
+
* loaded). The `legacy` one-shot variant was removed with its launcher (2026-07-27) — an
|
|
31
|
+
* exported branch no product path took was what let this module's prose claim a second
|
|
32
|
+
* live consumer for months. Reviving a one-shot posture means adding it back deliberately,
|
|
33
|
+
* with a consumer, not un-deleting a dead enum member. */
|
|
34
|
+
export type ResumeArgsVariant = "v2-control";
|
|
34
35
|
|
|
35
36
|
export interface ResumePiArgsInput {
|
|
36
|
-
/**
|
|
37
|
-
* resident citizen (`--entwurf-control`, extensions loaded). */
|
|
37
|
+
/** v2-control = resident citizen (`--entwurf-control`, extensions loaded). */
|
|
38
38
|
variant: ResumeArgsVariant;
|
|
39
39
|
/** ABSOLUTE path of the session JSONL to resume — pi's `--session <path>` (#50 C2).
|
|
40
40
|
* It replaced `--session-id <gardenId>`, which did two jobs that are no longer the
|
|
@@ -46,7 +46,7 @@ export interface ResumePiArgsInput {
|
|
|
46
46
|
* instead of resuming one. A path cannot do that. */
|
|
47
47
|
sessionFile: string;
|
|
48
48
|
/** The explicit `--extension …` re-injection (ACP bridge / provider resolution).
|
|
49
|
-
* Preserved verbatim
|
|
49
|
+
* Preserved verbatim — load-bearing for a entwurf resume. */
|
|
50
50
|
explicitExtensionArgs: readonly string[];
|
|
51
51
|
/** Recorded provider (may be null/undefined — then no `--provider` flag is emitted). */
|
|
52
52
|
provider: string | null | undefined;
|
|
@@ -54,37 +54,31 @@ export interface ResumePiArgsInput {
|
|
|
54
54
|
model: string;
|
|
55
55
|
/** The resume prompt — the final positional, run as the model turn under `-p`. */
|
|
56
56
|
prompt: string;
|
|
57
|
-
/**
|
|
58
|
-
* for legacy (the legacy path computes its own preflight elsewhere). */
|
|
57
|
+
/** The decider's `plan.launchArgs` (`["--approve"]` or `[]`). */
|
|
59
58
|
launchArgs?: readonly string[];
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
* Build the `pi` argv for a resume spawn. The
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
* Build the `pi` argv for a resume spawn. The prefix is `--mode json -p` (headless JSON
|
|
63
|
+
* child, prompt-as-turn); then the resident posture; then
|
|
64
|
+
* `[…ext args] --session <file> [--provider <p>] --model <m> <prompt>`.
|
|
66
65
|
*
|
|
67
66
|
* Invariants the gate pins:
|
|
68
|
-
* - legacy carries `--no-extensions` and NO `--entwurf-control`.
|
|
69
67
|
* - v2-control carries `--entwurf-control` and NO `--no-extensions`, plus `-p` + prompt.
|
|
70
|
-
* - `explicitExtensionArgs` appears exactly once
|
|
71
|
-
* - `launchArgs` is included
|
|
72
|
-
* - provider/model/prompt identity is
|
|
68
|
+
* - `explicitExtensionArgs` appears exactly once.
|
|
69
|
+
* - `launchArgs` is included before the suffix.
|
|
70
|
+
* - provider/model/prompt identity layout is fixed.
|
|
73
71
|
*/
|
|
74
72
|
export function buildResumePiArgs(input: ResumePiArgsInput): string[] {
|
|
75
73
|
const args: string[] = ["--mode", "json", "-p"];
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// The keep-alive legacy avoided is the goal here. `--approve`/launchArgs ride along.
|
|
83
|
-
args.push("--entwurf-control");
|
|
84
|
-
args.push(...(input.launchArgs ?? []));
|
|
85
|
-
}
|
|
75
|
+
// Resident citizen: stand the control socket up (A1) and keep extensions loaded. The
|
|
76
|
+
// keep-alive the removed one-shot launcher had to avoid is precisely the goal here —
|
|
77
|
+
// the resumed session must stay addressable. `--approve`/launchArgs ride along.
|
|
78
|
+
args.push("--entwurf-control");
|
|
79
|
+
args.push(...(input.launchArgs ?? []));
|
|
86
80
|
|
|
87
|
-
//
|
|
81
|
+
// Suffix — the fixed identity layout.
|
|
88
82
|
args.push(...input.explicitExtensionArgs);
|
|
89
83
|
args.push("--session", input.sessionFile);
|
|
90
84
|
if (input.provider) args.push("--provider", input.provider);
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* "Can a reply to THIS session actually land where its model will see it?"
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* with every fact INJECTED (no IO) so the gate can pin each row.
|
|
6
|
+
* This module closed the former split where the MCP and native pi surfaces inferred
|
|
7
|
+
* `replyable: true` from identity-carrier presence alone. Identity and reachability
|
|
8
|
+
* are separate: pi's env carries the garden id established by record birth, while
|
|
9
|
+
* replyability requires its control socket; a native marker must be record-backed,
|
|
10
|
+
* while replyability additionally requires its own rail's live facts. This is the
|
|
11
|
+
* single truth-table both surfaces compute from, with every fact INJECTED (no IO) so
|
|
12
|
+
* the gate can pin each row.
|
|
14
13
|
*
|
|
15
14
|
* Axes by origin:
|
|
16
15
|
* - pi-session: replyable ⟺ a live control socket exists at the canonical path.
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
* — which never arms a watch — would report replyable:false forever.
|
|
34
33
|
* - external-mcp: never replyable — no authoritative reply address.
|
|
35
34
|
*
|
|
36
|
-
* `origin` stays
|
|
35
|
+
* `origin` stays sender-carrier PROVENANCE, never the citizen identity authority and never a rail.
|
|
37
36
|
* Which rail a meta citizen's reply rides is a SECOND axis — `metaDeliveryDomain`, derived
|
|
38
37
|
* by the caller from `nativePushSupported(backend)`, not from `wakeMode` (direct-inject also
|
|
39
38
|
* covers codex/pi, which have no native-push adapter). Fail-closed: an unsupplied domain is
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
* from the bridge boot closure would re-couple the harness-neutral meta-bridge to
|
|
10
10
|
* pi (the `check-entwurf-bridge-pi-free` gate fails if it does). The bridge needs
|
|
11
11
|
* the contract CONSTANTS + `resolveDispatch` (pi-free core), never these schemas.
|
|
12
|
-
*
|
|
12
|
+
* Consumer: `check-entwurf-v2-contract` ONLY. This is a gate-side REPRESENTATION of the
|
|
13
|
+
* frozen contract, not a shipped tool schema — measured 2026-07-27, nothing else in the
|
|
14
|
+
* import graph reaches it. The two surfaces a model actually reads build their own:
|
|
15
|
+
* `pi-extensions/entwurf-control.ts` (TypeBox `entwurfV2Parameters` + tool description) and
|
|
16
|
+
* `mcp/entwurf-bridge/src/index.ts` (Zod). Change a model-facing description THERE; changing
|
|
17
|
+
* it here reaches no model, and `check-entwurf-v2-surface` is what pins those two.
|
|
13
18
|
*
|
|
14
19
|
* StringEnum (typebox 1.x) inside Type.Object (typebox 0.34) — the same mix the
|
|
15
20
|
* existing entwurf tools use. The logic types in the core are hand-written unions,
|
|
@@ -37,7 +42,7 @@ export const EntwurfV2InputSchema = Type.Object(
|
|
|
37
42
|
target: Type.String({
|
|
38
43
|
pattern: SESSION_ID_RE.source,
|
|
39
44
|
description:
|
|
40
|
-
"garden-id of an EXISTING citizen (pattern-enforced).
|
|
45
|
+
"garden-id of an EXISTING citizen (pattern-enforced). Fresh sibling creation is out of scope on every current surface — there is no fallback verb that still spawns. A malformed/typo gid is bad-target, never a new spawn.",
|
|
41
46
|
}),
|
|
42
47
|
intent: StringEnum(ENTWURF_INTENTS, {
|
|
43
48
|
description:
|
|
@@ -7,25 +7,28 @@
|
|
|
7
7
|
* `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
|
|
8
8
|
* harness-neutral MCP bridge reaches at boot — carries no pi dependency.
|
|
9
9
|
*
|
|
10
|
-
* Why a frozen contract BEFORE the fact-provider (step 4):
|
|
11
|
-
*
|
|
12
|
-
* discovery first
|
|
13
|
-
*
|
|
14
|
-
* it; dispatch computes from facts at call
|
|
15
|
-
*
|
|
10
|
+
* Why a frozen contract BEFORE the fact-provider (step 4): the legacy 3-verb
|
|
11
|
+
* surface (`entwurf`/`entwurf_resume`/`entwurf_send`) was still live when this
|
|
12
|
+
* was written, and building discovery first would have baked verb-routing into
|
|
13
|
+
* the fact layer, taking `entwurf_peers` wrong (동결결정 10 순서 근거). So the
|
|
14
|
+
* SHAPE is locked here; the facts read it; dispatch computes from facts at call
|
|
15
|
+
* time (step 5). Those v1 verbs were REMOVED in the 0.12 cutover — the ordering
|
|
16
|
+
* argument is history, but the layering it produced is the live design.
|
|
16
17
|
*
|
|
17
18
|
* Source-verified invariants folded in (Opus 실측 + GPT 보정 + Fable R1-R5, 2026-06-11):
|
|
18
19
|
* - F1: caller intent is DECLARED in the input, so the contract a caller
|
|
19
20
|
* receives is deterministic — never computed from liveness at call time.
|
|
20
21
|
* `owned-outcome` (caller owns completion) ≠ `fire-and-forget` (ack only).
|
|
21
|
-
* - R1:
|
|
22
|
-
*
|
|
23
|
-
* so its liveness is `unsupported`, NOT folded into dead/indeterminate
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* - R1: liveness is defined PER CAPABILITY DOMAIN. The control-socket domain
|
|
23
|
+
* currently contains backend `pi`; claude-code is self-fetch with no socket,
|
|
24
|
+
* so its socket liveness is `unsupported`, NOT folded into dead/indeterminate.
|
|
25
|
+
* This is transport capability, not identity rank: every target is first a
|
|
26
|
+
* record citizen. `unsupported` is a 4th FACT value, not a 4th table column.
|
|
26
27
|
* - R2: `target` is the garden-id of an EXISTING citizen. spawn-new is out of
|
|
27
|
-
* v2 scope
|
|
28
|
-
*
|
|
28
|
+
* v2 scope — it was the legacy `entwurf` verb's, and since that verb's removal
|
|
29
|
+
* fresh creation is a deferred capability with NO surface, not a fallback that
|
|
30
|
+
* still exists elsewhere. Absent/typo gid = `bad-target` (so F6 "오타 gid가
|
|
31
|
+
* 신규 spawn 사고 막기" holds automatically).
|
|
29
32
|
* - N1/F3: an `indeterminate` target never spawns. N2: `fire-and-forget` to a
|
|
30
33
|
* `dormant` target is "reject for now" (mailbox-wake lacks a reply-correlation
|
|
31
34
|
* id in the substrate; an additive extension later, not a permanent no).
|
|
@@ -67,16 +70,16 @@ export const DISPATCH_LIVENESSES = ["live", "dormant", "indeterminate"] as const
|
|
|
67
70
|
export type DispatchLiveness = (typeof DISPATCH_LIVENESSES)[number];
|
|
68
71
|
|
|
69
72
|
// ── Backend liveness domain (R1 + F4) ──────────────────────────────────────
|
|
70
|
-
// Backends whose SOCKET liveness predicate is DEFINED
|
|
71
|
-
// domain
|
|
73
|
+
// Backends whose SOCKET liveness predicate is DEFINED. The control-socket
|
|
74
|
+
// capability domain currently contains `pi` (connect + RPC `get_info`).
|
|
72
75
|
// claude-code (self-fetch, no socket) has no liveness predicate at all → `unsupported`.
|
|
73
76
|
// codex/antigravity are direct-inject; antigravity's liveness IS measured, but by the
|
|
74
77
|
// SEPARATE native-push adapter rail (a live app-server conversation probe), NOT this
|
|
75
78
|
// pi-socket domain — so it must NEVER be added here. Adding it would pull agy into the
|
|
76
|
-
//
|
|
77
|
-
// reporting agy `unsupported` = "outside the
|
|
79
|
+
// control-socket table (inspectSocket/probeSocket are socket-only); the fact layer keeps
|
|
80
|
+
// reporting agy `unsupported` = "outside the control-socket liveness domain", NOT
|
|
78
81
|
// unreachable (the native-push rail measures it — entwurf-v2-decider.ts). Widening
|
|
79
|
-
// THIS set is a deliberate future decision
|
|
82
|
+
// THIS set is a deliberate future decision, gated by a real compatible
|
|
80
83
|
// control-socket predicate — never by silently mapping sessions to dead/indeterminate
|
|
81
84
|
// (R1 핵심). check-entwurf-facts pins this == ["pi"] and asserts the native-push
|
|
82
85
|
// domain is disjoint from it.
|
|
@@ -89,12 +92,12 @@ export function isLivenessSupported(backend: string): boolean {
|
|
|
89
92
|
|
|
90
93
|
// ── Native-push backend domain (봉인 2/4) ───────────────────────────────────
|
|
91
94
|
// A backend whose liveness is measured by the SEPARATE native-push adapter rail (a
|
|
92
|
-
// live app-server conversation probe — antigravity's LS gRPC), NOT
|
|
93
|
-
// socket. This domain is DISJOINT from LIVENESS_DOMAIN_BACKENDS
|
|
94
|
-
//
|
|
95
|
+
// live app-server conversation probe — antigravity's LS gRPC), NOT a control
|
|
96
|
+
// socket. This domain is DISJOINT from LIVENESS_DOMAIN_BACKENDS: an agy session
|
|
97
|
+
// is `unsupported` on the socket FACT axis (entwurf_peers) yet fully
|
|
95
98
|
// measured + deliverable on the native-push axis. The two are separate rails on
|
|
96
99
|
// purpose — check-entwurf-facts pins both sets and asserts their intersection is ∅
|
|
97
|
-
// (a backend can never be in both
|
|
100
|
+
// (a backend can never be in both domains).
|
|
98
101
|
export const NATIVE_PUSH_BACKENDS = ["antigravity"] as const;
|
|
99
102
|
export type NativePushBackend = (typeof NATIVE_PUSH_BACKENDS)[number];
|
|
100
103
|
|
|
@@ -239,8 +242,9 @@ export const ENTWURF_V2_ACTIONS = ["send", "resume"] as const;
|
|
|
239
242
|
export const ENTWURF_V2_OWNERSHIPS = ["ack-only", "owned"] as const;
|
|
240
243
|
// Delivery mode of the message to the target (how it is injected) — steer =
|
|
241
244
|
// interrupt the current turn, follow_up = queue after it. A SEPARATE axis from
|
|
242
|
-
// both the intent/ownership axis (F1) and the liveness-routing axis
|
|
243
|
-
// entwurf_send
|
|
245
|
+
// both the intent/ownership axis (F1) and the liveness-routing axis. The removed
|
|
246
|
+
// v1 `entwurf_send` carried the same steer|follow_up surface, so this axis is
|
|
247
|
+
// inherited vocabulary, not a second live delivery verb.
|
|
244
248
|
export const ENTWURF_V2_MODES = ["steer", "follow_up"] as const;
|
|
245
249
|
|
|
246
250
|
export type DispatchVerdict =
|
|
@@ -252,7 +256,10 @@ export type DispatchVerdict =
|
|
|
252
256
|
// intent × dispatch-liveness → exactly one verdict (Q2). v2-initial ALLOWS
|
|
253
257
|
// exactly two cells (fire-and-forget+live = send; owned-outcome+dormant =
|
|
254
258
|
// resume); the other four reject. The reject cells are honest "지금은 없음"
|
|
255
|
-
// locks (N2)
|
|
259
|
+
// locks (N2). They were written while the legacy 3-verb surface still covered
|
|
260
|
+
// those flows; that surface is GONE, so a reject cell is now a real absence with
|
|
261
|
+
// no fallback verb behind it — reopening one takes a new contract, never a
|
|
262
|
+
// quiet re-admission.
|
|
256
263
|
export const DISPATCH_TABLE: Record<EntwurfIntent, Record<DispatchLiveness, DispatchVerdict>> = {
|
|
257
264
|
"fire-and-forget": {
|
|
258
265
|
live: { action: "send", transport: "control-socket", ownership: "ack-only" },
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* MCP-schema bypass for pi-native/internal callers).
|
|
27
27
|
* 2. resolveTarget — no citizen → bad-target; a record-less control socket →
|
|
28
28
|
* record-less-socket (#50 C4: the record is the sole address authority);
|
|
29
|
-
* quarantined (
|
|
29
|
+
* quarantined (out-of-socket-domain record sharing a socket/symlink) → target-address-conflict.
|
|
30
30
|
* PROBE-FREE.
|
|
31
31
|
* 3. backend → isLivenessSupported.
|
|
32
32
|
* 4. acquireLock — IN-DOMAIN ONLY (?7), BEFORE lstat/connect, so the probe
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import type { MailboxDeliverabilityResult } from "./entwurf-deliverability.ts";
|
|
50
|
-
import {
|
|
50
|
+
import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.ts";
|
|
51
51
|
import type { PreflightOutcome } from "./entwurf-preflight.ts";
|
|
52
52
|
import {
|
|
53
53
|
type EntwurfIntent,
|
|
@@ -75,7 +75,7 @@ import type { SocketLiveness } from "./socket-probe.ts";
|
|
|
75
75
|
|
|
76
76
|
// Re-export the shared conflict predicate so producers of a TargetResolution have a
|
|
77
77
|
// single import site for it (it is the SAME fn the fact-provider listing uses).
|
|
78
|
-
export {
|
|
78
|
+
export { isOutOfSocketDomainGardenIdConflict };
|
|
79
79
|
|
|
80
80
|
// ── observe timeout (?3) ───────────────────────────────────────────────────
|
|
81
81
|
// The bounded wait 5c's release-watcher gives a spawned child to surface its
|
|
@@ -172,8 +172,8 @@ export type DispatchDecision =
|
|
|
172
172
|
|
|
173
173
|
// ── target resolution (E: single-target, not a whole-store scan) ────────────
|
|
174
174
|
// A non-null identity is an existing citizen. `preProbeAddressConflict` is the
|
|
175
|
-
// PROBE-FREE
|
|
176
|
-
//
|
|
175
|
+
// PROBE-FREE record-side conflict: a backend outside the control-socket domain
|
|
176
|
+
// sharing a real/symlinked socket (`isOutOfSocketDomainGardenIdConflict`). Production uses one
|
|
177
177
|
// readMetaIdentityByGardenId + a target socket/symlink check; the gate injects it.
|
|
178
178
|
// Do NOT call listEntwurfFacts here to find the target — its socket probe would run
|
|
179
179
|
// before the lock (the 1C TOCTOU). The shared predicate is the only thing the
|
|
@@ -377,12 +377,12 @@ export async function decideDispatch(input: DispatchInput, deps: DispatchDecider
|
|
|
377
377
|
return { kind: "execute", receipt, plan, lock: null };
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
// 4-5.
|
|
380
|
+
// 4-5. control-socket domain (currently backend pi): lock → inspect → route.
|
|
381
381
|
return decideInDomain(gardenId, input, deps, ctx, identity.cwd);
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
// ──
|
|
385
|
-
//
|
|
384
|
+
// ── control-socket-domain probe (steps 4-5; currently backend pi). Every target
|
|
385
|
+
// is record-backed; a record-less socket rejects before this branch. ────────────
|
|
386
386
|
// The lock lifecycle (B2) lives here: acquire BEFORE lstat/connect, every reject path
|
|
387
387
|
// releases explicitly (rejectAfterRelease), every execute path that keeps the lock sets
|
|
388
388
|
// retainLock=true, and a thrown IO error releases the still-held lock before rethrowing so
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* entwurf-v2-lock — the per-gid dispatch lock primitive (0.11 Stage 0 step 5a,
|
|
3
3
|
* 버킷 B F2). LOAD-BEARING: the guard against a double-spawn of the same dormant
|
|
4
4
|
* target by two V2 dispatchers that share the substrate through different entry
|
|
5
|
-
* points. SCOPE (honest): this protects v2/v2 only.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* points. SCOPE (honest): this protects v2/v2 only. It was written while the legacy
|
|
6
|
+
* `entwurf_resume` still ran unchanged (동결결정 10 scope A) without taking this
|
|
7
|
+
* lock, which left a v2/legacy concurrent-resume gap. That verb was REMOVED in the
|
|
8
|
+
* 0.12 cutover, so the gap is closed by subtraction — not by this lock growing to
|
|
9
|
+
* cover it. Any NEW resume entry point must take this lock or the gap reopens.
|
|
9
10
|
*
|
|
10
11
|
* ENVIRONMENT ASSUMPTION (stale reclaim): `hostname` equality is used as the
|
|
11
12
|
* proxy for "same machine", so a holder pid is reclaim-probed with kill(0) only
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
mailboxConversationalDeliverable,
|
|
43
43
|
receiverMarkerMatchesIdentity,
|
|
44
44
|
} from "./entwurf-deliverability.ts";
|
|
45
|
-
import {
|
|
45
|
+
import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.ts";
|
|
46
46
|
// 0.12.1 B-2: TYPE-ONLY import — `entwurf-preflight.ts` value-imports
|
|
47
47
|
// `@earendil-works/pi-coding-agent` (ProjectTrustStore), so a static value-import
|
|
48
48
|
// here would re-couple the harness-neutral MCP bridge to pi at boot
|
|
@@ -189,7 +189,7 @@ async function lazyProductionPreflight(input: PreflightInput): Promise<Preflight
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/** Map a record-side socket inspection to the singleton (socketGids, symlinkedGids) the
|
|
192
|
-
* `
|
|
192
|
+
* `isOutOfSocketDomainGardenIdConflict` predicate consumes. `indeterminate` fails LOUD (QB2): an
|
|
193
193
|
* unprovable conflict must NOT be folded to "no conflict" — that would silently allow an
|
|
194
194
|
* unsupported-backend mailbox send onto a quarantined address. */
|
|
195
195
|
function conflictSetsFor(
|
|
@@ -294,20 +294,23 @@ export function makeProductionEntwurfV2Deps(opts: ProductionEntwurfV2Opts): Entw
|
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
const identity = io.readIdentity(gid, sessionsDir);
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
// and (b) turn a pi target's `indeterminate` lstat into a top-level throw, stealing it
|
|
302
|
-
// from the under-lock `inspectSocket → indeterminate → indeterminate-no-spawn` path.
|
|
297
|
+
// This pre-probe conflict applies only OUTSIDE the control-socket capability domain.
|
|
298
|
+
// An in-domain target's lstat/connect MUST run under the later per-target lock, so it
|
|
299
|
+
// short-circuits here with no pre-lock IO. Otherwise an indeterminate lstat would be
|
|
300
|
+
// stolen from the under-lock `inspectSocket → indeterminate-no-spawn` path.
|
|
303
301
|
if (isLivenessSupported(identity.backend)) {
|
|
304
302
|
return { identity, preProbeAddressConflict: false };
|
|
305
303
|
}
|
|
306
|
-
// Only
|
|
307
|
-
//
|
|
304
|
+
// Only a citizen outside the control-socket domain reaches this record-side lstat;
|
|
305
|
+
// `indeterminate` fails loud (QB2 — never silently "no conflict").
|
|
308
306
|
const inspection = await io.inspectPath(controlSocketPath(gid, controlSocketDir));
|
|
309
307
|
const { socketGids, symlinkedGids } = conflictSetsFor(gid, inspection);
|
|
310
|
-
const preProbeAddressConflict =
|
|
308
|
+
const preProbeAddressConflict = isOutOfSocketDomainGardenIdConflict(
|
|
309
|
+
identity.backend,
|
|
310
|
+
gid,
|
|
311
|
+
socketGids,
|
|
312
|
+
symlinkedGids,
|
|
313
|
+
);
|
|
311
314
|
return { identity, preProbeAddressConflict };
|
|
312
315
|
};
|
|
313
316
|
|
|
@@ -135,7 +135,9 @@ export function resolveResumeLaunchIdentity(plan: SpawnBgPlan): LaunchIdentity {
|
|
|
135
135
|
const record = readAddressableMetaIdentity(plan.sessionId);
|
|
136
136
|
if (record.backend !== "pi") {
|
|
137
137
|
throw new Error(
|
|
138
|
-
`entwurf-v2-spawn-production: ${plan.sessionId} is a ${record.backend} citizen —
|
|
138
|
+
`entwurf-v2-spawn-production: ${plan.sessionId} is a ${record.backend} citizen — ` +
|
|
139
|
+
`spawn-bg resume is a host-adapter capability, and its domain currently contains backend pi only. ` +
|
|
140
|
+
`This is a relaunch capability, not the control-socket rail and not citizen rank.`,
|
|
139
141
|
);
|
|
140
142
|
}
|
|
141
143
|
const sessionFile = record.transcriptPath;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* meta-session —
|
|
2
|
+
* meta-session — the shared V3 garden-citizen RECORD AUTHORITY.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* This module entered through the native meta-bridge (#30), then became the one
|
|
5
|
+
* identity store for every addressable citizen, including `backend:"pi"` (#50).
|
|
6
|
+
* A record is a backend-owned-session bib card: it binds the backend's native id
|
|
7
|
+
* and transcript pointer to a garden id without taking over that backend's runtime,
|
|
8
|
+
* auth, or transcript. The module name is history; the live identity schema is not
|
|
9
|
+
* a native-only or pi-exception axis.
|
|
10
10
|
*
|
|
11
11
|
* Two layers, clearly sectioned:
|
|
12
12
|
* 1. RECORD functions + types (mint / serialize / parse / certifyActiveStore /
|
|
@@ -61,7 +61,7 @@ import { generateSessionId, SESSION_ID_RE } from "./session-id.js";
|
|
|
61
61
|
// Errors
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
|
|
64
|
-
/** A
|
|
64
|
+
/** A garden-citizen record is malformed, or an input violates the record contract. */
|
|
65
65
|
export class MetaRecordError extends Error {
|
|
66
66
|
constructor(message: string) {
|
|
67
67
|
super(message);
|
|
@@ -142,8 +142,9 @@ export function requireNonEmptyString(value: unknown, field: string): string {
|
|
|
142
142
|
|
|
143
143
|
/** Validate the 3-backend NATIVE bridge axis (sender/receiver markers, capability
|
|
144
144
|
* drift guard). Not a record-schema validator: identity records take
|
|
145
|
-
* `requireCitizenBackend` (which admits `pi`)
|
|
146
|
-
*
|
|
145
|
+
* `requireCitizenBackend` (which admits `pi`). Markers stay native-3 because the
|
|
146
|
+
* pi adapter carries its record-established garden id into children via env rather
|
|
147
|
+
* than using the native-hook pid marker rail. */
|
|
147
148
|
export function requireBackend(value: unknown): MetaBackend {
|
|
148
149
|
if (typeof value !== "string" || !META_BACKENDS.includes(value as MetaBackend)) {
|
|
149
150
|
throw new MetaRecordError(
|
|
@@ -208,7 +209,7 @@ function isoNow(now: Date): string {
|
|
|
208
209
|
/** The one live identity schema number. */
|
|
209
210
|
export const META_SCHEMA_VERSION_V3 = 3 as const;
|
|
210
211
|
|
|
211
|
-
/**
|
|
212
|
+
/** Every backend admitted by the one V3 record-citizen schema. */
|
|
212
213
|
export const META_CITIZEN_BACKENDS = ["claude-code", "antigravity", "codex", "pi"] as const;
|
|
213
214
|
export type MetaCitizenBackend = (typeof META_CITIZEN_BACKENDS)[number];
|
|
214
215
|
|
|
@@ -510,15 +511,11 @@ export function parseMetaIdentity(json: string): MetaIdentity {
|
|
|
510
511
|
// const for the three existing backends (the drift guard) and COVERS exactly
|
|
511
512
|
// META_CITIZEN_BACKENDS (pi included).
|
|
512
513
|
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
// pi's dormant→resume→mailbox path is self-fetch-shaped, so pi is really
|
|
519
|
-
// BIMODAL; a single wakeMode field cannot express both. Splitting it
|
|
520
|
-
// (mailboxWakeMode vs controlSocketWakeMode) is out of 3C scope — for now the
|
|
521
|
-
// single field reports pi's primary live capability (direct-inject) honestly.
|
|
514
|
+
// `wakeMode` describes the last centimetre, not citizen rank. The control-socket
|
|
515
|
+
// adapter injects the body into a pi turn, so backend `pi` is `direct-inject`;
|
|
516
|
+
// Claude's mailbox is `self-fetch`; native-push adapters are direct injection.
|
|
517
|
+
// Dormant spawn-bg resume is a separate transport/ownership decision and does not
|
|
518
|
+
// turn the pi record into a mailbox citizen.
|
|
522
519
|
// ---------------------------------------------------------------------------
|
|
523
520
|
|
|
524
521
|
/** Bump only on a breaking capability-registry shape change; the parser refuses other versions. */
|