@cotal-ai/auth 0.71.0 → 0.72.1
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/auth-admin.d.ts +1 -0
- package/dist/auth-admin.d.ts.map +1 -1
- package/dist/auth-admin.js +1 -0
- package/dist/auth-admin.js.map +1 -1
- package/dist/authority-client.d.ts +2 -0
- package/dist/authority-client.d.ts.map +1 -1
- package/dist/authority-client.js +16 -3
- package/dist/authority-client.js.map +1 -1
- package/dist/barrier-evict.d.ts +1 -0
- package/dist/barrier-evict.d.ts.map +1 -1
- package/dist/barrier-evict.js +2 -0
- package/dist/barrier-evict.js.map +1 -1
- package/dist/drain-repair.d.ts +1 -0
- package/dist/drain-repair.d.ts.map +1 -1
- package/dist/drain-repair.js +3 -0
- package/dist/drain-repair.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -11
- package/dist/index.js.map +1 -1
- package/dist/ledger-scanner.d.ts +1 -0
- package/dist/ledger-scanner.d.ts.map +1 -1
- package/dist/ledger-scanner.js +1 -0
- package/dist/ledger-scanner.js.map +1 -1
- package/dist/owned-connections.d.ts +25 -0
- package/dist/owned-connections.d.ts.map +1 -0
- package/dist/owned-connections.js +49 -0
- package/dist/owned-connections.js.map +1 -0
- package/dist/plane-claim.d.ts +53 -0
- package/dist/plane-claim.d.ts.map +1 -1
- package/dist/plane-claim.js +17 -0
- package/dist/plane-claim.js.map +1 -1
- package/dist/records-scanner.d.ts +1 -0
- package/dist/records-scanner.d.ts.map +1 -1
- package/dist/records-scanner.js +1 -0
- package/dist/records-scanner.js.map +1 -1
- package/dist/retirement-cleaner.d.ts +1 -0
- package/dist/retirement-cleaner.d.ts.map +1 -1
- package/dist/retirement-cleaner.js +1 -0
- package/dist/retirement-cleaner.js.map +1 -1
- package/dist/service.d.ts +22 -14
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +98 -46
- package/dist/service.js.map +1 -1
- package/package.json +6 -5
package/dist/service.js
CHANGED
|
@@ -64,6 +64,7 @@ import { findCotalRoot, loadManagerInstanceIdentity, userAuthStateDir, workspace
|
|
|
64
64
|
import { decodeJwt } from "jose";
|
|
65
65
|
import { deriveOwnerForIdpSubject, platformControlOwner } from "./derive.js";
|
|
66
66
|
import { makePlatformControlAuthority, makePlatformControlReadiness, requireManagerAuthorityHolder } from "./platform-control.js";
|
|
67
|
+
import { OwnedConnections } from "./owned-connections.js";
|
|
67
68
|
import { startAuthCallout } from "./callout.js";
|
|
68
69
|
import { createIdpBridge, verifyIdpToken } from "./idp.js";
|
|
69
70
|
import { PUBLIC_EXCHANGE_VIEWS, assertTransferWriterClaim } from "./token.js";
|
|
@@ -199,11 +200,13 @@ async function registerSelfAuthorizedInstance(nc, args, register) {
|
|
|
199
200
|
* transit the auth callout — the callout cannot deadlock on its own reader.
|
|
200
201
|
*/
|
|
201
202
|
export async function openAuthAuthorityPlane(opts) {
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
return await openAuthAuthorityPlaneWithCustody(opts);
|
|
204
|
+
}
|
|
205
|
+
/** The context's custody callback is private. Failed construction unwinds from one place. */
|
|
206
|
+
async function openAuthAuthorityPlaneWithCustody(opts, onConnection) {
|
|
204
207
|
const unwind = [];
|
|
205
208
|
try {
|
|
206
|
-
return await buildAuthAuthorityPlane(opts, unwind);
|
|
209
|
+
return await buildAuthAuthorityPlane(opts, unwind, onConnection);
|
|
207
210
|
}
|
|
208
211
|
catch (e) {
|
|
209
212
|
for (const close of unwind.reverse())
|
|
@@ -211,14 +214,14 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
211
214
|
throw e;
|
|
212
215
|
}
|
|
213
216
|
}
|
|
214
|
-
/** The body of
|
|
215
|
-
async function buildAuthAuthorityPlane(opts, unwind) {
|
|
217
|
+
/** The body of the plane opener, which owns the unwind of a failed construction. */
|
|
218
|
+
async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
|
|
216
219
|
const { server, space, dataAccount, log } = opts;
|
|
217
220
|
const standingTtl = opts.standingRenewableTtlSeconds ?? STANDING_RENEWABLE_TTL_SEC;
|
|
218
221
|
if (!Number.isSafeInteger(standingTtl) || standingTtl < 5 || standingTtl > STANDING_RENEWABLE_TTL_SEC) {
|
|
219
222
|
throw new Error(`standingRenewableTtlSeconds must be an integer between 5 and ${STANDING_RENEWABLE_TTL_SEC} seconds (got ${standingTtl})`);
|
|
220
223
|
}
|
|
221
|
-
const writer = await openAuthorityClient({ server, space, dataAccount, label: `cotal:auth-mint:${space}`, grants: (id) => authorityWriterGrants(space, id), log });
|
|
224
|
+
const writer = await openAuthorityClient({ onConnection, server, space, dataAccount, label: `cotal:auth-mint:${space}`, grants: (id) => authorityWriterGrants(space, id), log });
|
|
222
225
|
unwind.push(() => writer.close());
|
|
223
226
|
await ensureAuthorityStores(await jetstreamManager(writer.nc), new Kvm(writer.nc), space);
|
|
224
227
|
const registry = await openLifecycleRegistry(writer.nc, space);
|
|
@@ -239,7 +242,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
239
242
|
const iid = authIdentity.instanceId;
|
|
240
243
|
const artifacts = authClusterArtifacts();
|
|
241
244
|
const values = [...authContractArtifactValues(), artifacts.document, artifacts.manifest];
|
|
242
|
-
const regClient = await openAuthorityClient({
|
|
245
|
+
const regClient = await openAuthorityClient({ onConnection,
|
|
243
246
|
server, space, dataAccount, label: `cotal:auth-registration:${space}`,
|
|
244
247
|
grants: (id) => registrationExecutorGrants(space, id, AUTH_SERVICE_ENDPOINT, iid),
|
|
245
248
|
log,
|
|
@@ -257,13 +260,13 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
257
260
|
await regClient.close();
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
|
-
const reader = await openSupervisedConnectReader({ server, space, dataAccount, log });
|
|
263
|
+
const reader = await openSupervisedConnectReader({ onConnection, server, space, dataAccount, log });
|
|
261
264
|
unwind.push(() => reader.close());
|
|
262
265
|
// The typed remote-manager issuer is a distinct self-minted connection. It adds only the
|
|
263
266
|
// endpoint-manager gate/credential family to the root issuance surface and is never exposed as
|
|
264
267
|
// a generic mint endpoint. Its JWTs are signed for caller-generated public nkeys; private seeds
|
|
265
268
|
// stay on the participant machine.
|
|
266
|
-
const remoteIssuer = await openAuthorityClient({ server, space, dataAccount, label: `cotal:remote-manager-issuer:${space}`, grants: (id) => remoteManagerIssuerGrants(space, id), log });
|
|
269
|
+
const remoteIssuer = await openAuthorityClient({ onConnection, server, space, dataAccount, label: `cotal:remote-manager-issuer:${space}`, grants: (id) => remoteManagerIssuerGrants(space, id), log });
|
|
267
270
|
unwind.push(() => remoteIssuer.close());
|
|
268
271
|
// The BARRIER EXECUTOR: the third self-minted connection, with its own registry bind — the
|
|
269
272
|
// mint writer stays the minimal issuance credential ("barriers are NOT this credential's
|
|
@@ -295,17 +298,17 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
295
298
|
let fatalReason;
|
|
296
299
|
let fireFatal;
|
|
297
300
|
const fenced = new Promise((r) => { fireFatal = r; });
|
|
298
|
-
const barrier = await openAuthorityClient({ server, space, dataAccount, label: `cotal:auth-barrier:${space}`, grants: (id) => authorityBarrierGrants(space, id), log });
|
|
301
|
+
const barrier = await openAuthorityClient({ onConnection, server, space, dataAccount, label: `cotal:auth-barrier:${space}`, grants: (id) => authorityBarrierGrants(space, id), log });
|
|
299
302
|
unwind.push(() => barrier.close());
|
|
300
303
|
// Unwound after both scanners close: held → released is valid only once neither scanner can act.
|
|
301
304
|
// A failed open throws its own error, so a release failing on the way out is only logged: the
|
|
302
305
|
// row stays held and the next open reclaims it like a crash.
|
|
303
306
|
unwind.push(async () => hold?.release().catch((r) => log(r.message)));
|
|
304
|
-
const ledgerCand = await openAuthLedgerScannerCandidate({ server, space, dataAccount, log });
|
|
307
|
+
const ledgerCand = await openAuthLedgerScannerCandidate({ onConnection, server, space, dataAccount, log });
|
|
305
308
|
unwind.push(() => ledgerCand.close());
|
|
306
|
-
const recordsCand = await openRecordsScannerCandidate({ server, space, dataAccount, log });
|
|
309
|
+
const recordsCand = await openRecordsScannerCandidate({ onConnection, server, space, dataAccount, log });
|
|
307
310
|
unwind.push(() => recordsCand.close());
|
|
308
|
-
const oracle = opts.probePlaneOracle ?? makeDeliveryAdminPlaneOracle({ space, server, dataAccount, log });
|
|
311
|
+
const oracle = opts.probePlaneOracle ?? makeDeliveryAdminPlaneOracle({ onConnection, space, server, dataAccount, log });
|
|
309
312
|
hold = await acquirePlaneClaim({ nc: barrier.nc, space, ledger: ledgerCand.tuple, records: recordsCand.tuple, oracle, log });
|
|
310
313
|
const scanner = ledgerCand.activate(hold.guard);
|
|
311
314
|
const recordsScanner = recordsCand.activate(hold.guard);
|
|
@@ -328,7 +331,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
328
331
|
unwind.push(async () => { closing = true; });
|
|
329
332
|
opts.probePlaneDeath?.({ ledger: () => ledgerCand.close(), records: () => recordsCand.close() });
|
|
330
333
|
const barrierReg = await openLifecycleRegistry(barrier.nc, space, scanner, recordsScanner);
|
|
331
|
-
const evictPrincipal = opts.probeEvictor ?? makeDeliveryAdminEvictor({ space, server, dataAccount, log });
|
|
334
|
+
const evictPrincipal = opts.probeEvictor ?? makeDeliveryAdminEvictor({ onConnection, space, server, dataAccount, log });
|
|
332
335
|
// The RETIREMENT deps (#29 piece 4): the barrier's injected mechanics, assembled from reviewed
|
|
333
336
|
// §13.9 profiles only. The obligation drain runs per endpoint on a SHORT-LIVED client minted
|
|
334
337
|
// with that endpoint's admission-mediator profile (the daemon owns no standing mediator; the
|
|
@@ -351,11 +354,11 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
351
354
|
// effect gets the EffectCancelledFact union member. Never a forged success: a reader sees
|
|
352
355
|
// the effect did NOT run and which retirement cancelled it; a racing real completion wins
|
|
353
356
|
// by landing first.
|
|
354
|
-
const repairers = makeDrainRepairers({ server, space, dataAccount, log });
|
|
357
|
+
const repairers = makeDrainRepairers({ onConnection, server, space, dataAccount, log });
|
|
355
358
|
const retirement = {
|
|
356
359
|
evictPrincipal,
|
|
357
360
|
drainTargetObligations: async (endpoint, targetUid, opId) => {
|
|
358
|
-
const drain = await openAuthorityClient({
|
|
361
|
+
const drain = await openAuthorityClient({ onConnection,
|
|
359
362
|
server, space, dataAccount,
|
|
360
363
|
label: `cotal:ep-drain:${space}:${endpoint}`,
|
|
361
364
|
grants: (id) => admissionMediatorGrants(space, endpoint, id),
|
|
@@ -372,7 +375,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
372
375
|
await drain.close();
|
|
373
376
|
}
|
|
374
377
|
},
|
|
375
|
-
...makeRetirementCleaners({ server, space, dataAccount, log }),
|
|
378
|
+
...makeRetirementCleaners({ onConnection, server, space, dataAccount, log }),
|
|
376
379
|
now: Date.now,
|
|
377
380
|
};
|
|
378
381
|
// Boot crash-resume BEFORE the plane answers anything (SPEC 13.1): finish every takeover this
|
|
@@ -389,7 +392,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
389
392
|
// stays with the plane's own registry + retirement deps (the drain rides the ONE sealed records
|
|
390
393
|
// scanner exactly like the boot resume); the listener only authorizes (subject attribution +
|
|
391
394
|
// the FRESH space-manager-lease holder check) and dispatches.
|
|
392
|
-
const authAdmin = await openAuthAdminListener({ server, space, dataAccount, reg: barrierReg, retirement, barrierFlight: retirementFlights, instanceId: authServeInstanceId, epoch: authServeGrant.epoch, grant: authServeGrant, log });
|
|
395
|
+
const authAdmin = await openAuthAdminListener({ onConnection, server, space, dataAccount, reg: barrierReg, retirement, barrierFlight: retirementFlights, instanceId: authServeInstanceId, epoch: authServeGrant.epoch, grant: authServeGrant, log });
|
|
393
396
|
unwind.push(() => authAdmin.close());
|
|
394
397
|
const fileArm = ledgerAuthorizeConnect(opts.dir);
|
|
395
398
|
const recordsJsm = await jetstreamManager(remoteIssuer.nc);
|
|
@@ -495,7 +498,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
495
498
|
// window, not the operator — the agent cannot "restart the auth service", so it gets a retryable
|
|
496
499
|
// unavailability, while the operator's state-3 copy stays on the log and the exit line.
|
|
497
500
|
const refuseIfFenced = () => {
|
|
498
|
-
if (fatalReason !== undefined)
|
|
501
|
+
if (fatalReason !== undefined || closing)
|
|
499
502
|
throw new EpEnvelopeError("unavailable", `the auth service for space "${space}" is momentarily unavailable (it detected a fault and is restarting); retry shortly`);
|
|
500
503
|
};
|
|
501
504
|
// The plane holds only the data account's signing key. The operator and system halves stay blank
|
|
@@ -894,12 +897,12 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
894
897
|
scanner,
|
|
895
898
|
observeManagerGate,
|
|
896
899
|
});
|
|
897
|
-
const evictHolders = makeDeliveryAdminHolderEvictor({ space, server, dataAccount, log });
|
|
900
|
+
const evictHolders = makeDeliveryAdminHolderEvictor({ onConnection, space, server, dataAccount, log });
|
|
898
901
|
if (authorized.operation === "evict-family-principal") {
|
|
899
902
|
const evictions = await evictHolders(authorized.principals);
|
|
900
903
|
return completeRemoteManagerMaintenance(authorized, owner, { evictions });
|
|
901
904
|
}
|
|
902
|
-
const principalOracle = makeDeliveryAdminPrincipalOracle({ space, server, dataAccount, log });
|
|
905
|
+
const principalOracle = makeDeliveryAdminPrincipalOracle({ onConnection, space, server, dataAccount, log });
|
|
903
906
|
const executorIdentity = newIdentity();
|
|
904
907
|
const executorCreds = await mintCreds(issuerAuth(), executorIdentity, "endpoint-serve-executor", {
|
|
905
908
|
endpointServeExecutor: { endpoint: "manager", instanceId: authorized.targetInstanceId },
|
|
@@ -910,6 +913,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
910
913
|
...standaloneConnectOpts({ creds: executorCreds, tls: false }),
|
|
911
914
|
maxReconnectAttempts: 0,
|
|
912
915
|
});
|
|
916
|
+
onConnection?.(maintenanceNc, `cotal:remote-manager-maintenance:${space}`);
|
|
913
917
|
let report;
|
|
914
918
|
try {
|
|
915
919
|
const maintenanceKvm = new Kvm(maintenanceNc);
|
|
@@ -999,6 +1003,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
999
1003
|
const provisioner = new CotalEndpoint({
|
|
1000
1004
|
space,
|
|
1001
1005
|
servers: server,
|
|
1006
|
+
onConnection: (nc) => onConnection?.(nc, `cotal:provisioner:${space}`),
|
|
1002
1007
|
channels: [],
|
|
1003
1008
|
creds: await mintCreds(issuerAuth(), identity, "provisioner"),
|
|
1004
1009
|
card: { id: identity.id, name: "provisioner", role: "provisioner", kind: "endpoint" },
|
|
@@ -1126,6 +1131,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
1126
1131
|
}),
|
|
1127
1132
|
maxReconnectAttempts: 0,
|
|
1128
1133
|
});
|
|
1134
|
+
onConnection?.(admitterNc, `cotal:run-admitter:${space}`);
|
|
1129
1135
|
try {
|
|
1130
1136
|
return await admitRemoteRun({
|
|
1131
1137
|
request, owner, space, accountPublicKey: dataAccount.pub, proofSecret: dataAccount.signingSeed, endpoint: "manager",
|
|
@@ -1180,6 +1186,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
1180
1186
|
}),
|
|
1181
1187
|
maxReconnectAttempts: 0,
|
|
1182
1188
|
});
|
|
1189
|
+
onConnection?.(readerNc, `cotal:run-attempt-reader:${space}`);
|
|
1183
1190
|
try {
|
|
1184
1191
|
const replay = await replayRunJournal(jetstream(readerNc), await jetstreamManager(readerNc), space, runId, takeoverId);
|
|
1185
1192
|
return replay.records.flatMap((stored) => stored.record.kind === "step" ? [stored.record.entry] : []);
|
|
@@ -1260,7 +1267,7 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
1260
1267
|
},
|
|
1261
1268
|
registerHostInstance: async (host, instanceId) => {
|
|
1262
1269
|
refuseIfFenced();
|
|
1263
|
-
const regClient = await openAuthorityClient({
|
|
1270
|
+
const regClient = await openAuthorityClient({ onConnection,
|
|
1264
1271
|
server, space, dataAccount, label: `cotal:host-registration:${space}`,
|
|
1265
1272
|
grants: (id) => registrationExecutorGrants(space, id, host.endpoint, instanceId),
|
|
1266
1273
|
log,
|
|
@@ -1286,29 +1293,42 @@ async function buildAuthAuthorityPlane(opts, unwind) {
|
|
|
1286
1293
|
// Clean-close order (SPEC 13.13): the rail stops answering first, then scan-capable
|
|
1287
1294
|
// clients down, then `held → released` (never released while either scanner can still
|
|
1288
1295
|
// act), then the barrier that wrote it. An unreachable release keeps the barrier alive
|
|
1289
|
-
// for a later close retry;
|
|
1296
|
+
// for a later close retry; successful scanner teardown is not run a second time.
|
|
1290
1297
|
closing = true;
|
|
1291
|
-
|
|
1292
|
-
await authAdmin
|
|
1293
|
-
await reader.close();
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
+
const scannerFlight = closeScanners ??= (async () => {
|
|
1299
|
+
await authAdmin?.close();
|
|
1300
|
+
const results = await Promise.allSettled([reader.close(), recordsScanner.close(), scanner.close()]);
|
|
1301
|
+
const errors = results.filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
1302
|
+
if (errors.length)
|
|
1303
|
+
throw new AggregateError(errors, "auth scan-capable clients failed to close");
|
|
1304
|
+
})();
|
|
1298
1305
|
try {
|
|
1299
|
-
await
|
|
1306
|
+
await scannerFlight;
|
|
1300
1307
|
}
|
|
1301
1308
|
catch (e) {
|
|
1302
|
-
|
|
1309
|
+
if (closeScanners === scannerFlight)
|
|
1310
|
+
closeScanners = undefined;
|
|
1303
1311
|
throw e;
|
|
1304
1312
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1313
|
+
let releaseFailure;
|
|
1314
|
+
try {
|
|
1315
|
+
await hold.release();
|
|
1316
|
+
}
|
|
1317
|
+
catch (e) {
|
|
1318
|
+
releaseFailure = e;
|
|
1311
1319
|
}
|
|
1320
|
+
// Keep the barrier for an unreachable release retry. All other release refusals terminate
|
|
1321
|
+
// the remaining clients but retain the release failure, even if their closes fail too.
|
|
1322
|
+
if (planeClaimRefusal(releaseFailure) !== "release-unreachable") {
|
|
1323
|
+
const results = await Promise.allSettled([barrier.close(), remoteIssuer.close(), writer.close()]);
|
|
1324
|
+
const errors = results.filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
1325
|
+
if (releaseFailure !== undefined && errors.length)
|
|
1326
|
+
throw new AggregateError([releaseFailure, ...errors], "auth claim release and connection close failed");
|
|
1327
|
+
if (errors.length)
|
|
1328
|
+
throw new AggregateError(errors, "auth remaining clients failed to close");
|
|
1329
|
+
}
|
|
1330
|
+
if (releaseFailure !== undefined)
|
|
1331
|
+
throw releaseFailure;
|
|
1312
1332
|
},
|
|
1313
1333
|
};
|
|
1314
1334
|
}
|
|
@@ -1560,7 +1580,9 @@ async function startAuthContext(o) {
|
|
|
1560
1580
|
throw new Error(`auth-service: can't reach the broker at ${server} with the callout creds - is the mesh up (with the callout account preloaded)?`);
|
|
1561
1581
|
// ---- The authority plane (R1): stores ensured + registry + supervised reader, BEFORE the
|
|
1562
1582
|
// callout exists — a connect must never be answered without the credential arm bound.
|
|
1563
|
-
const
|
|
1583
|
+
const owned = new OwnedConnections();
|
|
1584
|
+
const onConnection = (nc, label) => owned.track(nc, label);
|
|
1585
|
+
const plane = await openAuthAuthorityPlaneWithCustody({
|
|
1564
1586
|
server,
|
|
1565
1587
|
space,
|
|
1566
1588
|
dir,
|
|
@@ -1569,7 +1591,7 @@ async function startAuthContext(o) {
|
|
|
1569
1591
|
log: (l) => console.error(l),
|
|
1570
1592
|
localManager: o.localManager,
|
|
1571
1593
|
...(o.standingRenewableTtlSeconds !== undefined ? { standingRenewableTtlSeconds: o.standingRenewableTtlSeconds } : {}),
|
|
1572
|
-
});
|
|
1594
|
+
}, onConnection);
|
|
1573
1595
|
let nc;
|
|
1574
1596
|
let http;
|
|
1575
1597
|
let publicHttp;
|
|
@@ -1592,6 +1614,7 @@ async function startAuthContext(o) {
|
|
|
1592
1614
|
authenticator: credsAuthenticator(new TextEncoder().encode(callout.calloutCreds)),
|
|
1593
1615
|
name: `cotal:auth-service:${space}`,
|
|
1594
1616
|
});
|
|
1617
|
+
onConnection(nc, `cotal:auth-service:${space}`);
|
|
1595
1618
|
startAuthCallout(nc, {
|
|
1596
1619
|
xkeySeed: callout.xkey.seed,
|
|
1597
1620
|
authAccount: { pub: callout.account.pub, signingSeed: callout.account.signingSeed },
|
|
@@ -1708,20 +1731,44 @@ async function startAuthContext(o) {
|
|
|
1708
1731
|
if (state === "ready")
|
|
1709
1732
|
state = "draining";
|
|
1710
1733
|
const flight = (async () => {
|
|
1711
|
-
|
|
1734
|
+
const admissionFlight = closeAdmission ??= (async () => {
|
|
1712
1735
|
await Promise.all([closeServer(http), closeServer(publicHttp)]);
|
|
1713
1736
|
await reader?.client.then((c) => c.close(), () => { });
|
|
1714
|
-
})()
|
|
1737
|
+
})();
|
|
1738
|
+
let admissionFailure;
|
|
1739
|
+
try {
|
|
1740
|
+
await admissionFlight;
|
|
1741
|
+
}
|
|
1742
|
+
catch (e) {
|
|
1743
|
+
admissionFailure = e;
|
|
1744
|
+
if (closeAdmission === admissionFlight)
|
|
1745
|
+
closeAdmission = undefined;
|
|
1746
|
+
}
|
|
1747
|
+
let planeFailure;
|
|
1715
1748
|
try {
|
|
1716
1749
|
await plane.close();
|
|
1717
1750
|
}
|
|
1718
|
-
|
|
1751
|
+
catch (e) {
|
|
1752
|
+
planeFailure = e;
|
|
1753
|
+
}
|
|
1754
|
+
let calloutFailure;
|
|
1755
|
+
try {
|
|
1719
1756
|
await callNc.close();
|
|
1720
1757
|
}
|
|
1758
|
+
catch (cause) {
|
|
1759
|
+
calloutFailure = new Error(`auth connection cotal:auth-service:${space} failed to close`, { cause });
|
|
1760
|
+
}
|
|
1761
|
+
const failures = [admissionFailure, planeFailure, calloutFailure].filter((e) => e !== undefined);
|
|
1762
|
+
if (failures.length > 1)
|
|
1763
|
+
throw new AggregateError(failures, "auth context close failed");
|
|
1764
|
+
if (failures.length)
|
|
1765
|
+
throw failures[0];
|
|
1766
|
+
await owned.closeRemaining();
|
|
1767
|
+
owned.complete();
|
|
1721
1768
|
})();
|
|
1722
1769
|
closePromise = flight;
|
|
1723
|
-
void flight.catch((
|
|
1724
|
-
if (
|
|
1770
|
+
void flight.catch(() => {
|
|
1771
|
+
if (closePromise === flight)
|
|
1725
1772
|
closePromise = undefined;
|
|
1726
1773
|
});
|
|
1727
1774
|
return flight;
|
|
@@ -1769,9 +1816,12 @@ async function startAuthContext(o) {
|
|
|
1769
1816
|
const readStatus = (owner) => async (instanceId) => {
|
|
1770
1817
|
refuseUnlessReady();
|
|
1771
1818
|
if (reader?.instanceId !== instanceId) {
|
|
1772
|
-
void reader?.client.then((c) => c.close(), () => { })
|
|
1819
|
+
void reader?.client.then((c) => c.close(), () => { }).catch((e) => {
|
|
1820
|
+
console.error(`auth readiness reader close failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1821
|
+
});
|
|
1773
1822
|
const caller = { owner, actor: `manager_ready_${instanceId}`, uid: mintLifecycleUid() };
|
|
1774
1823
|
const client = openAuthorityClient({
|
|
1824
|
+
onConnection,
|
|
1775
1825
|
server,
|
|
1776
1826
|
space,
|
|
1777
1827
|
dataAccount: { pub: keys.dataAccount.pub, signingSeed: keys.dataAccount.signingSeed },
|
|
@@ -1792,6 +1842,8 @@ async function startAuthContext(o) {
|
|
|
1792
1842
|
url,
|
|
1793
1843
|
...(publicUrl !== undefined ? { publicUrl } : {}),
|
|
1794
1844
|
cap,
|
|
1845
|
+
closed: owned.closed,
|
|
1846
|
+
connections: () => owned.connections(),
|
|
1795
1847
|
...(platformDeps !== undefined ? {
|
|
1796
1848
|
platformControlAuthority: makePlatformControlAuthority({
|
|
1797
1849
|
...platformDeps,
|