@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.
Files changed (48) hide show
  1. package/README.md +2 -2
  2. package/build/client/_app/immutable/chunks/D0UIlUGZ.js +1 -0
  3. package/build/client/_app/immutable/chunks/D0UIlUGZ.js.br +0 -0
  4. package/build/client/_app/immutable/chunks/D0UIlUGZ.js.gz +0 -0
  5. package/build/client/_app/immutable/entry/{app.CVz6aYsT.js → app.CR-imLox.js} +2 -2
  6. package/build/client/_app/immutable/entry/app.CR-imLox.js.br +0 -0
  7. package/build/client/_app/immutable/entry/app.CR-imLox.js.gz +0 -0
  8. package/build/client/_app/immutable/entry/start.DV-AjeAB.js +1 -0
  9. package/build/client/_app/immutable/entry/start.DV-AjeAB.js.br +2 -0
  10. package/build/client/_app/immutable/entry/start.DV-AjeAB.js.gz +0 -0
  11. package/build/client/_app/immutable/nodes/{1.BBtxY46Q.js → 1.CTEedoSY.js} +1 -1
  12. package/build/client/_app/immutable/nodes/1.CTEedoSY.js.br +0 -0
  13. package/build/client/_app/immutable/nodes/1.CTEedoSY.js.gz +0 -0
  14. package/build/client/_app/version.json +1 -1
  15. package/build/client/_app/version.json.br +0 -0
  16. package/build/client/_app/version.json.gz +0 -0
  17. package/build/handler.js +4 -4
  18. package/build/index.js +4 -4
  19. package/build/server/chunks/chunks/{internal.js-D8EA_he2.js → internal.js-WOmQXGMa.js} +2 -2
  20. package/build/server/chunks/chunks/{internal.js-D8EA_he2.js.map → internal.js-WOmQXGMa.js.map} +1 -1
  21. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/{_server.ts.js-Dyfsc-VA.js → _server.ts.js-CAsXxBRq.js} +7 -2
  22. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-CAsXxBRq.js.map +1 -0
  23. package/build/server/chunks/{handler-BjXBCd1c.js → handler-BIDedSZq.js} +3 -3
  24. package/build/server/chunks/{handler-BjXBCd1c.js.map → handler-BIDedSZq.js.map} +1 -1
  25. package/build/server/chunks/{index.js-BJYcV8V7.js → index.js-YVPJa0so.js} +2 -2
  26. package/build/server/chunks/{index.js-BJYcV8V7.js.map → index.js-YVPJa0so.js.map} +1 -1
  27. package/build/server/chunks/{manifest.js-Ddaot0P4.js → manifest.js-aGRKuiqF.js} +4 -4
  28. package/build/server/chunks/{manifest.js-Ddaot0P4.js.map → manifest.js-aGRKuiqF.js.map} +1 -1
  29. package/build/server/chunks/nodes/{1.js-BRigw_9J.js → 1.js-Dhh3ErZY.js} +2 -2
  30. package/build/server/chunks/nodes/{1.js-BRigw_9J.js.map → 1.js-Dhh3ErZY.js.map} +1 -1
  31. package/package.json +6 -2
  32. package/build/client/_app/immutable/chunks/BmeSanva.js +0 -1
  33. package/build/client/_app/immutable/chunks/BmeSanva.js.br +0 -0
  34. package/build/client/_app/immutable/chunks/BmeSanva.js.gz +0 -0
  35. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.br +0 -0
  36. package/build/client/_app/immutable/entry/app.CVz6aYsT.js.gz +0 -0
  37. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js +0 -1
  38. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.br +0 -2
  39. package/build/client/_app/immutable/entry/start.Bt5tVOz8.js.gz +0 -0
  40. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.br +0 -0
  41. package/build/client/_app/immutable/nodes/1.BBtxY46Q.js.gz +0 -0
  42. package/build/server/chunks/entries/endpoints/api/graph/schema-registry/_server.ts.js-Dyfsc-VA.js.map +0 -1
  43. package/scripts/build-serenity-v6-concept-review-pack.mjs +0 -493
  44. package/scripts/build-serenity-v6-review-pack.mjs +0 -479
  45. package/scripts/create-serenity-production-v6.mjs +0 -627
  46. package/scripts/export-serenity-v6-backup.mjs +0 -178
  47. package/scripts/import-serenity-v6-user-decisions.mjs +0 -543
  48. package/scripts/materialize-serenity-v6-snapshots.mjs +0 -675
