tradeblocks-mcp 3.3.1 → 3.5.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.
Files changed (77) hide show
  1. package/dist/{chunk-4HZLVUOZ.js → chunk-5HBZ3TZM.js} +2350 -336
  2. package/dist/chunk-5HBZ3TZM.js.map +1 -0
  3. package/dist/{chunk-NWNSKALN.js → chunk-DHHEGI3Q.js} +15 -8
  4. package/dist/chunk-DHHEGI3Q.js.map +1 -0
  5. package/dist/{chunk-2I3S2ZLT.js → chunk-GD3STARM.js} +278 -45
  6. package/dist/chunk-GD3STARM.js.map +1 -0
  7. package/dist/market/provenance/index.d.ts +644 -0
  8. package/dist/market/provenance/index.js +23067 -0
  9. package/dist/market/provenance/index.js.map +1 -0
  10. package/dist/{market-provider-6U33TQUT.js → market-provider-JJIJ7QMU.js} +3 -3
  11. package/dist/{sync-3UBC37VW.js → sync-AWI764HE.js} +3 -3
  12. package/dist/test-exports.js +4174 -549
  13. package/dist/test-exports.js.map +1 -1
  14. package/package.json +8 -2
  15. package/server/{chunk-RM4B2VKS.js → chunk-4BY4JOI3.js} +2300 -370
  16. package/server/chunk-4BY4JOI3.js.map +1 -0
  17. package/server/{chunk-RJDJHZ5Y.js → chunk-65OJTDFW.js} +276 -45
  18. package/server/chunk-65OJTDFW.js.map +1 -0
  19. package/server/{chunk-OMUDNJBJ.js → chunk-KP4NYKL5.js} +15 -8
  20. package/server/chunk-KP4NYKL5.js.map +1 -0
  21. package/server/index.d.ts +37 -21
  22. package/server/index.js +3780 -477
  23. package/server/index.js.map +1 -1
  24. package/server/{market-provider-L56RFHAC.js → market-provider-DIPCCFJU.js} +3 -3
  25. package/server/{sync-CENKDN53.js → sync-2SFFAZ67.js} +3 -3
  26. package/src/db/connection.ts +142 -3
  27. package/src/db/index.ts +1 -0
  28. package/src/db/market-datasets.ts +208 -24
  29. package/src/db/market-views.ts +28 -11
  30. package/src/db/parquet-writer.ts +234 -11
  31. package/src/market/ingestor/index.ts +1 -0
  32. package/src/market/ingestor/market-ingestor.ts +7 -0
  33. package/src/market/ingestor/types.ts +18 -0
  34. package/src/market/provenance/canonical-json.ts +119 -0
  35. package/src/market/provenance/canonical-market-resolver.ts +801 -0
  36. package/src/market/provenance/content-manifest.ts +1934 -0
  37. package/src/market/provenance/content-object-store.ts +179 -0
  38. package/src/market/provenance/dataset-registry.ts +144 -0
  39. package/src/market/provenance/index.ts +137 -0
  40. package/src/market/provenance/partition-commit-attempt.ts +89 -0
  41. package/src/market/provenance/partition-commit-store.ts +1847 -0
  42. package/src/market/provenance/rate-slices.ts +60 -0
  43. package/src/market/provenance/refresh-completion.ts +989 -0
  44. package/src/market/provenance/xnys-session-calendar.ts +170 -0
  45. package/src/market/stores/coverage.ts +48 -0
  46. package/src/market/stores/duckdb-enriched-store.ts +26 -5
  47. package/src/market/stores/duckdb-quote-store.ts +29 -18
  48. package/src/market/stores/enriched-legacy-migration.ts +297 -0
  49. package/src/market/stores/enriched-sql.ts +19 -3
  50. package/src/market/stores/enriched-store.ts +16 -2
  51. package/src/market/stores/index.ts +143 -14
  52. package/src/market/stores/parquet-chain-store.ts +4 -1
  53. package/src/market/stores/parquet-enriched-store.ts +162 -41
  54. package/src/market/stores/parquet-oi-daily-store.ts +3 -3
  55. package/src/market/stores/parquet-quote-store.ts +37 -24
  56. package/src/market/stores/parquet-spot-store.ts +14 -10
  57. package/src/market/stores/quote-store.ts +5 -0
  58. package/src/market/stores/rth-aggregation.ts +4 -2
  59. package/src/market/stores/spot-store.ts +75 -6
  60. package/src/market/stores/types.ts +27 -0
  61. package/src/test-exports.ts +154 -2
  62. package/src/tools/market-enrichment.ts +2 -4
  63. package/src/utils/market-enricher.ts +249 -66
  64. package/src/utils/option-quote-greeks.ts +20 -8
  65. package/src/utils/providers/thetadata/bulk-roots.ts +10 -0
  66. package/src/utils/providers/thetadata.ts +5 -17
  67. package/src/utils/quote-parquet-projection.ts +50 -1
  68. package/dist/chunk-2I3S2ZLT.js.map +0 -1
  69. package/dist/chunk-4HZLVUOZ.js.map +0 -1
  70. package/dist/chunk-NWNSKALN.js.map +0 -1
  71. package/server/chunk-OMUDNJBJ.js.map +0 -1
  72. package/server/chunk-RJDJHZ5Y.js.map +0 -1
  73. package/server/chunk-RM4B2VKS.js.map +0 -1
  74. /package/dist/{market-provider-6U33TQUT.js.map → market-provider-JJIJ7QMU.js.map} +0 -0
  75. /package/dist/{sync-3UBC37VW.js.map → sync-AWI764HE.js.map} +0 -0
  76. /package/server/{market-provider-L56RFHAC.js.map → market-provider-DIPCCFJU.js.map} +0 -0
  77. /package/server/{sync-CENKDN53.js.map → sync-2SFFAZ67.js.map} +0 -0
@@ -1,20 +1,23 @@
1
1
  import {
2
2
  REPORTING_TRADE_COLUMN_ALIASES,
3
3
  convertTatRowToReportingTrade,
4
- isTatFormat
5
- } from "./chunk-2I3S2ZLT.js";
4
+ describeReadParquetColumns,
5
+ isTatFormat,
6
+ quoteParquetCanonicalProjection,
7
+ readParquetGlobSql
8
+ } from "./chunk-GD3STARM.js";
6
9
 
7
10
  // src/sync/index.ts
8
11
  import { existsSync as existsSync2 } from "fs";
9
- import * as fs9 from "fs/promises";
10
- import * as path12 from "path";
12
+ import * as fs11 from "fs/promises";
13
+ import * as path15 from "path";
11
14
 
12
15
  // src/db/connection.ts
13
16
  import { DuckDBInstance } from "@duckdb/node-api";
14
17
  import { execFile } from "child_process";
15
- import * as fs4 from "fs/promises";
18
+ import * as fs6 from "fs/promises";
16
19
  import * as os from "os";
17
- import * as path8 from "path";
20
+ import * as path11 from "path";
18
21
  import { promisify } from "util";
19
22
 
20
23
  // src/db/schemas.ts
@@ -117,109 +120,1867 @@ async function ensureReportingDataTable(conn) {
117
120
  await conn.run(`ALTER TABLE trades.reporting_data ADD COLUMN ticker VARCHAR`);
118
121
  }
119
122
  }
