@the-ai-company/cbio-node-runtime 1.58.0 → 1.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 (137) hide show
  1. package/README.md +55 -24
  2. package/dist/clients/agent/client.d.ts +9 -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 +15 -11
  7. package/dist/clients/owner/client.js +83 -30
  8. package/dist/clients/owner/client.js.map +1 -1
  9. package/dist/clients/owner/contracts.d.ts +32 -22
  10. package/dist/clients/owner/index.d.ts +1 -1
  11. package/dist/runtime/index.d.ts +1 -1
  12. package/dist/vault-core/contracts.d.ts +204 -26
  13. package/dist/vault-core/contracts.js +6 -2
  14. package/dist/vault-core/contracts.js.map +1 -1
  15. package/dist/vault-core/core.d.ts +23 -4
  16. package/dist/vault-core/core.js +480 -121
  17. package/dist/vault-core/core.js.map +1 -1
  18. package/dist/vault-core/defaults.d.ts +9 -2
  19. package/dist/vault-core/defaults.js +41 -14
  20. package/dist/vault-core/defaults.js.map +1 -1
  21. package/dist/vault-core/index.d.ts +1 -1
  22. package/dist/vault-core/index.js.map +1 -1
  23. package/dist/vault-core/persistence.d.ts +12 -2
  24. package/dist/vault-core/persistence.js +43 -4
  25. package/dist/vault-core/persistence.js.map +1 -1
  26. package/dist/vault-core/ports.d.ts +7 -1
  27. package/dist/vault-core/tool-metadata.js +27 -10
  28. package/dist/vault-core/tool-metadata.js.map +1 -1
  29. package/dist/vault-ingress/defaults.d.ts +2 -0
  30. package/dist/vault-ingress/defaults.js +6 -0
  31. package/dist/vault-ingress/defaults.js.map +1 -1
  32. package/dist/vault-ingress/index.d.ts +54 -9
  33. package/dist/vault-ingress/index.js +160 -46
  34. package/dist/vault-ingress/index.js.map +1 -1
  35. package/dist/vault-ingress/remote-transport.d.ts +2 -0
  36. package/dist/vault-ingress/remote-transport.js +33 -4
  37. package/dist/vault-ingress/remote-transport.js.map +1 -1
  38. package/docs/ARCHITECTURE.md +1 -1
  39. package/docs/REFERENCE.md +43 -30
  40. package/docs/WORKS_WITH_CUSTOM_FETCH.md +2 -2
  41. package/docs/api/README.md +5 -5
  42. package/docs/api/classes/IdentityError.md +1 -1
  43. package/docs/api/classes/OwnerClientError.md +1 -1
  44. package/docs/api/classes/VaultCore.md +176 -16
  45. package/docs/api/classes/VaultCoreError.md +1 -1
  46. package/docs/api/enumerations/IdentityErrorCode.md +1 -1
  47. package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
  48. package/docs/api/functions/createAgentClient.md +1 -1
  49. package/docs/api/functions/createIdentity.md +1 -1
  50. package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
  51. package/docs/api/functions/createOwnerSession.md +1 -1
  52. package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
  53. package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
  54. package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
  55. package/docs/api/functions/createVault.md +1 -1
  56. package/docs/api/functions/createVaultClient.md +1 -1
  57. package/docs/api/functions/createVaultCore.md +1 -1
  58. package/docs/api/functions/createVaultCoreDependencies.md +1 -1
  59. package/docs/api/functions/createVaultService.md +1 -1
  60. package/docs/api/functions/createWorkspaceStorage.md +1 -1
  61. package/docs/api/functions/deriveIdentityId.md +1 -1
  62. package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
  63. package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
  64. package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
  65. package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
  66. package/docs/api/functions/initializeVaultCustody.md +1 -1
  67. package/docs/api/functions/listVaults.md +1 -1
  68. package/docs/api/functions/readVaultProfile.md +1 -1
  69. package/docs/api/functions/recoverVault.md +1 -1
  70. package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
  71. package/docs/api/functions/restoreIdentity.md +1 -1
  72. package/docs/api/functions/updateVaultMetadata.md +1 -1
  73. package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
  74. package/docs/api/functions/writeVaultProfile.md +1 -1
  75. package/docs/api/interfaces/AgentClient.md +33 -1
  76. package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
  77. package/docs/api/interfaces/AgentDispatchTransport.md +33 -1
  78. package/docs/api/interfaces/AgentIdentity.md +1 -1
  79. package/docs/api/interfaces/AgentSigner.md +1 -1
  80. package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +9 -9
  81. package/docs/api/interfaces/CbioRuntime.md +1 -1
  82. package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
  83. package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
  84. package/docs/api/interfaces/CreateOwnerSessionOptions.md +1 -1
  85. package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
  86. package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
  87. package/docs/api/interfaces/CreateVaultOptions.md +1 -1
  88. package/docs/api/interfaces/CreatedVault.md +1 -1
  89. package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
  90. package/docs/api/interfaces/IStorageProvider.md +1 -1
  91. package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
  92. package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
  93. package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
  94. package/docs/api/interfaces/{OwnerStoreSecretInput.md → OwnerCreateSecretInput.md} +2 -2
  95. package/docs/api/interfaces/{VaultDeleteSecretInput.md → OwnerRemoveSecretInput.md} +2 -2
  96. package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
  97. package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
  98. package/docs/api/interfaces/OwnerSession.md +1 -1
  99. package/docs/api/interfaces/{OwnerWriteSecretInput.md → OwnerUpdateSecretInput.md} +2 -2
  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 +112 -48
  108. package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
  109. package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
  110. package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
  111. package/docs/api/interfaces/VaultGrantCapabilityInput.md +9 -21
  112. package/docs/api/interfaces/VaultGrantCapabilityRequest.md +1 -1
  113. package/docs/api/interfaces/VaultIdentity.md +1 -1
  114. package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
  115. package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
  116. package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
  117. package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
  118. package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
  119. package/docs/api/interfaces/VaultMetadata.md +1 -1
  120. package/docs/api/interfaces/VaultObject.md +1 -1
  121. package/docs/api/interfaces/VaultProfile.md +1 -1
  122. package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
  123. package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
  124. package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
  125. package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
  126. package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
  127. package/docs/api/interfaces/VaultSigner.md +1 -1
  128. package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +11 -17
  129. package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
  130. package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
  131. package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
  132. package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
  133. package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +1 -1
  134. package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
  135. package/docs/zh/README.md +27 -9
  136. package/examples/process-isolation.ts +6 -4
  137. 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
+ 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 {
@@ -21,22 +22,32 @@ function toAuditEntry(deps, actor, action, outcome, detail, options) {
21
22
  agentId: options?.agentId,
22
23
  };
23
24
  }
