@exulu/backend 3.5.0 → 3.6.0

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/index.cjs CHANGED
@@ -11452,6 +11452,52 @@ var usersSchema = {
11452
11452
  // triggered by the key with project_id_ for LiteLLM cost attribution.
11453
11453
  name: "project",
11454
11454
  type: "uuid"
11455
+ },
11456
+ // ── Lead capture (see lead-capture spec §5) ─────────────────────────────
11457
+ // Consent must be *provable* under Art. 7(1) GDPR: who agreed, when, and
11458
+ // to which wording. Hence a timestamp per consent and a version string
11459
+ // pointing at the exact text that was shown.
11460
+ {
11461
+ // Current state of the optional marketing consent.
11462
+ name: "marketing_consent",
11463
+ type: "boolean",
11464
+ default: false
11465
+ },
11466
+ {
11467
+ // When marketing consent was GIVEN.
11468
+ name: "marketing_consent_at",
11469
+ type: "date"
11470
+ },
11471
+ {
11472
+ // When marketing consent was WITHDRAWN. Deliberately a separate column:
11473
+ // overwriting marketing_consent_at on withdrawal would destroy the grant
11474
+ // time, and with it the ability to prove the order of events if someone
11475
+ // later claims they were mailed after withdrawing.
11476
+ name: "marketing_consent_withdrawn_at",
11477
+ type: "date"
11478
+ },
11479
+ {
11480
+ // When the mandatory storage/processing consent was given.
11481
+ name: "processing_consent_at",
11482
+ type: "date"
11483
+ },
11484
+ {
11485
+ // When the user asked for erasure (Art. 17). The row survives so the
11486
+ // one-month deadline stays queryable even if the notification mail fails.
11487
+ name: "erasure_requested_at",
11488
+ type: "date"
11489
+ },
11490
+ {
11491
+ // Where the lead came from, e.g. "eu-ai-act-bot".
11492
+ name: "signup_source",
11493
+ type: "text"
11494
+ },
11495
+ {
11496
+ // Version of the consent wording that was actually displayed, e.g.
11497
+ // "2026-08-18". The wording itself lives in the frontend repo under
11498
+ // version control; this column points at it.
11499
+ name: "consent_version",
11500
+ type: "text"
11455
11501
  }
11456
11502
  ]
11457
11503
  };
@@ -11760,6 +11806,32 @@ var addCoreFields = (schema) => {
11760
11806
  }
11761
11807
  return schema;
11762
11808
  };