120
-
121
- // src/db/market-schemas.ts
122
- async function ensureMutableMarketTables(conn) {
123
- await conn.run(`
124
- CREATE TABLE IF NOT EXISTS market._sync_metadata (
125
- source VARCHAR NOT NULL,
126
- ticker VARCHAR NOT NULL,
127
- target_table VARCHAR NOT NULL,
128
-
129
- content_hash VARCHAR,
130
- max_date VARCHAR,
131
- wilder_state JSON,
132
- synced_at TIMESTAMP NOT NULL,
133
-
134
- PRIMARY KEY (source, ticker, target_table)
135
- )
136
- `);
137
- }
138
- async function ensureMarketDataTables(conn) {
139
- for (const name of ["daily", "date_context", "intraday", "data_coverage"]) {
140
- try {
141
- await conn.run(`DROP VIEW IF EXISTS market.${name}`);
142
- } catch {
143
- }
144
- try {
145
- await conn.run(`DROP TABLE IF EXISTS market.${name}`);
146
- } catch {
147
- }
123
+
124
+ // src/db/market-schemas.ts
125
+ async function ensureMutableMarketTables(conn) {
126
+ await conn.run(`
127
+ CREATE TABLE IF NOT EXISTS market._sync_metadata (
128
+ source VARCHAR NOT NULL,
129
+ ticker VARCHAR NOT NULL,
130
+ target_table VARCHAR NOT NULL,
131
+
132
+ content_hash VARCHAR,
133
+ max_date VARCHAR,
134
+ wilder_state JSON,
135
+ synced_at TIMESTAMP NOT NULL,
136
+
137
+ PRIMARY KEY (source, ticker, target_table)
138
+ )
139
+ `);
140
+ }
141
+ async function ensureMarketDataTables(conn) {
142
+ for (const name of ["daily", "date_context", "intraday", "data_coverage"]) {
143
+ try {
144
+ await conn.run(`DROP VIEW IF EXISTS market.${name}`);
145
+ } catch {
146
+ }
147
+ try {
148
+ await conn.run(`DROP TABLE IF EXISTS market.${name}`);
149
+ } catch {
150
+ }
151
+ }
152
+ await conn.run(`
153
+ CREATE TABLE IF NOT EXISTS market.spot (
154
+ ticker VARCHAR NOT NULL,
155
+ date VARCHAR NOT NULL,
156
+ time VARCHAR NOT NULL,
157
+ open DOUBLE,
158
+ high DOUBLE,
159
+ low DOUBLE,
160
+ close DOUBLE,
161
+ bid DOUBLE,
162
+ ask DOUBLE,
163
+ PRIMARY KEY (ticker, date, time)
164
+ )
165
+ `);
166
+ await conn.run(`
167
+ CREATE TABLE IF NOT EXISTS market.enriched (
168
+ ticker VARCHAR NOT NULL,
169
+ date VARCHAR NOT NULL,
170
+ Prior_Close DOUBLE,
171
+ Gap_Pct DOUBLE,
172
+ ATR_Pct DOUBLE,
173
+ RSI_14 DOUBLE,
174
+ Price_vs_EMA21_Pct DOUBLE,
175
+ Price_vs_SMA50_Pct DOUBLE,
176
+ Realized_Vol_5D DOUBLE,
177
+ Realized_Vol_20D DOUBLE,
178
+ Return_5D DOUBLE,
179
+ Return_20D DOUBLE,
180
+ Intraday_Range_Pct DOUBLE,
181
+ Intraday_Return_Pct DOUBLE,
182
+ Close_Position_In_Range DOUBLE,
183
+ Gap_Filled INTEGER,
184
+ Consecutive_Days INTEGER,
185
+ Prev_Return_Pct DOUBLE,
186
+ Prior_Range_vs_ATR DOUBLE,
187
+ High_Time DOUBLE,
188
+ Low_Time DOUBLE,
189
+ High_Before_Low INTEGER,
190
+ Reversal_Type INTEGER,
191
+ Opening_Drive_Strength DOUBLE,
192
+ Intraday_Realized_Vol DOUBLE,
193
+ Day_of_Week INTEGER,
194
+ Month INTEGER,
195
+ Is_Opex INTEGER,
196
+ ivr DOUBLE,
197
+ ivp DOUBLE,
198
+ PRIMARY KEY (ticker, date)
199
+ )
200
+ `);
201
+ await conn.run(`
202
+ CREATE TABLE IF NOT EXISTS market.enriched_context (
203
+ date VARCHAR NOT NULL,
204
+ Vol_Regime INTEGER,
205
+ Term_Structure_State INTEGER,
206
+ Trend_Direction VARCHAR,
207
+ VIX_Spike_Pct DOUBLE,
208
+ VIX_Gap_Pct DOUBLE,
209
+ PRIMARY KEY (date)
210
+ )
211
+ `);
212
+ }
213
+
214
+ // src/db/parquet-writer.ts
215
+ import { createHash as createHash3 } from "crypto";
216
+ import { createReadStream } from "fs";
217
+ import * as fs3 from "fs/promises";
218
+ import * as path6 from "path";
219
+
220
+ // src/market/provenance/partition-commit-store.ts
221
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
222
+ import { constants, readFileSync } from "fs";
223
+ import * as fs2 from "fs/promises";
224
+ import { hostname } from "os";
225
+ import * as path3 from "path";
226
+
227
+ // src/market/provenance/canonical-json.ts
228
+ import { createHash } from "crypto";
229
+ var CANONICAL_JSON_VERSION = 1;
230
+ var SHA256_ADDRESS_RE = /^sha256:([0-9a-f]{64})$/;
231
+ function compareCodePoints(left, right) {
232
+ const leftPoints = Array.from(left, (char) => char.codePointAt(0));
233
+ const rightPoints = Array.from(right, (char) => char.codePointAt(0));
234
+ const length = Math.min(leftPoints.length, rightPoints.length);
235
+ for (let i = 0; i < length; i++) {
236
+ if (leftPoints[i] !== rightPoints[i]) return leftPoints[i] - rightPoints[i];
237
+ }
238
+ return leftPoints.length - rightPoints.length;
239
+ }
240
+ function encode(value, seen) {
241
+ if (value === null) return "null";
242
+ switch (typeof value) {
243
+ case "string":
244
+ return JSON.stringify(value.normalize("NFC"));
245
+ case "boolean":
246
+ return value ? "true" : "false";
247
+ case "number":
248
+ if (!Number.isSafeInteger(value)) {
249
+ throw new TypeError("Canonical JSON v1 supports only safe integers");
250
+ }
251
+ return JSON.stringify(value);
252
+ case "undefined":
253
+ case "function":
254
+ case "symbol":
255
+ case "bigint":
256
+ throw new TypeError(`Canonical JSON does not support ${typeof value}`);
257
+ case "object":
258
+ break;
259
+ }
260
+ const object = value;
261
+ if (seen.has(object)) throw new TypeError("Canonical JSON does not support cyclic values");
262
+ seen.add(object);
263
+ try {
264
+ if (Array.isArray(value)) {
265
+ const elements = [];
266
+ for (let i = 0; i < value.length; i++) {
267
+ if (!(i in value)) throw new TypeError("Canonical JSON does not support sparse arrays");
268
+ elements.push(encode(value[i], seen));
269
+ }
270
+ return `[${elements.join(",")}]`;
271
+ }
272
+ const prototype = Object.getPrototypeOf(value);
273
+ if (prototype !== Object.prototype && prototype !== null) {
274
+ throw new TypeError("Canonical JSON accepts only plain objects and arrays");
275
+ }
276
+ const normalizedKeys = /* @__PURE__ */ new Map();
277
+ for (const sourceKey of Object.keys(value)) {
278
+ const normalizedKey = sourceKey.normalize("NFC");
279
+ const collision = normalizedKeys.get(normalizedKey);
280
+ if (collision !== void 0 && collision !== sourceKey) {
281
+ throw new TypeError(
282
+ `Canonical JSON key collision after NFC normalization: ${JSON.stringify(collision)} and ${JSON.stringify(sourceKey)}`
283
+ );
284
+ }
285
+ normalizedKeys.set(normalizedKey, sourceKey);
286
+ }
287
+ const entries = [...normalizedKeys.entries()].sort(([left], [right]) => compareCodePoints(left, right)).map(
288
+ ([normalizedKey, sourceKey]) => `${JSON.stringify(normalizedKey)}:${encode(value[sourceKey], seen)}`
289
+ );
290
+ return `{${entries.join(",")}}`;
291
+ } finally {
292
+ seen.delete(object);
293
+ }
294
+ }
295
+ function canonicalJson(value) {
296
+ return encode(value, /* @__PURE__ */ new WeakSet());
297
+ }
298
+ function canonicalJsonBytes(value) {
299
+ return Buffer.from(canonicalJson(value), "utf8");
300
+ }
301
+ function addressCanonicalJson(value) {
302
+ return addressBytes(canonicalJsonBytes(value));
303
+ }
304
+ function addressBytes(bytes) {
305
+ const digest = createHash("sha256").update(bytes).digest("hex");
306
+ return `sha256:${digest}`;
307
+ }
308
+ function parseCanonicalJsonAddress(address) {
309
+ return parseSha256Address(address);
310
+ }
311
+ function parseSha256Address(address) {
312
+ const match = SHA256_ADDRESS_RE.exec(address);
313
+ if (!match) throw new TypeError(`Invalid SHA-256 address: ${JSON.stringify(address)}`);
314
+ return match[1];
315
+ }
316
+
317
+ // src/market/provenance/content-object-store.ts
318
+ import * as fs from "fs/promises";
319
+ import * as path from "path";
320
+ import { randomUUID } from "crypto";
321
+ var ContentObjectCollisionError = class extends Error {
322
+ address;
323
+ objectPath;
324
+ constructor(address, objectPath) {
325
+ super(`Content object collision or corruption at ${address} (${objectPath})`);
326
+ this.name = "ContentObjectCollisionError";
327
+ this.address = address;
328
+ this.objectPath = objectPath;
329
+ }
330
+ };
331
+ function deepFreeze(value) {
332
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
333
+ for (const child of Object.values(value)) deepFreeze(child);
334
+ Object.freeze(value);
335
+ }
336
+ return value;
337
+ }
338
+ var ContentObjectStore = class {
339
+ rootDir;
340
+ constructor(rootDir) {
341
+ this.rootDir = rootDir;
342
+ }
343
+ objectPath(address) {
344
+ const digest = parseCanonicalJsonAddress(address);
345
+ return path.join(this.rootDir, "objects", "sha256", digest.slice(0, 2), `${digest}.json`);
346
+ }
347
+ async syncDirectory(directory) {
348
+ const handle = await fs.open(directory, "r");
349
+ try {
350
+ await handle.sync();
351
+ } finally {
352
+ await handle.close();
353
+ }
354
+ }
355
+ async ensureDurableDirectory(directory) {
356
+ const parent = path.dirname(directory);
357
+ if (parent !== directory) await this.ensureDurableDirectory(parent);
358
+ try {
359
+ const handle = await fs.open(directory, "r");
360
+ try {
361
+ await handle.sync();
362
+ } finally {
363
+ await handle.close();
364
+ }
365
+ if (parent !== directory) await this.syncDirectory(parent);
366
+ return;
367
+ } catch (error) {
368
+ if (error.code !== "ENOENT") throw error;
369
+ }
370
+ if (parent === directory)
371
+ throw new Error(`Cannot create content-object directory ${directory}`);
372
+ let created = false;
373
+ try {
374
+ await fs.mkdir(directory);
375
+ created = true;
376
+ } catch (error) {
377
+ if (error.code !== "EEXIST") throw error;
378
+ }
379
+ if (created) {
380
+ await this.syncDirectory(directory);
381
+ await this.syncDirectory(parent);
382
+ }
383
+ }
384
+ async syncExistingObject(objectPath) {
385
+ const handle = await fs.open(objectPath, "r");
386
+ try {
387
+ await handle.sync();
388
+ } finally {
389
+ await handle.close();
390
+ }
391
+ await this.syncDirectory(path.dirname(objectPath));
392
+ }
393
+ async put(value) {
394
+ const bytes = canonicalJsonBytes(value);
395
+ const address = addressBytes(bytes);
396
+ const materialized = deepFreeze(JSON.parse(bytes.toString("utf8")));
397
+ const objectPath = this.objectPath(address);
398
+ const objectDir = path.dirname(objectPath);
399
+ await this.ensureDurableDirectory(objectDir);
400
+ const tempPath = path.join(objectDir, `.${path.basename(objectPath)}.tmp-${randomUUID()}`);
401
+ let handle;
402
+ try {
403
+ handle = await fs.open(tempPath, "wx", 292);
404
+ await handle.writeFile(bytes);
405
+ await handle.sync();
406
+ await handle.close();
407
+ handle = void 0;
408
+ await fs.link(tempPath, objectPath);
409
+ await this.syncDirectory(objectDir);
410
+ await fs.unlink(tempPath);
411
+ await this.syncDirectory(objectDir);
412
+ return {
413
+ address,
414
+ value: materialized,
415
+ path: objectPath,
416
+ bytes: bytes.byteLength,
417
+ created: true
418
+ };
419
+ } catch (error) {
420
+ await handle?.close();
421
+ handle = void 0;
422
+ await fs.unlink(tempPath).catch(() => void 0);
423
+ if (error.code !== "EEXIST") throw error;
424
+ const existing = await fs.readFile(objectPath);
425
+ if (!existing.equals(bytes)) throw new ContentObjectCollisionError(address, objectPath);
426
+ await this.syncExistingObject(objectPath);
427
+ return {
428
+ address,
429
+ value: materialized,
430
+ path: objectPath,
431
+ bytes: bytes.byteLength,
432
+ created: false
433
+ };
434
+ } finally {
435
+ await handle?.close();
436
+ }
437
+ }
438
+ async get(address) {
439
+ const objectPath = this.objectPath(address);
440
+ const bytes = await fs.readFile(objectPath);
441
+ const digestAddress = addressBytes(bytes);
442
+ if (digestAddress !== address) throw new ContentObjectCollisionError(address, objectPath);
443
+ const value = JSON.parse(bytes.toString("utf8"));
444
+ if (!canonicalJsonBytes(value).equals(bytes)) {
445
+ throw new ContentObjectCollisionError(address, objectPath);
446
+ }
447
+ return deepFreeze(value);
448
+ }
449
+ };
450
+
451
+ // src/market/provenance/dataset-registry.ts
452
+ import * as path2 from "path";
453
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
454
+ function deepFreezeDatasetRegistry(registry) {
455
+ for (const definition of Object.values(registry)) {
456
+ Object.freeze(definition.partitionKeys);
457
+ Object.freeze(definition.provenance);
458
+ Object.freeze(definition);
459
+ }
460
+ return Object.freeze(registry);
461
+ }
462
+ var MARKET_DATASETS = deepFreezeDatasetRegistry({
463
+ spot: {
464
+ subdir: "spot",
465
+ partitionKeys: ["ticker", "date"],
466
+ filename: "data.parquet",
467
+ schemaRevision: 1,
468
+ provenance: { kind: "bounded-session", sessionKey: "date" }
469
+ },
470
+ enriched: {
471
+ subdir: "enriched",
472
+ partitionKeys: ["ticker", "date"],
473
+ filename: "data.parquet",
474
+ schemaRevision: 1,
475
+ provenance: { kind: "bounded-session", sessionKey: "date" }
476
+ },
477
+ enriched_context: {
478
+ subdir: "enriched/context",
479
+ partitionKeys: ["date"],
480
+ filename: "data.parquet",
481
+ schemaRevision: 1,
482
+ provenance: { kind: "bounded-session", sessionKey: "date" }
483
+ },
484
+ option_chain: {
485
+ subdir: "option_chain",
486
+ partitionKeys: ["underlying", "date"],
487
+ filename: "data.parquet",
488
+ schemaRevision: 1,
489
+ provenance: { kind: "bounded-session", sessionKey: "date" }
490
+ },
491
+ option_quote_minutes: {
492
+ subdir: "option_quote_minutes",
493
+ partitionKeys: ["underlying", "date"],
494
+ filename: "data.parquet",
495
+ schemaRevision: 1,
496
+ provenance: { kind: "bounded-session", sessionKey: "date" }
497
+ },
498
+ option_oi_daily: {
499
+ subdir: "option_oi_daily",
500
+ partitionKeys: ["underlying", "date"],
501
+ filename: "data.parquet",
502
+ schemaRevision: 1,
503
+ provenance: { kind: "bounded-session", sessionKey: "date" }
504
+ }
505
+ });
506
+ function isRealMarketSessionDate(value) {
507
+ if (!ISO_DATE_RE.test(value)) return false;
508
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
509
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
510
+ }
511
+ function marketDatasetDefinition(dataset) {
512
+ return Object.hasOwn(MARKET_DATASETS, dataset) ? MARKET_DATASETS[dataset] : void 0;
513
+ }
514
+ function canonicalPartitionDataset(dataset) {
515
+ const definition = marketDatasetDefinition(dataset);
516
+ return definition?.provenance.kind === "bounded-session" ? definition : void 0;
517
+ }
518
+ function validatePartitionIdentity(identity) {
519
+ const definition = canonicalPartitionDataset(identity.dataset);
520
+ if (!definition) {
521
+ throw new TypeError(`Unregistered provenance dataset: ${JSON.stringify(identity.dataset)}`);
522
+ }
523
+ const expectedKeys = [...definition.partitionKeys].sort();
524
+ const observedKeys = Object.keys(identity.partition).sort();
525
+ if (observedKeys.length !== expectedKeys.length || observedKeys.some((key, index) => key !== expectedKeys[index])) {
526
+ throw new TypeError(
527
+ `Invalid provenance partition keys: ${JSON.stringify({ dataset: identity.dataset, observedKeys, expectedKeys })}`
528
+ );
529
+ }
530
+ for (const [key, value] of Object.entries(identity.partition)) {
531
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || !/^[A-Za-z0-9._-]+$/.test(value)) {
532
+ throw new TypeError(`Invalid provenance partition: ${JSON.stringify({ key, value })}`);
533
+ }
534
+ }
535
+ const session = identity.partition[definition.provenance.sessionKey];
536
+ if (!isRealMarketSessionDate(session)) {
537
+ throw new TypeError(`Invalid provenance partition date: ${JSON.stringify(session)}`);
538
+ }
539
+ }
540
+ function canonicalPartitionRelativePath(identity) {
541
+ validatePartitionIdentity(identity);
542
+ const definition = canonicalPartitionDataset(identity.dataset);
543
+ return path2.posix.join(
544
+ definition.subdir,
545
+ ...definition.partitionKeys.map((key) => `${key}=${identity.partition[key]}`),
546
+ definition.filename
547
+ );
548
+ }
549
+
550
+ // src/market/provenance/partition-commit-store.ts
551
+ var PARTITION_COMMIT_RECEIPT_KIND = "tradeblocks.market-data.partition-commit";
552
+ var PARTITION_COMMIT_RECEIPT_VERSION = 1;
553
+ var PARTITION_COMMIT_EVENT_KIND = "tradeblocks.market-data.partition-commit-event";
554
+ var PARTITION_COMMIT_EVENT_VERSION = 1;
555
+ var PARTITION_HEAD_KIND = "tradeblocks.market-data.partition-head";
556
+ var PARTITION_HEAD_VERSION = 1;
557
+ var INTERNAL_HISTORICAL_PARTITION_ADOPTION = /* @__PURE__ */ Symbol(
558
+ "tradeblocks.internal.historical-partition-adoption"
559
+ );
560
+ var partitionCommitTestFaults = /* @__PURE__ */ new WeakMap();
561
+ function setPartitionCommitTestFault(store, fault) {
562
+ if (fault) partitionCommitTestFaults.set(store, fault);
563
+ else partitionCommitTestFaults.delete(store);
564
+ }
565
+ function attachPartitionLockCleanupError(primary, cleanup) {
566
+ if (primary instanceof Error) {
567
+ try {
568
+ Object.defineProperty(primary, "cleanupError", {
569
+ value: cleanup,
570
+ enumerable: false,
571
+ configurable: true
572
+ });
573
+ return primary;
574
+ } catch {
575
+ }
576
+ }
577
+ return new AggregateError(
578
+ [primary, cleanup],
579
+ "Partition operation failed and its lock claim could not be released",
580
+ { cause: primary }
581
+ );
582
+ }
583
+ var PartitionFileIntegrityError = class extends Error {
584
+ filePath;
585
+ constructor(filePath, reason, cause) {
586
+ super(`Invalid partition file (${reason}): ${filePath}`, {
587
+ ...cause === void 0 ? {} : { cause }
588
+ });
589
+ this.name = "PartitionFileIntegrityError";
590
+ this.filePath = filePath;
591
+ }
592
+ };
593
+ var TOKEN_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
594
+ var CURRENT_HOSTNAME = hostname();
595
+ var CURRENT_BOOT_ID = (() => {
596
+ try {
597
+ const value = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
598
+ return value.length > 0 ? value : "unavailable";
599
+ } catch {
600
+ return "unavailable";
601
+ }
602
+ })();
603
+ function validateIdentity(identity) {
604
+ validatePartitionIdentity(identity);
605
+ }
606
+ function canonicalRelativePath(identity) {
607
+ return canonicalPartitionRelativePath(identity);
608
+ }
609
+ function validateRelativePath(relativePath) {
610
+ if (relativePath.length === 0 || relativePath.includes("\\") || path3.posix.isAbsolute(relativePath) || relativePath.split("/").some((part) => part === ".." || part === "." || part === "")) {
611
+ throw new TypeError(`Invalid provenance relative path: ${JSON.stringify(relativePath)}`);
612
+ }
613
+ }
614
+ function validateRegistryPath(identity, relativePath) {
615
+ validateRelativePath(relativePath);
616
+ const expected = canonicalRelativePath(identity);
617
+ if (relativePath !== expected) {
618
+ throw new TypeError(
619
+ `Provenance path does not match the registered partition: ${JSON.stringify({ expected, observed: relativePath })}`
620
+ );
621
+ }
622
+ }
623
+ function validateFingerprint(file) {
624
+ parseSha256Address(file.address);
625
+ if (!Number.isSafeInteger(file.bytes) || file.bytes < 0) {
626
+ throw new TypeError(`Invalid provenance byte count: ${file.bytes}`);
627
+ }
628
+ if (!Number.isSafeInteger(file.rows) || file.rows < 0) {
629
+ throw new TypeError(`Invalid provenance row count: ${file.rows}`);
630
+ }
631
+ }
632
+ function validateCoverage(coverage, rows) {
633
+ if (coverage.kind === "empty") {
634
+ if (rows !== 0) throw new TypeError("Non-empty partition cannot have empty logical coverage");
635
+ return;
636
+ }
637
+ if (!isRealMarketSessionDate(coverage.from) || !isRealMarketSessionDate(coverage.through)) {
638
+ throw new TypeError(`Invalid logical date coverage: ${JSON.stringify(coverage)}`);
639
+ }
640
+ if (coverage.from > coverage.through) {
641
+ throw new TypeError(`Logical coverage starts after it ends: ${JSON.stringify(coverage)}`);
642
+ }
643
+ if (rows === 0) throw new TypeError("Empty partition cannot have non-empty logical coverage");
644
+ }
645
+ function validateQuality(quality, file) {
646
+ for (const [name, count] of Object.entries(quality)) {
647
+ if (!Number.isSafeInteger(count) || count < 0) {
648
+ throw new TypeError(`Invalid provenance ${name}: ${count}`);
649
+ }
650
+ }
651
+ if (quality.writtenRows !== file.rows) {
652
+ throw new TypeError(
653
+ `Provenance writtenRows ${quality.writtenRows} does not match file rows ${file.rows}`
654
+ );
655
+ }
656
+ if (quality.inputRows !== quality.writtenRows + quality.droppedRows) {
657
+ throw new TypeError("Provenance inputRows must equal writtenRows + droppedRows");
658
+ }
659
+ }
660
+ function validateInput(input) {
661
+ validateIdentity(input);
662
+ if (!Number.isSafeInteger(input.schemaRevision) || input.schemaRevision < 1) {
663
+ throw new TypeError(`Invalid schema revision: ${input.schemaRevision}`);
664
+ }
665
+ validateRegistryPath(input, input.relativePath);
666
+ validateFingerprint(input.file);
667
+ validateCoverage(input.coverage, input.file.rows);
668
+ validateQuality(input.quality, input.file);
669
+ const date = input.partition[canonicalPartitionDataset(input.dataset).provenance.sessionKey];
670
+ if (input.coverage.kind === "date-range" && (input.coverage.from !== date || input.coverage.through !== date)) {
671
+ throw new TypeError(
672
+ `Partition logical coverage must equal its registered date: ${JSON.stringify({ date, coverage: input.coverage })}`
673
+ );
674
+ }
675
+ }
676
+ var CANONICAL_PARQUET_SCHEMAS = Object.freeze({
677
+ spot: [
678
+ ["ticker", "VARCHAR"],
679
+ ["date", "VARCHAR"],
680
+ ["time", "VARCHAR"],
681
+ ["open", "DOUBLE"],
682
+ ["high", "DOUBLE"],
683
+ ["low", "DOUBLE"],
684
+ ["close", "DOUBLE"],
685
+ ["bid", "DOUBLE"],
686
+ ["ask", "DOUBLE"]
687
+ ],
688
+ enriched: [
689
+ ["ticker", "VARCHAR"],
690
+ ["date", "VARCHAR"],
691
+ ["Prior_Close", "DOUBLE"],
692
+ ["Gap_Pct", "DOUBLE"],
693
+ ["ATR_Pct", "DOUBLE"],
694
+ ["RSI_14", "DOUBLE"],
695
+ ["Price_vs_EMA21_Pct", "DOUBLE"],
696
+ ["Price_vs_SMA50_Pct", "DOUBLE"],
697
+ ["Realized_Vol_5D", "DOUBLE"],
698
+ ["Realized_Vol_20D", "DOUBLE"],
699
+ ["Return_5D", "DOUBLE"],
700
+ ["Return_20D", "DOUBLE"],
701
+ ["Intraday_Range_Pct", "DOUBLE"],
702
+ ["Intraday_Return_Pct", "DOUBLE"],
703
+ ["Close_Position_In_Range", "DOUBLE"],
704
+ ["Gap_Filled", "INTEGER"],
705
+ ["Consecutive_Days", "INTEGER"],
706
+ ["Prev_Return_Pct", "DOUBLE"],
707
+ ["Prior_Range_vs_ATR", "DOUBLE"],
708
+ ["High_Time", "DOUBLE"],
709
+ ["Low_Time", "DOUBLE"],
710
+ ["High_Before_Low", "INTEGER"],
711
+ ["Reversal_Type", "INTEGER"],
712
+ ["Opening_Drive_Strength", "DOUBLE"],
713
+ ["Intraday_Realized_Vol", "DOUBLE"],
714
+ ["Day_of_Week", "INTEGER"],
715
+ ["Month", "INTEGER"],
716
+ ["Is_Opex", "INTEGER"],
717
+ ["ivr", "DOUBLE"],
718
+ ["ivp", "DOUBLE"]
719
+ ],
720
+ enriched_context: [
721
+ ["date", "VARCHAR"],
722
+ ["Vol_Regime", "INTEGER"],
723
+ ["Term_Structure_State", "INTEGER"],
724
+ ["Trend_Direction", "VARCHAR"],
725
+ ["VIX_Spike_Pct", "DOUBLE"],
726
+ ["VIX_Gap_Pct", "DOUBLE"]
727
+ ],
728
+ option_chain: [
729
+ ["underlying", "VARCHAR"],
730
+ ["date", "VARCHAR"],
731
+ ["ticker", "VARCHAR"],
732
+ ["contract_type", "VARCHAR"],
733
+ ["strike", "DOUBLE"],
734
+ ["expiration", "VARCHAR"],
735
+ ["dte", "INTEGER"],
736
+ ["exercise_style", "VARCHAR"]
737
+ ],
738
+ option_quote_minutes: [
739
+ ["underlying", "VARCHAR"],
740
+ ["date", "VARCHAR"],
741
+ ["ticker", "VARCHAR"],
742
+ ["time", "VARCHAR"],
743
+ ["bid", "DOUBLE"],
744
+ ["ask", "DOUBLE"],
745
+ ["mid", "DOUBLE"],
746
+ ["last_updated_ns", "BIGINT"],
747
+ ["source", "VARCHAR"],
748
+ ["delta", "FLOAT"],
749
+ ["gamma", "FLOAT"],
750
+ ["theta", "FLOAT"],
751
+ ["vega", "FLOAT"],
752
+ ["iv", "FLOAT"],
753
+ ["greeks_source", "VARCHAR"],
754
+ ["greeks_revision", "INTEGER"],
755
+ ["rate_type", "VARCHAR"],
756
+ ["rate_value", "DOUBLE"],
757
+ ["gamma_source", "VARCHAR"]
758
+ ],
759
+ option_oi_daily: [
760
+ ["underlying", "VARCHAR"],
761
+ ["date", "VARCHAR"],
762
+ ["ticker", "VARCHAR"],
763
+ ["expiration", "VARCHAR"],
764
+ ["strike", "DOUBLE"],
765
+ ["right", "VARCHAR"],
766
+ ["open_interest", "BIGINT"],
767
+ ["source", "VARCHAR"]
768
+ ]
769
+ });
770
+ function escapeSqlLiteral(value) {
771
+ return value.replaceAll("'", "''");
772
+ }
773
+ function canonicalDuckDbType(value) {
774
+ const normalized = String(value).toUpperCase();
775
+ return normalized === "REAL" ? "FLOAT" : normalized;
776
+ }
777
+ async function inspectCanonicalParquet(conn, filePath, identity) {
778
+ const expectedSchema = CANONICAL_PARQUET_SCHEMAS[identity.dataset];
779
+ if (!expectedSchema) throw new TypeError(`No canonical Parquet schema for ${identity.dataset}`);
780
+ const source = `read_parquet('${escapeSqlLiteral(filePath)}', hive_partitioning=false)`;
781
+ const described = await conn.runAndReadAll(`DESCRIBE SELECT * FROM ${source}`);
782
+ const schema = described.getRows().map((row2) => [String(row2[0]), canonicalDuckDbType(row2[1])]);
783
+ if (canonicalJsonBytes(schema).compare(canonicalJsonBytes(expectedSchema)) !== 0) {
784
+ throw new Error(
785
+ `Existing partition Parquet schema does not match revision 1: ${JSON.stringify({ observed: schema, expected: expectedSchema })}`
786
+ );
787
+ }
788
+ const definition = canonicalPartitionDataset(identity.dataset);
789
+ const sessionColumn = definition.provenance.sessionKey;
790
+ const predicates = Object.entries(identity.partition).map(
791
+ ([key, value]) => `COUNT(*) FILTER (WHERE "${key}" IS NULL OR CAST("${key}" AS VARCHAR) <> '${escapeSqlLiteral(value)}')::BIGINT`
792
+ ).join(", ");
793
+ const inspected = await conn.runAndReadAll(
794
+ `SELECT COUNT(*)::BIGINT,
795
+ COUNT("${sessionColumn}")::BIGINT,
796
+ MIN(CAST("${sessionColumn}" AS VARCHAR)),
797
+ MAX(CAST("${sessionColumn}" AS VARCHAR)),
798
+ ${predicates}
799
+ FROM ${source}`
800
+ );
801
+ const row = inspected.getRows()[0];
802
+ const rows = Number(row[0]);
803
+ const coveredRows = Number(row[1]);
804
+ if (!Number.isSafeInteger(rows) || rows <= 0 || coveredRows !== rows) {
805
+ throw new Error("Existing canonical Parquet must contain non-null rows for its session");
806
+ }
807
+ for (const mismatches of row.slice(4)) {
808
+ if (Number(mismatches) !== 0) {
809
+ throw new Error("Existing canonical Parquet rows disagree with the registered partition");
810
+ }
811
+ }
812
+ const from = String(row[2]);
813
+ const through = String(row[3]);
814
+ const expectedSession = identity.partition[sessionColumn];
815
+ if (from !== expectedSession || through !== expectedSession) {
816
+ throw new Error("Existing canonical Parquet coverage disagrees with its partition session");
817
+ }
818
+ return { rows, coverage: { kind: "date-range", from, through } };
819
+ }
820
+ function validateReceipt(receipt, identity) {
821
+ if (receipt.kind !== PARTITION_COMMIT_RECEIPT_KIND || receipt.version !== PARTITION_COMMIT_RECEIPT_VERSION || receipt.dataset !== identity.dataset || addressCanonicalJson(receipt.partition) !== addressCanonicalJson(identity.partition)) {
822
+ throw new Error("Partition receipt does not match its identity");
823
+ }
824
+ validateInput(receipt);
825
+ if (receipt.classification !== "append" && receipt.classification !== "repair") {
826
+ throw new Error(`Invalid partition receipt classification: ${String(receipt.classification)}`);
827
+ }
828
+ if (receipt.classification === "append" && receipt.parent !== void 0) {
829
+ throw new Error("Append receipt must not have a parent");
830
+ }
831
+ if (receipt.classification === "repair" && receipt.parent === void 0) {
832
+ throw new Error("Repair receipt must have a parent");
833
+ }
834
+ if (receipt.parent !== void 0) parseCanonicalJsonAddress(receipt.parent);
835
+ }
836
+ function identityAddress(identity) {
837
+ return addressCanonicalJson({
838
+ kind: "tradeblocks.market-data.partition-identity",
839
+ version: 1,
840
+ dataset: identity.dataset,
841
+ partition: identity.partition
842
+ });
843
+ }
844
+ function sameInode(left, right) {
845
+ return left.dev === right.dev && left.ino === right.ino;
846
+ }
847
+ async function openRegularFileNoFollow(filePath) {
848
+ let handle;
849
+ try {
850
+ handle = await fs2.open(
851
+ filePath,
852
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
853
+ );
854
+ } catch (error) {
855
+ if (error.code === "ELOOP") {
856
+ throw new PartitionFileIntegrityError(filePath, "symbolic links are not allowed", error);
857
+ }
858
+ throw error;
859
+ }
860
+ try {
861
+ const stat2 = await handle.stat();
862
+ if (!stat2.isFile()) {
863
+ throw new PartitionFileIntegrityError(filePath, "expected a regular file");
864
+ }
865
+ if (stat2.nlink !== 1) {
866
+ throw new PartitionFileIntegrityError(filePath, "expected an unshared regular file inode");
867
+ }
868
+ return { handle, stat: stat2 };
869
+ } catch (error) {
870
+ await handle.close();
871
+ throw error;
872
+ }
873
+ }
874
+ async function exactOpenFileAddress(handle) {
875
+ const hash = createHash2("sha256");
876
+ let bytes = 0;
877
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
878
+ while (true) {
879
+ const read = await handle.read(buffer, 0, buffer.byteLength, bytes);
880
+ if (read.bytesRead === 0) break;
881
+ hash.update(buffer.subarray(0, read.bytesRead));
882
+ bytes += read.bytesRead;
883
+ }
884
+ return { address: `sha256:${hash.digest("hex")}`, bytes };
885
+ }
886
+ async function requireNamedRegularInode(filePath, expected) {
887
+ let named;
888
+ try {
889
+ named = await fs2.lstat(filePath);
890
+ } catch (error) {
891
+ throw new PartitionFileIntegrityError(
892
+ filePath,
893
+ "verified inode is no longer named here",
894
+ error
895
+ );
896
+ }
897
+ if (!named.isFile()) {
898
+ throw new PartitionFileIntegrityError(filePath, "canonical entry is not a regular file");
899
+ }
900
+ if (named.nlink !== 1 || expected.nlink !== 1) {
901
+ throw new PartitionFileIntegrityError(filePath, "canonical inode has multiple hard links");
902
+ }
903
+ if (!sameInode(expected, named)) {
904
+ throw new PartitionFileIntegrityError(filePath, "canonical entry changed during verification");
905
+ }
906
+ }
907
+ async function exactFileAddress(filePath) {
908
+ const opened = await openRegularFileNoFollow(filePath);
909
+ try {
910
+ const fingerprint = await exactOpenFileAddress(opened.handle);
911
+ const after = await opened.handle.stat();
912
+ if (!after.isFile() || after.nlink !== 1 || !sameInode(opened.stat, after)) {
913
+ throw new PartitionFileIntegrityError(filePath, "open inode changed during verification");
914
+ }
915
+ await requireNamedRegularInode(filePath, opened.stat);
916
+ return fingerprint;
917
+ } finally {
918
+ await opened.handle.close();
919
+ }
920
+ }
921
+ async function snapshotOpenFile(source, snapshotPath) {
922
+ const snapshot = await fs2.open(snapshotPath, "wx", 256);
923
+ const hash = createHash2("sha256");
924
+ let bytes = 0;
925
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
926
+ try {
927
+ while (true) {
928
+ const read = await source.read(buffer, 0, buffer.byteLength, bytes);
929
+ if (read.bytesRead === 0) break;
930
+ hash.update(buffer.subarray(0, read.bytesRead));
931
+ let written = 0;
932
+ while (written < read.bytesRead) {
933
+ const result = await snapshot.write(
934
+ buffer,
935
+ written,
936
+ read.bytesRead - written,
937
+ bytes + written
938
+ );
939
+ if (result.bytesWritten === 0) throw new Error("Unable to write inspection snapshot");
940
+ written += result.bytesWritten;
941
+ }
942
+ bytes += read.bytesRead;
943
+ }
944
+ await snapshot.sync();
945
+ } finally {
946
+ await snapshot.close();
947
+ }
948
+ return { address: `sha256:${hash.digest("hex")}`, bytes };
949
+ }
950
+ function sameCommitContent(previous, input) {
951
+ return previous.schemaRevision === input.schemaRevision && previous.relativePath === input.relativePath && addressCanonicalJson(previous.coverage) === addressCanonicalJson(input.coverage) && addressCanonicalJson(previous.quality) === addressCanonicalJson(input.quality) && previous.file.address === input.file.address && previous.file.bytes === input.file.bytes && previous.file.rows === input.file.rows;
952
+ }
953
+ function captureIdentity(identity) {
954
+ const captured = JSON.parse(
955
+ canonicalJsonBytes({ dataset: identity.dataset, partition: identity.partition }).toString(
956
+ "utf8"
957
+ )
958
+ );
959
+ validateIdentity(captured);
960
+ return Object.freeze({ ...captured, partition: Object.freeze(captured.partition) });
961
+ }
962
+ function captureInput(input) {
963
+ const captured = JSON.parse(
964
+ canonicalJsonBytes({
965
+ dataset: input.dataset,
966
+ partition: input.partition,
967
+ schemaRevision: input.schemaRevision,
968
+ relativePath: input.relativePath,
969
+ coverage: input.coverage,
970
+ quality: input.quality,
971
+ file: input.file
972
+ }).toString("utf8")
973
+ );
974
+ validateInput(captured);
975
+ Object.freeze(captured.partition);
976
+ Object.freeze(captured.coverage);
977
+ Object.freeze(captured.quality);
978
+ Object.freeze(captured.file);
979
+ return Object.freeze(captured);
980
+ }
981
+ var FilePartitionCommitStore = class {
982
+ marketRootDir;
983
+ objects;
984
+ provenanceRootDir;
985
+ staleLockMs;
986
+ lockWaitMs;
987
+ constructor(marketRootDir, options = {}) {
988
+ this.marketRootDir = marketRootDir;
989
+ this.provenanceRootDir = path3.join(marketRootDir, ".provenance");
990
+ this.objects = new ContentObjectStore(this.provenanceRootDir);
991
+ this.staleLockMs = options.staleLockMs ?? 3e4;
992
+ this.lockWaitMs = options.lockWaitMs ?? 5e3;
993
+ }
994
+ identityDigest(identity) {
995
+ return parseCanonicalJsonAddress(identityAddress(identity));
996
+ }
997
+ headPath(identity) {
998
+ const digest = this.identityDigest(identity);
999
+ return path3.join(this.provenanceRootDir, "heads", digest.slice(0, 2), `${digest}.json`);
1000
+ }
1001
+ eventIndexDir(identity) {
1002
+ const digest = this.identityDigest(identity);
1003
+ return path3.join(this.provenanceRootDir, "events", digest.slice(0, 2), digest);
1004
+ }
1005
+ lockRoot(identity) {
1006
+ const digest = this.identityDigest(identity);
1007
+ return path3.join(this.provenanceRootDir, "locks", digest.slice(0, 2), digest);
1008
+ }
1009
+ targetPath(relativePath) {
1010
+ validateRelativePath(relativePath);
1011
+ return path3.join(this.marketRootDir, ...relativePath.split("/"));
1012
+ }
1013
+ async validateMarketRoot() {
1014
+ const root = path3.resolve(this.marketRootDir);
1015
+ const rootStat = await fs2.lstat(root);
1016
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
1017
+ throw new PartitionFileIntegrityError(root, "market root must be a real directory");
1018
+ }
1019
+ return fs2.realpath(root);
1020
+ }
1021
+ async validatedTargetPath(relativePath, expectedRealRoot) {
1022
+ validateRelativePath(relativePath);
1023
+ const root = path3.resolve(this.marketRootDir);
1024
+ const realRoot = await this.validateMarketRoot();
1025
+ if (realRoot !== expectedRealRoot) {
1026
+ throw new PartitionFileIntegrityError(root, "market root changed during publication");
1027
+ }
1028
+ const components = relativePath.split("/");
1029
+ let current = root;
1030
+ for (const component of components.slice(0, -1)) {
1031
+ current = path3.join(current, component);
1032
+ const stat2 = await fs2.lstat(current);
1033
+ if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
1034
+ throw new PartitionFileIntegrityError(
1035
+ current,
1036
+ "target path components must be real directories"
1037
+ );
1038
+ }
1039
+ }
1040
+ const realParent = await fs2.realpath(current);
1041
+ const containment = path3.relative(realRoot, realParent);
1042
+ if (containment === ".." || containment.startsWith(`..${path3.sep}`) || path3.isAbsolute(containment)) {
1043
+ throw new PartitionFileIntegrityError(
1044
+ current,
1045
+ "target parent escapes the configured market root"
1046
+ );
1047
+ }
1048
+ return path3.join(current, components.at(-1));
1049
+ }
1050
+ async quarantineClaim(claimedPath) {
1051
+ try {
1052
+ await fs2.lstat(claimedPath);
1053
+ } catch (error) {
1054
+ if (error.code === "ENOENT") return;
1055
+ throw error;
1056
+ }
1057
+ const quarantineDir = path3.join(this.provenanceRootDir, "rejected-prepared");
1058
+ await this.ensureDurableDirectory(quarantineDir);
1059
+ const quarantinePath = path3.join(quarantineDir, randomUUID2());
1060
+ await fs2.rename(claimedPath, quarantinePath);
1061
+ await this.syncDirectory(path3.dirname(claimedPath));
1062
+ await this.syncDirectory(quarantineDir);
1063
+ }
1064
+ async restoreOrQuarantineClaim(claimedPath, preparedPath) {
1065
+ let restored = false;
1066
+ let claimedRemoved = false;
1067
+ try {
1068
+ await fs2.link(claimedPath, preparedPath);
1069
+ restored = true;
1070
+ await fs2.unlink(claimedPath);
1071
+ claimedRemoved = true;
1072
+ await this.syncDirectory(path3.dirname(preparedPath));
1073
+ return;
1074
+ } catch (error) {
1075
+ if (claimedRemoved) throw error;
1076
+ if (restored) await fs2.unlink(preparedPath).catch(() => void 0);
1077
+ await this.quarantineClaim(claimedPath);
1078
+ }
1079
+ }
1080
+ async syncDirectory(directory) {
1081
+ const handle = await fs2.open(directory, "r");
1082
+ try {
1083
+ await handle.sync();
1084
+ } finally {
1085
+ await handle.close();
1086
+ }
1087
+ }
1088
+ async ensureDurableDirectory(directory) {
1089
+ const parent = path3.dirname(directory);
1090
+ if (parent !== directory) await this.ensureDurableDirectory(parent);
1091
+ try {
1092
+ const handle = await fs2.open(directory, "r");
1093
+ try {
1094
+ await handle.sync();
1095
+ } finally {
1096
+ await handle.close();
1097
+ }
1098
+ if (parent !== directory) await this.syncDirectory(parent);
1099
+ return;
1100
+ } catch (error) {
1101
+ if (error.code !== "ENOENT") throw error;
1102
+ }
1103
+ if (parent === directory) throw new Error(`Cannot create provenance directory ${directory}`);
1104
+ let created = false;
1105
+ try {
1106
+ await fs2.mkdir(directory);
1107
+ created = true;
1108
+ } catch (error) {
1109
+ if (error.code !== "EEXIST") throw error;
1110
+ }
1111
+ if (created) {
1112
+ await this.syncDirectory(directory);
1113
+ await this.syncDirectory(parent);
1114
+ }
1115
+ }
1116
+ isProcessAlive(pid) {
1117
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
1118
+ try {
1119
+ process.kill(pid, 0);
1120
+ return true;
1121
+ } catch (error) {
1122
+ return error.code === "EPERM";
1123
+ }
1124
+ }
1125
+ async readCanonicalFile(filePath) {
1126
+ try {
1127
+ const bytes = await fs2.readFile(filePath);
1128
+ const value = JSON.parse(bytes.toString("utf8"));
1129
+ if (!canonicalJsonBytes(value).equals(bytes)) return null;
1130
+ return value;
1131
+ } catch {
1132
+ return null;
1133
+ }
1134
+ }
1135
+ validOwner(owner, token) {
1136
+ return Boolean(
1137
+ owner && owner.kind === "tradeblocks.market-data.partition-lock-owner" && owner.version === 1 && owner.token === token && TOKEN_RE.test(owner.token) && Number.isSafeInteger(owner.pid) && owner.pid > 0 && typeof owner.hostname === "string" && owner.hostname.length > 0 && typeof owner.bootId === "string" && owner.bootId.length > 0 && Number.isSafeInteger(owner.createdAtMs) && owner.createdAtMs >= 0
1138
+ );
1139
+ }
1140
+ validTicket(ticket, token) {
1141
+ return Boolean(
1142
+ ticket && ticket.kind === "tradeblocks.market-data.partition-lock-ticket" && ticket.version === 1 && ticket.token === token && Number.isSafeInteger(ticket.number) && ticket.number >= 1
1143
+ );
1144
+ }
1145
+ ownerProvablyDead(owner) {
1146
+ if (owner.hostname !== CURRENT_HOSTNAME) return false;
1147
+ if (owner.bootId !== "unavailable" && CURRENT_BOOT_ID !== "unavailable" && owner.bootId !== CURRENT_BOOT_ID) {
1148
+ return true;
1149
+ }
1150
+ return !this.isProcessAlive(owner.pid);
1151
+ }
1152
+ async recoverClaim(claimsDir, token) {
1153
+ if (!TOKEN_RE.test(token)) return false;
1154
+ const claimPath = path3.join(claimsDir, token);
1155
+ let claimStat;
1156
+ try {
1157
+ claimStat = await fs2.stat(claimPath);
1158
+ } catch (error) {
1159
+ if (error.code === "ENOENT") return true;
1160
+ throw error;
1161
+ }
1162
+ const owner = await this.readCanonicalFile(
1163
+ path3.join(claimPath, "owner.json")
1164
+ );
1165
+ const validOwner = this.validOwner(owner, token);
1166
+ if (!validOwner) return false;
1167
+ const createdAtMs = owner.createdAtMs;
1168
+ if (Date.now() - createdAtMs < this.staleLockMs) return false;
1169
+ if (!this.ownerProvablyDead(owner)) return false;
1170
+ const quarantineDir = path3.join(path3.dirname(claimsDir), "quarantine");
1171
+ await this.ensureDurableDirectory(quarantineDir);
1172
+ const quarantinePath = path3.join(
1173
+ quarantineDir,
1174
+ `${token}-${claimStat.ino}-${Math.floor(claimStat.birthtimeMs)}`
1175
+ );
1176
+ try {
1177
+ await fs2.rename(claimPath, quarantinePath);
1178
+ await this.syncDirectory(claimsDir);
1179
+ await this.syncDirectory(quarantineDir);
1180
+ return true;
1181
+ } catch (error) {
1182
+ const code = error.code;
1183
+ if (code === "ENOENT" || code === "EEXIST" || code === "ENOTEMPTY") return false;
1184
+ throw error;
1185
+ }
1186
+ }
1187
+ async publishClaim(claimsDir, token) {
1188
+ const owner = {
1189
+ kind: "tradeblocks.market-data.partition-lock-owner",
1190
+ version: 1,
1191
+ token,
1192
+ pid: process.pid,
1193
+ hostname: CURRENT_HOSTNAME,
1194
+ bootId: CURRENT_BOOT_ID,
1195
+ createdAtMs: Date.now()
1196
+ };
1197
+ const publishingPath = path3.join(claimsDir, `.publishing-${token}`);
1198
+ const claimPath = path3.join(claimsDir, token);
1199
+ await fs2.mkdir(publishingPath);
1200
+ try {
1201
+ const ownerHandle = await fs2.open(path3.join(publishingPath, "owner.json"), "wx", 292);
1202
+ try {
1203
+ await ownerHandle.writeFile(canonicalJsonBytes(owner));
1204
+ await ownerHandle.sync();
1205
+ } finally {
1206
+ await ownerHandle.close();
1207
+ }
1208
+ await this.syncDirectory(publishingPath);
1209
+ await fs2.rename(publishingPath, claimPath);
1210
+ await this.syncDirectory(claimsDir);
1211
+ return claimPath;
1212
+ } catch (error) {
1213
+ await fs2.rm(publishingPath, { recursive: true, force: true });
1214
+ throw error;
1215
+ }
1216
+ }
1217
+ async assignTicket(claimsDir, claimPath, token) {
1218
+ let maximum = 0;
1219
+ for (const entry of await fs2.readdir(claimsDir, { withFileTypes: true })) {
1220
+ if (!entry.isDirectory() || !TOKEN_RE.test(entry.name)) continue;
1221
+ const ticket2 = await this.readCanonicalFile(
1222
+ path3.join(claimsDir, entry.name, "ticket.json")
1223
+ );
1224
+ if (this.validTicket(ticket2, entry.name)) maximum = Math.max(maximum, ticket2.number);
1225
+ }
1226
+ if (!Number.isSafeInteger(maximum + 1)) throw new Error("Partition lock ticket overflow");
1227
+ const ticket = {
1228
+ kind: "tradeblocks.market-data.partition-lock-ticket",
1229
+ version: 1,
1230
+ token,
1231
+ number: maximum + 1
1232
+ };
1233
+ const handle = await fs2.open(path3.join(claimPath, "ticket.json"), "wx", 292);
1234
+ try {
1235
+ await handle.writeFile(canonicalJsonBytes(ticket));
1236
+ await handle.sync();
1237
+ } finally {
1238
+ await handle.close();
1239
+ }
1240
+ await this.syncDirectory(claimPath);
1241
+ return ticket.number;
1242
+ }
1243
+ async releaseClaim(claimsDir, claimPath, token) {
1244
+ await partitionCommitTestFaults.get(this)?.("before-release-claim");
1245
+ const owner = await this.readCanonicalFile(
1246
+ path3.join(claimPath, "owner.json")
1247
+ );
1248
+ if (!this.validOwner(owner, token) || owner.pid !== process.pid) {
1249
+ throw new Error(
1250
+ `Refusing to release a partition claim owned by another writer: ${claimPath}`
1251
+ );
1252
+ }
1253
+ const releasedDir = path3.join(path3.dirname(claimsDir), "released");
1254
+ await this.ensureDurableDirectory(releasedDir);
1255
+ const releasedPath = path3.join(releasedDir, token);
1256
+ await fs2.rename(claimPath, releasedPath);
1257
+ await this.syncDirectory(claimsDir);
1258
+ await this.syncDirectory(releasedDir);
1259
+ await fs2.rm(releasedPath, { recursive: true });
1260
+ await this.syncDirectory(releasedDir);
1261
+ }
1262
+ async withPartitionLock(identity, operation) {
1263
+ validateIdentity(identity);
1264
+ const lockRoot = this.lockRoot(identity);
1265
+ const claimsDir = path3.join(lockRoot, "claims");
1266
+ await this.ensureDurableDirectory(claimsDir);
1267
+ const token = randomUUID2();
1268
+ const claimPath = await this.publishClaim(claimsDir, token);
1269
+ let ownTicket;
1270
+ try {
1271
+ ownTicket = await this.assignTicket(claimsDir, claimPath, token);
1272
+ } catch (error) {
1273
+ try {
1274
+ await this.releaseClaim(claimsDir, claimPath, token);
1275
+ } catch (cleanupError) {
1276
+ throw attachPartitionLockCleanupError(error, cleanupError);
1277
+ }
1278
+ throw error;
1279
+ }
1280
+ const deadline = Date.now() + this.lockWaitMs;
1281
+ let acquired = false;
1282
+ let operationFailed = false;
1283
+ let operationError;
1284
+ try {
1285
+ while (Date.now() <= deadline) {
1286
+ let blocked = false;
1287
+ const entries = await fs2.readdir(claimsDir, { withFileTypes: true });
1288
+ for (const entry of entries) {
1289
+ if (!entry.isDirectory() || entry.name === token) continue;
1290
+ if (!TOKEN_RE.test(entry.name)) {
1291
+ if (!entry.name.startsWith(".publishing-")) blocked = true;
1292
+ continue;
1293
+ }
1294
+ const otherPath = path3.join(claimsDir, entry.name);
1295
+ const owner = await this.readCanonicalFile(
1296
+ path3.join(otherPath, "owner.json")
1297
+ );
1298
+ if (!this.validOwner(owner, entry.name)) {
1299
+ blocked = true;
1300
+ continue;
1301
+ }
1302
+ const ticket = await this.readCanonicalFile(
1303
+ path3.join(otherPath, "ticket.json")
1304
+ );
1305
+ if (!this.validTicket(ticket, entry.name)) {
1306
+ try {
1307
+ await fs2.stat(path3.join(otherPath, "ticket.json"));
1308
+ blocked = true;
1309
+ } catch (error) {
1310
+ if (error.code !== "ENOENT") throw error;
1311
+ if (!await this.recoverClaim(claimsDir, entry.name)) blocked = true;
1312
+ }
1313
+ continue;
1314
+ }
1315
+ if (ticket.number < ownTicket || ticket.number === ownTicket && entry.name < token) {
1316
+ if (!await this.recoverClaim(claimsDir, entry.name)) blocked = true;
1317
+ }
1318
+ }
1319
+ if (!blocked) {
1320
+ acquired = true;
1321
+ break;
1322
+ }
1323
+ await new Promise((resolve3) => setTimeout(resolve3, 10));
1324
+ }
1325
+ if (!acquired) throw new Error(`Timed out acquiring partition lock: ${lockRoot}`);
1326
+ return await operation();
1327
+ } catch (error) {
1328
+ operationFailed = true;
1329
+ operationError = error;
1330
+ throw error;
1331
+ } finally {
1332
+ try {
1333
+ await this.releaseClaim(claimsDir, claimPath, token);
1334
+ } catch (cleanupError) {
1335
+ if (operationFailed) {
1336
+ throw attachPartitionLockCleanupError(operationError, cleanupError);
1337
+ }
1338
+ throw cleanupError;
1339
+ }
1340
+ }
1341
+ }
1342
+ async readIndexedEvent(identity, eventPath, address) {
1343
+ const bytes = await fs2.readFile(eventPath);
1344
+ if (addressBytes(bytes) !== address) {
1345
+ throw new ContentObjectCollisionError(address, eventPath);
1346
+ }
1347
+ const event = JSON.parse(bytes.toString("utf8"));
1348
+ if (!canonicalJsonBytes(event).equals(bytes)) {
1349
+ throw new ContentObjectCollisionError(address, eventPath);
1350
+ }
1351
+ if (event.kind !== PARTITION_COMMIT_EVENT_KIND || event.version !== PARTITION_COMMIT_EVENT_VERSION || event.dataset !== identity.dataset || addressCanonicalJson(event.partition) !== addressCanonicalJson(identity.partition)) {
1352
+ throw new Error(`Invalid partition commit event at ${eventPath}`);
1353
+ }
1354
+ parseCanonicalJsonAddress(event.receipt);
1355
+ if (event.previous !== void 0) parseCanonicalJsonAddress(event.previous);
1356
+ const commit = await this.readCommit(event.receipt);
1357
+ validateReceipt(commit.receipt, identity);
1358
+ return { event, commit };
1359
+ }
1360
+ async readAuthorityTip(identity) {
1361
+ const indexDir = this.eventIndexDir(identity);
1362
+ let entries;
1363
+ try {
1364
+ entries = await fs2.readdir(indexDir);
1365
+ } catch (error) {
1366
+ if (error.code === "ENOENT") return null;
1367
+ throw error;
1368
+ }
1369
+ const events = /* @__PURE__ */ new Map();
1370
+ for (const entry of entries) {
1371
+ const match = /^([0-9a-f]{64})\.json$/.exec(entry);
1372
+ if (!match) throw new Error(`Unexpected partition event index entry: ${entry}`);
1373
+ const address = `sha256:${match[1]}`;
1374
+ events.set(
1375
+ address,
1376
+ await this.readIndexedEvent(identity, path3.join(indexDir, entry), address)
1377
+ );
1378
+ }
1379
+ if (events.size === 0) return null;
1380
+ const roots = [...events.entries()].filter(([, value]) => value.event.previous === void 0);
1381
+ if (roots.length !== 1) throw new Error("Partition event authority must have exactly one root");
1382
+ let [currentAddress, current] = roots[0];
1383
+ if (current.commit.receipt.classification !== "append") {
1384
+ throw new Error("Partition event root must reference an append receipt");
1385
+ }
1386
+ const visited = /* @__PURE__ */ new Set();
1387
+ while (true) {
1388
+ if (visited.has(currentAddress))
1389
+ throw new Error("Partition event authority contains a cycle");
1390
+ visited.add(currentAddress);
1391
+ const children = [...events.entries()].filter(
1392
+ ([, value]) => value.event.previous === currentAddress
1393
+ );
1394
+ if (children.length === 0) break;
1395
+ if (children.length !== 1)
1396
+ throw new Error("Partition event authority contains ambiguous tips");
1397
+ const [childAddress, child] = children[0];
1398
+ if (child.commit.receipt.classification !== "repair" || child.commit.receipt.parent !== current.commit.address) {
1399
+ throw new Error("Partition event authority receipt ancestry is inconsistent");
1400
+ }
1401
+ currentAddress = childAddress;
1402
+ current = child;
1403
+ }
1404
+ if (visited.size !== events.size) {
1405
+ throw new Error("Partition event authority contains a disconnected or missing-parent event");
1406
+ }
1407
+ return { eventAddress: currentAddress, commit: current.commit };
1408
+ }
1409
+ async readProjectedHead(identity) {
1410
+ const headPath = this.headPath(identity);
1411
+ let bytes;
1412
+ try {
1413
+ bytes = await fs2.readFile(headPath);
1414
+ } catch (error) {
1415
+ if (error.code === "ENOENT") return null;
1416
+ throw error;
1417
+ }
1418
+ try {
1419
+ const head = JSON.parse(bytes.toString("utf8"));
1420
+ if (!canonicalJsonBytes(head).equals(bytes)) return null;
1421
+ if (head.kind !== PARTITION_HEAD_KIND || head.version !== PARTITION_HEAD_VERSION || head.dataset !== identity.dataset || addressCanonicalJson(head.partition) !== addressCanonicalJson(identity.partition)) {
1422
+ return null;
1423
+ }
1424
+ parseCanonicalJsonAddress(head.receipt);
1425
+ parseCanonicalJsonAddress(head.event);
1426
+ return head;
1427
+ } catch {
1428
+ return null;
1429
+ }
1430
+ }
1431
+ async writeHead(identity, tip) {
1432
+ const headPath = this.headPath(identity);
1433
+ await this.ensureDurableDirectory(path3.dirname(headPath));
1434
+ const tempPath = `${headPath}.tmp-${randomUUID2()}`;
1435
+ const head = {
1436
+ kind: PARTITION_HEAD_KIND,
1437
+ version: PARTITION_HEAD_VERSION,
1438
+ dataset: identity.dataset,
1439
+ partition: identity.partition,
1440
+ receipt: tip.commit.address,
1441
+ event: tip.eventAddress
1442
+ };
1443
+ let handle;
1444
+ try {
1445
+ handle = await fs2.open(tempPath, "wx", 420);
1446
+ await handle.writeFile(canonicalJsonBytes(head));
1447
+ await handle.sync();
1448
+ await handle.close();
1449
+ handle = void 0;
1450
+ await fs2.rename(tempPath, headPath);
1451
+ await this.syncDirectory(path3.dirname(headPath));
1452
+ } catch (error) {
1453
+ await handle?.close();
1454
+ await fs2.unlink(tempPath).catch(() => void 0);
1455
+ throw error;
1456
+ }
1457
+ }
1458
+ async authorityWithRebuiltHead(identity) {
1459
+ const tip = await this.readAuthorityTip(identity);
1460
+ const head = await this.readProjectedHead(identity);
1461
+ if (!tip) {
1462
+ if (head) throw new Error("Partition head exists without immutable event authority");
1463
+ return null;
1464
+ }
1465
+ if (head?.receipt !== tip.commit.address || head.event !== tip.eventAddress) {
1466
+ await this.writeHead(identity, tip);
1467
+ }
1468
+ return tip;
1469
+ }
1470
+ async appendEvent(identity, receipt, previous) {
1471
+ const event = {
1472
+ kind: PARTITION_COMMIT_EVENT_KIND,
1473
+ version: PARTITION_COMMIT_EVENT_VERSION,
1474
+ dataset: identity.dataset,
1475
+ partition: identity.partition,
1476
+ receipt,
1477
+ ...previous ? { previous } : {}
1478
+ };
1479
+ const stored = await this.objects.put(event);
1480
+ const indexDir = this.eventIndexDir(identity);
1481
+ await this.ensureDurableDirectory(indexDir);
1482
+ const digest = parseCanonicalJsonAddress(stored.address);
1483
+ const eventPath = path3.join(indexDir, `${digest}.json`);
1484
+ try {
1485
+ await fs2.link(stored.path, eventPath);
1486
+ await this.syncDirectory(indexDir);
1487
+ } catch (error) {
1488
+ if (error.code !== "EEXIST") throw error;
1489
+ const existing = await fs2.readFile(eventPath);
1490
+ if (addressBytes(existing) !== stored.address || !existing.equals(canonicalJsonBytes(event))) {
1491
+ throw new ContentObjectCollisionError(stored.address, eventPath);
1492
+ }
1493
+ const handle = await fs2.open(eventPath, "r");
1494
+ try {
1495
+ await handle.sync();
1496
+ } finally {
1497
+ await handle.close();
1498
+ }
1499
+ await this.syncDirectory(indexDir);
1500
+ }
1501
+ return stored.address;
1502
+ }
1503
+ async readCommit(address) {
1504
+ parseCanonicalJsonAddress(address);
1505
+ const receipt = await this.objects.get(address);
1506
+ validateReceipt(receipt, receipt);
1507
+ return Object.freeze({ address, receipt, created: false });
1508
+ }
1509
+ /**
1510
+ * Adopt an existing canonical Parquet partition after producer-owned schema,
1511
+ * identity, coverage, row-count, and exact-byte inspection.
1512
+ */
1513
+ async [INTERNAL_HISTORICAL_PARTITION_ADOPTION](conn, identity) {
1514
+ const captured = captureIdentity(identity);
1515
+ const definition = canonicalPartitionDataset(captured.dataset);
1516
+ const relativePath = canonicalRelativePath(captured);
1517
+ const targetPath = path3.resolve(this.targetPath(relativePath));
1518
+ return this.withPartitionLock(captured, async () => {
1519
+ const realMarketRoot = await this.validateMarketRoot();
1520
+ const validatedTargetPath = await this.validatedTargetPath(relativePath, realMarketRoot);
1521
+ if (validatedTargetPath !== targetPath) {
1522
+ throw new PartitionFileIntegrityError(
1523
+ validatedTargetPath,
1524
+ "validated target differs from the registered path"
1525
+ );
1526
+ }
1527
+ const opened = await openRegularFileNoFollow(targetPath);
1528
+ const snapshotRoot = path3.join(this.provenanceRootDir, "inspection-snapshots");
1529
+ await this.ensureDurableDirectory(snapshotRoot);
1530
+ const snapshotDir = await fs2.mkdtemp(path3.join(snapshotRoot, "adopt-"));
1531
+ const snapshotPath = path3.join(snapshotDir, "partition.parquet");
1532
+ try {
1533
+ const snapshotFingerprint = await snapshotOpenFile(opened.handle, snapshotPath);
1534
+ const snapshotObserved = await exactFileAddress(snapshotPath);
1535
+ if (snapshotObserved.address !== snapshotFingerprint.address || snapshotObserved.bytes !== snapshotFingerprint.bytes) {
1536
+ throw new PartitionFileIntegrityError(
1537
+ snapshotPath,
1538
+ "inspection snapshot bytes disagree with the pinned canonical inode"
1539
+ );
1540
+ }
1541
+ await partitionCommitTestFaults.get(this)?.("historical-after-snapshot");
1542
+ const inspected = await inspectCanonicalParquet(conn, snapshotPath, captured);
1543
+ const snapshotAfter = await exactFileAddress(snapshotPath);
1544
+ if (snapshotAfter.address !== snapshotFingerprint.address || snapshotAfter.bytes !== snapshotFingerprint.bytes) {
1545
+ throw new PartitionFileIntegrityError(
1546
+ snapshotPath,
1547
+ "inspection snapshot changed during semantic inspection"
1548
+ );
1549
+ }
1550
+ const canonicalAfter = await exactOpenFileAddress(opened.handle);
1551
+ const afterStat = await opened.handle.stat();
1552
+ if (!afterStat.isFile() || afterStat.nlink !== 1 || !sameInode(opened.stat, afterStat) || canonicalAfter.address !== snapshotFingerprint.address || canonicalAfter.bytes !== snapshotFingerprint.bytes) {
1553
+ throw new PartitionFileIntegrityError(
1554
+ targetPath,
1555
+ "canonical inode changed during historical semantic inspection"
1556
+ );
1557
+ }
1558
+ await requireNamedRegularInode(targetPath, opened.stat);
1559
+ return this.adoptExistingFileCommit(
1560
+ {
1561
+ dataset: captured.dataset,
1562
+ partition: captured.partition,
1563
+ schemaRevision: definition.schemaRevision,
1564
+ relativePath,
1565
+ coverage: inspected.coverage,
1566
+ quality: { inputRows: inspected.rows, writtenRows: inspected.rows, droppedRows: 0 },
1567
+ file: { ...snapshotFingerprint, rows: inspected.rows }
1568
+ },
1569
+ true
1570
+ );
1571
+ } finally {
1572
+ await opened.handle.close();
1573
+ await fs2.rm(snapshotDir, { recursive: true });
1574
+ }
1575
+ });
1576
+ }
1577
+ /**
1578
+ * Establish immutable authority for a canonical file that predates receipt
1579
+ * publication. This never replaces the data file: it pins and hashes the
1580
+ * existing regular inode under the partition lock, then appends authority
1581
+ * only when the caller-supplied rows/coverage/fingerprint agree exactly.
1582
+ */
1583
+ async adoptExistingFileCommit(input, lockHeld = false) {
1584
+ const captured = captureInput(input);
1585
+ const targetPath = path3.resolve(this.targetPath(captured.relativePath));
1586
+ const realMarketRoot = await this.validateMarketRoot();
1587
+ const adopt = async () => {
1588
+ const validatedTargetPath = await this.validatedTargetPath(
1589
+ captured.relativePath,
1590
+ realMarketRoot
1591
+ );
1592
+ if (validatedTargetPath !== targetPath) {
1593
+ throw new PartitionFileIntegrityError(
1594
+ validatedTargetPath,
1595
+ "validated target differs from the registered path"
1596
+ );
1597
+ }
1598
+ const opened = await openRegularFileNoFollow(targetPath);
1599
+ const handle = opened.handle;
1600
+ const pinnedStat = opened.stat;
1601
+ try {
1602
+ if (await this.validatedTargetPath(captured.relativePath, realMarketRoot) !== targetPath) {
1603
+ throw new PartitionFileIntegrityError(
1604
+ targetPath,
1605
+ "target parent changed before existing-file adoption"
1606
+ );
1607
+ }
1608
+ const observed = await exactOpenFileAddress(handle);
1609
+ const afterHash = await handle.stat();
1610
+ if (!afterHash.isFile() || afterHash.nlink !== 1 || !sameInode(pinnedStat, afterHash)) {
1611
+ throw new PartitionFileIntegrityError(targetPath, "existing inode changed while hashing");
1612
+ }
1613
+ await requireNamedRegularInode(targetPath, pinnedStat);
1614
+ if (observed.address !== captured.file.address || observed.bytes !== captured.file.bytes) {
1615
+ throw new Error("Existing partition bytes do not match the supplied fingerprint");
1616
+ }
1617
+ const previous = await this.authorityWithRebuiltHead(captured);
1618
+ if (previous && sameCommitContent(previous.commit.receipt, captured)) {
1619
+ return previous.commit;
1620
+ }
1621
+ const receipt = {
1622
+ kind: PARTITION_COMMIT_RECEIPT_KIND,
1623
+ version: PARTITION_COMMIT_RECEIPT_VERSION,
1624
+ schemaRevision: captured.schemaRevision,
1625
+ dataset: captured.dataset,
1626
+ partition: captured.partition,
1627
+ relativePath: captured.relativePath,
1628
+ coverage: captured.coverage,
1629
+ quality: captured.quality,
1630
+ file: captured.file,
1631
+ classification: previous ? "repair" : "append",
1632
+ ...previous ? { parent: previous.commit.address } : {}
1633
+ };
1634
+ const published = await this.objects.put(receipt);
1635
+ const stored = Object.freeze({
1636
+ address: published.address,
1637
+ receipt: published.value,
1638
+ created: published.created
1639
+ });
1640
+ if (await this.validatedTargetPath(captured.relativePath, realMarketRoot) !== targetPath) {
1641
+ throw new PartitionFileIntegrityError(
1642
+ targetPath,
1643
+ "target parent changed before authority publication"
1644
+ );
1645
+ }
1646
+ await requireNamedRegularInode(targetPath, pinnedStat);
1647
+ const beforeEvent = await exactOpenFileAddress(handle);
1648
+ if (beforeEvent.address !== stored.receipt.file.address || beforeEvent.bytes !== stored.receipt.file.bytes) {
1649
+ throw new Error("Existing partition changed before authority publication");
1650
+ }
1651
+ const eventAddress = await this.appendEvent(
1652
+ captured,
1653
+ stored.address,
1654
+ previous?.eventAddress
1655
+ );
1656
+ await this.writeHead(captured, { eventAddress, commit: stored });
1657
+ if (await this.validatedTargetPath(captured.relativePath, realMarketRoot) !== targetPath) {
1658
+ throw new PartitionFileIntegrityError(
1659
+ targetPath,
1660
+ "target parent changed during authority publication"
1661
+ );
1662
+ }
1663
+ await requireNamedRegularInode(targetPath, pinnedStat);
1664
+ const finalObserved = await exactOpenFileAddress(handle);
1665
+ const afterFinalHash = await handle.stat();
1666
+ if (!afterFinalHash.isFile() || afterFinalHash.nlink !== 1 || !sameInode(pinnedStat, afterFinalHash)) {
1667
+ throw new PartitionFileIntegrityError(
1668
+ targetPath,
1669
+ "existing inode changed during final verification"
1670
+ );
1671
+ }
1672
+ await requireNamedRegularInode(targetPath, pinnedStat);
1673
+ if (finalObserved.address !== stored.receipt.file.address || finalObserved.bytes !== stored.receipt.file.bytes) {
1674
+ throw new Error("Existing partition changed during authority publication");
1675
+ }
1676
+ return stored;
1677
+ } finally {
1678
+ await handle.close();
1679
+ }
1680
+ };
1681
+ return lockHeld ? adopt() : this.withPartitionLock(captured, adopt);
1682
+ }
1683
+ async publishFileCommit(input) {
1684
+ const captured = captureInput(input);
1685
+ const preparedPath = path3.resolve(String(input.preparedPath));
1686
+ const expectedTargetPath = path3.resolve(String(input.expectedTargetPath));
1687
+ const targetPath = path3.resolve(this.targetPath(captured.relativePath));
1688
+ if (expectedTargetPath !== targetPath) {
1689
+ throw new TypeError(
1690
+ `Provenance target does not match the store-owned market path: ${JSON.stringify({ expected: targetPath, observed: expectedTargetPath })}`
1691
+ );
1692
+ }
1693
+ if (preparedPath === targetPath || path3.dirname(preparedPath) !== path3.dirname(targetPath)) {
1694
+ throw new TypeError("Prepared partition file must be a distinct sibling of its target");
1695
+ }
1696
+ const realMarketRoot = await this.validateMarketRoot();
1697
+ return this.withPartitionLock(captured, async () => {
1698
+ const validatedTargetPath = await this.validatedTargetPath(
1699
+ captured.relativePath,
1700
+ realMarketRoot
1701
+ );
1702
+ if (validatedTargetPath !== targetPath) {
1703
+ throw new PartitionFileIntegrityError(
1704
+ validatedTargetPath,
1705
+ "validated target differs from the registered path"
1706
+ );
1707
+ }
1708
+ const targetDirectory = path3.dirname(targetPath);
1709
+ const claimedPath = path3.join(targetDirectory, `.provenance-claim-${randomUUID2()}`);
1710
+ let claimed = false;
1711
+ let targetTouched = false;
1712
+ let claimedHandle;
1713
+ let claimedStat;
1714
+ try {
1715
+ await fs2.rename(preparedPath, claimedPath);
1716
+ claimed = true;
1717
+ await this.syncDirectory(targetDirectory);
1718
+ const opened = await openRegularFileNoFollow(claimedPath);
1719
+ claimedHandle = opened.handle;
1720
+ claimedStat = opened.stat;
1721
+ await partitionCommitTestFaults.get(this)?.("after-claim-open");
1722
+ const prepared = await exactOpenFileAddress(claimedHandle);
1723
+ const afterPreparedHash = await claimedHandle.stat();
1724
+ if (!afterPreparedHash.isFile() || afterPreparedHash.nlink !== 1 || !sameInode(claimedStat, afterPreparedHash)) {
1725
+ throw new PartitionFileIntegrityError(claimedPath, "claimed inode changed while hashing");
1726
+ }
1727
+ await requireNamedRegularInode(claimedPath, claimedStat);
1728
+ if (prepared.address !== captured.file.address || prepared.bytes !== captured.file.bytes) {
1729
+ throw new Error("Prepared partition bytes do not match the supplied fingerprint");
1730
+ }
1731
+ await claimedHandle.sync();
1732
+ await this.syncDirectory(targetDirectory);
1733
+ const previous = await this.authorityWithRebuiltHead(captured);
1734
+ let stored;
1735
+ let pendingEvent;
1736
+ if (previous && sameCommitContent(previous.commit.receipt, captured)) {
1737
+ stored = previous.commit;
1738
+ } else {
1739
+ const receipt = {
1740
+ kind: PARTITION_COMMIT_RECEIPT_KIND,
1741
+ version: PARTITION_COMMIT_RECEIPT_VERSION,
1742
+ schemaRevision: captured.schemaRevision,
1743
+ dataset: captured.dataset,
1744
+ partition: captured.partition,
1745
+ relativePath: captured.relativePath,
1746
+ coverage: captured.coverage,
1747
+ quality: captured.quality,
1748
+ file: captured.file,
1749
+ classification: previous ? "repair" : "append",
1750
+ ...previous ? { parent: previous.commit.address } : {}
1751
+ };
1752
+ const publishedReceipt = await this.objects.put(receipt);
1753
+ stored = {
1754
+ address: publishedReceipt.address,
1755
+ receipt: publishedReceipt.value,
1756
+ created: publishedReceipt.created
1757
+ };
1758
+ Object.freeze(stored);
1759
+ pendingEvent = { receipt: stored, previousEvent: previous?.eventAddress };
1760
+ }
1761
+ if (await this.validatedTargetPath(captured.relativePath, realMarketRoot) !== targetPath) {
1762
+ throw new PartitionFileIntegrityError(targetPath, "target parent changed before install");
1763
+ }
1764
+ const beforeInstall = await claimedHandle.stat();
1765
+ if (!beforeInstall.isFile() || beforeInstall.nlink !== 1 || !sameInode(claimedStat, beforeInstall)) {
1766
+ throw new PartitionFileIntegrityError(
1767
+ claimedPath,
1768
+ "claimed inode changed before install"
1769
+ );
1770
+ }
1771
+ await requireNamedRegularInode(claimedPath, claimedStat);
1772
+ await fs2.rename(claimedPath, targetPath);
1773
+ claimed = false;
1774
+ targetTouched = true;
1775
+ await this.syncDirectory(targetDirectory);
1776
+ await requireNamedRegularInode(targetPath, claimedStat);
1777
+ const afterInstall = await claimedHandle.stat();
1778
+ if (!afterInstall.isFile() || afterInstall.nlink !== 1 || !sameInode(claimedStat, afterInstall)) {
1779
+ throw new PartitionFileIntegrityError(targetPath, "installed inode changed");
1780
+ }
1781
+ const observed = await exactOpenFileAddress(claimedHandle);
1782
+ const afterInstalledHash = await claimedHandle.stat();
1783
+ if (!afterInstalledHash.isFile() || afterInstalledHash.nlink !== 1 || !sameInode(claimedStat, afterInstalledHash)) {
1784
+ throw new PartitionFileIntegrityError(
1785
+ targetPath,
1786
+ "installed inode changed while hashing"
1787
+ );
1788
+ }
1789
+ await requireNamedRegularInode(targetPath, claimedStat);
1790
+ await claimedHandle.sync();
1791
+ if (observed.address !== stored.receipt.file.address || observed.bytes !== stored.receipt.file.bytes) {
1792
+ throw new Error("Installed partition bytes disagree with immutable commit authority");
1793
+ }
1794
+ if (pendingEvent) {
1795
+ const eventAddress = await this.appendEvent(
1796
+ captured,
1797
+ pendingEvent.receipt.address,
1798
+ pendingEvent.previousEvent
1799
+ );
1800
+ await partitionCommitTestFaults.get(this)?.("after-event-before-head");
1801
+ await this.writeHead(captured, { eventAddress, commit: pendingEvent.receipt });
1802
+ }
1803
+ const tip = await this.authorityWithRebuiltHead(captured);
1804
+ if (!tip || tip.commit.address !== stored.address) {
1805
+ throw new Error("Installed partition and authoritative head disagree");
1806
+ }
1807
+ if (await this.validatedTargetPath(captured.relativePath, realMarketRoot) !== targetPath) {
1808
+ throw new PartitionFileIntegrityError(
1809
+ targetPath,
1810
+ "target parent changed before publication completed"
1811
+ );
1812
+ }
1813
+ const beforeReturn = await claimedHandle.stat();
1814
+ if (!beforeReturn.isFile() || beforeReturn.nlink !== 1 || !sameInode(claimedStat, beforeReturn)) {
1815
+ throw new PartitionFileIntegrityError(
1816
+ targetPath,
1817
+ "installed inode changed before publication completed"
1818
+ );
1819
+ }
1820
+ await requireNamedRegularInode(targetPath, claimedStat);
1821
+ const finalObserved = await exactOpenFileAddress(claimedHandle);
1822
+ const afterFinalHash = await claimedHandle.stat();
1823
+ if (!afterFinalHash.isFile() || afterFinalHash.nlink !== 1 || !sameInode(claimedStat, afterFinalHash)) {
1824
+ throw new PartitionFileIntegrityError(
1825
+ targetPath,
1826
+ "installed inode changed during final verification"
1827
+ );
1828
+ }
1829
+ await requireNamedRegularInode(targetPath, claimedStat);
1830
+ if (finalObserved.address !== stored.receipt.file.address || finalObserved.bytes !== stored.receipt.file.bytes) {
1831
+ throw new Error("Installed partition changed before publication completed");
1832
+ }
1833
+ return stored;
1834
+ } catch (error) {
1835
+ if (targetTouched) {
1836
+ throw new PartitionFilePublicationError(targetPath, captured.file, error);
1837
+ }
1838
+ throw error;
1839
+ } finally {
1840
+ await claimedHandle?.close();
1841
+ if (claimed) await this.restoreOrQuarantineClaim(claimedPath, preparedPath);
1842
+ }
1843
+ });
1844
+ }
1845
+ async inspectPartition(identity) {
1846
+ const captured = captureIdentity(identity);
1847
+ const realMarketRoot = await this.validateMarketRoot();
1848
+ return this.withPartitionLock(captured, async () => {
1849
+ const tip = await this.authorityWithRebuiltHead(captured);
1850
+ const relativePath = tip?.commit.receipt.relativePath ?? canonicalRelativePath(captured);
1851
+ validateRegistryPath(captured, relativePath);
1852
+ let observed;
1853
+ try {
1854
+ const targetPath = await this.validatedTargetPath(relativePath, realMarketRoot);
1855
+ observed = await exactFileAddress(targetPath);
1856
+ } catch (error) {
1857
+ if (error.code === "ENOENT") {
1858
+ return tip ? { status: "missing", receipt: tip.commit } : { status: "absent" };
1859
+ }
1860
+ throw error;
1861
+ }
1862
+ if (!tip) return { status: "orphan", observed };
1863
+ if (tip.commit.receipt.file.address !== observed.address || tip.commit.receipt.file.bytes !== observed.bytes) {
1864
+ return { status: "mismatch", receipt: tip.commit, observed };
1865
+ }
1866
+ return { status: "match", receipt: tip.commit };
1867
+ });
1868
+ }
1869
+ };
1870
+ async function publishRefreshCompletionAuthority(store, completion) {
1871
+ const digest = parseCanonicalJsonAddress(completion);
1872
+ const source = store.objects.objectPath(completion);
1873
+ const directory = path3.join(store.provenanceRootDir, "refresh-completions", digest.slice(0, 2));
1874
+ const marker = path3.join(directory, `${digest}.json`);
1875
+ await fs2.mkdir(directory, { recursive: true });
1876
+ try {
1877
+ await fs2.link(source, marker);
1878
+ } catch (error) {
1879
+ if (error.code !== "EEXIST") throw error;
1880
+ }
1881
+ const bytes = await fs2.readFile(marker);
1882
+ if (addressBytes(bytes) !== completion) {
1883
+ throw new ContentObjectCollisionError(completion, marker);
1884
+ }
1885
+ const handle = await fs2.open(marker, "r");
1886
+ try {
1887
+ await handle.sync();
1888
+ } finally {
1889
+ await handle.close();
1890
+ }
1891
+ const directoryHandle = await fs2.open(directory, "r");
1892
+ try {
1893
+ await directoryHandle.sync();
1894
+ } finally {
1895
+ await directoryHandle.close();
1896
+ }
1897
+ }
1898
+ async function verifyRefreshCompletionAuthority(store, completion) {
1899
+ const digest = parseCanonicalJsonAddress(completion);
1900
+ const marker = path3.join(
1901
+ store.provenanceRootDir,
1902
+ "refresh-completions",
1903
+ digest.slice(0, 2),
1904
+ `${digest}.json`
1905
+ );
1906
+ const stat2 = await fs2.lstat(marker);
1907
+ if (!stat2.isFile() || stat2.isSymbolicLink()) {
1908
+ throw new Error("Canonical refresh completion has no producer authority marker");
1909
+ }
1910
+ const bytes = await fs2.readFile(marker);
1911
+ const object = await fs2.readFile(store.objects.objectPath(completion));
1912
+ if (addressBytes(bytes) !== completion || !bytes.equals(object)) {
1913
+ throw new Error("Canonical refresh completion authority marker is corrupt");
148
1914
  }
149
- await conn.run(`
150
- CREATE TABLE IF NOT EXISTS market.spot (
151
- ticker VARCHAR NOT NULL,
152
- date VARCHAR NOT NULL,
153
- time VARCHAR NOT NULL,
154
- open DOUBLE,
155
- high DOUBLE,
156
- low DOUBLE,
157
- close DOUBLE,
158
- bid DOUBLE,
159
- ask DOUBLE,
160
- PRIMARY KEY (ticker, date, time)
161
- )
162
- `);
163
- await conn.run(`
164
- CREATE TABLE IF NOT EXISTS market.enriched (
165
- ticker VARCHAR NOT NULL,
166
- date VARCHAR NOT NULL,
167
- Prior_Close DOUBLE,
168
- Gap_Pct DOUBLE,
169
- ATR_Pct DOUBLE,
170
- RSI_14 DOUBLE,
171
- Price_vs_EMA21_Pct DOUBLE,
172
- Price_vs_SMA50_Pct DOUBLE,
173
- Realized_Vol_5D DOUBLE,
174
- Realized_Vol_20D DOUBLE,
175
- Return_5D DOUBLE,
176
- Return_20D DOUBLE,
177
- Intraday_Range_Pct DOUBLE,
178
- Intraday_Return_Pct DOUBLE,
179
- Close_Position_In_Range DOUBLE,
180
- Gap_Filled INTEGER,
181
- Consecutive_Days INTEGER,
182
- Prev_Return_Pct DOUBLE,
183
- Prior_Range_vs_ATR DOUBLE,
184
- High_Time DOUBLE,
185
- Low_Time DOUBLE,
186
- High_Before_Low INTEGER,
187
- Reversal_Type INTEGER,
188
- Opening_Drive_Strength DOUBLE,
189
- Intraday_Realized_Vol DOUBLE,
190
- Day_of_Week INTEGER,
191
- Month INTEGER,
192
- Is_Opex INTEGER,
193
- ivr DOUBLE,
194
- ivp DOUBLE,
195
- PRIMARY KEY (ticker, date)
196
- )
197
- `);
198
- await conn.run(`
199
- CREATE TABLE IF NOT EXISTS market.enriched_context (
200
- date VARCHAR NOT NULL,
201
- Vol_Regime INTEGER,
202
- Term_Structure_State INTEGER,
203
- Trend_Direction VARCHAR,
204
- VIX_Spike_Pct DOUBLE,
205
- VIX_Gap_Pct DOUBLE,
206
- PRIMARY KEY (date)
207
- )
208
- `);
209
1915
  }
