@intx/db 0.1.2 → 0.3.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 (241) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +23 -4
  3. package/dist/approval-store.d.ts +52 -0
  4. package/dist/approval-store.js +79 -0
  5. package/dist/asset-resolution.d.ts +52 -0
  6. package/dist/asset-resolution.js +66 -0
  7. package/dist/catalog-resolution.d.ts +76 -0
  8. package/dist/catalog-resolution.js +151 -0
  9. package/dist/client.d.ts +19 -0
  10. package/dist/client.js +18 -0
  11. package/dist/config.d.ts +11 -0
  12. package/dist/config.js +16 -0
  13. package/dist/connection.d.ts +3 -0
  14. package/dist/connection.js +26 -0
  15. package/dist/credential-resolution.d.ts +222 -0
  16. package/dist/credential-resolution.js +255 -0
  17. package/dist/grant-store.d.ts +3 -0
  18. package/dist/grant-store.js +59 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.js +20 -0
  21. package/dist/migrate.d.ts +36 -0
  22. package/dist/migrate.js +135 -0
  23. package/dist/model-source-resolution.d.ts +115 -0
  24. package/dist/model-source-resolution.js +291 -0
  25. package/dist/parse-row.d.ts +373 -0
  26. package/dist/parse-row.js +232 -0
  27. package/dist/pg-error.d.ts +3 -0
  28. package/dist/pg-error.js +19 -0
  29. package/dist/pricing.d.ts +15 -0
  30. package/dist/pricing.js +24 -0
  31. package/dist/rekey-credential-secrets.d.ts +13 -0
  32. package/dist/rekey-credential-secrets.js +61 -0
  33. package/dist/schema/approvals.d.ts +245 -0
  34. package/dist/schema/approvals.js +52 -0
  35. package/dist/schema/assets.d.ts +143 -0
  36. package/{src/schema/assets.ts → dist/schema/assets.js} +7 -16
  37. package/dist/schema/auth.d.ts +606 -0
  38. package/dist/schema/auth.js +47 -0
  39. package/dist/schema/catalog.d.ts +811 -0
  40. package/dist/schema/catalog.js +108 -0
  41. package/dist/schema/column-types.d.ts +26 -0
  42. package/dist/schema/column-types.js +15 -0
  43. package/dist/schema/credentials.d.ts +305 -0
  44. package/{src/schema/credentials.ts → dist/schema/credentials.js} +16 -22
  45. package/dist/schema/git-tokens.d.ts +256 -0
  46. package/{src/schema/git-tokens.ts → dist/schema/git-tokens.js} +16 -39
  47. package/dist/schema/grants.d.ts +211 -0
  48. package/dist/schema/grants.js +30 -0
  49. package/dist/schema/index.d.ts +25 -0
  50. package/dist/schema/index.js +25 -0
  51. package/dist/schema/messages.d.ts +414 -0
  52. package/dist/schema/messages.js +82 -0
  53. package/dist/schema/oauth-clients.d.ts +246 -0
  54. package/{src/schema/oauth-clients.ts → dist/schema/oauth-clients.js} +8 -14
  55. package/dist/schema/offerings.d.ts +160 -0
  56. package/dist/schema/offerings.js +24 -0
  57. package/dist/schema/principals.d.ts +128 -0
  58. package/dist/schema/principals.js +23 -0
  59. package/dist/schema/providers.d.ts +237 -0
  60. package/dist/schema/providers.js +22 -0
  61. package/dist/schema/roles.d.ts +242 -0
  62. package/dist/schema/roles.js +37 -0
  63. package/dist/schema/session-assets.d.ts +92 -0
  64. package/dist/schema/session-assets.js +45 -0
  65. package/dist/schema/sessions.d.ts +143 -0
  66. package/dist/schema/sessions.js +29 -0
  67. package/dist/schema/sidecar-allocation.d.ts +406 -0
  68. package/dist/schema/sidecar-allocation.js +57 -0
  69. package/dist/schema/sidecar.d.ts +145 -0
  70. package/dist/schema/sidecar.js +28 -0
  71. package/dist/schema/signal-correlations.d.ts +177 -0
  72. package/dist/schema/signal-correlations.js +31 -0
  73. package/dist/schema/tenants.d.ts +235 -0
  74. package/{src/schema/tenants.ts → dist/schema/tenants.js} +10 -27
  75. package/dist/schema/wallets.d.ts +354 -0
  76. package/dist/schema/wallets.js +43 -0
  77. package/dist/schema/workflow-definitions.d.ts +371 -0
  78. package/dist/schema/workflow-definitions.js +97 -0
  79. package/dist/schema/workflow-run-dispatch.d.ts +328 -0
  80. package/dist/schema/workflow-run-dispatch.js +56 -0
  81. package/dist/schema/workflow-run-execution.d.ts +126 -0
  82. package/dist/schema/workflow-run-execution.js +48 -0
  83. package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
  84. package/dist/schema/workflow-run-launch-spec.js +26 -0
  85. package/dist/schema/workflow-run.d.ts +230 -0
  86. package/dist/schema/workflow-run.js +102 -0
  87. package/dist/sidecar-allocation-store.d.ts +171 -0
  88. package/dist/sidecar-allocation-store.js +512 -0
  89. package/dist/signal-correlation-store.d.ts +36 -0
  90. package/dist/signal-correlation-store.js +63 -0
  91. package/dist/tenant-hierarchy.d.ts +23 -0
  92. package/dist/tenant-hierarchy.js +60 -0
  93. package/dist/workflow-definition-store.d.ts +55 -0
  94. package/dist/workflow-definition-store.js +93 -0
  95. package/dist/workflow-run-dispatch-store.d.ts +80 -0
  96. package/dist/workflow-run-dispatch-store.js +298 -0
  97. package/dist/workflow-run-launch-spec-store.d.ts +13 -0
  98. package/dist/workflow-run-launch-spec-store.js +30 -0
  99. package/dist/workflow-run-store.d.ts +54 -0
  100. package/dist/workflow-run-store.js +85 -0
  101. package/migrations/0029_loose_warlock.sql +1 -0
  102. package/migrations/0030_session_asset_audit_split.sql +5 -0
  103. package/migrations/0031_gigantic_nicolaos.sql +68 -0
  104. package/migrations/0032_lethal_misty_knight.sql +1 -0
  105. package/migrations/0033_old_payback.sql +1 -0
  106. package/migrations/0034_sleepy_songbird.sql +11 -0
  107. package/migrations/0035_violet_giant_man.sql +3 -0
  108. package/migrations/0036_sharp_the_executioner.sql +2 -0
  109. package/migrations/0037_credential_use_backfill.sql +39 -0
  110. package/migrations/0038_chilly_blacklash.sql +37 -0
  111. package/migrations/0039_quick_carnage.sql +2 -0
  112. package/migrations/0040_reshape_approval_origin.sql +24 -0
  113. package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
  114. package/migrations/0042_youthful_mantis.sql +2 -0
  115. package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
  116. package/migrations/0044_model_offering_quirks.sql +1 -0
  117. package/migrations/0045_create_workflow_run.sql +13 -0
  118. package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
  119. package/migrations/0047_create_workflow_definition.sql +30 -0
  120. package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
  121. package/migrations/0049_curly_omega_flight.sql +2 -0
  122. package/migrations/0050_late_crystal.sql +10 -0
  123. package/migrations/0051_funny_madelyne_pryor.sql +1 -0
  124. package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
  125. package/migrations/0053_session_mail_session_id_index.sql +1 -0
  126. package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
  127. package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
  128. package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
  129. package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
  130. package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
  131. package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
  132. package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
  133. package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
  134. package/migrations/0062_drop_agent_asset_table.sql +11 -0
  135. package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
  136. package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
  137. package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
  138. package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
  139. package/migrations/0067_add_workflow_definition_kind.sql +10 -0
  140. package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
  141. package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
  142. package/migrations/0070_flashy_proteus.sql +13 -0
  143. package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
  144. package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
  145. package/migrations/0074_add_provider_api_base_url.sql +1 -0
  146. package/migrations/0075_workflow_run_launch_spec.sql +17 -0
  147. package/migrations/0076_sidecar_allocation.sql +40 -0
  148. package/migrations/0077_workflow_run_dispatch.sql +29 -0
  149. package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
  150. package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
  151. package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
  152. package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
  153. package/migrations/0082_blue_black_queen.sql +1 -0
  154. package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
  155. package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
  156. package/migrations/meta/0029_snapshot.json +2664 -0
  157. package/migrations/meta/0030_snapshot.json +2670 -0
  158. package/migrations/meta/0031_snapshot.json +3100 -0
  159. package/migrations/meta/0032_snapshot.json +3106 -0
  160. package/migrations/meta/0033_snapshot.json +3112 -0
  161. package/migrations/meta/0034_snapshot.json +3198 -0
  162. package/migrations/meta/0035_snapshot.json +3225 -0
  163. package/migrations/meta/0036_snapshot.json +3237 -0
  164. package/migrations/meta/0037_snapshot.json +3237 -0
  165. package/migrations/meta/0038_snapshot.json +3470 -0
  166. package/migrations/meta/0039_snapshot.json +3470 -0
  167. package/migrations/meta/0040_snapshot.json +3489 -0
  168. package/migrations/meta/0041_snapshot.json +3489 -0
  169. package/migrations/meta/0042_snapshot.json +3489 -0
  170. package/migrations/meta/0043_snapshot.json +3498 -0
  171. package/migrations/meta/0044_snapshot.json +3504 -0
  172. package/migrations/meta/0045_snapshot.json +3589 -0
  173. package/migrations/meta/0046_snapshot.json +3607 -0
  174. package/migrations/meta/0047_snapshot.json +3832 -0
  175. package/migrations/meta/0048_snapshot.json +3853 -0
  176. package/migrations/meta/0049_snapshot.json +3854 -0
  177. package/migrations/meta/0050_snapshot.json +3924 -0
  178. package/migrations/meta/0051_snapshot.json +3940 -0
  179. package/migrations/meta/0052_snapshot.json +3931 -0
  180. package/migrations/meta/0053_snapshot.json +3952 -0
  181. package/migrations/meta/0054_snapshot.json +3952 -0
  182. package/migrations/meta/0055_snapshot.json +3952 -0
  183. package/migrations/meta/0056_snapshot.json +3952 -0
  184. package/migrations/meta/0057_snapshot.json +3839 -0
  185. package/migrations/meta/0058_snapshot.json +3839 -0
  186. package/migrations/meta/0059_snapshot.json +3839 -0
  187. package/migrations/meta/0060_snapshot.json +3830 -0
  188. package/migrations/meta/0061_snapshot.json +3821 -0
  189. package/migrations/meta/0062_snapshot.json +3723 -0
  190. package/migrations/meta/0063_snapshot.json +3723 -0
  191. package/migrations/meta/0064_snapshot.json +3723 -0
  192. package/migrations/meta/0065_snapshot.json +3729 -0
  193. package/migrations/meta/0066_snapshot.json +3729 -0
  194. package/migrations/meta/0067_snapshot.json +3736 -0
  195. package/migrations/meta/0068_snapshot.json +3347 -0
  196. package/migrations/meta/0069_snapshot.json +3340 -0
  197. package/migrations/meta/0070_snapshot.json +3444 -0
  198. package/migrations/meta/0072_snapshot.json +3450 -0
  199. package/migrations/meta/0073_snapshot.json +3455 -0
  200. package/migrations/meta/0074_snapshot.json +3461 -0
  201. package/migrations/meta/0075_snapshot.json +3567 -0
  202. package/migrations/meta/0076_snapshot.json +3851 -0
  203. package/migrations/meta/0077_snapshot.json +4067 -0
  204. package/migrations/meta/0078_snapshot.json +4078 -0
  205. package/migrations/meta/0079_snapshot.json +4078 -0
  206. package/migrations/meta/0080_snapshot.json +4078 -0
  207. package/migrations/meta/0081_snapshot.json +4096 -0
  208. package/migrations/meta/0082_snapshot.json +4102 -0
  209. package/migrations/meta/0083_snapshot.json +4096 -0
  210. package/migrations/meta/0084_snapshot.json +4096 -0
  211. package/migrations/meta/_journal.json +385 -0
  212. package/package.json +25 -11
  213. package/drizzle.config.ts +0 -23
  214. package/src/client.ts +0 -24
  215. package/src/config.ts +0 -19
  216. package/src/connection.ts +0 -27
  217. package/src/credential-resolution.ts +0 -378
  218. package/src/grant-store.ts +0 -51
  219. package/src/index.ts +0 -32
  220. package/src/migrate.test.ts +0 -35
  221. package/src/migrate.ts +0 -168
  222. package/src/parse-row.test.ts +0 -113
  223. package/src/parse-row.ts +0 -185
  224. package/src/schema/agent-assets.ts +0 -21
  225. package/src/schema/agents.ts +0 -49
  226. package/src/schema/auth.ts +0 -51
  227. package/src/schema/grants.ts +0 -26
  228. package/src/schema/index.ts +0 -19
  229. package/src/schema/instances.ts +0 -36
  230. package/src/schema/messages.ts +0 -108
  231. package/src/schema/offerings.ts +0 -20
  232. package/src/schema/principals.ts +0 -28
  233. package/src/schema/providers.ts +0 -23
  234. package/src/schema/roles.ts +0 -51
  235. package/src/schema/session-assets.ts +0 -49
  236. package/src/schema/sessions.ts +0 -26
  237. package/src/schema/sidecar.ts +0 -14
  238. package/src/schema/wallets.ts +0 -44
  239. package/src/tenant-hierarchy.ts +0 -34
  240. package/tsconfig.json +0 -4
  241. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,255 @@