11809
+ var otpSendAttemptsSchema = {
11810
+ type: "otp_send_attempts",
11811
+ name: {
11812
+ plural: "otp_send_attempts",
11813
+ singular: "otp_send_attempt"
11814
+ },
11815
+ fields: [
11816
+ {
11817
+ name: "key",
11818
+ type: "text",
11819
+ index: true,
11820
+ unique: true,
11821
+ required: true
11822
+ },
11823
+ {
11824
+ name: "count",
11825
+ type: "number",
11826
+ required: true
11827
+ },
11828
+ {
11829
+ name: "window_start",
11830
+ type: "date",
11831
+ required: true
11832
+ }
11833
+ ]
11834
+ };
11763
11835
  var coreSchemas = {
11764
11836
  get: () => {
11765
11837
  const license = checkLicense();
@@ -11780,7 +11852,8 @@ var coreSchemas = {
11780
11852
  contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
11781
11853
  sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
11782
11854
  transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
11783
- imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
11855
+ imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema),
11856
+ otpSendAttemptsSchema: () => addCoreFields(otpSendAttemptsSchema)
11784
11857
  };
11785
11858
  if (license["agent-feedback"]) {
11786
11859
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -31560,7 +31633,8 @@ var {
31560
31633
  promptFavoritesSchema: promptFavoritesSchema3,
31561
31634
  transcriptionJobsSchema: transcriptionJobsSchema3,
31562
31635
  imageGenerationsSchema: imageGenerationsSchema2,
31563
- sharedArtifactsSchema: sharedArtifactsSchema2
31636
+ sharedArtifactsSchema: sharedArtifactsSchema2,
31637
+ otpSendAttemptsSchema: otpSendAttemptsSchema2
31564
31638
  } = coreSchemas.get();
31565
31639
  var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
31566
31640
  for (const field of fields) {
@@ -31631,7 +31705,8 @@ var up = async function(knex) {
31631
31705
  variablesSchema3(),
31632
31706
  skillsSchema3(),
31633
31707
  workflowTemplatesSchema3(),
31634
- workflowTriggersSchema2()
31708
+ workflowTriggersSchema2(),
31709
+ otpSendAttemptsSchema2()
31635
31710
  ];
31636
31711
  const createTable = async (schema) => {
31637
31712
  if (!await knex.schema.hasTable(schema.name.plural)) {
@@ -33398,7 +33473,16 @@ ${setupResult.output || ""}`);
33398
33473
  args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk), "--max-size-mb", "25"],
33399
33474
  timeout: 5 * 60 * 1e3
33400
33475
  });
33401
- const pdfChunks = JSON.parse(splitResult.stdout);
33476
+ let pdfChunks;
33477
+ try {
33478
+ pdfChunks = JSON.parse(splitResult.stdout);
33479
+ } catch (err) {
33480
+ throw new Error(
33481
+ `[EXULU] split_pdf.py returned invalid JSON on stdout: ${err.message}
33482
+ stdout: ${splitResult.stdout.slice(0, 500)}
33483
+ stderr: ${splitResult.stderr.slice(-1e3)}`
33484
+ );
33485
+ }
33402
33486
  console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
33403
33487
  const chunkLimit = (0, import_p_limit.default)(3);
33404
33488
  const chunkResults = await Promise.all(
package/dist/index.js CHANGED
@@ -2568,6 +2568,52 @@ var usersSchema = {
2568
2568
  // triggered by the key with project_id_ for LiteLLM cost attribution.
2569
2569
  name: "project",
2570
2570
  type: "uuid"
2571
+ },
2572
+ // ── Lead capture (see lead-capture spec §5) ─────────────────────────────
2573
+ // Consent must be *provable* under Art. 7(1) GDPR: who agreed, when, and
2574
+ // to which wording. Hence a timestamp per consent and a version string
2575
+ // pointing at the exact text that was shown.
2576
+ {
2577
+ // Current state of the optional marketing consent.
2578
+ name: "marketing_consent",
2579
+ type: "boolean",
2580
+ default: false
2581
+ },
2582
+ {
2583
+ // When marketing consent was GIVEN.
2584
+ name: "marketing_consent_at",
2585
+ type: "date"
2586
+ },
2587
+ {
2588
+ // When marketing consent was WITHDRAWN. Deliberately a separate column:
2589
+ // overwriting marketing_consent_at on withdrawal would destroy the grant
2590
+ // time, and with it the ability to prove the order of events if someone
2591
+ // later claims they were mailed after withdrawing.
2592
+ name: "marketing_consent_withdrawn_at",
2593
+ type: "date"
2594
+ },
2595
+ {
2596
+ // When the mandatory storage/processing consent was given.
2597
+ name: "processing_consent_at",
2598
+ type: "date"
2599
+ },
2600
+ {
2601
+ // When the user asked for erasure (Art. 17). The row survives so the
2602
+ // one-month deadline stays queryable even if the notification mail fails.
2603
+ name: "erasure_requested_at",
2604
+ type: "date"
2605
+ },
2606
+ {
2607
+ // Where the lead came from, e.g. "eu-ai-act-bot".
2608
+ name: "signup_source",
2609
+ type: "text"
2610
+ },
2611
+ {
2612
+ // Version of the consent wording that was actually displayed, e.g.
2613
+ // "2026-08-18". The wording itself lives in the frontend repo under
2614
+ // version control; this column points at it.
2615
+ name: "consent_version",
2616
+ type: "text"
2571
2617
  }
2572
2618
  ]
2573
2619
  };
@@ -2876,6 +2922,32 @@ var addCoreFields = (schema) => {
2876
2922
  }
2877
2923
  return schema;
2878
2924
  };
2925
+ var otpSendAttemptsSchema = {
2926
+ type: "otp_send_attempts",
2927
+ name: {
2928
+ plural: "otp_send_attempts",
2929
+ singular: "otp_send_attempt"
2930
+ },
2931
+ fields: [
2932
+ {
2933
+ name: "key",
2934
+ type: "text",
2935
+ index: true,
2936
+ unique: true,
2937
+ required: true
2938
+ },
2939
+ {
2940
+ name: "count",
2941
+ type: "number",
2942
+ required: true
2943
+ },
2944
+ {
2945
+ name: "window_start",
2946
+ type: "date",
2947
+ required: true
2948
+ }
2949
+ ]
2950
+ };
2879
2951
  var coreSchemas = {
2880
2952
  get: () => {
2881
2953
  const license = checkLicense();
@@ -2896,7 +2968,8 @@ var coreSchemas = {
2896
2968
  contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
2897
2969
  sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
2898
2970
  transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
2899
- imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
2971
+ imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema),
2972
+ otpSendAttemptsSchema: () => addCoreFields(otpSendAttemptsSchema)
2900
2973
  };
2901
2974
  if (license["agent-feedback"]) {
2902
2975
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -22174,7 +22247,8 @@ var {
22174
22247
  promptFavoritesSchema: promptFavoritesSchema3,
22175
22248
  transcriptionJobsSchema: transcriptionJobsSchema3,
22176
22249
  imageGenerationsSchema: imageGenerationsSchema2,
22177
- sharedArtifactsSchema: sharedArtifactsSchema2
22250
+ sharedArtifactsSchema: sharedArtifactsSchema2,
22251
+ otpSendAttemptsSchema: otpSendAttemptsSchema2
22178
22252
  } = coreSchemas.get();
22179
22253
  var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
22180
22254
  for (const field of fields) {
@@ -22245,7 +22319,8 @@ var up = async function(knex) {
22245
22319
  variablesSchema3(),
22246
22320
  skillsSchema3(),
22247
22321
  workflowTemplatesSchema3(),
22248
- workflowTriggersSchema2()
22322
+ workflowTriggersSchema2(),
22323
+ otpSendAttemptsSchema2()
22249
22324
  ];
22250
22325
  const createTable = async (schema) => {
22251
22326
  if (!await knex.schema.hasTable(schema.name.plural)) {
@@ -23992,7 +24067,16 @@ ${setupResult.output || ""}`);
23992
24067
  args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk), "--max-size-mb", "25"],
23993
24068
  timeout: 5 * 60 * 1e3
23994
24069
  });
