@the-ai-company/cbio-node-runtime 1.62.0 → 1.62.2

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 (121) hide show
  1. package/README.md +8 -15
  2. package/dist/clients/agent/client.js +1 -4
  3. package/dist/clients/agent/client.js.map +1 -1
  4. package/dist/clients/owner/client.d.ts +0 -1
  5. package/dist/clients/owner/client.js +5 -23
  6. package/dist/clients/owner/client.js.map +1 -1
  7. package/dist/clients/owner/contracts.d.ts +2 -3
  8. package/dist/vault-core/contracts.d.ts +18 -30
  9. package/dist/vault-core/contracts.js.map +1 -1
  10. package/dist/vault-core/core.d.ts +1 -1
  11. package/dist/vault-core/core.js +80 -187
  12. package/dist/vault-core/core.js.map +1 -1
  13. package/dist/vault-core/index.d.ts +1 -1
  14. package/dist/vault-core/index.js.map +1 -1
  15. package/dist/vault-core/read-policy.d.ts +2 -0
  16. package/dist/vault-core/read-policy.js +40 -0
  17. package/dist/vault-core/read-policy.js.map +1 -0
  18. package/dist/vault-core/tool-metadata.js +1 -1
  19. package/dist/vault-core/tool-metadata.js.map +1 -1
  20. package/dist/vault-ingress/index.d.ts +2 -8
  21. package/dist/vault-ingress/index.js +12 -68
  22. package/dist/vault-ingress/index.js.map +1 -1
  23. package/dist/vault-ingress/remote-transport.js +1 -4
  24. package/dist/vault-ingress/remote-transport.js.map +1 -1
  25. package/docs/REFERENCE.md +3 -6
  26. package/docs/api/README.md +2 -2
  27. package/docs/api/classes/IdentityError.md +1 -1
  28. package/docs/api/classes/OwnerClientError.md +1 -1
  29. package/docs/api/classes/VaultCore.md +1 -17
  30. package/docs/api/classes/VaultCoreError.md +1 -1
  31. package/docs/api/enumerations/IdentityErrorCode.md +1 -1
  32. package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
  33. package/docs/api/functions/createAgentClient.md +1 -1
  34. package/docs/api/functions/createIdentity.md +1 -1
  35. package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
  36. package/docs/api/functions/createOwnerSession.md +1 -1
  37. package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
  38. package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
  39. package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
  40. package/docs/api/functions/createVault.md +1 -1
  41. package/docs/api/functions/createVaultClient.md +1 -1
  42. package/docs/api/functions/createVaultCore.md +1 -1
  43. package/docs/api/functions/createVaultCoreDependencies.md +1 -1
  44. package/docs/api/functions/createVaultService.md +1 -1
  45. package/docs/api/functions/createWorkspaceStorage.md +1 -1
  46. package/docs/api/functions/deriveIdentityId.md +1 -1
  47. package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
  48. package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
  49. package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
  50. package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
  51. package/docs/api/functions/initializeVaultCustody.md +1 -1
  52. package/docs/api/functions/listVaults.md +1 -1
  53. package/docs/api/functions/readVaultProfile.md +1 -1
  54. package/docs/api/functions/recoverVault.md +1 -1
  55. package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
  56. package/docs/api/functions/restoreIdentity.md +1 -1
  57. package/docs/api/functions/updateVaultMetadata.md +1 -1
  58. package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
  59. package/docs/api/functions/writeVaultProfile.md +1 -1
  60. package/docs/api/interfaces/AgentClient.md +1 -1
  61. package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
  62. package/docs/api/interfaces/AgentDispatchTransport.md +1 -1
  63. package/docs/api/interfaces/AgentIdentity.md +1 -1
  64. package/docs/api/interfaces/AgentSigner.md +1 -1
  65. package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +1 -1
  66. package/docs/api/interfaces/CbioRuntime.md +1 -1
  67. package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
  68. package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
  69. package/docs/api/interfaces/CreateOwnerSessionOptions.md +1 -1
  70. package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
  71. package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
  72. package/docs/api/interfaces/CreateVaultOptions.md +1 -1
  73. package/docs/api/interfaces/CreatedVault.md +1 -1
  74. package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
  75. package/docs/api/interfaces/IStorageProvider.md +1 -1
  76. package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
  77. package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
  78. package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
  79. package/docs/api/interfaces/OwnerCreateSecretInput.md +1 -1
  80. package/docs/api/interfaces/OwnerRemoveSecretInput.md +1 -1
  81. package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
  82. package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
  83. package/docs/api/interfaces/OwnerSession.md +1 -1
  84. package/docs/api/interfaces/OwnerUpdateSecretInput.md +1 -1
  85. package/docs/api/interfaces/RecoverVaultOptions.md +1 -1
  86. package/docs/api/interfaces/RecoveredVault.md +1 -1
  87. package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
  88. package/docs/api/interfaces/Signer.md +1 -1
  89. package/docs/api/interfaces/VaultApproveCapabilityRequestInput.md +1 -1
  90. package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
  91. package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
  92. package/docs/api/interfaces/VaultClient.md +1 -17
  93. package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
  94. package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
  95. package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
  96. package/docs/api/interfaces/VaultGrantCapabilityInput.md +1 -1
  97. package/docs/api/interfaces/VaultGrantCapabilityRequest.md +1 -1
  98. package/docs/api/interfaces/VaultIdentity.md +1 -1
  99. package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
  100. package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
  101. package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
  102. package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
  103. package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
  104. package/docs/api/interfaces/VaultMetadata.md +1 -1
  105. package/docs/api/interfaces/VaultObject.md +1 -1
  106. package/docs/api/interfaces/VaultProfile.md +1 -1
  107. package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
  108. package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
  109. package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
  110. package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
  111. package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
  112. package/docs/api/interfaces/VaultSigner.md +1 -1
  113. package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +1 -1
  114. package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
  115. package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
  116. package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
  117. package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
  118. package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +1 -1
  119. package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
  120. package/docs/zh/README.md +5 -7
  121. package/package.json +1 -1
