@junghanacs/entwurf 0.21.0 → 0.23.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 +3 -2
- package/BASELINE.md +3 -1
- package/CHANGELOG.md +658 -0
- package/DELIVERY.md +167 -29
- package/README.md +104 -14
- package/VERIFY.md +73 -11
- package/docs/external-mcp-host.md +16 -6
- package/docs/setup-clean-host.md +73 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +54 -31
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +289 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +171 -263
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -31
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
- package/package.json +4 -4
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/codex-caller-seat.ts +204 -0
- package/pi-extensions/lib/codex-declaration.js +612 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +325 -52
- package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
- package/pi-extensions/lib/entwurf-facts.ts +14 -1
- package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
- package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
- package/pi-extensions/lib/entwurf-self-address.ts +33 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
- package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
- package/pi-extensions/lib/fresh-call-composition.ts +493 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
- package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
- package/pi-extensions/lib/herdr-placement.ts +276 -0
- package/pi-extensions/lib/mux-fresh-call.ts +257 -304
- package/run.sh +522 -10
- package/scripts/check-codex-app-server-launch.ts +445 -0
- package/scripts/check-codex-birth-hook.ts +187 -0
- package/scripts/check-entwurf-control-rpc.ts +138 -0
- package/scripts/check-entwurf-fact-provider.ts +4 -2
- package/scripts/check-entwurf-facts.ts +12 -6
- package/scripts/check-entwurf-peers-surface.ts +3 -1
- package/scripts/check-entwurf-self-address.ts +68 -0
- package/scripts/check-entwurf-v2-production.ts +42 -1
- package/scripts/check-entwurf-v2-send.ts +26 -7
- package/scripts/check-fresh-call-dispatch.ts +301 -0
- package/scripts/check-gate-qualification.ts +19 -5
- package/scripts/check-herdr-activation.ts +1293 -0
- package/scripts/check-herdr-fresh-call.ts +1250 -0
- package/scripts/check-herdr-placement.ts +383 -0
- package/scripts/check-herdr-plugin-build.ts +842 -0
- package/scripts/check-herdr-plugin-profile.ts +371 -0
- package/scripts/check-herdr-plugin.ts +523 -0
- package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
- package/scripts/check-herdr-sandbox.ts +456 -0
- package/scripts/check-herdr-supply.ts +184 -0
- package/scripts/check-mux-launch-tmux.ts +331 -35
- package/scripts/check-mux-launcher-fence.ts +138 -0
- package/scripts/check-peer-facts.ts +331 -0
- package/scripts/check-release-gate-outcomes.ts +12 -0
- package/scripts/check-typing-call-fence.ts +296 -0
- package/scripts/codex-app-server-launch.sh +275 -0
- package/scripts/codex-birth-doctor.sh +159 -16
- package/scripts/codex-birth-install.sh +178 -72
- package/scripts/codex-birth-uninstall.sh +162 -7
- package/scripts/codex-socket-path.ts +33 -0
- package/scripts/codex-terminal-title-config.py +500 -0
- package/scripts/codex_toml_io.py +121 -0
- package/scripts/fixtures/herdr-supply.json +29 -0
- package/scripts/herdr-activation.mjs +536 -0
- package/scripts/herdr-plugin-activate.mjs +270 -0
- package/scripts/herdr-plugin-deactivate.mjs +193 -0
- package/scripts/herdr-runtime.mjs +1203 -0
- package/scripts/install-herdr-ci.sh +96 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +13 -5
- package/scripts/lib/codex-fresh-source-receipts.ts +29 -2
- package/scripts/lib/tree-digest.ts +97 -0
- package/scripts/meta-bridge-install.sh +19 -2
- package/scripts/meta-bridge-state.py +167 -11
- package/scripts/mutants/codex-app-server-launch.json +157 -0
- package/scripts/mutants/codex-caller-seat.json +334 -0
- package/scripts/mutants/codex-native.json +195 -29
- package/scripts/mutants/control-socket-disconnect.json +57 -0
- package/scripts/mutants/fresh-call-dispatch.json +154 -0
- package/scripts/mutants/herdr-activation.json +305 -0
- package/scripts/mutants/herdr-fresh-call.json +448 -0
- package/scripts/mutants/herdr-placement.json +161 -0
- package/scripts/mutants/herdr-plugin-build.json +160 -0
- package/scripts/mutants/herdr-plugin-profile.json +202 -0
- package/scripts/mutants/herdr-plugin.json +133 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
- package/scripts/mutants/herdr-supply.json +133 -0
- package/scripts/mutants/meta-hook-session-switch.json +2 -2
- package/scripts/mutants/mux-fresh-call.json +184 -25
- package/scripts/mutants/mux-launcher-fence.json +13 -0
- package/scripts/mutants/omp-fresh.json +4 -4
- package/scripts/mutants/peer-facts.json +98 -0
- package/scripts/mutants/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/mutants/v2-surface.json +22 -0
- package/scripts/peer-facts.ts +120 -0
- package/scripts/register-pi-package.py +8 -0
- package/scripts/register-pi-provider.py +88 -12
- package/scripts/smoke-codex-birth.sh +281 -12
- package/scripts/smoke-codex-config-state.sh +192 -3
- package/scripts/smoke-codex-fresh-live.ts +277 -37
- package/scripts/smoke-entwurf-chain-live.ts +50 -0
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
- package/scripts/smoke-setup-verdict.sh +13 -11
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* about it: no `sendable`, no `deliverable`, no transport. The listing stays a listing —
|
|
14
14
|
* dispatch still computes routing at call time from the same underlying markers.
|
|
15
15
|
*
|
|
16
|
+
* THE PLACEMENT AXIS IS MEASURED ONCE, NOT PER CITIZEN (#116 S1). `receiver` and
|
|
17
|
+
* `transcript` are per-citizen filesystem questions; placement is one question asked of
|
|
18
|
+
* one placement owner about every pane it has. So the herdr read happens ABOVE this
|
|
19
|
+
* module (the provider does it once) and arrives here as an already-built index. That
|
|
20
|
+
* is also the anti-watcher shape: one read per listing, no retry, no wait for a pane
|
|
21
|
+
* whose session reference has not landed yet.
|
|
22
|
+
*
|
|
16
23
|
* ONE MEASUREMENT, TWO WORDS. `receiver` is derived from the SAME
|
|
17
24
|
* `resolveMailboxReceiverFacts` composition the v2 dispatch seam and `entwurf_self` use.
|
|
18
25
|
* The surfaces are allowed to phrase it differently — a listing wants an enum, a reject
|
|
@@ -20,9 +27,16 @@
|
|
|
20
27
|
* rather than a second opinion about the same markers.
|
|
21
28
|
*/
|
|
22
29
|
|
|
30
|
+
import { execFileSync } from "node:child_process";
|
|
23
31
|
import * as fs from "node:fs";
|
|
24
32
|
import { resolveMailboxReceiverFacts } from "./entwurf-deliverability.ts";
|
|
25
33
|
import type { PeerObservations, ReceiverObservation, TranscriptObservation } from "./entwurf-facts.ts";
|
|
34
|
+
import {
|
|
35
|
+
buildPlacementIndex,
|
|
36
|
+
type HerdrPlacementIndex,
|
|
37
|
+
parseHerdrPaneList,
|
|
38
|
+
resolvePlacement,
|
|
39
|
+
} from "./herdr-placement.ts";
|
|
26
40
|
import {
|
|
27
41
|
type MetaBackend,
|
|
28
42
|
type MetaIdentity,
|
|
@@ -71,7 +85,63 @@ function observeTranscript(identity: MetaIdentity): TranscriptObservation {
|
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
|
|
74
|
-
/**
|
|
88
|
+
/** How long the one placement read may take before we stop waiting for it. A listing
|
|
89
|
+
* must not hang on a placement owner: the column is a convenience and `unobserved` is
|
|
90
|
+
* a complete answer. */
|
|
91
|
+
const HERDR_READ_TIMEOUT_MS = 2000;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Read the placement owner ONCE, or decline to.
|
|
95
|
+
*
|
|
96
|
+
* TWO ENV FACTS DECIDE, AND BOTH ARE HERDR'S OWN (measured, herdr 0.9.0). `HERDR_ENV=1`
|
|
97
|
+
* is how herdr tells a process it is running inside herdr, and `HERDR_BIN_PATH` is the
|
|
98
|
+
* invocation path herdr's own plugin contract tells callers to use. Neither is
|
|
99
|
+
* discovered: if herdr did not put them in this process's environment we are not inside
|
|
100
|
+
* herdr and we make no claim. There is no path guess, no socket scan, and no PATH
|
|
101
|
+
* lookup — this must stay as explicit as Hard Rule 6 wants configuration to be.
|
|
102
|
+
*
|
|
103
|
+
* EVERY FAILURE IS `null`, NEVER AN EMPTY INDEX. A missing binary, a timeout, a nonzero
|
|
104
|
+
* exit or a payload we cannot parse all mean nobody measured. An empty index would say
|
|
105
|
+
* something much stronger — "herdr was read and has none of your citizens" — about a
|
|
106
|
+
* read that did not happen.
|
|
107
|
+
*/
|
|
108
|
+
export function readHerdrPlacementIndex(env: NodeJS.ProcessEnv = process.env): HerdrPlacementIndex | null {
|
|
109
|
+
if (env.HERDR_ENV !== "1") return null;
|
|
110
|
+
const bin = env.HERDR_BIN_PATH;
|
|
111
|
+
if (typeof bin !== "string" || bin.length === 0) return null;
|
|
112
|
+
let stdout: string;
|
|
113
|
+
try {
|
|
114
|
+
stdout = execFileSync(bin, ["pane", "list"], {
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: HERDR_READ_TIMEOUT_MS,
|
|
117
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
118
|
+
});
|
|
119
|
+
} catch {
|
|
120
|
+
// Bounded environment probe: a placement owner that did not answer is a citizen
|
|
121
|
+
// column that reads `unobserved`, not a listing that fails.
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const rows = parseHerdrPaneList(stdout);
|
|
125
|
+
return rows === null ? null : buildPlacementIndex(rows);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build the production observer over an already-resolved placement index.
|
|
130
|
+
*
|
|
131
|
+
* `null` means no placement owner was read on this host — every citizen then reads
|
|
132
|
+
* `unobserved`, which is the only honest answer when nobody looked. It is NOT `none`:
|
|
133
|
+
* that word is reserved for a herdr that WAS read and does not have this citizen.
|
|
134
|
+
*/
|
|
135
|
+
export function makeObservePeerFacts(placementIndex: HerdrPlacementIndex | null) {
|
|
136
|
+
return (identity: MetaIdentity): PeerObservations => ({
|
|
137
|
+
receiver: observeReceiver(identity),
|
|
138
|
+
transcript: observeTranscript(identity),
|
|
139
|
+
placement: resolvePlacement(placementIndex, identity),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The production observer with no placement owner read — the shape every caller that
|
|
144
|
+
* has not resolved an index gets, and the default on a host with no herdr. */
|
|
75
145
|
export function observePeerFacts(identity: MetaIdentity): PeerObservations {
|
|
76
|
-
return
|
|
146
|
+
return makeObservePeerFacts(null)(identity);
|
|
77
147
|
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import type { EntwurfDiagnostic, EntwurfFactsResult } from "./entwurf-fact-provider.ts";
|
|
27
27
|
import type { PeerFact } from "./entwurf-facts.ts";
|
|
28
|
+
import { renderPlacement } from "./herdr-placement.ts";
|
|
28
29
|
|
|
29
30
|
/** The full `entwurf_peers` JSON payload: citizens + diagnostics, nothing else.
|
|
30
31
|
* NO verb-routing field anywhere (the gate enforces this by deep key scan) and
|
|
@@ -50,11 +51,18 @@ export const ENTWURF_PEERS_RENDER_LIMIT = 32;
|
|
|
50
51
|
// be indistinguishable here. `receiver=active|inactive|none|n/a` is a projection of the
|
|
51
52
|
// same measurement dispatch uses; `transcript=exists|absent` is the record's transcript,
|
|
52
53
|
// existence only, never the path.
|
|
54
|
+
//
|
|
55
|
+
// `placement` (#116 S1) is the third observed fact and the one most likely to be
|
|
56
|
+
// MISREAD, so it is placed at the END of the row — away from `liveness` — and printed
|
|
57
|
+
// as `herdr <pane>` rather than a bare pane id. A pane says where a citizen was last
|
|
58
|
+
// SEEN by a placement owner; it says nothing about whether that citizen is running, and
|
|
59
|
+
// nothing about how to reach it. `unobserved` = nobody looked (no herdr here), `none` =
|
|
60
|
+
// herdr was read and does not have this one, `ambiguous` = more than one pane claimed it.
|
|
53
61
|
function renderPeerLine(p: PeerFact): string {
|
|
54
62
|
const model = p.model ?? "(unknown)";
|
|
55
63
|
return (
|
|
56
64
|
`- ${p.gardenId} backend=${p.backend} liveness=${p.liveness} receiver=${p.receiver} ` +
|
|
57
|
-
`transcript=${p.transcript} cwd=${p.cwd} model=${model}`
|
|
65
|
+
`transcript=${p.transcript} cwd=${p.cwd} model=${model} placement=${renderPlacement(p.placement)}`
|
|
58
66
|
);
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -168,3 +168,36 @@ export function computeSelfAddressability(facts: SelfAddressabilityFacts): SelfA
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
/** Facts the uncitizened-notice decision is made from. Nothing is read here — all three are given. */
|
|
173
|
+
export interface UncitizenedNoticeFacts {
|
|
174
|
+
/** Did this session get `--entwurf-control`? A citizen is never told it is not one. */
|
|
175
|
+
controlEnabled: boolean;
|
|
176
|
+
/** Is there a human surface to say it on? `pi -p …` has none, and neither does any pipeline. */
|
|
177
|
+
hasUI: boolean;
|
|
178
|
+
/** Has this process already said it? `refreshServer` runs again on session switches. */
|
|
179
|
+
alreadyShown: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The mirror of `computeSelfAddressability`, as a decision rather than a claim.
|
|
184
|
+
*
|
|
185
|
+
* That function exists so a surface never CLAIMS an addressability it lacks. This one exists so a
|
|
186
|
+
* surface does not stay SILENT about lacking one: `[관측: GLG, 날것 PC, 2026-09-17]` a herdr plugin
|
|
187
|
+
* install wires this extension at user scope, so it loads in every pi on the host while citizenship
|
|
188
|
+
* stays argv-gated on purpose — and a plain `pi` after a green install is therefore silent,
|
|
189
|
+
* tool-less and indistinguishable from an install that did nothing.
|
|
190
|
+
*
|
|
191
|
+
* WHY THIS IS A FUNCTION AND NOT THREE `if`s AT THE CALL SITE. It was three `if`s, and its gate was
|
|
192
|
+
* a source regex asserting the call existed. `[측정 2026-09-17, 독립 검수 claude-opus-5 + 재현]` two
|
|
193
|
+
* mutants walked straight through that green: moving the call into the CITIZEN branch (so citizens
|
|
194
|
+
* are told they are not citizens and plain pi stays silent), and deleting the once-latch (so the
|
|
195
|
+
* line repeats on every session switch). A regex can see that a call exists; it cannot see which
|
|
196
|
+
* branch it is in or how often it fires. Those are properties of FACTS, so the facts are now
|
|
197
|
+
* arguments and the answer is a value a truth table can exhaust.
|
|
198
|
+
*/
|
|
199
|
+
export function decideUncitizenedNotice(facts: UncitizenedNoticeFacts): boolean {
|
|
200
|
+
if (facts.controlEnabled) return false;
|
|
201
|
+
if (!facts.hasUI) return false;
|
|
202
|
+
return !facts.alreadyShown;
|
|
203
|
+
}
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Why a result type richer than the receipt: the carry-over contracts from 5c demand it.
|
|
14
14
|
* - N3 (5c-2b): a dead-path re-resolve `rejected` carries the resolver's `rejectReason`
|
|
15
|
-
* (dormant-fire-forget-unsupported / mailbox-undeliverable / …)
|
|
16
|
-
*
|
|
15
|
+
* (dormant-fire-forget-unsupported / mailbox-undeliverable / …), while an in-band
|
|
16
|
+
* refusal carries its supplied receiver error. The runner carries either verbatim;
|
|
17
|
+
* receiver evidence is not reclassified as resolver taxonomy.
|
|
17
18
|
* - N1 (5c-2a): a non-`failed` outcome whose `releaseLock` then threw is a
|
|
18
19
|
* `SendDeliveredReleaseFailedError` — the delivery HAPPENED, the lock is dirty, a
|
|
19
20
|
* re-send would double-deliver. The runner surfaces this as `execution-failed` with
|
|
@@ -101,12 +101,12 @@ export interface ControlSocketSendDeps {
|
|
|
101
101
|
|
|
102
102
|
export interface ControlSocketSendResult {
|
|
103
103
|
outcome: SendFinalOutcome;
|
|
104
|
-
/** Present ONLY on a `rejected` outcome
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
104
|
+
/** Present ONLY on a `rejected` outcome. It carries either the dead-path re-resolver's
|
|
105
|
+
* machine-readable reason (dormant-fire-forget-unsupported / mailbox-undeliverable /
|
|
106
|
+
* indeterminate-no-spawn / bad-target / target-address-conflict), or the non-empty
|
|
107
|
+
* error returned by an in-band RPC/mailbox refusal. The latter is receiver evidence,
|
|
108
|
+
* not a resolver taxonomy and is never invented when the completed result has no error.
|
|
109
|
+
* The 5d runner carries this verbatim to the sender-visible surface. */
|
|
110
110
|
rejectReason?: string;
|
|
111
111
|
/** #98 R, fallback leg: the `.msg` a dead-socket re-resolve enqueued. Present ONLY
|
|
112
112
|
* when the fallback actually routed to the mailbox and the enqueue succeeded — a
|
|
@@ -120,7 +120,7 @@ export interface ControlSocketSendResult {
|
|
|
120
120
|
|
|
121
121
|
// A drive step's verdict: the terminal outcome, plus the original error to RETHROW on
|
|
122
122
|
// a `failed` (the hand releases first, then rethrows — never swallows the failure), plus
|
|
123
|
-
// the optional resolver
|
|
123
|
+
// the optional resolver or in-band receiver reason to carry on a `rejected` (N3).
|
|
124
124
|
interface SendDrive {
|
|
125
125
|
outcome: SendFinalOutcome;
|
|
126
126
|
error?: unknown;
|
|
@@ -177,8 +177,9 @@ async function driveSend(plan: ControlSocketPlan, lock: LockClaim, deps: Control
|
|
|
177
177
|
// dead ⇒ proven non-delivery ⇒ same-lock one-shot re-resolve (lock still held).
|
|
178
178
|
return await driveDeadFallback(plan, lock, deps);
|
|
179
179
|
}
|
|
180
|
-
// A completed RPC: ack ⇒ sent; in-band refusal ⇒ rejected, NO fallback.
|
|
181
|
-
|
|
180
|
+
// A completed RPC: ack ⇒ sent; in-band refusal ⇒ rejected, NO fallback. Preserve
|
|
181
|
+
// its receiver error verbatim when supplied; do not make a reason up when absent.
|
|
182
|
+
return result.success ? { outcome: "sent" } : inBandRejected(result);
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
/**
|
|
@@ -187,6 +188,10 @@ async function driveSend(plan: ControlSocketPlan, lock: LockClaim, deps: Control
|
|
|
187
188
|
* re-enter the fallback, it finalizes as failed). The hand only executes; the resolver
|
|
188
189
|
* decided.
|
|
189
190
|
*/
|
|
191
|
+
function inBandRejected(result: RpcSendResult): SendDrive {
|
|
192
|
+
return { outcome: "rejected", rejectReason: result.error === "" ? undefined : result.error };
|
|
193
|
+
}
|
|
194
|
+
|
|
190
195
|
async function driveDeadFallback(
|
|
191
196
|
plan: ControlSocketPlan,
|
|
192
197
|
lock: LockClaim,
|
|
@@ -219,7 +224,7 @@ async function driveDeadFallback(
|
|
|
219
224
|
// connect failure finalizes as failed (no further fallback).
|
|
220
225
|
try {
|
|
221
226
|
const r = await deps.sendOverSocket(rePlan);
|
|
222
|
-
return
|
|
227
|
+
return r.success ? { outcome: "fallback-sent" } : inBandRejected(r);
|
|
223
228
|
} catch (err) {
|
|
224
229
|
return { outcome: "failed", error: err };
|
|
225
230
|
}
|
|
@@ -231,7 +236,7 @@ async function driveDeadFallback(
|
|
|
231
236
|
// #98 R: this leg writes a `.msg` exactly like the primary mailbox rail, so it
|
|
232
237
|
// owes the sender the same per-message receipt. Carried only on success — a
|
|
233
238
|
// `rejected` enqueue wrote no file to name.
|
|
234
|
-
return r.success ? { outcome: "fallback-sent", messagePath: r.messagePath } :
|
|
239
|
+
return r.success ? { outcome: "fallback-sent", messagePath: r.messagePath } : inBandRejected(r);
|
|
235
240
|
} catch (err) {
|
|
236
241
|
return { outcome: "failed", error: err };
|
|
237
242
|
}
|