@intx/db 0.3.0 → 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 (71) hide show
  1. package/README.md +11 -0
  2. package/dist/approval-store.d.ts +7 -0
  3. package/dist/approval-store.js +13 -0
  4. package/dist/backfill-principal-keys.d.ts +9 -0
  5. package/dist/backfill-principal-keys.js +51 -0
  6. package/dist/client.d.ts +27 -2
  7. package/dist/client.js +6 -0
  8. package/dist/config.d.ts +1 -0
  9. package/dist/config.js +2 -0
  10. package/dist/connection.js +1 -0
  11. package/dist/credential-resolution.d.ts +54 -5
  12. package/dist/credential-resolution.js +109 -0
  13. package/dist/index.d.ts +11 -5
  14. package/dist/index.js +10 -4
  15. package/dist/model-source-resolution.d.ts +6 -5
  16. package/dist/model-source-resolution.js +42 -13
  17. package/dist/parse-row.d.ts +39 -8
  18. package/dist/parse-row.js +8 -0
  19. package/dist/principal-key-store.d.ts +51 -0
  20. package/dist/principal-key-store.js +82 -0
  21. package/dist/principal-store.d.ts +44 -0
  22. package/dist/principal-store.js +67 -0
  23. package/dist/schema/approvals.js +5 -0
  24. package/dist/schema/git-tokens.js +1 -1
  25. package/dist/schema/index.d.ts +2 -0
  26. package/dist/schema/index.js +2 -0
  27. package/dist/schema/principal-keys.d.ts +126 -0
  28. package/dist/schema/principal-keys.js +28 -0
  29. package/dist/schema/sidecar-allocation.d.ts +19 -40
  30. package/dist/schema/sidecar-allocation.js +4 -8
  31. package/dist/schema/sidecar.d.ts +0 -17
  32. package/dist/schema/sidecar.js +4 -12
  33. package/dist/schema/tenants.js +13 -3
  34. package/dist/schema/workflow-probe.d.ts +358 -0
  35. package/dist/schema/workflow-probe.js +51 -0
  36. package/dist/schema/workflow-run-dispatch.d.ts +17 -0
  37. package/dist/schema/workflow-run-dispatch.js +7 -0
  38. package/dist/schema/workflow-run.d.ts +27 -0
  39. package/dist/schema/workflow-run.js +8 -0
  40. package/dist/sender-key-resolver.d.ts +91 -0
  41. package/dist/sender-key-resolver.js +184 -0
  42. package/dist/sidecar-allocation-store.d.ts +38 -3
  43. package/dist/sidecar-allocation-store.js +224 -22
  44. package/dist/signer-identity.d.ts +12 -0
  45. package/dist/signer-identity.js +15 -0
  46. package/dist/tenant-hierarchy.d.ts +2 -0
  47. package/dist/tenant-hierarchy.js +27 -0
  48. package/dist/workflow-probe-store.d.ts +47 -0
  49. package/dist/workflow-probe-store.js +124 -0
  50. package/dist/workflow-run-dispatch-store.d.ts +3 -0
  51. package/dist/workflow-run-dispatch-store.js +12 -2
  52. package/migrations/0085_add_approval_run_idx.sql +1 -0
  53. package/migrations/0086_cool_human_cannonball.sql +1 -0
  54. package/migrations/0087_drop_sidecar_placement.sql +3 -0
  55. package/migrations/0088_thick_sprite.sql +32 -0
  56. package/migrations/0089_tense_selene.sql +13 -0
  57. package/migrations/0090_tenant_domain_lower_unique.sql +2 -0
  58. package/migrations/0091_workflow_run_dispatch_sender_address.sql +21 -0
  59. package/migrations/0092_sidecar_destroy_failed.sql +4 -0
  60. package/migrations/0093_sidecar_initialization.sql +2 -0
  61. package/migrations/meta/0085_snapshot.json +4111 -0
  62. package/migrations/meta/0086_snapshot.json +4117 -0
  63. package/migrations/meta/0087_snapshot.json +4100 -0
  64. package/migrations/meta/0088_snapshot.json +4286 -0
  65. package/migrations/meta/0089_snapshot.json +4376 -0
  66. package/migrations/meta/0090_snapshot.json +4387 -0
  67. package/migrations/meta/0091_snapshot.json +4397 -0
  68. package/migrations/meta/0092_snapshot.json +4397 -0
  69. package/migrations/meta/0093_snapshot.json +4407 -0
  70. package/migrations/meta/_journal.json +63 -0
  71. package/package.json +6 -5
