@cotal-ai/auth 0.58.0 → 0.60.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 (87) hide show
  1. package/dist/authority-client.d.ts.map +1 -1
  2. package/dist/authority-client.js +5 -1
  3. package/dist/authority-client.js.map +1 -1
  4. package/dist/barrier-evict.d.ts +7 -0
  5. package/dist/barrier-evict.d.ts.map +1 -1
  6. package/dist/barrier-evict.js +83 -21
  7. package/dist/barrier-evict.js.map +1 -1
  8. package/dist/callout.d.ts.map +1 -1
  9. package/dist/callout.js +9 -3
  10. package/dist/callout.js.map +1 -1
  11. package/dist/commands.js +18 -11
  12. package/dist/commands.js.map +1 -1
  13. package/dist/derive.d.ts +5 -0
  14. package/dist/derive.d.ts.map +1 -1
  15. package/dist/derive.js +15 -1
  16. package/dist/derive.js.map +1 -1
  17. package/dist/grant-command.d.ts +9 -8
  18. package/dist/grant-command.d.ts.map +1 -1
  19. package/dist/grant-command.js +9 -8
  20. package/dist/grant-command.js.map +1 -1
  21. package/dist/idp.d.ts +10 -1
  22. package/dist/idp.d.ts.map +1 -1
  23. package/dist/idp.js +11 -3
  24. package/dist/idp.js.map +1 -1
  25. package/dist/index.d.ts +5 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/issuer.d.ts +3 -1
  30. package/dist/issuer.d.ts.map +1 -1
  31. package/dist/issuer.js +6 -2
  32. package/dist/issuer.js.map +1 -1
  33. package/dist/ledger.d.ts +6 -7
  34. package/dist/ledger.d.ts.map +1 -1
  35. package/dist/ledger.js +1 -1
  36. package/dist/ledger.js.map +1 -1
  37. package/dist/managed-agent-enrollment.d.ts +29 -1
  38. package/dist/managed-agent-enrollment.d.ts.map +1 -1
  39. package/dist/managed-agent-enrollment.js +45 -1
  40. package/dist/managed-agent-enrollment.js.map +1 -1
  41. package/dist/manager-admin-authorization.d.ts +13 -4
  42. package/dist/manager-admin-authorization.d.ts.map +1 -1
  43. package/dist/manager-admin-authorization.js +15 -7
  44. package/dist/manager-admin-authorization.js.map +1 -1
  45. package/dist/manager-authority.d.ts +11 -7
  46. package/dist/manager-authority.d.ts.map +1 -1
  47. package/dist/manager-authority.js +9 -7
  48. package/dist/manager-authority.js.map +1 -1
  49. package/dist/manager-goal-index.d.ts +2 -3
  50. package/dist/manager-goal-index.d.ts.map +1 -1
  51. package/dist/manager-goal-index.js +2 -2
  52. package/dist/manager-goal-index.js.map +1 -1
  53. package/dist/manager-maintenance.d.ts +2 -3
  54. package/dist/manager-maintenance.d.ts.map +1 -1
  55. package/dist/manager-maintenance.js +8 -2
  56. package/dist/manager-maintenance.js.map +1 -1
  57. package/dist/permissions.d.ts +15 -1
  58. package/dist/permissions.d.ts.map +1 -1
  59. package/dist/permissions.js +20 -1
  60. package/dist/permissions.js.map +1 -1
  61. package/dist/platform-control.d.ts +47 -0
  62. package/dist/platform-control.d.ts.map +1 -0
  63. package/dist/platform-control.js +117 -0
  64. package/dist/platform-control.js.map +1 -0
  65. package/dist/provider.d.ts.map +1 -1
  66. package/dist/provider.js +36 -11
  67. package/dist/provider.js.map +1 -1
  68. package/dist/records-scanner.d.ts.map +1 -1
  69. package/dist/records-scanner.js +3 -2
  70. package/dist/records-scanner.js.map +1 -1
  71. package/dist/retained-manager-validation.d.ts +9 -6
  72. package/dist/retained-manager-validation.d.ts.map +1 -1
  73. package/dist/retained-manager-validation.js +9 -7
  74. package/dist/retained-manager-validation.js.map +1 -1
  75. package/dist/service.d.ts +75 -29
  76. package/dist/service.d.ts.map +1 -1
  77. package/dist/service.js +127 -31
  78. package/dist/service.js.map +1 -1
  79. package/dist/session-redemption.d.ts +51 -0
  80. package/dist/session-redemption.d.ts.map +1 -0
  81. package/dist/session-redemption.js +61 -0
  82. package/dist/session-redemption.js.map +1 -0
  83. package/dist/token.d.ts +15 -2
  84. package/dist/token.d.ts.map +1 -1
  85. package/dist/token.js +30 -3
  86. package/dist/token.js.map +1 -1
  87. package/package.json +3 -3
