@cotal-ai/manager 0.78.0 → 0.79.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 +10 -10
- package/dist/attach-endpoint.d.ts.map +1 -1
- package/dist/attach-endpoint.js +9 -9
- package/dist/attach-endpoint.js.map +1 -1
- package/dist/commands.js +2 -2
- package/dist/commands.js.map +1 -1
- package/dist/console/app.js +1 -1
- package/dist/console/index.html +1 -1
- package/dist/console-crypto-shim.d.ts +1 -1
- package/dist/console-crypto-shim.js +1 -1
- package/dist/console-session-entry.js +1 -1
- package/dist/endpoint-evict.d.ts +2 -2
- package/dist/endpoint-evict.js +3 -3
- package/dist/endpoint-evict.js.map +1 -1
- package/dist/manager-service-contract.d.ts +12 -13
- package/dist/manager-service-contract.d.ts.map +1 -1
- package/dist/manager-service-contract.js +22 -23
- package/dist/manager-service-contract.js.map +1 -1
- package/dist/manager.d.ts +47 -47
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +100 -95
- package/dist/manager.js.map +1 -1
- package/dist/session/bridge.d.ts +3 -3
- package/dist/session/bridge.js +1 -1
- package/dist/session/bridge.js.map +1 -1
- package/dist/session/establish.d.ts +10 -10
- package/dist/session/establish.d.ts.map +1 -1
- package/dist/session/establish.js +3 -3
- package/dist/session/establish.js.map +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/plane.js +5 -5
- package/package.json +12 -12
package/dist/manager.js
CHANGED
|
@@ -23,11 +23,11 @@ import { TranscriptReceiver } from "./transcript-receive.js";
|
|
|
23
23
|
import { jetstream, jetstreamManager } from "@nats-io/jetstream";
|
|
24
24
|
import { Kvm } from "@nats-io/kv";
|
|
25
25
|
import { recordsBucket, epAuthBucket, ensureAuthorityStores, ensureContractStore, createEndpointStreams, contractStoreContext, publishContractArtifact, contractArtifactCanonicalBytes, standaloneConnectOpts, rawDigest, STANDING_RENEWABLE_TTL_SEC as MANAGED_STATIC_TTL_SEC, newArtifactSigner, RotatingSigner, generationAnchor, sessionsBucket, SESSION_GRANT_MAX_TTL_MS, REMOTE_MANAGER_IDENTITY_NAMES, } from "@cotal-ai/core";
|
|
26
|
-
//
|
|
26
|
+
// The manager's ONE §13.6 session plane — offer mint + one-use redeem + PTY-bridge
|
|
27
27
|
// standup for `attach`, over a dedicated standing session-LEDGER connection (the byte rails ride
|
|
28
28
|
// per-session credentials on their own short-lived connections).
|
|
29
29
|
import { ManagerSessionPlane, openSessionLedgerKv } from "./session/index.js";
|
|
30
|
-
//
|
|
30
|
+
// The manager as an ordinary v0.4 `service` endpoint — the §13.1
|
|
31
31
|
// endpoint-serve credential subsystem (gate provisioning, registration barrier, mint fence) plus
|
|
32
32
|
// the register/authorize/serve seams, all driven over a scoped one-shot executor connection.
|
|
33
33
|
import { provisionEndpointGateOpen, endpointRegistrationBarrier, serveIssuanceGateKv, commitSiblingIssuance, markLedgerRowRevoked, epcredRowKey, epgateKey, parseEndpointGate, readEndpointGateGeneration, registerServingInstance, deregisterServiceInstance, serveEndpoint, bindGoal, createGoal, transitionGoal, commitGoalResult, requestGoalCancel, goalAlreadyTerminal, settleGoalUncertain, readGoalResult, readGoalStatus, readGoalSpec, recordGoalIndex, readGoalIndex, clearGoalIndex, listGoalIndex, mintCheckpoint, resumeCheckpoint, readCheckpointSettle, readCheckpointSpec, expireCheckpoint, GOAL_TERMINAL_STATES, goalRefOf, goalProgressTopic, epeSubject, submissionFingerprint, EpEnvelopeError, lifecycleBlocked, lifecycleBlockedFrom, renderLifecycleBlocked, } from "@cotal-ai/core";
|
|
@@ -329,13 +329,13 @@ const STORE_CHALLENGE_TIMEOUT_MS = 5_000;
|
|
|
329
329
|
export class Manager {
|
|
330
330
|
space;
|
|
331
331
|
servers;
|
|
332
|
-
/**
|
|
332
|
+
/** The broker ws listener port (loopback) `cotal up` allocated, for the console session
|
|
333
333
|
* client's wsUrl. Undefined ⇒ no console session client (POST /session 503s). */
|
|
334
334
|
wsPort;
|
|
335
335
|
name;
|
|
336
336
|
workspaceRoot;
|
|
337
337
|
eventsRequired;
|
|
338
|
-
/**
|
|
338
|
+
/** The operator-set global live-session ceiling (see {@link ManagerOptions.maxSessions}). */
|
|
339
339
|
maxSessions;
|
|
340
340
|
/** The ONE secret store for every kind this manager touches (daemon-cred remint + agent kinds).
|
|
341
341
|
* See {@link ManagerOptions.secretStore}. */
|
|
@@ -401,7 +401,7 @@ export class Manager {
|
|
|
401
401
|
* never reused across restarts) — the endpoint's presence key AND the `managerInstance` audit
|
|
402
402
|
* coordinate every static activation records. */
|
|
403
403
|
managerLifecycleUid = mintLifecycleUid();
|
|
404
|
-
/** The persisted LOGICAL instance id (SPEC 13.6 item 7
|
|
404
|
+
/** The persisted LOGICAL instance id (SPEC 13.6 item 7): STABLE across restart, so a
|
|
405
405
|
* restart re-registers the SAME id with an ADVANCED epoch through the §13.1 gate (the successor
|
|
406
406
|
* fences the predecessor's epoch — the (i) fence bites on a real restart). It is the registration
|
|
407
407
|
* instanceId, the served-status id, the goal spec's executor.instanceId, the epe route, and the
|
|
@@ -412,19 +412,19 @@ export class Manager {
|
|
|
412
412
|
* {@link provisionEndpointGateOpen} idempotent (no core barrier change) and gives verified
|
|
413
413
|
* eviction a stable target (the predecessor's connections under this principal). Set in start(). */
|
|
414
414
|
managerServeIdentity;
|
|
415
|
-
/**
|
|
415
|
+
/** The manager's v0.4 service-endpoint serve state — the serve handle +
|
|
416
416
|
* its dedicated connection, the STABLE serve identity (renewals re-mint the same nkey), the
|
|
417
417
|
* branded serve grant, and the CURRENT credential (the connection's authenticator reads it on
|
|
418
418
|
* every (re)connect, so a renewal is adopted without re-registration). Absent on open meshes,
|
|
419
|
-
* in user mode
|
|
419
|
+
* in user mode, and before registration completes. */
|
|
420
420
|
serviceServe;
|
|
421
421
|
/** #1577: serve connections whose transport is down while the client reconnects them. Such a
|
|
422
422
|
* connection is open (`isClosed()` is false under unbounded reconnects) yet the broker holds
|
|
423
423
|
* none of its rails, so the liveness responder must not grade it `bound`. Keyed by the
|
|
424
424
|
* connection, so a re-dial that swaps in a fresh one starts from its own state. */
|
|
425
425
|
serveTransportDown = new WeakSet();
|
|
426
|
-
/**
|
|
427
|
-
* standing connection DISJOINT from the serve credential
|
|
426
|
+
/** The spawn-as-action SELF-MEDIATED goal-writer connection + ActionContext — a
|
|
427
|
+
* standing connection DISJOINT from the serve credential, scoped to exactly this endpoint's
|
|
428
428
|
* goal bind/terminal facts + goal-record writes ({@link goalWriterGrants}). Auth mode mints the
|
|
429
429
|
* `goal-writer` cred; an open mesh uses a bare connection (no credential system to mint from).
|
|
430
430
|
* `gate` (auth mode) is the own-issuance-gate READER for the own-gate currency belt — the
|
|
@@ -442,29 +442,29 @@ export class Manager {
|
|
|
442
442
|
* live) and stashed here for {@link startGoalWriter} to build the standing connection from. */
|
|
443
443
|
goalWriterIdentity;
|
|
444
444
|
goalWriterCreds;
|
|
445
|
-
/**
|
|
445
|
+
/** The manager's ONE §13.6 session plane — offer mint + one-use redeem + PTY-bridge
|
|
446
446
|
* standup for `attach`. The face's establisher and the CLI attach handler both call THIS one
|
|
447
447
|
* plane; the manager never constructs a second. Undefined until {@link startSessionPlane}. */
|
|
448
448
|
sessionPlane;
|
|
449
|
-
/**
|
|
449
|
+
/** The standing session-LEDGER connection + its mutable creds holder (the authenticator
|
|
450
450
|
* presents the refreshed cred on the next reconnect after a half-TTL renewal — the goal-writer
|
|
451
451
|
* precedent). Auth mode only; an open mesh runs the plane over a bare connection. */
|
|
452
452
|
sessionLedgerConn;
|
|
453
453
|
/** Renews the session signing key long before its window closes. A key that expires unattended
|
|
454
454
|
* takes the whole session plane down until the manager is restarted, which kills live sessions. */
|
|
455
455
|
sessionKeyRenewTimer;
|
|
456
|
-
/**
|
|
456
|
+
/** Maps credentialId → the nkey that credential was minted for, for the live per-session
|
|
457
457
|
* SERVING credentials. The §13.1 ledger row records the holder principal, and the row is written
|
|
458
458
|
* at stage time (after the mint), so the two steps need this one hop. Entries are dropped at
|
|
459
459
|
* revoke; a session that never staged drops its entry when the manager exits. */
|
|
460
460
|
sessionServingKeys = new Map();
|
|
461
|
-
/**
|
|
461
|
+
/** The STABLE session-LEDGER identity (auth mode) — minted once at registration
|
|
462
462
|
* alongside the serve + goal-writer identities; a renewal re-mints the SAME nkey with a fresh
|
|
463
463
|
* bounded exp and re-stages its distinct credId into the §13.1 revocation family. The current
|
|
464
464
|
* credential is minted INSIDE {@link registerManagerService}'s run block and stashed here. */
|
|
465
465
|
sessionLedgerIdentity;
|
|
466
466
|
sessionLedgerCreds;
|
|
467
|
-
/**
|
|
467
|
+
/** The acceptance replied for each in-flight goalId this incarnation accepted, so an
|
|
468
468
|
* idempotent same-goalId retry serves the IDENTICAL acceptance (same allocated name/triple) without
|
|
469
469
|
* a second spawn. Durable cross-incarnation reconstruction rides the goal index; here the
|
|
470
470
|
* live map covers same-incarnation retries, with the committed result fact as the fallback. */
|
|
@@ -474,7 +474,7 @@ export class Manager {
|
|
|
474
474
|
* Spawn-as-action REFUSES to accept until it completes, so the sweep never races a live goal's
|
|
475
475
|
* acceptance (settling one mid-flight would steal its real terminal). */
|
|
476
476
|
goalReconcileDone = false;
|
|
477
|
-
/**
|
|
477
|
+
/** The live spawn goal ref for each managed agent name, so a despawn MID-GOAL
|
|
478
478
|
* drives the cancel path (transition -> cancel terminal). Cleared when the goal terminalizes. */
|
|
479
479
|
agentGoals = new Map();
|
|
480
480
|
/**
|
|
@@ -681,7 +681,7 @@ export class Manager {
|
|
|
681
681
|
this.attach = new AttachEndpoint(() => this.list(),
|
|
682
682
|
// Initial /feed replay for a connecting console: the current peer roster.
|
|
683
683
|
() => [{ event: "roster", data: this.ep?.getRoster() ?? [] }], opts.consolePort ?? 0,
|
|
684
|
-
//
|
|
684
|
+
// The console's mesh §13.6 session establisher — injected ONLY when a broker ws
|
|
685
685
|
// listener exists (cotal up allocated a wsPort). Never a second plane; it drives THE plane.
|
|
686
686
|
opts.wsPort !== undefined ? (name) => this.establishConsoleSession(name) : undefined,
|
|
687
687
|
// Loopback unless the OPERATOR said otherwise. A broker *dial* address is not a manager *bind*
|
|
@@ -889,7 +889,7 @@ export class Manager {
|
|
|
889
889
|
throw new Error(`space "${this.space}" has user-auth state on disk but no mesh registry entry - a user-mode manager needs the authoritative record (\`cotal up\` writes it before the control plane); \`cotal up --user-auth\` this space, or remove the stale ${userAuthStateDir(this.workspaceRoot, this.space)}`);
|
|
890
890
|
if (!this.remoteAuthority && this.userMode && !this.auth)
|
|
891
891
|
throw new Error(`space "${this.space}" has user-auth state but no auth.json under ${authDir(this.workspaceRoot)} - the pre-flip manager still needs the space trust bundle; re-run \`cotal up --user-auth\` here`);
|
|
892
|
-
//
|
|
892
|
+
// SPEC 13.6 item 7: the LOGICAL instance id + serve identity PERSIST across restart
|
|
893
893
|
// (a space-scoped manager identity file under .cotal). A restart re-registers the SAME id with an
|
|
894
894
|
// ADVANCED epoch (the successor fences the predecessor); a fresh mint over a malformed file is
|
|
895
895
|
// refused loud (no-fallbacks - a restart never silently becomes a fresh instance). A second
|
|
@@ -978,7 +978,7 @@ export class Manager {
|
|
|
978
978
|
this.ep.on("presence-view", () => this.logSeatMeshTransitions());
|
|
979
979
|
await this.ep.start();
|
|
980
980
|
await this.ep.setActivity(`supervisor (${this.runtimeKind})`);
|
|
981
|
-
// Per-instance liveness lease (
|
|
981
|
+
// Per-instance liveness lease (the old per-space singleton is DEMOTED). Acquire
|
|
982
982
|
// THIS logical instance's own key (atomic CAS create). A DIFFERENT instance (a second manager in a
|
|
983
983
|
// second workspace root) has a distinct id ⇒ a distinct key ⇒ it coexists; the create THROWS only
|
|
984
984
|
// when the SAME instance id is already live (a same-root double-start, or a restart racing the
|
|
@@ -1035,7 +1035,7 @@ export class Manager {
|
|
|
1035
1035
|
const startupReconcile = this.auth && !this.userMode ? this.reconcileStaticLifecycles() : undefined;
|
|
1036
1036
|
if (startupReconcile)
|
|
1037
1037
|
void startupReconcile.catch((e) => console.error(`! ${STARTUP_RECONCILING}: ${rejectionText(e)} - no per-alias retry could be planned; a later manager start re-reads unfinished durable terminals`));
|
|
1038
|
-
//
|
|
1038
|
+
// The manager serves NO ctl tiers - its whole control surface is the v0.4
|
|
1039
1039
|
// service endpoint registered below. The old three-tier rail (self/manager/admin) is deleted;
|
|
1040
1040
|
// `ctl.delivery`/`ctl.delivery-admin` (the delivery daemon) and `ctl.auth-admin` (the auth
|
|
1041
1041
|
// plane) are separate services and keep their rails.
|
|
@@ -1101,8 +1101,8 @@ export class Manager {
|
|
|
1101
1101
|
// the fence before it can publish a fresh service registration after shutdown.
|
|
1102
1102
|
if (this.staticReconcileStopping)
|
|
1103
1103
|
return;
|
|
1104
|
-
//
|
|
1105
|
-
// and serve its typed command surface on the ep rails -
|
|
1104
|
+
// Register the manager as an ordinary v0.4 `service` endpoint (SPEC §13.7/§13.9)
|
|
1105
|
+
// and serve its typed command surface on the ep rails - the ONLY control door, in
|
|
1106
1106
|
// EVERY mesh mode. Static + user meshes mint the scoped executor + endpoint-serve credential;
|
|
1107
1107
|
// an open mesh runs the same gate/registration ceremony over bare connections and never mints
|
|
1108
1108
|
// (there is no credential system - the broker enforces nothing, matching the old open-mesh ctl
|
|
@@ -1110,10 +1110,10 @@ export class Manager {
|
|
|
1110
1110
|
await this.registerManagerService();
|
|
1111
1111
|
if (this.staticReconcileStopping)
|
|
1112
1112
|
return;
|
|
1113
|
-
//
|
|
1113
|
+
// Stand up the standing goal-writer connection for spawn-as-action — AFTER
|
|
1114
1114
|
// registration (it writes this endpoint's goal facts/records), disjoint from the serve cred.
|
|
1115
1115
|
await this.startGoalWriter();
|
|
1116
|
-
//
|
|
1116
|
+
// Stand up the ONE §13.6 session plane for `attach` — AFTER registration too (it
|
|
1117
1117
|
// rides the serve grant's epoch + the family-staged session-ledger cred), on its own standing
|
|
1118
1118
|
// connection disjoint from both the serve and goal-writer creds.
|
|
1119
1119
|
await this.startSessionPlane();
|
|
@@ -1483,7 +1483,7 @@ export class Manager {
|
|
|
1483
1483
|
}
|
|
1484
1484
|
}
|
|
1485
1485
|
}
|
|
1486
|
-
//
|
|
1486
|
+
// The manager is the `endpoint-serve` renewal owner for its OWN
|
|
1487
1487
|
// service credential — re-mint the SAME serve identity with a fresh bounded exp THROUGH the
|
|
1488
1488
|
// §13.1 mint fence over a scoped one-shot executor (every renewal stages a distinct ledger
|
|
1489
1489
|
// row and wins the gate CAS; never the standing connection). The serve connection's
|
|
@@ -1529,7 +1529,7 @@ export class Manager {
|
|
|
1529
1529
|
console.error(`! goal-writer renewal: ${rejectionText(e)} - spawn-as-action stops accepting at this cred's expiry unless the manager restarts`);
|
|
1530
1530
|
}
|
|
1531
1531
|
}
|
|
1532
|
-
//
|
|
1532
|
+
// The manager is also the session-ledger's renewal owner — re-mint the SAME nkey
|
|
1533
1533
|
// with a fresh bounded exp AND re-stage its new credId into the §13.1 family, through the
|
|
1534
1534
|
// scoped executor. Without this the standing session-ledger connection dies at its TTL and
|
|
1535
1535
|
// `attach` stops establishing sessions until a restart. The connection's authenticator presents
|
|
@@ -2644,7 +2644,7 @@ export class Manager {
|
|
|
2644
2644
|
return { ok: false, error: rejectionText(e) };
|
|
2645
2645
|
}
|
|
2646
2646
|
}
|
|
2647
|
-
/** The ONE shared control-admission chokepoint
|
|
2647
|
+
/** The ONE shared control-admission chokepoint BOTH dispatch doors
|
|
2648
2648
|
* run — the v0.3 `ctl` door ({@link handle}) and the v0.4 `ep` service handlers
|
|
2649
2649
|
* ({@link serveGated}): the maintenance/resume fence (`beginLifecycle`: a resume-pending or
|
|
2650
2650
|
* non-active manager accepts no ordinary control work) and then the F5(a) membership gate
|
|
@@ -2679,7 +2679,7 @@ export class Manager {
|
|
|
2679
2679
|
admission.release();
|
|
2680
2680
|
}
|
|
2681
2681
|
}
|
|
2682
|
-
/** The ep door's ADMIN flag for a caller
|
|
2682
|
+
/** The ep door's ADMIN flag for a caller. Static mesh: `true` — the
|
|
2683
2683
|
* admin-grade rows (any-mode despawn/attach, the `manager.admin` family, `launch`) are minted
|
|
2684
2684
|
* only into operator instruments (§13.2: `any` is operator-policy-mintable; the agent/spawn
|
|
2685
2685
|
* rollups never carry them), so REACHING the handler is holding the admin tier, exactly as
|
|
@@ -2688,9 +2688,9 @@ export class Manager {
|
|
|
2688
2688
|
* subject-parsed caller tuple to its host-owned authorization callback, with no participant-ledger
|
|
2689
2689
|
* fallback. The same fresh authority drives {@link psOwnerFilter}, so a revoked scope demotes the
|
|
2690
2690
|
* very next call even on a still-valid bearer. Local ledger read failures authorize nothing;
|
|
2691
|
-
* remote host-state faults throw and fail the operation closed.
|
|
2692
|
-
*
|
|
2693
|
-
*
|
|
2691
|
+
* remote host-state faults throw and fail the operation closed.
|
|
2692
|
+
* NAMED RESIDUAL: the static `true` has no serve-time re-check — a LEAKED static admin
|
|
2693
|
+
* instrument keeps its reach until the credential's bounded
|
|
2694
2694
|
* TTL (the one-shot 5-minute profile), the same static-revoke≠reconnect-death class ruled
|
|
2695
2695
|
* across this campaign; static revocation is the TTL, not a ledger. */
|
|
2696
2696
|
async epAdminReach(caller) {
|
|
@@ -2796,15 +2796,15 @@ export class Manager {
|
|
|
2796
2796
|
if (accepted.by !== agent.name)
|
|
2797
2797
|
throw new EpEnvelopeError("permission-denied", `run-answer amend is allowed to ${agent.name} only for an answer recorded under its own name; this step's accepted answer is ${JSON.stringify(accepted.by)}'s`);
|
|
2798
2798
|
}
|
|
2799
|
-
/** The v0.4 typed command table
|
|
2799
|
+
/** The v0.4 typed command table: every ordinary handler runs the SHARED
|
|
2800
2800
|
* admission chokepoint ({@link serveGated}) and then delegates to the SAME op core the ctl
|
|
2801
|
-
* door dispatches (
|
|
2801
|
+
* door dispatches (one core, two thin doors). The resume/preservation family
|
|
2802
2802
|
* deliberately BYPASSES serveGated — exactly as it sits before {@link admitControl} on the ctl
|
|
2803
2803
|
* door (those ops must run while `resumeRequired` fences ordinary work) — riding its own state
|
|
2804
|
-
* fences; its ep gate is the admin-grade `manager.admin` capability grant (
|
|
2804
|
+
* fences; its ep gate is the admin-grade `manager.admin` capability grant (static
|
|
2805
2805
|
* admin-class commands are capability-gated + untargeted, never a fabricated ledger mode).
|
|
2806
2806
|
*
|
|
2807
|
-
* TIER SEMANTICS on the ep door
|
|
2807
|
+
* TIER SEMANTICS on the ep door: the tier lives in the CALLER'S
|
|
2808
2808
|
* GRANT, refined per-op exactly as the ctl doors refine their subject tier. Owner-mode
|
|
2809
2809
|
* `despawn`/`attach` keep the privileged semantics (`admin=false`, own-domain via
|
|
2810
2810
|
* {@link authorizeNamed}) — every spawn-capable agent holds those rows. ANY-mode requests are
|
|
@@ -2828,13 +2828,13 @@ export class Manager {
|
|
|
2828
2828
|
const callerOf = (ctx) => principalKey(ctx.subject.caller.owner, ctx.subject.caller.actor).key;
|
|
2829
2829
|
// A ctl-core failure reply becomes the §13.3 structured error the serve boundary publishes.
|
|
2830
2830
|
// The data half of a failure reply (e.g. a degraded resume result) rides the error MESSAGE
|
|
2831
|
-
// only — the
|
|
2831
|
+
// only — the action model gives failures a typed channel.
|
|
2832
2832
|
const unwrap = (r) => {
|
|
2833
2833
|
if (!r.ok)
|
|
2834
2834
|
throw new EpEnvelopeError("failed-precondition", r.error ?? "the operation failed");
|
|
2835
2835
|
return r.data;
|
|
2836
2836
|
};
|
|
2837
|
-
// The admin-family serve gate
|
|
2837
|
+
// The admin-family serve gate: every `manager.admin`-class
|
|
2838
2838
|
// command re-checks operator reach AT SERVE TIME - static: true (the mint boundary already
|
|
2839
2839
|
// gates the rows to instruments); user mesh: the caller's CURRENT ledger scope must still
|
|
2840
2840
|
// carry `admin` ({@link epAdminReach}'s fresh read), so a revoked scope demotes the very next
|
|
@@ -2942,8 +2942,8 @@ export class Manager {
|
|
|
2942
2942
|
throw new EpEnvelopeError(persona.error.startsWith("no persona") ? "not-found" : "failed-precondition", persona.error);
|
|
2943
2943
|
return { agent: persona.agent };
|
|
2944
2944
|
}),
|
|
2945
|
-
//
|
|
2946
|
-
// progress + terminal off-handler (no ~30s block). The blocking reply path is gone
|
|
2945
|
+
// `spawn` is an ACTION - accept a goal + reply the acceptance floor payload, drive
|
|
2946
|
+
// progress + terminal off-handler (no ~30s block). The blocking reply path is gone.
|
|
2947
2947
|
// #373: arming the event plane (events: true, or the omission that arms it by default)
|
|
2948
2948
|
// follows owner-domain reach on a user mesh. Resolve the caller's admin tier once for
|
|
2949
2949
|
// the residual cross-owner case; opStart decides before connector resolution or grant
|
|
@@ -3013,7 +3013,7 @@ export class Manager {
|
|
|
3013
3013
|
// deployer bearer would then bypass owner-equality (operator launch) despite the view holding
|
|
3014
3014
|
// no admin rows. Uniform owner-equality removes that divergence in the least-privilege
|
|
3015
3015
|
// direction.
|
|
3016
|
-
//
|
|
3016
|
+
// Manifest `launch` is an ACTION through the SAME chokepoint as spawn -
|
|
3017
3017
|
// the manifest resolve + owner-equality authz run in opLaunch's accept path, then the goal
|
|
3018
3018
|
// drives progress + terminal. The acceptance floor is the allocated identity + goal coords.
|
|
3019
3019
|
launch: (ctx) => this.serveGated(ctx, () => this.serveSpawnGoal(ctx, (h) => this.opLaunch(args(ctx), callerOf(ctx), false, h, ctx.subject.route))),
|
|
@@ -3624,7 +3624,7 @@ export class Manager {
|
|
|
3624
3624
|
rmSync(a.restart.sessionStatePath, { force: true });
|
|
3625
3625
|
if (a.seatHome)
|
|
3626
3626
|
rmSync(a.seatHome, { recursive: true, force: true });
|
|
3627
|
-
//
|
|
3627
|
+
// End any live §13.6 attach session bound to THIS incarnation with the honest
|
|
3628
3628
|
// `target-despawn` reason. Fires once per agent on every free path (despawn / self-stop / reap /
|
|
3629
3629
|
// exit) via the `agents` guard above; a no-op when no plane or no live session for the target.
|
|
3630
3630
|
this.sessionPlane?.endForTarget(a.name, a.lifecycleUid, "target-despawn");
|
|
@@ -3973,9 +3973,14 @@ export class Manager {
|
|
|
3973
3973
|
console.error(`despawn ${a.name}: the agent's retirement completed; the name is free for reuse`);
|
|
3974
3974
|
}
|
|
3975
3975
|
}
|
|
3976
|
-
else {
|
|
3976
|
+
else if (cur) {
|
|
3977
3977
|
console.error(`despawn ${a.name}: retirement confirmed for a prior lifecycle of "${a.name}"; the current hold is left intact`);
|
|
3978
3978
|
}
|
|
3979
|
+
else {
|
|
3980
|
+
// No hold stands, as after an orphaned spawn's rollback: that lifecycle was never despawned and
|
|
3981
|
+
// has no successor, so this line names neither.
|
|
3982
|
+
console.error(`retire ${a.name}: lifecycle ${a.lifecycleUid} retired; the name had no hold to clear`);
|
|
3983
|
+
}
|
|
3979
3984
|
}
|
|
3980
3985
|
/** The teardown's ASYNC BROKER PHASE: mint the ephemeral target-pinned deprovisioner cred and
|
|
3981
3986
|
* delete the agent's broker footprint (dm_/dlv_ durables + read-ACL row). Split from
|
|
@@ -4397,7 +4402,7 @@ export class Manager {
|
|
|
4397
4402
|
}
|
|
4398
4403
|
return live;
|
|
4399
4404
|
}
|
|
4400
|
-
/** THE single name-liveness predicate both the hard-pinned collision refuse
|
|
4405
|
+
/** THE single name-liveness predicate both the hard-pinned collision refuse and
|
|
4401
4406
|
* uniqueName's numbering consult, so they can never drift: a name is taken if this manager
|
|
4402
4407
|
* reserves/manages/retires it OR a roster-live occupant already holds it. Pass a pre-built
|
|
4403
4408
|
* {@link liveRosterNames} set when checking many names in one allocation. */
|
|
@@ -5230,9 +5235,9 @@ export class Manager {
|
|
|
5230
5235
|
// unconditional: a half-wired endpoint without the seam must fail loud here, not silently
|
|
5231
5236
|
// allocate off a pre-snapshot roster.
|
|
5232
5237
|
await this.ep.waitForPresenceSnapshot();
|
|
5233
|
-
//
|
|
5238
|
+
// Spawn-as-action: a HARD-PINNED name — an imperative `--name`/identity override
|
|
5234
5239
|
// or a manifest-declared name (opts.resolved) — that collides with a LIVE/provisioning/reserved
|
|
5235
|
-
// incarnation REFUSES loud at accept, BEFORE any reserve/mint/bind
|
|
5240
|
+
// incarnation REFUSES loud at accept, BEFORE any reserve/mint/bind, never a silent `_2`
|
|
5236
5241
|
// suffix (so an address-by-triple caller's pinned name can't be re-pointed). A PERSONA-DERIVED
|
|
5237
5242
|
// base name (no pin) keeps uniqueName's collision numbering, so multi-peer `spawn reviewer` twice
|
|
5238
5243
|
// still yields reviewer + reviewer_2. The alias-reuse gate above is orthogonal and already fired.
|
|
@@ -5243,7 +5248,7 @@ export class Manager {
|
|
|
5243
5248
|
// this manager's agents/reserved/retiring PLUS the roster-live set) - a hard-pinned name
|
|
5244
5249
|
// colliding with ANY live incarnation (managed, unmanaged foreground/connector, or another
|
|
5245
5250
|
// manager's agent) refuses cleanly at accept, rather than minting the collision and black-
|
|
5246
|
-
// holing on the broker/auth refusal (
|
|
5251
|
+
// holing on the broker/auth refusal (a pinned name live under another manager MUST refuse).
|
|
5247
5252
|
if (this.nameInUse(identityName))
|
|
5248
5253
|
return { ok: false, error: `the name "${identityName}" is hard-pinned (${opts.resolved ? "manifest-declared" : "--name/identity override"}) but is already held by a live incarnation (managed here, an unmanaged foreground/connector session, or another manager's agent); a pinned same-name collision refuses at accept - pick another name or despawn the existing one` };
|
|
5249
5254
|
name = identityName;
|
|
@@ -5285,11 +5290,11 @@ export class Manager {
|
|
|
5285
5290
|
// before anything recorded how to address them, so a crash in that window left a live seat
|
|
5286
5291
|
// outside every manager and the successor retired the slot over it.
|
|
5287
5292
|
const custody = this.reserveCustody();
|
|
5288
|
-
// ACCEPT SEAM (
|
|
5293
|
+
// ACCEPT SEAM (spawn-as-action): the incarnation identity is minted and NOTHING has
|
|
5289
5294
|
// been provisioned yet — the action serve path binds the goal + replies the acceptance HERE. A
|
|
5290
5295
|
// throw (bind conflict / duplicate goalId) aborts the spawn before provisioning: the catch below
|
|
5291
5296
|
// returns the failure and the finally releases the reserve, so a refused accept leaves zero
|
|
5292
|
-
// footprint
|
|
5297
|
+
// footprint. The hosted enrollment arm is the exception, below. Blocking callers
|
|
5293
5298
|
// (roster boot) pass no hooks and this is a no-op.
|
|
5294
5299
|
// The ALLOCATED agent's addressing triple (the acceptance floor names what was actually
|
|
5295
5300
|
// allocated, never the requested-but-unallocated name). Static/open key on DEV_OWNER + the
|
|
@@ -5521,7 +5526,7 @@ export class Manager {
|
|
|
5521
5526
|
spec.env = { ...spec.env, COTAL_MANAGER_INSTANCE: this.managerInstanceId };
|
|
5522
5527
|
handle = await this.spawnCustodied(name, spec, cwd, custody);
|
|
5523
5528
|
}
|
|
5524
|
-
hooks?.onLaunched?.(); //
|
|
5529
|
+
hooks?.onLaunched?.(); // The "launched" progress edge (process spawned, pre-presence)
|
|
5525
5530
|
const managed = {
|
|
5526
5531
|
name,
|
|
5527
5532
|
role,
|
|
@@ -5688,7 +5693,7 @@ export class Manager {
|
|
|
5688
5693
|
// OMIT an absent role: the goal terminal commits this data through the strict
|
|
5689
5694
|
// canonicalJson (undefined never coerces to null, SPEC 13.6), so a role-less spawn would
|
|
5690
5695
|
// otherwise fail its succeeded terminal. The CLI/connector already render an absent role as
|
|
5691
|
-
// "no role", so dropping the key preserves the reply (
|
|
5696
|
+
// "no role", so dropping the key preserves the reply (surfaced by readiness:live).
|
|
5692
5697
|
const okData = { name, agent, id: managed.id, mode: handle.kind, lifecycleUid, ...(role !== undefined ? { role } : {}), ...(opts.eventsNotice !== undefined ? { eventsNotice: opts.eventsNotice } : {}) };
|
|
5693
5698
|
await hooks?.onOutcome?.({ kind: "succeeded", data: okData });
|
|
5694
5699
|
return { ok: true, data: okData };
|
|
@@ -6635,7 +6640,7 @@ export class Manager {
|
|
|
6635
6640
|
return { ok: false, error: `no agent "${name}"` };
|
|
6636
6641
|
return this.despawnCore(a, caller, admin, args.graceful !== false);
|
|
6637
6642
|
}
|
|
6638
|
-
/** The ONE named-terminal core both doors share
|
|
6643
|
+
/** The ONE named-terminal core both doors share: the ctl named `stop`
|
|
6639
6644
|
* and the v0.4 targeted `despawn` are the same terminal — authorize by the shared policy
|
|
6640
6645
|
* ({@link authorizeNamed}: own-child / owner-domain on privileged, any on admin), stop, track.
|
|
6641
6646
|
* The ep door runs the SAME two pieces separately so a policy denial surfaces as the §13.3
|
|
@@ -6653,7 +6658,7 @@ export class Manager {
|
|
|
6653
6658
|
despawnAuthorized(a, graceful, trackNonAdmin, caller) {
|
|
6654
6659
|
this.stopHandle(a, graceful);
|
|
6655
6660
|
this.trackStoppedHandle(a, trackNonAdmin, { kind: "stopped-requested", requester: caller });
|
|
6656
|
-
void this.cancelAgentGoal(a.name, graceful ? "graceful" : "terminate"); //
|
|
6661
|
+
void this.cancelAgentGoal(a.name, graceful ? "graceful" : "terminate"); // Cancel a live spawn goal
|
|
6657
6662
|
return { ok: true, data: { name: a.name, stopped: true, graceful } };
|
|
6658
6663
|
}
|
|
6659
6664
|
/** Retire one agent this manager launched under a delegated user intent, through the host
|
|
@@ -6808,7 +6813,7 @@ export class Manager {
|
|
|
6808
6813
|
await nc.drain().catch(() => nc.close());
|
|
6809
6814
|
}
|
|
6810
6815
|
}
|
|
6811
|
-
/**
|
|
6816
|
+
/** The open-mesh counterpart of {@link withEndpointServeExecutor}: an OPEN mesh has no
|
|
6812
6817
|
* credential system, so there is no scoped executor to mint - the same §13.1 gate/records
|
|
6813
6818
|
* writes ride a bare one-shot connection (the broker enforces nothing on an open mesh; the
|
|
6814
6819
|
* ceremony still produces the real gate, epoch, and registration the serve rails run on). */
|
|
@@ -6829,7 +6834,7 @@ export class Manager {
|
|
|
6829
6834
|
await nc.drain().catch(() => nc.close());
|
|
6830
6835
|
}
|
|
6831
6836
|
}
|
|
6832
|
-
/** The served manager-level health summary
|
|
6837
|
+
/** The served manager-level health summary. */
|
|
6833
6838
|
managerStatusData() {
|
|
6834
6839
|
return {
|
|
6835
6840
|
instanceId: this.managerInstanceId,
|
|
@@ -6936,7 +6941,7 @@ export class Manager {
|
|
|
6936
6941
|
})().catch(() => this.serveTransportDown.add(nc));
|
|
6937
6942
|
let handle;
|
|
6938
6943
|
try {
|
|
6939
|
-
// The
|
|
6944
|
+
// The typed surface + the derived `describe`. The descriptor stays PUBLIC in static
|
|
6940
6945
|
// mode: the broker grant (who holds each command's request-publish row) is the
|
|
6941
6946
|
// load-bearing authority tier, and a static single-operator mesh leaks nothing by listing
|
|
6942
6947
|
// command names; the trusted per-caller `view(caller)` scoping joins the user-mode
|
|
@@ -7029,8 +7034,8 @@ export class Manager {
|
|
|
7029
7034
|
await this.stop();
|
|
7030
7035
|
process.exit(1);
|
|
7031
7036
|
}
|
|
7032
|
-
/**
|
|
7033
|
-
* command surface on the ep rails -
|
|
7037
|
+
/** Register the manager as an ordinary v0.4 `service` endpoint and serve its typed
|
|
7038
|
+
* command surface on the ep rails - the manager's ONLY control door. On an AUTH mesh
|
|
7034
7039
|
* the whole credential path is the SAME one an ordinary endpoint traverses (the enforcement
|
|
7035
7040
|
* test that keeps "ordinary" honest): provision the §13.1 issuance gate, drive the
|
|
7036
7041
|
* registration BARRIER's gate CAS, then release the serve credential only on the mint FENCE's
|
|
@@ -7065,7 +7070,7 @@ export class Manager {
|
|
|
7065
7070
|
console.error(`remote manager service endpoint activated: ${MANAGER_ENDPOINT}/${this.managerInstanceId} (epoch ${state.grant.epoch})`);
|
|
7066
7071
|
return;
|
|
7067
7072
|
}
|
|
7068
|
-
// The §13.7 contract store is REGISTRATION's dependency, ensured here MODE-NEUTRALLY
|
|
7073
|
+
// The §13.7 contract store is REGISTRATION's dependency, ensured here MODE-NEUTRALLY:
|
|
7069
7074
|
// it used to ride the static-only lifecycle reconcile, so a USER-mode manager registered
|
|
7070
7075
|
// against an absent stream and its artifact publish died no-responders (live-repro'd). A
|
|
7071
7076
|
// provisioner one-shot creates-or-verifies it (config-B immutability incl. the shadowed-legacy
|
|
@@ -7075,7 +7080,7 @@ export class Manager {
|
|
|
7075
7080
|
const provCreds = auth ? await mintCreds(auth, newIdentity(), "provisioner") : undefined;
|
|
7076
7081
|
const provNc = await this.dial({ ...standaloneConnectOpts({ creds: provCreds, /* not yet wired to a recorded transport */ tls: false }), maxReconnectAttempts: 0 });
|
|
7077
7082
|
try {
|
|
7078
|
-
//
|
|
7083
|
+
// The manager now WRITES goal facts (EPF) + progress events (EPE), so the §13.12
|
|
7079
7084
|
// endpoint streams must exist. Nothing provisioned them before spawn-as-action (no endpoint
|
|
7080
7085
|
// wrote to EPF/EPE), so the manager ensures the full set here over the provisioner (whose
|
|
7081
7086
|
// STREAM.CREATE now covers them), idempotently - createEndpointStreams is a superset of
|
|
@@ -7103,7 +7108,7 @@ export class Manager {
|
|
|
7103
7108
|
const authority = {
|
|
7104
7109
|
authorize: (name, owner) => ({ authorized: name === MANAGER_ENDPOINT && owner === DEV_OWNER, revision: 0 }),
|
|
7105
7110
|
};
|
|
7106
|
-
// The STABLE serve identity
|
|
7111
|
+
// The STABLE serve identity: the PERSISTED serve nkey, reused across restart so the
|
|
7107
7112
|
// gate binds the SAME principal (§13.1 serving-principal binding) - provisionEndpointGateOpen
|
|
7108
7113
|
// stays idempotent and verified eviction has a stable target. Renewals re-mint the same nkey
|
|
7109
7114
|
// with a fresh bounded exp; a restart re-provisions the same (idempotent) gate + re-registers.
|
|
@@ -7114,7 +7119,7 @@ export class Manager {
|
|
|
7114
7119
|
// The STABLE goal-writer identity — a SIBLING credential in the same §13.1 family
|
|
7115
7120
|
// (not the gate's bound serving principal), so the run block can family-stage it.
|
|
7116
7121
|
this.goalWriterIdentity = siblings.goalWriter;
|
|
7117
|
-
//
|
|
7122
|
+
// The STABLE session-LEDGER identity — another SIBLING in the SAME §13.1 family, so
|
|
7118
7123
|
// the takeover barrier revokes a deposed manager's ledger cred alongside its goal-writer. The
|
|
7119
7124
|
// per-session serving creds join the same family, each with its own fresh identity.
|
|
7120
7125
|
this.sessionLedgerIdentity = siblings.sessionLedger;
|
|
@@ -7135,17 +7140,17 @@ export class Manager {
|
|
|
7135
7140
|
}
|
|
7136
7141
|
};
|
|
7137
7142
|
const publishAndProvision = async ({ authKv, nc: execNc }) => {
|
|
7138
|
-
// §13.7 contract-artifact publication
|
|
7143
|
+
// §13.7 contract-artifact publication: every schema root + its closure manifest, plus
|
|
7139
7144
|
// the cluster document + ITS manifest, land in the EPC store BEFORE the registration that
|
|
7140
7145
|
// advertises their digests — so a caller can always fetch-verify-compile a registered
|
|
7141
|
-
// digest (the
|
|
7146
|
+
// digest (the generic-invoke read path). Create-only + content-addressed: a retry
|
|
7142
7147
|
// or a same-artifact republish is an idempotent lost-CAS. The registration itself still
|
|
7143
7148
|
// verifies against the in-memory copies (the manager is the author).
|
|
7144
7149
|
const storeCtx = await contractStoreContext(execNc, this.space);
|
|
7145
7150
|
for (const value of [...managerContractArtifactValues(), artifacts.document, artifacts.manifest])
|
|
7146
7151
|
await publishContractArtifact(storeCtx, contractArtifactCanonicalBytes(value));
|
|
7147
|
-
// §13.1 pre-registration
|
|
7148
|
-
// principal — provisioned ONCE, on the FIRST registration. On a RESTART
|
|
7152
|
+
// §13.1 pre-registration: the issuance gate, born open@gen0 bound to the serve
|
|
7153
|
+
// principal — provisioned ONCE, on the FIRST registration. On a RESTART the gate
|
|
7149
7154
|
// already EXISTS (advanced past gen0 by the prior registration), so re-provisioning the gen0
|
|
7150
7155
|
// row would conflict "foreign content"; the persisted instanceId makes this a TAKEOVER, and
|
|
7151
7156
|
// registerServiceInstance below freezes + re-registers the EXISTING gate (advancing the epoch
|
|
@@ -7156,7 +7161,7 @@ export class Manager {
|
|
|
7156
7161
|
};
|
|
7157
7162
|
const completeRegistration = async ({ recordsKv, authKv, nc: execNc }) => {
|
|
7158
7163
|
await publishAndProvision({ recordsKv, authKv, nc: execNc });
|
|
7159
|
-
//
|
|
7164
|
+
// On an AUTH mesh a RE-registration (restart of the persisted instanceId)
|
|
7160
7165
|
// must VERIFY-EVICT the superseded serve family BEFORE the epoch advances (§13.1 "old authority
|
|
7161
7166
|
// dies before new authority is visible"). Inject the SCOPED delivery-admin evictor; the OPEN
|
|
7162
7167
|
// mesh mints no serve family, so no evictor (the empty-family path never consults it and a
|
|
@@ -7186,7 +7191,7 @@ export class Manager {
|
|
|
7186
7191
|
// authKv (the fence is live), so its credId lands in `epcred.<e>.<iid>` and the takeover
|
|
7187
7192
|
// barrier revokes it. Open mesh: no mint (no credential system; the goal-writer conn is bare).
|
|
7188
7193
|
const goalWriterCreds = auth ? await this.mintAndStageGoalWriter(authKv) : undefined;
|
|
7189
|
-
//
|
|
7194
|
+
// Mint + family-stage the session-LEDGER credential HERE too (same executor, same
|
|
7190
7195
|
// fence, same §13.1 family), so takeover revokes a deposed manager's ledger connection. The
|
|
7191
7196
|
// per-session SERVING credentials are minted later, one per redemption, into the SAME family.
|
|
7192
7197
|
// Open mesh: no mint (no credential system; the ledger connection stays bare).
|
|
@@ -7249,7 +7254,7 @@ export class Manager {
|
|
|
7249
7254
|
const auth = this.auth;
|
|
7250
7255
|
const identity = this.goalWriterIdentity;
|
|
7251
7256
|
// The issuance gate + §13.1 revocation family are keyed by the REGISTRATION instanceId
|
|
7252
|
-
// (the persisted logical id
|
|
7257
|
+
// (the persisted logical id), NOT the per-process lifecycleUid — the barrier
|
|
7253
7258
|
// enumerates `epcred.<e>.<managerInstanceId>`, so the goal-writer must stage into that family.
|
|
7254
7259
|
const iid = this.managerInstanceId;
|
|
7255
7260
|
const creds = await mintCreds(auth, identity, "goal-writer", { goalWriter: { endpoint: MANAGER_ENDPOINT } });
|
|
@@ -7287,8 +7292,8 @@ export class Manager {
|
|
|
7287
7292
|
});
|
|
7288
7293
|
nc.closed().then((err) => { void this.onGoalWriterConnectionClosed(err ?? undefined); });
|
|
7289
7294
|
gw.nc = nc;
|
|
7290
|
-
// The (i) fence resolver (SPEC 13.6
|
|
7291
|
-
// epoch. This manager reconciles only ITS OWN goals
|
|
7295
|
+
// The (i) fence resolver (SPEC 13.6): resolve an executing instance's CURRENT gate
|
|
7296
|
+
// epoch. This manager reconciles only ITS OWN goals, so it resolves its own
|
|
7292
7297
|
// registration instanceId to this incarnation's serve-grant epoch; a foreign/retired instance
|
|
7293
7298
|
// is `null` (no current terminal to surface). A successor incarnation carries an ADVANCED epoch
|
|
7294
7299
|
// here, so its reads pick the current-epoch subject and the predecessor's terminal is fenced out.
|
|
@@ -7341,7 +7346,7 @@ export class Manager {
|
|
|
7341
7346
|
return;
|
|
7342
7347
|
console.error("! manager goal-writer could not be re-dialed; leaving the credential to the next renewal pass");
|
|
7343
7348
|
}
|
|
7344
|
-
/**
|
|
7349
|
+
/** Spawn-as-action: stand up the standing self-mediated goal-writer connection +
|
|
7345
7350
|
* ActionContext. Mode-dual, mirroring {@link registerManagerService}: an AUTH mesh uses the
|
|
7346
7351
|
* scoped `goal-writer` credential already minted + family-STAGED inside registration's run block
|
|
7347
7352
|
* ({@link mintAndStageGoalWriter} — DISJOINT grant from the serve cred, SHARED §13.1 revocation
|
|
@@ -7375,7 +7380,7 @@ export class Manager {
|
|
|
7375
7380
|
catch { /* best effort */ }
|
|
7376
7381
|
}
|
|
7377
7382
|
}
|
|
7378
|
-
/**
|
|
7383
|
+
/** Mint the standing `session-ledger` credential and STAGE it into this instance's
|
|
7379
7384
|
* §13.1 revocation family (`epcred.<e>.<iid>`), over the passed executor's `authKv` — EXACTLY the
|
|
7380
7385
|
* {@link mintAndStageGoalWriter} pattern, under the same open-and-commit fence.
|
|
7381
7386
|
*
|
|
@@ -7388,7 +7393,7 @@ export class Manager {
|
|
|
7388
7393
|
async mintAndStageSessionLedger(authKv) {
|
|
7389
7394
|
const auth = this.auth;
|
|
7390
7395
|
const identity = this.sessionLedgerIdentity;
|
|
7391
|
-
// Registration instanceId (
|
|
7396
|
+
// Registration instanceId (the persisted logical id), not the per-process lifecycleUid:
|
|
7392
7397
|
// the barrier enumerates `epcred.<e>.<managerInstanceId>`, so the ledger cred joins that family.
|
|
7393
7398
|
const iid = this.managerInstanceId;
|
|
7394
7399
|
const fence = serveIssuanceGateKv(authKv, this.space, { endpoint: MANAGER_ENDPOINT, instanceId: iid });
|
|
@@ -7455,7 +7460,7 @@ export class Manager {
|
|
|
7455
7460
|
return;
|
|
7456
7461
|
console.error("! manager session-ledger could not be re-dialed; leaving the credential to the next renewal pass");
|
|
7457
7462
|
}
|
|
7458
|
-
/**
|
|
7463
|
+
/** Stand up the ONE §13.6 session plane on its own standing connection. Mode-dual,
|
|
7459
7464
|
* mirroring {@link startGoalWriter}: an AUTH mesh presents the scoped `session-ledger` cred
|
|
7460
7465
|
* already minted + family-staged inside registration's run block ({@link mintAndStageSessionLedger});
|
|
7461
7466
|
* an OPEN mesh uses a bare connection (no credential system to mint from). The connection presents
|
|
@@ -7506,10 +7511,10 @@ export class Manager {
|
|
|
7506
7511
|
const keyId = rotating.current().keyId;
|
|
7507
7512
|
this.sessionPlane = new ManagerSessionPlane({
|
|
7508
7513
|
space: this.space,
|
|
7509
|
-
// The session's serving identity is the persisted REGISTRATION instanceId
|
|
7514
|
+
// The session's serving identity is the persisted REGISTRATION instanceId, not the
|
|
7510
7515
|
// per-process lifecycleUid: a restarted manager re-registers the SAME logical instanceId with
|
|
7511
7516
|
// an ADVANCED epoch, so a client re-attaches by the same instance while the epoch fences the
|
|
7512
|
-
// old incarnation's sessions
|
|
7517
|
+
// old incarnation's sessions.
|
|
7513
7518
|
serving: { instanceId: this.managerInstanceId, epoch: serveEpoch },
|
|
7514
7519
|
// Read through the rotator on EVERY use rather than capturing a key: signing takes the newest
|
|
7515
7520
|
// generation, and resolution accepts any generation still inside its overlap so an artifact
|
|
@@ -7538,7 +7543,7 @@ export class Manager {
|
|
|
7538
7543
|
console.error(`manager session plane standing (endpoint ${MANAGER_ENDPOINT}, epoch ${serveEpoch}, ${(this.auth || this.remoteAuthority) ? "scoped session-ledger cred, §13.1 family-staged" : "open/bare"})`);
|
|
7539
7544
|
}
|
|
7540
7545
|
/**
|
|
7541
|
-
* The per-session SERVING credential seam (
|
|
7546
|
+
* The per-session SERVING credential seam (SPEC 13.6): the manager mints, gate-stages,
|
|
7542
7547
|
* connects and revokes ONE credential per live session, replacing a standing credential that held
|
|
7543
7548
|
* `eps.manager.*.<epoch>.{in,out}` and so reached every live session's bytes at its epoch.
|
|
7544
7549
|
*
|
|
@@ -7713,8 +7718,8 @@ export class Manager {
|
|
|
7713
7718
|
await nc.drain().catch(() => nc.close());
|
|
7714
7719
|
}
|
|
7715
7720
|
}
|
|
7716
|
-
// Single-manager
|
|
7717
|
-
// at a time), so all are reconciled. The `iid` field is the hook
|
|
7721
|
+
// Single-manager: EVERY inherited entry belongs to a DEAD predecessor (only one manager
|
|
7722
|
+
// at a time), so all are reconciled. The `iid` field is the hook the multi-instance sweep
|
|
7718
7723
|
// filters on (skip a goal whose accepting `iid` is a still-LIVE sibling — never settle its goal).
|
|
7719
7724
|
for (const entry of entries) {
|
|
7720
7725
|
if (this.goalAcceptances.has(entry.ref.goalId) || this.turnAcceptances.has(turnKey(entry.ref)))
|
|
@@ -7788,9 +7793,9 @@ export class Manager {
|
|
|
7788
7793
|
const t = setTimeout(() => { settle().catch((e) => console.error(`! goal reconcile settle ${ref.goalId}: ${rejectionText(e)}`)); }, remaining + 100);
|
|
7789
7794
|
t.unref?.();
|
|
7790
7795
|
}
|
|
7791
|
-
/**
|
|
7796
|
+
/** Publish a goal PROGRESS event on the caller-scoped epe subtree, over the SERVE
|
|
7792
7797
|
* connection (which holds the `epe.<e>.<iid>.<epoch>.>` egress grant; the goal-writer deliberately
|
|
7793
|
-
* does not). The terminal rides a final event `phase:"terminal"` (
|
|
7798
|
+
* does not). The terminal rides a final event `phase:"terminal"` (the caller follows epe to
|
|
7794
7799
|
* the terminal; the durable result fact + inspect/ps are the reconcile authority). A dropped event
|
|
7795
7800
|
* is non-fatal (the terminal is authoritative in the journal). */
|
|
7796
7801
|
emitGoalProgress(ref, epoch, event) {
|
|
@@ -7838,13 +7843,13 @@ export class Manager {
|
|
|
7838
7843
|
if (observed.processEpoch !== epoch)
|
|
7839
7844
|
throw new EpEnvelopeError("expired", `the manager's issuance gate epoch is ${observed.processEpoch} but this goal was accepted under epoch ${epoch}; a superseded incarnation never commits a goal terminal (SPEC 13.6)`);
|
|
7840
7845
|
}
|
|
7841
|
-
/** Serve `spawn`/`launch` as an ACTION
|
|
7846
|
+
/** Serve `spawn`/`launch` as an ACTION. Authz already ran in {@link serveGated}. The
|
|
7842
7847
|
* accept path runs INLINE on the handler ({@link startAgent} with hooks): the goal binds + the
|
|
7843
|
-
* acceptance replies the moment the identity is minted, BEFORE any provision
|
|
7848
|
+
* acceptance replies the moment the identity is minted, BEFORE any provision, or on the
|
|
7844
7849
|
* hosted enrollment arm once the host has enrolled the agent; progress and the terminal are driven
|
|
7845
7850
|
* OFF-handler, so the ~30s readiness wait no longer blocks the reply.
|
|
7846
7851
|
* Returns the acceptance floor payload {name, owner, actor, uid, goalId, fingerprint, executor}
|
|
7847
|
-
* (the ALLOCATED identity). goalId = the request id (env.id
|
|
7852
|
+
* (the ALLOCATED identity). goalId = the request id (env.id). */
|
|
7848
7853
|
async serveSpawnGoal(ctx, run) {
|
|
7849
7854
|
if (this.execution === "none")
|
|
7850
7855
|
throw new EpEnvelopeError("unimplemented", this.executionRefusal());
|
|
@@ -7862,7 +7867,7 @@ export class Manager {
|
|
|
7862
7867
|
const epoch = executor.epoch;
|
|
7863
7868
|
const acceptedAt = Date.now();
|
|
7864
7869
|
// Idempotent same-goalId retry (a client re-send): serve the IDENTICAL acceptance without
|
|
7865
|
-
// re-running the accept path — so a HARD-PINNED retry does not trip the
|
|
7870
|
+
// re-running the accept path — so a HARD-PINNED retry does not trip the same-name refuse and no
|
|
7866
7871
|
// name is re-allocated. Same-incarnation rides the live map; the create-only bindGoal in onAccepted
|
|
7867
7872
|
// still fences a CONCURRENT same-goalId race (the loser aborts and serves the winner's acceptance).
|
|
7868
7873
|
const prior = this.goalAcceptances.get(goalId);
|
|
@@ -7969,8 +7974,8 @@ export class Manager {
|
|
|
7969
7974
|
// No claim needed here: `ownsGoal` is still false, so the commit path refuses this attempt.
|
|
7970
7975
|
throw new EpEnvelopeError("failed-precondition", `goal "${goalId}" was accepted by instance "${idx.existing.iid}"; that instance's acceptance is served and this attempt provisions nothing (SPEC 13.6)`);
|
|
7971
7976
|
}
|
|
7972
|
-
// Bind AFTER the accept-path checks (
|
|
7973
|
-
// a create-only CAS per goalId (
|
|
7977
|
+
// Bind AFTER the accept-path checks (name/capacity/persona) + identity mint, BEFORE any provision:
|
|
7978
|
+
// a create-only CAS per goalId (a refused accept above left zero bind, zero reserve).
|
|
7974
7979
|
const b = await bindGoal(gw.ctx, ref, fingerprint);
|
|
7975
7980
|
if (!b.bound) {
|
|
7976
7981
|
if (b.existing.fingerprint !== fingerprint)
|
|
@@ -8002,7 +8007,7 @@ export class Manager {
|
|
|
8002
8007
|
});
|
|
8003
8008
|
acceptance = { name, owner: agentTriple.owner, actor: agentTriple.actor, uid: agentTriple.uid, goalId, fingerprint, readinessDeadlineMs: readinessTimeoutMs, executor };
|
|
8004
8009
|
this.goalAcceptances.set(goalId, acceptance);
|
|
8005
|
-
this.agentGoals.set(name, ref); //
|
|
8010
|
+
this.agentGoals.set(name, ref); // A despawn of this name mid-goal drives the cancel path
|
|
8006
8011
|
resolveAccept(acceptance);
|
|
8007
8012
|
this.emitGoalProgress(ref, epoch, { phase: "handoff" });
|
|
8008
8013
|
},
|
|
@@ -8015,7 +8020,7 @@ export class Manager {
|
|
|
8015
8020
|
onTerminalDeferred: () => { terminalEntered = true; },
|
|
8016
8021
|
});
|
|
8017
8022
|
bg.then((reply) => {
|
|
8018
|
-
// Refused BEFORE onAccepted (
|
|
8023
|
+
// Refused BEFORE onAccepted (hard-pin collision, capacity, persona-not-found) — no goal bound.
|
|
8019
8024
|
if (acceptance === undefined) {
|
|
8020
8025
|
const details = reply.details;
|
|
8021
8026
|
rejectAccept(new EpEnvelopeError("failed-precondition", reply.error ?? "spawn refused at accept", details));
|
|
@@ -8605,12 +8610,12 @@ export class Manager {
|
|
|
8605
8610
|
console.error(`turn reconcile ${p.goalId}: pending relay to ${p.seat.name}/${p.seat.uid} adopted (deadline ${p.deadlineAt})`);
|
|
8606
8611
|
this.ensureTurnSweep();
|
|
8607
8612
|
}
|
|
8608
|
-
/**
|
|
8613
|
+
/** Settle race: a despawn MID-GOAL drives the goal's cancel terminal - transition to
|
|
8609
8614
|
* `cancelling`, then commit the `cancel` cause on the goal-writer connection. First-terminal-fact
|
|
8610
8615
|
* wins: if the readiness outcome already committed (succeeded/failed/uncertain) the transition or
|
|
8611
8616
|
* the create-only commit loses gracefully and the readiness terminal stands. Fire-and-forget from
|
|
8612
8617
|
* despawn (the process teardown is authoritative for the agent; this settles the GOAL honestly).
|
|
8613
|
-
* Cancel rides the despawn's own authorizeNamed reach
|
|
8618
|
+
* Cancel rides the despawn's own authorizeNamed reach - there is no cancel-by-goalId. */
|
|
8614
8619
|
async cancelAgentGoal(name, mode) {
|
|
8615
8620
|
const gw = this.goalWriter;
|
|
8616
8621
|
const ref = this.agentGoals.get(name);
|
|
@@ -9161,12 +9166,12 @@ export class Manager {
|
|
|
9161
9166
|
retiredSlots++;
|
|
9162
9167
|
continue;
|
|
9163
9168
|
}
|
|
9164
|
-
//
|
|
9169
|
+
// RECONCILE OWNERSHIP (multi-manager-per-space): a manager adjudicates ONLY the non-retired
|
|
9165
9170
|
// rows THIS logical instance owns. A SIBLING manager's active/provisioning row is LEFT UNTOUCHED —
|
|
9166
9171
|
// sweep-terminalizing it would destroy the sibling's live agent (the historical all-agents-kill
|
|
9167
|
-
// hazard, now cross-instance). A legacy row (
|
|
9172
|
+
// hazard, now cross-instance). A legacy row (no owner recorded) predates multi-manager,
|
|
9168
9173
|
// so this manager is its legitimate single-manager-past successor and reconciles it. An orphaned
|
|
9169
|
-
// sibling row is reclaimed only by an explicit operator CAS takeover
|
|
9174
|
+
// sibling row is reclaimed only by an explicit operator CAS takeover, never here.
|
|
9170
9175
|
if (ownedBySibling(row, this.managerInstanceId)) {
|
|
9171
9176
|
preservedForeign++;
|
|
9172
9177
|
continue;
|
|
@@ -9608,7 +9613,7 @@ export class Manager {
|
|
|
9608
9613
|
throw new EpEnvelopeError("unavailable", `input for seat "${a.name}" failed: runtime accepted ${String(bytes)} of ${intendedBytes} bytes`);
|
|
9609
9614
|
return { name: a.name, bytes };
|
|
9610
9615
|
}
|
|
9611
|
-
/** The post-authorization attach effect
|
|
9616
|
+
/** The post-authorization attach effect: mint the holder-bound §13.6 offer, redeem it
|
|
9612
9617
|
* through the ONE session plane (one-use CAS + presenter-equality), and stand up the PTY bridge —
|
|
9613
9618
|
* atomically. The reply is the SIGNED grant (no ws:// URL, non-bearer, never logged); the caller
|
|
9614
9619
|
* redeems it over the mesh with a per-session rails-only cred it mints itself. Only streamable
|
|
@@ -9650,7 +9655,7 @@ export class Manager {
|
|
|
9650
9655
|
const carried = a.launch.resumed?.host;
|
|
9651
9656
|
return { ok: true, data: { grant, ...(carried ? { resumedFrom: { host: carried, source: a.launch.resumed.source } } : {}) } };
|
|
9652
9657
|
}
|
|
9653
|
-
/**
|
|
9658
|
+
/** The console's mesh §13.6 session establisher (backing `POST /session/<name>` on the
|
|
9654
9659
|
* loopback face). Drives THE ONE plane — same establishAttach as the ep `attach` command — with
|
|
9655
9660
|
* the loopback OPERATOR as holder (same-host trust boundary), then hands the browser everything it
|
|
9656
9661
|
* needs to open the caller rail over the broker ws listener: the holder-bound grant, a per-session
|