1916
+ var PartitionFilePublicationError = class extends Error {
1917
+ targetPath;
1918
+ file;
1919
+ constructor(targetPath, file, cause) {
1920
+ super(`Partition file installed without a complete projected commit: ${targetPath}`, { cause });
1921
+ this.name = "PartitionFilePublicationError";
1922
+ this.targetPath = targetPath;
1923
+ this.file = file;
1924
+ }
1925
+ };
210
1926
 
211
- // src/db/parquet-writer.ts
212
- import * as fs from "fs/promises";
213
- import * as path3 from "path";
1927
+ // src/market/provenance/partition-commit-attempt.ts
1928
+ import { AsyncLocalStorage } from "async_hooks";
1929
+ var attempts = new AsyncLocalStorage();
1930
+ function compareCodePoints2(left, right) {
1931
+ const leftPoints = Array.from(left, (character) => character.codePointAt(0));
1932
+ const rightPoints = Array.from(right, (character) => character.codePointAt(0));
1933
+ const length = Math.min(leftPoints.length, rightPoints.length);
1934
+ for (let index = 0; index < length; index++) {
1935
+ if (leftPoints[index] !== rightPoints[index]) return leftPoints[index] - rightPoints[index];
1936
+ }
1937
+ return leftPoints.length - rightPoints.length;
1938
+ }
1939
+ function commitTuple(commit) {
1940
+ const partition = Object.entries(commit.receipt.partition).sort(([left], [right]) => compareCodePoints2(left, right)).flatMap(([key, value]) => [key, value]);
1941
+ return [commit.receipt.dataset, ...partition, commit.address];
1942
+ }
1943
+ function compareTuple(left, right) {
1944
+ const length = Math.min(left.length, right.length);
1945
+ for (let index = 0; index < length; index++) {
1946
+ const compared = compareCodePoints2(left[index], right[index]);
1947
+ if (compared !== 0) return compared;
1948
+ }
1949
+ return left.length - right.length;
1950
+ }
1951
+ async function runPartitionCommitAttempt(options, operation) {
1952
+ if (options.attemptId.trim().length === 0) {
1953
+ throw new TypeError("Partition commit attemptId must not be empty");
1954
+ }
1955
+ const active = { ...options, receiptAddresses: [] };
1956
+ const value = await attempts.run(active, operation);
1957
+ const receipts = await Promise.all(
1958
+ [...new Set(active.receiptAddresses)].map((address) => active.recorder.readCommit(address))
1959
+ );
1960
+ receipts.sort((left, right) => compareTuple(commitTuple(left), commitTuple(right)));
1961
+ return {
1962
+ attemptId: active.attemptId,
1963
+ value,
1964
+ receipts: Object.freeze(receipts)
1965
+ };
1966
+ }
1967
+ function activePartitionCommitAttempt() {
1968
+ return attempts.getStore();
1969
+ }
1970
+ function capturePartitionCommitReceipt(receipt) {
1971
+ const active = attempts.getStore();
1972
+ if (!active) return;
1973
+ active.receiptAddresses.push(receipt.address);
1974
+ }
214
1975
 
