@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.
Files changed (45) hide show
  1. package/dist/auth-admin.d.ts +1 -0
  2. package/dist/auth-admin.d.ts.map +1 -1
  3. package/dist/auth-admin.js +1 -0
  4. package/dist/auth-admin.js.map +1 -1
  5. package/dist/authority-client.d.ts +2 -0
  6. package/dist/authority-client.d.ts.map +1 -1
  7. package/dist/authority-client.js +16 -3
  8. package/dist/authority-client.js.map +1 -1
  9. package/dist/barrier-evict.d.ts +1 -0
  10. package/dist/barrier-evict.d.ts.map +1 -1
  11. package/dist/barrier-evict.js +2 -0
  12. package/dist/barrier-evict.js.map +1 -1
  13. package/dist/drain-repair.d.ts +1 -0
  14. package/dist/drain-repair.d.ts.map +1 -1
  15. package/dist/drain-repair.js +3 -0
  16. package/dist/drain-repair.js.map +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -11
  20. package/dist/index.js.map +1 -1
  21. package/dist/ledger-scanner.d.ts +1 -0
  22. package/dist/ledger-scanner.d.ts.map +1 -1
  23. package/dist/ledger-scanner.js +1 -0
  24. package/dist/ledger-scanner.js.map +1 -1
  25. package/dist/owned-connections.d.ts +25 -0
  26. package/dist/owned-connections.d.ts.map +1 -0
  27. package/dist/owned-connections.js +49 -0
  28. package/dist/owned-connections.js.map +1 -0
  29. package/dist/plane-claim.d.ts +53 -0
  30. package/dist/plane-claim.d.ts.map +1 -1
  31. package/dist/plane-claim.js +17 -0
  32. package/dist/plane-claim.js.map +1 -1
  33. package/dist/records-scanner.d.ts +1 -0
  34. package/dist/records-scanner.d.ts.map +1 -1
  35. package/dist/records-scanner.js +1 -0
  36. package/dist/records-scanner.js.map +1 -1
  37. package/dist/retirement-cleaner.d.ts +1 -0
  38. package/dist/retirement-cleaner.d.ts.map +1 -1
  39. package/dist/retirement-cleaner.js +1 -0
  40. package/dist/retirement-cleaner.js.map +1 -1
  41. package/dist/service.d.ts +22 -14
  42. package/dist/service.d.ts.map +1 -1
  43. package/dist/service.js +98 -46
  44. package/dist/service.js.map +1 -1
  45. 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
- // Construction records each resource's close here as soon as it opens, so a throw anywhere up to
203
- // the returned plane closes exactly what is open, in reverse order.
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 {@link openAuthAuthorityPlane}, which owns the unwind of a failed construction. */
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; the scanner teardown is not run a second time.
1296
+ // for a later close retry; successful scanner teardown is not run a second time.
1290
1297
  closing = true;
1291
- await (closeScanners ??= (async () => {
1292
- await authAdmin.close();
1293
- await reader.close();
1294
- await recordsScanner.close();
1295
- await scanner.close();
1296
- })());
1297
- let retryable = false;
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 hold.release();
1306
+ await scannerFlight;
1300
1307
  }
1301
1308
  catch (e) {
1302
- retryable = planeClaimRefusal(e) === "release-unreachable";
1309
+ if (closeScanners === scannerFlight)
1310
+ closeScanners = undefined;
1303
1311
  throw e;
1304
1312
  }
1305
- finally {
1306
- if (!retryable) {
1307
- await barrier.close();
1308
- await remoteIssuer.close();
1309
- await writer.close();
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 plane = await openAuthAuthorityPlane({
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
- await (closeAdmission ??= (async () => {
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
- finally {
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((e) => {
1724
- if (planeClaimRefusal(e) === "release-unreachable" && closePromise === flight)
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,