@cotal-ai/auth 0.62.0 → 0.63.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/authority-client.d.ts +5 -0
- package/dist/authority-client.d.ts.map +1 -1
- package/dist/authority-client.js +15 -1
- package/dist/authority-client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +6 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +14 -1
- package/dist/ledger.js.map +1 -1
- package/dist/manager-authority.d.ts +34 -1
- package/dist/manager-authority.d.ts.map +1 -1
- package/dist/manager-authority.js +122 -13
- package/dist/manager-authority.js.map +1 -1
- package/dist/manager-contract.d.ts +4 -1
- package/dist/manager-contract.d.ts.map +1 -1
- package/dist/manager-contract.js +11 -4
- package/dist/manager-contract.js.map +1 -1
- package/dist/permissions.d.ts +15 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +5 -2
- package/dist/permissions.js.map +1 -1
- package/dist/service.d.ts +5 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +111 -15
- package/dist/service.js.map +1 -1
- package/package.json +3 -3
package/dist/service.js
CHANGED
|
@@ -59,7 +59,7 @@ import { resolve } from "node:path";
|
|
|
59
59
|
import { connect, credsAuthenticator } from "@nats-io/transport-node";
|
|
60
60
|
import { jetstreamManager } from "@nats-io/jetstream";
|
|
61
61
|
import { Kvm } from "@nats-io/kv";
|
|
62
|
-
import { contractRefToHex, contractStoreContext, fetchContractArtifact, verifyClusterManifest, verifyClusterRoot, admissionBucket, admissionMediatorGrants, assertDerivedOwnerToken, assertLifecycleToken, assertValidOwnerToken, authorizeTrustedServeSnapshot, commitSiblingIssuance, credsClaims, EpEnvelopeError, ensureAuthorityStores, epAuthBucket, isReachable, jwtFromCreds, managedRetirementOpId, mintCreds, mintPublicUserJwt, newIdentity, observeHostedRunAttempt, openRecordsBucket, parseEndpointGate, parseServiceSpec, parseServiceStatus, rawDigest, parseSecretStoreIdentity, readCheckpointStatus, readRunAdmission, readRunRecord, readSvcRecordLeader, reconcileEndpointGate, sameSecretStoreIdentity, recordSpecKey, recordStatusKey, RECORD_KINDS, recordsBucket, remoteManagerActors, retirementFrontierStreams, runDriverCaller, serveIssuanceGateKv, standaloneConnectOpts, STANDING_RENEWABLE_TTL_SEC, withIssuerSession, authorizeServeGrant, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, SERVICE_READY, writeServiceStatus } from "@cotal-ai/core";
|
|
62
|
+
import { contractRefToHex, contractStoreContext, fetchContractArtifact, verifyClusterManifest, verifyClusterRoot, admissionBucket, admissionMediatorGrants, assertDerivedOwnerToken, assertLifecycleToken, assertValidOwnerToken, authorizeTrustedServeSnapshot, commitSiblingIssuance, credsClaims, EpEnvelopeError, ensureAuthorityStores, epAuthBucket, isReachable, jwtFromCreds, managedRetirementOpId, mintCreds, mintPublicUserJwt, newIdentity, observeHostedRunAttempt, openRecordsBucket, parseEndpointGate, parseServiceSpec, parseServiceStatus, rawDigest, parseSecretStoreIdentity, readCheckpointStatus, readRunAdmission, readRunRecord, readSvcRecordLeader, reconcileEndpointGate, sameSecretStoreIdentity, recordSpecKey, recordStatusKey, RECORD_KINDS, recordsBucket, remoteManagerActors, retirementFrontierStreams, runDriverCaller, serveIssuanceGateKv, standaloneConnectOpts, STANDING_RENEWABLE_TTL_SEC, withIssuerSession, acceptedReadGrant, actorLedgerSource, connectionAcceptedToken, importNativeSubjectPermissions, mintGeneration, parseActorLedgerSource, writeAcceptedRow, authorizeServeGrant, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, SERVICE_READY, writeServiceStatus } from "@cotal-ai/core";
|
|
63
63
|
import { findCotalRoot, loadManagerInstanceIdentity, userAuthStateDir, workspaceSecretStore, createAuthInstanceIdentity, loadAuthInstanceIdentity } from "@cotal-ai/workspace";
|
|
64
64
|
import { decodeJwt } from "jose";
|
|
65
65
|
import { deriveOwnerForIdpSubject, platformControlOwner } from "./derive.js";
|
|
@@ -70,15 +70,15 @@ import { PUBLIC_EXCHANGE_VIEWS } from "./token.js";
|
|
|
70
70
|
import { grantCoordinates, verifySessionRedemption } from "./session-redemption.js";
|
|
71
71
|
import { pinnedJwksResolver } from "./issuer.js";
|
|
72
72
|
import { calloutPermissions } from "./permissions.js";
|
|
73
|
-
import { admitRemoteRun, authorizeRemoteManagerRenewal, authorizeRemoteRunAttempt, issueRemoteManagerAuthority, parseRemoteRunAdmissionRequest, parseRemoteRunAttemptRequest } from "./manager-authority.js";
|
|
73
|
+
import { admitRemoteRun, authorizeRemoteManagerRenewal, authorizeRemoteRunAttempt, issueRemoteManagerAuthority, observedRunRequest, parseRemoteRunAdmissionRequest, parseRemoteRunAttemptRequest } from "./manager-authority.js";
|
|
74
74
|
import { authorizeRemoteRetainedAgentValidation, completeRemoteRetainedAgentValidation, remoteManagerCurrentRegistrationProof } from "./retained-manager-validation.js";
|
|
75
75
|
import { authorizeRemoteManagedAgentEnrollment, authorizeRemoteManagedAgentPrepareRetirement, authorizeRemoteManagedAgentRuntimeCreate, authorizeRemoteManagedAgentRuntimeStatus } from "./managed-agent-enrollment.js";
|
|
76
76
|
import { authorizeRemoteManagerGoalIndexScan, completeRemoteManagerGoalIndexScan } from "./manager-goal-index.js";
|
|
77
77
|
import { authorizeRemoteManagerAdmin } from "./manager-admin-authorization.js";
|
|
78
78
|
import { authorizeRemoteManagerMaintenance, completeRemoteManagerMaintenance } from "./manager-maintenance.js";
|
|
79
79
|
import { validateRetainedManagedAgent } from "./continuity.js";
|
|
80
|
-
import { reconstructRemoteManagerServeGrant, remoteManagerServeGrantFromCluster } from "./manager-contract.js";
|
|
81
|
-
import { authorityBarrierGrants, authorityWriterGrants, openAuthorityClient, openSupervisedConnectReader, platformReadinessGrants, remoteManagerIssuerGrants, remoteManagerRegistrationProof, authRegistrationExecutorGrants } from "./authority-client.js";
|
|
80
|
+
import { reconstructRemoteManagerServeGrant, remoteManagerServeGrantFromCluster, remoteManagerSurface } from "./manager-contract.js";
|
|
81
|
+
import { authorityBarrierGrants, authorityWriterGrants, openAuthorityClient, openSupervisedConnectReader, platformReadinessGrants, remoteManagerIssuerGrants, remoteManagerRegistrationProof, authRegistrationExecutorGrants, servedRunRequestSubjects } from "./authority-client.js";
|
|
82
82
|
import { authorizeConnectCredential } from "./connect-reader.js";
|
|
83
83
|
import { ensureRootCredential } from "./root-credential.js";
|
|
84
84
|
import { activateLifecycleAtUid, observeGate, openLifecycleRegistry, readLifecycleHeadForOperation } from "./lifecycle-registry.js";
|
|
@@ -93,7 +93,7 @@ import { makeDrainRepairers } from "./drain-repair.js";
|
|
|
93
93
|
import { joinOrStartRetirement, openAuthAdminListener } from "./auth-admin.js";
|
|
94
94
|
import { AUTH_SERVICE_ENDPOINT, authClusterArtifacts, authContractArtifactValues } from "./auth-service-contract.js";
|
|
95
95
|
import { drainTargetForEndpoint, openAdmissionMediator } from "./admission-mediator.js";
|
|
96
|
-
import { AGENT_BEARER_TTL_SEC, findInteractiveActor, findManagedActor, ledgerAclResolver, ledgerAuthorizeAgentExchange, ledgerAuthorizeConnect, ledgerAuthorizeGrant, } from "./ledger.js";
|
|
96
|
+
import { AGENT_BEARER_TTL_SEC, findInteractiveActor, findManagedActor, findActorUnified, ledgerAclResolver, ledgerActorSourceIsLive, ledgerAuthorizeAgentExchange, ledgerAuthorizeConnect, ledgerAuthorizeGrant, } from "./ledger.js";
|
|
97
97
|
import { AUTH_PROVIDER_NAME, clearAuthServiceInfo, loadCalloutAuth, loadIssuer, loadOwnerSecret, loadPinnedIdp, loadServiceKeys, saveAuthServiceInfo, spaceIssuer, } from "./store.js";
|
|
98
98
|
/** JWKS max-age seconds — the cache contract's knob. Exported so a rotation tool can compute the
|
|
99
99
|
* retire floor (max-age + max bearer TTL) from it. */
|
|
@@ -121,6 +121,7 @@ const PUBLIC_FAILED_PER_MIN = 30; // per-PEER refused-exchange window (rolling m
|
|
|
121
121
|
const PUBLIC_PEER_BUCKETS_MAX = 1024; // bounded LRU of per-peer failure buckets
|
|
122
122
|
const PUBLIC_MAX_IN_FLIGHT = 64; // global concurrent-admission cap on the public listener
|
|
123
123
|
const PUBLIC_DEADLINE_MS = 10_000; // hard wall-clock deadline per public request
|
|
124
|
+
const OBSERVED_RUN_REQUEST_WINDOW_MS = 5 * 60_000; // how long an observed served resume or answer can still be forwarded
|
|
124
125
|
/** Package-internal policy step for the hosted retirement requester. The HTTP production door calls
|
|
125
126
|
* this after deriving the owner and manager actors; broker-free smokes exercise the same decision. */
|
|
126
127
|
export function authorizeRemoteManagerRetirement(args) {
|
|
@@ -431,21 +432,21 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
431
432
|
const fileArm = ledgerAuthorizeConnect(opts.dir);
|
|
432
433
|
const recordsJsm = await jetstreamManager(remoteIssuer.nc);
|
|
433
434
|
const loadLocalManager = opts.localManager ?? (() => undefined);
|
|
434
|
-
// Standing renewal
|
|
435
|
-
// registration revision: spec (leader read) -> closure manifest -> root
|
|
436
|
-
// against its content digest. The request never selects the surface.
|
|
435
|
+
// Standing renewal and a served resume or answer read the manager surface from the REGISTERED
|
|
436
|
+
// service spec at the gate's registration revision: spec (leader read) -> closure manifest -> root
|
|
437
|
+
// document, each verified against its content digest. The request never selects the surface.
|
|
437
438
|
const registeredManagerCluster = async (owner, instanceId, observed) => {
|
|
438
439
|
const rec = await readSvcRecordLeader(recordsJsm, space, recordSpecKey(RECORD_KINDS.svc, ["manager", instanceId]));
|
|
439
440
|
if (!rec || "deleted" in rec || rec.revision !== observed.registrationRevision)
|
|
440
|
-
throw new EpEnvelopeError("failed-precondition", "
|
|
441
|
+
throw new EpEnvelopeError("failed-precondition", "found no manager service spec at the gate's registration revision");
|
|
441
442
|
const spec = parseServiceSpec(rec.value, { endpoint: "manager" });
|
|
442
443
|
if (spec.owner !== owner)
|
|
443
|
-
throw new EpEnvelopeError("permission-denied", "manager
|
|
444
|
+
throw new EpEnvelopeError("permission-denied", "the manager service spec belongs to another owner");
|
|
444
445
|
const store = await contractStoreContext(remoteIssuer.nc, space);
|
|
445
446
|
const read = async (digest) => {
|
|
446
447
|
const bytes = await fetchContractArtifact(store, contractRefToHex(digest));
|
|
447
448
|
if (!bytes)
|
|
448
|
-
throw new EpEnvelopeError("failed-precondition", `
|
|
449
|
+
throw new EpEnvelopeError("failed-precondition", `cannot read registered manager contract artifact ${digest}`);
|
|
449
450
|
return JSON.parse(new TextDecoder().decode(bytes));
|
|
450
451
|
};
|
|
451
452
|
for (const closure of spec.clusterDigests) {
|
|
@@ -454,7 +455,7 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
454
455
|
if (verifyClusterRoot(root, document).urn === "ai.cotal.manager")
|
|
455
456
|
return document;
|
|
456
457
|
}
|
|
457
|
-
throw new EpEnvelopeError("failed-precondition", "manager
|
|
458
|
+
throw new EpEnvelopeError("failed-precondition", "the manager service spec registers no manager cluster");
|
|
458
459
|
};
|
|
459
460
|
const managerGate = async (owner, instanceId) => {
|
|
460
461
|
const localManager = loadLocalManager();
|
|
@@ -532,6 +533,47 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
532
533
|
if (fatalReason !== undefined)
|
|
533
534
|
throw new EpEnvelopeError("unavailable", `the auth service for space "${space}" is momentarily unavailable (it detected a fault and is restarting); retry shortly`);
|
|
534
535
|
};
|
|
536
|
+
// The issuer window's signer: the data account's signing key, the same `hostAuth` shape the run
|
|
537
|
+
// admission builds.
|
|
538
|
+
const issuerAuth = () => ({
|
|
539
|
+
space,
|
|
540
|
+
operator: { seed: "", jwt: "" },
|
|
541
|
+
account: { pub: dataAccount.pub, seed: "", jwt: "", signingSeed: dataAccount.signingSeed, signingPub: "" },
|
|
542
|
+
sys: { pub: "", jwt: "" },
|
|
543
|
+
});
|
|
544
|
+
// The source check the issuing host resolves with: an actor-ledger row is attested here, from the
|
|
545
|
+
// ledger this service owns; every other coordinate goes to core's check (SPEC 13.15).
|
|
546
|
+
const composedSourceIsLive = (session) => (source) => parseActorLedgerSource(source) ? Promise.resolve(ledgerActorSourceIsLive(opts.dir)(source)) : session.sourceIsLive(source);
|
|
547
|
+
// A served resume or answer is issued only for a request this host saw its caller publish, only
|
|
548
|
+
// once, and only for what that request's envelope asked (SPEC 14.8): the forward's coordinates
|
|
549
|
+
// are the manager's word. The manager forwards from inside the handler serving the request, so an
|
|
550
|
+
// observation older than the window has no forward left to bind; the window also bounds the table.
|
|
551
|
+
const observedRunRequests = new Map();
|
|
552
|
+
for (const subject of servedRunRequestSubjects(space))
|
|
553
|
+
remoteIssuer.nc.subscribe(subject, {
|
|
554
|
+
callback: (_err, msg) => {
|
|
555
|
+
const now = Date.now();
|
|
556
|
+
for (const [seen, { expires }] of observedRunRequests) {
|
|
557
|
+
if (expires > now)
|
|
558
|
+
break;
|
|
559
|
+
observedRunRequests.delete(seen);
|
|
560
|
+
}
|
|
561
|
+
const request = observedRunRequest(msg.subject, msg.data);
|
|
562
|
+
if (request === undefined)
|
|
563
|
+
return;
|
|
564
|
+
// Re-inserting keeps the table in expiry order, which the prune above stops on.
|
|
565
|
+
observedRunRequests.delete(msg.subject);
|
|
566
|
+
observedRunRequests.set(msg.subject, { expires: now + OBSERVED_RUN_REQUEST_WINDOW_MS, request });
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
const takeObservedRunRequest = async (subject) => {
|
|
570
|
+
// The broker queued the request to this connection before the manager could read it, and a
|
|
571
|
+
// flush returns only after this connection has read everything its server queued before it.
|
|
572
|
+
await remoteIssuer.nc.flush();
|
|
573
|
+
const observed = observedRunRequests.get(subject);
|
|
574
|
+
observedRunRequests.delete(subject);
|
|
575
|
+
return observed !== undefined && observed.expires > Date.now() ? observed.request : undefined;
|
|
576
|
+
};
|
|
535
577
|
return {
|
|
536
578
|
authorizeConnect: async (t) => {
|
|
537
579
|
refuseIfFenced();
|
|
@@ -549,6 +591,40 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
549
591
|
if (verdict !== "candidate")
|
|
550
592
|
throw new Error(`manager instance ${instanceId} is ${verdict} at the permission mint`);
|
|
551
593
|
},
|
|
594
|
+
issueUserCaller: async ({ t, connId, mint }) => {
|
|
595
|
+
refuseIfFenced();
|
|
596
|
+
const owner = assertDerivedOwnerToken(t.owner);
|
|
597
|
+
const actor = t.act.actor;
|
|
598
|
+
const uid = assertLifecycleToken(t.act.lifecycleUid ?? "", "lifecycleUid");
|
|
599
|
+
const acceptedToken = connectionAcceptedToken(connId);
|
|
600
|
+
return withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, async (session) => {
|
|
601
|
+
// The row this connection's nonce names, read the way the client reads it.
|
|
602
|
+
const existing = await session.nc.request(acceptedReadGrant(space, acceptedToken), new Uint8Array(0), { timeout: 3000 });
|
|
603
|
+
const code = existing.headers?.code ?? 0;
|
|
604
|
+
const status = existing.headers?.get("Status") ?? "";
|
|
605
|
+
const missing = code === 404 || status.startsWith("404");
|
|
606
|
+
if (!missing && (code >= 400 || status !== ""))
|
|
607
|
+
throw new Error(`callout permissions: the accepted-row read for this connection failed (${code || status})`);
|
|
608
|
+
if (missing) {
|
|
609
|
+
const generation = mintGeneration();
|
|
610
|
+
const perms = mint({ generation, acceptedToken });
|
|
611
|
+
const ref = { space, owner, actor, uid, generation };
|
|
612
|
+
const prepared = await session.store.stage({
|
|
613
|
+
version: 1, ref, sources: [actorLedgerSource(space, owner, actor, uid)], permissions: importNativeSubjectPermissions(perms),
|
|
614
|
+
});
|
|
615
|
+
await session.store.release(prepared, async () => { });
|
|
616
|
+
await writeAcceptedRow(session.accepted, acceptedToken, ref);
|
|
617
|
+
return perms;
|
|
618
|
+
}
|
|
619
|
+
const row = JSON.parse(new TextDecoder().decode(existing.data));
|
|
620
|
+
const ref = row.ref;
|
|
621
|
+
if (row.version !== 1 || ref === undefined || ref.space !== space || ref.owner !== owner || ref.actor !== actor || ref.uid !== uid)
|
|
622
|
+
throw new Error("callout permissions: this connection's accepted row names another issued reference; reconnect under a new nonce");
|
|
623
|
+
const perms = mint({ generation: ref.generation, acceptedToken });
|
|
624
|
+
await session.store.confirm(ref, importNativeSubjectPermissions(perms));
|
|
625
|
+
return perms;
|
|
626
|
+
});
|
|
627
|
+
},
|
|
552
628
|
verifySession: async (principal, claim) => {
|
|
553
629
|
refuseIfFenced();
|
|
554
630
|
return verifySessionRedemption({
|
|
@@ -996,7 +1072,7 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
996
1072
|
return gate.observe();
|
|
997
1073
|
},
|
|
998
1074
|
issued: session.store,
|
|
999
|
-
sourceIsLive: (
|
|
1075
|
+
sourceIsLive: composedSourceIsLive(session),
|
|
1000
1076
|
admissions: await new Kvm(admitterNc).open(admissionBucket(space)),
|
|
1001
1077
|
});
|
|
1002
1078
|
}
|
|
@@ -1011,7 +1087,19 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
1011
1087
|
requireManagerAuthorityHolder(holder, String(request?.instanceId), 'manager run attempt needs scope "supervise"; spawn/admin do not imply it');
|
|
1012
1088
|
const req = parseRemoteRunAttemptRequest(request);
|
|
1013
1089
|
const recordsKv = await openRecordsBucket(remoteIssuer.nc, space);
|
|
1014
|
-
const
|
|
1090
|
+
const authorize = (session) => authorizeRemoteRunAttempt({
|
|
1091
|
+
...(session ? { issued: session.store, sourceIsLive: composedSourceIsLive(session) } : {}),
|
|
1092
|
+
isLiveManagedActor: (owner, actor, lifecycleUid) => {
|
|
1093
|
+
const row = findActorUnified(opts.dir, owner, actor);
|
|
1094
|
+
return row?.kind === "managed-agent" && row.lifecycleUid === lifecycleUid;
|
|
1095
|
+
},
|
|
1096
|
+
takeObserved: takeObservedRunRequest,
|
|
1097
|
+
registeredCommand: async (instanceId, registrationRevision, command) => {
|
|
1098
|
+
const registered = remoteManagerSurface(await registeredManagerCluster(owner, instanceId, { registrationRevision }))[command];
|
|
1099
|
+
if (registered === undefined)
|
|
1100
|
+
throw new EpEnvelopeError("failed-precondition", `the registered manager cluster declares no ${command}`);
|
|
1101
|
+
return registered;
|
|
1102
|
+
},
|
|
1015
1103
|
request,
|
|
1016
1104
|
owner,
|
|
1017
1105
|
space,
|
|
@@ -1025,7 +1113,15 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
1025
1113
|
readAdmission: (runId) => readRunAdmission(recordsJsm, space, "manager", runId),
|
|
1026
1114
|
readRunStatus: async (runId) => (await readRunRecord(recordsKv, "manager", runId))?.status?.value,
|
|
1027
1115
|
checkpointWaiting: async (token) => (await readCheckpointStatus(recordsKv, { endpoint: "manager", token }))?.value.state === "waiting",
|
|
1116
|
+
checkpointSettled: async (token) => {
|
|
1117
|
+
const status = (await readCheckpointStatus(recordsKv, { endpoint: "manager", token }))?.value;
|
|
1118
|
+
return status?.state === "resumed" && status.settledAnswerId !== undefined;
|
|
1119
|
+
},
|
|
1028
1120
|
});
|
|
1121
|
+
// A served resume or answer resolves its caller's issuance, which needs one issuer window.
|
|
1122
|
+
const grant = req.attempt?.served !== undefined || req.operator?.served !== undefined
|
|
1123
|
+
? await withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, (session) => authorize(session))
|
|
1124
|
+
: await authorize();
|
|
1029
1125
|
const hostAuth = {
|
|
1030
1126
|
space,
|
|
1031
1127
|
operator: { seed: "", jwt: "" },
|
|
@@ -1378,7 +1474,7 @@ async function startAuthContext(o) {
|
|
|
1378
1474
|
space,
|
|
1379
1475
|
token: { key: issuer.localKeySet(), issuer: issuer.issuer },
|
|
1380
1476
|
authorizeActor: plane.authorizeConnect,
|
|
1381
|
-
permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller, plane.verifySession),
|
|
1477
|
+
permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller, plane.verifySession, plane.issueUserCaller),
|
|
1382
1478
|
log: (l) => console.error(l),
|
|
1383
1479
|
});
|
|
1384
1480
|
// The subscription must be ON the broker before readiness is signaled — an `up` that recorded a
|