215
1976
  // src/db/market-datasets.ts
216
- import * as path2 from "path";
1977
+ import * as path5 from "path";
217
1978
 
218
1979
  // src/db/data-root.ts
219
- import path from "path";
1980
+ import path4 from "path";
220
1981
  var KEY = "__tradeblocks_data_root__";
221
1982
  function resolveDbPath(dataRoot, kind) {
222
- return path.join(dataRoot, "database", `${kind}.duckdb`);
1983
+ return path4.join(dataRoot, "database", `${kind}.duckdb`);
223
1984
  }
224
1985
  function setDataRoot(dir) {
225
1986
  globalThis[KEY] = dir;
@@ -242,23 +2003,26 @@ var PARTITIONED_DATASETS = {
242
2003
  option_quote_minutes: "option_quote_minutes"
243
2004
  };
244
2005
  function resolveMarketDir(dataDir) {
245
- return path2.join(getDataRoot(dataDir), "market");
2006
+ return path5.join(getDataRoot(dataDir), "market");
246
2007
  }
247
2008
  function resolveCanonicalMarketFile(dataDir, dataset) {
248
- return path2.join(resolveMarketDir(dataDir), SINGLE_FILE_DATASETS[dataset]);
2009
+ return path5.join(resolveMarketDir(dataDir), SINGLE_FILE_DATASETS[dataset]);
249
2010
  }
250
2011
  function resolveCanonicalMarketPartitionDir(dataDir, dataset) {
251
- return path2.join(resolveMarketDir(dataDir), PARTITIONED_DATASETS[dataset]);
2012
+ return path5.join(resolveMarketDir(dataDir), PARTITIONED_DATASETS[dataset]);
252
2013
  }
253
2014
  function resolveCanonicalMarketPartitionPath(dataDir, dataset, date) {
254
- return path2.join(resolveCanonicalMarketPartitionDir(dataDir, dataset), `date=${date}`);
2015
+ return path5.join(resolveCanonicalMarketPartitionDir(dataDir, dataset), `date=${date}`);
255
2016
  }
256
2017
  function resolveCanonicalMarketPartitionFile(dataDir, dataset, date) {
257
- return path2.join(resolveCanonicalMarketPartitionPath(dataDir, dataset, date), "data.parquet");
2018
+ return path5.join(resolveCanonicalMarketPartitionPath(dataDir, dataset, date), "data.parquet");
258
2019
  }
259
2020
  function canonicalMarketTableName(dataset) {
260
2021
  return `market.${dataset}`;
261
2022
  }
2023
+ function provenanceOptions(dataset, partition, schemaRevision, relativePath, coverage, quality) {
2024
+ return { dataset, partition, schemaRevision, relativePath, coverage, quality };
2025
+ }
262
2026
  var DATASETS_V3 = {
263
2027
  spot: { subdir: "spot", partitionKeys: ["ticker", "date"], filename: "data.parquet" },
264
2028
  enriched: { subdir: "enriched", partitionKeys: ["ticker"], filename: "data.parquet" },
@@ -279,96 +2043,359 @@ var DATASETS_V3 = {
279
2043
  filename: "data.parquet"
280
2044
  }
281
2045
  };
2046
+ var BOUNDED_PROVENANCE_DATASETS = MARKET_DATASETS;
2047
+ function assertCanonicalPartitionDate(value, helper) {
2048
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
2049
+ throw new TypeError(
2050
+ `${helper}: date must be an ISO calendar date (YYYY-MM-DD): ${JSON.stringify(value)}`
2051
+ );
2052
+ }
2053
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
2054
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
2055
+ throw new TypeError(`${helper}: date is not a real calendar date: ${JSON.stringify(value)}`);
2056
+ }
2057
+ }
282
2058
  async function writeSpotPartition(conn, args) {
283
- const def = DATASETS_V3.spot;
2059
+ assertCanonicalPartitionDate(args.date, "writeSpotPartition");
2060
+ const def = BOUNDED_PROVENANCE_DATASETS.spot;
284
2061
  return writeParquetPartition(conn, {
285
- baseDir: path2.join(resolveMarketDir(args.dataDir), def.subdir),
2062
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
286
2063
  partitions: { ticker: args.ticker, date: args.date },
287
2064
  // order matches def.partitionKeys
288
2065
  selectQuery: args.selectQuery,
289
2066
  compression: args.compression,
290
- filename: def.filename
2067
+ filename: def.filename,
2068
+ provenance: provenanceOptions(
2069
+ "spot",
2070
+ { ticker: args.ticker, date: args.date },
2071
+ def.schemaRevision,
2072
+ path5.posix.join(def.subdir, `ticker=${args.ticker}`, `date=${args.date}`, def.filename),
2073
+ { kind: "prepared-date-range", column: "date" },
2074
+ args.quality
2075
+ )
291
2076
  });
292
2077
  }