package/dist/service.js CHANGED
@@ -61,15 +61,17 @@ import { Kvm } from "@nats-io/kv";
61
61
  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
62
  import { findCotalRoot, loadManagerInstanceIdentity, userAuthStateDir, workspaceSecretStore, createAuthInstanceIdentity, loadAuthInstanceIdentity } from "@cotal-ai/workspace";
63
63
  import { decodeJwt } from "jose";
64
- import { deriveOwnerForIdpSubject } from "./derive.js";
64
+ import { deriveOwnerForIdpSubject, platformControlOwner } from "./derive.js";
65
+ import { makePlatformControlAuthority, requireManagerAuthorityHolder } from "./platform-control.js";
65
66
  import { startAuthCallout } from "./callout.js";
66
67
  import { createIdpBridge, verifyIdpToken } from "./idp.js";
67
68
  import { PUBLIC_EXCHANGE_VIEWS } from "./token.js";
69
+ import { grantCoordinates, verifySessionRedemption } from "./session-redemption.js";
68
70
  import { pinnedJwksResolver } from "./issuer.js";
69
71
  import { calloutPermissions } from "./permissions.js";
70
72
  import { admitRemoteRun, authorizeRemoteManagerRenewal, authorizeRemoteRunAttempt, issueRemoteManagerAuthority, parseRemoteRunAdmissionRequest, parseRemoteRunAttemptRequest } from "./manager-authority.js";
71
73
  import { authorizeRemoteRetainedAgentValidation, completeRemoteRetainedAgentValidation, remoteManagerCurrentRegistrationProof } from "./retained-manager-validation.js";
72
- import { authorizeRemoteManagedAgentEnrollment, authorizeRemoteManagedAgentPrepareRetirement } from "./managed-agent-enrollment.js";
74
+ import { authorizeRemoteManagedAgentEnrollment, authorizeRemoteManagedAgentPrepareRetirement, authorizeRemoteManagedAgentRuntimeCreate, authorizeRemoteManagedAgentRuntimeStatus } from "./managed-agent-enrollment.js";
73
75
  import { authorizeRemoteManagerGoalIndexScan, completeRemoteManagerGoalIndexScan } from "./manager-goal-index.js";
74
76
  import { authorizeRemoteManagerAdmin } from "./manager-admin-authorization.js";
75
77
  import { authorizeRemoteManagerMaintenance, completeRemoteManagerMaintenance } from "./manager-maintenance.js";
@@ -83,7 +85,7 @@ import { openAuthLedgerScannerCandidate } from "./ledger-scanner.js";
83
85
  import { openRecordsScannerCandidate } from "./records-scanner.js";
84
86
  import { acquirePlaneClaim, makeDeliveryAdminPlaneOracle, makeDeliveryAdminPrincipalOracle, scannerDeathCopy } from "./plane-claim.js";
85
87
  import { enumerateOperationIntents, resumeAgentTakeover } from "./credential-ledger.js";
86
- import { makeDeliveryAdminEvictor } from "./barrier-evict.js";
88
+ import { makeDeliveryAdminEvictor, makeDeliveryAdminHolderEvictor } from "./barrier-evict.js";
87
89
  import { resumeAgentRetirement, runAgentRetirementBarrier } from "./retirement-barrier.js";
88
90
  import { makeRetirementCleaners } from "./retirement-cleaner.js";
89
91
  import { makeDrainRepairers } from "./drain-repair.js";
