@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
@@ -1,8 +1,148 @@
1
1
  import { eq, and, isNull } from "drizzle-orm";
2
+ import { toolConsumer } from "@intx/authz";
3
+ import { credentialAad } from "@intx/types";
2
4
  import { credential } from "./schema/credentials.js";
3
5
  import { oauthClient } from "./schema/oauth-clients.js";
4
6
  import { provider } from "./schema/providers.js";
5
7
  import { getAncestorChain } from "./tenant-hierarchy.js";
8
+ /**
9
+ * Thrown by `resolveCredentialRequirement` when more than one credential
10
+ * matches a requirement and no name disambiguates them. A distinct type so
11
+ * callers can catch *this* condition (a launch-blocking configuration error)
12
+ * without also swallowing the DB reads the resolver performs first -- an
13
+ * infrastructure failure must surface, not be mislabeled as ambiguity.
14
+ */
15
+ export class AmbiguousCredentialError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "AmbiguousCredentialError";
19
+ }
20
+ }
21
+ /**
22
+ * Thrown when a referenced credential is not a tenant-owned credential the given
23
+ * tenant can use -- it does not exist, is not reachable in the tenant's ancestor
24
+ * chain, or is principal-owned. A distinct type so a caller can map a client's
25
+ * bad credential reference to a 4xx (a launch-blocking configuration error),
26
+ * separate from the infrastructure faults the resolvers otherwise surface.
27
+ */
28
+ export class CredentialUnauthorizedError extends Error {
29
+ credentialId;
30
+ tenantId;
31
+ constructor(credentialId, tenantId) {
32
+ super(`credential ${credentialId} is not a tenant-owned credential usable by tenant ${tenantId}`);
33
+ this.name = "CredentialUnauthorizedError";
34
+ this.credentialId = credentialId;
35
+ this.tenantId = tenantId;
36
+ }
37
+ }
38
+ /**
39
+ * Resolve a credential USABLE by the launching tenant purely through ownership:
40
+ * it exists, is reachable in the tenant's ancestor chain, and is tenant-owned
41
+ * (`principalId IS NULL`). Returns the row -- the proof of authority -- or null.
42
+ *
43
+ * This is the one "usable by ownership" resolver: a tenant may use what it (or
44
+ * an ancestor) owns, and descendants inherit; a principal-owned credential is
45
+ * never usable this way, its delegation flowing from its owner instead. It
46
+ * wraps `resolveCredentialById` (the ancestor-chain check) with the tenant-owned
47
+ * gate, so callers do not re-implement the predicate. `buildSource` expands it
48
+ * inline only to distinguish an unresolved reference from a principal-owned one.
49
+ */
50
+ export async function resolveTenantOwnedCredentialById(db, tenantId, credentialId) {
51
+ const row = await resolveCredentialById(db, tenantId, credentialId);
52
+ return row !== null && row.principalId === null ? row : null;
53
+ }
54
+ /**
55
+ * Resolve a set of inference-source credentialIds into the credential material
56
+ * delivered on a run's unified credential-material cell. An inference source
57
+ * references its credential by id only; for each DISTINCT id this resolves the
58
+ * secret under the SAME tenant-ownership authority `buildSource` uses -- the
59
+ * credential must exist, be reachable in the tenant's ancestor chain, and be
60
+ * tenant-owned (`principalId IS NULL`) -- then decrypts it at the single point of
61
+ * use. `providerKey`/`origin` come from the credential's own provider row
62
+ * (`provider.plugin` / `provider.apiBaseUrl`), so the material describes the
63
+ * credential itself, independent of any caller-supplied source fields.
64
+ *
65
+ * Fails CLOSED, by throwing, on the first credentialId that is unresolved, not
66
+ * tenant-owned, references a missing provider, or whose provider has no API
67
+ * origin to pin -- a secret is never dropped nor delivered without an origin. The
68
+ * single resolver for every inference source credentialId -> material, shared by
69
+ * the deploy composition (top-level + inline body sources) and any other caller
70
+ * that must materialize inference credentials for the cell.
71
+ */
72
+ export async function resolveInferenceMaterials(db, tenantId, credentialIds, credentialCipher) {
73
+ const materials = new Map();
74
+ for (const credentialId of credentialIds) {
75
+ if (materials.has(credentialId))
76
+ continue;
77
+ const row = await resolveTenantOwnedCredentialById(db, tenantId, credentialId);
78
+ if (row === null) {
79
+ throw new CredentialUnauthorizedError(credentialId, tenantId);
80
+ }
81
+ const providerRow = await db.query.provider.findFirst({
82
+ where: eq(provider.id, row.providerId),
83
+ });
84
+ if (providerRow === undefined) {
85
+ throw new Error(`credential ${credentialId} references provider ${row.providerId}, which does not exist`);
86
+ }
87
+ if (providerRow.apiBaseUrl === null || providerRow.apiBaseUrl === "") {
88
+ throw new Error(`provider ${providerRow.name} backing credential ${credentialId} has no API base URL; cannot pin an origin for its material`);
89
+ }
90
+ materials.set(credentialId, {
91
+ credentialId: row.id,
92
+ providerKey: providerRow.plugin,
93
+ origin: providerRow.apiBaseUrl,
94
+ secret: await credentialCipher.decrypt(row.secret, credentialAad(row.id, "secret")),
95
+ });
96
+ }
97
+ return [...materials.values()];
98
+ }
99
+ /**
100
+ * Re-resolve the CURRENT material for a set of already-authorized credentialIds,
101
+ * for the reconnect resync. Unlike `resolveInferenceMaterials`, a credential
102
+ * that no longer exists or is `revoked` is DROPPED (omitted) rather than
103
+ * throwing: the resync reflects credential lifecycle, so a dead id is simply
104
+ * absent from the reconciled delivery and the child evicts it.
105
+ *
106
+ * A credential that IS alive but whose material cannot be resolved (its provider
107
+ * vanished or has no API base URL) is NOT a lifecycle removal, so it throws --
108
+ * the caller aborts the whole reconcile rather than delivering a partial set
109
+ * paired with a spurious revoke. A rotated secret is picked up because the row's
110
+ * current secret is decrypted here.
111
+ *
112
+ * Ids are looked up by primary key (globally unique) and come from the
113
+ * deployment's own persisted delivery, so no re-authorization is performed --
114
+ * this reflects lifecycle only.
115
+ */
116
+ export async function reresolveCurrentMaterials(db, credentialIds, credentialCipher) {
117
+ const materials = new Map();
118
+ for (const credentialId of credentialIds) {
119
+ if (materials.has(credentialId))
120
+ continue;
121
+ const row = await db.query.credential.findFirst({
122
+ where: eq(credential.id, credentialId),
123
+ });
124
+ if (row === undefined)
125
+ continue; // deleted -> drop
126
+ if (row.status === "revoked")
127
+ continue; // revoked -> drop
128
+ const providerRow = await db.query.provider.findFirst({
129
+ where: eq(provider.id, row.providerId),
130
+ });
131
+ if (providerRow === undefined) {
132
+ throw new Error(`credential ${credentialId} references provider ${row.providerId}, which does not exist`);
133
+ }
134
+ if (providerRow.apiBaseUrl === null || providerRow.apiBaseUrl === "") {
135
+ throw new Error(`provider ${providerRow.name} backing credential ${credentialId} has no API base URL; cannot pin an origin for its material`);
136
+ }
137
+ materials.set(credentialId, {
138
+ credentialId: row.id,
139
+ providerKey: providerRow.plugin,
140
+ origin: providerRow.apiBaseUrl,
141
+ secret: await credentialCipher.decrypt(row.secret, credentialAad(row.id, "secret")),
142
+ });
143
+ }
144
+ return [...materials.values()];
145
+ }
6
146
  /**
7
147
  * Resolves a provider by name, walking up the tenant hierarchy.
8
148
  * Returns the first match (child shadows parent).
@@ -104,13 +244,121 @@ export async function resolveCredentialRequirement(db, tenantId, requirement, cr
104
244
  return requirement.scopes.every((s) => rowScopes.includes(s));
105
245
  });
106
246
  const [sole] = matching;
107
- if (matching.length === 1 && sole)
108
- return sole;
247
+ // Return the resolved provider alongside the credential. The provider was
248
+ // already fetched above to constrain the credential query, so surfacing it
249
+ // spares the caller a second lookup for the provider facts (plugin, base
250
+ // URL) a resolved credential is always paired with.
251
+ if (matching.length === 1 && sole) {
252
+ return { credential: sole, provider: resolvedProvider };
253
+ }
109
254
  if (matching.length > 1) {
110
- throw new Error(`Ambiguous credential match: ${matching.length} credentials match ` +
255
+ throw new AmbiguousCredentialError(`Ambiguous credential match: ${matching.length} credentials match ` +
111
256
  `provider=${requirement.providerName} source=${requirement.source} ` +
112
257
  `in tenant ${tid}. Specify a name to disambiguate.`);
113
258
  }
114
259
  }
115
260
  return null;
116
261
  }
262
+ /**
263
+ * Resolve a definition's credential bindings into the material + per-handle
264
+ * descriptors delivered to its tools. Each credential's secret is decrypted
265
+ * once, keyed by credentialId (a credential backing several handles is
266
+ * decrypted once).
267
+ *
268
+ * This path delivers credential material only; it does not mint, stamp, or
269
+ * carry any grant. Credential-use authorization is enforced by a separate grant
270
+ * layer.
271
+ *
272
+ * The workflow-deploy path (`deployCodeSourcedWorkflow`) is the only caller
273
+ * today, using `delivery`. Returning a discriminated result rather than an HTTP
274
+ * response keeps it safe to call off the request path: a configuration failure
275
+ * is `ok: false`, and a transient DB read fault THROWS so the caller surfaces it
276
+ * rather than silently dropping a still-valid credential.
277
+ */
278
+ export async function buildCredentialDelivery(args) {
279
+ const materials = new Map();
280
+ const descriptors = [];
281
+ for (const binding of args.bindings) {
282
+ const context = {
283
+ provider: binding.provider,
284
+ package: binding.package,
285
+ handle: binding.handle,
286
+ };
287
+ let resolved;
288
+ try {
289
+ resolved = await resolveCredentialRequirement(args.db, args.tenantId, {
290
+ providerName: binding.provider,
291
+ source: binding.locator,
292
+ ...(binding.name !== undefined ? { name: binding.name } : {}),
293
+ }, args.creatorPrincipalId, args.invokerPrincipalId);
294
+ }
295
+ catch (e) {
296
+ // AmbiguousCredentialError is a launch-blocking config failure; any other
297
+ // throw is a DB read fault the resolver performs first -- surface it, do
298
+ // not mislabel it as revocation.
299
+ if (!(e instanceof AmbiguousCredentialError))
300
+ throw e;
301
+ return {
302
+ ok: false,
303
+ reason: {
304
+ code: "ambiguous",
305
+ binding: context,
306
+ message: `Ambiguous credential for the binding on provider ${binding.provider} (package ${binding.package}, handle ${binding.handle}): ${e.message}`,
307
+ },
308
+ };
309
+ }
310
+ if (resolved === null) {
311
+ return {
312
+ ok: false,
313
+ reason: {
314
+ code: "unresolved",
315
+ binding: context,
316
+ message: `No credential resolves the binding for provider ${binding.provider} (package ${binding.package}, handle ${binding.handle})`,
317
+ },
318
+ };
319
+ }
320
+ // The credential is delivered as an origin-pinned http handle, so its
321
+ // provider must declare an API origin. A provider without one (OAuth-login
322
+ // only) cannot back a tool credential; fail closed rather than deliver an
323
+ // un-pinnable secret.
324
+ const providerOrigin = resolved.provider.apiBaseUrl;
325
+ if (providerOrigin === null || providerOrigin === "") {
326
+ return {
327
+ ok: false,
328
+ reason: {
329
+ code: "no_origin",
330
+ binding: context,
331
+ message: `Provider ${binding.provider} has no API base URL; cannot deliver an origin-pinned credential (package ${binding.package}, handle ${binding.handle})`,
332
+ },
333
+ };
334
+ }
335
+ const credentialId = resolved.credential.id;
336
+ // A binding resolves only a tenant-owned credential (the `tenant` locator
337
+ // filters `principalId IS NULL` in resolveCredentialRequirement), so its use
338
+ // is authorized by ownership -- already proven by this walk-up resolution,
339
+ // not re-checked here. This path delivers credential material only; it does
340
+ // not mint or carry any grant. Credential-use authorization is enforced by a
341
+ // separate grant layer.
342
+ descriptors.push({
343
+ handle: binding.handle,
344
+ credentialId,
345
+ consumer: toolConsumer(binding.package),
346
+ });
347
+ if (!materials.has(credentialId)) {
348
+ // Decrypt at the single point of use. A decrypt failure propagates and
349
+ // fails the caller closed; there is no placeholder secret.
350
+ materials.set(credentialId, {
351
+ credentialId,
352
+ providerKey: resolved.provider.plugin,
353
+ origin: providerOrigin,
354
+ secret: await args.credentialCipher.decrypt(resolved.credential.secret, credentialAad(credentialId, "secret")),
355
+ });
356
+ }
357
+ }
358
+ return {
359
+ ok: true,
360
+ delivery: descriptors.length > 0
361
+ ? { bindings: descriptors, materials: [...materials.values()] }
362
+ : undefined,
363
+ };
364
+ }
@@ -2,6 +2,7 @@ import { eq, and, or, isNull, gt, inArray } from "drizzle-orm";
2
2
  import { grant } from "./schema/grants.js";