@@ -3,67 +3,8 @@ import { VaultCoreError } from "./errors.js";
3
3
  import { verifySignature } from "../protocol/crypto.js";
4
4
  import { getAgentToolbox } from "./tool-metadata.js";
5
5
  import { InMemoryRequestRecordRegistry } from "./defaults.js";
6
+ import { applyResponseReadPolicy } from "./read-policy.js";
6
7
  const VAULT_MASTER_ID = "vault-master";
7
- function redactResponseShapeValue(value) {
8
- if (value === null || value === undefined) {
9
- return null;
10
- }
11
- if (Array.isArray(value)) {
12
- return value.map((entry) => redactResponseShapeValue(entry));
13
- }
14
- if (typeof value === "object") {
15
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, redactResponseShapeValue(entry)]));
16
- }
17
- return null;
18
- }
19
- function applyResponseReadPolicy(body, policy) {
20
- if (body === undefined)
21
- return body;
22
- if (policy.mode === "full")
23
- return body;
24
- if (policy.mode === "none")
25
- return undefined;
26
- let parsed;
27
- try {
28
- parsed = JSON.parse(body);
29
- }
30
- catch {
31
- return policy.mode === "shape_only" ? JSON.stringify(null) : undefined;
32
- }
33
- if (policy.mode === "shape_only") {
34
- return JSON.stringify(redactResponseShapeValue(parsed));
35
- }
36
- if (policy.mode !== "custom") {
37
- return body;
38
- }
39
- const result = {};
40
- for (const path of policy.paths ?? []) {
41
- const segments = path.split(".").filter(Boolean);
42
- if (!segments.length)
43
- continue;
44
- let source = parsed;
45
- let valid = true;
46
- for (const segment of segments) {
47
- if (source && typeof source === "object" && segment in source) {
48
- source = source[segment];
49
- }
50
- else {
51
- valid = false;
52
- break;
53
- }
54
- }
55
- if (!valid)
56
- continue;
57
- let target = result;
58
- for (let index = 0; index < segments.length - 1; index += 1) {
59
- const segment = segments[index];
60
- target[segment] ??= {};
61
- target = target[segment];
62
- }
63
- target[segments[segments.length - 1]] = source;
64
- }
65
- return JSON.stringify(result);
66
- }
67
8
  function toAuditEntry(deps, actor, action, outcome, detail, options) {
68
9
  return {
69
10
  entryId: deps.ids.newAuditEntryId(),
@@ -176,14 +117,7 @@ export class VaultCore {
176
117
  scope: state.write.scope,
177
118
  methods: [...state.write.methods],
178
119
  },
179
- read: state.actions.read.status === "APPROVED"
180
- ? {
181
- mode: state.read.mode,
182
- paths: state.read.paths ? [...state.read.paths] : undefined,
183
- }
184
- : {
185
- mode: "none",
186
- },
120
+ read: { paths: [...(state.readGrant ?? [])] },
187
121
  issuedAt: state.issuedAt ?? state.requestedAt,
188
122
  expiresAt: state.expiresAt,
189
123
  rateLimit: state.rateLimit,
@@ -204,26 +138,35 @@ export class VaultCore {
204
138
  methods: [...state.write.methods],
205
139
  },
206
140
  read: {
207
- mode: state.read.mode,
208
- paths: state.read.paths ? [...state.read.paths] : undefined,
141
+ paths: [...state.read.paths],
209
142
  },
210
143
  issuedAt: state.issuedAt,
211
144
  requestedAt: state.requestedAt,
212
145
  expiresAt: state.expiresAt,
213
146
  rateLimit: state.rateLimit,
214
147
  skipAudit: state.skipAudit,
148
+ writeGrant: state.writeGrant,
149
+ writeGrantedAt: state.writeGrantedAt,
150
+ readGrant: state.readGrant ? [...state.readGrant] : null,
151
+ readGrantedAt: state.readGrantedAt,
215
152
  reason: state.reason,
216
153
  secretId: state.secretId,
217
154
  targetUrl: state.targetUrl,
218
- actions: {
219
- write: { ...state.actions.write },
220
- read: { ...state.actions.read },
221
- },
222
155
  }));
