@cotal-ai/auth 0.67.0 → 0.69.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.
Files changed (55) hide show
  1. package/dist/authority-client.d.ts +4 -4
  2. package/dist/authority-client.d.ts.map +1 -1
  3. package/dist/authority-client.js +5 -5
  4. package/dist/authority-client.js.map +1 -1
  5. package/dist/barrier-evict.d.ts +27 -5
  6. package/dist/barrier-evict.d.ts.map +1 -1
  7. package/dist/barrier-evict.js +9 -9
  8. package/dist/barrier-evict.js.map +1 -1
  9. package/dist/commands.js +1 -16
  10. package/dist/commands.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/ledger-scanner.d.ts.map +1 -1
  16. package/dist/ledger-scanner.js +2 -7
  17. package/dist/ledger-scanner.js.map +1 -1
  18. package/dist/login.d.ts +3 -2
  19. package/dist/login.d.ts.map +1 -1
  20. package/dist/login.js +19 -7
  21. package/dist/login.js.map +1 -1
  22. package/dist/managed-agent-enrollment.d.ts.map +1 -1
  23. package/dist/managed-agent-enrollment.js +18 -36
  24. package/dist/managed-agent-enrollment.js.map +1 -1
  25. package/dist/manager-authority.d.ts +23 -12
  26. package/dist/manager-authority.d.ts.map +1 -1
  27. package/dist/manager-authority.js +52 -22
  28. package/dist/manager-authority.js.map +1 -1
  29. package/dist/manager-contract.d.ts +1 -1
  30. package/dist/manager-contract.d.ts.map +1 -1
  31. package/dist/manager-contract.js +5 -7
  32. package/dist/manager-contract.js.map +1 -1
  33. package/dist/manager-maintenance.d.ts +1 -1
  34. package/dist/manager-maintenance.d.ts.map +1 -1
  35. package/dist/manager-maintenance.js +12 -9
  36. package/dist/manager-maintenance.js.map +1 -1
  37. package/dist/plane-claim.d.ts +12 -2
  38. package/dist/plane-claim.d.ts.map +1 -1
  39. package/dist/plane-claim.js +30 -23
  40. package/dist/plane-claim.js.map +1 -1
  41. package/dist/provider.d.ts.map +1 -1
  42. package/dist/provider.js +1 -22
  43. package/dist/provider.js.map +1 -1
  44. package/dist/records-scanner.d.ts.map +1 -1
  45. package/dist/records-scanner.js +3 -8
  46. package/dist/records-scanner.js.map +1 -1
  47. package/dist/serialized.d.ts +8 -0
  48. package/dist/serialized.d.ts.map +1 -0
  49. package/dist/serialized.js +15 -0
  50. package/dist/serialized.js.map +1 -0
  51. package/dist/service.d.ts +12 -3
  52. package/dist/service.d.ts.map +1 -1
  53. package/dist/service.js +109 -123
  54. package/dist/service.js.map +1 -1
  55. package/package.json +4 -3
package/dist/service.js CHANGED
@@ -57,9 +57,9 @@ import { createHash, randomBytes } from "node:crypto";
57
57
  import { createServer } from "node:http";
58
58
  import { resolve } from "node:path";
59
59
  import { connect, credsAuthenticator } from "@nats-io/transport-node";
60
- import { jetstreamManager } from "@nats-io/jetstream";
60
+ import { jetstream, jetstreamManager } from "@nats-io/jetstream";
61
61
  import { Kvm } from "@nats-io/kv";
62
- import { CotalEndpoint, deprovisionAgent, isConcreteChannel, provisionAgentDurables, contractDigest, contractRefToHex, contractStoreContext, endpointToken, 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, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, registerServingInstance } from "@cotal-ai/core";
62
+ import { CotalEndpoint, deprovisionAgent, isConcreteChannel, provisionAgentDurables, contractDigest, contractRefToHex, contractStoreContext, endpointToken, fetchContractArtifact, verifyClusterManifest, verifyClusterRoot, admissionBucket, admissionMediatorGrants, assertDerivedOwnerToken, assertLifecycleToken, assertValidOwnerToken, authorizeTrustedServeSnapshot, commitSiblingIssuance, credsClaims, EpEnvelopeError, ensureAuthorityStores, epAuthBucket, isReachable, jwtFromCreds, managedRetirementOpId, mintCreds, mintPublicUserJwt, newIdentity, newTakeoverId, observeHostedRunAttempt, openRecordsBucket, parseEndpointGate, parseServiceSpec, parseServiceStatus, rawDigest, parseSecretStoreIdentity, readCheckpointStatus, readRunAdmission, readRunRecord, readSvcRecordLeader, reconcileEndpointGate, replayRunJournal, sameSecretStoreIdentity, recordSpecKey, recordStatusKey, RECORD_KINDS, recordsBucket, remoteManagerActors, retirementFrontierStreams, runDriverCaller, serveIssuanceGateKv, standaloneConnectOpts, STANDING_RENEWABLE_TTL_SEC, withIssuerSession, acceptedReadGrant, actorLedgerSource, connectionAcceptedToken, importNativeSubjectPermissions, mintGeneration, parseActorLedgerSource, writeAcceptedRow, invokeCommand, resolveService, contractArtifactCanonicalBytes, DEV_OWNER, endpointRegistrationBarrier, mintLifecycleUid, principalKey, provisionEndpointGateOpen, publishContractArtifact, registerServiceInstance, registerServingInstance } 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";
@@ -90,6 +90,7 @@ import { makeDeliveryAdminEvictor, makeDeliveryAdminHolderEvictor } from "./barr
90
90
  import { resumeAgentRetirement, runAgentRetirementBarrier } from "./retirement-barrier.js";
91
91
  import { makeRetirementCleaners } from "./retirement-cleaner.js";
92
92
  import { makeDrainRepairers } from "./drain-repair.js";
93
+ import { serializedFor } from "./serialized.js";
93
94
  import { joinOrStartRetirement, openAuthAdminListener } from "./auth-admin.js";
94
95
  import { AUTH_SERVICE_ENDPOINT, authClusterArtifacts, authContractArtifactValues } from "./auth-service-contract.js";
95
96
  import { drainTargetForEndpoint, openAdmissionMediator } from "./admission-mediator.js";
@@ -122,20 +123,12 @@ const PUBLIC_PEER_BUCKETS_MAX = 1024; // bounded LRU of per-peer failure buckets
122
123
  const PUBLIC_MAX_IN_FLIGHT = 64; // global concurrent-admission cap on the public listener
123
124
  const PUBLIC_DEADLINE_MS = 10_000; // hard wall-clock deadline per public request
124
125
  const HOST_FENCE_POLL_MS = 1_000; // how often awaitHostFence re-reads the host gate
125
- const OBSERVED_RUN_REQUEST_WINDOW_MS = 5 * 60_000; // how long an observed served resume or answer can still be forwarded
126
+ const OBSERVED_RUN_REQUEST_WINDOW_MS = 5 * 60_000; // how long an observed served run request can still be forwarded
126
127
  /** Enrollment and retirement preparation of one managed agent run one at a time, so neither acts on
127
128
  * a grant the other is still writing or releasing. The chain lives at module level, keyed by the
128
129
  * ledger directory, so two plane instances over one ledger share it. */
129
130
  const MANAGED_ALIAS_CHAINS = new Map();
130
- const serializedForAlias = (dir, owner, actor, fn) => {
131
- const key = `${resolve(dir)}:${principalKey(owner, actor).key}`;
132
- const run = (MANAGED_ALIAS_CHAINS.get(key) ?? Promise.resolve()).then(fn, fn);
133
- const tail = run.then(() => undefined, () => undefined);
134
- MANAGED_ALIAS_CHAINS.set(key, tail);
135
- void tail.then(() => { if (MANAGED_ALIAS_CHAINS.get(key) === tail)
136
- MANAGED_ALIAS_CHAINS.delete(key); });
137
- return run;
138
- };
131
+ const serializedForAlias = (dir, owner, actor, fn) => serializedFor(MANAGED_ALIAS_CHAINS, `${resolve(dir)}:${principalKey(owner, actor).key}`, fn);
139
132
  /** Package-internal policy step for the hosted retirement requester. The HTTP production door calls
140
133
  * this after deriving the owner and manager actors; broker-free smokes exercise the same decision. */
