@mindflight/mindbrain-personal-studio 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/build/client/_app/immutable/chunks/D0UIlUGZ.js +1 -0
- package/build/client/_app/immutable/chunks/D0UIlUGZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/D0UIlUGZ.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CVz6aYsT.js → app.CR-imLox.js} +2 -2
- package/build/client/_app/immutable/entry/app.CR-imLox.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CR-imLox.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js +1 -0
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js.br +2 -0
- package/build/client/_app/immutable/entry/start.DV-AjeAB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BBtxY46Q.js → 1.CTEedoSY.js} +1 -1
- package/build/client/_app/immutable/nodes/1.CTEedoSY.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.CTEedoSY.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +4 -4
- package/build/index.js +4 -4
- package/build/server/chunks/chunks/{internal.js-D8EA_he2.js → internal.js-WOmQXGMa.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-D8EA_he2.js.map → internal.js-WOmQXGMa.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/{_server.ts.js-Dyfsc-VA.js → _server.ts.js-CAsXxBRq.js} +7 -2
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-CAsXxBRq.js.map +1 -0
- package/build/server/chunks/{handler-BjXBCd1c.js → handler-BIDedSZq.js} +3 -3
- package/build/server/chunks/{handler-BjXBCd1c.js.map → handler-BIDedSZq.js.map} +1 -1
- package/build/server/chunks/{index.js-BJYcV8V7.js → index.js-YVPJa0so.js} +2 -2
- package/build/server/chunks/{index.js-BJYcV8V7.js.map → index.js-YVPJa0so.js.map} +1 -1
- package/build/server/chunks/{manifest.js-Ddaot0P4.js → manifest.js-aGRKuiqF.js} +4 -4
- package/build/server/chunks/{manifest.js-Ddaot0P4.js.map → manifest.js-aGRKuiqF.js.map} +1 -1
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js → 1.js-Dhh3ErZY.js} +2 -2
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js.map → 1.js-Dhh3ErZY.js.map} +1 -1
- package/package.json +6 -2
- package/build/client/_app/immutable/chunks/BmeSanva.js +0 -1
- package/build/client/_app/immutable/chunks/BmeSanva.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmeSanva.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CVz6aYsT.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CVz6aYsT.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js +0 -1
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.br +0 -2
- package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.gz +0 -0
- package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js.map +0 -1
- package/scripts/build-serenity-v6-concept-review-pack.mjs +0 -493
- package/scripts/build-serenity-v6-review-pack.mjs +0 -479
- package/scripts/create-serenity-production-v6.mjs +0 -627
- package/scripts/export-serenity-v6-backup.mjs +0 -178
- package/scripts/import-serenity-v6-user-decisions.mjs +0 -543
- package/scripts/materialize-serenity-v6-snapshots.mjs +0 -675
|
@@ -1,627 +0,0 @@
|
|
|
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
|
-
}
|