223
156
  }
224
157
  _isExecutablePendingState(state) {
225
158
  return !!(state.requestId && state.targetUrl && state.proof);
226
159
  }
160
+ async _resolveRequestState(record) {
161
+ const byRequestId = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, record.requestId);
162
+ if (byRequestId) {
163
+ return byRequestId;
164
+ }
165
+ if (record.capabilityId) {
166
+ return this._deps.capabilityStates.getByCapabilityId(this._deps.vaultId, record.agentId, record.capabilityId);
167
+ }
168
+ return null;
169
+ }
227
170
  async _executePendingCapabilityState(command, mode) {
228
171
  if (command.vaultId.value !== this._deps.vaultId.value) {
229
172
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
@@ -232,8 +175,8 @@ export class VaultCore {
232
175
  if (!pending) {
233
176
  throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
234
177
  }
235
- if (pending.actions.write.status !== "APPROVED") {
236
- throw new VaultCoreError("write approval required before execution", "VAULT_WRITE_DENIED");
178
+ if (pending.writeGrant !== "once" && pending.writeGrant !== "always") {
179
+ throw new VaultCoreError("write grant required before execution", "VAULT_WRITE_DENIED");
237
180
  }
238
181
  if (mode === "once" && pending.source !== "dispatch_discovery") {
239
182
  throw new VaultCoreError("one-time execution is only available for dispatch discovery requests", "VAULT_WRITE_DENIED");
@@ -250,10 +193,7 @@ export class VaultCore {
250
193
  scope: pending.targetUrl ?? pending.write.scope,
251
194
  methods: [...pending.write.methods],
252
195
  },
253
- read: {
254
- mode: pending.read.mode,
255
- paths: pending.read.paths ? [...pending.read.paths] : undefined,
256
- },
196
+ read: { paths: [...(pending.readGrant ?? [])] },
257
197
  issuedAt,
258
198
  expiresAt: pending.expiresAt,
259
199
  rateLimit: pending.rateLimit,
@@ -296,6 +236,8 @@ export class VaultCore {
296
236
  source: "owner_grant",
297
237
  issuedAt,
298
238
  decidedAt: issuedAt,
239
+ writeGrant: "always",
240
+ writeGrantedAt: pending.writeGrantedAt ?? issuedAt,
299
241
  });
300
242
  }
301
243
  else {
@@ -353,7 +295,7 @@ export class VaultCore {
353
295
  }
354
296
  async _listVisibleSecretsForAgent(agentId) {
355
297
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
356
- .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
298
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.writeGrant === "always")
357
299
  .map((state) => this._stateToGrantedCapability(state));
358
300
  const capabilityMap = new Map();
359
301
  for (const capability of capabilities) {
@@ -367,8 +309,7 @@ export class VaultCore {
367
309
  methods: [...capability.write.methods],
368
310
  },
369
311
  read: {
370
- mode: capability.read.mode,
371
- paths: capability.read.paths ? [...capability.read.paths] : undefined,
312
+ paths: [...capability.read.paths],
372
313
  },
373
314
  });
374
315
  capabilityMap.set(secretId, existing);
@@ -419,7 +360,7 @@ export class VaultCore {
419
360
  });
420
361
  }
421
362
  toVisibleRequestRecord(record, state) {
422
- const readStatus = state?.actions.read.status ?? "PENDING";
363
+ const readGrant = state?.readGrant ?? null;
423
364
  return {
424
365
  requestId: record.requestId,
425
366
  createdAt: record.createdAt,
@@ -431,9 +372,9 @@ export class VaultCore {
431
372
  executionStatus: record.execution.status,
432
373
  responseStatus: record.response?.status,
433
374
  error: record.response?.error,
434
- readStatus,
375
+ readGrant,
435
376
  hasResponseBody: typeof record.response?.body === "string" && record.response.body.length > 0,
436
- resultVisible: readStatus === "APPROVED",
377
+ resultVisible: typeof record.response?.body === "string" && record.response.body.length > 0,
437
378
  };
438
379
  }
439
380
  toOwnerVisibleRequestRecord(record, state) {
@@ -449,8 +390,8 @@ export class VaultCore {
449
390
  executionStatus: record.execution.status,
450
391
  responseStatus: record.response?.status,
451
392
  error: record.response?.error,
452
- writeStatus: state?.actions.write.status ?? "PENDING",
453
- readStatus: state?.actions.read.status ?? "PENDING",
393
+ writeGrant: state?.writeGrant ?? null,
394
+ readGrant: state?.readGrant ?? null,
454
395
  hasResponseBody: typeof record.response?.body === "string" && record.response.body.length > 0,
455
396
  };
456
397
  }
@@ -477,10 +418,10 @@ export class VaultCore {
477
418
  error: record.response.error,
478
419
  }
479
420
  : undefined,
480
- actions: {
481
- write: state?.actions.write ?? { action: "write", status: "PENDING" },
482
- read: state?.actions.read ?? { action: "read", status: "PENDING" },
483
- },
421
+ writeGrant: state?.writeGrant ?? null,
422
+ writeGrantedAt: state?.writeGrantedAt,
423
+ readGrant: state?.readGrant ?? null,
424
+ readGrantedAt: state?.readGrantedAt,
484
425
  executionStatus: record.execution.status,
485
426
  };
486
427
  }
