@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.
Files changed (152) hide show
  1. package/AGENTS.md +48 -15
  2. package/BASELINE.md +3 -3
  3. package/CHANGELOG.md +45 -0
  4. package/CONTRIBUTING.md +13 -9
  5. package/DELIVERY.md +7 -6
  6. package/README.md +27 -27
  7. package/VERIFY.md +22 -14
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +9 -4
  12. package/docs/external-mcp-host.md +4 -5
  13. package/docs/setup-clean-host.md +8 -7
  14. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +148 -28
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  37. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  38. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  39. package/mcp/entwurf-bridge/src/index.ts +164 -28
  40. package/mcp/entwurf-bridge/start.sh +2 -2
  41. package/mcp/entwurf-bridge/test.sh +23 -9
  42. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  43. package/package.json +22 -11
  44. package/pi-extensions/entwurf-control.ts +218 -40
  45. package/pi-extensions/lib/acp/backend.ts +71 -12
  46. package/pi-extensions/lib/acp/overlay.ts +8 -6
  47. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  48. package/pi-extensions/lib/entwurf-core.ts +15 -15
  49. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  50. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  51. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  52. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  53. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  54. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  55. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  56. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  57. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  58. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  59. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  60. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  61. package/pi-extensions/lib/meta-session.ts +93 -5
  62. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  63. package/pi-extensions/lib/mux-launch.ts +267 -0
  64. package/pi-extensions/lib/mux-placement.ts +387 -0
  65. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  66. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  67. package/pi-extensions/lib/session-id.js +8 -5
  68. package/pi-extensions/lib/socket-discovery.ts +3 -3
  69. package/run.sh +471 -201
  70. package/scripts/agy-bridge-config.py +5 -1
  71. package/scripts/check-acp-backend-preflight.ts +1 -1
  72. package/scripts/check-acp-overlay.ts +13 -3
  73. package/scripts/check-acp-stream-hooks.ts +504 -0
  74. package/scripts/check-elapsed.sh +25 -0
  75. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  76. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  77. package/scripts/check-entwurf-control-rpc.ts +4 -3
  78. package/scripts/check-entwurf-resume-args.ts +72 -70
  79. package/scripts/check-entwurf-session-identity.ts +14 -10
  80. package/scripts/check-entwurf-v2-contract.ts +34 -59
  81. package/scripts/check-entwurf-v2-decider.ts +17 -177
  82. package/scripts/check-entwurf-v2-lock.ts +5 -2
  83. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  84. package/scripts/check-entwurf-v2-production.ts +2 -91
  85. package/scripts/check-entwurf-v2-release.ts +10 -105
  86. package/scripts/check-entwurf-v2-runner.ts +4 -85
  87. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  88. package/scripts/check-entwurf-v2-send.ts +0 -28
  89. package/scripts/check-entwurf-v2-surface.ts +157 -128
  90. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  91. package/scripts/check-fresh-cut-gate.sh +1 -1
  92. package/scripts/check-gate-qualification.ts +98 -7
  93. package/scripts/check-install-container.sh +10 -2
  94. package/scripts/check-install-surface.ts +1 -1
  95. package/scripts/check-keyset-overlap.py +1 -1
  96. package/scripts/check-meta-facts.ts +249 -0
  97. package/scripts/check-meta-identity-consumers.ts +1 -1
  98. package/scripts/check-meta-session.ts +169 -0
  99. package/scripts/check-mux-launch-tmux.ts +316 -0
  100. package/scripts/check-mux-launch.ts +288 -0
  101. package/scripts/check-mux-launcher-fence.ts +264 -0
  102. package/scripts/check-mux-parent-artifact.ts +195 -0
  103. package/scripts/check-mux-placement-tmux.ts +322 -0
  104. package/scripts/check-mux-placement.ts +323 -0
  105. package/scripts/check-mux-resume-call.ts +282 -0
  106. package/scripts/check-probe-cli-shim.ts +25 -22
  107. package/scripts/check-probe-ordering.ts +84 -76
  108. package/scripts/check-release-gate-outcomes.ts +127 -7
  109. package/scripts/check-resume-launch-identity.ts +244 -0
  110. package/scripts/check-socket-discovery.ts +1 -1
  111. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  112. package/scripts/inventory-verification-surface.ts +349 -0
  113. package/scripts/lib/claude-launcher-fence.ts +322 -0
  114. package/scripts/lib/mutation-qualify.ts +109 -3
  115. package/scripts/meta-bridge-doctor.sh +6 -8
  116. package/scripts/meta-facts.ts +60 -0
  117. package/scripts/mutants/acp-overlay.json +17 -0
  118. package/scripts/mutants/acp-stream-hooks.json +158 -0
  119. package/scripts/mutants/bridge-boot-resume.json +45 -0
  120. package/scripts/mutants/meta-facts.json +50 -0
  121. package/scripts/mutants/meta-identity.json +36 -0
  122. package/scripts/mutants/mux-boundary.json +196 -0
  123. package/scripts/mutants/mux-fresh-call.json +185 -0
  124. package/scripts/mutants/mux-launcher-fence.json +123 -0
  125. package/scripts/mutants/mux-parent-artifact.json +39 -0
  126. package/scripts/mutants/mux-resume-call.json +148 -0
  127. package/scripts/mutants/probe-ordering.json +0 -1037
  128. package/scripts/mutants/release-gate.json +35 -0
  129. package/scripts/mutants/resume-args.json +76 -0
  130. package/scripts/mutants/resume-launch-identity.json +96 -0
  131. package/scripts/mutants/v2-surface.json +58 -18
  132. package/scripts/mutants/v2-visible-resume.json +215 -0
  133. package/scripts/new-session-id.ts +9 -4
  134. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  135. package/scripts/smoke-agy-native-push-live.ts +6 -17
  136. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  137. package/scripts/smoke-meta-honesty.sh +1 -1
  138. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  139. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  140. package/scripts/smoke-pi-attach.ts +1 -1
  141. package/scripts/smoke-user-scope-citizen.sh +1 -1
  142. package/scripts/tsconfig.json +1 -0
  143. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  144. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  145. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  146. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  147. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  148. package/scripts/check-acp-sdk-surface.ts +0 -275
  149. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  150. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  151. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  152. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -467