1
+ import { eq, and, isNull } from "drizzle-orm";
2
+ import { toolConsumer } from "@intx/authz";
3
+ import { credentialAad } from "@intx/types";
4
+ import { credential } from "./schema/credentials.js";
5
+ import { oauthClient } from "./schema/oauth-clients.js";
6
+ import { provider } from "./schema/providers.js";
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
+ * Resolve a credential USABLE by the launching tenant purely through ownership:
23
+ * it exists, is reachable in the tenant's ancestor chain, and is tenant-owned
24
+ * (`principalId IS NULL`). Returns the row -- the proof of authority -- or null.
25
+ *
26
+ * This is the one "usable by ownership" resolver: a tenant may use what it (or
27
+ * an ancestor) owns, and descendants inherit; a principal-owned credential is
28
+ * never usable this way, its delegation flowing from its owner instead. It
29
+ * wraps `resolveCredentialById` (the ancestor-chain check) with the tenant-owned
30
+ * gate, so callers do not re-implement the predicate. `buildSource` expands it
31
+ * inline only to distinguish an unresolved reference from a principal-owned one.
32
+ */
33
+ export async function resolveTenantOwnedCredentialById(db, tenantId, credentialId) {
34
+ const row = await resolveCredentialById(db, tenantId, credentialId);
35
+ return row !== null && row.principalId === null ? row : null;
36
+ }
37
+ /**
38
+ * Resolves a provider by name, walking up the tenant hierarchy.
39
+ * Returns the first match (child shadows parent).
40
+ */
41
+ export async function resolveProviderByName(db, tenantId, name) {
42
+ const chain = await getAncestorChain(db, tenantId);
43
+ for (const tid of chain) {
44
+ const row = await db.query.provider.findFirst({
45
+ where: and(eq(provider.tenantId, tid), eq(provider.name, name)),
46
+ });
47
+ if (row)
48
+ return row;
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Resolves an OAuth client for a provider, walking up the tenant hierarchy.
54
+ * Returns the first match (child shadows parent).
55
+ */
56
+ export async function resolveOAuthClient(db, tenantId, providerId) {
57
+ const chain = await getAncestorChain(db, tenantId);
58
+ for (const tid of chain) {
59
+ const row = await db.query.oauthClient.findFirst({
60
+ where: and(eq(oauthClient.tenantId, tid), eq(oauthClient.providerId, providerId)),
61
+ });
62
+ if (row)
63
+ return row;
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Resolves a credential by name, walking up the tenant hierarchy.
69
+ * Returns the first match (child shadows parent).
70
+ */
71
+ export async function resolveCredentialByName(db, tenantId, name) {
72
+ const chain = await getAncestorChain(db, tenantId);
73
+ for (const tid of chain) {
74
+ const row = await db.query.credential.findFirst({
75
+ where: and(eq(credential.tenantId, tid), eq(credential.name, name)),
76
+ });
77
+ if (row)
78
+ return row;
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Resolves a credential by ID, validating that it belongs to the
84
+ * given tenant or one of its ancestors.
85
+ */
86
+ export async function resolveCredentialById(db, tenantId, credentialId) {
87
+ const row = await db.query.credential.findFirst({
88
+ where: eq(credential.id, credentialId),
89
+ });
90
+ if (!row)
91
+ return null;
92
+ const chain = await getAncestorChain(db, tenantId);
93
+ if (!chain.includes(row.tenantId))
94
+ return null;
95
+ return row;
96
+ }
97
+ /**
98
+ * Resolves a credential matching an agent definition requirement.
99
+ * Used at agent launch time by the control plane to satisfy a definition's
100
+ * tool and integration credentials (inference sources resolve through the
101
+ * catalog, not this path).
102
+ */
103
+ export async function resolveCredentialRequirement(db, tenantId, requirement, creatorPrincipalId, invokerPrincipalId) {
104
+ const resolvedProvider = await resolveProviderByName(db, tenantId, requirement.providerName);
105
+ if (!resolvedProvider)
106
+ return null;
107
+ const chain = await getAncestorChain(db, tenantId);
108
+ const principalFilter = requirement.source === "tenant"
109
+ ? null
110
+ : requirement.source === "creator"
111
+ ? creatorPrincipalId
112
+ : invokerPrincipalId;
113
+ for (const tid of chain) {
114
+ const conditions = [
115
+ eq(credential.tenantId, tid),
116
+ eq(credential.providerId, resolvedProvider.id),
117
+ eq(credential.status, "active"),
118
+ ];
119
+ if (principalFilter === null) {
120
+ conditions.push(isNull(credential.principalId));
121
+ }
122
+ else if (principalFilter) {
123
+ conditions.push(eq(credential.principalId, principalFilter));
124
+ }
125
+ if (requirement.name) {
126
+ conditions.push(eq(credential.name, requirement.name));
127
+ }
128
+ const rows = await db.query.credential.findMany({
129
+ where: and(...conditions),
130
+ });
131
+ const matching = rows.filter((row) => {
132
+ if (!requirement.scopes || requirement.scopes.length === 0)
133
+ return true;
134
+ const rowScopes = row.scopes ?? [];
135
+ return requirement.scopes.every((s) => rowScopes.includes(s));
136
+ });
137
+ const [sole] = matching;
138
+ // Return the resolved provider alongside the credential. The provider was
139
+ // already fetched above to constrain the credential query, so surfacing it
140
+ // spares the caller a second lookup for the provider facts (plugin, base
141
+ // URL) a resolved credential is always paired with.
142
+ if (matching.length === 1 && sole) {
143
+ return { credential: sole, provider: resolvedProvider };
144
+ }
145
+ if (matching.length > 1) {
146
+ throw new AmbiguousCredentialError(`Ambiguous credential match: ${matching.length} credentials match ` +
147
+ `provider=${requirement.providerName} source=${requirement.source} ` +
148
+ `in tenant ${tid}. Specify a name to disambiguate.`);
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ /**
154
+ * Resolve a definition's credential bindings into the material + per-handle
155
+ * descriptors delivered to its tools. Each credential's secret is decrypted
156
+ * once, keyed by credentialId (a credential backing several handles is
157
+ * decrypted once).
158
+ *
159
+ * This path delivers credential material only; it does not mint, stamp, or
160
+ * carry any grant. Credential-use authorization is enforced by a separate grant
161
+ * layer.
162
+ *
163
+ * The workflow-deploy path (`deployCodeSourcedWorkflow`) is the only caller
164
+ * today, using `delivery`. Returning a discriminated result rather than an HTTP
165
+ * response keeps it safe to call off the request path: a configuration failure
166
+ * is `ok: false`, and a transient DB read fault THROWS so the caller surfaces it
167
+ * rather than silently dropping a still-valid credential.
168
+ */
169
+ export async function buildCredentialDelivery(args) {
170
+ const materials = new Map();
171
+ const descriptors = [];
172
+ for (const binding of args.bindings) {
173
+ const context = {
174
+ provider: binding.provider,
175
+ package: binding.package,
176
+ handle: binding.handle,
177
+ };
178
+ let resolved;
179
+ try {
180
+ resolved = await resolveCredentialRequirement(args.db, args.tenantId, {
181
+ providerName: binding.provider,
182
+ source: binding.locator,
183
+ ...(binding.name !== undefined ? { name: binding.name } : {}),
184
+ }, args.creatorPrincipalId, args.invokerPrincipalId);
185
+ }
186
+ catch (e) {
187
+ // AmbiguousCredentialError is a launch-blocking config failure; any other
188
+ // throw is a DB read fault the resolver performs first -- surface it, do
189
+ // not mislabel it as revocation.
190
+ if (!(e instanceof AmbiguousCredentialError))
191
+ throw e;
192
+ return {
193
+ ok: false,
194
+ reason: {
195
+ code: "ambiguous",
196
+ binding: context,
197
+ message: `Ambiguous credential for the binding on provider ${binding.provider} (package ${binding.package}, handle ${binding.handle}): ${e.message}`,
198
+ },
199
+ };
200
+ }
201
+ if (resolved === null) {
202
+ return {
203
+ ok: false,
204
+ reason: {
205
+ code: "unresolved",
206
+ binding: context,
207
+ message: `No credential resolves the binding for provider ${binding.provider} (package ${binding.package}, handle ${binding.handle})`,
208
+ },
209
+ };
210
+ }
211
+ // The credential is delivered as an origin-pinned http handle, so its
212
+ // provider must declare an API origin. A provider without one (OAuth-login
213
+ // only) cannot back a tool credential; fail closed rather than deliver an
214
+ // un-pinnable secret.
215
+ const providerOrigin = resolved.provider.apiBaseUrl;
216
+ if (providerOrigin === null || providerOrigin === "") {
217
+ return {
218
+ ok: false,
219
+ reason: {
220
+ code: "no_origin",
221
+ binding: context,
222
+ message: `Provider ${binding.provider} has no API base URL; cannot deliver an origin-pinned credential (package ${binding.package}, handle ${binding.handle})`,
223
+ },
224
+ };
225
+ }
226
+ const credentialId = resolved.credential.id;
227
+ // A binding resolves only a tenant-owned credential (the `tenant` locator
228
+ // filters `principalId IS NULL` in resolveCredentialRequirement), so its use
229
+ // is authorized by ownership -- already proven by this walk-up resolution,
230
+ // not re-checked here. This path delivers credential material only; it does
231
+ // not mint or carry any grant. Credential-use authorization is enforced by a
232
+ // separate grant layer.
233
+ descriptors.push({
234
+ handle: binding.handle,
235
+ credentialId,
236
+ consumer: toolConsumer(binding.package),
237
+ });
238
+ if (!materials.has(credentialId)) {
239
+ // Decrypt at the single point of use. A decrypt failure propagates and
240
+ // fails the caller closed; there is no placeholder secret.
241
+ materials.set(credentialId, {
242
+ credentialId,
243
+ providerKey: resolved.provider.plugin,
244
+ origin: providerOrigin,
245
+ secret: await args.credentialCipher.decrypt(resolved.credential.secret, credentialAad(credentialId, "secret")),
246
+ });
247
+ }
248
+ }
249
+ return {
250
+ ok: true,
251
+ delivery: descriptors.length > 0
252
+ ? { bindings: descriptors, materials: [...materials.values()] }
253
+ : undefined,
254
+ };
255
+ }
@@ -0,0 +1,3 @@
1
+ import type { GrantStore } from "@intx/types/authz";
2
+ import type { DB } from "./client.js";
3
+ export declare function createGrantStore(db: DB["db"]): GrantStore;
@@ -0,0 +1,59 @@
1
+ import { eq, and, or, isNull, gt, inArray } from "drizzle-orm";
2
+ import { grant } from "./schema/grants.js";
3
+ import { principalRole } from "./schema/roles.js";
4
+ import { parseGrantRow } from "./parse-row.js";
5
+ import { getAncestorChain } from "./tenant-hierarchy.js";
6
+ function toGrantRule(row) {
7
+ const parsed = parseGrantRow(row);
8
+ return {
9
+ id: parsed.id,
10
+ resource: parsed.resource,
11
+ action: parsed.action,
12
+ effect: parsed.effect,
13
+ origin: parsed.origin,
14
+ conditions: parsed.conditions,
15
+ expiresAt: parsed.expiresAt,
16
+ roleId: parsed.roleId,
17
+ principalId: parsed.principalId,
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
+ }
41
+ export function createGrantStore(db) {
42
+ return {
43
+ async collectGrants(principalId, tenantId) {
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));
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,22 @@
1
+ export { createDB, type DB, type DBExecutor } from "./client.js";
2
+ export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
3
+ export type { DBConfig } from "./config.js";
4
+ export { runMigrations, dropSchema } from "./migrate.js";
5
+ export { rekeyCredentialSecrets, type RekeyReport, } from "./rekey-credential-secrets.js";
6
+ export { createGrantStore } from "./grant-store.js";
7
+ export { createApprovalStore, type ApprovalStore, type ResolveApprovalArgs, } from "./approval-store.js";
8
+ export { createSignalCorrelationStore, type SignalCorrelationStore, } from "./signal-correlation-store.js";
9
+ export { createWorkflowRunStore, type WorkflowRunStore, } from "./workflow-run-store.js";
10
+ export { createWorkflowRunLaunchSpecStore, type WorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
11
+ export { createWorkflowRunDispatchStore, WorkflowRunDispatchPayloadConflictError, type AcknowledgeWorkflowRunDispatchArgs, type ClaimWorkflowRunDispatchArgs, type EnqueueWorkflowRunDispatchArgs, type EnqueueWorkflowRunDispatchResult, type EnqueueWorkflowSignalDispatchArgs, type RetryWorkflowRunDispatchArgs, type WorkflowRunDispatchStore, } from "./workflow-run-dispatch-store.js";
12
+ export { createSidecarAllocationStore, type BeginSidecarReleaseArgs, type BeginSidecarReplacementArgs, type BindInitialSidecarArgs, type BindReplacementSidecarArgs, type ClaimSidecarAllocationArgs, type CreatePendingSidecarAllocationArgs, type FailSidecarAllocationArgs, type MarkSidecarAllocatedArgs, type MarkSidecarConnectionLostArgs, type MarkSidecarConnectionReadyArgs, type MarkSidecarReleasedArgs, type ParkSidecarReconciliationPolicy, type ScheduleSidecarAllocationRetryArgs, type SidecarAllocation, type SidecarAllocationStore, } from "./sidecar-allocation-store.js";
13
+ export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, type WorkflowDefinitionRollbackResult, type WorkflowDefinitionSelector, } from "./workflow-definition-store.js";
14
+ export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
15
+ export { resolveActivePrice, type ModelPricingRow } from "./pricing.js";
16
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, AmbiguousCredentialError, buildCredentialDelivery, } from "./credential-resolution.js";
17
+ export type { BuildCredentialDeliveryResult, CredentialDeliveryFailure, } from "./credential-resolution.js";
18
+ export { resolveAssetByName, resolveAssetById, listAssetsForTenant, type AssetRow, type AssetWithOrigin, } from "./asset-resolution.js";
19
+ export { listVisibleModels, listVisibleProviders, listVisibleOfferings, type ModelRow, type ModelProviderRow, type ModelOfferingRow, type Origin, type VisibleModel, type VisibleProvider, type ResolvedOffering, } from "./catalog-resolution.js";
20
+ export { resolveModelSources, resolveInferencePreferences, resolveInstanceModelSources, resolveSourcesByOfferingIds, type CatalogSourceResolution, type OfferingSourceResolution, type SourceSkip, } from "./model-source-resolution.js";
21
+ export { parseGrantRow, parseApprovalRow, parsePrincipalRow, parseSignalCorrelationRow, parseWorkflowRunRow, parseWorkflowRunDispatchRow, parseWorkflowRunLaunchSpecRow, parseWorkflowDefinitionRow, parseWorkflowDefinitionVersionRow, parseOfferingRow, parseModelOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
22
+ export * as schema from "./schema/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ export { createDB } from "./client.js";
2
+ export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
3
+ export { runMigrations, dropSchema } from "./migrate.js";
4
+ export { rekeyCredentialSecrets, } from "./rekey-credential-secrets.js";
5
+ export { createGrantStore } from "./grant-store.js";
6
+ export { createApprovalStore, } from "./approval-store.js";
7
+ export { createSignalCorrelationStore, } from "./signal-correlation-store.js";
8
+ export { createWorkflowRunStore, } from "./workflow-run-store.js";
9
+ export { createWorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
10
+ export { createWorkflowRunDispatchStore, WorkflowRunDispatchPayloadConflictError, } from "./workflow-run-dispatch-store.js";
11
+ export { createSidecarAllocationStore, } from "./sidecar-allocation-store.js";
12
+ export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, } from "./workflow-definition-store.js";
13
+ export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
14
+ export { resolveActivePrice } from "./pricing.js";
15
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, AmbiguousCredentialError, buildCredentialDelivery, } from "./credential-resolution.js";
16
+ export { resolveAssetByName, resolveAssetById, listAssetsForTenant, } from "./asset-resolution.js";
17
+ export { listVisibleModels, listVisibleProviders, listVisibleOfferings, } from "./catalog-resolution.js";
18
+ export { resolveModelSources, resolveInferencePreferences, resolveInstanceModelSources, resolveSourcesByOfferingIds, } from "./model-source-resolution.js";
19
+ export { parseGrantRow, parseApprovalRow, parsePrincipalRow, parseSignalCorrelationRow, parseWorkflowRunRow, parseWorkflowRunDispatchRow, parseWorkflowRunLaunchSpecRow, parseWorkflowDefinitionRow, parseWorkflowDefinitionVersionRow, parseOfferingRow, parseModelOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
20
+ export * as schema from "./schema/index.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Rewrite drizzle-generated schema-qualified references like
3
+ * `REFERENCES "public"."<table>"` so they point at the target
4
+ * schema. The pattern matches `"public".` only when followed by
5
+ * another quoted identifier, leaving bare `"public"` inside string
6
+ * literals alone.
7
+ */
8
+ export declare function rewriteSchemaQualifiedReferences(sql: string, schemaIdent: string): string;
9
+ /**
10
+ * Apply the drizzle-generated migration SQL into the given postgres
11
+ * schema. The schema is created if absent; nothing else is dropped.
12
+ *
13
+ * The migration source files reference the destination schema
14
+ * literally (e.g. `"public"."user"` in FK constraints). To make the
15
+ * same source apply cleanly into an arbitrary schema, we substitute
16
+ * the literal token `"public"` with the quoted target schema before
17
+ * executing each file. This is a textual substitution rather than a
18
+ * postgres-side `search_path` trick because the FK references are
19
+ * fully qualified; `search_path` would not redirect them.
20
+ *
21
+ * The substitution is bounded: the migration source is
22
+ * machine-generated and never contains the string `"public"` other
23
+ * than in schema-qualified identifiers, so there is no ambiguity to
24
+ * worry about. The substitution is a no-op when the target schema is
25
+ * literally `public`, which is the common case.
26
+ */
27
+ export declare function runMigrations(configRaw: unknown, options: {
28
+ schema: string;
29
+ }): Promise<void>;
30
+ /**
31
+ * Drop the given schema along with everything in it. Used by the
32
+ * integration-test harness to tear down per-test schemas.
33
+ */
34
+ export declare function dropSchema(configRaw: unknown, options: {
35
+ schema: string;
36
+ }): Promise<void>;
@@ -0,0 +1,135 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { type } from "arktype";
4
+ import postgres from "postgres";
5
+ import { DBConfig } from "./config.js";
6
+ // Resolution of the migrations directory: the package layout pins
7
+ // the drizzle-generated SQL at `<pkgRoot>/migrations`. This file
8
+ // lives at `<pkgRoot>/src/migrate.ts`, so the directory is one level
9
+ // up from `__dirname`. Resolving via `import.meta` keeps the runtime
10
+ // honest about where it is reading SQL from instead of relying on a
11
+ // cwd-relative path that breaks under callers that change `cwd`.
12
+ const MIGRATIONS_DIR = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", "migrations");
13
+ function quoteIdentifier(name) {
14
+ return `"${name.replace(/"/g, '""')}"`;
15
+ }
16
+ /**
17
+ * Rewrite drizzle-generated schema-qualified references like
18
+ * `REFERENCES "public"."<table>"` so they point at the target
19
+ * schema. The pattern matches `"public".` only when followed by
20
+ * another quoted identifier, leaving bare `"public"` inside string
21
+ * literals alone.
22
+ */
23
+ export function rewriteSchemaQualifiedReferences(sql, schemaIdent) {
24
+ return sql.replace(/"public"\.(?=")/g, `${schemaIdent}.`);
25
+ }
26
+ /**
27
+ * Construct the single-connection postgres client used by the
28
+ * migration and teardown entry points. Both paths share the same SSL
29
+ * passthrough and suppress NOTICE-level diagnostics (cascade reports,
30
+ * "schema already exists") that postgres.js logs by default but are
31
+ * informational only in this harness context. The optional `searchPath`
32
+ * pins `search_path` on connection-open so unqualified `CREATE TABLE`
33
+ * statements land in the caller's schema.
34
+ */
35
+ function createMigrationClient(config, searchPath) {
36
+ return postgres({
37
+ host: config.host,
38
+ port: config.port,
39
+ user: config.user,
40
+ password: config.password,
41
+ database: config.database,
42
+ max: 1,
43
+ onnotice: () => undefined,
44
+ ...(config.ssl !== undefined && { ssl: config.ssl }),
45
+ ...(searchPath !== undefined && {
46
+ connection: { search_path: searchPath },
47
+ }),
48
+ });
49
+ }
50
+ /**
51
+ * Apply the drizzle-generated migration SQL into the given postgres
52
+ * schema. The schema is created if absent; nothing else is dropped.
53
+ *
54
+ * The migration source files reference the destination schema
55
+ * literally (e.g. `"public"."user"` in FK constraints). To make the
56
+ * same source apply cleanly into an arbitrary schema, we substitute
57
+ * the literal token `"public"` with the quoted target schema before
58
+ * executing each file. This is a textual substitution rather than a
59
+ * postgres-side `search_path` trick because the FK references are
60
+ * fully qualified; `search_path` would not redirect them.
61
+ *
62
+ * The substitution is bounded: the migration source is
63
+ * machine-generated and never contains the string `"public"` other
64
+ * than in schema-qualified identifiers, so there is no ambiguity to
65
+ * worry about. The substitution is a no-op when the target schema is
66
+ * literally `public`, which is the common case.
67
+ */
68
+ export async function runMigrations(configRaw, options) {
69
+ const config = DBConfig(configRaw);
70
+ if (config instanceof type.errors) {
71
+ throw new Error(`Invalid database config: ${config.summary}`);
72
+ }
73
+ const { schema } = options;
74
+ if (schema.length === 0) {
75
+ throw new Error("runMigrations: schema name must not be empty");
76
+ }
77
+ const schemaIdent = quoteIdentifier(schema);
78
+ // Pin search_path on the migration connection so unqualified
79
+ // `CREATE TABLE "name"` statements land in the target schema.
80
+ // The FK references in the source SQL are already schema-qualified
81
+ // (`"public"."user"`); we rewrite those to the target schema
82
+ // below. Together these two mechanisms route every object the
83
+ // migration touches into the caller's schema.
84
+ const sql = createMigrationClient(config, schemaIdent);
85
+ try {
86
+ // The CREATE SCHEMA must run on a connection that does not
87
+ // require the schema to already exist for search_path to be
88
+ // applied. postgres.js sets the GUC after connection-open, so
89
+ // CREATE SCHEMA IF NOT EXISTS here is the first statement and
90
+ // creates the schema before search_path matters.
91
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS ${schemaIdent}`);
92
+ const files = (await readdir(MIGRATIONS_DIR))
93
+ .filter((f) => f.endsWith(".sql"))
94
+ .sort();
95
+ if (files.length === 0) {
96
+ throw new Error(`runMigrations: no .sql files found in ${MIGRATIONS_DIR}`);
97
+ }
98
+ for (const file of files) {
99
+ const raw = await readFile(path.join(MIGRATIONS_DIR, file), "utf-8");
100
+ const rendered = rewriteSchemaQualifiedReferences(raw, schemaIdent);
101
+ // drizzle emits multi-statement files separated by its own
102
+ // statement-breakpoint marker. Split on it and execute each
103
+ // statement individually so a syntax error in one statement
104
+ // surfaces with the right context.
105
+ const statements = rendered
106
+ .split("--> statement-breakpoint")
107
+ .map((s) => s.trim())
108
+ .filter((s) => s.length > 0);
109
+ for (const stmt of statements) {
110
+ await sql.unsafe(stmt);
111
+ }
112
+ }
113
+ }
114
+ finally {
115
+ await sql.end();
116
+ }
117
+ }
118
+ /**
119
+ * Drop the given schema along with everything in it. Used by the
120
+ * integration-test harness to tear down per-test schemas.
121
+ */
122
+ export async function dropSchema(configRaw, options) {
123
+ const config = DBConfig(configRaw);
124
+ if (config instanceof type.errors) {
125
+ throw new Error(`Invalid database config: ${config.summary}`);
126
+ }
127
+ const sql = createMigrationClient(config);
128
+ try {
129
+ const schemaIdent = quoteIdentifier(options.schema);
130
+ await sql.unsafe(`DROP SCHEMA IF EXISTS ${schemaIdent} CASCADE`);
131
+ }
132
+ finally {
133
+ await sql.end();
134
+ }
135
+ }
@@ -0,0 +1,115 @@
1
+ import { type CredentialCipher, type ModelRequirement, type ProviderPreference } from "@intx/types";
2
+ import type { InferenceSource } from "@intx/types/runtime";
3
+ import type { DB } from "./client.js";
4
+ /**
5
+ * Why a single offering could not be turned into a launchable source.
6
+ * `wallet_backed` providers are storable in the catalog but not launchable
7
+ * in this credential-backed-only release.
8
+ */
9
+ export type SourceSkip = {
10
+ reason: "wallet_backed";
11
+ provider: string;
12
+ } | {
13
+ reason: "credential_unresolved";
14
+ provider: string;
15
+ } | {
16
+ reason: "credential_unauthorized";
17
+ provider: string;
18
+ } | {
19
+ reason: "provider_misconfigured";
20
+ provider: string;
21
+ };
22
+ /**
23
+ * Outcome of resolving an agent's model requirements to an ordered set of
24
+ * inference sources. The order is the routing order: the head is the
25
+ * default, the tail is the failover chain.
26
+ *
27
+ * `model_unavailable.skips` enumerates the offerings that were eligible for
28
+ * the model but could not produce a launchable source (wallet-backed, or an
29
+ * unresolvable credential). It is empty when no offering was eligible at all
30
+ * — the model is absent from the tenant catalog, or the capability and
31
+ * preference filters excluded every offering — a case the launch path can
32
+ * distinguish from a populated `skips` when explaining the failure.
33
+ */
34
+ export type CatalogSourceResolution = {
35
+ ok: true;
36
+ sources: InferenceSource[];
37
+ } | {
38
+ ok: false;
39
+ reason: "no_requirements";
40
+ } | {
41
+ ok: false;
42
+ reason: "model_unavailable";
43
+ model: string;
44
+ skips: SourceSkip[];
45
+ };
46
+ export type OfferingSourceResolution = {
47
+ ok: true;
48
+ sources: InferenceSource[];
49
+ } | {
50
+ ok: false;
51
+ reason: "offering_unavailable";
52
+ offeringId: string;
53
+ skip?: SourceSkip;
54
+ };
55
+ /**
56
+ * Rebuild an exact, ordered source chain from durable catalog offering ids.
57
+ * This is the recovery counterpart to launch-time source selection: it keeps
58
+ * secrets out of persisted launch specs and rechecks tenant visibility and
59
+ * credential ownership on every launch.
60
+ */
61
+ export declare function resolveSourcesByOfferingIds(db: DB["db"], tenantId: string, offeringIds: readonly string[], credentialCipher?: CredentialCipher): Promise<OfferingSourceResolution>;
62
+ /**
63
+ * Resolves an agent's model requirements against the tenant catalog into an
64
+ * ordered `InferenceSource[]` for the harness.
65
+ *
66
+ * For each requirement, the tenant-visible offerings for the named model are
67
+ * filtered by the required capabilities, ordered by catalog priority, then
68
+ * reordered by the creator preference and finally the invoker preference
69
+ * (invoker preferences key on the canonical model name). Each surviving
70
+ * offering is resolved to a credential-backed source; offerings that cannot
71
+ * produce one are skipped. A required model that yields no source makes the
72
+ * agent unlaunchable.
73
+ *
74
+ * A credential-backed source is emitted only when the launching tenant owns
75
+ * the referenced credential within its hierarchy (ownership is the authority);
76
+ * otherwise the offering is skipped (`credential_unauthorized`) and its secret
77
+ * is withheld.
78
+ */
79
+ export declare function resolveModelSources(db: DB["db"], tenantId: string, requirements: ModelRequirement[], opts?: {
80
+ invokerPreferences?: Record<string, ProviderPreference>;
81
+ credentialCipher?: CredentialCipher;
82
+ }): Promise<CatalogSourceResolution>;
83
+ /**
84
+ * Resolve an agent's model requirements to the credential-free
85
+ * `{ provider, model }` inference preferences a folded workflow definition
86
+ * carries on its step agent. Reuses `resolveModelSources` -- the same catalog
87
+ * + creator-grant resolution the instance launch path runs -- and projects its
88
+ * ordered sources to their `{ provider (plugin), model }` identity, dropping
89
+ * the credentials: a definition's inference preference is hash-only, and the
90
+ * credential-bearing sources are supplied per deploy on the workflow path.
91
+ *
92
+ * A requirement set that resolves to no source is a hard failure. A folded
93
+ * agent whose model is unresolvable is undeployable, so this raises rather
94
+ * than synthesizing an empty preference list that would silently strip the
95
+ * agent's inference. It also raises when the resolved sources are not
96
+ * injective on `(provider, model)` -- the projection would otherwise freeze
97
+ * an ambiguous preference list whose lost `offering.id` distinctions cannot be
98
+ * recovered after the agent's columns are dropped.
99
+ */
100
+ export declare function resolveInferencePreferences(db: DB["db"], tenantId: string, requirements: ModelRequirement[]): Promise<{
101
+ provider: string;
102
+ model: string;
103
+ }[]>;
104
+ /**
105
+ * Resolves the ordered sources for a running instance from persisted state:
106
+ * the definition's model requirements and the invoker's launch-time
107
+ * preferences stored on the instance row. The credential-rotation and
108
+ * catalog-edit source push resolves through this, so a running instance's
109
+ * source list is a pure function of persisted state — re-resolution
110
+ * reproduces the launch ordering, including the invoker's reorder/restrict.
111
+ */
112
+ export declare function resolveInstanceModelSources(db: DB["db"], tenantId: string, instance: {
113
+ definitionId: string;
114
+ modelPreferences: unknown;
115
+ }, credentialCipher?: CredentialCipher): Promise<CatalogSourceResolution>;