@mindflight/mindbrain-personal-studio 0.6.1

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 (166) hide show
  1. package/.env.example +50 -0
  2. package/README.md +134 -0
  3. package/bin/mindbrain-studio.mjs +20 -0
  4. package/build/client/_app/immutable/assets/2.BXRKVU9n.css +1 -0
  5. package/build/client/_app/immutable/assets/2.BXRKVU9n.css.br +0 -0
  6. package/build/client/_app/immutable/assets/2.BXRKVU9n.css.gz +0 -0
  7. package/build/client/_app/immutable/chunks/-OSExkSk.js +2 -0
  8. package/build/client/_app/immutable/chunks/-OSExkSk.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/-OSExkSk.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/BIHI7g3E.js +1 -0
  11. package/build/client/_app/immutable/chunks/BIHI7g3E.js.br +2 -0
  12. package/build/client/_app/immutable/chunks/BIHI7g3E.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/BJB7pSge.js +1 -0
  14. package/build/client/_app/immutable/chunks/BJB7pSge.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/BJB7pSge.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/BNUkRZqg.js +1 -0
  17. package/build/client/_app/immutable/chunks/BNUkRZqg.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/BNUkRZqg.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/BP2JbIUZ.js +1 -0
  20. package/build/client/_app/immutable/chunks/BP2JbIUZ.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/BP2JbIUZ.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/BVrgUHq-.js +1 -0
  23. package/build/client/_app/immutable/chunks/BVrgUHq-.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/BVrgUHq-.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/BmeSanva.js +1 -0
  26. package/build/client/_app/immutable/chunks/BmeSanva.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/BmeSanva.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/BnkCgNCx.js +1 -0
  29. package/build/client/_app/immutable/chunks/BnkCgNCx.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/BnkCgNCx.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/C5KYKPa8.js +311 -0
  32. package/build/client/_app/immutable/chunks/C5KYKPa8.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/C5KYKPa8.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/CBDzfqXr.js +1 -0
  35. package/build/client/_app/immutable/chunks/CBDzfqXr.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/CBDzfqXr.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/Cp9dXlPT.js +1 -0
  38. package/build/client/_app/immutable/chunks/Cp9dXlPT.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/Cp9dXlPT.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/D0daF8Eh.js +2 -0
  41. package/build/client/_app/immutable/chunks/D0daF8Eh.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/D0daF8Eh.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/D4SoBmS0.js +1 -0
  44. package/build/client/_app/immutable/chunks/D4SoBmS0.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/D4SoBmS0.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/DpVrWha3.js +1 -0
  47. package/build/client/_app/immutable/chunks/DpVrWha3.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/DpVrWha3.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/DyXQogav.js +1 -0
  50. package/build/client/_app/immutable/chunks/DyXQogav.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/DyXQogav.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/Gs0cNFYG.js +1 -0
  53. package/build/client/_app/immutable/chunks/Gs0cNFYG.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/Gs0cNFYG.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/NXeHtWpC.js +1 -0
  56. package/build/client/_app/immutable/chunks/NXeHtWpC.js.br +0 -0
  57. package/build/client/_app/immutable/chunks/NXeHtWpC.js.gz +0 -0
  58. package/build/client/_app/immutable/chunks/i0D5dc1Y.js +1 -0
  59. package/build/client/_app/immutable/chunks/i0D5dc1Y.js.br +0 -0
  60. package/build/client/_app/immutable/chunks/i0D5dc1Y.js.gz +0 -0
  61. package/build/client/_app/immutable/chunks/nnWk1BBo.js +1 -0
  62. package/build/client/_app/immutable/chunks/nnWk1BBo.js.br +0 -0
  63. package/build/client/_app/immutable/chunks/nnWk1BBo.js.gz +0 -0
  64. package/build/client/_app/immutable/entry/app.CVz6aYsT.js +2 -0
  65. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.br +0 -0
  66. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.gz +0 -0
  67. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js +1 -0
  68. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.br +2 -0
  69. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.gz +0 -0
  70. package/build/client/_app/immutable/nodes/0.C2sXq0eH.js +1 -0
  71. package/build/client/_app/immutable/nodes/0.C2sXq0eH.js.br +0 -0
  72. package/build/client/_app/immutable/nodes/0.C2sXq0eH.js.gz +0 -0
  73. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js +1 -0
  74. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.br +0 -0
  75. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.gz +0 -0
  76. package/build/client/_app/immutable/nodes/2.D0pSa9ko.js +9 -0
  77. package/build/client/_app/immutable/nodes/2.D0pSa9ko.js.br +0 -0
  78. package/build/client/_app/immutable/nodes/2.D0pSa9ko.js.gz +0 -0
  79. package/build/client/_app/version.json +1 -0
  80. package/build/client/_app/version.json.br +0 -0
  81. package/build/client/_app/version.json.gz +0 -0
  82. package/build/client/robots.txt +3 -0
  83. package/build/client/robots.txt.br +0 -0
  84. package/build/client/robots.txt.gz +0 -0
  85. package/build/env.js +70 -0
  86. package/build/env.js.map +1 -0
  87. package/build/handler.js +26 -0
  88. package/build/handler.js.map +1 -0
  89. package/build/index.js +149 -0
  90. package/build/index.js.map +1 -0
  91. package/build/server/chunks/chunks/edgeLinkModes.js-CfRqrZtd.js +50 -0
  92. package/build/server/chunks/chunks/edgeLinkModes.js-CfRqrZtd.js.map +1 -0
  93. package/build/server/chunks/chunks/environment.js-Bf8x1V_n.js +16 -0
  94. package/build/server/chunks/chunks/environment.js-Bf8x1V_n.js.map +1 -0
  95. package/build/server/chunks/chunks/exports.js-DFR3riS9.js +217 -0
  96. package/build/server/chunks/chunks/exports.js-DFR3riS9.js.map +1 -0
  97. package/build/server/chunks/chunks/false.js-CRHihH2U.js +4 -0
  98. package/build/server/chunks/chunks/false.js-CRHihH2U.js.map +1 -0
  99. package/build/server/chunks/chunks/graphRepository.js-D2DARfVY.js +280 -0
  100. package/build/server/chunks/chunks/graphRepository.js-D2DARfVY.js.map +1 -0
  101. package/build/server/chunks/chunks/internal.js-D8EA_he2.js +119 -0
  102. package/build/server/chunks/chunks/internal.js-D8EA_he2.js.map +1 -0
  103. package/build/server/chunks/chunks/mindbrainClient.js-BFAJqRSL.js +108 -0
  104. package/build/server/chunks/chunks/mindbrainClient.js-BFAJqRSL.js.map +1 -0
  105. package/build/server/chunks/chunks/ontologyInspect.js-Dm3l7KgF.js +13 -0
  106. package/build/server/chunks/chunks/ontologyInspect.js-Dm3l7KgF.js.map +1 -0
  107. package/build/server/chunks/chunks/render-context.js-CTu5Wkzp.js +475 -0
  108. package/build/server/chunks/chunks/render-context.js-CTu5Wkzp.js.map +1 -0
  109. package/build/server/chunks/chunks/renderer.js-DsOhU1UZ.js +1243 -0
  110. package/build/server/chunks/chunks/renderer.js-DsOhU1UZ.js.map +1 -0
  111. package/build/server/chunks/chunks/root.js-DCnlE3XY.js +2815 -0
  112. package/build/server/chunks/chunks/root.js-DCnlE3XY.js.map +1 -0
  113. package/build/server/chunks/chunks/shared-server.js-DaWdgxVh.js +11 -0
  114. package/build/server/chunks/chunks/shared-server.js-DaWdgxVh.js.map +1 -0
  115. package/build/server/chunks/chunks/shared.js-DuDDTJVi.js +1338 -0
  116. package/build/server/chunks/chunks/shared.js-DuDDTJVi.js.map +1 -0
  117. package/build/server/chunks/chunks/utils.js-DGYJFmnf.js +39 -0
  118. package/build/server/chunks/chunks/utils.js-DGYJFmnf.js.map +1 -0
  119. package/build/server/chunks/entries/endpoints/api/brain/_...path_/_server.ts.js-MgPpKBks.js +82 -0
  120. package/build/server/chunks/entries/endpoints/api/brain/_...path_/_server.ts.js-MgPpKBks.js.map +1 -0
  121. package/build/server/chunks/entries/endpoints/api/brain/health/_server.ts.js-DnJQNmWV.js +19 -0
  122. package/build/server/chunks/entries/endpoints/api/brain/health/_server.ts.js-DnJQNmWV.js.map +1 -0
  123. package/build/server/chunks/entries/endpoints/api/graph/_server.ts.js-Bxo7qHVz.js +37 -0
  124. package/build/server/chunks/entries/endpoints/api/graph/_server.ts.js-Bxo7qHVz.js.map +1 -0
  125. package/build/server/chunks/entries/endpoints/api/graph/count/_server.ts.js-BajaLnqp.js +37 -0
  126. package/build/server/chunks/entries/endpoints/api/graph/count/_server.ts.js-BajaLnqp.js.map +1 -0
  127. package/build/server/chunks/entries/endpoints/api/graph/ontologies/_server.ts.js-BFq2mbbX.js +146 -0
  128. package/build/server/chunks/entries/endpoints/api/graph/ontologies/_server.ts.js-BFq2mbbX.js.map +1 -0
  129. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js +154 -0
  130. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js.map +1 -0
  131. package/build/server/chunks/entries/endpoints/api/projections/catalog/_server.ts.js-Ds1RymA0.js +351 -0
  132. package/build/server/chunks/entries/endpoints/api/projections/catalog/_server.ts.js-Ds1RymA0.js.map +1 -0
  133. package/build/server/chunks/entries/fallbacks/error.svelte.js-B7V4yZjo.js +51 -0
  134. package/build/server/chunks/entries/fallbacks/error.svelte.js-B7V4yZjo.js.map +1 -0
  135. package/build/server/chunks/entries/pages/_layout.svelte.js-CoC9BAeH.js +15 -0
  136. package/build/server/chunks/entries/pages/_layout.svelte.js-CoC9BAeH.js.map +1 -0
  137. package/build/server/chunks/entries/pages/_page.svelte.js-DMEbD3K6.js +891 -0
  138. package/build/server/chunks/entries/pages/_page.svelte.js-DMEbD3K6.js.map +1 -0
  139. package/build/server/chunks/handler-BjXBCd1c.js +1698 -0
  140. package/build/server/chunks/handler-BjXBCd1c.js.map +1 -0
  141. package/build/server/chunks/index.js-BJYcV8V7.js +4183 -0
  142. package/build/server/chunks/index.js-BJYcV8V7.js.map +1 -0
  143. package/build/server/chunks/manifest.js-Ddaot0P4.js +91 -0
  144. package/build/server/chunks/manifest.js-Ddaot0P4.js.map +1 -0
  145. package/build/server/chunks/nodes/0.js-87v83UX9.js +9 -0
  146. package/build/server/chunks/nodes/0.js-87v83UX9.js.map +1 -0
  147. package/build/server/chunks/nodes/1.js-BRigw_9J.js +9 -0
  148. package/build/server/chunks/nodes/1.js-BRigw_9J.js.map +1 -0
  149. package/build/server/chunks/nodes/2.js-D9-B_qyX.js +9 -0
  150. package/build/server/chunks/nodes/2.js-D9-B_qyX.js.map +1 -0
  151. package/build/shims.js +33 -0
  152. package/build/shims.js.map +1 -0
  153. package/fixtures/minimal-graph.sqlite +0 -0
  154. package/package.json +81 -0
  155. package/scripts/backend-sqlite.mjs +190 -0
  156. package/scripts/build-serenity-v6-concept-review-pack.mjs +493 -0
  157. package/scripts/build-serenity-v6-review-pack.mjs +479 -0
  158. package/scripts/create-serenity-production-v6.mjs +627 -0
  159. package/scripts/dev-sqlite.mjs +86 -0
  160. package/scripts/export-serenity-v6-backup.mjs +178 -0
  161. package/scripts/import-serenity-v6-user-decisions.mjs +543 -0
  162. package/scripts/lib/sqlite-runtime.mjs +197 -0
  163. package/scripts/materialize-serenity-v6-snapshots.mjs +675 -0
  164. package/scripts/publish-npm.mjs +134 -0
  165. package/scripts/seed-immeuble-projections.mjs +169 -0
  166. package/scripts/studio-sqlite.mjs +74 -0
