@piecemaker-legal/piecemaker 2.0.9 → 2.0.11
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/dist/assets/FindReplaceDialog-KU5TEIBJ-BucOHLdm.js +1 -0
- package/dist/assets/FootnotePropertiesDialog-W3YHM4BV-D5UqbelV.js +1 -0
- package/dist/assets/HyperlinkDialog-ZYH7ZHRF-8yEhLigd.js +144 -0
- package/dist/assets/HyperlinkDialog-ZYH7ZHRF-qIPBOssF.css +1 -0
- package/dist/assets/ImagePositionDialog-HEWYR6Q3-wGIBYxCm.js +1 -0
- package/dist/assets/ImagePropertiesDialog-RAB47C6M-D86PckZ2.js +1 -0
- package/dist/assets/PageSetupDialog-AIGIXO3E-DLETP-7v.js +1 -0
- package/dist/assets/SplitCellDialog-IFBKYCD6-CwdQBXeb.js +1 -0
- package/dist/assets/TablePropertiesDialog-TMLKGOOW-j2nA7KJs.js +1 -0
- package/dist/assets/WatermarkDialog-KRL7WMIS-XFJ9gTkC.js +1 -0
- package/dist/assets/{abnfDiagram-VCTEODGH-WrsSAHOe.js → abnfDiagram-VCTEODGH-DiCrND_Y.js} +1 -1
- package/dist/assets/arc-DOjgbMYz.js +1 -0
- package/dist/assets/architectureDiagram-5GKGNRK7-brzIFVhl.js +36 -0
- package/dist/assets/{blockDiagram-I7D4REHJ-zGMnF7Ox.js → blockDiagram-I7D4REHJ-C8JhTjQQ.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-ClWw3bqt.js → c4Diagram-7LVT6UL2-jfYxccyk.js} +1 -1
- package/dist/assets/channel-Dajzu2W1.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-C-9ICPHv.js → chunk-2Q5K7J3B-CrlEShp9.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-Bawy3teT.js → chunk-5VM5RSS4-DzPlUHGq.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-DQ375RQO.js → chunk-F27PBJKO-Cp9cpeWU.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-CflNF2f6.js → chunk-IMKFNOWR-BifUldEN.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CIXQv0gc.js → chunk-JWPE2WC7-ggPEIkWu.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-Cza5j6Qw.js → chunk-POPQ4Y6H-3h_nayRX.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-GQ_VvWZR.js → chunk-SVP7TREG-CH29aG6J.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-BEmU16W5.js → chunk-TICWLB2K-PluJUM5k.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-C1KpkLYH.js → chunk-XXDRQBXY-DXU39Vwg.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-dDEP_giv.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-dDEP_giv.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-CZE48TRG.js → cose-bilkent-JH36ORCC-Dz6roYrN.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-L-tx1eC-.js → cynefin-OW5HDTMX-BLzrpSit.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-Coa8oJwR.js → cynefinDiagram-5FMLGOSQ-kfxnERDZ.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-Bu_3Y75K.js → dagre-GXQ25YYZ-DP57bp-N.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CFiiV4es.js → diagram-S7CK7UJ4-DAlsrQVW.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CCXheGAA.js → diagram-UQ7AKVKN-By5yrMer.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-B70m4lr_.js → diagram-VSXAHHWV-Dx89kryN.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-CHkiMOVA.js → diagram-VX7I27RA-B9fARitZ.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-_awpc4Bs.js → diagram-Z3DM3KII-DC15CEH1.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-CZzjH86H.js → ebnfDiagram-PWID7BFC-BKQzghGF.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-9DgiHZg9.js → erDiagram-RLTQ6QDP-CPY6RM7G.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DHY_503b.js → flowDiagram-HODETNUW-4dL4GJcf.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-DPiiYZ_l.js → ganttDiagram-EL5Y4UJY-D4-cVcfb.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-Ddd_evm7.js → gitGraphDiagram-WWUBYQGX-BMeVIEiX.js} +1 -1
- package/dist/assets/{index-tibHq7Qp.js → index-DNtFDOni.js} +140 -140
- package/dist/assets/index-Dc9wNuS4.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-BPRNhfwb.js → infoDiagram-27XIBGKW-D5qMSUEU.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-BXx6aqkq.js → ishikawaDiagram-5VMMS53U-BDnyKRHm.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-BK8TZM29.js → journeyDiagram-3NMN7TZE-dGz5ZGzq.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-CHioRmxz.js → kanban-definition-UXKFOSKX-D7UAz7o_.js} +6 -6
- package/dist/assets/layout-DKrmEGrz.js +1 -0
- package/dist/assets/{linear-CTEhUcKG.js → linear-CrTdMY-p.js} +1 -1
- package/dist/assets/{mermaid.core-DYUb_UUB.js → mermaid.core-CXQOT5G6.js} +7 -7
- package/dist/assets/{mindmap-definition-YA3MSWOX-D7ODZ4oS.js → mindmap-definition-YA3MSWOX-VgEiYfay.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-8J-_2_kw.js → pegDiagram-XKGWAZYB-9IqT8gGx.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-ebg0yfMi.js → pieDiagram-E7YTZNPT-D98pRE-t.js} +1 -1
- package/dist/assets/processTemplate-GPFDLQMX-B1sVV2O0.js +22 -0
- package/dist/assets/{quadrantDiagram-AXDQQJYC-BoWkDiFb.js → quadrantDiagram-AXDQQJYC-b9PwkYzL.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BRJ77ldz.js → railroadDiagram-O6MQD6OU-Dz87hsPE.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-Z0JJgktA.js → requirementDiagram-BXWQKSXE-CkcV73dK.js} +1 -1
- package/dist/assets/sankeyDiagram-P5KCCOFB-UBN31Yrs.js +40 -0
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-BPAjtXwp.js → sequenceDiagram-WJ2MYXX4-CpQxd5Za.js} +1 -1
- package/dist/assets/{server.browser-BzI1jrZw.js → server.browser-BY3bL-IX.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BGZPCzM4.js → sizeCapture-INFHLROL-mCsFmyzw.js} +1 -1
- package/dist/assets/stateDiagram-D77RDMKH-B0MTBN-3.js +1 -0
- package/dist/assets/stateDiagram-v2-MP3YSRHH-DzBPN39W.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-DvuB15qF.js → swimlanes-42K2YHIH-Dzydjm0e.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-BXQpbUMD.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-ZIziYCIF.js → timeline-definition-24CTP7MA-Dz0toOLu.js} +3 -3
- package/dist/assets/{vendor-codemirror-CjW0lHR5.js → vendor-codemirror-CN-sJePf.js} +2 -2
- package/dist/assets/{vendor-react-D809iwXR.js → vendor-react-B22wqN3J.js} +5 -5
- package/dist/assets/{vennDiagram-4TSXK5OY-C5_B9gWa.js → vennDiagram-4TSXK5OY-DOWp7dkf.js} +4 -4
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DMlH9Afw.js → wardleyDiagram-VM6X3IG4-CUxsztUB.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-BldhJt7i.js → xychartDiagram-S5SC5T6Z-DS96vNED.js} +1 -1
- package/dist/index.html +4 -4
- package/dist/sw.js +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +4 -53
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +2 -3
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js +30 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js +24 -0
- package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js.map +1 -0
- package/dist-server/server/piecemaker/agent-instructions.js +148 -0
- package/dist-server/server/piecemaker/agent-instructions.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js +2 -3
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -1
- package/dist-server/server/piecemaker/docx-document.js +50 -0
- package/dist-server/server/piecemaker/docx-document.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +13 -2
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/model-discovery/codex-catalog.js +46 -0
- package/dist-server/server/piecemaker/model-discovery/codex-catalog.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/index.js +42 -0
- package/dist-server/server/piecemaker/model-discovery/index.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js +19 -0
- package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js.map +1 -0
- package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js +61 -0
- package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js.map +1 -0
- package/dist-server/server/piecemaker/project-registry.js +29 -0
- package/dist-server/server/piecemaker/project-registry.js.map +1 -0
- package/package.json +12 -1
- package/scripts/piecemaker/cli/disable-anonymizer.mjs +19 -0
- package/scripts/piecemaker/cli/lib/config.mjs +0 -1
- package/scripts/piecemaker/cli/lib/plugins.mjs +1 -0
- package/scripts/piecemaker/cli/lib/services.mjs +1 -10
- package/scripts/piecemaker/cli/piecemaker.mjs +0 -3
- package/server/modules/providers/list/mistral/mistral-models.provider.ts +4 -53
- package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +2 -3
- package/server/modules/providers/list/mistral/mistral-vibe-models.ts +43 -0
- package/server/modules/providers/tests/mistral-vibe-models.test.ts +27 -0
- package/server/piecemaker/agent-instructions.ts +148 -0
- package/server/piecemaker/anonymizer/client-config.cjs +92 -92
- package/server/piecemaker/anonymizer/dictionary.cjs +34 -3
- package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +9 -16
- package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +1 -1
- package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +251 -268
- package/server/piecemaker/anonymizer/hudsucker-smoke.cjs +343 -0
- package/server/piecemaker/anonymizer/lifecycle.ts +2 -3
- package/server/piecemaker/anonymizer/rewriter-bridge.cjs +13 -4
- package/server/piecemaker/anonymizer/rewriter-worker.cjs +85 -0
- package/server/piecemaker/anonymizer/service.cjs +109 -179
- package/server/piecemaker/anonymizer/sqlite-dictionary.cjs +12 -1
- package/server/piecemaker/docx-document.ts +54 -0
- package/server/piecemaker/harness/decisions.cjs +1 -1
- package/server/piecemaker/harness/index.cjs +2 -3
- package/server/piecemaker/harness/verification.cjs +1 -1
- package/server/piecemaker/index.ts +15 -3
- package/server/piecemaker/model-discovery/codex-catalog.ts +67 -0
- package/server/piecemaker/model-discovery/index.ts +69 -0
- package/server/piecemaker/model-discovery/mistral-catalog.ts +36 -0
- package/server/piecemaker/model-discovery/stdio-rpc.ts +79 -0
- package/server/piecemaker/project-registry.ts +34 -0
- package/server/piecemaker/router.cjs +6 -8
- package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +31 -77
- package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +2 -84
- package/server/piecemaker/vendor/installer/lib/service.mjs +4 -24
- package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +5 -5
- package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +10 -21
- package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -11
- package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +6 -36
- package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +30 -12
- package/server/piecemaker/vendor/piecemaker-plugin/README.md +2 -9
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +5 -5
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +8 -8
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +2 -2
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +4 -6
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +2 -90
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +71 -38
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +8 -38
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +82 -61
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +189 -41
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +4 -55
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +24 -117
- package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +4 -18
- package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +2 -2
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +14 -148
- package/server/piecemaker/vendor/websocket-server/case-registry.cjs +16 -42
- package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +0 -73
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +0 -7
- package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +3 -12
- package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +6 -6
- package/dist/assets/arc-DAA6wH-C.js +0 -1
- package/dist/assets/architectureDiagram-5GKGNRK7-BGNHr7ef.js +0 -36
- package/dist/assets/channel-BAQyfMll.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BNGCuvrR.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BNGCuvrR.js +0 -1
- package/dist/assets/index-WwknP0l4.css +0 -1
- package/dist/assets/layout-DEnkyfTY.js +0 -1
- package/dist/assets/sankeyDiagram-P5KCCOFB-4PW5HTOP.js +0 -40
- package/dist/assets/stateDiagram-D77RDMKH-o-vEcKwz.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BsuDaKkN.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-DcCAyKlT.js +0 -8
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +0 -78
- package/scripts/piecemaker/hooks/proxy-guard.mjs +0 -251
- package/server/piecemaker/anonymizer/providers.cjs +0 -120
- package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +0 -482
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +0 -602
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
const { execFileSync, spawn } = require('node:child_process');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const http = require('node:http');
|
|
4
|
+
const https = require('node:https');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const tls = require('node:tls');
|
|
8
|
+
|
|
9
|
+
const Database = require('better-sqlite3');
|
|
10
|
+
|
|
11
|
+
const { startRewriterWorker } = require('./rewriter-worker.cjs');
|
|
12
|
+
|
|
13
|
+
const HOST = 'api.anthropic.com';
|
|
14
|
+
const NAME = 'Jean Dupont';
|
|
15
|
+
const CODE = 'PERSONNE_PHYSIQUE_01';
|
|
16
|
+
const CHECK_TIMEOUT_MS = 10000;
|
|
17
|
+
const SANDBOX_EXEC = '/usr/bin/sandbox-exec';
|
|
18
|
+
|
|
19
|
+
function defaultBinary() {
|
|
20
|
+
const name = process.platform === 'win32' ? 'piecemaker-hudsucker.exe' : 'piecemaker-hudsucker';
|
|
21
|
+
return [
|
|
22
|
+
path.join(__dirname, 'bin', name),
|
|
23
|
+
path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name),
|
|
24
|
+
].find((candidate) => fs.existsSync(candidate)) || path.join(__dirname, 'bin', name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mappingDatabase(directory) {
|
|
28
|
+
const file = path.join(directory, 'auth.db');
|
|
29
|
+
const database = new Database(file);
|
|
30
|
+
database.exec(`
|
|
31
|
+
CREATE TABLE piecemaker_nodes (project_id TEXT, id TEXT, label TEXT, PRIMARY KEY (project_id, id));
|
|
32
|
+
CREATE TABLE piecemaker_mappings (project_id TEXT, node_id TEXT, real_value TEXT, masked_value TEXT, updated_at TEXT);
|
|
33
|
+
`);
|
|
34
|
+
database.prepare('INSERT INTO piecemaker_nodes VALUES (?, ?, ?)').run('smoke', 'n0', NAME);
|
|
35
|
+
database.prepare('INSERT INTO piecemaker_mappings VALUES (?, ?, ?, ?, ?)').run('smoke', 'n0', NAME, CODE, '2026-01-01T00:00:00.000Z');
|
|
36
|
+
database.close();
|
|
37
|
+
return file;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function temporaryAuthority(directory) {
|
|
41
|
+
const cert = path.join(directory, 'ca.crt');
|
|
42
|
+
const key = path.join(directory, 'ca.key');
|
|
43
|
+
execFileSync('openssl', [
|
|
44
|
+
'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
|
|
45
|
+
'-keyout', key, '-out', cert, '-days', '1',
|
|
46
|
+
'-subj', '/CN=PieceMaker Smoke CA',
|
|
47
|
+
'-addext', 'basicConstraints=critical,CA:true,pathlen:0',
|
|
48
|
+
'-addext', 'keyUsage=critical,keyCertSign,cRLSign',
|
|
49
|
+
], { stdio: 'ignore' });
|
|
50
|
+
return { cert, key };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sseEvent(text) {
|
|
54
|
+
return `event: content_block_delta\ndata: ${JSON.stringify({
|
|
55
|
+
type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text },
|
|
56
|
+
})}\n\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function startUpstream() {
|
|
60
|
+
const seen = [];
|
|
61
|
+
const server = http.createServer((request, response) => {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
request.on('data', (chunk) => chunks.push(chunk));
|
|
64
|
+
request.on('end', () => {
|
|
65
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
66
|
+
seen.push(body);
|
|
67
|
+
if (request.headers['x-smoke-mode'] === 'sse') {
|
|
68
|
+
response.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
69
|
+
for (const text of ['Le client ', 'PERSONNE_', 'PHYSIQUE', '_01', ' a signé.']) response.write(sseEvent(text));
|
|
70
|
+
response.end('event: content_block_stop\ndata: {"type":"content_block_stop","index":0}\n\n');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
response.writeHead(200, { 'content-type': 'application/json' });
|
|
74
|
+
response.end(JSON.stringify({ echo: body }));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
server.listen(0, '127.0.0.1', () => resolve({ seen, server, port: server.address().port }));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sandboxProfile(directory) {
|
|
83
|
+
const writable = JSON.stringify(fs.realpathSync(directory));
|
|
84
|
+
return [
|
|
85
|
+
'(version 1)',
|
|
86
|
+
'(allow default)',
|
|
87
|
+
'(deny network-outbound)',
|
|
88
|
+
'(allow network-outbound (remote ip "localhost:*"))',
|
|
89
|
+
'(allow network-outbound (remote unix-socket))',
|
|
90
|
+
'(deny file-write*)',
|
|
91
|
+
`(allow file-write* (subpath ${writable}) (literal "/dev/null") (literal "/dev/tty"))`,
|
|
92
|
+
].join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function launch(binary, args, directory, sandboxed) {
|
|
96
|
+
const env = {
|
|
97
|
+
...process.env,
|
|
98
|
+
HOME: directory,
|
|
99
|
+
USERPROFILE: directory,
|
|
100
|
+
HTTPS_PROXY: '', HTTP_PROXY: '', ALL_PROXY: '', https_proxy: '', http_proxy: '', all_proxy: '',
|
|
101
|
+
};
|
|
102
|
+
const options = { cwd: directory, env, stdio: ['pipe', 'pipe', 'pipe'] };
|
|
103
|
+
if (sandboxed) return spawn(SANDBOX_EXEC, ['-p', sandboxProfile(directory), binary, ...args], options);
|
|
104
|
+
return spawn(binary, args, options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function waitForListening(child, logs) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
let buffer = '';
|
|
110
|
+
const timer = setTimeout(() => reject(new Error(`aucune écoute après 15 s : ${logs.text || buffer}`)), 15000);
|
|
111
|
+
child.stdout.on('data', (chunk) => {
|
|
112
|
+
buffer += chunk;
|
|
113
|
+
const match = buffer.match(/listening 127\.0\.0\.1:(\d+)/);
|
|
114
|
+
if (!match) return;
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
if (Number(match[1]) === 0) reject(new Error('port annoncé 0 : binaire antérieur au source, à recompiler'));
|
|
117
|
+
else resolve(Number(match[1]));
|
|
118
|
+
});
|
|
119
|
+
child.once('error', (error) => {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
reject(error);
|
|
122
|
+
});
|
|
123
|
+
child.once('exit', (code, signal) => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
reject(new Error(`sorti (${code ?? signal}) : ${logs.text || buffer}`));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function withTimeout(promise, label) {
|
|
131
|
+
let timer;
|
|
132
|
+
return Promise.race([
|
|
133
|
+
promise,
|
|
134
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(`${label} : délai dépassé`)), CHECK_TIMEOUT_MS); }),
|
|
135
|
+
]).finally(() => clearTimeout(timer));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function health(port) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
http.get({ host: '127.0.0.1', port, path: '/health' }, (response) => {
|
|
141
|
+
let body = '';
|
|
142
|
+
response.on('data', (chunk) => { body += chunk; });
|
|
143
|
+
response.on('end', () => resolve({ status: response.statusCode, body }));
|
|
144
|
+
}).on('error', reject);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function refuseDirectResolution(hostname, _options, callback) {
|
|
149
|
+
callback(new Error(`résolution directe de ${hostname} interdite : tout doit passer par le proxy`));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function postThroughProxy(port, ca, { body, contentType = 'application/json', mode = 'json' }) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const connect = http.request({ host: '127.0.0.1', port, method: 'CONNECT', path: `${HOST}:443`, headers: { host: `${HOST}:443` } });
|
|
155
|
+
connect.once('error', reject);
|
|
156
|
+
connect.once('connect', (answer, socket) => {
|
|
157
|
+
if (answer.statusCode !== 200) {
|
|
158
|
+
socket.destroy();
|
|
159
|
+
reject(new Error(`CONNECT refusé (${answer.statusCode})`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const request = https.request({
|
|
163
|
+
host: HOST,
|
|
164
|
+
path: '/v1/messages',
|
|
165
|
+
method: 'POST',
|
|
166
|
+
lookup: refuseDirectResolution,
|
|
167
|
+
headers: { 'content-type': contentType, 'content-length': Buffer.byteLength(body), 'x-smoke-mode': mode },
|
|
168
|
+
createConnection: () => tls.connect({ socket, servername: HOST, ca }),
|
|
169
|
+
}, (response) => {
|
|
170
|
+
let text = '';
|
|
171
|
+
response.setEncoding('utf8');
|
|
172
|
+
response.on('data', (chunk) => { text += chunk; });
|
|
173
|
+
response.on('end', () => {
|
|
174
|
+
socket.destroy();
|
|
175
|
+
resolve({ status: response.statusCode, body: text });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
request.once('error', reject);
|
|
179
|
+
request.end(body);
|
|
180
|
+
});
|
|
181
|
+
connect.end();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function streamedText(body) {
|
|
186
|
+
return body
|
|
187
|
+
.split('\n')
|
|
188
|
+
.filter((line) => line.startsWith('data: '))
|
|
189
|
+
.map((line) => {
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(line.slice(6))?.delta?.text || '';
|
|
192
|
+
} catch {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
.join('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function waitForExit(child, timeoutMs) {
|
|
200
|
+
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
|
|
201
|
+
return new Promise((resolve) => {
|
|
202
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
203
|
+
child.once('exit', () => {
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
resolve(true);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runHudsuckerSmokeTest({ binary: requestedBinary = defaultBinary(), caCert = null, caKey = null, sandbox = true, report = () => {} } = {}) {
|
|
211
|
+
const binary = path.resolve(requestedBinary);
|
|
212
|
+
const checks = [];
|
|
213
|
+
const record = (name, ok, detail = '') => {
|
|
214
|
+
checks.push({ name, ok, detail });
|
|
215
|
+
report({ name, ok, detail });
|
|
216
|
+
return ok;
|
|
217
|
+
};
|
|
218
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-hudsucker-smoke-'));
|
|
219
|
+
const sandboxed = sandbox && process.platform === 'darwin' && fs.existsSync(SANDBOX_EXEC);
|
|
220
|
+
let child = null;
|
|
221
|
+
let bridge = null;
|
|
222
|
+
let upstream = null;
|
|
223
|
+
try {
|
|
224
|
+
if (!fs.existsSync(binary)) {
|
|
225
|
+
record('binaire présent', false, binary);
|
|
226
|
+
return { ok: false, sandboxed, checks };
|
|
227
|
+
}
|
|
228
|
+
const authority = caCert && caKey ? { cert: caCert, key: caKey } : temporaryAuthority(directory);
|
|
229
|
+
for (const file of [authority.cert, authority.key]) {
|
|
230
|
+
if (!fs.existsSync(file)) {
|
|
231
|
+
record('autorité locale présente', false, file);
|
|
232
|
+
return { ok: false, sandboxed, checks };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const ca = fs.readFileSync(authority.cert);
|
|
236
|
+
|
|
237
|
+
bridge = await startRewriterWorker({ databasePath: mappingDatabase(directory), homeDir: directory });
|
|
238
|
+
upstream = await startUpstream();
|
|
239
|
+
|
|
240
|
+
const logs = { text: '' };
|
|
241
|
+
child = launch(binary, [
|
|
242
|
+
'--listen', '127.0.0.1:0',
|
|
243
|
+
'--rewriter', `http://127.0.0.1:${bridge.port}`,
|
|
244
|
+
'--ca-cert', authority.cert,
|
|
245
|
+
'--ca-key', authority.key,
|
|
246
|
+
'--hosts', HOST,
|
|
247
|
+
'--upstream-map', `${HOST}=127.0.0.1:${upstream.port}`,
|
|
248
|
+
], directory, sandboxed);
|
|
249
|
+
child.stderr.on('data', (chunk) => { logs.text += chunk; });
|
|
250
|
+
|
|
251
|
+
let port;
|
|
252
|
+
try {
|
|
253
|
+
port = await waitForListening(child, logs);
|
|
254
|
+
record('démarrage', true, `écoute sur 127.0.0.1:${port}${sandboxed ? ', dans sandbox-exec' : ''}`);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
record('démarrage', false, error.message);
|
|
257
|
+
return { ok: false, sandboxed, checks };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const step = async (name, run) => {
|
|
261
|
+
try {
|
|
262
|
+
const detail = await withTimeout(run(), name);
|
|
263
|
+
record(name, true, detail || '');
|
|
264
|
+
} catch (error) {
|
|
265
|
+
record(name, false, error.message);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
await step('santé', async () => {
|
|
270
|
+
const { status, body } = await health(port);
|
|
271
|
+
if (status !== 200 || !body.includes('"rewriter":"ok"')) throw new Error(`HTTP ${status} ${body}`);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await step('anonymisation JSON aller-retour', async () => {
|
|
275
|
+
const before = upstream.seen.length;
|
|
276
|
+
const response = await postThroughProxy(port, ca, {
|
|
277
|
+
body: JSON.stringify({ messages: [{ role: 'user', content: `Contrat de ${NAME}` }] }),
|
|
278
|
+
});
|
|
279
|
+
const sent = upstream.seen[before] || '';
|
|
280
|
+
if (response.status !== 200) throw new Error(`HTTP ${response.status} ${response.body}`);
|
|
281
|
+
if (sent.includes(NAME) || !sent.includes(CODE)) throw new Error(`le serveur distant a reçu : ${sent}`);
|
|
282
|
+
if (!response.body.includes(NAME) || response.body.includes(CODE)) throw new Error(`réponse non restituée : ${response.body}`);
|
|
283
|
+
return `le distant voit ${CODE}, le client lit « ${NAME} »`;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await step('flux SSE restitué', async () => {
|
|
287
|
+
const response = await postThroughProxy(port, ca, { body: JSON.stringify({ stream: true, messages: [] }), mode: 'sse' });
|
|
288
|
+
const text = streamedText(response.body);
|
|
289
|
+
if (response.status !== 200 || text !== `Le client ${NAME} a signé.`) throw new Error(`HTTP ${response.status}, texte « ${text} »`);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await step('refus d’un corps non JSON', async () => {
|
|
293
|
+
const before = upstream.seen.length;
|
|
294
|
+
const response = await postThroughProxy(port, ca, { body: `Contrat de ${NAME}`, contentType: 'text/plain' });
|
|
295
|
+
if (response.status !== 415 || upstream.seen.length !== before) throw new Error(`HTTP ${response.status}, transmis : ${upstream.seen.length - before}`);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await step('refus si le réécrivain tombe', async () => {
|
|
299
|
+
await bridge.close();
|
|
300
|
+
bridge = null;
|
|
301
|
+
const before = upstream.seen.length;
|
|
302
|
+
const response = await postThroughProxy(port, ca, { body: JSON.stringify({ messages: [{ role: 'user', content: NAME }] }) });
|
|
303
|
+
if (response.status !== 502 || upstream.seen.length !== before) throw new Error(`HTTP ${response.status}, transmis : ${upstream.seen.length - before}`);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await step('arrêt avec son parent', async () => {
|
|
307
|
+
child.stdin.end();
|
|
308
|
+
if (!(await waitForExit(child, 3000))) throw new Error('toujours en vie 3 s après la fermeture de son entrée');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return { ok: checks.every((check) => check.ok), sandboxed, checks };
|
|
312
|
+
} catch (error) {
|
|
313
|
+
record('banc de test', false, error.message);
|
|
314
|
+
return { ok: false, sandboxed, checks };
|
|
315
|
+
} finally {
|
|
316
|
+
if (child && child.exitCode === null && child.signalCode === null) {
|
|
317
|
+
child.kill('SIGKILL');
|
|
318
|
+
await waitForExit(child, 2000);
|
|
319
|
+
}
|
|
320
|
+
if (bridge) await bridge.close();
|
|
321
|
+
if (upstream) await new Promise((resolve) => upstream.server.close(resolve));
|
|
322
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function argument(name) {
|
|
327
|
+
const index = process.argv.indexOf(name);
|
|
328
|
+
return index === -1 ? null : process.argv[index + 1] || null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (require.main === module) {
|
|
332
|
+
runHudsuckerSmokeTest({
|
|
333
|
+
binary: argument('--binary') || defaultBinary(),
|
|
334
|
+
caCert: argument('--ca-cert'),
|
|
335
|
+
caKey: argument('--ca-key'),
|
|
336
|
+
sandbox: !process.argv.includes('--no-sandbox'),
|
|
337
|
+
report: ({ name, ok, detail }) => console.log(`${ok ? 'OK ' : 'ÉCHEC'} ${name}${detail ? ` — ${detail}` : ''}`),
|
|
338
|
+
}).then((result) => {
|
|
339
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = { runHudsuckerSmokeTest };
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
type AnonymizerService = {
|
|
2
2
|
start(): Promise<unknown>;
|
|
3
3
|
stop(): Promise<unknown>;
|
|
4
|
-
status(): { enabled: boolean; listening: boolean;
|
|
4
|
+
status(): { enabled: boolean; listening: boolean; reason?: string };
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export async function startRequiredAnonymizer(service: AnonymizerService) {
|
|
8
8
|
const ensureReady = () => {
|
|
9
9
|
const status = service.status();
|
|
10
|
+
if (status.reason === 'disabled') return;
|
|
10
11
|
if (!status.enabled || !status.listening) throw new Error('PieceMaker : le proxy PII est indisponible. Démarrage IA refusé.');
|
|
11
|
-
const missing = ['claude', 'codex'].filter((provider) => status.coverage[provider]?.state !== 'filtered');
|
|
12
|
-
if (missing.length) throw new Error(`PieceMaker : routage PII non configuré pour ${missing.join(', ')}. Démarrage IA refusé.`);
|
|
13
12
|
};
|
|
14
13
|
try {
|
|
15
14
|
await service.start();
|
|
@@ -84,19 +84,26 @@ function startRewriterBridge({ dictionary, harness = null }) {
|
|
|
84
84
|
const server = http.createServer((request, response) => {
|
|
85
85
|
const provider = header(request, 'x-piecemaker-provider') || 'claude';
|
|
86
86
|
const current = () => dictionary.get();
|
|
87
|
+
const outbound = () => (dictionary.getForOutbound ? dictionary.getForOutbound() : dictionary.get());
|
|
88
|
+
|
|
89
|
+
if (request.method === 'GET' && request.url === '/health') {
|
|
90
|
+
response.writeHead(200);
|
|
91
|
+
response.end('ok');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
87
94
|
|
|
88
95
|
if (request.method === 'POST' && request.url === '/v1/request') {
|
|
89
96
|
readBody(request).then((body) => {
|
|
90
97
|
stats.requests += 1;
|
|
91
98
|
const contentType = header(request, 'content-type') || 'application/json';
|
|
92
|
-
|
|
93
|
-
const dict = current();
|
|
99
|
+
const dict = outbound();
|
|
94
100
|
const rewritten = !body.length || dict.empty
|
|
95
101
|
? body
|
|
96
102
|
: Buffer.from(rewriteJsonBody(body, (text) => anonymize(text, dict)), 'utf8');
|
|
97
103
|
if (!rewritten.equals(body)) stats.anonymized += 1;
|
|
98
104
|
response.writeHead(200, { 'content-type': 'application/octet-stream', 'content-length': rewritten.length });
|
|
99
105
|
response.end(rewritten);
|
|
106
|
+
setImmediate(() => observeRequest(harness, body, contentType, provider));
|
|
100
107
|
}).catch((error) => {
|
|
101
108
|
stats.failures += 1;
|
|
102
109
|
stats.lastError = error.message;
|
|
@@ -159,15 +166,16 @@ function startRewriterBridge({ dictionary, harness = null }) {
|
|
|
159
166
|
const id = header(request, 'x-piecemaker-ws');
|
|
160
167
|
const direction = header(request, 'x-piecemaker-direction');
|
|
161
168
|
readBody(request).then((body) => {
|
|
162
|
-
const dict = current();
|
|
163
169
|
const text = body.toString('utf8');
|
|
164
170
|
if (direction === 'out') {
|
|
165
|
-
|
|
171
|
+
const dict = outbound();
|
|
166
172
|
const rewritten = dict.empty ? text : rewriteJsonBody(text, (value) => anonymize(value, dict));
|
|
167
173
|
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
168
174
|
response.end(rewritten);
|
|
175
|
+
setImmediate(() => observeRequest(harness, body, 'application/json', provider));
|
|
169
176
|
return;
|
|
170
177
|
}
|
|
178
|
+
const dict = current();
|
|
171
179
|
let entry = sessions.get(id);
|
|
172
180
|
if (!entry) {
|
|
173
181
|
entry = { rewriter: createSseRewriter((value) => deanonymize(value, dict)) };
|
|
@@ -202,6 +210,7 @@ function startRewriterBridge({ dictionary, harness = null }) {
|
|
|
202
210
|
response.writeHead(404);
|
|
203
211
|
response.end();
|
|
204
212
|
});
|
|
213
|
+
server.requestTimeout = 0;
|
|
205
214
|
|
|
206
215
|
return new Promise((resolve, reject) => {
|
|
207
216
|
server.once('error', reject);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const { SHARE_ENV, Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
|
|
3
|
+
|
|
4
|
+
const STATS_INTERVAL_MS = 1000;
|
|
5
|
+
const CLOSE_TIMEOUT_MS = 2000;
|
|
6
|
+
|
|
7
|
+
function runInsideWorker() {
|
|
8
|
+
const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
|
|
9
|
+
const { startRewriterBridge } = require('./rewriter-bridge.cjs');
|
|
10
|
+
const { createHarnessJuridique } = require('../harness/index.cjs');
|
|
11
|
+
|
|
12
|
+
const dictionary = createSqliteDictionaryLoader({ databasePath: workerData.databasePath });
|
|
13
|
+
const harness = createHarnessJuridique({ homeDir: workerData.homeDir, verifyResponses: false });
|
|
14
|
+
|
|
15
|
+
startRewriterBridge({ dictionary, harness }).then((bridge) => {
|
|
16
|
+
let published = '';
|
|
17
|
+
const publishStats = () => {
|
|
18
|
+
const snapshot = JSON.stringify(bridge.stats);
|
|
19
|
+
if (snapshot === published) return;
|
|
20
|
+
published = snapshot;
|
|
21
|
+
parentPort.postMessage({ type: 'stats', stats: bridge.stats });
|
|
22
|
+
};
|
|
23
|
+
const timer = setInterval(publishStats, STATS_INTERVAL_MS);
|
|
24
|
+
parentPort.on('message', (message) => {
|
|
25
|
+
if (message?.type !== 'close') return;
|
|
26
|
+
clearInterval(timer);
|
|
27
|
+
publishStats();
|
|
28
|
+
bridge.close().finally(() => {
|
|
29
|
+
dictionary.close();
|
|
30
|
+
parentPort.postMessage({ type: 'closed' });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
parentPort.postMessage({ type: 'listening', port: bridge.port });
|
|
34
|
+
}, (error) => {
|
|
35
|
+
dictionary.close();
|
|
36
|
+
parentPort.postMessage({ type: 'error', message: error.message });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function startRewriterWorker({ databasePath, homeDir }) {
|
|
41
|
+
const worker = new Worker(path.join(__dirname, 'rewriter-worker.cjs'), {
|
|
42
|
+
workerData: { databasePath, homeDir },
|
|
43
|
+
env: SHARE_ENV,
|
|
44
|
+
});
|
|
45
|
+
const stats = { requests: 0, anonymized: 0, deanonymized: 0, failures: 0, lastError: null };
|
|
46
|
+
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const fail = (error) => {
|
|
49
|
+
worker.terminate();
|
|
50
|
+
reject(error);
|
|
51
|
+
};
|
|
52
|
+
worker.on('error', (error) => {
|
|
53
|
+
stats.failures += 1;
|
|
54
|
+
stats.lastError = error.message;
|
|
55
|
+
fail(error);
|
|
56
|
+
});
|
|
57
|
+
worker.once('exit', (code) => reject(new Error(`réécrivain arrêté (${code})`)));
|
|
58
|
+
worker.on('message', (message) => {
|
|
59
|
+
if (message?.type === 'stats') Object.assign(stats, message.stats);
|
|
60
|
+
if (message?.type === 'error') fail(new Error(message.message));
|
|
61
|
+
if (message?.type !== 'listening') return;
|
|
62
|
+
resolve({
|
|
63
|
+
port: message.port,
|
|
64
|
+
stats,
|
|
65
|
+
close() {
|
|
66
|
+
return new Promise((done) => {
|
|
67
|
+
const timer = setTimeout(() => worker.terminate().then(() => done()), CLOSE_TIMEOUT_MS);
|
|
68
|
+
worker.once('exit', () => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
done();
|
|
71
|
+
});
|
|
72
|
+
worker.on('message', (reply) => {
|
|
73
|
+
if (reply?.type === 'closed') worker.terminate();
|
|
74
|
+
});
|
|
75
|
+
worker.postMessage({ type: 'close' });
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!isMainThread && workerData?.databasePath) runInsideWorker();
|
|
84
|
+
|
|
85
|
+
module.exports = { startRewriterWorker };
|