3
3
  import { principalRole } from "./schema/roles.js";
4
4
  import { parseGrantRow } from "./parse-row.js";
5
+ import { getAncestorChain } from "./tenant-hierarchy.js";
5
6
  function toGrantRule(row) {
6
7
  const parsed = parseGrantRow(row);
7
8
  return {
@@ -16,22 +17,43 @@ function toGrantRule(row) {
16
17
  principalId: parsed.principalId,
17
18
  };
18
19
  }
20
+ /**
21
+ * Collects a principal's live (non-expired) grants — the ones the principal
22
+ * owns directly plus the ones granted to any role it holds — restricted to
23
+ * the tenants matched by `tenantScope`. The scope is the sole knob that
24
+ * decides single-tenant versus chain-aware collection.
25
+ */
26
+ async function collectGrantsScoped(db, principalId, tenantScope) {
27
+ const roleAssignments = await db.query.principalRole.findMany({
28
+ where: eq(principalRole.principalId, principalId),
29
+ });
30
+ const roleIds = roleAssignments.map((a) => a.roleId);
31
+ const now = new Date();
32
+ const ownership = [eq(grant.principalId, principalId)];
33
+ if (roleIds.length > 0) {
34
+ ownership.push(inArray(grant.roleId, roleIds));
35
+ }
36
+ const rows = await db.query.grant.findMany({
37
+ where: and(tenantScope, or(...ownership), or(isNull(grant.expiresAt), gt(grant.expiresAt, now))),
38
+ });
39
+ return rows.map(toGrantRule);
40
+ }
19
41
  export function createGrantStore(db) {
20
42
  return {
21
43
  async collectGrants(principalId, tenantId) {
22
- const roleAssignments = await db.query.principalRole.findMany({
23
- where: eq(principalRole.principalId, principalId),
24
- });
25
- const roleIds = roleAssignments.map((a) => a.roleId);
26
- const now = new Date();
27
- const ownership = [eq(grant.principalId, principalId)];
28
- if (roleIds.length > 0) {
29
- ownership.push(inArray(grant.roleId, roleIds));
30
- }
31
- const rows = await db.query.grant.findMany({
32
- where: and(eq(grant.tenantId, tenantId), or(...ownership), or(isNull(grant.expiresAt), gt(grant.expiresAt, now))),
33
- });
34
- return rows.map(toGrantRule);
44
+ return collectGrantsScoped(db, principalId, eq(grant.tenantId, tenantId));
45
+ },
46
+ // Union the principal's grants across the tenant ancestor chain — the
47
+ // acting tenant plus every ancestor up to the root. Mirrors the
48
+ // ancestor-chain resolution credential lookup already performs, so a
49
+ // `credential:{id}` / `use` grant stamped with an ancestor tenant (as the
50
+ // mint path and 0037 backfill do) still authorizes use of a credential
51
+ // inherited down the chain. Kept distinct from the single-tenant
52
+ // `collectGrants` so only the source-resolution credential-use check
53
+ // widens to the chain; the general RBAC path stays single-tenant.
54
+ async collectGrantsInChain(principalId, tenantId) {
55
+ const chain = await getAncestorChain(db, tenantId);
56
+ return collectGrantsScoped(db, principalId, inArray(grant.tenantId, chain));
35
57
  },
36
58
  };
37
59
  }
package/dist/index.d.ts CHANGED
@@ -1,13 +1,28 @@
1
- export { createDB, type DB } from "./client.js";
1
+ export { createDB, type AnyPgDatabase, type DB, type DBExecutor, } from "./client.js";
2
2
  export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
3
3
  export type { DBConfig } from "./config.js";
4
4
  export { runMigrations, dropSchema } from "./migrate.js";
5
+ export { rekeyCredentialSecrets, type RekeyReport, } from "./rekey-credential-secrets.js";
6
+ export { backfillPrincipalKeys, type BackfillPrincipalKeysReport, } from "./backfill-principal-keys.js";
5
7
  export { createGrantStore } from "./grant-store.js";
6
- export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
8
+ export { createPrincipalStore, type PrincipalStore } from "./principal-store.js";
9
+ export { createPrincipalKeyStore, type CreatePrincipalKeyStoreDeps, type PrincipalKeyStore, } from "./principal-key-store.js";
10
+ export { lookupLocalPrincipalSigner } from "./signer-identity.js";
11
+ export { resolveSenderKey, resolveFrameSenderKey, auditSenderKeys, type SenderKeyResolution, type SenderKeyAuditReport, } from "./sender-key-resolver.js";
12
+ export { createApprovalStore, type ApprovalStore, type ResolveApprovalArgs, } from "./approval-store.js";
13
+ export { createSignalCorrelationStore, type SignalCorrelationStore, } from "./signal-correlation-store.js";
14
+ export { createWorkflowRunStore, type WorkflowRunStore, } from "./workflow-run-store.js";
15
+ export { createWorkflowRunLaunchSpecStore, type WorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
16
+ export { createWorkflowRunDispatchStore, WorkflowRunDispatchPayloadConflictError, type AcknowledgeWorkflowRunDispatchArgs, type ClaimWorkflowRunDispatchArgs, type EnqueueWorkflowRunDispatchArgs, type EnqueueWorkflowRunDispatchResult, type EnqueueWorkflowSignalDispatchArgs, type RetryWorkflowRunDispatchArgs, type WorkflowRunDispatchStore, } from "./workflow-run-dispatch-store.js";
17
+ export { createSidecarAllocationStore, type BeginSidecarReleaseArgs, type BeginSidecarReplacementArgs, type BindInitialSidecarArgs, type BindReplacementSidecarArgs, type CreateAdoptedSidecarAllocationArgs, type ClaimSidecarAllocationArgs, type CreatePendingSidecarAllocationArgs, type FailSidecarAllocationArgs, type MarkSidecarAllocatedArgs, type MarkSidecarConnectionLostArgs, type MarkSidecarConnectionReadyArgs, type MarkSidecarDestroyFailedArgs, type MarkSidecarReleasedArgs, type ParkSidecarReconciliationPolicy, type ScheduleSidecarAllocationRetryArgs, type SidecarAllocation, type SidecarAllocationStore, } from "./sidecar-allocation-store.js";
18
+ export { createWorkflowProbeStore, type BindWorkflowProbeSidecarArgs, type CreateWorkflowProbeArgs, type WorkflowProbe, type WorkflowProbeStore, } from "./workflow-probe-store.js";
19
+ export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, type WorkflowDefinitionRollbackResult, type WorkflowDefinitionSelector, } from "./workflow-definition-store.js";
20
+ export { getAncestorChain, getDescendantTenants, resolveTenantSidecarCapabilityPolicies, } from "./tenant-hierarchy.js";
7
21
  export { resolveActivePrice, type ModelPricingRow } from "./pricing.js";
8
- export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, } from "./credential-resolution.js";
22
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, resolveInferenceMaterials, reresolveCurrentMaterials, AmbiguousCredentialError, CredentialUnauthorizedError, buildCredentialDelivery, } from "./credential-resolution.js";
23
+ export type { BuildCredentialDeliveryResult, CredentialDeliveryFailure, } from "./credential-resolution.js";
9
24
  export { resolveAssetByName, resolveAssetById, listAssetsForTenant, type AssetRow, type AssetWithOrigin, } from "./asset-resolution.js";