@@ -0,0 +1,627 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const SOURCE_WORKSPACE = 'serenity-production-v5';
7
+ const TARGET_WORKSPACE = 'serenity-production-v6';
8
+ const REVIEW_WORKSPACES = ['serenity-v4', 'serenity-v4-s2'];
9
+ const DEFAULT_DB = '/home/dlamotte/.ghostcrab/databases/ghostcrab-serenity-v4-demos.sqlite';
10
+ const DEFAULT_OUT_DIR = 'docs/demo/export-audit/2026-06-26/serenity-production-v6-migration';
11
+ const ENTITY_OFFSET = 1_000_000;
12
+ const RELATION_OFFSET = 1_000_000;
13
+
14
+ const args = new Set(process.argv.slice(2));
15
+ const apply = args.has('--apply');
16
+ const dbPath = valueAfter('--db') ?? process.env.GHOSTCRAB_SQLITE_PATH ?? DEFAULT_DB;
17
+ const outDir = valueAfter('--out-dir') ?? DEFAULT_OUT_DIR;
18
+
19
+ function valueAfter(flag) {
20
+ const index = process.argv.indexOf(flag);
21
+ return index >= 0 ? process.argv[index + 1] : null;
22
+ }
23
+
24
+ function runSql(sql, { json = false, readonly = false } = {}) {
25
+ const argv = [];
26
+ if (readonly) argv.push('-readonly');
27
+ if (json) argv.push('-json');
28
+ argv.push(dbPath, sql);
29
+ const result = spawnSync('sqlite3', argv, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 128 });
30
+ if (result.status !== 0) {
31
+ throw new Error(result.stderr || result.stdout || `sqlite3 failed for ${sql.slice(0, 120)}`);
32
+ }
33
+ return json ? JSON.parse(result.stdout || '[]') : result.stdout;
34
+ }
35
+
36
+ function runSqlFile(sql) {
37
+ const result = spawnSync('sqlite3', [dbPath], {
38
+ encoding: 'utf8',
39
+ input: sql,
40
+ maxBuffer: 1024 * 1024 * 128
41
+ });
42
+ if (result.status !== 0) {
43
+ throw new Error(result.stderr || result.stdout || 'sqlite3 transaction failed');
44
+ }
45
+ return result.stdout;
46
+ }
47
+
48
+ function quote(value) {
49
+ if (value === null || value === undefined) return 'NULL';
50
+ if (typeof value === 'number') return Number.isFinite(value) ? String(value) : 'NULL';
51
+ return `'${String(value).replaceAll("'", "''")}'`;
52
+ }
53
+
54
+ function replaceText(value) {
55
+ if (value === null || value === undefined) return value;
56
+ return String(value)
57
+ .replaceAll('serenity-production-v5', TARGET_WORKSPACE)
58
+ .replaceAll('serenity_production_v5', 'serenity_production_v6')
59
+ .replaceAll('Serenity Production V5', 'Serenity Production V6');
60
+ }
61
+
62
+ function replaceOrPrefixId(value, prefix) {
63
+ const replaced = replaceText(value);
64
+ if (replaced === null || replaced === undefined) return replaced;
65
+ return replaced === String(value) ? `${prefix}:${TARGET_WORKSPACE}:${value}` : replaced;
66
+ }
67
+
68
+ function insert(table, columns, row) {
69
+ const values = columns.map((column) => quote(row[column])).join(', ');
70
+ return `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${values});`;
71
+ }
72
+
73
+ function rows(sql) {
74
+ return runSql(sql, { json: true, readonly: true });
75
+ }
76
+
77
+ function cloneRows(table, columns, sourceRows, mapRow) {
78
+ return sourceRows.map((row, index) => insert(table, columns, mapRow(row, index)));
79
+ }
80
+
81
+ function objectMapValues(obj, fn) {
82
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)]));
83
+ }
84
+
85
+ function cloneSimpleTextRow(row, overrides = {}) {
86
+ return { ...objectMapValues(row, replaceText), ...overrides };
87
+ }
88
+
89
+ function sqlIn(values) {
90
+ return values.map(quote).join(', ');
91
+ }
92
+
93
+ function ensureNoTargetWorkspace() {
94
+ const existing = rows(
95
+ `SELECT workspace_id FROM workspaces WHERE workspace_id = ${quote(TARGET_WORKSPACE)} OR id = ${quote(TARGET_WORKSPACE)}`
96
+ );
97
+ if (existing.length > 0) {
98
+ throw new Error(`${TARGET_WORKSPACE} already exists; refusing to overwrite`);
99
+ }
100
+ }
101
+
102
+ function readSourceData() {
103
+ const ontologyIds = rows(
104
+ `SELECT ontology_id FROM ontologies WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY ontology_id`
105
+ ).map((row) => row.ontology_id);
106
+ const artifactIds = rows(
107
+ `SELECT artifact_id FROM mindbrain_answer_artifacts WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY artifact_id`
108
+ ).map((row) => row.artifact_id);
109
+ return {
110
+ workspaces: rows(`SELECT * FROM workspaces WHERE workspace_id = ${quote(SOURCE_WORKSPACE)}`),
111
+ ontologies: rows(`SELECT * FROM ontologies WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY ontology_id`),
112
+ ontologyEntityTypes: rows(
113
+ `SELECT * FROM ontology_entity_types WHERE ontology_id IN (${sqlIn(ontologyIds)}) ORDER BY ontology_id, entity_type`
114
+ ),
115
+ ontologyEdgeTypes: rows(
116
+ `SELECT * FROM ontology_edge_types WHERE ontology_id IN (${sqlIn(ontologyIds)}) ORDER BY ontology_id, edge_type`
117
+ ),
118
+ ontologyTriples: rows(
119
+ `SELECT * FROM ontology_triples_raw WHERE ontology_id IN (${sqlIn(ontologyIds)}) ORDER BY ontology_id, triple_index`
120
+ ),
121
+ agentFacts: rows(`SELECT * FROM agent_facts WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY id`),
122
+ graphEntities: rows(`SELECT * FROM graph_entity WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY entity_id`),
123
+ graphRelations: rows(`SELECT * FROM graph_relation WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY relation_id`),
124
+ answerArtifacts: rows(
125
+ `SELECT * FROM mindbrain_answer_artifacts WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} ORDER BY artifact_id`
126
+ ),
127
+ answerEvents:
128
+ artifactIds.length === 0
129
+ ? []
130
+ : rows(
131
+ `SELECT * FROM mindbrain_answer_events WHERE artifact_id IN (${sqlIn(artifactIds)}) ORDER BY event_id`
132
+ )
133
+ };
134
+ }
135
+
136
+ function buildCloneSql(data) {
137
+ const statements = [];
138
+ const entityIdMap = new Map();
139
+ for (const entity of data.graphEntities) entityIdMap.set(entity.entity_id, entity.entity_id + ENTITY_OFFSET);
140
+ const relationIdMap = new Map();
141
+ for (const relation of data.graphRelations) relationIdMap.set(relation.relation_id, relation.relation_id + RELATION_OFFSET);
142
+
143
+ statements.push(
144
+ ...cloneRows(
145
+ 'workspaces',
146
+ [
147
+ 'id',
148
+ 'workspace_id',
149
+ 'label',
150
+ 'pg_schema',
151
+ 'description',
152
+ 'created_by',
153
+ 'status',
154
+ 'domain_profile',
155
+ 'domain_profile_json',
156
+ 'created_at',
157
+ 'updated_at'
158
+ ],
159
+ data.workspaces,
160
+ (row) =>
161
+ cloneSimpleTextRow(row, {
162
+ id: TARGET_WORKSPACE,
163
+ workspace_id: TARGET_WORKSPACE,
164
+ label: 'Serenity Production V6',
165
+ description:
166
+ 'Clone of Serenity Production V5 prepared for SOP-governed model extension; no V4 relations promoted automatically.'
167
+ })
168
+ )
169
+ );
170
+
171
+ statements.push(
172
+ ...cloneRows(
173
+ 'ontologies',
174
+ ['ontology_id', 'workspace_id', 'name', 'version', 'frozen', 'source_kind', 'metadata_json', 'created_at'],
175
+ data.ontologies,
176
+ (row) => cloneSimpleTextRow(row, { workspace_id: TARGET_WORKSPACE })
177
+ )
178
+ );
179
+
180
+ statements.push(
181
+ ...cloneRows(
182
+ 'ontology_entity_types',
183
+ ['ontology_id', 'entity_type', 'label', 'metadata_json'],
184
+ data.ontologyEntityTypes,
185
+ (row) => cloneSimpleTextRow(row)
186
+ )
187
+ );
188
+
189
+ statements.push(
190
+ ...cloneRows(
191
+ 'ontology_edge_types',
192
+ ['ontology_id', 'edge_type', 'directed', 'source_entity_type', 'target_entity_type', 'metadata_json'],
193
+ data.ontologyEdgeTypes,
194
+ (row) => cloneSimpleTextRow(row)
195
+ )
196
+ );
197
+
198
+ statements.push(
199
+ ...cloneRows(
200
+ 'ontology_triples_raw',
201
+ [
202
+ 'ontology_id',
203
+ 'triple_index',
204
+ 'subject_kind',
205
+ 'subject',
206
+ 'predicate',
207
+ 'object_kind',
208
+ 'object_value',
209
+ 'object_datatype',
210
+ 'object_language',
211
+ 'source_line',
212
+ 'metadata_json'
213
+ ],
214
+ data.ontologyTriples,
215
+ (row) => cloneSimpleTextRow(row)
216
+ )
217
+ );
218
+
219
+ const maxDocId =
220
+ rows(`SELECT COALESCE(MAX(doc_id), 0) AS max_doc_id FROM agent_facts`)[0]?.max_doc_id ?? 0;
221
+ statements.push(
222
+ ...cloneRows(
223
+ 'agent_facts',
224
+ [
225
+ 'id',
226
+ 'schema_id',
227
+ 'content',
228
+ 'facets',
229
+ 'facets_json',
230
+ 'embedding_blob',
231
+ 'embedding',
232
+ 'created_by',
233
+ 'created_at',
234
+ 'created_at_unix',
235
+ 'updated_at',
236
+ 'updated_at_unix',
237
+ 'version',
238
+ 'supersedes',
239
+ 'valid_from_unix',
240
+ 'valid_until_unix',
241
+ 'workspace_id',
242
+ 'source_ref',
243
+ 'doc_id'
244
+ ],
245
+ data.agentFacts,
246
+ (row, index) =>
247
+ cloneSimpleTextRow(row, {
248
+ id: replaceOrPrefixId(row.id, 'agent-fact'),
249
+ workspace_id: TARGET_WORKSPACE,
250
+ source_ref: row.source_ref ? replaceOrPrefixId(row.source_ref, 'source-ref') : null,
251
+ doc_id: maxDocId + index + 1
252
+ })
253
+ )
254
+ );
255
+
256
+ statements.push(
257
+ ...cloneRows(
258
+ 'graph_entity',
259
+ [
260
+ 'entity_id',
261
+ 'workspace_id',
262
+ 'entity_type',
263
+ 'name',
264
+ 'confidence',
265
+ 'deprecated_at',
266
+ 'metadata_json',
267
+ 'created_at_unix'
268
+ ],
269
+ data.graphEntities,
270
+ (row) =>
271
+ cloneSimpleTextRow(row, {
272
+ entity_id: entityIdMap.get(row.entity_id),
273
+ workspace_id: TARGET_WORKSPACE
274
+ })
275
+ )
276
+ );
277
+
278
+ statements.push(
279
+ ...cloneRows(
280
+ 'graph_relation',
281
+ [
282
+ 'relation_id',
283
+ 'workspace_id',
284
+ 'relation_type',
285
+ 'source_id',
286
+ 'target_id',
287
+ 'valid_from_unix',
288
+ 'valid_to_unix',
289
+ 'confidence',
290
+ 'deprecated_at',
291
+ 'run_id',
292
+ 'patch_id',
293
+ 'metadata_json',
294
+ 'created_at_unix'
295
+ ],
296
+ data.graphRelations,
297
+ (row) =>
298
+ cloneSimpleTextRow(row, {
299
+ relation_id: relationIdMap.get(row.relation_id),
300
+ workspace_id: TARGET_WORKSPACE,
301
+ source_id: entityIdMap.get(row.source_id),
302
+ target_id: entityIdMap.get(row.target_id)
303
+ })
304
+ )
305
+ );
306
+
307
+ statements.push(
308
+ ...cloneRows(
309
+ 'mindbrain_answer_artifacts',
310
+ [
311
+ 'artifact_id',
312
+ 'slug',
313
+ 'workspace_id',
314
+ 'agent_id',
315
+ 'scope',
316
+ 'artifact_kind',
317
+ 'public_label_key',
318
+ 'public_label',
319
+ 'lifecycle',
320
+ 'state',
321
+ 'current_version',
322
+ 'payload_json',
323
+ 'legacy_ref',
324
+ 'created_at_unix',
325
+ 'updated_at_unix'
326
+ ],
327
+ data.answerArtifacts,
328
+ (row) => cloneSimpleTextRow(row, { workspace_id: TARGET_WORKSPACE })
329
+ )
330
+ );
331
+
332
+ statements.push(
333
+ ...cloneRows(
334
+ 'mindbrain_answer_events',
335
+ [
336
+ 'event_id',
337
+ 'artifact_id',
338
+ 'event_kind',
339
+ 'from_version',
340
+ 'to_version',
341
+ 'signal_json',
342
+ 'created_at_unix'
343
+ ],
344
+ data.answerEvents,
345
+ (row) =>
346
+ cloneSimpleTextRow(row, {
347
+ event_id: replaceOrPrefixId(row.event_id, 'answer-event')
348
+ })
349
+ )
350
+ );
351
+
352
+ return { entityIdMap, relationIdMap, sql: statements.join('\n') };
353
+ }
354
+
355
+ function parseJsonField(row, key) {
356
+ try {
357
+ return JSON.parse(row[key] || '{}');
358
+ } catch {
359
+ return {};
360
+ }
361
+ }
362
+
363
+ function stableSources(sourceString) {
364
+ return String(sourceString || '')
365
+ .split(',')
366
+ .filter(Boolean)
367
+ .sort();
368
+ }
369
+
370
+ function relationDecision(candidate, supportedByScenarios) {
371
+ const relation = candidate.relation_type;
372
+ const sources = stableSources(candidate.sources);
373
+ const hasScenario = supportedByScenarios.has(relation);
374
+ const hasOnlyWeakSources =
375
+ sources.length > 0 &&
376
+ sources.every((source) =>
377
+ ['fake_data', 'serenity_v4_fake_data_smoke', 'snapshot_claims_evidence_matrix'].includes(source)
378
+ );
379
+ const isGeneric = ['referencer', 'supported_by'].includes(relation);
380
+ if (hasScenario) return 'review_required';
381
+ if (isGeneric) return 'reject_generic_relation';
382
+ if (hasOnlyWeakSources) return 'reject_weak_source_only';
383
+ return 'reject_no_sop_or_scenario';
384
+ }
385
+
386
+ function buildReviewReports() {
387
+ const scenarioRows = rows(
388
+ `SELECT artifact_id, slug, scope, public_label, payload_json
389
+ FROM mindbrain_answer_artifacts
390
+ WHERE workspace_id = ${quote(SOURCE_WORKSPACE)}
391
+ AND artifact_kind = 'analysis_plan'
392
+ ORDER BY slug`
393
+ );
394
+ const scenarios = scenarioRows.map((row) => {
395
+ const payload = parseJsonField(row, 'payload_json');
396
+ return {
397
+ artifact_id: row.artifact_id,
398
+ slug: row.slug,
399
+ scope: row.scope,
400
+ public_label: row.public_label,
401
+ workflow: String(payload.description || '').match(/workflow=([^;]+)/)?.[1] ?? null,
402
+ projection_id: payload.projection_id ?? null,
403
+ required_edges: Array.isArray(payload.required_edges) ? payload.required_edges : [],
404
+ required_schemas: Array.isArray(payload.required_schemas) ? payload.required_schemas : [],
405
+ required_facets: Array.isArray(payload.required_facets) ? payload.required_facets : [],
406
+ scenario_refs: Array.isArray(payload.scenario_refs) ? payload.scenario_refs : []
407
+ };
408
+ });
409
+ const supportedByEdge = new Map();
410
+ for (const scenario of scenarios) {
411
+ for (const edge of scenario.required_edges) {
412
+ if (!supportedByEdge.has(edge)) supportedByEdge.set(edge, []);
413
+ supportedByEdge.get(edge).push({
414
+ artifact_id: scenario.artifact_id,
415
+ slug: scenario.slug,
416
+ scope: scenario.scope,
417
+ workflow: scenario.workflow,
418
+ scenario_refs: scenario.scenario_refs
419
+ });
420
+ }
421
+ }
422
+
423
+ const relationCandidates = rows(
424
+ `SELECT workspace_id, relation_type, COUNT(*) AS instance_count,
425
+ group_concat(DISTINCT json_extract(metadata_json, '$.source')) AS sources,
426
+ group_concat(DISTINCT json_extract(metadata_json, '$.linkml_class')) AS linkml_classes
427
+ FROM graph_relation
428
+ WHERE workspace_id IN (${sqlIn(REVIEW_WORKSPACES)})
429
+ GROUP BY workspace_id, relation_type
430
+ ORDER BY workspace_id, instance_count DESC, relation_type`
431
+ ).map((candidate) => ({
432
+ ...candidate,
433
+ decision: relationDecision(candidate, supportedByEdge),
434
+ supported_scenarios: supportedByEdge.get(candidate.relation_type) ?? [],
435
+ reason:
436
+ relationDecision(candidate, supportedByEdge) === 'review_required'
437
+ ? 'Referenced by at least one V5 analysis_plan required_edges entry; requires human SOP review before promotion.'
438
+ : relationDecision(candidate, supportedByEdge) === 'reject_generic_relation'
439
+ ? 'Relation is too generic to promote without a precise SOP.'
440
+ : relationDecision(candidate, supportedByEdge) === 'reject_weak_source_only'
441
+ ? 'Relation only appears in weak/generated smoke data sources.'
442
+ : 'No V5 SOP/scenario required_edges evidence found.'
443
+ }));
444
+
445
+ const acceptedRelations = relationCandidates.filter((candidate) => candidate.decision === 'promote');
446
+ const reviewRequiredRelations = relationCandidates.filter(
447
+ (candidate) => candidate.decision === 'review_required'
448
+ );
449
+ const rejectedRelations = relationCandidates.filter((candidate) => candidate.decision.startsWith('reject_'));
450
+
451
+ const v5SchemaRows = rows(
452
+ `SELECT COALESCE(NULLIF(json_extract(facets_json, '$.schema_id'), ''),
453
+ NULLIF(json_extract(content, '$.schema_id'), ''),
454
+ id) AS schema_id
455
+ FROM agent_facts
456
+ WHERE workspace_id = ${quote(SOURCE_WORKSPACE)} AND schema_id = 'mindbrain:schema'`
457
+ ).map((row) => row.schema_id);
458
+ const v5SchemaSet = new Set(v5SchemaRows);
459
+ const requiredSchemas = new Map();
460
+ for (const scenario of scenarios) {
461
+ for (const schema of scenario.required_schemas) {
462
+ const candidates = [
463
+ schema,
464
+ `${SOURCE_WORKSPACE}:${schema}`,
465
+ `${TARGET_WORKSPACE}:${schema}`,
466
+ replaceText(schema)
467
+ ];
468
+ const existsInV5 = candidates.some((candidate) => v5SchemaSet.has(candidate));
469
+ if (!existsInV5) {
470
+ if (!requiredSchemas.has(schema)) requiredSchemas.set(schema, []);
471
+ requiredSchemas.get(schema).push({
472
+ artifact_id: scenario.artifact_id,
473
+ slug: scenario.slug,
474
+ scope: scenario.scope,
475
+ workflow: scenario.workflow
476
+ });
477
+ }
478
+ }
479
+ }
480
+ const missingSchemasRequiredBySop = [...requiredSchemas.entries()]
481
+ .map(([schema_id, required_by]) => ({ schema_id, required_by }))
482
+ .sort((a, b) => a.schema_id.localeCompare(b.schema_id));
483
+
484
+ const summary = {
485
+ source_workspace: SOURCE_WORKSPACE,
486
+ target_workspace: TARGET_WORKSPACE,
487
+ review_workspaces: REVIEW_WORKSPACES,
488
+ policy: {
489
+ auto_promote_v4_relations: false,
490
+ default_relation_decision: 'reject unless supported by SOP/scenario and manually reviewed',
491
+ sop_source: 'serenity-production-v5 analysis_plan required_edges/workflow/scenario payloads'
492
+ },
493
+ counts: {
494
+ scenarios_reviewed: scenarios.length,
495
+ relation_candidates: relationCandidates.length,
496
+ accepted_relations: acceptedRelations.length,
497
+ review_required_relations: reviewRequiredRelations.length,
498
+ rejected_relations: rejectedRelations.length,
499
+ missing_schemas_required_by_sop: missingSchemasRequiredBySop.length
500
+ }
501
+ };
502
+
503
+ return {
504
+ summary,
505
+ scenarioSopMap: scenarios,
506
+ acceptedRelations,
507
+ reviewRequiredRelations,
508
+ rejectedRelations,
509
+ missingSchemasRequiredBySop
510
+ };
511
+ }
512
+
513
+ function writeReports(reports) {
514
+ mkdirSync(outDir, { recursive: true });
515
+ for (const [name, value] of Object.entries({
516
+ 'migration-summary.json': reports.summary,
517
+ 'scenario_sop_map.json': reports.scenarioSopMap,
518
+ 'accepted_relations.json': reports.acceptedRelations,
519
+ 'review_required_relations.json': reports.reviewRequiredRelations,
520
+ 'rejected_relations.json': reports.rejectedRelations,
521
+ 'missing_schemas_required_by_sop.json': reports.missingSchemasRequiredBySop
522
+ })) {
523
+ writeFileSync(join(outDir, name), `${JSON.stringify(value, null, 2)}\n`);
524
+ }
525
+ }
526
+
527
+ function writeMarkdownReport(reports, applied) {
528
+ const md = `# Serenity Production V6 Migration
529
+
530
+ Generated with \`scripts/create-serenity-production-v6.mjs${applied ? ' --apply' : ''}\`.
531
+
532
+ ## Policy
533
+
534
+ - V6 starts as a clone of \`${SOURCE_WORKSPACE}\`.
535
+ - V4/V4-S2 relations are candidates only.
536
+ - No V4 relation is promoted automatically.
537
+ - A relation must be tied to a SOP/scenario before promotion.
538
+
539
+ ## Counts
540
+
541
+ | Metric | Count |
542
+ |---|---:|
543
+ | V5 scenarios reviewed | ${reports.summary.counts.scenarios_reviewed} |
544
+ | V4/V4-S2 relation candidates | ${reports.summary.counts.relation_candidates} |
545
+ | Accepted relations | ${reports.summary.counts.accepted_relations} |
546
+ | Review-required relations | ${reports.summary.counts.review_required_relations} |
547
+ | Rejected relations | ${reports.summary.counts.rejected_relations} |
548
+ | Missing schemas required by SOP | ${reports.summary.counts.missing_schemas_required_by_sop} |
549
+
550
+ ## Files
551
+
552
+ - \`migration-summary.json\`
553
+ - \`scenario_sop_map.json\`
554
+ - \`accepted_relations.json\`
555
+ - \`review_required_relations.json\`
556
+ - \`rejected_relations.json\`
557
+ - \`missing_schemas_required_by_sop.json\`
558
+
559
+ ## Apply Status
560
+
561
+ ${applied ? `\`${TARGET_WORKSPACE}\` was created in the SQLite database.` : 'Dry run only; no database writes were performed.'}
562
+ `;
563
+ writeFileSync(join(outDir, 'README.md'), md);
564
+ }
565
+
566
+ function countsFor(workspaceId) {
567
+ const [countRow] = rows(`WITH ws(workspace_id) AS (VALUES(${quote(workspaceId)}))
568
+ SELECT ws.workspace_id AS workspace,
569
+ (SELECT COUNT(*) FROM graph_entity ge WHERE ge.workspace_id=ws.workspace_id) AS graph_entities,
570
+ (SELECT COUNT(*) FROM graph_relation gr WHERE gr.workspace_id=ws.workspace_id) AS graph_relations,
571
+ (SELECT COUNT(*) FROM ontologies o WHERE o.workspace_id=ws.workspace_id) AS ontologies,
572
+ (SELECT COUNT(*) FROM ontology_entity_types et WHERE et.ontology_id IN (SELECT ontology_id FROM ontologies WHERE workspace_id=ws.workspace_id)) AS ontology_entity_types,
573
+ (SELECT COUNT(*) FROM ontology_edge_types edge WHERE edge.ontology_id IN (SELECT ontology_id FROM ontologies WHERE workspace_id=ws.workspace_id)) AS ontology_edge_types,
574
+ (SELECT COUNT(*) FROM ontology_triples_raw tr WHERE tr.ontology_id IN (SELECT ontology_id FROM ontologies WHERE workspace_id=ws.workspace_id)) AS ontology_triples,
575
+ (SELECT COUNT(*) FROM agent_facts af WHERE af.workspace_id=ws.workspace_id) AS agent_facts,
576
+ (SELECT COUNT(*) FROM mindbrain_answer_artifacts a WHERE a.workspace_id=ws.workspace_id) AS artifacts
577
+ FROM ws`);
578
+ return countRow;
579
+ }
580
+
581
+ function backupDatabase() {
582
+ const backupPath = `/tmp/${TARGET_WORKSPACE}-precreate-${new Date().toISOString().replaceAll(':', '-')}.sqlite`;
583
+ const result = spawnSync('sqlite3', [dbPath, `.backup '${backupPath}'`], { encoding: 'utf8' });
584
+ if (result.status !== 0) throw new Error(result.stderr || result.stdout || 'sqlite backup failed');
585
+ return backupPath;
586
+ }
587
+
588
+ function main() {
589
+ ensureNoTargetWorkspace();
590
+ const sourceData = readSourceData();
591
+ const clone = buildCloneSql(sourceData);
592
+ const reports = buildReviewReports();
593
+ reports.summary.source_counts = countsFor(SOURCE_WORKSPACE);
594
+ reports.summary.target_counts_before = countsFor(TARGET_WORKSPACE);
595
+ reports.summary.clone_plan = {
596
+ entity_id_offset: ENTITY_OFFSET,
597
+ relation_id_offset: RELATION_OFFSET,
598
+ sql_statement_count: clone.sql.split('\n').filter(Boolean).length
599
+ };
600
+ writeReports(reports);
601
+ writeMarkdownReport(reports, false);
602
+
603
+ if (!apply) {
604
+ console.log(`dry-run ok; wrote reports to ${outDir}`);
605
+ console.log(JSON.stringify(reports.summary, null, 2));
606
+ return;
607
+ }
608
+
609
+ const backupPath = backupDatabase();
610
+ const tx = `.bail on\nPRAGMA foreign_keys=ON;\nBEGIN IMMEDIATE;\n${clone.sql}\nCOMMIT;\n`;
611
+ runSqlFile(tx);
612
+ const targetCounts = countsFor(TARGET_WORKSPACE);
613
+ reports.summary.target_counts_after = targetCounts;
614
+ reports.summary.backup_path = backupPath;
615
+ writeReports(reports);
616
+ writeMarkdownReport(reports, true);
617
+ console.log(`created ${TARGET_WORKSPACE}`);
618
+ console.log(`backup: ${backupPath}`);
619
+ console.log(JSON.stringify(reports.summary, null, 2));
620
+ }
621
+
622
+ try {
623
+ main();
624
+ } catch (error) {
625
+ console.error(error instanceof Error ? error.message : error);
626
+ process.exit(1);
627
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import {
6
+ healthOk,
7
+ loadDotEnv,
8
+ readRuntimeJson,
9
+ resolveStudioDataTarget,
10
+ runtimePathsForSqlite,
11
+ studioRootFromScript
12
+ } from "./lib/sqlite-runtime.mjs";
13
+
14
+ const studioRoot = studioRootFromScript(import.meta.url);
15
+ loadDotEnv(studioRoot);
16
+ const { sqlitePath, passthrough, mode, fallbackReason } = resolveStudioDataTarget(studioRoot, process.argv.slice(2));
17
+ const paths = runtimePathsForSqlite(studioRoot, sqlitePath);
18
+ const explicitUrl = process.env.MINDBRAIN_HTTP_URL?.trim() || process.env.GHOSTCRAB_MINDBRAIN_URL?.trim();
19
+ const runtime = readRuntimeJson(paths.jsonPath);
20
+ const baseUrl = explicitUrl || runtime?.base_url;
21
+
22
+ if (!existsSync(sqlitePath)) {
23
+ console.error(`error: SQLite not found at ${sqlitePath}`);
24
+ console.error("Pass --sqlite /path/to/db.sqlite, or set GHOSTCRAB_SQLITE_PATH.");
25
+ process.exit(1);
26
+ }
27
+
28
+ if (fallbackReason) {
29
+ console.error(`==> ${fallbackReason}; using packaged demo fixture.`);
30
+ }
31
+
32
+ if (mode === "sqlite-demo") {
33
+ console.error(`==> Studio dev demo | SQLite: ${sqlitePath}`);
34
+ const child = spawn(process.execPath, ["./node_modules/vite/bin/vite.js", "dev", ...passthrough], {
35
+ cwd: studioRoot,
36
+ env: {
37
+ ...process.env,
38
+ GHOSTCRAB_SQLITE_PATH: sqlitePath,
39
+ DATA_SOURCE: "sqlite-demo",
40
+ MINDBRAIN_RUNTIME_SOURCE: "demo-fixture"
41
+ },
42
+ stdio: "inherit"
43
+ });
44
+ child.on("exit", (code, signal) => {
45
+ process.exitCode = code ?? (signal ? 128 : 0);
46
+ });
47
+ } else {
48
+ if (!baseUrl) {
49
+ console.error(`error: no backend runtime found at ${paths.jsonPath}`);
50
+ console.error(`Run: pnpm backend -- --sqlite "${sqlitePath}"`);
51
+ process.exit(1);
52
+ }
53
+
54
+ if (!explicitUrl && runtime?.sqlite_path !== sqlitePath) {
55
+ console.error("error: backend runtime points at a different SQLite database");
56
+ console.error(` runtime: ${runtime?.sqlite_path ?? "unknown"}`);
57
+ console.error(` expected: ${sqlitePath}`);
58
+ process.exit(1);
59
+ }
60
+
61
+ if (!(await healthOk(baseUrl, 1500))) {
62
+ console.error(`error: GhostCrab backend is not healthy at ${baseUrl}`);
63
+ console.error(`Run: pnpm backend -- --sqlite "${sqlitePath}"`);
64
+ process.exit(1);
65
+ }
66
+
67
+ console.error(`==> Studio dev | SQLite: ${sqlitePath} | Backend: ${baseUrl}`);
68
+
69
+ const viteArgs = ["./node_modules/vite/bin/vite.js", "dev", ...passthrough];
70
+ const child = spawn(process.execPath, viteArgs, {
71
+ cwd: studioRoot,
72
+ env: {
73
+ ...process.env,
74
+ GHOSTCRAB_SQLITE_PATH: sqlitePath,
75
+ DATA_SOURCE: "brain",
76
+ MINDBRAIN_HTTP_URL: baseUrl,
77
+ MINDBRAIN_RUNTIME_SOURCE: explicitUrl ? "env" : "runtime-json",
78
+ MINDBRAIN_RUNTIME_PATH: explicitUrl ? "" : paths.jsonPath
79
+ },
80
+ stdio: "inherit"
81
+ });
82
+
83
+ child.on("exit", (code, signal) => {
84
+ process.exitCode = code ?? (signal ? 128 : 0);
85
+ });
86
+ }