@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.
Files changed (207) hide show
  1. package/README.md +13 -2
  2. package/dist/approval-store.d.ts +59 -0
  3. package/dist/approval-store.js +92 -0
  4. package/dist/asset-resolution.d.ts +2 -2
  5. package/dist/backfill-principal-keys.d.ts +9 -0
  6. package/dist/backfill-principal-keys.js +51 -0
  7. package/dist/client.d.ts +37 -2
  8. package/dist/client.js +6 -0
  9. package/dist/config.d.ts +1 -0
  10. package/dist/config.js +2 -0
  11. package/dist/connection.js +12 -8
  12. package/dist/credential-resolution.d.ts +181 -20
  13. package/dist/credential-resolution.js +251 -3
  14. package/dist/grant-store.js +35 -13
  15. package/dist/index.d.ts +20 -5
  16. package/dist/index.js +19 -5
  17. package/dist/model-source-resolution.d.ts +54 -7
  18. package/dist/model-source-resolution.js +177 -22
  19. package/dist/parse-row.d.ts +245 -34
  20. package/dist/parse-row.js +117 -18
  21. package/dist/principal-key-store.d.ts +51 -0
  22. package/dist/principal-key-store.js +82 -0
  23. package/dist/principal-store.d.ts +44 -0
  24. package/dist/principal-store.js +67 -0
  25. package/dist/rekey-credential-secrets.d.ts +13 -0
  26. package/dist/rekey-credential-secrets.js +61 -0
  27. package/dist/schema/approvals.d.ts +245 -0
  28. package/dist/schema/approvals.js +57 -0
  29. package/dist/schema/catalog.d.ts +17 -0
  30. package/dist/schema/catalog.js +7 -1
  31. package/dist/schema/credentials.d.ts +1 -1
  32. package/dist/schema/git-tokens.js +1 -1
  33. package/dist/schema/grants.js +8 -2
  34. package/dist/schema/index.d.ts +10 -4
  35. package/dist/schema/index.js +10 -4
  36. package/dist/schema/messages.d.ts +5 -5
  37. package/dist/schema/messages.js +20 -9
  38. package/dist/schema/offerings.js +8 -2
  39. package/dist/schema/{workflow-deployments.d.ts → principal-keys.d.ts} +29 -32
  40. package/dist/schema/principal-keys.js +28 -0
  41. package/dist/schema/principals.d.ts +2 -2
  42. package/dist/schema/principals.js +7 -5
  43. package/dist/schema/providers.d.ts +17 -0
  44. package/dist/schema/providers.js +5 -0
  45. package/dist/schema/roles.js +6 -2
  46. package/dist/schema/session-assets.d.ts +1 -38
  47. package/dist/schema/session-assets.js +36 -19
  48. package/dist/schema/sessions.js +7 -2
  49. package/dist/schema/sidecar-allocation.d.ts +385 -0
  50. package/dist/schema/sidecar-allocation.js +53 -0
  51. package/dist/schema/sidecar.d.ts +2 -2
  52. package/dist/schema/sidecar.js +3 -1
  53. package/dist/schema/signal-correlations.d.ts +177 -0
  54. package/dist/schema/signal-correlations.js +31 -0
  55. package/dist/schema/tenants.js +13 -3
  56. package/dist/schema/wallets.d.ts +3 -3
  57. package/dist/schema/wallets.js +4 -2
  58. package/dist/schema/{agents.d.ts → workflow-definitions.d.ts} +79 -113
  59. package/dist/schema/workflow-definitions.js +97 -0
  60. package/dist/schema/workflow-probe.d.ts +358 -0
  61. package/dist/schema/workflow-probe.js +51 -0
  62. package/dist/schema/workflow-run-dispatch.d.ts +345 -0
  63. package/dist/schema/workflow-run-dispatch.js +63 -0
  64. package/dist/schema/{agent-assets.d.ts → workflow-run-execution.d.ts} +44 -27
  65. package/dist/schema/workflow-run-execution.js +48 -0
  66. package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
  67. package/dist/schema/workflow-run-launch-spec.js +26 -0
  68. package/dist/schema/{instances.d.ts → workflow-run.d.ts} +61 -66
  69. package/dist/schema/workflow-run.js +110 -0
  70. package/dist/sender-key-resolver.d.ts +91 -0
  71. package/dist/sender-key-resolver.js +184 -0
  72. package/dist/sidecar-allocation-store.d.ts +206 -0
  73. package/dist/sidecar-allocation-store.js +714 -0
  74. package/dist/signal-correlation-store.d.ts +36 -0
  75. package/dist/signal-correlation-store.js +63 -0
  76. package/dist/signer-identity.d.ts +12 -0
  77. package/dist/signer-identity.js +15 -0
  78. package/dist/tenant-hierarchy.d.ts +4 -1
  79. package/dist/tenant-hierarchy.js +37 -5
  80. package/dist/workflow-definition-store.d.ts +55 -0
  81. package/dist/workflow-definition-store.js +93 -0
  82. package/dist/workflow-probe-store.d.ts +47 -0
  83. package/dist/workflow-probe-store.js +124 -0
  84. package/dist/workflow-run-dispatch-store.d.ts +83 -0
  85. package/dist/workflow-run-dispatch-store.js +308 -0
  86. package/dist/workflow-run-launch-spec-store.d.ts +13 -0
  87. package/dist/workflow-run-launch-spec-store.js +30 -0
  88. package/dist/workflow-run-store.d.ts +54 -0
  89. package/dist/workflow-run-store.js +85 -0
  90. package/migrations/0037_credential_use_backfill.sql +39 -0
  91. package/migrations/0038_chilly_blacklash.sql +37 -0
  92. package/migrations/0039_quick_carnage.sql +2 -0
  93. package/migrations/0040_reshape_approval_origin.sql +24 -0
  94. package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
  95. package/migrations/0042_youthful_mantis.sql +2 -0
  96. package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
  97. package/migrations/0044_model_offering_quirks.sql +1 -0
  98. package/migrations/0045_create_workflow_run.sql +13 -0
  99. package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
  100. package/migrations/0047_create_workflow_definition.sql +30 -0
  101. package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
  102. package/migrations/0049_curly_omega_flight.sql +2 -0
  103. package/migrations/0050_late_crystal.sql +10 -0
  104. package/migrations/0051_funny_madelyne_pryor.sql +1 -0
  105. package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
  106. package/migrations/0053_session_mail_session_id_index.sql +1 -0
  107. package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
  108. package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
  109. package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
  110. package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
  111. package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
  112. package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
  113. package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
  114. package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
  115. package/migrations/0062_drop_agent_asset_table.sql +11 -0
  116. package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
  117. package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
  118. package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
  119. package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
  120. package/migrations/0067_add_workflow_definition_kind.sql +10 -0
  121. package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
  122. package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
  123. package/migrations/0070_flashy_proteus.sql +13 -0
  124. package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
  125. package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
  126. package/migrations/0074_add_provider_api_base_url.sql +1 -0
  127. package/migrations/0075_workflow_run_launch_spec.sql +17 -0
  128. package/migrations/0076_sidecar_allocation.sql +40 -0
  129. package/migrations/0077_workflow_run_dispatch.sql +29 -0
  130. package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
  131. package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
  132. package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
  133. package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
  134. package/migrations/0082_blue_black_queen.sql +1 -0
  135. package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
  136. package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
  137. package/migrations/0085_add_approval_run_idx.sql +1 -0
  138. package/migrations/0086_cool_human_cannonball.sql +1 -0
  139. package/migrations/0087_drop_sidecar_placement.sql +3 -0
  140. package/migrations/0088_thick_sprite.sql +32 -0
  141. package/migrations/0089_tense_selene.sql +13 -0
  142. package/migrations/0090_tenant_domain_lower_unique.sql +2 -0
  143. package/migrations/0091_workflow_run_dispatch_sender_address.sql +21 -0
  144. package/migrations/0092_sidecar_destroy_failed.sql +4 -0
  145. package/migrations/0093_sidecar_initialization.sql +2 -0
  146. package/migrations/meta/0037_snapshot.json +3237 -0
  147. package/migrations/meta/0038_snapshot.json +3470 -0
  148. package/migrations/meta/0039_snapshot.json +3470 -0
  149. package/migrations/meta/0040_snapshot.json +3489 -0
  150. package/migrations/meta/0041_snapshot.json +3489 -0
  151. package/migrations/meta/0042_snapshot.json +3489 -0
  152. package/migrations/meta/0043_snapshot.json +3498 -0
  153. package/migrations/meta/0044_snapshot.json +3504 -0
  154. package/migrations/meta/0045_snapshot.json +3589 -0
  155. package/migrations/meta/0046_snapshot.json +3607 -0
  156. package/migrations/meta/0047_snapshot.json +3832 -0
  157. package/migrations/meta/0048_snapshot.json +3853 -0
  158. package/migrations/meta/0049_snapshot.json +3854 -0
  159. package/migrations/meta/0050_snapshot.json +3924 -0
  160. package/migrations/meta/0051_snapshot.json +3940 -0
  161. package/migrations/meta/0052_snapshot.json +3931 -0
  162. package/migrations/meta/0053_snapshot.json +3952 -0
  163. package/migrations/meta/0054_snapshot.json +3952 -0
  164. package/migrations/meta/0055_snapshot.json +3952 -0
  165. package/migrations/meta/0056_snapshot.json +3952 -0
  166. package/migrations/meta/0057_snapshot.json +3839 -0
  167. package/migrations/meta/0058_snapshot.json +3839 -0
  168. package/migrations/meta/0059_snapshot.json +3839 -0
  169. package/migrations/meta/0060_snapshot.json +3830 -0
  170. package/migrations/meta/0061_snapshot.json +3821 -0
  171. package/migrations/meta/0062_snapshot.json +3723 -0
  172. package/migrations/meta/0063_snapshot.json +3723 -0
  173. package/migrations/meta/0064_snapshot.json +3723 -0
  174. package/migrations/meta/0065_snapshot.json +3729 -0
  175. package/migrations/meta/0066_snapshot.json +3729 -0
  176. package/migrations/meta/0067_snapshot.json +3736 -0
  177. package/migrations/meta/0068_snapshot.json +3347 -0
  178. package/migrations/meta/0069_snapshot.json +3340 -0
  179. package/migrations/meta/0070_snapshot.json +3444 -0
  180. package/migrations/meta/0072_snapshot.json +3450 -0
  181. package/migrations/meta/0073_snapshot.json +3455 -0
  182. package/migrations/meta/0074_snapshot.json +3461 -0
  183. package/migrations/meta/0075_snapshot.json +3567 -0
  184. package/migrations/meta/0076_snapshot.json +3851 -0
  185. package/migrations/meta/0077_snapshot.json +4067 -0
  186. package/migrations/meta/0078_snapshot.json +4078 -0
  187. package/migrations/meta/0079_snapshot.json +4078 -0
  188. package/migrations/meta/0080_snapshot.json +4078 -0
  189. package/migrations/meta/0081_snapshot.json +4096 -0
  190. package/migrations/meta/0082_snapshot.json +4102 -0
  191. package/migrations/meta/0083_snapshot.json +4096 -0
  192. package/migrations/meta/0084_snapshot.json +4096 -0
  193. package/migrations/meta/0085_snapshot.json +4111 -0
  194. package/migrations/meta/0086_snapshot.json +4117 -0
  195. package/migrations/meta/0087_snapshot.json +4100 -0
  196. package/migrations/meta/0088_snapshot.json +4286 -0
  197. package/migrations/meta/0089_snapshot.json +4376 -0
  198. package/migrations/meta/0090_snapshot.json +4387 -0
  199. package/migrations/meta/0091_snapshot.json +4397 -0
  200. package/migrations/meta/0092_snapshot.json +4397 -0
  201. package/migrations/meta/0093_snapshot.json +4407 -0
  202. package/migrations/meta/_journal.json +392 -0
  203. package/package.json +7 -3
  204. package/dist/schema/agent-assets.js +0 -15
  205. package/dist/schema/agents.js +0 -57
  206. package/dist/schema/instances.js +0 -39
  207. package/dist/schema/workflow-deployments.js +0 -37
