@cotal-ai/runtime 0.57.0 → 0.59.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/journal-store.d.ts +27 -2
- package/dist/journal-store.d.ts.map +1 -1
- package/dist/journal-store.js +42 -2
- package/dist/journal-store.js.map +1 -1
- package/dist/mesh-handler.d.ts +23 -19
- package/dist/mesh-handler.d.ts.map +1 -1
- package/dist/mesh-handler.js +120 -32
- package/dist/mesh-handler.js.map +1 -1
- package/dist/resolve-checkpoint.d.ts +51 -0
- package/dist/resolve-checkpoint.d.ts.map +1 -1
- package/dist/resolve-checkpoint.js +121 -3
- package/dist/resolve-checkpoint.js.map +1 -1
- package/dist/run-command.d.ts +1 -1
- package/dist/run-command.d.ts.map +1 -1
- package/dist/run-command.js +103 -116
- package/dist/run-command.js.map +1 -1
- package/dist/run-driver.d.ts +17 -0
- package/dist/run-driver.d.ts.map +1 -1
- package/dist/run-driver.js +54 -13
- package/dist/run-driver.js.map +1 -1
- package/dist/run-effect-host.d.ts +1 -0
- package/dist/run-effect-host.d.ts.map +1 -1
- package/dist/run-effect-host.js +1 -0
- package/dist/run-effect-host.js.map +1 -1
- package/dist/run-host.d.ts +11 -2
- package/dist/run-host.d.ts.map +1 -1
- package/dist/run-host.js +97 -12
- package/dist/run-host.js.map +1 -1
- package/dist/run-scope-authority.d.ts +16 -1
- package/dist/run-scope-authority.d.ts.map +1 -1
- package/dist/run-scope-authority.js +51 -10
- package/dist/run-scope-authority.js.map +1 -1
- package/package.json +5 -5
package/dist/mesh-handler.js
CHANGED
|
@@ -432,6 +432,16 @@ export class MeshHandler {
|
|
|
432
432
|
const prev = this.handoffMemos.get(scope);
|
|
433
433
|
this.handoffMemos.set(scope, prev !== undefined && (prev === "ambiguous" || prev.to === to) ? "ambiguous" : { to, fromGoalId });
|
|
434
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Despawn the seats of a run that completed (the driver's `releaseSeats` is the caller). On the
|
|
437
|
+
* mediated path the run's own journal decides which seats those are, never the entries handed in.
|
|
438
|
+
*/
|
|
439
|
+
async release(entries) {
|
|
440
|
+
const owed = this.services ? await this.services.authority.releaseEntries() : entries;
|
|
441
|
+
for (const e of owed)
|
|
442
|
+
if (e.kind === "spawn" && e.requestId !== undefined)
|
|
443
|
+
await this.dischargeSpawn(e);
|
|
444
|
+
}
|
|
435
445
|
/**
|
|
436
446
|
* End the external state of a cancelled scope's LOSERS: the world half of the discharge the
|
|
437
447
|
* scope entry's `cancel.issued` records (§7.6, and the driver's `dischargeCancellations` is the
|
|
@@ -574,7 +584,7 @@ export class MeshHandler {
|
|
|
574
584
|
if (fact !== undefined)
|
|
575
585
|
break;
|
|
576
586
|
if (this.now() >= deadline)
|
|
577
|
-
throw new Error(`the
|
|
587
|
+
throw new Error(`the spawn goal "${goalId}" is accepted but reached no terminal within its ${window}ms readiness window; its agent cannot be released yet, and the discharge stays open to retry`);
|
|
578
588
|
await new Promise((r) => setTimeout(r, GOAL_POLL_MS).unref());
|
|
579
589
|
}
|
|
580
590
|
}
|
|
@@ -586,19 +596,35 @@ export class MeshHandler {
|
|
|
586
596
|
// between the acceptance and the bind). The seat — if one came up — is not addressable from
|
|
587
597
|
// here, and no retry will ever learn more, so throwing would wedge every future sweep of
|
|
588
598
|
// this run behind an answer that cannot arrive. Name the leak for the operator instead.
|
|
589
|
-
console.error(`! discharge: the
|
|
599
|
+
console.error(`! discharge: the spawn goal "${goalId}" settled ${fact.state} with no readable agent identity; if its seat is up it must be despawned by hand (cotal ps)`);
|
|
590
600
|
return;
|
|
591
601
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
+
// The manager that committed the terminal allocated the seat, and a manager's target resolver
|
|
603
|
+
// knows only its own seats: any other member answers `expired` for this one while it runs. So a
|
|
604
|
+
// miss says "already gone" only from the allocator. One from another member is re-issued on the
|
|
605
|
+
// class rail, and so is a split refusal `invokeManager` gave up repairing, since it states that
|
|
606
|
+
// nothing ran. A bounded run of them leaves the discharge open rather than claiming it gone.
|
|
607
|
+
const allocator = fact.committer?.instanceId;
|
|
608
|
+
for (let attempt = 0;; attempt += 1) {
|
|
609
|
+
const reply = await this.invokeManager(await this.manager(), "despawn", { graceful: true }, {
|
|
610
|
+
target: { mode: "owner", ...target },
|
|
611
|
+
deadlineMs: SPAWN_ACCEPT_DEADLINE_MS,
|
|
612
|
+
});
|
|
613
|
+
// Tolerated refusals are the two "already gone" shapes: `not-found` (no such agent), and
|
|
614
|
+
// `expired` (the target's lifecycle mapping is gone or rotated — this despawn pins one
|
|
615
|
+
// incarnation, and an incarnation the mapping no longer names is not running).
|
|
616
|
+
const code = reply.reply.ok === false ? reply.reply.error?.code : undefined;
|
|
617
|
+
if (code === undefined)
|
|
618
|
+
return;
|
|
619
|
+
const unrun = replyRefusedBeforeEffect(reply.reply.error);
|
|
620
|
+
if (!unrun && code !== "not-found" && code !== "expired")
|
|
621
|
+
throw new Error(`the spawn's agent could not be despawned: ${reply.reply.error?.message ?? "refused"}`);
|
|
622
|
+
if (!unrun && (allocator === undefined || reply.responder.instanceId === allocator))
|
|
623
|
+
return;
|
|
624
|
+
if (attempt === DESPAWN_ROUTE_ATTEMPTS)
|
|
625
|
+
throw new Error(`the spawn goal "${goalId}" was allocated by manager instance ${allocator ?? "(unrecorded)"}, but none of ${attempt + 1} despawns was answered by it, and neither another manager's miss nor a refusal that ran nothing means it is gone; the discharge stays open to retry`);
|
|
626
|
+
this.managerService = undefined;
|
|
627
|
+
}
|
|
602
628
|
}
|
|
603
629
|
/**
|
|
604
630
|
* Release a cancelled conclave's MEMBERSHIP (spec §7.5): a cancelled body performs no new
|
|
@@ -721,8 +747,10 @@ export class MeshHandler {
|
|
|
721
747
|
: this.now() + parseDuration(req.timeout ?? this.binding.defaultCheckpointTimeout);
|
|
722
748
|
// Once per attempt, before the pause exists: a crash between the bind and the mint leaves a
|
|
723
749
|
// pending entry that says what it was going to ask, which is the harmless direction.
|
|
750
|
+
// `onExpiry` rides beside them for the operator view, as this attempt was armed (`fail` when the
|
|
751
|
+
// program sets none). What an expiry does is still decided from the source, never read from here.
|
|
724
752
|
if (ctx.resume?.asks === undefined)
|
|
725
|
-
await ctx.bind({ asks: req.prompt, deadlineAt: deadline, ...(req.to !== undefined ? { addressee: req.to } : {}) });
|
|
753
|
+
await ctx.bind({ asks: req.prompt, deadlineAt: deadline, onExpiry: req.onExpiry ?? "fail", ...(req.to !== undefined ? { addressee: req.to } : {}) });
|
|
726
754
|
// AN ESCALATION IS ADDRESSED, so where the addressee is an agent of this run it is TOLD.
|
|
727
755
|
// Attempt 0 has no addressee (the reference allows `to` only with `onExpiry: "escalate"`,
|
|
728
756
|
// and the escalation is the second mint), and a `to` that names no agent of this run is a
|
|
@@ -931,11 +959,13 @@ export class MeshHandler {
|
|
|
931
959
|
* "down" here and "down or gone" at a conclave join are one definition. No live row carrying
|
|
932
960
|
* the name AND this incarnation is the death, with the reason split by what the name shows now:
|
|
933
961
|
* `"lapsed"` when nothing live holds the name any more, `"superseded"` when a live row holds it
|
|
934
|
-
* under a DIFFERENT incarnation — this incarnation dead with a successor already up.
|
|
962
|
+
* under a DIFFERENT incarnation — this incarnation dead with a successor already up. A lapse is
|
|
963
|
+
* read as the death only once the row has stayed absent for {@link LAPSE_CONFIRM_MS}: a seat whose
|
|
964
|
+
* connector stalled past the row's TTL renews it under the same uid, and that seat is not down.
|
|
935
965
|
*
|
|
936
|
-
* NOTHING BINDS, because a death is re-observable where a matched message is not: a
|
|
937
|
-
*
|
|
938
|
-
*
|
|
966
|
+
* NOTHING BINDS, because a death is re-observable where a matched message is not: a dead
|
|
967
|
+
* incarnation's uid never heartbeats again, so a crash between the observation and the settle
|
|
968
|
+
* re-observes the same death on resume (after a fresh confirmation window) — at worst
|
|
939
969
|
* with the reason upgraded from `"lapsed"` to `"superseded"` by a successor that appeared in
|
|
940
970
|
* between. `at` is the time of OBSERVATION: presence records no time of death, and inventing
|
|
941
971
|
* one would be a value the planes cannot back.
|
|
@@ -958,6 +988,8 @@ export class MeshHandler {
|
|
|
958
988
|
: { endpoint: this.binding.endpoint, token: ctx.requestId };
|
|
959
989
|
if (primary !== undefined)
|
|
960
990
|
await this.arm(primary, this.now() + parseDuration(req.timeout));
|
|
991
|
+
let lapsedSince;
|
|
992
|
+
let lastReadAt = 0;
|
|
961
993
|
for (;;) {
|
|
962
994
|
if (ctx.signal.cancelled) {
|
|
963
995
|
if (primary !== undefined)
|
|
@@ -968,6 +1000,7 @@ export class MeshHandler {
|
|
|
968
1000
|
if (ended !== undefined)
|
|
969
1001
|
return null;
|
|
970
1002
|
let rows;
|
|
1003
|
+
const readAt = this.now();
|
|
971
1004
|
try {
|
|
972
1005
|
rows = await this.presenceRows();
|
|
973
1006
|
}
|
|
@@ -984,10 +1017,16 @@ export class MeshHandler {
|
|
|
984
1017
|
}
|
|
985
1018
|
if (!rows.some((p) => p.card?.name === name && p.lifecycleUid === uid)) {
|
|
986
1019
|
const reason = rows.some((p) => p.card?.name === name) ? "superseded" : "lapsed";
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1020
|
+
lapsedSince = lapseWindow(lapsedSince, lastReadAt, this.now());
|
|
1021
|
+
if (reason === "superseded" || readAt - lapsedSince >= LAPSE_CONFIRM_MS) {
|
|
1022
|
+
if (primary !== undefined)
|
|
1023
|
+
await this.cancelTimer(primary);
|
|
1024
|
+
return { agent: ev.agent, reason, at: this.now() };
|
|
1025
|
+
}
|
|
990
1026
|
}
|
|
1027
|
+
else
|
|
1028
|
+
lapsedSince = undefined;
|
|
1029
|
+
lastReadAt = readAt;
|
|
991
1030
|
await new Promise((r) => setTimeout(r, WAIT_POLL_MS).unref());
|
|
992
1031
|
}
|
|
993
1032
|
}
|
|
@@ -1151,6 +1190,10 @@ export class MeshHandler {
|
|
|
1151
1190
|
// automatic manager discovery is outside this bounded repair, so it refuses here too.
|
|
1152
1191
|
if (req.placement !== undefined && req.placement.endpoint !== this.binding.endpoint)
|
|
1153
1192
|
throw new EffectError("L4000", "spawn", `spawn(${req.persona}) placement targets endpoint ${JSON.stringify(req.placement.endpoint)} but this run is bound to ${JSON.stringify(this.binding.endpoint)}; refusing rather than dispatching off-binding`);
|
|
1193
|
+
// The manager reads only a boolean `events`, so any other value would arm the default plane
|
|
1194
|
+
// the author was trying to opt out of. Refused before anything is submitted.
|
|
1195
|
+
if (req.events !== undefined && typeof req.events !== "boolean")
|
|
1196
|
+
throw new EffectError("L4000", "spawn", `spawn(${req.persona}) events must be true or false; refusing ${JSON.stringify(req.events)} rather than launching with the default event plane`);
|
|
1154
1197
|
// A recorded goalId is a previous attempt's ACCEPTANCE: the submission landed and its
|
|
1155
1198
|
// identity was bound before the crash. Go straight back to the terminal. An entry that says
|
|
1156
1199
|
// `adoptedFrom` names the goal the ORPHANED spawn submitted, not this step's own request id:
|
|
@@ -1325,7 +1368,9 @@ export class MeshHandler {
|
|
|
1325
1368
|
* goal-bound hold (its expiry commits `failed` reason `turn-deadline`), and this client arms its
|
|
1326
1369
|
* OWN pause under the step's request id — the L4003 authority that survives a dead manager.
|
|
1327
1370
|
* DEATH likewise: the manager's reap hook fails pending turns `agent-down`, and this client
|
|
1328
|
-
* watches presence itself (the L4002 authority when the manager died with the seat).
|
|
1371
|
+
* watches presence itself (the L4002 authority when the manager died with the seat). The watch
|
|
1372
|
+
* reads death the way `wait(down)` does: a superseded incarnation at once, a lapsed row only
|
|
1373
|
+
* once it has stayed absent for {@link LAPSE_CONFIRM_MS}.
|
|
1329
1374
|
*
|
|
1330
1375
|
* Handoff honoring (lang §5.3) happens HERE: the scope's pending memo is spent at every turn's
|
|
1331
1376
|
* begin, and when this turn targets its `to`, the link rides the submission (`handoffFrom`, the
|
|
@@ -1442,6 +1487,8 @@ export class MeshHandler {
|
|
|
1442
1487
|
// The client's OWN deadline authority: minted on the first pass, attached to on re-entry.
|
|
1443
1488
|
await this.arm(primary, deadlineAt);
|
|
1444
1489
|
const actx = await this.actionCtx();
|
|
1490
|
+
let lapsedSince;
|
|
1491
|
+
let lastReadAt = 0;
|
|
1445
1492
|
try {
|
|
1446
1493
|
for (;;) {
|
|
1447
1494
|
if (ctx.signal.cancelled) {
|
|
@@ -1457,6 +1504,7 @@ export class MeshHandler {
|
|
|
1457
1504
|
if (ended !== undefined)
|
|
1458
1505
|
throw new EffectError("L4003", "turn-deadline", `turn(${name}#${uid}) deadline elapsed before a yield`);
|
|
1459
1506
|
let rows;
|
|
1507
|
+
const readAt = this.now();
|
|
1460
1508
|
try {
|
|
1461
1509
|
rows = await this.presenceRows();
|
|
1462
1510
|
}
|
|
@@ -1469,9 +1517,15 @@ export class MeshHandler {
|
|
|
1469
1517
|
}
|
|
1470
1518
|
if (!rows.some((pr) => pr.card?.name === name && pr.lifecycleUid === uid)) {
|
|
1471
1519
|
const reason = rows.some((pr) => pr.card?.name === name) ? "superseded" : "lapsed";
|
|
1472
|
-
|
|
1473
|
-
|
|
1520
|
+
lapsedSince = lapseWindow(lapsedSince, lastReadAt, this.now());
|
|
1521
|
+
if (reason === "superseded" || readAt - lapsedSince >= LAPSE_CONFIRM_MS) {
|
|
1522
|
+
await this.cancelTimer(primary);
|
|
1523
|
+
throw new EffectError("L4002", "turn", `turn(${name}#${uid}) found the agent down (${reason}) before a yield`);
|
|
1524
|
+
}
|
|
1474
1525
|
}
|
|
1526
|
+
else
|
|
1527
|
+
lapsedSince = undefined;
|
|
1528
|
+
lastReadAt = readAt;
|
|
1475
1529
|
await new Promise((r) => setTimeout(r, WAIT_POLL_MS).unref());
|
|
1476
1530
|
}
|
|
1477
1531
|
}
|
|
@@ -1483,11 +1537,6 @@ export class MeshHandler {
|
|
|
1483
1537
|
throw e;
|
|
1484
1538
|
}
|
|
1485
1539
|
}
|
|
1486
|
-
/** Map a turn goal's terminal onto the effect's contract: `succeeded` carries the TurnResult
|
|
1487
|
-
* (a handoff addressee resolved against the roster — L4005 outside it, L4004 across
|
|
1488
|
-
* worktrees), `failed` splits on the manager's recorded reason, `cancelled` unwinds. The
|
|
1489
|
-
* consumed notices are marked HERE, by the goal that carried them, tolerating the re-entry
|
|
1490
|
-
* conflict (a crash between the terminal and the mark re-marks on resume). */
|
|
1491
1540
|
/**
|
|
1492
1541
|
* A yielded handoff's refusal class: L4005 when the addressee is not in this run's roster,
|
|
1493
1542
|
* L4004 when it sits in a different worktree than the seat handing off, undefined when the
|
|
@@ -1503,6 +1552,11 @@ export class MeshHandler {
|
|
|
1503
1552
|
return "L4004";
|
|
1504
1553
|
return undefined;
|
|
1505
1554
|
}
|
|
1555
|
+
/** Map a turn goal's terminal onto the effect's contract: `succeeded` carries the TurnResult
|
|
1556
|
+
* (a handoff addressee resolved against the roster — L4005 outside it, L4004 across
|
|
1557
|
+
* worktrees), `failed` splits on the manager's recorded reason, `cancelled` unwinds. The
|
|
1558
|
+
* consumed notices are marked HERE, by the goal that carried them, tolerating the re-entry
|
|
1559
|
+
* conflict (a crash between the terminal and the mark re-marks on resume). */
|
|
1506
1560
|
async turnOutcome(req, fact, ext, scope, name, uid) {
|
|
1507
1561
|
if (fact.state === "cancelled")
|
|
1508
1562
|
throw new Cancelled(`the turn goal for ${name}#${uid} was cancelled`);
|
|
@@ -2289,6 +2343,26 @@ export { waitConsumerName, waitConsumerConfig } from "@cotal-ai/core";
|
|
|
2289
2343
|
/** How long one poll of a wait's consumer blocks. The deadline itself is durable; this is only how
|
|
2290
2344
|
* late its observation can be, and a shorter poll buys latency at the cost of fetch traffic. */
|
|
2291
2345
|
const WAIT_POLL_MS = 2_000;
|
|
2346
|
+
/**
|
|
2347
|
+
* How long an incarnation's presence row must stay absent before `turn` and `wait(down)` read it as
|
|
2348
|
+
* `lapsed`. A row is gone 6s after its last heartbeat, and a live seat whose connector stalls for
|
|
2349
|
+
* longer (host load, a reconnect) comes back under the same lifecycle uid on its next heartbeat. One
|
|
2350
|
+
* absent read failed a turn L4002 while the seat went on working (#2344). Five liveness windows lets
|
|
2351
|
+
* a stalled seat renew, and stays short against a turn's deadline. A `superseded` read needs no
|
|
2352
|
+
* wait: a different incarnation already holds the name. The window is measured from the end of the
|
|
2353
|
+
* read that opened it to the start of the read that closes it, over reads {@link lapseWindow} chains.
|
|
2354
|
+
*/
|
|
2355
|
+
const LAPSE_CONFIRM_MS = 30_000;
|
|
2356
|
+
/**
|
|
2357
|
+
* The longest span from the start of one presence read to the end of the next across which an
|
|
2358
|
+
* absent row still counts as continuously absent: the presence bucket's 6s TTL. A heartbeat keeps
|
|
2359
|
+
* the row live at least that long, so a renewal cannot fall between two reads that close. A slower
|
|
2360
|
+
* read or a run of failed scans leaves the gap unobserved, and the lapse window starts over.
|
|
2361
|
+
*/
|
|
2362
|
+
const PRESENCE_GAP_MS = 6_000;
|
|
2363
|
+
/** The start of the lapse window after an absent read that ended at `now`: kept when the previous
|
|
2364
|
+
* read began within {@link PRESENCE_GAP_MS}, otherwise this read opens a new one. */
|
|
2365
|
+
const lapseWindow = (lapsedSince, lastReadAt, now) => lapsedSince === undefined || now - lastReadAt > PRESENCE_GAP_MS ? now : lapsedSince;
|
|
2292
2366
|
/**
|
|
2293
2367
|
* Read a spawn's `supervise` as the restart policy this host can enforce: `restarts`, a positive
|
|
2294
2368
|
* integer of in-window process deaths the manager may restart under the same handle, and
|
|
@@ -2320,6 +2394,12 @@ export function readSupervise(raw, persona) {
|
|
|
2320
2394
|
throw new Error(`spawn(${persona}): supervise.restarts must be a positive integer`);
|
|
2321
2395
|
return { restarts, windowMs: windowMs ?? parseDuration("10m") };
|
|
2322
2396
|
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Read a spawn's `permits` as the budgets this host can enforce: `turns`, a positive integer of
|
|
2399
|
+
* turns the run may dispatch to the agent, and `wallClock`, a duration from the spawn after which
|
|
2400
|
+
* no turn is admitted. Anything else (tokens, spend) is a budget this host has no meter for, and a
|
|
2401
|
+
* budget it cannot enforce is refused loudly rather than accepted as a silent no-op.
|
|
2402
|
+
*/
|
|
2323
2403
|
function readPermits(raw, persona) {
|
|
2324
2404
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
2325
2405
|
throw new Error(`spawn(${persona}): permits must be a record of budgets, got ${JSON.stringify(raw)}`);
|
|
@@ -2341,7 +2421,6 @@ function readPermits(raw, persona) {
|
|
|
2341
2421
|
}
|
|
2342
2422
|
return out;
|
|
2343
2423
|
}
|
|
2344
|
-
/** How many incomplete presence scans the worktree guard tolerates before it fails loudly. */
|
|
2345
2424
|
/**
|
|
2346
2425
|
* The keyed view of an append log: the last record written for each step, in the order the run
|
|
2347
2426
|
* first reached it.
|
|
@@ -2361,6 +2440,7 @@ function foldEntries(entries) {
|
|
|
2361
2440
|
return entries;
|
|
2362
2441
|
return new Journal({ run: first.run, entries, readOnly: true }).entries();
|
|
2363
2442
|
}
|
|
2443
|
+
/** How many incomplete presence scans the worktree guard tolerates before it fails loudly. */
|
|
2364
2444
|
const WORKTREE_SCAN_ATTEMPTS = 5;
|
|
2365
2445
|
/** How often an action's durable terminal fact is looked for. Same argument as `WAIT_POLL_MS`. */
|
|
2366
2446
|
const GOAL_POLL_MS = 2_000;
|
|
@@ -2378,6 +2458,12 @@ const TURN_ACCEPT_DEADLINE_MS = 30_000;
|
|
|
2378
2458
|
* attempt costs a describe and an invoke round trip and never an elapsed deadline: a call nobody
|
|
2379
2459
|
* answers raises its own `deadline-exceeded`, which is not a bind refusal and is not re-issued. */
|
|
2380
2460
|
const BIND_SPLIT_REISSUES = 8;
|
|
2461
|
+
/** How many class-rail despawns a discharge sends before it stops waiting for the allocating
|
|
2462
|
+
* manager to answer. Each is one {@link MeshHandler.invokeManager} call, whose describe and invoke
|
|
2463
|
+
* both land on the allocator with probability 1/m^2 per trip in a space of m managers, so one call
|
|
2464
|
+
* reaches it with probability (1 - ((m-1)/m)^9) / m: about 1/2 for m = 2 and 0.23 for m = 4. All
|
|
2465
|
+
* 65 then miss with probability about 2^-65 and 4e-8. */
|
|
2466
|
+
const DESPAWN_ROUTE_ATTEMPTS = 64;
|
|
2381
2467
|
/** A step key's enclosing scope: the journal's own rendering (`entry.scope`), re-derived so the
|
|
2382
2468
|
* live path and the adoption rebuild key the handoff memos identically. */
|
|
2383
2469
|
function scopeOf(key) {
|
|
@@ -2387,9 +2473,6 @@ function scopeOf(key) {
|
|
|
2387
2473
|
/** The terminal wait a discharge grants a goal whose entry recorded no readiness window (the
|
|
2388
2474
|
* crash-before-bind case). Matches the manager's default readiness budget. */
|
|
2389
2475
|
const DISCHARGE_TERMINAL_BOUND_MS = 30_000;
|
|
2390
|
-
/** The manager `spawn` args a {@link SpawnRequest} submits: persona names the persona file
|
|
2391
|
-
* (`name`), `join` becomes the seat's channel subscriptions. `permits` stay on the run (they
|
|
2392
|
-
* bind at `turn`); `supervise` travels because the manager is who restarts the process. */
|
|
2393
2476
|
/**
|
|
2394
2477
|
* #1616 proof item 5 — ALIAS NORMALIZATION POLICY. One clone reached through a symlink and through
|
|
2395
2478
|
* its realpath is ONE writable directory, and the single-writer rule keys on identity, so the two
|
|
@@ -2420,6 +2503,10 @@ function readCwdResolution(value) {
|
|
|
2420
2503
|
return undefined;
|
|
2421
2504
|
return { cwd: r.cwd, endpoint: r.endpoint, instanceId: r.instanceId, ...(typeof r.host === "string" ? { host: r.host } : {}) };
|
|
2422
2505
|
}
|
|
2506
|
+
/** The manager `spawn` args a {@link SpawnRequest} submits: persona names the persona file
|
|
2507
|
+
* (`name`), `join` becomes the seat's channel subscriptions. `permits` stay on the run (they
|
|
2508
|
+
* bind at `turn`); `supervise` travels because the manager is who restarts the process, and
|
|
2509
|
+
* `events` because the manager is who arms the event plane (`false` is `--no-events`). */
|
|
2423
2510
|
export function spawnArgs(req) {
|
|
2424
2511
|
return {
|
|
2425
2512
|
name: req.persona,
|
|
@@ -2433,6 +2520,7 @@ export function spawnArgs(req) {
|
|
|
2433
2520
|
...(req.role !== undefined ? { role: req.role } : {}),
|
|
2434
2521
|
...(req.join !== undefined && req.join.length > 0 ? { subscribe: req.join.map((c) => c.channel) } : {}),
|
|
2435
2522
|
...(req.supervise !== undefined ? { supervise: readSupervise(req.supervise, req.persona) } : {}),
|
|
2523
|
+
...(req.events !== undefined ? { events: req.events } : {}),
|
|
2436
2524
|
};
|
|
2437
2525
|
}
|
|
2438
2526
|
/** The acceptance-floor fields worth binding: the allocated identity a discharge despawns by, and
|