thinkwork-cli 0.12.16 → 0.13.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/cli.js +517 -10403
- package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
- package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
- package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
- package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
- package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
- package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
- package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
- package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
- package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
- package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
- package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
- package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
- package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
- package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
- package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
- package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
- package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
- package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
- package/dist/drizzle/0203_threads_mode_override.sql +61 -0
- package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
- package/dist/drizzle/0205_routines_git_python.sql +185 -0
- package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
- package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
- package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
- package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
- package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
- package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
- package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
- package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
- package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
- package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
- package/dist/drizzle/0218_document_plates.sql +30 -0
- package/dist/drizzle/0219_document_section_waivers.sql +29 -0
- package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
- package/dist/drizzle/0221_artifact_shares.sql +29 -0
- package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
- package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
- package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
- package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
- package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
- package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
- package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
- package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
- package/dist/drizzle/0230_analyst_rls.sql +1613 -0
- package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
- package/dist/plugins/catalog/package.json +0 -1
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
- package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
- package/dist/plugins/catalog/src/contracts.ts +112 -0
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
- package/dist/plugins/n8n/README.md +4 -36
- package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
- package/dist/plugins/n8n/src/manifest.ts +0 -5
- package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
- package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
- package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
- package/dist/plugins/n8n/test/manifest.test.ts +0 -1
- package/dist/plugins/twenty/package.json +5 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
- package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
- package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
- package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
- package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
- package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
- package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
- package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
- package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
- package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
- package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
- package/dist/plugins/twenty/src/manifest.ts +99 -0
- package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
- package/dist/plugins/twenty/test/manifest.test.ts +12 -2
- package/dist/terraform/examples/greenfield/main.tf +90 -380
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
- package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
- package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
- package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
- package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
- package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
- package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
- package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
- package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
- package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
- package/dist/terraform/modules/app/ses-email/main.tf +14 -2
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
- package/dist/terraform/modules/thinkwork/README.md +0 -1
- package/dist/terraform/modules/thinkwork/main.tf +167 -446
- package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
- package/dist/terraform/modules/thinkwork/variables.tf +55 -309
- package/dist/terraform/schema.graphql +4 -0
- package/dist/workspace-defaults/files/AGENTS.md +45 -31
- package/dist/workspace-defaults/files/CONTEXT.md +48 -4
- package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
- package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
- package/dist/workspace-defaults/files/SPACE.md +34 -19
- package/dist/workspace-defaults/files/TOOLS.md +30 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
- package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
- package/package.json +1 -1
- package/dist/plugins/company-brain/README.md +0 -37
- package/dist/plugins/company-brain/package.json +0 -33
- package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
- package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
- package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
- package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
- package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
- package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
- package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
- package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
- package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
- package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
- package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
- package/dist/plugins/company-brain/src/api/migration.ts +0 -665
- package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
- package/dist/plugins/company-brain/src/index.ts +0 -62
- package/dist/plugins/company-brain/src/manifest.ts +0 -112
- package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
- package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
- package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
- package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
- package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
- package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
- package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
- package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
- package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
- package/dist/plugins/company-brain/tsconfig.json +0 -8
|
@@ -1,665 +0,0 @@
|
|
|
1
|
-
import { GraphQLError } from "graphql";
|
|
2
|
-
import { and, desc, eq, inArray } from "drizzle-orm";
|
|
3
|
-
import { getDb, type Database } from "@thinkwork/database-pg";
|
|
4
|
-
import {
|
|
5
|
-
brainArtifactManifests,
|
|
6
|
-
brainSubstrateEvents,
|
|
7
|
-
brainSubstrateMigrations,
|
|
8
|
-
brainSubstrateStates,
|
|
9
|
-
} from "@thinkwork/database-pg/schema";
|
|
10
|
-
|
|
11
|
-
type BrainMigrationDb = Pick<Database, "select" | "insert" | "update"> & {
|
|
12
|
-
transaction?<T>(fn: (tx: BrainMigrationDb) => Promise<T>): Promise<T>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type BrainSubstrateStateRow = typeof brainSubstrateStates.$inferSelect;
|
|
16
|
-
export type BrainSubstrateMigrationRow =
|
|
17
|
-
typeof brainSubstrateMigrations.$inferSelect;
|
|
18
|
-
export type BrainSubstrateMigrationInsert =
|
|
19
|
-
typeof brainSubstrateMigrations.$inferInsert;
|
|
20
|
-
export type BrainArtifactManifestRow =
|
|
21
|
-
typeof brainArtifactManifests.$inferSelect;
|
|
22
|
-
export type BrainSubstrateEventInsert =
|
|
23
|
-
typeof brainSubstrateEvents.$inferInsert;
|
|
24
|
-
|
|
25
|
-
export type BrainMigrationPhase =
|
|
26
|
-
| "requested"
|
|
27
|
-
| "snapshotting"
|
|
28
|
-
| "provisioning"
|
|
29
|
-
| "replaying"
|
|
30
|
-
| "validating"
|
|
31
|
-
| "cutover"
|
|
32
|
-
| "completed"
|
|
33
|
-
| "failed"
|
|
34
|
-
| "rolled_back";
|
|
35
|
-
|
|
36
|
-
export type BrainMigrationStatus =
|
|
37
|
-
| "requested"
|
|
38
|
-
| "running"
|
|
39
|
-
| "completed"
|
|
40
|
-
| "failed"
|
|
41
|
-
| "rolled_back"
|
|
42
|
-
| "canceled";
|
|
43
|
-
|
|
44
|
-
export interface RequestCompanyBrainProductionMigrationInput {
|
|
45
|
-
tenantId: string;
|
|
46
|
-
requestedByUserId?: string | null;
|
|
47
|
-
embeddingModel?: string | null;
|
|
48
|
-
vectorDimension?: number | null;
|
|
49
|
-
allowEmptySourceSet?: boolean | null;
|
|
50
|
-
emptySourceReason?: string | null;
|
|
51
|
-
operatorEvidence?: Record<string, unknown> | null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface UpdateCompanyBrainMigrationInput {
|
|
55
|
-
tenantId: string;
|
|
56
|
-
migrationId: string;
|
|
57
|
-
phase: BrainMigrationPhase;
|
|
58
|
-
status?: BrainMigrationStatus | null;
|
|
59
|
-
validationSummary?: Record<string, unknown> | null;
|
|
60
|
-
operatorEvidence?: Record<string, unknown> | null;
|
|
61
|
-
errorMessage?: string | null;
|
|
62
|
-
rollbackWindowClosesAt?: Date | string | null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface BrainMigrationDeps {
|
|
66
|
-
runInTransaction?<T>(
|
|
67
|
-
fn: (deps: BrainMigrationDeps) => Promise<T>,
|
|
68
|
-
): Promise<T>;
|
|
69
|
-
getSubstrateState(tenantId: string): Promise<BrainSubstrateStateRow | null>;
|
|
70
|
-
getMigration(args: {
|
|
71
|
-
tenantId: string;
|
|
72
|
-
migrationId: string;
|
|
73
|
-
}): Promise<BrainSubstrateMigrationRow | null>;
|
|
74
|
-
getActiveMigration(args: {
|
|
75
|
-
tenantId: string;
|
|
76
|
-
substrateId: string;
|
|
77
|
-
}): Promise<BrainSubstrateMigrationRow | null>;
|
|
78
|
-
listReplayManifests(tenantId: string): Promise<BrainArtifactManifestRow[]>;
|
|
79
|
-
createMigration(
|
|
80
|
-
values: BrainSubstrateMigrationInsert,
|
|
81
|
-
): Promise<BrainSubstrateMigrationRow>;
|
|
82
|
-
updateMigration(args: {
|
|
83
|
-
tenantId: string;
|
|
84
|
-
migrationId: string;
|
|
85
|
-
patch: Partial<typeof brainSubstrateMigrations.$inferInsert>;
|
|
86
|
-
}): Promise<BrainSubstrateMigrationRow>;
|
|
87
|
-
updateSubstrate(args: {
|
|
88
|
-
tenantId: string;
|
|
89
|
-
patch: Partial<typeof brainSubstrateStates.$inferInsert>;
|
|
90
|
-
}): Promise<void>;
|
|
91
|
-
appendEvent(values: BrainSubstrateEventInsert): Promise<void>;
|
|
92
|
-
now(): Date;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const PHASE_ORDER: BrainMigrationPhase[] = [
|
|
96
|
-
"requested",
|
|
97
|
-
"snapshotting",
|
|
98
|
-
"provisioning",
|
|
99
|
-
"replaying",
|
|
100
|
-
"validating",
|
|
101
|
-
"cutover",
|
|
102
|
-
"completed",
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
export function createDrizzleBrainMigrationDeps(
|
|
106
|
-
db: BrainMigrationDb = getDb(),
|
|
107
|
-
): BrainMigrationDeps {
|
|
108
|
-
return {
|
|
109
|
-
async runInTransaction(fn) {
|
|
110
|
-
if (!db.transaction) return fn(createDrizzleBrainMigrationDeps(db));
|
|
111
|
-
return db.transaction((tx) =>
|
|
112
|
-
fn(createDrizzleBrainMigrationDeps(tx as BrainMigrationDb)),
|
|
113
|
-
);
|
|
114
|
-
},
|
|
115
|
-
async getSubstrateState(tenantId) {
|
|
116
|
-
const [row] = await db
|
|
117
|
-
.select()
|
|
118
|
-
.from(brainSubstrateStates)
|
|
119
|
-
.where(eq(brainSubstrateStates.tenant_id, tenantId))
|
|
120
|
-
.for("update");
|
|
121
|
-
return row ?? null;
|
|
122
|
-
},
|
|
123
|
-
async getMigration({ tenantId, migrationId }) {
|
|
124
|
-
const [row] = await db
|
|
125
|
-
.select()
|
|
126
|
-
.from(brainSubstrateMigrations)
|
|
127
|
-
.where(
|
|
128
|
-
and(
|
|
129
|
-
eq(brainSubstrateMigrations.tenant_id, tenantId),
|
|
130
|
-
eq(brainSubstrateMigrations.id, migrationId),
|
|
131
|
-
),
|
|
132
|
-
)
|
|
133
|
-
.limit(1);
|
|
134
|
-
return row ?? null;
|
|
135
|
-
},
|
|
136
|
-
async getActiveMigration({ tenantId, substrateId }) {
|
|
137
|
-
const [row] = await db
|
|
138
|
-
.select()
|
|
139
|
-
.from(brainSubstrateMigrations)
|
|
140
|
-
.where(
|
|
141
|
-
and(
|
|
142
|
-
eq(brainSubstrateMigrations.tenant_id, tenantId),
|
|
143
|
-
eq(brainSubstrateMigrations.substrate_id, substrateId),
|
|
144
|
-
inArray(brainSubstrateMigrations.status, ["requested", "running"]),
|
|
145
|
-
),
|
|
146
|
-
)
|
|
147
|
-
.orderBy(desc(brainSubstrateMigrations.created_at))
|
|
148
|
-
.limit(1);
|
|
149
|
-
return row ?? null;
|
|
150
|
-
},
|
|
151
|
-
async listReplayManifests(tenantId) {
|
|
152
|
-
return db
|
|
153
|
-
.select()
|
|
154
|
-
.from(brainArtifactManifests)
|
|
155
|
-
.where(
|
|
156
|
-
and(
|
|
157
|
-
eq(brainArtifactManifests.tenant_id, tenantId),
|
|
158
|
-
eq(brainArtifactManifests.status, "active"),
|
|
159
|
-
inArray(brainArtifactManifests.manifest_kind, [
|
|
160
|
-
"source_artifact",
|
|
161
|
-
"ingestion_manifest",
|
|
162
|
-
]),
|
|
163
|
-
),
|
|
164
|
-
)
|
|
165
|
-
.orderBy(desc(brainArtifactManifests.updated_at));
|
|
166
|
-
},
|
|
167
|
-
async createMigration(values) {
|
|
168
|
-
const [row] = await db
|
|
169
|
-
.insert(brainSubstrateMigrations)
|
|
170
|
-
.values(values)
|
|
171
|
-
.returning();
|
|
172
|
-
if (!row) throw invariant("Company Brain migration was not created");
|
|
173
|
-
return row;
|
|
174
|
-
},
|
|
175
|
-
async updateMigration({ tenantId, migrationId, patch }) {
|
|
176
|
-
const [row] = await db
|
|
177
|
-
.update(brainSubstrateMigrations)
|
|
178
|
-
.set(patch)
|
|
179
|
-
.where(
|
|
180
|
-
and(
|
|
181
|
-
eq(brainSubstrateMigrations.tenant_id, tenantId),
|
|
182
|
-
eq(brainSubstrateMigrations.id, migrationId),
|
|
183
|
-
),
|
|
184
|
-
)
|
|
185
|
-
.returning();
|
|
186
|
-
if (!row) throw notFound("Company Brain migration not found");
|
|
187
|
-
return row;
|
|
188
|
-
},
|
|
189
|
-
async updateSubstrate({ tenantId, patch }) {
|
|
190
|
-
await db
|
|
191
|
-
.update(brainSubstrateStates)
|
|
192
|
-
.set(patch)
|
|
193
|
-
.where(eq(brainSubstrateStates.tenant_id, tenantId));
|
|
194
|
-
},
|
|
195
|
-
async appendEvent(values) {
|
|
196
|
-
await db.insert(brainSubstrateEvents).values(values);
|
|
197
|
-
},
|
|
198
|
-
now: () => new Date(),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function withMigrationTransaction<T>(
|
|
203
|
-
deps: BrainMigrationDeps,
|
|
204
|
-
fn: (deps: BrainMigrationDeps) => Promise<T>,
|
|
205
|
-
): Promise<T> {
|
|
206
|
-
return deps.runInTransaction ? deps.runInTransaction(fn) : fn(deps);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export async function requestCompanyBrainProductionMigration(
|
|
210
|
-
input: RequestCompanyBrainProductionMigrationInput,
|
|
211
|
-
deps: BrainMigrationDeps = createDrizzleBrainMigrationDeps(),
|
|
212
|
-
): Promise<BrainSubstrateMigrationRow> {
|
|
213
|
-
return withMigrationTransaction(deps, async (txDeps) => {
|
|
214
|
-
const substrate = await txDeps.getSubstrateState(input.tenantId);
|
|
215
|
-
if (!substrate) {
|
|
216
|
-
throw badInput(
|
|
217
|
-
"Company Brain substrate is not installed for this tenant",
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (substrate.storage_tier !== "default") {
|
|
221
|
-
throw badInput(
|
|
222
|
-
"Company Brain production migration requires default tier",
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
if (substrate.active_backend !== "default") {
|
|
226
|
-
throw badInput("Company Brain migration requires default active backend");
|
|
227
|
-
}
|
|
228
|
-
if (substrate.status !== "ready") {
|
|
229
|
-
throw badInput(`Company Brain substrate is ${substrate.status}`);
|
|
230
|
-
}
|
|
231
|
-
const activeMigration = await txDeps.getActiveMigration({
|
|
232
|
-
tenantId: input.tenantId,
|
|
233
|
-
substrateId: substrate.id,
|
|
234
|
-
});
|
|
235
|
-
if (activeMigration) {
|
|
236
|
-
throw badInput("Company Brain production migration is already active");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const manifests = await txDeps.listReplayManifests(input.tenantId);
|
|
240
|
-
const vectorDimension = normalizePositiveInt(
|
|
241
|
-
input.vectorDimension ?? substrate.vector_dimension,
|
|
242
|
-
"vectorDimension",
|
|
243
|
-
);
|
|
244
|
-
const embeddingModel =
|
|
245
|
-
nonEmpty(input.embeddingModel) ?? nonEmpty(substrate.embedding_model);
|
|
246
|
-
const validationSummary = buildRequestValidationSummary({
|
|
247
|
-
input,
|
|
248
|
-
substrate,
|
|
249
|
-
manifests,
|
|
250
|
-
vectorDimension,
|
|
251
|
-
embeddingModel,
|
|
252
|
-
});
|
|
253
|
-
const now = txDeps.now();
|
|
254
|
-
const migration = await txDeps.createMigration({
|
|
255
|
-
tenant_id: input.tenantId,
|
|
256
|
-
substrate_id: substrate.id,
|
|
257
|
-
from_storage_tier: "default",
|
|
258
|
-
to_storage_tier: "production",
|
|
259
|
-
phase: "requested",
|
|
260
|
-
status: "requested",
|
|
261
|
-
requested_by_user_id: input.requestedByUserId ?? null,
|
|
262
|
-
embedding_model: embeddingModel,
|
|
263
|
-
vector_dimension: vectorDimension,
|
|
264
|
-
validation_summary: validationSummary,
|
|
265
|
-
operator_evidence: input.operatorEvidence ?? {},
|
|
266
|
-
requested_at: now,
|
|
267
|
-
created_at: now,
|
|
268
|
-
updated_at: now,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
await txDeps.updateSubstrate({
|
|
272
|
-
tenantId: input.tenantId,
|
|
273
|
-
patch: {
|
|
274
|
-
status: "migrating",
|
|
275
|
-
health_status: "degraded",
|
|
276
|
-
updated_at: now,
|
|
277
|
-
operator_evidence: mergeEvidence(substrate.operator_evidence, {
|
|
278
|
-
latestMigrationId: migration.id,
|
|
279
|
-
}),
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
await txDeps.appendEvent({
|
|
283
|
-
tenant_id: input.tenantId,
|
|
284
|
-
substrate_id: substrate.id,
|
|
285
|
-
migration_id: migration.id,
|
|
286
|
-
event_type: "brain.migration.requested",
|
|
287
|
-
message: "Company Brain production migration requested",
|
|
288
|
-
payload: redactedMigrationEventPayload(migration),
|
|
289
|
-
created_at: now,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
return migration;
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export async function updateCompanyBrainMigration(
|
|
297
|
-
input: UpdateCompanyBrainMigrationInput,
|
|
298
|
-
deps: BrainMigrationDeps = createDrizzleBrainMigrationDeps(),
|
|
299
|
-
): Promise<BrainSubstrateMigrationRow> {
|
|
300
|
-
return withMigrationTransaction(deps, async (txDeps) => {
|
|
301
|
-
const [substrate, migration] = await Promise.all([
|
|
302
|
-
txDeps.getSubstrateState(input.tenantId),
|
|
303
|
-
txDeps.getMigration({
|
|
304
|
-
tenantId: input.tenantId,
|
|
305
|
-
migrationId: input.migrationId,
|
|
306
|
-
}),
|
|
307
|
-
]);
|
|
308
|
-
if (!substrate) {
|
|
309
|
-
throw badInput(
|
|
310
|
-
"Company Brain substrate is not installed for this tenant",
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
if (!migration) throw notFound("Company Brain migration not found");
|
|
314
|
-
validatePhaseTransition(migration, input);
|
|
315
|
-
|
|
316
|
-
const now = txDeps.now();
|
|
317
|
-
const status = normalizeStatusForPhase(input.phase, input.status);
|
|
318
|
-
const validationSummary = publicValidationSummary(
|
|
319
|
-
migration.validation_summary,
|
|
320
|
-
input.validationSummary ?? {},
|
|
321
|
-
);
|
|
322
|
-
validateCutoverReadiness({
|
|
323
|
-
phase: input.phase,
|
|
324
|
-
status,
|
|
325
|
-
validationSummary,
|
|
326
|
-
migration,
|
|
327
|
-
substrate,
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
const updated = await txDeps.updateMigration({
|
|
331
|
-
tenantId: input.tenantId,
|
|
332
|
-
migrationId: input.migrationId,
|
|
333
|
-
patch: {
|
|
334
|
-
phase: input.phase,
|
|
335
|
-
status,
|
|
336
|
-
validation_summary: validationSummary,
|
|
337
|
-
operator_evidence: mergeEvidence(
|
|
338
|
-
migration.operator_evidence,
|
|
339
|
-
input.operatorEvidence ?? {},
|
|
340
|
-
),
|
|
341
|
-
error_message:
|
|
342
|
-
input.errorMessage === undefined
|
|
343
|
-
? migration.error_message
|
|
344
|
-
: input.errorMessage,
|
|
345
|
-
started_at:
|
|
346
|
-
migration.started_at ?? (input.phase === "requested" ? null : now),
|
|
347
|
-
completed_at: terminalPhase(input.phase) ? now : migration.completed_at,
|
|
348
|
-
rollback_window_closes_at: normalizeDate(
|
|
349
|
-
input.rollbackWindowClosesAt ?? migration.rollback_window_closes_at,
|
|
350
|
-
),
|
|
351
|
-
updated_at: now,
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
if (input.phase === "completed" && status === "completed") {
|
|
356
|
-
await txDeps.updateSubstrate({
|
|
357
|
-
tenantId: input.tenantId,
|
|
358
|
-
patch: {
|
|
359
|
-
storage_tier: "production",
|
|
360
|
-
active_backend: "production",
|
|
361
|
-
status: "ready",
|
|
362
|
-
health_status: "healthy",
|
|
363
|
-
updated_at: now,
|
|
364
|
-
operator_evidence: mergeEvidence(substrate.operator_evidence, {
|
|
365
|
-
latestMigrationId: migration.id,
|
|
366
|
-
activeMigrationCompletedAt: now.toISOString(),
|
|
367
|
-
}),
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
} else if (input.phase === "failed" || status === "failed") {
|
|
371
|
-
await txDeps.updateSubstrate({
|
|
372
|
-
tenantId: input.tenantId,
|
|
373
|
-
patch: {
|
|
374
|
-
active_backend: substrate.active_backend,
|
|
375
|
-
status: "degraded",
|
|
376
|
-
health_status: "degraded",
|
|
377
|
-
last_failure_message: input.errorMessage ?? migration.error_message,
|
|
378
|
-
last_failure_at: now,
|
|
379
|
-
updated_at: now,
|
|
380
|
-
},
|
|
381
|
-
});
|
|
382
|
-
} else if (input.phase === "rolled_back" || status === "rolled_back") {
|
|
383
|
-
await txDeps.updateSubstrate({
|
|
384
|
-
tenantId: input.tenantId,
|
|
385
|
-
patch: {
|
|
386
|
-
storage_tier: "default",
|
|
387
|
-
active_backend: "default",
|
|
388
|
-
status: "ready",
|
|
389
|
-
health_status: "healthy",
|
|
390
|
-
updated_at: now,
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
} else {
|
|
394
|
-
await txDeps.updateSubstrate({
|
|
395
|
-
tenantId: input.tenantId,
|
|
396
|
-
patch: {
|
|
397
|
-
status: "migrating",
|
|
398
|
-
health_status: "degraded",
|
|
399
|
-
updated_at: now,
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
await txDeps.appendEvent({
|
|
405
|
-
tenant_id: input.tenantId,
|
|
406
|
-
substrate_id: substrate.id,
|
|
407
|
-
migration_id: migration.id,
|
|
408
|
-
event_type: `brain.migration.${input.phase}`,
|
|
409
|
-
message: `Company Brain migration moved to ${input.phase}`,
|
|
410
|
-
payload: redactedMigrationEventPayload(updated),
|
|
411
|
-
created_at: now,
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
return updated;
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function buildRequestValidationSummary(args: {
|
|
419
|
-
input: RequestCompanyBrainProductionMigrationInput;
|
|
420
|
-
substrate: BrainSubstrateStateRow;
|
|
421
|
-
manifests: BrainArtifactManifestRow[];
|
|
422
|
-
vectorDimension: number;
|
|
423
|
-
embeddingModel: string | null;
|
|
424
|
-
}): Record<string, unknown> {
|
|
425
|
-
const { input, manifests, vectorDimension, embeddingModel } = args;
|
|
426
|
-
const totalSources = manifests.reduce(
|
|
427
|
-
(sum, manifest) => sum + Number(manifest.source_count ?? 0),
|
|
428
|
-
0,
|
|
429
|
-
);
|
|
430
|
-
const totalObjects = manifests.reduce(
|
|
431
|
-
(sum, manifest) => sum + Number(manifest.object_count ?? 0),
|
|
432
|
-
0,
|
|
433
|
-
);
|
|
434
|
-
if (totalSources === 0 && !input.allowEmptySourceSet) {
|
|
435
|
-
throw badInput(
|
|
436
|
-
"Company Brain migration requires replayable source manifests or an explicit empty-source approval",
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
if (totalSources === 0 && !nonEmpty(input.emptySourceReason)) {
|
|
440
|
-
throw badInput("emptySourceReason is required for empty-source migration");
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const mismatchedDimensions = manifests
|
|
444
|
-
.filter(
|
|
445
|
-
(manifest) =>
|
|
446
|
-
manifest.vector_dimension != null &&
|
|
447
|
-
manifest.vector_dimension !== vectorDimension,
|
|
448
|
-
)
|
|
449
|
-
.map((manifest) => manifest.id);
|
|
450
|
-
if (mismatchedDimensions.length > 0) {
|
|
451
|
-
throw badInput("Manifest vector dimension does not match migration target");
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const mismatchedModels =
|
|
455
|
-
embeddingModel == null
|
|
456
|
-
? []
|
|
457
|
-
: manifests
|
|
458
|
-
.filter(
|
|
459
|
-
(manifest) =>
|
|
460
|
-
manifest.embedding_model != null &&
|
|
461
|
-
manifest.embedding_model !== embeddingModel,
|
|
462
|
-
)
|
|
463
|
-
.map((manifest) => manifest.id);
|
|
464
|
-
if (mismatchedModels.length > 0) {
|
|
465
|
-
throw badInput("Manifest embedding model does not match migration target");
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
replayManifestCount: manifests.length,
|
|
470
|
-
sourceCount: totalSources,
|
|
471
|
-
objectCount: totalObjects,
|
|
472
|
-
vectorDimension,
|
|
473
|
-
embeddingModel,
|
|
474
|
-
emptySourceApproved: totalSources === 0,
|
|
475
|
-
emptySourceReason:
|
|
476
|
-
totalSources === 0 ? nonEmpty(input.emptySourceReason) : null,
|
|
477
|
-
validationPassed: false,
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function validatePhaseTransition(
|
|
482
|
-
migration: BrainSubstrateMigrationRow,
|
|
483
|
-
input: UpdateCompanyBrainMigrationInput,
|
|
484
|
-
) {
|
|
485
|
-
const currentPhase = migration.phase as BrainMigrationPhase;
|
|
486
|
-
if (currentPhase === "failed" && input.phase === "rolled_back") return;
|
|
487
|
-
if (terminalPhase(currentPhase)) {
|
|
488
|
-
throw badInput("Company Brain migration is already terminal");
|
|
489
|
-
}
|
|
490
|
-
if (input.phase === "failed") return;
|
|
491
|
-
if (input.phase === "rolled_back") {
|
|
492
|
-
throw badInput("Rollback requires a failed Company Brain migration");
|
|
493
|
-
}
|
|
494
|
-
const currentIndex = PHASE_ORDER.indexOf(currentPhase);
|
|
495
|
-
const nextIndex = PHASE_ORDER.indexOf(input.phase);
|
|
496
|
-
if (
|
|
497
|
-
currentIndex < 0 ||
|
|
498
|
-
nextIndex < 0 ||
|
|
499
|
-
(nextIndex !== currentIndex && nextIndex !== currentIndex + 1)
|
|
500
|
-
) {
|
|
501
|
-
throw badInput("Invalid Company Brain migration phase transition");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function validateCutoverReadiness(args: {
|
|
506
|
-
phase: BrainMigrationPhase;
|
|
507
|
-
status: BrainMigrationStatus;
|
|
508
|
-
validationSummary: Record<string, unknown>;
|
|
509
|
-
migration: BrainSubstrateMigrationRow;
|
|
510
|
-
substrate: BrainSubstrateStateRow;
|
|
511
|
-
}) {
|
|
512
|
-
if (args.phase !== "cutover" && args.phase !== "completed") return;
|
|
513
|
-
if (args.status !== "running" && args.status !== "completed") {
|
|
514
|
-
throw badInput("Cutover requires running or completed migration status");
|
|
515
|
-
}
|
|
516
|
-
if (args.validationSummary.validationPassed !== true) {
|
|
517
|
-
throw badInput("Cutover requires validationPassed evidence");
|
|
518
|
-
}
|
|
519
|
-
const expectedDimension = args.migration.vector_dimension;
|
|
520
|
-
const observedDimension = Number(args.validationSummary.vectorDimension);
|
|
521
|
-
if (
|
|
522
|
-
expectedDimension != null &&
|
|
523
|
-
(!Number.isFinite(observedDimension) ||
|
|
524
|
-
observedDimension !== expectedDimension)
|
|
525
|
-
) {
|
|
526
|
-
throw badInput("Cutover vector dimension evidence does not match request");
|
|
527
|
-
}
|
|
528
|
-
if (args.substrate.active_backend !== "default") {
|
|
529
|
-
throw badInput("Cutover requires default to remain the active backend");
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function normalizeStatusForPhase(
|
|
534
|
-
phase: BrainMigrationPhase,
|
|
535
|
-
requestedStatus: BrainMigrationStatus | null | undefined,
|
|
536
|
-
): BrainMigrationStatus {
|
|
537
|
-
const expected = statusForPhase(phase);
|
|
538
|
-
if (!requestedStatus) return expected;
|
|
539
|
-
if (phase === "cutover" && requestedStatus === "completed") {
|
|
540
|
-
throw badInput("Completed status requires completed migration phase");
|
|
541
|
-
}
|
|
542
|
-
if (requestedStatus !== expected) {
|
|
543
|
-
throw badInput("Company Brain migration status does not match phase");
|
|
544
|
-
}
|
|
545
|
-
return requestedStatus;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function statusForPhase(phase: BrainMigrationPhase): BrainMigrationStatus {
|
|
549
|
-
if (phase === "requested") return "requested";
|
|
550
|
-
if (phase === "completed") return "completed";
|
|
551
|
-
if (phase === "failed") return "failed";
|
|
552
|
-
if (phase === "rolled_back") return "rolled_back";
|
|
553
|
-
return "running";
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function terminalPhase(phase: string): boolean {
|
|
557
|
-
return phase === "completed" || phase === "failed" || phase === "rolled_back";
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function normalizePositiveInt(value: unknown, name: string): number {
|
|
561
|
-
const parsed = Number(value);
|
|
562
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
563
|
-
throw badInput(`${name} must be a positive integer`);
|
|
564
|
-
}
|
|
565
|
-
return parsed;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function normalizeDate(value: Date | string | null | undefined) {
|
|
569
|
-
if (!value) return null;
|
|
570
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
571
|
-
if (Number.isNaN(date.getTime())) {
|
|
572
|
-
throw badInput("rollbackWindowClosesAt must be a valid date");
|
|
573
|
-
}
|
|
574
|
-
return date;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function nonEmpty(value: unknown): string | null {
|
|
578
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function mergeEvidence(
|
|
582
|
-
prior: unknown,
|
|
583
|
-
next: Record<string, unknown>,
|
|
584
|
-
): Record<string, unknown> {
|
|
585
|
-
const priorRecord =
|
|
586
|
-
prior && typeof prior === "object" && !Array.isArray(prior)
|
|
587
|
-
? (prior as Record<string, unknown>)
|
|
588
|
-
: {};
|
|
589
|
-
return { ...priorRecord, ...next };
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function publicValidationSummary(
|
|
593
|
-
prior: unknown,
|
|
594
|
-
next: Record<string, unknown>,
|
|
595
|
-
): Record<string, unknown> {
|
|
596
|
-
const merged = mergeEvidence(prior, next);
|
|
597
|
-
const summary: Record<string, unknown> = {};
|
|
598
|
-
copyNumber(summary, merged, "replayManifestCount");
|
|
599
|
-
copyNumber(summary, merged, "sourceCount");
|
|
600
|
-
copyNumber(summary, merged, "objectCount");
|
|
601
|
-
copyNumber(summary, merged, "graphEntityCount");
|
|
602
|
-
copyNumber(summary, merged, "graphEdgeCount");
|
|
603
|
-
copyNumber(summary, merged, "vectorDimension");
|
|
604
|
-
copyString(summary, merged, "embeddingModel");
|
|
605
|
-
copyString(summary, merged, "ontologyVersion");
|
|
606
|
-
copyBoolean(summary, merged, "emptySourceApproved");
|
|
607
|
-
copyString(summary, merged, "emptySourceReason");
|
|
608
|
-
copyBoolean(summary, merged, "validationPassed");
|
|
609
|
-
copyBoolean(summary, merged, "vectorIndexHealthy");
|
|
610
|
-
copyBoolean(summary, merged, "retrievalParityPassed");
|
|
611
|
-
return summary;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function copyNumber(
|
|
615
|
-
target: Record<string, unknown>,
|
|
616
|
-
source: Record<string, unknown>,
|
|
617
|
-
key: string,
|
|
618
|
-
) {
|
|
619
|
-
const value = Number(source[key]);
|
|
620
|
-
if (Number.isFinite(value)) target[key] = value;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function copyString(
|
|
624
|
-
target: Record<string, unknown>,
|
|
625
|
-
source: Record<string, unknown>,
|
|
626
|
-
key: string,
|
|
627
|
-
) {
|
|
628
|
-
const value = nonEmpty(source[key]);
|
|
629
|
-
if (value) target[key] = value;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function copyBoolean(
|
|
633
|
-
target: Record<string, unknown>,
|
|
634
|
-
source: Record<string, unknown>,
|
|
635
|
-
key: string,
|
|
636
|
-
) {
|
|
637
|
-
if (typeof source[key] === "boolean") target[key] = source[key];
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function redactedMigrationEventPayload(
|
|
641
|
-
migration: BrainSubstrateMigrationRow,
|
|
642
|
-
): Record<string, unknown> {
|
|
643
|
-
return {
|
|
644
|
-
migrationId: migration.id,
|
|
645
|
-
phase: migration.phase,
|
|
646
|
-
status: migration.status,
|
|
647
|
-
fromStorageTier: migration.from_storage_tier,
|
|
648
|
-
toStorageTier: migration.to_storage_tier,
|
|
649
|
-
validationSummary: migration.validation_summary,
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function badInput(message: string): GraphQLError {
|
|
654
|
-
return new GraphQLError(message, { extensions: { code: "BAD_USER_INPUT" } });
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function notFound(message: string): GraphQLError {
|
|
658
|
-
return new GraphQLError(message, { extensions: { code: "NOT_FOUND" } });
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function invariant(message: string): GraphQLError {
|
|
662
|
-
return new GraphQLError(message, {
|
|
663
|
-
extensions: { code: "INTERNAL_SERVER_ERROR" },
|
|
664
|
-
});
|
|
665
|
-
}
|