@@ -557,10 +498,10 @@ export class VaultCore {
557
498
  source: "owner_grant",
558
499
  requestId: undefined,
559
500
  requestedAt: command.capability.issuedAt,
560
- actions: {
561
- write: { action: "write", status: "APPROVED", decidedAt: command.capability.issuedAt },
562
- read: { action: "read", status: "APPROVED", decidedAt: command.capability.issuedAt },
563
- },
501
+ writeGrant: "always",
502
+ writeGrantedAt: command.capability.issuedAt,
503
+ readGrant: [...command.capability.read.paths],
504
+ readGrantedAt: command.capability.issuedAt,
564
505
  });
565
506
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CAPABILITY, AuditOutcome.SUCCEEDED, `capability registered: ${command.capability.capabilityId}`, {
566
507
  capabilityId: command.capability.capabilityId,
@@ -601,18 +542,15 @@ export class VaultCore {
601
542
  methods: [...command.capability.write.methods],
602
543
  },
603
544
  read: {
604
- mode: command.capability.read.mode,
605
- paths: command.capability.read.paths ? [...command.capability.read.paths] : undefined,
545
+ paths: [...command.capability.read.paths],
606
546
  },
607
547
  rateLimit: command.capability.rateLimit,
608
548
  skipAudit: command.capability.skipAudit,
609
549
  expiresAt: command.capability.expiresAt,
610
550
  reason: command.reason,
611
551
  requestedAt: command.requestedAt,
612
- actions: {
613
- write: { action: "write", status: "PENDING" },
614
- read: { action: "read", status: "PENDING" },
615
- },
552
+ writeGrant: null,
553
+ readGrant: null,
616
554
  };
617
555
  await this._deps.capabilityStates.upsert(pendingRecord);