package/README.md CHANGED
@@ -31,12 +31,23 @@ const row = await db.query.agent.findFirst({
31
31
  });
32
32
  ```
33
33
 
34
- The `parseRow` functions (`parseAgentRow`, `parseGrantRow`, and so
35
- on) validate each table's `jsonb` columns once and return fully
34
+ The `parseRow` functions (`parseWorkflowDefinitionRow`, `parseGrantRow`,
35
+ and so on) validate each table's `jsonb` columns once and return fully
36
36
  typed values; downstream code uses those values without
37
37
  re-casting. See `CONVENTIONS.md` for the project-wide rule against
38
38
  cast-at-callsite in DB consumers.
39
39
 
40
+ Connections created by `createDB` set PostgreSQL's `statement_timeout` to
41
+ 60,000 milliseconds. Override it with `statementTimeoutMs` in `DBConfig`
42
+ (a positive integer up to 2,147,483,647 milliseconds). The Hub and the
43
+ `bin/` database scripts read this override from `DB_STATEMENT_TIMEOUT_MS`.
44
+
45
+ PostgreSQL cancels statements that exceed the deadline, including time
46
+ waiting for locks. An uncaught timeout rolls back the enclosing transaction.
47
+ This bounds individual statements once they reach PostgreSQL; pool waits,
48
+ network stalls, and time between statements in an open transaction remain
49
+ outside that deadline. Migration clients use their own connection settings.
50
+
40
51
  ## Model catalog
41
52
 
42
53
  The `model`, `model_provider`, `model_offering`, and `model_pricing`
@@ -0,0 +1,59 @@
1
+ import type { DB, DBExecutor } from "./client.js";
2
+ import { approval } from "./schema/approvals.js";
3
+ import { parseApprovalRow } from "./parse-row.js";
4
+ type DBHandle = DB["db"];
5
+ type ApprovalRow = typeof approval.$inferSelect;
6
+ type ApprovalInsert = typeof approval.$inferInsert;
7
+ type ParsedApproval = ReturnType<typeof parseApprovalRow>;
8
+ type ApprovalResolution = Exclude<ApprovalRow["status"], "pending">;
9
+ type ApprovalScope = NonNullable<ApprovalRow["scope"]>;
10
+ export type ResolveApprovalArgs = {
11
+ status: ApprovalResolution;
12
+ scope?: ApprovalScope;
13
+ resolvedAt: Date;
14
+ };
15
+ /**
16
+ * Store for the `approval` table backing the approval round-trip. Each method
17
+ * accepts an optional transaction handle so the resolver can flip the approval
18
+ * and its signal correlation in a single `db.transaction`; when omitted the
19
+ * method runs against the store's own connection.
20
+ */
21
+ export declare function createApprovalStore(db: DBHandle): {
22
+ create(row: ApprovalInsert, tx?: DBExecutor): Promise<ParsedApproval>;
23
+ /**
24
+ * Idempotent variant of `create`. On a `correlationId` unique conflict the
25
+ * insert is a no-op and this returns `null` rather than throwing, so a
26
+ * redelivered register frame (sidecar reconnect, workflow-log replay,
27
+ * supervisor restart re-emitting) does not fail the co-write. Conflicts
28
+ * on `correlationId` -- not the `id` primary key -- because the register
29
+ * co-write mints a fresh `id` per frame while the correlation is the stable
30
+ * dedup key. Returns the parsed row only when this call performed the
31
+ * insert.
32
+ */
33
+ createIfAbsent(row: ApprovalInsert, tx?: DBExecutor): Promise<ParsedApproval | null>;
34
+ findByCorrelationId(correlationId: string, tx?: DBExecutor): Promise<ParsedApproval | null>;
35
+ /**
36
+ * Look up an approval by its primary key. The resolve/reject routes key on
37
+ * the `approvalId` path parameter, which is the row's `id`, so they need a
38
+ * by-id read distinct from the by-correlation read the register co-write
39
+ * uses. Returns null when no row carries the id.
40
+ */
41
+ findById(id: string, tx?: DBExecutor): Promise<ParsedApproval | null>;
42
+ /**
43
+ * List a run's approvals, newest first, scoped by `tenantId` so one
44
+ * tenant's approvals never leak into another's view. Serves the run
45
+ * approvals list route; callers apply whatever status/scope predicate they
46
+ * need over the result.
47
+ */
48
+ listByRunId(tenantId: string, runId: string, tx?: DBExecutor): Promise<ParsedApproval[]>;
49
+ /**
50
+ * Conditionally resolve a pending approval. The `WHERE status = 'pending'`
51
+ * guard makes resolution terminal at the database: the first caller to
52
+ * resolve a given correlation gets the updated row back; any later caller
53
+ * (a duplicate delivery, a timeout racing an approval) matches no row and
54
+ * receives null.
55
+ */
56
+ resolve(correlationId: string, args: ResolveApprovalArgs, tx?: DBExecutor): Promise<ParsedApproval | null>;
57
+ };
58
+ export type ApprovalStore = ReturnType<typeof createApprovalStore>;
59
+ export {};
@@ -0,0 +1,92 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { approval } from "./schema/approvals.js";
3
+ import { parseApprovalRow } from "./parse-row.js";
4
+ /**
5
+ * Store for the `approval` table backing the approval round-trip. Each method
6
+ * accepts an optional transaction handle so the resolver can flip the approval
7
+ * and its signal correlation in a single `db.transaction`; when omitted the
8
+ * method runs against the store's own connection.
9
+ */
10
+ export function createApprovalStore(db) {
11
+ return {
12
+ async create(row, tx) {
13
+ const [inserted] = await (tx ?? db)
14
+ .insert(approval)
15
+ .values(row)
16
+ .returning();
17
+ if (inserted === undefined) {
18
+ throw new Error(`approvalStore.create: insert returned no row for ${row.id}`);
19
+ }
20
+ return parseApprovalRow(inserted);
21
+ },
22
+ /**
23
+ * Idempotent variant of `create`. On a `correlationId` unique conflict the
24
+ * insert is a no-op and this returns `null` rather than throwing, so a
25
+ * redelivered register frame (sidecar reconnect, workflow-log replay,
26
+ * supervisor restart re-emitting) does not fail the co-write. Conflicts
27
+ * on `correlationId` -- not the `id` primary key -- because the register
28
+ * co-write mints a fresh `id` per frame while the correlation is the stable
29
+ * dedup key. Returns the parsed row only when this call performed the
30
+ * insert.
31
+ */
32
+ async createIfAbsent(row, tx) {
33
+ const [inserted] = await (tx ?? db)
34
+ .insert(approval)
35
+ .values(row)
36
+ .onConflictDoNothing({ target: approval.correlationId })
37
+ .returning();
38
+ return inserted === undefined ? null : parseApprovalRow(inserted);
39
+ },
40
+ async findByCorrelationId(correlationId, tx) {
41
+ const row = await (tx ?? db).query.approval.findFirst({
42
+ where: eq(approval.correlationId, correlationId),
43
+ });
44
+ return row === undefined ? null : parseApprovalRow(row);
45
+ },
46
+ /**
47
+ * Look up an approval by its primary key. The resolve/reject routes key on
48
+ * the `approvalId` path parameter, which is the row's `id`, so they need a
49
+ * by-id read distinct from the by-correlation read the register co-write
50
+ * uses. Returns null when no row carries the id.
51
+ */
52
+ async findById(id, tx) {
53
+ const row = await (tx ?? db).query.approval.findFirst({
54
+ where: eq(approval.id, id),
55
+ });
56
+ return row === undefined ? null : parseApprovalRow(row);
57
+ },
58
+ /**
59
+ * List a run's approvals, newest first, scoped by `tenantId` so one
60
+ * tenant's approvals never leak into another's view. Serves the run
61
+ * approvals list route; callers apply whatever status/scope predicate they
62
+ * need over the result.
63
+ */
64
+ async listByRunId(tenantId, runId, tx) {
65
+ const rows = await (tx ?? db).query.approval.findMany({
66
+ where: and(eq(approval.tenantId, tenantId), eq(approval.runId, runId)),
67
+ orderBy: (a, { desc }) => desc(a.createdAt),
68
+ });
69
+ return rows.map(parseApprovalRow);
70
+ },
71
+ /**
72
+ * Conditionally resolve a pending approval. The `WHERE status = 'pending'`
73
+ * guard makes resolution terminal at the database: the first caller to
74
+ * resolve a given correlation gets the updated row back; any later caller
75
+ * (a duplicate delivery, a timeout racing an approval) matches no row and
76
+ * receives null.
77
+ */
78
+ async resolve(correlationId, args, tx) {
79
+ const [updated] = await (tx ?? db)
80
+ .update(approval)
81
+ .set({
82
+ status: args.status,
83
+ scope: args.scope ?? null,
84
+ resolvedAt: args.resolvedAt,
85
+ updatedAt: new Date(),
86
+ })
87
+ .where(and(eq(approval.correlationId, correlationId), eq(approval.status, "pending")))
88
+ .returning();
89
+ return updated === undefined ? null : parseApprovalRow(updated);
90
+ },
91
+ };
92
+ }
@@ -23,8 +23,8 @@ export declare function resolveAssetByName(db: DB["db"], tenantId: string, kind:
23
23
  createdAt: Date;
24
24
  updatedAt: Date;
25
25
  tenantId: string;
26
- creatorPrincipalId: string | null;
27
26
  displayName: string | null;
27
+ creatorPrincipalId: string | null;
28
28
  } | null>;
29
29
  /**
30
30
  * Resolves an asset by ID, validating that it belongs to the given
@@ -38,8 +38,8 @@ export declare function resolveAssetById(db: DB["db"], tenantId: string, assetId
38
38
  createdAt: Date;
39
39
  updatedAt: Date;
40
40
  tenantId: string;
41
- creatorPrincipalId: string | null;
42
41
  displayName: string | null;
42
+ creatorPrincipalId: string | null;
43
43
  } | null>;
44
44
  /**
45
45
  * Lists assets visible to the tenant, including those inherited from
@@ -0,0 +1,9 @@
1
+ import type { DB } from "./client.js";
2
+ import type { PrincipalKeyStore } from "./principal-key-store.js";
3
+ export type BackfillPrincipalKeysReport = {
4
+ /** Principals that were keyless and received a fresh active key. */
5
+ keysGenerated: number;
6
+ /** Principals that already held an active key and were left untouched. */
7
+ alreadyKeyed: number;
8
+ };
9
+ export declare function backfillPrincipalKeys(db: DB["db"], principalKeyStore: PrincipalKeyStore): Promise<BackfillPrincipalKeysReport>;
@@ -0,0 +1,51 @@
1
+ // Backfill per-principal signing keys onto the principals that predate the
2
+ // feature.
3
+ //
4
+ // Minting a key lives only in the principal-creation owner, so every principal
5
+ // CREATED after that landed already has an active key. This one-shot pass keys
6
+ // the pre-existing population so the whole non-agent principal set matches the
7
+ // invariant "every non-agent principal has an active key".
8
+ //
9
+ // Scope is every `user` and `workflow` principal REGARDLESS of status, not just
10
+ // active ones. A status flip (e.g. an invite moving from `invited` to `active`)
11
+ // reuses the same principal row and does not re-mint, so an active-only pass
12
+ // would leave a pre-existing `invited`/`suspended` principal keyless and turn it
13
+ // into a keyless ACTIVE principal the moment it is activated. `agent` is a
14
+ // legacy/inert kind that is never keyed at creation either, so it is skipped to
15
+ // keep the populations consistent.
16
+ //
17
+ // Idempotent: a principal that already holds an active key is skipped, so the
18
+ // pass is safe to re-run or resume after a partial failure. Each principal is
19
+ // keyed in its own autocommit, so a partial failure leaves the earlier
20
+ // principals keyed. Safe to run against a LIVE hub -- nothing reads principal
21
+ // keys yet, the hub only mints for newly-created principals (disjoint from this
22
+ // pass's pre-existing set), and the `principal_key` active-key unique index
23
+ // blocks a double active key.
24
+ import { and, eq, inArray } from "drizzle-orm";
25
+ import { principal } from "./schema/principals.js";
26
+ import { principalKey } from "./schema/principal-keys.js";
27
+ const BACKFILLED_KINDS = ["user", "workflow"];
28
+ export async function backfillPrincipalKeys(db, principalKeyStore) {
29
+ // The left join is scoped to the active key, so `activeKeyId` is null exactly
30
+ // when the principal has no active key -- a principal holding only a retired
31
+ // key counts as keyless and is re-keyed. The active-key unique index makes at
32
+ // most one active row per principal, so each principal appears once.
33
+ const rows = await db
34
+ .select({ principalId: principal.id, activeKeyId: principalKey.id })
35
+ .from(principal)
36
+ .leftJoin(principalKey, and(eq(principalKey.principalId, principal.id), eq(principalKey.status, "active")))
37
+ .where(inArray(principal.kind, [...BACKFILLED_KINDS]));
38
+ const report = {
39
+ keysGenerated: 0,
40
+ alreadyKeyed: 0,
41
+ };
42
+ for (const row of rows) {
43
+ if (row.activeKeyId !== null) {
44
+ report.alreadyKeyed += 1;
45
+ continue;
46
+ }
47
+ await principalKeyStore.generate(row.principalId);
48
+ report.keysGenerated += 1;
49
+ }
50
+ return report;
51
+ }
package/dist/client.d.ts CHANGED
@@ -1,9 +1,44 @@
1
+ import type { ExtractTablesWithRelations } from "drizzle-orm";
2
+ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
1
3
  import * as schema from "./schema/index.js";
4
+ /**
5
+ * A drizzle database over the hub schema, typed against the driver-agnostic
6
+ * `PgDatabase` base rather than a single driver. `createDB` builds one over
7
+ * postgres-js; a caller can equally build one over pglite -- `drizzle(handle,
8
+ * { schema })` from `drizzle-orm/pglite`, with the schema re-exported from
9
+ * `@intx/db/schema` -- and pass it to the store factories. The required
10
+ * `$client` keeps a bare `PgTransaction` unassignable (a transaction carries no
11
+ * `$client`), so a parameter typed against this still rejects a tx where only a
12
+ * top-level database belongs.
13
+ */
14
+ export type AnyPgDatabase = PgDatabase<PgQueryResultHKT, typeof schema, ExtractTablesWithRelations<typeof schema>> & {
15
+ $client: unknown;
16
+ };
17
+ /**
18
+ * The hub database handle: the database itself, plus `transaction` and
19
+ * `close`. `createDB` returns the postgres-js instantiation, but the handle is
20
+ * typed against `AnyPgDatabase` so the stores accept any pg driver -- a
21
+ * caller-built pglite database included.
22
+ */
23
+ export interface DB {
24
+ db: AnyPgDatabase;
25
+ transaction: AnyPgDatabase["transaction"];
26
+ close: () => Promise<void>;
27
+ }
2
28
  export declare function createDB(raw: unknown): {
3
29
  db: import("drizzle-orm/postgres-js").PostgresJsDatabase<typeof schema> & {
4
30
  $client: import("postgres").Sql<{}>;
5
31
  };
6
- transaction: <T>(transaction: (tx: import("drizzle-orm/pg-core").PgTransaction<import("drizzle-orm/postgres-js").PostgresJsQueryResultHKT, typeof schema, import("drizzle-orm").ExtractTablesWithRelations<typeof schema>>) => Promise<T>, config?: import("drizzle-orm/pg-core").PgTransactionConfig) => Promise<T>;
32
+ transaction: <T>(transaction: (tx: import("drizzle-orm/pg-core").PgTransaction<import("drizzle-orm/postgres-js").PostgresJsQueryResultHKT, typeof schema, ExtractTablesWithRelations<typeof schema>>) => Promise<T>, config?: import("drizzle-orm/pg-core").PgTransactionConfig) => Promise<T>;
7
33
  close: () => Promise<void>;
8
34
  };
9
- export type DB = ReturnType<typeof createDB>;
35
+ /**
36
+ * A handle that can execute queries: either the top-level `db` or a
37
+ * transaction handle passed into a `db.transaction` callback. Store methods
38
+ * that accept an optional `tx` type it against this so a caller can hand in
39
+ * the transaction object and have the write join the surrounding transaction.
40
+ * `DB["db"]` alone rejects a `PgTransaction` (it lacks the `$client` field the
41
+ * top-level database carries), so a bare `DB["db"]` parameter cannot accept a
42
+ * tx.
43
+ */
44
+ export type DBExecutor = DB["db"] | Parameters<Parameters<DB["db"]["transaction"]>[0]>[0];
package/dist/client.js CHANGED
@@ -16,3 +16,9 @@ export function createDB(raw) {
16
16
  close: () => sql.end(),
17
17
  };
18
18
  }
19
+ // `createDB` returns the concrete postgres-js handle so its own callers keep
20
+ // the driver's precise result types (e.g. a typed `db.execute`). This assures
21
+ // at the definition site that the concrete handle still satisfies `DB`, the
22
+ // driver-agnostic contract the stores consume.
23
+ const _createDBReturnsDB = createDB;
24
+ void _createDBReturnsDB;
package/dist/config.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare const DBConfig: import("arktype/internal/variants/object.ts").Obj
6
6
  database: string;
7
7
  ssl?: boolean;
8
8
  max?: number;
9
+ statementTimeoutMs?: number;
9
10
  schema?: string;
10
11
  }, {}>;
11
12
  export type DBConfig = typeof DBConfig.infer;
package/dist/config.js CHANGED
@@ -7,6 +7,8 @@ export const DBConfig = type({
7
7
  database: "string",
8
8
  "ssl?": "boolean",
9
9
  "max?": "number.integer > 0",
10
+ // PostgreSQL statement deadline in milliseconds. Defaults to 60 seconds.
11
+ "statementTimeoutMs?": "0 < number.integer <= 2147483647",
10
12
  // Postgres schema name. When set, the connection's `search_path` is
11
13
  // pinned to this schema and migrations apply into it. This is the
12
14
  // mechanism the integration-test harness uses to give each spawned
@@ -11,13 +11,17 @@ export function createConnection(config) {
11
11
  database: config.database,
12
12
  max: config.max ?? 10,
13
13
  ...(config.ssl !== undefined && { ssl: config.ssl }),
14
- ...(config.schema !== undefined && {
15
- // Pin the connection's search_path so unqualified table
16
- // references resolve to the caller's schema. The migration
17
- // runner emits SQL with the schema baked into FK references,
18
- // but ORM-issued queries bind table names without a schema
19
- // qualifier and rely on this setting.
20
- connection: { search_path: quoteIdentifier(config.schema) },
21
- }),
14
+ connection: {
15
+ TimeZone: "UTC",
16
+ statement_timeout: config.statementTimeoutMs ?? 60_000,
17
+ ...(config.schema !== undefined && {
18
+ // Pin the connection's search_path so unqualified table
19
+ // references resolve to the caller's schema. The migration
20
+ // runner emits SQL with the schema baked into FK references,
21
+ // but ORM-issued queries bind table names without a schema
22
+ // qualifier and rely on this setting.
23
+ search_path: quoteIdentifier(config.schema),
24
+ }),
25
+ },
22
26
  });
23
27
  }
@@ -1,4 +1,95 @@
1
+ import type { CredentialBinding, CredentialCipher } from "@intx/types";
2
+ import type { CredentialDelivery, CredentialMaterialEntry } from "@intx/types/sidecar";
1
3
  import type { DB } from "./client.js";
4
+ /**
5
+ * Thrown by `resolveCredentialRequirement` when more than one credential
6
+ * matches a requirement and no name disambiguates them. A distinct type so
7
+ * callers can catch *this* condition (a launch-blocking configuration error)
8
+ * without also swallowing the DB reads the resolver performs first -- an
9
+ * infrastructure failure must surface, not be mislabeled as ambiguity.
10
+ */
11
+ export declare class AmbiguousCredentialError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /**
15
+ * Thrown when a referenced credential is not a tenant-owned credential the given
16
+ * tenant can use -- it does not exist, is not reachable in the tenant's ancestor
17
+ * chain, or is principal-owned. A distinct type so a caller can map a client's
18
+ * bad credential reference to a 4xx (a launch-blocking configuration error),
19
+ * separate from the infrastructure faults the resolvers otherwise surface.
20
+ */
21
+ export declare class CredentialUnauthorizedError extends Error {
22
+ readonly credentialId: string;
23
+ readonly tenantId: string;
24
+ constructor(credentialId: string, tenantId: string);
25
+ }
26
+ /**
27
+ * Resolve a credential USABLE by the launching tenant purely through ownership:
28
+ * it exists, is reachable in the tenant's ancestor chain, and is tenant-owned
29
+ * (`principalId IS NULL`). Returns the row -- the proof of authority -- or null.
30
+ *
31
+ * This is the one "usable by ownership" resolver: a tenant may use what it (or
32
+ * an ancestor) owns, and descendants inherit; a principal-owned credential is
33
+ * never usable this way, its delegation flowing from its owner instead. It
34
+ * wraps `resolveCredentialById` (the ancestor-chain check) with the tenant-owned
35
+ * gate, so callers do not re-implement the predicate. `buildSource` expands it
36
+ * inline only to distinguish an unresolved reference from a principal-owned one.
37
+ */
38
+ export declare function resolveTenantOwnedCredentialById(db: DB["db"], tenantId: string, credentialId: string): Promise<{
39
+ id: string;
40
+ name: string;
41
+ createdAt: Date;
42
+ updatedAt: Date;
43
+ expiresAt: Date | null;
44
+ providerId: string;
45
+ tenantId: string;
46
+ status: "active" | "error" | "expired" | "revoked";
47
+ principalId: string | null;
48
+ description: string | null;
49
+ scopes: string[] | null;
50
+ metadata: unknown;
51
+ oauthClientId: string | null;
52
+ type: "api_key" | "oauth_token" | "certificate" | "other";
53
+ secret: string;
54
+ refreshSecret: string | null;
55
+ } | null>;
56
+ /**
57
+ * Resolve a set of inference-source credentialIds into the credential material
58
+ * delivered on a run's unified credential-material cell. An inference source
59
+ * references its credential by id only; for each DISTINCT id this resolves the
60
+ * secret under the SAME tenant-ownership authority `buildSource` uses -- the
61
+ * credential must exist, be reachable in the tenant's ancestor chain, and be
62
+ * tenant-owned (`principalId IS NULL`) -- then decrypts it at the single point of
63
+ * use. `providerKey`/`origin` come from the credential's own provider row
64
+ * (`provider.plugin` / `provider.apiBaseUrl`), so the material describes the
65
+ * credential itself, independent of any caller-supplied source fields.
66
+ *
67
+ * Fails CLOSED, by throwing, on the first credentialId that is unresolved, not
68
+ * tenant-owned, references a missing provider, or whose provider has no API
69
+ * origin to pin -- a secret is never dropped nor delivered without an origin. The
70
+ * single resolver for every inference source credentialId -> material, shared by
71
+ * the deploy composition (top-level + inline body sources) and any other caller
72
+ * that must materialize inference credentials for the cell.
73
+ */
74
+ export declare function resolveInferenceMaterials(db: DB["db"], tenantId: string, credentialIds: Iterable<string>, credentialCipher: CredentialCipher): Promise<CredentialMaterialEntry[]>;
75
+ /**
76
+ * Re-resolve the CURRENT material for a set of already-authorized credentialIds,
77
+ * for the reconnect resync. Unlike `resolveInferenceMaterials`, a credential
78
+ * that no longer exists or is `revoked` is DROPPED (omitted) rather than
79
+ * throwing: the resync reflects credential lifecycle, so a dead id is simply
80
+ * absent from the reconciled delivery and the child evicts it.
81
+ *
82
+ * A credential that IS alive but whose material cannot be resolved (its provider
83
+ * vanished or has no API base URL) is NOT a lifecycle removal, so it throws --
84
+ * the caller aborts the whole reconcile rather than delivering a partial set
85
+ * paired with a spurious revoke. A rotated secret is picked up because the row's
86
+ * current secret is decrypted here.
87
+ *
88
+ * Ids are looked up by primary key (globally unique) and come from the
89
+ * deployment's own persisted delivery, so no re-authorization is performed --
90
+ * this reflects lifecycle only.
91
+ */
92
+ export declare function reresolveCurrentMaterials(db: DB["db"], credentialIds: Iterable<string>, credentialCipher: CredentialCipher): Promise<CredentialMaterialEntry[]>;
2
93
  /**
3
94
  * Resolves a provider by name, walking up the tenant hierarchy.
4
95
  * Returns the first match (child shadows parent).
@@ -10,6 +101,7 @@ export declare function resolveProviderByName(db: DB["db"], tenantId: string, na
10
101
  updatedAt: Date;
11
102
  tenantId: string;
12
103
  plugin: string;
104
+ apiBaseUrl: string | null;
13
105
  authorizationUrl: string | null;
14
106
  tokenUrl: string | null;
15
107
  userInfoUrl: string | null;
@@ -45,9 +137,9 @@ export declare function resolveCredentialByName(db: DB["db"], tenantId: string,
45
137
  expiresAt: Date | null;
46
138
  providerId: string;
47
139
  tenantId: string;
48
- status: "active" | "expired" | "revoked" | "error";
49
- description: string | null;
140
+ status: "active" | "error" | "expired" | "revoked";
50
141
  principalId: string | null;
142
+ description: string | null;
51
143
  scopes: string[] | null;
52
144
  metadata: unknown;
53
145
  oauthClientId: string | null;
@@ -67,9 +159,9 @@ export declare function resolveCredentialById(db: DB["db"], tenantId: string, cr
67
159
  expiresAt: Date | null;
68
160
  providerId: string;
69
161
  tenantId: string;
70
- status: "active" | "expired" | "revoked" | "error";
71
- description: string | null;
162
+ status: "active" | "error" | "expired" | "revoked";
72
163
  principalId: string | null;
164
+ description: string | null;
73
165
  scopes: string[] | null;
74
166
  metadata: unknown;
75
167
  oauthClientId: string | null;
@@ -90,21 +182,90 @@ type CredentialRequirement = {
90
182
  * catalog, not this path).
91
183
  */
92
184
  export declare function resolveCredentialRequirement(db: DB["db"], tenantId: string, requirement: CredentialRequirement, creatorPrincipalId: string | null, invokerPrincipalId: string | null): Promise<{
93
- id: string;
94
- name: string;
95
- createdAt: Date;
96
- updatedAt: Date;
97
- expiresAt: Date | null;
98
- providerId: string;
99
- tenantId: string;
100
- status: "active" | "expired" | "revoked" | "error";
101
- description: string | null;
102
- principalId: string | null;
103
- scopes: string[] | null;
104
- metadata: unknown;
105
- oauthClientId: string | null;
106
- type: "api_key" | "oauth_token" | "certificate" | "other";
107
- secret: string;
108
- refreshSecret: string | null;
185
+ credential: {
186
+ id: string;
187
+ name: string;
188
+ createdAt: Date;
189
+ updatedAt: Date;
190
+ expiresAt: Date | null;
191
+ providerId: string;
192
+ tenantId: string;
193
+ status: "active" | "error" | "expired" | "revoked";
194
+ principalId: string | null;
195
+ description: string | null;
196
+ scopes: string[] | null;
197
+ metadata: unknown;
198
+ oauthClientId: string | null;
199
+ type: "api_key" | "oauth_token" | "certificate" | "other";
200
+ secret: string;
201
+ refreshSecret: string | null;
202
+ };
203
+ provider: {
204
+ id: string;
205
+ name: string;
206
+ createdAt: Date;
207
+ updatedAt: Date;
208
+ tenantId: string;
209
+ plugin: string;
210
+ apiBaseUrl: string | null;
211
+ authorizationUrl: string | null;
212
+ tokenUrl: string | null;
213
+ userInfoUrl: string | null;
214
+ scopes: string[] | null;
215
+ metadata: unknown;
216
+ };
109
217
  } | null>;
218
+ /**
219
+ * A launch-blocking CONFIGURATION failure for one binding: a binding no
220
+ * credential resolves, a provider with no API origin (can't pin an http
221
+ * handle), or an ambiguous match. Distinct from an infrastructure failure (a DB
222
+ * read fault), which `buildCredentialDelivery` THROWS rather than returning --
223
+ * so a caller never mistakes a transient fault for revocation.
224
+ */
225
+ export type CredentialDeliveryFailure = {
226
+ code: "unresolved" | "no_origin" | "ambiguous";
227
+ binding: {
228
+ provider: string;
229
+ package: string;
230
+ handle: string;
231
+ };
232
+ message: string;
233
+ };
234
+ /**
235
+ * Outcome of `buildCredentialDelivery`. `ok: true` carries the material +
236
+ * descriptors delivered to the tools; `delivery` is `undefined` when there are
237
+ * no bindings. `ok: false` carries the first launch-blocking configuration
238
+ * failure for the caller to map to a fail-closed response.
239
+ */
240
+ export type BuildCredentialDeliveryResult = {
241
+ ok: true;
242
+ delivery: CredentialDelivery | undefined;
243
+ } | {
244
+ ok: false;
245
+ reason: CredentialDeliveryFailure;
246
+ };
247
+ /**
248
+ * Resolve a definition's credential bindings into the material + per-handle
249
+ * descriptors delivered to its tools. Each credential's secret is decrypted
250
+ * once, keyed by credentialId (a credential backing several handles is
251
+ * decrypted once).
252
+ *
253
+ * This path delivers credential material only; it does not mint, stamp, or
254
+ * carry any grant. Credential-use authorization is enforced by a separate grant
255
+ * layer.
256
+ *
257
+ * The workflow-deploy path (`deployCodeSourcedWorkflow`) is the only caller
258
+ * today, using `delivery`. Returning a discriminated result rather than an HTTP
259
+ * response keeps it safe to call off the request path: a configuration failure
260
+ * is `ok: false`, and a transient DB read fault THROWS so the caller surfaces it
261
+ * rather than silently dropping a still-valid credential.
262
+ */
263
+ export declare function buildCredentialDelivery(args: {
264
+ db: DB["db"];
265
+ tenantId: string;
266
+ bindings: readonly CredentialBinding[];
267
+ creatorPrincipalId: string | null;
268
+ invokerPrincipalId: string | null;
269
+ credentialCipher: CredentialCipher;
270
+ }): Promise<BuildCredentialDeliveryResult>;
110
271
  export {};