293
2078
  async function writeChainPartition(conn, args) {
294
- const def = DATASETS_V3.option_chain;
2079
+ assertCanonicalPartitionDate(args.date, "writeChainPartition");
2080
+ const def = BOUNDED_PROVENANCE_DATASETS.option_chain;
295
2081
  return writeParquetPartition(conn, {
296
- baseDir: path2.join(resolveMarketDir(args.dataDir), def.subdir),
2082
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
297
2083
  partitions: { underlying: args.underlying, date: args.date },
298
2084
  selectQuery: args.selectQuery,
299
2085
  compression: args.compression,
300
- filename: def.filename
2086
+ filename: def.filename,
2087
+ provenance: provenanceOptions(
2088
+ "option_chain",
2089
+ { underlying: args.underlying, date: args.date },
2090
+ def.schemaRevision,
2091
+ path5.posix.join(
2092
+ def.subdir,
2093
+ `underlying=${args.underlying}`,
2094
+ `date=${args.date}`,
2095
+ def.filename
2096
+ ),
2097
+ { kind: "prepared-date-range", column: "date" },
2098
+ args.quality
2099
+ )
301
2100
  });
302
2101
  }
303
2102
  async function writeQuoteMinutesPartition(conn, args) {
304
- const def = DATASETS_V3.option_quote_minutes;
2103
+ assertCanonicalPartitionDate(args.date, "writeQuoteMinutesPartition");
2104
+ const def = BOUNDED_PROVENANCE_DATASETS.option_quote_minutes;
305
2105
  const sortedSelect = `SELECT * FROM (${args.selectQuery}) AS q ORDER BY q.ticker, q.time`;
306
2106
  return writeParquetPartition(conn, {
307
- baseDir: path2.join(resolveMarketDir(args.dataDir), def.subdir),
2107
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
308
2108
  partitions: { underlying: args.underlying, date: args.date },
309
2109
  selectQuery: sortedSelect,
310
2110
  compression: args.compression,
311
- filename: def.filename
2111
+ filename: def.filename,
2112
+ provenance: provenanceOptions(
2113
+ "option_quote_minutes",
2114
+ { underlying: args.underlying, date: args.date },
2115
+ def.schemaRevision,
2116
+ path5.posix.join(
2117
+ def.subdir,
2118
+ `underlying=${args.underlying}`,
2119
+ `date=${args.date}`,
2120
+ def.filename
2121
+ ),
2122
+ { kind: "prepared-date-range", column: "date" },
2123
+ args.quality
2124
+ )
312
2125
  });
313
2126
  }
314
2127
  async function writeOiDailyPartition(conn, args) {
315
- const def = DATASETS_V3.option_oi_daily;
2128
+ assertCanonicalPartitionDate(args.date, "writeOiDailyPartition");
2129
+ const def = BOUNDED_PROVENANCE_DATASETS.option_oi_daily;
316
2130
  const sortedSelect = `SELECT * FROM (${args.selectQuery}) AS q ORDER BY q.ticker`;
317
2131
  return writeParquetPartition(conn, {
318
- baseDir: path2.join(resolveMarketDir(args.dataDir), def.subdir),
2132
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
319
2133
  partitions: { underlying: args.underlying, date: args.date },
320
2134
  selectQuery: sortedSelect,
321
2135
  compression: args.compression,
322
- filename: def.filename
2136
+ filename: def.filename,
2137
+ provenance: provenanceOptions(
2138
+ "option_oi_daily",
2139
+ { underlying: args.underlying, date: args.date },
2140
+ def.schemaRevision,
2141
+ path5.posix.join(
2142
+ def.subdir,
2143
+ `underlying=${args.underlying}`,
2144
+ `date=${args.date}`,
2145
+ def.filename
2146
+ ),
2147
+ { kind: "prepared-date-range", column: "date" },
2148
+ args.quality
2149
+ )
323
2150
  });
324
2151
  }
325
2152
  async function writeEnrichedTickerFile(conn, args) {
326
2153
  const def = DATASETS_V3.enriched;
327
2154
  return writeParquetPartition(conn, {
328
- baseDir: path2.join(resolveMarketDir(args.dataDir), def.subdir),
2155
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
329
2156
  partitions: { ticker: args.ticker },
330
2157
  selectQuery: args.selectQuery,
331
2158
  compression: args.compression,
332
2159
  filename: def.filename
333
2160
  });
334
2161
  }
2162
+ async function writeEnrichedTickerPartition(conn, args) {
2163
+ assertCanonicalPartitionDate(args.date, "writeEnrichedTickerPartition");
2164
+ const def = BOUNDED_PROVENANCE_DATASETS.enriched;
2165
+ return writeParquetPartition(conn, {
2166
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
2167
+ partitions: { ticker: args.ticker, date: args.date },
2168
+ selectQuery: args.selectQuery,
2169
+ compression: args.compression,
2170
+ filename: def.filename,
2171
+ provenance: provenanceOptions(
2172
+ "enriched",
2173
+ { ticker: args.ticker, date: args.date },
2174
+ def.schemaRevision,
2175
+ path5.posix.join(def.subdir, `ticker=${args.ticker}`, `date=${args.date}`, def.filename),
2176
+ { kind: "prepared-date-range", column: "date" },
2177
+ args.quality
2178
+ )
2179
+ });
2180
+ }
335
2181
  async function writeEnrichedContext(conn, args) {
336
2182
  const def = DATASETS_V3.enriched_context;
337
- const targetPath = path2.join(resolveMarketDir(args.dataDir), def.subdir, def.filename);
338
2183
  return writeParquetAtomic(conn, {
339
- targetPath,
2184
+ targetPath: path5.join(resolveMarketDir(args.dataDir), def.subdir, def.filename),
340
2185
  selectQuery: args.selectQuery,
341
2186
  compression: args.compression
342
2187
  });
343
2188
  }