@@ -1,41 +1,43 @@
1
1
  /**
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). One place owns the
4
- * `--no-extensions` / `--entwurf-control` decision so the launch shape cannot drift.
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.
5
4
  *
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.
5
+ * ── What changed, and why the old shape was not reusable ──
14
6
  *
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.
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`.
17
14
  *
18
- * Provider/model identity is the caller's existing authority (readSessionIdentity /
19
- * getEntwurfExplicitExtensions) — this builder only LAYS OUT argv, it never resolves
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.)
15
+ * So the shipped posture is now one shape, and it is the only one:
24
16
  *
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.
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
- /** 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";
35
-
36
40
  export interface ResumePiArgsInput {
37
- /** v2-control = resident citizen (`--entwurf-control`, extensions loaded). */
38
- variant: ResumeArgsVariant;
39
41
  /** ABSOLUTE path of the session JSONL to resume — pi's `--session <path>` (#50 C2).
40
42
  * It replaced `--session-id <gardenId>`, which did two jobs that are no longer the
41
43
  * same string: it named the session to reopen AND fixed the control-socket key. The
@@ -52,37 +54,24 @@ export interface ResumePiArgsInput {
52
54
  provider: string | null | undefined;
53
55
  /** The resolved launch model (caller applies `modelOverride ?? resumeModel`). */
54
56
  model: string;
55
- /** The resume prompt — the final positional, run as the model turn under `-p`. */
56
- prompt: string;
57
- /** The decider's `plan.launchArgs` (`["--approve"]` or `[]`). */
58
- launchArgs?: readonly string[];
59
57
  }
60
58
 
61
59
  /**
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>`.
60
+ * Build the `pi` flags for a VISIBLE resume, in the measured order.
65
61
  *
66
62
  * Invariants the gate pins:
67
- * - v2-control carries `--entwurf-control` and NO `--no-extensions`, plus `-p` + prompt.
68
- * - `explicitExtensionArgs` appears exactly once.
69
- * - `launchArgs` is included before the suffix.
70
- * - provider/model/prompt identity layout is fixed.
63
+ * - `--entwurf-control` is FIRST: the resumed session must stand its control socket up, or
64
+ * the transcript comes back with no address and the citizen is still unreachable.
65
+ * - no `--mode`, no `-p`, no positional prompt — the window is interactive and no turn runs.
66
+ * - `explicitExtensionArgs` appears exactly once, after the control flag and before
67
+ * `--session`.
68
+ * - provider is emitted only when recorded; `--model <m>` is the tail.
71
69
  */