618
556
  for (const observer of this._capabilityStateObservers) {
@@ -635,7 +573,7 @@ export class VaultCore {
635
573
  throw new VaultCoreError("capability lookup vault mismatch", "VAULT_IDENTITY_DENIED");
636
574
  }
637
575
  const state = await this._deps.capabilityStates.getByCapabilityId(vaultId, agentId, capabilityId);
638
- return state && state.capabilityId && state.issuedAt && state.actions.write.status === "APPROVED"
576
+ return state && state.capabilityId && state.issuedAt && state.writeGrant === "always"
639
577
  ? this._stateToGrantedCapability(state)
640
578
  : null;
641
579
  }
@@ -881,7 +819,7 @@ export class VaultCore {
881
819
  return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null };
882
820
  }
883
821
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id))
884
- .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
822
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.writeGrant === "always")
885
823
  .map((state) => this._stateToGrantedCapability(state));
886
824
  const requestedCapabilityId = request.capability?.capabilityId;
887
825
  const candidateCapabilities = requestedCapabilityId
@@ -903,7 +841,7 @@ export class VaultCore {
903
841
  methods: [request.method],
904
842
  },
905
843
  read: {
906
- mode: "none",
844
+ paths: [],
907
845
  },
908
846
  requestedAt: request.requestedAt,
909
847
  reason: request.reason,
@@ -912,10 +850,8 @@ export class VaultCore {
912
850
  headers: request.headers,
913
851
  body: request.body,
914
852
  proof: request.proof,
915
- actions: {
916
- write: { action: "write", status: "PENDING" },
917
- read: { action: "read", status: "PENDING" },
918
- },
853
+ writeGrant: null,
854
+ readGrant: null,
919
855
  };
920
856
  await this._deps.capabilityStates.upsert(pendingRecord);
921
857
  // Notify observers
@@ -1082,7 +1018,7 @@ export class VaultCore {
1082
1018
  }
1083
1019
  async ownerListCapabilities(actor, agentId, request) {
1084
1020
  const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
1085
- .filter((state) => !!state.capabilityId && !!state.issuedAt && state.actions.write.status === "APPROVED")
1021
+ .filter((state) => !!state.capabilityId && !!state.issuedAt && state.writeGrant === "always")
1086
1022
  .map((state) => this._stateToGrantedCapability(state));
1087
1023
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_CAPABILITIES, AuditOutcome.ALLOWED, "capabilities listed", {
1088
1024
  requestId: request?.requestId,
@@ -1092,20 +1028,18 @@ export class VaultCore {
1092
1028
  }
1093
1029
  async ownerListRequests(actor, agentId, request) {
1094
1030
  const records = await this._deps.requests.list(this._deps.vaultId, agentId);
1095
- const states = await this._deps.capabilityStates.list(this._deps.vaultId, agentId);
1096
- const stateByRequestId = new Map(states.filter((state) => state.requestId).map((state) => [state.requestId, state]));
1097
1031
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_REQUESTS, AuditOutcome.ALLOWED, "request records listed", {
1098
1032
  requestId: request?.requestId,
1099
1033
  agentId,
1100
1034
  }));
1101
- return records.map((record) => this.toOwnerVisibleRequestRecord(record, stateByRequestId.get(record.requestId) ?? null));
1035
+ return Promise.all(records.map(async (record) => this.toOwnerVisibleRequestRecord(record, await this._resolveRequestState(record))));
1102
1036
  }
1103
1037
  async ownerGetRequest(actor, targetRequestId, request) {
1104
1038
  const record = await this._deps.requests.get(this._deps.vaultId, targetRequestId);
1105
1039
  if (!record) {
1106
1040
  throw new VaultCoreError("request record not found", "VAULT_READ_DENIED");
1107
1041
  }
1108
- const state = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, targetRequestId);
1042
+ const state = await this._resolveRequestState(record);
1109
1043
  await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.READ_REQUEST, AuditOutcome.ALLOWED, "request record read", {
1110
1044
  requestId: request?.requestId,
1111
1045
  agentId: record.agentId,
@@ -1149,9 +1083,7 @@ export class VaultCore {
1149
1083
  }
1150
1084
  await this._verifyAgentControlProof(request, "list_requests");
1151
1085
  const records = await this._deps.requests.list(this._deps.vaultId, request.agent.id);
1152
- const states = await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id);
1153
- const stateByRequestId = new Map(states.filter((state) => state.requestId).map((state) => [state.requestId, state]));
1154
- return records.map((record) => this.toVisibleRequestRecord(record, stateByRequestId.get(record.requestId) ?? null));
1086
+ return Promise.all(records.map(async (record) => this.toVisibleRequestRecord(record, await this._resolveRequestState(record))));
1155
1087
  }
