thinkwork-cli 0.12.15 → 0.12.17

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