@intx/db 0.2.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.
- package/README.md +2 -2
- package/dist/approval-store.d.ts +52 -0
- package/dist/approval-store.js +79 -0
- package/dist/asset-resolution.d.ts +2 -2
- package/dist/client.d.ts +10 -0
- package/dist/connection.js +11 -8
- package/dist/credential-resolution.d.ts +130 -18
- package/dist/credential-resolution.js +142 -3
- package/dist/grant-store.js +35 -13
- package/dist/index.d.ts +13 -4
- package/dist/index.js +11 -3
- package/dist/model-source-resolution.d.ts +52 -6
- package/dist/model-source-resolution.js +145 -19
- package/dist/parse-row.d.ts +212 -32
- package/dist/parse-row.js +109 -18
- package/dist/rekey-credential-secrets.d.ts +13 -0
- package/dist/rekey-credential-secrets.js +61 -0
- package/dist/schema/approvals.d.ts +245 -0
- package/dist/schema/approvals.js +52 -0
- package/dist/schema/catalog.d.ts +17 -0
- package/dist/schema/catalog.js +7 -1
- package/dist/schema/credentials.d.ts +1 -1
- package/dist/schema/git-tokens.js +1 -1
- package/dist/schema/grants.js +8 -2
- package/dist/schema/index.d.ts +8 -4
- package/dist/schema/index.js +8 -4
- package/dist/schema/messages.d.ts +5 -5
- package/dist/schema/messages.js +20 -9
- package/dist/schema/offerings.js +8 -2
- package/dist/schema/principals.d.ts +2 -2
- package/dist/schema/principals.js +7 -5
- package/dist/schema/providers.d.ts +17 -0
- package/dist/schema/providers.js +5 -0
- package/dist/schema/roles.js +6 -2
- package/dist/schema/session-assets.d.ts +1 -38
- package/dist/schema/session-assets.js +36 -19
- package/dist/schema/sessions.js +7 -2
- package/dist/schema/sidecar-allocation.d.ts +406 -0
- package/dist/schema/sidecar-allocation.js +57 -0
- package/dist/schema/sidecar.d.ts +19 -2
- package/dist/schema/sidecar.js +13 -3
- package/dist/schema/signal-correlations.d.ts +177 -0
- package/dist/schema/signal-correlations.js +31 -0
- package/dist/schema/wallets.d.ts +3 -3
- package/dist/schema/wallets.js +4 -2
- package/dist/schema/{agents.d.ts → workflow-definitions.d.ts} +79 -113
- package/dist/schema/workflow-definitions.js +97 -0
- package/dist/schema/workflow-run-dispatch.d.ts +328 -0
- package/dist/schema/workflow-run-dispatch.js +56 -0
- package/dist/schema/{workflow-deployments.d.ts → workflow-run-execution.d.ts} +43 -46
- package/dist/schema/workflow-run-execution.js +48 -0
- package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
- package/dist/schema/workflow-run-launch-spec.js +26 -0
- package/dist/schema/{instances.d.ts → workflow-run.d.ts} +39 -71
- package/dist/schema/workflow-run.js +102 -0
- package/dist/sidecar-allocation-store.d.ts +171 -0
- package/dist/sidecar-allocation-store.js +512 -0
- package/dist/signal-correlation-store.d.ts +36 -0
- package/dist/signal-correlation-store.js +63 -0
- package/dist/tenant-hierarchy.d.ts +2 -1
- package/dist/tenant-hierarchy.js +10 -5
- package/dist/workflow-definition-store.d.ts +55 -0
- package/dist/workflow-definition-store.js +93 -0
- package/dist/workflow-run-dispatch-store.d.ts +80 -0
- package/dist/workflow-run-dispatch-store.js +298 -0
- package/dist/workflow-run-launch-spec-store.d.ts +13 -0
- package/dist/workflow-run-launch-spec-store.js +30 -0
- package/dist/workflow-run-store.d.ts +54 -0
- package/dist/workflow-run-store.js +85 -0
- package/migrations/0037_credential_use_backfill.sql +39 -0
- package/migrations/0038_chilly_blacklash.sql +37 -0
- package/migrations/0039_quick_carnage.sql +2 -0
- package/migrations/0040_reshape_approval_origin.sql +24 -0
- package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
- package/migrations/0042_youthful_mantis.sql +2 -0
- package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
- package/migrations/0044_model_offering_quirks.sql +1 -0
- package/migrations/0045_create_workflow_run.sql +13 -0
- package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
- package/migrations/0047_create_workflow_definition.sql +30 -0
- package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
- package/migrations/0049_curly_omega_flight.sql +2 -0
- package/migrations/0050_late_crystal.sql +10 -0
- package/migrations/0051_funny_madelyne_pryor.sql +1 -0
- package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
- package/migrations/0053_session_mail_session_id_index.sql +1 -0
- package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
- package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
- package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
- package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
- package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
- package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
- package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
- package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
- package/migrations/0062_drop_agent_asset_table.sql +11 -0
- package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
- package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
- package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
- package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
- package/migrations/0067_add_workflow_definition_kind.sql +10 -0
- package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
- package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
- package/migrations/0070_flashy_proteus.sql +13 -0
- package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
- package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
- package/migrations/0074_add_provider_api_base_url.sql +1 -0
- package/migrations/0075_workflow_run_launch_spec.sql +17 -0
- package/migrations/0076_sidecar_allocation.sql +40 -0
- package/migrations/0077_workflow_run_dispatch.sql +29 -0
- package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
- package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
- package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
- package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
- package/migrations/0082_blue_black_queen.sql +1 -0
- package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
- package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
- package/migrations/meta/0037_snapshot.json +3237 -0
- package/migrations/meta/0038_snapshot.json +3470 -0
- package/migrations/meta/0039_snapshot.json +3470 -0
- package/migrations/meta/0040_snapshot.json +3489 -0
- package/migrations/meta/0041_snapshot.json +3489 -0
- package/migrations/meta/0042_snapshot.json +3489 -0
- package/migrations/meta/0043_snapshot.json +3498 -0
- package/migrations/meta/0044_snapshot.json +3504 -0
- package/migrations/meta/0045_snapshot.json +3589 -0
- package/migrations/meta/0046_snapshot.json +3607 -0
- package/migrations/meta/0047_snapshot.json +3832 -0
- package/migrations/meta/0048_snapshot.json +3853 -0
- package/migrations/meta/0049_snapshot.json +3854 -0
- package/migrations/meta/0050_snapshot.json +3924 -0
- package/migrations/meta/0051_snapshot.json +3940 -0
- package/migrations/meta/0052_snapshot.json +3931 -0
- package/migrations/meta/0053_snapshot.json +3952 -0
- package/migrations/meta/0054_snapshot.json +3952 -0
- package/migrations/meta/0055_snapshot.json +3952 -0
- package/migrations/meta/0056_snapshot.json +3952 -0
- package/migrations/meta/0057_snapshot.json +3839 -0
- package/migrations/meta/0058_snapshot.json +3839 -0
- package/migrations/meta/0059_snapshot.json +3839 -0
- package/migrations/meta/0060_snapshot.json +3830 -0
- package/migrations/meta/0061_snapshot.json +3821 -0
- package/migrations/meta/0062_snapshot.json +3723 -0
- package/migrations/meta/0063_snapshot.json +3723 -0
- package/migrations/meta/0064_snapshot.json +3723 -0
- package/migrations/meta/0065_snapshot.json +3729 -0
- package/migrations/meta/0066_snapshot.json +3729 -0
- package/migrations/meta/0067_snapshot.json +3736 -0
- package/migrations/meta/0068_snapshot.json +3347 -0
- package/migrations/meta/0069_snapshot.json +3340 -0
- package/migrations/meta/0070_snapshot.json +3444 -0
- package/migrations/meta/0072_snapshot.json +3450 -0
- package/migrations/meta/0073_snapshot.json +3455 -0
- package/migrations/meta/0074_snapshot.json +3461 -0
- package/migrations/meta/0075_snapshot.json +3567 -0
- package/migrations/meta/0076_snapshot.json +3851 -0
- package/migrations/meta/0077_snapshot.json +4067 -0
- package/migrations/meta/0078_snapshot.json +4078 -0
- package/migrations/meta/0079_snapshot.json +4078 -0
- package/migrations/meta/0080_snapshot.json +4078 -0
- package/migrations/meta/0081_snapshot.json +4096 -0
- package/migrations/meta/0082_snapshot.json +4102 -0
- package/migrations/meta/0083_snapshot.json +4096 -0
- package/migrations/meta/0084_snapshot.json +4096 -0
- package/migrations/meta/_journal.json +329 -0
- package/package.json +6 -3
- package/dist/schema/agent-assets.d.ts +0 -109
- package/dist/schema/agent-assets.js +0 -15
- package/dist/schema/agents.js +0 -57
- package/dist/schema/instances.js +0 -39
- package/dist/schema/workflow-deployments.js +0 -37
package/dist/index.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
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";
|
|
4
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";
|
|
5
13
|
export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
|
|
6
14
|
export { resolveActivePrice } from "./pricing.js";
|
|
7
|
-
export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, } from "./credential-resolution.js";
|
|
15
|
+
export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, resolveTenantOwnedCredentialById, AmbiguousCredentialError, buildCredentialDelivery, } from "./credential-resolution.js";
|
|
8
16
|
export { resolveAssetByName, resolveAssetById, listAssetsForTenant, } from "./asset-resolution.js";
|
|
9
17
|
export { listVisibleModels, listVisibleProviders, listVisibleOfferings, } from "./catalog-resolution.js";
|
|
10
|
-
export { resolveModelSources, resolveInstanceModelSources, } from "./model-source-resolution.js";
|
|
11
|
-
export {
|
|
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";
|
|
12
20
|
export * as schema from "./schema/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
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
3
|
import type { DB } from "./client.js";
|
|
4
4
|
/**
|
|
@@ -12,6 +12,9 @@ export type SourceSkip = {
|
|
|
12
12
|
} | {
|
|
13
13
|
reason: "credential_unresolved";
|
|
14
14
|
provider: string;
|
|
15
|
+
} | {
|
|
16
|
+
reason: "credential_unauthorized";
|
|
17
|
+
provider: string;
|
|
15
18
|
} | {
|
|
16
19
|
reason: "provider_misconfigured";
|
|
17
20
|
provider: string;
|
|
@@ -40,6 +43,22 @@ export type CatalogSourceResolution = {
|
|
|
40
43
|
model: string;
|
|
41
44
|
skips: SourceSkip[];
|
|
42
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>;
|
|
43
62
|
/**
|
|
44
63
|
* Resolves an agent's model requirements against the tenant catalog into an
|
|
45
64
|
* ordered `InferenceSource[]` for the harness.
|
|
@@ -51,19 +70,46 @@ export type CatalogSourceResolution = {
|
|
|
51
70
|
* offering is resolved to a credential-backed source; offerings that cannot
|
|
52
71
|
* produce one are skipped. A required model that yields no source makes the
|
|
53
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.
|
|
54
78
|
*/
|
|
55
79
|
export declare function resolveModelSources(db: DB["db"], tenantId: string, requirements: ModelRequirement[], opts?: {
|
|
56
80
|
invokerPreferences?: Record<string, ProviderPreference>;
|
|
81
|
+
credentialCipher?: CredentialCipher;
|
|
57
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
|
+
}[]>;
|
|
58
104
|
/**
|
|
59
105
|
* Resolves the ordered sources for a running instance from persisted state:
|
|
60
|
-
* the
|
|
61
|
-
* preferences stored on the instance row.
|
|
62
|
-
*
|
|
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
|
|
63
109
|
* source list is a pure function of persisted state — re-resolution
|
|
64
110
|
* reproduces the launch ordering, including the invoker's reorder/restrict.
|
|
65
111
|
*/
|
|
66
112
|
export declare function resolveInstanceModelSources(db: DB["db"], tenantId: string, instance: {
|
|
67
|
-
|
|
113
|
+
definitionId: string;
|
|
68
114
|
modelPreferences: unknown;
|
|
69
|
-
}): Promise<CatalogSourceResolution>;
|
|
115
|
+
}, credentialCipher?: CredentialCipher): Promise<CatalogSourceResolution>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { and, eq } from "drizzle-orm";
|
|
2
|
-
import {
|
|
2
|
+
import { createNoopCredentialCipher } from "@intx/crypto";
|
|
3
|
+
import { InvokerModelPreferences, ModelRequirements, credentialAad, } from "@intx/types";
|
|
3
4
|
import { listVisibleOfferings, } from "./catalog-resolution.js";
|
|
4
5
|
import { resolveCredentialById } from "./credential-resolution.js";
|
|
5
|
-
import {
|
|
6
|
+
import { parseModelOfferingRow } from "./parse-row.js";
|
|
7
|
+
import { workflowDefinition } from "./schema/workflow-definitions.js";
|
|
6
8
|
function byPriority(a, b) {
|
|
7
9
|
if (a.offering.priority !== b.offering.priority) {
|
|
8
10
|
return a.offering.priority - b.offering.priority;
|
|
@@ -41,7 +43,7 @@ function applyPreference(candidates, preference) {
|
|
|
41
43
|
// the rest as fallback after the named providers.
|
|
42
44
|
return preference.mode === "pin" ? named : [...named, ...rest];
|
|
43
45
|
}
|
|
44
|
-
async function buildSource(db, tenantId, resolved) {
|
|
46
|
+
async function buildSource(db, tenantId, resolved, credentialCipher) {
|
|
45
47
|
const { provider, model, offering } = resolved;
|
|
46
48
|
if (provider.credentialId === null) {
|
|
47
49
|
// No credential reference. A wallet-backed provider is a valid catalog
|
|
@@ -60,7 +62,7 @@ async function buildSource(db, tenantId, resolved) {
|
|
|
60
62
|
}
|
|
61
63
|
// Resolve the secret through the tenant-scoped credential resolver so a
|
|
62
64
|
// provider row referencing a credential outside the tenant's ancestor
|
|
63
|
-
// chain cannot leak that secret (
|
|
65
|
+
// chain cannot leak that secret (the chain is the authority).
|
|
64
66
|
const credential = await resolveCredentialById(db, tenantId, provider.credentialId);
|
|
65
67
|
if (credential === null) {
|
|
66
68
|
return {
|
|
@@ -68,18 +70,74 @@ async function buildSource(db, tenantId, resolved) {
|
|
|
68
70
|
skip: { reason: "credential_unresolved", provider: provider.name },
|
|
69
71
|
};
|
|
70
72
|
}
|
|
73
|
+
// Authority to use a credential through a catalog provider is ownership
|
|
74
|
+
// within the tenant hierarchy -- the same rule the tool-binding path resolves
|
|
75
|
+
// (`source: "tenant"`). `resolveCredentialById` above already proved the
|
|
76
|
+
// credential is reachable in this tenant's ancestor chain, so a tenant-owned
|
|
77
|
+
// credential (`principalId IS NULL`) is authorized here by ownership alone,
|
|
78
|
+
// with no personal grant and no role grant consulted. This runs live on every
|
|
79
|
+
// launch and rotation, so a catalog edit that adds an eligible credential is
|
|
80
|
+
// picked up without re-materializing any grant. A principal-owned credential
|
|
81
|
+
// is not usable through a shared catalog provider; fail closed.
|
|
82
|
+
if (credential.principalId !== null) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
skip: { reason: "credential_unauthorized", provider: provider.name },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Validate the row once at this DB-to-runtime boundary. This narrows the
|
|
89
|
+
// jsonb `quirks` from `unknown` to a `Record | null` and checks
|
|
90
|
+
// `capabilities` against the curated enum. `quirks` is spread in only when
|
|
91
|
+
// non-null so a source with no accommodations omits the key entirely
|
|
92
|
+
// (InferenceSource.quirks is present-or-absent, never null). The capability
|
|
93
|
+
// filter in resolveModelSources still reads the raw row's `capabilities`
|
|
94
|
+
// for its `.includes` check; that read-only comparison cannot be corrupted
|
|
95
|
+
// into a wrong routing decision, so it is left as-is.
|
|
96
|
+
const parsed = parseModelOfferingRow(offering);
|
|
71
97
|
return {
|
|
72
98
|
ok: true,
|
|
73
99
|
source: {
|
|
74
100
|
id: offering.id,
|
|
75
101
|
provider: provider.plugin,
|
|
76
102
|
baseURL: provider.baseURL,
|
|
77
|
-
|
|
103
|
+
// Decrypt the stored secret at the one point it is used. Strict: a
|
|
104
|
+
// non-ciphertext value (a row not yet re-keyed, or a write that failed to
|
|
105
|
+
// encrypt) throws rather than delivering a bad key -- fail closed.
|
|
106
|
+
apiKey: await credentialCipher.decrypt(credential.secret, credentialAad(credential.id, "secret")),
|
|
78
107
|
model: model.canonicalName,
|
|
79
|
-
capabilities:
|
|
108
|
+
capabilities: parsed.capabilities,
|
|
109
|
+
...(parsed.quirks !== null ? { quirks: parsed.quirks } : {}),
|
|
80
110
|
},
|
|
81
111
|
};
|
|
82
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Rebuild an exact, ordered source chain from durable catalog offering ids.
|
|
115
|
+
* This is the recovery counterpart to launch-time source selection: it keeps
|
|
116
|
+
* secrets out of persisted launch specs and rechecks tenant visibility and
|
|
117
|
+
* credential ownership on every launch.
|
|
118
|
+
*/
|
|
119
|
+
export async function resolveSourcesByOfferingIds(db, tenantId, offeringIds, credentialCipher = createNoopCredentialCipher()) {
|
|
120
|
+
const visible = await listVisibleOfferings(db, tenantId);
|
|
121
|
+
const byId = new Map(visible.map((entry) => [entry.offering.id, entry]));
|
|
122
|
+
const sources = [];
|
|
123
|
+
for (const offeringId of offeringIds) {
|
|
124
|
+
const offering = byId.get(offeringId);
|
|
125
|
+
if (offering === undefined) {
|
|
126
|
+
return { ok: false, reason: "offering_unavailable", offeringId };
|
|
127
|
+
}
|
|
128
|
+
const built = await buildSource(db, tenantId, offering, credentialCipher);
|
|
129
|
+
if (!built.ok) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: "offering_unavailable",
|
|
133
|
+
offeringId,
|
|
134
|
+
skip: built.skip,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
sources.push(built.source);
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, sources };
|
|
140
|
+
}
|
|
83
141
|
/**
|
|
84
142
|
* Resolves an agent's model requirements against the tenant catalog into an
|
|
85
143
|
* ordered `InferenceSource[]` for the harness.
|
|
@@ -91,11 +149,17 @@ async function buildSource(db, tenantId, resolved) {
|
|
|
91
149
|
* offering is resolved to a credential-backed source; offerings that cannot
|
|
92
150
|
* produce one are skipped. A required model that yields no source makes the
|
|
93
151
|
* agent unlaunchable.
|
|
152
|
+
*
|
|
153
|
+
* A credential-backed source is emitted only when the launching tenant owns
|
|
154
|
+
* the referenced credential within its hierarchy (ownership is the authority);
|
|
155
|
+
* otherwise the offering is skipped (`credential_unauthorized`) and its secret
|
|
156
|
+
* is withheld.
|
|
94
157
|
*/
|
|
95
158
|
export async function resolveModelSources(db, tenantId, requirements, opts) {
|
|
96
159
|
if (requirements.length === 0) {
|
|
97
160
|
return { ok: false, reason: "no_requirements" };
|
|
98
161
|
}
|
|
162
|
+
const credentialCipher = opts?.credentialCipher ?? createNoopCredentialCipher();
|
|
99
163
|
const visible = await listVisibleOfferings(db, tenantId);
|
|
100
164
|
const sources = [];
|
|
101
165
|
for (const requirement of requirements) {
|
|
@@ -111,7 +175,7 @@ export async function resolveModelSources(db, tenantId, requirements, opts) {
|
|
|
111
175
|
const skips = [];
|
|
112
176
|
const modelSources = [];
|
|
113
177
|
for (const candidate of candidates) {
|
|
114
|
-
const built = await buildSource(db, tenantId, candidate);
|
|
178
|
+
const built = await buildSource(db, tenantId, candidate, credentialCipher);
|
|
115
179
|
if (built.ok) {
|
|
116
180
|
modelSources.push(built.source);
|
|
117
181
|
}
|
|
@@ -131,26 +195,87 @@ export async function resolveModelSources(db, tenantId, requirements, opts) {
|
|
|
131
195
|
}
|
|
132
196
|
return { ok: true, sources };
|
|
133
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Resolve an agent's model requirements to the credential-free
|
|
200
|
+
* `{ provider, model }` inference preferences a folded workflow definition
|
|
201
|
+
* carries on its step agent. Reuses `resolveModelSources` -- the same catalog
|
|
202
|
+
* + creator-grant resolution the instance launch path runs -- and projects its
|
|
203
|
+
* ordered sources to their `{ provider (plugin), model }` identity, dropping
|
|
204
|
+
* the credentials: a definition's inference preference is hash-only, and the
|
|
205
|
+
* credential-bearing sources are supplied per deploy on the workflow path.
|
|
206
|
+
*
|
|
207
|
+
* A requirement set that resolves to no source is a hard failure. A folded
|
|
208
|
+
* agent whose model is unresolvable is undeployable, so this raises rather
|
|
209
|
+
* than synthesizing an empty preference list that would silently strip the
|
|
210
|
+
* agent's inference. It also raises when the resolved sources are not
|
|
211
|
+
* injective on `(provider, model)` -- the projection would otherwise freeze
|
|
212
|
+
* an ambiguous preference list whose lost `offering.id` distinctions cannot be
|
|
213
|
+
* recovered after the agent's columns are dropped.
|
|
214
|
+
*/
|
|
215
|
+
export async function resolveInferencePreferences(db, tenantId, requirements) {
|
|
216
|
+
const resolution = await resolveModelSources(db, tenantId, requirements);
|
|
217
|
+
if (!resolution.ok) {
|
|
218
|
+
throw new Error(`cannot resolve inference preferences for the folded definition: ${resolution.reason}`);
|
|
219
|
+
}
|
|
220
|
+
// The projection to `{ provider (plugin), model }` drops the `offering.id`
|
|
221
|
+
// that keys each resolved source. Two distinct offerings can share a
|
|
222
|
+
// provider plugin and canonical model -- the catalog permits two
|
|
223
|
+
// `model_provider` rows on the same plugin (uniqueness is on the provider
|
|
224
|
+
// name), each offering the same model -- so the projection is only
|
|
225
|
+
// well-defined when the resolved sources are injective on `(provider,
|
|
226
|
+
// model)`. If they collapse, the folded definition's inference preferences
|
|
227
|
+
// cannot distinguish which offering each entry meant, and once the agent's
|
|
228
|
+
// columns are dropped that distinction is unrecoverable. Refuse to
|
|
229
|
+
// synthesize an ambiguous preference list: fail loud so the collapse
|
|
230
|
+
// surfaces in the fold/materialize manifest rather than freezing silently.
|
|
231
|
+
const preferences = resolution.sources.map((source) => ({
|
|
232
|
+
provider: source.provider,
|
|
233
|
+
model: source.model,
|
|
234
|
+
}));
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
for (const preference of preferences) {
|
|
237
|
+
// Join on a NUL: it cannot appear in a provider plugin or canonical model
|
|
238
|
+
// name, so the composite key is collision-free. Written as an explicit
|
|
239
|
+
// unicode escape rather than an invisible raw control byte in the source.
|
|
240
|
+
const key = `${preference.provider}\u0000${preference.model}`;
|
|
241
|
+
if (seen.has(key)) {
|
|
242
|
+
throw new Error(`cannot resolve inference preferences for the folded definition: ` +
|
|
243
|
+
`the model resolution is not injective on (provider, model) -- ` +
|
|
244
|
+
`two offerings collapse to (${preference.provider}, ${preference.model}); ` +
|
|
245
|
+
`a folded agent requires a distinguishable inference preference per source`);
|
|
246
|
+
}
|
|
247
|
+
seen.add(key);
|
|
248
|
+
}
|
|
249
|
+
return preferences;
|
|
250
|
+
}
|
|
134
251
|
/**
|
|
135
252
|
* Resolves the ordered sources for a running instance from persisted state:
|
|
136
|
-
* the
|
|
137
|
-
* preferences stored on the instance row.
|
|
138
|
-
*
|
|
253
|
+
* the definition's model requirements and the invoker's launch-time
|
|
254
|
+
* preferences stored on the instance row. The credential-rotation and
|
|
255
|
+
* catalog-edit source push resolves through this, so a running instance's
|
|
139
256
|
* source list is a pure function of persisted state — re-resolution
|
|
140
257
|
* reproduces the launch ordering, including the invoker's reorder/restrict.
|
|
141
258
|
*/
|
|
142
|
-
export async function resolveInstanceModelSources(db, tenantId, instance) {
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
259
|
+
export async function resolveInstanceModelSources(db, tenantId, instance, credentialCipher) {
|
|
260
|
+
// Resolve from the run's own definition by primary key. This is the SAME row
|
|
261
|
+
// the launch resolves its requirements from, so a rotation or catalog edit
|
|
262
|
+
// reproduces the launch's model resolution rather than drifting. Scope to the
|
|
263
|
+
// resolving tenant, matching the launch route: a definition in another tenant
|
|
264
|
+
// resolves to nothing rather than contributing another tenant's models. A
|
|
265
|
+
// definition with no creator principal cannot authorize a credential-backed
|
|
266
|
+
// source, so it fails closed like a missing definition.
|
|
267
|
+
const definitionRow = await db.query.workflowDefinition.findFirst({
|
|
268
|
+
where: and(eq(workflowDefinition.id, instance.definitionId), eq(workflowDefinition.tenantId, tenantId)),
|
|
148
269
|
});
|
|
149
|
-
|
|
270
|
+
// A credential-backed source is authorized by tenant ownership of the
|
|
271
|
+
// credential (walked in `buildSource`), not by the definition's creator, so a
|
|
272
|
+
// definition with no creator can still launch a tenant-owned inference
|
|
273
|
+
// source. Only a missing definition (or one in another tenant) fails closed.
|
|
274
|
+
if (definitionRow === undefined) {
|
|
150
275
|
return { ok: false, reason: "no_requirements" };
|
|
151
276
|
}
|
|
152
|
-
const requirements =
|
|
153
|
-
? ModelRequirements.assert(
|
|
277
|
+
const requirements = definitionRow.modelRequirements !== null
|
|
278
|
+
? ModelRequirements.assert(definitionRow.modelRequirements)
|
|
154
279
|
: [];
|
|
155
280
|
const preferences = instance.modelPreferences !== null
|
|
156
281
|
? InvokerModelPreferences.assert(instance.modelPreferences)
|
|
@@ -161,5 +286,6 @@ export async function resolveInstanceModelSources(db, tenantId, instance) {
|
|
|
161
286
|
}
|
|
162
287
|
return resolveModelSources(db, tenantId, requirements, {
|
|
163
288
|
invokerPreferences,
|
|
289
|
+
...(credentialCipher !== undefined ? { credentialCipher } : {}),
|
|
164
290
|
});
|
|
165
291
|
}
|