@@ -546,6 +548,18 @@ export async function openAuthAuthorityPlane(opts) {
546
548
  if (verdict !== "candidate")
547
549
  throw new Error(`manager instance ${instanceId} is ${verdict} at the permission mint`);
548
550
  },
551
+ verifySession: async (principal, claim) => {
552
+ refuseIfFenced();
553
+ return verifySessionRedemption({
554
+ jsm: recordsJsm,
555
+ space,
556
+ managerGate: async (owner, instanceId) => {
557
+ const verdict = await managerGate(owner, assertLifecycleToken(instanceId, "serving.instanceId"));
558
+ const gate = await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
559
+ return { verdict, ...(gate ? { processEpoch: gate.processEpoch } : {}) };
560
+ },
561
+ }, principal, claim);
562
+ },
549
563
  retireInteractiveLifecycle: async (args) => {
550
564
  refuseIfFenced();
551
565
  const owner = assertDerivedOwnerToken(args.owner);
@@ -604,13 +618,12 @@ export async function openAuthAuthorityPlane(opts) {
604
618
  await flight;
605
619
  return { retired: true, lifecycleUid };
606
620
  },
607
- issueManagerServiceAuthority: async ({ owner, scope, request }) => {
621
+ issueManagerServiceAuthority: async (holder) => {
608
622
  refuseIfFenced();
623
+ const { owner } = holder;
609
624
  const observeManagerGate = async (instanceId) => (await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe()) ?? null;
610
625
  return issueRemoteManagerAuthority({
611
- owner,
612
- scope,
613
- request,
626
+ ...holder,
614
627
  authorizeRenewal: ({ owner: o, request: r }) => authorizeRemoteManagerRenewal({
615
628
  request: r, owner: o, space, accountPublicKey: dataAccount.pub, proofSecret: dataAccount.signingSeed,
616
629
  observeManagerGate,
@@ -808,14 +821,13 @@ export async function openAuthAuthorityPlane(opts) {
808
821
  },
809
822
  });
810
823
  },
811
- maintainRemoteManager: async ({ owner, scope, request }) => {
824
+ maintainRemoteManager: async (holder) => {
812
825
  refuseIfFenced();
826
+ const { owner } = holder;
813
827
  const gateKv = await new Kvm(remoteIssuer.nc).open(epAuthBucket(space));
814
828
  const authorized = await authorizeRemoteManagerMaintenance({
815
- owner,
816
- scope,
829
+ ...holder,
817
830
  space,
818
- request,
819
831
  scanner,
820
832
  observeManagerGate: async (instanceId) => serveIssuanceGateKv(gateKv, space, { endpoint: "manager", instanceId }).observe(),
821
833
  });
@@ -854,7 +866,7 @@ export async function openAuthAuthorityPlane(opts) {
854
866
  const result = await principalOracle(principal);
855
867
  return { state: result.state, detail: result.note ?? `delivery-daemon principal sweep, sweepComplete=${String(result.sweepComplete)}` };
856
868
  },
857
- evict: async (principal) => (await evict(principal)).verifiedGone,
869
+ evictHolders: makeDeliveryAdminHolderEvictor({ space, server, dataAccount, log }),
858
870
  log,
859
871
  });
860
872
  }
@@ -863,14 +875,12 @@ export async function openAuthAuthorityPlane(opts) {
863
875
  }
864
876
  return completeRemoteManagerMaintenance(authorized, owner, { reconciliation: report });
865
877
  },
866
- validateRetainedAgent: async ({ owner, scope, request }) => {
878
+ validateRetainedAgent: async (holder) => {
867
879
  refuseIfFenced();
868
880
  return authorizeRemoteRetainedAgentValidation({
869
- owner,
870
- scope,
881
+ ...holder,
871
882
  proofSecret: dataAccount.signingSeed,
872
883
  space,
873
- request,
874
884
  observeManagerGate: async (instanceId) => {
875
885
  const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
876
886
  return gate.observe();
@@ -905,10 +915,27 @@ export async function openAuthAuthorityPlane(opts) {
905
915
  },
906
916
  });
907
917
  },
908
- scanManagerGoalIndex: async ({ owner, scope, request }) => {
918
+ verifyManagedAgentRuntime: async ({ owner, request }) => {
919
+ refuseIfFenced();
920
+ const args = {
921
+ owner,
922
+ dir: opts.dir,
923
+ proofSecret: dataAccount.signingSeed,
924
+ space,
925
+ observeManagerGate: async (instanceId) => {
926
+ const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
927
+ return gate.observe();
928
+ },
929
+ };
930
+ return request.kind === "manager-managed-agent-runtime-status"
931
+ ? authorizeRemoteManagedAgentRuntimeStatus({ ...args, request })
932
+ : authorizeRemoteManagedAgentRuntimeCreate({ ...args, request });
933
+ },
934
+ scanManagerGoalIndex: async (holder) => {
909
935
  refuseIfFenced();
936
+ const { owner } = holder;
910
937
  const authorized = await authorizeRemoteManagerGoalIndexScan({
911
- owner, scope, proofSecret: dataAccount.signingSeed, space, request,
938
+ ...holder, proofSecret: dataAccount.signingSeed, space,
912
939
  observeManagerGate: async (instanceId) => {
913
940
  const gate = serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId });
914
941
  return gate.observe();
@@ -916,11 +943,11 @@ export async function openAuthAuthorityPlane(opts) {
916
943
  });
917
944
  return completeRemoteManagerGoalIndexScan(authorized, owner, await recordsScanner.scanManagerGoalIndex(owner));
918
945
  },
919
- authorizeManagerAdmin: async ({ owner, scope, request }) => {
946
+ authorizeManagerAdmin: async ({ request, ...holder }) => {
920
947
  refuseIfFenced();
921
948
  return authorizeRemoteManagerAdmin({
922
- managerOwner: owner,
923
- managerScope: scope,
949
+ managerOwner: holder.owner,
950
+ ...(holder.holder === "platform" ? { managerAssignment: holder.assignment } : { managerScope: holder.scope }),
924
951
  proofSecret: dataAccount.signingSeed,
925
952
  space,
926
953
  dir: opts.dir,
@@ -931,10 +958,10 @@ export async function openAuthAuthorityPlane(opts) {
931
958
  },
932
959
  });
933
960
  },
934
- admitManagerRun: async ({ owner, scope, request }) => {
961
+ admitManagerRun: async ({ request, ...holder }) => {
935
962
  refuseIfFenced();
936
- if (!scope.includes("supervise"))
937
- throw new EpEnvelopeError("permission-denied", 'manager run admission needs scope "supervise"; spawn/admin do not imply it');
963
+ const { owner } = holder;
964
+ requireManagerAuthorityHolder(holder, String(request?.instanceId), 'manager run admission needs scope "supervise"; spawn/admin do not imply it');
938
965
  const runId = parseRemoteRunAdmissionRequest(request).run.runId;
939
966
  const hostAuth = {
940
967
  space,
@@ -968,10 +995,10 @@ export async function openAuthAuthorityPlane(opts) {
968
995
  }
969
996
  });
970
997
  },
971
- issueManagerRunAttempt: async ({ owner, scope, request }) => {
998
+ issueManagerRunAttempt: async ({ request, ...holder }) => {
972
999
  refuseIfFenced();
973
- if (!scope.includes("supervise"))
974
- throw new EpEnvelopeError("permission-denied", 'manager run attempt needs scope "supervise"; spawn/admin do not imply it');
1000
+ const { owner } = holder;
1001
+ requireManagerAuthorityHolder(holder, String(request?.instanceId), 'manager run attempt needs scope "supervise"; spawn/admin do not imply it');
975
1002
  const req = parseRemoteRunAttemptRequest(request);
976
1003
  const recordsKv = await openRecordsBucket(remoteIssuer.nc, space);
977
1004
  const grant = await authorizeRemoteRunAttempt({
@@ -1049,6 +1076,12 @@ export async function openAuthAuthorityPlane(opts) {
1049
1076
  credentials: { operator },
1050
1077
  };
1051
1078
  },
1079
+ observeManagerInstance: async (instanceId) => {
1080
+ refuseIfFenced();
1081
+ const gate = await serveIssuanceGateKv(await new Kvm(remoteIssuer.nc).open(epAuthBucket(space)), space, { endpoint: "manager", instanceId }).observe();
1082
+ const rec = await readSvcRecordLeader(recordsJsm, space, recordSpecKey(RECORD_KINDS.svc, ["manager", instanceId]));
1083
+ return { registered: rec !== undefined && !("deleted" in rec), gate: gate ?? null };
1084
+ },
1052
1085
  fenced,
1053
1086
  close: async () => {
1054
1087
  // Clean-close order (SPEC 13.13): the rail stops answering first, then scan-capable
@@ -1259,6 +1292,8 @@ export async function startAuthService(inputs) {
1259
1292
  const port = inputs.port ?? 0;
1260
1293
  if (!Number.isInteger(port) || port < 0 || port > 65535)
1261
1294
  throw new Error(`auth-service: port must be a port number, got ${port}`);
1295
+ if (inputs.platformControl !== undefined && typeof inputs.platformControl?.observeAssignment !== "function")
1296
+ throw new Error("auth-service: platformControl requires an observeAssignment function");
1262
1297
  const started = await startAuthContext({
1263
1298
  space: inputs.space,
1264
1299
  server: inputs.servers,
@@ -1268,6 +1303,8 @@ export async function startAuthService(inputs) {
1268
1303
  hostedStore: true,
1269
1304
  localManager: () => undefined,
1270
1305
  context: inputs.context,
1306
+ ...(inputs.platformControl !== undefined ? { platformControl: inputs.platformControl } : {}),
1307
+ ...(inputs.standingRenewableTtlSeconds !== undefined ? { standingRenewableTtlSeconds: inputs.standingRenewableTtlSeconds } : {}),
1271
1308
  });
1272
1309
  return started.handle;
1273
1310
  }
@@ -1303,6 +1340,7 @@ async function startAuthContext(o) {
1303
1340
  dataAccount: { pub: keys.dataAccount.pub, signingSeed: keys.dataAccount.signingSeed },
1304
1341
  log: (l) => console.error(l),
1305
1342
  localManager: o.localManager,
1343
+ ...(o.standingRenewableTtlSeconds !== undefined ? { standingRenewableTtlSeconds: o.standingRenewableTtlSeconds } : {}),
1306
1344
  });
1307
1345
  let nc;
1308
1346
  let http;
@@ -1333,7 +1371,7 @@ async function startAuthContext(o) {
1333
1371
  space,
1334
1372
  token: { key: issuer.localKeySet(), issuer: issuer.issuer },
1335
1373
  authorizeActor: plane.authorizeConnect,
1336
- permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller),
1374
+ permissionsFor: calloutPermissions(ledgerAclResolver(dir), plane.authorizeManagerCaller, plane.verifySession),
1337
1375
  log: (l) => console.error(l),
1338
1376
  });
1339
1377
  // The subscription must be ON the broker before readiness is signaled — an `up` that recorded a
@@ -1362,6 +1400,7 @@ async function startAuthContext(o) {
1362
1400
  validateRetainedAgent: plane.validateRetainedAgent,
1363
1401
  verifyManagedAgentEnrollment: plane.verifyManagedAgentEnrollment,
1364
1402
  verifyManagedAgentPrepareRetirement: plane.verifyManagedAgentPrepareRetirement,
1403
+ verifyManagedAgentRuntime: plane.verifyManagedAgentRuntime,
1365
1404
  scanManagerGoalIndex: plane.scanManagerGoalIndex,
1366
1405
  authorizeManagerAdmin: plane.authorizeManagerAdmin,
1367
1406
  admitManagerRun: plane.admitManagerRun,
@@ -1376,6 +1415,7 @@ async function startAuthContext(o) {
1376
1415
  dir,
1377
1416
  mintConnectCredential: plane.mintConnectCredential,
1378
1417
  selectManagerInstance: plane.selectManagerInstance,
1418
+ verifySession: plane.verifySession,
1379
1419
  };
1380
1420
  const loopback = createServer((req, res) => void handle(req, res, ctx));
1381
1421
  http = loopback;
@@ -1445,9 +1485,46 @@ async function startAuthContext(o) {
1445
1485
  return end;
1446
1486
  });
1447
1487
  const context = o.context ?? { accountPublicKey: keys.dataAccount.pub, lifecycleUid: "" };
1488
+ const platformControl = o.platformControl;
1489
+ // The human route's retained-validation composition, for the platform holder.
1490
+ const validateRetainedForHolder = async (holder, request) => {
1491
+ const retained = await plane.validateRetainedAgent({ ...holder, request });
1492
+ const authority = await validateRetainedManagedAgent({
1493
+ store: secrets,
1494
+ dir,
1495
+ space,
1496
+ owner: retained.target.owner,
1497
+ actor: retained.target.actor,
1498
+ actorToken: retained.actorToken,
1499
+ sentinelCreds: retained.sentinelCreds,
1500
+ });
1501
+ return completeRemoteRetainedAgentValidation(retained, holder.owner, authority);
1502
+ };
1448
1503
  const service = {
1449
1504
  url,
1450
1505
  ...(publicUrl !== undefined ? { publicUrl } : {}),
1506
+ ...(platformControl !== undefined ? {
1507
+ platformControlAuthority: makePlatformControlAuthority({
1508
+ space,
1509
+ accountPublicKey: keys.dataAccount.pub,
1510
+ owner: platformControlOwner(ownerSecret, space, keys.dataAccount.pub),
1511
+ observeAssignment: (s, a) => platformControl.observeAssignment(s, a),
1512
+ observeManagerInstance: plane.observeManagerInstance,
1513
+ dispatch: (holder, request) => {
1514
+ if (state !== "ready" || closePromise !== undefined)
1515
+ throw new EpEnvelopeError("unavailable", "auth-service context is not ready");
1516
+ switch (request.kind) {
1517
+ case "manager-service-authority": return plane.issueManagerServiceAuthority({ ...holder, request });
1518
+ case "manager-service-maintenance": return plane.maintainRemoteManager({ ...holder, request });
1519
+ case "manager-goal-index-scan": return plane.scanManagerGoalIndex({ ...holder, request });
1520
+ case "manager-admin-authorization": return plane.authorizeManagerAdmin({ ...holder, request });
1521
+ case "manager-run-admission": return plane.admitManagerRun({ ...holder, request });
1522
+ case "manager-run-attempt": return plane.issueManagerRunAttempt({ ...holder, request });
1523
+ case "manager-retained-agent-validation": return validateRetainedForHolder(holder, request);
1524
+ }
1525
+ },
1526
+ }),
1527
+ } : {}),
1451
1528
  readiness() {
1452
1529
  if (state === "unavailable")
1453
1530
  return { state, context, cause: cause ?? "auth-service context is unavailable" };
@@ -1482,6 +1559,9 @@ export async function dispatchManagerAuthorityRequest(ctx, owner, body) {
1482
1559
  // would answer a manager-lifecycle phase for an agent-lifecycle request.
1483
1560
  if (request.kind === "manager-managed-agent-enrollment" || request.kind === "manager-managed-agent-prepare-retirement")
1484
1561
  throw new EpEnvelopeError("unimplemented", "managed agent enrollment and retirement preparation must be handled by host platform interception");
1562
+ // The hosted runtime kinds read and drive host-owned intent state that stock does not hold.
1563
+ if (request.kind === "manager-managed-agent-runtime-create" || request.kind === "manager-managed-agent-runtime-status")
1564
+ throw new EpEnvelopeError("unimplemented", "managed agent runtime create and status must be handled by host platform interception");
1485
1565
  if (request.kind === "manager-retained-agent-validation") {
1486
1566
  const retained = await ctx.validateRetainedAgent({
1487
1567
  owner,
@@ -1731,8 +1811,9 @@ async function handleVerifyManagedAgentEnrollment(req, res, ctx) {
1731
1811
  if (typeof raw.owner !== "string" || raw.request === null || typeof raw.request !== "object" || Array.isArray(raw.request))
1732
1812
  return send(res, 400, { error: "managed agent enrollment verification needs a string owner and an object request" });
1733
1813
  const request = raw.request;
1734
- if (request.kind !== "manager-managed-agent-enrollment" && request.kind !== "manager-managed-agent-prepare-retirement")
1735
- return send(res, 400, { error: 'managed agent enrollment verification serves only kind "manager-managed-agent-enrollment" or "manager-managed-agent-prepare-retirement"' });
1814
+ const runtimeKind = request.kind === "manager-managed-agent-runtime-create" || request.kind === "manager-managed-agent-runtime-status";
1815
+ if (request.kind !== "manager-managed-agent-enrollment" && request.kind !== "manager-managed-agent-prepare-retirement" && !runtimeKind)
1816
+ return send(res, 400, { error: 'managed agent enrollment verification serves only kind "manager-managed-agent-enrollment", "manager-managed-agent-prepare-retirement", "manager-managed-agent-runtime-create" or "manager-managed-agent-runtime-status"' });
1736
1817
  if (typeof request.actor !== "string")
1737
1818
  return send(res, 400, { error: "managed agent enrollment verification request requires an actor" });
1738
1819
  try {
@@ -1742,6 +1823,14 @@ async function handleVerifyManagedAgentEnrollment(req, res, ctx) {
1742
1823
  // caller. An ungranted actor throws the ledger's own operator-exact sentence.
1743
1824
  const row = ledgerAuthorizeGrant(ctx.dir)(owner, request.actor);
1744
1825
  const scope = row.scope ?? [];
1826
+ if (runtimeKind) {
1827
+ // The plane re-reads the same ledger row itself: the runtime decision never takes a scope.
1828
+ const decision = await ctx.verifyManagedAgentRuntime({
1829
+ owner,
1830
+ request: raw.request,
1831
+ });
1832
+ return send(res, 200, { authorized: true, ...decision });
1833
+ }
1745
1834
  if (request.kind === "manager-managed-agent-enrollment") {
1746
1835
  const verified = await ctx.verifyManagedAgentEnrollment({
1747
1836
  owner,
@@ -1819,7 +1908,9 @@ async function handleExchange(req, res, ctx, policy) {
1819
1908
  const peer = policy.peerKey(req);
1820
1909
  const peerThrottled = policy.throttled(ctx, peer);
1821
1910
  const body = await readJsonBody(req);
1822
- const { idpToken, actor, actorToken, owner, ttlSec, view, managerInstanceId } = body;
1911
+ const { idpToken, actor, actorToken, owner, ttlSec, view, managerInstanceId, sessionGrant } = body;
1912
+ if ((view === "session-caller") !== (sessionGrant !== undefined))
1913
+ return send(res, 400, { error: 'view "session-caller" and sessionGrant come together or not at all' });
1823
1914
  if (ttlSec !== undefined && typeof ttlSec !== "number")
1824
1915
  return send(res, 400, { error: "ttlSec must be a number" });
1825
1916
  if (view !== undefined && typeof view !== "string")
@@ -1898,11 +1989,16 @@ async function handleExchange(req, res, ctx, policy) {
1898
1989
  const selectedManagerInstanceId = view === "manager-caller"
1899
1990
  ? await ctx.selectManagerInstance(deriveOwnerForIdpSubject(ctx.ownerSecret, ctx.bridgeIdp.issuer, (await verifyIdpToken(idpToken, ctx.bridgeIdp)).sub), managerInstanceId)
1900
1991
  : undefined;
1992
+ // #2312: a session-caller bearer is signed only once the identity plane has decided that THIS
1993
+ // principal holds THAT redeemed session. The bridge's `verifySession` hook runs the decision on
1994
+ // the principal it derived, before signing; the callout re-runs it at the mint.
1995
+ const session = view === "session-caller" ? grantCoordinates(sessionGrant) : undefined;
1901
1996
  const r = await ctx.bridge.exchange(idpToken, {
1902
1997
  actor,
1903
1998
  ttlSec,
1904
1999
  view: view,
1905
2000
  managerInstanceId: selectedManagerInstanceId,
2001
+ ...(session ? { verifySession: (p) => ctx.verifySession(p, session) } : {}),
1906
2002
  });
1907
2003
  return send(res, 200, r);
1908
2004
  }