@cotal-ai/manager 0.52.1 → 0.54.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/commands.js +22 -1
- package/dist/commands.js.map +1 -1
- package/dist/manager-service-contract.d.ts +5 -1
- package/dist/manager-service-contract.d.ts.map +1 -1
- package/dist/manager-service-contract.js +18 -2
- package/dist/manager-service-contract.js.map +1 -1
- package/dist/manager.d.ts +37 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +150 -13
- package/dist/manager.js.map +1 -1
- package/dist/reconcile-gate.d.ts +1 -100
- package/dist/reconcile-gate.d.ts.map +1 -1
- package/dist/reconcile-gate.js +1 -200
- package/dist/reconcile-gate.js.map +1 -1
- package/dist/remote-authority.d.ts +4 -1
- package/dist/remote-authority.d.ts.map +1 -1
- package/dist/remote-authority.js +59 -0
- package/dist/remote-authority.js.map +1 -1
- package/dist/remote-register.d.ts +3 -0
- package/dist/remote-register.d.ts.map +1 -1
- package/dist/remote-register.js +29 -2
- package/dist/remote-register.js.map +1 -1
- package/dist/runtime/custodial-pty.d.ts +8 -0
- package/dist/runtime/custodial-pty.d.ts.map +1 -1
- package/dist/runtime/custodial-pty.js +31 -4
- package/dist/runtime/custodial-pty.js.map +1 -1
- package/package.json +10 -10
package/dist/manager.js
CHANGED
|
@@ -4,8 +4,8 @@ import { hostname } from "node:os";
|
|
|
4
4
|
import { credsAuthenticator } from "@nats-io/transport-node";
|
|
5
5
|
import { existsSync, lstatSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
6
6
|
import { isAbsolute, join, dirname, resolve } from "node:path";
|
|
7
|
-
import { CotalEndpoint, DEFAULT_SERVER, DEV_OWNER, MANAGER_LEASE_RENEW_MS, STANDING_RENEWABLE_TTL_SEC, divergentSecretStoreNotice, parseSecretStoreIdentity, sameSecretStoreIdentity, agentFilePath, clearSpaceHistory, dialerFor, connectorServers, spawnEnvAllow, deprovisionAgent, firstFreeName, spawnNameError, idFromCreds, inspectCredHealth, composeWirePersona, loadAgentFile, listPersonaCatalog, personaCatalogDescription, personaCatalogReadable, loadCotalConfig, mintCreds, mintGeneration, mintAcceptedToken, openIssuedStore, acceptedBucket, issuedBucket, mintLifecycleUid, managedRetirementOpId, mkSecretDir, newIdentity, actionContext, parsePrincipalKey, parseShareSelection, principalKey, probeConnect, provisionAgent, provisionAgentDurables, registry, resolveAuthProvider, saveAgentFile, subjectMatches, AUTH_ENDPOINT, EP_CMD_RETIRE_LIFECYCLE, epRequestSubject, epCallerReplyFilter, parseEpSubject, controlServiceSubject, eventChannelPrincipal, } from "@cotal-ai/core";
|
|
8
|
-
import { agentAuthState, agentCredsDir, agentLifecycleSecretFilePaths, agentSecretFilePaths, agentSecretKeyForFile, authDir, connectorInstallHint, DEFAULT_CONNECTOR, defaultAgentType, DELIVERY_CREDS_KIND, extensionConnectors, findCotalRoot, getSpaceAuth, hasUserAuthState, loadExtensionsManifest, loadManagerInstanceIdentity, loadMeshes, manifestExtensionNames, materializeFromManifest, materializeSecretToFile, MEMBERSHIP_RW_CREDS_KIND, mergeLaunchOptions, remintDaemonCreds, resolveOnPath, createManagerInstanceIdentity, spaceMaterialKey, SYSTEM_CREDS_FILES, userAuthStateDir, workspaceSecretStore, writeRenewalRecord } from "@cotal-ai/workspace";
|
|
7
|
+
import { CotalEndpoint, DEFAULT_SERVER, DEV_OWNER, MANAGER_LEASE_RENEW_MS, STANDING_RENEWABLE_TTL_SEC, divergentSecretStoreNotice, parseDaemonStoreAnswer, parseSecretStoreIdentity, sameSecretStoreIdentity, agentFilePath, clearSpaceHistory, dialerFor, connectorServers, spawnEnvAllow, deprovisionAgent, firstFreeName, spawnNameError, idFromCreds, inspectCredHealth, composeWirePersona, loadAgentFile, listPersonaCatalog, personaCatalogDescription, personaCatalogReadable, loadCotalConfig, mintCreds, mintGeneration, mintAcceptedToken, openIssuedStore, acceptedBucket, issuedBucket, mintLifecycleUid, managedRetirementOpId, mkSecretDir, newIdentity, actionContext, parsePrincipalKey, parseShareSelection, principalKey, probeConnect, provisionAgent, provisionAgentDurables, registry, resolveAuthProvider, saveAgentFile, subjectMatches, AUTH_ENDPOINT, EP_CMD_RETIRE_LIFECYCLE, epRequestSubject, epCallerReplyFilter, parseEpSubject, controlServiceSubject, eventChannelPrincipal, } from "@cotal-ai/core";
|
|
8
|
+
import { agentAuthState, agentCredsDir, agentLifecycleSecretFilePaths, agentSecretFilePaths, agentSecretKeyForFile, authDir, connectorInstallHint, DEFAULT_CONNECTOR, defaultAgentType, DELIVERY_CREDS_KIND, extensionConnectors, findCotalRoot, getSpaceAuth, hasUserAuthState, loadExtensionsManifest, loadManagerInstanceIdentity, loadMeshes, localTrustOfSpace, manifestExtensionNames, materializeFromManifest, materializeSecretToFile, MEMBERSHIP_RW_CREDS_KIND, mergeLaunchOptions, remintDaemonCreds, resolveOnPath, createManagerInstanceIdentity, spaceMaterialKey, SYSTEM_CREDS_FILES, userAuthStateDir, workspaceSecretStore, writeRenewalRecord } from "@cotal-ai/workspace";
|
|
9
9
|
import { createRuntime, isCustodialRuntime, requireRuntimeAdopt, requireRuntimeReap, RuntimeReapUnproven, } from "./runtime/index.js";
|
|
10
10
|
import { AttachEndpoint } from "./attach-endpoint.js";
|
|
11
11
|
import { makeManagerEndpointEvictionEvidence, makeManagerEndpointEvictor } from "./endpoint-evict.js";
|
|
@@ -536,6 +536,7 @@ export class Manager {
|
|
|
536
536
|
resumeDurableCommitToken;
|
|
537
537
|
resumedAgentNames = new Set();
|
|
538
538
|
remoteAuthority;
|
|
539
|
+
remoteExecutorCreds;
|
|
539
540
|
/** Every broker dial this manager makes, through ONE transport decision.
|
|
540
541
|
*
|
|
541
542
|
* `this.servers` is whatever the mesh record held (`cotal supervise` reads it from there), so it
|
|
@@ -555,6 +556,7 @@ export class Manager {
|
|
|
555
556
|
this.eventsRequired = opts.eventsRequired === true;
|
|
556
557
|
this.maxSessions = opts.maxSessions;
|
|
557
558
|
this.remoteAuthority = opts.remoteAuthority;
|
|
559
|
+
this.remoteExecutorCreds = opts.remoteAuthority?.executorCreds;
|
|
558
560
|
if (opts.remoteAuthority)
|
|
559
561
|
this.managerLifecycleUid = opts.remoteAuthority.lifecycleUid;
|
|
560
562
|
this.secrets = opts.secretStore ?? workspaceSecretStore(this.workspaceRoot);
|
|
@@ -663,9 +665,24 @@ export class Manager {
|
|
|
663
665
|
// through the SecretStore seam (`this.secrets` — the injected `ManagerOptions.secretStore`, or
|
|
664
666
|
// the local `.cotal/auth/auth.json` FS default), so a HOSTED composition mints from its KMS/Vault
|
|
665
667
|
// and no signing seed is ever read from the hosted disk. `this.space` cross-checks the bundle.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
668
|
+
//
|
|
669
|
+
// REMOTE AUTHORITY (#1944): a manager holding host-issued remote authority mints nothing from a
|
|
670
|
+
// local signer, so it never composes or validates local trust. It asks ONE question of the
|
|
671
|
+
// store — does THIS space's own local signing trust exist (`localTrustOfSpace`) — because that
|
|
672
|
+
// alone is the conflict of authorities a supervisor refuses. The stock behaviour this replaces
|
|
673
|
+
// read the whole root's trust chain (`getSpaceAuth`): with no records for this space it fell
|
|
674
|
+
// back to the legacy monolith of an UNRELATED static tenant and reported that bundle as
|
|
675
|
+
// "corrupt or mislabeled". Other tenants' records — split (their account + the shared
|
|
676
|
+
// per-root broker record) or monolith — are not this space's trust and never refuse; an
|
|
677
|
+
// unreadable record on a key that is read refuses loud with the store's own parse message.
|
|
678
|
+
if (this.remoteAuthority) {
|
|
679
|
+
if ((await localTrustOfSpace(this.secrets, this.space)).present)
|
|
680
|
+
throw new Error("remote manager-service authority cannot be combined with local space signing trust - choose one authority path, never a fallback");
|
|
681
|
+
this.auth = undefined;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
this.auth = await getSpaceAuth(this.secrets, this.space);
|
|
685
|
+
}
|
|
669
686
|
// USER-MODE detection is FAIL-CLOSED on the on-disk marker (the space-scoped state dir), never
|
|
670
687
|
// on the mutable mesh registry alone — registry drift/tamper must not let a user-auth space
|
|
671
688
|
// take the static self-mint branch. A marker/registry disagreement is a refused start with the
|
|
@@ -818,6 +835,11 @@ export class Manager {
|
|
|
818
835
|
this.credRenewTimer = setInterval(() => { void this.renewDaemonCreds(); }, credRenewIntervalMs(STANDING_RENEWABLE_TTL_SEC));
|
|
819
836
|
this.credRenewTimer.unref?.();
|
|
820
837
|
}
|
|
838
|
+
else if (this.remoteAuthority) {
|
|
839
|
+
await this.renewRemoteExecutor();
|
|
840
|
+
this.credRenewTimer = setInterval(() => { void this.renewRemoteExecutor(); }, credRenewIntervalMs(5 * 60));
|
|
841
|
+
this.credRenewTimer.unref?.();
|
|
842
|
+
}
|
|
821
843
|
// stop() fences before it waits for an accepted startup reconciliation terminal. Once that
|
|
822
844
|
// terminal drains, start() and stop() resume from the same await boundary; start must observe
|
|
823
845
|
// the fence before it can publish a fresh service registration after shutdown.
|
|
@@ -868,13 +890,27 @@ export class Manager {
|
|
|
868
890
|
// manager records each agent's read ACL at spawn (`commitAcl`, in provisionAgent) so the daemon can
|
|
869
891
|
// re-authorize it; that is the only Plane-3 state the manager touches, and it rides minting.
|
|
870
892
|
}
|
|
893
|
+
/** The delivery shard whose lease names the process that reloads the standing credentials.
|
|
894
|
+
* Delivery is single-shard today and the endpoint's own responder reads row 0; naming it here
|
|
895
|
+
* keeps the manager's binding check and that reply arguing about the same row. */
|
|
896
|
+
static DELIVERY_SHARD = 0;
|
|
871
897
|
/** Proof that this manager remints into the store the delivery daemon reloads from.
|
|
872
898
|
* Fingerprint-only `reloadCreds` is safe only after this. Two named, different stores
|
|
873
899
|
* refuse the pass. A daemon that is not bound yet is not a named store: start and
|
|
874
900
|
* remint still proceed (tests, delayed delivery), writing this manager's store. The
|
|
875
901
|
* challenge runs again before every remint, so a later daemon on a foreign store is
|
|
876
902
|
* refused then rather than certified by an earlier absence. A request timeout is not
|
|
877
|
-
* absence: a hung rail would skip the proof, so it fails closed.
|
|
903
|
+
* absence: a hung rail would skip the proof, so it fails closed.
|
|
904
|
+
*
|
|
905
|
+
* ABSENCE IS READ OFF THE LEASE ROW, never off the rail's no-responder shape alone (#1694).
|
|
906
|
+
* The rail is queue-grouped, so "no responder answered" is itself something the rail reports,
|
|
907
|
+
* and a responder that answers in a shape the client turns into that error makes the pass read
|
|
908
|
+
* "absent" while a holder is live on the row — certifying every later remint with no proof at
|
|
909
|
+
* all. So a no-responder outcome is decided by the row: a row that names a holder makes the
|
|
910
|
+
* pass refuse (undetermined — a live daemon went unanswered, which must not read as "no
|
|
911
|
+
* daemon"), and only a row that is absent or holderless settles "absent", the state start()
|
|
912
|
+
* treats as not-yet-bound. An unreadable row (a failed read, or a key that exists but does not
|
|
913
|
+
* parse) is a genuine unknown and refuses fail-closed, like a hung rail. */
|
|
878
914
|
async daemonStoreRelation() {
|
|
879
915
|
let reply;
|
|
880
916
|
try {
|
|
@@ -883,24 +919,77 @@ export class Manager {
|
|
|
883
919
|
catch (e) {
|
|
884
920
|
const msg = e.message;
|
|
885
921
|
if (isAbsentDeliveryAdmin(msg))
|
|
886
|
-
return
|
|
922
|
+
return this.absentByLeaseRow();
|
|
887
923
|
throw new Error(`could not challenge the delivery daemon's SecretStore: ${msg}`);
|
|
888
924
|
}
|
|
889
925
|
if (!reply.ok)
|
|
890
926
|
throw new Error(reply.error ?? "delivery daemon refused to name the SecretStore it reloads from");
|
|
891
|
-
let
|
|
927
|
+
let answer;
|
|
892
928
|
try {
|
|
893
|
-
|
|
929
|
+
answer = parseDaemonStoreAnswer(reply.data);
|
|
894
930
|
}
|
|
895
931
|
catch (e) {
|
|
896
|
-
throw new Error(`delivery daemon named an unreadable SecretStore: ${e.message}`
|
|
897
|
-
|
|
932
|
+
throw new Error(`delivery daemon named an unreadable SecretStore: ${e.message}. A daemon older ` +
|
|
933
|
+
`than #1694 replies with a bare store identity and no answerer binding; this manager ` +
|
|
934
|
+
`cannot establish which process answered, so nothing reminted - upgrade the delivery daemon`);
|
|
935
|
+
}
|
|
936
|
+
// The answerer's own claim first, so an honest non-holder is refused by its own admission and
|
|
937
|
+
// the operator-facing reason names that. This is the CHEAP half of the test and NOT the binding
|
|
938
|
+
// one: a responder that does not hold the lease can assert `true` here just as easily.
|
|
939
|
+
if (!answer.holdsDeliveryLease)
|
|
940
|
+
throw new Error("the delivery-admin rail was answered by a responder that does not hold this space's " +
|
|
941
|
+
"delivery lease, so its SecretStore is not the store the daemon reloads from - nothing reminted");
|
|
942
|
+
// THE BINDING TEST, measured by this manager rather than asserted by the answerer. The lease
|
|
943
|
+
// row's holder is read here under this manager's own credential; the answerer must BE that
|
|
944
|
+
// principal. A reply asserting the claim while some other process holds the shard fails here,
|
|
945
|
+
// which is the case a bare store identity could not see. An unreadable row is an unknown and
|
|
946
|
+
// refuses too: this test may never be satisfied by failing to run.
|
|
947
|
+
const holder = await this.ep.deliveryLeaseHolder(Manager.DELIVERY_SHARD);
|
|
948
|
+
if (holder === undefined)
|
|
949
|
+
throw new Error("this manager could not read the delivery lease row, so it cannot verify that the answering " +
|
|
950
|
+
"responder is the process that reloads the standing credentials - nothing reminted");
|
|
951
|
+
if (holder !== answer.responder)
|
|
952
|
+
throw new Error(`the delivery-admin rail was answered by ${answer.responder}, which is not the holder of ` +
|
|
953
|
+
`this space's delivery lease (${holder}), so its SecretStore is not the store the daemon ` +
|
|
954
|
+
`reloads from - nothing reminted`);
|
|
955
|
+
const daemon = answer.identity;
|
|
898
956
|
if (!sameSecretStoreIdentity(this.secretStoreIdentity, daemon)) {
|
|
899
957
|
console.error(`! ${divergentSecretStoreNotice(this.secretStoreIdentity, daemon)}`);
|
|
900
958
|
return "divergent";
|
|
901
959
|
}
|
|
902
960
|
return "shared";
|
|
903
961
|
}
|
|
962
|
+
/** Settle a no-responder challenge outcome from the lease row, never from the rail's own
|
|
963
|
+
* reporting shape (#1694). The three cases:
|
|
964
|
+
*
|
|
965
|
+
* 1. The row READS but names a holder: a live daemon holds the shard and the rail went
|
|
966
|
+
* unanswered. That is undetermined, not "no daemon" — treating it as absent would let this
|
|
967
|
+
* manager remint with no proof while a reloading daemon runs, which is exactly what the
|
|
968
|
+
* challenge exists to prevent. Refuse.
|
|
969
|
+
* 2. The row is unreadable — a read that fails (a denied or erroring bucket) OR a key that
|
|
970
|
+
* exists but does not parse as a lease record (`readDeliveryLeaseEntry` throws for both):
|
|
971
|
+
* undetermined, and it refuses the same fail-closed way a hung rail does. Never a silent
|
|
972
|
+
* "absent" over a row the manager could not read.
|
|
973
|
+
* 3. The row is absent or holderless (no key, a DEL/PURGE tombstone, or a row that names no
|
|
974
|
+
* holder): today's "absent", the state start() treats as a daemon that is not bound yet.
|
|
975
|
+
*
|
|
976
|
+
* `deliveryLeaseHolder` swallows its read failures into `undefined`, which would fold case 2
|
|
977
|
+
* into case 3 — so this reads the entry directly and lets the read's own failure refuse. */
|
|
978
|
+
async absentByLeaseRow() {
|
|
979
|
+
let entry;
|
|
980
|
+
try {
|
|
981
|
+
entry = await this.ep.readDeliveryLeaseEntry(Manager.DELIVERY_SHARD);
|
|
982
|
+
}
|
|
983
|
+
catch (e) {
|
|
984
|
+
throw new Error(`could not read the delivery lease row to settle whether the delivery daemon is absent ` +
|
|
985
|
+
`(${e.message}) - absence is undetermined, nothing reminted`);
|
|
986
|
+
}
|
|
987
|
+
const holder = entry?.info.holder;
|
|
988
|
+
if (holder !== undefined && holder !== "")
|
|
989
|
+
throw new Error(`the delivery-admin rail reported no responder while the delivery lease names a holder ` +
|
|
990
|
+
`(${holder}) - absence cannot be read off the rail alone, and nothing reminted`);
|
|
991
|
+
return "absent";
|
|
992
|
+
}
|
|
904
993
|
/** Keep this manager's renewal lease alive between credential passes (#1634). The manager bucket
|
|
905
994
|
* expires keys at MANAGER_LEASE_TTL_MS, far below the credential pass's cadence, so the lease
|
|
906
995
|
* needs its own heartbeat like the per-instance liveness lease. A non-owner calls this too: the
|
|
@@ -933,7 +1022,23 @@ export class Manager {
|
|
|
933
1022
|
*
|
|
934
1023
|
* A manager failing either test serves the space normally and skips only the daemon remint. */
|
|
935
1024
|
async claimDaemonRenewalOwnership() {
|
|
936
|
-
|
|
1025
|
+
// EVERY refusal outcome drops a held lease, not only "divergent". The refusal paths THROW
|
|
1026
|
+
// (#1694: a non-holder answer, an unbindable answerer, an unreadable or holder-naming row on a
|
|
1027
|
+
// no-responder rail), and a thrown refusal caught by renewDaemonCreds would otherwise leave a
|
|
1028
|
+
// manager that once owned the renewal still holding the lease behind its own heartbeat — the
|
|
1029
|
+
// pass refuses, the log says so, and the space keeps a renewal owner that must not remint.
|
|
1030
|
+
// Deciding here, inside the claim, is what keeps the release on the refusal path itself.
|
|
1031
|
+
let relation;
|
|
1032
|
+
try {
|
|
1033
|
+
relation = await this.daemonStoreRelation();
|
|
1034
|
+
}
|
|
1035
|
+
catch (e) {
|
|
1036
|
+
if (this.daemonRenewalOwner)
|
|
1037
|
+
await this.ep.releaseDaemonRenewalLease().catch(() => { });
|
|
1038
|
+
this.daemonRenewalOwner = false;
|
|
1039
|
+
throw e;
|
|
1040
|
+
}
|
|
1041
|
+
if (relation === "divergent") {
|
|
937
1042
|
// Not our daemon's store: drop any lease we hold rather than sit on it, so the manager that
|
|
938
1043
|
// CAN remint is not blocked behind us.
|
|
939
1044
|
if (this.daemonRenewalOwner)
|
|
@@ -1106,6 +1211,23 @@ export class Manager {
|
|
|
1106
1211
|
release();
|
|
1107
1212
|
}
|
|
1108
1213
|
}
|
|
1214
|
+
/** Renew the remote registration executor through the typed host protocol. A healthy credential
|
|
1215
|
+
* is retained; near-expiry and expired credentials are replaced for the same nkey. */
|
|
1216
|
+
async renewRemoteExecutor(force = false) {
|
|
1217
|
+
if (!this.remoteAuthority)
|
|
1218
|
+
return;
|
|
1219
|
+
const current = this.remoteExecutorCreds;
|
|
1220
|
+
if (!force && current && inspectCredHealth(current).state === "healthy")
|
|
1221
|
+
return;
|
|
1222
|
+
try {
|
|
1223
|
+
this.remoteExecutorCreds = await this.remoteAuthority.renewExecutor();
|
|
1224
|
+
}
|
|
1225
|
+
catch (e) {
|
|
1226
|
+
console.error(`! remote manager executor renewal: ${e.message} - registration maintenance and clean deregistration remain unavailable until renewal succeeds`);
|
|
1227
|
+
if (force)
|
|
1228
|
+
throw e;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1109
1231
|
/** Warn, on every renewal pass, when a $SYS credential is at or past its renewal point.
|
|
1110
1232
|
*
|
|
1111
1233
|
* The manager is the renewal owner for every credential it CAN re-sign, and these two are the ones
|
|
@@ -2193,6 +2315,16 @@ export class Manager {
|
|
|
2193
2315
|
const data = unwrap(await this.opModels(args(ctx)));
|
|
2194
2316
|
return { catalogs: Array.isArray(data) ? data : [data] };
|
|
2195
2317
|
}),
|
|
2318
|
+
goalResult: (ctx) => this.serveGated(ctx, async () => {
|
|
2319
|
+
const gw = this.goalWriter;
|
|
2320
|
+
if (!gw || gw.nc.isClosed())
|
|
2321
|
+
throw new EpEnvelopeError("unavailable", "the manager goal-reader connection is not standing; the accepted goal is unaffected");
|
|
2322
|
+
const goalId = args(ctx).goalId;
|
|
2323
|
+
// Only the broker-authenticated caller's full incarnation can name this read. The
|
|
2324
|
+
// trusted goal-writer keeps the raw EPF authority; serveEndpoint derives the reply rail.
|
|
2325
|
+
const result = await readGoalResult(gw.ctx, goalRefOf(ctx.subject, goalId));
|
|
2326
|
+
return { goalId, ...(result === undefined ? {} : { result }) };
|
|
2327
|
+
}),
|
|
2196
2328
|
resolveCwd: (ctx) => this.serveGated(ctx, () => this.resolveSpawnCwd(args(ctx).cwd)),
|
|
2197
2329
|
// P2 item 2: `spawn` is an ACTION - accept a goal + reply the acceptance floor payload, drive
|
|
2198
2330
|
// progress + terminal off-handler (no ~30s block). The blocking reply path is gone (pin 8).
|
|
@@ -3122,6 +3254,7 @@ export class Manager {
|
|
|
3122
3254
|
? { ...restart.opts, resume: undefined, prompt: undefined, continueSession }
|
|
3123
3255
|
: { ...restart.opts, resume: undefined, prompt: undefined };
|
|
3124
3256
|
const spec = connector.buildLaunch(opts);
|
|
3257
|
+
spec.env = { ...spec.env, COTAL_MANAGER_INSTANCE: this.managerInstanceId };
|
|
3125
3258
|
const wanted = this.managedPrincipal(a);
|
|
3126
3259
|
const joinedAfter = this.ep.getRoster()
|
|
3127
3260
|
.filter((p) => p.card.id === wanted && p.lifecycleUid === a.lifecycleUid)
|
|
@@ -4122,6 +4255,7 @@ export class Manager {
|
|
|
4122
4255
|
workspaceRoot: this.workspaceRoot,
|
|
4123
4256
|
};
|
|
4124
4257
|
const spec = connector.buildLaunch(launchOpts);
|
|
4258
|
+
spec.env = { ...spec.env, COTAL_MANAGER_INSTANCE: this.managerInstanceId };
|
|
4125
4259
|
const handle = await this.spawnCustodied(name, spec, cwd, custody);
|
|
4126
4260
|
hooks?.onLaunched?.(); // P2 item 2: the "launched" progress edge (process spawned, pre-presence)
|
|
4127
4261
|
const managed = {
|
|
@@ -4681,6 +4815,7 @@ export class Manager {
|
|
|
4681
4815
|
workspaceRoot: this.workspaceRoot,
|
|
4682
4816
|
};
|
|
4683
4817
|
const spec = connector.buildLaunch(launchOpts);
|
|
4818
|
+
spec.env = { ...spec.env, COTAL_MANAGER_INSTANCE: this.managerInstanceId };
|
|
4684
4819
|
const value = { spec, launchOpts, ...authority };
|
|
4685
4820
|
prepared?.set(entry.name, value);
|
|
4686
4821
|
if (preflightOnly)
|
|
@@ -5108,7 +5243,9 @@ export class Manager {
|
|
|
5108
5243
|
* This is NEVER the manager's standing seed/supervisor connection. */
|
|
5109
5244
|
async withEndpointServeExecutor(fn) {
|
|
5110
5245
|
const identity = this.remoteAuthority?.identities.executor ?? newIdentity();
|
|
5111
|
-
|
|
5246
|
+
if (this.remoteAuthority && (!this.remoteExecutorCreds || inspectCredHealth(this.remoteExecutorCreds).state !== "healthy"))
|
|
5247
|
+
await this.renewRemoteExecutor(true);
|
|
5248
|
+
const creds = this.remoteExecutorCreds ?? (this.auth
|
|
5112
5249
|
? await mintCreds(this.auth, identity, "endpoint-serve-executor", {
|
|
5113
5250
|
endpointServeExecutor: { endpoint: MANAGER_ENDPOINT, instanceId: this.managerInstanceId },
|
|
5114
5251
|
...(this.endpointServeExecutorExpiresInSeconds !== undefined
|