23995
- const pdfChunks = JSON.parse(splitResult.stdout);
24070
+ let pdfChunks;
24071
+ try {
24072
+ pdfChunks = JSON.parse(splitResult.stdout);
24073
+ } catch (err) {
24074
+ throw new Error(
24075
+ `[EXULU] split_pdf.py returned invalid JSON on stdout: ${err.message}
24076
+ stdout: ${splitResult.stdout.slice(0, 500)}
24077
+ stderr: ${splitResult.stderr.slice(-1e3)}`
24078
+ );
24079
+ }
23996
24080
  console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
23997
24081
  const chunkLimit = pLimit(3);
23998
24082
  const chunkResults = await Promise.all(
@@ -837,8 +837,20 @@ async function processPdf(
837
837
  timeout: 5 * 60 * 1000,
838
838
  });
839
839
 
840
- const pdfChunks: Array<{ path: string; start_page: number; end_page: number }> =
841
- JSON.parse(splitResult.stdout);
840
+ // split_pdf.py contracts to put nothing but the JSON payload on stdout.
841
+ // If something slips in anyway, the bare SyntaxError only quotes the first
842
+ // few characters ("Unexpected token 'w'") and names neither the script nor
843
+ // the offending output — so re-throw with the actual streams attached.
844
+ let pdfChunks: Array<{ path: string; start_page: number; end_page: number }>;
845
+ try {
846
+ pdfChunks = JSON.parse(splitResult.stdout);
847
+ } catch (err) {
848
+ throw new Error(
849
+ `[EXULU] split_pdf.py returned invalid JSON on stdout: ${(err as Error).message}\n` +
850
+ `stdout: ${splitResult.stdout.slice(0, 500)}\n` +
851
+ `stderr: ${splitResult.stderr.slice(-1000)}`
852
+ );
853
+ }
842
854
 
843
855
  console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