package/README.md CHANGED
@@ -37,6 +37,17 @@ typed values; downstream code uses those values without
37
37
  re-casting. See `CONVENTIONS.md` for the project-wide rule against
38
38
  cast-at-callsite in DB consumers.
39
39
 
40
+ Connections created by `createDB` set PostgreSQL's `statement_timeout` to
41
+ 60,000 milliseconds. Override it with `statementTimeoutMs` in `DBConfig`
42
+ (a positive integer up to 2,147,483,647 milliseconds). The Hub and the
43
+ `bin/` database scripts read this override from `DB_STATEMENT_TIMEOUT_MS`.
44
+
45
+ PostgreSQL cancels statements that exceed the deadline, including time
46
+ waiting for locks. An uncaught timeout rolls back the enclosing transaction.
47
+ This bounds individual statements once they reach PostgreSQL; pool waits,
48
+ network stalls, and time between statements in an open transaction remain
49
+ outside that deadline. Migration clients use their own connection settings.
50
+
40
51
  ## Model catalog
41
52
 
42
53
  The `model`, `model_provider`, `model_offering`, and `model_pricing`
@@ -39,6 +39,13 @@ export declare function createApprovalStore(db: DBHandle): {
39
39
  * uses. Returns null when no row carries the id.
40
40
  */
41
41
  findById(id: string, tx?: DBExecutor): Promise<ParsedApproval | null>;
42
+ /**
43
+ * List a run's approvals, newest first, scoped by `tenantId` so one
44
+ * tenant's approvals never leak into another's view. Serves the run
45
+ * approvals list route; callers apply whatever status/scope predicate they
46
+ * need over the result.
47
+ */
48
+ listByRunId(tenantId: string, runId: string, tx?: DBExecutor): Promise<ParsedApproval[]>;
42
49
  /**
43
50
  * Conditionally resolve a pending approval. The `WHERE status = 'pending'`
44
51
  * guard makes resolution terminal at the database: the first caller to
@@ -55,6 +55,19 @@ export function createApprovalStore(db) {
55
55
  });
56
56
  return row === undefined ? null : parseApprovalRow(row);
57
57
  },
58
+ /**
59
+ * List a run's approvals, newest first, scoped by `tenantId` so one
60
+ * tenant's approvals never leak into another's view. Serves the run
61
+ * approvals list route; callers apply whatever status/scope predicate they
62
+ * need over the result.
63
+ */
64
+ async listByRunId(tenantId, runId, tx) {
65
+ const rows = await (tx ?? db).query.approval.findMany({
66
+ where: and(eq(approval.tenantId, tenantId), eq(approval.runId, runId)),
67
+ orderBy: (a, { desc }) => desc(a.createdAt),
68
+ });
69
+ return rows.map(parseApprovalRow);
70
+ },
58
71
  /**
59
72
  * Conditionally resolve a pending approval. The `WHERE status = 'pending'`
60
73
  * guard makes resolution terminal at the database: the first caller to
@@ -0,0 +1,9 @@
1
+ import type { DB } from "./client.js";
2
+ import type { PrincipalKeyStore } from "./principal-key-store.js";
3
+ export type BackfillPrincipalKeysReport = {
4
+ /** Principals that were keyless and received a fresh active key. */
5
+ keysGenerated: number;
6
+ /** Principals that already held an active key and were left untouched. */
7
+ alreadyKeyed: number;
8
+ };
9
+ export declare function backfillPrincipalKeys(db: DB["db"], principalKeyStore: PrincipalKeyStore): Promise<BackfillPrincipalKeysReport>;
@@ -0,0 +1,51 @@
1
+ // Backfill per-principal signing keys onto the principals that predate the
2
+ // feature.
3
+ //
4
+ // Minting a key lives only in the principal-creation owner, so every principal
5
+ // CREATED after that landed already has an active key. This one-shot pass keys
6
+ // the pre-existing population so the whole non-agent principal set matches the
7
+ // invariant "every non-agent principal has an active key".
8
+ //
9
+ // Scope is every `user` and `workflow` principal REGARDLESS of status, not just
10
+ // active ones. A status flip (e.g. an invite moving from `invited` to `active`)
11
+ // reuses the same principal row and does not re-mint, so an active-only pass
12
+ // would leave a pre-existing `invited`/`suspended` principal keyless and turn it
13
+ // into a keyless ACTIVE principal the moment it is activated. `agent` is a
14
+ // legacy/inert kind that is never keyed at creation either, so it is skipped to
15
+ // keep the populations consistent.
16
+ //
17
+ // Idempotent: a principal that already holds an active key is skipped, so the
18
+ // pass is safe to re-run or resume after a partial failure. Each principal is
19
+ // keyed in its own autocommit, so a partial failure leaves the earlier
20
+ // principals keyed. Safe to run against a LIVE hub -- nothing reads principal
21
+ // keys yet, the hub only mints for newly-created principals (disjoint from this
22
+ // pass's pre-existing set), and the `principal_key` active-key unique index
23
+ // blocks a double active key.
24
+ import { and, eq, inArray } from "drizzle-orm";
25
+ import { principal } from "./schema/principals.js";
26
+ import { principalKey } from "./schema/principal-keys.js";
27
+ const BACKFILLED_KINDS = ["user", "workflow"];
28
+ export async function backfillPrincipalKeys(db, principalKeyStore) {
29
+ // The left join is scoped to the active key, so `activeKeyId` is null exactly
30
+ // when the principal has no active key -- a principal holding only a retired
31
+ // key counts as keyless and is re-keyed. The active-key unique index makes at
32
+ // most one active row per principal, so each principal appears once.
33
+ const rows = await db
34
+ .select({ principalId: principal.id, activeKeyId: principalKey.id })
35
+ .from(principal)
36
+ .leftJoin(principalKey, and(eq(principalKey.principalId, principal.id), eq(principalKey.status, "active")))
37
+ .where(inArray(principal.kind, [...BACKFILLED_KINDS]));
38
+ const report = {
39
+ keysGenerated: 0,
40
+ alreadyKeyed: 0,
41
+ };
42
+ for (const row of rows) {
43
+ if (row.activeKeyId !== null) {
44
+ report.alreadyKeyed += 1;
45
+ continue;
46
+ }
47
+ await principalKeyStore.generate(row.principalId);
48
+ report.keysGenerated += 1;
49
+ }
50
+ return report;
51
+ }
package/dist/client.d.ts CHANGED
@@ -1,12 +1,37 @@
1
+ import type { ExtractTablesWithRelations } from "drizzle-orm";
2
+ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
1
3
  import * as schema from "./schema/index.js";
