@cotal-ai/manager 0.16.0 → 0.17.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/attach-endpoint.d.ts +53 -59
- package/dist/attach-endpoint.d.ts.map +1 -1
- package/dist/attach-endpoint.js +120 -244
- package/dist/attach-endpoint.js.map +1 -1
- package/dist/commands.js +9 -4
- package/dist/commands.js.map +1 -1
- package/dist/console/app.js +68 -30
- package/dist/console/index.html +3 -2
- package/dist/console/session-bundle.js +9736 -0
- package/dist/console-crypto-shim.d.ts +16 -0
- package/dist/console-crypto-shim.d.ts.map +1 -0
- package/dist/console-crypto-shim.js +20 -0
- package/dist/console-crypto-shim.js.map +1 -0
- package/dist/console-session-entry.d.ts +2 -0
- package/dist/console-session-entry.d.ts.map +1 -0
- package/dist/console-session-entry.js +26 -0
- package/dist/console-session-entry.js.map +1 -0
- package/dist/endpoint-evict.d.ts +29 -0
- package/dist/endpoint-evict.d.ts.map +1 -0
- package/dist/endpoint-evict.js +80 -0
- package/dist/endpoint-evict.js.map +1 -0
- package/dist/manager-service-contract.d.ts +132 -0
- package/dist/manager-service-contract.d.ts.map +1 -0
- package/dist/manager-service-contract.js +332 -0
- package/dist/manager-service-contract.js.map +1 -0
- package/dist/manager.d.ts +504 -22
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +2193 -257
- package/dist/manager.js.map +1 -1
- package/dist/runtime/pty.d.ts.map +1 -1
- package/dist/runtime/pty.js +15 -2
- package/dist/runtime/pty.js.map +1 -1
- package/dist/session/bridge.d.ts +63 -0
- package/dist/session/bridge.d.ts.map +1 -0
- package/dist/session/bridge.js +157 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/establish.d.ts +212 -0
- package/dist/session/establish.d.ts.map +1 -0
- package/dist/session/establish.js +174 -0
- package/dist/session/establish.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +15 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/plane.d.ts +120 -0
- package/dist/session/plane.d.ts.map +1 -0
- package/dist/session/plane.js +327 -0
- package/dist/session/plane.js.map +1 -0
- package/dist/static-lifecycle.d.ts +97 -0
- package/dist/static-lifecycle.d.ts.map +1 -0
- package/dist/static-lifecycle.js +262 -0
- package/dist/static-lifecycle.js.map +1 -0
- package/package.json +9 -5
package/dist/manager.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ControlReply, MeshLaunchAgent, SecretStore } from "@cotal-ai/core";
|
|
2
2
|
import { type RuntimeMode } from "./runtime/index.js";
|
|
3
|
+
import { type Identity } from "@cotal-ai/core";
|
|
3
4
|
/** Backstop for the detached-launch readiness race (#159 B1). `startAgent` waits on two REAL outcomes —
|
|
4
5
|
* the assigned id joining the mesh (presence) = started, the child process exiting = failed — NOT a
|
|
5
6
|
* liveness-inferring timer. This is only the last-resort bound for "neither happened in time": the launch
|
|
@@ -18,12 +19,15 @@ export interface ManagerOptions {
|
|
|
18
19
|
workspaceRoot?: string;
|
|
19
20
|
/** Port for the console + attach HTTP/WS endpoint. 0 → ephemeral. */
|
|
20
21
|
consolePort?: number;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
22
|
+
/** P2 item 6: the broker's WebSocket listener port (loopback), allocated by `cotal up`. When set,
|
|
23
|
+
* the console page becomes a mesh §13.6 session client — `POST /session/<name>` returns the grant +
|
|
24
|
+
* a per-session cred + this ws URL. Absent ⇒ no console session client (the route 503s). */
|
|
25
|
+
wsPort?: number;
|
|
26
|
+
/** Bind address for the console endpoint, and the host it advertises in {@link Manager.consoleUrl}.
|
|
27
|
+
* Defaults to loopback, which keeps the console reachable only from this machine. Set it only if
|
|
28
|
+
* you intend to expose the console (see the security note on {@link AttachEndpoint}). A wildcard
|
|
29
|
+
* here binds every interface and still advertises loopback, since a wildcard is not an address a
|
|
30
|
+
* client can dial. The TERMINAL is unaffected either way: it rides the mesh session, not this face. */
|
|
27
31
|
attachHost?: string;
|
|
28
32
|
/** Internal/test override for the preservation child-exit deadline. */
|
|
29
33
|
preserveStopTimeoutMs?: number;
|
|
@@ -43,6 +47,13 @@ export interface ManagerOptions {
|
|
|
43
47
|
* unchanged. It must be the SAME store the delivery daemon reads (`runDelivery(args, store)`), or a
|
|
44
48
|
* hosted remint writes one store while the daemon reads another and rides to expiry. */
|
|
45
49
|
secretStore?: SecretStore;
|
|
50
|
+
/** P2 item 6: the global ceiling on concurrently live §13.6 sessions this manager will serve.
|
|
51
|
+
* Defaults to {@link MAX_LIVE_SESSIONS_DEFAULT}. Each session mints a credential and opens its
|
|
52
|
+
* own connection, and establishment is caller-triggered, so this is the process-level resource
|
|
53
|
+
* bound; exceeding it refuses `resource-exhausted` before either happens. Operator-set because
|
|
54
|
+
* the right number is deployment-shaped — the browser console holds one session per open pane.
|
|
55
|
+
* {@link ManagerSessionPlaneDeps.maxSessions} carries the per-caller-scoping residual. */
|
|
56
|
+
maxSessions?: number;
|
|
46
57
|
}
|
|
47
58
|
export type ManagerMaintenanceState = "active" | "preserving" | "preserved";
|
|
48
59
|
export type ManagerResumeIdentity = {
|
|
@@ -230,11 +241,66 @@ export interface StartAgentOpts {
|
|
|
230
241
|
* through a pluggable {@link Runtime} (pty by default). It does NOT proxy agent
|
|
231
242
|
* mesh traffic — terminal I/O streams over its own attach endpoint instead.
|
|
232
243
|
*/
|
|
244
|
+
/** Runtime hooks the spawn-as-action serve path (P2 item 2) injects into {@link Manager.startAgent}.
|
|
245
|
+
* Roster boot and the blocking callers pass none (unchanged behavior). */
|
|
246
|
+
export interface SpawnHooks {
|
|
247
|
+
/** Fires synchronously AFTER the incarnation identity (nkey + lifecycleUid) is minted but BEFORE
|
|
248
|
+
* any provision/side-effect — the accept seam: it binds the goal and replies the acceptance. A
|
|
249
|
+
* THROW here aborts the spawn before provisioning (the existing catch returns the failure and the
|
|
250
|
+
* finally releases the reserve, so no footprint leaks) — this is the bind-conflict refusal path. */
|
|
251
|
+
onAccepted?: (allocated: {
|
|
252
|
+
name: string;
|
|
253
|
+
identity: Identity;
|
|
254
|
+
lifecycleUid: string;
|
|
255
|
+
agentTriple: {
|
|
256
|
+
owner: string;
|
|
257
|
+
actor: string;
|
|
258
|
+
uid: string;
|
|
259
|
+
};
|
|
260
|
+
}) => Promise<void> | void;
|
|
261
|
+
/** Fires once the child process has been launched (the "launched" progress edge). */
|
|
262
|
+
onLaunched?: () => void;
|
|
263
|
+
/** Fires at the readiness verdict (presence join → succeeded / process exit → failed / window
|
|
264
|
+
* elapsed → uncertain), carrying the succeeded reply data — the async serve body commits the goal
|
|
265
|
+
* terminal + emits the final progress event here. Awaited, but the caller swallows its own errors
|
|
266
|
+
* so a terminal-commit failure never disrupts the (already-replied) spawn. */
|
|
267
|
+
onOutcome?: (outcome: {
|
|
268
|
+
kind: "succeeded" | "failed" | "uncertain";
|
|
269
|
+
data?: unknown;
|
|
270
|
+
}) => Promise<void> | void;
|
|
271
|
+
/** Fires when this spawn ends with its goal's terminal owned by ANOTHER path — today only a
|
|
272
|
+
* despawn/stop that lands inside the readiness window, whose own handler commits `cancel`.
|
|
273
|
+
* It commits nothing; it only claims the terminal so the post-accept fallback does not
|
|
274
|
+
* manufacture a `failed` from the non-ok reply and race the real `cancel`. Without it, a
|
|
275
|
+
* deliberate stop mid-launch reports the agent as having died on launch. */
|
|
276
|
+
onTerminalDeferred?: () => void;
|
|
277
|
+
}
|
|
278
|
+
/** The spawn-as-action acceptance (P2 item 2 floor): the ALLOCATED agent identity (name + the
|
|
279
|
+
* addressing triple item-1 addresses by) plus the goal coordinates (goalId = the request id) and
|
|
280
|
+
* the executor coordinate (the manager incarnation its terminal fences on). Carries NO secret
|
|
281
|
+
* material (pin 7); it names what was actually allocated, never a requested-but-unallocated name. */
|
|
282
|
+
export interface SpawnAcceptance {
|
|
283
|
+
name: string;
|
|
284
|
+
owner: string;
|
|
285
|
+
actor: string;
|
|
286
|
+
uid: string;
|
|
287
|
+
goalId: string;
|
|
288
|
+
fingerprint: string;
|
|
289
|
+
executor: {
|
|
290
|
+
lifecycleUid: string;
|
|
291
|
+
epoch: number;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
233
294
|
export declare class Manager {
|
|
234
295
|
private readonly space;
|
|
235
296
|
private readonly servers;
|
|
297
|
+
/** P2 item 6: the broker ws listener port (loopback) `cotal up` allocated, for the console session
|
|
298
|
+
* client's wsUrl. Undefined ⇒ no console session client (POST /session 503s). */
|
|
299
|
+
private readonly wsPort?;
|
|
236
300
|
private readonly name;
|
|
237
301
|
private readonly workspaceRoot;
|
|
302
|
+
/** P2 item 6: the operator-set global live-session ceiling (see {@link ManagerOptions.maxSessions}). */
|
|
303
|
+
private readonly maxSessions?;
|
|
238
304
|
/** The ONE secret store for every kind this manager touches (daemon-cred remint + agent kinds).
|
|
239
305
|
* See {@link ManagerOptions.secretStore}. */
|
|
240
306
|
private readonly secrets;
|
|
@@ -264,6 +330,82 @@ export declare class Manager {
|
|
|
264
330
|
* same-name SUCCESSOR (which can spawn after the hold clears but before this flight's `nc.close`
|
|
265
331
|
* yield settles) never joins the predecessor's rail request and skips its own retirement. */
|
|
266
332
|
private retiringFlight;
|
|
333
|
+
/** Wire principals of RETIRED static incarnations (Unit B, F5(a)): populated at every completed
|
|
334
|
+
* static terminal and from the boot sweep's retired slot rows, so a copied credential of a
|
|
335
|
+
* retired incarnation is refused at the control surface even across a manager restart. The
|
|
336
|
+
* durable truth is the slot row + principal-keyed head; this set is the in-memory index of it
|
|
337
|
+
* (one string per retired incarnation — bounded by lifecycle count, never pruned in-process). */
|
|
338
|
+
private readonly retiredPrincipals;
|
|
339
|
+
/** This manager process's own incarnation uid (SPEC 13.1; minted once per supervisor process,
|
|
340
|
+
* never reused across restarts) — the endpoint's presence key AND the `managerInstance` audit
|
|
341
|
+
* coordinate every static activation records. */
|
|
342
|
+
private readonly managerLifecycleUid;
|
|
343
|
+
/** The persisted LOGICAL instance id (SPEC 13.6 item 7, P2 item 3): STABLE across restart, so a
|
|
344
|
+
* restart re-registers the SAME id with an ADVANCED epoch through the §13.1 gate (the successor
|
|
345
|
+
* fences the predecessor's epoch — the (i) fence bites on a real restart). It is the registration
|
|
346
|
+
* instanceId, the served-status id, the goal spec's executor.instanceId, the epe route, and the
|
|
347
|
+
* resolveExecutorEpoch key — DISTINCT from {@link managerLifecycleUid} (per-process: the presence
|
|
348
|
+
* node uid + the managerInstance audit coordinate every static activation records). Set in start(). */
|
|
349
|
+
private managerInstanceId;
|
|
350
|
+
/** The persisted serve nkey identity: reusing the SAME principal across restart keeps
|
|
351
|
+
* {@link provisionEndpointGateOpen} idempotent (no core barrier change) and gives verified
|
|
352
|
+
* eviction a stable target (the predecessor's connections under this principal). Set in start(). */
|
|
353
|
+
private managerServeIdentity;
|
|
354
|
+
/** P2 item 1 (1a-serve): the manager's v0.4 service-endpoint serve state — the serve handle +
|
|
355
|
+
* its dedicated connection, the STABLE serve identity (renewals re-mint the same nkey), the
|
|
356
|
+
* branded serve grant, and the CURRENT credential (the connection's authenticator reads it on
|
|
357
|
+
* every (re)connect, so a renewal is adopted without re-registration). Absent on open meshes,
|
|
358
|
+
* in user mode (the named 1a follow-up), and before registration completes. */
|
|
359
|
+
private serviceServe?;
|
|
360
|
+
/** P2 item 2 (spawn-as-action): the SELF-MEDIATED goal-writer connection + ActionContext — a
|
|
361
|
+
* standing connection DISJOINT from the serve credential (Q2), scoped to exactly this endpoint's
|
|
362
|
+
* goal bind/terminal facts + goal-record writes ({@link goalWriterGrants}). Auth mode mints the
|
|
363
|
+
* `goal-writer` cred; an open mesh uses a bare connection (no credential system to mint from).
|
|
364
|
+
* `gate` (auth mode) is the own-issuance-gate READER for the must-5 (a) currency belt — the
|
|
365
|
+
* manager reads its OWN `epgate.<e>.<iid>` epoch over this connection before a terminal commit
|
|
366
|
+
* and skips a superseded commit (the fast-fail belt paired with the (b) barrier-revoke fence). */
|
|
367
|
+
private goalWriter?;
|
|
368
|
+
/** P2 item 2 must-5 (b): the STABLE goal-writer identity (auth mode) — minted once at
|
|
369
|
+
* registration alongside the serve identity; a renewal re-mints the SAME nkey with a fresh
|
|
370
|
+
* bounded exp and re-stages its distinct credId into the §13.1 revocation family. The current
|
|
371
|
+
* goal-writer credential is minted INSIDE {@link registerManagerService}'s run block (fence
|
|
372
|
+
* live) and stashed here for {@link startGoalWriter} to build the standing connection from. */
|
|
373
|
+
private goalWriterIdentity?;
|
|
374
|
+
private goalWriterCreds?;
|
|
375
|
+
/** P2 item 6: the manager's ONE §13.6 session plane — offer mint + one-use redeem + PTY-bridge
|
|
376
|
+
* standup for `attach`. The face's establisher and the CLI attach handler both call THIS one
|
|
377
|
+
* plane; the manager never constructs a second. Undefined until {@link startSessionPlane}. */
|
|
378
|
+
private sessionPlane?;
|
|
379
|
+
/** P2 item 6: the standing session-LEDGER connection + its mutable creds holder (the authenticator
|
|
380
|
+
* presents the refreshed cred on the next reconnect after a half-TTL renewal — the goal-writer
|
|
381
|
+
* precedent). Auth mode only; an open mesh runs the plane over a bare connection. */
|
|
382
|
+
private sessionLedgerConn?;
|
|
383
|
+
/** P2 item 6: credentialId → the nkey that credential was minted for, for the live per-session
|
|
384
|
+
* SERVING credentials. The §13.1 ledger row records the holder principal, and the row is written
|
|
385
|
+
* at stage time (after the mint), so the two steps need this one hop. Entries are dropped at
|
|
386
|
+
* revoke; a session that never staged drops its entry when the manager exits. */
|
|
387
|
+
private readonly sessionServingKeys;
|
|
388
|
+
/** P2 item 6: the STABLE session-LEDGER identity (auth mode) — minted once at registration
|
|
389
|
+
* alongside the serve + goal-writer identities; a renewal re-mints the SAME nkey with a fresh
|
|
390
|
+
* bounded exp and re-stages its distinct credId into the §13.1 revocation family. The current
|
|
391
|
+
* credential is minted INSIDE {@link registerManagerService}'s run block and stashed here. */
|
|
392
|
+
private sessionLedgerIdentity?;
|
|
393
|
+
private sessionLedgerCreds?;
|
|
394
|
+
/** P2 item 2: the acceptance replied for each in-flight goalId this incarnation accepted, so an
|
|
395
|
+
* idempotent same-goalId retry serves the IDENTICAL acceptance (same allocated name/triple) without
|
|
396
|
+
* a second spawn. Durable cross-incarnation reconstruction rides the must-5 goal-index; here the
|
|
397
|
+
* live map covers same-incarnation retries, with the committed result fact as the fallback. */
|
|
398
|
+
private goalAcceptances;
|
|
399
|
+
/** P2 item 2 must-5 Q-B: the boot reconcile of the durable goal index runs ONCE at start (a
|
|
400
|
+
* fresh incarnation inherits the endpoint's accepted-but-unterminal goals from any predecessor).
|
|
401
|
+
* Spawn-as-action REFUSES to accept until it completes, so the sweep never races a live goal's
|
|
402
|
+
* acceptance (settling one mid-flight would steal its real terminal). */
|
|
403
|
+
private goalReconcileDone;
|
|
404
|
+
/** P2 item 2 (M4): the live spawn goal ref for each managed agent name, so a despawn MID-GOAL
|
|
405
|
+
* drives the cancel path (transition -> cancel terminal). Cleared when the goal terminalizes. */
|
|
406
|
+
private agentGoals;
|
|
407
|
+
/** Process start, for the served `status` uptime. */
|
|
408
|
+
private readonly startedAtMs;
|
|
267
409
|
/** SINGLE-FLIGHT guard for {@link deprovision} (INT-2/C): one in-flight teardown per
|
|
268
410
|
* (name, lifecycleUid). The detached freeSlot teardown and every same-name-spawn nudge that
|
|
269
411
|
* re-drives it JOIN one promise instead of launching a SECOND, concurrent teardown. Without it,
|
|
@@ -324,6 +466,20 @@ export declare class Manager {
|
|
|
324
466
|
* (no responder) is recorded honestly: each daemon's own 75% renewal timer remains the adoption backstop.
|
|
325
467
|
* Never throws — renewal failure must be LOUD (log + record), not fatal to the supervisor. */
|
|
326
468
|
private renewDaemonCreds;
|
|
469
|
+
/** Warn, on every renewal pass, when a $SYS credential is at or past its renewal point.
|
|
470
|
+
*
|
|
471
|
+
* The manager is the renewal owner for every credential it CAN re-sign, and these two are the ones
|
|
472
|
+
* it cannot: they are `rotation-renewed`, so no resident process re-mints them and they simply die
|
|
473
|
+
* on their 30-day horizon. Before this, a mesh that never ran `doctor auth` got no signal at all,
|
|
474
|
+
* it discovered the expiry as an "Authorization Violation" in the delivery log and a refused
|
|
475
|
+
* membership adoption, weeks after the warning would have been actionable (#338). The pass runs
|
|
476
|
+
* every half-TTL of the 24h class, so this repeats about twice a day for the ~7 days between the
|
|
477
|
+
* renewal point and expiry: loud enough to be seen, bounded enough not to be noise.
|
|
478
|
+
*
|
|
479
|
+
* Diagnostic only, and deliberately non-fatal: renewal is an operator action (`cotal down` then
|
|
480
|
+
* `cotal up --rotate-sys`, which needs a broker restart), so the manager must report it, never
|
|
481
|
+
* attempt it. An absent file is the unprovisioned space, reported by the daemon that needs it. */
|
|
482
|
+
private warnOnSystemCredExpiry;
|
|
327
483
|
/** Admit one lifecycle/control operation while active. The synchronous increment is the fence:
|
|
328
484
|
* preserveState flips state before its first await, so work is either counted or rejected. */
|
|
329
485
|
private beginLifecycle;
|
|
@@ -365,19 +521,89 @@ export declare class Manager {
|
|
|
365
521
|
private teardownManagedAgents;
|
|
366
522
|
private stopRetainedAgentsOnExit;
|
|
367
523
|
stop(): Promise<void>;
|
|
368
|
-
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
|
|
524
|
+
/** Stop the v0.4 service-endpoint serve loop (drain subscriptions, await in-flight handlers)
|
|
525
|
+
* and drop its dedicated connection. Best-effort by design — both exit paths (graceful stop,
|
|
526
|
+
* lease-loss fail-close) must complete their remaining teardown even if the broker is gone. */
|
|
527
|
+
private stopServiceServe;
|
|
528
|
+
/** Refresh THIS instance's liveness lease before the bucket TTL expires it. On loss (missed the TTL —
|
|
529
|
+
* this instance stalled past the renew window) FAIL CLOSED for THIS INSTANCE ONLY: stop serving +
|
|
530
|
+
* tear down OUR managed agents + exit, so a stalled instance can't keep double-processing under a key
|
|
531
|
+
* a same-id restart may re-acquire. Keyed per instance, so this NEVER frees or touches a sibling
|
|
532
|
+
* manager's key and NEVER freezes the space (security pin 6) — the sibling keeps serving. We do NOT
|
|
533
|
+
* re-acquire (a same-id restart may already be live) and do NOT release the key (it may be the
|
|
534
|
+
* restart's). A DIFFERENT instance losing ITS key is a separate, independent event. */
|
|
372
535
|
private renewLease;
|
|
373
|
-
private
|
|
536
|
+
private opFinalizeResume;
|
|
537
|
+
private opCommitResume;
|
|
538
|
+
private opResumePreserved;
|
|
539
|
+
private opPreservationCtl;
|
|
540
|
+
/** The ONE shared control-admission chokepoint (P2 item 1, checklist 3/8) BOTH dispatch doors
|
|
541
|
+
* run — the v0.3 `ctl` door ({@link handle}) and the v0.4 `ep` service handlers
|
|
542
|
+
* ({@link serveGated}): the maintenance/resume fence (`beginLifecycle`: a resume-pending or
|
|
543
|
+
* non-active manager accepts no ordinary control work) and then the F5(a) membership gate
|
|
544
|
+
* ({@link lifecycleMembershipRefusal}: a retiring/terminalizing/retired managed incarnation's
|
|
545
|
+
* AUTHENTICATED principal holds no control authority even with a valid JWT). Refusal carries
|
|
546
|
+
* WHICH fence refused so the ep door can map onto the §13.3 catalog; admission returns the
|
|
547
|
+
* accepted-work release. Never re-implemented per door — a fence on one door is a bypass. */
|
|
548
|
+
private admitControl;
|
|
549
|
+
/** Run one v0.4 service-command handler through the SHARED admission chokepoint
|
|
550
|
+
* ({@link admitControl}) on the broker-authenticated caller principal, mapping the two fences
|
|
551
|
+
* onto the §13.3 catalog: maintenance/resume → `unavailable`, F5(a) membership →
|
|
552
|
+
* `permission-denied`. The serve boundary publishes the structured error reply. */
|
|
553
|
+
private serveGated;
|
|
554
|
+
/** The ep door's ADMIN flag for a caller (the 1c tier refinement). Static mesh: `true` — the
|
|
555
|
+
* admin-grade rows (any-mode despawn/attach, the `manager.admin` family, `launch`) are minted
|
|
556
|
+
* only into operator instruments (§13.2: `any` is operator-policy-mintable; the agent/spawn
|
|
557
|
+
* rollups never carry them), so REACHING the handler is holding the admin tier, exactly as
|
|
558
|
+
* holding `ctl.<admin>` is today. User mesh: the caller's CURRENT ledger scope must carry
|
|
559
|
+
* `admin` — the same fresh-read authority {@link psOwnerFilter} consults, so a revoked scope
|
|
560
|
+
* demotes the very next call even on a still-valid bearer. Fail-closed: an unreadable ledger
|
|
561
|
+
* authorizes nothing. NAMED RESIDUAL (critic, 1c.2b): the static `true` has no serve-time
|
|
562
|
+
* re-check — a LEAKED static admin instrument keeps its reach until the credential's bounded
|
|
563
|
+
* TTL (the one-shot 5-minute profile), the same static-revoke≠reconnect-death class ruled
|
|
564
|
+
* across this campaign; static revocation is the TTL, not a ledger. */
|
|
565
|
+
private epAdminReach;
|
|
566
|
+
/** A targeted request's admin flag: mode `any` (the operator instrument's cross-agent form,
|
|
567
|
+
* rev 3) resolves through {@link epAdminReach}; a user-mode any-mode caller whose CURRENT
|
|
568
|
+
* ledger row lost `admin` since its rows were minted refuses loud rather than silently
|
|
569
|
+
* downgrading to the owner path (the request's declared mode is honored or denied, never
|
|
570
|
+
* reinterpreted). Owner mode is always the privileged (own-domain) path. */
|
|
571
|
+
private epAnyModeAdmin;
|
|
572
|
+
/** The v0.4 typed command table (P2 item 1, slice 1b): every ordinary handler runs the SHARED
|
|
573
|
+
* admission chokepoint ({@link serveGated}) and then delegates to the SAME op core the ctl
|
|
574
|
+
* door dispatches (checklist 8: one core, two thin doors). The resume/preservation family
|
|
575
|
+
* deliberately BYPASSES serveGated — exactly as it sits before {@link admitControl} on the ctl
|
|
576
|
+
* door (those ops must run while `resumeRequired` fences ordinary work) — riding its own state
|
|
577
|
+
* fences; its ep gate is the admin-grade `manager.admin` capability grant (the 1b rule: static
|
|
578
|
+
* admin-class commands are capability-gated + untargeted, never a fabricated ledger mode).
|
|
579
|
+
*
|
|
580
|
+
* TIER SEMANTICS on the ep door (the 1c grant-migration table): the tier lives in the CALLER'S
|
|
581
|
+
* GRANT, refined per-op exactly as the ctl doors refine their subject tier. Owner-mode
|
|
582
|
+
* `despawn`/`attach` keep the privileged semantics (`admin=false`, own-domain via
|
|
583
|
+
* {@link authorizeNamed}) — every spawn-capable agent holds those rows. ANY-mode requests are
|
|
584
|
+
* the operator instrument's cross-agent reach (rev 3): the any-mode subject row is mintable
|
|
585
|
+
* only under operator policy (§13.2), so on a static mesh holding it IS the admin tier, and in
|
|
586
|
+
* user mode the caller's CURRENT ledger scope must still carry `admin`
|
|
587
|
+
* ({@link epAdminReach}, the same fresh-read authority `psOwnerFilter` consults). The
|
|
588
|
+
* `manager.admin` family (purge + the resume/preservation ops) is capability-gated at mint AND
|
|
589
|
+
* re-checked at serve time via {@link epAdminReach} (the `adminGated` wrapper) so a user's
|
|
590
|
+
* revoked scope demotes the next call. `launch` is OWNER-EQUALITY on this door for everyone
|
|
591
|
+
* (freelance HIGH #2): the deploy path is its only consumer and stamps the caller's own owner,
|
|
592
|
+
* so cross-owner launch was a ctl-tier incidental never exercised, and keying it on the actor's
|
|
593
|
+
* ledger scope broke the deployer-view attenuation - uniform owner-equality is the safe tier.
|
|
594
|
+
* TWO DELIBERATE NARROWINGS vs the ctl doors (NOT bit-exact parity, panel-accepted): (1)
|
|
595
|
+
* `define-persona` is `admin=false` for everyone (own-persona discipline; no ep consumer needs
|
|
596
|
+
* cross-owner persona writes - an operator redefines via config, not the wire), where the ctl
|
|
597
|
+
* admin tier allowed operator cross-owner redefine; (2) launch is owner-equality-only, above.
|
|
598
|
+
* Both are least-privilege reductions, never widenings. */
|
|
599
|
+
private managerServiceDefs;
|
|
374
600
|
private commitResumeActivation;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
601
|
+
/** Collapsed despawn/attach authorization (P4b). The caller already reached the command's ep
|
|
602
|
+
* row (cred-gated: owner-mode rows via the spawn capability, any-mode rows only in admin
|
|
603
|
+
* instruments). With admin=true (any-mode) any named target is allowed (operator). Otherwise
|
|
604
|
+
* a named target is allowed if it's the caller's OWN child (`spawner == caller`) — and, on a
|
|
605
|
+
* user mesh, if it runs under the CALLER'S OWNER (owner-domain) or the caller's ledger row
|
|
606
|
+
* holds `admin`, read fresh. The policy is the pure
|
|
381
607
|
* {@link authorizeNamedControl}; this wrapper only binds the manager's state (the mode flag +
|
|
382
608
|
* the provider-backed ledger read — a build with no provider authorizes nothing extra,
|
|
383
609
|
* fail-closed via the policy's catch). Error string when denied, `undefined` when allowed. */
|
|
@@ -476,9 +702,26 @@ export declare class Manager {
|
|
|
476
702
|
/** Agent names become `.cotal/agents/<name>.md` paths and mesh identities, so they must be bare
|
|
477
703
|
* tokens, never a path — blocks traversal / arbitrary writes from a model-supplied name. */
|
|
478
704
|
private nameError;
|
|
479
|
-
/** First free name in the series `base`, `base-2`, `base-3`, … — checked against
|
|
480
|
-
* in-flight (reserved) slots
|
|
481
|
-
*
|
|
705
|
+
/** First free name in the series `base`, `base-2`, `base-3`, … — checked against live slots,
|
|
706
|
+
* in-flight (reserved) slots, names held pending retirement, AND the live mesh roster. The
|
|
707
|
+
* roster check covers occupants this manager does not manage (a foreground `cotal spawn`, a
|
|
708
|
+
* connector session, another manager's agent): allocating their name would mint a sibling the
|
|
709
|
+
* broker/auth then refuses to admit, surfacing as a 30s launch-uncertain black hole instead of
|
|
710
|
+
* the auto-number the join path gives. Presence is ADVISORY (SPEC §6) — this is an availability
|
|
711
|
+
* choice at allocation, never an authority check (the broker still enforces): a stale
|
|
712
|
+
* still-live-looking row only costs a numbered suffix, and a missed freshly-joined occupant is
|
|
713
|
+
* still refused downstream exactly as before. Offline rows do NOT occupy — a properly retired
|
|
714
|
+
* name stays reusable. */
|
|
715
|
+
/** The roster's LIVE occupant names (status !== offline) — occupants this manager may NOT manage
|
|
716
|
+
* (a foreground `cotal spawn`, a connector session, ANOTHER manager's agent). Allocating over any
|
|
717
|
+
* of them mints a sibling the broker/auth then refuses to admit, surfacing as the 30s launch-
|
|
718
|
+
* uncertain black hole. */
|
|
719
|
+
private liveRosterNames;
|
|
720
|
+
/** THE single name-liveness predicate both the hard-pinned collision refuse (M6, P2 item 2) and
|
|
721
|
+
* uniqueName's numbering consult, so they can never drift: a name is taken if this manager
|
|
722
|
+
* reserves/manages/retires it OR a roster-live occupant already holds it. Pass a pre-built
|
|
723
|
+
* {@link liveRosterNames} set when checking many names in one allocation. */
|
|
724
|
+
private nameInUse;
|
|
482
725
|
private uniqueName;
|
|
483
726
|
/** Spawn a teammate by persona ref (`name` loads `.cotal/agents/<name>.md`; the peer presents
|
|
484
727
|
* under that file's own `name:`), as if a peer asked via the control plane. Used to pre-spawn the
|
|
@@ -532,7 +775,7 @@ export declare class Manager {
|
|
|
532
775
|
* `spawner` is the authenticated id of the peer that requested the spawn (`req.from.id`),
|
|
533
776
|
* defaulting to the manager's own id for roster/pre-spawn — recorded for the spawner
|
|
534
777
|
* ledger (own-children despawn + reap-on-parent-exit). */
|
|
535
|
-
startAgent(opts: StartAgentOpts, spawner?: string): Promise<ControlReply>;
|
|
778
|
+
startAgent(opts: StartAgentOpts, spawner?: string, hooks?: SpawnHooks): Promise<ControlReply>;
|
|
536
779
|
private startAgentActive;
|
|
537
780
|
/** Preflight the whole inventory before launching its first process, then adopt each exact retained
|
|
538
781
|
* principal without provisioning. A later runtime launch failure is reported per-agent, but malformed
|
|
@@ -579,11 +822,237 @@ export declare class Manager {
|
|
|
579
822
|
* recycle floor's contribution to the ceiling (P4c). Lazy: pruned only when the gate consults it. */
|
|
580
823
|
private coolingCount;
|
|
581
824
|
private opStop;
|
|
825
|
+
/** The ONE named-terminal core both doors share (P2 item 1, checklist 8): the ctl named `stop`
|
|
826
|
+
* and the v0.4 targeted `despawn` are the same terminal — authorize by the shared policy
|
|
827
|
+
* ({@link authorizeNamed}: own-child / owner-domain on privileged, any on admin), stop, track.
|
|
828
|
+
* The ep door runs the SAME two pieces separately so a policy denial surfaces as the §13.3
|
|
829
|
+
* `permission-denied` (never a generic failure). */
|
|
830
|
+
private despawnCore;
|
|
831
|
+
/** The post-authorization terminal effect (both doors). `trackNonAdmin` mirrors the ctl door's
|
|
832
|
+
* `trackStoppedHandle(a, !admin)` disposition. */
|
|
833
|
+
private despawnAuthorized;
|
|
834
|
+
/** Resolve a v0.4 TARGET triple (owner, actor, lifecycleUid — broker-validated subject/body
|
|
835
|
+
* agreement, currency re-checked by the serve boundary's resolver) to the live managed agent it
|
|
836
|
+
* names. Static agents key `(DEV_OWNER, nkey)`; user-mode agents store the principal dot-form
|
|
837
|
+
* in `id`. A uid mismatch is a superseded incarnation — never resolved to its successor. */
|
|
838
|
+
private findManagedByTarget;
|
|
582
839
|
/** Open a short-lived PROVISIONER connection, run the onboarding ops on it, and drain it (closure (ii),
|
|
583
840
|
* residual 2). The DM/DLV consumer-create surface — the irreducible onboarding power — lives only for
|
|
584
841
|
* this window, never as a standing grant on the long-lived supervisor. A provision-only endpoint
|
|
585
842
|
* (no presence/consume/channel-watch) connected with memory-only `provisioner` creds; it sets its own
|
|
586
843
|
* `inboxPrefix` so JS-API replies land on the `_INBOX_<id>.>` the provisioner cred subscribes. */
|
|
844
|
+
/** Run one static §13.1 lifecycle OPERATION over an ephemeral, key-pinned `lifecycle-executor`
|
|
845
|
+
* connection (Unit B): the credential's grants name exactly ONE incarnation's head/uid/gate/
|
|
846
|
+
* cred-family/slot keys, so the write authority exists only for this operation's window and
|
|
847
|
+
* can move nothing else. The transport is the direct-KV binding the shared core saga drives. */
|
|
848
|
+
private withLifecycleExecutor;
|
|
849
|
+
/** Run one §13.1 ENDPOINT-SERVE credential operation (P2 item 1, 1a-serve) over an ephemeral,
|
|
850
|
+
* key-pinned `endpoint-serve-executor` connection: the credential's grants name exactly the
|
|
851
|
+
* manager instance's `epgate`/`epcred` keys plus its registration's two records keys, so the
|
|
852
|
+
* gate CAS, the mint fence, and the spec/governance writes ride a one-shot scoped authority —
|
|
853
|
+
* NEVER the manager's standing seed/supervisor connection (the panel's "no seed shortcut"). */
|
|
854
|
+
private withEndpointServeExecutor;
|
|
855
|
+
/** 1d open-mesh counterpart of {@link withEndpointServeExecutor}: an OPEN mesh has no
|
|
856
|
+
* credential system, so there is no scoped executor to mint - the same §13.1 gate/records
|
|
857
|
+
* writes ride a bare one-shot connection (the broker enforces nothing on an open mesh; the
|
|
858
|
+
* ceremony still produces the real gate, epoch, and registration the serve rails run on). */
|
|
859
|
+
private withOpenServeConnection;
|
|
860
|
+
/** The served manager-level health summary (1a's one read-only command). */
|
|
861
|
+
private managerStatusData;
|
|
862
|
+
/** P2 item 1: register the manager as an ordinary v0.4 `service` endpoint and serve its typed
|
|
863
|
+
* command surface on the ep rails - since 1d the manager's ONLY control door. On an AUTH mesh
|
|
864
|
+
* the whole credential path is the SAME one an ordinary endpoint traverses (the enforcement
|
|
865
|
+
* test that keeps "ordinary" honest): provision the §13.1 issuance gate, drive the
|
|
866
|
+
* registration BARRIER's gate CAS, then release the serve credential only on the mint FENCE's
|
|
867
|
+
* revision-pinned CAS win — all over the scoped one-shot executor ({@link
|
|
868
|
+
* withEndpointServeExecutor}), never a seed-signed shortcut. Holding the signing seed only
|
|
869
|
+
* AUTHORIZES the reserved single-label name (`manager`, operator name authority, DEV_OWNER).
|
|
870
|
+
* On an OPEN mesh the same gate/registration/serve-grant ceremony runs over bare one-shot
|
|
871
|
+
* connections and NO credential is ever minted: there is no credential system to issue from,
|
|
872
|
+
* so the gate legitimately keeps an empty `epcred` family (the §13.1 fence is issuance-only)
|
|
873
|
+
* and the serve connection is bare — the broker enforces nothing on an open mesh, exactly the
|
|
874
|
+
* old open-mesh ctl trust ("open = single-trusted-host"). */
|
|
875
|
+
private registerManagerService;
|
|
876
|
+
/** P2 item 2 must-5 (b): mint the standing `goal-writer` credential and STAGE it into this
|
|
877
|
+
* instance's §13.1 revocation family (`epcred.<e>.<iid>`), over the passed executor's `authKv`
|
|
878
|
+
* (the scoped `endpoint-serve-executor`, which holds the epcred write grant). The GRANT profile
|
|
879
|
+
* stays goal-writer-only (Q2 — disjoint from the serve credential); only the FAMILY membership
|
|
880
|
+
* is shared, so the registration barrier's existing enumerate+revoke+evict catches the
|
|
881
|
+
* goal-writer on takeover/retire with NO barrier code change. Used both at registration (the run
|
|
882
|
+
* block's `authKv`) and at renewal (a fresh executor's), re-minting the SAME stable nkey with a
|
|
883
|
+
* fresh bounded exp — each issuance writes a DISTINCT ledger row (per-JWT credentialId digest). */
|
|
884
|
+
private mintAndStageGoalWriter;
|
|
885
|
+
/** P2 item 2 (spawn-as-action): stand up the standing self-mediated goal-writer connection +
|
|
886
|
+
* ActionContext. Mode-dual, mirroring {@link registerManagerService}: an AUTH mesh uses the
|
|
887
|
+
* scoped `goal-writer` credential already minted + family-STAGED inside registration's run block
|
|
888
|
+
* ({@link mintAndStageGoalWriter} — DISJOINT grant from the serve cred, SHARED §13.1 revocation
|
|
889
|
+
* family); an OPEN mesh uses a bare connection (no credential system to mint from - the broker
|
|
890
|
+
* enforces nothing). The connection presents the CURRENT credential on every (re)connect (a
|
|
891
|
+
* renewal is adopted without reconnecting the whole endpoint); the ActionContext bonds its
|
|
892
|
+
* KV + JS + JSM to this one connection and space (SPEC 13.4), so a composition mixup cannot splice
|
|
893
|
+
* goal state across brokers. */
|
|
894
|
+
private startGoalWriter;
|
|
895
|
+
/** Drain the goal-writer connection (best-effort, both exit paths). */
|
|
896
|
+
private stopGoalWriter;
|
|
897
|
+
/** P2 item 6: mint the standing `session-ledger` credential and STAGE it into this instance's
|
|
898
|
+
* §13.1 revocation family (`epcred.<e>.<iid>`), over the passed executor's `authKv` — EXACTLY the
|
|
899
|
+
* {@link mintAndStageGoalWriter} pattern, under the same open-and-commit fence.
|
|
900
|
+
*
|
|
901
|
+
* This credential carries the DEDICATED sessions-bucket ledger rows and no session rail at all.
|
|
902
|
+
* It therefore takes no epoch pin: §13.6 makes it the durable revocation authority that must
|
|
903
|
+
* outlive the serving endpoint, so scoping it to one serving epoch would defeat its purpose. The
|
|
904
|
+
* epoch lives where it belongs, on the per-session serving credentials, which are minted per
|
|
905
|
+
* redemption into this same family. Re-minting the SAME nkey on renewal writes a DISTINCT ledger
|
|
906
|
+
* row (per-JWT credentialId digest). */
|
|
907
|
+
private mintAndStageSessionLedger;
|
|
908
|
+
/** P2 item 6: stand up the ONE §13.6 session plane on its own standing connection. Mode-dual,
|
|
909
|
+
* mirroring {@link startGoalWriter}: an AUTH mesh presents the scoped `session-ledger` cred
|
|
910
|
+
* already minted + family-staged inside registration's run block ({@link mintAndStageSessionLedger});
|
|
911
|
+
* an OPEN mesh uses a bare connection (no credential system to mint from). The connection presents
|
|
912
|
+
* the CURRENT credential on every (re)connect, so a half-TTL renewal is adopted without reconnecting.
|
|
913
|
+
*
|
|
914
|
+
* The offer SIGNER is a per-incarnation in-memory keypair: the static collapsed path mints AND
|
|
915
|
+
* redeems the offer in one call ({@link ManagerSessionPlane.establishAttach}), so the manager
|
|
916
|
+
* self-signs and self-verifies its own §13.6 grants and the keypair never leaves the process — a
|
|
917
|
+
* holder never verifies the signature (it presents the grant back over the rail; the broker's
|
|
918
|
+
* per-session caller cred is the holder's real fence). The plane's ledger lives in the DEDICATED
|
|
919
|
+
* sessions bucket (createEndpointStreams provisioned it at registration). */
|
|
920
|
+
private startSessionPlane;
|
|
921
|
+
/**
|
|
922
|
+
* The per-session SERVING credential seam (P2 item 6, SPEC 13.6): the manager mints, gate-stages,
|
|
923
|
+
* connects and revokes ONE credential per live session, replacing a standing credential that held
|
|
924
|
+
* `eps.manager.*.<epoch>.{in,out}` and so reached every live session's bytes at its epoch.
|
|
925
|
+
*
|
|
926
|
+
* Each session gets its OWN nkey identity, so the §13.1 barrier's evict-by-holderPrincipal reaches
|
|
927
|
+
* it individually, and each is staged into `epcred.manager.<instanceId>` — the SAME family the
|
|
928
|
+
* ledger and goal-writer creds join. That is how manager takeover still kills a deposed manager's
|
|
929
|
+
* sessions: the barrier enumerates the family, revokes every row, and evicts every holder, so the
|
|
930
|
+
* per-session creds die with the incarnation exactly as the standing one did, with the blast
|
|
931
|
+
* radius of a leaked credential cut from "every session at this epoch" to "one dead session".
|
|
932
|
+
*
|
|
933
|
+
* OPEN MESH: no credential system exists to mint from, so the seam mints nothing and opens a bare
|
|
934
|
+
* connection. That is not a degraded auth path — an open mesh has no broker enforcement at all —
|
|
935
|
+
* and it is still per-session: the connection and the ledger row are still one-per-session, so
|
|
936
|
+
* teardown behaves identically in both modes.
|
|
937
|
+
*/
|
|
938
|
+
private sessionServingCredentials;
|
|
939
|
+
/** Tear the session plane down (best-effort, both exit paths): end every live bridge with the
|
|
940
|
+
* honest `manager-restart` reason (this incarnation is going away; any successor takes a new epoch
|
|
941
|
+
* and refuses these grants), then drain each session's own connection and the ledger connection. */
|
|
942
|
+
private stopSessionPlane;
|
|
943
|
+
/** P2 item 2 must-5 Q-B — the boot reconcile: a fresh incarnation (a manager restart takes a NEW
|
|
944
|
+
* instanceId, so the in-memory acceptance map starts empty) inherits the endpoint's accepted-but-
|
|
945
|
+
* unterminal goals from any predecessor. Enumerate the durable index over a scoped PROVISIONER
|
|
946
|
+
* (records CONSUMER.CREATE; the goal-writer holds NO enumeration grant, exactly the ruling) and
|
|
947
|
+
* settle each orphan so an accepted goal is NEVER dropped across a restart. Open mesh: a bare
|
|
948
|
+
* connection (the broker enforces nothing). Runs ONCE at start, BEFORE spawn-as-action begins
|
|
949
|
+
* accepting (the `goalReconcileDone` gate), so it never races a live goal's acceptance. Never
|
|
950
|
+
* fatal — a reconcile failure is logged and the gate opens either way. */
|
|
951
|
+
private reconcileGoalIndex;
|
|
952
|
+
/** Settle ONE inherited goal by evidence: no goal record (a crash between the index write and the
|
|
953
|
+
* goal-record create) leaves the pointer untouched (never settle a goal that was never accepted,
|
|
954
|
+
* and clearing it would race a live goal mid-creation); a TERMINAL goal clears the index
|
|
955
|
+
* (converged — the predecessor committed but died before clearing); a NON-TERMINAL goal settles
|
|
956
|
+
* `uncertain` (the accepting incarnation is gone, so the success signal will never reach us — the
|
|
957
|
+
* bounded readiness outcome the plan maps the window to). Within the readiness window it arms a
|
|
958
|
+
* bounded, unref'd timer to settle at the deadline (an early uncertain would steal a still-possible
|
|
959
|
+
* success the substrate guards against). */
|
|
960
|
+
private reconcileOneGoal;
|
|
961
|
+
/** P2 item 2: publish a goal PROGRESS event on the caller-scoped epe subtree, over the SERVE
|
|
962
|
+
* connection (which holds the `epe.<e>.<iid>.<epoch>.>` egress grant; the goal-writer deliberately
|
|
963
|
+
* does not). The terminal rides a final event `phase:"terminal"` (Q1 — the caller follows epe to
|
|
964
|
+
* the terminal; the durable result fact + inspect/ps are the reconcile authority). A dropped event
|
|
965
|
+
* is non-fatal (the terminal is authoritative in the journal). */
|
|
966
|
+
private emitGoalProgress;
|
|
967
|
+
/** The own-gate currency belt: before the goal-writer commits a terminal fact, the manager reads
|
|
968
|
+
* its OWN issuance gate epoch and REFUSES the commit if superseded. This NARROWS the window; it
|
|
969
|
+
* is not the fence. Layer 1 below is closed by the sibling-mint fence; layers 2 and 3 are not
|
|
970
|
+
* closed by any planned slice and must not be described as temporary.
|
|
971
|
+
*
|
|
972
|
+
* THE RESIDUAL, STACKED:
|
|
973
|
+
* 1. SIBLING-MINT INJECTION. A §13.1 barrier's revoke/evict loop closes only over the family
|
|
974
|
+
* SNAPSHOT IT ENUMERATED, so a sibling mint that observes the gate and stages a ledger row
|
|
975
|
+
* WITHOUT the observe/open/commit fence can be staged and released AFTER that enumerate and
|
|
976
|
+
* never be revoked. This layer is closed exactly where BOTH sibling mint sites
|
|
977
|
+
* ({@link mintAndStageGoalWriter}, {@link mintAndStageSessionLedger}) route their stage
|
|
978
|
+
* through `commitSiblingIssuance` (the revision-pinned CAS that makes a losing mint release
|
|
979
|
+
* nothing), and open exactly where they do not — state the mechanism, never the branch.
|
|
980
|
+
* 2. THE BARRIER WINDOW. A gate FREEZE neither kills this connection nor advances the epoch,
|
|
981
|
+
* and this belt compares `processEpoch` alone, so it still PASSES from barrier start until
|
|
982
|
+
* the reopen. A ledger revoke marks a row; it does not re-check a live JWT mid-publish. The
|
|
983
|
+
* durable kill is the CLUSTER-VERIFIED EVICTION, so a deposed manager can INITIATE new
|
|
984
|
+
* terminal publishes from barrier start until eviction is verified — not merely finish bytes
|
|
985
|
+
* already on the wire. Successor EXISTENCE and corpse DEATH are different phases, so the
|
|
986
|
+
* barrier's ordering licenses no conclusion about when the corpse stops being able to write.
|
|
987
|
+
* 3. OPEN MESH. No credential family exists, so the revoke/evict loop is vacuous and this belt
|
|
988
|
+
* is COOPERATIVE only: a non-conformant process simply does not run it.
|
|
989
|
+
* The named follow-up that would close 2 and 3 is the gate-linearized commit (routing the
|
|
990
|
+
* terminal through the issuance gate's own CAS), deliberately deferred as substrate territory.
|
|
991
|
+
* An earlier revision of this comment claimed the residual was "closed by item-3 slice 3.0,
|
|
992
|
+
* never a permanent residual". That asserted a closure that does not exist. */
|
|
993
|
+
private assertGoalWriterEpochCurrent;
|
|
994
|
+
/** Serve `spawn`/`launch` as an ACTION (P2 item 2). Authz already ran in {@link serveGated}. The
|
|
995
|
+
* accept path runs INLINE on the handler ({@link startAgent} with hooks): the goal binds + the
|
|
996
|
+
* acceptance replies the moment the identity is minted, BEFORE any provision (pin 1); progress and
|
|
997
|
+
* the terminal are driven OFF-handler, so the ~30s readiness wait no longer blocks the reply.
|
|
998
|
+
* Returns the acceptance floor payload {name, owner, actor, uid, goalId, fingerprint, executor}
|
|
999
|
+
* (the ALLOCATED identity). goalId = the request id (env.id, Q3). */
|
|
1000
|
+
private serveSpawnGoal;
|
|
1001
|
+
/** H2: an acceptance served from a WINNER'S durable acceptance floor (the goal-index entry it
|
|
1002
|
+
* wrote before its own ack). Refuses `unavailable` rather than inventing one — see
|
|
1003
|
+
* {@link cachedSpawnAcceptance} for why an empty identity is never an acceptable answer. */
|
|
1004
|
+
private acceptanceFromIndex;
|
|
1005
|
+
/** Reconstruct a cached acceptance for a same-goalId retry NOT in the live map (a prior incarnation
|
|
1006
|
+
* accepted it, or a concurrent winner whose map write this reader has not yet observed).
|
|
1007
|
+
*
|
|
1008
|
+
* H2 — WHY THIS PREFERS THE INDEX OVER THE TERMINAL. It used to read only the committed terminal
|
|
1009
|
+
* and fall back to `{name:"", actor:"", uid:""}` when there was none. That is the common case,
|
|
1010
|
+
* not a corner: a client retry over ANYCAST reaches a sibling while the winner is still
|
|
1011
|
+
* provisioning, so no terminal exists yet, and the caller was handed an ACCEPTED reply naming an
|
|
1012
|
+
* empty agent it can never address. The acceptance floor in the goal index exists from the moment
|
|
1013
|
+
* of acceptance, so it answers precisely the window the terminal cannot. Where neither is
|
|
1014
|
+
* readable the honest answer is a REFUSAL: an accepted goal whose identity nobody can name is
|
|
1015
|
+
* `unavailable`, never a hollow success. */
|
|
1016
|
+
private cachedSpawnAcceptance;
|
|
1017
|
+
/** M4 (settle race): a despawn MID-GOAL drives the goal's cancel terminal - transition to
|
|
1018
|
+
* `cancelling`, then commit the `cancel` cause on the goal-writer connection. First-terminal-fact
|
|
1019
|
+
* wins: if the readiness outcome already committed (succeeded/failed/uncertain) the transition or
|
|
1020
|
+
* the create-only commit loses gracefully and the readiness terminal stands. Fire-and-forget from
|
|
1021
|
+
* despawn (the process teardown is authoritative for the agent; this settles the GOAL honestly).
|
|
1022
|
+
* Cancel rides the despawn's own authorizeNamed reach (pin 5) - there is no cancel-by-goalId. */
|
|
1023
|
+
private cancelAgentGoal;
|
|
1024
|
+
/** The static F1 terminal for one departed incarnation (Unit B): delegates the gate/head CAS
|
|
1025
|
+
* sequence to the shared core saga over the executor transport; the footprint teardown (creds
|
|
1026
|
+
* file + broker durables/ACL) runs INSIDE the barrier as its cleanup step. On completion the
|
|
1027
|
+
* wire principal joins {@link retiredPrincipals} (the F5 refusal index) and the name hold
|
|
1028
|
+
* clears (ABA-guarded by uid). A PRE-UNIT-B lifecycle (no slot row — spawned before the
|
|
1029
|
+
* durable registry existed) has nothing to terminalize: its footprint teardown runs directly
|
|
1030
|
+
* and the hold clears, the honest upgrade path. */
|
|
1031
|
+
private driveStaticRetirement;
|
|
1032
|
+
/** F5(b) push renewal of ONE live managed-static credential (Unit B): re-mint the SAME nkey
|
|
1033
|
+
* identity with the SAME scope (recorded on the managed row at spawn) and a fresh bounded
|
|
1034
|
+
* exp, ledger the new credentialId (slot record first, then the row, then the file — a
|
|
1035
|
+
* credential is never materialized before its ledger row exists), and re-sign the SAME
|
|
1036
|
+
* lifecycle-keyed file the agent endpoint's source seam re-reads. Never advances the epoch,
|
|
1037
|
+
* never routes through any barrier (renewal is the THIRD transition). */
|
|
1038
|
+
private renewManagedStaticCred;
|
|
1039
|
+
/** The Unit B reconciliation (F3 "no active orphan"): ensure the authority stores, then sweep
|
|
1040
|
+
* every durable slot row and act by the TOTAL resume table — `provisioning`/`terminalizing`
|
|
1041
|
+
* re-drive the exact-op terminal; an `active` row survives ONLY when a LIVE managed agent this
|
|
1042
|
+
* process owns backs it at the same uid (`adopted`), else its process is gone and it
|
|
1043
|
+
* terminalizes; `retired` rows seed the F5 refusal index. Two call sites: the BOOT sweep
|
|
1044
|
+
* (`postAdoption=false`, under the lease before control serving) DEFERS active-non-adopted
|
|
1045
|
+
* slots while a resume is still pending (adoption runs after it); the POST-ADOPTION sweep
|
|
1046
|
+
* (`postAdoption=true`, inside finalizeResume while `resumeRequired` still fences ordinary
|
|
1047
|
+
* spawns) terminalizes any active slot the resume did not claim. */
|
|
1048
|
+
private reconcileStaticLifecycles;
|
|
1049
|
+
/** The F5(a) membership gate (Unit B, the F5-bind design): decide a control caller by its
|
|
1050
|
+
* AUTHENTICATED wire principal. A LIVE managed slot passes (unless terminalizing); a RETIRING
|
|
1051
|
+
* hold or a RETIRED static incarnation refuses even with a tier-valid JWT (the
|
|
1052
|
+
* copied-credential vector — its subject can never collide with a successor's, so this match
|
|
1053
|
+
* is non-forgeable); any OTHER principal is not a managed lifecycle (an operator instrument:
|
|
1054
|
+
* the credential tier governs, exactly as before). Never name alone, never a payload field. */
|
|
1055
|
+
private lifecycleMembershipRefusal;
|
|
587
1056
|
private withProvisioner;
|
|
588
1057
|
/** Purge the space's retained message backlog (chat, optionally DMs). Privileged — the manager mints a
|
|
589
1058
|
* short-lived "purger" cred (same destructive grant as `cotal history clear`, isolated off the
|
|
@@ -600,7 +1069,20 @@ export declare class Manager {
|
|
|
600
1069
|
* preserves everything else — and is allowed on the privileged tier only if `file.owner == caller`,
|
|
601
1070
|
* else admin is required. Fail-closed: an ownerless file (legacy / operator-written) is admin-only. */
|
|
602
1071
|
private opDefinePersona;
|
|
603
|
-
|
|
1072
|
+
/** The post-authorization attach effect (P2 item 6): mint the holder-bound §13.6 offer, redeem it
|
|
1073
|
+
* through the ONE session plane (one-use CAS + presenter-equality), and stand up the PTY bridge —
|
|
1074
|
+
* atomically. The reply is the SIGNED grant (no ws:// URL, non-bearer, never logged); the caller
|
|
1075
|
+
* redeems it over the mesh with a per-session rails-only cred it mints itself. Only streamable
|
|
1076
|
+
* backends (pty/host) attach; an external runtime's attach() throws with per-runtime guidance. */
|
|
1077
|
+
private attachAuthorized;
|
|
1078
|
+
/** P2 item 6: the console's mesh §13.6 session establisher (backing `POST /session/<name>` on the
|
|
1079
|
+
* loopback face). Drives THE ONE plane — same establishAttach as the ep `attach` command — with
|
|
1080
|
+
* the loopback OPERATOR as holder (same-host trust boundary), then hands the browser everything it
|
|
1081
|
+
* needs to open the caller rail over the broker ws listener: the holder-bound grant, a per-session
|
|
1082
|
+
* RAILS-ONLY caller cred (static mints from the seed, TTL-bound to the session; an open mesh has no
|
|
1083
|
+
* credential system so the browser connects bare), and the ws URL. NO 127.0.0.1 terminal transport
|
|
1084
|
+
* — the terminal rides the mesh session. Injected only when a wsPort exists (see the constructor). */
|
|
1085
|
+
private establishConsoleSession;
|
|
604
1086
|
/** Managed agents cross-referenced with live presence (the manager sees the roster). */
|
|
605
1087
|
/** `ownerFilter`: restrict to agents whose spawn-time stored `userOwner` equals it (the ps/status
|
|
606
1088
|
* owner-domain bound); undefined = unbounded. {@link NO_OWNER_MATCHES} matches nothing. */
|