@junghanacs/entwurf 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +48 -15
- package/BASELINE.md +3 -3
- package/CHANGELOG.md +45 -0
- package/CONTRIBUTING.md +13 -9
- package/DELIVERY.md +7 -6
- package/README.md +27 -27
- package/VERIFY.md +22 -14
- package/demo/README.md +1 -1
- package/demo/demo-baseline.sh +1 -3
- package/demo/demo.sh +2 -5
- package/docs/acp-backend-rail.md +9 -4
- package/docs/external-mcp-host.md +4 -5
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +148 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
- package/mcp/entwurf-bridge/src/index.ts +164 -28
- package/mcp/entwurf-bridge/start.sh +2 -2
- package/mcp/entwurf-bridge/test.sh +23 -9
- package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
- package/package.json +22 -11
- package/pi-extensions/entwurf-control.ts +218 -40
- package/pi-extensions/lib/acp/backend.ts +71 -12
- package/pi-extensions/lib/acp/overlay.ts +8 -6
- package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
- package/pi-extensions/lib/entwurf-core.ts +15 -15
- package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
- package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
- package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
- package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
- package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
- package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
- package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
- package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
- package/pi-extensions/lib/meta-session.ts +93 -5
- package/pi-extensions/lib/mux-fresh-call.ts +328 -0
- package/pi-extensions/lib/mux-launch.ts +267 -0
- package/pi-extensions/lib/mux-placement.ts +387 -0
- package/pi-extensions/lib/mux-resume-call.ts +221 -0
- package/pi-extensions/lib/resume-launch-identity.ts +162 -0
- package/pi-extensions/lib/session-id.js +8 -5
- package/pi-extensions/lib/socket-discovery.ts +3 -3
- package/run.sh +471 -201
- package/scripts/agy-bridge-config.py +5 -1
- package/scripts/check-acp-backend-preflight.ts +1 -1
- package/scripts/check-acp-overlay.ts +13 -3
- package/scripts/check-acp-stream-hooks.ts +504 -0
- package/scripts/check-elapsed.sh +25 -0
- package/scripts/check-entwurf-bridge-boot.ts +51 -4
- package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
- package/scripts/check-entwurf-control-rpc.ts +4 -3
- package/scripts/check-entwurf-resume-args.ts +72 -70
- package/scripts/check-entwurf-session-identity.ts +14 -10
- package/scripts/check-entwurf-v2-contract.ts +34 -59
- package/scripts/check-entwurf-v2-decider.ts +17 -177
- package/scripts/check-entwurf-v2-lock.ts +5 -2
- package/scripts/check-entwurf-v2-matrix.ts +3 -53
- package/scripts/check-entwurf-v2-production.ts +2 -91
- package/scripts/check-entwurf-v2-release.ts +10 -105
- package/scripts/check-entwurf-v2-runner.ts +4 -85
- package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
- package/scripts/check-entwurf-v2-send.ts +0 -28
- package/scripts/check-entwurf-v2-surface.ts +157 -128
- package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
- package/scripts/check-fresh-cut-gate.sh +1 -1
- package/scripts/check-gate-qualification.ts +98 -7
- package/scripts/check-install-container.sh +10 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-keyset-overlap.py +1 -1
- package/scripts/check-meta-facts.ts +249 -0
- package/scripts/check-meta-identity-consumers.ts +1 -1
- package/scripts/check-meta-session.ts +169 -0
- package/scripts/check-mux-launch-tmux.ts +316 -0
- package/scripts/check-mux-launch.ts +288 -0
- package/scripts/check-mux-launcher-fence.ts +264 -0
- package/scripts/check-mux-parent-artifact.ts +195 -0
- package/scripts/check-mux-placement-tmux.ts +322 -0
- package/scripts/check-mux-placement.ts +323 -0
- package/scripts/check-mux-resume-call.ts +282 -0
- package/scripts/check-probe-cli-shim.ts +25 -22
- package/scripts/check-probe-ordering.ts +84 -76
- package/scripts/check-release-gate-outcomes.ts +127 -7
- package/scripts/check-resume-launch-identity.ts +244 -0
- package/scripts/check-socket-discovery.ts +1 -1
- package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
- package/scripts/inventory-verification-surface.ts +349 -0
- package/scripts/lib/claude-launcher-fence.ts +322 -0
- package/scripts/lib/mutation-qualify.ts +109 -3
- package/scripts/meta-bridge-doctor.sh +6 -8
- package/scripts/meta-facts.ts +60 -0
- package/scripts/mutants/acp-overlay.json +17 -0
- package/scripts/mutants/acp-stream-hooks.json +158 -0
- package/scripts/mutants/bridge-boot-resume.json +45 -0
- package/scripts/mutants/meta-facts.json +50 -0
- package/scripts/mutants/meta-identity.json +36 -0
- package/scripts/mutants/mux-boundary.json +196 -0
- package/scripts/mutants/mux-fresh-call.json +185 -0
- package/scripts/mutants/mux-launcher-fence.json +123 -0
- package/scripts/mutants/mux-parent-artifact.json +39 -0
- package/scripts/mutants/mux-resume-call.json +148 -0
- package/scripts/mutants/probe-ordering.json +0 -1037
- package/scripts/mutants/release-gate.json +35 -0
- package/scripts/mutants/resume-args.json +76 -0
- package/scripts/mutants/resume-launch-identity.json +96 -0
- package/scripts/mutants/v2-surface.json +58 -18
- package/scripts/mutants/v2-visible-resume.json +215 -0
- package/scripts/new-session-id.ts +9 -4
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-agy-native-push-live.ts +6 -17
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-meta-honesty.sh +1 -1
- package/scripts/smoke-mux-fresh-call-live.ts +365 -0
- package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
- package/scripts/smoke-pi-attach.ts +1 -1
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- package/scripts/tsconfig.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
- package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
- package/scripts/check-acp-sdk-surface.ts +0 -275
- package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
- package/scripts/check-entwurf-v2-spawn.ts +0 -399
- package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -467
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// claude-agent-acp's SettingsManager loads the operator's `~/.claude/settings.json`
|
|
4
4
|
// DIRECTLY (CLAUDE_CONFIG_DIR is the only knob that redirects that read). So the
|
|
5
|
-
// operator's native `permissions.defaultMode` (
|
|
6
|
-
// per-cwd memory/projects state would otherwise leak into entwurf ACP
|
|
5
|
+
// operator's native `permissions.defaultMode` (whatever its current value), hooks,
|
|
6
|
+
// plugins, and per-cwd memory/projects state would otherwise leak into entwurf ACP
|
|
7
7
|
// sessions. The overlay redirects SettingsManager at a pi-owned directory whose
|
|
8
8
|
// `settings.json` WE author (minimal, `hooks:{}`), while keeping exactly the
|
|
9
9
|
// operator entries a backend needs (credentials, caches, built-in skills)
|
|
@@ -86,9 +86,11 @@ export const OVERLAY_EMPTY_DIRS = new Set(["projects", "sessions"]);
|
|
|
86
86
|
export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "settings.json"]);
|
|
87
87
|
/**
|
|
88
88
|
* Minimal overlay settings.json. Only fields with a reason to pin:
|
|
89
|
-
* - `permissions.defaultMode: "
|
|
90
|
-
*
|
|
91
|
-
*
|
|
89
|
+
* - `permissions.defaultMode: "bypassPermissions"` is deliberate unattended
|
|
90
|
+
* ACP operation: a tool call must never suspend a model turn on an interactive
|
|
91
|
+
* permission prompt. This does not widen the callable surface — explicit
|
|
92
|
+
* `tools`/`disallowedTools` still shape it, and `permissionAllow` still rides
|
|
93
|
+
* the inline Claude settings — or bypass backend authentication.
|
|
92
94
|
* - `autoMemoryEnabled: false` — SDK opt-out for auto-memory (defense in
|
|
93
95
|
* depth; the tiny non-empty engraving/preset replacement is the primary
|
|
94
96
|
* write-containment lever for Claude ACP).
|
|
@@ -97,7 +99,7 @@ export const OVERLAY_BINARY_OWNED = new Set([".claude.json", "backups", "setting
|
|
|
97
99
|
*/
|
|
98
100
|
export function overlaySettingsJson() {
|
|
99
101
|
return `${JSON.stringify({
|
|
100
|
-
permissions: { defaultMode: "
|
|
102
|
+
permissions: { defaultMode: "bypassPermissions" },
|
|
101
103
|
autoMemoryEnabled: false,
|
|
102
104
|
hooks: {},
|
|
103
105
|
}, null, 2)}\n`;
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
import * as net from "node:net";
|
|
17
17
|
/**
|
|
18
18
|
* THE `<sender_info>` synthesis — the one place the sender envelope becomes
|
|
19
|
-
* message text.
|
|
19
|
+
* message text. ONE consumer since the visible-first cut (#50 C3):
|
|
20
20
|
* - the live socket rail's RECEIVER (entwurf-control handleCommand("send")
|
|
21
|
-
* appends it to the delivered customMessage)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* appends it to the delivered customMessage).
|
|
22
|
+
*
|
|
23
|
+
* The second consumer was the dormant spawn-resume rail's SENDER, which appended
|
|
24
|
+
* the same shape to a resume prompt so a resumed citizen woke knowing who called.
|
|
25
|
+
* That rail is gone. The shape stays single-sourced here so a future VISIBLE
|
|
26
|
+
* resume renders an identical envelope rather than inventing a second one.
|
|
25
27
|
* `wants_reply` is emitted only when explicitly true — an unset/false marker
|
|
26
28
|
* renders nothing (etiquette marker, not transport contract).
|
|
27
29
|
*/
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* entwurf-core — sync entwurf execution, host-agnostic.
|
|
3
3
|
*
|
|
4
|
-
* DIRECT importers, MEASURED from the import graph (2026-
|
|
4
|
+
* DIRECT importers, RE-MEASURED from the import graph (2026-08-06) — not from memory:
|
|
5
5
|
* - pi-extensions/entwurf-control.ts (the pi adapter)
|
|
6
|
-
* - pi-extensions/lib/
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* - pi-extensions/lib/resume-launch-identity.ts (record-authoritative launch identity,
|
|
7
|
+
* itself consumer-zero since the visible-first cut — see its header)
|
|
8
|
+
* - plus gates/smokes (check-shell-quote, check-package-source-routing,
|
|
9
|
+
* check-entwurf-session-identity, new-session-id, resolve-acp-bridge)
|
|
10
10
|
*
|
|
11
|
-
* The MCP bridge
|
|
12
|
-
*
|
|
13
|
-
* entwurf-v2-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* runtime, only for spawn-bg production.
|
|
11
|
+
* The MCP bridge does NOT reach this module on any path, and that changed with the cut:
|
|
12
|
+
* its one edge ran `index.ts → entwurf-v2-surface.ts → entwurf-v2-production.ts →
|
|
13
|
+
* entwurf-v2-spawn-production.ts → entwurf-core.ts`, and the spawn-production link was
|
|
14
|
+
* deleted with the `spawn-bg` transport. Do not restate the old "transitive consumer,
|
|
15
|
+
* for spawn-bg production" line; re-measure before claiming either way.
|
|
17
16
|
*
|
|
18
17
|
* NOT consumers on any path: `pi-extensions/entwurf.ts` (the v1 pi native tool surface) and
|
|
19
18
|
* `pi-extensions/lib/entwurf-async.ts` were REMOVED in the 0.12 cutover. Do not reintroduce
|
|
@@ -113,9 +112,9 @@ export function normalizeCodexEntwurfModelForAcp(model) {
|
|
|
113
112
|
// The Entwurf Target Registry is GONE (#50 C3). `pi/entwurf-targets.json` and its
|
|
114
113
|
// reader chain (loadEntwurfTargets / resolveEntwurfTarget / EntwurfRegistryError +
|
|
115
114
|
// the ~/.pi/agent symlink machinery) were the v1 "narrow door" for spawn-model
|
|
116
|
-
// policy — but v2 never spawns from a model tuple: entwurf_v2
|
|
117
|
-
// already-identified record-backed citizen, and the model axis is the
|
|
118
|
-
// own (Identity Preservation Rule). The last readers were the RT-dead
|
|
115
|
+
// policy — but v2 never spawns at all, from a model tuple or otherwise: entwurf_v2
|
|
116
|
+
// addresses an already-identified record-backed citizen, and the model axis is the
|
|
117
|
+
// citizen's own (Identity Preservation Rule). The last readers were the RT-dead
|
|
119
118
|
// buildSessionName mirror and the v1 spawn guard, both swept with this cut.
|
|
120
119
|
// Bridge-extension routing for provider=entwurf survives below (getRegistryRouting
|
|
121
120
|
// ← scripts/resolve-acp-bridge.ts) and takes a caller-supplied target — no file.
|
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* entwurf-resume-args — the SINGLE source of truth for the `pi` argv
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
2
|
+
* entwurf-resume-args — the SINGLE source of truth for the `pi` argv that reopens a dormant
|
|
3
|
+
* citizen, MEASURED against the runtime rather than inherited from the removed transport.
|
|
4
|
+
*
|
|
5
|
+
* ── What changed, and why the old shape was not reusable ──
|
|
6
|
+
*
|
|
7
|
+
* Until the visible-first cut this builder emitted `--mode json -p … <prompt>`: a headless JSON
|
|
8
|
+
* child running one prompt-as-turn, which is what `spawn-bg`'s detached `defaultSpawnChild`
|
|
9
|
+
* needed. The design notes carried that argv forward as "visibility-neutral, reuse verbatim".
|
|
10
|
+
* It is not. Measured 2026-08-06 (private tmux + fixture, `pi --help`: `--print, -p` =
|
|
11
|
+
* *non-interactive mode*): dropped into a tmux window, the headless prefix produces a JSON
|
|
12
|
+
* stream and a turn, not a window an operator can type into. The visible dialect is the one
|
|
13
|
+
* `mux-fresh-call` already measured for a fresh sibling — no `--mode`, no `-p`.
|
|
14
|
+
*
|
|
15
|
+
* So the shipped posture is now one shape, and it is the only one:
|
|
16
|
+
*
|
|
17
|
+
* pi --entwurf-control [-e <bridge> …] --session <file> [--provider <p>] --model <m>
|
|
18
|
+
*
|
|
19
|
+
* There is deliberately NO prompt and no `launchArgs`. A resume opens the window; talking to the
|
|
20
|
+
* citizen afterwards is `entwurf_v2`'s job on the socket this launch stands up. That split is
|
|
21
|
+
* what makes the resumed turn free: measured, a promptless resume left the transcript
|
|
22
|
+
* byte-identical (1666 → 1666) and started no model turn at all.
|
|
23
|
+
*
|
|
24
|
+
* The one-shot `legacy` variant (removed 2026-07-27) and the headless prefix (removed here) are
|
|
25
|
+
* not kept behind a variant flag for compatibility. An exported branch no product path takes is
|
|
26
|
+
* exactly what let this module's prose claim a live consumer for months.
|
|
27
|
+
*
|
|
28
|
+
* Provider/model identity is the caller's authority (`resolveResumeLaunchIdentity`) — this
|
|
29
|
+
* builder only LAYS OUT argv, it never resolves identity. `explicitExtensionArgs` is preserved
|
|
30
|
+
* verbatim: a recorded `provider=entwurf` resume needs the bridge re-injected or pi cannot
|
|
31
|
+
* resolve the provider (#29). Measured on that axis: the resolver emits
|
|
32
|
+
* `["-e", "<bridge>"]`, and the argv carries it exactly once, between `--entwurf-control` and
|
|
33
|
+
* `--session`.
|
|
34
|
+
*
|
|
35
|
+
* The runtime path itself is NOT here. Resolving `pi` on PATH and proving it launchable belongs
|
|
36
|
+
* to the mux lane (`mux-launch`), so this module emits the flags after the runtime and nothing
|
|
37
|
+
* more — which is also why it stays import-free and can be read by both tsconfigs.
|
|
27
38
|
*/
|
|
28
39
|
/**
|
|
29
|
-
* Build the `pi`
|
|
30
|
-
* child, prompt-as-turn); then the resident posture; then
|
|
31
|
-
* `[…ext args] --session <file> [--provider <p>] --model <m> <prompt>`.
|
|
40
|
+
* Build the `pi` flags for a VISIBLE resume, in the measured order.
|
|
32
41
|
*
|
|
33
42
|
* Invariants the gate pins:
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
43
|
+
* - `--entwurf-control` is FIRST: the resumed session must stand its control socket up, or
|
|
44
|
+
* the transcript comes back with no address and the citizen is still unreachable.
|
|
45
|
+
* - no `--mode`, no `-p`, no positional prompt — the window is interactive and no turn runs.
|
|
46
|
+
* - `explicitExtensionArgs` appears exactly once, after the control flag and before
|
|
47
|
+
* `--session`.
|
|
48
|
+
* - provider is emitted only when recorded; `--model <m>` is the tail.
|
|
38
49
|
*/
|
|
39
50
|
export function buildResumePiArgs(input) {
|
|
40
|
-
const args = ["--
|
|
41
|
-
// Resident citizen: stand the control socket up (A1) and keep extensions loaded. The
|
|
42
|
-
// keep-alive the removed one-shot launcher had to avoid is precisely the goal here —
|
|
43
|
-
// the resumed session must stay addressable. `--approve`/launchArgs ride along.
|
|
44
|
-
args.push("--entwurf-control");
|
|
45
|
-
args.push(...(input.launchArgs ?? []));
|
|
46
|
-
// Suffix — the fixed identity layout.
|
|
51
|
+
const args = ["--entwurf-control"];
|
|
47
52
|
args.push(...input.explicitExtensionArgs);
|
|
48
53
|
args.push("--session", input.sessionFile);
|
|
49
54
|
if (input.provider)
|
|
50
55
|
args.push("--provider", input.provider);
|
|
51
|
-
args.push("--model", input.model
|
|
56
|
+
args.push("--model", input.model);
|
|
52
57
|
return args;
|
|
53
58
|
}
|
|
@@ -17,20 +17,43 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Source-verified invariants folded in (Opus 실측 + GPT 보정 + Fable R1-R5, 2026-06-11):
|
|
19
19
|
* - F1: caller intent is DECLARED in the input, so the contract a caller
|
|
20
|
-
* receives is deterministic — never computed from liveness at call time.
|
|
21
|
-
*
|
|
20
|
+
* receives is deterministic — never computed from liveness at call time. One
|
|
21
|
+
* intent remains: `fire-and-forget` (the ack is the end of the contract). The
|
|
22
|
+
* second intent, `owned-outcome`, is GONE — see the visible-first note below.
|
|
23
|
+
*
|
|
24
|
+
* ── Visible-first subtraction (2026-08-06, GLG) ────────────────────────────
|
|
25
|
+
* `owned-outcome` and the `spawn-bg` transport are removed from this contract, not
|
|
26
|
+
* deprecated. The only resume this verb ever had launched a DETACHED, window-less
|
|
27
|
+
* `pi` child; under the visible-first operating rule a citizen the operator cannot
|
|
28
|
+
* see must not be started at all, and a reachable hidden path is worse than a
|
|
29
|
+
* temporary absence of the capability. So the subtraction is a hard cut: the intent
|
|
30
|
+
* leaves the enum, the transport leaves the table, and an old caller fails schema
|
|
31
|
+
* validation rather than meeting a polite reject string.
|
|
32
|
+
*
|
|
33
|
+
* What this leaves behind, deliberately: a DORMANT in-domain citizen is currently
|
|
34
|
+
* unreachable (`fire-and-forget` × dormant has always rejected). Visible same-id
|
|
35
|
+
* resume is a SEPARATE lifecycle capability — a distinct verb over the mux launch
|
|
36
|
+
* lane, not an intent on this delivery verb — and it is not implemented here. The
|
|
37
|
+
* leaves it will need (record-authoritative launch identity, the resume argv
|
|
38
|
+
* builder, the per-target lock, socket inspection) are preserved and still shipped.
|
|
39
|
+
*
|
|
40
|
+
* READ THE SCOPE EXACTLY. Background execution is not judged impossible and is not forbidden
|
|
41
|
+
* forever. It is retired NOW so that no hidden bypass exists while visible-first governance is
|
|
42
|
+
* being established. A future background lifecycle may be added back as an explicitly
|
|
43
|
+
* authorized, FIRST-CLASS capability with its own verb and its own receipts. What it must never
|
|
44
|
+
* be again is what it was here: an automatic fallback, or a hidden branch inside visible resume
|
|
45
|
+
* or `entwurf_v2`. The shape is what was removed, not the idea of a detached process.
|
|
22
46
|
* - R1: liveness is defined PER CAPABILITY DOMAIN. The control-socket domain
|
|
23
47
|
* currently contains backend `pi`; claude-code is self-fetch with no socket,
|
|
24
48
|
* so its socket liveness is `unsupported`, NOT folded into dead/indeterminate.
|
|
25
49
|
* This is transport capability, not identity rank: every target is first a
|
|
26
50
|
* record citizen. `unsupported` is a 4th FACT value, not a 4th table column.
|
|
27
51
|
* - R2: `target` is the garden-id of an EXISTING citizen. spawn-new is out of
|
|
28
|
-
* v2 scope —
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `dormant` target is "reject for now" (mailbox-wake lacks a reply-correlation
|
|
52
|
+
* v2 scope — fresh creation now has the separate `entwurf_fresh_call` surface,
|
|
53
|
+
* never a fallback inside this resolver. Absent/typo gid = `bad-target` (so F6
|
|
54
|
+
* "오타 gid가 신규 spawn 사고 막기" holds automatically).
|
|
55
|
+
* - N1/F3: an `indeterminate` target is never launched into. N2: `fire-and-forget`
|
|
56
|
+
* to a `dormant` target is "reject for now" (mailbox-wake lacks a reply-correlation
|
|
34
57
|
* id in the substrate; an additive extension later, not a permanent no).
|
|
35
58
|
* - Q2: every cell is a SINGLE verdict — no "default", no escape hatch (a
|
|
36
59
|
* "default reject" would re-admit the call-time nondeterminism F1 closes).
|
|
@@ -38,8 +61,7 @@
|
|
|
38
61
|
* is NOT a reject — the 0.10.0 meta-bridge mailbox delivers without liveness.
|
|
39
62
|
* `unsupported` is the "no liveness predicate" fact, not a delivery verdict; so
|
|
40
63
|
* ff+unsupported routes to the `meta-mailbox` transport, gated by a SEPARATE
|
|
41
|
-
* `mailboxDeliverable` fact (NOT a column of the
|
|
42
|
-
* owned-outcome+unsupported still rejects (self-fetch needs real liveness).
|
|
64
|
+
* `mailboxDeliverable` fact (NOT a column of the 3-cell table — Fable (i)).
|
|
43
65
|
*
|
|
44
66
|
* The decision table here is a constant; `check-entwurf-v2-contract` asserts it
|
|
45
67
|
* exhaustively + proves the "table cell ↔ receipt" round-trip. THAT round-trip
|
|
@@ -48,17 +70,25 @@
|
|
|
48
70
|
*/
|
|
49
71
|
// ── Caller-declared intent (F1) ────────────────────────────────────────────
|
|
50
72
|
// The outcome contract is an INPUT, not an inference. `fire-and-forget` = the
|
|
51
|
-
// RPC ack is the end of the contract (entwurf-control.ts:29-37).
|
|
52
|
-
//
|
|
53
|
-
|
|
73
|
+
// RPC ack is the end of the contract (entwurf-control.ts:29-37).
|
|
74
|
+
//
|
|
75
|
+
// This enum has ONE member on purpose. It kept a second — `owned-outcome`, whose
|
|
76
|
+
// only allow cell launched a hidden detached child — until the visible-first cut
|
|
77
|
+
// (header). A one-member enum is not a smell here: the axis is real (a caller
|
|
78
|
+
// still declares its outcome contract), and the value that is gone is gone from
|
|
79
|
+
// the wire, so an old caller is refused by schema validation instead of being
|
|
80
|
+
// quietly re-routed.
|
|
81
|
+
export const ENTWURF_INTENTS = ["fire-and-forget"];
|
|
54
82
|
// ── Liveness axes ──────────────────────────────────────────────────────────
|
|
55
83
|
// FactLiveness (R1/R3b) = what `entwurf_peers` exposes: the 3 socket-probe
|
|
56
84
|
// values PLUS `unsupported` (predicate undefined for this backend). Four values.
|
|
57
85
|
export const FACT_LIVENESSES = ["alive", "dead", "indeterminate", "unsupported"];
|
|
58
86
|
// DispatchLiveness = the in-domain routing axis the table is keyed on. The
|
|
59
|
-
// socket result maps: alive→live (send), dead→dormant (
|
|
60
|
-
// indeterminate→indeterminate (never
|
|
61
|
-
// handled by the domain guard before the table
|
|
87
|
+
// socket result maps: alive→live (send), dead→dormant (unreachable since the
|
|
88
|
+
// visible-first cut), indeterminate→indeterminate (never dispatched into).
|
|
89
|
+
// `unsupported` is NOT here — it is handled by the domain guard before the table
|
|
90
|
+
// is consulted. The axis keeps all three values because they are FACTS about a
|
|
91
|
+
// target; only the verdicts behind two of them changed.
|
|
62
92
|
export const DISPATCH_LIVENESSES = ["live", "dormant", "indeterminate"];
|
|
63
93
|
// ── Backend liveness domain (R1 + F4) ──────────────────────────────────────
|
|
64
94
|
// Backends whose SOCKET liveness predicate is DEFINED. The control-socket
|
|
@@ -110,17 +140,17 @@ export function dispatchLivenessOf(socket) {
|
|
|
110
140
|
// attempted. A post-dispatch "send-fail fallback" (transport failed after the
|
|
111
141
|
// verdict) is a SEPARATE axis (bucket B) and must NOT be merged into this enum.
|
|
112
142
|
export const ENTWURF_V2_REJECT_REASONS = [
|
|
113
|
-
|
|
143
|
+
// The name is inherited vocabulary and stays a frozen wire string: with the spawn
|
|
144
|
+
// transport gone there is nothing left to spawn, but the RULE it names is intact —
|
|
145
|
+
// an in-domain target whose probe was inconclusive is never dispatched into. Renaming
|
|
146
|
+
// a public reject string is its own contract cut, not a side effect of this one.
|
|
147
|
+
"indeterminate-no-spawn", // N1/F3: never dispatch into an indeterminate target
|
|
114
148
|
"dormant-fire-forget-unsupported", // N2: fire-and-forget to a dormant target — reject for now
|
|
115
|
-
"owned-live-no-autosend", // Q2/F1: owned-outcome to a live target is not an auto-send
|
|
116
|
-
"backend-liveness-unsupported", // R1: backend has no liveness predicate (e.g. claude-code) — owned-outcome only
|
|
117
149
|
"mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
|
|
118
|
-
"native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
|
|
119
|
-
"native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"untrusted-fail-fast", // 동결결정 5: controlled launch into an untrusted cwd
|
|
123
|
-
"record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface. Replaces the retired A1-narrow acceptance (and its post-probe `socket-only-no-resume-authority`).
|
|
150
|
+
"native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
|
|
151
|
+
"native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never coerced to dead.
|
|
152
|
+
"bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
|
|
153
|
+
"record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface.
|
|
124
154
|
"target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
|
|
125
155
|
"target-address-conflict", // F3: a quarantined citizen (garden-id-socket-conflict / symlinked socket) — the gid resolves to two different receivers (record vs socket), so dispatch refuses to pick. The ONLY in-band honest channel for a dispatch-level identity-split (the listing diagnostic channel is not visible to a v2 caller, who only gets a receipt). Pre-resolver, like bad-target/target-locked — NOT a RESOLVER_REJECT_REASONS member.
|
|
126
156
|
];
|
|
@@ -134,10 +164,11 @@ export const ENTWURF_V2_REJECT_REASONS = [
|
|
|
134
164
|
// in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
|
|
135
165
|
// backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
|
|
136
166
|
// observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
|
|
137
|
-
// the RESOLVER_REJECT_REASONS (
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
167
|
+
// the RESOLVER_REJECT_REASONS (3, post-probe) and the native-push set (2, also
|
|
168
|
+
// post-probe) — carries a non-null FactLiveness, as does every success. (The
|
|
169
|
+
// visible-first cut removed the one post-probe reject that was NOT a resolver
|
|
170
|
+
// member: `untrusted-fail-fast`, which only ever fired behind a resume verdict.)
|
|
171
|
+
// This null/non-null split is REASON-DEPENDENT, so the receipt
|
|
141
172
|
// schema (which allows null on every reject branch) cannot enforce it alone — the
|
|
142
173
|
// semantic fixture in `check-entwurf-v2-contract` does, via `isPreProbeReject` /
|
|
143
174
|
// `rejectObservedLivenessWellFormed` below (the SSOT 5b mints against).
|
|
@@ -155,33 +186,24 @@ export function isPreProbeReject(reason) {
|
|
|
155
186
|
* pure SSOT predicate so 5b mints against it and the gate proves it: a pre-probe
|
|
156
187
|
* reject MUST carry `null`; every other reject MUST carry a non-null FactLiveness.
|
|
157
188
|
* Catches the illegal `{ok:false, reason:"bad-target", observedLiveness:"indeterminate"}`
|
|
158
|
-
* (pre-probe with a stamped value) and `{ok:false, reason:"
|
|
189
|
+
* (pre-probe with a stamped value) and `{ok:false, reason:"dormant-fire-forget-unsupported",
|
|
159
190
|
* observedLiveness:null}` (post-probe with no value) — both reason-dependent, so
|
|
160
191
|
* unreachable by the schema's blanket `FactLiveness | null`.
|
|
161
|
-
*
|
|
162
|
-
* NOTE this predicate FREEZES the 1B ordering into the contract: classifying
|
|
163
|
-
* `untrusted-fail-fast` as post-probe (non-null required) encodes "preflight runs
|
|
164
|
-
* AFTER the probe". Moving preflight back ahead of the probe would make its
|
|
165
|
-
* observedLiveness un-measured (null) and reopen this predicate + the enum split.
|
|
166
192
|
*/
|
|
167
193
|
export function rejectObservedLivenessWellFormed(reason, observedLiveness) {
|
|
168
194
|
return isPreProbeReject(reason) ? observedLiveness === null : observedLiveness !== null;
|
|
169
195
|
}
|
|
170
|
-
// Reasons the RESOLVER emits — the in-domain
|
|
171
|
-
// unsupported domain-guard mini-table (
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// stage, not an earlier one. All three are pre-claimed in the enum so bucket B
|
|
179
|
-
// does not reopen it.
|
|
196
|
+
// Reasons the RESOLVER emits — the in-domain 3-cell table cells PLUS the
|
|
197
|
+
// unsupported domain-guard mini-table (mailbox-undeliverable for a fail-closed
|
|
198
|
+
// fire-and-forget). NOT just the table (the F-mailbox mini-table emits one of
|
|
199
|
+
// these), hence RESOLVER_ not TABLE_. The remaining taxonomy members are produced
|
|
200
|
+
// by stages BEFORE the resolver: `bad-target` (target resolution) and
|
|
201
|
+
// `target-locked` (lockfile). Both are pre-claimed in the enum so bucket B does
|
|
202
|
+
// not reopen it. There is no longer any AFTER-the-resolver reject: the one that
|
|
203
|
+
// existed, `untrusted-fail-fast`, fired only behind the removed resume verdict.
|
|
180
204
|
export const RESOLVER_REJECT_REASONS = [
|
|
181
205
|
"indeterminate-no-spawn",
|
|
182
206
|
"dormant-fire-forget-unsupported",
|
|
183
|
-
"owned-live-no-autosend",
|
|
184
|
-
"backend-liveness-unsupported",
|
|
185
207
|
"mailbox-undeliverable",
|
|
186
208
|
];
|
|
187
209
|
// ── Transport + verdict ────────────────────────────────────────────────────
|
|
@@ -193,16 +215,20 @@ export const RESOLVER_REJECT_REASONS = [
|
|
|
193
215
|
// send arm (ack-only), but it requires a live-probe (NATIVE_PUSH_DISPATCH_TABLE),
|
|
194
216
|
// where meta-mailbox is liveness-free. It is NOT a mailbox enqueue and NOT a pi socket
|
|
195
217
|
// send — it is its own rail.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
//
|
|
219
|
+
// This list contains DELIVERY outcomes only. A mux may launch a live runtime, but
|
|
220
|
+
// its session handle is neither a receipt transport nor address authority. Once
|
|
221
|
+
// that runtime becomes a record-backed citizen, delivery still uses one of the
|
|
222
|
+
// rails below. `spawn-bg` — the detached, window-less resume child — was removed
|
|
223
|
+
// with `owned-outcome` (header): every remaining transport delivers to a citizen
|
|
224
|
+
// that is already running, so this verb no longer starts a process at all.
|
|
225
|
+
export const ENTWURF_V2_TRANSPORTS = ["control-socket", "meta-mailbox", "native-push"];
|
|
203
226
|
// Allow-branch facets (exported so the schema↔types gate asserts every enum).
|
|
204
|
-
|
|
205
|
-
|
|
227
|
+
// Both are single-valued since the cut: the only `resume`/`owned` verdict was the
|
|
228
|
+
// spawn-bg cell. They stay as enums because they are the receipt's own vocabulary
|
|
229
|
+
// and a future visible-resume verb would speak them again.
|
|
230
|
+
export const ENTWURF_V2_ACTIONS = ["send"];
|
|
231
|
+
export const ENTWURF_V2_OWNERSHIPS = ["ack-only"];
|
|
206
232
|
// Delivery mode of the message to the target (how it is injected) — steer =
|
|
207
233
|
// interrupt the current turn, follow_up = queue after it. A SEPARATE axis from
|
|
208
234
|
// both the intent/ownership axis (F1) and the liveness-routing axis. The removed
|
|
@@ -210,28 +236,26 @@ export const ENTWURF_V2_OWNERSHIPS = ["ack-only", "owned"];
|
|
|
210
236
|
// inherited vocabulary, not a second live delivery verb.
|
|
211
237
|
export const ENTWURF_V2_MODES = ["steer", "follow_up"];
|
|
212
238
|
// ── The FROZEN decision table ──────────────────────────────────────────────
|
|
213
|
-
// intent × dispatch-liveness → exactly one verdict (Q2).
|
|
214
|
-
// exactly
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
239
|
+
// intent × dispatch-liveness → exactly one verdict (Q2). THREE cells since the
|
|
240
|
+
// visible-first cut, of which exactly ONE allows: fire-and-forget+live = send.
|
|
241
|
+
// The other two reject. The reject cells are honest "지금은 없음" locks (N2).
|
|
242
|
+
// They were written while the legacy 3-verb surface still covered those flows;
|
|
243
|
+
// that surface is GONE, so a reject cell is a real absence with no fallback verb
|
|
244
|
+
// behind it — reopening one takes a new contract, never a quiet re-admission.
|
|
245
|
+
//
|
|
246
|
+
// The `dormant` cell is the one that now carries the whole cost of the cut: an
|
|
247
|
+
// in-domain citizen that is not running cannot be reached by ANY intent. That is
|
|
248
|
+
// the intended fail-closed state, not an oversight — the resume that used to
|
|
249
|
+
// answer here was hidden, and a visible replacement is a separate capability.
|
|
220
250
|
export const DISPATCH_TABLE = {
|
|
221
251
|
"fire-and-forget": {
|
|
222
252
|
live: { action: "send", transport: "control-socket", ownership: "ack-only" },
|
|
223
253
|
dormant: { action: "reject", reason: "dormant-fire-forget-unsupported" },
|
|
224
254
|
indeterminate: { action: "reject", reason: "indeterminate-no-spawn" },
|
|
225
255
|
},
|
|
226
|
-
"owned-outcome": {
|
|
227
|
-
// wants_reply is etiquette, not ownership — owned+live never auto-sends (Q2/F1).
|
|
228
|
-
live: { action: "reject", reason: "owned-live-no-autosend" },
|
|
229
|
-
dormant: { action: "resume", transport: "spawn-bg", ownership: "owned" },
|
|
230
|
-
indeterminate: { action: "reject", reason: "indeterminate-no-spawn" },
|
|
231
|
-
},
|
|
232
256
|
};
|
|
233
257
|
// ── The unsupported-backend mailbox mini-table (F-mailbox) ─────────────────
|
|
234
|
-
// SEPARATE from the in-domain
|
|
258
|
+
// SEPARATE from the in-domain 3-cell DISPATCH_TABLE (Fable (i)): a backend with no
|
|
235
259
|
// pi-socket liveness predicate never enters the liveness-keyed table. Instead the
|
|
236
260
|
// domain guard routes it here, keyed on intent alone. Reaches here: claude-code
|
|
237
261
|
// (self-fetch mailbox) and codex (no adapter yet). Does NOT reach here: antigravity —
|
|
@@ -244,17 +268,20 @@ export const DISPATCH_TABLE = {
|
|
|
244
268
|
// fact is false (fail-closed). The ack is enqueue+doorbell, NOT read, and
|
|
245
269
|
// observedLiveness stays `unsupported` — the receipt's `meta-mailbox` transport
|
|
246
270
|
// is what says "this went to the mailbox".
|
|
247
|
-
// - owned-outcome has no real liveness to own on a self-fetch backend → reject.
|
|
248
271
|
//
|
|
249
272
|
// N2 asymmetry (명문화 — without this the two tables read as contradictory):
|
|
250
273
|
// fire-and-forget+dormant-PI = reject vs fire-and-forget+unsupported-CITIZEN = mailbox.
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
274
|
+
// The axis is NOT which backend a citizen runs, and this cell grants no backend a
|
|
275
|
+
// privilege. It is one question asked of both: does an ACTIVE RECEIVER actually drain
|
|
276
|
+
// the mailbox this message would land in? A self-fetch citizen reaches this allow cell
|
|
277
|
+
// only through the separate `mailboxDeliverable` fact, which IS that receiver check —
|
|
278
|
+
// when nobody drains, this very cell rejects as `mailbox-undeliverable`. An in-domain
|
|
279
|
+
// `dormant` pi has no drainer at all: its mailbox reader was the running session, and
|
|
280
|
+
// the session is confirmed not running, so an enqueue would be a silent pileup no one
|
|
281
|
+
// ever reads. Same rule, two answers. (It reads as a backend split only if you skip
|
|
282
|
+
// the deliverability fact — which is exactly what makes claude-code look privileged.)
|
|
255
283
|
export const UNSUPPORTED_DISPATCH_TABLE = {
|
|
256
284
|
"fire-and-forget": { action: "send", transport: "meta-mailbox", ownership: "ack-only" },
|
|
257
|
-
"owned-outcome": { action: "reject", reason: "backend-liveness-unsupported" },
|
|
258
285
|
};
|
|
259
286
|
/**
|
|
260
287
|
* The ONLY sanctioned way to mint a reject receipt (?6 enforcement). A pure
|
|
@@ -263,9 +290,9 @@ export const UNSUPPORTED_DISPATCH_TABLE = {
|
|
|
263
290
|
* observedLiveness:"indeterminate"}`, which the blanket `FactLiveness | null`
|
|
264
291
|
* schema accepts. This constructor THROWS on a well-formedness violation, so
|
|
265
292
|
* every reject path (resolveDispatch's own mints below + the 5b stages that
|
|
266
|
-
* produce bad-target / target-locked / target-address-conflict /
|
|
267
|
-
*
|
|
268
|
-
*
|
|
293
|
+
* produce bad-target / target-locked / target-address-conflict / record-less-socket)
|
|
294
|
+
* routes through one chokepoint and the bypass surface is zero. 5b MUST build
|
|
295
|
+
* rejects with this, never by object literal.
|
|
269
296
|
*/
|
|
270
297
|
export function makeRejectReceipt(reason, observedLiveness) {
|
|
271
298
|
if (!rejectObservedLivenessWellFormed(reason, observedLiveness)) {
|
|
@@ -277,11 +304,11 @@ export function makeRejectReceipt(reason, observedLiveness) {
|
|
|
277
304
|
* PURE dispatch decision over already-resolved facts. Before reaching here the
|
|
278
305
|
* caller has resolved the target (→ `bad-target` if no existing citizen) and, for
|
|
279
306
|
* an in-domain backend, acquired the per-gid lock (→ `target-locked`) and probed
|
|
280
|
-
* liveness UNDER that lock. This function only decides the liveness-routed
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
307
|
+
* liveness UNDER that lock. This function only decides the liveness-routed verdict.
|
|
308
|
+
* There is NO trust preflight anywhere on this path: it existed solely to guard the
|
|
309
|
+
* resume verdict that launched a child into a target cwd, and that verdict is gone.
|
|
310
|
+
* Do NOT reintroduce a global pre-resolver preflight — that re-breaks F-mailbox,
|
|
311
|
+
* and this verb no longer launches anything into any cwd.
|
|
285
312
|
*
|
|
286
313
|
* Two facts in: `liveness` (the 4-value FactLiveness) and `mailboxDeliverable`
|
|
287
314
|
* (F-mailbox — a SEPARATE axis from liveness, NOT a column of either table, NOT
|
|
@@ -293,8 +320,8 @@ export function makeRejectReceipt(reason, observedLiveness) {
|
|
|
293
320
|
* backend the liveness-routed table is authoritative and the flag is ignored.
|
|
294
321
|
*
|
|
295
322
|
* R1 domain guard runs first: an `unsupported` liveness is routed through the
|
|
296
|
-
* UNSUPPORTED_DISPATCH_TABLE (mailbox mini-table), never the
|
|
297
|
-
* No
|
|
323
|
+
* UNSUPPORTED_DISPATCH_TABLE (mailbox mini-table), never the 3-cell table.
|
|
324
|
+
* No send, no I/O — step 5 executes the chosen transport.
|
|
298
325
|
*/
|
|
299
326
|
export function resolveDispatch(intent, liveness, mailboxDeliverable) {
|
|
300
327
|
if (liveness === "unsupported") {
|
|
@@ -339,20 +366,16 @@ export function resolveDispatch(intent, liveness, mailboxDeliverable) {
|
|
|
339
366
|
// fire-and-forget × alive → native-push send (the ONE allow cell)
|
|
340
367
|
// fire-and-forget × dead → reject native-push-target-dead
|
|
341
368
|
// fire-and-forget × indeterminate → reject native-push-probe-indeterminate
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
369
|
+
//
|
|
370
|
+
// The `owned-outcome` row is gone with the intent (header). It had rejected on every
|
|
371
|
+
// liveness with `native-push-no-resume-authority` — a real fact about this rail that
|
|
372
|
+
// simply has no caller left to tell, since no caller can declare that intent.
|
|
345
373
|
export const NATIVE_PUSH_DISPATCH_TABLE = {
|
|
346
374
|
"fire-and-forget": {
|
|
347
375
|
alive: { action: "send", transport: "native-push", ownership: "ack-only" },
|
|
348
376
|
dead: { action: "reject", reason: "native-push-target-dead" },
|
|
349
377
|
indeterminate: { action: "reject", reason: "native-push-probe-indeterminate" },
|
|
350
378
|
},
|
|
351
|
-
"owned-outcome": {
|
|
352
|
-
alive: { action: "reject", reason: "native-push-no-resume-authority" },
|
|
353
|
-
dead: { action: "reject", reason: "native-push-no-resume-authority" },
|
|
354
|
-
indeterminate: { action: "reject", reason: "native-push-no-resume-authority" },
|
|
355
|
-
},
|
|
356
379
|
};
|
|
357
380
|
// The reasons the native-push resolver emits — a THIRD post-probe reject set, parallel
|
|
358
381
|
// to RESOLVER_REJECT_REASONS (pi/mailbox). All post-probe: resolveNativePushDispatch
|
|
@@ -361,7 +384,6 @@ export const NATIVE_PUSH_DISPATCH_TABLE = {
|
|
|
361
384
|
export const NATIVE_PUSH_REJECT_REASONS = [
|
|
362
385
|
"native-push-target-dead",
|
|
363
386
|
"native-push-probe-indeterminate",
|
|
364
|
-
"native-push-no-resume-authority",
|
|
365
387
|
];
|
|
366
388
|
/**
|
|
367
389
|
* PURE native-push dispatch decision (봉인 4). Given the caller intent and the adapter
|