4
+ /**
5
+ * A drizzle database over the hub schema, typed against the driver-agnostic
6
+ * `PgDatabase` base rather than a single driver. `createDB` builds one over
7
+ * postgres-js; a caller can equally build one over pglite -- `drizzle(handle,
8
+ * { schema })` from `drizzle-orm/pglite`, with the schema re-exported from
9
+ * `@intx/db/schema` -- and pass it to the store factories. The required
10
+ * `$client` keeps a bare `PgTransaction` unassignable (a transaction carries no
11
+ * `$client`), so a parameter typed against this still rejects a tx where only a
12
+ * top-level database belongs.
13
+ */
14
+ export type AnyPgDatabase = PgDatabase<PgQueryResultHKT, typeof schema, ExtractTablesWithRelations<typeof schema>> & {
15
+ $client: unknown;
16
+ };
17
+ /**
18
+ * The hub database handle: the database itself, plus `transaction` and
19
+ * `close`. `createDB` returns the postgres-js instantiation, but the handle is
20
+ * typed against `AnyPgDatabase` so the stores accept any pg driver -- a
21
+ * caller-built pglite database included.
22
+ */
23
+ export interface DB {
24
+ db: AnyPgDatabase;
25
+ transaction: AnyPgDatabase["transaction"];
26
+ close: () => Promise<void>;
27
+ }
2
28
  export declare function createDB(raw: unknown): {
3
29
  db: import("drizzle-orm/postgres-js").PostgresJsDatabase<typeof schema> & {
4
30
  $client: import("postgres").Sql<{}>;
5
31
  };
6
- transaction: <T>(transaction: (tx: import("drizzle-orm/pg-core").PgTransaction<import("drizzle-orm/postgres-js").PostgresJsQueryResultHKT, typeof schema, import("drizzle-orm").ExtractTablesWithRelations<typeof schema>>) => Promise<T>, config?: import("drizzle-orm/pg-core").PgTransactionConfig) => Promise<T>;
32
+ transaction: <T>(transaction: (tx: import("drizzle-orm/pg-core").PgTransaction<import("drizzle-orm/postgres-js").PostgresJsQueryResultHKT, typeof schema, ExtractTablesWithRelations<typeof schema>>) => Promise<T>, config?: import("drizzle-orm/pg-core").PgTransactionConfig) => Promise<T>;
7
33
  close: () => Promise<void>;
8
34
  };
