@piecemaker-legal/piecemaker 1.1.1 → 2.0.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 (141) hide show
  1. package/dist/assets/{abnfDiagram-VCTEODGH-DEXWh3LH.js → abnfDiagram-VCTEODGH-BeQS_HnO.js} +1 -1
  2. package/dist/assets/{arc-CoTRLXKW.js → arc-ClYjD3aM.js} +1 -1
  3. package/dist/assets/{architectureDiagram-5GKGNRK7-Cg-jO9oR.js → architectureDiagram-5GKGNRK7-Csdihb1L.js} +1 -1
  4. package/dist/assets/{blockDiagram-I7D4REHJ-ChXwQSIq.js → blockDiagram-I7D4REHJ-BuXey9ht.js} +1 -1
  5. package/dist/assets/{c4Diagram-7LVT6UL2-Bw2VGggd.js → c4Diagram-7LVT6UL2-DdO-ovOy.js} +1 -1
  6. package/dist/assets/channel-DhuuSCHK.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-C1WdqrLP.js → chunk-2Q5K7J3B-D3hzWzSc.js} +1 -1
  8. package/dist/assets/{chunk-5VM5RSS4-BerUZBa1.js → chunk-5VM5RSS4-DeT3cR6k.js} +1 -1
  9. package/dist/assets/{chunk-F27PBJKO-5W7ax2gB.js → chunk-F27PBJKO-bazGi1ru.js} +1 -1
  10. package/dist/assets/{chunk-IMKFNOWR-JgDN1h4T.js → chunk-IMKFNOWR-BE3ZXrPd.js} +1 -1
  11. package/dist/assets/{chunk-JWPE2WC7-ulNHnEdQ.js → chunk-JWPE2WC7-dTAsgByE.js} +1 -1
  12. package/dist/assets/{chunk-POPQ4Y6H-D4aTDC2T.js → chunk-POPQ4Y6H-DKYrzMSd.js} +1 -1
  13. package/dist/assets/{chunk-SVP7TREG-BA5zX4XF.js → chunk-SVP7TREG-BTtrMvhE.js} +1 -1
  14. package/dist/assets/{chunk-TICWLB2K-DhOULVhX.js → chunk-TICWLB2K-CZPHShPS.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-CkpCgrVg.js → chunk-XXDRQBXY-B0byFHaU.js} +1 -1
  16. package/dist/assets/classDiagram-ZZMXUADV-CO0E82Dl.js +1 -0
  17. package/dist/assets/classDiagram-v2-VYDZK3BY-CO0E82Dl.js +1 -0
  18. package/dist/assets/{cose-bilkent-JH36ORCC-0xUSfjfV.js → cose-bilkent-JH36ORCC-BbLCzlaU.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-BE6fNu2_.js → cynefin-OW5HDTMX-CQ8ui4YD.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-5FMLGOSQ-rFMnZ9b3.js → cynefinDiagram-5FMLGOSQ-T-GL1aIf.js} +1 -1
  21. package/dist/assets/{dagre-GXQ25YYZ-C1p4ffd7.js → dagre-GXQ25YYZ-CXxVKznH.js} +1 -1
  22. package/dist/assets/{diagram-S7CK7UJ4-BV_3Xj4O.js → diagram-S7CK7UJ4-B_-1Mf_7.js} +1 -1
  23. package/dist/assets/{diagram-UQ7AKVKN-Bb_Evkrm.js → diagram-UQ7AKVKN-Bs-OB8xI.js} +1 -1
  24. package/dist/assets/{diagram-VSXAHHWV-BzgupjxD.js → diagram-VSXAHHWV-BpUqq93o.js} +1 -1
  25. package/dist/assets/{diagram-VX7I27RA-D1c67z76.js → diagram-VX7I27RA-CZ6hSUo6.js} +1 -1
  26. package/dist/assets/{diagram-Z3DM3KII-DsJAJGKE.js → diagram-Z3DM3KII-DVZiDO6S.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-PWID7BFC-B1ge_obq.js → ebnfDiagram-PWID7BFC-D_1izoKK.js} +1 -1
  28. package/dist/assets/{erDiagram-RLTQ6QDP-DrV-tggw.js → erDiagram-RLTQ6QDP-_jSi4sg-.js} +1 -1
  29. package/dist/assets/{flowDiagram-HODETNUW-CVzDiSQ3.js → flowDiagram-HODETNUW-BtBM7ez3.js} +1 -1
  30. package/dist/assets/{ganttDiagram-EL5Y4UJY-CGRdSN9D.js → ganttDiagram-EL5Y4UJY-Bz2nxw7c.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CAfvFXzL.js → gitGraphDiagram-WWUBYQGX-8Q3I3hoE.js} +1 -1
  32. package/dist/assets/{index-DaxH81ZJ.js → index-CZUj0Tlu.js} +152 -152
  33. package/dist/assets/index-DcfdIMj-.css +1 -0
  34. package/dist/assets/{infoDiagram-27XIBGKW-DTshEkGK.js → infoDiagram-27XIBGKW-Cc6eKjfU.js} +1 -1
  35. package/dist/assets/{ishikawaDiagram-5VMMS53U-DkbshVxp.js → ishikawaDiagram-5VMMS53U-Bd3w97qM.js} +1 -1
  36. package/dist/assets/{journeyDiagram-3NMN7TZE-v5qE8xpn.js → journeyDiagram-3NMN7TZE-Cy6XbK5X.js} +1 -1
  37. package/dist/assets/{kanban-definition-UXKFOSKX-a9CNMFLt.js → kanban-definition-UXKFOSKX-kyLqWDpr.js} +1 -1
  38. package/dist/assets/{layout-DrW4R1TH.js → layout-DXpPSEft.js} +1 -1
  39. package/dist/assets/{linear-Cs3qpXT_.js → linear-DH_h9cDZ.js} +1 -1
  40. package/dist/assets/{mermaid.core-C7Ti-Fty.js → mermaid.core-BghXiB7p.js} +6 -6
  41. package/dist/assets/{mindmap-definition-YA3MSWOX-B0VDdIcG.js → mindmap-definition-YA3MSWOX-C2xxEXsP.js} +1 -1
  42. package/dist/assets/{pegDiagram-XKGWAZYB-CV1L7MjP.js → pegDiagram-XKGWAZYB-DuppHX4-.js} +1 -1
  43. package/dist/assets/{pieDiagram-E7YTZNPT-DSCuzVTV.js → pieDiagram-E7YTZNPT-BCukPZ2a.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-AXDQQJYC-hzX-9AKo.js → quadrantDiagram-AXDQQJYC-Dm_ojY_7.js} +1 -1
  45. package/dist/assets/{railroadDiagram-O6MQD6OU-BbmzIqcp.js → railroadDiagram-O6MQD6OU-BzdOv7BJ.js} +1 -1
  46. package/dist/assets/{requirementDiagram-BXWQKSXE-CQCRRBSL.js → requirementDiagram-BXWQKSXE-DgewPP_g.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-P5KCCOFB-kiaLcW6X.js → sankeyDiagram-P5KCCOFB-vEIMvCSt.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bgx8ST7Q.js → sequenceDiagram-WJ2MYXX4-BL_31NoY.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-BTjJ-ygN.js → sizeCapture-INFHLROL-Ch6u4xo6.js} +1 -1
  50. package/dist/assets/{stateDiagram-D77RDMKH-D48RR1FL.js → stateDiagram-D77RDMKH-ByquMuJv.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-MP3YSRHH-B-XwSR-z.js → stateDiagram-v2-MP3YSRHH-_QRJP_vn.js} +1 -1
  52. package/dist/assets/{swimlanes-42K2YHIH-BhE-QuEZ.js → swimlanes-42K2YHIH-BZyXEHOb.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-VR7AAH4N-sqZwZwbP.js +8 -0
  54. package/dist/assets/{timeline-definition-24CTP7MA-Cm7EUWWt.js → timeline-definition-24CTP7MA-CLBivayK.js} +1 -1
  55. package/dist/assets/{vennDiagram-4TSXK5OY-X-nC9wZ0.js → vennDiagram-4TSXK5OY-3dJ2PRqR.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VM6X3IG4-D33YvEQZ.js → wardleyDiagram-VM6X3IG4-C279LT36.js} +1 -1
  57. package/dist/assets/{xychartDiagram-S5SC5T6Z-lziwBKsM.js → xychartDiagram-S5SC5T6Z-FoqPDrei.js} +1 -1
  58. package/dist/index.html +2 -2
  59. package/dist/sw.js +1 -1
  60. package/dist-server/server/index.js +3 -1
  61. package/dist-server/server/index.js.map +1 -1
  62. package/dist-server/server/piecemaker/harness/citations.routes.js +18 -1
  63. package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -1
  64. package/dist-server/server/piecemaker/harness/legifrance-html.js +112 -0
  65. package/dist-server/server/piecemaker/harness/legifrance-html.js.map +1 -0
  66. package/dist-server/server/piecemaker/index.js +7 -3
  67. package/dist-server/server/piecemaker/index.js.map +1 -1
  68. package/dist-server/server/piecemaker/knowledge/index.js +13 -6
  69. package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
  70. package/dist-server/server/piecemaker/knowledge/local-routes.js +55 -0
  71. package/dist-server/server/piecemaker/knowledge/local-routes.js.map +1 -0
  72. package/dist-server/server/piecemaker/knowledge/scan-jobs.js +3 -1
  73. package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -1
  74. package/dist-server/server/piecemaker/library/index.js +14 -41
  75. package/dist-server/server/piecemaker/library/index.js.map +1 -1
  76. package/dist-server/server/piecemaker/library/store.js +11 -3
  77. package/dist-server/server/piecemaker/library/store.js.map +1 -1
  78. package/dist-server/server/piecemaker/library/workspace-installation.js +26 -3
  79. package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
  80. package/dist-server/server/piecemaker/timesheet/service.js +8 -7
  81. package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
  82. package/dist-server/server/piecemaker/timesheet/store.js +5 -3
  83. package/dist-server/server/piecemaker/timesheet/store.js.map +1 -1
  84. package/package.json +1 -1
  85. package/product.config.json +2 -0
  86. package/scripts/piecemaker/build-app-icon.py +88 -0
  87. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +2 -2
  88. package/scripts/piecemaker/hooks/proxy-guard.mjs +19 -54
  89. package/scripts/release/assert-piecemaker-version.mjs +25 -0
  90. package/server/index.ts +4 -1
  91. package/server/piecemaker/anonymizer/client-config.cjs +45 -162
  92. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +127 -0
  93. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.lock +2396 -0
  94. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +14 -0
  95. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +501 -0
  96. package/server/piecemaker/anonymizer/providers.cjs +14 -135
  97. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +221 -0
  98. package/server/piecemaker/anonymizer/service.cjs +144 -28
  99. package/server/piecemaker/harness/citations.routes.ts +23 -1
  100. package/server/piecemaker/harness/legifrance-html.ts +107 -0
  101. package/server/piecemaker/index.ts +8 -3
  102. package/server/piecemaker/knowledge/index.ts +14 -6
  103. package/server/piecemaker/knowledge/local-routes.ts +72 -0
  104. package/server/piecemaker/knowledge/scan-jobs.ts +4 -1
  105. package/server/piecemaker/library/index.ts +14 -34
  106. package/server/piecemaker/library/store.ts +11 -3
  107. package/server/piecemaker/library/workspace-installation.ts +20 -3
  108. package/server/piecemaker/timesheet/service.ts +9 -7
  109. package/server/piecemaker/timesheet/store.ts +5 -3
  110. package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +0 -1
  111. package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
  112. package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
  113. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +56 -206
  114. package/server/piecemaker/vendor/installer/lib/commandes.mjs +2 -4
  115. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +74 -0
  116. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -358
  117. package/server/piecemaker/vendor/installer/lib/state.mjs +0 -6
  118. package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +4 -4
  119. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -31
  120. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +3 -4
  121. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +1 -49
  122. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +14 -12
  123. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +17 -476
  124. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +2 -2
  125. package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +11 -2
  126. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +13 -37
  127. package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +1 -7
  128. package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +14 -85
  129. package/dist/assets/channel-DdzrPZ6Y.js +0 -1
  130. package/dist/assets/classDiagram-ZZMXUADV-Dim2o8E5.js +0 -1
  131. package/dist/assets/classDiagram-v2-VYDZK3BY-Dim2o8E5.js +0 -1
  132. package/dist/assets/index-D_Dc_nQv.css +0 -1
  133. package/dist/assets/swimlanesDiagram-VR7AAH4N-DR525d5V.js +0 -8
  134. package/server/piecemaker/anonymizer/proxy.cjs +0 -365
  135. package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +0 -189
  136. package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +0 -305
  137. package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +0 -171
  138. package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +0 -271
  139. package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +0 -64
  140. package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +0 -56
  141. package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +0 -52
@@ -13,10 +13,12 @@ const path = require('path');
13
13
  /** Racine du dépôt git, quatre niveaux au-dessus de ce fichier vendorisé. */
14
14
  const GIT_REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
15
15
 
16
- const DEPRECATED_MAPPING_HOOKS = new Set([
16
+ const DEPRECATED_HOOKS = new Set([
17
17
  'anonymize-read.mjs',
18
18
  'deanonymize-write.mjs',
19
19
  'piecemaker-central-anonymize.mjs',
20
+ 'verify-citations.mjs',
21
+ 'decision-cache.mjs',
20
22
  ]);
21
23
 
22
24
  function settingsPath(userHome) {
@@ -41,24 +43,24 @@ function commandScriptName(command) {
41
43
  return match?.[1] || '';
42
44
  }
43
45
 
44
- function deprecatedMappingHookName(command) {
46
+ function deprecatedHookName(command) {
45
47
  const text = String(command || '');
46
- return [...DEPRECATED_MAPPING_HOOKS].find((name) => text.includes(name)) || '';
48
+ return [...DEPRECATED_HOOKS].find((name) => text.includes(name)) || '';
47
49
  }
48
50
 
49
- function deprecatedMappingHooks(settings) {
51
+ function deprecatedHooks(settings) {
50
52
  if (!settings?.hooks || typeof settings.hooks !== 'object') return [];
51
53
  return Object.entries(settings.hooks).flatMap(([event, groups]) =>
52
54
  (Array.isArray(groups) ? groups : []).flatMap((group) =>
53
55
  (Array.isArray(group?.hooks) ? group.hooks : [])
54
- .map((hook) => ({ event, command: hook?.command, script: deprecatedMappingHookName(hook?.command) }))
56
+ .map((hook) => ({ event, command: hook?.command, script: deprecatedHookName(hook?.command) }))
55
57
  .filter((entry) => entry.script),
56
58
  ),
57
59
  );
58
60
  }
59
61
 
60
- /** Retire les anciens hooks de substitution, remplacés par le proxy PII. */
61
- function removeDeprecatedMappingHooks(userHome) {
62
+ /** Retire les hooks hérités dont la fonction est passée dans le proxy PII. */
63
+ function removeDeprecatedHooks(userHome) {
62
64
  const target = settingsPath(userHome);
63
65
  let changed = false;
64
66
  let removed = 0;
@@ -77,7 +79,7 @@ function removeDeprecatedMappingHooks(userHome) {
77
79
  continue;
78
80
  }
79
81
  const hooks = group.hooks;
80
- const kept = hooks.filter((hook) => !deprecatedMappingHookName(hook?.command));
82
+ const kept = hooks.filter((hook) => !deprecatedHookName(hook?.command));
81
83
  const removedFromGroup = hooks.length - kept.length;
82
84
  removed += removedFromGroup;
83
85
  eventChanged ||= removedFromGroup > 0;
@@ -155,7 +157,7 @@ function claudeHooksStatus(repoRoot, userHome) {
155
157
  const expected = expectedCommands(repoRoot);
156
158
  if (!expected) return { ok: false, reason: 'source-hooks-absent', missing: [] };
157
159
  const settings = readJson(settingsPath(userHome), {});
158
- const deprecated = deprecatedMappingHooks(settings);
160
+ const deprecated = deprecatedHooks(settings);
159
161
  const missing = expected.filter((entry) => {
160
162
  const current = findHook(settings, entry.event, entry.script);
161
163
  return current?.hook.command !== entry.command
@@ -167,9 +169,9 @@ function claudeHooksStatus(repoRoot, userHome) {
167
169
  }
168
170
 
169
171
  function installClaudeHooks(repoRoot, userHome) {
170
- const cleanup = removeDeprecatedMappingHooks(userHome);
172
+ const cleanup = removeDeprecatedHooks(userHome);
171
173
  if (!cleanup.ok) {
172
- return { ok: false, changed: cleanup.changed, reason: `Nettoyage des anciens hooks de mapping impossible : ${cleanup.reason}` };
174
+ return { ok: false, changed: cleanup.changed, reason: `Nettoyage des hooks hérités impossible : ${cleanup.reason}` };
173
175
  }
174
176
  const expected = expectedCommands(repoRoot);
175
177
  if (!expected) {
@@ -375,6 +377,6 @@ module.exports = {
375
377
  installClaudeHooks,
376
378
  installClaudeSessionHook,
377
379
  installClaudeStatusLine,
378
- removeDeprecatedMappingHooks,
380
+ removeDeprecatedHooks,
379
381
  settingsPath,
380
382
  };
@@ -24,21 +24,15 @@ const {
24
24
  } = require('./process-group.cjs');
25
25
 
26
26
  const {
27
- createCommit,
28
27
  originalFilesOverview,
29
- resolveCase,
30
28
  safeCaseFiles,
31
29
  } = require('../piecemaker-plugin/scripts/lib/commits.cjs');
32
- const { documentKey, WORKSPACE_SUBDIR } = require('../piecemaker-plugin/scripts/lib/protection.cjs');
30
+ const { documentKey } = require('../piecemaker-plugin/scripts/lib/protection.cjs');
33
31
  const {
34
- caseConversionOutputDirectory,
35
32
  classifyRelativeCaseFolderPath,
36
33
  readCaseFolderStructure,
37
34
  } = require('./case-folder-structure.cjs');
38
- const {
39
- markFilesAnonymized,
40
- markFilesConverted,
41
- } = require('../piecemaker-plugin/scripts/lib/anonymization-state.cjs');
35
+ const { markFilesAnonymized } = require('../piecemaker-plugin/scripts/lib/anonymization-state.cjs');
42
36
  // Le mapping vit dans le plugin : c'est le seul des trois consommateurs (hooks,
43
37
  // pipeline, routeur du task pane) qui soit distribué seul.
44
38
  const {
@@ -58,8 +52,6 @@ const { isSocieteCode, societeCounterKey, detectCompanySigle, LEGAL_FORM_TOKENS
58
52
  const { syncCentralMapping } = require('../piecemaker-plugin/scripts/lib/central-mapping.cjs');
59
53
 
60
54
  const SCRIPTS_DIR = path.join(__dirname, 'scripts');
61
- const CONVERTER_SCRIPT = () => process.env.SMART_CONVERTER_PATH || path.join(SCRIPTS_DIR, 'smart_converter.py');
62
- const PIPELINE_SCRIPT = () => process.env.PIECEMAKER_PIPELINE_PATH || path.join(SCRIPTS_DIR, 'convert_and_scan_pipeline.py');
63
55
  const PIECEMAKER_HOME = path.join(os.homedir(), '.piecemaker');
64
56
  const configuredPythonPath = () => {
65
57
  try {
@@ -466,67 +458,6 @@ async function migrateLegacySensitiveMaps(caseRoot) {
466
458
 
467
459
  // ── Travaux de conversion / anonymisation ──────────────────────────────────
468
460
 
469
- const jobs = new Map();
470
- const JOB_RETENTION_MS = 30 * 60 * 1000;
471
-
472
- function pruneJobs() {
473
- const now = Date.now();
474
- for (const [id, job] of jobs) {
475
- if (job.state !== 'running' && job.finishedAt && now - Date.parse(job.finishedAt) > JOB_RETENTION_MS) jobs.delete(id);
476
- }
477
- }
478
-
479
- /** Travail rendu déjà terminé — rien à traiter, aucun processus lancé. */
480
- function finishedJob({ case: caseName, caseRoot, action, total, files, result }) {
481
- const now = new Date().toISOString();
482
- return {
483
- id: crypto.randomUUID(),
484
- case: caseName,
485
- caseRoot,
486
- action,
487
- state: 'done',
488
- phase: 'mapping',
489
- percent: 100,
490
- processed: total,
491
- total,
492
- skipped: result?.skipped || 0,
493
- files,
494
- log: [],
495
- error: null,
496
- result,
497
- cancelled: false,
498
- child: null,
499
- startedAt: now,
500
- finishedAt: now,
501
- };
502
- }
503
-
504
- /**
505
- * Forme exposée par l'API. `caseRoot`, `child` et `reserveBytes` en sont retirés :
506
- * ils ne servent qu'à l'ordonnancement interne, et la réponse de
507
- * `GET /api/admin/originals/job` n'a pas à véhiculer un chemin absolu du disque du
508
- * cabinet ni des octets de réservation. `queuePosition` est ajouté pour un job en
509
- * file, pour que l'admin puisse afficher « N devant ».
510
- */
511
- function publicJob(job) {
512
- if (!job) return null;
513
- const {
514
- cancelReason,
515
- child,
516
- completion,
517
- processGroupId,
518
- reserveBytes,
519
- stopPromise,
520
- timeoutId,
521
- ...rest
522
- } = job;
523
- if (job.state === 'queued') {
524
- const position = waiting.findIndex((entry) => entry.job.id === job.id);
525
- rest.queuePosition = position >= 0 ? position + 1 : 1;
526
- }
527
- return rest;
528
- }
529
-
530
461
  function appendLog(job, line) {
531
462
  const text = String(line || '').trim();
532
463
  if (!text) return;
@@ -534,24 +465,6 @@ function appendLog(job, line) {
534
465
  if (job.log.length > MAX_LOG_LINES) job.log.splice(0, job.log.length - MAX_LOG_LINES);
535
466
  }
536
467
 
537
- /**
538
- * Le verrou porte sur la **racine** du dossier, pas sur son nom : deux dossiers
539
- * juridiques enregistrés distincts peuvent porter le même nom, et un
540
- * traitement lent sur l'une bloquait alors tout traitement sur l'autre. Un job en
541
- * file compte aussi : sinon un double-clic empilerait deux traitements du même
542
- * dossier au lieu d'un seul.
543
- */
544
- function runningJobForCase(caseRoot) {
545
- for (const job of jobs.values()) {
546
- if (['running', 'queued'].includes(job.state) && job.caseRoot === caseRoot) return job;
547
- }
548
- return null;
549
- }
550
-
551
- function getJob(jobId) {
552
- return publicJob(jobs.get(String(jobId || '')));
553
- }
554
-
555
468
  // ── Contrôle d'admission : sérialise GLiNER, plafonne les conversions par la RAM ─
556
469
 
557
470
  /** Descripteurs de traitements admis mais pas encore lancés, du plus ancien au plus récent. */
@@ -560,17 +473,12 @@ let reservedBytes = 0;
560
473
  let acceptingJobs = true;
561
474
 
562
475
  /**
563
- * Jobs admis via `runManagedPythonJob` (pas de `legalCase`, donc absents de
564
- * `jobs`) : `runningAnonymize` doit les voir pour que l'exclusivité GLiNER
565
- * reste vraie dans les deux sens, quel que soit le consommateur qui a lancé
566
- * le traitement en cours.
476
+ * Traitements en cours, tous consommateurs confondus : l'exclusivité GLiNER
477
+ * doit rester vraie quel que soit celui qui a lancé le traitement.
567
478
  */
568
479
  const runningManaged = new Set();
569
480
 
570
481
  function runningAnonymize() {
571
- for (const job of jobs.values()) {
572
- if (job.state === 'running' && job.action === 'anonymize') return true;
573
- }
574
482
  for (const job of runningManaged) {
575
483
  if (job.action === 'anonymize') return true;
576
484
  }
@@ -590,9 +498,7 @@ function canAdmit(job) {
590
498
 
591
499
  /**
592
500
  * Admet les traitements en file qui rentrent désormais, du plus ancien au plus
593
- * récent. Un descripteur `runManagedPythonJob` (identifié par la présence de
594
- * `run`, sans `legalCase`) suit son propre lancement : même file, même
595
- * contrôle d'admission, exécution différente.
501
+ * récent.
596
502
  */
597
503
  function pumpQueue() {
598
504
  if (!acceptingJobs) return;
@@ -601,8 +507,7 @@ function pumpQueue() {
601
507
  if (!canAdmit(descriptor.job)) continue;
602
508
  waiting.splice(index, 1);
603
509
  index -= 1;
604
- if (descriptor.run) launchManagedJob(descriptor);
605
- else launchJob(descriptor);
510
+ launchManagedJob(descriptor);
606
511
  }
607
512
  }
608
513
 
@@ -613,9 +518,9 @@ function pumpQueue() {
613
518
  * stderr conservé pour diagnostiquer un échec.
614
519
  *
615
520
  * `progressScale` reporte le pourcentage 0-100 de cet appel dans la part
616
- * `[offset, offset + weight]` du travail global : un `runJob` qui enchaîne
617
- * plusieurs appels (un par groupe de sortie) garde ainsi une progression
618
- * monotone plutôt qu'un pourcentage qui repart de zéro à chaque groupe.
521
+ * `[offset, offset + weight]` du travail global : un appelant qui enchaîne
522
+ * plusieurs appels garde ainsi une progression monotone plutôt qu'un
523
+ * pourcentage qui repart de zéro à chaque appel.
619
524
  *
620
525
  * `convert_and_scan_pipeline.py` enchaîne lui-même CONVERT (markitdown) puis
621
526
  * SCAN/CHUNKS (GLiNER) au sein d'un même appel, chacun avec son propre 0-100 :
@@ -771,9 +676,9 @@ function spawnTracked(job, script, args, progressScale = {}) {
771
676
  * contrôle d'admission que les pièces originales (exclusivité GLiNER, budget
772
677
  * RAM, file d'attente, nice, timeout, arrêt propre du groupe de process).
773
678
  *
774
- * Contrairement à `startOriginalsJob`, cette fonction ne connaît aucune
775
- * notion de dossier juridique enregistré (`legalCase`, mapping, commit) :
776
- * elle sert les consommateurs qui ont leur propre gestion du résultat (le
679
+ * Cette fonction ne connaît aucune notion de dossier juridique enregistré
680
+ * (`legalCase`, mapping, commit) : elle sert les consommateurs qui ont leur
681
+ * propre gestion du résultat (le
777
682
  * pipeline `knowledge`, dont le `projectId` n'est pas un dossier juridique du
778
683
  * registre) mais qui doivent néanmoins partager le même verrou GLiNER et le
779
684
  * même budget RAM que l'administration — sinon rien n'empêche deux scans
@@ -835,7 +740,7 @@ function runManagedPythonJob({ action, script, args, onProgress, signal } = {})
835
740
  });
836
741
  }
837
742
 
838
- /** Variante de `launchJob` pour un descripteur `runManagedPythonJob` (pas de `legalCase`/commit à gérer ici). */
743
+ /** Lancement d'un descripteur `runManagedPythonJob` (pas de `legalCase`/commit à gérer ici). */
839
744
  function launchManagedJob(descriptor) {
840
745
  const { job, run, resolve, reject } = descriptor;
841
746
  reservedBytes += job.reserveBytes;
@@ -868,334 +773,6 @@ function launchManagedJob(descriptor) {
868
773
  })();
869
774
  }
870
775
 
871
- /**
872
- * Migration « au prochain traitement », rattachée aux pièces du lot : range dans
873
- * le dossier de conversion métier le Markdown que d'anciennes versions ont laissé à la racine
874
- * pour une pièce effectivement (re)traitée. Le rattachement au lot est délibéré —
875
- * comme `sessionArtifactPaths` — pour ne jamais déplacer un document de travail
876
- * de l'utilisateur ni un fichier modifié en parallèle. Retourne les chemins
877
- * racine retirés (POSIX) : le commit du lot les enregistre comme suppressions,
878
- * sinon la relocalisation resterait un changement en attente.
879
- *
880
- * Le mapping n'est **pas** relocalisé ici : sa consolidation reste l'apanage du
881
- * chemin de succès (`writeCaseMapping`), qui lit les copies racine puis les
882
- * supprime. Un scan en échec ne doit pas migrer prématurément un ancien mapping.
883
- */
884
- function outputDirectoryForOriginal(caseRoot, absoluteFile) {
885
- const relative = path.relative(caseRoot, absoluteFile).split(path.sep).join('/');
886
- return caseConversionOutputDirectory(caseRoot, relative)
887
- || path.join(caseRoot, WORKSPACE_SUBDIR);
888
- }
889
-
890
- function migrateRootArtifacts(caseRoot, absoluteFiles) {
891
- const sourceByKey = new Map(absoluteFiles.map((file) => [documentKey(file), file]));
892
- if (!sourceByKey.size) return [];
893
- let entries;
894
- try {
895
- entries = fs.readdirSync(caseRoot, { withFileTypes: true });
896
- } catch {
897
- return [];
898
- }
899
- const toMove = entries.filter((entry) =>
900
- entry.isFile()
901
- && path.extname(entry.name).toLowerCase() === '.md'
902
- && sourceByKey.has(documentKey(entry.name)));
903
- if (!toMove.length) return [];
904
- const removed = [];
905
- for (const entry of toMove) {
906
- const source = path.join(caseRoot, entry.name);
907
- const outputDirectory = outputDirectoryForOriginal(caseRoot, sourceByKey.get(documentKey(entry.name)));
908
- fs.mkdirSync(outputDirectory, { recursive: true });
909
- const destination = path.join(outputDirectory, entry.name);
910
- // Le sous-dossier fait autorité : un doublon plus récent y gagne, on se
911
- // contente d'écarter la copie racine périmée.
912
- if (fs.existsSync(destination)) fs.rmSync(source, { force: true });
913
- else fs.renameSync(source, destination);
914
- removed.push(entry.name);
915
- }
916
- return removed;
917
- }
918
-
919
- async function runJob(job, legalCase, absoluteFiles, options) {
920
- job.migratedFromRoot = migrateRootArtifacts(legalCase.root, absoluteFiles);
921
- if (job.cancelled) throw cancellationError(job);
922
- if (job.action === 'convert') {
923
- // `smart_converter.py` ne prend qu'un fichier : on avance dossier par
924
- // dossier pour garder une progression lisible même sans ligne PROGRESS.
925
- job.phase = 'convert';
926
- for (const [index, absolute] of absoluteFiles.entries()) {
927
- if (job.cancelled) throw cancellationError(job);
928
- job.processed = index;
929
- job.percent = Math.round((index / absoluteFiles.length) * 100);
930
- const outputDirectory = outputDirectoryForOriginal(legalCase.root, absolute);
931
- fs.mkdirSync(outputDirectory, { recursive: true });
932
- const args = [absolute, '-o', outputDirectory];
933
- if (options.engine) args.push('--engine', options.engine);
934
- if (options.mode) args.push('--mode', options.mode);
935
- if (options.lang) args.push('--lang', options.lang);
936
- await spawnTracked(job, CONVERTER_SCRIPT(), args);
937
- markFilesConverted(legalCase.root, [absolute]);
938
- job.processed = index + 1;
939
- job.percent = Math.round(((index + 1) / absoluteFiles.length) * 100);
940
- appendLog(job, `Conversion ${job.processed}/${absoluteFiles.length}`);
941
- }
942
- return { converted: absoluteFiles.length };
943
- }
944
-
945
- const before = readCaseMapping(legalCase.root);
946
- const mappingFile = caseMappingFile(legalCase.root);
947
-
948
- // `--state-file` découple le statut « analysé » du contenu sensible ; les
949
- // cartes brutes restent temporaires et ne sont jamais déposées ici.
950
- // Le script n'accepte qu'un répertoire de sortie. On regroupe donc les
951
- // pièces par répertoire de sortie (zones métier et sous-dossier de travail
952
- // générique confondus).
953
- const groups = new Map();
954
- for (const absolute of absoluteFiles) {
955
- const outputDirectory = outputDirectoryForOriginal(legalCase.root, absolute);
956
- if (!groups.has(outputDirectory)) groups.set(outputDirectory, []);
957
- groups.get(outputDirectory).push(absolute);
958
- }
959
- // Chaque groupe ne rapporte qu'une part de `job.percent`, proportionnelle
960
- // à sa part du nombre total de fichiers du lot : sans quoi la barre
961
- // repartirait de zéro à chaque nouveau groupe.
962
- let filesDone = 0;
963
- for (const [outputDirectory, groupFiles] of groups) {
964
- if (job.cancelled) throw cancellationError(job);
965
- fs.mkdirSync(outputDirectory, { recursive: true });
966
- const args = [...groupFiles, '-o', outputDirectory, '--mapping-file', mappingFile];
967
- // `--case-root` découple la clé du manifeste de `--output` : les pièces
968
- // vivent sous le dossier juridique, pas sous le sous-dossier de sortie.
969
- args.push('--case-root', legalCase.root);
970
- args.push('--state-file', path.join(legalCase.root, '.piecemaker', 'anonymization-state.json'));
971
- if (options.skipExisting) args.push('--skip-existing');
972
- if (options.engine) args.push('--engine', options.engine);
973
- if (options.mode) args.push('--mode', options.mode);
974
- if (options.lang) args.push('--lang', options.lang);
975
- const offset = absoluteFiles.length ? (filesDone / absoluteFiles.length) * 100 : 0;
976
- const weight = absoluteFiles.length ? (groupFiles.length / absoluteFiles.length) * 100 : 100;
977
- await spawnTracked(job, PIPELINE_SCRIPT(), args, { offset, weight });
978
- filesDone += groupFiles.length;
979
- }
980
- job.phase = 'mapping';
981
- job.percent = 100;
982
- // Migration unique des dossiers créés par les anciennes versions : leurs
983
- // scans sont absorbés dans le mapping et leur état technique avant suppression.
984
- const mapping = await rebuildCaseMapping(legalCase.root);
985
- const mappingAdded = Math.max(0, mapping.total - Object.keys(before.mapping).length);
986
- appendLog(job, `Mapping : ${mappingAdded} nouvelle(s) entrée(s), ${mapping.total} au total`);
987
- if (mapping.migratedScans) appendLog(job, `${mapping.migratedScans} ancien(s) sensitive map migré(s)`);
988
- return { scanned: absoluteFiles.length, mappingAdded, mappingTotal: mapping.total };
989
- }
990
-
991
- /**
992
- * Chemins produits par le lot courant. Le filtre est volontairement rattaché
993
- * aux pièces sélectionnées : un autre fichier Markdown/JSON modifié pendant un
994
- * OCR long ne doit jamais entrer dans le commit de cette session.
995
- */
996
- async function sessionArtifactPaths(legalCase, absoluteFiles, action) {
997
- const documentKeys = new Set(absoluteFiles.map((file) => documentKey(file)));
998
- const mappingPath = path.relative(legalCase.root, caseMappingFile(legalCase.root)).split(path.sep).join('/');
999
- const safeFiles = await safeCaseFiles(legalCase.root);
1000
- return safeFiles.filter((relative) => {
1001
- if (action === 'anonymize' && relative === mappingPath) return true;
1002
- const segments = relative.split('/');
1003
- const basename = segments.at(-1) || '';
1004
- const extension = path.extname(basename).toLowerCase();
1005
- const insideDocumentOutput = segments.slice(0, -1).some((segment) => documentKeys.has(documentKey(segment)));
1006
- if (insideDocumentOutput) return true;
1007
- if (extension === '.md') return documentKeys.has(documentKey(basename));
1008
- return false;
1009
- });
1010
- }
1011
-
1012
- async function commitJobArtifacts(job, legalCase, absoluteFiles, homeDir) {
1013
- if (!homeDir) return null;
1014
- const artifactPaths = await sessionArtifactPaths(legalCase, absoluteFiles, job.action);
1015
- // Les Markdown relocalisés depuis la racine rejoignent le commit du lot pour
1016
- // que leur suppression à l'ancien emplacement y soit enregistrée proprement.
1017
- const paths = [...new Set([...artifactPaths, ...(job.migratedFromRoot || [])])];
1018
- if (!paths.length) throw new Error('Traitement terminé, mais aucun fichier produit ne peut être commité.');
1019
- job.phase = 'commit';
1020
- appendLog(job, `Commit automatique de ${paths.length} fichier(s)`);
1021
- const count = absoluteFiles.length;
1022
- const commit = await createCommit({
1023
- casesRoot: legalCase.casesRoot,
1024
- caseName: legalCase.name,
1025
- homeDir,
1026
- label: job.action === 'convert'
1027
- ? `Conversion de ${count} pièce${count > 1 ? 's' : ''}`
1028
- : `Conversion et analyse PII de ${count} pièce${count > 1 ? 's' : ''}`,
1029
- sessionId: job.id,
1030
- event: job.action === 'convert' ? 'admin-conversion' : 'admin-scan',
1031
- paths,
1032
- waitForLockMs: 10_000,
1033
- });
1034
- if (commit.skipped === 'busy') throw new Error('Traitement terminé, mais l’historique est occupé : commit automatique non créé.');
1035
- return {
1036
- created: commit.created,
1037
- hash: commit.commit || null,
1038
- files: commit.files || [],
1039
- };
1040
- }
1041
-
1042
- /**
1043
- * Démarre un travail sur les pièces d'un dossier.
1044
- * `action` vaut `convert` (Markdown seul) ou `anonymize` (Markdown + scan PII
1045
- * + régénération du mapping). `files` contient des chemins relatifs au dossier
1046
- * juridique, tels que renvoyés par `listOriginals`.
1047
- */
1048
- async function startOriginalsJob({ casesRoot, caseName, action, files = [], options = {}, homeDir = null } = {}) {
1049
- if (!['convert', 'anonymize'].includes(action)) throw new Error('Action inconnue sur les pièces originales.');
1050
- if (!acceptingJobs) throw new Error('Le serveur est en cours d’arrêt : aucun nouveau traitement ne peut démarrer.');
1051
- const legalCase = resolveCase(casesRoot, caseName);
1052
- const busy = runningJobForCase(legalCase.root);
1053
- if (busy) throw new Error('Un traitement est déjà en cours sur ce dossier.');
1054
-
1055
- const originals = await listOriginals(legalCase.root);
1056
- const wanted = new Set(files.map((file) => String(file || '').replaceAll('\\', '/')));
1057
- // Les ressources sont volontairement hors périmètre : accessibles à l'IA telles
1058
- // quelles, elles ne sont ni converties ni scannées. On les écarte même si elles
1059
- // sont explicitement cochées, pour que le drapeau reste la seule vérité.
1060
- const selected = (wanted.size ? originals.filter((file) => wanted.has(file.path)) : originals)
1061
- .filter((file) => !file.resource);
1062
- if (!selected.length) {
1063
- throw new Error('Aucune pièce à traiter dans ce dossier.');
1064
- }
1065
-
1066
- // Sans sélection, le travail porte sur tout le dossier et ne refait que ce
1067
- // qui manque : le modèle GLiNER ne se charge pas si tout est déjà scanné.
1068
- // Cocher des pièces vaut demande explicite de les retraiter.
1069
- const forced = wanted.size > 0 || options.force === true;
1070
- const pending = forced
1071
- ? selected
1072
- : selected.filter((file) => (action === 'convert' ? !file.converted : !(file.converted && file.scanned)));
1073
- const skipped = selected.length - pending.length;
1074
-
1075
- pruneJobs();
1076
- if (!pending.length) {
1077
- // Rien à faire : on rend un travail déjà terminé plutôt qu'une erreur, pour
1078
- // que l'administration affiche « à jour » et non un échec.
1079
- const job = finishedJob({
1080
- case: legalCase.name,
1081
- caseRoot: legalCase.root,
1082
- action,
1083
- total: 0,
1084
- files: [],
1085
- result: { converted: 0, scanned: 0, skipped, upToDate: true },
1086
- });
1087
- jobs.set(job.id, job);
1088
- return publicJob(job);
1089
- }
1090
-
1091
- // Les chemins sont relatifs au dossier juridique lui-même : les pièces ne
1092
- // vivent plus dans un sous-dossier dédié, elles sont là où le cabinet les a
1093
- // rangées, racine et sous-dossiers confondus.
1094
- const absoluteFiles = pending.map((file) => {
1095
- const absolute = path.resolve(legalCase.root, ...file.path.split('/'));
1096
- if (!absolute.startsWith(`${legalCase.root}${path.sep}`)) {
1097
- throw new Error('Pièce hors du dossier juridique.');
1098
- }
1099
- if (!fs.existsSync(absolute)) throw new Error(`Pièce introuvable : ${file.name}`);
1100
- return absolute;
1101
- });
1102
-
1103
- const job = {
1104
- id: crypto.randomUUID(),
1105
- case: legalCase.name,
1106
- caseRoot: legalCase.root,
1107
- action,
1108
- state: 'queued',
1109
- phase: 'convert',
1110
- percent: 0,
1111
- processed: 0,
1112
- total: pending.length,
1113
- skipped,
1114
- files: pending.map((file) => file.path),
1115
- reserveBytes: JOB_RESERVE_BYTES(action),
1116
- log: [],
1117
- error: null,
1118
- result: null,
1119
- cancelled: false,
1120
- cancelReason: null,
1121
- child: null,
1122
- completion: null,
1123
- processGroupId: null,
1124
- stopPromise: null,
1125
- timeoutId: null,
1126
- queuedAt: new Date().toISOString(),
1127
- startedAt: null,
1128
- finishedAt: null,
1129
- };
1130
-
1131
- // Le descripteur porte tout ce dont `launchJob` a besoin, qu'il démarre
1132
- // maintenant ou plus tard depuis la file.
1133
- return admitOrQueue({ job, legalCase, absoluteFiles, options, homeDir, skipped, forced });
1134
- }
1135
-
1136
- /** Démarre le traitement maintenant s'il rentre, sinon le met en file d'attente. */
1137
- function admitOrQueue(descriptor) {
1138
- const { job } = descriptor;
1139
- jobs.set(job.id, job);
1140
- if (canAdmit(job)) {
1141
- launchJob(descriptor);
1142
- } else {
1143
- job.state = 'queued';
1144
- waiting.push(descriptor);
1145
- }
1146
- return publicJob(job);
1147
- }
1148
-
1149
- /**
1150
- * Lance réellement le traitement : réserve sa RAM, exécute le script Python, puis
1151
- * — quoi qu'il arrive — libère la réservation et relance la file. Le bloc
1152
- * `finally` est le seul endroit qui rend une place : un traitement qui échoue
1153
- * ne doit pas bloquer la file pour autant.
1154
- */
1155
- function launchJob(descriptor) {
1156
- const { job, legalCase, absoluteFiles, options, homeDir, skipped, forced } = descriptor;
1157
- job.state = 'running';
1158
- job.startedAt = new Date().toISOString();
1159
- reservedBytes += job.reserveBytes;
1160
- job.timeoutId = setTimeout(() => {
1161
- void stopRunningJob(job, 'timeout').catch((error) => {
1162
- appendLog(job, `Nettoyage après délai dépassé : ${error.message}`);
1163
- });
1164
- }, JOB_TIMEOUT_MS());
1165
-
1166
- job.completion = (async () => {
1167
- try {
1168
- const result = await runJob(job, legalCase, absoluteFiles, { ...options, skipExisting: !forced });
1169
- if (job.cancelled) throw cancellationError(job);
1170
- const commit = await commitJobArtifacts(job, legalCase, absoluteFiles, homeDir);
1171
- if (job.cancelled) throw cancellationError(job);
1172
- job.state = 'done';
1173
- job.percent = 100;
1174
- job.processed = job.total;
1175
- job.result = { ...result, skipped, commit };
1176
- } catch (error) {
1177
- job.state = 'error';
1178
- job.error = error.message;
1179
- } finally {
1180
- clearTimeout(job.timeoutId);
1181
- job.timeoutId = null;
1182
- const processGroupId = job.processGroupId;
1183
- try {
1184
- await verifyJobProcessTreeStopped(job, processGroupId);
1185
- } catch (error) {
1186
- job.state = 'error';
1187
- job.error = `Nettoyage incomplet du pipeline : ${error.message}`;
1188
- }
1189
- job.child = null;
1190
- job.processGroupId = null;
1191
- job.stopPromise = null;
1192
- job.finishedAt = new Date().toISOString();
1193
- reservedBytes -= job.reserveBytes;
1194
- pumpQueue();
1195
- }
1196
- })();
1197
- }
1198
-
1199
776
  let stopAllPromise = null;
1200
777
 
1201
778
  /**
@@ -1211,23 +788,12 @@ function stopOriginalsJobs() {
1211
788
  const { job } = descriptor;
1212
789
  job.cancelled = true;
1213
790
  job.cancelReason = 'server-shutdown';
1214
- if (descriptor.run) {
1215
- // Descripteur `runManagedPythonJob` : rejeter sa promesse est le seul
1216
- // moyen de le débloquer, il n'a pas d'état `job.state`/`jobs` propre.
1217
- descriptor.reject(cancellationError(job));
1218
- continue;
1219
- }
1220
- job.state = 'error';
1221
- job.error = 'Traitement interrompu par l’arrêt du serveur.';
1222
- job.finishedAt = new Date().toISOString();
791
+ descriptor.reject(cancellationError(job));
1223
792
  }
1224
793
 
1225
- const running = [...jobs.values()].filter((job) => job.state === 'running');
1226
- const managedRunning = [...runningManaged];
1227
- const allRunning = [...running, ...managedRunning];
1228
- const groups = [...new Set(allRunning.map((job) => job.processGroupId).filter(Boolean))];
1229
- await Promise.allSettled(allRunning.map((job) => stopRunningJob(job, 'server-shutdown')));
1230
- await Promise.allSettled(running.map((job) => job.completion).filter(Boolean));
794
+ const running = [...runningManaged];
795
+ const groups = [...new Set(running.map((job) => job.processGroupId).filter(Boolean))];
796
+ await Promise.allSettled(running.map((job) => stopRunningJob(job, 'server-shutdown')));
1231
797
 
1232
798
  // Contrôle défensif final, y compris si un enfant a fermé ses pipes avant
1233
799
  // que son événement `exit` ait été traité par le suivi normal du job.
@@ -1238,29 +804,7 @@ function stopOriginalsJobs() {
1238
804
  return stopAllPromise;
1239
805
  }
1240
806
 
1241
- function cancelOriginalsJob(jobId) {
1242
- const job = jobs.get(String(jobId || ''));
1243
- if (!job) return null;
1244
- // Un job encore en file n'a pas de process : on le retire de la file et on
1245
- // laisse la place au suivant.
1246
- if (job.state === 'queued') {
1247
- const index = waiting.findIndex((entry) => entry.job.id === job.id);
1248
- if (index >= 0) waiting.splice(index, 1);
1249
- job.state = 'error';
1250
- job.error = 'Traitement interrompu.';
1251
- job.finishedAt = new Date().toISOString();
1252
- pumpQueue();
1253
- return publicJob(job);
1254
- }
1255
- if (job.state !== 'running') return null;
1256
- void stopRunningJob(job, 'user-cancellation').catch((error) => {
1257
- appendLog(job, `Nettoyage après annulation : ${error.message}`);
1258
- });
1259
- return publicJob(job);
1260
- }
1261
-
1262
807
  module.exports = {
1263
- cancelOriginalsJob,
1264
808
  groupEntityHits,
1265
809
  // Point d'entrée partagé : tout consommateur d'un pipeline GLiNER/markitdown
1266
810
  // (même hors dossier juridique enregistré, ex. `knowledge/pipeline.ts`) doit
@@ -1270,12 +814,9 @@ module.exports = {
1270
814
  // Ré-exportés pour les routes de l'administration : l'implémentation vit
1271
815
  // désormais dans `piecemaker-plugin/scripts/lib/mapping.cjs`.
1272
816
  caseMappingFile,
1273
- getJob,
1274
817
  listOriginals,
1275
818
  readCaseMapping,
1276
819
  rebuildCaseMapping,
1277
820
  saveCaseMapping,
1278
- sessionArtifactPaths,
1279
- startOriginalsJob,
1280
821
  writeCaseMapping,
1281
822
  };