@the-ai-company/cbio-node-runtime 1.57.0 → 1.59.1

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 -30
  2. package/dist/clients/agent/client.d.ts +3 -1
  3. package/dist/clients/agent/client.js +41 -11
  4. package/dist/clients/agent/client.js.map +1 -1
  5. package/dist/clients/agent/contracts.d.ts +5 -2
  6. package/dist/clients/owner/client.d.ts +8 -10
  7. package/dist/clients/owner/client.js +45 -38
  8. package/dist/clients/owner/client.js.map +1 -1
  9. package/dist/clients/owner/contracts.d.ts +11 -25
  10. package/dist/clients/owner/index.d.ts +1 -1
  11. package/dist/runtime/index.d.ts +1 -1
  12. package/dist/runtime/index.js.map +1 -1
  13. package/dist/vault-core/contracts.d.ts +133 -46
  14. package/dist/vault-core/contracts.js +4 -3
  15. package/dist/vault-core/contracts.js.map +1 -1
  16. package/dist/vault-core/core.d.ts +10 -5
  17. package/dist/vault-core/core.js +315 -160
  18. package/dist/vault-core/core.js.map +1 -1
  19. package/dist/vault-core/defaults.d.ts +8 -4
  20. package/dist/vault-core/defaults.js +37 -70
  21. package/dist/vault-core/defaults.js.map +1 -1
  22. package/dist/vault-core/index.d.ts +1 -1
  23. package/dist/vault-core/index.js.map +1 -1
  24. package/dist/vault-core/persistence.d.ts +11 -2
  25. package/dist/vault-core/persistence.js +37 -1
  26. package/dist/vault-core/persistence.js.map +1 -1
  27. package/dist/vault-core/ports.d.ts +7 -2
  28. package/dist/vault-core/tool-metadata.js +25 -8
  29. package/dist/vault-core/tool-metadata.js.map +1 -1
  30. package/dist/vault-ingress/defaults.d.ts +2 -0
  31. package/dist/vault-ingress/defaults.js +6 -0
  32. package/dist/vault-ingress/defaults.js.map +1 -1
  33. package/dist/vault-ingress/index.d.ts +39 -10
  34. package/dist/vault-ingress/index.js +142 -56
  35. package/dist/vault-ingress/index.js.map +1 -1
  36. package/dist/vault-ingress/remote-transport.d.ts +2 -0
  37. package/dist/vault-ingress/remote-transport.js +33 -4
  38. package/dist/vault-ingress/remote-transport.js.map +1 -1
  39. package/docs/ARCHITECTURE.md +1 -1
  40. package/docs/REFERENCE.md +36 -27
  41. package/docs/WORKS_WITH_CUSTOM_FETCH.md +2 -2
  42. package/docs/api/README.md +2 -4
  43. package/docs/api/classes/IdentityError.md +1 -1
  44. package/docs/api/classes/OwnerClientError.md +1 -1
  45. package/docs/api/classes/VaultCore.md +81 -33
  46. package/docs/api/classes/VaultCoreError.md +1 -1
  47. package/docs/api/enumerations/IdentityErrorCode.md +1 -1
  48. package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
  49. package/docs/api/functions/createAgentClient.md +1 -1
  50. package/docs/api/functions/createIdentity.md +1 -1
  51. package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
  52. package/docs/api/functions/createOwnerSession.md +1 -1
  53. package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
  54. package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
  55. package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
  56. package/docs/api/functions/createVault.md +1 -1
  57. package/docs/api/functions/createVaultClient.md +1 -1
  58. package/docs/api/functions/createVaultCore.md +1 -1
  59. package/docs/api/functions/createVaultCoreDependencies.md +1 -1
  60. package/docs/api/functions/createVaultService.md +1 -1
  61. package/docs/api/functions/createWorkspaceStorage.md +1 -1
  62. package/docs/api/functions/deriveIdentityId.md +1 -1
  63. package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
  64. package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
  65. package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
  66. package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
  67. package/docs/api/functions/initializeVaultCustody.md +1 -1
  68. package/docs/api/functions/listVaults.md +1 -1
  69. package/docs/api/functions/readVaultProfile.md +1 -1
  70. package/docs/api/functions/recoverVault.md +1 -1
  71. package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
  72. package/docs/api/functions/restoreIdentity.md +1 -1
  73. package/docs/api/functions/updateVaultMetadata.md +1 -1
  74. package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
  75. package/docs/api/functions/writeVaultProfile.md +1 -1
  76. package/docs/api/interfaces/AgentClient.md +27 -1
  77. package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
  78. package/docs/api/interfaces/AgentDispatchTransport.md +33 -1
  79. package/docs/api/interfaces/AgentIdentity.md +1 -1
  80. package/docs/api/interfaces/AgentSigner.md +1 -1
  81. package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +9 -9
  82. package/docs/api/interfaces/CbioRuntime.md +1 -1
  83. package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
  84. package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
  85. package/docs/api/interfaces/CreateOwnerSessionOptions.md +1 -1
  86. package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
  87. package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
  88. package/docs/api/interfaces/CreateVaultOptions.md +1 -1
  89. package/docs/api/interfaces/CreatedVault.md +1 -1
  90. package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
  91. package/docs/api/interfaces/IStorageProvider.md +1 -1
  92. package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
  93. package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
  94. package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
  95. package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
  96. package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
  97. package/docs/api/interfaces/OwnerSession.md +1 -1
  98. package/docs/api/interfaces/OwnerStoreSecretInput.md +1 -1
  99. package/docs/api/interfaces/OwnerWriteSecretInput.md +1 -7
  100. package/docs/api/interfaces/RecoverVaultOptions.md +1 -1
  101. package/docs/api/interfaces/RecoveredVault.md +1 -1
  102. package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
  103. package/docs/api/interfaces/Signer.md +1 -1
  104. package/docs/api/interfaces/VaultApproveCapabilityRequestInput.md +1 -1
  105. package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
  106. package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
  107. package/docs/api/interfaces/VaultClient.md +58 -44
  108. package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
  109. package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
  110. package/docs/api/interfaces/VaultDeleteSecretInput.md +1 -1
  111. package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
  112. package/docs/api/interfaces/VaultGrantCapabilityInput.md +9 -21
  113. package/docs/api/interfaces/VaultGrantCapabilityRequest.md +1 -1
  114. package/docs/api/interfaces/VaultIdentity.md +1 -1
  115. package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
  116. package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
  117. package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
  118. package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
  119. package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
  120. package/docs/api/interfaces/VaultMetadata.md +1 -1
  121. package/docs/api/interfaces/VaultObject.md +1 -1
  122. package/docs/api/interfaces/VaultProfile.md +1 -1
  123. package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
  124. package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
  125. package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
  126. package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
  127. package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
  128. package/docs/api/interfaces/VaultSigner.md +1 -1
  129. package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +11 -17
  130. package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
  131. package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
  132. package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
  133. package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
  134. package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +1 -1
  135. package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
  136. package/docs/zh/README.md +26 -16
  137. package/examples/process-isolation.ts +7 -5
  138. package/package.json +1 -1
  139. package/docs/api/interfaces/OwnerDefineSecretTargetsInput.md +0 -23
  140. package/docs/api/interfaces/OwnerSecretTargetBinding.md +0 -35
@@ -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
+ import { InMemoryRequestRecordRegistry } from "./defaults.js";
5
6
  const VAULT_MASTER_ID = "vault-master";
6
7
  function toAuditEntry(deps, actor, action, outcome, detail, options) {
7
8
  return {
@@ -23,15 +24,16 @@ function toAuditEntry(deps, actor, action, outcome, detail, options) {
23
24
  }
24
25
  function buildSecretRecord(deps, command) {
25
26
  const now = deps.clock.nowIso();
27
+ const source = command.source?.kind === "request" && command.source.requestId
28
+ ? { kind: "request", requestId: command.source.requestId }
29
+ : { kind: "manual" };
26
30
  return {
27
31
  vaultId: deps.vaultId,
28
32
  secretId: deps.ids.newSecretId(),
29
33
  alias: { value: command.alias },
30
34
  version: deps.ids.newVersion(),
31
35
  issuerId: command.kind === "issuer.write_secret" ? command.issuerSiteId : null,
32
- targetBindings: command.kind === "issuer.write_secret"
33
- ? [...(command.targetBindings ?? [{ kind: "site", targetId: command.issuerSiteId }])]
34
- : [...(command.targetBindings ?? [])],
36
+ source,
35
37
  createdAt: now,
36
38
  updatedAt: now,
37
39
  };
@@ -97,12 +99,21 @@ export class VaultCore {
97
99
  vaultId: state.vaultId,
98
100
  capabilityId: state.capabilityId ?? "",
99
101
  agentId: state.agentId,
100
- secretIds: state.secretIds ? [...state.secretIds] : undefined,
101
- secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
102
102
  operation: state.operation,
103
103
  customFlowId: state.customFlowId,
104
- scope: state.scope,
105
- methods: [...state.methods],
104
+ write: {
105
+ secretIds: state.write.secretIds ? [...state.write.secretIds] : undefined,
106
+ scope: state.write.scope,
107
+ methods: [...state.write.methods],
108
+ },
109
+ read: state.actions.read.status === "APPROVED"
110
+ ? {
111
+ mode: state.read.mode,
112
+ paths: state.read.paths ? [...state.read.paths] : undefined,
113
+ }
114
+ : {
115
+ mode: "none",
116
+ },
106
117
  issuedAt: state.issuedAt ?? state.requestedAt,
107
118
  expiresAt: state.expiresAt,
108
119
  rateLimit: state.rateLimit,
@@ -111,49 +122,68 @@ export class VaultCore {
111
122
  }
112
123
  async _buildAgentCapabilityStates(agentId) {
113
124
  return (await this._deps.capabilityStates.list(this._deps.vaultId, agentId)).map((state) => ({
114
- status: state.status,
115
125
  source: state.source,
116
126
  agentId: state.agentId,
117
127
  requestId: state.requestId,
118
128
  capabilityId: state.capabilityId,
119
129
  operation: state.operation,
120
- secretIds: state.secretIds ? [...state.secretIds] : undefined,
121
- secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
122
130
  customFlowId: state.customFlowId,
123
- scope: state.scope,
124
- methods: [...state.methods],
131
+ write: {
132
+ secretIds: state.write.secretIds ? [...state.write.secretIds] : undefined,
133
+ scope: state.write.scope,
134
+ methods: [...state.write.methods],
135
+ },
136
+ read: {
137
+ mode: state.read.mode,
138
+ paths: state.read.paths ? [...state.read.paths] : undefined,
139
+ },
125
140
  issuedAt: state.issuedAt,
126
141
  requestedAt: state.requestedAt,
127
142
  expiresAt: state.expiresAt,
128
143
  rateLimit: state.rateLimit,
129
144
  skipAudit: state.skipAudit,
130
145
  justification: state.justification,
131
- secretAlias: state.secretAlias,
146
+ secretId: state.secretId,
132
147
  targetUrl: state.targetUrl,
148
+ actions: {
149
+ write: { ...state.actions.write },
150
+ read: { ...state.actions.read },
151
+ },
133
152
  }));
134
153
  }
135
154
  _isExecutablePendingState(state) {
136
- return !!(state.requestId && state.targetUrl && state.secretAlias && state.proof);
155
+ return !!(state.requestId && state.targetUrl && state.proof);
137
156
  }
138
157
  async _executePendingCapabilityState(command, mode) {
139
158
  if (command.vaultId.value !== this._deps.vaultId.value) {
140
159
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
141
160
  }
142
161
  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");
162
+ if (!pending) {
163
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
164
+ }
165
+ if (pending.actions.write.status !== "APPROVED") {
166
+ throw new VaultCoreError("write approval required before execution", "VAULT_WRITE_DENIED");
167
+ }
168
+ if (mode === "once" && pending.source !== "dispatch_discovery") {
169
+ throw new VaultCoreError("one-time execution is only available for dispatch discovery requests", "VAULT_WRITE_DENIED");
145
170
  }
146
171
  const issuedAt = this._deps.clock.nowIso();
147
172
  const capability = {
148
173
  vaultId: this._deps.vaultId,
149
174
  agentId: pending.agentId,
150
175
  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
176
  operation: pending.operation,
154
177
  customFlowId: pending.customFlowId,
155
- scope: pending.targetUrl ?? pending.scope,
156
- methods: [...pending.methods],
178
+ write: {
179
+ secretIds: pending.write.secretIds ? [...pending.write.secretIds] : undefined,
180
+ scope: pending.targetUrl ?? pending.write.scope,
181
+ methods: [...pending.write.methods],
182
+ },
183
+ read: {
184
+ mode: pending.read.mode,
185
+ paths: pending.read.paths ? [...pending.read.paths] : undefined,
186
+ },
157
187
  issuedAt,
158
188
  expiresAt: pending.expiresAt,
159
189
  rateLimit: pending.rateLimit,
@@ -165,9 +195,9 @@ export class VaultCore {
165
195
  vaultId: this._deps.vaultId,
166
196
  agent: { kind: "agent", id: pending.agentId },
167
197
  capability,
168
- secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
198
+ secretId: pending.secretId,
169
199
  targetUrl: pending.targetUrl,
170
- method: pending.methods[0] ?? "POST",
200
+ method: pending.write.methods[0] ?? "POST",
171
201
  headers: pending.headers,
172
202
  body: pending.body,
173
203
  proof: pending.proof,
@@ -180,8 +210,8 @@ export class VaultCore {
180
210
  vaultId: this._deps.vaultId,
181
211
  requestId: pending.requestId ?? command.requestId,
182
212
  status: DispatchStatus.SUCCEEDED,
183
- targetUrl: pending.scope,
184
- method: pending.methods[0] ?? "POST",
213
+ targetUrl: pending.write.scope,
214
+ method: pending.write.methods[0] ?? "POST",
185
215
  };
186
216
  }
187
217
  else {
@@ -191,26 +221,13 @@ export class VaultCore {
191
221
  await this._deps.capabilityStates.upsert({
192
222
  ...pending,
193
223
  capabilityId: capability.capabilityId,
194
- status: "GRANTED",
195
224
  source: "owner_grant",
196
225
  issuedAt,
197
226
  decidedAt: issuedAt,
198
227
  });
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
228
  }
206
229
  else {
207
230
  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
231
  }
215
232
  return result;
216
233
  }
@@ -232,8 +249,8 @@ export class VaultCore {
232
249
  capabilityId: request.capability?.capabilityId,
233
250
  operation: request.capability?.operation ?? AuditAction.AUTHORIZE_DISPATCH,
234
251
  targetUrl: request.targetUrl,
235
- secretAlias: options?.secretAlias ?? request.secretAlias,
236
- secretId: options?.secretId,
252
+ secretAlias: options?.secretAlias,
253
+ secretId: options?.secretId ?? request.secretId,
237
254
  }));
238
255
  }
239
256
  async _verifyAgentControlProof(request, action, payload = {}) {
@@ -264,29 +281,35 @@ export class VaultCore {
264
281
  }
265
282
  async _listVisibleSecretsForAgent(agentId) {
266
283
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
267
- .filter((state) => state.status === "GRANTED")
284
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
268
285
  .map((state) => this._stateToGrantedCapability(state));
269
286
  const capabilityMap = new Map();
270
287
  for (const capability of capabilities) {
271
- for (const alias of capability.secretAliases ?? []) {
272
- const existing = capabilityMap.get(alias) ?? [];
288
+ for (const secretId of capability.write.secretIds ?? []) {
289
+ const existing = capabilityMap.get(secretId) ?? [];
273
290
  existing.push({
274
291
  capabilityId: capability.capabilityId,
275
- scope: capability.scope,
276
- methods: [...capability.methods],
292
+ write: {
293
+ secretIds: capability.write.secretIds ? [...capability.write.secretIds] : undefined,
294
+ scope: capability.write.scope,
295
+ methods: [...capability.write.methods],
296
+ },
297
+ read: {
298
+ mode: capability.read.mode,
299
+ paths: capability.read.paths ? [...capability.read.paths] : undefined,
300
+ },
277
301
  });
278
- capabilityMap.set(alias, existing);
302
+ capabilityMap.set(secretId, existing);
279
303
  }
280
304
  }
281
305
  const records = await this._deps.secrets.list(this._deps.vaultId);
282
306
  return records.map((record) => {
283
- const authorizedCapabilities = capabilityMap.get(record.alias.value) ?? [];
307
+ const authorizedCapabilities = capabilityMap.get(record.secretId.value) ?? [];
284
308
  return {
285
309
  vaultId: record.vaultId,
286
- secretId: record.secretId,
287
310
  alias: record.alias,
288
311
  issuerId: record.issuerId,
289
- targetBindings: [...record.targetBindings],
312
+ source: record.source,
290
313
  createdAt: record.createdAt,
291
314
  updatedAt: record.updatedAt,
292
315
  isAuthorizedForAgent: authorizedCapabilities.length > 0,
@@ -294,6 +317,48 @@ export class VaultCore {
294
317
  };
295
318
  });
296
319
  }
320
+ async _recordRequestExecution(request, capability, result) {
321
+ await this._deps.requests.save({
322
+ vaultId: this._deps.vaultId,
323
+ requestId: request.requestId,
324
+ agentId: request.agent.id,
325
+ capabilityId: capability?.capabilityId,
326
+ operation: capability?.operation ?? "dispatch_http",
327
+ createdAt: this._deps.clock.nowIso(),
328
+ request: {
329
+ targetUrl: request.targetUrl,
330
+ method: request.method,
331
+ headers: request.headers,
332
+ body: request.body,
333
+ secretId: request.secretId,
334
+ },
335
+ response: {
336
+ status: result.responseStatus,
337
+ body: result.responseBody,
338
+ error: result.error,
339
+ },
340
+ execution: {
341
+ status: result.status,
342
+ },
343
+ });
344
+ }
345
+ toVisibleRequestRecord(record, state) {
346
+ const readStatus = state?.actions.read.status ?? "PENDING";
347
+ return {
348
+ requestId: record.requestId,
349
+ createdAt: record.createdAt,
350
+ capabilityId: record.capabilityId,
351
+ operation: record.operation,
352
+ targetUrl: record.request.targetUrl,
353
+ method: record.request.method,
354
+ executionStatus: record.execution.status,
355
+ responseStatus: record.response?.status,
356
+ error: record.response?.error,
357
+ readStatus,
358
+ hasResponseBody: typeof record.response?.body === "string" && record.response.body.length > 0,
359
+ resultVisible: readStatus === "APPROVED",
360
+ };
361
+ }
297
362
  ownerOnCapabilityState(callback) {
298
363
  this._capabilityStateObservers.add(callback);
299
364
  return () => {
@@ -364,10 +429,13 @@ export class VaultCore {
364
429
  try {
365
430
  await this._deps.capabilityStates.upsert({
366
431
  ...command.capability,
367
- status: "GRANTED",
368
432
  source: "owner_grant",
369
433
  requestId: undefined,
370
434
  requestedAt: command.capability.issuedAt,
435
+ actions: {
436
+ write: { action: "write", status: "APPROVED", decidedAt: command.capability.issuedAt },
437
+ read: { action: "read", status: "APPROVED", decidedAt: command.capability.issuedAt },
438
+ },
371
439
  });
372
440
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CAPABILITY, AuditOutcome.SUCCEEDED, `capability registered: ${command.capability.capabilityId}`, {
373
441
  capabilityId: command.capability.capabilityId,
@@ -390,27 +458,36 @@ export class VaultCore {
390
458
  if (!command.agentId.trim()) {
391
459
  throw new VaultCoreError("capability request agent id required", "VAULT_IDENTITY_DENIED");
392
460
  }
393
- if (!command.scope.scope.trim()) {
461
+ if (!command.capability.write.scope.trim()) {
394
462
  throw new VaultCoreError("capability request scope required", "VAULT_IDENTITY_DENIED");
395
463
  }
396
- if (command.scope.methods.length === 0) {
464
+ if (command.capability.write.methods.length === 0) {
397
465
  throw new VaultCoreError("capability request method required", "VAULT_IDENTITY_DENIED");
398
466
  }
399
467
  const pendingRecord = {
400
468
  vaultId: this._deps.vaultId,
401
- status: "PENDING",
402
469
  source: "explicit_request",
403
470
  requestId: command.requestId,
404
471
  agentId: command.agentId,
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,
472
+ operation: command.capability.operation,
473
+ write: {
474
+ secretIds: command.capability.write.secretIds ? [...command.capability.write.secretIds] : undefined,
475
+ scope: command.capability.write.scope,
476
+ methods: [...command.capability.write.methods],
477
+ },
478
+ read: {
479
+ mode: command.capability.read.mode,
480
+ paths: command.capability.read.paths ? [...command.capability.read.paths] : undefined,
481
+ },
482
+ rateLimit: command.capability.rateLimit,
483
+ skipAudit: command.capability.skipAudit,
484
+ expiresAt: command.capability.expiresAt,
412
485
  justification: command.justification,
413
486
  requestedAt: command.requestedAt,
487
+ actions: {
488
+ write: { action: "write", status: "PENDING" },
489
+ read: { action: "read", status: "PENDING" },
490
+ },
414
491
  };
415
492
  await this._deps.capabilityStates.upsert(pendingRecord);
416
493
  for (const observer of this._capabilityStateObservers) {
@@ -424,7 +501,7 @@ export class VaultCore {
424
501
  await this._appendAudit(toAuditEntry(this._deps, command.requester, AuditAction.SUBMIT_CAPABILITY_REQUEST, AuditOutcome.PENDING, `capability request submitted for agent: ${command.agentId}`, {
425
502
  requestId: command.requestId,
426
503
  agentId: command.agentId,
427
- operation: command.scope.operation,
504
+ operation: command.capability.operation,
428
505
  }));
429
506
  return pendingRecord;
430
507
  }
@@ -433,17 +510,19 @@ export class VaultCore {
433
510
  throw new VaultCoreError("capability lookup vault mismatch", "VAULT_IDENTITY_DENIED");
434
511
  }
435
512
  const state = await this._deps.capabilityStates.getByCapabilityId(vaultId, agentId, capabilityId);
436
- return state && state.status === "GRANTED" ? this._stateToGrantedCapability(state) : null;
513
+ return state && state.capabilityId && state.issuedAt && state.actions.write.status === "APPROVED"
514
+ ? this._stateToGrantedCapability(state)
515
+ : null;
437
516
  }
438
517
  async ownerRegisterCustomFlow(command) {
439
518
  if (command.vaultId.value !== this._deps.vaultId.value) {
440
- throw new VaultCoreError("custom flow vault mismatch", "VAULT_IDENTITY_DENIED");
519
+ throw new VaultCoreError("request template vault mismatch", "VAULT_IDENTITY_DENIED");
441
520
  }
442
521
  if (!command.flow.flowId.trim()) {
443
- throw new VaultCoreError("custom flow id required", "VAULT_IDENTITY_DENIED");
522
+ throw new VaultCoreError("request template id required", "VAULT_IDENTITY_DENIED");
444
523
  }
445
524
  if (command.flow.mode !== "send_secret" && !command.flow.responseSecret) {
446
- throw new VaultCoreError("custom flow response secret rule required", "VAULT_IDENTITY_DENIED");
525
+ throw new VaultCoreError("request template response secret rule required", "VAULT_IDENTITY_DENIED");
447
526
  }
448
527
  try {
449
528
  await this._deps.customFlows.register({
@@ -457,7 +536,7 @@ export class VaultCore {
457
536
  responseSecret: command.flow.responseSecret,
458
537
  createdAt: this._deps.clock.nowIso(),
459
538
  });
460
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CUSTOM_FLOW, AuditOutcome.SUCCEEDED, `custom http flow registered: ${command.flow.flowId}`));
539
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CUSTOM_FLOW, AuditOutcome.SUCCEEDED, `request template registered: ${command.flow.flowId}`));
461
540
  }
462
541
  catch (error) {
463
542
  const detail = error instanceof Error ? error.message : String(error);
@@ -467,13 +546,7 @@ export class VaultCore {
467
546
  }
468
547
  async _storeCustomFlowSecret(flow, alias, plaintext) {
469
548
  const actor = { kind: "owner", id: flow.ownerId };
470
- const targetBindings = [{
471
- kind: "site",
472
- targetId: flow.flowId,
473
- targetUrl: flow.targetUrl,
474
- methods: [flow.method],
475
- paths: [new URL(flow.targetUrl).pathname || "/"],
476
- }];
549
+ const requestId = this._deps.ids.newRequestId("custom_flow_store");
477
550
  const existing = await this._deps.secrets.getByAlias({ value: alias });
478
551
  if (existing) {
479
552
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.REASSIGN_ALIAS, AuditOutcome.DENIED, "alias already bound to existing secret; explicit alias lifecycle required", {
@@ -485,17 +558,20 @@ export class VaultCore {
485
558
  const record = buildSecretRecord(this._deps, {
486
559
  kind: "owner.write_secret",
487
560
  vaultId: this._deps.vaultId,
488
- requestId: this._deps.ids.newRequestId("custom_flow_store"),
561
+ requestId,
489
562
  owner: actor,
490
563
  alias,
491
564
  plaintext,
492
- targetBindings,
565
+ source: {
566
+ kind: "request",
567
+ requestId,
568
+ },
493
569
  requestedAt: this._deps.clock.nowIso(),
494
570
  });
495
571
  try {
496
572
  await this._deps.custody.store(record.secretId, plaintext);
497
573
  await this._deps.secrets.save(record);
498
- await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, `custom flow stored secret: ${alias}`, {
574
+ await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, `request template stored secret: ${alias}`, {
499
575
  secretAlias: record.alias.value,
500
576
  secretId: record.secretId.value,
501
577
  }));
@@ -566,56 +642,20 @@ export class VaultCore {
566
642
  secretId: record.secretId.value,
567
643
  }));
568
644
  }
569
- async ownerDefineSecretTargets(command) {
570
- if (command.vaultId.value !== this._deps.vaultId.value) {
571
- throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
572
- }
573
- try {
574
- await this._deps.policy.authorizeDefineSecretTargets(command);
575
- }
576
- catch (error) {
577
- const detail = error instanceof Error ? error.message : String(error);
578
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.DEFINE_SECRET_TARGETS, AuditOutcome.DENIED, detail, {
579
- secretAlias: command.alias,
580
- }));
581
- throw error;
582
- }
583
- const existing = await this._deps.secrets.getByAlias({ value: command.alias });
584
- if (!existing) {
585
- const error = new VaultCoreError("secret not found", "VAULT_SECRET_NOT_FOUND");
586
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.DEFINE_SECRET_TARGETS, AuditOutcome.DENIED, error.message, {
587
- secretAlias: command.alias,
588
- }));
589
- throw error;
590
- }
591
- const nextRecord = {
592
- ...existing,
593
- targetBindings: [...command.targetBindings],
594
- updatedAt: this._deps.clock.nowIso(),
595
- };
596
- await this._deps.secrets.save(nextRecord);
597
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.DEFINE_SECRET_TARGETS, AuditOutcome.SUCCEEDED, "secret targets defined", {
598
- requestId: command.requestId,
599
- secretAlias: nextRecord.alias.value,
600
- secretId: nextRecord.secretId.value,
601
- }));
602
- return nextRecord;
603
- }
604
645
  async agentAuthorizeDispatch(request) {
605
646
  if (request.vaultId.value !== this._deps.vaultId.value) {
606
647
  throw new VaultCoreError("request vault mismatch", "VAULT_DISPATCH_DENIED");
607
648
  }
608
- const record = request.secretAlias
609
- ? await this._deps.secrets.getByAlias({ value: request.secretAlias })
649
+ const record = request.secretId
650
+ ? await this._deps.secrets.getById({ value: request.secretId })
610
651
  : null;
611
- if (request.secretAlias && !record) {
652
+ if (request.secretId && !record) {
612
653
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, "secret not found");
613
654
  return {
614
655
  vaultId: this._deps.vaultId,
615
656
  decision: "deny",
616
657
  reason: "secret not found",
617
658
  secretId: null,
618
- executorTarget: null,
619
659
  };
620
660
  }
621
661
  try {
@@ -626,7 +666,7 @@ export class VaultCore {
626
666
  catch (error) {
627
667
  const detail = error instanceof Error ? error.message : String(error);
628
668
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
629
- secretAlias: record?.alias.value ?? request.secretAlias,
669
+ secretAlias: record?.alias.value,
630
670
  secretId: record?.secretId.value,
631
671
  });
632
672
  throw error;
@@ -634,40 +674,43 @@ export class VaultCore {
634
674
  // DISCOVERY LOGIC: Find best matching capability
635
675
  const agentRecord = await this._deps.agentIdentities.get(this._deps.vaultId, request.agent.id);
636
676
  if (!agentRecord) {
637
- return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null, executorTarget: null };
677
+ return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null };
638
678
  }
639
679
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id))
640
- .filter((state) => state.status === "GRANTED")
680
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
641
681
  .map((state) => this._stateToGrantedCapability(state));
642
682
  const requestedCapabilityId = request.capability?.capabilityId;
643
683
  const candidateCapabilities = requestedCapabilityId
644
684
  ? capabilities.filter((cap) => cap.capabilityId === requestedCapabilityId)
645
685
  : capabilities;
646
686
  const capability = candidateCapabilities.find((cap) => this.isCapabilityMatch(cap, request, record?.secretId.value));
647
- const executorTarget = record
648
- ? record.targetBindings.find((binding) => binding.targetUrl === request.targetUrl)
649
- ?? record.targetBindings.find((binding) => binding.targetId === request.targetUrl)
650
- ?? null
651
- : null;
652
687
  if (!capability) {
653
688
  // It's a discovery case if the agent and secret exist but no capability matches
654
689
  const pendingRecord = {
655
690
  vaultId: this._deps.vaultId,
656
- status: "PENDING",
657
691
  source: "dispatch_discovery",
658
692
  requestId: request.requestId,
659
693
  agentId: request.agent.id,
660
694
  capabilityId: undefined,
661
695
  operation: "dispatch_http",
662
- secretAliases: request.secretAlias ? [request.secretAlias] : [],
663
- scope: request.targetUrl,
664
- methods: [request.method],
696
+ write: {
697
+ secretIds: request.secretId ? [request.secretId] : undefined,
698
+ scope: request.targetUrl,
699
+ methods: [request.method],
700
+ },
701
+ read: {
702
+ mode: "none",
703
+ },
665
704
  requestedAt: request.requestedAt,
666
- secretAlias: request.secretAlias ?? "unknown",
705
+ secretId: request.secretId,
667
706
  targetUrl: request.targetUrl,
668
707
  headers: request.headers,
669
708
  body: request.body,
670
709
  proof: request.proof,
710
+ actions: {
711
+ write: { action: "write", status: "PENDING" },
712
+ read: { action: "read", status: "PENDING" },
713
+ },
671
714
  };
672
715
  await this._deps.capabilityStates.upsert(pendingRecord);
673
716
  // Notify observers
@@ -680,7 +723,7 @@ export class VaultCore {
680
723
  }
681
724
  }
682
725
  await this._appendDecisionAudit(request, AuditOutcome.PENDING, "dispatch stalled for manual discovery approval", {
683
- secretAlias: record?.alias.value ?? request.secretAlias,
726
+ secretAlias: record?.alias.value,
684
727
  secretId: record?.secretId.value,
685
728
  });
686
729
  return {
@@ -688,7 +731,6 @@ export class VaultCore {
688
731
  decision: "pending",
689
732
  reason: "no matching capability found (discovery needed)",
690
733
  secretId: record?.secretId ?? null,
691
- executorTarget,
692
734
  };
693
735
  }
694
736
  try {
@@ -700,7 +742,7 @@ export class VaultCore {
700
742
  catch (error) {
701
743
  const detail = error instanceof Error ? error.message : String(error);
702
744
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
703
- secretAlias: record?.alias.value ?? request.secretAlias,
745
+ secretAlias: record?.alias.value,
704
746
  secretId: record?.secretId.value,
705
747
  });
706
748
  return {
@@ -708,13 +750,12 @@ export class VaultCore {
708
750
  decision: "deny",
709
751
  reason: detail,
710
752
  secretId: record?.secretId ?? null,
711
- executorTarget,
712
753
  };
713
754
  }
714
755
  // Capability found, proceed
715
756
  if (!capability.skipAudit) {
716
757
  await this._appendDecisionAudit(request, AuditOutcome.ALLOWED, "dispatch authorized", {
717
- secretAlias: record?.alias.value ?? request.secretAlias,
758
+ secretAlias: record?.alias.value,
718
759
  secretId: record?.secretId.value,
719
760
  });
720
761
  }
@@ -723,7 +764,6 @@ export class VaultCore {
723
764
  decision: "allow",
724
765
  reason: null,
725
766
  secretId: record?.secretId ?? null,
726
- executorTarget,
727
767
  capability, // Expose the found capability for subsequent steps
728
768
  };
729
769
  }
@@ -766,9 +806,11 @@ export class VaultCore {
766
806
  secretAlias: record.alias.value,
767
807
  secretId: record.secretId.value,
768
808
  }));
809
+ await this._recordRequestExecution(request, authorization.capability, result);
769
810
  return {
770
811
  ...result,
771
812
  vaultId: this._deps.vaultId,
813
+ responseBody: undefined,
772
814
  };
773
815
  }
774
816
  async ownerReadAudit(actor, query, request) {
@@ -812,18 +854,16 @@ export class VaultCore {
812
854
  }
813
855
  }
814
856
  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) {
857
+ if (request.secretId) {
858
+ const idMatched = secretId ? (capability.write.secretIds?.includes(secretId) ?? false) : false;
859
+ if (!idMatched) {
820
860
  return false;
821
861
  }
822
862
  }
823
- if (request.method && capability.methods?.length > 0 && !capability.methods.includes(request.method)) {
863
+ if (request.method && capability.write.methods?.length > 0 && !capability.write.methods.includes(request.method)) {
824
864
  return false;
825
865
  }
826
- if (capability.scope && !isScopeMatch(capability.scope, request.targetUrl)) {
866
+ if (capability.write.scope && !isScopeMatch(capability.write.scope, request.targetUrl)) {
827
867
  return false;
828
868
  }
829
869
  return true;
@@ -837,7 +877,7 @@ export class VaultCore {
837
877
  }
838
878
  async ownerListCapabilities(actor, agentId, request) {
839
879
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
840
- .filter((state) => state.status === "GRANTED")
880
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
841
881
  .map((state) => this._stateToGrantedCapability(state));
842
882
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_CAPABILITIES, AuditOutcome.ALLOWED, "capabilities listed", {
843
883
  requestId: request?.requestId,
@@ -852,10 +892,9 @@ export class VaultCore {
852
892
  }));
853
893
  return records.map((record) => ({
854
894
  vaultId: record.vaultId,
855
- secretId: record.secretId,
856
895
  alias: record.alias,
857
896
  issuerId: record.issuerId,
858
- targetBindings: [...record.targetBindings],
897
+ source: record.source,
859
898
  createdAt: record.createdAt,
860
899
  updatedAt: record.updatedAt,
861
900
  }));
@@ -874,6 +913,35 @@ export class VaultCore {
874
913
  await this._verifyAgentControlProof(request, "list_secrets");
875
914
  return this._listVisibleSecretsForAgent(request.agent.id);
876
915
  }
916
+ async agentListRequests(request) {
917
+ if (request.vaultId.value !== this._deps.vaultId.value) {
918
+ throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
919
+ }
920
+ await this._verifyAgentControlProof(request, "list_requests");
921
+ const records = await this._deps.requests.list(this._deps.vaultId, request.agent.id);
922
+ const states = await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id);
923
+ const stateByRequestId = new Map(states.filter((state) => state.requestId).map((state) => [state.requestId, state]));
924
+ return records.map((record) => this.toVisibleRequestRecord(record, stateByRequestId.get(record.requestId) ?? null));
925
+ }
926
+ async agentGetRequest(request) {
927
+ if (request.vaultId.value !== this._deps.vaultId.value) {
928
+ throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
929
+ }
930
+ await this._verifyAgentControlProof(request, "read_request_result", { targetRequestId: request.targetRequestId });
931
+ const record = await this._deps.requests.get(this._deps.vaultId, request.targetRequestId);
932
+ if (!record || record.agentId !== request.agent.id) {
933
+ throw new VaultCoreError("request record not found", "VAULT_READ_DENIED");
934
+ }
935
+ const state = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, request.targetRequestId);
936
+ const readApproved = state?.actions.read.status === "APPROVED";
937
+ return {
938
+ requestId: record.requestId,
939
+ executionStatus: record.execution.status,
940
+ responseStatus: record.response?.status,
941
+ responseBody: readApproved ? record.response?.body : undefined,
942
+ error: record.response?.error,
943
+ };
944
+ }
877
945
  async agentGetRuntimeManifest(command) {
878
946
  if (command.vaultId.value !== this._deps.vaultId.value) {
879
947
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
@@ -906,10 +974,9 @@ export class VaultCore {
906
974
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
907
975
  }
908
976
  await this._verifyAgentControlProof(command, "submit_capability_request", {
909
- scope: command.scope.scope,
910
- methods: command.scope.methods,
911
- operation: command.scope.operation,
912
- secretAliases: command.scope.secretAliases ?? [],
977
+ write: command.capability.write,
978
+ read: command.capability.read,
979
+ operation: command.capability.operation,
913
980
  justification: command.justification ?? null,
914
981
  });
915
982
  return this.ownerSubmitCapabilityRequest({
@@ -917,7 +984,7 @@ export class VaultCore {
917
984
  requestId: command.requestId,
918
985
  requester: command.agent,
919
986
  agentId: command.agent.id,
920
- scope: command.scope,
987
+ capability: command.capability,
921
988
  justification: command.justification,
922
989
  requestedAt: command.requestedAt,
923
990
  });
@@ -927,10 +994,14 @@ export class VaultCore {
927
994
  if (!existing) {
928
995
  throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
929
996
  }
997
+ const decidedAt = this._deps.clock.nowIso();
930
998
  await this._deps.capabilityStates.upsert({
931
999
  ...existing,
932
- status: "REJECTED",
933
- decidedAt: this._deps.clock.nowIso(),
1000
+ decidedAt,
1001
+ actions: {
1002
+ write: { action: "write", status: "REJECTED", decidedAt },
1003
+ read: { action: "read", status: "REJECTED", decidedAt },
1004
+ },
934
1005
  });
935
1006
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REVOKE_CAPABILITY, AuditOutcome.SUCCEEDED, "capability revoked", {
936
1007
  requestId: command.requestId,
@@ -982,29 +1053,110 @@ export class VaultCore {
982
1053
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
983
1054
  }
984
1055
  return (await this._deps.capabilityStates.list(command.vaultId, command.agentId))
985
- .filter((state) => !command.status || state.status === command.status);
1056
+ .filter((state) => !command.writeStatus || state.actions.write.status === command.writeStatus)
1057
+ .filter((state) => !command.readStatus || state.actions.read.status === command.readStatus);
1058
+ }
1059
+ async ownerApproveCapabilityWrite(command) {
1060
+ if (command.vaultId.value !== this._deps.vaultId.value) {
1061
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1062
+ }
1063
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1064
+ if (!pending) {
1065
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1066
+ }
1067
+ if (pending.actions.write.status !== "PENDING") {
1068
+ throw new VaultCoreError("write approval not pending", "VAULT_WRITE_DENIED");
1069
+ }
1070
+ const decidedAt = this._deps.clock.nowIso();
1071
+ const next = {
1072
+ ...pending,
1073
+ decidedAt,
1074
+ actions: {
1075
+ ...pending.actions,
1076
+ write: {
1077
+ action: "write",
1078
+ status: "APPROVED",
1079
+ decidedAt,
1080
+ },
1081
+ },
1082
+ };
1083
+ await this._deps.capabilityStates.upsert(next);
1084
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_WRITE, AuditOutcome.SUCCEEDED, `approved write policy for capability request ${command.requestId}`, {
1085
+ requestId: command.requestId,
1086
+ agentId: pending.agentId,
1087
+ operation: pending.operation,
1088
+ }));
1089
+ return next;
1090
+ }
1091
+ async ownerApproveCapabilityRead(command) {
1092
+ if (command.vaultId.value !== this._deps.vaultId.value) {
1093
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1094
+ }
1095
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1096
+ if (!pending) {
1097
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1098
+ }
1099
+ if (pending.actions.write.status !== "APPROVED") {
1100
+ throw new VaultCoreError("write approval required before read approval", "VAULT_WRITE_DENIED");
1101
+ }
1102
+ if (pending.actions.read.status !== "PENDING") {
1103
+ throw new VaultCoreError("read approval not pending", "VAULT_WRITE_DENIED");
1104
+ }
1105
+ const decidedAt = this._deps.clock.nowIso();
1106
+ const next = {
1107
+ ...pending,
1108
+ decidedAt,
1109
+ actions: {
1110
+ ...pending.actions,
1111
+ read: {
1112
+ action: "read",
1113
+ status: "APPROVED",
1114
+ decidedAt,
1115
+ },
1116
+ },
1117
+ };
1118
+ await this._deps.capabilityStates.upsert(next);
1119
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `approved read policy for capability request ${command.requestId}`, {
1120
+ requestId: command.requestId,
1121
+ agentId: pending.agentId,
1122
+ operation: pending.operation,
1123
+ }));
1124
+ return next;
986
1125
  }
987
- async ownerExecuteCapabilityStateOnce(command) {
1126
+ async ownerAllowOnce(command) {
988
1127
  return this._executePendingCapabilityState(command, "once");
989
1128
  }
990
- async ownerExecuteCapabilityStateAndGrant(command) {
1129
+ async ownerAllowAlways(command) {
991
1130
  return this._executePendingCapabilityState(command, "grant");
992
1131
  }
993
- async ownerRejectCapabilityState(command) {
1132
+ async ownerDeny(command) {
994
1133
  if (command.vaultId.value !== this._deps.vaultId.value) {
995
1134
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
996
1135
  }
997
1136
  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");
1137
+ if (!pending) {
1138
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1139
+ }
1140
+ const decidedAt = this._deps.clock.nowIso();
1141
+ const rejectWrite = pending.actions.write.status === "PENDING";
1142
+ const rejectRead = !rejectWrite && pending.actions.read.status === "PENDING";
1143
+ if (!rejectWrite && !rejectRead) {
1144
+ throw new VaultCoreError("no capability action approval is pending", "VAULT_WRITE_DENIED");
1000
1145
  }
1001
1146
  const rejectedState = {
1002
1147
  ...pending,
1003
- status: "REJECTED",
1004
- decidedAt: this._deps.clock.nowIso(),
1148
+ decidedAt,
1149
+ actions: {
1150
+ write: rejectWrite
1151
+ ? { action: "write", status: "REJECTED", decidedAt }
1152
+ : { ...pending.actions.write },
1153
+ read: rejectRead
1154
+ ? { action: "read", status: "REJECTED", decidedAt }
1155
+ : { ...pending.actions.read },
1156
+ },
1005
1157
  };
1006
1158
  await this._deps.capabilityStates.upsert(rejectedState);
1007
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
1159
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, rejectWrite ? AuditAction.REJECT_CAPABILITY_WRITE : AuditAction.REJECT_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
1008
1160
  requestId: command.requestId,
1009
1161
  agentId: pending.agentId,
1010
1162
  operation: pending.operation,
@@ -1013,6 +1165,9 @@ export class VaultCore {
1013
1165
  }
1014
1166
  }
1015
1167
  export function createVaultCore(deps) {
1016
- return new VaultCore(deps);
1168
+ return new VaultCore({
1169
+ ...deps,
1170
+ requests: deps.requests ?? new InMemoryRequestRecordRegistry(),
1171
+ });
1017
1172
  }
1018
1173
  //# sourceMappingURL=core.js.map