10
25
  export { listVisibleModels, listVisibleProviders, listVisibleOfferings, type ModelRow, type ModelProviderRow, type ModelOfferingRow, type Origin, type VisibleModel, type VisibleProvider, type ResolvedOffering, } from "./catalog-resolution.js";
11
- export { resolveModelSources, resolveInstanceModelSources, type CatalogSourceResolution, type SourceSkip, } from "./model-source-resolution.js";
12
- export { parseAgentRow, parseAgentVersionRow, parseGrantRow, parseOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
26
+ export { resolveModelSources, resolveInferencePreferences, resolveInstanceModelSources, resolveSourcesByOfferingIds, type CatalogSourceResolution, type OfferingSourceResolution, type SourceSkip, } from "./model-source-resolution.js";
27
+ export { parseGrantRow, parseApprovalRow, parsePrincipalRow, parsePrincipalKeyRow, parseSignalCorrelationRow, parseWorkflowRunRow, parseWorkflowRunDispatchRow, parseWorkflowRunLaunchSpecRow, parseWorkflowDefinitionRow, parseWorkflowDefinitionVersionRow, parseOfferingRow, parseModelOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
13
28
  export * as schema from "./schema/index.js";
package/dist/index.js CHANGED
@@ -1,12 +1,26 @@
1
- export { createDB } from "./client.js";
1
+ export { createDB, } from "./client.js";
2
2
  export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
3
3
  export { runMigrations, dropSchema } from "./migrate.js";
4
+ export { rekeyCredentialSecrets, } from "./rekey-credential-secrets.js";
5
+ export { backfillPrincipalKeys, } from "./backfill-principal-keys.js";
4
6
  export { createGrantStore } from "./grant-store.js";
5
- export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
7
+ export { createPrincipalStore } from "./principal-store.js";
8
+ export { createPrincipalKeyStore, } from "./principal-key-store.js";
9
+ export { lookupLocalPrincipalSigner } from "./signer-identity.js";
10
+ export { resolveSenderKey, resolveFrameSenderKey, auditSenderKeys, } from "./sender-key-resolver.js";
11
+ export { createApprovalStore, } from "./approval-store.js";
12
+ export { createSignalCorrelationStore, } from "./signal-correlation-store.js";
13
+ export { createWorkflowRunStore, } from "./workflow-run-store.js";
14
+ export { createWorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
15
+ export { createWorkflowRunDispatchStore, WorkflowRunDispatchPayloadConflictError, } from "./workflow-run-dispatch-store.js";
16
+ export { createSidecarAllocationStore, } from "./sidecar-allocation-store.js";
17
+ export { createWorkflowProbeStore, } from "./workflow-probe-store.js";
18
+ export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, } from "./workflow-definition-store.js";
19
+ export { getAncestorChain, getDescendantTenants, resolveTenantSidecarCapabilityPolicies, } from "./tenant-hierarchy.js";
6
20
  export { resolveActivePrice } from "./pricing.js";
7
- export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, } from "./credential-resolution.js";
21
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, resolveInferenceMaterials, reresolveCurrentMaterials, AmbiguousCredentialError, CredentialUnauthorizedError, buildCredentialDelivery, } from "./credential-resolution.js";
8
22
  export { resolveAssetByName, resolveAssetById, listAssetsForTenant, } from "./asset-resolution.js";
