@the-ai-company/cbio-node-runtime 1.55.1 → 1.57.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 (140) hide show
  1. package/README.md +53 -29
  2. package/dist/clients/agent/client.d.ts +2 -2
  3. package/dist/clients/agent/contracts.d.ts +3 -2
  4. package/dist/clients/owner/client.d.ts +8 -11
  5. package/dist/clients/owner/client.js +61 -43
  6. package/dist/clients/owner/client.js.map +1 -1
  7. package/dist/clients/owner/contracts.d.ts +23 -10
  8. package/dist/clients/owner/index.d.ts +1 -1
  9. package/dist/runtime/index.d.ts +3 -1
  10. package/dist/runtime/index.js +1 -0
  11. package/dist/runtime/index.js.map +1 -1
  12. package/dist/runtime/owner-session.d.ts +26 -0
  13. package/dist/runtime/owner-session.js +89 -0
  14. package/dist/runtime/owner-session.js.map +1 -0
  15. package/dist/vault-core/contracts.d.ts +48 -35
  16. package/dist/vault-core/contracts.js.map +1 -1
  17. package/dist/vault-core/core.d.ts +16 -21
  18. package/dist/vault-core/core.js +278 -159
  19. package/dist/vault-core/core.js.map +1 -1
  20. package/dist/vault-core/defaults.d.ts +8 -20
  21. package/dist/vault-core/defaults.js +14 -37
  22. package/dist/vault-core/defaults.js.map +1 -1
  23. package/dist/vault-core/index.d.ts +3 -3
  24. package/dist/vault-core/index.js +1 -1
  25. package/dist/vault-core/index.js.map +1 -1
  26. package/dist/vault-core/persistence.d.ts +8 -6
  27. package/dist/vault-core/persistence.js +17 -9
  28. package/dist/vault-core/persistence.js.map +1 -1
  29. package/dist/vault-core/ports.d.ts +8 -20
  30. package/dist/vault-ingress/defaults.d.ts +2 -2
  31. package/dist/vault-ingress/index.d.ts +14 -33
  32. package/dist/vault-ingress/index.js +18 -31
  33. package/dist/vault-ingress/index.js.map +1 -1
  34. package/dist/vault-ingress/remote-transport.d.ts +2 -2
  35. package/dist/vault-ingress/remote-transport.js.map +1 -1
  36. package/docs/MIGRATION-1.51.md +4 -1
  37. package/docs/REFERENCE.md +44 -30
  38. package/docs/api/README.md +7 -2
  39. package/docs/api/classes/IdentityError.md +1 -1
  40. package/docs/api/classes/OwnerClientError.md +1 -1
  41. package/docs/api/classes/VaultCore.md +34 -94
  42. package/docs/api/classes/VaultCoreError.md +1 -1
  43. package/docs/api/enumerations/IdentityErrorCode.md +1 -1
  44. package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
  45. package/docs/api/functions/createAgentClient.md +1 -1
  46. package/docs/api/functions/createIdentity.md +1 -1
  47. package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
  48. package/docs/api/functions/createOwnerSession.md +37 -0
  49. package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
  50. package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
  51. package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
  52. package/docs/api/functions/createVault.md +1 -1
  53. package/docs/api/functions/createVaultClient.md +1 -1
  54. package/docs/api/functions/createVaultCore.md +1 -1
  55. package/docs/api/functions/createVaultCoreDependencies.md +1 -1
  56. package/docs/api/functions/createVaultService.md +1 -1
  57. package/docs/api/functions/createWorkspaceStorage.md +1 -1
  58. package/docs/api/functions/deriveIdentityId.md +1 -1
  59. package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
  60. package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
  61. package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
  62. package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
  63. package/docs/api/functions/initializeVaultCustody.md +1 -1
  64. package/docs/api/functions/listVaults.md +1 -1
  65. package/docs/api/functions/readVaultProfile.md +1 -1
  66. package/docs/api/functions/recoverVault.md +1 -1
  67. package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
  68. package/docs/api/functions/restoreIdentity.md +1 -1
  69. package/docs/api/functions/updateVaultMetadata.md +1 -1
  70. package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
  71. package/docs/api/functions/writeVaultProfile.md +1 -1
  72. package/docs/api/interfaces/AgentClient.md +5 -5
  73. package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
  74. package/docs/api/interfaces/AgentDispatchTransport.md +5 -5
  75. package/docs/api/interfaces/AgentIdentity.md +1 -1
  76. package/docs/api/interfaces/AgentSigner.md +1 -1
  77. package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +1 -1
  78. package/docs/api/interfaces/CbioRuntime.md +39 -1
  79. package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
  80. package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
  81. package/docs/api/interfaces/CreateOwnerSessionOptions.md +245 -0
  82. package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
  83. package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
  84. package/docs/api/interfaces/CreateVaultOptions.md +1 -1
  85. package/docs/api/interfaces/CreatedVault.md +1 -1
  86. package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
  87. package/docs/api/interfaces/IStorageProvider.md +1 -1
  88. package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
  89. package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
  90. package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
  91. package/docs/api/interfaces/OwnerDefineSecretTargetsInput.md +1 -1
  92. package/docs/api/interfaces/OwnerSecretTargetBinding.md +1 -1
  93. package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
  94. package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
  95. package/docs/api/interfaces/OwnerSession.md +95 -0
  96. package/docs/api/interfaces/OwnerStoreSecretInput.md +1 -1
  97. package/docs/api/interfaces/OwnerWriteSecretInput.md +1 -1
  98. package/docs/api/interfaces/RecoverVaultOptions.md +5 -1
  99. package/docs/api/interfaces/RecoveredVault.md +1 -1
  100. package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
  101. package/docs/api/interfaces/Signer.md +1 -1
  102. package/docs/api/interfaces/VaultApproveCapabilityRequestInput.md +1 -1
  103. package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
  104. package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
  105. package/docs/api/interfaces/VaultClient.md +41 -77
  106. package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
  107. package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
  108. package/docs/api/interfaces/VaultDeleteSecretInput.md +1 -1
  109. package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
  110. package/docs/api/interfaces/VaultGrantCapabilityInput.md +25 -1
  111. package/docs/api/interfaces/VaultGrantCapabilityRequest.md +23 -0
  112. package/docs/api/interfaces/VaultIdentity.md +1 -1
  113. package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
  114. package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
  115. package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
  116. package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
  117. package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
  118. package/docs/api/interfaces/VaultMetadata.md +1 -1
  119. package/docs/api/interfaces/VaultObject.md +1 -1
  120. package/docs/api/interfaces/VaultProfile.md +1 -1
  121. package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
  122. package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
  123. package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
  124. package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
  125. package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
  126. package/docs/api/interfaces/VaultSigner.md +1 -1
  127. package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +1 -1
  128. package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
  129. package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
  130. package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
  131. package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
  132. package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +7 -0
  133. package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
  134. package/docs/es/README.md +6 -9
  135. package/docs/fr/README.md +6 -10
  136. package/docs/ja/README.md +6 -9
  137. package/docs/ko/README.md +6 -9
  138. package/docs/pt/README.md +6 -9
  139. package/docs/zh/README.md +101 -42
  140. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { AuditAction, AuditOutcome, DispatchStatus, } from "./contracts.js";