9
- export type DB = ReturnType<typeof createDB>;
10
35
  /**
11
36
  * A handle that can execute queries: either the top-level `db` or a
12
37
  * transaction handle passed into a `db.transaction` callback. Store methods
package/dist/client.js CHANGED
@@ -16,3 +16,9 @@ export function createDB(raw) {
16
16
  close: () => sql.end(),
17
17
  };
18
18
  }
19
+ // `createDB` returns the concrete postgres-js handle so its own callers keep
20
+ // the driver's precise result types (e.g. a typed `db.execute`). This assures
21
+ // at the definition site that the concrete handle still satisfies `DB`, the
22
+ // driver-agnostic contract the stores consume.
23
+ const _createDBReturnsDB = createDB;
24
+ void _createDBReturnsDB;
package/dist/config.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare const DBConfig: import("arktype/internal/variants/object.ts").Obj
6
6
  database: string;
7
7
  ssl?: boolean;
8
8
  max?: number;
9
+ statementTimeoutMs?: number;
9
10
  schema?: string;
10
11
  }, {}>;
11
12
  export type DBConfig = typeof DBConfig.infer;
package/dist/config.js CHANGED
@@ -7,6 +7,8 @@ export const DBConfig = type({
7
7
  database: "string",
8
8
  "ssl?": "boolean",
9
9
  "max?": "number.integer > 0",
10
+ // PostgreSQL statement deadline in milliseconds. Defaults to 60 seconds.
11
+ "statementTimeoutMs?": "0 < number.integer <= 2147483647",
10
12
  // Postgres schema name. When set, the connection's `search_path` is
11
13
  // pinned to this schema and migrations apply into it. This is the
12
14
  // mechanism the integration-test harness uses to give each spawned
@@ -13,6 +13,7 @@ export function createConnection(config) {
13
13
  ...(config.ssl !== undefined && { ssl: config.ssl }),
14
14
  connection: {
15
15
  TimeZone: "UTC",
16
+ statement_timeout: config.statementTimeoutMs ?? 60_000,
16
17
  ...(config.schema !== undefined && {
17
18
  // Pin the connection's search_path so unqualified table
18
19
  // references resolve to the caller's schema. The migration
@@ -1,5 +1,5 @@
1
1
  import type { CredentialBinding, CredentialCipher } from "@intx/types";
2
- import type { CredentialDelivery } from "@intx/types/sidecar";
2
+ import type { CredentialDelivery, CredentialMaterialEntry } from "@intx/types/sidecar";
3
3
  import type { DB } from "./client.js";
4
4
  /**
5
5
  * Thrown by `resolveCredentialRequirement` when more than one credential
@@ -11,6 +11,18 @@ import type { DB } from "./client.js";
11
11
  export declare class AmbiguousCredentialError extends Error {
12
12
  constructor(message: string);
13
13
  }
14
+ /**
15
+ * Thrown when a referenced credential is not a tenant-owned credential the given
16
+ * tenant can use -- it does not exist, is not reachable in the tenant's ancestor
17
+ * chain, or is principal-owned. A distinct type so a caller can map a client's
18
+ * bad credential reference to a 4xx (a launch-blocking configuration error),
19
+ * separate from the infrastructure faults the resolvers otherwise surface.
20
+ */
21
+ export declare class CredentialUnauthorizedError extends Error {
22
+ readonly credentialId: string;
23
+ readonly tenantId: string;
24
+ constructor(credentialId: string, tenantId: string);
25
+ }
14
26
  /**
15
27
  * Resolve a credential USABLE by the launching tenant purely through ownership:
16
28
  * it exists, is reachable in the tenant's ancestor chain, and is tenant-owned
@@ -32,8 +44,8 @@ export declare function resolveTenantOwnedCredentialById(db: DB["db"], tenantId:
32
44
  providerId: string;
33
45
  tenantId: string;
34
46
  status: "active" | "error" | "expired" | "revoked";
35
- description: string | null;
36
47
  principalId: string | null;
48
+ description: string | null;
37
49
  scopes: string[] | null;
38
50
  metadata: unknown;
39
51
  oauthClientId: string | null;
@@ -41,6 +53,43 @@ export declare function resolveTenantOwnedCredentialById(db: DB["db"], tenantId:
41
53
  secret: string;
42
54
  refreshSecret: string | null;
43
55
  } | null>;
56
+ /**
57
+ * Resolve a set of inference-source credentialIds into the credential material
58
+ * delivered on a run's unified credential-material cell. An inference source
59
+ * references its credential by id only; for each DISTINCT id this resolves the
60
+ * secret under the SAME tenant-ownership authority `buildSource` uses -- the
61
+ * credential must exist, be reachable in the tenant's ancestor chain, and be
62
+ * tenant-owned (`principalId IS NULL`) -- then decrypts it at the single point of
63
+ * use. `providerKey`/`origin` come from the credential's own provider row
64
+ * (`provider.plugin` / `provider.apiBaseUrl`), so the material describes the
65
+ * credential itself, independent of any caller-supplied source fields.
66
+ *
67
+ * Fails CLOSED, by throwing, on the first credentialId that is unresolved, not
68
+ * tenant-owned, references a missing provider, or whose provider has no API
69
+ * origin to pin -- a secret is never dropped nor delivered without an origin. The
70
+ * single resolver for every inference source credentialId -> material, shared by
71
+ * the deploy composition (top-level + inline body sources) and any other caller
72
+ * that must materialize inference credentials for the cell.
73
+ */
74
+ export declare function resolveInferenceMaterials(db: DB["db"], tenantId: string, credentialIds: Iterable<string>, credentialCipher: CredentialCipher): Promise<CredentialMaterialEntry[]>;
75
+ /**
76
+ * Re-resolve the CURRENT material for a set of already-authorized credentialIds,
77
+ * for the reconnect resync. Unlike `resolveInferenceMaterials`, a credential
78
+ * that no longer exists or is `revoked` is DROPPED (omitted) rather than
79
+ * throwing: the resync reflects credential lifecycle, so a dead id is simply
80
+ * absent from the reconciled delivery and the child evicts it.
81
+ *
82
+ * A credential that IS alive but whose material cannot be resolved (its provider
83
+ * vanished or has no API base URL) is NOT a lifecycle removal, so it throws --
84
+ * the caller aborts the whole reconcile rather than delivering a partial set
85
+ * paired with a spurious revoke. A rotated secret is picked up because the row's
86
+ * current secret is decrypted here.
87
+ *
88
+ * Ids are looked up by primary key (globally unique) and come from the
89
+ * deployment's own persisted delivery, so no re-authorization is performed --
90
+ * this reflects lifecycle only.
91
+ */
92
+ export declare function reresolveCurrentMaterials(db: DB["db"], credentialIds: Iterable<string>, credentialCipher: CredentialCipher): Promise<CredentialMaterialEntry[]>;
44
93
  /**
45
94
  * Resolves a provider by name, walking up the tenant hierarchy.
46
95
  * Returns the first match (child shadows parent).
@@ -89,8 +138,8 @@ export declare function resolveCredentialByName(db: DB["db"], tenantId: string,
89
138
  providerId: string;
90
139
  tenantId: string;
91
140
  status: "active" | "error" | "expired" | "revoked";
92
- description: string | null;
93
141
  principalId: string | null;
142
+ description: string | null;
94
143
  scopes: string[] | null;
95
144
  metadata: unknown;
96
145
  oauthClientId: string | null;
@@ -111,8 +160,8 @@ export declare function resolveCredentialById(db: DB["db"], tenantId: string, cr
111
160
  providerId: string;
112
161
  tenantId: string;
113
162
  status: "active" | "error" | "expired" | "revoked";
114
- description: string | null;
115
163
  principalId: string | null;
164
+ description: string | null;
116
165
  scopes: string[] | null;
117
166
  metadata: unknown;
118
167
  oauthClientId: string | null;
@@ -142,8 +191,8 @@ export declare function resolveCredentialRequirement(db: DB["db"], tenantId: str
142
191
  providerId: string;
143
192
  tenantId: string;
144
193
  status: "active" | "error" | "expired" | "revoked";
145
- description: string | null;
146
194
  principalId: string | null;
195
+ description: string | null;
147
196
  scopes: string[] | null;
148
197
  metadata: unknown;
149
198
  oauthClientId: string | null;
@@ -18,6 +18,23 @@ export class AmbiguousCredentialError extends Error {
18
18
  this.name = "AmbiguousCredentialError";
19
19
  }
20
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
+ }
21
38
  /**
22
39
  * Resolve a credential USABLE by the launching tenant purely through ownership:
23
40
  * it exists, is reachable in the tenant's ancestor chain, and is tenant-owned
@@ -34,6 +51,98 @@ export async function resolveTenantOwnedCredentialById(db, tenantId, credentialI
34
51
  const row = await resolveCredentialById(db, tenantId, credentialId);
35
52
  return row !== null && row.principalId === null ? row : null;
36
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
+ }
37
146
  /**
38
147
  * Resolves a provider by name, walking up the tenant hierarchy.
39
148
  * Returns the first match (child shadows parent).
package/dist/index.d.ts CHANGED
@@ -1,22 +1,28 @@
1
- export { createDB, type DB, type DBExecutor } 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
5
  export { rekeyCredentialSecrets, type RekeyReport, } from "./rekey-credential-secrets.js";
6
+ export { backfillPrincipalKeys, type BackfillPrincipalKeysReport, } from "./backfill-principal-keys.js";
6
7
  export { createGrantStore } from "./grant-store.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";
7
12
  export { createApprovalStore, type ApprovalStore, type ResolveApprovalArgs, } from "./approval-store.js";
8
13
  export { createSignalCorrelationStore, type SignalCorrelationStore, } from "./signal-correlation-store.js";
9
14
  export { createWorkflowRunStore, type WorkflowRunStore, } from "./workflow-run-store.js";
10
15
  export { createWorkflowRunLaunchSpecStore, type WorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
11
16
  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";
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";
13
19
  export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, type WorkflowDefinitionRollbackResult, type WorkflowDefinitionSelector, } from "./workflow-definition-store.js";
14
- export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
20
+ export { getAncestorChain, getDescendantTenants, resolveTenantSidecarCapabilityPolicies, } from "./tenant-hierarchy.js";
15
21
  export { resolveActivePrice, type ModelPricingRow } from "./pricing.js";
16
- export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, AmbiguousCredentialError, buildCredentialDelivery, } from "./credential-resolution.js";
22
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, resolveInferenceMaterials, reresolveCurrentMaterials, AmbiguousCredentialError, CredentialUnauthorizedError, buildCredentialDelivery, } from "./credential-resolution.js";
17
23
  export type { BuildCredentialDeliveryResult, CredentialDeliveryFailure, } from "./credential-resolution.js";
18
24
  export { resolveAssetByName, resolveAssetById, listAssetsForTenant, type AssetRow, type AssetWithOrigin, } from "./asset-resolution.js";
19
25
  export { listVisibleModels, listVisibleProviders, listVisibleOfferings, type ModelRow, type ModelProviderRow, type ModelOfferingRow, type Origin, type VisibleModel, type VisibleProvider, type ResolvedOffering, } from "./catalog-resolution.js";
20
26
  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";
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";
22
28
  export * as schema from "./schema/index.js";
package/dist/index.js CHANGED
@@ -1,20 +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
4
  export { rekeyCredentialSecrets, } from "./rekey-credential-secrets.js";
5
+ export { backfillPrincipalKeys, } from "./backfill-principal-keys.js";
5
6
  export { createGrantStore } from "./grant-store.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";
6
11
  export { createApprovalStore, } from "./approval-store.js";
7
12
  export { createSignalCorrelationStore, } from "./signal-correlation-store.js";
8
13
  export { createWorkflowRunStore, } from "./workflow-run-store.js";
9
14
  export { createWorkflowRunLaunchSpecStore, } from "./workflow-run-launch-spec-store.js";
10
15
  export { createWorkflowRunDispatchStore, WorkflowRunDispatchPayloadConflictError, } from "./workflow-run-dispatch-store.js";
11
16
  export { createSidecarAllocationStore, } from "./sidecar-allocation-store.js";
17
+ export { createWorkflowProbeStore, } from "./workflow-probe-store.js";
12
18
  export { createWorkflowDefinitionStore, loadFrozenGrantSnapshot, resolveDefinitionIdForAsset, } from "./workflow-definition-store.js";
13
- export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
19
+ export { getAncestorChain, getDescendantTenants, resolveTenantSidecarCapabilityPolicies, } from "./tenant-hierarchy.js";
14
20
  export { resolveActivePrice } from "./pricing.js";
15
- export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, AmbiguousCredentialError, buildCredentialDelivery, } from "./credential-resolution.js";
21
+ export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, resolveInferenceMaterials, reresolveCurrentMaterials, AmbiguousCredentialError, CredentialUnauthorizedError, buildCredentialDelivery, } from "./credential-resolution.js";
16
22
  export { resolveAssetByName, resolveAssetById, listAssetsForTenant, } from "./asset-resolution.js";
17
23
  export { listVisibleModels, listVisibleProviders, listVisibleOfferings, } from "./catalog-resolution.js";
18
24
  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";
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";
20
26
  export * as schema from "./schema/index.js";
@@ -1,5 +1,6 @@
1
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.
@@ -34,6 +35,7 @@ export type SourceSkip = {
34
35
  export type CatalogSourceResolution = {
35
36
  ok: true;
36
37
  sources: InferenceSource[];
38
+ materials: CredentialMaterialEntry[];
37
39
  } | {
38
40
  ok: false;
39
41
  reason: "no_requirements";
@@ -58,7 +60,7 @@ export type OfferingSourceResolution = {
58
60
  * secrets out of persisted launch specs and rechecks tenant visibility and
59
61
  * credential ownership on every launch.
60
62
  */
