@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
@@ -0,0 +1,36 @@
1
+ import type { DB, DBExecutor } from "./client.js";
2
+ import { signalCorrelation } from "./schema/signal-correlations.js";
3
+ import { parseSignalCorrelationRow } from "./parse-row.js";
4
+ type DBHandle = DB["db"];
5
+ type SignalCorrelationInsert = typeof signalCorrelation.$inferInsert;
6
+ type ParsedSignalCorrelation = ReturnType<typeof parseSignalCorrelationRow>;
7
+ /**
8
+ * Store for the `signal_correlation` table, which maps an in-flight signal
9
+ * (correlation id) to the run and address that must be resumed when the signal
10
+ * resolves. Each method accepts an optional transaction handle so the resolver
11
+ * can claim the correlation and flip its approval atomically.
12
+ */
13
+ export declare function createSignalCorrelationStore(db: DBHandle): {
14
+ register(row: SignalCorrelationInsert, tx?: DBExecutor): Promise<ParsedSignalCorrelation>;
15
+ /**
16
+ * Idempotent variant of `register`. On a `correlationId` primary-key
17
+ * conflict the insert is a no-op and this returns `null` rather than
18
+ * throwing, so a redelivered register frame (sidecar reconnect,
19
+ * workflow-log replay, supervisor restart re-emitting) does not fail the
20
+ * co-write. Returns the parsed row only when this call performed the
21
+ * insert.
22
+ */
23
+ registerIfAbsent(row: SignalCorrelationInsert, tx?: DBExecutor): Promise<ParsedSignalCorrelation | null>;
24
+ resolveRoute(correlationId: string, tx?: DBExecutor): Promise<ParsedSignalCorrelation | null>;
25
+ /**
26
+ * Atomically claim a correlation for terminal delivery. The
27
+ * `resolved_at IS NULL` guard makes the claim single-shot: the first caller
28
+ * stamps `resolvedAt` and gets the row back; any second caller matches no
29
+ * row and receives null, so a redelivered signal is not delivered twice.
30
+ * When `signalId` is provided it is persisted on the same update, recording
31
+ * which signal instance won the claim for redelivery idempotency.
32
+ */
33
+ claimTerminal(correlationId: string, resolvedAt: Date, signalId: string | null, tx?: DBExecutor): Promise<ParsedSignalCorrelation | null>;
34
+ };
35
+ export type SignalCorrelationStore = ReturnType<typeof createSignalCorrelationStore>;
36
+ export {};
@@ -0,0 +1,63 @@
1
+ import { and, eq, isNull } from "drizzle-orm";
2
+ import { signalCorrelation } from "./schema/signal-correlations.js";
3
+ import { parseSignalCorrelationRow } from "./parse-row.js";
4
+ /**
5
+ * Store for the `signal_correlation` table, which maps an in-flight signal
6
+ * (correlation id) to the run and address that must be resumed when the signal
7
+ * resolves. Each method accepts an optional transaction handle so the resolver
8
+ * can claim the correlation and flip its approval atomically.
9
+ */
10
+ export function createSignalCorrelationStore(db) {
11
+ return {
12
+ async register(row, tx) {
13
+ const [inserted] = await (tx ?? db)
14
+ .insert(signalCorrelation)
15
+ .values(row)
16
+ .returning();
17
+ if (inserted === undefined) {
18
+ throw new Error(`signalCorrelationStore.register: insert returned no row for ${row.correlationId}`);
19
+ }
20
+ return parseSignalCorrelationRow(inserted);
21
+ },
22
+ /**
23
+ * Idempotent variant of `register`. On a `correlationId` primary-key
24
+ * conflict the insert is a no-op and this returns `null` rather than
25
+ * throwing, so a redelivered register frame (sidecar reconnect,
26
+ * workflow-log replay, supervisor restart re-emitting) does not fail the
27
+ * co-write. Returns the parsed row only when this call performed the
28
+ * insert.
29
+ */
30
+ async registerIfAbsent(row, tx) {
31
+ const [inserted] = await (tx ?? db)
32
+ .insert(signalCorrelation)
33
+ .values(row)
34
+ .onConflictDoNothing({ target: signalCorrelation.correlationId })
35
+ .returning();
36
+ return inserted === undefined
37
+ ? null
38
+ : parseSignalCorrelationRow(inserted);
39
+ },
40
+ async resolveRoute(correlationId, tx) {
41
+ const row = await (tx ?? db).query.signalCorrelation.findFirst({
42
+ where: eq(signalCorrelation.correlationId, correlationId),
43
+ });
44
+ return row === undefined ? null : parseSignalCorrelationRow(row);
45
+ },
46
+ /**
47
+ * Atomically claim a correlation for terminal delivery. The
48
+ * `resolved_at IS NULL` guard makes the claim single-shot: the first caller
49
+ * stamps `resolvedAt` and gets the row back; any second caller matches no
50
+ * row and receives null, so a redelivered signal is not delivered twice.
51
+ * When `signalId` is provided it is persisted on the same update, recording
52
+ * which signal instance won the claim for redelivery idempotency.
53
+ */
54
+ async claimTerminal(correlationId, resolvedAt, signalId, tx) {
55
+ const [claimed] = await (tx ?? db)
56
+ .update(signalCorrelation)
57
+ .set({ resolvedAt, signalId })
58
+ .where(and(eq(signalCorrelation.correlationId, correlationId), isNull(signalCorrelation.resolvedAt)))
59
+ .returning();
60
+ return claimed === undefined ? null : parseSignalCorrelationRow(claimed);
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,12 @@
1
+ import type { LocalPrincipalSigner } from "@intx/types";
2
+ import type { DBExecutor } from "./client.js";
3
+ import type { PrincipalKeyStore } from "./principal-key-store.js";
4
+ /**
5
+ * Resolve a principal's trusted signer identity from the hub's own key store.
6
+ *
7
+ * The returned `publicKey` is the principal's active key as the hub holds it, so
8
+ * a verifier can check a signature against it without trusting any key on the
9
+ * wire. Throws when the principal has no active key -- an unresolvable signer
10
+ * must fail loudly rather than default.
11
+ */
12
+ export declare function lookupLocalPrincipalSigner(principalKeyStore: PrincipalKeyStore, principalId: string, tx?: DBExecutor): Promise<LocalPrincipalSigner>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resolve a principal's trusted signer identity from the hub's own key store.
3
+ *
4
+ * The returned `publicKey` is the principal's active key as the hub holds it, so
5
+ * a verifier can check a signature against it without trusting any key on the
6
+ * wire. Throws when the principal has no active key -- an unresolvable signer
7
+ * must fail loudly rather than default.
8
+ */
9
+ export async function lookupLocalPrincipalSigner(principalKeyStore, principalId, tx) {
10
+ return {
11
+ kind: "local-principal",
12
+ principalId,
13
+ publicKey: await principalKeyStore.getPublicKey(principalId, tx),
14
+ };
15
+ }
@@ -1,11 +1,14 @@
1
+ import { type TenantSidecarCapabilityPolicy } from "@intx/types";
1
2
  import type { DB } from "./client.js";
2
3
  /**
3
4
  * Walks the tenant parentId chain from the given tenant up to the root.
4
5
  * Returns an ordered array of tenant IDs: [tenantId, parentId, grandparentId, ...rootId].
5
6
  *
6
- * Protects against cycles with a depth limit.
7
+ * Throws if the hierarchy contains a cycle instead of returning a partial
8
+ * chain that could omit inherited policy or configuration.
7
9
  */
8
10
  export declare function getAncestorChain(db: DB["db"], tenantId: string): Promise<string[]>;
11
+ export declare function resolveTenantSidecarCapabilityPolicies(db: DB["db"], tenantId: string): Promise<readonly TenantSidecarCapabilityPolicy[]>;
9
12
  /**
10
13
  * Returns the tenant and every distinct tenant in its subtree (children,
11
14
  * their children, and so on), discovered breadth-first. Used to find every
@@ -1,27 +1,59 @@
1
+ import { type } from "arktype";
1
2
  import { eq, inArray } from "drizzle-orm";
3
+ import { TenantConfig } from "@intx/types";
2
4
  import { tenant } from "./schema/tenants.js";
3
- const MAX_DEPTH = 20;
4
5
  /**
5
6
  * Walks the tenant parentId chain from the given tenant up to the root.
6
7
  * Returns an ordered array of tenant IDs: [tenantId, parentId, grandparentId, ...rootId].
7
8
  *
8
- * Protects against cycles with a depth limit.
9
+ * Throws if the hierarchy contains a cycle instead of returning a partial
10
+ * chain that could omit inherited policy or configuration.
9
11
  */
10
12
  export async function getAncestorChain(db, tenantId) {
11
- const chain = [tenantId];
13
+ const chain = [];
14
+ const visited = new Set();
12
15
  let currentId = tenantId;
13
- for (let depth = 0; depth < MAX_DEPTH; depth++) {
16
+ while (true) {
17
+ if (visited.has(currentId)) {
18
+ throw new Error(`Tenant hierarchy contains a cycle at tenant ${currentId}`);
19
+ }
20
+ visited.add(currentId);
21
+ chain.push(currentId);
14
22
  const row = await db.query.tenant.findFirst({
15
23
  where: eq(tenant.id, currentId),
16
24
  columns: { parentId: true },
17
25
  });
18
26
  if (!row?.parentId)
19
27
  break;
20
- chain.push(row.parentId);
21
28
  currentId = row.parentId;
22
29
  }
23
30
  return chain;
24
31
  }
32
+ export async function resolveTenantSidecarCapabilityPolicies(db, tenantId) {
33
+ const chain = await getAncestorChain(db, tenantId);
34
+ const rows = await db.query.tenant.findMany({
35
+ where: inArray(tenant.id, chain),
36
+ columns: { id: true, config: true },
37
+ });
38
+ const rowsById = new Map(rows.map((row) => [row.id, row]));
39
+ const policies = [];
40
+ for (const ancestorId of chain) {
41
+ const row = rowsById.get(ancestorId);
42
+ if (row === undefined)
43
+ continue;
44
+ if (row.config === null)
45
+ continue;
46
+ const config = TenantConfig(row.config);
47
+ if (config instanceof type.errors) {
48
+ throw new Error(`Tenant ${row.id} has invalid configuration: ${config.summary}`);
49
+ }
50
+ const rules = config.sidecarPlacement?.capabilities;
51
+ if (rules !== undefined && rules.length > 0) {
52
+ policies.push({ tenantId: row.id, rules });
53
+ }
54
+ }
55
+ return policies;
56
+ }
25
57
  /**
26
58
  * Returns the tenant and every distinct tenant in its subtree (children,
27
59
  * their children, and so on), discovered breadth-first. Used to find every
@@ -0,0 +1,55 @@
1
+ import { GrantWalkSnapshot } from "@intx/types";
2
+ import type { DB, DBExecutor } from "./client.js";
3
+ import { parseWorkflowDefinitionRow } from "./parse-row.js";
4
+ type DBHandle = DB["db"];
5
+ type ParsedWorkflowDefinition = ReturnType<typeof parseWorkflowDefinitionRow>;
6
+ /**
7
+ * The selector that keys a workflow definition's identity: the asset it
8
+ * projects and the content hash of its wire projection. A single asset backs
9
+ * many definitions distinguished by their wire hash, so both fields are
10
+ * required to name exactly one definition.
11
+ */
12
+ export type WorkflowDefinitionSelector = {
13
+ assetId: string;
14
+ wireHash: string;
15
+ };
16
+ /**
17
+ * The definition a selector names, or null when no definition has been folded
18
+ * for that `(assetId, wireHash)` pair. This is the single expression of the
19
+ * deployment -> definition mapping (a deployment names its asset and wire hash);
20
+ * the run backfill and the native-run insert sites both resolve through it.
21
+ * Null on a miss is deliberate, not an error: a deployment whose selector the
22
+ * run-once fold never covered has no definition yet, and its runs anchor on
23
+ * `runId` until that gap closes.
24
+ */
25
+ export declare function resolveDefinitionIdForAsset(db: DBExecutor, selector: WorkflowDefinitionSelector): Promise<string | null>;
26
+ /**
27
+ * Read the deploy-approved grant-walk snapshot frozen onto a definition's
28
+ * version row, validated as a `GrantWalkSnapshot` at this boundary. Returns
29
+ * `null` when the version row is absent or its `grantSnapshot` column is still
30
+ * `null` -- the "not yet approved" state, mirroring `approvedWireHash`. The
31
+ * caller fails closed on `null`; it never substitutes an empty grant set.
32
+ */
33
+ export declare function loadFrozenGrantSnapshot(db: DBExecutor, definitionId: string): Promise<GrantWalkSnapshot | null>;
34
+ export type WorkflowDefinitionRollbackResult = {
35
+ ok: true;
36
+ definition: ParsedWorkflowDefinition;
37
+ } | {
38
+ ok: false;
39
+ reason: "definition_not_found" | "version_not_found";
40
+ };
41
+ /**
42
+ * Store for the first-class `workflow_definition` and its version history.
43
+ */
44
+ export declare function createWorkflowDefinitionStore(db: DBHandle): {
45
+ /**
46
+ * Roll a definition back to a prior version: deactivate the current
47
+ * version, activate the target, and repoint `currentVersion` in one
48
+ * transaction, so a reader never sees two active versions or a
49
+ * `currentVersion` that names an inactive row. Returns a discriminated
50
+ * result rather than throwing for the not-found cases the route reports as
51
+ * 404 / 400.
52
+ */
53
+ rollback(tenantId: string, definitionId: string, targetVersion: string): Promise<WorkflowDefinitionRollbackResult>;
54
+ };
55
+ export {};
@@ -0,0 +1,93 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { GrantWalkSnapshot } from "@intx/types";
3
+ import { workflowDefinition, workflowDefinitionVersion, } from "./schema/workflow-definitions.js";
4
+ import { parseWorkflowDefinitionRow } from "./parse-row.js";
5
+ // The version `ensureWorkflowDefinitionForAsset` projects for a fresh
6
+ // definition, and therefore the row the approval freeze stamps the grant
7
+ // snapshot onto. Hand-coupled to the ensure helper's initial version; if that
8
+ // helper ever projects a different version this must follow.
9
+ const FROZEN_VERSION = "1";
10
+ /**
11
+ * The definition a selector names, or null when no definition has been folded
12
+ * for that `(assetId, wireHash)` pair. This is the single expression of the
13
+ * deployment -> definition mapping (a deployment names its asset and wire hash);
14
+ * the run backfill and the native-run insert sites both resolve through it.
15
+ * Null on a miss is deliberate, not an error: a deployment whose selector the
16
+ * run-once fold never covered has no definition yet, and its runs anchor on
17
+ * `runId` until that gap closes.
18
+ */
19
+ export async function resolveDefinitionIdForAsset(db, selector) {
20
+ const row = await db
21
+ .select({ id: workflowDefinition.id })
22
+ .from(workflowDefinition)
23
+ .where(and(eq(workflowDefinition.assetId, selector.assetId), eq(workflowDefinition.wireHash, selector.wireHash)))
24
+ .limit(1)
25
+ .then((rows) => rows[0]);
26
+ return row?.id ?? null;
27
+ }
28
+ /**
29
+ * Read the deploy-approved grant-walk snapshot frozen onto a definition's
30
+ * version row, validated as a `GrantWalkSnapshot` at this boundary. Returns
31
+ * `null` when the version row is absent or its `grantSnapshot` column is still
32
+ * `null` -- the "not yet approved" state, mirroring `approvedWireHash`. The
33
+ * caller fails closed on `null`; it never substitutes an empty grant set.
34
+ */
35
+ export async function loadFrozenGrantSnapshot(db, definitionId) {
36
+ const row = await db
37
+ .select({ grantSnapshot: workflowDefinitionVersion.grantSnapshot })
38
+ .from(workflowDefinitionVersion)
39
+ .where(and(eq(workflowDefinitionVersion.definitionId, definitionId), eq(workflowDefinitionVersion.version, FROZEN_VERSION)))
40
+ .limit(1)
41
+ .then((rows) => rows[0]);
42
+ if (row === undefined || row.grantSnapshot === null)
43
+ return null;
44
+ return GrantWalkSnapshot.assert(row.grantSnapshot);
45
+ }
46
+ /**
47
+ * Store for the first-class `workflow_definition` and its version history.
48
+ */
49
+ export function createWorkflowDefinitionStore(db) {
50
+ return {
51
+ /**
52
+ * Roll a definition back to a prior version: deactivate the current
53
+ * version, activate the target, and repoint `currentVersion` in one
54
+ * transaction, so a reader never sees two active versions or a
55
+ * `currentVersion` that names an inactive row. Returns a discriminated
56
+ * result rather than throwing for the not-found cases the route reports as
57
+ * 404 / 400.
58
+ */
59
+ async rollback(tenantId, definitionId, targetVersion) {
60
+ return db.transaction(async (tx) => {
61
+ const existing = await tx.query.workflowDefinition.findFirst({
62
+ where: and(eq(workflowDefinition.id, definitionId), eq(workflowDefinition.tenantId, tenantId)),
63
+ });
64
+ if (existing === undefined) {
65
+ return { ok: false, reason: "definition_not_found" };
66
+ }
67
+ const target = await tx.query.workflowDefinitionVersion.findFirst({
68
+ where: and(eq(workflowDefinitionVersion.definitionId, definitionId), eq(workflowDefinitionVersion.version, targetVersion)),
69
+ });
70
+ if (target === undefined) {
71
+ return { ok: false, reason: "version_not_found" };
72
+ }
73
+ await tx
74
+ .update(workflowDefinitionVersion)
75
+ .set({ status: "inactive" })
76
+ .where(and(eq(workflowDefinitionVersion.definitionId, definitionId), eq(workflowDefinitionVersion.version, existing.currentVersion)));
77
+ await tx
78
+ .update(workflowDefinitionVersion)
79
+ .set({ status: "active" })
80
+ .where(and(eq(workflowDefinitionVersion.definitionId, definitionId), eq(workflowDefinitionVersion.version, targetVersion)));
81
+ const [updated] = await tx
82
+ .update(workflowDefinition)
83
+ .set({ currentVersion: targetVersion, updatedAt: new Date() })
84
+ .where(eq(workflowDefinition.id, definitionId))
85
+ .returning();
86
+ if (updated === undefined) {
87
+ throw new Error(`workflowDefinitionStore.rollback: update returned no row for ${definitionId}`);
88
+ }
89
+ return { ok: true, definition: parseWorkflowDefinitionRow(updated) };
90
+ });
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,47 @@
1
+ import type { WorkflowProbeResultFrame } from "@intx/types/sidecar";
2
+ import type { WorkflowDefinitionSource } from "@intx/types/workflow-sources";
3
+ import type { DB, DBExecutor } from "./client.js";
4
+ import { workflowProbe, type WorkflowProbeStatus } from "./schema/index.js";
5
+ type DBHandle = DB["db"];
6
+ type WorkflowProbeResult = Omit<WorkflowProbeResultFrame, "type" | "requestId">;
7
+ type WorkflowProbeRow = typeof workflowProbe.$inferSelect;
8
+ export type WorkflowProbe = WorkflowProbeRow;
9
+ export type CreateWorkflowProbeArgs = {
10
+ readonly id: string;
11
+ readonly tenantId: string;
12
+ readonly definitionAssetId: string;
13
+ readonly source: WorkflowDefinitionSource;
14
+ readonly entry: string;
15
+ readonly pin?: string;
16
+ readonly provisionerId: string;
17
+ readonly provisionerApiVersion: 1;
18
+ readonly provisionerBindingFingerprint: string;
19
+ readonly now?: Date;
20
+ };
21
+ export type BindWorkflowProbeSidecarArgs = {
22
+ readonly probeId: string;
23
+ readonly sidecarId: string;
24
+ readonly tokenHashSha256: Uint8Array;
25
+ readonly now?: Date;
26
+ };
27
+ export declare function createWorkflowProbeStore(db: DBHandle): {
28
+ create(args: CreateWorkflowProbeArgs, tx?: DBExecutor): Promise<WorkflowProbe>;
29
+ bindSidecar(args: BindWorkflowProbeSidecarArgs, tx?: DBExecutor): Promise<WorkflowProbe | null>;
30
+ markProbing(args: {
31
+ probeId: string;
32
+ externalRef?: string;
33
+ now?: Date;
34
+ }): Promise<WorkflowProbe | null>;
35
+ recordResult(probeId: string, result: WorkflowProbeResult, now?: Date): Promise<WorkflowProbe | null>;
36
+ transition(probeId: string, from: readonly WorkflowProbeStatus[], to: WorkflowProbeStatus, opts?: {
37
+ failureCode?: string;
38
+ failureMessage?: string;
39
+ now?: Date;
40
+ tx?: DBExecutor;
41
+ }): Promise<WorkflowProbe | null>;
42
+ get(probeId: string): Promise<WorkflowProbe | null>;
43
+ listActive(): Promise<WorkflowProbe[]>;
44
+ listReleasing(): Promise<WorkflowProbe[]>;
45
+ };
46
+ export type WorkflowProbeStore = ReturnType<typeof createWorkflowProbeStore>;
47
+ export {};
@@ -0,0 +1,124 @@
1
+ import { and, eq, inArray, sql } from "drizzle-orm";
2
+ import { sidecar, workflowProbe } from "./schema/index.js";
3
+ const activeStatuses = [
4
+ "pending",
5
+ "provisioning",
6
+ "probing",
7
+ "releasing",
8
+ ];
9
+ function timestamp(now) {
10
+ return now ?? sql `now()`;
11
+ }
12
+ export function createWorkflowProbeStore(db) {
13
+ return {
14
+ async create(args, tx) {
15
+ const createdAt = timestamp(args.now);
16
+ const [created] = await (tx ?? db)
17
+ .insert(workflowProbe)
18
+ .values({
19
+ id: args.id,
20
+ tenantId: args.tenantId,
21
+ definitionAssetId: args.definitionAssetId,
22
+ source: args.source,
23
+ entry: args.entry,
24
+ ...(args.pin !== undefined ? { pin: args.pin } : {}),
25
+ provisionerId: args.provisionerId,
26
+ provisionerApiVersion: args.provisionerApiVersion,
27
+ provisionerBindingFingerprint: args.provisionerBindingFingerprint,
28
+ createdAt,
29
+ updatedAt: createdAt,
30
+ })
31
+ .returning();
32
+ if (created === undefined) {
33
+ throw new Error(`Failed to create workflow probe ${args.id}`);
34
+ }
35
+ return created;
36
+ },
37
+ async bindSidecar(args, tx) {
38
+ const bind = async (executor) => {
39
+ const updatedAt = timestamp(args.now);
40
+ const [probe] = await executor
41
+ .select({ status: workflowProbe.status })
42
+ .from(workflowProbe)
43
+ .where(eq(workflowProbe.id, args.probeId))
44
+ .limit(1)
45
+ .for("update");
46
+ if (probe?.status !== "pending")
47
+ return null;
48
+ await executor.insert(sidecar).values({
49
+ id: args.sidecarId,
50
+ url: null,
51
+ tokenHashSha256: args.tokenHashSha256,
52
+ status: "offline",
53
+ createdAt: updatedAt,
54
+ updatedAt,
55
+ });
56
+ const [updated] = await executor
57
+ .update(workflowProbe)
58
+ .set({
59
+ sidecarId: args.sidecarId,
60
+ status: "provisioning",
61
+ updatedAt,
62
+ })
63
+ .where(eq(workflowProbe.id, args.probeId))
64
+ .returning();
65
+ return updated ?? null;
66
+ };
67
+ return tx === undefined ? db.transaction(bind) : bind(tx);
68
+ },
69
+ async markProbing(args) {
70
+ const [updated] = await db
71
+ .update(workflowProbe)
72
+ .set({
73
+ status: "probing",
74
+ ...(args.externalRef !== undefined
75
+ ? { externalRef: args.externalRef }
76
+ : {}),
77
+ updatedAt: timestamp(args.now),
78
+ })
79
+ .where(and(eq(workflowProbe.id, args.probeId), eq(workflowProbe.status, "provisioning")))
80
+ .returning();
81
+ return updated ?? null;
82
+ },
83
+ async recordResult(probeId, result, now) {
84
+ const [updated] = await db
85
+ .update(workflowProbe)
86
+ .set({ result, updatedAt: timestamp(now) })
87
+ .where(and(eq(workflowProbe.id, probeId), eq(workflowProbe.status, "probing")))
88
+ .returning();
89
+ return updated ?? null;
90
+ },
91
+ async transition(probeId, from, to, opts) {
92
+ const [updated] = await (opts?.tx ?? db)
93
+ .update(workflowProbe)
94
+ .set({
95
+ status: to,
96
+ ...(opts?.failureCode !== undefined
97
+ ? { failureCode: opts.failureCode }
98
+ : {}),
99
+ ...(opts?.failureMessage !== undefined
100
+ ? { failureMessage: opts.failureMessage }
101
+ : {}),
102
+ updatedAt: timestamp(opts?.now),
103
+ })
104
+ .where(and(eq(workflowProbe.id, probeId), inArray(workflowProbe.status, [...from])))
105
+ .returning();
106
+ return updated ?? null;
107
+ },
108
+ async get(probeId) {
109
+ return ((await db.query.workflowProbe.findFirst({
110
+ where: eq(workflowProbe.id, probeId),
111
+ })) ?? null);
112
+ },
113
+ async listActive() {
114
+ return db.query.workflowProbe.findMany({
115
+ where: inArray(workflowProbe.status, [...activeStatuses]),
116
+ });
117
+ },
118
+ async listReleasing() {
119
+ return db.query.workflowProbe.findMany({
120
+ where: eq(workflowProbe.status, "releasing"),
121
+ });
122
+ },
123
+ };
124
+ }
@@ -0,0 +1,83 @@
1
+ import { type SQL } from "drizzle-orm";
2
+ import { type RunGrantsFrame as RunGrantsFrameType, type SignalDeliverFrame as SignalDeliverFrameType } from "@intx/types/sidecar";
3
+ import type { DB, DBExecutor } from "./client.js";
4
+ import { parseWorkflowRunDispatchRow } from "./parse-row.js";
5
+ type DBHandle = DB["db"];
6
+ type ParsedDispatch = ReturnType<typeof parseWorkflowRunDispatchRow>;
7
+ export type EnqueueWorkflowRunDispatchArgs = {
8
+ readonly id: string;
9
+ readonly anchorRunId: string;
10
+ readonly messageId: string;
11
+ readonly senderAddress: string;
12
+ readonly rawMessage: Uint8Array;
13
+ readonly stepGrants: RunGrantsFrameType["stepGrants"];
14
+ readonly now?: Date;
15
+ };
16
+ export type EnqueueWorkflowSignalDispatchArgs = {
17
+ readonly id: string;
18
+ readonly anchorRunId: string;
19
+ readonly signal: Omit<SignalDeliverFrameType, "type">;
20
+ readonly now?: Date;
21
+ };
22
+ export type EnqueueWorkflowRunDispatchResult = {
23
+ readonly dispatch: ParsedDispatch;
24
+ readonly created: boolean;
25
+ };
26
+ type InsertOrReconcileDispatchArgs = {
27
+ readonly operation: "enqueue" | "enqueueSignal";
28
+ readonly id: string;
29
+ readonly anchorRunId: string;
30
+ readonly messageId: string;
31
+ readonly kind: ParsedDispatch["kind"];
32
+ readonly senderAddress: string | null;
33
+ readonly rawMessage: Uint8Array;
34
+ readonly stepGrants: RunGrantsFrameType["stepGrants"];
35
+ readonly now?: Date;
36
+ };
37
+ export declare class WorkflowRunDispatchPayloadConflictError extends Error {
38
+ readonly messageId: string;
39
+ readonly payloadKind: "message" | "signal";
40
+ constructor(messageId: string, payloadKind: "message" | "signal", operation: InsertOrReconcileDispatchArgs["operation"]);
41
+ }
42
+ export type ClaimWorkflowRunDispatchArgs = {
43
+ readonly excludedDispatchIds?: readonly string[];
44
+ readonly leaseId: string;
45
+ readonly leaseDurationMs: number;
46
+ };
47
+ export type AcknowledgeWorkflowRunDispatchArgs = {
48
+ readonly allocationId: string;
49
+ readonly anchorRunId: string;
50
+ readonly messageId: string;
51
+ readonly generation: number;
52
+ readonly now?: Date;
53
+ };
54
+ export type RetryWorkflowRunDispatchArgs = {
55
+ readonly dispatchId: string;
56
+ readonly nextAttemptAt: Date;
57
+ readonly code: string;
58
+ readonly message: string;
59
+ readonly expectedLeaseId?: string;
60
+ readonly now?: Date;
61
+ };
62
+ export type FailWorkflowRunDispatchArgs = {
63
+ readonly anchorRunId: string;
64
+ readonly messageId: string;
65
+ readonly code: string;
66
+ readonly message: string;
67
+ readonly now?: Date;
68
+ };
69
+ export declare function createWorkflowRunDispatchStore(db: DBHandle): {
70
+ enqueue(args: EnqueueWorkflowRunDispatchArgs, tx?: DBExecutor): Promise<EnqueueWorkflowRunDispatchResult>;
71
+ enqueueSignal(args: EnqueueWorkflowSignalDispatchArgs, tx?: DBExecutor): Promise<EnqueueWorkflowRunDispatchResult>;
72
+ claimNextPending(args: ClaimWorkflowRunDispatchArgs): Promise<ParsedDispatch | null>;
73
+ acknowledge(args: AcknowledgeWorkflowRunDispatchArgs): Promise<ParsedDispatch | null>;
74
+ scheduleRetry(args: RetryWorkflowRunDispatchArgs): Promise<ParsedDispatch | null>;
75
+ settle(anchorRunId: string, messageId: string, now?: Date): Promise<ParsedDispatch | null>;
76
+ fail(args: FailWorkflowRunDispatchArgs): Promise<ParsedDispatch | null>;
77
+ failUnsettled(anchorRunId: string, code: string, message: string, now?: Date | SQL, tx?: DBExecutor): Promise<number>;
78
+ requeueUnsettled(anchorRunId: string): Promise<number>;
79
+ findById(id: string): Promise<ParsedDispatch | null>;
80
+ listUnsettled(anchorRunId: string): Promise<ParsedDispatch[]>;
81
+ };
82
+ export type WorkflowRunDispatchStore = ReturnType<typeof createWorkflowRunDispatchStore>;
83
+ export {};