2189
+ async function writeEnrichedContextPartition(conn, args) {
2190
+ assertCanonicalPartitionDate(args.date, "writeEnrichedContextPartition");
2191
+ const completeness = await conn.runAndReadAll(
2192
+ `SELECT date, Vol_Regime, Term_Structure_State, Trend_Direction,
2193
+ VIX_Spike_Pct, VIX_Gap_Pct
2194
+ FROM (${args.selectQuery}) AS bounded_enriched_context`
2195
+ );
2196
+ const rows = completeness.getRows();
2197
+ if (rows.length !== 1) {
2198
+ throw new Error(
2199
+ `Enriched context partition ${args.date} must contain exactly one logical session row`
2200
+ );
2201
+ }
2202
+ const [date, volRegime, termStructure, trendDirection, vixSpikePct, vixGapPct] = rows[0];
2203
+ if (String(date) !== args.date) {
2204
+ throw new Error(`Enriched context partition ${args.date} contains a different logical date`);
2205
+ }
2206
+ if (!Number.isInteger(volRegime) || Number(volRegime) < 1 || Number(volRegime) > 6 || !Number.isInteger(termStructure) || ![-1, 0, 1].includes(Number(termStructure)) || typeof trendDirection !== "string" || !["up", "down", "flat"].includes(trendDirection) || typeof vixSpikePct !== "number" || !Number.isFinite(vixSpikePct) || typeof vixGapPct !== "number" || !Number.isFinite(vixGapPct)) {
2207
+ throw new Error(
2208
+ `Enriched context partition ${args.date} is missing required VIX completeness fields`
2209
+ );
2210
+ }
2211
+ const def = BOUNDED_PROVENANCE_DATASETS.enriched_context;
2212
+ return writeParquetPartition(conn, {
2213
+ baseDir: path5.join(resolveMarketDir(args.dataDir), def.subdir),
2214
+ partitions: { date: args.date },
2215
+ selectQuery: args.selectQuery,
2216
+ compression: args.compression,
2217
+ filename: def.filename,
2218
+ provenance: provenanceOptions(
2219
+ "enriched_context",
2220
+ { date: args.date },
2221
+ def.schemaRevision,
2222
+ path5.posix.join(def.subdir, `date=${args.date}`, def.filename),
2223
+ { kind: "prepared-date-range", column: "date" },
2224
+ args.quality
2225
+ )
2226
+ });
2227
+ }
344
2228
 
345
2229
  // src/db/parquet-writer.ts
346
2230
  function isParquetMode() {
347
2231
  return process.env.TRADEBLOCKS_PARQUET === "true";
348
2232
  }
2233
+ function captureWriteProvenance(provenance) {
2234
+ if (!provenance) return void 0;
2235
+ const captured = {
2236
+ dataset: provenance.dataset,
2237
+ partition: provenance.partition,
2238
+ schemaRevision: provenance.schemaRevision,
2239
+ relativePath: provenance.relativePath,
2240
+ coverage: provenance.coverage,
2241
+ ...provenance.quality === void 0 ? {} : { quality: provenance.quality }
2242
+ };
2243
+ return JSON.parse(canonicalJsonBytes(captured).toString("utf8"));
2244
+ }
2245
+ var ParquetProvenanceOrphanError = class extends Error {
2246
+ targetPath;
2247
+ file;
2248
+ constructor(targetPath, file, cause) {
2249
+ super(`Parquet data committed without provenance receipt: ${targetPath}`, { cause });
2250
+ this.name = "ParquetProvenanceOrphanError";
2251
+ this.targetPath = targetPath;
2252
+ this.file = file;
2253
+ }
2254
+ };
2255
+ var UnmanifestedParquetWriteError = class extends Error {
2256
+ targetPath;
2257
+ constructor(targetPath) {
2258
+ super(`Active partition commit attempt refuses an unregistered Parquet write: ${targetPath}`);
2259
+ this.name = "UnmanifestedParquetWriteError";
2260
+ this.targetPath = targetPath;
2261
+ }
2262
+ };
2263
+ async function exactFileFingerprint(filePath, rows) {
2264
+ const hash = createHash3("sha256");
2265
+ let bytes = 0;
2266
+ for await (const chunk of createReadStream(filePath)) {
2267
+ const buffer = chunk;
2268
+ bytes += buffer.byteLength;
2269
+ hash.update(buffer);
2270
+ }
2271
+ return { address: `sha256:${hash.digest("hex")}`, bytes, rows };
2272
+ }
2273
+ function escapeSqlLiteral2(value) {
2274
+ return value.replaceAll("'", "''");
2275
+ }
2276
+ async function inspectPreparedParquet(conn, preparedPath, coverage) {
2277
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(coverage.column)) {
2278
+ throw new TypeError(`Unsafe provenance coverage column: ${JSON.stringify(coverage.column)}`);
2279
+ }
2280
+ const source = `read_parquet('${escapeSqlLiteral2(preparedPath)}', hive_partitioning=false)`;
2281
+ const reader = await conn.runAndReadAll(
2282
+ `SELECT COUNT(*)::BIGINT,
2283
+ COUNT("${coverage.column}")::BIGINT,
2284
+ MIN(CAST("${coverage.column}" AS VARCHAR)),
2285
+ MAX(CAST("${coverage.column}" AS VARCHAR))
2286
+ FROM ${source}`
2287
+ );
2288
+ const row = reader.getRows()[0];
2289
+ const rowCount = Number(row[0]);
2290
+ const coveredRows = Number(row[1]);
2291
+ if (!Number.isSafeInteger(rowCount) || rowCount < 0 || !Number.isSafeInteger(coveredRows)) {
2292
+ throw new Error("Prepared Parquet returned an invalid row count");
2293
+ }
2294
+ if (rowCount === 0) {
2295
+ if (coveredRows !== 0 || row[2] != null || row[3] != null) {
2296
+ throw new Error("Empty prepared Parquet returned inconsistent logical coverage");
2297
+ }
2298
+ return { rowCount, coverage: { kind: "empty" } };
2299
+ }
2300
+ if (coveredRows !== rowCount || row[2] == null || row[3] == null) {
2301
+ throw new Error(
2302
+ `Prepared Parquet contains NULL or incomplete logical coverage in ${coverage.column}`
2303
+ );
2304
+ }
2305
+ return {
2306
+ rowCount,
2307
+ coverage: { kind: "date-range", from: String(row[2]), through: String(row[3]) }
2308
+ };
2309
+ }
2310
+ async function syncFile(filePath) {
2311
+ const handle = await fs3.open(filePath, "r");
2312
+ try {
2313
+ await handle.sync();
2314
+ } finally {
2315
+ await handle.close();
2316
+ }
2317
+ }
2318
+ async function syncDirectory(directory) {
2319
+ const handle = await fs3.open(directory, "r");
2320
+ try {
2321
+ await handle.sync();
2322
+ } finally {
2323
+ await handle.close();
2324
+ }
2325
+ }
2326
+ function resolveQuality(rowCount, quality) {
2327
+ if (!quality) {
2328
+ throw new Error("Partition provenance requires explicit inputRows and droppedRows");
2329
+ }
2330
+ if ("kind" in quality) {
2331
+ return { inputRows: rowCount, writtenRows: rowCount, droppedRows: 0 };
2332
+ }
2333
+ if (!Number.isSafeInteger(quality.inputRows) || !Number.isSafeInteger(quality.droppedRows) || quality.inputRows < 0 || quality.droppedRows < 0 || quality.inputRows !== rowCount + quality.droppedRows) {
2334
+ throw new Error(
2335
+ `Partition provenance quality must satisfy inputRows (${quality.inputRows}) = writtenRows (${rowCount}) + droppedRows (${quality.droppedRows})`
2336
+ );
2337
+ }
2338
+ return {
2339
+ inputRows: quality.inputRows,
2340
+ writtenRows: rowCount,
2341
+ droppedRows: quality.droppedRows
2342
+ };
2343
+ }
349
2344
  async function writeParquetAtomic(conn, opts) {
350
2345
  const {
351
2346
  targetPath,
352
2347
  selectQuery,
353
2348
  stagingName = `_staging_${Date.now()}`,
354
- compression = "ZSTD"
2349
+ compression = "ZSTD",
2350
+ provenance: callerProvenance
355
2351
  } = opts;
2352
+ const provenance = captureWriteProvenance(callerProvenance);
2353
+ const provenanceAttempt = activePartitionCommitAttempt();
2354
+ if (provenanceAttempt && !provenance) throw new UnmanifestedParquetWriteError(targetPath);
356
2355
  const tempPath = `${targetPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
357
2356
  try {
358
2357
  await conn.run(`CREATE TEMP TABLE "${stagingName}" AS ${selectQuery}`);
359
- await fs.mkdir(path3.dirname(targetPath), { recursive: true });
2358
+ await fs3.mkdir(path6.dirname(targetPath), { recursive: true });
360
2359
  await conn.run(
361
2360
  `COPY "${stagingName}" TO '${tempPath}' (FORMAT PARQUET, COMPRESSION ${compression})`
362
2361
  );
363
- await fs.rename(tempPath, targetPath);
364
- const reader = await conn.runAndReadAll(
365
- `SELECT COUNT(*)::INTEGER AS cnt FROM "${stagingName}"`
2362
+ const inspected = provenanceAttempt ? await inspectPreparedParquet(conn, tempPath, provenance.coverage) : void 0;
2363
+ const rowCount = inspected ? inspected.rowCount : Number(
2364
+ (await conn.runAndReadAll(`SELECT COUNT(*)::INTEGER AS cnt FROM "${stagingName}"`)).getRows()[0][0]
366
2365
  );
367
- const rowCount = Number(reader.getRows()[0][0]);
2366
+ const logicalCoverage = inspected?.coverage;
2367
+ const quality = provenanceAttempt ? resolveQuality(rowCount, provenance.quality) : void 0;
2368
+ const fingerprint = provenanceAttempt ? await exactFileFingerprint(tempPath, rowCount) : void 0;
2369
+ if (provenance && provenanceAttempt && fingerprint && logicalCoverage && quality) {
2370
+ try {
2371
+ const stored = await provenanceAttempt.recorder.publishFileCommit({
2372
+ dataset: provenance.dataset,
2373
+ partition: provenance.partition,
2374
+ schemaRevision: provenance.schemaRevision,
2375
+ relativePath: provenance.relativePath,
2376
+ coverage: logicalCoverage,
2377
+ quality,
2378
+ file: fingerprint,
2379
+ preparedPath: tempPath,
2380
+ expectedTargetPath: targetPath
2381
+ });
2382
+ capturePartitionCommitReceipt(stored);
2383
+ return { rowCount, provenance: stored };
2384
+ } catch (error) {
2385
+ if (error instanceof PartitionFilePublicationError) {
2386
+ throw new ParquetProvenanceOrphanError(targetPath, fingerprint, error);
2387
+ }
2388
+ throw error;
2389
+ }
2390
+ }
2391
+ await syncFile(tempPath);
2392
+ await syncDirectory(path6.dirname(tempPath));
2393
+ await fs3.rename(tempPath, targetPath);
2394
+ await syncDirectory(path6.dirname(targetPath));
368
2395
  return { rowCount };
369
2396
  } catch (writeErr) {
370
2397
  try {
371
- await fs.unlink(tempPath);
2398
+ await fs3.unlink(tempPath);
372
2399
  } catch {
373
2400
  }
374
2401
  throw writeErr;
@@ -396,34 +2423,35 @@ async function writeParquetPartition(conn, opts) {
396
2423
  }
397
2424
  }
398
2425
  const partitionSegments = Object.entries(partitions).map(([k, v]) => `${k}=${v}`);
399
- const targetPath = path3.join(opts.baseDir, ...partitionSegments, filename);
2426
+ const targetPath = path6.join(opts.baseDir, ...partitionSegments, filename);
400
2427
  return writeParquetAtomic(conn, {
401
2428
  targetPath,
402
2429
  selectQuery: opts.selectQuery,
403
- compression: opts.compression
2430
+ compression: opts.compression,
2431
+ provenance: opts.provenance
404
2432
  });
405
2433
  }
406
2434
 
407
2435
  // src/db/json-adapters.ts
408
- import * as fs3 from "fs/promises";
409
- import * as path5 from "path";
2436
+ import * as fs5 from "fs/promises";
2437
+ import * as path8 from "path";
410
2438
  import { z } from "zod";
411
2439
 
412
2440
  // src/db/json-store.ts
413
- import * as fs2 from "fs/promises";
414
- import * as path4 from "path";
2441
+ import * as fs4 from "fs/promises";
2442
+ import * as path7 from "path";
415
2443
  function isEnoent(err) {
416
2444
  return typeof err === "object" && err !== null && err.code === "ENOENT";
417
2445
  }
418
2446
  async function writeJsonFile(filePath, data) {
419
2447
  const tmpPath = filePath + ".tmp";
420
- await fs2.mkdir(path4.dirname(filePath), { recursive: true });
421
- await fs2.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
422
- await fs2.rename(tmpPath, filePath);
2448
+ await fs4.mkdir(path7.dirname(filePath), { recursive: true });
2449
+ await fs4.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
2450
+ await fs4.rename(tmpPath, filePath);
423
2451
  }
424
2452
  async function readJsonFile(filePath) {
425
2453
  try {
426
- const content = await fs2.readFile(filePath, "utf-8");
2454
+ const content = await fs4.readFile(filePath, "utf-8");
427
2455
  return JSON.parse(content);
428
2456
  } catch (err) {
429
2457
  if (isEnoent(err)) {
@@ -434,7 +2462,7 @@ async function readJsonFile(filePath) {
434
2462
  }
435
2463
  async function deleteJsonFile(filePath) {
436
2464
  try {
437
- await fs2.unlink(filePath);
2465
+ await fs4.unlink(filePath);
438
2466
  return true;
439
2467
  } catch (err) {
440
2468
  if (isEnoent(err)) {
@@ -445,8 +2473,8 @@ async function deleteJsonFile(filePath) {
445
2473
  }
446
2474
  async function listJsonFiles(dirPath, suffix = ".json") {
447
2475
  try {
448
- const entries = await fs2.readdir(dirPath, { withFileTypes: true });
449
- return entries.filter((e) => e.isFile() && e.name.endsWith(suffix)).map((e) => path4.join(dirPath, e.name)).sort();
2476
+ const entries = await fs4.readdir(dirPath, { withFileTypes: true });
2477
+ return entries.filter((e) => e.isFile() && e.name.endsWith(suffix)).map((e) => path7.join(dirPath, e.name)).sort();
450
2478
  } catch (err) {
451
2479
  if (isEnoent(err)) {
452
2480
  return [];
@@ -474,7 +2502,7 @@ function jsonToProfile(json) {
474
2502
  };
475
2503
  }
476
2504
  function profilePath(blockId, strategyName, blocksDir) {
477
- return path5.join(blocksDir, blockId, "profiles", toFileSlug(strategyName) + ".json");
2505
+ return path8.join(blocksDir, blockId, "profiles", toFileSlug(strategyName) + ".json");
478
2506
  }
479
2507
  async function upsertProfileJson(profile, blocksDir) {
480
2508
  const filePath = profilePath(profile.blockId, profile.strategyName, blocksDir);
@@ -496,7 +2524,7 @@ async function getProfileJson(blockId, strategyName, blocksDir) {
496
2524
  }
497
2525
  async function listProfilesJson(blocksDir, blockId) {
498
2526
  if (blockId) {
499
- const profileDir = path5.join(blocksDir, blockId, "profiles");
2527
+ const profileDir = path8.join(blocksDir, blockId, "profiles");
500
2528
  const files = await listJsonFiles(profileDir);
501
2529
  const profiles2 = [];
502
2530
  for (const file of files) {
@@ -508,7 +2536,7 @@ async function listProfilesJson(blocksDir, blockId) {
508
2536
  const profiles = [];
509
2537
  let entries;
510
2538
  try {
511
- entries = await fs3.readdir(blocksDir, { withFileTypes: true });
2539
+ entries = await fs5.readdir(blocksDir, { withFileTypes: true });
512
2540
  } catch {
513
2541
  return [];
514
2542
  }
@@ -536,7 +2564,7 @@ function jsonToSyncMeta(json) {
536
2564
  };
537
2565
  }
538
2566
  function syncMetaPath(blockId, blocksDir) {
539
- return path5.join(blocksDir, blockId, ".sync-meta.json");
2567
+ return path8.join(blocksDir, blockId, ".sync-meta.json");
540
2568
  }
541
2569
  async function getSyncMetadataJson(blockId, blocksDir) {
542
2570
  const filePath = syncMetaPath(blockId, blocksDir);
@@ -554,7 +2582,7 @@ async function deleteSyncMetadataJson(blockId, blocksDir) {
554
2582
  async function getAllSyncedBlockIdsJson(blocksDir) {
555
2583
  let entries;
556
2584
  try {
557
- entries = await fs3.readdir(blocksDir, { withFileTypes: true });
2585
+ entries = await fs5.readdir(blocksDir, { withFileTypes: true });
558
2586
  } catch {
559
2587
  return [];
560
2588
  }
@@ -563,7 +2591,7 @@ async function getAllSyncedBlockIdsJson(blocksDir) {
563
2591
  if (!entry.isDirectory()) continue;
564
2592
  const metaPath = syncMetaPath(entry.name, blocksDir);
565
2593
  try {
566
- await fs3.access(metaPath);
2594
+ await fs5.access(metaPath);
567
2595
  ids.push(entry.name);
568
2596
  } catch {
569
2597
  }
@@ -574,7 +2602,7 @@ function marketMetaKey(source, ticker, targetTable) {
574
2602
  return `${source}|${ticker}|${targetTable}`;
575
2603
  }
576
2604
  function marketMetaFilePath(dataDir) {
577
- return path5.join(getDataRoot(dataDir), "market-meta", "sync-metadata.json");
2605
+ return path8.join(getDataRoot(dataDir), "market-meta", "sync-metadata.json");
578
2606
  }
579
2607
  async function getMarketImportMetadataJson(source, ticker, targetTable, dataDir) {
580
2608
  const filePath = marketMetaFilePath(dataDir);
@@ -602,7 +2630,7 @@ function flatLogKey(entry) {
602
2630
  return `${entry.date}|${entry.asset_class}|${entry.underlying}`;
603
2631
  }
604
2632
  function flatLogFilePath(dataDir) {
605
- return path5.join(getDataRoot(dataDir), "market-meta", "flat-import-log.json");
2633
+ return path8.join(getDataRoot(dataDir), "market-meta", "flat-import-log.json");
606
2634
  }
607
2635
  async function getFlatImportLogJson(assetClass, underlying, from, to, dataDir) {
608
2636
  const filePath = flatLogFilePath(dataDir);
@@ -640,7 +2668,7 @@ var EnrichmentWatermarksSchema = z.object({
640
2668
  watermarks: z.record(z.string().regex(/^[A-Z0-9._-]+$/), TickerEntrySchema)
641
2669
  });
642
2670
  function watermarksFilePath(dataDir) {
643
- return path5.join(getDataRoot(dataDir), "market-meta", "enrichment-watermarks.json");
2671
+ return path8.join(getDataRoot(dataDir), "market-meta", "enrichment-watermarks.json");
644
2672
  }
645
2673
  async function loadEnrichmentWatermarks(dataDir) {
646
2674
  const raw = await readJsonFile(watermarksFilePath(dataDir));
@@ -947,121 +2975,19 @@ async function deleteProfile(conn, blockId, strategyName, baseDir) {
947
2975
 
948
2976
  // src/db/market-views.ts
949
2977
  import { existsSync, readdirSync } from "fs";
950
- import * as path6 from "path";
951
-
952
- // src/utils/quote-parquet-projection.ts
953
- var describeCache = /* @__PURE__ */ new Map();
954
- function escapeSqlLiteral(value) {
955
- return value.replace(/'/g, "''");
956
- }
957
- function readParquetGlobSql(glob) {
958
- return `read_parquet('${escapeSqlLiteral(glob)}', hive_partitioning=true, union_by_name=true)`;
959
- }
960
- function readParquetFilesSql(files) {
961
- if (files.length === 0) {
962
- throw new Error("readParquetFilesSql: files must not be empty");
963
- }
964
- const fileList = files.map((filePath) => `'${escapeSqlLiteral(filePath)}'`).join(", ");
965
- return `read_parquet([${fileList}], hive_partitioning=true, union_by_name=true)`;
966
- }
967
- async function describeReadParquetColumns(conn, readParquetSql) {
968
- let pending = describeCache.get(readParquetSql);
969
- if (!pending) {
970
- pending = (async () => {
971
- const reader = await conn.runAndReadAll(`DESCRIBE SELECT * FROM ${readParquetSql}`);
972
- return new Set(reader.getRows().map((row) => String(row[0]).toLowerCase()));
973
- })();
974
- describeCache.set(readParquetSql, pending);
975
- }
976
- return pending;
977
- }
978
- async function describeQueryColumns(conn, querySql) {
979
- const reader = await conn.runAndReadAll(`DESCRIBE ${querySql}`);
980
- return new Set(reader.getRows().map((row) => String(row[0]).toLowerCase()));
981
- }
982
- function hasColumn2(columns, name) {
983
- return columns.has(name.toLowerCase());
984
- }
985
- function quoteParquetColumnExpr(columns, alias, name, fallbackType) {
986
- return hasColumn2(columns, name) ? `${alias}.${name}` : `NULL::${fallbackType}`;
987
- }
988
- function quoteParquetMidExpr(columns, alias) {
989
- return hasColumn2(columns, "mid") ? `${alias}.mid` : `((CAST(${alias}.bid AS DOUBLE) + CAST(${alias}.ask AS DOUBLE)) / 2)`;
990
- }
991
- var ALL_GREEKS = ["delta", "gamma", "theta", "vega", "iv"];
992
- function quoteParquetGreekProjection(columns, alias = "q", needed = ALL_GREEKS) {
993
- const wantSet = new Set(needed);
994
- const projectGreek = (name) => wantSet.has(name) ? `${quoteParquetColumnExpr(columns, alias, name, "DOUBLE")} AS ${name}` : `NULL::DOUBLE AS ${name}`;
995
- return [
996
- projectGreek("delta"),
997
- projectGreek("gamma"),
998
- projectGreek("theta"),
999
- projectGreek("vega"),
1000
- projectGreek("iv"),
1001
- `${quoteParquetColumnExpr(columns, alias, "greeks_source", "VARCHAR")} AS greeks_source`,
1002
- `${quoteParquetColumnExpr(columns, alias, "greeks_revision", "INTEGER")} AS greeks_revision`,
1003
- `${quoteParquetColumnExpr(columns, alias, "rate_type", "VARCHAR")} AS rate_type`,
1004
- `${quoteParquetColumnExpr(columns, alias, "rate_value", "DOUBLE")} AS rate_value`,
1005
- `${quoteParquetColumnExpr(columns, alias, "gamma_source", "VARCHAR")} AS gamma_source`
1006
- ].join(",\n ");
1007
- }
1008
- function quoteParquetGreekWriteProjection(columns, alias = "q") {
1009
- const castReal = (name) => hasColumn2(columns, name) ? `CAST(${alias}.${name} AS REAL)` : `NULL::REAL`;
1010
- return [
1011
- `${castReal("delta")} AS delta`,
1012
- `${castReal("gamma")} AS gamma`,
1013
- `${castReal("theta")} AS theta`,
1014
- `${castReal("vega")} AS vega`,
1015
- `${castReal("iv")} AS iv`,
1016
- `${quoteParquetColumnExpr(columns, alias, "greeks_source", "VARCHAR")} AS greeks_source`,
1017
- `${quoteParquetColumnExpr(columns, alias, "greeks_revision", "INTEGER")} AS greeks_revision`,
1018
- `${quoteParquetColumnExpr(columns, alias, "rate_type", "VARCHAR")} AS rate_type`,
1019
- `${quoteParquetColumnExpr(columns, alias, "rate_value", "DOUBLE")} AS rate_value`,
1020
- `${quoteParquetColumnExpr(columns, alias, "gamma_source", "VARCHAR")} AS gamma_source`
1021
- ].join(",\n ");
1022
- }
1023
- function quoteParquetCanonicalProjection(columns, alias = "q") {
1024
- return [
1025
- `${quoteParquetColumnExpr(columns, alias, "underlying", "VARCHAR")} AS underlying`,
1026
- `${quoteParquetColumnExpr(columns, alias, "date", "VARCHAR")} AS date`,
1027
- `${quoteParquetColumnExpr(columns, alias, "ticker", "VARCHAR")} AS ticker`,
1028
- `${quoteParquetColumnExpr(columns, alias, "time", "VARCHAR")} AS time`,
1029
- `${quoteParquetColumnExpr(columns, alias, "bid", "DOUBLE")} AS bid`,
1030
- `${quoteParquetColumnExpr(columns, alias, "ask", "DOUBLE")} AS ask`,
1031
- `${quoteParquetMidExpr(columns, alias)} AS mid`,
1032
- `${quoteParquetColumnExpr(columns, alias, "last_updated_ns", "BIGINT")} AS last_updated_ns`,
1033
- `${quoteParquetColumnExpr(columns, alias, "source", "VARCHAR")} AS source`,
1034
- quoteParquetGreekProjection(columns, alias)
1035
- ].join(",\n ");
1036
- }
1037
- function quoteParquetCanonicalWriteProjection(columns, alias = "q") {
1038
- return [
1039
- `${quoteParquetColumnExpr(columns, alias, "underlying", "VARCHAR")} AS underlying`,
1040
- `${quoteParquetColumnExpr(columns, alias, "date", "VARCHAR")} AS date`,
1041
- `${quoteParquetColumnExpr(columns, alias, "ticker", "VARCHAR")} AS ticker`,
1042
- `${quoteParquetColumnExpr(columns, alias, "time", "VARCHAR")} AS time`,
1043
- `${quoteParquetColumnExpr(columns, alias, "bid", "DOUBLE")} AS bid`,
1044
- `${quoteParquetColumnExpr(columns, alias, "ask", "DOUBLE")} AS ask`,
1045
- `${quoteParquetMidExpr(columns, alias)} AS mid`,
1046
- `${quoteParquetColumnExpr(columns, alias, "last_updated_ns", "BIGINT")} AS last_updated_ns`,
1047
- `${quoteParquetColumnExpr(columns, alias, "source", "VARCHAR")} AS source`,
1048
- quoteParquetGreekWriteProjection(columns, alias)
1049
- ].join(",\n ");
1050
- }
1051
-
1052
- // src/db/market-views.ts
2978
+ import * as path9 from "path";
1053
2979
  function hasParquetPartitions(dir, partitionKey = "date") {
1054
2980
  if (!existsSync(dir)) return false;
1055
2981
  try {
1056
2982
  const prefix = `${partitionKey}=`;
1057
2983
  return readdirSync(dir).some((entry) => {
1058
2984
  if (!entry.startsWith(prefix)) return false;
1059
- const partDir = path6.join(dir, entry);
2985
+ const partDir = path9.join(dir, entry);
1060
2986
  try {
1061
2987
  const entries = readdirSync(partDir);
1062
2988
  for (const sub of entries) {
1063
2989
  if (sub.endsWith(".parquet")) return true;
1064
- const nested = path6.join(partDir, sub);
2990
+ const nested = path9.join(partDir, sub);
1065
2991
  try {
1066
2992
  if (readdirSync(nested).some((f) => f.endsWith(".parquet"))) return true;
1067
2993
  } catch {
@@ -1081,14 +3007,29 @@ function hasEnrichedTickerFiles(dir) {
1081
3007
  try {
1082
3008
  return readdirSync(dir).some((entry) => {
1083
3009
  if (!entry.startsWith("ticker=")) return false;
1084
- return existsSync(path6.join(dir, entry, "data.parquet"));
3010
+ const tickerDir = path9.join(dir, entry);
3011
+ try {
3012
+ return readdirSync(tickerDir).some(
3013
+ (dateEntry) => dateEntry.startsWith("date=") && existsSync(path9.join(tickerDir, dateEntry, "data.parquet"))
3014
+ );
3015
+ } catch {
3016
+ return false;
3017
+ }
1085
3018
  });
1086
3019
  } catch {
1087
3020
  return false;
1088
3021
  }
1089
3022
  }
1090
3023
  function hasEnrichedContextFile(dir) {
1091
- return existsSync(path6.join(dir, "context", "data.parquet"));
3024
+ const contextDir = path9.join(dir, "context");
3025
+ if (!existsSync(contextDir)) return false;
3026
+ try {
3027
+ return readdirSync(contextDir).some(
3028
+ (entry) => entry.startsWith("date=") && existsSync(path9.join(contextDir, entry, "data.parquet"))
3029
+ );
3030
+ } catch {
3031
+ return false;
3032
+ }
1092
3033
  }
1093
3034
  async function createMarketParquetViews(conn, dataDir) {
1094
3035
  const viewsCreated = [];
@@ -1169,7 +3110,7 @@ async function createMarketParquetViews(conn, dataDir) {
1169
3110
  await conn.run("DROP TABLE IF EXISTS market.option_delta_index");
1170
3111
  } catch {
1171
3112
  }
1172
- const spotDir = path6.join(resolveMarketDir(dataDir), "spot");
3113
+ const spotDir = path9.join(resolveMarketDir(dataDir), "spot");
1173
3114
  if (hasParquetPartitions(spotDir, "ticker")) {
1174
3115
  try {
1175
3116
  await conn.run("DROP VIEW IF EXISTS market.spot");
@@ -1191,7 +3132,7 @@ async function createMarketParquetViews(conn, dataDir) {
1191
3132
  }
1192
3133
  tablesKept.push("spot");
1193
3134
  }
1194
- const enrichedDir = path6.join(resolveMarketDir(dataDir), "enriched");
3135
+ const enrichedDir = path9.join(resolveMarketDir(dataDir), "enriched");
1195
3136
  if (hasEnrichedTickerFiles(enrichedDir)) {
1196
3137
  try {
1197
3138
  await conn.run("DROP VIEW IF EXISTS market.enriched");
@@ -1203,7 +3144,7 @@ async function createMarketParquetViews(conn, dataDir) {
1203
3144
  }
1204
3145
  await conn.run(
1205
3146
  `CREATE OR REPLACE VIEW market.enriched AS
1206
- SELECT * FROM read_parquet('${enrichedDir}/ticker=*/data.parquet', hive_partitioning=true)`
3147
+ SELECT * FROM read_parquet('${enrichedDir}/ticker=*/date=*/data.parquet', hive_partitioning=true)`
1207
3148
  );