61
- export declare function resolveSourcesByOfferingIds(db: DB["db"], tenantId: string, offeringIds: readonly string[], credentialCipher?: CredentialCipher): Promise<OfferingSourceResolution>;
63
+ export declare function resolveSourcesByOfferingIds(db: DB["db"], tenantId: string, offeringIds: readonly string[], credentialCipher: CredentialCipher): Promise<OfferingSourceResolution>;
62
64
  /**
63
65
  * Resolves an agent's model requirements against the tenant catalog into an
64
66
  * ordered `InferenceSource[]` for the harness.
@@ -76,9 +78,8 @@ export declare function resolveSourcesByOfferingIds(db: DB["db"], tenantId: stri
76
78
  * otherwise the offering is skipped (`credential_unauthorized`) and its secret
77
79
  * is withheld.
78
80
  */
79
- 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?: {
80
82
  invokerPreferences?: Record<string, ProviderPreference>;
81
- credentialCipher?: CredentialCipher;
82
83
  }): Promise<CatalogSourceResolution>;
83
84
  /**
84
85
  * Resolve an agent's model requirements to the credential-free
@@ -97,7 +98,7 @@ export declare function resolveModelSources(db: DB["db"], tenantId: string, requ
97
98
  * an ambiguous preference list whose lost `offering.id` distinctions cannot be
98
99
  * recovered after the agent's columns are dropped.
99
100
  */
100
- export declare function resolveInferencePreferences(db: DB["db"], tenantId: string, requirements: ModelRequirement[]): Promise<{
101
+ export declare function resolveInferencePreferences(db: DB["db"], tenantId: string, requirements: ModelRequirement[], credentialCipher: CredentialCipher): Promise<{
101
102
  provider: string;
102
103
  model: string;
103
104
  }[]>;
@@ -112,4 +113,4 @@ export declare function resolveInferencePreferences(db: DB["db"], tenantId: stri
112
113
  export declare function resolveInstanceModelSources(db: DB["db"], tenantId: string, instance: {
113
114
  definitionId: string;
114
115
  modelPreferences: unknown;
115
- }, credentialCipher?: CredentialCipher): Promise<CatalogSourceResolution>;
116
+ }, credentialCipher: CredentialCipher): Promise<CatalogSourceResolution>;