@@ -1,675 +0,0 @@
1
- #!/usr/bin/env node
2
- import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { spawnSync } from 'node:child_process';
5
-
6
- const WORKSPACE = 'serenity-production-v6';
7
- const DEFAULT_DB = '/home/dlamotte/.ghostcrab/databases/ghostcrab-serenity-v4-demos.sqlite';
8
- const DEFAULT_BACKUP_DIR = '/tmp/serenity-v6-snapshot-backups';
9
- const GENERATOR = 'serenity-v6-snapshot-generator';
10
- const args = new Set(process.argv.slice(2));
11
- const apply = args.has('--apply');
12
- const verbose = args.has('--verbose');
13
- const emitSqlPath = valueAfter('--emit-sql');
14
- const dbPath = valueAfter('--db') ?? process.env.GHOSTCRAB_SQLITE_PATH ?? DEFAULT_DB;
15
- const backupDir = valueAfter('--backup-dir') ?? DEFAULT_BACKUP_DIR;
16
- const now = Math.floor(Date.now() / 1000);
17
-
18
- const NEW_SCENARIOS = [
19
- {
20
- scenario_id: 'scn.v6.mandataire_appel_fonds_contact',
21
- projection_id: 'scenario_v6_mandataire_appel_fonds_contact',
22
- public_label: 'Mandataire contacte avec les proprietaires lors d un appel de fonds',
23
- business_question:
24
- "Qui doit recevoir l'appel de fonds quand un mandataire represente plusieurs lots et comment prouver que le calcul reste rattache aux bons proprietaires ?",
25
- copropriete: 'copropriete.tilleuls',
26
- workflow: 'fund_call_delegated_contact',
27
- records: [
28
- ['mandataire', 'Mandataire Dupont famille Tilleuls'],
29
- ['personne', 'Claire Dupont mandataire'],
30
- ['lot', 'Lot A3 represente par mandataire'],
31
- ['fund_call', 'Appel FDRO delegue Tilleuls T2'],
32
- ['fund_call_line', 'Ligne appel fonds lot A3 mandataire'],
33
- ['preuve_documentaire', 'Courrier de mandat et preuve d envoi']
34
- ],
35
- statuses: [
36
- 'mandat: received -> verified -> active',
37
- 'fund_call: draft -> computed -> emitted',
38
- 'notification: prepared -> sent_to_owner_and_mandataire -> acknowledged'
39
- ],
40
- requirements: [
41
- 'fund_call.contacts_mandataire',
42
- 'mandataire.represents_lot',
43
- 'fund_call_line.account_for_lot'
44
- ]
45
- },
46
- {
47
- scenario_id: 'scn.v6.offre_prix_revision_bon_commande',
48
- projection_id: 'scenario_v6_offre_prix_revision_bon_commande',
49
- public_label: 'Offre de prix revisee puis acceptee comme bon de commande',
50
- business_question:
51
- "Quelle version du devis a ete acceptee, quel montant engage-t-elle et quel bon de commande en decoule ?",
52
- copropriete: 'copropriete.horizon',
53
- workflow: 'quote_revision_to_purchase_order',
54
- records: [
55
- ['demande_travaux', 'Demande remplacement parlophone Galerie Horizon'],
56
- ['offre_prix', 'Offre prix initiale Parlophone SA'],
57
- ['offre_prix', 'Offre prix revisee Parlophone SA'],
58
- ['bon_commande', 'Bon commande parlophone Horizon'],
59
- ['supplier_intervention', 'Intervention planifiee parlophone Horizon'],
60
- ['preuve_documentaire', 'PV acceptation devis revise']
61
- ],
62
- statuses: [
63
- 'quote: requested -> received -> revised -> accepted',
64
- 'purchase_order: draft -> signed -> sent_to_supplier',
65
- 'intervention: planned -> waiting_supplier'
66
- ],
67
- requirements: [
68
- 'demande_travaux.requests_quote',
69
- 'offre_prix.revised_as',
70
- 'offre_prix.accepted_as_purchase_order'
71
- ]
72
- },
73
- {
74
- scenario_id: 'scn.v6.extrait_bancaire_rapprochement',
75
- projection_id: 'scenario_v6_extrait_bancaire_rapprochement',
76
- public_label: 'Extrait bancaire rapproche avec ecriture comptable et facture',
77
- business_question:
78
- "Quel mouvement bancaire justifie le paiement, quelle facture est soldee et quelle ecriture comptable ferme le rapprochement ?",
79
- copropriete: 'copropriete.erables',
80
- workflow: 'bank_statement_reconciliation',
81
- records: [
82
- ['extrait_compte_bancaire', 'Extrait bancaire Erables mars 2026'],
83
- ['bank_movement', 'Mouvement paiement chaufferie mars 2026'],
84
- ['supplier_invoice', 'Facture chaufferie Erables mars 2026'],
85
- ['accounting_entry', 'Ecriture paiement facture chaufferie'],
86
- ['payment_reconciliation', 'Rapprochement facture paiement chaufferie'],
87
- ['preuve_documentaire', 'PDF extrait bancaire et facture fournisseur']
88
- ],
89
- statuses: [
90
- 'bank_statement: imported -> parsed -> reconciled',
91
- 'invoice: received -> booked -> paid',
92
- 'reconciliation: candidate -> matched -> verified'
93
- ],
94
- requirements: [
95
- 'extrait_compte_bancaire.documents_account',
96
- 'bank_movement.pays_invoice',
97
- 'accounting_entry.supports_reconciliation'
98
- ]
99
- }
100
- ];
101
-
102
- function valueAfter(flag) {
103
- const index = process.argv.indexOf(flag);
104
- return index >= 0 ? process.argv[index + 1] : null;
105
- }
106
-
107
- function runSql(sql, { json = false, readonly = false } = {}) {
108
- const argv = [];
109
- if (readonly) argv.push('-readonly');
110
- if (json) argv.push('-json');
111
- argv.push(dbPath, sql);
112
- const result = spawnSync('sqlite3', argv, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 128 });
113
- if (result.status !== 0) throw new Error(result.stderr || result.stdout || `sqlite3 failed: ${sql.slice(0, 180)}`);
114
- return json ? JSON.parse(result.stdout || '[]') : result.stdout.trim();
115
- }
116
-
117
- function runSqlFile(sql) {
118
- if (verbose) console.error(`[${GENERATOR}] applying sql bytes=${Buffer.byteLength(sql, 'utf8')}`);
119
- mkdirSync(backupDir, { recursive: true });
120
- const sqlPath = join(backupDir, `${WORKSPACE}-${process.pid}-${Date.now()}.sql`);
121
- writeFileSync(sqlPath, sql);
122
- const result = spawnSync('sqlite3', [dbPath, `.read ${sqlPath}`], {
123
- encoding: 'utf8',
124
- maxBuffer: 1024 * 1024 * 128
125
- });
126
- if (result.status !== 0) throw new Error(result.stderr || result.stdout || 'sqlite3 transaction failed');
127
- return result.stdout;
128
- }
129
-
130
- function q(value) {
131
- if (value === null || value === undefined) return 'NULL';
132
- if (typeof value === 'number') return Number.isFinite(value) ? String(value) : 'NULL';
133
- if (typeof value === 'boolean') return value ? '1' : '0';
134
- return `'${String(value).replaceAll("'", "''")}'`;
135
- }
136
-
137
- function slug(value) {
138
- return String(value)
139
- .toLowerCase()
140
- .normalize('NFD')
141
- .replace(/[\u0300-\u036f]/g, '')
142
- .replace(/[^a-z0-9]+/g, '_')
143
- .replace(/^_+|_+$/g, '');
144
- }
145
-
146
- function parseJson(value, fallback = {}) {
147
- try {
148
- return JSON.parse(value || '');
149
- } catch {
150
- return fallback;
151
- }
152
- }
153
-
154
- function jsonSql(value) {
155
- return q(JSON.stringify(value));
156
- }
157
-
158
- function readScenarioPlans() {
159
- return runSql(
160
- `
161
- SELECT artifact_id, slug, public_label, scope, payload_json
162
- FROM mindbrain_answer_artifacts
163
- WHERE workspace_id=${q(WORKSPACE)}
164
- AND artifact_kind='analysis_plan'
165
- AND scope LIKE ${q(`${WORKSPACE}:scenario:%`)}
166
- ORDER BY artifact_id;
167
- `,
168
- { json: true, readonly: true }
169
- ).map((row) => {
170
- const payload = parseJson(row.payload_json);
171
- const refs = Array.isArray(payload.scenario_refs) ? payload.scenario_refs : [];
172
- const scenarioId = refs[0] ?? payload.projection_id ?? row.slug;
173
- return {
174
- source: 'existing',
175
- artifact_id: row.artifact_id,
176
- slug: row.slug,
177
- public_label: row.public_label,
178
- scope: row.scope,
179
- projection_id: payload.projection_id ?? slug(row.public_label),
180
- business_question: payload.business_question ?? payload.summary ?? row.public_label,
181
- scenario_id: scenarioId,
182
- required_edges: Array.isArray(payload.required_edges) ? payload.required_edges : [],
183
- required_facets: Array.isArray(payload.required_facets) ? payload.required_facets : [],
184
- scenario_refs: refs
185
- };
186
- });
187
- }
188
-
189
- function readScenarioEntityMap() {
190
- const rows = runSql(
191
- `
192
- SELECT entity_id, entity_type, name, metadata_json
193
- FROM graph_entity
194
- WHERE workspace_id=${q(WORKSPACE)}
195
- AND (
196
- entity_type='scenario_gestion'
197
- OR json_extract(metadata_json, '$.scenario_id') IS NOT NULL
198
- )
199
- ORDER BY entity_id;
200
- `,
201
- { json: true, readonly: true }
202
- );
203
- const byScenario = new Map();
204
- for (const row of rows) {
205
- const metadata = parseJson(row.metadata_json);
206
- const scenarioId = metadata.scenario_id ?? (row.entity_type === 'scenario_gestion' ? row.name : null);
207
- if (!scenarioId) continue;
208
- if (!byScenario.has(scenarioId)) byScenario.set(scenarioId, []);
209
- byScenario.get(scenarioId).push({
210
- entity_id: Number(row.entity_id),
211
- entity_type: row.entity_type,
212
- name: row.name,
213
- metadata
214
- });
215
- }
216
- return byScenario;
217
- }
218
-
219
- function readScenarioRelationCounts() {
220
- const rows = runSql(
221
- `
222
- SELECT s.name AS scenario_id, COUNT(r.relation_id) AS relation_count,
223
- group_concat(DISTINCT r.relation_type) AS relation_types
224
- FROM graph_entity s
225
- LEFT JOIN graph_relation r ON r.workspace_id=s.workspace_id AND r.source_id=s.entity_id
226
- WHERE s.workspace_id=${q(WORKSPACE)}
227
- AND s.entity_type='scenario_gestion'
228
- GROUP BY s.entity_id, s.name;
229
- `,
230
- { json: true, readonly: true }
231
- );
232
- return new Map(
233
- rows.map((row) => [
234
- row.scenario_id,
235
- {
236
- relation_count: Number(row.relation_count ?? 0),
237
- relation_types: row.relation_types ? String(row.relation_types).split(',') : []
238
- }
239
- ])
240
- );
241
- }
242
-
243
- function newScenarioPlan(def) {
244
- return {
245
- source: 'generated',
246
- artifact_id: `analysis_plan__${WORKSPACE.replaceAll('-', '_')}_scenario_${def.projection_id}`,
247
- slug: `${WORKSPACE.replaceAll('-', '_')}_scenario_${def.projection_id}`,
248
- public_label: def.public_label,
249
- scope: `${WORKSPACE}:scenario:${def.projection_id}`,
250
- projection_id: def.projection_id,
251
- business_question: def.business_question,
252
- scenario_id: def.scenario_id,
253
- required_edges: ['scenario_requires_record', 'scenario_requires_relation', 'scenario_has_status_event', 'scenario_for_copropriete'],
254
- required_facets: ['status', 'scenario_status', 'copropriete_id', 'actor_refs', 'evidence_refs'],
255
- scenario_refs: [def.scenario_id],
256
- def
257
- };
258
- }
259
-
260
- function syntheticRuntimeRows(def) {
261
- const rows = [{ entity_type: 'scenario_gestion', name: def.scenario_id, role: 'scenario' }];
262
- for (let index = 0; index < def.records.length; index += 1) {
263
- const [entityType, label] = def.records[index];
264
- rows.push({
265
- entity_type: entityType,
266
- name: `${def.scenario_id}.${slug(entityType)}.${String(index + 1).padStart(2, '0')}`,
267
- label,
268
- role: 'runtime_record'
269
- });
270
- }
271
- for (let index = 0; index < def.statuses.length; index += 1) {
272
- rows.push({
273
- entity_type: 'status_event',
274
- name: `${def.scenario_id}.status_event.${String(index + 1).padStart(2, '0')}`,
275
- label: def.statuses[index],
276
- role: 'status_event',
277
- transition: def.statuses[index]
278
- });
279
- }
280
- for (let index = 0; index < def.requirements.length; index += 1) {
281
- rows.push({
282
- entity_type: 'relation_requirement',
283
- name: `${def.scenario_id}.relation_requirement.${String(index + 1).padStart(2, '0')}`,
284
- label: def.requirements[index],
285
- role: 'relation_requirement',
286
- relation_requirement: def.requirements[index]
287
- });
288
- }
289
- return rows;
290
- }
291
-
292
- function syntheticEntityNameSql(name) {
293
- return `(SELECT entity_id FROM graph_entity WHERE workspace_id=${q(WORKSPACE)} AND name=${q(name)} LIMIT 1)`;
294
- }
295
-
296
- function buildNewScenarioSql(plan) {
297
- const def = plan.def;
298
- const statements = [];
299
- const basePayload = {
300
- projection_id: plan.projection_id,
301
- source: `${GENERATOR}.analysis_plan`,
302
- origin: 'synthetic_scenario',
303
- decision_level: 'operational_scenario',
304
- artifact_kind: 'analysis_plan',
305
- proj_type: 'STEP',
306
- scope: plan.scope,
307
- label: plan.public_label,
308
- business_question: plan.business_question,
309
- description: def.public_label,
310
- required_schemas: [],
311
- required_facets: plan.required_facets,
312
- required_edges: plan.required_edges,
313
- retrieval_jobs: ['scenario_state', 'scenario_evidence'],
314
- status_filter_policy: 'current_by_default',
315
- scenario_refs: plan.scenario_refs,
316
- synthetic: true,
317
- generated_by: GENERATOR
318
- };
319
- statements.push(`
320
- INSERT INTO mindbrain_answer_artifacts (
321
- artifact_id, slug, workspace_id, agent_id, scope, artifact_kind,
322
- public_label_key, public_label, lifecycle, state, current_version,
323
- payload_json, legacy_ref, created_at_unix, updated_at_unix
324
- )
325
- VALUES (
326
- ${q(plan.artifact_id)}, ${q(plan.slug)}, ${q(WORKSPACE)}, ${q(`agent:${WORKSPACE}:snapshot-generator`)},
327
- ${q(plan.scope)}, 'analysis_plan', ${q(`analysis_plan.${WORKSPACE}.${plan.projection_id}`)},
328
- ${q(plan.public_label)}, 'active', 'open', 1, ${jsonSql(basePayload)}, NULL, ${now}, ${now}
329
- )
330
- ON CONFLICT(artifact_id) DO UPDATE SET
331
- public_label=excluded.public_label,
332
- scope=excluded.scope,
333
- payload_json=excluded.payload_json,
334
- updated_at_unix=excluded.updated_at_unix;
335
- `);
336
- statements.push(`
337
- INSERT INTO mindbrain_answer_artifacts (
338
- artifact_id, slug, workspace_id, agent_id, scope, artifact_kind,
339
- public_label_key, public_label, lifecycle, state, current_version,
340
- payload_json, legacy_ref, created_at_unix, updated_at_unix
341
- )
342
- VALUES (
343
- ${q(`live_answer_view__${plan.slug}`)}, ${q(plan.slug)}, ${q(WORKSPACE)}, NULL, NULL,
344
- 'live_answer_view', ${q(`live_answer_view.${WORKSPACE}.${plan.projection_id}`)},
345
- ${q(plan.public_label)}, 'active', 'refreshed', 1,
346
- ${jsonSql({
347
- source_plan_id: plan.artifact_id,
348
- source_plan_scope: plan.scope,
349
- projection_id: plan.projection_id,
350
- summary: plan.business_question,
351
- refresh_checks: ['graph relation coverage', 'status filter coverage', 'required evidence coverage'],
352
- required_edges: plan.required_edges,
353
- required_facets: plan.required_facets,
354
- scenario_refs: plan.scenario_refs,
355
- synthetic: true,
356
- generated_by: GENERATOR
357
- })},
358
- NULL, ${now}, ${now}
359
- )
360
- ON CONFLICT(artifact_id) DO UPDATE SET
361
- payload_json=excluded.payload_json,
362
- state=excluded.state,
363
- updated_at_unix=excluded.updated_at_unix;
364
- `);
365
- for (const row of syntheticRuntimeRows(def)) {
366
- const metadata = {
367
- display_name: row.label ?? def.public_label,
368
- source_id: row.name,
369
- source: GENERATOR,
370
- scenario_id: def.scenario_id,
371
- runtime_record_type: row.role,
372
- transition: row.transition,
373
- relation_requirement: row.relation_requirement,
374
- synthetic: true
375
- };
376
- statements.push(`
377
- INSERT INTO graph_entity (workspace_id, entity_type, name, confidence, metadata_json)
378
- VALUES (${q(WORKSPACE)}, ${q(row.entity_type)}, ${q(row.name)}, 0.96, ${jsonSql(metadata)})
379
- ON CONFLICT(workspace_id, entity_type, name)
380
- DO UPDATE SET confidence=excluded.confidence, metadata_json=excluded.metadata_json, deprecated_at=NULL;
381
- `);
382
- }
383
- for (const row of syntheticRuntimeRows(def).filter((item) => item.name !== def.scenario_id)) {
384
- const relationType =
385
- row.role === 'status_event'
386
- ? 'scenario_has_status_event'
387
- : row.role === 'relation_requirement'
388
- ? 'scenario_requires_relation'
389
- : 'scenario_requires_record';
390
- statements.push(relationSql(relationType, def.scenario_id, row.name, {
391
- source: GENERATOR,
392
- scenario_id: def.scenario_id,
393
- synthetic: true
394
- }));
395
- }
396
- return statements;
397
- }
398
-
399
- function relationSql(type, sourceName, targetName, metadata) {
400
- return `
401
- INSERT INTO graph_relation (workspace_id, relation_type, source_id, target_id, confidence, metadata_json)
402
- SELECT ${q(WORKSPACE)}, ${q(type)}, s.entity_id, t.entity_id, 0.98, ${jsonSql({
403
- ...metadata,
404
- relation_group: 'synthetic_scenario'
405
- })}
406
- FROM graph_entity s, graph_entity t
407
- WHERE s.workspace_id=${q(WORKSPACE)} AND s.name=${q(sourceName)}
408
- AND t.workspace_id=${q(WORKSPACE)} AND t.name=${q(targetName)};
409
- `;
410
- }
411
-
412
- function topEntitiesForPlan(plan, entityMap) {
413
- const rows = entityMap.get(plan.scenario_id) ?? [];
414
- const scenario = rows.find((row) => row.entity_type === 'scenario_gestion');
415
- const runtime = rows.filter((row) => row.entity_type !== 'scenario_gestion');
416
- const evidence = runtime.filter((row) =>
417
- ['preuve_documentaire', 'proof_document', 'death_certificate_evidence', 'claim_statement'].includes(row.entity_type)
418
- );
419
- const statusEvents = runtime.filter((row) => row.entity_type === 'status_event');
420
- const requirements = runtime.filter((row) => row.entity_type === 'relation_requirement');
421
- return {
422
- scenario,
423
- runtime,
424
- evidence,
425
- statusEvents,
426
- requirements,
427
- seedIds: [scenario, ...runtime.slice(0, 5)].filter(Boolean).map((row) => row.entity_id)
428
- };
429
- }
430
-
431
- function snapshotPayload(plan, kind, entityMap, relationCounts) {
432
- const entities = topEntitiesForPlan(plan, entityMap);
433
- const relationInfo = relationCounts.get(plan.scenario_id) ?? { relation_count: 0, relation_types: [] };
434
- const runtimeIds = entities.runtime.map((row) => row.entity_id).filter(Number.isFinite);
435
- const evidenceIds = entities.evidence.map((row) => row.entity_id).filter(Number.isFinite);
436
- const deltaIds = entities.requirements.slice(0, 4).map((row) => row.entity_id).filter(Number.isFinite);
437
- const timeline = entities.statusEvents.map((row) => ({
438
- entity_id: row.entity_id,
439
- name: row.name,
440
- transition: row.metadata.transition ?? row.metadata.display_name ?? row.name
441
- }));
442
- const base = {
443
- source_plan_id: plan.artifact_id,
444
- source_plan_scope: plan.scope,
445
- projection_id: plan.projection_id,
446
- scenario_id: plan.scenario_id,
447
- scenario_refs: plan.scenario_refs,
448
- business_question: plan.business_question,
449
- snapshot_kind: kind,
450
- summary:
451
- kind === 'scenario_state'
452
- ? `${plan.public_label}: ${runtimeIds.length} objets runtime, ${timeline.length} transitions et ${relationInfo.relation_count} relations scenario.`
453
- : `${plan.public_label}: ${evidenceIds.length} preuves directes, ${deltaIds.length} exigences/deltas et ${relationInfo.relation_types.length} types de relations.`,
454
- runtime_entity_ids: runtimeIds,
455
- evidence_entity_ids: evidenceIds,
456
- delta_entity_ids: deltaIds,
457
- seed_ids: entities.seedIds,
458
- status_timeline: timeline,
459
- relation_coverage: {
460
- relation_count: relationInfo.relation_count,
461
- relation_types: relationInfo.relation_types
462
- },
463
- required_edges: plan.required_edges,
464
- required_facets: plan.required_facets,
465
- synthetic: plan.source === 'generated',
466
- generated_by: GENERATOR,
467
- generated_at_unix: now
468
- };
469
- if (kind === 'scenario_evidence') {
470
- base.deltas = entities.requirements.slice(0, 6).map((row) => ({
471
- entity_id: row.entity_id,
472
- requirement: row.metadata.relation_requirement ?? row.metadata.display_name ?? row.name,
473
- status: evidenceIds.length > 0 ? 'covered' : 'needs_evidence'
474
- }));
475
- }
476
- return base;
477
- }
478
-
479
- function buildSnapshotSql(plan, kind, entityMap, relationCounts) {
480
- const payload = snapshotPayload(plan, kind, entityMap, relationCounts);
481
- const snapshotSlug = `${plan.slug}__${kind}`;
482
- const artifactId = `answer_snapshot__${snapshotSlug}`;
483
- const title = `${plan.public_label} - ${kind === 'scenario_state' ? 'etat scenario' : 'preuves et deltas'}`;
484
- const projectionName = `projection_result.${plan.projection_id}.${kind}`;
485
- const metadata = {
486
- artifact_id: artifactId,
487
- artifact_kind: 'answer_snapshot',
488
- public_label: title,
489
- projection_id: plan.projection_id,
490
- source_plan_id: plan.artifact_id,
491
- scenario_id: plan.scenario_id,
492
- snapshot_kind: kind,
493
- seed_ids: payload.seed_ids,
494
- source: GENERATOR,
495
- workspace_id: WORKSPACE,
496
- synthetic: payload.synthetic,
497
- relation_group: 'snapshot'
498
- };
499
- const relationStatements = [];
500
- const scenarioEntity = (entityMap.get(plan.scenario_id) ?? []).find((row) => row.entity_type === 'scenario_gestion');
501
- if (scenarioEntity) {
502
- relationStatements.push(`
503
- INSERT INTO graph_relation (workspace_id, relation_type, source_id, target_id, confidence, metadata_json)
504
- SELECT ${q(WORKSPACE)}, 'SNAPSHOT_FOR_SCENARIO', p.entity_id, ${scenarioEntity.entity_id}, 1.0, ${jsonSql(metadata)}
505
- FROM graph_entity p
506
- WHERE p.workspace_id=${q(WORKSPACE)} AND p.entity_type='ProjectionResult' AND p.name=${q(projectionName)};
507
- `);
508
- }
509
- for (const targetId of payload.seed_ids.slice(0, 8)) {
510
- relationStatements.push(`
511
- INSERT INTO graph_relation (workspace_id, relation_type, source_id, target_id, confidence, metadata_json)
512
- SELECT ${q(WORKSPACE)}, 'SNAPSHOT_REFERENCES_OBJECT', p.entity_id, ${targetId}, 0.97, ${jsonSql(metadata)}
513
- FROM graph_entity p
514
- WHERE p.workspace_id=${q(WORKSPACE)} AND p.entity_type='ProjectionResult' AND p.name=${q(projectionName)};
515
- `);
516
- }
517
- for (const targetId of payload.delta_entity_ids.slice(0, 4)) {
518
- relationStatements.push(`
519
- INSERT INTO graph_relation (workspace_id, relation_type, source_id, target_id, confidence, metadata_json)
520
- SELECT ${q(WORKSPACE)}, 'SNAPSHOT_HAS_DELTA', p.entity_id, ${targetId}, 0.94, ${jsonSql(metadata)}
521
- FROM graph_entity p
522
- WHERE p.workspace_id=${q(WORKSPACE)} AND p.entity_type='ProjectionResult' AND p.name=${q(projectionName)};
523
- `);
524
- }
525
- return [
526
- `
527
- INSERT INTO mindbrain_answer_artifacts (
528
- artifact_id, slug, workspace_id, agent_id, scope, artifact_kind,
529
- public_label_key, public_label, lifecycle, state, current_version,
530
- payload_json, legacy_ref, created_at_unix, updated_at_unix
531
- )
532
- VALUES (
533
- ${q(artifactId)}, ${q(snapshotSlug)}, ${q(WORKSPACE)}, NULL, ${q(plan.scope)},
534
- 'answer_snapshot', ${q(`answer_snapshot.${WORKSPACE}.${plan.projection_id}.${kind}`)},
535
- ${q(title)}, 'frozen', 'ready', 1, ${jsonSql(payload)}, NULL, ${now}, ${now}
536
- )
537
- ON CONFLICT(artifact_id) DO UPDATE SET
538
- public_label=excluded.public_label,
539
- scope=excluded.scope,
540
- lifecycle=excluded.lifecycle,
541
- state=excluded.state,
542
- current_version=excluded.current_version,
543
- payload_json=excluded.payload_json,
544
- updated_at_unix=excluded.updated_at_unix;
545
- `,
546
- `
547
- INSERT INTO graph_entity (workspace_id, entity_type, name, confidence, metadata_json)
548
- VALUES (${q(WORKSPACE)}, 'ProjectionResult', ${q(projectionName)}, 1.0, ${jsonSql(metadata)})
549
- ON CONFLICT(workspace_id, entity_type, name)
550
- DO UPDATE SET confidence=excluded.confidence, metadata_json=excluded.metadata_json, deprecated_at=NULL;
551
- `,
552
- ...relationStatements
553
- ];
554
- }
555
-
556
- function buildNewScenarioDataSql(generatedPlans) {
557
- const statements = [];
558
- statements.push(
559
- `DELETE FROM graph_relation WHERE workspace_id=${q(WORKSPACE)} AND json_extract(metadata_json, '$.source')=${q(GENERATOR)} AND json_extract(metadata_json, '$.relation_group')='synthetic_scenario';`
560
- );
561
- for (const plan of generatedPlans) statements.push(...buildNewScenarioSql(plan));
562
- return ['PRAGMA foreign_keys=ON;', 'BEGIN IMMEDIATE;', ...statements, 'COMMIT;'].join('\n');
563
- }
564
-
565
- function buildSnapshotDataSql(plans) {
566
- const entityMap = readScenarioEntityMap();
567
- const relationCounts = readScenarioRelationCounts();
568
- const statements = [
569
- `DELETE FROM graph_relation WHERE workspace_id=${q(WORKSPACE)} AND json_extract(metadata_json, '$.source')=${q(GENERATOR)} AND json_extract(metadata_json, '$.relation_group')='snapshot';`
570
- ];
571
- for (const plan of plans) {
572
- for (const kind of ['scenario_state', 'scenario_evidence']) {
573
- statements.push(...buildSnapshotSql(plan, kind, entityMap, relationCounts));
574
- }
575
- }
576
- return ['PRAGMA foreign_keys=ON;', 'BEGIN IMMEDIATE;', ...statements, 'COMMIT;'].join('\n');
577
- }
578
-
579
- function backupDb() {
580
- mkdirSync(backupDir, { recursive: true });
581
- const backupPath = join(backupDir, `${WORKSPACE}-pre-snapshots-${new Date().toISOString().replaceAll(':', '-')}.sqlite`);
582
- copyFileSync(dbPath, backupPath);
583
- return backupPath;
584
- }
585
-
586
- function counts() {
587
- const rows = runSql(
588
- `
589
- SELECT 'answer_snapshot_artifacts' AS name, COUNT(*) AS count
590
- FROM mindbrain_answer_artifacts
591
- WHERE workspace_id=${q(WORKSPACE)} AND artifact_kind='answer_snapshot'
592
- UNION ALL
593
- SELECT 'projection_results' AS name, COUNT(*) AS count
594
- FROM graph_entity
595
- WHERE workspace_id=${q(WORKSPACE)} AND entity_type='ProjectionResult'
596
- UNION ALL
597
- SELECT 'scenario_plans' AS name, COUNT(*) AS count
598
- FROM mindbrain_answer_artifacts
599
- WHERE workspace_id=${q(WORKSPACE)} AND artifact_kind='analysis_plan' AND scope LIKE ${q(`${WORKSPACE}:scenario:%`)};
600
- `,
601
- { json: true, readonly: true }
602
- );
603
- return Object.fromEntries(rows.map((row) => [row.name, Number(row.count)]));
604
- }
605
-
606
- function main() {
607
- if (verbose) console.error(`[${GENERATOR}] reading counts`);
608
- const before = counts();
609
- if (verbose) console.error(`[${GENERATOR}] reading existing scenario plans`);
610
- const existingPlans = readScenarioPlans();
611
- const generatedPlans = NEW_SCENARIOS.map(newScenarioPlan);
612
- const existingPlanIds = new Set(existingPlans.map((plan) => plan.artifact_id));
613
- const missingGeneratedPlans = generatedPlans.filter((plan) => !existingPlanIds.has(plan.artifact_id));
614
- const expectedPlanCount = existingPlans.length + missingGeneratedPlans.length;
615
- const plannedSnapshots = expectedPlanCount * 2;
616
- if (emitSqlPath) {
617
- const previewSql = [
618
- '-- new scenario data',
619
- buildNewScenarioDataSql(generatedPlans),
620
- '-- snapshot data for current plans only',
621
- buildSnapshotDataSql(existingPlans)
622
- ].join('\n');
623
- writeFileSync(emitSqlPath, previewSql);
624
- console.log(JSON.stringify({ mode: 'emit-sql', path: emitSqlPath, bytes: Buffer.byteLength(previewSql, 'utf8') }, null, 2));
625
- return;
626
- }
627
- if (!apply) {
628
- console.log(
629
- JSON.stringify(
630
- {
631
- mode: 'dry-run',
632
- db_path: dbPath,
633
- workspace_id: WORKSPACE,
634
- existing_scenarios: existingPlans.length,
635
- new_scenarios: missingGeneratedPlans.length,
636
- planned_snapshots: plannedSnapshots,
637
- planned_projection_results: plannedSnapshots,
638
- before
639
- },
640
- null,
641
- 2
642
- )
643
- );
644
- return;
645
- }
646
- if (verbose) console.error(`[${GENERATOR}] backing up`);
647
- const backupPath = backupDb();
648
- if (verbose) console.error(`[${GENERATOR}] applying generated scenario data`);
649
- runSqlFile(buildNewScenarioDataSql(generatedPlans));
650
- if (verbose) console.error(`[${GENERATOR}] reading plans after generated data`);
651
- const plansAfterScenarioData = readScenarioPlans();
652
- if (verbose) console.error(`[${GENERATOR}] applying snapshot data for ${plansAfterScenarioData.length} plans`);
653
- runSqlFile(buildSnapshotDataSql(plansAfterScenarioData));
654
- if (verbose) console.error(`[${GENERATOR}] reading final counts`);
655
- const after = counts();
656
- console.log(
657
- JSON.stringify(
658
- {
659
- mode: 'apply',
660
- db_path: dbPath,
661
- workspace_id: WORKSPACE,
662
- backup_path: backupPath,
663
- existing_scenarios: existingPlans.length,
664
- new_scenarios: missingGeneratedPlans.length,
665
- planned_snapshots: plannedSnapshots,
666
- before,
667
- after
668
- },
669
- null,
670
- 2
671
- )
672
- );
673
- }
674
-
675
- main();