2
2
  import { VaultCoreError } from "./errors.js";
3
3
  import { verifySignature } from "../protocol/crypto.js";
4
4
  import { getAgentToolbox } from "./tool-metadata.js";
5
+ const VAULT_MASTER_ID = "vault-master";
5
6
  function toAuditEntry(deps, actor, action, outcome, detail, options) {
6
7
  return {
7
8
  entryId: deps.ids.newAuditEntryId(),
@@ -35,11 +36,37 @@ function buildSecretRecord(deps, command) {
35
36
  updatedAt: now,
36
37
  };
37
38
  }
39
+ function normalizeScopeTarget(targetUrl) {
40
+ try {
41
+ const parsed = new URL(targetUrl);
42
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
43
+ return null;
44
+ }
45
+ parsed.protocol = parsed.protocol.toLowerCase();
46
+ parsed.hostname = parsed.hostname.toLowerCase();
47
+ parsed.hash = "";
48
+ parsed.search = "";
49
+ if ((parsed.protocol === "https:" && parsed.port === "443") || (parsed.protocol === "http:" && parsed.port === "80")) {
50
+ parsed.port = "";
51
+ }
52
+ parsed.pathname = parsed.pathname || "/";
53
+ return parsed.toString();
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
38
59
  function isScopeMatch(scope, targetUrl) {
60
+ const normalizedTarget = normalizeScopeTarget(targetUrl);
61
+ if (!normalizedTarget) {
62
+ return false;
63
+ }
39
64
  if (scope.endsWith("*")) {
40
- return targetUrl.startsWith(scope.slice(0, -1));
65
+ const normalizedPrefix = normalizeScopeTarget(scope.slice(0, -1));
66
+ return normalizedPrefix ? normalizedTarget.startsWith(normalizedPrefix) : false;
41
67
  }
42
- return scope === targetUrl;
68
+ const normalizedScope = normalizeScopeTarget(scope);
69
+ return normalizedScope === normalizedTarget;
43
70
  }
44
71
  function createAgentControlBinding(requestId, requestedAt, agentId, action, payload = {}) {
45
72
  return JSON.stringify({
@@ -56,11 +83,137 @@ function createAgentControlBinding(requestId, requestedAt, agentId, action, payl
56
83
  */
57
84
  export class VaultCore {
58
85
  _deps;
59
- _pendingObservers = new Set();
60
- _pendingCapabilityObservers = new Set();
86
+ _capabilityStateObservers = new Set();
61
87
  constructor(_deps) {
62
88
  this._deps = _deps;
63
89
  }
90
+ _assertOwnerPrincipal(actor, code = "VAULT_AUDIT_DENIED") {
91
+ if (actor.kind !== "owner" || actor.id !== VAULT_MASTER_ID) {
92
+ throw new VaultCoreError("owner access denied", code);
93
+ }
94
+ }
95
+ _stateToGrantedCapability(state) {
96
+ return {
97
+ vaultId: state.vaultId,
98
+ capabilityId: state.capabilityId ?? "",
99
+ agentId: state.agentId,
100
+ secretIds: state.secretIds ? [...state.secretIds] : undefined,
101
+ secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
102
+ operation: state.operation,
103
+ customFlowId: state.customFlowId,
104
+ scope: state.scope,
105
+ methods: [...state.methods],
106
+ issuedAt: state.issuedAt ?? state.requestedAt,
107
+ expiresAt: state.expiresAt,
108
+ rateLimit: state.rateLimit,
109
+ skipAudit: state.skipAudit,
110
+ };
111
+ }
112
+ async _buildAgentCapabilityStates(agentId) {
113
+ return (await this._deps.capabilityStates.list(this._deps.vaultId, agentId)).map((state) => ({
114
+ status: state.status,
115
+ source: state.source,
116
+ agentId: state.agentId,
117
+ requestId: state.requestId,
118
+ capabilityId: state.capabilityId,
119
+ operation: state.operation,
120
+ secretIds: state.secretIds ? [...state.secretIds] : undefined,
121
+ secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
122
+ customFlowId: state.customFlowId,
123
+ scope: state.scope,
124
+ methods: [...state.methods],
125
+ issuedAt: state.issuedAt,
126
+ requestedAt: state.requestedAt,
127
+ expiresAt: state.expiresAt,
128
+ rateLimit: state.rateLimit,
129
+ skipAudit: state.skipAudit,
130
+ justification: state.justification,
131
+ secretAlias: state.secretAlias,
132
+ targetUrl: state.targetUrl,
133
+ }));
134
+ }
135
+ _isExecutablePendingState(state) {
136
+ return !!(state.requestId && state.targetUrl && state.secretAlias && state.proof);
137
+ }
138
+ async _executePendingCapabilityState(command, mode) {
139
+ if (command.vaultId.value !== this._deps.vaultId.value) {
140
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
141
+ }
142
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
143
+ if (!pending || pending.status !== "PENDING") {
144
+ throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
145
+ }
146
+ const issuedAt = this._deps.clock.nowIso();
147
+ const capability = {
148
+ vaultId: this._deps.vaultId,
149
+ agentId: pending.agentId,
150
+ capabilityId: pending.capabilityId ?? this._deps.ids.newCapabilityId(),
151
+ secretIds: pending.secretIds ? [...pending.secretIds] : undefined,
152
+ secretAliases: pending.secretAliases ? [...pending.secretAliases] : (pending.secretAlias ? [pending.secretAlias] : []),
153
+ operation: pending.operation,
154
+ customFlowId: pending.customFlowId,
155
+ scope: pending.targetUrl ?? pending.scope,
156
+ methods: [...pending.methods],
157
+ issuedAt,
158
+ expiresAt: pending.expiresAt,
159
+ rateLimit: pending.rateLimit,
160
+ skipAudit: pending.skipAudit,
161
+ };
162
+ let result;
163
+ if (this._isExecutablePendingState(pending)) {
164
+ result = await this.agentDispatchSecret({
165
+ vaultId: this._deps.vaultId,
166
+ agent: { kind: "agent", id: pending.agentId },
167
+ capability,
168
+ secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
169
+ targetUrl: pending.targetUrl,
170
+ method: pending.methods[0] ?? "POST",
171
+ headers: pending.headers,
172
+ body: pending.body,
173
+ proof: pending.proof,
174
+ requestId: pending.requestId,
175
+ requestedAt: pending.requestedAt,
176
+ });
177
+ }
178
+ else if (mode === "grant") {
179
+ result = {
180
+ vaultId: this._deps.vaultId,
181
+ requestId: pending.requestId ?? command.requestId,
182
+ status: DispatchStatus.SUCCEEDED,
183
+ targetUrl: pending.scope,
184
+ method: pending.methods[0] ?? "POST",
185
+ };
186
+ }
187
+ else {
188
+ throw new VaultCoreError("pending capability state is not executable", "VAULT_WRITE_DENIED");
189
+ }
190
+ if (mode === "grant") {
191
+ await this._deps.capabilityStates.upsert({
192
+ ...pending,
193
+ capabilityId: capability.capabilityId,
194
+ status: "GRANTED",
195
+ source: "owner_grant",
196
+ issuedAt,
197
+ decidedAt: issuedAt,
198
+ });
199
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed and granted capability state ${command.requestId}`, {
200
+ requestId: command.requestId,
201
+ agentId: pending.agentId,
202
+ capabilityId: capability.capabilityId,
203
+ operation: capability.operation,
204
+ }));
205
+ }
206
+ else {
207
+ await this._deps.capabilityStates.deleteByRequestId(command.vaultId, command.requestId);
208
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed once and deleted capability state ${command.requestId}`, {
209
+ requestId: command.requestId,
210
+ agentId: pending.agentId,
211
+ capabilityId: capability.capabilityId,
212
+ operation: capability.operation,
213
+ }));
214
+ }
215
+ return result;
216
+ }
64
217
  get vaultId() {
65
218
  return this._deps.vaultId;
66
219
  }
@@ -110,7 +263,9 @@ export class VaultCore {
110
263
  }
111
264
  }
112
265
  async _listVisibleSecretsForAgent(agentId) {
113
- const capabilities = await this._deps.capabilities.list(this._deps.vaultId, agentId);
266
+ const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
267
+ .filter((state) => state.status === "GRANTED")
268
+ .map((state) => this._stateToGrantedCapability(state));
114
269
  const capabilityMap = new Map();
115
270
  for (const capability of capabilities) {
116
271
  for (const alias of capability.secretAliases ?? []) {
@@ -139,16 +294,10 @@ export class VaultCore {
139
294
  };
140
295
  });
141
296
  }
142
- ownerOnPendingDispatch(callback) {
143
- this._pendingObservers.add(callback);
144
- return () => {
145
- this._pendingObservers.delete(callback);
146
- };
147
- }
148
- ownerOnPendingCapabilityRequest(callback) {
149
- this._pendingCapabilityObservers.add(callback);
297
+ ownerOnCapabilityState(callback) {
298
+ this._capabilityStateObservers.add(callback);
150
299
  return () => {
151
- this._pendingCapabilityObservers.delete(callback);
300
+ this._capabilityStateObservers.delete(callback);
152
301
  };
153
302
  }
154
303
  async ownerRegisterAgentIdentity(command) {
@@ -213,7 +362,13 @@ export class VaultCore {
213
362
  throw new VaultCoreError("capability id required", "VAULT_IDENTITY_DENIED");
214
363
  }
215
364
  try {
216
- await this._deps.capabilities.register(command.capability);
365
+ await this._deps.capabilityStates.upsert({
366
+ ...command.capability,
367
+ status: "GRANTED",
368
+ source: "owner_grant",
369
+ requestId: undefined,
370
+ requestedAt: command.capability.issuedAt,
371
+ });
217
372
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CAPABILITY, AuditOutcome.SUCCEEDED, `capability registered: ${command.capability.capabilityId}`, {
218
373
  capabilityId: command.capability.capabilityId,
219
374
  operation: command.capability.operation,
@@ -243,28 +398,27 @@ export class VaultCore {
243
398
  }
244
399
  const pendingRecord = {
245
400
  vaultId: this._deps.vaultId,
401
+ status: "PENDING",
402
+ source: "explicit_request",
246
403
  requestId: command.requestId,
247
- requester: command.requester,
248
404
  agentId: command.agentId,
249
- scope: {
250
- operation: command.scope.operation,
251
- secretAliases: command.scope.secretAliases ? [...command.scope.secretAliases] : [],
252
- scope: command.scope.scope,
253
- methods: [...command.scope.methods],
254
- rateLimit: command.scope.rateLimit,
255
- skipAudit: command.scope.skipAudit,
256
- expiresAt: command.scope.expiresAt,
257
- },
405
+ operation: command.scope.operation,
406
+ secretAliases: command.scope.secretAliases ? [...command.scope.secretAliases] : [],
407
+ scope: command.scope.scope,
408
+ methods: [...command.scope.methods],
409
+ rateLimit: command.scope.rateLimit,
410
+ skipAudit: command.scope.skipAudit,
411
+ expiresAt: command.scope.expiresAt,
258
412
  justification: command.justification,
259
413
  requestedAt: command.requestedAt,
260
414
  };
261
- await this._deps.pendingCapabilityRequests.save(pendingRecord);
262
- for (const observer of this._pendingCapabilityObservers) {
415
+ await this._deps.capabilityStates.upsert(pendingRecord);
416
+ for (const observer of this._capabilityStateObservers) {
263
417
  try {
264
418
  observer(pendingRecord);
265
419
  }
266
420
  catch (error) {
267
- console.error("VaultCore: error in pending capability observer:", error);
421
+ console.error("VaultCore: error in capability state observer:", error);
268
422
  }
269
423
  }
270
424
  await this._appendAudit(toAuditEntry(this._deps, command.requester, AuditAction.SUBMIT_CAPABILITY_REQUEST, AuditOutcome.PENDING, `capability request submitted for agent: ${command.agentId}`, {
@@ -278,7 +432,8 @@ export class VaultCore {
278
432
  if (vaultId.value !== this._deps.vaultId.value) {
279
433
  throw new VaultCoreError("capability lookup vault mismatch", "VAULT_IDENTITY_DENIED");
280
434
  }
281
- return this._deps.capabilities.get(vaultId, agentId, capabilityId);
435
+ const state = await this._deps.capabilityStates.getByCapabilityId(vaultId, agentId, capabilityId);
436
+ return state && state.status === "GRANTED" ? this._stateToGrantedCapability(state) : null;
282
437
  }
283
438
  async ownerRegisterCustomFlow(command) {
284
439
  if (command.vaultId.value !== this._deps.vaultId.value) {
@@ -481,8 +636,14 @@ export class VaultCore {
481
636
  if (!agentRecord) {
482
637
  return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null, executorTarget: null };
483
638
  }
484
- const capabilities = await this._deps.capabilities.list(this._deps.vaultId, request.agent.id);
485
- const capability = capabilities.find(cap => this.isCapabilityMatch(cap, request));
639
+ const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id))
640
+ .filter((state) => state.status === "GRANTED")
641
+ .map((state) => this._stateToGrantedCapability(state));
642
+ const requestedCapabilityId = request.capability?.capabilityId;
643
+ const candidateCapabilities = requestedCapabilityId
644
+ ? capabilities.filter((cap) => cap.capabilityId === requestedCapabilityId)
645
+ : capabilities;
646
+ const capability = candidateCapabilities.find((cap) => this.isCapabilityMatch(cap, request, record?.secretId.value));
486
647
  const executorTarget = record
487
648
  ? record.targetBindings.find((binding) => binding.targetUrl === request.targetUrl)
488
649
  ?? record.targetBindings.find((binding) => binding.targetId === request.targetUrl)
@@ -491,25 +652,31 @@ export class VaultCore {
491
652
  if (!capability) {
492
653
  // It's a discovery case if the agent and secret exist but no capability matches
493
654
  const pendingRecord = {
655
+ vaultId: this._deps.vaultId,
656
+ status: "PENDING",
657
+ source: "dispatch_discovery",
494
658
  requestId: request.requestId,
495
659
  agentId: request.agent.id,
496
660
  capabilityId: undefined,
661
+ operation: "dispatch_http",
662
+ secretAliases: request.secretAlias ? [request.secretAlias] : [],
663
+ scope: request.targetUrl,
664
+ methods: [request.method],
665
+ requestedAt: request.requestedAt,
497
666
  secretAlias: request.secretAlias ?? "unknown",
498
667
  targetUrl: request.targetUrl,
499
- method: request.method,
500
668
  headers: request.headers,
501
669
  body: request.body,
502
- requestedAt: request.requestedAt,
503
670
  proof: request.proof,
504
671
  };
505
- await this._deps.pendingRequests.save(pendingRecord);
672
+ await this._deps.capabilityStates.upsert(pendingRecord);
506
673
  // Notify observers
507
- for (const observer of this._pendingObservers) {
674
+ for (const observer of this._capabilityStateObservers) {
508
675
  try {
509
676
  observer(pendingRecord);
510
677
  }
511
678
  catch (error) {
512
- console.error("VaultCore: error in pending observer:", error);
679
+ console.error("VaultCore: error in capability state observer:", error);
513
680
  }
514
681
  }
515
682
  await this._appendDecisionAudit(request, AuditOutcome.PENDING, "dispatch stalled for manual discovery approval", {
@@ -524,6 +691,26 @@ export class VaultCore {
524
691
  executorTarget,
525
692
  };
526
693
  }
694
+ try {
695
+ await this._deps.policy.authorizeDispatch({
696
+ ...request,
697
+ capability,
698
+ }, record);
699
+ }
700
+ catch (error) {
701
+ const detail = error instanceof Error ? error.message : String(error);
702
+ await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
703
+ secretAlias: record?.alias.value ?? request.secretAlias,
704
+ secretId: record?.secretId.value,
705
+ });
706
+ return {
707
+ vaultId: this._deps.vaultId,
708
+ decision: "deny",
709
+ reason: detail,
710
+ secretId: record?.secretId ?? null,
711
+ executorTarget,
712
+ };
713
+ }
527
714
  // Capability found, proceed
528
715
  if (!capability.skipAudit) {
529
716
  await this._appendDecisionAudit(request, AuditOutcome.ALLOWED, "dispatch authorized", {
@@ -585,11 +772,13 @@ export class VaultCore {
585
772
  };
586
773
  }
587
774
  async ownerReadAudit(actor, query, request) {
775
+ this._assertOwnerPrincipal(actor, "VAULT_AUDIT_DENIED");
588
776
  const entries = await this._deps.audit.query(query);
589
777
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.READ_AUDIT, AuditOutcome.ALLOWED, "audit queried"));
590
778
  return entries;
591
779
  }
592
780
  async ownerExportSecret(actor, alias, request) {
781
+ this._assertOwnerPrincipal(actor, "VAULT_AUDIT_DENIED");
593
782
  try {
594
783
  const record = await this._deps.secrets.getByAlias({ value: alias });
595
784
  if (!record) {
@@ -622,10 +811,14 @@ export class VaultCore {
622
811
  throw error;
623
812
  }
624
813
  }
625
- isCapabilityMatch(capability, request) {
626
- // Basic Iron Triangle match
627
- if (request.secretAlias && !capability.secretAliases?.includes(request.secretAlias)) {
628
- return false;
814
+ isCapabilityMatch(capability, request, secretId) {
815
+ // Match either alias- or id-based capability grants when a secret is specified.
816
+ if (request.secretAlias) {
817
+ const aliasMatched = capability.secretAliases?.includes(request.secretAlias) ?? false;
818
+ const idMatched = secretId ? (capability.secretIds?.includes(secretId) ?? false) : false;
819
+ if (!aliasMatched && !idMatched) {
820
+ return false;
821
+ }
629
822
  }
630
823
  if (request.method && capability.methods?.length > 0 && !capability.methods.includes(request.method)) {
631
824
  return false;
@@ -643,7 +836,9 @@ export class VaultCore {
643
836
  return identities;
644
837
  }
645
838
  async ownerListCapabilities(actor, agentId, request) {
646
- const capabilities = await this._deps.capabilities.list(this._deps.vaultId, agentId);
839
+ const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
840
+ .filter((state) => state.status === "GRANTED")
841
+ .map((state) => this._stateToGrantedCapability(state));
647
842
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_CAPABILITIES, AuditOutcome.ALLOWED, "capabilities listed", {
648
843
  requestId: request?.requestId,
649
844
  agentId,
@@ -670,7 +865,7 @@ export class VaultCore {
670
865
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
671
866
  }
672
867
  await this._verifyAgentControlProof(request, "list_capabilities");
673
- return this._deps.capabilities.list(this._deps.vaultId, request.agent.id);
868
+ return this._buildAgentCapabilityStates(request.agent.id);
674
869
  }
675
870
  async agentListSecrets(request) {
676
871
  if (request.vaultId.value !== this._deps.vaultId.value) {
@@ -683,13 +878,25 @@ export class VaultCore {
683
878
  if (command.vaultId.value !== this._deps.vaultId.value) {
684
879
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
685
880
  }
686
- const capabilities = await this._deps.capabilities.list(this._deps.vaultId, command.agent.id);
881
+ await this._verifyAgentControlProof(command, "get_manifest");
882
+ const agentRecord = await this._deps.agentIdentities.get(this._deps.vaultId, command.agent.id);
883
+ if (!agentRecord) {
884
+ throw new VaultCoreError("agent identity not registered", "VAULT_DISPATCH_DENIED");
885
+ }
886
+ const capabilities = await this._buildAgentCapabilityStates(command.agent.id);
687
887
  const vaultNickname = "CBIO Vault"; // TODO: Pull from profile if available
688
888
  return {
689
889
  agentId: command.agent.id,
690
890
  vaultId: this._deps.vaultId.value,
691
891
  vaultNickname,
692
892
  issuedAt: this._deps.clock.nowIso(),
893
+ agent: {
894
+ agentId: agentRecord.agentId,
895
+ identityId: agentRecord.identityId,
896
+ publicKey: agentRecord.publicKey,
897
+ nickname: agentRecord.nickname,
898
+ metadata: agentRecord.metadata,
899
+ },
693
900
  capabilities,
694
901
  tools: getAgentToolbox(),
695
902
  };
@@ -716,7 +923,15 @@ export class VaultCore {
716
923
  });
717
924
  }
718
925
  async ownerRevokeCapability(command) {
719
- await this._deps.policy.revokeCapability(command.vaultId, command.agentId, command.capabilityId);
926
+ const existing = await this._deps.capabilityStates.getByCapabilityId(command.vaultId, command.agentId, command.capabilityId);
927
+ if (!existing) {
928
+ throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
929
+ }
930
+ await this._deps.capabilityStates.upsert({
931
+ ...existing,
932
+ status: "REJECTED",
933
+ decidedAt: this._deps.clock.nowIso(),
934
+ });
720
935
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REVOKE_CAPABILITY, AuditOutcome.SUCCEEDED, "capability revoked", {
721
936
  requestId: command.requestId,
722
937
  agentId: command.agentId,
@@ -762,135 +977,39 @@ export class VaultCore {
762
977
  await this._deps.sessionTokens.revoke(request.token);
763
978
  await this._appendAudit(toAuditEntry(this._deps, request.actor, AuditAction.REVOKE_SESSION_TOKEN, AuditOutcome.SUCCEEDED, "session token revoked"));
764
979
  }
765
- async ownerListPendingDispatches(command) {
980
+ async ownerListCapabilityStates(command) {
766
981
  if (command.vaultId.value !== this._deps.vaultId.value) {
767
982
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
768
983
  }
769
- return this._deps.pendingRequests.list(command.vaultId);
984
+ return (await this._deps.capabilityStates.list(command.vaultId, command.agentId))
985
+ .filter((state) => !command.status || state.status === command.status);
770
986
  }
771
- async ownerListPendingCapabilityRequests(command) {
772
- if (command.vaultId.value !== this._deps.vaultId.value) {
773
- throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
774
- }
775
- return this._deps.pendingCapabilityRequests.list(command.vaultId);
987
+ async ownerExecuteCapabilityStateOnce(command) {
988
+ return this._executePendingCapabilityState(command, "once");
776
989
  }
777
- async ownerApproveCapabilityRequest(command) {
778
- if (command.vaultId.value !== this._deps.vaultId.value) {
779
- throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
780
- }
781
- const pending = await this._deps.pendingCapabilityRequests.get(command.requestId);
782
- if (!pending) {
783
- throw new VaultCoreError("pending capability request not found", "VAULT_REQUEST_NOT_FOUND");
784
- }
785
- const capability = {
786
- vaultId: this._deps.vaultId,
787
- agentId: pending.agentId,
788
- capabilityId: command.capabilityId ?? this._deps.ids.newCapabilityId(),
789
- operation: pending.scope.operation,
790
- secretAliases: pending.scope.secretAliases ? [...pending.scope.secretAliases] : [],
791
- scope: pending.scope.scope,
792
- methods: [...pending.scope.methods],
793
- rateLimit: pending.scope.rateLimit,
794
- skipAudit: pending.scope.skipAudit,
795
- expiresAt: pending.scope.expiresAt,
796
- issuedAt: this._deps.clock.nowIso(),
797
- };
798
- await this._deps.capabilities.register(capability);
799
- await this._deps.pendingCapabilityRequests.delete(command.requestId);
800
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `approved capability request ${command.requestId}`, {
801
- requestId: command.requestId,
802
- agentId: pending.agentId,
803
- capabilityId: capability.capabilityId,
804
- operation: capability.operation,
805
- }));
806
- return capability;
990
+ async ownerExecuteCapabilityStateAndGrant(command) {
991
+ return this._executePendingCapabilityState(command, "grant");
807
992
  }
808
- async ownerRejectCapabilityRequest(command) {
993
+ async ownerRejectCapabilityState(command) {
809
994
  if (command.vaultId.value !== this._deps.vaultId.value) {
810
995
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
811
996
  }
812
- const pending = await this._deps.pendingCapabilityRequests.get(command.requestId);
813
- if (!pending) {
814
- throw new VaultCoreError("pending capability request not found", "VAULT_REQUEST_NOT_FOUND");
997
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
998
+ if (!pending || pending.status !== "PENDING") {
999
+ throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
815
1000
  }
816
- await this._deps.pendingCapabilityRequests.delete(command.requestId);
1001
+ const rejectedState = {
1002
+ ...pending,
1003
+ status: "REJECTED",
1004
+ decidedAt: this._deps.clock.nowIso(),
1005
+ };
1006
+ await this._deps.capabilityStates.upsert(rejectedState);
817
1007
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
818
1008
  requestId: command.requestId,
819
1009
  agentId: pending.agentId,
820
- operation: pending.scope.operation,
821
- }));
822
- }
823
- async ownerApproveDispatch(command) {
824
- if (command.vaultId.value !== this._deps.vaultId.value) {
825
- throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
826
- }
827
- const pending = await this._deps.pendingRequests.get(command.requestId);
828
- if (!pending) {
829
- throw new VaultCoreError("pending request not found", "VAULT_REQUEST_NOT_FOUND");
830
- }
831
- const agentRecord = await this._deps.agentIdentities.get(this._deps.vaultId, pending.agentId);
832
- if (!agentRecord) {
833
- throw new VaultCoreError("agent identity not found", "VAULT_AGENT_NOT_FOUND");
834
- }
835
- let capability;
836
- if (pending.capabilityId) {
837
- const existing = await this._deps.capabilities.get(this._deps.vaultId, pending.agentId, pending.capabilityId);
838
- if (!existing) {
839
- throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
840
- }
841
- capability = existing;
842
- }
843
- else {
844
- // Discovery case: derive from request
845
- const capabilityId = this._deps.ids.newCapabilityId();
846
- capability = {
847
- vaultId: this._deps.vaultId,
848
- agentId: pending.agentId,
849
- capabilityId,
850
- secretAliases: [pending.secretAlias],
851
- methods: [pending.method],
852
- scope: pending.targetUrl,
853
- operation: "dispatch_http",
854
- issuedAt: this._deps.clock.nowIso(),
855
- skipAudit: command.skipAudit ?? false,
856
- };
857
- if (command.permanent) {
858
- await this._deps.capabilities.register(capability);
859
- }
860
- }
861
- const result = await this.agentDispatchSecret({
862
- vaultId: this._deps.vaultId,
863
- agent: { kind: "agent", id: pending.agentId },
864
- capability: capability,
865
- secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
866
- targetUrl: pending.targetUrl,
867
- method: pending.method,
868
- headers: pending.headers,
869
- body: pending.body,
870
- proof: pending.proof,
871
- requestId: pending.requestId,
872
- requestedAt: pending.requestedAt,
873
- });
874
- await this._deps.pendingRequests.delete(command.requestId);
875
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_DISPATCH, AuditOutcome.SUCCEEDED, `approved dispatch ${command.requestId}${command.permanent ? " and granted permanent capability" : ""}`, {
876
- requestId: command.requestId,
877
- agentId: pending.agentId,
878
- capabilityId: capability.capabilityId,
879
- }));
880
- return result;
881
- }
882
- async ownerRejectDispatch(command) {
883
- if (command.vaultId.value !== this._deps.vaultId.value) {
884
- throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
885
- }
886
- const pending = await this._deps.pendingRequests.get(command.requestId);
887
- if (!pending) {
888
- throw new VaultCoreError("pending request not found", "VAULT_REQUEST_NOT_FOUND");
889
- }
890
- await this._deps.pendingRequests.delete(command.requestId);
891
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_DISPATCH, AuditOutcome.SUCCEEDED, `rejected dispatch ${command.requestId}`, {
892
- requestId: command.requestId,
1010
+ operation: pending.operation,
893
1011
  }));
1012
+ return rejectedState;
894
1013
  }
895
1014
  }
896
1015
  export function createVaultCore(deps) {