@intx/db 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/dist/approval-store.d.ts +59 -0
- package/dist/approval-store.js +92 -0
- package/dist/asset-resolution.d.ts +2 -2
- package/dist/backfill-principal-keys.d.ts +9 -0
- package/dist/backfill-principal-keys.js +51 -0
- package/dist/client.d.ts +37 -2
- package/dist/client.js +6 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/connection.js +12 -8
- package/dist/credential-resolution.d.ts +181 -20
- package/dist/credential-resolution.js +251 -3
- package/dist/grant-store.js +35 -13
- package/dist/index.d.ts +20 -5
- package/dist/index.js +19 -5
- package/dist/model-source-resolution.d.ts +54 -7
- package/dist/model-source-resolution.js +177 -22
- package/dist/parse-row.d.ts +245 -34
- package/dist/parse-row.js +117 -18
- package/dist/principal-key-store.d.ts +51 -0
- package/dist/principal-key-store.js +82 -0
- package/dist/principal-store.d.ts +44 -0
- package/dist/principal-store.js +67 -0
- package/dist/rekey-credential-secrets.d.ts +13 -0
- package/dist/rekey-credential-secrets.js +61 -0
- package/dist/schema/approvals.d.ts +245 -0
- package/dist/schema/approvals.js +57 -0
- package/dist/schema/catalog.d.ts +17 -0
- package/dist/schema/catalog.js +7 -1
- package/dist/schema/credentials.d.ts +1 -1
- package/dist/schema/git-tokens.js +1 -1
- package/dist/schema/grants.js +8 -2
- package/dist/schema/index.d.ts +10 -4
- package/dist/schema/index.js +10 -4
- package/dist/schema/messages.d.ts +5 -5
- package/dist/schema/messages.js +20 -9
- package/dist/schema/offerings.js +8 -2
- package/dist/schema/{workflow-deployments.d.ts → principal-keys.d.ts} +29 -32
- package/dist/schema/principal-keys.js +28 -0
- package/dist/schema/principals.d.ts +2 -2
- package/dist/schema/principals.js +7 -5
- package/dist/schema/providers.d.ts +17 -0
- package/dist/schema/providers.js +5 -0
- package/dist/schema/roles.js +6 -2
- package/dist/schema/session-assets.d.ts +1 -38
- package/dist/schema/session-assets.js +36 -19
- package/dist/schema/sessions.js +7 -2
- package/dist/schema/sidecar-allocation.d.ts +385 -0
- package/dist/schema/sidecar-allocation.js +53 -0
- package/dist/schema/sidecar.d.ts +2 -2
- package/dist/schema/sidecar.js +3 -1
- package/dist/schema/signal-correlations.d.ts +177 -0
- package/dist/schema/signal-correlations.js +31 -0
- package/dist/schema/tenants.js +13 -3
- package/dist/schema/wallets.d.ts +3 -3
- package/dist/schema/wallets.js +4 -2
- package/dist/schema/{agents.d.ts → workflow-definitions.d.ts} +79 -113
- package/dist/schema/workflow-definitions.js +97 -0
- package/dist/schema/workflow-probe.d.ts +358 -0
- package/dist/schema/workflow-probe.js +51 -0
- package/dist/schema/workflow-run-dispatch.d.ts +345 -0
- package/dist/schema/workflow-run-dispatch.js +63 -0
- package/dist/schema/{agent-assets.d.ts → workflow-run-execution.d.ts} +44 -27
- package/dist/schema/workflow-run-execution.js +48 -0
- package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
- package/dist/schema/workflow-run-launch-spec.js +26 -0
- package/dist/schema/{instances.d.ts → workflow-run.d.ts} +61 -66
- package/dist/schema/workflow-run.js +110 -0
- package/dist/sender-key-resolver.d.ts +91 -0
- package/dist/sender-key-resolver.js +184 -0
- package/dist/sidecar-allocation-store.d.ts +206 -0
- package/dist/sidecar-allocation-store.js +714 -0
- package/dist/signal-correlation-store.d.ts +36 -0
- package/dist/signal-correlation-store.js +63 -0
- package/dist/signer-identity.d.ts +12 -0
- package/dist/signer-identity.js +15 -0
- package/dist/tenant-hierarchy.d.ts +4 -1
- package/dist/tenant-hierarchy.js +37 -5
- package/dist/workflow-definition-store.d.ts +55 -0
- package/dist/workflow-definition-store.js +93 -0
- package/dist/workflow-probe-store.d.ts +47 -0
- package/dist/workflow-probe-store.js +124 -0
- package/dist/workflow-run-dispatch-store.d.ts +83 -0
- package/dist/workflow-run-dispatch-store.js +308 -0
- package/dist/workflow-run-launch-spec-store.d.ts +13 -0
- package/dist/workflow-run-launch-spec-store.js +30 -0
- package/dist/workflow-run-store.d.ts +54 -0
- package/dist/workflow-run-store.js +85 -0
- package/migrations/0037_credential_use_backfill.sql +39 -0
- package/migrations/0038_chilly_blacklash.sql +37 -0
- package/migrations/0039_quick_carnage.sql +2 -0
- package/migrations/0040_reshape_approval_origin.sql +24 -0
- package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
- package/migrations/0042_youthful_mantis.sql +2 -0
- package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
- package/migrations/0044_model_offering_quirks.sql +1 -0
- package/migrations/0045_create_workflow_run.sql +13 -0
- package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
- package/migrations/0047_create_workflow_definition.sql +30 -0
- package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
- package/migrations/0049_curly_omega_flight.sql +2 -0
- package/migrations/0050_late_crystal.sql +10 -0
- package/migrations/0051_funny_madelyne_pryor.sql +1 -0
- package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
- package/migrations/0053_session_mail_session_id_index.sql +1 -0
- package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
- package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
- package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
- package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
- package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
- package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
- package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
- package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
- package/migrations/0062_drop_agent_asset_table.sql +11 -0
- package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
- package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
- package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
- package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
- package/migrations/0067_add_workflow_definition_kind.sql +10 -0
- package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
- package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
- package/migrations/0070_flashy_proteus.sql +13 -0
- package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
- package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
- package/migrations/0074_add_provider_api_base_url.sql +1 -0
- package/migrations/0075_workflow_run_launch_spec.sql +17 -0
- package/migrations/0076_sidecar_allocation.sql +40 -0
- package/migrations/0077_workflow_run_dispatch.sql +29 -0
- package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
- package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
- package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
- package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
- package/migrations/0082_blue_black_queen.sql +1 -0
- package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
- package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
- package/migrations/0085_add_approval_run_idx.sql +1 -0
- package/migrations/0086_cool_human_cannonball.sql +1 -0
- package/migrations/0087_drop_sidecar_placement.sql +3 -0
- package/migrations/0088_thick_sprite.sql +32 -0
- package/migrations/0089_tense_selene.sql +13 -0
- package/migrations/0090_tenant_domain_lower_unique.sql +2 -0
- package/migrations/0091_workflow_run_dispatch_sender_address.sql +21 -0
- package/migrations/0092_sidecar_destroy_failed.sql +4 -0
- package/migrations/0093_sidecar_initialization.sql +2 -0
- package/migrations/meta/0037_snapshot.json +3237 -0
- package/migrations/meta/0038_snapshot.json +3470 -0
- package/migrations/meta/0039_snapshot.json +3470 -0
- package/migrations/meta/0040_snapshot.json +3489 -0
- package/migrations/meta/0041_snapshot.json +3489 -0
- package/migrations/meta/0042_snapshot.json +3489 -0
- package/migrations/meta/0043_snapshot.json +3498 -0
- package/migrations/meta/0044_snapshot.json +3504 -0
- package/migrations/meta/0045_snapshot.json +3589 -0
- package/migrations/meta/0046_snapshot.json +3607 -0
- package/migrations/meta/0047_snapshot.json +3832 -0
- package/migrations/meta/0048_snapshot.json +3853 -0
- package/migrations/meta/0049_snapshot.json +3854 -0
- package/migrations/meta/0050_snapshot.json +3924 -0
- package/migrations/meta/0051_snapshot.json +3940 -0
- package/migrations/meta/0052_snapshot.json +3931 -0
- package/migrations/meta/0053_snapshot.json +3952 -0
- package/migrations/meta/0054_snapshot.json +3952 -0
- package/migrations/meta/0055_snapshot.json +3952 -0
- package/migrations/meta/0056_snapshot.json +3952 -0
- package/migrations/meta/0057_snapshot.json +3839 -0
- package/migrations/meta/0058_snapshot.json +3839 -0
- package/migrations/meta/0059_snapshot.json +3839 -0
- package/migrations/meta/0060_snapshot.json +3830 -0
- package/migrations/meta/0061_snapshot.json +3821 -0
- package/migrations/meta/0062_snapshot.json +3723 -0
- package/migrations/meta/0063_snapshot.json +3723 -0
- package/migrations/meta/0064_snapshot.json +3723 -0
- package/migrations/meta/0065_snapshot.json +3729 -0
- package/migrations/meta/0066_snapshot.json +3729 -0
- package/migrations/meta/0067_snapshot.json +3736 -0
- package/migrations/meta/0068_snapshot.json +3347 -0
- package/migrations/meta/0069_snapshot.json +3340 -0
- package/migrations/meta/0070_snapshot.json +3444 -0
- package/migrations/meta/0072_snapshot.json +3450 -0
- package/migrations/meta/0073_snapshot.json +3455 -0
- package/migrations/meta/0074_snapshot.json +3461 -0
- package/migrations/meta/0075_snapshot.json +3567 -0
- package/migrations/meta/0076_snapshot.json +3851 -0
- package/migrations/meta/0077_snapshot.json +4067 -0
- package/migrations/meta/0078_snapshot.json +4078 -0
- package/migrations/meta/0079_snapshot.json +4078 -0
- package/migrations/meta/0080_snapshot.json +4078 -0
- package/migrations/meta/0081_snapshot.json +4096 -0
- package/migrations/meta/0082_snapshot.json +4102 -0
- package/migrations/meta/0083_snapshot.json +4096 -0
- package/migrations/meta/0084_snapshot.json +4096 -0
- package/migrations/meta/0085_snapshot.json +4111 -0
- package/migrations/meta/0086_snapshot.json +4117 -0
- package/migrations/meta/0087_snapshot.json +4100 -0
- package/migrations/meta/0088_snapshot.json +4286 -0
- package/migrations/meta/0089_snapshot.json +4376 -0
- package/migrations/meta/0090_snapshot.json +4387 -0
- package/migrations/meta/0091_snapshot.json +4397 -0
- package/migrations/meta/0092_snapshot.json +4397 -0
- package/migrations/meta/0093_snapshot.json +4407 -0
- package/migrations/meta/_journal.json +392 -0
- package/package.json +7 -3
- package/dist/schema/agent-assets.js +0 -15
- package/dist/schema/agents.js +0 -57
- package/dist/schema/instances.js +0 -39
- package/dist/schema/workflow-deployments.js +0 -37
package/dist/parse-row.js
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
import { type } from "arktype";
|
|
2
|
-
import { Capability,
|
|
3
|
-
import {
|
|
2
|
+
import { Capability, CredentialBinding, GrantRequirement, grantEffects, grantOrigins, InvokerModelPreferences, ModelProviderPlugin, ModelRequirements, principalKinds, principalStatuses, signalKinds, TenantConfig, workflowDefinitionStatuses, workflowDefinitionVersionStatuses, } from "@intx/types";
|
|
3
|
+
import { WireGrantRule } from "@intx/types/grant-wire";
|
|
4
|
+
import { FrozenApprovalBundle, RepoAction } from "@intx/types/sidecar";
|
|
4
5
|
import { ToolPackagePinArray } from "@intx/types/tool-packages";
|
|
5
6
|
const JSONObject = type("Record<string, unknown>");
|
|
7
|
+
const StringArray = type("string[]");
|
|
8
|
+
const WireGrantRuleArray = WireGrantRule.array();
|
|
6
9
|
const GrantEffectValidator = type.enumerated(...grantEffects);
|
|
7
10
|
const GrantOriginValidator = type.enumerated(...grantOrigins);
|
|
8
|
-
const
|
|
9
|
-
const
|
|
11
|
+
const approvalScopes = ["once", "always"];
|
|
12
|
+
const ApprovalScopeValidator = type.enumerated(...approvalScopes);
|
|
13
|
+
const approvalStatuses = [
|
|
14
|
+
"pending",
|
|
15
|
+
"approved",
|
|
16
|
+
"rejected",
|
|
17
|
+
"timeout",
|
|
18
|
+
"expired",
|
|
19
|
+
];
|
|
20
|
+
const ApprovalStatusValidator = type.enumerated(...approvalStatuses);
|
|
21
|
+
const SignalKindValidator = type.enumerated(...signalKinds);
|
|
22
|
+
const workflowRunStatuses = [
|
|
23
|
+
"deployed",
|
|
24
|
+
"running",
|
|
25
|
+
"completed",
|
|
26
|
+
"failed",
|
|
27
|
+
"cancelled",
|
|
28
|
+
];
|
|
29
|
+
const WorkflowRunStatusValidator = type.enumerated(...workflowRunStatuses);
|
|
30
|
+
const WorkflowRunDispatchStatusValidator = type.enumerated("pending", "acknowledged", "settled", "failed");
|
|
31
|
+
const WorkflowRunDispatchKindValidator = type.enumerated("mail", "signal");
|
|
32
|
+
const WorkflowDefinitionStatusValidator = type.enumerated(...workflowDefinitionStatuses);
|
|
33
|
+
const WorkflowDefinitionVersionStatusValidator = type.enumerated(...workflowDefinitionVersionStatuses);
|
|
34
|
+
const PrincipalKindValidator = type.enumerated(...principalKinds);
|
|
35
|
+
const PrincipalStatusValidator = type.enumerated(...principalStatuses);
|
|
36
|
+
const principalKeyStatuses = ["active", "retired"];
|
|
37
|
+
const PrincipalKeyStatusValidator = type.enumerated(...principalKeyStatuses);
|
|
10
38
|
const credentialTypes = [
|
|
11
39
|
"api_key",
|
|
12
40
|
"oauth_token",
|
|
@@ -31,35 +59,36 @@ const turnPartTypes = [
|
|
|
31
59
|
"file",
|
|
32
60
|
"error",
|
|
33
61
|
"refusal",
|
|
62
|
+
"safety_rating",
|
|
34
63
|
"step-start",
|
|
35
64
|
"step-finish",
|
|
36
65
|
"snapshot",
|
|
37
66
|
"patch",
|
|
38
67
|
];
|
|
39
68
|
const TurnPartTypeValidator = type.enumerated(...turnPartTypes);
|
|
40
|
-
export function
|
|
69
|
+
export function parseWorkflowDefinitionRow(row) {
|
|
41
70
|
return {
|
|
42
71
|
...row,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
capabilities: row.capabilities !== null ? JSONObject.assert(row.capabilities) : null,
|
|
47
|
-
credentialRequirements: row.credentialRequirements !== null
|
|
48
|
-
? CredentialRequirement.array().assert(row.credentialRequirements)
|
|
72
|
+
status: WorkflowDefinitionStatusValidator.assert(row.status),
|
|
73
|
+
grantRequirements: row.grantRequirements !== null
|
|
74
|
+
? GrantRequirement.array().assert(row.grantRequirements)
|
|
49
75
|
: null,
|
|
50
76
|
modelRequirements: row.modelRequirements !== null
|
|
51
77
|
? ModelRequirements.assert(row.modelRequirements)
|
|
52
78
|
: null,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
// Nullable jsonb: `null` on a real drizzle row, `undefined` only on a
|
|
80
|
+
// partial row-shaped test stub that predates the column. Both mean "no
|
|
81
|
+
// bindings" -- treat them alike rather than asserting `undefined` as an
|
|
82
|
+
// array.
|
|
83
|
+
credentialBindings: row.credentialBindings === null || row.credentialBindings === undefined
|
|
84
|
+
? null
|
|
85
|
+
: CredentialBinding.array().assert(row.credentialBindings),
|
|
57
86
|
};
|
|
58
87
|
}
|
|
59
|
-
export function
|
|
88
|
+
export function parseWorkflowDefinitionVersionRow(row) {
|
|
60
89
|
return {
|
|
61
90
|
...row,
|
|
62
|
-
status:
|
|
91
|
+
status: WorkflowDefinitionVersionStatusValidator.assert(row.status),
|
|
63
92
|
};
|
|
64
93
|
}
|
|
65
94
|
export function parseGrantRow(row) {
|
|
@@ -70,6 +99,75 @@ export function parseGrantRow(row) {
|
|
|
70
99
|
conditions: row.conditions !== null ? JSONObject.assert(row.conditions) : null,
|
|
71
100
|
};
|
|
72
101
|
}
|
|
102
|
+
export function parseApprovalRow(row) {
|
|
103
|
+
return {
|
|
104
|
+
...row,
|
|
105
|
+
scope: row.scope !== null ? ApprovalScopeValidator.assert(row.scope) : null,
|
|
106
|
+
status: ApprovalStatusValidator.assert(row.status),
|
|
107
|
+
toolDefinition: JSONObject.assert(row.toolDefinition),
|
|
108
|
+
toolArguments: JSONObject.assert(row.toolArguments),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function parsePrincipalRow(row) {
|
|
112
|
+
return {
|
|
113
|
+
...row,
|
|
114
|
+
kind: PrincipalKindValidator.assert(row.kind),
|
|
115
|
+
status: PrincipalStatusValidator.assert(row.status),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function parsePrincipalKeyRow(row) {
|
|
119
|
+
return {
|
|
120
|
+
...row,
|
|
121
|
+
status: PrincipalKeyStatusValidator.assert(row.status),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function parseSignalCorrelationRow(row) {
|
|
125
|
+
return {
|
|
126
|
+
...row,
|
|
127
|
+
kind: SignalKindValidator.assert(row.kind),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function parseWorkflowRunRow(row) {
|
|
131
|
+
return {
|
|
132
|
+
...row,
|
|
133
|
+
status: WorkflowRunStatusValidator.assert(row.status),
|
|
134
|
+
modelPreferences: row.modelPreferences !== null
|
|
135
|
+
? InvokerModelPreferences.assert(row.modelPreferences)
|
|
136
|
+
: null,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function parseWorkflowRunLaunchSpecRow(row) {
|
|
140
|
+
const sourceOfferingIds = StringArray.assert(row.sourceOfferingIds);
|
|
141
|
+
assertLaunchSpecSources(sourceOfferingIds, row.defaultSourceOfferingId);
|
|
142
|
+
return {
|
|
143
|
+
...row,
|
|
144
|
+
frozenApprovalBundle: FrozenApprovalBundle.assert(row.frozenApprovalBundle),
|
|
145
|
+
sourceOfferingIds,
|
|
146
|
+
deployContent: JSONObject.assert(row.deployContent),
|
|
147
|
+
toolPackagePins: row.toolPackagePins !== null
|
|
148
|
+
? ToolPackagePinArray.assert(row.toolPackagePins)
|
|
149
|
+
: null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export function parseWorkflowRunDispatchRow(row) {
|
|
153
|
+
return {
|
|
154
|
+
...row,
|
|
155
|
+
kind: WorkflowRunDispatchKindValidator.assert(row.kind),
|
|
156
|
+
status: WorkflowRunDispatchStatusValidator.assert(row.status),
|
|
157
|
+
stepGrants: WireGrantRuleArray.assert(row.stepGrants),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function assertLaunchSpecSources(sourceOfferingIds, defaultSourceOfferingId) {
|
|
161
|
+
if (sourceOfferingIds.length === 0) {
|
|
162
|
+
throw new Error("workflow launch spec requires at least one source offering");
|
|
163
|
+
}
|
|
164
|
+
if (new Set(sourceOfferingIds).size !== sourceOfferingIds.length) {
|
|
165
|
+
throw new Error("workflow launch spec source offering ids must be unique");
|
|
166
|
+
}
|
|
167
|
+
if (!sourceOfferingIds.includes(defaultSourceOfferingId)) {
|
|
168
|
+
throw new Error(`workflow launch spec default source ${defaultSourceOfferingId} is not in its source offering ids`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
73
171
|
export function parseOfferingRow(row) {
|
|
74
172
|
return {
|
|
75
173
|
...row,
|
|
@@ -101,12 +199,13 @@ export function parseModelOfferingRow(row) {
|
|
|
101
199
|
return {
|
|
102
200
|
...row,
|
|
103
201
|
capabilities: Capability.array().assert(row.capabilities),
|
|
202
|
+
quirks: row.quirks !== null ? JSONObject.assert(row.quirks) : null,
|
|
104
203
|
};
|
|
105
204
|
}
|
|
106
205
|
export function parseTenantRow(row) {
|
|
107
206
|
return {
|
|
108
207
|
...row,
|
|
109
|
-
config: row.config !== null ?
|
|
208
|
+
config: row.config !== null ? TenantConfig.assert(row.config) : null,
|
|
110
209
|
};
|
|
111
210
|
}
|
|
112
211
|
export function parseWalletRow(row) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CredentialCipher } from "@intx/types";
|
|
2
|
+
import type { DB, DBExecutor } from "./client.js";
|
|
3
|
+
type DBHandle = DB["db"];
|
|
4
|
+
export type CreatePrincipalKeyStoreDeps = {
|
|
5
|
+
db: DBHandle;
|
|
6
|
+
/**
|
|
7
|
+
* Seals the private key seed at rest. Production supplies a real env-key
|
|
8
|
+
* cipher under `PRINCIPAL_KEY_ENCRYPTION_KEY`; tests and local dev may supply
|
|
9
|
+
* the noop cipher, which stores the seed as plaintext.
|
|
10
|
+
*/
|
|
11
|
+
cipher: CredentialCipher;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Store for the `principal_key` table -- a principal's Ed25519 signing key.
|
|
15
|
+
*
|
|
16
|
+
* The hub custodies the private key: it mints, seals, and signs with it on the
|
|
17
|
+
* principal's behalf. A signature therefore ATTRIBUTES an action to a principal
|
|
18
|
+
* but is NOT non-repudiable against the hub operator, who holds the key. See
|
|
19
|
+
* docs/AUTH.md.
|
|
20
|
+
*
|
|
21
|
+
* The store owns the one-active-key-per-principal invariant end to end: `sign`
|
|
22
|
+
* and `getPublicKey` resolve the single active row, and `generate` inserts a new
|
|
23
|
+
* active key that the table's partial unique index rejects if the principal
|
|
24
|
+
* already has one. The private seed never leaves this module -- `sign` decrypts,
|
|
25
|
+
* signs, and discards it; no method returns private material.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createPrincipalKeyStore({ db, cipher, }: CreatePrincipalKeyStoreDeps): {
|
|
28
|
+
/**
|
|
29
|
+
* Mint a fresh active signing key for a principal. Generates an Ed25519 key
|
|
30
|
+
* pair, seals the hex-encoded 32-byte seed with the cipher bound to the
|
|
31
|
+
* key's row and column, and inserts it. A principal that already holds an
|
|
32
|
+
* active key trips the partial unique index and this throws. Returns the
|
|
33
|
+
* hex-encoded public key; never returns private material.
|
|
34
|
+
*/
|
|
35
|
+
generate(principalId: string, tx?: DBExecutor): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Sign a message with the principal's active key. Decrypts the sealed seed,
|
|
38
|
+
* produces the raw 64-byte Ed25519 signature, and discards the seed. Throws
|
|
39
|
+
* when the principal has no active key. The seed stays a local that goes out
|
|
40
|
+
* of scope; it is never returned or logged.
|
|
41
|
+
*/
|
|
42
|
+
sign(principalId: string, message: Uint8Array, tx?: DBExecutor): Promise<Uint8Array>;
|
|
43
|
+
/**
|
|
44
|
+
* The hex-encoded public key of a principal's active signing key. Throws
|
|
45
|
+
* when the principal has no active key rather than defaulting, so a missing
|
|
46
|
+
* key surfaces at the call site instead of downstream.
|
|
47
|
+
*/
|
|
48
|
+
getPublicKey(principalId: string, tx?: DBExecutor): Promise<string>;
|
|
49
|
+
};
|
|
50
|
+
export type PrincipalKeyStore = ReturnType<typeof createPrincipalKeyStore>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { and, eq } from "drizzle-orm";
|
|
2
|
+
import { generateKeyPair, signEd25519 } from "@intx/crypto";
|
|
3
|
+
import { hexDecode, hexEncode, principalKeyAad } from "@intx/types";
|
|
4
|
+
import { generateId } from "@intx/hub-common";
|
|
5
|
+
import { principalKey } from "./schema/principal-keys.js";
|
|
6
|
+
/**
|
|
7
|
+
* Store for the `principal_key` table -- a principal's Ed25519 signing key.
|
|
8
|
+
*
|
|
9
|
+
* The hub custodies the private key: it mints, seals, and signs with it on the
|
|
10
|
+
* principal's behalf. A signature therefore ATTRIBUTES an action to a principal
|
|
11
|
+
* but is NOT non-repudiable against the hub operator, who holds the key. See
|
|
12
|
+
* docs/AUTH.md.
|
|
13
|
+
*
|
|
14
|
+
* The store owns the one-active-key-per-principal invariant end to end: `sign`
|
|
15
|
+
* and `getPublicKey` resolve the single active row, and `generate` inserts a new
|
|
16
|
+
* active key that the table's partial unique index rejects if the principal
|
|
17
|
+
* already has one. The private seed never leaves this module -- `sign` decrypts,
|
|
18
|
+
* signs, and discards it; no method returns private material.
|
|
19
|
+
*/
|
|
20
|
+
export function createPrincipalKeyStore({ db, cipher, }) {
|
|
21
|
+
async function loadActive(principalId, tx) {
|
|
22
|
+
const [row] = await (tx ?? db)
|
|
23
|
+
.select()
|
|
24
|
+
.from(principalKey)
|
|
25
|
+
.where(and(eq(principalKey.principalId, principalId), eq(principalKey.status, "active")))
|
|
26
|
+
.limit(1);
|
|
27
|
+
return row;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
/**
|
|
31
|
+
* Mint a fresh active signing key for a principal. Generates an Ed25519 key
|
|
32
|
+
* pair, seals the hex-encoded 32-byte seed with the cipher bound to the
|
|
33
|
+
* key's row and column, and inserts it. A principal that already holds an
|
|
34
|
+
* active key trips the partial unique index and this throws. Returns the
|
|
35
|
+
* hex-encoded public key; never returns private material.
|
|
36
|
+
*/
|
|
37
|
+
async generate(principalId, tx) {
|
|
38
|
+
const id = generateId("principalKey");
|
|
39
|
+
const keyPair = await generateKeyPair();
|
|
40
|
+
const sealedPrivateKey = await cipher.encrypt(hexEncode(keyPair.privateKey), principalKeyAad(id, "private_key"));
|
|
41
|
+
const publicKey = hexEncode(keyPair.publicKey);
|
|
42
|
+
const now = new Date();
|
|
43
|
+
await (tx ?? db).insert(principalKey).values({
|
|
44
|
+
id,
|
|
45
|
+
principalId,
|
|
46
|
+
publicKey,
|
|
47
|
+
privateKey: sealedPrivateKey,
|
|
48
|
+
status: "active",
|
|
49
|
+
createdAt: now,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
});
|
|
52
|
+
return publicKey;
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Sign a message with the principal's active key. Decrypts the sealed seed,
|
|
56
|
+
* produces the raw 64-byte Ed25519 signature, and discards the seed. Throws
|
|
57
|
+
* when the principal has no active key. The seed stays a local that goes out
|
|
58
|
+
* of scope; it is never returned or logged.
|
|
59
|
+
*/
|
|
60
|
+
async sign(principalId, message, tx) {
|
|
61
|
+
const row = await loadActive(principalId, tx);
|
|
62
|
+
if (row === undefined) {
|
|
63
|
+
throw new Error(`principalKeyStore.sign: principal ${principalId} has no active key`);
|
|
64
|
+
}
|
|
65
|
+
const seedHex = await cipher.decrypt(row.privateKey, principalKeyAad(row.id, "private_key"));
|
|
66
|
+
const seed = hexDecode(seedHex);
|
|
67
|
+
return signEd25519(seed, message);
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* The hex-encoded public key of a principal's active signing key. Throws
|
|
71
|
+
* when the principal has no active key rather than defaulting, so a missing
|
|
72
|
+
* key surfaces at the call site instead of downstream.
|
|
73
|
+
*/
|
|
74
|
+
async getPublicKey(principalId, tx) {
|
|
75
|
+
const row = await loadActive(principalId, tx);
|
|
76
|
+
if (row === undefined) {
|
|
77
|
+
throw new Error(`principalKeyStore.getPublicKey: principal ${principalId} has no active key`);
|
|
78
|
+
}
|
|
79
|
+
return row.publicKey;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DB, DBExecutor } from "./client.js";
|
|
2
|
+
import { principal } from "./schema/principals.js";
|
|
3
|
+
import { parsePrincipalRow } from "./parse-row.js";
|
|
4
|
+
import type { PrincipalKeyStore } from "./principal-key-store.js";
|
|
5
|
+
type DBHandle = DB["db"];
|
|
6
|
+
type PrincipalInsert = typeof principal.$inferInsert;
|
|
7
|
+
type ParsedPrincipal = ReturnType<typeof parsePrincipalRow>;
|
|
8
|
+
/**
|
|
9
|
+
* Store for the `principal` table -- the single owner of principal creation.
|
|
10
|
+
* Every principal insert in the system routes through this one factory, and
|
|
11
|
+
* every principal it creates is minted an active signing key in the same
|
|
12
|
+
* transaction, so a principal never exists without a key.
|
|
13
|
+
*
|
|
14
|
+
* Each method accepts an optional transaction handle so the principal row and
|
|
15
|
+
* its key join the transaction that co-writes the principal's roles and grants.
|
|
16
|
+
* When no handle is passed the store opens its own transaction, because the
|
|
17
|
+
* principal insert and the key mint are two writes that must commit together.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createPrincipalStore(db: DBHandle, principalKeyStore: PrincipalKeyStore): {
|
|
20
|
+
/**
|
|
21
|
+
* Insert a new principal and mint its signing key, failing loudly on a
|
|
22
|
+
* natural-key conflict. Callers that have already established the principal
|
|
23
|
+
* is new (a fresh tenant owner, an invite past its existence pre-check) use
|
|
24
|
+
* this so a concurrent duplicate surfaces as a unique violation rather than
|
|
25
|
+
* a silent success.
|
|
26
|
+
*/
|
|
27
|
+
create(row: PrincipalInsert, tx?: DBExecutor): Promise<ParsedPrincipal>;
|
|
28
|
+
/**
|
|
29
|
+
* Idempotent variant of `create` for the run path, where concurrent first
|
|
30
|
+
* deliveries race to reserve the same principal. On a win it inserts the
|
|
31
|
+
* principal, mints its key, and returns the parsed row; on a loss it returns
|
|
32
|
+
* `null` WITHOUT minting -- the winning transaction already minted the one
|
|
33
|
+
* active key -- so the caller can fall back to the winner's state.
|
|
34
|
+
*
|
|
35
|
+
* The conflict target is the natural key `(tenantId, kind, refId)`, NOT the
|
|
36
|
+
* surrogate `id`: user principals carry a random `generateId("principal")`,
|
|
37
|
+
* so their only real conflict key is the natural triple. The run path
|
|
38
|
+
* derives a deterministic id that agrees with the triple, so one target
|
|
39
|
+
* unifies both callers. Do not switch this to `principal.id`.
|
|
40
|
+
*/
|
|
41
|
+
createIfAbsent(row: PrincipalInsert, tx?: DBExecutor): Promise<ParsedPrincipal | null>;
|
|
42
|
+
};
|
|
43
|
+
export type PrincipalStore = ReturnType<typeof createPrincipalStore>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { principal } from "./schema/principals.js";
|
|
2
|
+
import { parsePrincipalRow } from "./parse-row.js";
|
|
3
|
+
/**
|
|
4
|
+
* Store for the `principal` table -- the single owner of principal creation.
|
|
5
|
+
* Every principal insert in the system routes through this one factory, and
|
|
6
|
+
* every principal it creates is minted an active signing key in the same
|
|
7
|
+
* transaction, so a principal never exists without a key.
|
|
8
|
+
*
|
|
9
|
+
* Each method accepts an optional transaction handle so the principal row and
|
|
10
|
+
* its key join the transaction that co-writes the principal's roles and grants.
|
|
11
|
+
* When no handle is passed the store opens its own transaction, because the
|
|
12
|
+
* principal insert and the key mint are two writes that must commit together.
|
|
13
|
+
*/
|
|
14
|
+
export function createPrincipalStore(db, principalKeyStore) {
|
|
15
|
+
return {
|
|
16
|
+
/**
|
|
17
|
+
* Insert a new principal and mint its signing key, failing loudly on a
|
|
18
|
+
* natural-key conflict. Callers that have already established the principal
|
|
19
|
+
* is new (a fresh tenant owner, an invite past its existence pre-check) use
|
|
20
|
+
* this so a concurrent duplicate surfaces as a unique violation rather than
|
|
21
|
+
* a silent success.
|
|
22
|
+
*/
|
|
23
|
+
async create(row, tx) {
|
|
24
|
+
const run = async (executor) => {
|
|
25
|
+
const [inserted] = await executor
|
|
26
|
+
.insert(principal)
|
|
27
|
+
.values(row)
|
|
28
|
+
.returning();
|
|
29
|
+
if (inserted === undefined) {
|
|
30
|
+
throw new Error(`principalStore.create: insert returned no row for ${row.id}`);
|
|
31
|
+
}
|
|
32
|
+
await principalKeyStore.generate(inserted.id, executor);
|
|
33
|
+
return parsePrincipalRow(inserted);
|
|
34
|
+
};
|
|
35
|
+
return tx === undefined ? db.transaction(run) : run(tx);
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Idempotent variant of `create` for the run path, where concurrent first
|
|
39
|
+
* deliveries race to reserve the same principal. On a win it inserts the
|
|
40
|
+
* principal, mints its key, and returns the parsed row; on a loss it returns
|
|
41
|
+
* `null` WITHOUT minting -- the winning transaction already minted the one
|
|
42
|
+
* active key -- so the caller can fall back to the winner's state.
|
|
43
|
+
*
|
|
44
|
+
* The conflict target is the natural key `(tenantId, kind, refId)`, NOT the
|
|
45
|
+
* surrogate `id`: user principals carry a random `generateId("principal")`,
|
|
46
|
+
* so their only real conflict key is the natural triple. The run path
|
|
47
|
+
* derives a deterministic id that agrees with the triple, so one target
|
|
48
|
+
* unifies both callers. Do not switch this to `principal.id`.
|
|
49
|
+
*/
|
|
50
|
+
async createIfAbsent(row, tx) {
|
|
51
|
+
const run = async (executor) => {
|
|
52
|
+
const [inserted] = await executor
|
|
53
|
+
.insert(principal)
|
|
54
|
+
.values(row)
|
|
55
|
+
.onConflictDoNothing({
|
|
56
|
+
target: [principal.tenantId, principal.kind, principal.refId],
|
|
57
|
+
})
|
|
58
|
+
.returning();
|
|
59
|
+
if (inserted === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
await principalKeyStore.generate(inserted.id, executor);
|
|
62
|
+
return parsePrincipalRow(inserted);
|
|
63
|
+
};
|
|
64
|
+
return tx === undefined ? db.transaction(run) : run(tx);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CredentialCipher } from "@intx/types";
|
|
2
|
+
import type { DB } from "./client.js";
|
|
3
|
+
export type RekeyReport = {
|
|
4
|
+
/** Credential `secret` columns newly encrypted. */
|
|
5
|
+
credentialSecrets: number;
|
|
6
|
+
/** Credential `refresh_secret` columns newly encrypted. */
|
|
7
|
+
credentialRefreshSecrets: number;
|
|
8
|
+
/** OAuth-client `client_secret` columns newly encrypted. */
|
|
9
|
+
oauthClientSecrets: number;
|
|
10
|
+
/** Columns already encrypted and left unchanged. */
|
|
11
|
+
alreadyEncrypted: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function rekeyCredentialSecrets(db: DB["db"], cipher: CredentialCipher): Promise<RekeyReport>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Re-key existing credential secrets: encrypt any secret still stored as
|
|
2
|
+
// plaintext, in place, under the given cipher.
|
|
3
|
+
//
|
|
4
|
+
// Ordinarily every secret is encrypted at its write site, so on a database
|
|
5
|
+
// created after encryption-at-rest landed there is nothing to do. This pass
|
|
6
|
+
// exists for the transition: a database whose rows predate encryption still
|
|
7
|
+
// holds plaintext, and the strict read path throws on a non-ciphertext value.
|
|
8
|
+
// Running this once (against a stopped hub, so no write races an un-re-keyed
|
|
9
|
+
// row) brings every row to the encrypted form.
|
|
10
|
+
//
|
|
11
|
+
// Idempotent: a value already a ciphertext is skipped, so the pass is safe to
|
|
12
|
+
// re-run or resume after a partial failure.
|
|
13
|
+
import { eq } from "drizzle-orm";
|
|
14
|
+
import { credentialAad } from "@intx/types";
|
|
15
|
+
import { isCiphertext } from "@intx/crypto";
|
|
16
|
+
import { credential } from "./schema/credentials.js";
|
|
17
|
+
import { oauthClient } from "./schema/oauth-clients.js";
|
|
18
|
+
export async function rekeyCredentialSecrets(db, cipher) {
|
|
19
|
+
const report = {
|
|
20
|
+
credentialSecrets: 0,
|
|
21
|
+
credentialRefreshSecrets: 0,
|
|
22
|
+
oauthClientSecrets: 0,
|
|
23
|
+
alreadyEncrypted: 0,
|
|
24
|
+
};
|
|
25
|
+
for (const row of await db.select().from(credential)) {
|
|
26
|
+
const updates = {};
|
|
27
|
+
if (isCiphertext(row.secret)) {
|
|
28
|
+
report.alreadyEncrypted += 1;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
updates.secret = await cipher.encrypt(row.secret, credentialAad(row.id, "secret"));
|
|
32
|
+
report.credentialSecrets += 1;
|
|
33
|
+
}
|
|
34
|
+
if (row.refreshSecret !== null) {
|
|
35
|
+
if (isCiphertext(row.refreshSecret)) {
|
|
36
|
+
report.alreadyEncrypted += 1;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
updates.refreshSecret = await cipher.encrypt(row.refreshSecret, credentialAad(row.id, "refreshSecret"));
|
|
40
|
+
report.credentialRefreshSecrets += 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (updates.secret !== undefined || updates.refreshSecret !== undefined) {
|
|
44
|
+
await db.update(credential).set(updates).where(eq(credential.id, row.id));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const row of await db.select().from(oauthClient)) {
|
|
48
|
+
if (isCiphertext(row.clientSecret)) {
|
|
49
|
+
report.alreadyEncrypted += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
await db
|
|
53
|
+
.update(oauthClient)
|
|
54
|
+
.set({
|
|
55
|
+
clientSecret: await cipher.encrypt(row.clientSecret, credentialAad(row.id, "clientSecret")),
|
|
56
|
+
})
|
|
57
|
+
.where(eq(oauthClient.id, row.id));
|
|
58
|
+
report.oauthClientSecrets += 1;
|
|
59
|
+
}
|
|
60
|
+
return report;
|
|
61
|
+
}
|