844
856
 
@@ -20,7 +20,24 @@ import os
20
20
  import json
21
21
  import argparse
22
22
 
23
- import fitz # PyMuPDF installed as a docling transitive dependency
23
+ # stdout is this script's result channel and the caller does JSON.parse() on it,
24
+ # so nothing else may write there. Our own prints all pass file=sys.stderr, but
25
+ # dependencies do not honour that: PyMuPDF sends its messages to sys.stdout by
26
+ # default, and importing the legacy `fitz` alias emits
27
+ # "warning: The `fitz` API is deprecated ..." — which lands ahead of the payload
28
+ # and fails the caller with "Unexpected token 'w'". PyMuPDF arrives unpinned as
29
+ # a docling transitive dependency, so a routine rebuild is enough to introduce a
30
+ # banner like that. Point sys.stdout at stderr before importing anything and
31
+ # keep a private handle for the result, so any library that prints — now or
32
+ # after a future dependency bump — is shunted to the log channel instead of
33
+ # corrupting the payload.
34
+ _stdout = sys.stdout
35
+ sys.stdout = sys.stderr
36
+
37
+ try:
38
+ import pymupdf as fitz # PyMuPDF >= 1.24.3, where the module was renamed
39
+ except ImportError: # older releases only ship the legacy `fitz` module
40
+ import fitz
24
41
 
25
42
 
26
43
  def _write_chunk(
@@ -145,7 +162,7 @@ if __name__ == "__main__":
145
162
 
146
163
  try:
147
164
  chunks = split_pdf(args.input_pdf, args.output_dir, args.chunk_size, max_size_bytes)
148
- print(json.dumps(chunks))
165
+ print(json.dumps(chunks), file=_stdout)
149
166
  except Exception as e:
150
167
  print(f"[split_pdf] ERROR: {e}", file=sys.stderr)
151
168
  sys.exit(1)
@@ -24,17 +24,26 @@ pyannote.audio>=3.3.0
24
24
  # Belt-and-suspenders: keep huggingface_hub on the 0.x line so pyannote 3.x's
25
25
  # `use_auth_token=` calls keep working (1.x removed that kwarg → diarization off).
26
26
  huggingface_hub<1.0
27
- fastapi
28
27
  uvicorn
29
28
  python-multipart
30
29
  requests
31
30
  # LiteLLM proxy — only used when EXULU_USE_LITELLM=true. Always installed so
32
31
  # the dep is ready when the env var is flipped. Pinned to a tested version;
33
32
  # upgrade deliberately.
34
- litellm[proxy]==1.85.1
33
+ litellm[proxy]==1.97.0
34
+ # FastAPI ceiling — NOT cosmetic. litellm 1.97.0 declares fastapi>=0.136.3,<1.0
35
+ # but imports the private `get_flat_dependant` from fastapi.dependencies.utils
36
+ # (proxy/management_endpoints/management_v1/common.py:7), which FastAPI deleted
37
+ # in 0.140.7. Any resolve that picks 0.140.7+ yields a proxy that cannot start:
38
+ # "ImportError: cannot import name 'get_flat_dependant'". 0.140.6 is the last
39
+ # version that still has it. NOTE: fastapi is also a first-party dependency of
40
+ # ee/python/transcription/server.py (not just a litellm transitive). Once
41
+ # litellm ships its own ceiling, replace this constrained pin with a bare
42
+ # `fastapi` line — do not delete it entirely.
43
+ fastapi>=0.136.3,<0.140.7 # to confirm this pin is still needed: grep -r get_flat_dependant $(pip show litellm | awk '/Location/{print $2}')/litellm/proxy/
35
44
  # Prisma Python client — required by LiteLLM proxy whenever a `database_url`
36
45
  # is set in config.litellm.yaml (used for user/budget tracking, key
37
- # management, etc.). NOT included in litellm[proxy] in 1.85.1, hence the
46
+ # management, etc.). NOT included in litellm[proxy] in 1.97.0, hence the
38
47
  # separate pin. setup.sh runs `prisma generate` against LiteLLM's bundled
39
48
  # schema after pip install so the Python client module is materialized.
40
49
  prisma==0.15.0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "3.5.0",
4
+ "version": "3.6.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {