@treeseed/sdk 0.13.0-rc.82 → 0.13.0-rc.83
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/dist/.treeseed-build-complete.json +1 -1
- package/dist/agent-capacity/artifact-mutation-receipt.d.ts +1 -1
- package/dist/agent-capacity/contracts/capacity/assignments/assignment-records.d.ts +1 -1
- package/dist/agent-capacity/contracts/capacity/workdays/workday-records.d.ts +1 -1
- package/dist/agent-capacity/contracts/runtime/runtime-observability.d.ts +3 -3
- package/dist/agent-capacity/contracts/support/decision-work.d.ts +1 -1
- package/dist/configuration/secrets-capability.d.ts +1 -0
- package/dist/configuration/secrets-capability.js +1 -0
- package/dist/operator-contracts/catalog/services/service-vault-operations.d.ts +17 -0
- package/dist/operator-contracts/catalog/services/service-vault-operations.js +114 -0
- package/dist/operator-contracts/control-plane-operations.d.ts +15 -0
- package/dist/operator-contracts/control-plane-operations.js +5 -3
- package/dist/secrets-capability/service-provider-contracts.js +4 -4
- package/dist/secrets-capability/service-vault-contracts.d.ts +48 -1
- package/dist/secrets-capability/service-vault-contracts.js +36 -1
- package/dist/secrets-capability/service-vault-crypto.d.ts +20 -0
- package/dist/secrets-capability/service-vault-crypto.js +210 -0
- package/dist/types/agents/agent-trigger-kinds.d.ts +0 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"completedAt":"2026-09-
|
|
1
|
+
{"completedAt":"2026-09-03T07:04:23.401Z"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const ARTIFACT_MUTATION_RECEIPT_SCHEMA: "treeseed.artifact-mutation-receipt/v1";
|
|
2
2
|
export type ArtifactMutationKind = 'treedx-content' | 'source-checkpoint';
|
|
3
3
|
export type ArtifactMutationPhase = 'provisional' | 'integrated';
|
|
4
|
-
import type { AgentWorkExecutionMode, UpstreamMutationPolicy } from './contracts/support/execution-mode.js';
|
|
4
|
+
import type { AgentWorkExecutionMode, UpstreamMutationPolicy } from './contracts/support/authority/execution-mode.js';
|
|
5
5
|
export interface ArtifactMutationEvidence {
|
|
6
6
|
ref: string;
|
|
7
7
|
digest?: string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type AgentExecutionMode = 'planning' | 'acting';
|
|
2
|
-
|
|
2
|
+
import type { AgentWorkExecutionMode } from '../../support/authority/execution-mode.js';
|
|
3
3
|
export type ProviderAssignmentStatus = 'pending' | 'leased' | 'running' | 'completed' | 'failed' | 'returned' | 'expired' | 'cancelled';
|
|
4
4
|
export type ProviderAssignmentLeaseState = 'unleased' | 'leased' | 'released' | 'expired';
|
|
5
5
|
export type AgentModeRunStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Portable durable records shared by workday control-plane and operator consumers. */
|
|
2
2
|
import type { WorkdayAgentSelection } from '../../../workday.js';
|
|
3
|
-
import type { AgentWorkExecutionMode } from '../../support/execution-mode.js';
|
|
3
|
+
import type { AgentWorkExecutionMode } from '../../support/authority/execution-mode.js';
|
|
4
4
|
export type CapacityWorkdayRunStatus = 'queued' | 'running' | 'completed' | 'cancelled' | 'failed' | 'degraded';
|
|
5
5
|
export type CapacityWorkdayEventStatus = 'recorded' | 'active' | 'completed' | 'warning' | 'error' | 'failed';
|
|
6
6
|
export type CapacityWorkdayDemandSource = 'objective' | 'question' | 'proposal' | 'decision-review' | 'knowledge-gap' | 'release-readiness' | 'idle-intent' | 'planning-input' | 'capacity-plan' | 'assignment-completion' | 'assignment-blockage' | 'workday-summary' | 'handoff' | 'research-workflow';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { CapacityPageInfo } from '../../../capacity/capacity-core/capacity-pagination.js';
|
|
2
2
|
import type { ExecutionCapabilityDemand, ExecutionCapabilitySupply } from '../../../types/agents.js';
|
|
3
|
-
import type { AgentCapacityEnvelope, AgentExecutionMode, AgentModeRun, DecisionExecutionInput, ProviderAssignment, ProviderAssignmentSynthesisSource, TreeDxProxyHandle, WorkdayCapacityEnvelope } from '../capacity/assignments/assignment-records.js';
|
|
4
|
-
import type { AgentKernelPolicy, AgentKernelProfile } from '../projects/agents/project-agent-class.js';
|
|
3
|
+
import type { AgentCapacityEnvelope, AgentExecutionMode, AgentModeRun, AgentModeRunUsageSettlement, DecisionExecutionInput, ProviderAssignment, ProviderAssignmentSynthesisSource, TreeDxProxyHandle, WorkdayCapacityEnvelope } from '../capacity/assignments/assignment-records.js';
|
|
4
|
+
import type { AgentKernelPolicy, AgentKernelProfile, ProjectAgentClass } from '../projects/agents/project-agent-class.js';
|
|
5
5
|
import type { CapacityLedgerEntry, CapacityReservation, CapacityUsageActual } from '../support/financial-records.js';
|
|
6
|
-
import type { WorkdayCapacityEnvelopeRecord } from '../support/planning-records.js';
|
|
6
|
+
import type { DecisionPlanningStatus, WorkdayCapacityEnvelopeRecord } from '../support/planning-records.js';
|
|
7
7
|
export type AgentKernelModeExecutionStatus = 'completed' | 'waiting' | 'failed' | 'returned';
|
|
8
8
|
export type AgentKernelModeFallbackCode = 'assignment_missing_project_or_agent' | 'assignment_governance_provenance_missing' | 'assignment_lease_expired' | 'assignment_mode_not_allowed' | 'assignment_missing_capacity_envelope' | 'assignment_missing_decision_input' | 'assignment_handler_failed' | 'assignment_project_not_synced' | 'assignment_agent_not_found' | 'assignment_insufficient_capability' | 'assignment_decision_not_ready' | 'assignment_capacity_plan_not_accepted' | 'assignment_capacity_not_reserved' | 'assignment_capability_handle_invalid' | 'assignment_capability_handle_secret_material' | 'assignment_capability_handle_write_not_ready' | 'assignment_capability_handle_workspace_denied' | 'assignment_repository_ref_scope_invalid' | 'assignment_workflow_operation_denied' | 'assignment_eligibility_capability_mismatch' | 'assignment_retry_policy_exceeded' | 'assignment_treedx_proxy_scope_invalid' | 'assignment_fallback_quota_exceeded' | 'assignment_output_invalid';
|
|
9
9
|
export interface AgentCapacityPlan {
|
|
@@ -189,7 +189,7 @@ export interface DecisionAssignmentGraph {
|
|
|
189
189
|
edges: DecisionAssignmentGraphEdge[];
|
|
190
190
|
compiledAt?: string | null;
|
|
191
191
|
compiledBy: 'api-control-plane';
|
|
192
|
-
executionMode?: import('./execution-mode.js').AgentWorkExecutionMode;
|
|
192
|
+
executionMode?: import('./authority/execution-mode.js').AgentWorkExecutionMode;
|
|
193
193
|
metadata?: Record<string, unknown>;
|
|
194
194
|
}
|
|
195
195
|
export interface DecisionAssignmentGraphRecord extends DecisionAssignmentGraph {
|
|
@@ -2,3 +2,4 @@ export * from '../secrets-capability/service-provider-contracts.js';
|
|
|
2
2
|
export * from '../secrets-capability/provider-operation-contracts.js';
|
|
3
3
|
export * from '../secrets-capability/workflow-operation-contracts.js';
|
|
4
4
|
export * from '../secrets-capability/service-vault-contracts.js';
|
|
5
|
+
export * from '../secrets-capability/service-vault-crypto.js';
|
|
@@ -2,3 +2,4 @@ export * from "../secrets-capability/service-provider-contracts.js";
|
|
|
2
2
|
export * from "../secrets-capability/provider-operation-contracts.js";
|
|
3
3
|
export * from "../secrets-capability/workflow-operation-contracts.js";
|
|
4
4
|
export * from "../secrets-capability/service-vault-contracts.js";
|
|
5
|
+
export * from "../secrets-capability/service-vault-crypto.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const SERVICE_VAULT_OPERATIONS: {
|
|
2
|
+
readonly userVaultKey: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
3
|
+
readonly putUserVaultKey: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
4
|
+
readonly teamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
5
|
+
readonly initializeTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
6
|
+
readonly resetTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
7
|
+
readonly rotateTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
8
|
+
readonly vaultGrantCandidates: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
9
|
+
readonly createVaultGrant: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
10
|
+
readonly deleteVaultGrant: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
11
|
+
readonly vaultCredentialEnvelopes: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
12
|
+
readonly credentialEnvelopes: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
13
|
+
readonly putCredentialEnvelope: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
14
|
+
readonly createOperationLease: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
15
|
+
readonly operationLease: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
16
|
+
readonly putOperationLeasePayload: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineOperation } from "../../operation-builder.js";
|
|
3
|
+
const empty = z.object({}).strict();
|
|
4
|
+
const none = z.undefined();
|
|
5
|
+
const record = z.record(z.unknown());
|
|
6
|
+
const teamPath = z.object({ teamId: z.string().min(1) }).strict();
|
|
7
|
+
function vaultOperation(operationId, method, path, pathSchema, options = {}) {
|
|
8
|
+
const read = method === "GET", risk = options.risk ?? "ordinary";
|
|
9
|
+
return defineOperation({
|
|
10
|
+
operationId,
|
|
11
|
+
description: `${read ? "Read" : "Apply"} ${operationId}.`,
|
|
12
|
+
rest: { method, path },
|
|
13
|
+
capability: read ? "secrets.read" : "secrets.write",
|
|
14
|
+
...path.includes("{") ? { parameters: `treeseed.${operationId}.parameters/v1` } : {},
|
|
15
|
+
authentication: "oauth",
|
|
16
|
+
oauthScopes: read ? ["treeseed:read"] : ["treeseed:projects:write"],
|
|
17
|
+
kind: read ? "read" : "mutation",
|
|
18
|
+
riskClass: risk,
|
|
19
|
+
confirmation: risk === "ordinary" ? "never" : "input_required",
|
|
20
|
+
surfaces: ["rest"],
|
|
21
|
+
cacheScope: read ? "principal" : "none",
|
|
22
|
+
pagination: "none",
|
|
23
|
+
redactedPaths: options.redactedPaths
|
|
24
|
+
}, { path: pathSchema, query: read ? record : empty, body: read ? none : record, output: record });
|
|
25
|
+
}
|
|
26
|
+
const SERVICE_VAULT_OPERATIONS = {
|
|
27
|
+
userVaultKey: vaultOperation("services.vault.user.key.show", "GET", "/v1/users/me/vault-key", empty),
|
|
28
|
+
putUserVaultKey: vaultOperation(
|
|
29
|
+
"services.vault.user.key.put",
|
|
30
|
+
"PUT",
|
|
31
|
+
"/v1/users/me/vault-key",
|
|
32
|
+
empty,
|
|
33
|
+
{ risk: "credential", redactedPaths: ["body.encryptedPrivateKeyEnvelope"] }
|
|
34
|
+
),
|
|
35
|
+
teamVault: vaultOperation("services.vault.team.show", "GET", "/v1/teams/{teamId}/vault", teamPath),
|
|
36
|
+
initializeTeamVault: vaultOperation(
|
|
37
|
+
"services.vault.team.initialize",
|
|
38
|
+
"POST",
|
|
39
|
+
"/v1/teams/{teamId}/vault",
|
|
40
|
+
teamPath,
|
|
41
|
+
{ risk: "credential", redactedPaths: ["body.wrappedTeamVaultKey"] }
|
|
42
|
+
),
|
|
43
|
+
resetTeamVault: vaultOperation(
|
|
44
|
+
"services.vault.team.reset",
|
|
45
|
+
"POST",
|
|
46
|
+
"/v1/teams/{teamId}/vault/reset",
|
|
47
|
+
teamPath,
|
|
48
|
+
{ risk: "destructive", redactedPaths: ["body.wrappedTeamVaultKey", "body.currentPassword"] }
|
|
49
|
+
),
|
|
50
|
+
rotateTeamVault: vaultOperation(
|
|
51
|
+
"services.vault.team.rotate",
|
|
52
|
+
"POST",
|
|
53
|
+
"/v1/teams/{teamId}/vault/rotate",
|
|
54
|
+
teamPath,
|
|
55
|
+
{ risk: "credential", redactedPaths: ["body.envelopes", "body.grants"] }
|
|
56
|
+
),
|
|
57
|
+
vaultGrantCandidates: vaultOperation("services.vault.grant.candidates.list", "GET", "/v1/teams/{teamId}/vault/grant-candidates", teamPath),
|
|
58
|
+
createVaultGrant: vaultOperation(
|
|
59
|
+
"services.vault.grants.create",
|
|
60
|
+
"POST",
|
|
61
|
+
"/v1/teams/{teamId}/vault/grants",
|
|
62
|
+
teamPath,
|
|
63
|
+
{ risk: "credential", redactedPaths: ["body.wrappedTeamVaultKey"] }
|
|
64
|
+
),
|
|
65
|
+
deleteVaultGrant: vaultOperation(
|
|
66
|
+
"services.vault.grants.delete",
|
|
67
|
+
"DELETE",
|
|
68
|
+
"/v1/teams/{teamId}/vault/grants/{grantId}",
|
|
69
|
+
z.object({ teamId: z.string().min(1), grantId: z.string().min(1) }).strict(),
|
|
70
|
+
{ risk: "destructive" }
|
|
71
|
+
),
|
|
72
|
+
vaultCredentialEnvelopes: vaultOperation(
|
|
73
|
+
"services.vault.credential.envelopes.list",
|
|
74
|
+
"GET",
|
|
75
|
+
"/v1/teams/{teamId}/vault/credential-envelopes",
|
|
76
|
+
teamPath
|
|
77
|
+
),
|
|
78
|
+
credentialEnvelopes: vaultOperation(
|
|
79
|
+
"services.credential.envelopes.list",
|
|
80
|
+
"GET",
|
|
81
|
+
"/v1/teams/{teamId}/services/{connectionId}/credential-envelopes",
|
|
82
|
+
z.object({ teamId: z.string().min(1), connectionId: z.string().min(1) }).strict()
|
|
83
|
+
),
|
|
84
|
+
putCredentialEnvelope: vaultOperation(
|
|
85
|
+
"services.credential.envelopes.put",
|
|
86
|
+
"POST",
|
|
87
|
+
"/v1/teams/{teamId}/services/{connectionId}/credential-envelopes",
|
|
88
|
+
z.object({ teamId: z.string().min(1), connectionId: z.string().min(1) }).strict(),
|
|
89
|
+
{ risk: "credential", redactedPaths: ["body.envelope"] }
|
|
90
|
+
),
|
|
91
|
+
createOperationLease: vaultOperation(
|
|
92
|
+
"services.operation.leases.create",
|
|
93
|
+
"POST",
|
|
94
|
+
"/v1/teams/{teamId}/service-operation-leases",
|
|
95
|
+
teamPath,
|
|
96
|
+
{ risk: "authority" }
|
|
97
|
+
),
|
|
98
|
+
operationLease: vaultOperation(
|
|
99
|
+
"services.operation.leases.show",
|
|
100
|
+
"GET",
|
|
101
|
+
"/v1/teams/{teamId}/service-operation-leases/{leaseId}",
|
|
102
|
+
z.object({ teamId: z.string().min(1), leaseId: z.string().min(1) }).strict()
|
|
103
|
+
),
|
|
104
|
+
putOperationLeasePayload: vaultOperation(
|
|
105
|
+
"services.operation.leases.payload.put",
|
|
106
|
+
"PUT",
|
|
107
|
+
"/v1/teams/{teamId}/service-operation-leases/{leaseId}/payload",
|
|
108
|
+
z.object({ teamId: z.string().min(1), leaseId: z.string().min(1) }).strict(),
|
|
109
|
+
{ risk: "credential", redactedPaths: ["body.sealedPayload"] }
|
|
110
|
+
)
|
|
111
|
+
};
|
|
112
|
+
export {
|
|
113
|
+
SERVICE_VAULT_OPERATIONS
|
|
114
|
+
};
|
|
@@ -1323,6 +1323,21 @@ export declare const CONTROL_PLANE_OPERATIONS: {
|
|
|
1323
1323
|
profileId: string;
|
|
1324
1324
|
connectionId: string;
|
|
1325
1325
|
}, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1326
|
+
readonly userVaultKey: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1327
|
+
readonly putUserVaultKey: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1328
|
+
readonly teamVault: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1329
|
+
readonly initializeTeamVault: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1330
|
+
readonly resetTeamVault: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1331
|
+
readonly rotateTeamVault: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1332
|
+
readonly vaultGrantCandidates: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1333
|
+
readonly createVaultGrant: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1334
|
+
readonly deleteVaultGrant: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1335
|
+
readonly vaultCredentialEnvelopes: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1336
|
+
readonly credentialEnvelopes: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1337
|
+
readonly putCredentialEnvelope: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1338
|
+
readonly createOperationLease: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1339
|
+
readonly operationLease: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1340
|
+
readonly putOperationLeasePayload: import("./control-plane-operation.js").ControlPlaneOperationBinding<any, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1326
1341
|
};
|
|
1327
1342
|
readonly agents: {
|
|
1328
1343
|
readonly list: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
@@ -8,6 +8,7 @@ import { capabilityOntologyOperations } from "./catalog/capability-ontology-oper
|
|
|
8
8
|
import { knowledgeShareOperations } from "./catalog/knowledge-share-operations.js";
|
|
9
9
|
import { PROVIDER_ENVIRONMENT_OPERATIONS } from "./catalog/provider-environment-operations.js";
|
|
10
10
|
import { HOSTED_TOPOLOGY_OPERATIONS } from "./catalog/hosted-topology-operations.js";
|
|
11
|
+
import { SERVICE_VAULT_OPERATIONS } from "./catalog/services/service-vault-operations.js";
|
|
11
12
|
import { buildControlPlaneCatalog, flattenControlPlaneOperations } from "./catalog/control-plane-catalog.js";
|
|
12
13
|
const empty = z.object({}).strict(), none = z.undefined(), record = z.record(z.unknown()), payload = record;
|
|
13
14
|
function read(operationId, path, capability, surfaces = ["rest"]) {
|
|
@@ -277,6 +278,7 @@ const CONTROL_PLANE_OPERATIONS = {
|
|
|
277
278
|
deleteWorkflowVariable: resource("repositories.workflow.variables.delete", "DELETE", "/v1/projects/{projectId}/workflow-configuration/variables/{name}", { projectId: z.string().min(1), name: z.string().min(1) }, { capability: "workflows.write", risk: "destructive", concurrency: true })
|
|
278
279
|
},
|
|
279
280
|
services: {
|
|
281
|
+
...SERVICE_VAULT_OPERATIONS,
|
|
280
282
|
providers: resource("services.providers.list", "GET", "/v1/service-providers", {}, { capability: "services.read", surfaces: ["rest", "cli", "mcp_tool"] }),
|
|
281
283
|
connections: resource("services.connections.list", "GET", "/v1/teams/{teamId}/services", { teamId: z.string().min(1) }, { capability: "services.read", surfaces: ["rest", "cli", "mcp_tool"], pagination: "cursor" }),
|
|
282
284
|
connection: resource("services.connections.show", "GET", "/v1/teams/{teamId}/services/{connectionId}", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "services.read", surfaces: ["rest", "cli", "mcp_resource"] }),
|
|
@@ -335,12 +337,12 @@ const CONTROL_PLANE_OPERATIONS = {
|
|
|
335
337
|
retry: resource("assignments.retry", "POST", "/v1/teams/{teamId}/capacity/assignments/{assignmentId}/requeue", { teamId: z.string().min(1), assignmentId: z.string().min(1) }, { capability: "assignments.execute", scopes: ["treeseed:execution"], surfaces: ["rest", "cli", "mcp_tool"], risk: "authority" })
|
|
336
338
|
},
|
|
337
339
|
operations: {
|
|
338
|
-
list: resource("operations.list", "GET", "/v1/platform/operations", {}, { capability: "operations.read", surfaces: ["rest", "mcp_tool"], pagination: "cursor" }),
|
|
340
|
+
list: resource("operations.list", "GET", "/v1/platform/operations", {}, { capability: "operations.read", surfaces: ["rest", "cli", "mcp_tool"], pagination: "cursor" }),
|
|
339
341
|
create: resource("operations.create", "POST", "/v1/platform/operations", {}, { capability: "operations.execute", scopes: ["treeseed:execution"] }),
|
|
340
|
-
show: resource("operations.show", "GET", "/v1/platform/operations/{operationId}", { operationId: z.string().min(1) }, { capability: "operations.read", surfaces: ["rest", "mcp_resource"] }),
|
|
342
|
+
show: resource("operations.show", "GET", "/v1/platform/operations/{operationId}", { operationId: z.string().min(1) }, { capability: "operations.read", surfaces: ["rest", "cli", "mcp_resource"] }),
|
|
341
343
|
events: resource("operations.events.list", "GET", "/v1/platform/operations/{operationId}/events", { operationId: z.string().min(1) }, { capability: "operations.read", pagination: "cursor" }),
|
|
342
344
|
cancel: resource("operations.cancel", "POST", "/v1/platform/operations/{operationId}/cancel", { operationId: z.string().min(1) }, { capability: "operations.execute", scopes: ["treeseed:execution"], risk: "destructive" }),
|
|
343
|
-
retry: resource("operations.retry", "POST", "/v1/platform/operations/{operationId}/retry", { operationId: z.string().min(1) }, { capability: "operations.execute", scopes: ["treeseed:execution"] })
|
|
345
|
+
retry: resource("operations.retry", "POST", "/v1/platform/operations/{operationId}/retry", { operationId: z.string().min(1) }, { capability: "operations.execute", scopes: ["treeseed:execution"], surfaces: ["rest", "cli"] })
|
|
344
346
|
},
|
|
345
347
|
seeds: {
|
|
346
348
|
runs: resource("seeds.runs.list", "GET", "/v1/seeds/runs", {}, { capability: "seeds.read", pagination: "cursor" }),
|
|
@@ -130,9 +130,9 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
130
130
|
{ type: "object-storage", label: "Object storage", description: "R2-backed immutable publication and private artifact storage.", credentialProfileIds: ["cloudflare-storage"], status: "available" }
|
|
131
131
|
],
|
|
132
132
|
credentialProfiles: [
|
|
133
|
-
{ id: "cloudflare-runtime", label: "Pages and Workers token", description: "A token limited to application deployment resources.", capabilities: ["frontend-hosting"], fields: [field("apiToken", "API token", "Encrypted in this browser and used only for authorized frontend operations.", true, true)], permissions: ["Account: Workers Scripts Edit", "Account: Pages Edit"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
134
|
-
{ id: "cloudflare-dns", label: "DNS token", description: "A token restricted to selected zones.", capabilities: ["dns-management"], fields: [field("apiToken", "DNS API token", "Encrypted separately from deployment authority.", true, true)], permissions: ["Zone: DNS Edit for only the managed zones"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
135
|
-
{ id: "cloudflare-storage", label: "Storage authority", description: "Vault-custodied R2 management and S3-compatible state credentials, isolated from runtime and DNS authority.", capabilities: ["object-storage"], fields: [field("apiToken", "Storage API token", "Encrypted separately and used only to reconcile authorized R2 resources.", true, true), field("accessKeyId", "R2 access key ID", "Vault-custodied S3-compatible access key identifier for encrypted OpenTofu state.", true, true), field("secretAccessKey", "R2 secret access key", "Vault-custodied S3-compatible secret key for encrypted OpenTofu state.", true, true)], permissions: ["Account: Workers R2 Storage Edit", "Object read and write for only the managed state bucket"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] }
|
|
133
|
+
{ id: "cloudflare-runtime", label: "Pages and Workers token", description: "A token limited to application deployment resources.", capabilities: ["frontend-hosting"], fields: [field("apiToken", "API token", "Encrypted in this browser and used only for authorized frontend operations.", true, true)], permissions: ["Account: Workers Scripts Edit", "Account: Pages Edit"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "client-encrypted", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
134
|
+
{ id: "cloudflare-dns", label: "DNS token", description: "A token restricted to selected zones.", capabilities: ["dns-management"], fields: [field("apiToken", "DNS API token", "Encrypted separately from deployment authority.", true, true)], permissions: ["Zone: DNS Edit for only the managed zones"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "client-encrypted", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
135
|
+
{ id: "cloudflare-storage", label: "Storage authority", description: "Vault-custodied R2 management and S3-compatible state credentials, isolated from runtime and DNS authority.", capabilities: ["object-storage"], fields: [field("apiToken", "Storage API token", "Encrypted separately and used only to reconcile authorized R2 resources.", true, true), field("accessKeyId", "R2 access key ID", "Vault-custodied S3-compatible access key identifier for encrypted OpenTofu state.", true, true), field("secretAccessKey", "R2 secret access key", "Vault-custodied S3-compatible secret key for encrypted OpenTofu state.", true, true), field("stateEncryptionKey", "OpenTofu state encryption key", "Generated in the browser and encrypted separately; never store this key with the R2 state object.", true, true)], permissions: ["Account: Workers R2 Storage Edit", "Object read and write for only the managed state bucket"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["environment-reference", "client-encrypted", "external-vault", "workload-identity"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] }
|
|
136
136
|
]
|
|
137
137
|
},
|
|
138
138
|
{
|
|
@@ -163,7 +163,7 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
163
163
|
permissions: ["Workspace access required by the selected operations"],
|
|
164
164
|
sharing: "provider-shared",
|
|
165
165
|
unattendedCompatible: true,
|
|
166
|
-
authoritySchemes: ["environment-reference", "external-vault", "workload-identity"],
|
|
166
|
+
authoritySchemes: ["environment-reference", "client-encrypted", "external-vault", "workload-identity"],
|
|
167
167
|
knowledgePageIds: ["provider.railway", "services.credentials", "vault.rotation"]
|
|
168
168
|
}]
|
|
169
169
|
},
|
|
@@ -2,6 +2,9 @@ export declare const SERVICE_VAULT_ENCRYPTION_VERSION: "service-vault-v1";
|
|
|
2
2
|
export declare const SERVICE_VAULT_AEAD_ALGORITHM: "xchacha20-poly1305-ietf";
|
|
3
3
|
export declare const SERVICE_VAULT_KEY_AGREEMENT: "x25519-sealed-box";
|
|
4
4
|
export declare const SERVICE_VAULT_KDF: "argon2id";
|
|
5
|
+
export declare const SECRET_OPERATION_PURPOSES: readonly ["provider-connection-validation", "remote-git-publication", "workflow-dispatch", "workflow-configuration", "hosted-topology-plan", "hosted-topology-apply", "hosted-topology-readback", "hosted-topology-rollback"];
|
|
6
|
+
export type SecretOperationPurpose = (typeof SECRET_OPERATION_PURPOSES)[number];
|
|
7
|
+
export type HostedSecretOperationPurpose = Extract<SecretOperationPurpose, `hosted-topology-${string}`>;
|
|
5
8
|
export type EncryptedCredentialEnvelope = {
|
|
6
9
|
version: typeof SERVICE_VAULT_ENCRYPTION_VERSION;
|
|
7
10
|
algorithm: typeof SERVICE_VAULT_AEAD_ALGORITHM;
|
|
@@ -37,7 +40,7 @@ export type SecretOperationLease = {
|
|
|
37
40
|
teamId: string;
|
|
38
41
|
connectionId: string;
|
|
39
42
|
capabilityType: string;
|
|
40
|
-
purpose:
|
|
43
|
+
purpose: SecretOperationPurpose;
|
|
41
44
|
resourceScope: Record<string, string>;
|
|
42
45
|
credentialProfileId: string;
|
|
43
46
|
actorUserId: string;
|
|
@@ -47,6 +50,47 @@ export type SecretOperationLease = {
|
|
|
47
50
|
expiresAt: string;
|
|
48
51
|
consumedAt?: string | null;
|
|
49
52
|
operationCorrelationId: string;
|
|
53
|
+
hostedBinding?: HostedSecretOperationBinding;
|
|
54
|
+
authorityRequests?: SecretOperationAuthorityRequest[];
|
|
55
|
+
};
|
|
56
|
+
export type HostedSecretOperationBinding = {
|
|
57
|
+
subjectType: 'declaration' | 'plan' | 'rollback';
|
|
58
|
+
subjectDigest: string;
|
|
59
|
+
deploymentId: string;
|
|
60
|
+
stackId: string;
|
|
61
|
+
environment: 'staging' | 'production';
|
|
62
|
+
};
|
|
63
|
+
export type SecretOperationAuthorityRequest = {
|
|
64
|
+
requestId: string;
|
|
65
|
+
connectionId: string;
|
|
66
|
+
credentialProfileId: string;
|
|
67
|
+
provider: 'cloudflare' | 'railway' | 'treeseed';
|
|
68
|
+
purpose: 'provider' | 'state-backend' | 'state-encryption';
|
|
69
|
+
capabilities: string[];
|
|
70
|
+
requiredFields: string[];
|
|
71
|
+
secretRef?: string;
|
|
72
|
+
};
|
|
73
|
+
export type SealedSecretOperationPayload = {
|
|
74
|
+
schemaVersion: 'treeseed.sealed-secret-operation-payload/v1';
|
|
75
|
+
leaseId: string;
|
|
76
|
+
teamId: string;
|
|
77
|
+
operationCorrelationId: string;
|
|
78
|
+
hostedBinding: HostedSecretOperationBinding;
|
|
79
|
+
algorithm: typeof SERVICE_VAULT_KEY_AGREEMENT;
|
|
80
|
+
ciphertext: string;
|
|
81
|
+
};
|
|
82
|
+
export type SecretOperationCredentialBundle = {
|
|
83
|
+
schemaVersion: 'treeseed.secret-operation-credential-bundle/v1';
|
|
84
|
+
leaseId: string;
|
|
85
|
+
teamId: string;
|
|
86
|
+
operationCorrelationId: string;
|
|
87
|
+
hostedBinding: HostedSecretOperationBinding;
|
|
88
|
+
materials: Array<{
|
|
89
|
+
requestId: string;
|
|
90
|
+
connectionId: string;
|
|
91
|
+
credentialProfileId: string;
|
|
92
|
+
values: Record<string, string>;
|
|
93
|
+
}>;
|
|
50
94
|
};
|
|
51
95
|
export type ServiceVaultAssociatedData = {
|
|
52
96
|
teamId: string;
|
|
@@ -58,4 +102,7 @@ export type ServiceVaultAssociatedData = {
|
|
|
58
102
|
};
|
|
59
103
|
export declare function containsForbiddenPlaintextSecretMaterial(value: unknown, path?: string): string[];
|
|
60
104
|
export declare function canonicalServiceVaultAssociatedData(input: ServiceVaultAssociatedData): string;
|
|
105
|
+
export declare function canonicalHostedSecretOperationBinding(input: HostedSecretOperationBinding): string;
|
|
106
|
+
export declare function validateHostedSecretOperationBinding(value: unknown): value is HostedSecretOperationBinding;
|
|
107
|
+
export declare function validateSealedSecretOperationPayload(value: unknown): value is SealedSecretOperationPayload;
|
|
61
108
|
export declare function validateEncryptedCredentialEnvelope(value: unknown): value is EncryptedCredentialEnvelope;
|
|
@@ -2,6 +2,16 @@ const SERVICE_VAULT_ENCRYPTION_VERSION = "service-vault-v1";
|
|
|
2
2
|
const SERVICE_VAULT_AEAD_ALGORITHM = "xchacha20-poly1305-ietf";
|
|
3
3
|
const SERVICE_VAULT_KEY_AGREEMENT = "x25519-sealed-box";
|
|
4
4
|
const SERVICE_VAULT_KDF = "argon2id";
|
|
5
|
+
const SECRET_OPERATION_PURPOSES = [
|
|
6
|
+
"provider-connection-validation",
|
|
7
|
+
"remote-git-publication",
|
|
8
|
+
"workflow-dispatch",
|
|
9
|
+
"workflow-configuration",
|
|
10
|
+
"hosted-topology-plan",
|
|
11
|
+
"hosted-topology-apply",
|
|
12
|
+
"hosted-topology-readback",
|
|
13
|
+
"hosted-topology-rollback"
|
|
14
|
+
];
|
|
5
15
|
const FORBIDDEN_SECRET_KEYS = /(?:passphrase|password|plaintext|derivedKey|privateKey|apiToken|accessToken|secretValue|credentialValue)$/iu;
|
|
6
16
|
function containsForbiddenPlaintextSecretMaterial(value, path = "") {
|
|
7
17
|
if (!value || typeof value !== "object") return [];
|
|
@@ -23,17 +33,42 @@ function canonicalServiceVaultAssociatedData(input) {
|
|
|
23
33
|
version: input.version
|
|
24
34
|
});
|
|
25
35
|
}
|
|
36
|
+
const digest = /^sha256:[a-f0-9]{64}$/u;
|
|
37
|
+
const identifier = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
38
|
+
function canonicalHostedSecretOperationBinding(input) {
|
|
39
|
+
return JSON.stringify({
|
|
40
|
+
subjectType: input.subjectType,
|
|
41
|
+
subjectDigest: input.subjectDigest,
|
|
42
|
+
deploymentId: input.deploymentId,
|
|
43
|
+
stackId: input.stackId,
|
|
44
|
+
environment: input.environment
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function validateHostedSecretOperationBinding(value) {
|
|
48
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
49
|
+
const binding = value;
|
|
50
|
+
return ["declaration", "plan", "rollback"].includes(String(binding.subjectType)) && digest.test(String(binding.subjectDigest)) && identifier.test(String(binding.deploymentId)) && identifier.test(String(binding.stackId)) && ["staging", "production"].includes(String(binding.environment));
|
|
51
|
+
}
|
|
52
|
+
function validateSealedSecretOperationPayload(value) {
|
|
53
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
54
|
+
const payload = value;
|
|
55
|
+
return payload.schemaVersion === "treeseed.sealed-secret-operation-payload/v1" && payload.algorithm === SERVICE_VAULT_KEY_AGREEMENT && [payload.leaseId, payload.teamId, payload.operationCorrelationId, payload.ciphertext].every((field) => typeof field === "string" && field.length > 0) && validateHostedSecretOperationBinding(payload.hostedBinding);
|
|
56
|
+
}
|
|
26
57
|
function validateEncryptedCredentialEnvelope(value) {
|
|
27
58
|
if (!value || typeof value !== "object") return false;
|
|
28
59
|
const envelope = value;
|
|
29
60
|
return envelope.version === SERVICE_VAULT_ENCRYPTION_VERSION && envelope.algorithm === SERVICE_VAULT_AEAD_ALGORITHM && [envelope.ciphertext, envelope.nonce, envelope.wrappedKey, envelope.wrappedKeyNonce, envelope.associatedData, envelope.associatedDataDigest, envelope.fingerprint].every((field) => typeof field === "string" && field.length > 0);
|
|
30
61
|
}
|
|
31
62
|
export {
|
|
63
|
+
SECRET_OPERATION_PURPOSES,
|
|
32
64
|
SERVICE_VAULT_AEAD_ALGORITHM,
|
|
33
65
|
SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
34
66
|
SERVICE_VAULT_KDF,
|
|
35
67
|
SERVICE_VAULT_KEY_AGREEMENT,
|
|
68
|
+
canonicalHostedSecretOperationBinding,
|
|
36
69
|
canonicalServiceVaultAssociatedData,
|
|
37
70
|
containsForbiddenPlaintextSecretMaterial,
|
|
38
|
-
validateEncryptedCredentialEnvelope
|
|
71
|
+
validateEncryptedCredentialEnvelope,
|
|
72
|
+
validateHostedSecretOperationBinding,
|
|
73
|
+
validateSealedSecretOperationPayload
|
|
39
74
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type EncryptedCredentialEnvelope, type EncryptedPrivateKeyEnvelope, type TeamVaultGrantEnvelope } from '@treeseed/sdk/secrets-capability';
|
|
2
|
+
export declare function createServiceVaultKey(): Promise<Uint8Array>;
|
|
3
|
+
export declare function createServiceVaultUserKeyPair(): Promise<{
|
|
4
|
+
publicKey: string;
|
|
5
|
+
privateKey: Uint8Array;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function encryptServiceVaultPrivateKey(privateKey: Uint8Array, publicKey: string, passphrase: string, options?: {
|
|
8
|
+
opsLimit?: number;
|
|
9
|
+
memLimit?: number;
|
|
10
|
+
}): Promise<EncryptedPrivateKeyEnvelope>;
|
|
11
|
+
export declare function decryptServiceVaultPrivateKey(envelope: EncryptedPrivateKeyEnvelope, passphrase: string): Promise<Uint8Array>;
|
|
12
|
+
export declare function createTeamVaultGrant(teamVaultKey: Uint8Array, recipientPublicKey: string): Promise<TeamVaultGrantEnvelope>;
|
|
13
|
+
export declare function openTeamVaultGrant(grant: TeamVaultGrantEnvelope, recipientPrivateKey: Uint8Array): Promise<Uint8Array>;
|
|
14
|
+
export declare function encryptServiceCredential(plaintext: string, teamVaultKey: Uint8Array, associatedData: string): Promise<EncryptedCredentialEnvelope>;
|
|
15
|
+
export declare function decryptServiceCredential(envelope: EncryptedCredentialEnvelope, teamVaultKey: Uint8Array, expectedAssociatedData: string): Promise<string>;
|
|
16
|
+
export declare function rewrapServiceCredential(envelope: EncryptedCredentialEnvelope, currentTeamVaultKey: Uint8Array, replacementTeamVaultKey: Uint8Array): Promise<EncryptedCredentialEnvelope>;
|
|
17
|
+
export declare function sealSecretOperationPayload(values: Record<string, string>, operationPublicKey: string): Promise<string>;
|
|
18
|
+
export declare function encryptGitHubActionsSecret(value: string, providerPublicKey: string): Promise<string>;
|
|
19
|
+
export declare function openSecretOperationPayload(sealedPayload: string, operationPublicKey: string, operationPrivateKey: Uint8Array): Promise<Record<string, string>>;
|
|
20
|
+
export declare function clearServiceVaultKey(value: Uint8Array | undefined): void;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import sodium from "libsodium-wrappers-sumo";
|
|
2
|
+
import {
|
|
3
|
+
SERVICE_VAULT_AEAD_ALGORITHM,
|
|
4
|
+
SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
5
|
+
SERVICE_VAULT_KDF,
|
|
6
|
+
SERVICE_VAULT_KEY_AGREEMENT
|
|
7
|
+
} from "@treeseed/sdk/secrets-capability";
|
|
8
|
+
const b64 = () => sodium.base64_variants.ORIGINAL;
|
|
9
|
+
const encode = (value) => sodium.to_base64(value, b64());
|
|
10
|
+
const decode = (value) => sodium.from_base64(value, b64());
|
|
11
|
+
const hash = (value, key = null) => encode(sodium.crypto_generichash(32, value, key));
|
|
12
|
+
async function ready() {
|
|
13
|
+
await sodium.ready;
|
|
14
|
+
return sodium;
|
|
15
|
+
}
|
|
16
|
+
function validKey(value, length, label) {
|
|
17
|
+
if (!(value instanceof Uint8Array) || value.length !== length) throw new Error(`${label} is invalid.`);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
async function createServiceVaultKey() {
|
|
21
|
+
const crypto = await ready();
|
|
22
|
+
return crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
|
|
23
|
+
}
|
|
24
|
+
async function createServiceVaultUserKeyPair() {
|
|
25
|
+
const crypto = await ready(), pair = crypto.crypto_box_keypair();
|
|
26
|
+
return { publicKey: encode(pair.publicKey), privateKey: pair.privateKey };
|
|
27
|
+
}
|
|
28
|
+
async function encryptServiceVaultPrivateKey(privateKey, publicKey, passphrase, options = {}) {
|
|
29
|
+
const crypto = await ready();
|
|
30
|
+
validKey(privateKey, crypto.crypto_box_SECRETKEYBYTES, "Service-vault private key");
|
|
31
|
+
validKey(decode(publicKey), crypto.crypto_box_PUBLICKEYBYTES, "Service-vault public key");
|
|
32
|
+
if (passphrase.length < 12) throw new Error("Service-vault passphrase must contain at least 12 characters.");
|
|
33
|
+
const salt = crypto.randombytes_buf(crypto.crypto_pwhash_SALTBYTES);
|
|
34
|
+
const opsLimit = options.opsLimit ?? crypto.crypto_pwhash_OPSLIMIT_MODERATE;
|
|
35
|
+
const memLimit = options.memLimit ?? crypto.crypto_pwhash_MEMLIMIT_MODERATE;
|
|
36
|
+
const key = crypto.crypto_pwhash(
|
|
37
|
+
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
38
|
+
passphrase,
|
|
39
|
+
salt,
|
|
40
|
+
opsLimit,
|
|
41
|
+
memLimit,
|
|
42
|
+
crypto.crypto_pwhash_ALG_ARGON2ID13
|
|
43
|
+
);
|
|
44
|
+
const nonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
45
|
+
try {
|
|
46
|
+
return {
|
|
47
|
+
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
48
|
+
algorithm: SERVICE_VAULT_AEAD_ALGORITHM,
|
|
49
|
+
kdf: { algorithm: SERVICE_VAULT_KDF, opsLimit, memLimit, salt: encode(salt) },
|
|
50
|
+
nonce: encode(nonce),
|
|
51
|
+
ciphertext: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(privateKey, publicKey, null, nonce, key)),
|
|
52
|
+
publicKey
|
|
53
|
+
};
|
|
54
|
+
} finally {
|
|
55
|
+
crypto.memzero(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function decryptServiceVaultPrivateKey(envelope, passphrase) {
|
|
59
|
+
const crypto = await ready();
|
|
60
|
+
if (envelope.version !== SERVICE_VAULT_ENCRYPTION_VERSION || envelope.algorithm !== SERVICE_VAULT_AEAD_ALGORITHM || envelope.kdf.algorithm !== SERVICE_VAULT_KDF) throw new Error("Service-vault private-key envelope is unsupported.");
|
|
61
|
+
if (!Number.isSafeInteger(envelope.kdf.opsLimit) || !Number.isSafeInteger(envelope.kdf.memLimit) || envelope.kdf.opsLimit < crypto.crypto_pwhash_OPSLIMIT_INTERACTIVE || envelope.kdf.opsLimit > crypto.crypto_pwhash_OPSLIMIT_SENSITIVE || envelope.kdf.memLimit < crypto.crypto_pwhash_MEMLIMIT_INTERACTIVE || envelope.kdf.memLimit > crypto.crypto_pwhash_MEMLIMIT_SENSITIVE)
|
|
62
|
+
throw new Error("Service-vault private-key KDF parameters are invalid.");
|
|
63
|
+
const key = crypto.crypto_pwhash(
|
|
64
|
+
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
65
|
+
passphrase,
|
|
66
|
+
decode(envelope.kdf.salt),
|
|
67
|
+
envelope.kdf.opsLimit,
|
|
68
|
+
envelope.kdf.memLimit,
|
|
69
|
+
crypto.crypto_pwhash_ALG_ARGON2ID13
|
|
70
|
+
);
|
|
71
|
+
try {
|
|
72
|
+
return validKey(crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
73
|
+
null,
|
|
74
|
+
decode(envelope.ciphertext),
|
|
75
|
+
envelope.publicKey,
|
|
76
|
+
decode(envelope.nonce),
|
|
77
|
+
key
|
|
78
|
+
), crypto.crypto_box_SECRETKEYBYTES, "Decrypted service-vault private key");
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error("Service-vault passphrase or private-key envelope is invalid.");
|
|
81
|
+
} finally {
|
|
82
|
+
crypto.memzero(key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function createTeamVaultGrant(teamVaultKey, recipientPublicKey) {
|
|
86
|
+
const crypto = await ready();
|
|
87
|
+
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
88
|
+
const publicKey = validKey(decode(recipientPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Recipient public key");
|
|
89
|
+
return {
|
|
90
|
+
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
91
|
+
algorithm: SERVICE_VAULT_KEY_AGREEMENT,
|
|
92
|
+
recipientPublicKey,
|
|
93
|
+
wrappedTeamVaultKey: encode(crypto.crypto_box_seal(teamVaultKey, publicKey))
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function openTeamVaultGrant(grant, recipientPrivateKey) {
|
|
97
|
+
const crypto = await ready();
|
|
98
|
+
if (grant.version !== SERVICE_VAULT_ENCRYPTION_VERSION || grant.algorithm !== SERVICE_VAULT_KEY_AGREEMENT)
|
|
99
|
+
throw new Error("Team vault grant is unsupported.");
|
|
100
|
+
const publicKey = validKey(decode(grant.recipientPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Recipient public key");
|
|
101
|
+
validKey(recipientPrivateKey, crypto.crypto_box_SECRETKEYBYTES, "Recipient private key");
|
|
102
|
+
try {
|
|
103
|
+
return validKey(
|
|
104
|
+
crypto.crypto_box_seal_open(decode(grant.wrappedTeamVaultKey), publicKey, recipientPrivateKey),
|
|
105
|
+
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
106
|
+
"Unwrapped team vault key"
|
|
107
|
+
);
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error("Team vault grant cannot be opened by this user key.");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function encryptServiceCredential(plaintext, teamVaultKey, associatedData) {
|
|
113
|
+
const crypto = await ready();
|
|
114
|
+
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
115
|
+
if (!plaintext || !associatedData) throw new Error("Credential value and associated data are required.");
|
|
116
|
+
const dataKey = crypto.crypto_aead_xchacha20poly1305_ietf_keygen();
|
|
117
|
+
const nonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
118
|
+
const wrappedKeyNonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
119
|
+
try {
|
|
120
|
+
return {
|
|
121
|
+
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
122
|
+
algorithm: SERVICE_VAULT_AEAD_ALGORITHM,
|
|
123
|
+
ciphertext: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, associatedData, null, nonce, dataKey)),
|
|
124
|
+
nonce: encode(nonce),
|
|
125
|
+
wrappedKey: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(dataKey, associatedData, null, wrappedKeyNonce, teamVaultKey)),
|
|
126
|
+
wrappedKeyNonce: encode(wrappedKeyNonce),
|
|
127
|
+
associatedData,
|
|
128
|
+
associatedDataDigest: hash(associatedData),
|
|
129
|
+
fingerprint: hash(plaintext, teamVaultKey)
|
|
130
|
+
};
|
|
131
|
+
} finally {
|
|
132
|
+
crypto.memzero(dataKey);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function decryptServiceCredential(envelope, teamVaultKey, expectedAssociatedData) {
|
|
136
|
+
const crypto = await ready();
|
|
137
|
+
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
138
|
+
if (envelope.version !== SERVICE_VAULT_ENCRYPTION_VERSION || envelope.algorithm !== SERVICE_VAULT_AEAD_ALGORITHM || envelope.associatedData !== expectedAssociatedData || envelope.associatedDataDigest !== hash(expectedAssociatedData))
|
|
139
|
+
throw new Error("Credential envelope binding is invalid.");
|
|
140
|
+
let dataKey;
|
|
141
|
+
try {
|
|
142
|
+
dataKey = crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
143
|
+
null,
|
|
144
|
+
decode(envelope.wrappedKey),
|
|
145
|
+
expectedAssociatedData,
|
|
146
|
+
decode(envelope.wrappedKeyNonce),
|
|
147
|
+
teamVaultKey
|
|
148
|
+
);
|
|
149
|
+
return crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
150
|
+
null,
|
|
151
|
+
decode(envelope.ciphertext),
|
|
152
|
+
expectedAssociatedData,
|
|
153
|
+
decode(envelope.nonce),
|
|
154
|
+
dataKey,
|
|
155
|
+
"text"
|
|
156
|
+
);
|
|
157
|
+
} catch {
|
|
158
|
+
throw new Error("Credential envelope cannot be decrypted.");
|
|
159
|
+
} finally {
|
|
160
|
+
if (dataKey) crypto.memzero(dataKey);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function rewrapServiceCredential(envelope, currentTeamVaultKey, replacementTeamVaultKey) {
|
|
164
|
+
const plaintext = await decryptServiceCredential(envelope, currentTeamVaultKey, envelope.associatedData);
|
|
165
|
+
return encryptServiceCredential(plaintext, replacementTeamVaultKey, envelope.associatedData);
|
|
166
|
+
}
|
|
167
|
+
async function sealSecretOperationPayload(values, operationPublicKey) {
|
|
168
|
+
const crypto = await ready();
|
|
169
|
+
if (!Object.keys(values).length || Object.values(values).some((value) => typeof value !== "string" || !value))
|
|
170
|
+
throw new Error("Secret operation payload must contain only non-empty string values.");
|
|
171
|
+
const payload = JSON.stringify(Object.fromEntries(Object.entries(values).sort(([left], [right]) => left.localeCompare(right))));
|
|
172
|
+
return encode(crypto.crypto_box_seal(payload, validKey(decode(operationPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Operation public key")));
|
|
173
|
+
}
|
|
174
|
+
async function encryptGitHubActionsSecret(value, providerPublicKey) {
|
|
175
|
+
const crypto = await ready();
|
|
176
|
+
if (!value) throw new Error("GitHub Actions secret value is required.");
|
|
177
|
+
return encode(crypto.crypto_box_seal(
|
|
178
|
+
value,
|
|
179
|
+
validKey(decode(providerPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "GitHub public key")
|
|
180
|
+
));
|
|
181
|
+
}
|
|
182
|
+
async function openSecretOperationPayload(sealedPayload, operationPublicKey, operationPrivateKey) {
|
|
183
|
+
const crypto = await ready();
|
|
184
|
+
try {
|
|
185
|
+
const plaintext = crypto.crypto_box_seal_open(decode(sealedPayload), decode(operationPublicKey), operationPrivateKey, "text");
|
|
186
|
+
const parsed = JSON.parse(plaintext);
|
|
187
|
+
if (!parsed || Array.isArray(parsed) || Object.values(parsed).some((value) => typeof value !== "string" || !value)) throw new Error();
|
|
188
|
+
return parsed;
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error("Secret operation payload cannot be opened.");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function clearServiceVaultKey(value) {
|
|
194
|
+
value?.fill(0);
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
clearServiceVaultKey,
|
|
198
|
+
createServiceVaultKey,
|
|
199
|
+
createServiceVaultUserKeyPair,
|
|
200
|
+
createTeamVaultGrant,
|
|
201
|
+
decryptServiceCredential,
|
|
202
|
+
decryptServiceVaultPrivateKey,
|
|
203
|
+
encryptGitHubActionsSecret,
|
|
204
|
+
encryptServiceCredential,
|
|
205
|
+
encryptServiceVaultPrivateKey,
|
|
206
|
+
openSecretOperationPayload,
|
|
207
|
+
openTeamVaultGrant,
|
|
208
|
+
rewrapServiceCredential,
|
|
209
|
+
sealSecretOperationPayload
|
|
210
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/sdk",
|
|
3
|
-
"version": "0.13.0-rc.
|
|
3
|
+
"version": "0.13.0-rc.83",
|
|
4
4
|
"description": "Portable TreeSeed contracts, standards, and typed remote control-plane clients.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@treeseed/treedx": "0.3.0-rc.4",
|
|
55
55
|
"esbuild": "^0.28.0",
|
|
56
|
+
"libsodium-wrappers-sumo": "0.8.4",
|
|
56
57
|
"typescript": "^5.9.3",
|
|
57
58
|
"yaml": "^2.8.1",
|
|
58
59
|
"zod": "^3.25.76"
|