@cotal-ai/auth 0.61.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 +13 -1
- package/dist/authority-client.d.ts.map +1 -1
- package/dist/authority-client.js +26 -2
- package/dist/authority-client.js.map +1 -1
- package/dist/delegated-user-intent.d.ts +125 -0
- package/dist/delegated-user-intent.d.ts.map +1 -0
- package/dist/delegated-user-intent.js +195 -0
- package/dist/delegated-user-intent.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +15 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +15 -2
- 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/platform-control.d.ts +8 -1
- package/dist/platform-control.d.ts.map +1 -1
- package/dist/platform-control.js +33 -15
- package/dist/platform-control.js.map +1 -1
- package/dist/service.d.ts +27 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +174 -25
- package/dist/service.js.map +1 -1
- package/package.json +3 -3
package/dist/service.js
CHANGED
|
@@ -59,29 +59,29 @@ 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, 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";
|
|
66
|
-
import { makePlatformControlAuthority, requireManagerAuthorityHolder } from "./platform-control.js";
|
|
66
|
+
import { makePlatformControlAuthority, makePlatformControlReadiness, requireManagerAuthorityHolder } from "./platform-control.js";
|
|
67
67
|
import { startAuthCallout } from "./callout.js";
|
|
68
68
|
import { createIdpBridge, verifyIdpToken } from "./idp.js";
|
|
69
69
|
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, 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
|
-
import { observeGate, openLifecycleRegistry, readLifecycleHeadForOperation } from "./lifecycle-registry.js";
|
|
84
|
+
import { activateLifecycleAtUid, observeGate, openLifecycleRegistry, readLifecycleHeadForOperation } from "./lifecycle-registry.js";
|
|
85
85
|
import { openAuthLedgerScannerCandidate } from "./ledger-scanner.js";
|
|
86
86
|
import { openRecordsScannerCandidate } from "./records-scanner.js";
|
|
87
87
|
import { acquirePlaneClaim, makeDeliveryAdminPlaneOracle, makeDeliveryAdminPrincipalOracle, scannerDeathCopy } from "./plane-claim.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({
|
|
@@ -619,6 +695,15 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
619
695
|
await flight;
|
|
620
696
|
return { retired: true, lifecycleUid };
|
|
621
697
|
},
|
|
698
|
+
activateManagedLifecycle: async (args) => {
|
|
699
|
+
refuseIfFenced();
|
|
700
|
+
await activateLifecycleAtUid(registry, {
|
|
701
|
+
owner: assertDerivedOwnerToken(args.owner),
|
|
702
|
+
actor: assertValidOwnerToken(args.actor),
|
|
703
|
+
lifecycleUid: assertLifecycleToken(args.lifecycleUid),
|
|
704
|
+
managerInstance: `auth-service:${space}`,
|
|
705
|
+
});
|
|
706
|
+
},
|
|
622
707
|
issueManagerServiceAuthority: async (holder) => {
|
|
623
708
|
refuseIfFenced();
|
|
624
709
|
const { owner } = holder;
|
|
@@ -987,7 +1072,7 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
987
1072
|
return gate.observe();
|
|
988
1073
|
},
|
|
989
1074
|
issued: session.store,
|
|
990
|
-
sourceIsLive: (
|
|
1075
|
+
sourceIsLive: composedSourceIsLive(session),
|
|
991
1076
|
admissions: await new Kvm(admitterNc).open(admissionBucket(space)),
|
|
992
1077
|
});
|
|
993
1078
|
}
|
|
@@ -1002,7 +1087,19 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
1002
1087
|
requireManagerAuthorityHolder(holder, String(request?.instanceId), 'manager run attempt needs scope "supervise"; spawn/admin do not imply it');
|
|
1003
1088
|
const req = parseRemoteRunAttemptRequest(request);
|
|
1004
1089
|
const recordsKv = await openRecordsBucket(remoteIssuer.nc, space);
|
|
1005
|
-
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
|
+
},
|
|
1006
1103
|
request,
|
|
1007
1104
|
owner,
|
|
1008
1105
|
space,
|
|
@@ -1016,7 +1113,15 @@ export async function openAuthAuthorityPlane(opts) {
|
|
|
1016
1113
|
readAdmission: (runId) => readRunAdmission(recordsJsm, space, "manager", runId),
|
|
1017
1114
|
readRunStatus: async (runId) => (await readRunRecord(recordsKv, "manager", runId))?.status?.value,
|
|
1018
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
|
+
},
|
|
1019
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();
|
|
1020
1125
|
const hostAuth = {
|
|
1021
1126
|
space,
|
|
1022
1127
|
operator: { seed: "", jwt: "" },
|
|
@@ -1369,7 +1474,7 @@ async function startAuthContext(o) {
|
|
|
1369
1474
|
space,
|
|
1370
1475
|
token: { key: issuer.localKeySet(), issuer: issuer.issuer },
|
|
1371
1476
|
authorizeActor: plane.authorizeConnect,
|
|
1372
|
-
permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller, plane.verifySession),
|
|
1477
|
+
permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller, plane.verifySession, plane.issueUserCaller),
|
|
1373
1478
|
log: (l) => console.error(l),
|
|
1374
1479
|
});
|
|
1375
1480
|
// The subscription must be ON the broker before readiness is signaled — an `up` that recorded a
|
|
@@ -1458,6 +1563,9 @@ async function startAuthContext(o) {
|
|
|
1458
1563
|
let state = "ready";
|
|
1459
1564
|
let cause;
|
|
1460
1565
|
let closePromise;
|
|
1566
|
+
// The readiness reader: one standing connection pinned to the assigned instance, replaced when
|
|
1567
|
+
// the assignment names another instance.
|
|
1568
|
+
let reader;
|
|
1461
1569
|
const close = () => {
|
|
1462
1570
|
if (closePromise !== undefined)
|
|
1463
1571
|
return closePromise;
|
|
@@ -1465,6 +1573,7 @@ async function startAuthContext(o) {
|
|
|
1465
1573
|
state = "draining";
|
|
1466
1574
|
closePromise = (async () => {
|
|
1467
1575
|
await Promise.all([closeServer(http), closeServer(publicHttp)]);
|
|
1576
|
+
await reader?.client.then((c) => c.close(), () => { });
|
|
1468
1577
|
await plane.close().catch(() => { });
|
|
1469
1578
|
await callNc.close().catch(() => { });
|
|
1470
1579
|
})();
|
|
@@ -1498,20 +1607,48 @@ async function startAuthContext(o) {
|
|
|
1498
1607
|
});
|
|
1499
1608
|
return completeRemoteRetainedAgentValidation(retained, holder.owner, authority);
|
|
1500
1609
|
};
|
|
1610
|
+
const refuseUnlessReady = () => {
|
|
1611
|
+
if (state !== "ready" || closePromise !== undefined)
|
|
1612
|
+
throw new EpEnvelopeError("unavailable", "auth-service context is not ready");
|
|
1613
|
+
};
|
|
1614
|
+
const platformDeps = platformControl === undefined ? undefined : {
|
|
1615
|
+
space,
|
|
1616
|
+
accountPublicKey: keys.dataAccount.pub,
|
|
1617
|
+
owner: platformControlOwner(ownerSecret, space, keys.dataAccount.pub),
|
|
1618
|
+
observeAssignment: (s, a) => platformControl.observeAssignment(s, a),
|
|
1619
|
+
observeManagerInstance: plane.observeManagerInstance,
|
|
1620
|
+
};
|
|
1621
|
+
const readStatus = (owner) => async (instanceId) => {
|
|
1622
|
+
refuseUnlessReady();
|
|
1623
|
+
if (reader?.instanceId !== instanceId) {
|
|
1624
|
+
void reader?.client.then((c) => c.close(), () => { });
|
|
1625
|
+
const caller = { owner, actor: `manager_ready_${instanceId}`, uid: mintLifecycleUid() };
|
|
1626
|
+
const client = openAuthorityClient({
|
|
1627
|
+
server,
|
|
1628
|
+
space,
|
|
1629
|
+
dataAccount: { pub: keys.dataAccount.pub, signingSeed: keys.dataAccount.signingSeed },
|
|
1630
|
+
label: `cotal:platform-readiness:${space}`,
|
|
1631
|
+
grants: (id) => platformReadinessGrants(space, id, caller, instanceId),
|
|
1632
|
+
log: (l) => console.error(l),
|
|
1633
|
+
});
|
|
1634
|
+
// A failed open is not cached: the next read opens again.
|
|
1635
|
+
client.catch(() => { if (reader?.client === client)
|
|
1636
|
+
reader = undefined; });
|
|
1637
|
+
reader = { instanceId, caller, client };
|
|
1638
|
+
}
|
|
1639
|
+
const { caller, client } = reader;
|
|
1640
|
+
const { nc: readerNc } = await client;
|
|
1641
|
+
return await invokeCommand(readerNc, space, await resolveService(readerNc, space, "manager", caller, { instanceId }), "status", undefined, {});
|
|
1642
|
+
};
|
|
1501
1643
|
const service = {
|
|
1502
1644
|
url,
|
|
1503
1645
|
...(publicUrl !== undefined ? { publicUrl } : {}),
|
|
1504
1646
|
cap,
|
|
1505
|
-
...(
|
|
1647
|
+
...(platformDeps !== undefined ? {
|
|
1506
1648
|
platformControlAuthority: makePlatformControlAuthority({
|
|
1507
|
-
|
|
1508
|
-
accountPublicKey: keys.dataAccount.pub,
|
|
1509
|
-
owner: platformControlOwner(ownerSecret, space, keys.dataAccount.pub),
|
|
1510
|
-
observeAssignment: (s, a) => platformControl.observeAssignment(s, a),
|
|
1511
|
-
observeManagerInstance: plane.observeManagerInstance,
|
|
1649
|
+
...platformDeps,
|
|
1512
1650
|
dispatch: (holder, request) => {
|
|
1513
|
-
|
|
1514
|
-
throw new EpEnvelopeError("unavailable", "auth-service context is not ready");
|
|
1651
|
+
refuseUnlessReady();
|
|
1515
1652
|
switch (request.kind) {
|
|
1516
1653
|
case "manager-service-authority": return plane.issueManagerServiceAuthority({ ...holder, request });
|
|
1517
1654
|
case "manager-service-maintenance": return plane.maintainRemoteManager({ ...holder, request });
|
|
@@ -1523,6 +1660,15 @@ async function startAuthContext(o) {
|
|
|
1523
1660
|
}
|
|
1524
1661
|
},
|
|
1525
1662
|
}),
|
|
1663
|
+
platformControlReadiness: makePlatformControlReadiness({ ...platformDeps, readStatus: readStatus(platformDeps.owner) }),
|
|
1664
|
+
observeManagerGate: async (instanceId) => {
|
|
1665
|
+
refuseUnlessReady();
|
|
1666
|
+
return (await plane.observeManagerInstance(instanceId)).gate;
|
|
1667
|
+
},
|
|
1668
|
+
activateManagedLifecycle: async (target) => {
|
|
1669
|
+
refuseUnlessReady();
|
|
1670
|
+
await plane.activateManagedLifecycle(target);
|
|
1671
|
+
},
|
|
1526
1672
|
} : {}),
|
|
1527
1673
|
readiness() {
|
|
1528
1674
|
if (state === "unavailable")
|
|
@@ -1561,6 +1707,9 @@ export async function dispatchManagerAuthorityRequest(ctx, owner, body) {
|
|
|
1561
1707
|
// The hosted runtime kinds read and drive host-owned intent state that stock does not hold.
|
|
1562
1708
|
if (request.kind === "manager-managed-agent-runtime-create" || request.kind === "manager-managed-agent-runtime-status")
|
|
1563
1709
|
throw new EpEnvelopeError("unimplemented", "managed agent runtime create and status must be handled by host platform interception");
|
|
1710
|
+
// A delegated user intent creates and consumes a record in the host's intent store, which stock does not hold.
|
|
1711
|
+
if (request.kind === "delegated-user-intent" || request.kind === "manager-delegated-user-intent-execution")
|
|
1712
|
+
throw new EpEnvelopeError("unimplemented", "delegated user intent admission and execution must be handled by host platform interception");
|
|
1564
1713
|
if (request.kind === "manager-retained-agent-validation") {
|
|
1565
1714
|
const retained = await ctx.validateRetainedAgent({
|
|
1566
1715
|
owner,
|