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.
Files changed (149) hide show
  1. package/dist/cli.js +517 -10403
  2. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
  3. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
  4. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
  5. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
  6. package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
  7. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
  8. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
  9. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
  10. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
  11. package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
  12. package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
  13. package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
  14. package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
  15. package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
  16. package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
  17. package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
  18. package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
  19. package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
  20. package/dist/drizzle/0203_threads_mode_override.sql +61 -0
  21. package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
  22. package/dist/drizzle/0205_routines_git_python.sql +185 -0
  23. package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
  24. package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
  25. package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
  26. package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
  27. package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
  28. package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
  29. package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
  30. package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
  31. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
  32. package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
  33. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
  34. package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
  35. package/dist/drizzle/0218_document_plates.sql +30 -0
  36. package/dist/drizzle/0219_document_section_waivers.sql +29 -0
  37. package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
  38. package/dist/drizzle/0221_artifact_shares.sql +29 -0
  39. package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
  40. package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
  41. package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
  42. package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
  43. package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
  44. package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
  45. package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
  46. package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
  47. package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
  48. package/dist/drizzle/0230_analyst_rls.sql +1613 -0
  49. package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
  50. package/dist/plugins/catalog/package.json +0 -1
  51. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  52. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
  53. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
  54. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  55. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  56. package/dist/plugins/catalog/src/contracts.ts +112 -0
  57. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  58. package/dist/plugins/n8n/README.md +4 -36
  59. package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
  60. package/dist/plugins/n8n/src/manifest.ts +0 -5
  61. package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
  62. package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
  63. package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
  64. package/dist/plugins/n8n/test/manifest.test.ts +0 -1
  65. package/dist/plugins/twenty/package.json +5 -0
  66. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
  67. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
  69. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
  70. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
  71. package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
  72. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
  73. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
  74. package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
  75. package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
  76. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
  77. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
  78. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
  79. package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
  80. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
  81. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
  82. package/dist/plugins/twenty/src/manifest.ts +99 -0
  83. package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
  84. package/dist/plugins/twenty/test/manifest.test.ts +12 -2
  85. package/dist/terraform/examples/greenfield/main.tf +90 -380
  86. package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
  87. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  88. package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
  89. package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
  90. package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
  91. package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
  92. package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
  93. package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
  94. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
  95. package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
  96. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
  97. package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
  98. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
  99. package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
  100. package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
  101. package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
  102. package/dist/terraform/modules/app/ses-email/main.tf +14 -2
  103. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
  104. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
  105. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
  106. package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
  107. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
  108. package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
  109. package/dist/terraform/modules/thinkwork/README.md +0 -1
  110. package/dist/terraform/modules/thinkwork/main.tf +167 -446
  111. package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
  112. package/dist/terraform/modules/thinkwork/variables.tf +55 -309
  113. package/dist/terraform/schema.graphql +4 -0
  114. package/dist/workspace-defaults/files/AGENTS.md +45 -31
  115. package/dist/workspace-defaults/files/CONTEXT.md +48 -4
  116. package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
  117. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
  118. package/dist/workspace-defaults/files/SPACE.md +34 -19
  119. package/dist/workspace-defaults/files/TOOLS.md +30 -0
  120. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
  121. package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
  122. package/package.json +1 -1
  123. package/dist/plugins/company-brain/README.md +0 -37
  124. package/dist/plugins/company-brain/package.json +0 -33
  125. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
  126. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
  127. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
  128. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
  129. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
  130. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
  131. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
  132. package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
  133. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
  134. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
  135. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
  136. package/dist/plugins/company-brain/src/api/migration.ts +0 -665
  137. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
  138. package/dist/plugins/company-brain/src/index.ts +0 -62
  139. package/dist/plugins/company-brain/src/manifest.ts +0 -112
  140. package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
  141. package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
  142. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
  143. package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
  144. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
  145. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
  146. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
  147. package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
  148. package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
  149. 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
- }