@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.
- package/README.md +53 -30
- package/dist/clients/agent/client.d.ts +3 -1
- package/dist/clients/agent/client.js +41 -11
- package/dist/clients/agent/client.js.map +1 -1
- package/dist/clients/agent/contracts.d.ts +5 -2
- package/dist/clients/owner/client.d.ts +8 -10
- package/dist/clients/owner/client.js +45 -38
- package/dist/clients/owner/client.js.map +1 -1
- package/dist/clients/owner/contracts.d.ts +11 -25
- package/dist/clients/owner/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/vault-core/contracts.d.ts +133 -46
- package/dist/vault-core/contracts.js +4 -3
- package/dist/vault-core/contracts.js.map +1 -1
- package/dist/vault-core/core.d.ts +10 -5
- package/dist/vault-core/core.js +315 -160
- package/dist/vault-core/core.js.map +1 -1
- package/dist/vault-core/defaults.d.ts +8 -4
- package/dist/vault-core/defaults.js +37 -70
- package/dist/vault-core/defaults.js.map +1 -1
- package/dist/vault-core/index.d.ts +1 -1
- package/dist/vault-core/index.js.map +1 -1
- package/dist/vault-core/persistence.d.ts +11 -2
- package/dist/vault-core/persistence.js +37 -1
- package/dist/vault-core/persistence.js.map +1 -1
- package/dist/vault-core/ports.d.ts +7 -2
- package/dist/vault-core/tool-metadata.js +25 -8
- package/dist/vault-core/tool-metadata.js.map +1 -1
- package/dist/vault-ingress/defaults.d.ts +2 -0
- package/dist/vault-ingress/defaults.js +6 -0
- package/dist/vault-ingress/defaults.js.map +1 -1
- package/dist/vault-ingress/index.d.ts +39 -10
- package/dist/vault-ingress/index.js +142 -56
- package/dist/vault-ingress/index.js.map +1 -1
- package/dist/vault-ingress/remote-transport.d.ts +2 -0
- package/dist/vault-ingress/remote-transport.js +33 -4
- package/dist/vault-ingress/remote-transport.js.map +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/REFERENCE.md +36 -27
- package/docs/WORKS_WITH_CUSTOM_FETCH.md +2 -2
- package/docs/api/README.md +2 -4
- package/docs/api/classes/IdentityError.md +1 -1
- package/docs/api/classes/OwnerClientError.md +1 -1
- package/docs/api/classes/VaultCore.md +81 -33
- package/docs/api/classes/VaultCoreError.md +1 -1
- package/docs/api/enumerations/IdentityErrorCode.md +1 -1
- package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
- package/docs/api/functions/createAgentClient.md +1 -1
- package/docs/api/functions/createIdentity.md +1 -1
- package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
- package/docs/api/functions/createOwnerSession.md +1 -1
- package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
- package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
- package/docs/api/functions/createVault.md +1 -1
- package/docs/api/functions/createVaultClient.md +1 -1
- package/docs/api/functions/createVaultCore.md +1 -1
- package/docs/api/functions/createVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createVaultService.md +1 -1
- package/docs/api/functions/createWorkspaceStorage.md +1 -1
- package/docs/api/functions/deriveIdentityId.md +1 -1
- package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
- package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
- package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
- package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
- package/docs/api/functions/initializeVaultCustody.md +1 -1
- package/docs/api/functions/listVaults.md +1 -1
- package/docs/api/functions/readVaultProfile.md +1 -1
- package/docs/api/functions/recoverVault.md +1 -1
- package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
- package/docs/api/functions/restoreIdentity.md +1 -1
- package/docs/api/functions/updateVaultMetadata.md +1 -1
- package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
- package/docs/api/functions/writeVaultProfile.md +1 -1
- package/docs/api/interfaces/AgentClient.md +27 -1
- package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
- package/docs/api/interfaces/AgentDispatchTransport.md +33 -1
- package/docs/api/interfaces/AgentIdentity.md +1 -1
- package/docs/api/interfaces/AgentSigner.md +1 -1
- package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +9 -9
- package/docs/api/interfaces/CbioRuntime.md +1 -1
- package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
- package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
- package/docs/api/interfaces/CreateOwnerSessionOptions.md +1 -1
- package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
- package/docs/api/interfaces/CreateVaultOptions.md +1 -1
- package/docs/api/interfaces/CreatedVault.md +1 -1
- package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
- package/docs/api/interfaces/IStorageProvider.md +1 -1
- package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
- package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
- package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
- package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
- package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
- package/docs/api/interfaces/OwnerSession.md +1 -1
- package/docs/api/interfaces/OwnerStoreSecretInput.md +1 -1
- package/docs/api/interfaces/OwnerWriteSecretInput.md +1 -7
- package/docs/api/interfaces/RecoverVaultOptions.md +1 -1
- package/docs/api/interfaces/RecoveredVault.md +1 -1
- package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
- package/docs/api/interfaces/Signer.md +1 -1
- package/docs/api/interfaces/VaultApproveCapabilityRequestInput.md +1 -1
- package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
- package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
- package/docs/api/interfaces/VaultClient.md +58 -44
- package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
- package/docs/api/interfaces/VaultDeleteSecretInput.md +1 -1
- package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
- package/docs/api/interfaces/VaultGrantCapabilityInput.md +9 -21
- package/docs/api/interfaces/VaultGrantCapabilityRequest.md +1 -1
- package/docs/api/interfaces/VaultIdentity.md +1 -1
- package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
- package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
- package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
- package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
- package/docs/api/interfaces/VaultMetadata.md +1 -1
- package/docs/api/interfaces/VaultObject.md +1 -1
- package/docs/api/interfaces/VaultProfile.md +1 -1
- package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
- package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
- package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultSigner.md +1 -1
- package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +11 -17
- package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
- package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
- package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
- package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
- package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +1 -1
- package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
- package/docs/zh/README.md +26 -16
- package/examples/process-isolation.ts +7 -5
- package/package.json +1 -1
- package/docs/api/interfaces/OwnerDefineSecretTargetsInput.md +0 -23
- package/docs/api/interfaces/OwnerSecretTargetBinding.md +0 -35
package/dist/vault-core/core.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
144
|
-
throw new VaultCoreError("
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
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 === "
|
|
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
|
|
272
|
-
const existing = capabilityMap.get(
|
|
288
|
+
for (const secretId of capability.write.secretIds ?? []) {
|
|
289
|
+
const existing = capabilityMap.get(secretId) ?? [];
|
|
273
290
|
existing.push({
|
|
274
291
|
capabilityId: capability.capabilityId,
|
|
275
|
-
|
|
276
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
461
|
+
if (!command.capability.write.scope.trim()) {
|
|
394
462
|
throw new VaultCoreError("capability request scope required", "VAULT_IDENTITY_DENIED");
|
|
395
463
|
}
|
|
396
|
-
if (command.
|
|
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.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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.
|
|
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.
|
|
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("
|
|
519
|
+
throw new VaultCoreError("request template vault mismatch", "VAULT_IDENTITY_DENIED");
|
|
441
520
|
}
|
|
442
521
|
if (!command.flow.flowId.trim()) {
|
|
443
|
-
throw new VaultCoreError("
|
|
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("
|
|
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, `
|
|
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
|
|
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
|
|
561
|
+
requestId,
|
|
489
562
|
owner: actor,
|
|
490
563
|
alias,
|
|
491
564
|
plaintext,
|
|
492
|
-
|
|
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, `
|
|
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.
|
|
609
|
-
? await this._deps.secrets.
|
|
649
|
+
const record = request.secretId
|
|
650
|
+
? await this._deps.secrets.getById({ value: request.secretId })
|
|
610
651
|
: null;
|
|
611
|
-
if (request.
|
|
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
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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 === "
|
|
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
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
operation: command.
|
|
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
|
-
|
|
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
|
-
|
|
933
|
-
|
|
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.
|
|
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
|
|
1126
|
+
async ownerAllowOnce(command) {
|
|
988
1127
|
return this._executePendingCapabilityState(command, "once");
|
|
989
1128
|
}
|
|
990
|
-
async
|
|
1129
|
+
async ownerAllowAlways(command) {
|
|
991
1130
|
return this._executePendingCapabilityState(command, "grant");
|
|
992
1131
|
}
|
|
993
|
-
async
|
|
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
|
|
999
|
-
throw new VaultCoreError("
|
|
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
|
-
|
|
1004
|
-
|
|
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.
|
|
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(
|
|
1168
|
+
return new VaultCore({
|
|
1169
|
+
...deps,
|
|
1170
|
+
requests: deps.requests ?? new InMemoryRequestRecordRegistry(),
|
|
1171
|
+
});
|
|
1017
1172
|
}
|
|
1018
1173
|
//# sourceMappingURL=core.js.map
|