@cotal-ai/manager 0.133.3 → 0.133.5
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/commands.js +1 -1
- package/dist/endpoint-evict.d.ts +1 -1
- package/dist/endpoint-evict.js +1 -1
- package/dist/holder-liveness.d.ts +1 -1
- package/dist/holder-liveness.js +1 -1
- package/dist/manager.d.ts +16 -16
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +62 -50
- package/dist/manager.js.map +1 -1
- package/dist/resume.d.ts +6 -0
- package/dist/resume.d.ts.map +1 -1
- package/dist/resume.js +4 -1
- package/dist/resume.js.map +1 -1
- package/dist/runtime/pty.js +2 -2
- package/dist/runtime/pty.js.map +1 -1
- package/dist/session/establish.d.ts +5 -5
- package/dist/session/establish.js +5 -5
- package/dist/session/establish.js.map +1 -1
- package/dist/session/plane.js +3 -3
- package/dist/session/plane.js.map +1 -1
- package/dist/static-lifecycle.d.ts +9 -9
- package/dist/static-lifecycle.d.ts.map +1 -1
- package/dist/static-lifecycle.js +13 -13
- package/dist/static-lifecycle.js.map +1 -1
- package/package.json +12 -12
package/dist/manager.js
CHANGED
|
@@ -20,10 +20,10 @@ import { RunHosting } from "./run-hosting.js";
|
|
|
20
20
|
import { enrolledAcl } from "./remote-authority.js";
|
|
21
21
|
import { rejectionText } from "./rejection-text.js";
|
|
22
22
|
import { controlSession } from "./control-session.js";
|
|
23
|
-
import { MAX_AGENTS, parseResumeCommitArgs, parseResumeControlArgs, parseResumeFinalizeArgs, readSeatRecord, removeSeatRecord, writeSeatRecord } from "./resume.js";
|
|
23
|
+
import { MAX_AGENTS, parseResumeCommitArgs, parseResumeControlArgs, parseResumeFinalizeArgs, readSeatRecord, removeSeatRecord, supervisePolicy, writeSeatRecord } from "./resume.js";
|
|
24
24
|
import { TranscriptReceiver } from "./transcript-receive.js";
|
|
25
|
-
//
|
|
26
|
-
// manager-side adapter (transport + slot orchestration + the
|
|
25
|
+
// The static §13.1 lifecycle executor: the shared grammar/stores from core plus the
|
|
26
|
+
// manager-side adapter (transport + slot orchestration + the terminal) — see static-lifecycle.ts.
|
|
27
27
|
import { jetstream, jetstreamManager } from "@nats-io/jetstream";
|
|
28
28
|
import { Kvm } from "@nats-io/kv";
|
|
29
29
|
import { recordsBucket, epAuthBucket, ensureAuthorityStores, ensureContractStore, createEndpointStreams, contractStoreContext, publishContractArtifact, contractArtifactCanonicalBytes, standaloneConnectOpts, credentialIdOf, STANDING_RENEWABLE_TTL_SEC as MANAGED_STATIC_TTL_SEC, STATIC_GENERATION_HORIZON_SEC, issuedHorizon, newArtifactSigner, RotatingSigner, generationAnchor, sessionsBucket, SESSION_GRANT_MAX_TTL_MS, REMOTE_MANAGER_IDENTITY_NAMES, } from "@cotal-ai/core";
|
|
@@ -144,7 +144,7 @@ const STARTUP_RECONCILING = "startup static lifecycle reconciliation";
|
|
|
144
144
|
* leaves the durable alias held and requires a later manager start. */
|
|
145
145
|
export const STATIC_RECONCILE_RETRY_DELAYS_MS = [1_000, 5_000, 30_000];
|
|
146
146
|
const STATIC_RECONCILE_MAX_ATTEMPTS = STATIC_RECONCILE_RETRY_DELAYS_MS.length + 1;
|
|
147
|
-
/** The STABLE retirement opId for one lifecycle
|
|
147
|
+
/** The STABLE retirement opId for one lifecycle: deterministic from the uid, so a
|
|
148
148
|
* despawn retry, a same-name-spawn nudge, and the auth service's boot resume all drive the SAME
|
|
149
149
|
* operation (the rail's idempotence table needs exactly one op per retiring incarnation). 26 hex
|
|
150
150
|
* chars = in the lifecycle-token grammar `[a-z0-9]{26,32}`, collision-resistant. */
|
|
@@ -450,7 +450,7 @@ export class Manager {
|
|
|
450
450
|
/** Expiry stamps (`startedAt + MIN_LIFETIME`) for slots that freed while still young — a
|
|
451
451
|
* count-only, lazily-pruned recycle floor (P4c). Pruned + summed into the ceiling gate. */
|
|
452
452
|
cooling = [];
|
|
453
|
-
/** Names RESERVED PENDING RETIREMENT
|
|
453
|
+
/** Names RESERVED PENDING RETIREMENT: a despawned agent's name stays held until
|
|
454
454
|
* the auth plane confirms its lifecycle's retirement TERMINAL over the auth-admin rail — the
|
|
455
455
|
* alias-reuse gate that closes the same-name despawn→respawn race at its root. An UNCERTAIN
|
|
456
456
|
* outcome (rail down, timeout) keeps the hold with the last attempt's copy; a same-name spawn
|
|
@@ -471,7 +471,7 @@ export class Manager {
|
|
|
471
471
|
* same-name SUCCESSOR (which can spawn after the hold clears but before this flight's `nc.close`
|
|
472
472
|
* yield settles) never joins the predecessor's rail request and skips its own retirement. */
|
|
473
473
|
retiringFlight = new Map();
|
|
474
|
-
/** Wire principals of RETIRED static incarnations
|
|
474
|
+
/** Wire principals of RETIRED static incarnations: populated at every completed
|
|
475
475
|
* static terminal and from the boot sweep's retired slot rows, so a copied credential of a
|
|
476
476
|
* retired incarnation is refused at the control surface even across a manager restart. The
|
|
477
477
|
* durable truth is the slot row + principal-keyed head; this set is the in-memory index of it
|
|
@@ -678,7 +678,7 @@ export class Manager {
|
|
|
678
678
|
* does not re-acquire: a manager that is stopping never puts its own key back. It also closes the
|
|
679
679
|
* lifecycle fence ({@link beginLifecycle}), so no spawn can launch a seat the stop never sees. */
|
|
680
680
|
leaseStopping = false;
|
|
681
|
-
/** The class-2 renewal owner's half-TTL schedule
|
|
681
|
+
/** The class-2 renewal owner's half-TTL schedule; armed only on auth meshes. */
|
|
682
682
|
credRenewTimer;
|
|
683
683
|
/** #2073: the `endpoint-serve` credential's OWN renewal, armed from its `[renewAt, exp)` window
|
|
684
684
|
* (`credsRenewalDelayMs`) rather than left to `credRenewTimer`'s boot-phased TTL/4 tick, which can
|
|
@@ -1182,7 +1182,7 @@ export class Manager {
|
|
|
1182
1182
|
// EPHEMERAL provisioner connection per spawn (withProvisioner); destructive purge mints a PURGER per
|
|
1183
1183
|
// call. So the always-on daemon holds no DM/DLV read, no consumer-create, no stream-admin tamper.
|
|
1184
1184
|
//
|
|
1185
|
-
// STANDING RENEWAL (
|
|
1185
|
+
// STANDING RENEWAL (class 1): the manager holds the DATA signing seed, so it is its
|
|
1186
1186
|
// own renewal owner — the cred rides the endpoint's SOURCE seam and self-remints (same identity,
|
|
1187
1187
|
// pinned by the endpoint) ahead of each bounded supervisor JWT's expiry. A copied supervisor
|
|
1188
1188
|
// cred is broker-dead within the matrix TTL.
|
|
@@ -1197,7 +1197,7 @@ export class Manager {
|
|
|
1197
1197
|
// lifecycle-keyed (SPEC 13.1, fail-before-presence). The manager process is the top of its
|
|
1198
1198
|
// own launch chain (the operator command IS its launcher): its incarnation uid is the
|
|
1199
1199
|
// per-process `managerLifecycleUid` field (also the `managerInstance` audit coordinate on
|
|
1200
|
-
// every static activation
|
|
1200
|
+
// every static activation).
|
|
1201
1201
|
lifecycleUid: this.remoteAuthority?.lifecycleUid ?? this.managerLifecycleUid,
|
|
1202
1202
|
// The supervisor serves control + watches presence; it never consumes chat/dm/task
|
|
1203
1203
|
// (no message handler). consume:false avoids binding consumers it doesn't use — and
|
|
@@ -1267,7 +1267,7 @@ export class Manager {
|
|
|
1267
1267
|
const s = this.serviceServe;
|
|
1268
1268
|
return s?.handle && !s.nc.isClosed() && !this.transportDown.has(s.nc) ? "bound" : "unbound";
|
|
1269
1269
|
});
|
|
1270
|
-
//
|
|
1270
|
+
// Static §13.1: after this instance holds its lease, collect the durable static rows
|
|
1271
1271
|
// now, but do not let their exact-op terminals make the whole space unreachable. The service
|
|
1272
1272
|
// comes up below, then the sweep overlaps the remaining registration work. Two-window
|
|
1273
1273
|
// heal-then-register is slower than one executor, but the overlap is still the no-outage
|
|
@@ -1280,7 +1280,7 @@ export class Manager {
|
|
|
1280
1280
|
// service endpoint registered below. The old three-tier rail (self/manager/admin) is deleted;
|
|
1281
1281
|
// `ctl.delivery`/`ctl.delivery-admin` (the delivery daemon) and `ctl.auth-admin` (the auth
|
|
1282
1282
|
// plane) are separate services and keep their rails.
|
|
1283
|
-
//
|
|
1283
|
+
// Class 2: the manager is the CLASS-2 RENEWAL OWNER — the one control-plane process
|
|
1284
1284
|
// that is resident in EVERY mesh mode (foreground `up`, `up --detach`, same-root refresh) and
|
|
1285
1285
|
// holds the signer. Ordered initial pass NOW (ensureControlPlane starts delivery BEFORE the
|
|
1286
1286
|
// manager, so the daemon's launch-time creds write always precedes this — no write race), then
|
|
@@ -1633,7 +1633,7 @@ export class Manager {
|
|
|
1633
1633
|
}
|
|
1634
1634
|
return false;
|
|
1635
1635
|
}
|
|
1636
|
-
/** One class-2 renewal pass
|
|
1636
|
+
/** One class-2 renewal pass: re-sign `.cotal/delivery.creds` + `.cotal/membership-rw.creds`
|
|
1637
1637
|
* for their existing nkeys, then request the delivery daemon's EXPLICIT `reloadCreds` adoption on the
|
|
1638
1638
|
* delivery-admin rail and persist the audit record (`.cotal/renewal.<spaceKey>.json`) that `cotal doctor auth`
|
|
1639
1639
|
* renders — so "file re-signed" and "daemon adopted" are distinguishable states. A missing daemon
|
|
@@ -1695,7 +1695,7 @@ export class Manager {
|
|
|
1695
1695
|
if (this.daemonRenewalOwner)
|
|
1696
1696
|
writeRenewalRecord(this.workspaceRoot, this.space, { ts: new Date().toISOString(), owner: "manager", results, adoption });
|
|
1697
1697
|
this.warnOnSystemCredExpiry();
|
|
1698
|
-
//
|
|
1698
|
+
// The MANAGER is the renewal owner for its managed-static agent creds —
|
|
1699
1699
|
// supervisor-side PUSH remint for recorded LIVE slots (the child JWT is never proof of
|
|
1700
1700
|
// incarnation; a copied credential cannot drive this and is stranded at its own row's TTL).
|
|
1701
1701
|
// Same class-2 mechanics as the daemon creds: re-sign the file for the SAME nkey; the
|
|
@@ -1928,7 +1928,7 @@ export class Manager {
|
|
|
1928
1928
|
for (const file of SYSTEM_CREDS_FILES) {
|
|
1929
1929
|
let health;
|
|
1930
1930
|
try {
|
|
1931
|
-
// The pair is PER-SPACE
|
|
1931
|
+
// The pair is PER-SPACE, so the location comes from the resolver and never from a
|
|
1932
1932
|
// path built here (§2 rule 1). Building `.cotal/<kind>` instead would not warn on a wrong
|
|
1933
1933
|
// file — it would silently warn on NOTHING, for every provisioned root, forever, which is
|
|
1934
1934
|
// exactly the signal #338 added this for. The resolver may also refuse (§2 rules 3, 4); that
|
|
@@ -2427,6 +2427,11 @@ export class Manager {
|
|
|
2427
2427
|
}
|
|
2428
2428
|
catch (e) {
|
|
2429
2429
|
failures.push(`${a.name}: stop failed: ${rejectionText(e)}`);
|
|
2430
|
+
// A refused hard stop never reached the child, so waiting for its exit could only time out
|
|
2431
|
+
// and restate this failure; the cut path reports it the same way. A delegated seat's
|
|
2432
|
+
// deprovision can fail after its retirement already closed the seat, so that wait stays.
|
|
2433
|
+
if (!a.handedOff)
|
|
2434
|
+
return;
|
|
2430
2435
|
}
|
|
2431
2436
|
try {
|
|
2432
2437
|
await this.awaitHandleExit(a.handle);
|
|
@@ -2881,13 +2886,13 @@ export class Manager {
|
|
|
2881
2886
|
if (managed)
|
|
2882
2887
|
managed.suppressCleanup = false;
|
|
2883
2888
|
}
|
|
2884
|
-
//
|
|
2889
|
+
// distsys/security CONDITIONAL @ 9e13648: the boot sweep DEFERRED every active
|
|
2885
2890
|
// slot while a resume was pending (it could not know which would be adopted). Now adoption
|
|
2886
2891
|
// is complete and `this.agents` is EXACTLY the adopted set, so re-sweep to terminalize any
|
|
2887
2892
|
// active slot the resume did NOT claim — a durable ACTIVE ORPHAN (crashed after slot->active
|
|
2888
2893
|
// before agents.set, then not in the resumed inventory). This runs while `resumeRequired` is
|
|
2889
2894
|
// still true, so no ordinary spawn can race it (beginLifecycle refuses non-resume ops), and
|
|
2890
|
-
// it closes both the alias wedge AND the
|
|
2895
|
+
// it closes both the alias wedge AND the membership-gate gap (the orphan's principal enters
|
|
2891
2896
|
// retiredPrincipals, so a copied JWT is refused). Best-effort + loud: a sweep failure must
|
|
2892
2897
|
// not fail the finalize (the next non-resume boot re-drives it), but it is never swallowed.
|
|
2893
2898
|
if (this.auth && !this.userMode)
|
|
@@ -3000,7 +3005,7 @@ export class Manager {
|
|
|
3000
3005
|
/** The ONE shared control-admission chokepoint BOTH dispatch doors
|
|
3001
3006
|
* run — the v0.3 `ctl` door ({@link handle}) and the v0.4 `ep` service handlers
|
|
3002
3007
|
* ({@link serveGated}): the maintenance/resume fence (`beginLifecycle`: a resume-pending or
|
|
3003
|
-
* non-active manager accepts no ordinary control work) and then the
|
|
3008
|
+
* non-active manager accepts no ordinary control work) and then the membership gate
|
|
3004
3009
|
* ({@link lifecycleMembershipRefusal}: a retiring/terminalizing/retired managed incarnation's
|
|
3005
3010
|
* AUTHENTICATED principal holds no control authority even with a valid JWT). Refusal carries
|
|
3006
3011
|
* WHICH fence refused so the ep door can map onto the §13.3 catalog; admission returns the
|
|
@@ -3018,7 +3023,7 @@ export class Manager {
|
|
|
3018
3023
|
}
|
|
3019
3024
|
/** Run one v0.4 service-command handler through the SHARED admission chokepoint
|
|
3020
3025
|
* ({@link admitControl}) on the broker-authenticated caller principal, mapping the two fences
|
|
3021
|
-
* onto the §13.3 catalog: maintenance/resume → `unavailable`,
|
|
3026
|
+
* onto the §13.3 catalog: maintenance/resume → `unavailable`, membership →
|
|
3022
3027
|
* `permission-denied`. The serve boundary publishes the structured error reply. */
|
|
3023
3028
|
async serveGated(ctx, fn) {
|
|
3024
3029
|
const caller = principalKey(ctx.subject.caller.owner, ctx.subject.caller.actor).key;
|
|
@@ -3705,7 +3710,7 @@ export class Manager {
|
|
|
3705
3710
|
* returned: a caller that must prove exit (shutdown, a failed resume) reports it as the cause at
|
|
3706
3711
|
* once instead of waiting out a stop the runtime already refused. */
|
|
3707
3712
|
stopHandle(a, graceful) {
|
|
3708
|
-
// The
|
|
3713
|
+
// The TERMINALIZING latch: flipped SYNCHRONOUSLY, before any await anywhere on
|
|
3709
3714
|
// this stop path — later renewals refuse, while an already-admitted renewal drains before the
|
|
3710
3715
|
// durable terminal begins.
|
|
3711
3716
|
a.terminalizing = true;
|
|
@@ -4130,7 +4135,7 @@ export class Manager {
|
|
|
4130
4135
|
freeSlot(a, floor, cause, acceptedBeforeFence = false) {
|
|
4131
4136
|
if (this.agents.get(a.name) !== a)
|
|
4132
4137
|
return; // already freed (exit raced despawn, etc.)
|
|
4133
|
-
//
|
|
4138
|
+
// The terminalizing latch also covers exit/reap paths that never rode stopHandle.
|
|
4134
4139
|
this.logSeatReaped(a, cause);
|
|
4135
4140
|
a.terminalizing = true;
|
|
4136
4141
|
this.agents.delete(a.name);
|
|
@@ -4154,7 +4159,7 @@ export class Manager {
|
|
|
4154
4159
|
// is usually the seat ending, but a stop that could not prove the process gone leaves one the
|
|
4155
4160
|
// terminal must still address before it frees the alias.
|
|
4156
4161
|
const target = { ...this.teardownTarget(a), runtime: a.handle?.reference };
|
|
4157
|
-
//
|
|
4162
|
+
// On a USER mesh the name is RESERVED PENDING RETIREMENT — despawn started this
|
|
4158
4163
|
// lifecycle's FULL teardown (footprint + standing-authority revoke + the auth-side retirement),
|
|
4159
4164
|
// and the alias frees only when all of it completes (not the retirement alone). The detached
|
|
4160
4165
|
// deprovision below drives it; a failed revoke or an unreachable rail keeps the name held,
|
|
@@ -4168,10 +4173,10 @@ export class Manager {
|
|
|
4168
4173
|
this.retiring.set(a.name, { opId: retireOpId(a.lifecycleUid), owner: p.owner, actor: p.actor, target, startedAt: Date.now() });
|
|
4169
4174
|
}
|
|
4170
4175
|
else if (this.auth) {
|
|
4171
|
-
//
|
|
4176
|
+
// A STATIC lifecycle now also holds its name pending its own terminal (the
|
|
4172
4177
|
// static retirement the detached deprovision below drives) — the alias frees only when the
|
|
4173
4178
|
// gate+head terminal completes, exactly the user-mode discipline. The wire principal is the
|
|
4174
|
-
// incarnation-unique nkey
|
|
4179
|
+
// incarnation-unique nkey; owner is the dev owner.
|
|
4175
4180
|
this.retiring.set(a.name, { opId: retireOpId(a.lifecycleUid), owner: DEV_OWNER, actor: a.id, target, startedAt: Date.now() });
|
|
4176
4181
|
}
|
|
4177
4182
|
// Auth mode: tear down the departed agent's minted broker footprint + creds file (#159 B2). The
|
|
@@ -4269,7 +4274,7 @@ export class Manager {
|
|
|
4269
4274
|
if (!this.auth)
|
|
4270
4275
|
return; // guaranteed by deprovision; re-checked for the deprovisionBroker narrowing
|
|
4271
4276
|
if (!this.userMode && !a.userOwner) {
|
|
4272
|
-
//
|
|
4277
|
+
// A STATIC lifecycle retires through the terminal barrier — freeze → head
|
|
4273
4278
|
// retiring → B1 ledger revoke → footprint cleanup (creds file + broker durables/ACL, INSIDE
|
|
4274
4279
|
// the barrier) → gate retired → head retired → alias free. The eviction step is the process
|
|
4275
4280
|
// kill the stop path already performed (static's best-effort eviction).
|
|
@@ -4351,7 +4356,7 @@ export class Manager {
|
|
|
4351
4356
|
}
|
|
4352
4357
|
if (shredFailure)
|
|
4353
4358
|
throw new Error(shredFailure);
|
|
4354
|
-
//
|
|
4359
|
+
// After the footprint teardown, ask the AUTH plane to RETIRE the lifecycle over
|
|
4355
4360
|
// the auth endpoint rail. The rail re-checks the SERVE-ISSUANCE GATE at serve time (not the
|
|
4356
4361
|
// space-manager lease - that check was replaced in 02794b2f) and refuses unless the registration
|
|
4357
4362
|
// this request names belongs to our own principal; the terminal (or an already-retired answer)
|
|
@@ -4359,7 +4364,7 @@ export class Manager {
|
|
|
4359
4364
|
// their operator copy — legible, retryable, never a silent half-state.
|
|
4360
4365
|
await this.requestRetirement(a);
|
|
4361
4366
|
}
|
|
4362
|
-
/** Request the auth-side retirement of a departed agent's lifecycle
|
|
4367
|
+
/** Request the auth-side retirement of a departed agent's lifecycle: an ephemeral
|
|
4363
4368
|
* `retirement-requester` credential (request + reply only), the generic `retireLifecycle` op,
|
|
4364
4369
|
* a STABLE opId (derived from the lifecycleUid, so every retry re-drives the SAME operation),
|
|
4365
4370
|
* and the four-outcome handling in operator vocabulary. */
|
|
@@ -5544,8 +5549,15 @@ export class Manager {
|
|
|
5544
5549
|
opts = ownLists(opts);
|
|
5545
5550
|
if (opts.resolved)
|
|
5546
5551
|
opts.resolved = ownLists(opts.resolved);
|
|
5547
|
-
if (opts.supervise)
|
|
5552
|
+
if (opts.supervise) {
|
|
5548
5553
|
opts.supervise = { restarts: opts.supervise.restarts, windowMs: opts.supervise.windowMs };
|
|
5554
|
+
// The budget is checked here, where it is held, not only at the wire: an in-process caller of
|
|
5555
|
+
// this export is refused before anything launches, rather than getting a seat that joins and is
|
|
5556
|
+
// torn down when its seat record will not take the policy (#3298).
|
|
5557
|
+
const policy = supervisePolicy.safeParse(opts.supervise);
|
|
5558
|
+
if (!policy.success)
|
|
5559
|
+
return { ok: false, error: `supervise.${policy.error.issues[0]?.path.join(".")}: expected a positive integer` };
|
|
5560
|
+
}
|
|
5549
5561
|
let runtime;
|
|
5550
5562
|
try {
|
|
5551
5563
|
await this.loadSeatRuntime(opts.runtime);
|
|
@@ -5766,7 +5778,7 @@ export class Manager {
|
|
|
5766
5778
|
// #966: a seat labelled `manager` that cannot spawn is a worker wearing a label, and the label
|
|
5767
5779
|
// is what other agents ROUTE on (role is the anycast address). The gap is silent today because
|
|
5768
5780
|
// the mismatch is only discovered minutes in, when the seat first reaches for a worker. The
|
|
5769
|
-
// persona write path takes content only (role/capabilities are POLICY
|
|
5781
|
+
// persona write path takes content only (role/capabilities are POLICY), so a peer-defined
|
|
5770
5782
|
// persona can never fix this by declaring the capability — the only honest outcomes are "the
|
|
5771
5783
|
// persona grants spawn" or "say so now". Refuse at accept, before any reserve/mint, naming the
|
|
5772
5784
|
// exact remediation for each author: an operator edits the file to add the grant; a peer that
|
|
@@ -5801,7 +5813,7 @@ export class Manager {
|
|
|
5801
5813
|
});
|
|
5802
5814
|
if (policyRefusal)
|
|
5803
5815
|
return { ok: false, error: policyRefusal };
|
|
5804
|
-
// The alias-reuse gate
|
|
5816
|
+
// The alias-reuse gate: a name whose previous agent is still retiring REFUSES
|
|
5805
5817
|
// legibly (never a silent suffix), and the refusal re-drives the FULL durable teardown so
|
|
5806
5818
|
// "retry the spawn" is also the nudge. It routes through `deprovision` (not `requestRetirement`
|
|
5807
5819
|
// alone) so a retry re-drives the standing-authority revoke (INT-2) AND the broker cleanup before
|
|
@@ -6048,11 +6060,11 @@ export class Manager {
|
|
|
6048
6060
|
}
|
|
6049
6061
|
}
|
|
6050
6062
|
else if (this.auth) {
|
|
6051
|
-
//
|
|
6052
|
-
// broker footprint — the
|
|
6063
|
+
// §13.1: reserve + activate this incarnation's DURABLE identity BEFORE any
|
|
6064
|
+
// broker footprint — the outer spawn intent first (slot row, phase `provisioning`),
|
|
6053
6065
|
// then the SHARED core activation saga (reserve uid -> gate frozen -> head CAS -> reopen
|
|
6054
6066
|
// LAST) over the key-pinned executor. The wire AUTHORITY principal is the incarnation-
|
|
6055
|
-
// unique nkey
|
|
6067
|
+
// unique nkey; the alias is protected by the name-keyed slot + freeSlot hold.
|
|
6056
6068
|
await this.withLifecycleExecutor({ owner: DEV_OWNER, actor: identity.id, lifecycleUid, alias: name }, (t) => activateStaticLifecycle(t, { owner: DEV_OWNER, alias: name, actor: identity.id, lifecycleUid, managerInstance: this.managerLifecycleUid, ownerInstanceId: this.managerInstanceId, runtime: custody }));
|
|
6057
6069
|
// From here the DURABLE registration exists: arm the rollback BEFORE minting, so a throw
|
|
6058
6070
|
// between activation and provisioning still drives the exact-op static terminal (the
|
|
@@ -6062,7 +6074,7 @@ export class Manager {
|
|
|
6062
6074
|
// — the shared onboarding step (provisionAgent). It runs on a short-lived PROVISIONER connection
|
|
6063
6075
|
// (NOT the supervisor's long-lived endpoint), so the DM/DLV consumer-create surface exists only
|
|
6064
6076
|
// for the provisioning window, never as a standing grant on the always-on daemon (residual 2).
|
|
6065
|
-
//
|
|
6077
|
+
// The credential is BOUNDED (`expiresAt`) — the manager push-renews it ahead of expiry.
|
|
6066
6078
|
const now = Math.floor(Date.now() / 1000);
|
|
6067
6079
|
const exp = now + MANAGED_STATIC_TTL_SEC;
|
|
6068
6080
|
// The credential is an ISSUANCE (SPEC 13.15): its caller rails carry a fresh generation
|
|
@@ -6234,7 +6246,7 @@ export class Manager {
|
|
|
6234
6246
|
}
|
|
6235
6247
|
: {}),
|
|
6236
6248
|
};
|
|
6237
|
-
//
|
|
6249
|
+
// The DURABLE slot takes the `active` phase before the in-memory row takes the
|
|
6238
6250
|
// name — a crash between the two leaves an active-but-unadopted slot the boot sweep
|
|
6239
6251
|
// terminalizes (never an untracked orphan). Static auth only; a failed CAS fails the spawn
|
|
6240
6252
|
// (the finally's rollback then drives the exact-op terminal).
|
|
@@ -6899,7 +6911,7 @@ export class Manager {
|
|
|
6899
6911
|
if (!batchReserved)
|
|
6900
6912
|
this.reserved.set(entry.name, resumeOwner(entry));
|
|
6901
6913
|
try {
|
|
6902
|
-
//
|
|
6914
|
+
// Recover the STATIC identity's nkey seed from the adopted credential (the
|
|
6903
6915
|
// creds file embeds it) so the manager stays this incarnation's RENEWAL OWNER across a
|
|
6904
6916
|
// preserve/resume — without it the adopted cred would die loud at its TTL with no remint.
|
|
6905
6917
|
let adoptedSeed;
|
|
@@ -7493,7 +7505,7 @@ export class Manager {
|
|
|
7493
7505
|
return undefined;
|
|
7494
7506
|
}
|
|
7495
7507
|
/** Run one static §13.1 lifecycle OPERATION over an ephemeral, key-pinned `lifecycle-executor`
|
|
7496
|
-
* connection
|
|
7508
|
+
* connection: the credential's grants name exactly ONE incarnation's head/uid/gate/
|
|
7497
7509
|
* cred-family/slot keys, so the write authority exists only for this operation's window and
|
|
7498
7510
|
* can move nothing else. The transport is the direct-KV binding the shared core saga drives. */
|
|
7499
7511
|
async withLifecycleExecutor(pin, fn) {
|
|
@@ -9810,11 +9822,11 @@ export class Manager {
|
|
|
9810
9822
|
const evidence = await requireRuntimeReap(runtime, a.runtime);
|
|
9811
9823
|
console.error(`static retirement ${a.name}: orphan seat process ${evidence.detail}`);
|
|
9812
9824
|
}
|
|
9813
|
-
/** The static
|
|
9825
|
+
/** The static terminal for one departed incarnation: delegates the gate/head CAS
|
|
9814
9826
|
* sequence to the shared core saga over the executor transport; the footprint teardown (creds
|
|
9815
9827
|
* file + broker durables/ACL) runs INSIDE the barrier as its cleanup step. On completion the
|
|
9816
|
-
* wire principal joins {@link retiredPrincipals} (the
|
|
9817
|
-
* clears (ABA-guarded by uid). A
|
|
9828
|
+
* wire principal joins {@link retiredPrincipals} (the retired-principal refusal index) and the name hold
|
|
9829
|
+
* clears (ABA-guarded by uid). A lifecycle with no slot row (spawned before the
|
|
9818
9830
|
* durable registry existed) has nothing to terminalize: its footprint teardown runs directly
|
|
9819
9831
|
* and the hold clears, the honest upgrade path. `onDeprovision` receives the accounting of the
|
|
9820
9832
|
* broker teardown this call ran, on success or on a {@link DeprovisionError}. */
|
|
@@ -9875,7 +9887,7 @@ export class Manager {
|
|
|
9875
9887
|
await this.withLifecycleExecutor({ owner: DEV_OWNER, actor: a.id, lifecycleUid: a.lifecycleUid, alias: a.name }, async (t) => {
|
|
9876
9888
|
const slot = await readStaticSlot(t, DEV_OWNER, a.name);
|
|
9877
9889
|
if (slot === undefined || slot.row.lifecycleUid !== a.lifecycleUid) {
|
|
9878
|
-
// No durable registration for THIS incarnation: a
|
|
9890
|
+
// No durable registration for THIS incarnation: a spawn from before the registry (or a slot already
|
|
9879
9891
|
// replaced by a successor — then this stale teardown must not touch the registry at all).
|
|
9880
9892
|
await reapThenCleanup();
|
|
9881
9893
|
return;
|
|
@@ -10125,7 +10137,7 @@ export class Manager {
|
|
|
10125
10137
|
for (const session of this.chatExpiry.remaining())
|
|
10126
10138
|
console.error(`! chat session ${session.alias} (uid ${session.lifecycleUid}) was not retired because ${why}; the next start of this manager instance retires it`);
|
|
10127
10139
|
}
|
|
10128
|
-
/**
|
|
10140
|
+
/** Push renewal of ONE live managed-static credential: re-mint the SAME nkey
|
|
10129
10141
|
* identity with the SAME scope (recorded on the managed row at spawn) and a fresh bounded
|
|
10130
10142
|
* exp, ledger the new credentialId (slot record first, then the row, then the file — a
|
|
10131
10143
|
* credential is never materialized before its ledger row exists), and re-sign the SAME
|
|
@@ -10183,11 +10195,11 @@ export class Manager {
|
|
|
10183
10195
|
this.refreshSeatRecord(a);
|
|
10184
10196
|
console.error(`managed cred renewal ${a.name}: re-signed for the same identity (exp +${exp - now}s); the agent endpoint's source re-read adopts it`);
|
|
10185
10197
|
}
|
|
10186
|
-
/** The
|
|
10198
|
+
/** The static-slot reconciliation ("no active orphan"): ensure the authority stores, then sweep
|
|
10187
10199
|
* every durable slot row and act by the TOTAL resume table — `provisioning`/`terminalizing`
|
|
10188
10200
|
* re-drive the exact-op terminal; an `active` row survives ONLY when a LIVE managed agent this
|
|
10189
10201
|
* manager process owns backs it at the same uid (`adopted`), else no managed owner claimed it and
|
|
10190
|
-
* its broker authority is contained and terminalized; `retired` rows seed the
|
|
10202
|
+
* its broker authority is contained and terminalized; `retired` rows seed the retired-principal refusal index. Two call sites: the BOOT sweep
|
|
10191
10203
|
* (`postAdoption=false`, under the lease before control serving) DEFERS active-non-adopted
|
|
10192
10204
|
* slots while a resume is still pending (adoption runs after it); the POST-ADOPTION sweep
|
|
10193
10205
|
* (`postAdoption=true`, inside finalizeResume while `resumeRequired` still fences ordinary
|
|
@@ -10479,7 +10491,7 @@ export class Manager {
|
|
|
10479
10491
|
const restartRows = [];
|
|
10480
10492
|
for (const row of slotRows) {
|
|
10481
10493
|
if (row.phase === "retired") {
|
|
10482
|
-
// A retirement is a GLOBAL refusal fact — seed the
|
|
10494
|
+
// A retirement is a GLOBAL refusal fact — seed the retired-principal index for EVERY retired incarnation
|
|
10483
10495
|
// regardless of which instance owned it, so a sibling-retired incarnation's copied credential
|
|
10484
10496
|
// is refused at this control surface too. Ownership gates only the DESTRUCTIVE sweep below.
|
|
10485
10497
|
this.retiredPrincipals.add(principalKey(row.owner, row.actor).key);
|
|
@@ -10507,7 +10519,7 @@ export class Manager {
|
|
|
10507
10519
|
}
|
|
10508
10520
|
// ADOPTION is genuine membership: a slot backed by a live managed agent THIS process owns
|
|
10509
10521
|
// at the SAME uid is never an orphan (empty at boot; exactly the adopted set at the
|
|
10510
|
-
// post-adoption sweep — the fix for the
|
|
10522
|
+
// post-adoption sweep — the fix for the resume hole).
|
|
10511
10523
|
const live = this.agents.get(row.alias);
|
|
10512
10524
|
const adopted = live !== undefined && live.lifecycleUid === row.lifecycleUid;
|
|
10513
10525
|
if (adopted) {
|
|
@@ -10637,7 +10649,7 @@ export class Manager {
|
|
|
10637
10649
|
this.releaseStaticReconcileSweep();
|
|
10638
10650
|
}
|
|
10639
10651
|
}
|
|
10640
|
-
/** The
|
|
10652
|
+
/** The membership gate: decide a control caller by its
|
|
10641
10653
|
* AUTHENTICATED wire principal. A LIVE managed slot passes (unless terminalizing); a RETIRING
|
|
10642
10654
|
* hold or a RETIRED static incarnation refuses even with a tier-valid JWT (the
|
|
10643
10655
|
* copied-credential vector — its subject can never collide with a successor's, so this match
|
|
@@ -10647,16 +10659,16 @@ export class Manager {
|
|
|
10647
10659
|
for (const a of this.agents.values()) {
|
|
10648
10660
|
if (this.managedPrincipal(a) === caller)
|
|
10649
10661
|
return a.terminalizing
|
|
10650
|
-
? `the caller's lifecycle ${a.lifecycleUid} is terminalizing; control is refused from the first terminal step
|
|
10662
|
+
? `the caller's lifecycle ${a.lifecycleUid} is terminalizing; control is refused from the first terminal step`
|
|
10651
10663
|
: undefined;
|
|
10652
10664
|
}
|
|
10653
10665
|
for (const [name, hold] of this.retiring) {
|
|
10654
10666
|
const held = hold.target.userOwner ? hold.target.id : principalKey(DEV_OWNER, hold.target.id).key;
|
|
10655
10667
|
if (held === caller)
|
|
10656
|
-
return `the caller's lifecycle ${hold.target.lifecycleUid} (name "${name}") is retiring; a retiring incarnation's credential holds no control authority
|
|
10668
|
+
return `the caller's lifecycle ${hold.target.lifecycleUid} (name "${name}") is retiring; a retiring incarnation's credential holds no control authority`;
|
|
10657
10669
|
}
|
|
10658
10670
|
if (this.retiredPrincipals.has(caller))
|
|
10659
|
-
return "the caller's lifecycle is retired; a retired incarnation's credential holds no control authority
|
|
10671
|
+
return "the caller's lifecycle is retired; a retired incarnation's credential holds no control authority";
|
|
10660
10672
|
return undefined;
|
|
10661
10673
|
}
|
|
10662
10674
|
/** Open a short-lived PROVISIONER connection, run the onboarding ops on it, and drain it (closure (ii),
|
|
@@ -10717,7 +10729,7 @@ export class Manager {
|
|
|
10717
10729
|
/** Persist a peer-defined persona as config. After this, `start name` auto-discovers
|
|
10718
10730
|
* .cotal/agents/<name>.md and the connector applies its persona/model at spawn.
|
|
10719
10731
|
*
|
|
10720
|
-
* CONTENT vs POLICY
|
|
10732
|
+
* CONTENT vs POLICY: `capabilities` and `owner` still have no slot, so a peer cannot
|
|
10721
10733
|
* self-grant spawn or claim ownership. Channel grants, role, and agent are content: they may
|
|
10722
10734
|
* arrive as explicit arguments or as a leading frontmatter block inside `persona`. A prompt
|
|
10723
10735
|
* that starts with `---` is parsed and merged (explicit args win) so the written file has one
|