1156
1088
  async agentGetRequest(request) {
1157
1089
  if (request.vaultId.value !== this._deps.vaultId.value) {
@@ -1162,11 +1094,8 @@ export class VaultCore {
1162
1094
  if (!record || record.agentId !== request.agent.id) {
1163
1095
  throw new VaultCoreError("request record not found", "VAULT_READ_DENIED");
1164
1096
  }
1165
- const state = await this._deps.capabilityStates.getByRequestId(this._deps.vaultId, request.targetRequestId);
1166
- const readApproved = state?.actions.read.status === "APPROVED";
1167
- const responseBody = readApproved
1168
- ? applyResponseReadPolicy(record.response?.body, state?.read ?? { mode: "full" })
1169
- : undefined;
1097
+ const state = await this._resolveRequestState(record);
1098
+ const responseBody = applyResponseReadPolicy(record.response?.body, { paths: [...(state?.readGrant ?? [])] });
1170
1099
  return {
1171
1100
  requestId: record.requestId,
1172
1101
  executionStatus: record.execution.status,
@@ -1234,10 +1163,10 @@ export class VaultCore {
1234
1163
  await this._deps.capabilityStates.upsert({
1235
1164
  ...existing,
1236
1165
  decidedAt,
1237
- actions: {
1238
- write: { action: "write", status: "REJECTED", decidedAt },
1239
- read: { action: "read", status: "REJECTED", decidedAt },
1240
- },
1166
+ writeGrant: "none",
1167
+ writeGrantedAt: decidedAt,
1168
+ readGrant: [],
1169
+ readGrantedAt: decidedAt,
1241
1170
  });
1242
1171
  await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REVOKE_CAPABILITY, AuditOutcome.SUCCEEDED, "capability revoked", {
1243
1172
  requestId: command.requestId,
@@ -1289,10 +1218,10 @@ export class VaultCore {
1289
1218
  throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
1290
1219
  }
1291
1220
  return (await this._deps.capabilityStates.list(command.vaultId, command.agentId))
1292
- .filter((state) => !command.writeStatus || state.actions.write.status === command.writeStatus)
1293
- .filter((state) => !command.readStatus || state.actions.read.status === command.readStatus);
1221
+ .filter((state) => command.writeGranted === undefined || (command.writeGranted ? state.writeGrant != null : state.writeGrant == null))
1222
+ .filter((state) => command.readGranted === undefined || (command.readGranted ? state.readGrant != null : state.readGrant == null));
1294
1223
  }
1295
- async ownerApproveCapabilityWrite(command) {
1224
+ async ownerApproveCapabilityRead(command) {
1296
1225
  if (command.vaultId.value !== this._deps.vaultId.value) {
1297
1226
  throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1298
1227
  }
@@ -1300,75 +1229,43 @@ export class VaultCore {
1300
1229
  if (!pending) {
1301
1230
  throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1302
1231
  }
1303
- if (pending.actions.write.status !== "PENDING") {
1304
- throw new VaultCoreError("write approval not pending", "VAULT_WRITE_DENIED");
1232
+ if (pending.writeGrant == null) {
1233
+ throw new VaultCoreError("write decision required before read grant", "VAULT_WRITE_DENIED");
1234
+ }
1235
+ if (pending.readGrant != null) {
1236
+ throw new VaultCoreError("read grant already decided", "VAULT_WRITE_DENIED");
1305
1237
  }
1306
1238
  const decidedAt = this._deps.clock.nowIso();
1307
1239
  const next = {
1308
1240
  ...pending,
1241
+ readGrant: [...(command.read?.paths ?? [])],
1242
+ readGrantedAt: decidedAt,
1309
1243
  decidedAt,
1310
- actions: {
1311
- ...pending.actions,
1312
- write: {
1313
- action: "write",
1314
- status: "APPROVED",
1315
- decidedAt,
1316
- },
1317
- },
1318
1244
  };
1319
1245
  await this._deps.capabilityStates.upsert(next);
1320
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_WRITE, AuditOutcome.SUCCEEDED, `approved write policy for capability request ${command.requestId}`, {
1246
+ await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `approved read policy for capability request ${command.requestId}`, {
1321
1247
  requestId: command.requestId,
1322
1248
  agentId: pending.agentId,
1323
1249
  operation: pending.operation,
1324
1250
  }));
1325
1251
  return next;
1326
1252
  }
1327
- async ownerApproveCapabilityRead(command) {
1328
- if (command.vaultId.value !== this._deps.vaultId.value) {
1329
- throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
1330
- }
1253
+ async ownerAllowOnce(command) {
1331
1254
  const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1332
1255
  if (!pending) {
1333
1256
  throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1334
1257
  }
1335
- if (pending.actions.write.status !== "APPROVED") {
1336
- throw new VaultCoreError("write approval required before read approval", "VAULT_WRITE_DENIED");
1337
- }
1338
- if (pending.actions.read.status !== "PENDING") {
1339
- throw new VaultCoreError("read approval not pending", "VAULT_WRITE_DENIED");
1340
- }
1341
1258
  const decidedAt = this._deps.clock.nowIso();
1342
- const next = {
1343
- ...pending,
1344
- read: command.read
1345
- ? {
1346
- mode: command.read.mode,
1347
- paths: command.read.paths ? [...command.read.paths] : undefined,
1348
- }
1349
- : pending.read,
1350
- decidedAt,
1351
- actions: {
1352
- ...pending.actions,
1353
- read: {
1354
- action: "read",
1355
- status: "APPROVED",
1356
- decidedAt,
1357
- },
1358
- },
1359
- };
1360
- await this._deps.capabilityStates.upsert(next);
1361
- await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `approved read policy for capability request ${command.requestId}`, {
1362
- requestId: command.requestId,
1363
- agentId: pending.agentId,
1364
- operation: pending.operation,
1365
- }));
1366
- return next;
1367
- }
1368
- async ownerAllowOnce(command) {
1259
+ await this._deps.capabilityStates.upsert({ ...pending, writeGrant: "once", writeGrantedAt: decidedAt, decidedAt });
1369
1260
  return this._executePendingCapabilityState(command, "once");
1370
1261
  }
1371
1262
  async ownerAllowAlways(command) {
1263
+ const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
1264
+ if (!pending) {
1265
+ throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1266
+ }
1267
+ const decidedAt = this._deps.clock.nowIso();
1268
+ await this._deps.capabilityStates.upsert({ ...pending, writeGrant: "always", writeGrantedAt: decidedAt, decidedAt });
1372
1269
  return this._executePendingCapabilityState(command, "grant");
1373
1270
  }
1374
1271
  async ownerDeny(command) {
@@ -1380,22 +1277,18 @@ export class VaultCore {
1380
1277
  throw new VaultCoreError("capability action record not found", "VAULT_REQUEST_NOT_FOUND");
1381
1278
  }
1382
1279
  const decidedAt = this._deps.clock.nowIso();
1383
- const rejectWrite = pending.actions.write.status === "PENDING";
1384
- const rejectRead = !rejectWrite && pending.actions.read.status === "PENDING";
1280
+ const rejectWrite = pending.writeGrant == null;
1281
+ const rejectRead = !rejectWrite && pending.readGrant == null;
1385
1282
  if (!rejectWrite && !rejectRead) {
1386
1283
  throw new VaultCoreError("no capability action approval is pending", "VAULT_WRITE_DENIED");
1387
1284
  }
1388
1285
  const rejectedState = {
1389
1286
  ...pending,
1390
1287
  decidedAt,
1391
- actions: {
1392
- write: rejectWrite
1393
- ? { action: "write", status: "REJECTED", decidedAt }
1394
- : { ...pending.actions.write },
1395
- read: rejectRead
1396
- ? { action: "read", status: "REJECTED", decidedAt }
1397
- : { ...pending.actions.read },
1398
- },
1288
+ writeGrant: rejectWrite ? "none" : pending.writeGrant,
1289
+ writeGrantedAt: rejectWrite ? decidedAt : pending.writeGrantedAt,
1290
+ readGrant: rejectRead ? [] : pending.readGrant,
1291
+ readGrantedAt: rejectRead ? decidedAt : pending.readGrantedAt,
1399
1292
  };
1400
1293
  await this._deps.capabilityStates.upsert(rejectedState);
1401
1294
  await this._appendAudit(toAuditEntry(this._deps, command.owner, rejectWrite ? AuditAction.REJECT_CAPABILITY_WRITE : AuditAction.REJECT_CAPABILITY_READ, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {