@cat-factory/node-server 0.87.10 → 0.89.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/dist/container.d.ts.map +1 -1
- package/dist/container.js +41 -4
- package/dist/container.js.map +1 -1
- package/dist/db/schema.d.ts +143 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +23 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/repositories/drizzle.d.ts +17 -2
- package/dist/repositories/drizzle.d.ts.map +1 -1
- package/dist/repositories/drizzle.js +132 -26
- package/dist/repositories/drizzle.js.map +1 -1
- package/drizzle/20260708095726_giant_sumo/migration.sql +9 -0
- package/drizzle/20260708095726_giant_sumo/snapshot.json +14993 -0
- package/drizzle/20260708102010_icy_yellow_claw/migration.sql +1 -0
- package/drizzle/20260708102010_icy_yellow_claw/snapshot.json +15006 -0
- package/drizzle/20260708165154_bizarre_gorgon/migration.sql +2 -0
- package/drizzle/20260708165154_bizarre_gorgon/snapshot.json +14924 -0
- package/package.json +19 -19
|
@@ -2,7 +2,7 @@ import { LLM_WARNING_FINISH_REASONS } from '@cat-factory/kernel';
|
|
|
2
2
|
import { agentRunKindSchema, decodeInitiativeRow, isWebSearchProvider, } from '@cat-factory/contracts';
|
|
3
3
|
import { decodeEnum, tryDecodeRows, blockInsertValues, blockPatchToColumns, parseIssueIntakeColumn, serializeIssueIntakeColumn, rowToBlock, rowToExecution, executionToDetail, rowToPipeline, rowToSandboxExperiment, rowToSandboxFixture, rowToSandboxGrade, rowToSandboxPromptVersion, rowToSandboxRun, rowToWorkspace, } from '@cat-factory/server';
|
|
4
4
|
import { and, asc, count, desc, eq, gte, inArray, isNull, lt, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
5
|
-
import { accountInvitations, passwordResetTokens, accountSettings, localSettings, accounts, agentContextSnapshots, agentSearchQueries, agentRuns, blocks, consensusSessions, incidentEnrichmentConnections, observabilityConnections, packageRegistryConnections, emailConnections, llmCallMetrics, provisioningLog, sharedStacks, memberships,
|
|
5
|
+
import { accountInvitations, passwordResetTokens, accountSettings, localSettings, accounts, agentContextSnapshots, agentSearchQueries, agentRuns, blocks, consensusSessions, incidentEnrichmentConnections, observabilityConnections, packageRegistryConnections, emailConnections, llmCallMetrics, provisioningLog, sharedStacks, memberships, riskPolicies, releaseHealthConfigs, testSecrets, pipelineScheduleRuns, pipelineSchedules, pipelines, requirementReviews, docInterviewSessions, kaizenGradings, kaizenVerifiedCombos, clarityReviews, binaryArtifacts, brainstormSessions, initiatives, sandboxPromptVersions, sandboxFixtures, sandboxExperiments, sandboxRuns, sandboxGrades, services, tokenUsage, trackerSettings, modelPresets, userIdentities, users, userSettings, workspaceFragmentDefaults, workspaceServices, workspaceSettings, workspaces, } from '../db/schema.js';
|
|
6
6
|
// Drizzle/Postgres implementations of the core kernel repository ports. The
|
|
7
7
|
// row<->domain mapping is the SAME shared mapping the Cloudflare D1 repos use
|
|
8
8
|
// (@cat-factory/server), so behaviour matches across stores; this layer only owns
|
|
@@ -222,6 +222,7 @@ class DrizzlePipelineRepository {
|
|
|
222
222
|
gating: pipeline.gating ? JSON.stringify(pipeline.gating) : null,
|
|
223
223
|
follow_ups: pipeline.followUps ? JSON.stringify(pipeline.followUps) : null,
|
|
224
224
|
tester_quality: pipeline.testerQuality ? JSON.stringify(pipeline.testerQuality) : null,
|
|
225
|
+
step_options: pipeline.stepOptions ? JSON.stringify(pipeline.stepOptions) : null,
|
|
225
226
|
labels: pipeline.labels ? JSON.stringify(pipeline.labels) : null,
|
|
226
227
|
archived: pipeline.archived ? 1 : null,
|
|
227
228
|
builtin: pipeline.builtin ? 1 : null,
|
|
@@ -246,6 +247,7 @@ class DrizzlePipelineRepository {
|
|
|
246
247
|
gating: pipeline.gating ? JSON.stringify(pipeline.gating) : null,
|
|
247
248
|
follow_ups: pipeline.followUps ? JSON.stringify(pipeline.followUps) : null,
|
|
248
249
|
tester_quality: pipeline.testerQuality ? JSON.stringify(pipeline.testerQuality) : null,
|
|
250
|
+
step_options: pipeline.stepOptions ? JSON.stringify(pipeline.stepOptions) : null,
|
|
249
251
|
labels: pipeline.labels ? JSON.stringify(pipeline.labels) : null,
|
|
250
252
|
archived: pipeline.archived ? 1 : null,
|
|
251
253
|
version: pipeline.version ?? null,
|
|
@@ -969,9 +971,41 @@ class DrizzleTokenUsageRepository {
|
|
|
969
971
|
input_tokens: usage.inputTokens,
|
|
970
972
|
output_tokens: usage.outputTokens,
|
|
971
973
|
cost_estimate: usage.costEstimate,
|
|
974
|
+
billing: usage.billing,
|
|
975
|
+
vendor: usage.vendor,
|
|
972
976
|
created_at: usage.createdAt,
|
|
973
977
|
});
|
|
974
978
|
}
|
|
979
|
+
async usageBreakdownForWorkspace(workspaceId, epochMs) {
|
|
980
|
+
// One GROUP BY over the workspace's current period — both billing kinds (the report
|
|
981
|
+
// shows total usage). Never a per-model loop. sum() of int columns is bigint; cast +
|
|
982
|
+
// coerce like the totals rollups. Ordered heaviest-first in SQL, mirroring the D1 repo.
|
|
983
|
+
const rows = await this.db
|
|
984
|
+
.select({
|
|
985
|
+
billing: tokenUsage.billing,
|
|
986
|
+
vendor: tokenUsage.vendor,
|
|
987
|
+
provider: tokenUsage.provider,
|
|
988
|
+
model: tokenUsage.model,
|
|
989
|
+
input: sql `coalesce(sum(${tokenUsage.input_tokens}), 0)::bigint`,
|
|
990
|
+
output: sql `coalesce(sum(${tokenUsage.output_tokens}), 0)::bigint`,
|
|
991
|
+
cost: sql `coalesce(sum(${tokenUsage.cost_estimate}), 0)::float8`,
|
|
992
|
+
calls: sql `count(*)::bigint`,
|
|
993
|
+
})
|
|
994
|
+
.from(tokenUsage)
|
|
995
|
+
.where(and(eq(tokenUsage.workspace_id, workspaceId), gte(tokenUsage.created_at, epochMs)))
|
|
996
|
+
.groupBy(tokenUsage.billing, tokenUsage.vendor, tokenUsage.provider, tokenUsage.model)
|
|
997
|
+
.orderBy(sql `(coalesce(sum(${tokenUsage.input_tokens}), 0) + coalesce(sum(${tokenUsage.output_tokens}), 0)) desc`);
|
|
998
|
+
return rows.map((r) => ({
|
|
999
|
+
billing: (r.billing === 'subscription' ? 'subscription' : 'metered'),
|
|
1000
|
+
vendor: r.vendor,
|
|
1001
|
+
provider: r.provider,
|
|
1002
|
+
model: r.model,
|
|
1003
|
+
inputTokens: Number(r.input ?? 0),
|
|
1004
|
+
outputTokens: Number(r.output ?? 0),
|
|
1005
|
+
costEstimate: r.cost ?? 0,
|
|
1006
|
+
calls: Number(r.calls ?? 0),
|
|
1007
|
+
}));
|
|
1008
|
+
}
|
|
975
1009
|
async totalsSince(epochMs) {
|
|
976
1010
|
// sum() of int columns is bigint in Postgres — cast to bigint (NOT int4, which
|
|
977
1011
|
// overflows past ~2.1B tokens) and coerce: node-postgres returns bigint as a
|
|
@@ -984,7 +1018,7 @@ class DrizzleTokenUsageRepository {
|
|
|
984
1018
|
cost: sql `coalesce(sum(${tokenUsage.cost_estimate}), 0)::float8`,
|
|
985
1019
|
})
|
|
986
1020
|
.from(tokenUsage)
|
|
987
|
-
.where(gte(tokenUsage.created_at, epochMs));
|
|
1021
|
+
.where(and(gte(tokenUsage.created_at, epochMs), eq(tokenUsage.billing, 'metered')));
|
|
988
1022
|
return {
|
|
989
1023
|
inputTokens: Number(row?.input ?? 0),
|
|
990
1024
|
outputTokens: Number(row?.output ?? 0),
|
|
@@ -999,7 +1033,7 @@ class DrizzleTokenUsageRepository {
|
|
|
999
1033
|
cost: sql `coalesce(sum(${tokenUsage.cost_estimate}), 0)::float8`,
|
|
1000
1034
|
})
|
|
1001
1035
|
.from(tokenUsage)
|
|
1002
|
-
.where(and(eq(tokenUsage.workspace_id, workspaceId), gte(tokenUsage.created_at, epochMs)));
|
|
1036
|
+
.where(and(eq(tokenUsage.workspace_id, workspaceId), gte(tokenUsage.created_at, epochMs), eq(tokenUsage.billing, 'metered')));
|
|
1003
1037
|
return {
|
|
1004
1038
|
inputTokens: Number(row?.input ?? 0),
|
|
1005
1039
|
outputTokens: Number(row?.output ?? 0),
|
|
@@ -1014,7 +1048,7 @@ class DrizzleTokenUsageRepository {
|
|
|
1014
1048
|
cost: sql `coalesce(sum(${tokenUsage.cost_estimate}), 0)::float8`,
|
|
1015
1049
|
})
|
|
1016
1050
|
.from(tokenUsage)
|
|
1017
|
-
.where(and(eq(tokenUsage.account_id, accountId), gte(tokenUsage.created_at, epochMs)));
|
|
1051
|
+
.where(and(eq(tokenUsage.account_id, accountId), gte(tokenUsage.created_at, epochMs), eq(tokenUsage.billing, 'metered')));
|
|
1018
1052
|
return {
|
|
1019
1053
|
inputTokens: Number(row?.input ?? 0),
|
|
1020
1054
|
outputTokens: Number(row?.output ?? 0),
|
|
@@ -1029,7 +1063,7 @@ class DrizzleTokenUsageRepository {
|
|
|
1029
1063
|
cost: sql `coalesce(sum(${tokenUsage.cost_estimate}), 0)::float8`,
|
|
1030
1064
|
})
|
|
1031
1065
|
.from(tokenUsage)
|
|
1032
|
-
.where(and(eq(tokenUsage.user_id, userId), gte(tokenUsage.created_at, epochMs)));
|
|
1066
|
+
.where(and(eq(tokenUsage.user_id, userId), gte(tokenUsage.created_at, epochMs), eq(tokenUsage.billing, 'metered')));
|
|
1033
1067
|
return {
|
|
1034
1068
|
inputTokens: Number(row?.input ?? 0),
|
|
1035
1069
|
outputTokens: Number(row?.output ?? 0),
|
|
@@ -2801,7 +2835,7 @@ class DrizzleInitiativeRepository {
|
|
|
2801
2835
|
.where(and(eq(initiatives.workspace_id, workspaceId), eq(initiatives.id, id)));
|
|
2802
2836
|
}
|
|
2803
2837
|
}
|
|
2804
|
-
function
|
|
2838
|
+
function rowToRiskPolicy(row) {
|
|
2805
2839
|
return {
|
|
2806
2840
|
id: row.id,
|
|
2807
2841
|
name: row.name,
|
|
@@ -2823,13 +2857,13 @@ function rowToMergePreset(row) {
|
|
|
2823
2857
|
}
|
|
2824
2858
|
/**
|
|
2825
2859
|
* Per-workspace merge threshold presets over Postgres (the Drizzle mirror of the
|
|
2826
|
-
* Worker's `
|
|
2860
|
+
* Worker's `D1RiskPolicyRepository`, migration 0024). Enforces the single-default
|
|
2827
2861
|
* invariant: promoting a preset to default demotes every other in the workspace
|
|
2828
2862
|
* before the upsert. The default preset cannot be removed (the service keeps that
|
|
2829
2863
|
* rule too; the DELETE also guards `is_default = 0`). Behaviourally identical to the
|
|
2830
2864
|
* D1 repo so the cross-runtime conformance suite asserts the same preset resolution.
|
|
2831
2865
|
*/
|
|
2832
|
-
class
|
|
2866
|
+
class DrizzleRiskPolicyRepository {
|
|
2833
2867
|
db;
|
|
2834
2868
|
constructor(db) {
|
|
2835
2869
|
this.db = db;
|
|
@@ -2837,27 +2871,27 @@ class DrizzleMergePresetRepository {
|
|
|
2837
2871
|
async get(workspaceId, id) {
|
|
2838
2872
|
const rows = await this.db
|
|
2839
2873
|
.select()
|
|
2840
|
-
.from(
|
|
2841
|
-
.where(and(eq(
|
|
2874
|
+
.from(riskPolicies)
|
|
2875
|
+
.where(and(eq(riskPolicies.workspace_id, workspaceId), eq(riskPolicies.id, id)))
|
|
2842
2876
|
.limit(1);
|
|
2843
|
-
return rows[0] ?
|
|
2877
|
+
return rows[0] ? rowToRiskPolicy(rows[0]) : null;
|
|
2844
2878
|
}
|
|
2845
2879
|
async list(workspaceId) {
|
|
2846
2880
|
const rows = await this.db
|
|
2847
2881
|
.select()
|
|
2848
|
-
.from(
|
|
2849
|
-
.where(eq(
|
|
2850
|
-
.orderBy(
|
|
2851
|
-
return rows.map(
|
|
2882
|
+
.from(riskPolicies)
|
|
2883
|
+
.where(eq(riskPolicies.workspace_id, workspaceId))
|
|
2884
|
+
.orderBy(riskPolicies.created_at);
|
|
2885
|
+
return rows.map(rowToRiskPolicy);
|
|
2852
2886
|
}
|
|
2853
2887
|
async getDefault(workspaceId) {
|
|
2854
2888
|
const rows = await this.db
|
|
2855
2889
|
.select()
|
|
2856
|
-
.from(
|
|
2857
|
-
.where(and(eq(
|
|
2858
|
-
.orderBy(
|
|
2890
|
+
.from(riskPolicies)
|
|
2891
|
+
.where(and(eq(riskPolicies.workspace_id, workspaceId), eq(riskPolicies.is_default, 1)))
|
|
2892
|
+
.orderBy(riskPolicies.created_at)
|
|
2859
2893
|
.limit(1);
|
|
2860
|
-
return rows[0] ?
|
|
2894
|
+
return rows[0] ? rowToRiskPolicy(rows[0]) : null;
|
|
2861
2895
|
}
|
|
2862
2896
|
async upsert(workspaceId, preset) {
|
|
2863
2897
|
const values = {
|
|
@@ -2885,15 +2919,15 @@ class DrizzleMergePresetRepository {
|
|
|
2885
2919
|
// Promoting this preset to default demotes any other default first.
|
|
2886
2920
|
if (preset.isDefault) {
|
|
2887
2921
|
await tx
|
|
2888
|
-
.update(
|
|
2922
|
+
.update(riskPolicies)
|
|
2889
2923
|
.set({ is_default: 0 })
|
|
2890
|
-
.where(and(eq(
|
|
2924
|
+
.where(and(eq(riskPolicies.workspace_id, workspaceId), sql `${riskPolicies.id} <> ${preset.id}`));
|
|
2891
2925
|
}
|
|
2892
2926
|
await tx
|
|
2893
|
-
.insert(
|
|
2927
|
+
.insert(riskPolicies)
|
|
2894
2928
|
.values(values)
|
|
2895
2929
|
.onConflictDoUpdate({
|
|
2896
|
-
target: [
|
|
2930
|
+
target: [riskPolicies.workspace_id, riskPolicies.id],
|
|
2897
2931
|
set: {
|
|
2898
2932
|
name: values.name,
|
|
2899
2933
|
max_complexity: values.max_complexity,
|
|
@@ -2915,8 +2949,8 @@ class DrizzleMergePresetRepository {
|
|
|
2915
2949
|
}
|
|
2916
2950
|
async remove(workspaceId, id) {
|
|
2917
2951
|
await this.db
|
|
2918
|
-
.delete(
|
|
2919
|
-
.where(and(eq(
|
|
2952
|
+
.delete(riskPolicies)
|
|
2953
|
+
.where(and(eq(riskPolicies.workspace_id, workspaceId), eq(riskPolicies.id, id), eq(riskPolicies.is_default, 0)));
|
|
2920
2954
|
}
|
|
2921
2955
|
}
|
|
2922
2956
|
// Shape-guarded parsers matching the D1 mirror (`D1SharedStackRepository`) EXACTLY, so a
|
|
@@ -3825,6 +3859,77 @@ class DrizzleReleaseHealthConfigRepository {
|
|
|
3825
3859
|
.where(and(eq(releaseHealthConfigs.workspace_id, workspaceId), eq(releaseHealthConfigs.block_id, blockId)));
|
|
3826
3860
|
}
|
|
3827
3861
|
}
|
|
3862
|
+
/**
|
|
3863
|
+
* A service frame's sensitive test credentials over Postgres (the Drizzle mirror of the
|
|
3864
|
+
* Worker's `D1TestSecretsRepository`, migration 0044). At most one row per (workspace, block);
|
|
3865
|
+
* `credentials` is a sealed envelope of the `TestSecretEntry[]` JSON, `summary` a non-secret
|
|
3866
|
+
* `TestSecretRef[]` display blob.
|
|
3867
|
+
*/
|
|
3868
|
+
export class DrizzleTestSecretsRepository {
|
|
3869
|
+
db;
|
|
3870
|
+
constructor(db) {
|
|
3871
|
+
this.db = db;
|
|
3872
|
+
}
|
|
3873
|
+
async getByBlock(workspaceId, blockId) {
|
|
3874
|
+
const rows = await this.db
|
|
3875
|
+
.select()
|
|
3876
|
+
.from(testSecrets)
|
|
3877
|
+
.where(and(eq(testSecrets.workspace_id, workspaceId), eq(testSecrets.block_id, blockId)))
|
|
3878
|
+
.limit(1);
|
|
3879
|
+
const row = rows[0];
|
|
3880
|
+
if (!row)
|
|
3881
|
+
return null;
|
|
3882
|
+
return {
|
|
3883
|
+
workspaceId: row.workspace_id,
|
|
3884
|
+
blockId: row.block_id,
|
|
3885
|
+
credentials: row.credentials,
|
|
3886
|
+
summary: row.summary,
|
|
3887
|
+
createdAt: row.created_at,
|
|
3888
|
+
updatedAt: row.updated_at,
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
async listByWorkspace(workspaceId) {
|
|
3892
|
+
const rows = await this.db
|
|
3893
|
+
.select()
|
|
3894
|
+
.from(testSecrets)
|
|
3895
|
+
.where(eq(testSecrets.workspace_id, workspaceId))
|
|
3896
|
+
.orderBy(testSecrets.block_id);
|
|
3897
|
+
return rows.map((row) => ({
|
|
3898
|
+
workspaceId: row.workspace_id,
|
|
3899
|
+
blockId: row.block_id,
|
|
3900
|
+
credentials: row.credentials,
|
|
3901
|
+
summary: row.summary,
|
|
3902
|
+
createdAt: row.created_at,
|
|
3903
|
+
updatedAt: row.updated_at,
|
|
3904
|
+
}));
|
|
3905
|
+
}
|
|
3906
|
+
async upsert(record) {
|
|
3907
|
+
const values = {
|
|
3908
|
+
workspace_id: record.workspaceId,
|
|
3909
|
+
block_id: record.blockId,
|
|
3910
|
+
credentials: record.credentials,
|
|
3911
|
+
summary: record.summary,
|
|
3912
|
+
created_at: record.createdAt,
|
|
3913
|
+
updated_at: record.updatedAt,
|
|
3914
|
+
};
|
|
3915
|
+
await this.db
|
|
3916
|
+
.insert(testSecrets)
|
|
3917
|
+
.values(values)
|
|
3918
|
+
.onConflictDoUpdate({
|
|
3919
|
+
target: [testSecrets.workspace_id, testSecrets.block_id],
|
|
3920
|
+
set: {
|
|
3921
|
+
credentials: values.credentials,
|
|
3922
|
+
summary: values.summary,
|
|
3923
|
+
updated_at: values.updated_at,
|
|
3924
|
+
},
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
async deleteByBlock(workspaceId, blockId) {
|
|
3928
|
+
await this.db
|
|
3929
|
+
.delete(testSecrets)
|
|
3930
|
+
.where(and(eq(testSecrets.workspace_id, workspaceId), eq(testSecrets.block_id, blockId)));
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3828
3933
|
/** Build the Drizzle/Postgres-backed core repositories. */
|
|
3829
3934
|
export function createDrizzleRepositories(db, clock) {
|
|
3830
3935
|
return {
|
|
@@ -3858,7 +3963,7 @@ export function createDrizzleRepositories(db, clock) {
|
|
|
3858
3963
|
clarityReviewRepository: new DrizzleClarityReviewRepository(db),
|
|
3859
3964
|
brainstormSessionRepository: new DrizzleBrainstormSessionRepository(db),
|
|
3860
3965
|
initiativeRepository: new DrizzleInitiativeRepository(db),
|
|
3861
|
-
|
|
3966
|
+
riskPolicyRepository: new DrizzleRiskPolicyRepository(db),
|
|
3862
3967
|
sharedStackRepository: new DrizzleSharedStackRepository(db),
|
|
3863
3968
|
workspaceSettingsRepository: new DrizzleWorkspaceSettingsRepository(db),
|
|
3864
3969
|
userSettingsRepository: new DrizzleUserSettingsRepository(db),
|
|
@@ -3867,6 +3972,7 @@ export function createDrizzleRepositories(db, clock) {
|
|
|
3867
3972
|
incidentEnrichmentConnectionRepository: new DrizzleIncidentEnrichmentConnectionRepository(db),
|
|
3868
3973
|
accountSettingsRepository: new DrizzleAccountSettingsRepository(db),
|
|
3869
3974
|
releaseHealthConfigRepository: new DrizzleReleaseHealthConfigRepository(db),
|
|
3975
|
+
testSecretsRepository: new DrizzleTestSecretsRepository(db),
|
|
3870
3976
|
provisioningLogRepository: new DrizzleProvisioningLogRepository(db),
|
|
3871
3977
|
};
|
|
3872
3978
|
}
|