@mindflight/mindbrain-personal-studio 0.6.1 → 0.6.3
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 +16 -8
- package/build/client/_app/immutable/chunks/CIErFlYG.js +1 -0
- package/build/client/_app/immutable/chunks/CIErFlYG.js.br +0 -0
- package/build/client/_app/immutable/chunks/CIErFlYG.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CVz6aYsT.js → app.mURYm8o_.js} +2 -2
- package/build/client/_app/immutable/entry/app.mURYm8o_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.mURYm8o_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js +1 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.br +2 -0
- package/build/client/_app/immutable/entry/start.D4M9ZeGO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BBtxY46Q.js → 1.DHKtMeFI.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DHKtMeFI.js.br +1 -0
- package/build/client/_app/immutable/nodes/1.DHKtMeFI.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-C3tV0XXj.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-D8EA_he2.js.map → internal.js-C3tV0XXj.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-DlaCCnxx.js} +3 -3
- package/build/server/chunks/{handler-BjXBCd1c.js.map → handler-DlaCCnxx.js.map} +1 -1
- package/build/server/chunks/{index.js-BJYcV8V7.js → index.js-BGfKWHak.js} +2 -2
- package/build/server/chunks/{index.js-BJYcV8V7.js.map → index.js-BGfKWHak.js.map} +1 -1
- package/build/server/chunks/{manifest.js-Ddaot0P4.js → manifest.js-CnmaNf5D.js} +4 -4
- package/build/server/chunks/{manifest.js-Ddaot0P4.js.map → manifest.js-CnmaNf5D.js.map} +1 -1
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js → 1.js-BypjwBYB.js} +2 -2
- package/build/server/chunks/nodes/{1.js-BRigw_9J.js.map → 1.js-BypjwBYB.js.map} +1 -1
- package/examples/immeuble/ACCEPTANCE.yaml +82 -0
- package/examples/immeuble/BIM_LITE.md +15 -0
- package/examples/immeuble/CHECKLIST.md +128 -0
- package/examples/immeuble/README.md +121 -0
- package/examples/immeuble/bundle/immeuble.bundle.json +22786 -0
- package/examples/immeuble/contracts/answer_artifacts.seed.jsonl +34 -0
- package/examples/immeuble/contracts/business_capabilities.seed.jsonl +25 -0
- package/examples/immeuble/contracts/consumer_contract.yaml +131 -0
- package/examples/immeuble/contracts/immeuble_structured_import_model.json +310 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical.json +375 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical.yaml +55 -0
- package/examples/immeuble/contracts/mapping_external_to_canonical_ws.json +65 -0
- package/examples/immeuble/contracts/model_contract.json +943 -0
- package/examples/immeuble/contracts/projection_catalog.yaml +366 -0
- package/examples/immeuble/contracts/scenarios.yaml +27 -0
- package/examples/immeuble/contracts/semantic_proposal.golden.json +1 -0
- package/examples/immeuble/contracts/source_profile.yaml +38 -0
- package/examples/immeuble/fake_data/DeltaFinding.csv +20 -0
- package/examples/immeuble/fake_data/ProjectionResult.csv +13 -0
- package/examples/immeuble/fake_data/ag_meeting.csv +4 -0
- package/examples/immeuble/fake_data/agenda_item.csv +4 -0
- package/examples/immeuble/fake_data/architect.csv +3 -0
- package/examples/immeuble/fake_data/architecture_firm.csv +2 -0
- package/examples/immeuble/fake_data/bank_account.csv +3 -0
- package/examples/immeuble/fake_data/billing_group.csv +41 -0
- package/examples/immeuble/fake_data/block.csv +10 -0
- package/examples/immeuble/fake_data/budget_line.csv +3 -0
- package/examples/immeuble/fake_data/building.csv +6 -0
- package/examples/immeuble/fake_data/cellar.csv +41 -0
- package/examples/immeuble/fake_data/change_order.csv +2 -0
- package/examples/immeuble/fake_data/charge_call.csv +58 -0
- package/examples/immeuble/fake_data/claim.csv +4 -0
- package/examples/immeuble/fake_data/coda_entry.csv +49 -0
- package/examples/immeuble/fake_data/compliance_certificate.csv +10 -0
- package/examples/immeuble/fake_data/contractor.csv +4 -0
- package/examples/immeuble/fake_data/decision.csv +5 -0
- package/examples/immeuble/fake_data/defect_reserve.csv +3 -0
- package/examples/immeuble/fake_data/household.csv +41 -0
- package/examples/immeuble/fake_data/inspection.csv +3 -0
- package/examples/immeuble/fake_data/insurance_policy.csv +4 -0
- package/examples/immeuble/fake_data/intervention.csv +13 -0
- package/examples/immeuble/fake_data/invoice.csv +3 -0
- package/examples/immeuble/fake_data/lease_contract.csv +12 -0
- package/examples/immeuble/fake_data/maintenance_ticket.csv +13 -0
- package/examples/immeuble/fake_data/meter.csv +4 -0
- package/examples/immeuble/fake_data/meter_reading.csv +19 -0
- package/examples/immeuble/fake_data/milestone.csv +3 -0
- package/examples/immeuble/fake_data/organization.csv +12 -0
- package/examples/immeuble/fake_data/parking_space.csv +8 -0
- package/examples/immeuble/fake_data/permit.csv +3 -0
- package/examples/immeuble/fake_data/person.csv +85 -0
- package/examples/immeuble/fake_data/private_garden.csv +7 -0
- package/examples/immeuble/fake_data/progress_event.csv +2 -0
- package/examples/immeuble/fake_data/quote.csv +3 -0
- package/examples/immeuble/fake_data/receipt.csv +2 -0
- package/examples/immeuble/fake_data/reminder.csv +11 -0
- package/examples/immeuble/fake_data/service_contract.csv +4 -0
- package/examples/immeuble/fake_data/shared_equipment.csv +8 -0
- package/examples/immeuble/fake_data/shared_space.csv +10 -0
- package/examples/immeuble/fake_data/unit.csv +41 -0
- package/examples/immeuble/fake_data/work_package.csv +4 -0
- package/examples/immeuble/fake_data/worksite_project.csv +3 -0
- package/examples/immeuble/gap-rules/L0-patrimoine.json +57 -0
- package/examples/immeuble/gap-rules/L1-syndic-naive.json +36 -0
- package/examples/immeuble/gap-rules/L1-syndic.json +61 -0
- package/examples/immeuble/gap-rules/L2-chantier.json +87 -0
- package/examples/immeuble/gap-rules/L2-exploitation.json +87 -0
- package/examples/immeuble/gap-rules/L2-finance.json +48 -0
- package/examples/immeuble/gap-rules/L2-maintenance.json +107 -0
- package/examples/immeuble/gap-rules/L2-syndic-filtered.json +39 -0
- package/examples/immeuble/gap-rules/L3-full.json +332 -0
- package/examples/immeuble/gap-rules/closed-world-contract.md +76 -0
- package/examples/immeuble/gap-rules/demo.json +51 -0
- package/examples/immeuble/gap-rules/expected-findings.yaml +100 -0
- package/examples/immeuble/gap-rules/gap-scenarios.yaml +79 -0
- package/examples/immeuble/gap-rules/motifs.json +38 -0
- package/examples/immeuble/gap-rules/syndic.json +40 -0
- package/examples/immeuble/import_manifest.yaml +25 -0
- package/examples/immeuble/import_ready/graph_edges_import.csv +848 -0
- package/examples/immeuble/import_ready/mfo_facets_import.csv +559 -0
- package/examples/immeuble/index.md +140 -0
- package/examples/immeuble/model/immeuble_model.json +418 -0
- package/examples/immeuble/reports/01-model.validation.json +56 -0
- package/examples/immeuble/reports/02-mapping.validation.json +9 -0
- package/examples/immeuble/reports/acceptance.validation.json +161 -0
- package/examples/immeuble/reports/consumer_contract.validation.json +162 -0
- package/examples/immeuble/reports/graph_edges.jsonl +847 -0
- package/examples/immeuble/reports/graph_nodes.jsonl +558 -0
- package/examples/immeuble/reports/hybrid-compare.json +144 -0
- package/examples/immeuble/reports/immeuble-import-scenario.json +233 -0
- package/examples/immeuble/reports/live-artifacts-refresh.validation.json +51 -0
- package/examples/immeuble/reports/pipeline_audit.json +59 -0
- package/examples/immeuble/reports/projection_audit.json +69 -0
- package/examples/immeuble/reports/projection_audit_immeuble.json +257 -0
- package/examples/immeuble/reports/projection_audit_immeuble.md +122 -0
- package/examples/immeuble/reports/projection_candidates.json +1596 -0
- package/examples/immeuble/reports/projection_candidates.md +117 -0
- package/examples/immeuble/reports/projection_model_validation.md +115 -0
- package/examples/immeuble/reports/reindex.json +4 -0
- package/examples/immeuble/reports/reset-immeuble-workspace.json +32 -0
- package/examples/immeuble/reports/schema-id-prefix-check.json +5 -0
- package/examples/immeuble/scripts/audit-immeuble-projections.mjs +254 -0
- package/examples/immeuble/scripts/build-immeuble-model.mjs +308 -0
- package/examples/immeuble/scripts/compare-immeuble-snapshots.sh +227 -0
- package/examples/immeuble/scripts/enrich-immeuble-demo.mjs +1135 -0
- package/examples/immeuble/scripts/reset-immeuble-workspace.mjs +139 -0
- package/examples/immeuble/scripts/run-immeuble-backend.sh +164 -0
- package/examples/immeuble/scripts/run-immeuble-import.mjs +232 -0
- package/examples/immeuble/scripts/run-immeuble-live-lab.sh +439 -0
- package/examples/immeuble/scripts/seed-immeuble-gap-rules.mjs +69 -0
- package/examples/immeuble/scripts/start-immeuble-demo.sh +52 -0
- package/examples/immeuble/scripts/starterkit/analysis-lenses.mjs +181 -0
- package/examples/immeuble/scripts/starterkit/analyze-projection-candidates.mjs +714 -0
- package/examples/immeuble/scripts/starterkit/audit-ghostcrab-projections.mjs +674 -0
- package/examples/immeuble/scripts/starterkit/facet-prefix.mjs +166 -0
- package/examples/immeuble/scripts/starterkit/sqlite-utils.mjs +131 -0
- package/examples/immeuble/scripts/verify-immeuble-acceptance.mjs +284 -0
- package/examples/immeuble/scripts/verify-immeuble-live-artifacts.mjs +140 -0
- package/examples/immeuble/scripts/yaml-lite.mjs +96 -0
- package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites-immo-mcp.md +161 -0
- package/examples/immeuble/sources/agent-prompts/prompts/00-prerequisites.md +39 -0
- package/examples/immeuble/sources/agent-prompts/prompts/01-discovery-and-model-proposal.md +42 -0
- package/examples/immeuble/sources/agent-prompts/prompts/02-ontology-register.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/03-gap-rules-design.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/04-document-ingest.md +40 -0
- package/examples/immeuble/sources/agent-prompts/prompts/05-graph-extraction.md +44 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-immo-mcp.md +109 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare-test-immo-mcp3.md +30 -0
- package/examples/immeuble/sources/agent-prompts/prompts/06-validate-and-compare.md +63 -0
- package/examples/immeuble/sources/checklists/gap-rules-checklist.md +42 -0
- package/examples/immeuble/sources/checklists/ontology-checklist.md +54 -0
- package/examples/immeuble/sources/documents/README.md +42 -0
- package/examples/immeuble/sources/documents/annexes-caves-garages-jardins.md +8 -0
- package/examples/immeuble/sources/documents/annexes-jardins-garages.md +1 -0
- package/examples/immeuble/sources/documents/baux-erables.md +1 -0
- package/examples/immeuble/sources/documents/baux-locatifs.md +11 -0
- package/examples/immeuble/sources/documents/coda-janvier-2026.md +10 -0
- package/examples/immeuble/sources/documents/composition-menages.md +1 -0
- package/examples/immeuble/sources/documents/composition-occupants.md +18 -0
- package/examples/immeuble/sources/documents/expected-coverage.json +70 -0
- package/examples/immeuble/sources/documents/extrait-coda-janvier-2026.md +1 -0
- package/examples/immeuble/sources/documents/groupes-facturation.md +30 -0
- package/examples/immeuble/sources/documents/manifest.json +79 -0
- package/examples/immeuble/sources/documents/note-architecte-chantier-erables.md +3 -0
- package/examples/immeuble/sources/documents/permis-urbanisme-erables.md +3 -0
- package/examples/immeuble/sources/documents/procedures-operationnelles.md +3 -0
- package/examples/immeuble/sources/documents/pv-ag-budget-2026.md +1 -0
- package/examples/immeuble/sources/documents/pv-reception-reserves-erables.md +3 -0
- package/examples/immeuble/sources/documents/registre-coproprietaires.md +24 -0
- package/examples/immeuble/sources/documents/reglement-copropriete-tilleuls.md +1 -0
- package/examples/immeuble/sources/documents/statuts-erables.md +22 -0
- package/examples/immeuble/sources/documents/statuts-tilleuls.md +19 -0
- package/examples/immeuble/sources/documents/succession-jean-dupont.md +3 -0
- package/examples/immeuble/sources/documents/titre-propriete-tilleuls-a3.md +1 -0
- package/examples/immeuble/sources/documents/trous-pedagogiques.md +3 -0
- package/examples/immeuble/sources/ontology/README.md +1 -0
- package/examples/immeuble/sources/ontology/core.yaml +444 -0
- package/examples/immeuble/success-criteria.yaml +35 -0
- package/fixtures/immeuble-demo.sqlite +0 -0
- package/package.json +16 -3
- package/scripts/lib/sqlite-runtime.mjs +1 -1
- package/scripts/load-immeuble-demo.sh +103 -0
- package/scripts/seed-immeuble-projections.mjs +75 -148
- package/scripts/verify-immeuble-demo-sources.mjs +93 -0
- package/scripts/verify-immeuble-demo.mjs +69 -0
- 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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reset a clean immeuble workspace and run the canonical pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node examples/immeuble/scripts/reset-immeuble-workspace.mjs [options]
|
|
7
|
+
*
|
|
8
|
+
* Options:
|
|
9
|
+
* --db <path> SQLite target (default: data/immeuble.sqlite)
|
|
10
|
+
* --keep-db Do not delete existing file first
|
|
11
|
+
* --with-bundle-load After import, load bundle/immeuble.bundle.json
|
|
12
|
+
* --with-artifact-seed Seed answer artifacts via demo-load profile
|
|
13
|
+
* --with-business-capabilities Seed ghostcrab:business-capability records
|
|
14
|
+
* --engine legacy|both Import engine (default: legacy)
|
|
15
|
+
* --skip-provenance-validation
|
|
16
|
+
* --require-hybrid Fail if hybrid-compare missing or non-zero deltas
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { dirname, join, resolve } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
|
|
24
|
+
const immeubleRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const pkgRoot = resolve(immeubleRoot, "..", "..");
|
|
26
|
+
const node = process.execPath;
|
|
27
|
+
const reportsDir = join(immeubleRoot, "reports");
|
|
28
|
+
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const dbPath = resolve(parseFlag(args, "--db", join(pkgRoot, "data/immeuble.sqlite")));
|
|
31
|
+
const engine = parseFlag(args, "--engine", "legacy");
|
|
32
|
+
const keepDb = args.includes("--keep-db");
|
|
33
|
+
const withBundle = args.includes("--with-bundle-load");
|
|
34
|
+
const withArtifacts = args.includes("--with-artifact-seed");
|
|
35
|
+
const withBusinessCapabilities = args.includes("--with-business-capabilities");
|
|
36
|
+
const skipProvenance = args.includes("--skip-provenance-validation");
|
|
37
|
+
const requireHybrid = args.includes("--require-hybrid");
|
|
38
|
+
|
|
39
|
+
const report = {
|
|
40
|
+
ok: true,
|
|
41
|
+
db_path: dbPath,
|
|
42
|
+
steps: [],
|
|
43
|
+
started_at: new Date().toISOString()
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
if (!keepDb && existsSync(dbPath)) {
|
|
48
|
+
rmSync(dbPath);
|
|
49
|
+
report.steps.push({ step: "delete_db", ok: true });
|
|
50
|
+
}
|
|
51
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
52
|
+
|
|
53
|
+
runStep("build", [join(immeubleRoot, "scripts/build-immeuble-model.mjs")]);
|
|
54
|
+
|
|
55
|
+
const importArgs = [
|
|
56
|
+
join(immeubleRoot, "scripts/run-immeuble-import.mjs"),
|
|
57
|
+
"--apply",
|
|
58
|
+
"--db", dbPath,
|
|
59
|
+
"--engine", engine,
|
|
60
|
+
"--skip-preflight",
|
|
61
|
+
"--force"
|
|
62
|
+
];
|
|
63
|
+
if (skipProvenance) importArgs.push("--skip-provenance-validation");
|
|
64
|
+
if (engine === "both") {
|
|
65
|
+
importArgs.push("--compare-output", join(reportsDir, "hybrid-compare.json"));
|
|
66
|
+
}
|
|
67
|
+
runStep("import", importArgs);
|
|
68
|
+
|
|
69
|
+
if (withArtifacts) {
|
|
70
|
+
runStep("artifact_seed", [
|
|
71
|
+
join(pkgRoot, "bin/gcp.mjs"),
|
|
72
|
+
"load", join(immeubleRoot, "contracts/answer_artifacts.seed.jsonl"),
|
|
73
|
+
"--workspace", "immeuble"
|
|
74
|
+
], { env: { GHOSTCRAB_SQLITE_PATH: dbPath } });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (withBusinessCapabilities) {
|
|
78
|
+
runStep("business_capability_seed", [
|
|
79
|
+
join(pkgRoot, "bin/gcp.mjs"),
|
|
80
|
+
"load", join(immeubleRoot, "contracts/business_capabilities.seed.jsonl"),
|
|
81
|
+
"--workspace", "immeuble"
|
|
82
|
+
], { env: { GHOSTCRAB_SQLITE_PATH: dbPath } });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (withBundle) {
|
|
86
|
+
runStep("bundle_load", [
|
|
87
|
+
join(pkgRoot, "bin/gcp.mjs"),
|
|
88
|
+
"load", join(immeubleRoot, "bundle/immeuble.bundle.json"),
|
|
89
|
+
"--workspace", "immeuble",
|
|
90
|
+
"--reindex", "all"
|
|
91
|
+
], { env: { GHOSTCRAB_SQLITE_PATH: dbPath } });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const verifyArgs = [
|
|
95
|
+
join(immeubleRoot, "scripts/verify-immeuble-acceptance.mjs"),
|
|
96
|
+
"--db", dbPath
|
|
97
|
+
];
|
|
98
|
+
if (requireHybrid) verifyArgs.push("--require-hybrid");
|
|
99
|
+
if (withBundle) verifyArgs.push("--require-bundle");
|
|
100
|
+
if (withBusinessCapabilities) verifyArgs.push("--require-business-capabilities");
|
|
101
|
+
runStep("verify_acceptance", verifyArgs);
|
|
102
|
+
|
|
103
|
+
runStep("audit_projections", [join(immeubleRoot, "scripts/audit-immeuble-projections.mjs")], { optional: true });
|
|
104
|
+
|
|
105
|
+
report.finished_at = new Date().toISOString();
|
|
106
|
+
writeFileSync(join(reportsDir, "reset-immeuble-workspace.json"), JSON.stringify(report, null, 2) + "\n", "utf8");
|
|
107
|
+
console.log(JSON.stringify({ ok: true, db_path: dbPath, steps: report.steps.length }, null, 2));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
report.ok = false;
|
|
110
|
+
report.error = err instanceof Error ? err.message : String(err);
|
|
111
|
+
report.finished_at = new Date().toISOString();
|
|
112
|
+
writeFileSync(join(reportsDir, "reset-immeuble-workspace.json"), JSON.stringify(report, null, 2) + "\n", "utf8");
|
|
113
|
+
console.error(JSON.stringify(report, null, 2));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function runStep(name, cmdArgs, opts = {}) {
|
|
118
|
+
const env = { ...process.env, ...(opts.env ?? {}), GHOSTCRAB_SQLITE_PATH: opts.env?.GHOSTCRAB_SQLITE_PATH ?? dbPath };
|
|
119
|
+
let res = spawnSync(node, cmdArgs, { cwd: pkgRoot, env, encoding: "utf8" });
|
|
120
|
+
if (res.status !== 0 && opts.fallback) {
|
|
121
|
+
res = spawnSync(opts.fallback[0], opts.fallback.slice(1), { cwd: pkgRoot, env, encoding: "utf8" });
|
|
122
|
+
}
|
|
123
|
+
const ok = res.status === 0;
|
|
124
|
+
report.steps.push({
|
|
125
|
+
step: name,
|
|
126
|
+
ok,
|
|
127
|
+
status: res.status,
|
|
128
|
+
stderr: (res.stderr || "").slice(0, 400)
|
|
129
|
+
});
|
|
130
|
+
if (!ok && !opts.optional) {
|
|
131
|
+
throw new Error(`${name} failed: ${res.stderr || res.stdout}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseFlag(argv, name, defaultValue) {
|
|
136
|
+
const index = argv.indexOf(name);
|
|
137
|
+
if (index === -1) return defaultValue;
|
|
138
|
+
return argv[index + 1] ?? defaultValue;
|
|
139
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Launch the Immeuble backend in standalone HTTP mode for live pipelines.
|
|
3
|
+
set -eu
|
|
4
|
+
|
|
5
|
+
REPO_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)"
|
|
6
|
+
IMMEUBLE_ROOT="$REPO_ROOT/examples/immeuble"
|
|
7
|
+
|
|
8
|
+
BACKEND_BIN="${GHOSTCRAB_BACKEND_BIN:-$REPO_ROOT/cmd/backend/zig-out/bin/ghostcrab-backend}"
|
|
9
|
+
BACKEND_ADDR=":8091"
|
|
10
|
+
DB_PATH="$REPO_ROOT/data/immeuble-lab.sqlite"
|
|
11
|
+
WORKSPACE_NAME="immeuble"
|
|
12
|
+
LOG_FILE="$IMMEUBLE_ROOT/.tmp-immeuble-backend.log"
|
|
13
|
+
READY_TIMEOUT_SEC=20
|
|
14
|
+
WAIT_FOR_HEALTH=1
|
|
15
|
+
FOREGROUND=0
|
|
16
|
+
KILL_PORT=1
|
|
17
|
+
|
|
18
|
+
usage() {
|
|
19
|
+
cat <<EOF
|
|
20
|
+
Usage: examples/immeuble/scripts/run-immeuble-backend.sh [options]
|
|
21
|
+
|
|
22
|
+
Run ghostcrab-backend in standalone HTTP mode with an Immeuble workspace context.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--db <path> SQLite file to use (default: $DB_PATH)
|
|
26
|
+
--backend-bin <path> ghostcrab-backend binary path
|
|
27
|
+
--backend-addr <:port> listen address (default: :8091)
|
|
28
|
+
--workspace-name <name> value for GHOSTCRAB_WORKSPACE_NAME (default: immeuble)
|
|
29
|
+
--log-file <path> where backend logs are written
|
|
30
|
+
--ready-timeout <sec> health-check timeout (default: 20)
|
|
31
|
+
--foreground keep backend attached in the current shell
|
|
32
|
+
--no-health-check do not wait for /health
|
|
33
|
+
--no-kill-port do not free the listen port before start
|
|
34
|
+
-h, --help show this help
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for arg in "$@"; do
|
|
39
|
+
case "$arg" in
|
|
40
|
+
-h|--help)
|
|
41
|
+
usage
|
|
42
|
+
exit 0
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
while [[ $# -gt 0 ]]; do
|
|
48
|
+
case "$1" in
|
|
49
|
+
--db)
|
|
50
|
+
DB_PATH="${2:?--db requires a value}"
|
|
51
|
+
shift 2
|
|
52
|
+
;;
|
|
53
|
+
--backend-bin)
|
|
54
|
+
BACKEND_BIN="${2:?--backend-bin requires a value}"
|
|
55
|
+
shift 2
|
|
56
|
+
;;
|
|
57
|
+
--backend-addr)
|
|
58
|
+
BACKEND_ADDR="${2:?--backend-addr requires a value}"
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
--workspace-name)
|
|
62
|
+
WORKSPACE_NAME="${2:?--workspace-name requires a value}"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
--log-file)
|
|
66
|
+
LOG_FILE="${2:?--log-file requires a value}"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
--ready-timeout)
|
|
70
|
+
READY_TIMEOUT_SEC="${2:?--ready-timeout requires a value}"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
--no-health-check)
|
|
74
|
+
WAIT_FOR_HEALTH=0
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
--foreground)
|
|
78
|
+
FOREGROUND=1
|
|
79
|
+
shift
|
|
80
|
+
;;
|
|
81
|
+
--no-kill-port)
|
|
82
|
+
KILL_PORT=0
|
|
83
|
+
shift
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
echo "[immeuble-backend] unknown argument: $1" >&2
|
|
87
|
+
usage
|
|
88
|
+
exit 1
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
if [[ ! -x "$BACKEND_BIN" ]]; then
|
|
94
|
+
echo "[immeuble-backend] backend binary not found: $BACKEND_BIN" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
mkdir -p "$(dirname "$DB_PATH")"
|
|
99
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
100
|
+
|
|
101
|
+
BACKEND_PORT="${BACKEND_ADDR##*:}"
|
|
102
|
+
BACKEND_URL="http://127.0.0.1:${BACKEND_PORT}"
|
|
103
|
+
|
|
104
|
+
if (( KILL_PORT == 1 )) && command -v fuser >/dev/null 2>&1; then
|
|
105
|
+
fuser -k "${BACKEND_ADDR#:}/tcp" 2>/dev/null || true
|
|
106
|
+
sleep 0.2
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
run_backend() {
|
|
110
|
+
env \
|
|
111
|
+
GHOSTCRAB_BACKEND_ADDR="$BACKEND_ADDR" \
|
|
112
|
+
GHOSTCRAB_SQLITE_PATH="$DB_PATH" \
|
|
113
|
+
GHOSTCRAB_WORKSPACE_NAME="$WORKSPACE_NAME" \
|
|
114
|
+
"$BACKEND_BIN"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (( FOREGROUND == 1 )); then
|
|
118
|
+
echo "[immeuble-backend] starting foreground: $BACKEND_BIN -- $BACKEND_ADDR (db=$DB_PATH)"
|
|
119
|
+
if ! run_backend 2>&1 | tee "$LOG_FILE"; then
|
|
120
|
+
echo "[immeuble-backend] backend exited with failure" >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
exit 0
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo "[immeuble-backend] starting detached: $BACKEND_BIN -- $BACKEND_ADDR (db=$DB_PATH)"
|
|
127
|
+
run_backend >"$LOG_FILE" 2>&1 &
|
|
128
|
+
BACKEND_PID=$!
|
|
129
|
+
|
|
130
|
+
if [[ "$WAIT_FOR_HEALTH" -ne 0 ]]; then
|
|
131
|
+
ready=0
|
|
132
|
+
for attempt in $(seq 1 200); do
|
|
133
|
+
if curl -sf "$BACKEND_URL/health" >/dev/null 2>&1; then
|
|
134
|
+
ready=1
|
|
135
|
+
break
|
|
136
|
+
fi
|
|
137
|
+
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
|
|
138
|
+
echo "[immeuble-backend] backend exited before health check" >&2
|
|
139
|
+
cat "$LOG_FILE" >&2
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
if (( READY_TIMEOUT_SEC > 0 )); then
|
|
143
|
+
# Keep legacy 0.2s cadence and enforce timeout.
|
|
144
|
+
sleep 0.2
|
|
145
|
+
if (( attempt >= READY_TIMEOUT_SEC * 5 )); then
|
|
146
|
+
break
|
|
147
|
+
fi
|
|
148
|
+
else
|
|
149
|
+
sleep 0.2
|
|
150
|
+
fi
|
|
151
|
+
done
|
|
152
|
+
|
|
153
|
+
if [[ "$ready" -ne 1 ]]; then
|
|
154
|
+
echo "[immeuble-backend] backend did not become healthy at $BACKEND_URL after ${READY_TIMEOUT_SEC}s" >&2
|
|
155
|
+
cat "$LOG_FILE" >&2
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
echo "[immeuble-backend] started"
|
|
161
|
+
echo "[immeuble-backend] BACKEND_PID=$BACKEND_PID"
|
|
162
|
+
echo "[immeuble-backend] BACKEND_URL=$BACKEND_URL"
|
|
163
|
+
echo "[immeuble-backend] BACKEND_DB=$DB_PATH"
|
|
164
|
+
echo "[immeuble-backend] BACKEND_LOG=$LOG_FILE"
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Immeuble import scenario — plan, apply, reindex, provenance, schema prefix checks.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node examples/immeuble/scripts/run-immeuble-import.mjs [--apply] [--engine both] ...
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
const immeubleRoot = resolve(fileURLToPath(import.meta.url), "..", "..");
|
|
16
|
+
const pkgRoot = resolve(immeubleRoot, "..", "..");
|
|
17
|
+
const gcp = join(pkgRoot, "bin", "gcp.mjs");
|
|
18
|
+
const runner = join(pkgRoot, "scripts", "run-structured-import-system.mjs");
|
|
19
|
+
const defaultManifest = join(immeubleRoot, "import_manifest.yaml");
|
|
20
|
+
const reportsDir = join(immeubleRoot, "reports");
|
|
21
|
+
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const workspaceId = parseFlag(args, "--workspace-id", "immeuble");
|
|
24
|
+
const engine = parseFlag(args, "--engine", "legacy");
|
|
25
|
+
const manifestPath = parseFlag(args, "--manifest", defaultManifest);
|
|
26
|
+
const dbPath = parseFlag(
|
|
27
|
+
args,
|
|
28
|
+
"--db",
|
|
29
|
+
join(mkdtempSync(join(tmpdir(), "gcp-immeuble-")), "immeuble.sqlite")
|
|
30
|
+
);
|
|
31
|
+
const evidenceDir = parseFlag(args, "--evidence-dir", reportsDir);
|
|
32
|
+
const compareOutput = parseFlag(args, "--compare-output", join(reportsDir, "hybrid-compare.json"));
|
|
33
|
+
const runWithApply = args.includes("--apply");
|
|
34
|
+
const runWithSkipPreflight = args.includes("--skip-preflight");
|
|
35
|
+
const runWithPreflight = args.includes("--preflight");
|
|
36
|
+
const runWithSkipProvenance =
|
|
37
|
+
args.includes("--skip-provenance-validation") || args.includes("--no-validate-provenance");
|
|
38
|
+
const runWithForce = args.includes("--force");
|
|
39
|
+
|
|
40
|
+
mkdirSync(evidenceDir, { recursive: true });
|
|
41
|
+
mkdirSync(reportsDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const evidence = {
|
|
44
|
+
workspace_id: workspaceId,
|
|
45
|
+
db_path: dbPath,
|
|
46
|
+
manifest: manifestPath,
|
|
47
|
+
engine,
|
|
48
|
+
apply: runWithApply,
|
|
49
|
+
preflight: runWithSkipPreflight ? "skipped" : runWithPreflight ? "forced" : "manifest-default",
|
|
50
|
+
provenance: runWithSkipProvenance ? "skipped" : "forced",
|
|
51
|
+
phases: [],
|
|
52
|
+
ok: true
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const plan = runRunner(false);
|
|
57
|
+
evidence.phases.push({ step: "plan", summary: plan.summary });
|
|
58
|
+
|
|
59
|
+
if (runWithApply) {
|
|
60
|
+
const apply = runRunner(true);
|
|
61
|
+
evidence.phases.push({ step: "apply", summary: apply.summary });
|
|
62
|
+
|
|
63
|
+
const reindexArgs = ["structured-import", "reindex", "--workspace-id", workspaceId, "--scope", "all"];
|
|
64
|
+
if (runWithForce) reindexArgs.splice(1, 0, "--force");
|
|
65
|
+
const reindex = runGcp(reindexArgs, true);
|
|
66
|
+
evidence.reindex = reindex.json;
|
|
67
|
+
writeFileSync(join(reportsDir, "reindex.json"), JSON.stringify(reindex.json, null, 2) + "\n", "utf8");
|
|
68
|
+
|
|
69
|
+
const applySummary = evidence.phases.find((phase) => phase.step === "apply")?.summary;
|
|
70
|
+
const hybridReindex = applySummary?.compare?.hybrid_summary ?? null;
|
|
71
|
+
const graphProjected =
|
|
72
|
+
typeof reindex.json?.graph_projected === "number" && reindex.json.graph_projected > 0
|
|
73
|
+
? reindex.json.graph_projected
|
|
74
|
+
: typeof hybridReindex?.graph_projected === "number"
|
|
75
|
+
? hybridReindex.graph_projected
|
|
76
|
+
: 0;
|
|
77
|
+
|
|
78
|
+
if (graphProjected <= 0) {
|
|
79
|
+
throw new Error(`reindex.graph_projected expected > 0, got ${reindex.json?.graph_projected ?? 0}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
(reindex.json?.graph_projected ?? 0) <= 0 &&
|
|
84
|
+
typeof hybridReindex?.graph_projected === "number" &&
|
|
85
|
+
hybridReindex.graph_projected > 0
|
|
86
|
+
) {
|
|
87
|
+
evidence.reindex = {
|
|
88
|
+
...reindex.json,
|
|
89
|
+
graph_projected: hybridReindex.graph_projected,
|
|
90
|
+
facet_assignments: hybridReindex.facet_assignments ?? reindex.json?.facet_assignments ?? 0,
|
|
91
|
+
source: "hybrid-apply"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const prefixCheck = assertSchemaIdPrefix(dbPath, workspaceId);
|
|
96
|
+
evidence.schema_id_prefix_check = prefixCheck;
|
|
97
|
+
writeFileSync(join(reportsDir, "schema-id-prefix-check.json"), JSON.stringify(prefixCheck, null, 2) + "\n", "utf8");
|
|
98
|
+
if (!prefixCheck.ok) {
|
|
99
|
+
throw new Error(`schema_id prefix violations: ${JSON.stringify(prefixCheck.violations.slice(0, 5))}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!runWithSkipProvenance) {
|
|
103
|
+
const provenance = runGcp(["structured-import", "validate-provenance", "--workspace-id", workspaceId], true);
|
|
104
|
+
evidence.provenance = provenance.json;
|
|
105
|
+
if (provenance.json?.ok !== true) {
|
|
106
|
+
throw new Error(`provenance validation failed: ${JSON.stringify(provenance.json)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (engine === "both") {
|
|
112
|
+
evidence.hybrid_compare_path = compareOutput;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const reportPath = join(evidenceDir, "immeuble-import-scenario.json");
|
|
116
|
+
evidence.report_path = reportPath;
|
|
117
|
+
writeFileSync(reportPath, JSON.stringify(evidence, null, 2) + "\n", "utf8");
|
|
118
|
+
console.log(JSON.stringify({ ...evidence, ok: true }, null, 2));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
evidence.ok = false;
|
|
121
|
+
evidence.error = err instanceof Error ? err.message : String(err);
|
|
122
|
+
const reportPath = join(evidenceDir, "immeuble-import-scenario.json");
|
|
123
|
+
evidence.report_path = reportPath;
|
|
124
|
+
writeFileSync(reportPath, JSON.stringify(evidence, null, 2) + "\n", "utf8");
|
|
125
|
+
console.error(JSON.stringify({ ...evidence, ok: false }, null, 2));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function runRunner(includeApply) {
|
|
130
|
+
const runnerArgs = [
|
|
131
|
+
"--manifest", manifestPath,
|
|
132
|
+
"--workspace-id", workspaceId,
|
|
133
|
+
"--db", dbPath,
|
|
134
|
+
"--engine", engine
|
|
135
|
+
];
|
|
136
|
+
if (compareOutput && engine === "both") {
|
|
137
|
+
runnerArgs.push("--compare-output", compareOutput);
|
|
138
|
+
}
|
|
139
|
+
if (runWithPreflight) runnerArgs.push("--preflight");
|
|
140
|
+
else if (runWithSkipPreflight) runnerArgs.push("--skip-preflight");
|
|
141
|
+
if (runWithSkipProvenance) runnerArgs.push("--skip-provenance-validation");
|
|
142
|
+
if (runWithForce) runnerArgs.push("--force");
|
|
143
|
+
if (includeApply) runnerArgs.push("--apply");
|
|
144
|
+
|
|
145
|
+
const res = runCommand(process.execPath, [runner, ...runnerArgs], "run-structured-import-system");
|
|
146
|
+
if (res.status !== 0) {
|
|
147
|
+
throw new Error(`${res.label} failed (${res.status}): ${res.output}`);
|
|
148
|
+
}
|
|
149
|
+
const summary = parseSummary(res.stdout);
|
|
150
|
+
assertActivity(summary, includeApply ? "apply" : "plan");
|
|
151
|
+
return { stdout: res.stdout, summary };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function runGcp(brainArgs, parseJson = false) {
|
|
155
|
+
const res = runCommand(process.execPath, [gcp, "brain", ...brainArgs], "gcp brain");
|
|
156
|
+
if (res.status !== 0) {
|
|
157
|
+
throw new Error(`${res.label} failed (${res.status}): ${res.output}`);
|
|
158
|
+
}
|
|
159
|
+
return { stdout: res.stdout, json: parseJson ? parseSummary(res.stdout) : null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function runCommand(executable, cmdArgs, label) {
|
|
163
|
+
const direct = spawnSync(executable, cmdArgs, {
|
|
164
|
+
cwd: pkgRoot,
|
|
165
|
+
env: { ...process.env, GHOSTCRAB_SQLITE_PATH: dbPath },
|
|
166
|
+
encoding: "utf8"
|
|
167
|
+
});
|
|
168
|
+
const output = [direct.stdout || "", direct.stderr || ""].join("");
|
|
169
|
+
return { status: direct.status ?? 0, stdout: direct.stdout || "", output, label };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseSummary(text) {
|
|
173
|
+
if (typeof text !== "string" || !text.trim()) return null;
|
|
174
|
+
try { return JSON.parse(text.trim()); } catch { /* continue */ }
|
|
175
|
+
const lines = text.trim().split("\n");
|
|
176
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
177
|
+
const line = lines[i].trim();
|
|
178
|
+
if (line.startsWith("{")) {
|
|
179
|
+
try { return JSON.parse(lines.slice(i).join("\n")); } catch { /* continue */ }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractKitSummary(payload) {
|
|
186
|
+
if (!payload || typeof payload !== "object") return null;
|
|
187
|
+
if (payload.runs && typeof payload.runs === "object") {
|
|
188
|
+
const merged = {};
|
|
189
|
+
for (const run of Object.values(payload.runs)) {
|
|
190
|
+
const s = run?.summary_parsed ?? (typeof run?.summary === "string" ? parseSummary(run.summary) : run?.summary);
|
|
191
|
+
if (s && typeof s === "object") Object.assign(merged, s);
|
|
192
|
+
}
|
|
193
|
+
return Object.keys(merged).length ? merged : null;
|
|
194
|
+
}
|
|
195
|
+
return payload.project ?? payload.summary_parsed ?? (typeof payload.summary === "string" ? parseSummary(payload.summary) : payload.summary);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function assertActivity(summary, label) {
|
|
199
|
+
const kit = extractKitSummary(summary);
|
|
200
|
+
const total = [
|
|
201
|
+
"entities_upserted", "facets_inserted", "edges_inserted", "facet_rows", "edge_rows"
|
|
202
|
+
].reduce((acc, key) => acc + (typeof kit?.[key] === "number" ? kit[key] : 0), 0);
|
|
203
|
+
if (total <= 0) {
|
|
204
|
+
throw new Error(`${label}: expected activity > 0, got ${JSON.stringify(kit)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function assertSchemaIdPrefix(db, workspace) {
|
|
209
|
+
let sqlite3;
|
|
210
|
+
try {
|
|
211
|
+
sqlite3 = spawnSync("sqlite3", [db, "-json", `SELECT schema_id FROM agent_facts WHERE workspace_id='${workspace}'`], { encoding: "utf8" });
|
|
212
|
+
} catch {
|
|
213
|
+
return { ok: true, skipped: true, reason: "sqlite3 not available" };
|
|
214
|
+
}
|
|
215
|
+
if (sqlite3.status !== 0) {
|
|
216
|
+
return { ok: true, skipped: true, reason: sqlite3.stderr || "sqlite3 query failed" };
|
|
217
|
+
}
|
|
218
|
+
let rows = [];
|
|
219
|
+
try { rows = JSON.parse(sqlite3.stdout || "[]"); } catch { rows = []; }
|
|
220
|
+
const violations = rows.filter((r) => r.schema_id && !String(r.schema_id).startsWith("immeuble:"));
|
|
221
|
+
return {
|
|
222
|
+
ok: violations.length === 0,
|
|
223
|
+
checked: rows.length,
|
|
224
|
+
violations: violations.map((r) => r.schema_id)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseFlag(argv, name, defaultValue) {
|
|
229
|
+
const index = argv.indexOf(name);
|
|
230
|
+
if (index === -1) return defaultValue;
|
|
231
|
+
return argv[index + 1] ?? defaultValue;
|
|
232
|
+
}
|