1208
3149
  viewsCreated.push("enriched");
1209
3150
  } else {
@@ -1224,7 +3165,7 @@ async function createMarketParquetViews(conn, dataDir) {
1224
3165
  }
1225
3166
  await conn.run(
1226
3167
  `CREATE OR REPLACE VIEW market.enriched_context AS
1227
- SELECT * FROM read_parquet('${path6.join(enrichedDir, "context", "data.parquet")}')`
3168
+ SELECT * FROM read_parquet('${path9.join(enrichedDir, "context", "date=*", "data.parquet")}', hive_partitioning=true)`
1228
3169
  );
1229
3170
  viewsCreated.push("enriched_context");
1230
3171
  } else {
@@ -1289,7 +3230,7 @@ async function createMarketParquetViews(conn, dataDir) {
1289
3230
  }
1290
3231
 
1291
3232
  // src/db/json-migration.ts
1292
- import * as path7 from "path";
3233
+ import * as path10 from "path";
1293
3234
  function toDate2(value) {
1294
3235
  if (value instanceof Date) return value;
1295
3236
  if (typeof value === "object" && value !== null && "micros" in value) {
@@ -1366,7 +3307,7 @@ async function migrateProfiles(conn, blocksDir) {
1366
3307
  createdAt: createdAt.toISOString(),
1367
3308
  updatedAt: updatedAt.toISOString()
1368
3309
  };
1369
- const filePath = path7.join(blocksDir, blockId, "profiles", toFileSlug(strategyName) + ".json");
3310
+ const filePath = path10.join(blocksDir, blockId, "profiles", toFileSlug(strategyName) + ".json");
1370
3311
  await writeJsonFile(filePath, profileJson);
1371
3312
  }
1372
3313
  return rows.length;
@@ -1399,7 +3340,7 @@ async function migrateSyncMetadata(conn, blocksDir) {
1399
3340
  return rows.length;
1400
3341
  }
1401
3342
  async function migrateMarketImportMetadata(conn, dataDir) {
1402
- const existingFile = await readJsonFile(path7.join(dataDir, "market-meta", "sync-metadata.json"));
3343
+ const existingFile = await readJsonFile(path10.join(dataDir, "market-meta", "sync-metadata.json"));
1403
3344
  if (existingFile !== null) return 0;
1404
3345
  let rows;
1405
3346
  try {
@@ -1426,7 +3367,7 @@ async function migrateMarketImportMetadata(conn, dataDir) {
1426
3367
  }
1427
3368
  async function migrateFlatImportLog(conn, dataDir) {
1428
3369
  const existingFile = await readJsonFile(
1429
- path7.join(dataDir, "market-meta", "flat-import-log.json")
3370
+ path10.join(dataDir, "market-meta", "flat-import-log.json")
1430
3371
  );
1431
3372
  if (existingFile !== null) return 0;
1432
3373
  let rows;
@@ -1557,7 +3498,7 @@ async function waitForProcessExit(pid, timeoutMs) {
1557
3498
  if (!isProcessAlive(pid)) {
1558
3499
  return true;
1559
3500
  }
1560
- await new Promise((resolve2) => setTimeout(resolve2, 100));
3501
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
1561
3502
  }
1562
3503
  return !isProcessAlive(pid);
1563
3504
  }
@@ -1571,7 +3512,7 @@ async function tryRecoverLockByTerminatingStaleProcess(errorMessage, dbPath, for
1571
3512
  return false;
1572
3513
  }
1573
3514
  const normalizedDbPath = dbPath.replace(/\\/g, "/");
1574
- const normalizedDbDir = path8.dirname(normalizedDbPath);
3515
+ const normalizedDbDir = path11.dirname(normalizedDbPath);
1575
3516
  const isTradeblocksProcess = command.includes("tradeblocks-mcp") || command.includes("/mcp-server/server/index.js") || command.includes("packages/mcp-server/server/index.js") || command.includes("\\mcp-server\\server\\index.js") || command.includes("packages\\mcp-server\\server\\index.js");
1576
3517
  const normalizedCommand = command.replace(/\\/g, "/");
1577
3518
  const targetsSameDb = normalizedCommand.includes(normalizedDbPath) || normalizedCommand.includes(normalizedDbDir);
@@ -1605,16 +3546,16 @@ function resolveMarketDbPath(dataDir) {
1605
3546
  const args = process.argv;
1606
3547
  for (let i = 0; i < args.length; i++) {
1607
3548
  if (args[i] === "--market-db" && args[i + 1]) {
1608
- return path8.resolve(args[i + 1]);
3549
+ return path11.resolve(args[i + 1]);
1609
3550
  }
1610
3551
  }
1611
3552
  if (process.env.MARKET_DB_PATH) {
1612
- return path8.resolve(process.env.MARKET_DB_PATH);
3553
+ return path11.resolve(process.env.MARKET_DB_PATH);
1613
3554
  }
1614
- return path8.join(dataDir, "market.duckdb");
3555
+ return path11.join(dataDir, "market.duckdb");
1615
3556
  }
1616
3557
  async function attachMarketDb(conn, marketDbPath, mode) {
1617
- await fs4.mkdir(path8.dirname(marketDbPath), { recursive: true });
3558
+ await fs6.mkdir(path11.dirname(marketDbPath), { recursive: true });
1618
3559
  const readOnlyClause = mode === "read_only" ? " (READ_ONLY)" : "";
1619
3560
  const escapedPath = marketDbPath.replace(/'/g, "''");
1620
3561
  try {
@@ -1624,11 +3565,11 @@ async function attachMarketDb(conn, marketDbPath, mode) {
1624
3565
  if (msg.includes("corrupt") || msg.includes("Invalid") || msg.includes("cannot open")) {
1625
3566
  console.error(`market.duckdb appears corrupted at ${marketDbPath}. Recreating.`);
1626
3567
  try {
1627
- await fs4.unlink(marketDbPath);
3568
+ await fs6.unlink(marketDbPath);
1628
3569
  } catch {
1629
3570
  }
1630
3571
  try {
1631
- await fs4.unlink(marketDbPath + ".wal");
3572
+ await fs6.unlink(marketDbPath + ".wal");
1632
3573
  } catch {
1633
3574
  }
1634
3575
  await conn.run(`ATTACH '${escapedPath}' AS market${readOnlyClause}`);
@@ -1661,12 +3602,12 @@ async function openReadWriteConnection(dbPath, threads, memoryLimit) {
1661
3602
  await ensureReportingDataTable(connection);
1662
3603
  await ensureMutableMarketTables(connection);
1663
3604
  await ensureProfilesSchema(connection);
1664
- const dataDir = path8.dirname(dbPath);
3605
+ const dataDir = path11.dirname(dbPath);
1665
3606
  const dataRoot = getDataRoot(dataDir);
1666
3607
  await createMarketParquetViews(connection, dataRoot);
1667
3608
  await ensureMarketDataTables(connection);
1668
3609
  try {
1669
- const blocksDir = (await import("./sync-3UBC37VW.js")).getBlocksDir(dataRoot);
3610
+ const blocksDir = (await import("./sync-AWI764HE.js")).getBlocksDir(dataRoot);
1670
3611
  await migrateMetadataToJson(connection, dataRoot, blocksDir);
1671
3612
  } catch (err) {
1672
3613
  console.warn(
@@ -1712,7 +3653,7 @@ async function getConnection(dataDir) {
1712
3653
  if (connection) {
1713
3654
  return connection;
1714
3655
  }
1715
- const dbPath = path8.join(dataDir, "analytics.duckdb");
3656
+ const dbPath = path11.join(dataDir, "analytics.duckdb");
1716
3657
  const threads = process.env.DUCKDB_THREADS || defaultThreads();
1717
3658
  const memoryLimit = process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();
1718
3659
  storedDbPath = dbPath;
@@ -1789,7 +3730,7 @@ async function closeConnection() {
1789
3730
  async function upgradeToReadWrite(dataDir, options) {
1790
3731
  if (connectionMode === "read_write" && connection) return connection;
1791
3732
  await closeConnection();
1792
- const dbPath = storedDbPath || path8.join(dataDir, "analytics.duckdb");
3733
+ const dbPath = storedDbPath || path11.join(dataDir, "analytics.duckdb");
1793
3734
  const threads = storedThreads || process.env.DUCKDB_THREADS || defaultThreads();
1794
3735
  const memoryLimit = storedMemoryLimit || process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();
1795
3736
  if (!storedMarketDbPath) {
@@ -1861,11 +3802,57 @@ async function openMarketOnlyConnection(baseDir) {
1861
3802
  };
1862
3803
  return { conn, marketDbPath, close };
1863
3804
  }
1864
- async function openMarketParquetConnection(baseDir) {
3805
+ var DUCKDB_MEMORY_LIMIT_PATTERN = /^\d+(\.\d+)?(KB|MB|GB|TB|KiB|MiB|GiB|TiB)$/i;
3806
+ function validateMemoryLimit(value) {
3807
+ const trimmed = value.trim();
3808
+ if (!DUCKDB_MEMORY_LIMIT_PATTERN.test(trimmed)) {
3809
+ throw new Error(
3810
+ `Invalid DuckDB memory_limit ${JSON.stringify(value)}. Expected a number followed by a unit, e.g. "4GB", "512MB", or "8GiB" (units: KB, MB, GB, TB, KiB, MiB, GiB, TiB).`
3811
+ );
3812
+ }
3813
+ return trimmed;
3814
+ }
3815
+ function validateThreads(value) {
3816
+ if (!Number.isInteger(value) || value <= 0) {
3817
+ throw new Error(
3818
+ `Invalid DuckDB threads ${JSON.stringify(value)}. Expected a positive integer.`
3819
+ );
3820
+ }
3821
+ return value;
3822
+ }
3823
+ function resolveResourceBounds(options) {
3824
+ const resolved = {};
3825
+ const memoryLimitRaw = options?.memoryLimit ?? envValue("TRADEBLOCKS_DUCKDB_MEMORY_LIMIT");
3826
+ if (memoryLimitRaw !== void 0) {
3827
+ resolved.memoryLimit = validateMemoryLimit(memoryLimitRaw);
3828
+ }
3829
+ const threadsEnv = envValue("TRADEBLOCKS_DUCKDB_THREADS");
3830
+ const threadsRaw = options?.threads ?? (threadsEnv !== void 0 ? Number(threadsEnv) : void 0);
3831
+ if (threadsRaw !== void 0) {
3832
+ resolved.threads = validateThreads(threadsRaw);
3833
+ }
3834
+ return resolved;
3835
+ }
3836
+ function envValue(name) {
3837
+ const raw = process.env[name];
3838
+ if (raw === void 0 || raw.trim() === "") return void 0;
3839
+ return raw;
3840
+ }
3841
+ async function applyResourceBounds(conn, bounds) {
3842
+ if (bounds.memoryLimit !== void 0) {
3843
+ await conn.run(`SET memory_limit='${bounds.memoryLimit}'`);
3844
+ }
3845
+ if (bounds.threads !== void 0) {
3846
+ await conn.run(`SET threads=${bounds.threads}`);
3847
+ }
3848
+ }
3849
+ async function openMarketParquetConnection(baseDir, options) {
3850
+ const bounds = resolveResourceBounds(options);
1865
3851
  const memoryInstance = await DuckDBInstance.create(":memory:", {
1866
3852
  enable_external_access: "true"
1867
3853
  });
1868
3854
  const conn = await memoryInstance.connect();
3855
+ await applyResourceBounds(conn, bounds);
1869
3856
  await conn.run("CREATE SCHEMA IF NOT EXISTS market");
1870
3857
  const dataRoot = getDataRoot(baseDir);
1871
3858
  await createMarketParquetViews(conn, dataRoot);
@@ -1884,12 +3871,12 @@ async function openMarketParquetConnection(baseDir) {
1884
3871
  };
1885
3872
  return { conn, dataRoot, close };
1886
3873
  }
1887
- function openMarketReadOnlyConnection(baseDir) {
1888
- return openMarketParquetConnection(baseDir);
3874
+ function openMarketReadOnlyConnection(baseDir, options) {
3875
+ return openMarketParquetConnection(baseDir, options);
1889
3876
  }
1890
3877
  async function getReadOnlyConnection(dataDir) {
1891
3878
  if (connection) return connection;
1892
- const dbPath = path8.join(dataDir, "analytics.duckdb");
3879
+ const dbPath = path11.join(dataDir, "analytics.duckdb");
1893
3880
  const threads = process.env.DUCKDB_THREADS || defaultThreads();
1894
3881
  const memoryLimit = process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();
1895
3882
  storedDbPath = dbPath;
@@ -1915,14 +3902,14 @@ function getCurrentConnection() {
1915
3902
  }
1916
3903
 
1917
3904
  // src/sync/block-sync.ts
1918
- import * as fs8 from "fs/promises";
1919
- import * as path11 from "path";
3905
+ import * as fs10 from "fs/promises";
3906
+ import * as path14 from "path";
1920
3907
 
1921
3908
  // src/sync/hasher.ts
1922
3909
  import * as crypto from "crypto";
1923
- import * as fs5 from "fs/promises";
3910
+ import * as fs7 from "fs/promises";
1924
3911
  async function hashFileContent(filePath) {
1925
- const buffer = await fs5.readFile(filePath);
3912
+ const buffer = await fs7.readFile(filePath);
1926
3913
  return crypto.createHash("sha256").update(buffer).digest("hex");
1927
3914
  }
1928
3915
 
@@ -2072,12 +4059,12 @@ function marketTickerDateKey(ticker, date) {
2072
4059
  }
2073
4060
 
2074
4061
  // src/utils/block-loader.ts
2075
- import * as fs7 from "fs/promises";
2076
- import * as path10 from "path";
4062
+ import * as fs9 from "fs/promises";
4063
+ import * as path13 from "path";
2077
4064
 
2078
4065
  // src/utils/csv-discovery.ts
2079
- import * as fs6 from "fs/promises";
2080
- import * as path9 from "path";
4066
+ import * as fs8 from "fs/promises";
4067
+ import * as path12 from "path";
2081
4068
  function parseCSVLine(line) {
2082
4069
  const result = [];
2083
4070
  let current = "";
@@ -2102,7 +4089,7 @@ function parseCSVLine(line) {
2102
4089
  return result;
2103
4090
  }
2104
4091
  async function readCsvHeaders(filePath) {
2105
- const content = await fs6.readFile(filePath, "utf-8");
4092
+ const content = await fs8.readFile(filePath, "utf-8");
2106
4093
  const firstLine = content.replace(/^\uFEFF/, "").split("\n")[0] || "";
2107
4094
  return parseCSVLine(firstLine).map((h) => h.toLowerCase().trim());
2108
4095
  }
@@ -2178,7 +4165,7 @@ async function discoverCsvFiles(folderPath) {
2178
4165
  const mappings = {};
2179
4166
  const unrecognized = [];
2180
4167
  try {
2181
- const entries = await fs6.readdir(folderPath, { withFileTypes: true });
4168
+ const entries = await fs8.readdir(folderPath, { withFileTypes: true });
2182
4169
  const csvFiles = entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".csv")).map((e) => e.name);
2183
4170
  if (csvFiles.includes("tradelog.csv")) {
2184
4171
  mappings.tradelog = "tradelog.csv";
@@ -2202,7 +4189,7 @@ async function discoverCsvFiles(folderPath) {
2202
4189
  if (csvFile === "tradelog.csv" || csvFile === "dailylog.csv" || csvFile === "reportinglog.csv" || csvFile === mappings.reportinglog) {
2203
4190
  continue;
2204
4191
  }
2205
- const csvPath = path9.join(folderPath, csvFile);
4192
+ const csvPath = path12.join(folderPath, csvFile);
2206
4193
  const detectedType = await detectCsvType(csvPath);
2207
4194
  if (detectedType) {
2208
4195
  if (detectedType === "tradelog" && !mappings.tradelog) {
@@ -2398,8 +4385,8 @@ function convertToDailyLogEntry(raw, blockId) {
2398
4385
  }
2399
4386
  }
2400
4387
  async function loadTrades(blockPath, filename = "tradelog.csv", blockId) {
2401
- const tradelogPath = path10.join(blockPath, filename);
2402
- const content = await fs7.readFile(tradelogPath, "utf-8");
4388
+ const tradelogPath = path13.join(blockPath, filename);
4389
+ const content = await fs9.readFile(tradelogPath, "utf-8");
2403
4390
  const records = parseCSV(content);
2404
4391
  const trades = [];
2405
4392
  for (const record of records) {
@@ -2416,10 +4403,10 @@ async function loadTrades(blockPath, filename = "tradelog.csv", blockId) {
2416
4403
  return trades;
2417
4404
  }
2418
4405
  async function loadDailyLogs(blockPath, blockId, filename = "dailylog.csv") {
2419
- const dailylogPath = path10.join(blockPath, filename);
4406
+ const dailylogPath = path13.join(blockPath, filename);
2420
4407
  try {
2421
- await fs7.access(dailylogPath);
2422
- const content = await fs7.readFile(dailylogPath, "utf-8");
4408
+ await fs9.access(dailylogPath);
4409
+ const content = await fs9.readFile(dailylogPath, "utf-8");
2423
4410
  const records = parseCSV(content);
2424
4411
  const entries = [];
2425
4412
  for (const record of records) {
@@ -2436,12 +4423,12 @@ async function loadDailyLogs(blockPath, blockId, filename = "dailylog.csv") {
2436
4423
  }
2437
4424
  async function loadBlock(baseDir, blockId) {
2438
4425
  const blocksDir = resolveBlocksBaseDir(baseDir);
2439
- const blockPath = path10.join(blocksDir, blockId);
4426
+ const blockPath = path13.join(blocksDir, blockId);
2440
4427
  const { mappings } = await discoverCsvFiles(blockPath);
2441
4428
  const tradelogFilename = mappings.tradelog || "tradelog.csv";
2442
- const tradelogPath = path10.join(blockPath, tradelogFilename);
4429
+ const tradelogPath = path13.join(blockPath, tradelogFilename);
2443
4430
  try {
2444
- await fs7.access(tradelogPath);
4431
+ await fs9.access(tradelogPath);
2445
4432
  } catch {
2446
4433
  throw new Error(`Block not found or missing tradelog: ${blockId}`);
2447
4434
  }
@@ -2542,7 +4529,7 @@ async function listBlocks(baseDir) {
2542
4529
  }
2543
4530
  const syncMeta = /* @__PURE__ */ new Map();
2544
4531
  if (isParquetMode()) {
2545
- const metaEntries = await fs7.readdir(blocksDir, { withFileTypes: true });
4532
+ const metaEntries = await fs9.readdir(blocksDir, { withFileTypes: true });
2546
4533
  for (const entry of metaEntries) {
2547
4534
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2548
4535
  const meta = await getSyncMetadataJson(entry.name, blocksDir);
@@ -2565,7 +4552,7 @@ async function listBlocks(baseDir) {
2565
4552
  });
2566
4553
  }
2567
4554
  }
2568
- const entries = await fs7.readdir(blocksDir, { withFileTypes: true });
4555
+ const entries = await fs9.readdir(blocksDir, { withFileTypes: true });
2569
4556
  const blockFolders = /* @__PURE__ */ new Set();
2570
4557
  for (const entry of entries) {
2571
4558
  if (!entry.isDirectory()) continue;
@@ -2609,7 +4596,7 @@ async function listBlocks(baseDir) {
2609
4596
  }
2610
4597
  blocks.push(info);
2611
4598
  } else if (!stats) {
2612
- const blockPath = path10.join(blocksDir, blockId);
4599
+ const blockPath = path13.join(blocksDir, blockId);
2613
4600
  try {
2614
4601
  const { mappings } = await discoverCsvFiles(blockPath);
2615
4602
  if (mappings.tradelog) {
@@ -2677,16 +4664,16 @@ function convertToReportingTrade(raw) {
2677
4664
  }
2678
4665
  async function loadReportingLog(baseDir, blockId) {
2679
4666
  const blocksDir = resolveBlocksBaseDir(baseDir);
2680
- const blockPath = path10.join(blocksDir, blockId);
4667
+ const blockPath = path13.join(blocksDir, blockId);
2681
4668
  const { mappings } = await discoverCsvFiles(blockPath);
2682
4669
  const filename = mappings.reportinglog || "reportinglog.csv";
2683
- const reportingLogPath = path10.join(blockPath, filename);
4670
+ const reportingLogPath = path13.join(blockPath, filename);
2684
4671
  try {
2685
- await fs7.access(reportingLogPath);
4672
+ await fs9.access(reportingLogPath);
2686
4673
  } catch {
2687
4674
  throw new Error(`reportinglog.csv not found in block: ${blockId}`);
2688
4675
  }
2689
- const content = await fs7.readFile(reportingLogPath, "utf-8");
4676
+ const content = await fs9.readFile(reportingLogPath, "utf-8");
2690
4677
  const records = parseCSV(content);
2691
4678
  const trades = [];
2692
4679
  for (const record of records) {
@@ -2756,11 +4743,11 @@ async function importCsv(baseDir, options) {
2756
4743
  let { csvType = "tradelog" } = options;
2757
4744
  const blocksDir = resolveBlocksBaseDir(baseDir);
2758
4745
  try {
2759
- await fs7.access(csvPath);
4746
+ await fs9.access(csvPath);
2760
4747
  } catch {
2761
4748
  throw new Error(`CSV file not found: ${csvPath}`);
2762
4749
  }
2763
- const content = await fs7.readFile(csvPath, "utf-8");
4750
+ const content = await fs9.readFile(csvPath, "utf-8");
2764
4751
  const records = parseCSV(content);
2765
4752
  if (csvType === "tradelog" && records.length > 0) {
2766
4753
  const headers = Object.keys(records[0]);
@@ -2777,9 +4764,9 @@ async function importCsv(baseDir, options) {
2777
4764
  if (!blockId) {
2778
4765
  throw new Error("Could not derive a valid block ID from the filename or provided name");
2779
4766
  }
2780
- const blockPath = path10.join(blocksDir, blockId);
4767
+ const blockPath = path13.join(blocksDir, blockId);
2781
4768
  try {
2782
- await fs7.access(blockPath);
4769
+ await fs9.access(blockPath);
2783
4770
  throw new Error(
2784
4771
  `Block "${blockId}" already exists. Use a different blockName or delete the existing block first.`
2785
4772
  );
@@ -2788,10 +4775,10 @@ async function importCsv(baseDir, options) {
2788
4775
  throw error;
2789
4776
  }
2790
4777
  }
2791
- await fs7.mkdir(blockPath, { recursive: true });
4778
+ await fs9.mkdir(blockPath, { recursive: true });
2792
4779
  const targetFilename = csvType === "tradelog" ? "tradelog.csv" : csvType === "dailylog" ? "dailylog.csv" : "reportinglog.csv";
2793
- const targetPath = path10.join(blockPath, targetFilename);
2794
- await fs7.copyFile(csvPath, targetPath);
4780
+ const targetPath = path13.join(blockPath, targetFilename);
4781
+ await fs9.copyFile(csvPath, targetPath);
2795
4782
  let dateRange = {
2796
4783
  start: null,
2797
4784
  end: null
@@ -3036,7 +5023,7 @@ function versionedHash(hash) {
3036
5023
  return `${hash}:${PARSE_VERSION}`;
3037
5024
  }
3038
5025
  async function syncBlockInternal(conn, blockId, blockPath) {
3039
- const blocksDir = path11.dirname(blockPath);
5026
+ const blocksDir = path14.dirname(blockPath);
3040
5027
  try {
3041
5028
  const existingMetadata = await getSyncMetadata(conn, blockId, blocksDir);
3042
5029
  const tradelogFilename = await findTradelogFile(blockPath);
@@ -3060,12 +5047,12 @@ async function syncBlockInternal(conn, blockId, blockPath) {
3060
5047
  error: "No tradelog CSV found in block"
3061
5048
  };
3062
5049
  }
3063
- const tradelogPath = path11.join(blockPath, tradelogFilename);
5050
+ const tradelogPath = path14.join(blockPath, tradelogFilename);
3064
5051
  const tradelogHash = versionedHash(await hashFileContent(tradelogPath));
3065
5052
  if (existingMetadata && existingMetadata.tradelog_hash === tradelogHash) {
3066
5053
  const optionalLogs = await findOptionalLogFiles(blockPath);
3067
5054
  if (optionalLogs.reportinglog) {
3068
- const reportinglogPath = path11.join(blockPath, optionalLogs.reportinglog);
5055
+ const reportinglogPath = path14.join(blockPath, optionalLogs.reportinglog);
3069
5056
  const reportinglogHash = versionedHash(await hashFileContent(reportinglogPath));
3070
5057
  if (existingMetadata.reportinglog_hash !== reportinglogHash) {
3071
5058
  } else {
@@ -3081,7 +5068,7 @@ async function syncBlockInternal(conn, blockId, blockPath) {
3081
5068
  await conn.run("BEGIN TRANSACTION");
3082
5069
  try {
3083
5070
  await conn.run("DELETE FROM trades.trade_data WHERE block_id = $1", [blockId]);
3084
- const csvContent = await fs8.readFile(tradelogPath, "utf-8");
5071
+ const csvContent = await fs10.readFile(tradelogPath, "utf-8");
3085
5072
  const records = parseCSV2(csvContent);
3086
5073
  const batchSize = 500;
3087
5074
  for (let i = 0; i < records.length; i += batchSize) {
@@ -3093,7 +5080,7 @@ async function syncBlockInternal(conn, blockId, blockPath) {
3093
5080
  if (optionalLogs.dailylog) {
3094
5081
  try {
3095
5082
  dailylogHash = versionedHash(
3096
- await hashFileContent(path11.join(blockPath, optionalLogs.dailylog))
5083
+ await hashFileContent(path14.join(blockPath, optionalLogs.dailylog))
3097
5084
  );
3098
5085
  } catch {
3099
5086
  }
@@ -3101,15 +5088,15 @@ async function syncBlockInternal(conn, blockId, blockPath) {
3101
5088
  if (optionalLogs.reportinglog) {
3102
5089
  try {
3103
5090
  reportinglogHash = versionedHash(
3104
- await hashFileContent(path11.join(blockPath, optionalLogs.reportinglog))
5091
+ await hashFileContent(path14.join(blockPath, optionalLogs.reportinglog))
3105
5092
  );
3106
5093
  } catch {
3107
5094
  }
3108
5095
  }
3109
5096
  await conn.run("DELETE FROM trades.reporting_data WHERE block_id = $1", [blockId]);
3110
5097
  if (optionalLogs.reportinglog && reportinglogHash) {
3111
- const reportingPath = path11.join(blockPath, optionalLogs.reportinglog);
3112
- const reportingContent = await fs8.readFile(reportingPath, "utf-8");
5098
+ const reportingPath = path14.join(blockPath, optionalLogs.reportinglog);
5099
+ const reportingContent = await fs10.readFile(reportingPath, "utf-8");
3113
5100
  const reportingRecords = parseCSV2(reportingContent);
3114
5101
  const reportingTrades = [];
3115
5102
  const reportingTickers = [];
@@ -3177,7 +5164,7 @@ async function detectBlockChanges(conn, baseDir) {
3177
5164
  const toSync = [];
3178
5165
  const toDelete = [];
3179
5166
  const syncedBlockIds = new Set(await getAllSyncedBlockIds(conn, baseDir));
3180
- const entries = await fs8.readdir(baseDir, { withFileTypes: true });
5167
+ const entries = await fs10.readdir(baseDir, { withFileTypes: true });
3181
5168
  const folderNames = /* @__PURE__ */ new Set();
3182
5169
  for (const entry of entries) {
3183
5170
  if (!entry.isDirectory()) continue;
@@ -3186,7 +5173,7 @@ async function detectBlockChanges(conn, baseDir) {
3186
5173
  continue;
3187
5174
  const blockId = entry.name;
3188
5175
  folderNames.add(blockId);
3189
- const blockPath = path11.join(baseDir, blockId);
5176
+ const blockPath = path14.join(baseDir, blockId);
3190
5177
  if (!syncedBlockIds.has(blockId)) {
3191
5178
  const tradelog = await findTradelogFile(blockPath);
3192
5179
  if (tradelog) {
@@ -3200,7 +5187,7 @@ async function detectBlockChanges(conn, baseDir) {
3200
5187
  continue;
3201
5188
  }
3202
5189
  try {
3203
- const tradelogPath = path11.join(blockPath, tradelogFilename);
5190
+ const tradelogPath = path14.join(blockPath, tradelogFilename);
3204
5191
  const currentHash = versionedHash(await hashFileContent(tradelogPath));
3205
5192
  const metadata = await getSyncMetadata(conn, blockId, baseDir);
3206
5193
  if (!metadata || metadata.tradelog_hash !== currentHash) {
@@ -3208,7 +5195,7 @@ async function detectBlockChanges(conn, baseDir) {
3208
5195
  } else {
3209
5196
  const optionalLogs = await findOptionalLogFiles(blockPath);
3210
5197
  if (optionalLogs.reportinglog) {
3211
- const reportinglogPath = path11.join(blockPath, optionalLogs.reportinglog);
5198
+ const reportinglogPath = path14.join(blockPath, optionalLogs.reportinglog);
3212
5199
  const reportingHash = versionedHash(await hashFileContent(reportinglogPath));
3213
5200
  if (metadata.reportinglog_hash !== reportingHash) {
3214
5201
  toSync.push(blockId);
@@ -3250,7 +5237,7 @@ function setBlocksDir(dir) {
3250
5237
  }
3251
5238
  function getBlocksDir(baseDir) {
3252
5239
  if (_blocksDir) return _blocksDir;
3253
- const nestedBlocksDir = path12.join(baseDir, "blocks");
5240
+ const nestedBlocksDir = path15.join(baseDir, "blocks");
3254
5241
  return existsSync2(nestedBlocksDir) ? nestedBlocksDir : baseDir;
3255
5242
  }
3256
5243
  async function syncAllBlocks(baseDir) {
@@ -3269,7 +5256,7 @@ async function syncAllBlocks(baseDir) {
3269
5256
  }
3270
5257
  }
3271
5258
  for (const blockId of toSync) {
3272
- const blockPath = path12.join(blocksDir, blockId);
5259
+ const blockPath = path15.join(blocksDir, blockId);
3273
5260
  const result = await syncBlockInternal(conn, blockId, blockPath);
3274
5261
  results.push(result);
3275
5262
  if (result.status === "error" && result.error) {
@@ -3288,9 +5275,9 @@ async function syncAllBlocks(baseDir) {
3288
5275
  async function syncBlock(blockId, baseDir) {
3289
5276
  const conn = await getConnection(baseDir);
3290
5277
  const blocksDir = getBlocksDir(baseDir);
3291
- const blockPath = path12.join(blocksDir, blockId);
5278
+ const blockPath = path15.join(blocksDir, blockId);
3292
5279
  try {
3293
- await fs9.access(blockPath);
5280
+ await fs11.access(blockPath);
3294
5281
  } catch {
3295
5282
  const existing = await getSyncMetadata(conn, blockId, blocksDir);
3296
5283
  if (existing) {
@@ -3305,6 +5292,35 @@ async function syncBlock(blockId, baseDir) {
3305
5292
  export {
3306
5293
  ensureMutableMarketTables,
3307
5294
  ensureMarketDataTables,
5295
+ CANONICAL_JSON_VERSION,
5296
+ canonicalJson,
5297
+ canonicalJsonBytes,
5298
+ addressCanonicalJson,
5299
+ addressBytes,
5300
+ parseCanonicalJsonAddress,
5301
+ parseSha256Address,
5302
+ ContentObjectCollisionError,
5303
+ ContentObjectStore,
5304
+ MARKET_DATASETS,
5305
+ isRealMarketSessionDate,
5306
+ marketDatasetDefinition,
5307
+ canonicalPartitionDataset,
5308
+ validatePartitionIdentity,
5309
+ canonicalPartitionRelativePath,
5310
+ PARTITION_COMMIT_RECEIPT_KIND,
5311
+ PARTITION_COMMIT_RECEIPT_VERSION,
5312
+ PARTITION_COMMIT_EVENT_KIND,
5313
+ PARTITION_COMMIT_EVENT_VERSION,
5314
+ INTERNAL_HISTORICAL_PARTITION_ADOPTION,
5315
+ setPartitionCommitTestFault,
5316
+ PartitionFileIntegrityError,
5317
+ FilePartitionCommitStore,
5318
+ publishRefreshCompletionAuthority,
5319
+ verifyRefreshCompletionAuthority,
5320
+ PartitionFilePublicationError,
5321
+ runPartitionCommitAttempt,
5322
+ activePartitionCommitAttempt,
5323
+ capturePartitionCommitReceipt,
3308
5324
  resolveDbPath,
3309
5325
  setDataRoot,
3310
5326
  getDataRoot,
@@ -3321,8 +5337,12 @@ export {
3321
5337
  writeQuoteMinutesPartition,
3322
5338
  writeOiDailyPartition,
3323
5339
  writeEnrichedTickerFile,
5340
+ writeEnrichedTickerPartition,
3324
5341
  writeEnrichedContext,
5342
+ writeEnrichedContextPartition,
3325
5343
  isParquetMode,
5344
+ ParquetProvenanceOrphanError,
5345
+ UnmanifestedParquetWriteError,
3326
5346
  writeParquetAtomic,
3327
5347
  writeParquetPartition,
3328
5348
  writeJsonFile,
@@ -3369,12 +5389,6 @@ export {
3369
5389
  getProfile,
3370
5390
  listProfiles,
3371
5391
  deleteProfile,
3372
- escapeSqlLiteral,
3373
- readParquetFilesSql,
3374
- describeReadParquetColumns,
3375
- describeQueryColumns,
3376
- quoteParquetCanonicalProjection,
3377
- quoteParquetCanonicalWriteProjection,
3378
5392
  createMarketParquetViews,
3379
5393
  migrateMetadataToJson,
3380
5394
  getConnection,
@@ -3393,4 +5407,4 @@ export {
3393
5407
  loadReportingLog,
3394
5408
  importCsv
3395
5409
  };
3396
- //# sourceMappingURL=chunk-4HZLVUOZ.js.map
5410
+ //# sourceMappingURL=chunk-5HBZ3TZM.js.map