24
- function buildSecretRecord(deps, command) {
25
+ function buildSecretRecord(deps, command, previousRecord) {
25
26
  const now = deps.clock.nowIso();
26
27
  const source = command.source?.kind === "request" && command.source.requestId
27
28
  ? { kind: "request", requestId: command.source.requestId }
28
29
  : { kind: "manual" };
30
+ const previousVersion = previousRecord ? Number.parseInt(previousRecord.version.value, 10) : 0;
31
+ const nextVersion = Number.isFinite(previousVersion) ? previousVersion + 1 : 1;
29
32
  return {
30
33
  vaultId: deps.vaultId,
31
34
  secretId: deps.ids.newSecretId(),
32
35
  alias: { value: command.alias },
33
- version: deps.ids.newVersion(),
36
+ version: { value: String(nextVersion) },
37
+ lifecycleStatus: "ACTIVE",
38
+ previousSecretId: previousRecord?.secretId,
34
39
  issuerId: command.kind === "issuer.write_secret" ? command.issuerSiteId : null,
35
40
  source,
36
41
  createdAt: now,
37
42
  updatedAt: now,
38
43
  };
39
44
  }
45
+ function isSecretActive(record) {
46
+ if (record.lifecycleStatus) {
47
+ return record.lifecycleStatus === "ACTIVE";
48
+ }
49
+ return !record.retiredAt;
50
+ }
40
51
  function normalizeScopeTarget(targetUrl) {
41
52
  try {
42
53
  const parsed = new URL(targetUrl);
@@ -98,12 +109,21 @@ export class VaultCore {
98
109
  vaultId: state.vaultId,
99
110
  capabilityId: state.capabilityId ?? "",
100
111
  agentId: state.agentId,
101
- secretIds: state.secretIds ? [...state.secretIds] : undefined,
102
- secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
103
112
  operation: state.operation,
104
113
  customFlowId: state.customFlowId,
105
- scope: state.scope,
106
- methods: [...state.methods],
114
+ write: {
115
+ secretIds: state.write.secretIds ? [...state.write.secretIds] : undefined,
116
+ scope: state.write.scope,
117
+ methods: [...state.write.methods],
118
+ },
119
+ read: state.actions.read.status === "APPROVED"
120
+ ? {
121
+ mode: state.read.mode,
122
+ paths: state.read.paths ? [...state.read.paths] : undefined,
123
+ }
124
+ : {
125
+ mode: "none",
126
+ },
107
127
  issuedAt: state.issuedAt ?? state.requestedAt,
108
128
  expiresAt: state.expiresAt,
109
129
  rateLimit: state.rateLimit,
@@ -112,49 +132,68 @@ export class VaultCore {
112
132
  }
113
133
  async _buildAgentCapabilityStates(agentId) {
114
134
  return (await this._deps.capabilityStates.list(this._deps.vaultId, agentId)).map((state) => ({
115
- status: state.status,
116
135
  source: state.source,
117
136
  agentId: state.agentId,
118
137
  requestId: state.requestId,
119
138
  capabilityId: state.capabilityId,
120
139
  operation: state.operation,
121
- secretIds: state.secretIds ? [...state.secretIds] : undefined,
122
- secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
123
140
  customFlowId: state.customFlowId,
124
- scope: state.scope,
125
- methods: [...state.methods],
141
+ write: {
142
+ secretIds: state.write.secretIds ? [...state.write.secretIds] : undefined,
143
+ scope: state.write.scope,
144
+ methods: [...state.write.methods],
145
+ },
146
+ read: {
147
+ mode: state.read.mode,
148
+ paths: state.read.paths ? [...state.read.paths] : undefined,
149
+ },
126
150
  issuedAt: state.issuedAt,
127
151
  requestedAt: state.requestedAt,
128
152
  expiresAt: state.expiresAt,
129
153
  rateLimit: state.rateLimit,
130
154
  skipAudit: state.skipAudit,
131
155
  justification: state.justification,
132
- secretAlias: state.secretAlias,
156
+ secretId: state.secretId,
133
157
  targetUrl: state.targetUrl,
158
+ actions: {
159
+ write: { ...state.actions.write },
160
+ read: { ...state.actions.read },
161
+ },
134
162
  }));
135
163
  }
136
164
  _isExecutablePendingState(state) {
137
- return !!(state.requestId && state.targetUrl && state.secretAlias && state.proof);
165
+ return !!(state.requestId && state.targetUrl && state.proof);
138
166
  }
139
167
  async _executePendingCapabilityState(command, mode) {
140
168
  if (command.vaultId.value !== this._deps.vaultId.value) {
141
169
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
142
170
  }
143
171
  const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
144
- if (!pending || pending.status !== "PENDING") {
145
- throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
172
+ if (!pending) {
173
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
174
+ }
175
+ if (pending.actions.write.status !== "APPROVED") {
176
+ throw new VaultCoreError("write approval required before execution", "VAULT_WRITE_DENIED");
177
+ }
178
+ if (mode === "once" && pending.source !== "dispatch_discovery") {
179
+ throw new VaultCoreError("one-time execution is only available for dispatch discovery requests", "VAULT_WRITE_DENIED");
146
180
  }
147
181
  const issuedAt = this._deps.clock.nowIso();
148
182
  const capability = {
149
183
  vaultId: this._deps.vaultId,
150
184
  agentId: pending.agentId,
151
185
  capabilityId: pending.capabilityId ?? this._deps.ids.newCapabilityId(),
152
- secretIds: pending.secretIds ? [...pending.secretIds] : undefined,
153
- secretAliases: pending.secretAliases ? [...pending.secretAliases] : (pending.secretAlias ? [pending.secretAlias] : []),
154
186
  operation: pending.operation,
155
187
  customFlowId: pending.customFlowId,
156
- scope: pending.targetUrl ?? pending.scope,
157
- methods: [...pending.methods],
188
+ write: {
189
+ secretIds: pending.write.secretIds ? [...pending.write.secretIds] : undefined,
190
+ scope: pending.targetUrl ?? pending.write.scope,
191
+ methods: [...pending.write.methods],
192
+ },
193
+ read: {
194
+ mode: pending.read.mode,
195
+ paths: pending.read.paths ? [...pending.read.paths] : undefined,
196
+ },
158
197
  issuedAt,
159
198
  expiresAt: pending.expiresAt,
160
199
  rateLimit: pending.rateLimit,
@@ -166,9 +205,9 @@ export class VaultCore {
166
205
  vaultId: this._deps.vaultId,
167
206
  agent: { kind: "agent", id: pending.agentId },
168
207
  capability,
169
- secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
208
+ secretId: pending.secretId,
170
209
  targetUrl: pending.targetUrl,
171
- method: pending.methods[0] ?? "POST",
210
+ method: pending.write.methods[0] ?? "POST",
172
211
  headers: pending.headers,
173
212
  body: pending.body,
174
213
  proof: pending.proof,
@@ -181,8 +220,8 @@ export class VaultCore {
181
220
  vaultId: this._deps.vaultId,
182
221
  requestId: pending.requestId ?? command.requestId,
183
222
  status: DispatchStatus.SUCCEEDED,
184
- targetUrl: pending.scope,
185
- method: pending.methods[0] ?? "POST",
223
+ targetUrl: pending.write.scope,
224
+ method: pending.write.methods[0] ?? "POST",
186
225
  };
187
226
  }
188
227
  else {
@@ -192,26 +231,13 @@ export class VaultCore {
192
231
  await this._deps.capabilityStates.upsert({
193
232
  ...pending,
194
233
  capabilityId: capability.capabilityId,
195
- status: "GRANTED",
196
234
  source: "owner_grant",
197
235
  issuedAt,
198
236
  decidedAt: issuedAt,
199
237
  });
200
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed and granted capability state ${command.requestId}`, {
201
- requestId: command.requestId,
202
- agentId: pending.agentId,
203
- capabilityId: capability.capabilityId,
204
- operation: capability.operation,
205
- }));
206
238
  }
207
239
  else {
208
240
  await this._deps.capabilityStates.deleteByRequestId(command.vaultId, command.requestId);
209
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed once and deleted capability state ${command.requestId}`, {
210
- requestId: command.requestId,
211
- agentId: pending.agentId,
212
- capabilityId: capability.capabilityId,
213
- operation: capability.operation,
214
- }));
215
241
  }
216
242
  return result;
217
243
  }
@@ -233,8 +259,8 @@ export class VaultCore {
233
259
  capabilityId: request.capability?.capabilityId,
234
260
  operation: request.capability?.operation ?? AuditAction.AUTHORIZE_DISPATCH,
235
261
  targetUrl: request.targetUrl,
236
- secretAlias: options?.secretAlias ?? request.secretAlias,
237
- secretId: options?.secretId,
262
+ secretAlias: options?.secretAlias,
263
+ secretId: options?.secretId ?? request.secretId,
238
264
  }));
239
265
  }
240
266
  async _verifyAgentControlProof(request, action, payload = {}) {
@@ -265,27 +291,36 @@ export class VaultCore {
265
291
  }
266
292
  async _listVisibleSecretsForAgent(agentId) {
267
293
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
268
- .filter((state) => state.status === "GRANTED")
294
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
269
295
  .map((state) => this._stateToGrantedCapability(state));
270
296
  const capabilityMap = new Map();
271
297
  for (const capability of capabilities) {
272
- for (const alias of capability.secretAliases ?? []) {
273
- const existing = capabilityMap.get(alias) ?? [];
298
+ for (const secretId of capability.write.secretIds ?? []) {
299
+ const existing = capabilityMap.get(secretId) ?? [];
274
300
  existing.push({
275
301
  capabilityId: capability.capabilityId,
276
- scope: capability.scope,
277
- methods: [...capability.methods],
302
+ write: {
303
+ secretIds: capability.write.secretIds ? [...capability.write.secretIds] : undefined,
304
+ scope: capability.write.scope,
305
+ methods: [...capability.write.methods],
306
+ },
307
+ read: {
308
+ mode: capability.read.mode,
309
+ paths: capability.read.paths ? [...capability.read.paths] : undefined,
310
+ },
278
311
  });
279
- capabilityMap.set(alias, existing);
312
+ capabilityMap.set(secretId, existing);
280
313
  }
281
314
  }
282
315
  const records = await this._deps.secrets.list(this._deps.vaultId);
283
316
  return records.map((record) => {
284
- const authorizedCapabilities = capabilityMap.get(record.alias.value) ?? [];
317
+ const authorizedCapabilities = capabilityMap.get(record.secretId.value) ?? [];
285
318
  return {
286
319
  vaultId: record.vaultId,
287
320
  secretId: record.secretId,
288
321
  alias: record.alias,
322
+ version: record.version,
323
+ lifecycleStatus: record.lifecycleStatus ?? "ACTIVE",
289
324
  issuerId: record.issuerId,
290
325
  source: record.source,
291
326
  createdAt: record.createdAt,
@@ -295,6 +330,94 @@ export class VaultCore {
295
330
  };
296
331
  });
297
332
  }
333
+ async _recordRequestExecution(request, capability, result) {
334
+ await this._deps.requests.save({
335
+ vaultId: this._deps.vaultId,
336
+ requestId: request.requestId,
337
+ agentId: request.agent.id,
338
+ capabilityId: capability?.capabilityId,
339
+ operation: capability?.operation ?? "dispatch_http",
340
+ createdAt: this._deps.clock.nowIso(),
341
+ request: {
342
+ targetUrl: request.targetUrl,
343
+ method: request.method,
344
+ headers: request.headers,
345
+ body: request.body,
346
+ secretId: request.secretId,
347
+ },
348
+ response: {
349
+ status: result.responseStatus,
350
+ body: result.responseBody,
351
+ error: result.error,
352
+ },
353
+ execution: {
354
+ status: result.status,
355
+ },
356
+ });
357
+ }
358
+ toVisibleRequestRecord(record, state) {
359
+ const readStatus = state?.actions.read.status ?? "PENDING";
360
+ return {
361
+ requestId: record.requestId,
362
+ createdAt: record.createdAt,
363
+ capabilityId: record.capabilityId,
364
+ operation: record.operation,
365
+ targetUrl: record.request.targetUrl,
366
+ method: record.request.method,
367
+ executionStatus: record.execution.status,
368
+ responseStatus: record.response?.status,
369
+ error: record.response?.error,
370
+ readStatus,
371
+ hasResponseBody: typeof record.response?.body === "string" && record.response.body.length > 0,
372
+ resultVisible: readStatus === "APPROVED",
373
+ };
374
+ }
375
+ toOwnerVisibleRequestRecord(record, state) {
376
+ return {
377
+ requestId: record.requestId,
378
+ createdAt: record.createdAt,
379
+ agentId: record.agentId,
380
+ capabilityId: record.capabilityId,
381
+ operation: record.operation,
382
+ targetUrl: record.request.targetUrl,
383
+ method: record.request.method,
384
+ executionStatus: record.execution.status,
385
+ responseStatus: record.response?.status,
386
+ error: record.response?.error,
387
+ writeStatus: state?.actions.write.status ?? "PENDING",
388
+ readStatus: state?.actions.read.status ?? "PENDING",
389
+ hasResponseBody: typeof record.response?.body === "string" && record.response.body.length > 0,
390
+ };
391
+ }
392
+ toOwnerRequestRecord(record, state) {
393
+ return {
394
+ requestId: record.requestId,
395
+ createdAt: record.createdAt,
396
+ agentId: record.agentId,
397
+ capabilityId: record.capabilityId,
398
+ operation: record.operation,
399
+ request: {
400
+ targetUrl: record.request.targetUrl,
401
+ method: record.request.method,
402
+ headers: record.request.headers ? { ...record.request.headers } : undefined,
403
+ body: record.request.body,
404
+ secretId: record.request.secretId,
405
+ },
406
+ response: record.response
407
+ ? {
408
+ status: record.response.status,
409
+ headers: record.response.headers ? { ...record.response.headers } : undefined,
410
+ body: record.response.body,
411
+ error: record.response.error,
412
+ }
413
+ : undefined,
414
+ actions: {
415
+ write: state?.actions.write ?? { action: "write", status: "PENDING" },
416
+ read: state?.actions.read ?? { action: "read", status: "PENDING" },
417
+ },
418
+ executionStatus: record.execution.status,
419
+ };
420
+ }
298
421
  ownerOnCapabilityState(callback) {
299
422
  this._capabilityStateObservers.add(callback);
300
423
  return () => {
@@ -365,10 +488,13 @@ export class VaultCore {
365
488
  try {
366
489
  await this._deps.capabilityStates.upsert({
367
490
  ...command.capability,
368
- status: "GRANTED",
369
491
  source: "owner_grant",
370
492
  requestId: undefined,
371
493
  requestedAt: command.capability.issuedAt,
494
+ actions: {
495
+ write: { action: "write", status: "APPROVED", decidedAt: command.capability.issuedAt },
496
+ read: { action: "read", status: "APPROVED", decidedAt: command.capability.issuedAt },
497
+ },
372
498
  });
373
499
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CAPABILITY, AuditOutcome.SUCCEEDED, `capability registered: ${command.capability.capabilityId}`, {
374
500
  capabilityId: command.capability.capabilityId,
@@ -391,27 +517,36 @@ export class VaultCore {
391
517
  if (!command.agentId.trim()) {
392
518
  throw new VaultCoreError("capability request agent id required", "VAULT_IDENTITY_DENIED");
393
519
  }
394
- if (!command.scope.scope.trim()) {
520
+ if (!command.capability.write.scope.trim()) {
395
521
  throw new VaultCoreError("capability request scope required", "VAULT_IDENTITY_DENIED");
396
522
  }
397
- if (command.scope.methods.length === 0) {
523
+ if (command.capability.write.methods.length === 0) {
398
524
  throw new VaultCoreError("capability request method required", "VAULT_IDENTITY_DENIED");
399
525
  }
400
526
  const pendingRecord = {
401
527
  vaultId: this._deps.vaultId,
402
- status: "PENDING",
403
528
  source: "explicit_request",
404
529
  requestId: command.requestId,
405
530
  agentId: command.agentId,
406
- operation: command.scope.operation,
407
- secretAliases: command.scope.secretAliases ? [...command.scope.secretAliases] : [],
408
- scope: command.scope.scope,
409
- methods: [...command.scope.methods],
410
- rateLimit: command.scope.rateLimit,
411
- skipAudit: command.scope.skipAudit,
412
- expiresAt: command.scope.expiresAt,
531
+ operation: command.capability.operation,
532
+ write: {
533
+ secretIds: command.capability.write.secretIds ? [...command.capability.write.secretIds] : undefined,
534
+ scope: command.capability.write.scope,
535
+ methods: [...command.capability.write.methods],
536
+ },
537
+ read: {
538
+ mode: command.capability.read.mode,
539
+ paths: command.capability.read.paths ? [...command.capability.read.paths] : undefined,
540
+ },
541
+ rateLimit: command.capability.rateLimit,
542
+ skipAudit: command.capability.skipAudit,
543
+ expiresAt: command.capability.expiresAt,
413
544
  justification: command.justification,
414
545
  requestedAt: command.requestedAt,
546
+ actions: {
547
+ write: { action: "write", status: "PENDING" },
548
+ read: { action: "read", status: "PENDING" },
549
+ },
415
550
  };
416
551
  await this._deps.capabilityStates.upsert(pendingRecord);
417
552
  for (const observer of this._capabilityStateObservers) {
@@ -425,7 +560,7 @@ export class VaultCore {
425
560
  await this._appendAudit(toAuditEntry(this._deps, command.requester, AuditAction.SUBMIT_CAPABILITY_REQUEST, AuditOutcome.PENDING, `capability request submitted for agent: ${command.agentId}`, {
426
561
  requestId: command.requestId,
427
562
  agentId: command.agentId,
428
- operation: command.scope.operation,
563
+ operation: command.capability.operation,
429
564
  }));
430
565
  return pendingRecord;
431
566
  }
@@ -434,17 +569,19 @@ export class VaultCore {
434
569
  throw new VaultCoreError("capability lookup vault mismatch", "VAULT_IDENTITY_DENIED");
435
570
  }
436
571
  const state = await this._deps.capabilityStates.getByCapabilityId(vaultId, agentId, capabilityId);
437
- return state && state.status === "GRANTED" ? this._stateToGrantedCapability(state) : null;
572
+ return state && state.capabilityId && state.issuedAt && state.actions.write.status === "APPROVED"
573
+ ? this._stateToGrantedCapability(state)
574
+ : null;
438
575
  }
439
576
  async ownerRegisterCustomFlow(command) {
440
577
  if (command.vaultId.value !== this._deps.vaultId.value) {
441
- throw new VaultCoreError("custom flow vault mismatch", "VAULT_IDENTITY_DENIED");
578
+ throw new VaultCoreError("request template vault mismatch", "VAULT_IDENTITY_DENIED");
442
579
  }
443
580
  if (!command.flow.flowId.trim()) {
444
- throw new VaultCoreError("custom flow id required", "VAULT_IDENTITY_DENIED");
581
+ throw new VaultCoreError("request template id required", "VAULT_IDENTITY_DENIED");
445
582
  }
446
583
  if (command.flow.mode !== "send_secret" && !command.flow.responseSecret) {
447
- throw new VaultCoreError("custom flow response secret rule required", "VAULT_IDENTITY_DENIED");
584
+ throw new VaultCoreError("request template response secret rule required", "VAULT_IDENTITY_DENIED");
448
585
  }
449
586
  try {
450
587
  await this._deps.customFlows.register({
@@ -458,7 +595,7 @@ export class VaultCore {
458
595
  responseSecret: command.flow.responseSecret,
459
596
  createdAt: this._deps.clock.nowIso(),
460
597
  });
461
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CUSTOM_FLOW, AuditOutcome.SUCCEEDED, `custom http flow registered: ${command.flow.flowId}`));
598
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CUSTOM_FLOW, AuditOutcome.SUCCEEDED, `request template registered: ${command.flow.flowId}`));
462
599
  }
463
600
  catch (error) {
464
601
  const detail = error instanceof Error ? error.message : String(error);
@@ -478,7 +615,7 @@ export class VaultCore {
478
615
  throw new VaultCoreError("alias already bound to existing secret", "VAULT_WRITE_DENIED");
479
616
  }
480
617
  const record = buildSecretRecord(this._deps, {
481
- kind: "owner.write_secret",
618
+ kind: "owner.create_secret",
482
619
  vaultId: this._deps.vaultId,
483
620
  requestId,
484
621
  owner: actor,
@@ -493,7 +630,7 @@ export class VaultCore {
493
630
  try {
494
631
  await this._deps.custody.store(record.secretId, plaintext);
495
632
  await this._deps.secrets.save(record);
496
- await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, `custom flow stored secret: ${alias}`, {
633
+ await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, `request template stored secret: ${alias}`, {
497
634
  secretAlias: record.alias.value,
498
635
  secretId: record.secretId.value,
499
636
  }));
@@ -507,7 +644,36 @@ export class VaultCore {
507
644
  }
508
645
  return record;
509
646
  }
510
- async ownerWriteSecret(command) {
647
+ async _getActiveSecretByAlias(alias) {
648
+ const matches = (await this._deps.secrets.list(this._deps.vaultId))
649
+ .filter((record) => record.alias.value === alias && isSecretActive(record));
650
+ if (matches.length > 1) {
651
+ throw new VaultCoreError(`multiple active secrets found for alias: ${alias}`, "VAULT_WRITE_DENIED");
652
+ }
653
+ return matches[0] ?? null;
654
+ }
655
+ async _persistNewSecretRecord(record, plaintext, actor, successDetail) {
656
+ try {
657
+ await this._deps.custody.store(record.secretId, plaintext);
658
+ await this._deps.secrets.save(record);
659
+ await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, successDetail, {
660
+ secretAlias: record.alias.value,
661
+ secretId: record.secretId.value,
662
+ }));
663
+ }
664
+ catch (error) {
665
+ await Promise.allSettled([
666
+ this._deps.secrets.delete(record.secretId),
667
+ this._deps.custody.delete(record.secretId),
668
+ ]);
669
+ throw error;
670
+ }
671
+ return record;
672
+ }
673
+ async ownerCreateSecret(command) {
674
+ return this.ownerWriteSecret(command);
675
+ }
676
+ async ownerUpdateSecret(command) {
511
677
  if (command.vaultId.value !== this._deps.vaultId.value) {
512
678
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
513
679
  }
@@ -516,47 +682,89 @@ export class VaultCore {
516
682
  }
517
683
  catch (error) {
518
684
  const detail = error instanceof Error ? error.message : String(error);
519
- await this._appendAudit(toAuditEntry(this._deps, command.kind === "owner.write_secret" ? command.owner : command.issuer, AuditAction.WRITE_SECRET, AuditOutcome.DENIED, detail, {
685
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.WRITE_SECRET, AuditOutcome.DENIED, detail, {
520
686
  secretAlias: command.alias,
521
687
  }));
522
688
  throw error;
523
689
  }
524
- const existing = await this._deps.secrets.getByAlias({ value: command.alias });
525
- if (existing) {
526
- await this._appendAudit(toAuditEntry(this._deps, command.kind === "owner.write_secret" ? command.owner : command.issuer, AuditAction.REASSIGN_ALIAS, AuditOutcome.DENIED, "alias already bound to existing secret; explicit alias lifecycle required", {
527
- secretAlias: existing.alias.value,
528
- secretId: existing.secretId.value,
529
- }));
530
- throw new VaultCoreError("alias already bound to existing secret", "VAULT_WRITE_DENIED");
690
+ const existing = await this._getActiveSecretByAlias(command.alias);
691
+ if (!existing) {
692
+ throw new VaultCoreError(`secret not found: ${command.alias}`, "VAULT_SECRET_NOT_FOUND");
531
693
  }
532
- const record = buildSecretRecord(this._deps, command);
694
+ const record = buildSecretRecord(this._deps, command, existing);
695
+ const supersededAt = this._deps.clock.nowIso();
696
+ const superseded = {
697
+ ...existing,
698
+ lifecycleStatus: "SUPERSEDED",
699
+ supersededBySecretId: record.secretId,
700
+ supersededAt,
701
+ retiredAt: supersededAt,
702
+ updatedAt: supersededAt,
703
+ };
704
+ let custodyStored = false;
705
+ let previousSuperseded = false;
706
+ let newRecordSaved = false;
533
707
  try {
534
708
  await this._deps.custody.store(record.secretId, command.plaintext);
709
+ custodyStored = true;
710
+ await this._deps.secrets.save(superseded);
711
+ previousSuperseded = true;
535
712
  await this._deps.secrets.save(record);
536
- await this._appendAudit(toAuditEntry(this._deps, command.kind === "owner.write_secret" ? command.owner : command.issuer, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, "secret stored", {
713
+ newRecordSaved = true;
714
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.WRITE_SECRET, AuditOutcome.SUCCEEDED, "secret updated", {
537
715
  secretAlias: record.alias.value,
538
716
  secretId: record.secretId.value,
539
717
  }));
718
+ return record;
540
719
  }
541
720
  catch (error) {
721
+ if (previousSuperseded) {
722
+ await Promise.allSettled([this._deps.secrets.save(existing)]);
723
+ }
542
724
  await Promise.allSettled([
543
- this._deps.secrets.delete(record.secretId),
544
- this._deps.custody.delete(record.secretId),
725
+ newRecordSaved ? this._deps.secrets.delete(record.secretId) : Promise.resolve(),
726
+ custodyStored ? this._deps.custody.delete(record.secretId) : Promise.resolve(),
545
727
  ]);
546
728
  throw error;
547
729
  }
548
- return record;
549
730
  }
550
- async ownerDeleteSecret(command) {
551
- const record = await this._deps.secrets.getByAlias({ value: command.alias });
731
+ async ownerWriteSecret(command) {
732
+ if (command.vaultId.value !== this._deps.vaultId.value) {
733
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
734
+ }
735
+ try {
736
+ await this._deps.policy.authorizeWrite(command);
737
+ }
738
+ catch (error) {
739
+ const detail = error instanceof Error ? error.message : String(error);
740
+ await this._appendAudit(toAuditEntry(this._deps, command.kind === "issuer.write_secret" ? command.issuer : command.owner, AuditAction.WRITE_SECRET, AuditOutcome.DENIED, detail, {
741
+ secretAlias: command.alias,
742
+ }));
743
+ throw error;
744
+ }
745
+ const existing = await this._getActiveSecretByAlias(command.alias);
746
+ if (existing) {
747
+ await this._appendAudit(toAuditEntry(this._deps, command.kind === "issuer.write_secret" ? command.issuer : command.owner, AuditAction.REASSIGN_ALIAS, AuditOutcome.DENIED, "alias already bound to existing secret; explicit alias lifecycle required", {
748
+ secretAlias: existing.alias.value,
749
+ secretId: existing.secretId.value,
750
+ }));
751
+ throw new VaultCoreError("alias already bound to existing secret", "VAULT_WRITE_DENIED");
752
+ }
753
+ const record = buildSecretRecord(this._deps, command);
754
+ return this._persistNewSecretRecord(record, command.plaintext, command.kind === "issuer.write_secret" ? command.issuer : command.owner, "secret created");
755
+ }
756
+ async ownerRemoveSecret(command) {
757
+ const record = await this._getActiveSecretByAlias(command.alias);
552
758
  if (!record) {
553
759
  throw new VaultCoreError(`secret not found: ${command.alias}`, "VAULT_SECRET_NOT_FOUND");
554
760
  }
555
- const retiredAt = this._deps.clock.nowIso();
761
+ const removedAt = this._deps.clock.nowIso();
556
762
  await this._deps.secrets.save({
557
763
  ...record,
558
- updatedAt: retiredAt,
559
- retiredAt,
764
+ lifecycleStatus: "REMOVED",
765
+ updatedAt: removedAt,
766
+ removedAt,
767
+ retiredAt: removedAt,
560
768
  });
561
769
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.DELETE_SECRET, AuditOutcome.SUCCEEDED, `retired secret ${command.alias}`, {
562
770
  requestId: command.requestId,
@@ -564,14 +772,17 @@ export class VaultCore {
564
772
  secretId: record.secretId.value,
565
773
  }));
566
774
  }
775
+ async ownerDeleteSecret(command) {
776
+ return this.ownerRemoveSecret(command);
777
+ }
567
778
  async agentAuthorizeDispatch(request) {
568
779
  if (request.vaultId.value !== this._deps.vaultId.value) {
569
780
  throw new VaultCoreError("request vault mismatch", "VAULT_DISPATCH_DENIED");
570
781
  }
571
- const record = request.secretAlias
572
- ? await this._deps.secrets.getByAlias({ value: request.secretAlias })
782
+ const record = request.secretId
783
+ ? await this._deps.secrets.getById({ value: request.secretId })
573
784
  : null;
574
- if (request.secretAlias && !record) {
785
+ if (request.secretId && !record) {
575
786
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, "secret not found");
576
787
  return {
577
788
  vaultId: this._deps.vaultId,
@@ -588,7 +799,7 @@ export class VaultCore {
588
799
  catch (error) {
589
800
  const detail = error instanceof Error ? error.message : String(error);
590
801
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
591
- secretAlias: record?.alias.value ?? request.secretAlias,
802
+ secretAlias: record?.alias.value,
592
803
  secretId: record?.secretId.value,
593
804
  });
594
805
  throw error;
@@ -599,7 +810,7 @@ export class VaultCore {
599
810
  return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null };
600
811
  }
601
812
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id))
602
- .filter((state) => state.status === "GRANTED")
813
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
603
814
  .map((state) => this._stateToGrantedCapability(state));
604
815
  const requestedCapabilityId = request.capability?.capabilityId;
605
816
  const candidateCapabilities = requestedCapabilityId
@@ -610,21 +821,29 @@ export class VaultCore {
610
821
  // It's a discovery case if the agent and secret exist but no capability matches
611
822
  const pendingRecord = {
612
823
  vaultId: this._deps.vaultId,
613
- status: "PENDING",
614
824
  source: "dispatch_discovery",
615
825
  requestId: request.requestId,
616
826
  agentId: request.agent.id,
617
827
  capabilityId: undefined,
618
828
  operation: "dispatch_http",
619
- secretAliases: request.secretAlias ? [request.secretAlias] : [],
620
- scope: request.targetUrl,
621
- methods: [request.method],
829
+ write: {
830
+ secretIds: request.secretId ? [request.secretId] : undefined,
831
+ scope: request.targetUrl,
832
+ methods: [request.method],
833
+ },
834
+ read: {
835
+ mode: "none",
836
+ },
622
837
  requestedAt: request.requestedAt,
623
- secretAlias: request.secretAlias ?? "unknown",
838
+ secretId: request.secretId,
624
839
  targetUrl: request.targetUrl,
625
840
  headers: request.headers,
626
841
  body: request.body,
627
842
  proof: request.proof,
843
+ actions: {
844
+ write: { action: "write", status: "PENDING" },
845
+ read: { action: "read", status: "PENDING" },
846
+ },
628
847
  };
629
848
  await this._deps.capabilityStates.upsert(pendingRecord);
630
849
  // Notify observers
@@ -637,7 +856,7 @@ export class VaultCore {
637
856
  }
638
857
  }
639
858
  await this._appendDecisionAudit(request, AuditOutcome.PENDING, "dispatch stalled for manual discovery approval", {
640
- secretAlias: record?.alias.value ?? request.secretAlias,
859
+ secretAlias: record?.alias.value,
641
860
  secretId: record?.secretId.value,
642
861
  });
643
862
  return {
@@ -656,7 +875,7 @@ export class VaultCore {
656
875
  catch (error) {
657
876
  const detail = error instanceof Error ? error.message : String(error);
658
877
  await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
659
- secretAlias: record?.alias.value ?? request.secretAlias,
878
+ secretAlias: record?.alias.value,
660
879
  secretId: record?.secretId.value,
661
880
  });
662
881
  return {
@@ -669,7 +888,7 @@ export class VaultCore {
669
888
  // Capability found, proceed
670
889
  if (!capability.skipAudit) {
671
890
  await this._appendDecisionAudit(request, AuditOutcome.ALLOWED, "dispatch authorized", {
672
- secretAlias: record?.alias.value ?? request.secretAlias,
891
+ secretAlias: record?.alias.value,
673
892
  secretId: record?.secretId.value,
674
893
  });
675
894
  }
@@ -720,9 +939,11 @@ export class VaultCore {
720
939
  secretAlias: record.alias.value,
721
940
  secretId: record.secretId.value,
722
941
  }));
942
+ await this._recordRequestExecution(request, authorization.capability, result);
723
943
  return {
724
944
  ...result,
725
945
  vaultId: this._deps.vaultId,
946
+ responseBody: undefined,
726
947
  };
727
948
  }
728
949
  async ownerReadAudit(actor, query, request) {
@@ -766,18 +987,16 @@ export class VaultCore {
766
987
  }
767
988
  }
768
989
  isCapabilityMatch(capability, request, secretId) {
769
- // Match either alias- or id-based capability grants when a secret is specified.
770
- if (request.secretAlias) {
771
- const aliasMatched = capability.secretAliases?.includes(request.secretAlias) ?? false;
772
- const idMatched = secretId ? (capability.secretIds?.includes(secretId) ?? false) : false;
773
- if (!aliasMatched && !idMatched) {
990
+ if (request.secretId) {
991
+ const idMatched = secretId ? (capability.write.secretIds?.includes(secretId) ?? false) : false;
992
+ if (!idMatched) {
774
993
  return false;
775
994
  }
776
995
  }
777
- if (request.method && capability.methods?.length > 0 && !capability.methods.includes(request.method)) {
996
+ if (request.method && capability.write.methods?.length > 0 && !capability.write.methods.includes(request.method)) {
778
997
  return false;
779
998
  }
780
- if (capability.scope && !isScopeMatch(capability.scope, request.targetUrl)) {
999
+ if (capability.write.scope && !isScopeMatch(capability.write.scope, request.targetUrl)) {
781
1000
  return false;
782
1001
  }
783
1002
  return true;
@@ -791,7 +1010,7 @@ export class VaultCore {
791
1010
  }
792
1011
  async ownerListCapabilities(actor, agentId, request) {
793
1012
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
794
- .filter((state) => state.status === "GRANTED")
1013
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
795
1014
  .map((state) => this._stateToGrantedCapability(state));
796
1015
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_CAPABILITIES, AuditOutcome.ALLOWED, "capabilities listed", {
797
1016
  requestId: request?.requestId,
@@ -799,6 +1018,28 @@ export class VaultCore {
799
1018
  }));
800
1019
  return capabilities;
801
1020
  }
1021
+ async ownerListRequests(actor, agentId, request) {
1022
+ const records = await this._deps.requests.list(this._deps.vaultId, agentId);
1023
+ const states = await this._deps.capabilityStates.list(this._deps.vaultId, agentId);
1024
+ const stateByRequestId = new Map(states.filter((state) => state.requestId).map((state) => [state.requestId, state]));
1025
+ await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_REQUESTS, AuditOutcome.ALLOWED, "request records listed", {
1026
+ requestId: request?.requestId,
1027
+ agentId,
1028
+ }));
1029
+ return records.map((record) => this.toOwnerVisibleRequestRecord(record, stateByRequestId.get(record.requestId) ?? null));
1030
+ }
1031
+ async ownerGetRequest(actor, targetRequestId, request) {
1032
+ const record = await this._deps.requests.get(this._deps.vaultId, targetRequestId);
1033
+ if (!record) {
1034
+ throw new VaultCoreError("request record not found", "VAULT_READ_DENIED");
1035
+ }
1036
+ const state = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, targetRequestId);
1037
+ await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.READ_REQUEST, AuditOutcome.ALLOWED, "request record read", {
1038
+ requestId: request?.requestId,
1039
+ agentId: record.agentId,
1040
+ }));
1041
+ return this.toOwnerRequestRecord(record, state);
1042
+ }
802
1043
  async ownerListSecrets(actor, request) {
803
1044
  const records = await this._deps.secrets.list(this._deps.vaultId);
804
1045
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.READ_AUDIT, AuditOutcome.ALLOWED, "secret metadata listed", {
@@ -808,6 +1049,8 @@ export class VaultCore {
808
1049
  vaultId: record.vaultId,
809
1050
  secretId: record.secretId,
810
1051
  alias: record.alias,
1052
+ version: record.version,
1053
+ lifecycleStatus: record.lifecycleStatus ?? "ACTIVE",
811
1054
  issuerId: record.issuerId,
812
1055
  source: record.source,
813
1056
  createdAt: record.createdAt,
@@ -828,6 +1071,35 @@ export class VaultCore {
828
1071
  await this._verifyAgentControlProof(request, "list_secrets");
829
1072
  return this._listVisibleSecretsForAgent(request.agent.id);
830
1073
  }
1074
+ async agentListRequests(request) {
1075
+ if (request.vaultId.value !== this._deps.vaultId.value) {
1076
+ throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
1077
+ }
1078
+ await this._verifyAgentControlProof(request, "list_requests");
1079
+ const records = await this._deps.requests.list(this._deps.vaultId, request.agent.id);
1080
+ const states = await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id);
1081
+ const stateByRequestId = new Map(states.filter((state) => state.requestId).map((state) => [state.requestId, state]));
1082
+ return records.map((record) => this.toVisibleRequestRecord(record, stateByRequestId.get(record.requestId) ?? null));
1083
+ }
1084
+ async agentGetRequest(request) {
1085
+ if (request.vaultId.value !== this._deps.vaultId.value) {
1086
+ throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
1087
+ }
1088
+ await this._verifyAgentControlProof(request, "read_request_result", { targetRequestId: request.targetRequestId });
1089
+ const record = await this._deps.requests.get(this._deps.vaultId, request.targetRequestId);
1090
+ if (!record || record.agentId !== request.agent.id) {
1091
+ throw new VaultCoreError("request record not found", "VAULT_READ_DENIED");
1092
+ }
1093
+ const state = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, request.targetRequestId);
1094
+ const readApproved = state?.actions.read.status === "APPROVED";
1095
+ return {
1096
+ requestId: record.requestId,
1097
+ executionStatus: record.execution.status,
1098
+ responseStatus: record.response?.status,
1099
+ responseBody: readApproved ? record.response?.body : undefined,
1100
+ error: record.response?.error,
1101
+ };
1102
+ }
831
1103
  async agentGetRuntimeManifest(command) {
832
1104
  if (command.vaultId.value !== this._deps.vaultId.value) {
833
1105
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
@@ -860,10 +1132,9 @@ export class VaultCore {
860
1132
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
861
1133
  }
862
1134
  await this._verifyAgentControlProof(command, "submit_capability_request", {
863
- scope: command.scope.scope,
864
- methods: command.scope.methods,
865
- operation: command.scope.operation,
866
- secretAliases: command.scope.secretAliases ?? [],
1135
+ write: command.capability.write,
1136
+ read: command.capability.read,
1137
+ operation: command.capability.operation,
867
1138
  justification: command.justification ?? null,
868
1139
  });
869
1140
  return this.ownerSubmitCapabilityRequest({
@@ -871,7 +1142,7 @@ export class VaultCore {
871
1142
  requestId: command.requestId,
872
1143
  requester: command.agent,
873
1144
  agentId: command.agent.id,
874
- scope: command.scope,
1145
+ capability: command.capability,
875
1146
  justification: command.justification,
876
1147
  requestedAt: command.requestedAt,
877
1148
  });
@@ -881,10 +1152,14 @@ export class VaultCore {
881
1152
  if (!existing) {
882
1153
  throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
883
1154
  }
1155
+ const decidedAt = this._deps.clock.nowIso();
884
1156
  await this._deps.capabilityStates.upsert({
885
1157
  ...existing,
886
- status: "REJECTED",
887
- decidedAt: this._deps.clock.nowIso(),
1158
+ decidedAt,
1159
+ actions: {
1160
+ write: { action: "write", status: "REJECTED", decidedAt },
1161
+ read: { action: "read", status: "REJECTED", decidedAt },
1162
+ },
888
1163
  });
889
1164
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REVOKE_CAPABILITY, AuditOutcome.SUCCEEDED, "capability revoked", {
890
1165
  requestId: command.requestId,
@@ -936,29 +1211,110 @@ export class VaultCore {
936
1211
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
937
1212
  }
938
1213
  return (await this._deps.capabilityStates.list(command.vaultId, command.agentId))
939
- .filter((state) => !command.status || state.status === command.status);
1214
+ .filter((state) => !command.writeStatus || state.actions.write.status === command.writeStatus)
1215
+ .filter((state) => !command.readStatus || state.actions.read.status === command.readStatus);
940
1216
  }
941
- async ownerExecuteCapabilityStateOnce(command) {
1217
+ async ownerApproveCapabilityWrite(command) {
1218
+ if (command.vaultId.value !== this._deps.vaultId.value) {
1219
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1220
+ }
1221
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1222
+ if (!pending) {
1223
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1224
+ }
1225
+ if (pending.actions.write.status !== "PENDING") {
1226
+ throw new VaultCoreError("write approval not pending", "VAULT_WRITE_DENIED");
1227
+ }
1228
+ const decidedAt = this._deps.clock.nowIso();
1229
+ const next = {
1230
+ ...pending,
1231
+ decidedAt,
1232
+ actions: {
1233
+ ...pending.actions,
1234
+ write: {
1235
+ action: "write",
1236
+ status: "APPROVED",
1237
+ decidedAt,
1238
+ },
1239
+ },
1240
+ };
1241
+ await this._deps.capabilityStates.upsert(next);
1242
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_WRITE, AuditOutcome.SUCCEEDED, `approved write policy for capability request ${command.requestId}`, {
1243
+ requestId: command.requestId,
1244
+ agentId: pending.agentId,
1245
+ operation: pending.operation,
1246
+ }));
1247
+ return next;
1248
+ }
1249
+ async ownerApproveCapabilityRead(command) {
1250
+ if (command.vaultId.value !== this._deps.vaultId.value) {
1251
+ throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1252
+ }
1253
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1254
+ if (!pending) {
1255
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1256
+ }
1257
+ if (pending.actions.write.status !== "APPROVED") {
1258
+ throw new VaultCoreError("write approval required before read approval", "VAULT_WRITE_DENIED");
1259
+ }
1260
+ if (pending.actions.read.status !== "PENDING") {
1261
+ throw new VaultCoreError("read approval not pending", "VAULT_WRITE_DENIED");
1262
+ }
1263
+ const decidedAt = this._deps.clock.nowIso();
1264
+ const next = {
1265
+ ...pending,
1266
+ decidedAt,
1267
+ actions: {
1268
+ ...pending.actions,
1269
+ read: {
1270
+ action: "read",
1271
+ status: "APPROVED",
1272
+ decidedAt,
1273
+ },
1274
+ },
1275
+ };
1276
+ await this._deps.capabilityStates.upsert(next);
1277
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `approved read policy for capability request ${command.requestId}`, {
1278
+ requestId: command.requestId,
1279
+ agentId: pending.agentId,
1280
+ operation: pending.operation,
1281
+ }));
1282
+ return next;
1283
+ }
1284
+ async ownerAllowOnce(command) {
942
1285
  return this._executePendingCapabilityState(command, "once");
943
1286
  }
944
- async ownerExecuteCapabilityStateAndGrant(command) {
1287
+ async ownerAllowAlways(command) {
945
1288
  return this._executePendingCapabilityState(command, "grant");
946
1289
  }
947
- async ownerRejectCapabilityState(command) {
1290
+ async ownerDeny(command) {
948
1291
  if (command.vaultId.value !== this._deps.vaultId.value) {
949
1292
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
950
1293
  }
951
1294
  const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
952
- if (!pending || pending.status !== "PENDING") {
953
- throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
1295
+ if (!pending) {
1296
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1297
+ }
1298
+ const decidedAt = this._deps.clock.nowIso();
1299
+ const rejectWrite = pending.actions.write.status === "PENDING";
1300
+ const rejectRead = !rejectWrite && pending.actions.read.status === "PENDING";
1301
+ if (!rejectWrite && !rejectRead) {
1302
+ throw new VaultCoreError("no capability action approval is pending", "VAULT_WRITE_DENIED");
954
1303
  }
955
1304
  const rejectedState = {
956
1305
  ...pending,
957
- status: "REJECTED",
958
- decidedAt: this._deps.clock.nowIso(),
1306
+ decidedAt,
1307
+ actions: {
1308
+ write: rejectWrite
1309
+ ? { action: "write", status: "REJECTED", decidedAt }
1310
+ : { ...pending.actions.write },
1311
+ read: rejectRead
1312
+ ? { action: "read", status: "REJECTED", decidedAt }
1313
+ : { ...pending.actions.read },
1314
+ },
959
1315
  };
960
1316
  await this._deps.capabilityStates.upsert(rejectedState);
961
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
1317
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, rejectWrite ? AuditAction.REJECT_CAPABILITY_WRITE : AuditAction.REJECT_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
962
1318
  requestId: command.requestId,
963
1319
  agentId: pending.agentId,
964
1320
  operation: pending.operation,
@@ -967,6 +1323,9 @@ export class VaultCore {
967
1323
  }
968
1324
  }
969
1325
  export function createVaultCore(deps) {
970
- return new VaultCore(deps);
1326
+ return new VaultCore({
1327
+ ...deps,
1328
+ requests: deps.requests ?? new InMemoryRequestRecordRegistry(),
1329
+ });
971
1330
  }
972
1331
  //# sourceMappingURL=core.js.map