72
70
  export function buildResumePiArgs(input: ResumePiArgsInput): string[] {
73
- const args: string[] = ["--mode", "json", "-p"];
74
-
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 ?? []));
80
-
81
- // Suffix — the fixed identity layout.
71
+ const args: string[] = ["--entwurf-control"];
82
72
  args.push(...input.explicitExtensionArgs);
83
73
  args.push("--session", input.sessionFile);
84
74
  if (input.provider) args.push("--provider", input.provider);
85
- args.push("--model", input.model, input.prompt);
86
-
75
+ args.push("--model", input.model);
87
76
  return args;
88
77
  }
@@ -46,7 +46,7 @@ export const EntwurfV2InputSchema = Type.Object(
46
46
  }),
47
47
  intent: StringEnum(ENTWURF_INTENTS, {
48
48
  description:
49
- "caller's declared outcome contract (F1): fire-and-forget = ack only, owned-outcome = caller owns completion.",
49
+ "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
50
50
  }),
51
51
  mode: Type.Optional(
52
52
  StringEnum(ENTWURF_V2_MODES, {
@@ -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
- * `owned-outcome` (caller owns completion) ≠ `fire-and-forget` (ack only).
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 — 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).
32
- * - N1/F3: an `indeterminate` target never spawns. N2: `fire-and-forget` to a
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 6-cell table — Fable (i)).
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
@@ -51,9 +73,15 @@ import type { SocketLiveness } from "./socket-probe.ts";
51
73
 
52
74
  // ── Caller-declared intent (F1) ────────────────────────────────────────────
53
75
  // The outcome contract is an INPUT, not an inference. `fire-and-forget` = the
54
- // RPC ack is the end of the contract (entwurf-control.ts:29-37). `owned-outcome`
55
- // = the caller owns the dispatched session's completion.
56
- export const ENTWURF_INTENTS = ["fire-and-forget", "owned-outcome"] as const;
76
+ // RPC ack is the end of the contract (entwurf-control.ts:29-37).
77
+ //
78
+ // This enum has ONE member on purpose. It kept a second — `owned-outcome`, whose
79
+ // only allow cell launched a hidden detached child — until the visible-first cut
80
+ // (header). A one-member enum is not a smell here: the axis is real (a caller
81
+ // still declares its outcome contract), and the value that is gone is gone from
82
+ // the wire, so an old caller is refused by schema validation instead of being
83
+ // quietly re-routed.
84
+ export const ENTWURF_INTENTS = ["fire-and-forget"] as const;
57
85
  export type EntwurfIntent = (typeof ENTWURF_INTENTS)[number];
58
86
 
59
87
  // ── Liveness axes ──────────────────────────────────────────────────────────
@@ -63,9 +91,11 @@ export const FACT_LIVENESSES = ["alive", "dead", "indeterminate", "unsupported"]
63
91
  export type FactLiveness = SocketLiveness | "unsupported";
64
92
 
65
93
  // DispatchLiveness = the in-domain routing axis the table is keyed on. The
66
- // socket result maps: alive→live (send), dead→dormant (resume from disk),
67
- // indeterminate→indeterminate (never spawn). `unsupported` is NOT here — it is
68
- // handled by the domain guard before the table is consulted.
94
+ // socket result maps: alive→live (send), dead→dormant (unreachable since the
95
+ // visible-first cut), indeterminate→indeterminate (never dispatched into).
96
+ // `unsupported` is NOT here — it is handled by the domain guard before the table
97
+ // is consulted. The axis keeps all three values because they are FACTS about a
98
+ // target; only the verdicts behind two of them changed.
69
99
  export const DISPATCH_LIVENESSES = ["live", "dormant", "indeterminate"] as const;
70
100
  export type DispatchLiveness = (typeof DISPATCH_LIVENESSES)[number];
71
101
 
@@ -135,17 +165,17 @@ export function dispatchLivenessOf(socket: SocketLiveness): DispatchLiveness {
135
165
  // attempted. A post-dispatch "send-fail fallback" (transport failed after the
136
166
  // verdict) is a SEPARATE axis (bucket B) and must NOT be merged into this enum.
137
167
  export const ENTWURF_V2_REJECT_REASONS = [
138
- "indeterminate-no-spawn", // N1/F3: never spawn an indeterminate target
168
+ // The name is inherited vocabulary and stays a frozen wire string: with the spawn
169
+ // transport gone there is nothing left to spawn, but the RULE it names is intact —
170
+ // an in-domain target whose probe was inconclusive is never dispatched into. Renaming
171
+ // a public reject string is its own contract cut, not a side effect of this one.
172
+ "indeterminate-no-spawn", // N1/F3: never dispatch into an indeterminate target
139
173
  "dormant-fire-forget-unsupported", // N2: fire-and-forget to a dormant target — reject for now
140
- "owned-live-no-autosend", // Q2/F1: owned-outcome to a live target is not an auto-send
141
- "backend-liveness-unsupported", // R1: backend has no liveness predicate (e.g. claude-code) — owned-outcome only
142
174
  "mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
143
- "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. NOT `backend-liveness-unsupported` — a native-push backend IS measured, so that name would be a lie.
144
- "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 spawns, never coerced to dead.
145
- "native-push-no-resume-authority", // 봉인 1: owned-outcome to a native-push target (any liveness — single, state-independent). A native-push backend has no resume/spawn authority (there is no pi-child to own), so the caller cannot own its completion; use fire-and-forget. Post-probe; observedLiveness = the measured value.
146
- "bad-target", // R2: absent/typo garden-id (no existing citizen); spawn-new out of v2 scope
147
- "untrusted-fail-fast", // 동결결정 5: controlled launch into an untrusted cwd
148
- "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`).
175
+ "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.
176
+ "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.
177
+ "bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
178
+ "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.
149
179
  "target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
150
180
  "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.
151
181
  ] as const;
@@ -161,10 +191,11 @@ export type EntwurfV2RejectReason = (typeof ENTWURF_V2_REJECT_REASONS)[number];
161
191
  // in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
162
192
  // backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
163
193
  // observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
164
- // the RESOLVER_REJECT_REASONS (5, post-probe) plus `untrusted-fail-fast` (1B: it
165
- // now runs AFTER the lock+probe, only on a resume verdict, so its observedLiveness
166
- // is the honest measured `dormant`) carries a non-null FactLiveness, as does
167
- // every success. This null/non-null split is REASON-DEPENDENT, so the receipt
194
+ // the RESOLVER_REJECT_REASONS (3, post-probe) and the native-push set (2, also
195
+ // post-probe) carries a non-null FactLiveness, as does every success. (The
196
+ // visible-first cut removed the one post-probe reject that was NOT a resolver
197
+ // member: `untrusted-fail-fast`, which only ever fired behind a resume verdict.)
198
+ // This null/non-null split is REASON-DEPENDENT, so the receipt
168
199
  // schema (which allows null on every reject branch) cannot enforce it alone — the
169
200
  // semantic fixture in `check-entwurf-v2-contract` does, via `isPreProbeReject` /
170
201
  // `rejectObservedLivenessWellFormed` below (the SSOT 5b mints against).
@@ -185,14 +216,9 @@ export function isPreProbeReject(reason: EntwurfV2RejectReason): reason is PrePr
185
216
  * pure SSOT predicate so 5b mints against it and the gate proves it: a pre-probe
186
217
  * reject MUST carry `null`; every other reject MUST carry a non-null FactLiveness.
187
218
  * Catches the illegal `{ok:false, reason:"bad-target", observedLiveness:"indeterminate"}`
188
- * (pre-probe with a stamped value) and `{ok:false, reason:"owned-live-no-autosend",
219
+ * (pre-probe with a stamped value) and `{ok:false, reason:"dormant-fire-forget-unsupported",
189
220
  * observedLiveness:null}` (post-probe with no value) — both reason-dependent, so
190
221
  * unreachable by the schema's blanket `FactLiveness | null`.
191
- *
192
- * NOTE this predicate FREEZES the 1B ordering into the contract: classifying
193
- * `untrusted-fail-fast` as post-probe (non-null required) encodes "preflight runs
194
- * AFTER the probe". Moving preflight back ahead of the probe would make its
195
- * observedLiveness un-measured (null) and reopen this predicate + the enum split.
196
222
  */
197
223
  export function rejectObservedLivenessWellFormed(
198
224
  reason: EntwurfV2RejectReason,
@@ -201,21 +227,17 @@ export function rejectObservedLivenessWellFormed(
201
227
  return isPreProbeReject(reason) ? observedLiveness === null : observedLiveness !== null;
202
228
  }
203
229
 
204
- // Reasons the RESOLVER emits — the in-domain 6-cell table cells PLUS the
205
- // unsupported domain-guard mini-table (backend-liveness-unsupported for
206
- // owned-outcome, mailbox-undeliverable for a fail-closed fire-and-forget). NOT
207
- // just the 6-cell table (the F-mailbox mini-table emits two of these), hence
208
- // RESOLVER_ not TABLE_. The remaining taxonomy members are produced by stages
209
- // OTHER than the resolver: `bad-target` (target resolution) and `target-locked`
210
- // (lockfile) run BEFORE the resolver, while `untrusted-fail-fast` is decided
211
- // AFTER it preflight runs only behind a resume verdict (1B), so it is a LATER
212
- // stage, not an earlier one. All three are pre-claimed in the enum so bucket B
213
- // does not reopen it.
230
+ // Reasons the RESOLVER emits — the in-domain 3-cell table cells PLUS the
231
+ // unsupported domain-guard mini-table (mailbox-undeliverable for a fail-closed
232
+ // fire-and-forget). NOT just the table (the F-mailbox mini-table emits one of
233
+ // these), hence RESOLVER_ not TABLE_. The remaining taxonomy members are produced
234
+ // by stages BEFORE the resolver: `bad-target` (target resolution) and
235
+ // `target-locked` (lockfile). Both are pre-claimed in the enum so bucket B does
236
+ // not reopen it. There is no longer any AFTER-the-resolver reject: the one that
237
+ // existed, `untrusted-fail-fast`, fired only behind the removed resume verdict.
214
238
  export const RESOLVER_REJECT_REASONS = [
215
239
  "indeterminate-no-spawn",
216
240
  "dormant-fire-forget-unsupported",
217
- "owned-live-no-autosend",
218
- "backend-liveness-unsupported",
219
241
  "mailbox-undeliverable",
220
242
  ] as const satisfies readonly EntwurfV2RejectReason[];
221
243
 
@@ -228,18 +250,22 @@ export const RESOLVER_REJECT_REASONS = [
228
250
  // send arm (ack-only), but it requires a live-probe (NATIVE_PUSH_DISPATCH_TABLE),
229
251
  // where meta-mailbox is liveness-free. It is NOT a mailbox enqueue and NOT a pi socket
230
252
  // send — it is its own rail.
231
- export const ENTWURF_V2_TRANSPORTS = [
232
- "control-socket",
233
- "spawn-bg",
234
- "tmux-live",
235
- "meta-mailbox",
236
- "native-push",
237
- ] as const;
253
+ //
254
+ // This list contains DELIVERY outcomes only. A mux may launch a live runtime, but
255
+ // its session handle is neither a receipt transport nor address authority. Once
256
+ // that runtime becomes a record-backed citizen, delivery still uses one of the
257
+ // rails below. `spawn-bg` — the detached, window-less resume child — was removed
258
+ // with `owned-outcome` (header): every remaining transport delivers to a citizen
259
+ // that is already running, so this verb no longer starts a process at all.
260
+ export const ENTWURF_V2_TRANSPORTS = ["control-socket", "meta-mailbox", "native-push"] as const;
238
261
  export type EntwurfV2Transport = (typeof ENTWURF_V2_TRANSPORTS)[number];
239
262
 
240
263
  // Allow-branch facets (exported so the schema↔types gate asserts every enum).
241
- export const ENTWURF_V2_ACTIONS = ["send", "resume"] as const;
242
- export const ENTWURF_V2_OWNERSHIPS = ["ack-only", "owned"] as const;
264
+ // Both are single-valued since the cut: the only `resume`/`owned` verdict was the
265
+ // spawn-bg cell. They stay as enums because they are the receipt's own vocabulary
266
+ // and a future visible-resume verb would speak them again.
267
+ export const ENTWURF_V2_ACTIONS = ["send"] as const;
268
+ export const ENTWURF_V2_OWNERSHIPS = ["ack-only"] as const;
243
269
  // Delivery mode of the message to the target (how it is injected) — steer =
244
270
  // interrupt the current turn, follow_up = queue after it. A SEPARATE axis from
245
271
  // both the intent/ownership axis (F1) and the liveness-routing axis. The removed
@@ -249,33 +275,30 @@ export const ENTWURF_V2_MODES = ["steer", "follow_up"] as const;
249
275
 
250
276
  export type DispatchVerdict =
251
277
  | { action: "send"; transport: "control-socket" | "meta-mailbox" | "native-push"; ownership: "ack-only" }
252
- | { action: "resume"; transport: "spawn-bg" | "tmux-live"; ownership: "owned" }
253
278
  | { action: "reject"; reason: EntwurfV2RejectReason };
254
279
 
255
280
  // ── The FROZEN decision table ──────────────────────────────────────────────
256
- // intent × dispatch-liveness → exactly one verdict (Q2). v2-initial ALLOWS
257
- // exactly two cells (fire-and-forget+live = send; owned-outcome+dormant =
258
- // resume); the other four reject. The reject cells are honest "지금은 없음"
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.
281
+ // intent × dispatch-liveness → exactly one verdict (Q2). THREE cells since the
282
+ // visible-first cut, of which exactly ONE allows: fire-and-forget+live = send.
283
+ // The other two reject. The reject cells are honest "지금은 없음" locks (N2).
284
+ // They were written while the legacy 3-verb surface still covered those flows;
285
+ // that surface is GONE, so a reject cell is a real absence with no fallback verb
286
+ // behind it — reopening one takes a new contract, never a quiet re-admission.
287
+ //
288
+ // The `dormant` cell is the one that now carries the whole cost of the cut: an
289
+ // in-domain citizen that is not running cannot be reached by ANY intent. That is
290
+ // the intended fail-closed state, not an oversight — the resume that used to
291
+ // answer here was hidden, and a visible replacement is a separate capability.
263
292
  export const DISPATCH_TABLE: Record<EntwurfIntent, Record<DispatchLiveness, DispatchVerdict>> = {
264
293
  "fire-and-forget": {
265
294
  live: { action: "send", transport: "control-socket", ownership: "ack-only" },
266
295
  dormant: { action: "reject", reason: "dormant-fire-forget-unsupported" },
267
296
  indeterminate: { action: "reject", reason: "indeterminate-no-spawn" },
268
297
  },
269
- "owned-outcome": {
270
- // wants_reply is etiquette, not ownership — owned+live never auto-sends (Q2/F1).
271
- live: { action: "reject", reason: "owned-live-no-autosend" },
272
- dormant: { action: "resume", transport: "spawn-bg", ownership: "owned" },
273
- indeterminate: { action: "reject", reason: "indeterminate-no-spawn" },
274
- },
275
298
  };
276
299
 
277
300
  // ── The unsupported-backend mailbox mini-table (F-mailbox) ─────────────────
278
- // SEPARATE from the in-domain 6-cell DISPATCH_TABLE (Fable (i)): a backend with no
301
+ // SEPARATE from the in-domain 3-cell DISPATCH_TABLE (Fable (i)): a backend with no
279
302
  // pi-socket liveness predicate never enters the liveness-keyed table. Instead the
280
303
  // domain guard routes it here, keyed on intent alone. Reaches here: claude-code
281
304
  // (self-fetch mailbox) and codex (no adapter yet). Does NOT reach here: antigravity —
@@ -288,17 +311,20 @@ export const DISPATCH_TABLE: Record<EntwurfIntent, Record<DispatchLiveness, Disp
288
311
  // fact is false (fail-closed). The ack is enqueue+doorbell, NOT read, and
289
312
  // observedLiveness stays `unsupported` — the receipt's `meta-mailbox` transport
290
313
  // is what says "this went to the mailbox".
291
- // - owned-outcome has no real liveness to own on a self-fetch backend → reject.
292
314
  //
293
315
  // N2 asymmetry (명문화 — without this the two tables read as contradictory):
294
316
  // fire-and-forget+dormant-PI = reject vs fire-and-forget+unsupported-CITIZEN = mailbox.
295
- // In-domain `dormant` is a CONFIRMED not-running pi, so enqueuing would be a
296
- // silent pileup (resume is the honest place). `unsupported` is UNKNOWN liveness
297
- // on a backend we cannot probe, so a best-effort mailbox doorbell is the most we
298
- // can honestly offer there is nothing to resume into.
317
+ // The axis is NOT which backend a citizen runs, and this cell grants no backend a
318
+ // privilege. It is one question asked of both: does an ACTIVE RECEIVER actually drain
319
+ // the mailbox this message would land in? A self-fetch citizen reaches this allow cell
320
+ // only through the separate `mailboxDeliverable` fact, which IS that receiver check —
321
+ // when nobody drains, this very cell rejects as `mailbox-undeliverable`. An in-domain
322
+ // `dormant` pi has no drainer at all: its mailbox reader was the running session, and
323
+ // the session is confirmed not running, so an enqueue would be a silent pileup no one
324
+ // ever reads. Same rule, two answers. (It reads as a backend split only if you skip
325
+ // the deliverability fact — which is exactly what makes claude-code look privileged.)
299
326
  export const UNSUPPORTED_DISPATCH_TABLE: Record<EntwurfIntent, DispatchVerdict> = {
300
327
  "fire-and-forget": { action: "send", transport: "meta-mailbox", ownership: "ack-only" },
301
- "owned-outcome": { action: "reject", reason: "backend-liveness-unsupported" },
302
328
  };
303
329
 
304
330
  // ── Dispatch receipt (R3) ──────────────────────────────────────────────────
@@ -307,9 +333,9 @@ export const UNSUPPORTED_DISPATCH_TABLE: Record<EntwurfIntent, DispatchVerdict>
307
333
  export type EntwurfV2Receipt =
308
334
  | {
309
335
  ok: true;
310
- action: "send" | "resume";
336
+ action: "send";
311
337
  transport: EntwurfV2Transport;
312
- ownership: "ack-only" | "owned";
338
+ ownership: "ack-only";
313
339
  observedLiveness: FactLiveness;
314
340
  }
315
341
  // observedLiveness is `FactLiveness | null` (?6): null for the pre-probe
@@ -331,9 +357,9 @@ export type EntwurfV2RejectReceipt = Extract<EntwurfV2Receipt, { ok: false }>;
331
357
  * observedLiveness:"indeterminate"}`, which the blanket `FactLiveness | null`
332
358
  * schema accepts. This constructor THROWS on a well-formedness violation, so
333
359
  * every reject path (resolveDispatch's own mints below + the 5b stages that
334
- * produce bad-target / target-locked / target-address-conflict / untrusted-
335
- * fail-fast) routes through one chokepoint and the bypass surface is zero. 5b
336
- * MUST build rejects with this, never by object literal.
360
+ * produce bad-target / target-locked / target-address-conflict / record-less-socket)
361
+ * routes through one chokepoint and the bypass surface is zero. 5b MUST build
362
+ * rejects with this, never by object literal.
337
363
  */
338
364
  export function makeRejectReceipt(
339
365
  reason: EntwurfV2RejectReason,
@@ -353,11 +379,11 @@ export function makeRejectReceipt(
353
379
  * PURE dispatch decision over already-resolved facts. Before reaching here the
354
380
  * caller has resolved the target (→ `bad-target` if no existing citizen) and, for
355
381
  * an in-domain backend, acquired the per-gid lock (→ `target-locked`) and probed
356
- * liveness UNDER that lock. This function only decides the liveness-routed
357
- * verdict. preflight (→ `untrusted-fail-fast`) is NOT a precondition here: per 1B
358
- * it runs only AFTER this resolver returns a resume verdict (the sole branch that
359
- * launches a child into a target cwd), so a send/mailbox verdict never touches it.
360
- * Do NOT reintroduce a global pre-resolver preflight that re-breaks F-mailbox.
382
+ * liveness UNDER that lock. This function only decides the liveness-routed verdict.
383
+ * There is NO trust preflight anywhere on this path: it existed solely to guard the
384
+ * resume verdict that launched a child into a target cwd, and that verdict is gone.
385
+ * Do NOT reintroduce a global pre-resolver preflight that re-breaks F-mailbox,
386
+ * and this verb no longer launches anything into any cwd.
361
387
  *
362
388
  * Two facts in: `liveness` (the 4-value FactLiveness) and `mailboxDeliverable`
363
389
  * (F-mailbox — a SEPARATE axis from liveness, NOT a column of either table, NOT
@@ -369,8 +395,8 @@ export function makeRejectReceipt(
369
395
  * backend the liveness-routed table is authoritative and the flag is ignored.
370
396
  *
371
397
  * R1 domain guard runs first: an `unsupported` liveness is routed through the
372
- * UNSUPPORTED_DISPATCH_TABLE (mailbox mini-table), never the 6-cell table.
373
- * No spawn, no send, no I/O — step 5 executes the chosen transport.
398
+ * UNSUPPORTED_DISPATCH_TABLE (mailbox mini-table), never the 3-cell table.
399
+ * No send, no I/O — step 5 executes the chosen transport.
374
400
  */
375
401
  export function resolveDispatch(
376
402
  intent: EntwurfIntent,
@@ -396,7 +422,7 @@ export function resolveDispatch(
396
422
  };
397
423
  }
398
424
  // liveness is now narrowed to SocketLiveness; deliverability does not apply.
399
- const cell = DISPATCH_TABLE[intent][dispatchLivenessOf(liveness)];
425
+ const cell: DispatchVerdict = DISPATCH_TABLE[intent][dispatchLivenessOf(liveness)];
400
426
  if (cell.action === "reject") {
401
427
  return makeRejectReceipt(cell.reason, liveness);
402
428
  }
@@ -420,20 +446,16 @@ export function resolveDispatch(
420
446
  // fire-and-forget × alive → native-push send (the ONE allow cell)
421
447
  // fire-and-forget × dead → reject native-push-target-dead
422
448
  // fire-and-forget × indeterminate → reject native-push-probe-indeterminate
423
- // owned-outcome × * → reject native-push-no-resume-authority (state-
424
- // independent: no pi-child to own; `backend-
425
- // liveness-unsupported` is NOT reused false name).
449
+ //
450
+ // The `owned-outcome` row is gone with the intent (header). It had rejected on every
451
+ // liveness with `native-push-no-resume-authority` a real fact about this rail that
452
+ // simply has no caller left to tell, since no caller can declare that intent.
426
453
  export const NATIVE_PUSH_DISPATCH_TABLE: Record<EntwurfIntent, Record<NativePushLiveness, DispatchVerdict>> = {
427
454
  "fire-and-forget": {
428
455
  alive: { action: "send", transport: "native-push", ownership: "ack-only" },
429
456
  dead: { action: "reject", reason: "native-push-target-dead" },
430
457
  indeterminate: { action: "reject", reason: "native-push-probe-indeterminate" },
431
458
  },
432
- "owned-outcome": {
433
- alive: { action: "reject", reason: "native-push-no-resume-authority" },
434
- dead: { action: "reject", reason: "native-push-no-resume-authority" },
435
- indeterminate: { action: "reject", reason: "native-push-no-resume-authority" },
436
- },
437
459
  };
438
460
 
439
461
  // The reasons the native-push resolver emits — a THIRD post-probe reject set, parallel
@@ -443,7 +465,6 @@ export const NATIVE_PUSH_DISPATCH_TABLE: Record<EntwurfIntent, Record<NativePush
443
465
  export const NATIVE_PUSH_REJECT_REASONS = [
444
466
  "native-push-target-dead",
445
467
  "native-push-probe-indeterminate",
446
- "native-push-no-resume-authority",
447
468
  ] as const satisfies readonly EntwurfV2RejectReason[];
448
469
 
449
470
  /**