9
23
  export { listVisibleModels, listVisibleProviders, listVisibleOfferings, } from "./catalog-resolution.js";
10
- export { resolveModelSources, resolveInstanceModelSources, } from "./model-source-resolution.js";
11
- export { parseAgentRow, parseAgentVersionRow, parseGrantRow, parseOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
24
+ export { resolveModelSources, resolveInferencePreferences, resolveInstanceModelSources, resolveSourcesByOfferingIds, } from "./model-source-resolution.js";
25
+ export { parseGrantRow, parseApprovalRow, parsePrincipalRow, parsePrincipalKeyRow, parseSignalCorrelationRow, parseWorkflowRunRow, parseWorkflowRunDispatchRow, parseWorkflowRunLaunchSpecRow, parseWorkflowDefinitionRow, parseWorkflowDefinitionVersionRow, parseOfferingRow, parseModelOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
12
26
  export * as schema from "./schema/index.js";
@@ -1,5 +1,6 @@
1
- import { type ModelRequirement, type ProviderPreference } from "@intx/types";
1
+ import { type CredentialCipher, type ModelRequirement, type ProviderPreference } from "@intx/types";
2
2
  import type { InferenceSource } from "@intx/types/runtime";
3
+ import type { CredentialMaterialEntry } from "@intx/types/sidecar";
3
4
  import type { DB } from "./client.js";
4
5
  /**
5
6
  * Why a single offering could not be turned into a launchable source.
@@ -12,6 +13,9 @@ export type SourceSkip = {
12
13
  } | {
13
14
  reason: "credential_unresolved";
14
15
  provider: string;
16
+ } | {
17
+ reason: "credential_unauthorized";
18
+ provider: string;
15
19
  } | {
16
20
  reason: "provider_misconfigured";
17
21
  provider: string;
@@ -31,6 +35,7 @@ export type SourceSkip = {
31
35
  export type CatalogSourceResolution = {
32
36
  ok: true;
33
37
  sources: InferenceSource[];
38
+ materials: CredentialMaterialEntry[];
34
39
  } | {
35
40
  ok: false;
36
41
  reason: "no_requirements";
@@ -40,6 +45,22 @@ export type CatalogSourceResolution = {
40
45
  model: string;
41
46
  skips: SourceSkip[];
42
47
  };
48
+ export type OfferingSourceResolution = {
49
+ ok: true;
50
+ sources: InferenceSource[];
51
+ } | {
52
+ ok: false;
53
+ reason: "offering_unavailable";
54
+ offeringId: string;
55
+ skip?: SourceSkip;
56
+ };
57
+ /**
58
+ * Rebuild an exact, ordered source chain from durable catalog offering ids.
59
+ * This is the recovery counterpart to launch-time source selection: it keeps
60
+ * secrets out of persisted launch specs and rechecks tenant visibility and
61
+ * credential ownership on every launch.
62
+ */
63
+ export declare function resolveSourcesByOfferingIds(db: DB["db"], tenantId: string, offeringIds: readonly string[], credentialCipher: CredentialCipher): Promise<OfferingSourceResolution>;
43
64
  /**
44
65
  * Resolves an agent's model requirements against the tenant catalog into an
45
66
  * ordered `InferenceSource[]` for the harness.
@@ -51,19 +72,45 @@ export type CatalogSourceResolution = {
51
72
  * offering is resolved to a credential-backed source; offerings that cannot
52
73
  * produce one are skipped. A required model that yields no source makes the
53
74
  * agent unlaunchable.
75
+ *
76
+ * A credential-backed source is emitted only when the launching tenant owns
77
+ * the referenced credential within its hierarchy (ownership is the authority);
78
+ * otherwise the offering is skipped (`credential_unauthorized`) and its secret
79
+ * is withheld.
54
80
  */
55
- export declare function resolveModelSources(db: DB["db"], tenantId: string, requirements: ModelRequirement[], opts?: {
81
+ export declare function resolveModelSources(db: DB["db"], tenantId: string, requirements: ModelRequirement[], credentialCipher: CredentialCipher, opts?: {
56
82
  invokerPreferences?: Record<string, ProviderPreference>;
57
83
  }): Promise<CatalogSourceResolution>;
84
+ /**
85
+ * Resolve an agent's model requirements to the credential-free
86
+ * `{ provider, model }` inference preferences a folded workflow definition
87
+ * carries on its step agent. Reuses `resolveModelSources` -- the same catalog
88
+ * + creator-grant resolution the instance launch path runs -- and projects its
89
+ * ordered sources to their `{ provider (plugin), model }` identity, dropping
90
+ * the credentials: a definition's inference preference is hash-only, and the
91
+ * credential-bearing sources are supplied per deploy on the workflow path.
92
+ *
93
+ * A requirement set that resolves to no source is a hard failure. A folded
94
+ * agent whose model is unresolvable is undeployable, so this raises rather
95
+ * than synthesizing an empty preference list that would silently strip the
96
+ * agent's inference. It also raises when the resolved sources are not
97
+ * injective on `(provider, model)` -- the projection would otherwise freeze
98
+ * an ambiguous preference list whose lost `offering.id` distinctions cannot be
99
+ * recovered after the agent's columns are dropped.
100
+ */
101
+ export declare function resolveInferencePreferences(db: DB["db"], tenantId: string, requirements: ModelRequirement[], credentialCipher: CredentialCipher): Promise<{
102
+ provider: string;
103
+ model: string;
104
+ }[]>;
58
105
  /**
59
106
  * Resolves the ordered sources for a running instance from persisted state:
60
- * the agent definition's model requirements and the invoker's launch-time
61
- * preferences stored on the instance row. Launch, credential-rotation push,
62
- * and sidecar reconnect all resolve through this, so a running instance's
107
+ * the definition's model requirements and the invoker's launch-time
108
+ * preferences stored on the instance row. The credential-rotation and
109
+ * catalog-edit source push resolves through this, so a running instance's
63
110
  * source list is a pure function of persisted state — re-resolution
64
111
  * reproduces the launch ordering, including the invoker's reorder/restrict.
65
112
  */
66
113
  export declare function resolveInstanceModelSources(db: DB["db"], tenantId: string, instance: {
67
- agentId: string;
114
+ definitionId: string;
68
115
  modelPreferences: unknown;
69
- }): Promise<CatalogSourceResolution>;
116
+ }, credentialCipher: CredentialCipher): Promise<CatalogSourceResolution>;