141
134
  export function authorizeRemoteManagerRetirement(args) {
@@ -310,6 +303,9 @@ export async function openAuthAuthorityPlane(opts) {
310
303
  let recordsScanner;
311
304
  let barrierReg;
312
305
  let closing = false;
306
+ // A failed open throws its own error, so a release failing on the way out is only logged: the
307
+ // row stays held and the next open reclaims it like a crash.
308
+ const releaseAfterFailedOpen = async () => hold?.release().catch((r) => log(r.message));
313
309
  // The plane-fatal channel (fact HIGH: a fenced plane must never keep serving): the mid-life
314
310
  // fence resolves it, every authority operation refuses from then on, and the composition root
315
311
  // downs the daemon. A clean close never resolves it.
@@ -349,7 +345,7 @@ export async function openAuthAuthorityPlane(opts) {
349
345
  closing = true;
350
346
  await recordsCand?.close();
351
347
  await ledgerCand?.close();
352
- await hold?.release();
348
+ await releaseAfterFailedOpen();
353
349
  await barrier?.close();
354
350
  await remoteIssuer.close();
355
351
  await reader.close();
@@ -415,7 +411,7 @@ export async function openAuthAuthorityPlane(opts) {
415
411
  closing = true;
416
412
  await recordsScanner.close();
417
413
  await scanner.close();
418
- await hold.release();
414
+ await releaseAfterFailedOpen();
419
415
  await barrier.close();
420
416
  await remoteIssuer.close();
421
417
  await reader.close();
@@ -438,7 +434,7 @@ export async function openAuthAuthorityPlane(opts) {
438
434
  closing = true;
439
435
  await recordsScanner.close();
440
436
  await scanner.close();
441
- await hold.release();
437
+ await releaseAfterFailedOpen();
442
438
  await barrier.close();
443
439
  await remoteIssuer.close();
444
440
  await reader.close();
@@ -447,6 +443,9 @@ export async function openAuthAuthorityPlane(opts) {
447
443
  }
448
444
  const fileArm = ledgerAuthorizeConnect(opts.dir);
449
445
  const recordsJsm = await jetstreamManager(remoteIssuer.nc);
446
+ const authKv = await new Kvm(remoteIssuer.nc).open(epAuthBucket(space));
447
+ const managerIssuanceGate = (instanceId) => serveIssuanceGateKv(authKv, space, { endpoint: "manager", instanceId });
448
+ const observeManagerGate = async (instanceId) => managerIssuanceGate(instanceId).observe();
450
449
  const loadLocalManager = opts.localManager ?? (() => undefined);
451
450
  // Standing renewal and a served resume or answer read the manager surface from the REGISTERED
452
451
  // service spec at the gate's registration revision: spec (leader read) -> closure manifest -> root
@@ -475,7 +474,7 @@ export async function openAuthAuthorityPlane(opts) {
475
474
  };
476
475
  const managerGate = async (owner, instanceId) => {
477
476
  const localManager = loadLocalManager();
478
- const gate = await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
477
+ const gate = await observeManagerGate(instanceId);
479
478
  if (!gate)
480
479
  return "unknown";
481
480
  if (gate.state !== "open")
@@ -549,8 +548,8 @@ export async function openAuthAuthorityPlane(opts) {
549
548
  if (fatalReason !== undefined)
550
549
  throw new EpEnvelopeError("unavailable", `the auth service for space "${space}" is momentarily unavailable (it detected a fault and is restarting); retry shortly`);
551
550
  };
552
- // The issuer window's signer: the data account's signing key, the same `hostAuth` shape the run
553
- // admission builds.
551
+ // The plane holds only the data account's signing key. The operator and system halves stay blank
552
+ // because core's mint reads only the space, the account pub and the signing seed.
554
553
  const issuerAuth = () => ({
555
554
  space,
556
555
  operator: { seed: "", jwt: "" },
@@ -560,10 +559,11 @@ export async function openAuthAuthorityPlane(opts) {
560
559
  // The source check the issuing host resolves with: an actor-ledger row is attested here, from the
561
560
  // ledger this service owns; every other coordinate goes to core's check (SPEC 13.15).
562
561
  const composedSourceIsLive = (session) => (source) => parseActorLedgerSource(source) ? Promise.resolve(ledgerActorSourceIsLive(opts.dir)(source)) : session.sourceIsLive(source);
563
- // A served resume or answer is issued only for a request this host saw its caller publish, only
564
- // once, and only for what that request's envelope asked (SPEC 14.8): the forward's coordinates
565
- // are the manager's word. The manager forwards from inside the handler serving the request, so an
566
- // observation older than the window has no forward left to bind; the window also bounds the table.
562
+ // A served run-start is admitted, and a served resume or answer issued, only for a request this
563
+ // host saw its caller publish, and only once; a resume or answer only for what that request's
564
+ // envelope asked (SPEC 14.8): the forward's coordinates are the manager's word. The manager
565
+ // forwards from inside the handler serving the request, so an observation older than the window
566
+ // has no forward left to bind; the window also bounds the table.
567
567
  const observedRunRequests = new Map();
568
568
  for (const subject of servedRunRequestSubjects(space))
569
569
  remoteIssuer.nc.subscribe(subject, {
@@ -590,7 +590,6 @@ export async function openAuthAuthorityPlane(opts) {
590
590
  observedRunRequests.delete(subject);
591
591
  return observed !== undefined && observed.expires > Date.now() ? observed.request : undefined;
592
592
  };
593
- const observeManagerGate = async (instanceId) => serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
594
593
  // A managed lifecycle's broker footprint, released under a deprovisioner pinned to that uid, so
595
594
  // no call here can reach a same-name successor's durables.
596
595
  const deprovisionManagedFootprint = async (owner, actor, lifecycleUid, memberChannels) => {
@@ -656,7 +655,7 @@ export async function openAuthAuthorityPlane(opts) {
656
655
  space,
657
656
  managerGate: async (owner, instanceId) => {
658
657
  const verdict = await managerGate(owner, assertLifecycleToken(instanceId, "serving.instanceId"));
659
- const gate = await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
658
+ const gate = await observeManagerGate(instanceId);
660
659
  return { verdict, ...(gate ? { processEpoch: gate.processEpoch } : {}) };
661
660
  },
662
661
  }, principal, claim);
@@ -731,7 +730,6 @@ export async function openAuthAuthorityPlane(opts) {
731
730
  issueManagerServiceAuthority: async (holder) => {
732
731
  refuseIfFenced();
733
732
  const { owner } = holder;
734
- const observeManagerGate = async (instanceId) => (await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe()) ?? null;
735
733
  return issueRemoteManagerAuthority({
736
734
  ...holder,
737
735
  authorizeRenewal: ({ owner: o, request: r }) => authorizeRemoteManagerRenewal({
@@ -746,7 +744,8 @@ export async function openAuthAuthorityPlane(opts) {
746
744
  },
747
745
  }),
748
746
  issue: async ({ actors, request: r }) => {
749
- const credential = async (key, profile, actor, opts) => mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, r.identities[key].id, profile, { ...opts, principal: { owner, actor }, lifecycleUid: r.managerLifecycleUid });
747
+ const signer = { space, account: dataAccount };
748
+ const credential = async (key, profile, actor, opts) => mintPublicUserJwt(signer, r.identities[key].id, profile, { ...opts, principal: { owner, actor }, lifecycleUid: r.managerLifecycleUid });
750
749
  const credentials = {};
751
750
  const siblingOn = (gate, observed) => async (key, profile, actor) => {
752
751
  const issued = await credential(key, profile, actor, profile === "goal-writer" ? { goalWriter: { endpoint: "manager" }, expiresInSeconds: standingTtl } : { expiresInSeconds: standingTtl });
@@ -767,7 +766,7 @@ export async function openAuthAuthorityPlane(opts) {
767
766
  return issued;
768
767
  };
769
768
  if (r.operation === "renew") {
770
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId: r.instanceId });
769
+ const gate = managerIssuanceGate(r.instanceId);
771
770
  const observed = await gate.observe();
772
771
  if (!observed || observed.state !== "open")
773
772
  throw new EpEnvelopeError("failed-precondition", "manager-service renewal found no current open manager gate");
@@ -798,7 +797,7 @@ export async function openAuthAuthorityPlane(opts) {
798
797
  throw new EpEnvelopeError("permission-denied", "manager-service session proof does not match this owner/lifecycle");
799
798
  const session = r.session;
800
799
  const exp = Math.min(session.exp, Math.floor(Date.now() / 1000) + 24 * 60 * 60);
801
- credentials.sessionServing = await mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, session.id, "session-serving", {
800
+ credentials.sessionServing = await mintPublicUserJwt(signer, session.id, "session-serving", {
802
801
  principal: { owner, actor: actors.serve },
803
802
  lifecycleUid: r.managerLifecycleUid,
804
803
  sessionServing: { endpoint: "manager", sessionId: session.sessionId, epoch: session.epoch },
@@ -810,7 +809,7 @@ export async function openAuthAuthorityPlane(opts) {
810
809
  const expectedProof = remoteManagerRegistrationProof(owner, r);
811
810
  if (r.registrationProof !== expectedProof)
812
811
  throw new EpEnvelopeError("permission-denied", "manager-service transfer reader proof does not match this owner/lifecycle");
813
- credentials.transferReader = await mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, r.transferReader.id, "transfer-reader", { principal: { owner, actor: actors.serve }, lifecycleUid: r.managerLifecycleUid, transferReader: { instanceId: r.instanceId } });
812
+ credentials.transferReader = await mintPublicUserJwt(signer, r.transferReader.id, "transfer-reader", { principal: { owner, actor: actors.serve }, lifecycleUid: r.managerLifecycleUid, transferReader: { instanceId: r.instanceId } });
814
813
  return { credentials };
815
814
  }
816
815
  if (r.operation === "retire") {
@@ -818,13 +817,13 @@ export async function openAuthAuthorityPlane(opts) {
818
817
  if (r.registrationProof !== expectedProof)
819
818
  throw new EpEnvelopeError("permission-denied", "manager-service retirement proof does not match this owner/lifecycle");
820
819
  const retirement = r.retirement;
821
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId: r.instanceId });
820
+ const gate = managerIssuanceGate(r.instanceId);
822
821
  const observed = await gate.observe();
823
822
  authorizeRemoteManagerRetirement({
824
823
  owner, serveActor: actors.serve, instanceId: r.instanceId,
825
824
  targetOwner: retirement.target.owner, serveEpoch: retirement.serveEpoch, gate: observed,
826
825
  });
827
- credentials.retirementRequester = await mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, retirement.id, "retirement-requester", {
826
+ credentials.retirementRequester = await mintPublicUserJwt(signer, retirement.id, "retirement-requester", {
828
827
  principal: { owner, actor: actors.serve },
829
828
  lifecycleUid: r.managerLifecycleUid,
830
829
  retirementRequester: {
@@ -839,7 +838,7 @@ export async function openAuthAuthorityPlane(opts) {
839
838
  if (r.operation === "renewStandingBundle") {
840
839
  // authorizeRenewal fresh-checked account, owner, epoch and the current-registration proof
841
840
  // (which binds the held nkeys and lifecycle). Re-observe so every row binds one revision.
842
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId: r.instanceId });
841
+ const gate = managerIssuanceGate(r.instanceId);
843
842
  const observed = await gate.observe();
844
843
  if (!observed || observed.state !== "open" || observed.processEpoch !== r.processEpoch)
845
844
  throw new EpEnvelopeError("conflict", "manager-service standing renewal gate moved before issuance");
@@ -851,7 +850,7 @@ export async function openAuthAuthorityPlane(opts) {
851
850
  remoteManager: { instanceId: r.instanceId, owner, actor: actors.executor },
852
851
  expiresInSeconds: 5 * 60,
853
852
  });
854
- credentials.serve = await mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, r.identities.serve.id, "endpoint-serve", {
853
+ credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", {
855
854
  principal: { owner, actor: actors.serve },
856
855
  lifecycleUid: r.managerLifecycleUid,
857
856
  expiresInSeconds: standingTtl,
@@ -864,7 +863,7 @@ export async function openAuthAuthorityPlane(opts) {
864
863
  return { credentials };
865
864
  }
866
865
  if (r.operation === "renewRunDriver") {
867
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId: r.instanceId });
866
+ const gate = managerIssuanceGate(r.instanceId);
868
867
  const observed = await gate.observe();
869
868
  if (!observed || observed.state !== "open" || observed.processEpoch !== r.processEpoch)
870
869
  throw new EpEnvelopeError("conflict", "manager-service run renewal gate moved before issuance");
@@ -886,14 +885,13 @@ export async function openAuthAuthorityPlane(opts) {
886
885
  epoch: r.run.epoch,
887
886
  owner,
888
887
  };
889
- const auth = { space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } };
890
- credentials.runDriver = await mintPublicUserJwt(auth, r.run.driverId, "run-driver", {
888
+ credentials.runDriver = await mintPublicUserJwt(signer, r.run.driverId, "run-driver", {
891
889
  principal: { owner, actor: caller.actor },
892
890
  lifecycleUid: r.managerLifecycleUid,
893
891
  runDriver: binding,
894
892
  expiresInSeconds: standingTtl,
895
893
  });
896
- credentials.runMediator = await mintPublicUserJwt(auth, r.run.mediatorId, "run-mediator", {
894
+ credentials.runMediator = await mintPublicUserJwt(signer, r.run.mediatorId, "run-mediator", {
897
895
  principal: { owner, actor: caller.actor },
898
896
  lifecycleUid: r.managerLifecycleUid,
899
897
  // Renewed with the placement the attempt was issued with (authorizeRemoteRunAttempt).
@@ -911,19 +909,17 @@ export async function openAuthAuthorityPlane(opts) {
911
909
  // cross HTTP, so the host independently validates the deterministic proof + canonical
912
910
  // artifact set and scopes the JWT to the already-registered instance rails. The grant
913
911
  // rows are reconstructed from the canonical manager command set by the host protocol.
914
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId: r.instanceId });
912
+ const gate = managerIssuanceGate(r.instanceId);
915
913
  const observed = await gate.observe();
916
914
  if (!observed)
917
915
  throw new EpEnvelopeError("failed-precondition", "manager-service activation found no issuance gate");
918
- credentials.serve = await mintPublicUserJwt({ space, account: { pub: dataAccount.pub, signingSeed: dataAccount.signingSeed } }, r.identities.serve.id, "endpoint-serve", {
916
+ credentials.serve = await mintPublicUserJwt(signer, r.identities.serve.id, "endpoint-serve", {
919
917
  principal: { owner, actor: actors.serve },
920
918
  lifecycleUid: r.managerLifecycleUid,
921
919
  expiresInSeconds: standingTtl,
922
- endpointServe: reconstructRemoteManagerServeGrant(r, owner, actors.serve, observed),
920
+ endpointServe: reconstructRemoteManagerServeGrant(r, owner, observed),
923
921
  serveIssuance: gate,
924
922
  });
925
- if (!observed)
926
- throw new EpEnvelopeError("failed-precondition", "manager-service activation found no issuance gate");
927
923
  const sibling = siblingOn(gate, observed);
928
924
  credentials.goalWriter = await sibling("goalWriter", "goal-writer", actors.goalWriter);
929
925
  credentials.sessionLedger = await sibling("sessionLedger", "session-ledger", actors.sessionLedger);
@@ -942,27 +938,20 @@ export async function openAuthAuthorityPlane(opts) {
942
938
  maintainRemoteManager: async (holder) => {
943
939
  refuseIfFenced();
944
940
  const { owner } = holder;
945
- const gateKv = await new Kvm(remoteIssuer.nc).open(epAuthBucket(space));
946
941
  const authorized = await authorizeRemoteManagerMaintenance({
947
942
  ...holder,
948
943
  space,
949
944
  scanner,
950
- observeManagerGate: async (instanceId) => serveIssuanceGateKv(gateKv, space, { endpoint: "manager", instanceId }).observe(),
945
+ observeManagerGate,
951
946
  });
952
- const evict = makeDeliveryAdminEvictor({ space, server, dataAccount, log });
947
+ const evictHolders = makeDeliveryAdminHolderEvictor({ space, server, dataAccount, log });
953
948
  if (authorized.operation === "evict-family-principal") {
954
- const eviction = await evict(authorized.principal);
955
- return completeRemoteManagerMaintenance(authorized, owner, { eviction });
949
+ const evictions = await evictHolders(authorized.principals);
950
+ return completeRemoteManagerMaintenance(authorized, owner, { evictions });
956
951
  }
957
952
  const principalOracle = makeDeliveryAdminPrincipalOracle({ space, server, dataAccount, log });
958
953
  const executorIdentity = newIdentity();
959
- const hostAuth = {
960
- space,
961
- operator: { seed: "", jwt: "" },
962
- account: { pub: dataAccount.pub, seed: "", jwt: "", signingSeed: dataAccount.signingSeed, signingPub: "" },
963
- sys: { pub: "", jwt: "" },
964
- };
965
- const executorCreds = await mintCreds(hostAuth, executorIdentity, "endpoint-serve-executor", {
954
+ const executorCreds = await mintCreds(issuerAuth(), executorIdentity, "endpoint-serve-executor", {
966
955
  endpointServeExecutor: { endpoint: "manager", instanceId: authorized.targetInstanceId },
967
956
  expiresInSeconds: 60,
968
957
  });
@@ -984,7 +973,7 @@ export async function openAuthAuthorityPlane(opts) {
984
973
  const result = await principalOracle(principal);
985
974
  return { state: result.state, detail: result.note ?? `delivery-daemon principal sweep, sweepComplete=${String(result.sweepComplete)}` };
986
975
  },
987
- evictHolders: makeDeliveryAdminHolderEvictor({ space, server, dataAccount, log }),
976
+ evictHolders: async (holders) => (await evictHolders(holders)).map((e) => e.verifiedGone),
988
977
  log,
989
978
  });
990
979
  }
@@ -999,10 +988,7 @@ export async function openAuthAuthorityPlane(opts) {
999
988
  ...holder,
1000
989
  proofSecret: dataAccount.signingSeed,
1001
990
  space,
1002
- observeManagerGate: async (instanceId) => {
1003
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1004
- return gate.observe();
1005
- },
991
+ observeManagerGate,
1006
992
  });
1007
993
  },
1008
994
  verifyManagedAgentEnrollment: async ({ owner, scope, request }) => {
@@ -1013,10 +999,7 @@ export async function openAuthAuthorityPlane(opts) {
1013
999
  proofSecret: dataAccount.signingSeed,
1014
1000
  space,
1015
1001
  request,
1016
- observeManagerGate: async (instanceId) => {
1017
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1018
- return gate.observe();
1019
- },
1002
+ observeManagerGate,
1020
1003
  });
1021
1004
  },
1022
1005
  verifyManagedAgentPrepareRetirement: async ({ owner, scope, request }) => {
@@ -1027,10 +1010,7 @@ export async function openAuthAuthorityPlane(opts) {
1027
1010
  proofSecret: dataAccount.signingSeed,
1028
1011
  space,
1029
1012
  request,
1030
- observeManagerGate: async (instanceId) => {
1031
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1032
- return gate.observe();
1033
- },
1013
+ observeManagerGate,
1034
1014
  });
1035
1015
  },
1036
1016
  enrollManagedAgent: async ({ owner, scope, request, sentinelCreds, agentBearerExchangeUrl }) => {
@@ -1155,10 +1135,7 @@ export async function openAuthAuthorityPlane(opts) {
1155
1135
  dir: opts.dir,
1156
1136
  proofSecret: dataAccount.signingSeed,
1157
1137
  space,
1158
- observeManagerGate: async (instanceId) => {
1159
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1160
- return gate.observe();
1161
- },
1138
+ observeManagerGate,
1162
1139
  };
1163
1140
  return request.kind === "manager-managed-agent-runtime-status"
1164
1141
  ? authorizeRemoteManagedAgentRuntimeStatus({ ...args, request })
@@ -1169,10 +1146,7 @@ export async function openAuthAuthorityPlane(opts) {
1169
1146
  const { owner } = holder;
1170
1147
  const authorized = await authorizeRemoteManagerGoalIndexScan({
1171
1148
  ...holder, proofSecret: dataAccount.signingSeed, space,
1172
- observeManagerGate: async (instanceId) => {
1173
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1174
- return gate.observe();
1175
- },
1149
+ observeManagerGate,
1176
1150
  });
1177
1151
  return completeRemoteManagerGoalIndexScan(authorized, owner, await recordsScanner.scanManagerGoalIndex(owner));
1178
1152
  },
@@ -1185,10 +1159,7 @@ export async function openAuthAuthorityPlane(opts) {
1185
1159
  space,
1186
1160
  dir: opts.dir,
1187
1161
  request,
1188
- observeManagerGate: async (instanceId) => {
1189
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1190
- return gate.observe();
1191
- },
1162
+ observeManagerGate,
1192
1163
  });
1193
1164
  },
1194
1165
  admitManagerRun: async ({ request, ...holder }) => {
@@ -1196,17 +1167,11 @@ export async function openAuthAuthorityPlane(opts) {
1196
1167
  const { owner } = holder;
1197
1168
  requireManagerAuthorityHolder(holder, String(request?.instanceId), 'manager run admission needs scope "supervise"; spawn/admin do not imply it');
1198
1169
  const runId = parseRemoteRunAdmissionRequest(request).run.runId;
1199
- const hostAuth = {
1200
- space,
1201
- operator: { seed: "", jwt: "" },
1202
- account: { pub: dataAccount.pub, seed: "", jwt: "", signingSeed: dataAccount.signingSeed, signingPub: "" },
1203
- sys: { pub: "", jwt: "" },
1204
- };
1205
- return withIssuerSession({ servers: server, space, auth: hostAuth, tls: false }, async (session) => {
1170
+ return withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, async (session) => {
1206
1171
  const admitterNc = await connect({
1207
1172
  servers: server,
1208
1173
  ...standaloneConnectOpts({
1209
- creds: await mintCreds(hostAuth, newIdentity(), "run-admitter", { runAdmitter: { endpoint: "manager", runId }, expiresInSeconds: 60 }),
1174
+ creds: await mintCreds(issuerAuth(), newIdentity(), "run-admitter", { runAdmitter: { endpoint: "manager", runId }, expiresInSeconds: 60 }),
1210
1175
  tls: false,
1211
1176
  }),
1212
1177
  maxReconnectAttempts: 0,
@@ -1214,12 +1179,10 @@ export async function openAuthAuthorityPlane(opts) {
1214
1179
  try {
1215
1180
  return await admitRemoteRun({
1216
1181
  request, owner, space, accountPublicKey: dataAccount.pub, proofSecret: dataAccount.signingSeed, endpoint: "manager",
1217
- observeManagerGate: async (instanceId) => {
1218
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1219
- return gate.observe();
1220
- },
1182
+ observeManagerGate,
1221
1183
  issued: session.store,
1222
1184
  sourceIsLive: composedSourceIsLive(session),
1185
+ takeObserved: takeObservedRunRequest,
1223
1186
  admissions: await new Kvm(admitterNc).open(admissionBucket(space)),
1224
1187
  });
1225
1188
  }
@@ -1253,12 +1216,28 @@ export async function openAuthAuthorityPlane(opts) {
1253
1216
  accountPublicKey: dataAccount.pub,
1254
1217
  proofSecret: dataAccount.signingSeed,
1255
1218
  endpoint: "manager",
1256
- observeManagerGate: async (instanceId) => {
1257
- const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
1258
- return gate.observe();
1259
- },
1219
+ observeManagerGate,
1260
1220
  readAdmission: (runId) => readRunAdmission(recordsJsm, space, "manager", runId),
1261
1221
  readRunStatus: async (runId) => (await readRunRecord(recordsKv, "manager", runId))?.status?.value,
1222
+ // Replayed under a read operator minted for that one run, the rows the manager's own read rides.
1223
+ readJournal: async (runId) => {
1224
+ const takeoverId = newTakeoverId();
1225
+ const readerNc = await connect({
1226
+ servers: server,
1227
+ ...standaloneConnectOpts({
1228
+ creds: await mintCreds(issuerAuth(), newIdentity(), "run-operator", { runOperator: { endpoint: "manager", takeoverId, runId }, expiresInSeconds: 60 }),
1229
+ tls: false,
1230
+ }),
1231
+ maxReconnectAttempts: 0,
1232
+ });
1233
+ try {
1234
+ const replay = await replayRunJournal(jetstream(readerNc), await jetstreamManager(readerNc), space, runId, takeoverId);
1235
+ return replay.records.flatMap((stored) => stored.record.kind === "step" ? [stored.record.entry] : []);
1236
+ }
1237
+ finally {
1238
+ await readerNc.drain().catch(() => readerNc.close());
1239
+ }
1240
+ },
1262
1241
  checkpointWaiting: async (token) => (await readCheckpointStatus(recordsKv, { endpoint: "manager", token }))?.value.state === "waiting",
1263
1242
  checkpointSettled: async (token) => {
1264
1243
  const status = (await readCheckpointStatus(recordsKv, { endpoint: "manager", token }))?.value;
@@ -1269,21 +1248,15 @@ export async function openAuthAuthorityPlane(opts) {
1269
1248
  const grant = req.attempt?.served !== undefined || req.operator?.served !== undefined
1270
1249
  ? await withIssuerSession({ servers: server, space, auth: issuerAuth(), tls: false }, (session) => authorize(session))
1271
1250
  : await authorize();
1272
- const hostAuth = {
1273
- space,
1274
- operator: { seed: "", jwt: "" },
1275
- account: { pub: dataAccount.pub, seed: "", jwt: "", signingSeed: dataAccount.signingSeed, signingPub: "" },
1276
- sys: { pub: "", jwt: "" },
1277
- };
1278
1251
  if (grant.kind === "attempt") {
1279
1252
  const caller = runDriverCaller(grant.driver.runDriver.runId, owner);
1280
- const driver = await mintPublicUserJwt(hostAuth, grant.driver.id, "run-driver", {
1253
+ const driver = await mintPublicUserJwt(issuerAuth(), grant.driver.id, "run-driver", {
1281
1254
  principal: { owner, actor: caller.actor },
1282
1255
  lifecycleUid: req.managerLifecycleUid,
1283
1256
  runDriver: grant.driver.runDriver,
1284
1257
  expiresInSeconds: standingTtl,
1285
1258
  });
1286
- const mediator = await mintPublicUserJwt(hostAuth, grant.mediator.id, "run-mediator", {
1259
+ const mediator = await mintPublicUserJwt(issuerAuth(), grant.mediator.id, "run-mediator", {
1287
1260
  principal: { owner, actor: caller.actor },
1288
1261
  lifecycleUid: req.managerLifecycleUid,
1289
1262
  runMediator: grant.mediator.runMediator,
@@ -1306,7 +1279,7 @@ export async function openAuthAuthorityPlane(opts) {
1306
1279
  credentials: { driver, mediator },
1307
1280
  };
1308
1281
  }
1309
- const operator = await mintPublicUserJwt(hostAuth, grant.operator.id, "run-operator", {
1282
+ const operator = await mintPublicUserJwt(issuerAuth(), grant.operator.id, "run-operator", {
1310
1283
  principal: { owner, actor: req.actor },
1311
1284
  lifecycleUid: req.managerLifecycleUid,
1312
1285
  runOperator: grant.operator.runOperator,
@@ -1331,7 +1304,7 @@ export async function openAuthAuthorityPlane(opts) {
1331
1304
  },
1332
1305
  observeManagerInstance: async (instanceId) => {
1333
1306
  refuseIfFenced();
1334
- const gate = await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
1307
+ const gate = await observeManagerGate(instanceId);
1335
1308
  const rec = await readSvcRecordLeader(recordsJsm, space, recordSpecKey(RECORD_KINDS.svc, ["manager", instanceId]));
1336
1309
  return { registered: rec !== undefined && !("deleted" in rec), gate: gate ?? null };
1337
1310
  },
@@ -1356,7 +1329,7 @@ export async function openAuthAuthorityPlane(opts) {
1356
1329
  },
1357
1330
  observeEndpointGate: async (endpoint, instanceId) => {
1358
1331
  refuseIfFenced();
1359
- return await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint, instanceId }).observe();
1332
+ return await serveIssuanceGateKv(authKv, space, { endpoint, instanceId }).observe();
1360
1333
  },
1361
1334
  fenced,
1362
1335
  close: async () => {
@@ -1368,10 +1341,14 @@ export async function openAuthAuthorityPlane(opts) {
1368
1341
  await reader.close();
1369
1342
  await recordsScanner.close();
1370
1343
  await scanner.close();
1371
- await hold.release();
1372
- await barrier.close();
1373
- await remoteIssuer.close();
1374
- await writer.close();
1344
+ try {
1345
+ await hold.release();
1346
+ }
1347
+ finally {
1348
+ await barrier.close();
1349
+ await remoteIssuer.close();
1350
+ await writer.close();
1351
+ }
1375
1352
  },
1376
1353
  };
1377
1354
  }
@@ -1522,7 +1499,13 @@ export async function runAuthService(args, store) {
1522
1499
  // The CLI wrapper, not the context, owns process signals and exit codes.
1523
1500
  const stop = async () => {
1524
1501
  clearAuthServiceInfo(dir); // a dead service must not satisfy the next start's readiness poll
1525
- await started.handle.close();
1502
+ try {
1503
+ await started.handle.close();
1504
+ }
1505
+ catch (e) {
1506
+ console.error(`✗ auth-service: ${e.message} - exiting`);
1507
+ process.exit(1);
1508
+ }
1526
1509
  process.exit(0);
1527
1510
  };
1528
1511
  process.on("SIGINT", () => void stop());
@@ -1549,7 +1532,10 @@ export async function runAuthService(args, store) {
1549
1532
  * installs no process signal handler, never exits the process, never selects a root from cwd,
1550
1533
  * and has no local manager. It returns only after the authority plane, the callout subscription
1551
1534
  * and the loopback listener are bound. A failed start releases what it acquired and throws. A
1552
- * mid-life fence or broker loss makes only this context `unavailable` and closes its resources. */
1535
+ * mid-life fence or broker loss makes only this context `unavailable` and closes its resources.
1536
+ * `close()` rejects when the context did not release its plane claim (SPEC 13.13): the row was
1537
+ * no longer its own, or the release write failed. The next start reclaims a row left held through
1538
+ * the liveness oracle. */
1553
1539
  export async function startAuthService(inputs) {
1554
1540
  if (!inputs.context?.accountPublicKey || !inputs.context.lifecycleUid || !inputs.space || !inputs.servers || !inputs.stateDir)
1555
1541
  throw new Error("auth-service: hosted context needs an account, lifecycle, space, server and stateDir");
@@ -1585,7 +1571,7 @@ export async function startAuthService(inputs) {
1585
1571
  ...(inputs.platformControl !== undefined ? { platformControl: inputs.platformControl } : {}),
1586
1572
  ...(inputs.standingRenewableTtlSeconds !== undefined ? { standingRenewableTtlSeconds: inputs.standingRenewableTtlSeconds } : {}),
1587
1573
  });
1588
- return started.handle;
1574
+ return { ...started.handle, readiness: () => ({ ...started.status(), context: inputs.context }) };
1589
1575
  }
1590
1576
  async function startAuthContext(o) {
1591
1577
  const { space, server, dir, secrets, port } = o;
@@ -1758,8 +1744,12 @@ async function startAuthContext(o) {
1758
1744
  closePromise = (async () => {
1759
1745
  await Promise.all([closeServer(http), closeServer(publicHttp)]);
1760
1746
  await reader?.client.then((c) => c.close(), () => { });
1761
- await plane.close().catch(() => { });
1762
- await callNc.close().catch(() => { });
1747
+ try {
1748
+ await plane.close();
1749
+ }
1750
+ finally {
1751
+ await callNc.close();
1752
+ }
1763
1753
  })();
1764
1754
  return closePromise;
1765
1755
  };
@@ -1771,11 +1761,11 @@ async function startAuthContext(o) {
1771
1761
  if (end.kind === "fenced" || end.cause !== undefined || closePromise === undefined) {
1772
1762
  state = "unavailable";
1773
1763
  cause = end.kind === "fenced" ? end.cause : `broker connection closed${end.cause !== undefined ? ` (${end.cause})` : ""}`;
1774
- void close();
1764
+ // A later close() returns this same promise, so its failure still reaches the host.
1765
+ close().catch(() => { });
1775
1766
  }
1776
1767
  return end;
1777
1768
  });
1778
- const context = o.context ?? { accountPublicKey: keys.dataAccount.pub, lifecycleUid: "" };
1779
1769
  const platformControl = o.platformControl;
1780
1770
  const host = platformControl?.host;
1781
1771
  // The human route's retained-validation composition, for the platform holder.
@@ -1878,15 +1868,11 @@ async function startAuthContext(o) {
1878
1868
  }
1879
1869
  },
1880
1870
  } : {}),
1881
- readiness() {
1882
- if (state === "unavailable")
1883
- return { state, context, cause: cause ?? "auth-service context is unavailable" };
1884
- return { state: closePromise === undefined ? "ready" : "draining", context };
1885
- },
1886
1871
  drain: close,
1887
1872
  close,
1888
1873
  };
1889
- return { handle: service, ended };
1874
+ const status = () => state === "unavailable" ? { state, cause: cause ?? "auth-service context is unavailable" } : { state: closePromise === undefined ? "ready" : "draining" };
1875
+ return { handle: service, status, ended };
1890
1876
  }
1891
1877
  catch (e) {
1892
1878
  // A failed start releases only what THIS context acquired, in reverse order.