@semiont/content 0.5.31 → 0.5.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,12 +7,24 @@ import path from "path";
7
7
 
8
8
  // src/git-staging.ts
9
9
  import { execFile } from "child_process";
10
+ import { resolve } from "path";
10
11
  import { promisify } from "util";
11
- import { recordGitCommand } from "@semiont/observability";
12
+ import { recordGitCommand, recordGitStagingFailure } from "@semiont/observability";
12
13
  var run = promisify(execFile);
14
+ var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 1600];
15
+ var isIndexLockContention = (error) => typeof error === "object" && error !== null && error.code === 128 && String(error.stderr ?? "").includes("index.lock");
13
16
  var DEFAULT_FLUSH_MS = 250;
14
17
  var DEFAULT_MAX_WAIT_MS = 2e3;
18
+ var stagers = /* @__PURE__ */ new Map();
15
19
  function createStager(cwd, options = {}) {
20
+ const key = resolve(cwd);
21
+ const existing = stagers.get(key);
22
+ if (existing) return existing;
23
+ const stager = buildStager(key, options);
24
+ stagers.set(key, stager);
25
+ return stager;
26
+ }
27
+ function buildStager(cwd, options = {}) {
16
28
  const flushMs = options.flushMs ?? DEFAULT_FLUSH_MS;
17
29
  const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
18
30
  const queued = /* @__PURE__ */ new Set();
@@ -23,7 +35,16 @@ function createStager(cwd, options = {}) {
23
35
  const git = async (args) => {
24
36
  const started = performance.now();
25
37
  try {
26
- await run("git", args, { cwd });
38
+ for (let attempt = 0; ; attempt++) {
39
+ try {
40
+ await run("git", args, { cwd });
41
+ return;
42
+ } catch (error) {
43
+ const last = attempt >= LOCK_RETRY_DELAYS_MS.length;
44
+ if (last || !isIndexLockContention(error)) throw error;
45
+ await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAYS_MS[attempt]));
46
+ }
47
+ }
27
48
  } finally {
28
49
  recordGitCommand(args[0] ?? "git", performance.now() - started);
29
50
  }
@@ -44,7 +65,17 @@ function createStager(cwd, options = {}) {
44
65
  const batch = [...queued];
45
66
  queued.clear();
46
67
  oldestAt = void 0;
47
- return serialize(() => git(["add", ...batch]));
68
+ return serialize(
69
+ () => git(["add", ...batch]).catch((error) => {
70
+ const lock = isIndexLockContention(error);
71
+ if (lock) {
72
+ for (const path4 of batch) queued.add(path4);
73
+ if (oldestAt === void 0) oldestAt = Date.now();
74
+ arm();
75
+ }
76
+ recordGitStagingFailure(lock ? "index-lock" : "other");
77
+ })
78
+ );
48
79
  };
49
80
  const arm = () => {
50
81
  clearTimer();
@@ -64,7 +95,13 @@ function createStager(cwd, options = {}) {
64
95
  arm();
65
96
  },
66
97
  run(args) {
67
- return drain().then(() => serialize(() => git(args)));
98
+ return drain().then(
99
+ () => serialize(
100
+ () => git(args).catch((error) => {
101
+ recordGitStagingFailure(isIndexLockContention(error) ? "index-lock" : "other");
102
+ })
103
+ )
104
+ );
68
105
  },
69
106
  flush() {
70
107
  return drain();
@@ -73,6 +110,7 @@ function createStager(cwd, options = {}) {
73
110
  return queued.size;
74
111
  },
75
112
  async dispose() {
113
+ stagers.delete(cwd);
76
114
  await drain();
77
115
  disposed = true;
78
116
  clearTimer();
@@ -598,11 +636,11 @@ function toRgb(image) {
598
636
  var IMAGE_RESOLVE_TIMEOUT_MS = 3e4;
599
637
  function resolveImage(page, ref) {
600
638
  const scope = ref.startsWith("g_") ? page.commonObjs : page.objs;
601
- return new Promise((resolve) => {
602
- const timer = setTimeout(() => resolve(null), IMAGE_RESOLVE_TIMEOUT_MS);
639
+ return new Promise((resolve2) => {
640
+ const timer = setTimeout(() => resolve2(null), IMAGE_RESOLVE_TIMEOUT_MS);
603
641
  const settle = (value) => {
604
642
  clearTimeout(timer);
605
- resolve(value);
643
+ resolve2(value);
606
644
  };
607
645
  try {
608
646
  scope.get(ref, settle);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/working-tree-store.ts","../src/git-staging.ts","../src/checksum.ts","../src/text-extractor.ts","../src/pdf-extractor.ts","../src/extract-pdf-text-layer.ts","../src/pdfjs-assets.ts","../src/pdf-tables.ts","../src/pdf-page-images.ts","../src/png-encode.ts","../src/ocr.ts","../src/ocr-geometry.ts","../src/anchored-text-store.ts","../src/representation-reads.ts"],"sourcesContent":["/**\n * Files in the project working tree, addressed by file:// URI —\n * \"file://docs/overview.md\" is {projectRoot}/docs/overview.md.\n *\n * `store` writes bytes the caller supplies; `register` adopts a file already\n * on disk. Both stream to hash, neither holds a representation in memory.\n */\n\nimport { promises as fs, createReadStream, createWriteStream } from 'fs';\nimport { createHash, randomUUID } from 'crypto';\nimport { Readable } from 'stream';\nimport { pipeline } from 'stream/promises';\nimport path from 'path';\nimport type { SemiontProject } from '@semiont/core/node';\nimport type { Logger, StoredResource } from '@semiont/core';\nimport { createStager, type Stager, type StagerOptions } from './git-staging.js';\n\n\n/** sha256 + byte count over a chunk stream — one definition for both write paths. */\nfunction hashingTap() {\n const hash = createHash('sha256');\n let byteSize = 0;\n return {\n update(chunk: Buffer): void {\n hash.update(chunk);\n byteSize += chunk.length;\n },\n get byteSize(): number {\n return byteSize;\n },\n digest(): string {\n return hash.digest('hex');\n },\n };\n}\n\nexport class WorkingTreeStore {\n private projectRoot: string;\n private gitSync: boolean;\n private logger?: Logger;\n\n private _stager?: Stager;\n private readonly staging: StagerOptions;\n\n /** `staging` is policy — how stale the index may get is the caller's call. */\n constructor(project: SemiontProject, logger?: Logger, staging: StagerOptions = {}) {\n this.projectRoot = project.root;\n this.gitSync = project.gitSync;\n this.logger = logger;\n this.staging = staging;\n }\n\n /** Created on first use — importers of this package may never stage. */\n private stager(): Stager {\n if (!this._stager) this._stager = createStager(this.projectRoot, this.staging);\n return this._stager;\n }\n\n /** Stage everything pending now — for a caller that wants the index current. */\n flushStaging(): Promise<void> {\n return this._stager ? this._stager.flush() : Promise.resolve();\n }\n\n /** Drain and stop. A stopped process must leave nothing unstaged. */\n async dispose(): Promise<void> {\n if (this._stager) await this._stager.dispose();\n }\n\n private shouldRunGit(noGit?: boolean): boolean {\n return this.gitSync && !noGit;\n }\n\n /**\n * Write bytes to the path storageUri names, whole or streamed.\n *\n * Atomic: bytes land in a temp file and are renamed into place only once\n * complete and once `expectedChecksum`, when given, agrees. A mismatch or a\n * torn stream leaves the target untouched, so `register` can never find\n * partial bytes an event names.\n *\n * @throws ChecksumMismatchError when expectedChecksum disagrees with the body\n */\n async store(\n content: Buffer | Readable,\n storageUri: string,\n options?: { noGit?: boolean; expectedChecksum?: string },\n ): Promise<StoredResource> {\n const filePath = this.resolveUri(storageUri);\n const source = Buffer.isBuffer(content) ? Readable.from([content]) : content;\n\n this.logger?.debug('Storing resource', { storageUri });\n\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tempPath = `${filePath}.${randomUUID()}.tmp`;\n const tap = hashingTap();\n\n try {\n await pipeline(\n source,\n async function* (chunks: AsyncIterable<Buffer>) {\n for await (const chunk of chunks) {\n tap.update(chunk);\n yield chunk;\n }\n },\n createWriteStream(tempPath),\n );\n\n const checksum = tap.digest();\n const byteSize = tap.byteSize;\n if (options?.expectedChecksum !== undefined && options.expectedChecksum !== checksum) {\n throw new ChecksumMismatchError(storageUri, options.expectedChecksum, checksum);\n }\n await fs.rename(tempPath, filePath);\n\n if (this.shouldRunGit(options?.noGit)) {\n this.stager().add(filePath);\n }\n\n this.logger?.info('Resource stored', { storageUri, checksum, byteSize });\n\n return {\n storageUri,\n checksum,\n byteSize,\n created: new Date().toISOString(),\n };\n } catch (error) {\n await fs.rm(tempPath, { force: true });\n throw error;\n }\n }\n\n /**\n * Adopt a file already on disk: stream it to hash it, then stage it.\n *\n * @throws ChecksumMismatchError if expectedChecksum is given and disagrees\n */\n async register(storageUri: string, expectedChecksum?: string, options?: { noGit?: boolean }): Promise<StoredResource> {\n const filePath = this.resolveUri(storageUri);\n\n this.logger?.debug('Registering resource', { storageUri });\n\n // Streamed, never read whole: this runs in the same process that streamed\n // the upload in, and a `readFile` would undo that bound.\n const tap = hashingTap();\n for await (const chunk of createReadStream(filePath)) {\n tap.update(chunk as Buffer);\n }\n const checksum = tap.digest();\n\n if (expectedChecksum !== undefined && checksum !== expectedChecksum) {\n throw new ChecksumMismatchError(storageUri, expectedChecksum, checksum);\n }\n\n if (this.shouldRunGit(options?.noGit)) {\n this.stager().add(filePath);\n }\n\n const byteSize = tap.byteSize;\n this.logger?.info('Resource registered', { storageUri, checksum, byteSize });\n\n return {\n storageUri,\n checksum,\n byteSize,\n created: new Date().toISOString(),\n };\n }\n\n /**\n * The same bytes as `retrieve`, streamed. Lazy: a missing file surfaces as\n * an `error` event on the stream, not a rejected promise — callers needing\n * that up front should resolve the descriptor first.\n */\n retrieveStream(storageUri: string): Readable {\n return createReadStream(this.resolveUri(storageUri));\n }\n\n async retrieve(storageUri: string): Promise<Buffer> {\n const filePath = this.resolveUri(storageUri);\n try {\n return await fs.readFile(filePath);\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n throw new Error(`Resource not found: ${storageUri}`);\n }\n throw error;\n }\n }\n\n /** `git mv` when the project syncs git, `fs.rename` otherwise. */\n async move(fromUri: string, toUri: string, options?: { noGit?: boolean }): Promise<void> {\n const fromPath = this.resolveUri(fromUri);\n const toPath = this.resolveUri(toUri);\n\n this.logger?.debug('Moving resource', { fromUri, toUri });\n\n await fs.mkdir(path.dirname(toPath), { recursive: true });\n\n if (this.shouldRunGit(options?.noGit)) {\n await this.stager().run(['mv', fromPath, toPath]);\n } else {\n await fs.rename(fromPath, toPath);\n }\n\n this.logger?.info('Resource moved', { fromUri, toUri });\n }\n\n /** @param options.keepFile - Drop from the index only; leave the file on disk. */\n async remove(storageUri: string, options?: { noGit?: boolean; keepFile?: boolean }): Promise<void> {\n const filePath = this.resolveUri(storageUri);\n const keepFile = options?.keepFile ?? false;\n\n this.logger?.debug('Removing resource', { storageUri, keepFile });\n\n const useGit = this.shouldRunGit(options?.noGit);\n\n if (useGit) {\n const gitArgs = keepFile\n ? ['rm', '--cached', filePath]\n : ['rm', filePath];\n await this.stager().run(gitArgs);\n this.logger?.info('Resource removed', { storageUri, keepFile, git: true });\n return;\n }\n\n if (keepFile) {\n this.logger?.info('Resource removed from index (file kept on disk)', { storageUri });\n return;\n }\n\n try {\n await fs.unlink(filePath);\n this.logger?.info('Resource removed', { storageUri });\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n this.logger?.warn('Resource file already absent', { storageUri });\n return;\n }\n throw error;\n }\n }\n\n resolveUri(storageUri: string): string {\n if (!storageUri.startsWith('file://')) {\n throw new Error(`Invalid storage URI (must start with file://): ${storageUri}`);\n }\n const relativePath = storageUri.slice('file://'.length);\n return path.join(this.projectRoot, relativePath);\n }\n}\n\n/** The file on disk is not the file the checksum names. */\nexport class ChecksumMismatchError extends Error {\n constructor(\n readonly storageUri: string,\n readonly expected: string,\n readonly actual: string,\n ) {\n super(\n `Checksum mismatch for ${storageUri}: expected ${expected.slice(0, 8)}... but got ${actual.slice(0, 8)}...\\n` +\n `The file on disk differs from the recorded checksum. Has it been modified since staging?`\n );\n this.name = 'ChecksumMismatchError';\n }\n}\n","/**\n * Deferred, deduped `git add`. The index is for humans who commit by hand, so\n * it must be current within seconds, not after every change.\n *\n * Serialized per repo — git's index is single-writer, and concurrent `git add`\n * fails on `index.lock` rather than retrying. Created on first use, never at\n * import: consumers of this package may never stage anything.\n */\n\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { recordGitCommand } from '@semiont/observability';\n\nconst run = promisify(execFile);\n\nexport interface StagerOptions {\n /** Quiet period after the last change before staging. */\n flushMs?: number;\n /** Ceiling on staleness: stage this long after the OLDEST pending path even\n * if changes keep arriving. Without it a continuous append stream resets\n * the debounce forever and the index never updates. */\n maxWaitMs?: number;\n}\n\nexport interface Stager {\n /** Queue a path. Returns immediately; deduped against what is pending. */\n add(path: string): void;\n /** Run an order-sensitive command (`mv`, `rm`): pending adds flush first,\n * then this runs alone — it must not overtake the adds it depends on. */\n run(args: string[]): Promise<void>;\n /** Stage everything pending now. */\n flush(): Promise<void>;\n /** Paths queued and not yet staged. */\n pending(): number;\n /** Drain and stop. A stopped process must leave nothing unstaged. */\n dispose(): Promise<void>;\n}\n\nconst DEFAULT_FLUSH_MS = 250;\nconst DEFAULT_MAX_WAIT_MS = 2_000;\n\nexport function createStager(cwd: string, options: StagerOptions = {}): Stager {\n const flushMs = options.flushMs ?? DEFAULT_FLUSH_MS;\n const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;\n\n const queued = new Set<string>();\n let timer: NodeJS.Timeout | undefined;\n let oldestAt: number | undefined;\n let inFlight: Promise<void> = Promise.resolve();\n let disposed = false;\n\n const git = async (args: string[]): Promise<void> => {\n const started = performance.now();\n try {\n await run('git', args, { cwd });\n } finally {\n recordGitCommand(args[0] ?? 'git', performance.now() - started);\n }\n };\n\n /** Serialize every invocation: one git per repo, no `index.lock` contention. */\n const serialize = (work: () => Promise<void>): Promise<void> => {\n inFlight = inFlight.then(work, work);\n return inFlight;\n };\n\n const clearTimer = () => {\n if (timer) { clearTimeout(timer); timer = undefined; }\n };\n\n const drain = (): Promise<void> => {\n clearTimer();\n if (queued.size === 0) return inFlight;\n const batch = [...queued];\n queued.clear();\n oldestAt = undefined;\n return serialize(() => git(['add', ...batch]));\n };\n\n const arm = () => {\n clearTimer();\n if (disposed || queued.size === 0) return;\n // Debounce, but never past the staleness ceiling measured from the OLDEST\n // pending path — a busy stream must not defer staging indefinitely.\n const sinceOldest = oldestAt === undefined ? 0 : Date.now() - oldestAt;\n const wait = Math.max(0, Math.min(flushMs, maxWaitMs - sinceOldest));\n timer = setTimeout(() => { void drain(); }, wait);\n timer.unref?.();\n };\n\n return {\n add(path) {\n if (disposed) return;\n if (queued.size === 0) oldestAt = Date.now();\n queued.add(path);\n arm();\n },\n run(args) {\n return drain().then(() => serialize(() => git(args)));\n },\n flush() {\n return drain();\n },\n pending() {\n return queued.size;\n },\n async dispose() {\n await drain();\n disposed = true;\n clearTimer();\n await inFlight;\n },\n };\n}\n","/**\n * Checksum utilities for content verification\n */\n\nimport { createHash } from 'crypto';\n\n/**\n * Calculate SHA-256 checksum of content\n * @param content The content to hash\n * @returns Hex-encoded SHA-256 hash\n */\nexport function calculateChecksum(content: string | Buffer): string {\n const hash = createHash('sha256');\n hash.update(content);\n return hash.digest('hex');\n}\n\n/**\n * Verify content against a checksum\n * @param content The content to verify\n * @param checksum The expected checksum\n * @returns True if content matches checksum\n */\nexport function verifyChecksum(content: string | Buffer, checksum: string): boolean {\n return calculateChecksum(content) === checksum;\n}\n","/**\n * TextExtractor — DERIVING text from bytes that carry none of their own.\n *\n * Scope note (READ-VS-EXTRACT P2/P3): this file used to hold a registry covering\n * both ways a resource yields text — decoding (charset-aware `Buffer → string`)\n * and deriving (parse a PDF, OCR it when there is no text layer). Those shared a\n * name and almost nothing else: microseconds vs. minutes,\n * total determinism vs. none across engine versions, no canonical artifact vs.\n * exactly one, and anyone-with-bytes vs. the Smelter alone. The registry made\n * them interchangeable at every call site.\n *\n * Decoding left: it is `decodeRepresentation` in `@semiont/core`, called\n * directly. What remains here is the deriving half, reached through\n * `derivingExtractorFor` and callable only with the store that persists its\n * output.\n *\n * Anchoring is unaffected: annotations anchor to native geometry (`items`),\n * never to extracted-text offsets, so re-derivation can never break an anchor.\n */\n\nimport { yieldsGeometryOf, type PdfTextItem } from '@semiont/core';\nimport type { AnchoredTextStore } from './anchored-text-store';\nimport { pdfExtractor } from './pdf-extractor';\n\nexport interface ExtractedText {\n /** Discriminant — mirrors the wire member (WIRE-UNION-DISCRIMINANTS P5c/D6). */\n kind: 'extracted';\n /** Reading-order plain text, ready for the chunker. */\n text: string;\n /**\n * Positioned text runs indexing `text`, for callers that anchor; absent for\n * pure text, where character offsets are the anchor. Named `items` to match\n * `AnchoredText`/`PdfTextLayer` — one concept, one name, and no collision\n * with the OCR engine's own \"blocks\" (which are page regions, not runs).\n */\n items?: PdfTextItem[];\n method: 'text-passthrough' | 'pdf-text-layer' | 'table' | 'form' | 'ocr';\n pdfClass?: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';\n /**\n * How well the engine read the pixels, when any of this text came from OCR.\n *\n * Extraction quality, deliberately NOT anchor confidence: the two answer\n * different questions. `AnchorConfidence` asks whether the renderer\n * relocated a stored span in the current text, and for a PDF the answer is\n * always \"exactly\" — the viewrect is absolute. This asks whether the glyphs\n * under that box were read correctly, which no client can recompute.\n * Reported for operators rather than stored on annotations, following the\n * existing rule that anchor-audit detail belongs in logs.\n */\n ocrConfidence?: {\n /** Mean per-word confidence, 0–100. */\n mean: number;\n /** Words the engine was unsure of — the number worth acting on. */\n lowConfidenceWords: number;\n totalWords: number;\n };\n /**\n * 1-indexed pages this extraction could not read — present only when a\n * document is partially covered (class C). Naming the gap is the point:\n * without it a hybrid document embeds its native pages and says nothing\n * about the rest, so coverage silently overstates what search can see.\n * This is the work list OCR consumes.\n */\n unreadPages?: number[];\n}\n\n/**\n * A named decline — an extractor that ran and decided it cannot yield text\n * says why, so the settled signal can carry the class reason (a bare null\n * could not name its class; SMELTER-MEDIA-TYPES Phase 0 log, note a).\n */\nexport interface ExtractionDecline {\n /** Discriminant — mirrors the wire member (WIRE-UNION-DISCRIMINANTS P5c/D6). */\n kind: 'declined';\n declined: 'no-text-layer' | 'encrypted' | 'corrupt' | 'too-large';\n}\n\n/**\n * Where a derivation may reuse an earlier recognition, and under what key.\n *\n * The caller supplies the key, and derives it from the bytes it actually\n * holds — `calculateChecksum` over the same Buffer it passes to `extract()` —\n * never from a descriptor's claim. A catalog-derived key can race a byte\n * change (bytes fetched at one moment, descriptor read at another) and file\n * or read geometry under an identity that does not describe the bytes being\n * extracted. The write path made recompute-over-claim the rule\n * (PERSIST-ANCHORS P1b); readers mirror it (P1c). One SHA-256 over bytes\n * already in memory is noise against the engine pass a hit avoids.\n *\n * REQUIRED, and that is the ownership rule (READ-VS-EXTRACT P2). It carries an\n * `AnchoredTextStore`, and only the Smelter holds one — so deriving is reachable\n * exactly to the process that can persist what it derived. The restriction is a\n * capability the caller must already hold, not a convention it must remember:\n * a would-be second producer cannot construct the argument, so it cannot compile.\n *\n * The seam is `extract()` itself (PERSIST-ANCHORS D1/P2b): a hit returns the\n * FINISHED outcome — classification, geometry, provenance, or a named decline —\n * so neither the native parse nor the engine runs.\n */\nexport interface ExtractionCache {\n key: string;\n store: AnchoredTextStore;\n}\n\n/**\n * Deriving text from bytes that carry none of their own.\n *\n * Named `ContentExtractor` until READ-VS-EXTRACT P3: the `Content` prefix named\n * the INPUT, when what distinguishes this type is that it produces TEXT — by\n * deriving, which since P3 is the only thing \"extraction\" means here. WHERE a\n * media type's text comes from at all is core's `TextSource`, which spans both\n * routes and is therefore not called extraction.\n *\n * Whether a text source yields positioned runs lives in `@semiont/core`'s\n * `yieldsGeometryOf`, NOT here (P1). It is a property of the source, and that\n * vocabulary is core's — declaring it per-implementation made it two facts that\n * could disagree, and forced consumers asking about a media type to resolve an\n * implementation to find out. `text-extractor.test.ts` gates core's answer\n * against what these extractors actually produce.\n */\nexport interface TextExtractor {\n /**\n * Derive text WITH geometry from bytes that carry no text of their own, or\n * decline with the class reason (scanned-without-OCR, encrypted, corrupt).\n * The caller skips embedding and settles skipped with that reason.\n *\n * Expensive, non-deterministic across engine versions, and the sole producer\n * of a canonical artifact — which is why `cache` is required rather than\n * optional (see `ExtractionCache`).\n */\n extract(content: Buffer, mediaType: string, cache: ExtractionCache): Promise<ExtractedText | ExtractionDecline>;\n}\n\n/**\n * The deriving extractor for a media type, or `null` when its text needs no\n * deriving.\n *\n * **This replaced a `Record<TextSource, TextExtractor | null>` keyed by\n * strategy (READ-VS-EXTRACT P2), and the deletion is the point.** That map held\n * one real extractor, a `null`, and — under 'decode' — a one-line wrapper around\n * core's `decodeRepresentation`, which five sites in `@semiont/make-meaning`\n * already called directly. Resolving \"give me an extractor for this media type\"\n * therefore returned, half the time, a trivial function dressed as the same\n * capability as OCR: identical at the call site, wildly different in cost,\n * determinism, and who is allowed to run it. That symmetry is what let a\n * detection worker OCR scanned PDFs for four months without anyone reading it as\n * a category error (#739).\n *\n * Decoding is now a direct `decodeRepresentation()` call at the two sites that\n * need it. There is no registry to resolve, so there is no way to reach OCR by\n * asking a generic question — and a caller that gets a non-null answer here still\n * cannot run it without an `AnchoredTextStore`.\n *\n * Keyed by P1's `yieldsGeometryOf`, so this and the Smelter's publish gate cannot\n * disagree about which media types have a canonical artifact.\n */\nexport function derivingExtractorFor(mediaType: string): TextExtractor | null {\n return yieldsGeometryOf(mediaType) ? pdfExtractor : null;\n}\n","/**\n * PDF extractor — the 'pdf-text-layer' strategy (SMELTER-MEDIA-TYPES).\n *\n * Wraps the shared `extractPdfTextLayer` reader (detection's other consumer)\n * and turns a PDF into text plus the geometry that indexes it, by class:\n *\n * A native text layer → read directly\n * B scanned → read the page pixels by OCR\n * C hybrid → both, with any page still unread reported\n * D tables → grid pages rewritten as markdown rows\n * E forms → AcroForm values folded in, anchored to widgets\n * F/G encrypted, corrupt → declined by name, from the parser error\n *\n * Everything runs inline. OCR was originally planned off the hot path, but\n * the Smelter's lanes are per-resource and concurrent, so a slow page delays\n * only its own resource — see SMELTER-MEDIA-TYPES Design §4 (revised).\n */\n\nimport { isObject, type PdfTextItem } from '@semiont/core';\nimport { extractPdfTextLayer } from './extract-pdf-text-layer';\nimport type { TextExtractor, ExtractedText, ExtractionDecline } from './text-extractor';\nimport type { PdfTextLayer } from './pdf-text-layer';\nimport { detectTable, renderTable } from './pdf-tables';\nimport { extractPageImages } from './pdf-page-images';\nimport { recognizeImages } from './ocr';\nimport { mapWordsToItems } from './ocr-geometry';\n\n\n/** One OCR'd page: its text, and word geometry with page-local offsets. */\ninterface OcrPageResult {\n text: string;\n items: PdfTextItem[];\n /** Per-word confidences, kept only long enough to summarize. */\n confidences: number[];\n}\n\n/**\n * Largest PDF this will attempt, in bytes.\n *\n * A PDF is a compressed container, so input size bounds nothing on its own —\n * but it is the one number available before the parser touches the file, and\n * refusing here means a hostile or pathological document never gets to expand\n * inside pdf.js. Chosen to sit above real corpora (a few hundred pages of\n * scanned FOIA material runs tens of megabytes) while still being a ceiling.\n *\n * A starting point, not a measured optimum — revisit against a real corpus\n * (SMELTER-MEDIA-TYPES, live-testing follow-up). The per-image budget in\n * `pdf-page-images` guards the decoded side, which is where the unbounded\n * growth actually lives.\n */\nexport const MAX_PDF_BYTES = 200 * 1024 * 1024;\n\n/** Whether a document is small enough to attempt. Exported because the\n * threshold is a judgement, and judgements deserve tests that do not have to\n * materialize two hundred megabytes to ask the question. */\nexport function withinByteBudget(bytes: number): boolean {\n return Number.isFinite(bytes) && bytes >= 0 && bytes <= MAX_PDF_BYTES;\n}\n\n/** Words below this are worth an operator's attention. Tesseract reports\n * 0–100; readable text on a clean scan sits well above this. */\nconst LOW_CONFIDENCE = 60;\n\nfunction summarize(confidences: number[]): ExtractedText['ocrConfidence'] {\n if (confidences.length === 0) return undefined;\n const total = confidences.reduce((sum, c) => sum + c, 0);\n return {\n mean: Math.round((total / confidences.length) * 10) / 10,\n lowConfidenceWords: confidences.filter((c) => c < LOW_CONFIDENCE).length,\n totalWords: confidences.length,\n };\n}\n\n/**\n * Read the pages that have no text layer by OCR'ing their pixels. Returns\n * results only for pages that yielded text; a page absent from the map stayed\n * unread. Pages with no extractable image never reach the engine.\n *\n * Each word is anchored through the matrix that placed its image, so a scanned\n * page ends up carrying the same kind of geometry a native one does.\n */\nasync function ocrPages(\n content: Buffer,\n pageNumbers?: number[],\n): Promise<OcrPageResult> {\n // Pure recognition since PERSIST-ANCHORS P2b: the caching seam lives at\n // `extract()`, which stores and serves the FINISHED outcome. This function\n // neither consults nor writes the store — it reads pixels.\n const imagesByPage = await extractPageImages(content, pageNumbers);\n if (imagesByPage.size === 0) return { text: '', items: [], confidences: [] };\n\n // One batch for the whole document: worker startup dominates per-page cost.\n const pages = [...imagesByPage.keys()].sort((a, b) => a - b);\n const batch = pages.flatMap((page) => imagesByPage.get(page)!.map((image) => image.png));\n const recognized = await recognizeImages(batch);\n\n const byPage = new Map<number, OcrPageResult>();\n let cursor = 0;\n for (const page of pages) {\n const images = imagesByPage.get(page)!;\n let text = '';\n const items: PdfTextItem[] = [];\n const confidences: number[] = [];\n for (const image of images) {\n const result = recognized[cursor++];\n if (!result?.text.trim()) continue;\n if (text) text += '\\n';\n items.push(...mapWordsToItems(result.words, image, page, text.length));\n confidences.push(...result.words.map((word) => word.confidence));\n text += result.text;\n }\n if (text) byPage.set(page, { text, items, confidences });\n }\n\n // Joined at base 0 — the document's own coordinates. Class C shifts by the\n // native text length at its call site.\n return joinPages(byPage, 0);\n}\n\n/**\n * Recovered pages in page order as one block of text, with every word's\n * offsets shifted to where its page actually lands. `baseOffset` is where this\n * block begins in the document being assembled.\n */\nfunction joinPages(byPage: Map<number, OcrPageResult>, baseOffset: number): OcrPageResult {\n let text = '';\n const items: PdfTextItem[] = [];\n const confidences: number[] = [];\n for (const [, page] of [...byPage.entries()].sort((a, b) => a[0] - b[0])) {\n if (text) text += '\\n\\n';\n const shift = baseOffset + text.length;\n for (const item of page.items) {\n items.push({ ...item, start: item.start + shift, end: item.end + shift });\n }\n confidences.push(...page.confidences);\n text += page.text;\n }\n return { text, items, confidences };\n}\n\n/**\n * pdf.js signals a password-protected document with PasswordException.\n * Matched by name, not instanceof — pdf.js exception classes descend from\n * its own BaseException, not Error. Everything else the parser throws is\n * class G.\n */\nexport function classifyPdfError(error: unknown): 'encrypted' | 'corrupt' {\n return isObject(error) && error.name === 'PasswordException' ? 'encrypted' : 'corrupt';\n}\n\n/**\n * Class E — fold filled AcroForm values into the embedding text.\n *\n * A form's answers live in the form dictionary, not the drawn page, so a\n * naive text-layer read returns the blank labels and loses every value.\n * Each value is appended as a `name: value` line and anchored by its widget\n * rectangle, so `items` stays a complete geometry index of `text`.\n */\nfunction foldFormFields(layer: PdfTextLayer): ExtractedText {\n let text = layer.text;\n const items: PdfTextItem[] = [...layer.items];\n for (const field of layer.fields) {\n const start = text.length + `${field.name}: `.length;\n text += `${field.name}: ${field.value}\\n`;\n items.push({\n start,\n end: start + field.value.length,\n page: field.page,\n x: field.x,\n y: field.y,\n width: field.width,\n height: field.height,\n });\n }\n return { kind: 'extracted', text, items, method: 'form', pdfClass: 'E' };\n}\n\n/**\n * Class D — rewrite grid pages as markdown, keep every other page verbatim.\n *\n * Returns null when no page is a table, so the caller falls back to class A.\n * Pages are shaped independently: the common report — prose sections around\n * an outcome table — gets row-coherent tables without disturbing its prose.\n */\nfunction shapeTables(layer: PdfTextLayer): ExtractedText | null {\n const pages = layer.pages.map((page) => {\n const pageItems = layer.items.filter((item) => item.page === page.pageNumber);\n return { page, pageItems, table: detectTable(pageItems, layer.text) };\n });\n if (!pages.some((p) => p.table)) return null;\n\n let text = '';\n const items: PdfTextItem[] = [];\n for (const { page, pageItems, table } of pages) {\n if (table) {\n const rendered = renderTable(table, page.pageNumber, text.length);\n text += rendered.text;\n items.push(...rendered.items);\n } else {\n // Verbatim page: copy its slice and shift its runs' offsets to match.\n const shift = text.length - page.textStart;\n text += layer.text.slice(page.textStart, page.textEnd);\n for (const item of pageItems) {\n items.push({ ...item, start: item.start + shift, end: item.end + shift });\n }\n }\n }\n return { kind: 'extracted', text, items, method: 'table', pdfClass: 'D' };\n}\n\nexport const pdfExtractor: TextExtractor = {\n // Every non-declined PDF extraction carries positioned runs — native text\n // layers and OCR both anchor by page geometry. That fact is declared in core\n // ('pdf-text-layer' → true) rather than here; this comment records the\n // behavior the census gate holds core's answer to.\n async extract(content, _mediaType, cache) {\n // The seam (PERSIST-ANCHORS D1/P2b): consult the store for the FINISHED\n // outcome before anything runs — byte gate, native parse, image decode\n // and OCR are all part of the stored answer, classification included.\n // The pre-P2b seam skipped only Tesseract, on the argument that the\n // text-layer parse \"has to run either way\" — true on a miss, false on a\n // hit. A hit is returned WHOLE, which is sound because the outcome is a\n // pure function of the bytes, the key IS the bytes' identity (the\n // caller's producer-supplied checksum — P1b/P1c), and STAMP covers the\n // code that did the deriving. Declines are first-class hits: \"we read\n // this and there was nothing\" costs a full recognition pass to discover,\n // so the negative is precisely the result worth keeping.\n const hit = await cache.store.read(cache.key);\n if (hit) return hit;\n\n const outcome = await extractPdf(content);\n\n // Store failures stay silent — the store may make things faster, never\n // make them fail. The path that must insist on a write is the smelter's\n // re-anchor publish (P0), not this seam.\n //\n // The catch is HERE rather than inside the store: `write` throws, so this\n // is where \"best-effort\" is chosen, by the seam that wants it. Previously\n // the store swallowed for every caller and this comment described a\n // property it did not own.\n try {\n if (outcome.kind === 'declined') await cache.store.write(cache.key, outcome);\n else if (outcome.items) await cache.store.write(cache.key, { ...outcome, items: outcome.items });\n } catch {\n // Cached nothing; the outcome below is still correct.\n }\n return outcome;\n },\n};\n\n/** The uncached pipeline: classify, shape, and read the document. */\nasync function extractPdf(content: Buffer): Promise<ExtractedText | ExtractionDecline> {\n // Before the parser sees it: everything downstream — parse, image decode,\n // OCR — expands from these bytes, so this is the only gate that costs\n // nothing to enforce.\n if (!withinByteBudget(content.length)) return { kind: 'declined', declined: 'too-large' };\n\n let layer;\n try {\n layer = await extractPdfTextLayer(content);\n } catch (error) {\n return { kind: 'declined', declined: classifyPdfError(error) };\n }\n // Class B — no text operators anywhere: the characters exist only as\n // pixels, so read them. 'no-text-layer' now means OCR genuinely came up\n // empty, not that we never tried.\n if (!layer) {\n const ocr = await ocrPages(content);\n if (!ocr.text) return { kind: 'declined', declined: 'no-text-layer' };\n const confidence = summarize(ocr.confidences);\n return {\n kind: 'extracted',\n text: ocr.text,\n items: ocr.items,\n method: 'ocr',\n pdfClass: 'B',\n ...(confidence ? { ocrConfidence: confidence } : {}),\n };\n }\n\n // One class per document, so a filled form outranks a grid: its values\n // are content that exists nowhere else, while a table's cells are at\n // worst reordered.\n const shaped = layer.fields.length > 0\n ? foldFormFields(layer)\n : shapeTables(layer)\n ?? { kind: 'extracted' as const, text: layer.text, items: layer.items, method: 'pdf-text-layer' as const, pdfClass: 'A' as const };\n\n // A page with no text-showing operators is scanned: its characters exist\n // only as pixels. Report those pages rather than dropping them silently —\n // the document embeds what it can now, and this is the list OCR works\n // from. 'C' (hybrid) replaces the plain-prose label only; a form or table\n // keeps its own class, and carries the gap just the same.\n const unreadPages = layer.pages.filter((page) => !page.hasTextLayer).map((page) => page.pageNumber);\n if (unreadPages.length === 0) return shaped;\n\n // Class C — read the scanned pages and append what OCR recovers. Appended\n // rather than spliced into reading order, so the items already computed\n // for the native pages keep pointing at the right characters; OCR text\n // carries no geometry of its own this phase (mapping pixel boxes back to\n // page points needs the image's placement transform — #739's critical\n // path, not embedding's).\n const recovered = await ocrPages(content, unreadPages);\n const readPages = new Set(recovered.items.map((item) => item.page));\n const stillUnread = unreadPages.filter((page) => !readPages.has(page));\n const hybridClass = shaped.pdfClass === 'A' ? 'C' as const : shaped.pdfClass;\n if (!recovered.text) {\n return { ...shaped, unreadPages: stillUnread, pdfClass: hybridClass };\n }\n // Appended, so the native pages' items keep pointing at the right\n // characters; the OCR'd words are offset to where they actually land.\n const shift = shaped.text.length;\n const ocr: OcrPageResult = {\n text: recovered.text,\n items: recovered.items.map((item) => ({ ...item, start: item.start + shift, end: item.end + shift })),\n confidences: recovered.confidences,\n };\n const confidence = summarize(ocr.confidences);\n return {\n ...shaped,\n text: `${shaped.text}${ocr.text}\\n`,\n items: [...(shaped.items ?? []), ...ocr.items],\n method: 'ocr',\n pdfClass: hybridClass,\n ...(confidence ? { ocrConfidence: confidence } : {}),\n ...(stillUnread.length > 0 ? { unreadPages: stillUnread } : {}),\n };\n}\n","/**\n * PDF Text Layer Extraction\n *\n * Extracts positioned text from native, non-scanned PDFs using pdfjs-dist.\n * Returns null for scanned/image-only PDFs (no text items).\n *\n * Coordinates are in PDF point space, originating from the bottom-left.\n * The Y-flip to canvas pixels happens downstream.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport { STANDARD_FONT_DATA_URL } from './pdfjs-assets';\nimport { isObject, isString, isNumber, isArray, anchorRuns, isTextRun, type PdfTextItem } from '@semiont/core';\nimport type { PdfTextLayer, PdfPageInfo, PdfFormField } from './pdf-text-layer';\n\n/**\n * One entry from pdf.js's `getFieldObjects()` map, narrowed to a filled\n * field. The API types entries as bare `Object`, so every field is checked:\n * group entries (a parent with `kidIds`) carry `page: -1` and no value and\n * are rejected here, leaving the widgets that actually hold content.\n */\nfunction toFormField(entry: unknown): PdfFormField | null {\n if (!isObject(entry)) return null;\n const { name, value, page, rect } = entry;\n if (!isString(name) || !isString(value) || !value.trim()) return null;\n if (!isNumber(page) || page < 0) return null;\n if (!isArray(rect) || rect.length < 4 || !rect.every(isNumber)) return null;\n const [x1, y1, x2, y2] = rect as [number, number, number, number];\n return {\n name,\n value: value.trim(),\n page: page + 1, // pdf.js reports 0-indexed; PdfTextItem is 1-indexed\n x: Math.min(x1, x2),\n y: Math.min(y1, y2),\n width: Math.abs(x2 - x1),\n height: Math.abs(y2 - y1),\n };\n}\n\n/**\n * Filled AcroForm values, one per field name (first filled widget wins, so a\n * radio group contributes a single answer). Returns [] for a document with\n * no form. XFA forms are out of scope: whatever their AcroForm shell exposes\n * is read the same way, and anything else simply yields no fields.\n */\nasync function readFormFields(doc: pdfjs.PDFDocumentProxy): Promise<PdfFormField[]> {\n const fieldObjects = await doc.getFieldObjects();\n if (!fieldObjects) return [];\n const byName = new Map<string, PdfFormField>();\n for (const entries of Object.values(fieldObjects)) {\n if (!isArray(entries)) continue;\n for (const entry of entries) {\n const field = toFormField(entry);\n if (field && !byName.has(field.name)) byName.set(field.name, field);\n }\n }\n return [...byName.values()];\n}\n\nexport async function extractPdfTextLayer(\n bytes: Uint8Array | Buffer\n): Promise<PdfTextLayer | null> {\n // A private copy, for two pdf.js contracts at once: it refuses Node\n // Buffers outright (\"provide binary data as Uint8Array\"), and it CONSUMES\n // the array it is given — the underlying ArrayBuffer is transferred and\n // detached, which would silently zero the caller's bytes. Callers keep\n // their bytes; pdf.js gets its own.\n const data = new Uint8Array(bytes);\n // pdf.js v5 removed the isEvalSupported option; this path only calls\n // getTextContent (no rendering / no PDF functions).\n const loadingTask = pdfjs.getDocument({ data, standardFontDataUrl: STANDARD_FONT_DATA_URL });\n\n try {\n // Inside the try so the finally's destroy() also runs when the\n // parse rejects (encrypted/corrupt input — the extractor's decline\n // path classifies that throw).\n const doc = await loadingTask.promise;\n const pages: PdfPageInfo[] = [];\n const items: PdfTextItem[] = [];\n let text = '';\n\n for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {\n const page = await doc.getPage(pageNum);\n const viewport = page.getViewport({ scale: 1.0 });\n const content = await page.getTextContent(); // all text items on the page\n const pageTextStart = text.length;\n\n // `anchorRuns` owns the offset and separator convention; the\n // browser canvas builds its page the same way, so a rectangle\n // quotes identically whichever side captured it. Marked-content\n // items (no `str`) are filtered here, at the pdf.js boundary —\n // core stays free of pdfjs-dist.\n const page1 = anchorRuns(content.items.filter(isTextRun), pageNum);\n\n // Offsets come back page-local; shift them into the document text.\n for (const item of page1.items) {\n items.push({ ...item, start: item.start + pageTextStart, end: item.end + pageTextStart });\n }\n text += page1.text;\n text += '\\n'; // page break\n\n pages.push({\n pageNumber: pageNum,\n widthPt: viewport.width,\n heightPt: viewport.height,\n textStart: pageTextStart,\n textEnd: text.length,\n hasTextLayer: page1.items.length > 0,\n });\n }\n\n // A document with no drawn text is a scanned page (class B) even when\n // it carries an AcroForm — form values augment a text layer, they do\n // not substitute for one. Keeping this condition on text items alone\n // also keeps the reader's null contract stable for detection.\n if (!pages.some((page) => page.hasTextLayer)) return null;\n\n return { pages, text, items, fields: await readFormFields(doc) };\n } finally {\n // Release the pdf.js document — Phase 2 runs this in a long-lived worker\n // pool. pdf.js 6.0 removed PDFDocumentProxy.destroy(); teardown moved to\n // PDFDocumentLoadingTask.destroy().\n await loadingTask.destroy();\n }\n}\n","/**\n * Where pdf.js finds the asset bundles it does not carry in its main build.\n *\n * pdf.js ships the Standard 14 font programs (Foxit substitutes for Helvetica,\n * Times, Courier, Symbol, ZapfDingbats) as separate `.pfb` files rather than in\n * `pdf.mjs`. Without a `standardFontDataUrl` it cannot load them, and every\n * document that references a standard font logs\n *\n * Warning: UnknownErrorException: Ensure that the `standardFontDataUrl` API\n * parameter is provided.\n *\n * once per font per document — 66 lines on a single 28-page PDF, drowning real\n * output. That noise is the reason to fix it; text extraction itself was never\n * affected, because `getTextContent()` reads the content stream and the font's\n * encoding, not its glyph outlines.\n *\n * Resolved through `require.resolve` rather than a path relative to this file:\n * the built `dist/` sits at a different depth than `src/`, and npm may hoist\n * `pdfjs-dist` to the workspace root or nest it under this package. Asking the\n * resolver is the only form that is correct in all of those, including inside\n * the service images where the tree is installed fresh.\n *\n * The trailing slash is required — pdf.js concatenates the filename onto this\n * string.\n */\n\nimport { createRequire } from 'module';\nimport path from 'path';\n\nconst require = createRequire(import.meta.url);\n\nexport const STANDARD_FONT_DATA_URL =\n `${path.join(path.dirname(require.resolve('pdfjs-dist/package.json')), 'standard_fonts')}${path.sep}`;\n","/**\n * Table reconstruction from PDF text-layer geometry (SMELTER-MEDIA-TYPES\n * class D).\n *\n * A PDF has no table structure — only positioned text runs. Read in reading\n * order a grid's cells interleave, so a row's values scatter across chunks\n * and semantic recall over an outcome table returns nothing useful. This\n * module recovers the grid from the geometry the reader already carries\n * (`PdfTextItem.x/y/width/height`), then renders markdown rows so a row's\n * cells stay adjacent for the shared chunker. No new dependency: the\n * clustering is the same arithmetic a table library would do, over data we\n * already have.\n *\n * PRECISION OVER RECALL. A false positive scrambles prose into a fake table;\n * a false negative merely falls back to class A, which is Phase 1 behavior.\n * So detection demands a strict, regular grid — every row the same cell\n * count, every column aligned — and declines everything else.\n */\n\nimport type { PdfTextItem } from '@semiont/core';\n\n/** A reconstructed cell: its text plus the bounding box of its runs. */\nexport interface TableCell {\n text: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** A header row plus at least two data rows — below this, prose in columns\n * is indistinguishable from a table. */\nconst MIN_ROWS = 3;\nconst MIN_COLUMNS = 2;\n\n/** Row grouping tolerance, as a fraction of text height: runs whose\n * baselines differ by less than half a line belong to one row. */\nconst ROW_TOLERANCE = 0.5;\n/** Horizontal gap that separates cells, as a fraction of text height. Word\n * spaces are far narrower; column gutters are far wider. */\nconst CELL_GAP = 0.8;\n\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n return sorted[Math.floor(sorted.length / 2)] ?? 0;\n}\n\n/** Group runs into visual rows, top of page first. */\nfunction groupRows(items: PdfTextItem[], tolerance: number): PdfTextItem[][] {\n const rows: PdfTextItem[][] = [];\n for (const item of [...items].sort((a, b) => b.y - a.y)) {\n const row = rows[rows.length - 1];\n if (row && Math.abs(row[0]!.y - item.y) <= tolerance) row.push(item);\n else rows.push([item]);\n }\n return rows;\n}\n\nfunction toCell(runs: PdfTextItem[], text: string): TableCell {\n const x = Math.min(...runs.map((r) => r.x));\n const y = Math.min(...runs.map((r) => r.y));\n const right = Math.max(...runs.map((r) => r.x + r.width));\n const top = Math.max(...runs.map((r) => r.y + r.height));\n return {\n text: runs.map((r) => text.slice(r.start, r.end)).join(' ').trim(),\n x,\n y,\n width: right - x,\n height: top - y,\n };\n}\n\n/** Split a row into cells: runs closer than a gutter belong to one cell. */\nfunction toCells(row: PdfTextItem[], gap: number, text: string): TableCell[] {\n const cells: TableCell[] = [];\n let current: PdfTextItem[] = [];\n for (const item of [...row].sort((a, b) => a.x - b.x)) {\n const previous = current[current.length - 1];\n if (previous && item.x - (previous.x + previous.width) > gap) {\n cells.push(toCell(current, text));\n current = [];\n }\n current.push(item);\n }\n if (current.length > 0) cells.push(toCell(current, text));\n return cells;\n}\n\n/**\n * Recover a grid from one page's runs, or null when the page is not a\n * regular table.\n */\nexport function detectTable(items: PdfTextItem[], text: string): TableCell[][] | null {\n if (items.length === 0) return null;\n const unit = median(items.map((i) => i.height).filter((h) => h > 0)) || 12;\n\n const rows = groupRows(items, unit * ROW_TOLERANCE).map((row) => toCells(row, unit * CELL_GAP, text));\n if (rows.length < MIN_ROWS) return null;\n\n const columnCount = rows[0]!.length;\n if (columnCount < MIN_COLUMNS) return null;\n if (!rows.every((row) => row.length === columnCount)) return null;\n\n // Every column must start at the same offset down the page; ragged left\n // edges mean prose that happens to wrap into columns, not a grid.\n for (let column = 0; column < columnCount; column++) {\n const lefts = rows.map((row) => row[column]!.x);\n if (Math.max(...lefts) - Math.min(...lefts) > unit) return null;\n }\n if (rows.some((row) => row.some((cell) => cell.text.length === 0))) return null;\n\n return rows;\n}\n\n/**\n * Render a grid as markdown rows, anchoring every cell to the geometry it\n * came from. `offset` is where this text lands in the assembled document, so\n * the returned items index the final string.\n */\nexport function renderTable(\n rows: TableCell[][],\n page: number,\n offset: number,\n): { text: string; items: PdfTextItem[] } {\n let text = '';\n const items: PdfTextItem[] = [];\n rows.forEach((row, rowIndex) => {\n text += '|';\n for (const cell of row) {\n text += ' ';\n const start = offset + text.length;\n text += cell.text;\n items.push({\n start,\n end: offset + text.length,\n page,\n x: cell.x,\n y: cell.y,\n width: cell.width,\n height: cell.height,\n });\n text += ' |';\n }\n text += '\\n';\n // Markdown needs the delimiter row for the header to read as a table.\n if (rowIndex === 0) text += `|${' --- |'.repeat(row.length)}\\n`;\n });\n return { text, items };\n}\n","/**\n * Embedded page images from a PDF — the pixels OCR reads.\n *\n * A scanned page holds its characters only as pixels inside an image object,\n * so reading it means getting that image out. We do NOT rasterize: pdf.js\n * decodes the embedded image in its worker (pure JS — JPEG, CCITT and JBIG2\n * decoders all live there) and hands back raw pixel planes, which means no\n * canvas gateway and no native dependency. Measured, not assumed — see\n * `.plans/SMELTER-MEDIA-TYPES.md` Resolved decision 10.\n *\n * Two consequences of extracting rather than rendering: we get the scan's own\n * resolution rather than choosing a render DPI (for a real scan that IS the\n * page, so it is what we want), and a page composed of vector overlays or\n * tiled strips yields more than one image, or none we can use. Anything we\n * cannot turn into pixels simply stays unread — never an error.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport { STANDARD_FONT_DATA_URL } from './pdfjs-assets';\nimport { isObject, isNumber, isString, isArray } from '@semiont/core';\nimport { encodePng } from './png-encode';\n\n/** pdf.js image kinds (`ImageKind` in its API). */\nconst GRAYSCALE_1BPP = 1;\nconst RGB_24BPP = 2;\nconst RGBA_32BPP = 3;\n\nconst IDENTITY: readonly number[] = [1, 0, 0, 1, 0, 0];\n\n/**\n * Largest image this will read, in pixels.\n *\n * Sizing this needs the WHOLE allocation chain, not just the decoded raster —\n * reading one image can hold several copies at once:\n *\n * pdf.js decoded samples 4 bytes/px worst case (RGBA; RGB is 3)\n * + `toRgb` conversion 3 bytes/px (RGBA and 1-bit both allocate a copy;\n * plain RGB is passed through, no copy)\n * + `encodePng` scanlines 3 bytes/px (`raw`, plus a filter byte per row)\n * + deflate output smaller, but live alongside the above\n * ────────────────────────────────────────────────────────────────────\n * ≈ 10 bytes/px transient peak for a single image\n *\n * So the budget below implies roughly half a gigabyte of transient peak for\n * one pathological page — the number to size a worker against. Stating three\n * bytes per pixel here (as an earlier revision did) understated it by ~3× and\n * gave a false sense of safety.\n *\n * Chosen to admit the legitimate large cases with headroom: US Letter at\n * 600dpi is ~34 MP and A0 at 300dpi is ~35 MP, against an ordinary US Letter\n * at 300dpi of ~8 MP.\n *\n * A starting point, not a measured optimum: revisit against a real scanned\n * corpus (SMELTER-MEDIA-TYPES, live-testing follow-up). Lowering the peak\n * itself means removing copies from the chain — passing the decoded samples\n * straight to the encoder — which is a refactor, not a smaller constant.\n */\nexport const MAX_IMAGE_PIXELS = 48_000_000;\n\n/** Worst-case bytes held per pixel while reading one image — the chain above.\n * Exported so the budget's real cost is asserted rather than assumed. */\nexport const PEAK_BYTES_PER_PIXEL = 10;\n\n/** Whether an image's dimensions are sane and inside the budget. Exported\n * because the threshold is a judgement, and judgements deserve tests. */\nexport function withinPixelBudget(width: number, height: number): boolean {\n if (!Number.isFinite(width) || !Number.isFinite(height)) return false;\n if (width <= 0 || height <= 0) return false;\n return width * height <= MAX_IMAGE_PIXELS;\n}\n\n/** An image painted on a page, with the matrix that placed it. */\nexport interface PlacedImage {\n ref: string;\n /** Natural pixel dimensions, as reported by the paint operator itself. */\n width: number;\n height: number;\n /** Maps the image's unit square onto the page, in PDF points. */\n ctm: number[];\n}\n\n/**\n * Walk an operator list and report every painted image with the matrix in\n * effect when it was painted.\n *\n * Exported for its own tests: the composition ORDER cannot be checked with a\n * generated fixture, because pdf-lib emits a single combined matrix per image\n * and identity × M equals M × identity. It is checked directly instead, with\n * two non-identity transforms.\n *\n * Order convention: `ctm = Util.transform(ctm, m)` puts each new matrix on the\n * right, so it applies to a point FIRST and the enclosing matrices after —\n * which is what PDF nesting means. `save`/`restore` bracket the stack.\n */\nexport function findPlacedImages(fnArray: number[], argsArray: unknown[][]): PlacedImage[] {\n const placed: PlacedImage[] = [];\n const stack: number[][] = [];\n let ctm: number[] = [...IDENTITY];\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i];\n const args = argsArray[i];\n if (op === pdfjs.OPS.save) {\n stack.push([...ctm]);\n } else if (op === pdfjs.OPS.restore) {\n ctm = stack.pop() ?? [...IDENTITY];\n } else if (op === pdfjs.OPS.transform) {\n if (isArray(args) && args.length >= 6 && args.every(isNumber)) {\n ctm = pdfjs.Util.transform(ctm, args as number[]);\n }\n } else if (op === pdfjs.OPS.paintImageXObject) {\n const ref = args?.[0];\n const width = args?.[1];\n const height = args?.[2];\n if (isString(ref) && isNumber(width) && isNumber(height)) {\n placed.push({ ref, width, height, ctm: [...ctm] });\n }\n }\n }\n return placed;\n}\n\n/**\n * The decoded samples, whichever byte view pdf.js chose.\n *\n * `/FlateDecode` images arrive as a `Uint8Array`; `/DCTDecode` (JPEG) — what\n * essentially every real scanned PDF uses — arrives as a `Uint8ClampedArray`,\n * which is NOT an instance of `Uint8Array`. Testing only for the latter\n * discarded every real scan while accepting every fixture in this repo, all\n * of which are Flate. Both index bytes identically, so both are read; the\n * clamped view is re-wrapped without copying its 12 MB buffer.\n */\nfunction asBytes(data: unknown): Uint8Array | null {\n if (data instanceof Uint8Array) return data;\n if (data instanceof Uint8ClampedArray) return new Uint8Array(data.buffer, data.byteOffset, data.length);\n return null;\n}\n\n/**\n * Normalize a decoded pdf.js image to 8-bit RGB, or null for a kind we do\n * not read. Unknown kinds leave the page unread rather than risk feeding an\n * OCR engine garbled pixels.\n */\nexport function toRgb(image: unknown): { width: number; height: number; rgb: Uint8Array } | null {\n if (!isObject(image)) return null;\n const { width, height, kind } = image;\n const data = asBytes(image.data);\n if (!isNumber(width) || !isNumber(height) || !data) return null;\n if (width <= 0 || height <= 0) return null;\n\n if (kind === RGB_24BPP) {\n return data.length >= width * height * 3 ? { width, height, rgb: data } : null;\n }\n\n if (kind === RGBA_32BPP) {\n if (data.length < width * height * 4) return null;\n const rgb = new Uint8Array(width * height * 3);\n for (let i = 0, o = 0; o < rgb.length; i += 4, o += 3) {\n rgb[o] = data[i]!;\n rgb[o + 1] = data[i + 1]!;\n rgb[o + 2] = data[i + 2]!;\n }\n return { width, height, rgb };\n }\n\n if (kind === GRAYSCALE_1BPP) {\n // Packed bilevel, rows padded to a byte boundary — the shape fax-encoded\n // scans arrive in. A set bit is white, matching pdf.js's own rendering.\n // If a real CCITT scan ever comes out inverted, this is the line to fix;\n // the failure mode is a page that OCRs to nothing, not corrupt output.\n const rowBytes = Math.ceil(width / 8);\n if (data.length < rowBytes * height) return null;\n const rgb = new Uint8Array(width * height * 3);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const bit = data[y * rowBytes + (x >> 3)]! & (0x80 >> (x & 7));\n const value = bit ? 0xFF : 0x00;\n const o = (y * width + x) * 3;\n rgb[o] = value;\n rgb[o + 1] = value;\n rgb[o + 2] = value;\n }\n }\n return { width, height, rgb };\n }\n\n return null;\n}\n\n/**\n * How long to wait for pdf.js to deliver one image before giving up on the\n * page. Generous: this is not a performance budget but a liveness backstop —\n * see `resolveImage`.\n */\nconst IMAGE_RESOLVE_TIMEOUT_MS = 30_000;\n\n/**\n * Resolve one image object; pdf.js delivers it asynchronously, so the callback\n * form is required — the synchronous getter throws.\n *\n * Two scopes, and asking the wrong one never answers. An image used by a single\n * page lives in `page.objs` as `img_p0_1`; an image used by MORE than one page\n * — a letterhead, a watermark, a scan pipeline that dedupes identical page\n * rasters — is promoted to pdf.js's global scope, renamed `g_d1_img_p1_1`, and\n * lives in `page.commonObjs`. `objs.get` on a global ref simply registers a\n * callback that is never invoked.\n *\n * The timeout is the second half, and it is about liveness rather than speed:\n * the smelter and the detection worker both `await` this, and a worker will not\n * claim another job while one is active — so a promise that never settles wedges\n * that worker permanently, on one bad document. Timing out yields `null`, which\n * leaves the page unread and reported, the same as an unreadable image kind.\n */\nfunction resolveImage(page: pdfjs.PDFPageProxy, ref: string): Promise<unknown> {\n // pdf.js marks globally-scoped objects with a `g_` prefix.\n const scope = ref.startsWith('g_') ? page.commonObjs : page.objs;\n return new Promise((resolve) => {\n const timer = setTimeout(() => resolve(null), IMAGE_RESOLVE_TIMEOUT_MS);\n const settle = (value: unknown) => {\n clearTimeout(timer);\n resolve(value);\n };\n try {\n scope.get(ref, settle);\n } catch {\n settle(null);\n }\n });\n}\n\n/** A page image ready for OCR, with everything needed to map results back. */\nexport interface PageImage {\n png: Buffer;\n /** Pixel dimensions of the decoded raster (may differ from the paint\n * operator's declared size if the image was resampled). */\n width: number;\n height: number;\n /** Maps the image's unit square onto the page, in PDF points. */\n ctm: number[];\n}\n\n/**\n * PNG-encoded images for the given pages (all pages when omitted), keyed by\n * 1-indexed page number, each with the matrix that placed it. Pages with no\n * usable image are absent from the map.\n */\nexport async function extractPageImages(\n bytes: Uint8Array | Buffer,\n pageNumbers?: number[],\n): Promise<Map<number, PageImage[]>> {\n const wanted = pageNumbers ? new Set(pageNumbers) : null;\n const loadingTask = pdfjs.getDocument({ data: new Uint8Array(bytes), standardFontDataUrl: STANDARD_FONT_DATA_URL });\n const byPage = new Map<number, PageImage[]>();\n\n try {\n const doc = await loadingTask.promise;\n for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {\n if (wanted && !wanted.has(pageNum)) continue;\n const page = await doc.getPage(pageNum);\n const ops = await page.getOperatorList();\n\n const images: PageImage[] = [];\n for (const placement of findPlacedImages(ops.fnArray, ops.argsArray)) {\n // Checked from the paint operator's own dimensions, BEFORE the\n // image is resolved — refusing after decoding would already\n // have paid the allocation this guards against.\n if (!withinPixelBudget(placement.width, placement.height)) continue;\n const rgb = toRgb(await resolveImage(page, placement.ref));\n if (!rgb) continue;\n images.push({\n png: encodePng(rgb.width, rgb.height, rgb.rgb),\n width: rgb.width,\n height: rgb.height,\n ctm: placement.ctm,\n });\n }\n if (images.length > 0) byPage.set(pageNum, images);\n }\n return byPage;\n } finally {\n await loadingTask.destroy();\n }\n}\n","/**\n * Minimal PNG encoder.\n *\n * OCR engines take an encoded image, while pdf.js hands back raw pixel\n * planes — this bridges the two. Deterministic, built on node's zlib, so a\n * package that deliberately carries no image dependency still does not.\n */\n\nimport zlib from 'zlib';\n\nconst CRC_TABLE = (() => {\n const table = new Int32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;\n table[n] = c;\n }\n return table;\n})();\n\nfunction crc32(buf: Buffer): number {\n let c = -1;\n for (const byte of buf) c = CRC_TABLE[(c ^ byte) & 0xFF]! ^ (c >>> 8);\n return (c ^ -1) >>> 0;\n}\n\nfunction chunk(type: string, data: Buffer): Buffer {\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length);\n const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(body));\n return Buffer.concat([length, body, crc]);\n}\n\n/** Encode 8-bit RGB pixels (length must be width × height × 3) as a PNG. */\nexport function encodePng(width: number, height: number, rgb: Uint8Array): Buffer {\n const stride = width * 3 + 1; // one filter byte per scanline\n const raw = Buffer.alloc(stride * height);\n for (let y = 0; y < height; y++) {\n raw[y * stride] = 0; // filter type: none\n Buffer.from(rgb.buffer, rgb.byteOffset + y * width * 3, width * 3)\n .copy(raw, y * stride + 1);\n }\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr[8] = 8; // bit depth\n ihdr[9] = 2; // color type: truecolor\n return Buffer.concat([\n Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),\n chunk('IHDR', ihdr),\n chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),\n chunk('IEND', Buffer.alloc(0)),\n ]);\n}\n","/**\n * OCR — reading text out of page pixels (tesseract.js).\n *\n * Runs inline, on the caller's thread of control, deliberately: the Smelter's\n * lanes are per-resource and concurrent (`groupBy` + `mergeMap`), so a slow\n * page delays only its own resource, never the fast text resources it shares\n * a worker with. Extraction stays ephemeral — nothing is cached, and a\n * rebuild re-reads the pixels (SMELTER-MEDIA-TYPES Design §3/§5).\n *\n * Deterministic for a pinned engine: the same bytes yield the same text, so\n * re-running costs time and nothing else.\n */\n\nimport { createRequire } from 'node:module';\nimport { createWorker } from 'tesseract.js';\nimport { isObject, isString } from '@semiont/core';\n\n/**\n * The vendored language data — `@tesseract.js-data/eng` ships the same\n * `eng.traineddata.gz` tesseract.js would otherwise fetch from a CDN, and\n * exports the directory holding it.\n *\n * OCR is core (SMELTER-MEDIA-TYPES decision 8), so it must never reach the\n * network at runtime: an air-gapped worker has to be able to read a scan, and\n * a CDN outage must not silently turn scanned documents unreadable. Because\n * this is an ordinary dependency, `npm install` vendors it into the smelter\n * and worker images — no Dockerfile fetch step, and the lockfile pins it.\n *\n * Resolved lazily so importing this module has no side effects.\n */\nlet cachedLangPath: string | undefined;\nfunction langPath(): string {\n if (cachedLangPath) return cachedLangPath;\n const data: unknown = createRequire(import.meta.url)('@tesseract.js-data/eng');\n if (!isObject(data) || !isString(data.langPath)) {\n throw new Error(\n 'Vendored OCR language data is missing or malformed: @tesseract.js-data/eng did not export a langPath',\n );\n }\n cachedLangPath = data.langPath;\n return cachedLangPath;\n}\n\n/**\n * The slice of tesseract's recognition tree this module reads. Declared\n * structurally rather than importing `Tesseract.Block`, so tests can build a\n * tree without satisfying a dozen fields nothing here looks at; a real\n * `Block[]` still satisfies it.\n */\nexport interface OcrBbox { x0: number; y0: number; x1: number; y1: number }\nexport interface OcrLine {\n /** The line's own box — the vertical extent shared by its words. */\n bbox: OcrBbox;\n words: { text: string; confidence: number; bbox: OcrBbox }[];\n}\nexport interface OcrBlock {\n paragraphs: { lines: OcrLine[] }[];\n}\n\n/** A recognized word, with the range it occupies in the assembled page text. */\nexport interface OcrWord {\n text: string;\n /** Offsets into `OcrPage.text` — `text.slice(start, end) === word.text`. */\n start: number;\n end: number;\n /** Image pixel space, top-left origin — mapped to PDF points downstream. */\n bbox: OcrBbox;\n confidence: number;\n}\n\nexport interface OcrPage {\n text: string;\n words: OcrWord[];\n}\n\n/**\n * Assemble a page's text from its recognition tree, recording where each word\n * lands as it is written.\n *\n * The text is built here rather than taken from tesseract's own `data.text`\n * precisely so the offsets are exact **by construction** — deriving offsets by\n * searching for words in a separately-produced string is where this kind of\n * code goes wrong. Words join with a space, lines with a newline, paragraphs\n * with a blank line.\n */\nexport function assemblePage(blocks: OcrBlock[] | null): OcrPage {\n let text = '';\n const words: OcrWord[] = [];\n\n for (const block of blocks ?? []) {\n for (const paragraph of block.paragraphs ?? []) {\n for (const line of paragraph.lines ?? []) {\n let wroteWord = false;\n for (const word of line.words ?? []) {\n const value = word.text.trim();\n if (!value) continue; // an empty box is not a word\n if (wroteWord) text += ' ';\n const start = text.length;\n text += value;\n words.push({\n text: value,\n start,\n end: text.length,\n // Horizontal extent from the word, vertical from the\n // line. OCR boxes hug their glyphs, so a descender\n // ('page') sits lower than its neighbours — and\n // `locate()` groups items into lines by comparing `y`\n // within a couple of points, a threshold that holds\n // because NATIVE runs take y from the shared baseline.\n // Passing per-word descenders through would split one\n // visual line into several rects and draw a highlight\n // as stacked fragments. Nothing is lost: `locate()`\n // bounds each line anyway, so per-word vertical extent\n // never reaches an annotation.\n bbox: {\n x0: word.bbox.x0,\n x1: word.bbox.x1,\n y0: line.bbox.y0,\n y1: line.bbox.y1,\n },\n confidence: word.confidence,\n });\n wroteWord = true;\n }\n if (wroteWord) text += '\\n';\n }\n text += '\\n';\n }\n }\n\n // Only trailing separators are removed, so no recorded offset moves.\n return { text: text.trimEnd(), words };\n}\n\n/**\n * Recognize a batch of PNG images, returning one result per image (empty\n * where nothing legible was found). One worker serves the whole batch —\n * startup is the expensive part, not the pages.\n */\nexport async function recognizeImages(images: Buffer[]): Promise<OcrPage[]> {\n if (images.length === 0) return [];\n // `cacheMethod: 'none'` — the data is already local, so there is nothing to\n // cache and no reason to write a copy into the working directory.\n const worker = await createWorker('eng', undefined, {\n langPath: langPath(),\n cacheMethod: 'none',\n });\n try {\n const results: OcrPage[] = [];\n for (const image of images) {\n // `blocks: true` is what carries the per-word geometry; without it\n // tesseract returns text only and `data.blocks` is null.\n const { data } = await worker.recognize(image, {}, { blocks: true, text: false });\n results.push(assemblePage(data.blocks));\n }\n return results;\n } finally {\n await worker.terminate();\n }\n}\n","/**\n * OCR word boxes → PDF-point geometry (#739).\n *\n * OCR reports boxes in the image's own pixel space, top-left origin. Anchoring\n * them means going through the matrix that placed the image on the page:\n *\n * pixel (px, py) → unit square (px/W, 1 − py/H) → CTM → PDF points\n *\n * Rotation and non-uniform scale fall out of the matrix, so there are no\n * special cases for them — the only explicit work is normalizing the result,\n * since a mirrored placement can invert an axis and consumers bound\n * rectangles rather than orienting them.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport type { OcrWord } from './ocr';\nimport type { PdfTextItem } from '@semiont/core';\n\n/** The placement of one image: its pixel size and its matrix onto the page. */\nexport interface ImagePlacement {\n width: number;\n height: number;\n ctm: number[];\n}\n\nfunction toPagePoint(px: number, py: number, placement: ImagePlacement): [number, number] {\n // Unit square, Y flipped: pixel rows run down, PDF space runs up.\n const point: [number, number] = [px / placement.width, 1 - py / placement.height];\n pdfjs.Util.applyTransform(point, placement.ctm); // mutates in place\n return point;\n}\n\n/**\n * Map recognized words onto the page, shifting their character offsets by\n * `textOffset` — where this page's text begins in the assembled document.\n */\nexport function mapWordsToItems(\n words: OcrWord[],\n placement: ImagePlacement,\n page: number,\n textOffset: number,\n): PdfTextItem[] {\n if (placement.width <= 0 || placement.height <= 0) return [];\n\n return words.map((word) => {\n // Both corners through the matrix, then bound them — a flipped or\n // rotated placement can put either one first.\n const [ax, ay] = toPagePoint(word.bbox.x0, word.bbox.y0, placement);\n const [bx, by] = toPagePoint(word.bbox.x1, word.bbox.y1, placement);\n const x = Math.min(ax, bx);\n const y = Math.min(ay, by);\n return {\n start: word.start + textOffset,\n end: word.end + textOffset,\n page,\n x,\n y,\n width: Math.abs(bx - ax),\n height: Math.abs(by - ay),\n };\n });\n}\n","/**\n * Anchored-text cache — the persistent half of ANCHORED-TEXT-CACHE.md Lane 2.\n *\n * OCR costs ~2.9 s per scanned page, and six passes read the same document (five\n * detection motivations plus the smelter's embed), each its own job in its own\n * process. This stores what the engine produced so only the first pass pays.\n *\n * **Derived values only.** Everything here is reproducible from the source\n * bytes, which is what makes a stamp miss safe. An authored coordinate map is\n * embedded in the PDF Semiont generated, not stored alongside one — see\n * `PDF-GENERATION.md`, which owns that decision and states the negative:\n * never this store.\n *\n * The seam is `extract()` (PERSIST-ANCHORS D1/P2b): the record is the FINISHED\n * extraction outcome — classification, geometry, provenance, or a named\n * decline — so a hit skips the native parse and the engine both, and every\n * geometry-yielding extraction stores an entry, native documents included.\n * That is what makes the anchored-text endpoint answer for every resource\n * whose extraction yields geometry, and what lets the reconcile planner treat\n * \"no entry under the current checksum\" as work (P0's third drift class).\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createRequire } from 'module';\nimport { getShardPath, isObject, isString, isNumber, isArray, type ExtractionOutcome, type Logger, type PdfTextItem } from '@semiont/core';\n\n\n/**\n * One line of recognized text: the geometry every word on it shares, plus the\n * per-word parts that differ.\n *\n * Grouping is by *contiguous runs* of equal `(y, h)`, never by scanning for all\n * items at a given y. That makes the codec lossless and order-preserving for\n * any input — compression is the only thing that depends on words actually\n * arriving in reading order, and correctness never is.\n *\n * Sharing `y`/`h` is measured-safe rather than assumed: within-line word-height\n * spread is 0.0pt in both native and OCR'd output, because the engine already\n * normalizes word boxes to the line. Per-word `x` and `width` are stored\n * explicitly and NOT derived from neighbouring split positions — deriving width\n * from the gap to the next word would widen every box to touch its neighbour,\n * which would silently change the coverage arithmetic `textUnder` is calibrated\n * on (RUN_COVERAGE_THRESHOLD, tuned against ink-tight boxes).\n */\nexport interface CachedLine {\n /** 1-indexed page. */\n p: number;\n /** PDF points, bottom-left origin — shared by every word on the line. */\n y: number;\n h: number;\n /** `[x, width, start, end]` per word; offsets index `CachedAnchoredText.text`. */\n words: [number, number, number, number][];\n}\n\n/**\n * The stored record: one extraction OUTCOME for the whole resource\n * (PERSIST-ANCHORS decision D1) — the anchored text with its provenance\n * (`method`, `pdfClass`, `ocrConfidence`, `unreadPages`), or a named decline.\n *\n * Whole-resource on every side, deliberately. The producer's own shape is a\n * per-page map, but that is an artifact of how `ocrPages` iterates, and letting\n * it reach storage would have forced every consumer — the transport, the\n * browser, a headless client — to reassemble pages it never asked to see.\n *\n * The `ocrConfidence` SUMMARY is stored (v2) — this repairs the regression\n * OCR-CONFIDENCE-LOST.md records, where a hit answered with no confidence at\n * all. Per-word confidences remain unstored: the summary is the record's\n * quality provenance; the word list is operator log detail.\n *\n * v1 records (bare `{ text, lines }`, no provenance) read as misses under the\n * v2 prefix; the reconcile planner's third drift class re-derives them.\n */\nexport type CachedAnchoredText =\n | ({\n v: 2;\n /** Engine + traineddata + our assembly code. A mismatch is a clean miss. */\n stamp: string;\n text: string;\n lines: CachedLine[];\n } & Omit<Extract<ExtractionOutcome, { kind: 'extracted' }>, 'kind' | 'text' | 'items'>)\n | ({\n v: 2;\n stamp: string;\n } & Omit<Extract<ExtractionOutcome, { kind: 'declined' }>, 'kind'>);\n\n/**\n * What the cached value must be recomputed against.\n *\n * Derived, never hand-maintained. A hand-bumped counter fails in the one\n * direction that matters: forgetting to bump it does not cost a recomputation,\n * it silently serves geometry built by different code. Over-invalidating costs\n * seconds of the work this cache exists to avoid; under-invalidating is\n * corruption, so the stamp is deliberately over-eager — a release of this\n * package busts the cache whether or not assembly actually changed.\n *\n * `@semiont/content`'s own version covers our assembly code (`anchorRuns`,\n * `assemblePage`, `mapWordsToItems` — the offset construction IS part of what\n * the cached value means). The engine and its traineddata are read separately\n * because both are pinned with carets and can move without a release here —\n * and different traineddata means different recognized text, which is a\n * difference in the value itself, not merely in how fast it was produced.\n *\n * pdf.js joined at P2b, because the seam did: the record is the finished\n * extraction outcome, so it depends on the native parse — classification,\n * text-layer read, table/form shaping — not just the engine. A parser upgrade\n * is a change in the value, and the entry must miss.\n */\nfunction buildStamp(): string {\n const require = createRequire(import.meta.url);\n const version = (specifier: string): string => {\n try {\n const pkg: unknown = require(specifier);\n return isObject(pkg) && isString(pkg.version) ? pkg.version : 'unknown';\n } catch {\n return 'unknown';\n }\n };\n return `content-${version('../package.json')}`\n + `+pdfjs-${version('pdfjs-dist/package.json')}`\n + `+tesseract-${version('tesseract.js/package.json')}`\n + `+eng-${version('@tesseract.js-data/eng/package.json')}`;\n}\n\nconst STAMP = buildStamp();\n\n/** Pack items into line records. Lossless and order-preserving for any input. */\nexport function encodeLines(items: PdfTextItem[]): CachedLine[] {\n const lines: CachedLine[] = [];\n for (const item of items) {\n const last = lines[lines.length - 1];\n if (last && last.p === item.page && last.y === item.y && last.h === item.height) {\n last.words.push([item.x, item.width, item.start, item.end]);\n } else {\n lines.push({ p: item.page, y: item.y, h: item.height, words: [[item.x, item.width, item.start, item.end]] });\n }\n }\n return lines;\n}\n\n/** The inverse of `encodeLines`. */\nexport function decodeLines(lines: CachedLine[]): PdfTextItem[] {\n const items: PdfTextItem[] = [];\n for (const line of lines) {\n for (const [x, width, start, end] of line.words) {\n items.push({ start, end, page: line.p, x, y: line.y, width, height: line.h });\n }\n }\n return items;\n}\n\nexport interface AnchoredTextStore {\n /**\n * The stored map for this key, or null for any miss. Never throws.\n *\n * The key is the **content checksum of the bytes the map derives from**\n * (PERSIST-ANCHORS decision A): a representation is its bytes, so the\n * checksum is its identity, and geometry derived from one revision of the\n * bytes is unreachable by a reader holding a different revision — by\n * construction, not by invalidation. Callers holding some other handle\n * (a resource id) reach the artifact through an index, not by a second\n * key scheme here.\n */\n read(key: string): Promise<ExtractionOutcome | null>;\n /**\n * Record an extraction outcome under the content checksum of its source\n * bytes. **THROWS on failure: a write that returns has written.**\n *\n * Asymmetric with `read` above, which never throws, and deliberately so —\n * a miss is a normal answer, a failed write is not. The store used to\n * swallow for everyone, which forced the one caller that needs a throw\n * (the Smelter's re-anchor publish, whose `smelt:rebuild-anchors-failed`\n * accounting rides on it) to route around the store entirely. Now the\n * contract is honest and **leniency is the caller's**, stated where it is\n * wanted: the read-through seam in `pdf-extractor` catches, because a\n * store may make extraction faster but must never make it fail.\n */\n write(key: string, outcome: ExtractionOutcome): Promise<void>;\n /**\n * Every key `read()` would currently HIT — entries under a stale stamp or\n * unreadable files are excluded, exactly as `read()` would exclude them.\n * That equivalence is load-bearing: the reconcile planner treats a listed\n * key as \"artifact present\" and plans re-derivation for the rest\n * (PERSIST-ANCHORS P0, the third drift class), so a key listed here but\n * missed by `read()` would be a permanent loss the diff can never see —\n * the exact shape of the post-engine-upgrade hole this filter closes.\n * One bulk call per reconcile, never a probe per resource. Never throws.\n */\n list(): Promise<string[]>;\n}\n\n/** Narrow a parsed entry, so a truncated or foreign file is a miss, not a crash. */\nfunction isCached(value: unknown): value is CachedAnchoredText {\n if (!isObject(value) || value.v !== 2 || !isString(value.stamp)) return false;\n if (isString(value.declined)) return true;\n if (!isString(value.text) || !isString(value.method) || !isArray(value.lines)) return false;\n return value.lines.every((line) =>\n isObject(line) && isNumber(line.p) && isNumber(line.y) && isNumber(line.h) && isArray(line.words)\n && line.words.every((w) => isArray(w) && w.length === 4 && w.every(isNumber)));\n}\n\n/** A key that could not have come from a checksum (or a legacy hex handle) is\n * refused outright rather than sanitized: a silently stripped key could share\n * a file with a different entry. Rejection replaces the old strip\n * (PERSIST-ANCHORS, *Smaller things*). */\nconst VALID_KEY = /^[A-Za-z0-9_-]+$/;\n\n/**\n * A file-backed store under `dir` — one file per content key, sharded as\n * `{ab}/{cd}/{key}.json` via the same `getShardPath` the event log uses\n * (PERSIST-ANCHORS decision E). Same convention, separate tree: `.semiont/`\n * is the KB's committed system of record; everything here is derived,\n * reclaimable, and never a source of truth.\n *\n * `dir` is the caller's, out of `Project.anchoredTextDir`: this package has no idea\n * which project it is serving. Every failure path is a miss rather than an\n * error, matching the rule extraction already follows for unreadable pages —\n * the cache may make things faster, never make them fail.\n */\nexport function createAnchoredTextStore(dir: string, logger?: Logger): AnchoredTextStore {\n const fileFor = (key: string): string | null => {\n if (!VALID_KEY.test(key)) return null;\n const [ab, cd] = getShardPath(key);\n return path.join(dir, ab, cd, `${key}.json`);\n };\n\n return {\n async read(key) {\n let hit: CachedAnchoredText | null = null;\n try {\n const file = fileFor(key);\n if (file === null) throw new Error('invalid key'); // refused → a miss like any other\n const parsed: unknown = JSON.parse(await fs.promises.readFile(file, 'utf8'));\n if (isCached(parsed) && parsed.stamp === STAMP) hit = parsed;\n } catch {\n hit = null; // absent, unreadable, truncated, or not ours\n }\n // Logged here rather than at the call sites: `prepare-detection` and\n // the smelter both extract, so each would see only its own share of\n // the traffic and the policy would be stated twice. Hit rate is what\n // keeps the Lane 0 decision auditable after the fact.\n logger?.debug('Anchored-text cache', {\n outcome: hit ? 'hit' : 'miss',\n key,\n ...(hit ? ('declined' in hit ? { declined: hit.declined } : { lines: hit.lines.length }) : {}),\n });\n if (!hit) return null;\n // `kind` is not persisted — the branch is implied by the record's\n // own shape, and re-added here so readers get the discriminated\n // wire union (WIRE-UNION-DISCRIMINANTS P5c).\n if ('declined' in hit) return { kind: 'declined', declined: hit.declined };\n const { v: _v, stamp: _stamp, lines, text, ...provenance } = hit;\n return { kind: 'extracted', text, items: decodeLines(lines), ...provenance };\n },\n\n async write(key, outcome) {\n const target = fileFor(key);\n if (target === null) {\n logger?.debug('Anchored-text cache: refusing invalid key', { key });\n return; // a store that cannot write is still a store\n }\n // Key order (`v`, `stamp`, first) is load-bearing: `list()` below\n // reads only a prefix of each file and matches the stamp there.\n const entry: CachedAnchoredText = outcome.kind === 'declined'\n ? { v: 2, stamp: STAMP, declined: outcome.declined }\n : (() => {\n // `kind` is deliberately destructured OUT: persisting it\n // would store a byte the branch already implies, and a\n // stored-shape change here would outrun the release-derived\n // STAMP (WIRE-UNION-DISCRIMINANTS P5c).\n const { kind: _kind, text, items, ...provenance } = outcome;\n return { v: 2, stamp: STAMP, text, lines: encodeLines(items), ...provenance };\n })();\n // Write-then-rename: a reader never observes a half-written entry,\n // and two writers racing on the same key both produce the same bytes.\n const temp = `${target}.${process.pid}.tmp`;\n try {\n await fs.promises.mkdir(path.dirname(target), { recursive: true });\n await fs.promises.writeFile(temp, JSON.stringify(entry), 'utf8');\n await fs.promises.rename(temp, target);\n } catch (error) {\n // Clean up the partial temp, then RETHROW. A write that returns\n // has written — see the interface doc. Callers that want\n // best-effort say so at their own call site.\n await fs.promises.rm(temp, { force: true }).catch(() => {});\n throw error;\n }\n },\n\n async list() {\n // Would-hit keys only (see the interface doc). The stamp check\n // reads a bounded prefix rather than parsing whole entries — an\n // artifact is ~32 KB per scanned page and this runs over every\n // entry at every reconcile. Sound because `write()` above puts\n // `v` and `stamp` first, so the current stamp appears within the\n // first bytes of every entry this store has ever written; a file\n // whose prefix doesn't match is either stale or not ours, and\n // both are misses for `read()` too. Keys round-trip through\n // filenames unchanged because every real key is hex — the same\n // fact that makes `fileFor`'s guard a no-op for them.\n const prefix = JSON.stringify({ v: 2, stamp: STAMP }).slice(0, -1) + ',';\n let rootNames: string[];\n try {\n rootNames = await fs.promises.readdir(dir);\n } catch {\n return []; // no directory yet: nothing has been written\n }\n\n // One-generation sweep (PERSIST-ANCHORS P1): a `.json` at the root\n // is a pre-P1 entry — flat layout, resource-id key, a dead scheme.\n // The rebuild path (P0's third drift class) re-derives anything\n // still needed, which is what makes this delete safe; leaving a\n // generation behind is how the store's size becomes unexplainable.\n // Done here because list() is the one bulk call every reconcile\n // already makes, so the sweep runs exactly when the planner is\n // about to notice what is missing. Best-effort, never throws.\n let swept = 0;\n for (const name of rootNames) {\n if (!name.endsWith('.json')) continue;\n await fs.promises.rm(path.join(dir, name), { force: true }).then(() => { swept += 1; }, () => {});\n }\n if (swept > 0) logger?.info('Anchored-text cache: swept pre-P1 flat entries', { swept });\n\n const keys: string[] = [];\n let sweptInterim = 0;\n for (const ab of rootNames) {\n if (!/^[0-9a-f]{2}$/.test(ab)) continue;\n let cdNames: string[];\n try {\n cdNames = await fs.promises.readdir(path.join(dir, ab));\n } catch {\n continue;\n }\n for (const cd of cdNames) {\n if (!/^[0-9a-f]{2}$/.test(cd)) continue;\n let names: string[];\n try {\n names = await fs.promises.readdir(path.join(dir, ab, cd));\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith('.json')) continue;\n // Interim-generation sweep (PERSIST-ANCHORS P1b): a\n // 32-hex basename is a resource-id key — writes that\n // landed sharded between P1a's rekey and P1b's\n // call-site switch. Checksums are 64-hex (SHA-256),\n // so the two generations are disjoint by length.\n // Reaped here for the same reason the flat sweep\n // lives here: one bulk call per reconcile, and never\n // a third scheme lingering silently.\n const base = name.slice(0, -'.json'.length);\n if (/^[0-9a-f]{32}$/.test(base)) {\n await fs.promises.rm(path.join(dir, ab, cd, name), { force: true }).then(() => { sweptInterim += 1; }, () => {});\n continue;\n }\n let handle: fs.promises.FileHandle | null = null;\n try {\n handle = await fs.promises.open(path.join(dir, ab, cd, name), 'r');\n const buf = Buffer.alloc(prefix.length);\n const { bytesRead } = await handle.read(buf, 0, prefix.length, 0);\n if (bytesRead === prefix.length && buf.toString('utf8') === prefix) {\n keys.push(base);\n }\n } catch {\n // unreadable is a miss, matching read()\n } finally {\n await handle?.close().catch(() => {});\n }\n }\n }\n }\n if (sweptInterim > 0) logger?.info('Anchored-text cache: swept interim resource-id entries', { swept: sweptInterim });\n return keys;\n },\n };\n}\n","/**\n * Reading a resource's bytes: the contract, the way it fails, and the\n * implementation that reaches the Archivist over HTTP.\n *\n * These live in `@semiont/content` because this package IS the byte layer —\n * the Archivist's whole job — and because the readers span the dependency\n * graph. `@semiont/make-meaning` holds the Archivist itself and satisfies\n * `ContentReads` in-process from the working tree; `@semiont/jobs` holds the\n * Worker and can only reach the record over the wire. make-meaning depends on\n * jobs, so anything both need has to sit under both (SINGLE-KB-MOUNT P4).\n *\n * Where the Archivist IS lives in `@semiont/core/node` (`archivistEndpoint`),\n * not here: an address is a config value plus an environment variable, and\n * the gateway needs it without needing a byte reader. One resolution, shared\n * with the gateway's own proxying — the address and the secret are deployment\n * facts, and a second copy of either is a second thing to get wrong.\n *\n * Absence fails loudly. A missing host or secret is a misconfiguration, never\n * a reason to fall back to reading a tree locally — the point of\n * SINGLE-KB-MOUNT is that exactly one process touches it.\n */\n\nimport type { IContentTransport, ResourceId } from '@semiont/core';\nimport { archivistEndpoint, type ArchivistAddressConfig } from '@semiont/core/node';\n\n/**\n * The byte read, and nothing else — DERIVED from the transport contract so it\n * cannot drift from it. Keyed by ResourceId because that is the transport's\n * key and the Archivist's: no caller converts to a tree address only to have\n * the far side convert back.\n */\nexport type ContentReads = Pick<IContentTransport, 'getBinary'>;\n\n/** Which half of the lookup failed — the gateway serves two different 404s. */\nexport type MissingReason = 'resource' | 'representation';\n\nexport class RepresentationMissing extends Error {\n constructor(readonly resourceId: string, readonly reason: MissingReason) {\n // NAMES THE RESOURCE. The client-visible wording is the gateway's, built\n // from `reason` — so this message is free to be diagnostic, and must be:\n // an operator reading a log needs to know which resource, which is what\n // the pre-collapse message gave them.\n super(\n reason === 'resource'\n ? `Resource not found: ${resourceId}`\n : `Resource representation not found: no storageUri for ${resourceId}`,\n );\n this.name = 'RepresentationMissing';\n }\n}\n\n/**\n * `ContentReads` against the Archivist — how a fleet process that holds no KB\n * mount reads bytes (SINGLE-KB-MOUNT P4).\n *\n * The address resolves HERE, at construction, not per read: a process with no\n * Archivist configured must die while an operator is watching it boot, rather\n * than fail every resource for the life of the process.\n *\n * A miss arrives as `RepresentationMissing` — the same error the in-process\n * face throws for the same fact, so no caller can tell whether the bytes were\n * a hop away. `reason` rides the wire precisely so this side need not guess.\n */\nexport function archivistContentReads(config: ArchivistAddressConfig): ContentReads {\n const { base, headers } = archivistEndpoint(config);\n\n return {\n getBinary: async (resourceId: ResourceId) => {\n const url = `${base}/resources/${encodeURIComponent(String(resourceId))}/content`;\n const res = await fetch(url, { headers });\n\n if (res.status === 404) {\n const { reason } = await res.json().catch(() => ({})) as { reason?: string };\n throw new RepresentationMissing(\n String(resourceId),\n reason === 'representation' ? 'representation' : 'resource',\n );\n }\n if (!res.ok) {\n throw new Error(`Archivist content read failed for ${String(resourceId)}: ${res.status} ${res.statusText}`);\n }\n\n return {\n data: await res.arrayBuffer(),\n contentType: res.headers.get('content-type') || 'application/octet-stream',\n };\n },\n };\n}\n"],"mappings":"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","names":["path","chunk","createHash","isObject","path","require","pdfjs","isObject","isNumber","isString","isArray","isArray","isNumber","isString","isObject","createRequire","isObject","isString","pdfjs","isObject","ocr","confidence","fs","path","createRequire","isObject","isString","isNumber","isArray","require"]}
1
+ {"version":3,"sources":["../src/working-tree-store.ts","../src/git-staging.ts","../src/checksum.ts","../src/text-extractor.ts","../src/pdf-extractor.ts","../src/extract-pdf-text-layer.ts","../src/pdfjs-assets.ts","../src/pdf-tables.ts","../src/pdf-page-images.ts","../src/png-encode.ts","../src/ocr.ts","../src/ocr-geometry.ts","../src/anchored-text-store.ts","../src/representation-reads.ts"],"sourcesContent":["/**\n * Files in the project working tree, addressed by file:// URI —\n * \"file://docs/overview.md\" is {projectRoot}/docs/overview.md.\n *\n * `store` writes bytes the caller supplies; `register` adopts a file already\n * on disk. Both stream to hash, neither holds a representation in memory.\n */\n\nimport { promises as fs, createReadStream, createWriteStream } from 'fs';\nimport { createHash, randomUUID } from 'crypto';\nimport { Readable } from 'stream';\nimport { pipeline } from 'stream/promises';\nimport path from 'path';\nimport type { SemiontProject } from '@semiont/core/node';\nimport type { Logger, StoredResource } from '@semiont/core';\nimport { createStager, type Stager, type StagerOptions } from './git-staging.js';\n\n\n/** sha256 + byte count over a chunk stream — one definition for both write paths. */\nfunction hashingTap() {\n const hash = createHash('sha256');\n let byteSize = 0;\n return {\n update(chunk: Buffer): void {\n hash.update(chunk);\n byteSize += chunk.length;\n },\n get byteSize(): number {\n return byteSize;\n },\n digest(): string {\n return hash.digest('hex');\n },\n };\n}\n\nexport class WorkingTreeStore {\n private projectRoot: string;\n private gitSync: boolean;\n private logger?: Logger;\n\n private _stager?: Stager;\n private readonly staging: StagerOptions;\n\n /** `staging` is policy — how stale the index may get is the caller's call. */\n constructor(project: SemiontProject, logger?: Logger, staging: StagerOptions = {}) {\n this.projectRoot = project.root;\n this.gitSync = project.gitSync;\n this.logger = logger;\n this.staging = staging;\n }\n\n /** Created on first use — importers of this package may never stage. */\n private stager(): Stager {\n if (!this._stager) this._stager = createStager(this.projectRoot, this.staging);\n return this._stager;\n }\n\n /** Stage everything pending now — for a caller that wants the index current. */\n flushStaging(): Promise<void> {\n return this._stager ? this._stager.flush() : Promise.resolve();\n }\n\n /** Drain and stop. A stopped process must leave nothing unstaged. */\n async dispose(): Promise<void> {\n if (this._stager) await this._stager.dispose();\n }\n\n private shouldRunGit(noGit?: boolean): boolean {\n return this.gitSync && !noGit;\n }\n\n /**\n * Write bytes to the path storageUri names, whole or streamed.\n *\n * Atomic: bytes land in a temp file and are renamed into place only once\n * complete and once `expectedChecksum`, when given, agrees. A mismatch or a\n * torn stream leaves the target untouched, so `register` can never find\n * partial bytes an event names.\n *\n * @throws ChecksumMismatchError when expectedChecksum disagrees with the body\n */\n async store(\n content: Buffer | Readable,\n storageUri: string,\n options?: { noGit?: boolean; expectedChecksum?: string },\n ): Promise<StoredResource> {\n const filePath = this.resolveUri(storageUri);\n const source = Buffer.isBuffer(content) ? Readable.from([content]) : content;\n\n this.logger?.debug('Storing resource', { storageUri });\n\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tempPath = `${filePath}.${randomUUID()}.tmp`;\n const tap = hashingTap();\n\n try {\n await pipeline(\n source,\n async function* (chunks: AsyncIterable<Buffer>) {\n for await (const chunk of chunks) {\n tap.update(chunk);\n yield chunk;\n }\n },\n createWriteStream(tempPath),\n );\n\n const checksum = tap.digest();\n const byteSize = tap.byteSize;\n if (options?.expectedChecksum !== undefined && options.expectedChecksum !== checksum) {\n throw new ChecksumMismatchError(storageUri, options.expectedChecksum, checksum);\n }\n await fs.rename(tempPath, filePath);\n\n if (this.shouldRunGit(options?.noGit)) {\n this.stager().add(filePath);\n }\n\n this.logger?.info('Resource stored', { storageUri, checksum, byteSize });\n\n return {\n storageUri,\n checksum,\n byteSize,\n created: new Date().toISOString(),\n };\n } catch (error) {\n await fs.rm(tempPath, { force: true });\n throw error;\n }\n }\n\n /**\n * Adopt a file already on disk: stream it to hash it, then stage it.\n *\n * @throws ChecksumMismatchError if expectedChecksum is given and disagrees\n */\n async register(storageUri: string, expectedChecksum?: string, options?: { noGit?: boolean }): Promise<StoredResource> {\n const filePath = this.resolveUri(storageUri);\n\n this.logger?.debug('Registering resource', { storageUri });\n\n // Streamed, never read whole: this runs in the same process that streamed\n // the upload in, and a `readFile` would undo that bound.\n const tap = hashingTap();\n for await (const chunk of createReadStream(filePath)) {\n tap.update(chunk as Buffer);\n }\n const checksum = tap.digest();\n\n if (expectedChecksum !== undefined && checksum !== expectedChecksum) {\n throw new ChecksumMismatchError(storageUri, expectedChecksum, checksum);\n }\n\n if (this.shouldRunGit(options?.noGit)) {\n this.stager().add(filePath);\n }\n\n const byteSize = tap.byteSize;\n this.logger?.info('Resource registered', { storageUri, checksum, byteSize });\n\n return {\n storageUri,\n checksum,\n byteSize,\n created: new Date().toISOString(),\n };\n }\n\n /**\n * The same bytes as `retrieve`, streamed. Lazy: a missing file surfaces as\n * an `error` event on the stream, not a rejected promise — callers needing\n * that up front should resolve the descriptor first.\n */\n retrieveStream(storageUri: string): Readable {\n return createReadStream(this.resolveUri(storageUri));\n }\n\n async retrieve(storageUri: string): Promise<Buffer> {\n const filePath = this.resolveUri(storageUri);\n try {\n return await fs.readFile(filePath);\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n throw new Error(`Resource not found: ${storageUri}`);\n }\n throw error;\n }\n }\n\n /** `git mv` when the project syncs git, `fs.rename` otherwise. */\n async move(fromUri: string, toUri: string, options?: { noGit?: boolean }): Promise<void> {\n const fromPath = this.resolveUri(fromUri);\n const toPath = this.resolveUri(toUri);\n\n this.logger?.debug('Moving resource', { fromUri, toUri });\n\n await fs.mkdir(path.dirname(toPath), { recursive: true });\n\n if (this.shouldRunGit(options?.noGit)) {\n await this.stager().run(['mv', fromPath, toPath]);\n } else {\n await fs.rename(fromPath, toPath);\n }\n\n this.logger?.info('Resource moved', { fromUri, toUri });\n }\n\n /** @param options.keepFile - Drop from the index only; leave the file on disk. */\n async remove(storageUri: string, options?: { noGit?: boolean; keepFile?: boolean }): Promise<void> {\n const filePath = this.resolveUri(storageUri);\n const keepFile = options?.keepFile ?? false;\n\n this.logger?.debug('Removing resource', { storageUri, keepFile });\n\n const useGit = this.shouldRunGit(options?.noGit);\n\n if (useGit) {\n const gitArgs = keepFile\n ? ['rm', '--cached', filePath]\n : ['rm', filePath];\n await this.stager().run(gitArgs);\n this.logger?.info('Resource removed', { storageUri, keepFile, git: true });\n return;\n }\n\n if (keepFile) {\n this.logger?.info('Resource removed from index (file kept on disk)', { storageUri });\n return;\n }\n\n try {\n await fs.unlink(filePath);\n this.logger?.info('Resource removed', { storageUri });\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n this.logger?.warn('Resource file already absent', { storageUri });\n return;\n }\n throw error;\n }\n }\n\n resolveUri(storageUri: string): string {\n if (!storageUri.startsWith('file://')) {\n throw new Error(`Invalid storage URI (must start with file://): ${storageUri}`);\n }\n const relativePath = storageUri.slice('file://'.length);\n return path.join(this.projectRoot, relativePath);\n }\n}\n\n/** The file on disk is not the file the checksum names. */\nexport class ChecksumMismatchError extends Error {\n constructor(\n readonly storageUri: string,\n readonly expected: string,\n readonly actual: string,\n ) {\n super(\n `Checksum mismatch for ${storageUri}: expected ${expected.slice(0, 8)}... but got ${actual.slice(0, 8)}...\\n` +\n `The file on disk differs from the recorded checksum. Has it been modified since staging?`\n );\n this.name = 'ChecksumMismatchError';\n }\n}\n","/**\n * Deferred, deduped `git add`. The index is for humans who commit by hand, so\n * it must be current within seconds, not after every change.\n *\n * Serialized per repo — git's index is single-writer, and concurrent `git add`\n * fails on `index.lock` rather than retrying. Created on first use, never at\n * import: consumers of this package may never stage anything.\n */\n\nimport { execFile } from 'child_process';\nimport { resolve } from 'path';\nimport { promisify } from 'util';\nimport { recordGitCommand, recordGitStagingFailure } from '@semiont/observability';\n\nconst run = promisify(execFile);\n\nexport interface StagerOptions {\n /** Quiet period after the last change before staging. */\n flushMs?: number;\n /** Ceiling on staleness: stage this long after the OLDEST pending path even\n * if changes keep arriving. Without it a continuous append stream resets\n * the debounce forever and the index never updates. */\n maxWaitMs?: number;\n}\n\nexport interface Stager {\n /** Queue a path. Returns immediately; deduped against what is pending. */\n add(path: string): void;\n /** Run an order-sensitive command (`mv`, `rm`): pending adds flush first,\n * then this runs alone — it must not overtake the adds it depends on. */\n run(args: string[]): Promise<void>;\n /** Stage everything pending now. */\n flush(): Promise<void>;\n /** Paths queued and not yet staged. */\n pending(): number;\n /** Drain and stop. A stopped process must leave nothing unstaged. */\n dispose(): Promise<void>;\n}\n\n/**\n * git exposes NO index-lock wait — `core.filesRefLockTimeout` and friends cover\n * refs, packed-refs, reftable and credentials, not the index — and `git add`\n * against a held lock fails in ~21 ms. So the wait is ours. Measured\n * 2026-09-08: this schedule absorbed an 800 ms external hold on attempt 5, at\n * 0.85 s of a ~3.15 s budget.\n */\nconst LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 1600];\n\n/** ONLY the lock race is retried; a bad pathspec or a broken repo fails fast. */\nconst isIndexLockContention = (error: unknown): boolean =>\n typeof error === 'object' &&\n error !== null &&\n (error as { code?: unknown }).code === 128 &&\n String((error as { stderr?: unknown }).stderr ?? '').includes('index.lock');\n\nconst DEFAULT_FLUSH_MS = 250;\nconst DEFAULT_MAX_WAIT_MS = 2_000;\n\n/**\n * One Stager per repo, keyed by resolved path.\n *\n * git's index is single-writer, and this module serializes per INSTANCE. Two\n * instances on one repo — the content store and the event log each built their\n * own — each believed it was the only writer and raced the other into\n * `index.lock`, killing the Archivist (ARCHIVIST-GIT-STAGER-CRASH).\n *\n * The FIRST caller's options win. A later caller cannot silently re-tune a\n * shared stager's debounce out from under the first.\n */\nconst stagers = new Map<string, Stager>();\n\nexport function createStager(cwd: string, options: StagerOptions = {}): Stager {\n const key = resolve(cwd);\n const existing = stagers.get(key);\n if (existing) return existing;\n const stager = buildStager(key, options);\n stagers.set(key, stager);\n return stager;\n}\n\nfunction buildStager(cwd: string, options: StagerOptions = {}): Stager {\n const flushMs = options.flushMs ?? DEFAULT_FLUSH_MS;\n const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;\n\n const queued = new Set<string>();\n let timer: NodeJS.Timeout | undefined;\n let oldestAt: number | undefined;\n let inFlight: Promise<void> = Promise.resolve();\n let disposed = false;\n\n const git = async (args: string[]): Promise<void> => {\n const started = performance.now();\n try {\n for (let attempt = 0; ; attempt++) {\n try {\n await run('git', args, { cwd });\n return;\n } catch (error) {\n const last = attempt >= LOCK_RETRY_DELAYS_MS.length;\n if (last || !isIndexLockContention(error)) throw error;\n await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAYS_MS[attempt]!));\n }\n }\n } finally {\n recordGitCommand(args[0] ?? 'git', performance.now() - started);\n }\n };\n\n /** Serialize every invocation: one git per repo, no `index.lock` contention. */\n const serialize = (work: () => Promise<void>): Promise<void> => {\n inFlight = inFlight.then(work, work);\n return inFlight;\n };\n\n const clearTimer = () => {\n if (timer) { clearTimeout(timer); timer = undefined; }\n };\n\n const drain = (): Promise<void> => {\n clearTimer();\n if (queued.size === 0) return inFlight;\n const batch = [...queued];\n queued.clear();\n oldestAt = undefined;\n return serialize(() =>\n git(['add', ...batch]).catch((error: unknown) => {\n // `queued` was emptied BEFORE the command ran, so a dropped batch is\n // permanently missing from the index — a quieter failure than the\n // crash and harder to notice. Re-queue it.\n //\n // ONLY for a lock race that outlived the retries. Re-queueing a\n // PERMANENT failure (bad pathspec, broken repo) would re-arm forever,\n // spinning one subprocess per cycle and burying the real error.\n const lock = isIndexLockContention(error);\n if (lock) {\n for (const path of batch) queued.add(path);\n if (oldestAt === undefined) oldestAt = Date.now();\n arm();\n }\n // NEVER rethrow. Staging the index is not in the critical path; a\n // failure is DEGRADED, not down. Rejecting here is what killed the\n // Archivist, and it would keep killing it through any caller that\n // forgot a `.catch` — so the guarantee lives at this boundary rather\n // than in every caller's discipline.\n recordGitStagingFailure(lock ? 'index-lock' : 'other');\n }),\n );\n };\n\n const arm = () => {\n clearTimer();\n if (disposed || queued.size === 0) return;\n // Debounce, but never past the staleness ceiling measured from the OLDEST\n // pending path — a busy stream must not defer staging indefinitely.\n const sinceOldest = oldestAt === undefined ? 0 : Date.now() - oldestAt;\n const wait = Math.max(0, Math.min(flushMs, maxWaitMs - sinceOldest));\n timer = setTimeout(() => { void drain(); }, wait);\n timer.unref?.();\n };\n\n return {\n add(path) {\n if (disposed) return;\n if (queued.size === 0) oldestAt = Date.now();\n queued.add(path);\n arm();\n },\n run(args) {\n return drain().then(() =>\n serialize(() =>\n git(args).catch((error: unknown) => {\n recordGitStagingFailure(isIndexLockContention(error) ? 'index-lock' : 'other');\n }),\n ),\n );\n },\n flush() {\n return drain();\n },\n pending() {\n return queued.size;\n },\n async dispose() {\n stagers.delete(cwd);\n await drain();\n disposed = true;\n clearTimer();\n await inFlight;\n },\n };\n}\n","/**\n * Checksum utilities for content verification\n */\n\nimport { createHash } from 'crypto';\n\n/**\n * Calculate SHA-256 checksum of content\n * @param content The content to hash\n * @returns Hex-encoded SHA-256 hash\n */\nexport function calculateChecksum(content: string | Buffer): string {\n const hash = createHash('sha256');\n hash.update(content);\n return hash.digest('hex');\n}\n\n/**\n * Verify content against a checksum\n * @param content The content to verify\n * @param checksum The expected checksum\n * @returns True if content matches checksum\n */\nexport function verifyChecksum(content: string | Buffer, checksum: string): boolean {\n return calculateChecksum(content) === checksum;\n}\n","/**\n * TextExtractor — DERIVING text from bytes that carry none of their own.\n *\n * Scope note (READ-VS-EXTRACT P2/P3): this file used to hold a registry covering\n * both ways a resource yields text — decoding (charset-aware `Buffer → string`)\n * and deriving (parse a PDF, OCR it when there is no text layer). Those shared a\n * name and almost nothing else: microseconds vs. minutes,\n * total determinism vs. none across engine versions, no canonical artifact vs.\n * exactly one, and anyone-with-bytes vs. the Smelter alone. The registry made\n * them interchangeable at every call site.\n *\n * Decoding left: it is `decodeRepresentation` in `@semiont/core`, called\n * directly. What remains here is the deriving half, reached through\n * `derivingExtractorFor` and callable only with the store that persists its\n * output.\n *\n * Anchoring is unaffected: annotations anchor to native geometry (`items`),\n * never to extracted-text offsets, so re-derivation can never break an anchor.\n */\n\nimport { yieldsGeometryOf, type PdfTextItem } from '@semiont/core';\nimport type { AnchoredTextStore } from './anchored-text-store';\nimport { pdfExtractor } from './pdf-extractor';\n\nexport interface ExtractedText {\n /** Discriminant — mirrors the wire member (WIRE-UNION-DISCRIMINANTS P5c/D6). */\n kind: 'extracted';\n /** Reading-order plain text, ready for the chunker. */\n text: string;\n /**\n * Positioned text runs indexing `text`, for callers that anchor; absent for\n * pure text, where character offsets are the anchor. Named `items` to match\n * `AnchoredText`/`PdfTextLayer` — one concept, one name, and no collision\n * with the OCR engine's own \"blocks\" (which are page regions, not runs).\n */\n items?: PdfTextItem[];\n method: 'text-passthrough' | 'pdf-text-layer' | 'table' | 'form' | 'ocr';\n pdfClass?: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';\n /**\n * How well the engine read the pixels, when any of this text came from OCR.\n *\n * Extraction quality, deliberately NOT anchor confidence: the two answer\n * different questions. `AnchorConfidence` asks whether the renderer\n * relocated a stored span in the current text, and for a PDF the answer is\n * always \"exactly\" — the viewrect is absolute. This asks whether the glyphs\n * under that box were read correctly, which no client can recompute.\n * Reported for operators rather than stored on annotations, following the\n * existing rule that anchor-audit detail belongs in logs.\n */\n ocrConfidence?: {\n /** Mean per-word confidence, 0–100. */\n mean: number;\n /** Words the engine was unsure of — the number worth acting on. */\n lowConfidenceWords: number;\n totalWords: number;\n };\n /**\n * 1-indexed pages this extraction could not read — present only when a\n * document is partially covered (class C). Naming the gap is the point:\n * without it a hybrid document embeds its native pages and says nothing\n * about the rest, so coverage silently overstates what search can see.\n * This is the work list OCR consumes.\n */\n unreadPages?: number[];\n}\n\n/**\n * A named decline — an extractor that ran and decided it cannot yield text\n * says why, so the settled signal can carry the class reason (a bare null\n * could not name its class; SMELTER-MEDIA-TYPES Phase 0 log, note a).\n */\nexport interface ExtractionDecline {\n /** Discriminant — mirrors the wire member (WIRE-UNION-DISCRIMINANTS P5c/D6). */\n kind: 'declined';\n declined: 'no-text-layer' | 'encrypted' | 'corrupt' | 'too-large';\n}\n\n/**\n * Where a derivation may reuse an earlier recognition, and under what key.\n *\n * The caller supplies the key, and derives it from the bytes it actually\n * holds — `calculateChecksum` over the same Buffer it passes to `extract()` —\n * never from a descriptor's claim. A catalog-derived key can race a byte\n * change (bytes fetched at one moment, descriptor read at another) and file\n * or read geometry under an identity that does not describe the bytes being\n * extracted. The write path made recompute-over-claim the rule\n * (PERSIST-ANCHORS P1b); readers mirror it (P1c). One SHA-256 over bytes\n * already in memory is noise against the engine pass a hit avoids.\n *\n * REQUIRED, and that is the ownership rule (READ-VS-EXTRACT P2). It carries an\n * `AnchoredTextStore`, and only the Smelter holds one — so deriving is reachable\n * exactly to the process that can persist what it derived. The restriction is a\n * capability the caller must already hold, not a convention it must remember:\n * a would-be second producer cannot construct the argument, so it cannot compile.\n *\n * The seam is `extract()` itself (PERSIST-ANCHORS D1/P2b): a hit returns the\n * FINISHED outcome — classification, geometry, provenance, or a named decline —\n * so neither the native parse nor the engine runs.\n */\nexport interface ExtractionCache {\n key: string;\n store: AnchoredTextStore;\n}\n\n/**\n * Deriving text from bytes that carry none of their own.\n *\n * Named `ContentExtractor` until READ-VS-EXTRACT P3: the `Content` prefix named\n * the INPUT, when what distinguishes this type is that it produces TEXT — by\n * deriving, which since P3 is the only thing \"extraction\" means here. WHERE a\n * media type's text comes from at all is core's `TextSource`, which spans both\n * routes and is therefore not called extraction.\n *\n * Whether a text source yields positioned runs lives in `@semiont/core`'s\n * `yieldsGeometryOf`, NOT here (P1). It is a property of the source, and that\n * vocabulary is core's — declaring it per-implementation made it two facts that\n * could disagree, and forced consumers asking about a media type to resolve an\n * implementation to find out. `text-extractor.test.ts` gates core's answer\n * against what these extractors actually produce.\n */\nexport interface TextExtractor {\n /**\n * Derive text WITH geometry from bytes that carry no text of their own, or\n * decline with the class reason (scanned-without-OCR, encrypted, corrupt).\n * The caller skips embedding and settles skipped with that reason.\n *\n * Expensive, non-deterministic across engine versions, and the sole producer\n * of a canonical artifact — which is why `cache` is required rather than\n * optional (see `ExtractionCache`).\n */\n extract(content: Buffer, mediaType: string, cache: ExtractionCache): Promise<ExtractedText | ExtractionDecline>;\n}\n\n/**\n * The deriving extractor for a media type, or `null` when its text needs no\n * deriving.\n *\n * **This replaced a `Record<TextSource, TextExtractor | null>` keyed by\n * strategy (READ-VS-EXTRACT P2), and the deletion is the point.** That map held\n * one real extractor, a `null`, and — under 'decode' — a one-line wrapper around\n * core's `decodeRepresentation`, which five sites in `@semiont/make-meaning`\n * already called directly. Resolving \"give me an extractor for this media type\"\n * therefore returned, half the time, a trivial function dressed as the same\n * capability as OCR: identical at the call site, wildly different in cost,\n * determinism, and who is allowed to run it. That symmetry is what let a\n * detection worker OCR scanned PDFs for four months without anyone reading it as\n * a category error (#739).\n *\n * Decoding is now a direct `decodeRepresentation()` call at the two sites that\n * need it. There is no registry to resolve, so there is no way to reach OCR by\n * asking a generic question — and a caller that gets a non-null answer here still\n * cannot run it without an `AnchoredTextStore`.\n *\n * Keyed by P1's `yieldsGeometryOf`, so this and the Smelter's publish gate cannot\n * disagree about which media types have a canonical artifact.\n */\nexport function derivingExtractorFor(mediaType: string): TextExtractor | null {\n return yieldsGeometryOf(mediaType) ? pdfExtractor : null;\n}\n","/**\n * PDF extractor — the 'pdf-text-layer' strategy (SMELTER-MEDIA-TYPES).\n *\n * Wraps the shared `extractPdfTextLayer` reader (detection's other consumer)\n * and turns a PDF into text plus the geometry that indexes it, by class:\n *\n * A native text layer → read directly\n * B scanned → read the page pixels by OCR\n * C hybrid → both, with any page still unread reported\n * D tables → grid pages rewritten as markdown rows\n * E forms → AcroForm values folded in, anchored to widgets\n * F/G encrypted, corrupt → declined by name, from the parser error\n *\n * Everything runs inline. OCR was originally planned off the hot path, but\n * the Smelter's lanes are per-resource and concurrent, so a slow page delays\n * only its own resource — see SMELTER-MEDIA-TYPES Design §4 (revised).\n */\n\nimport { isObject, type PdfTextItem } from '@semiont/core';\nimport { extractPdfTextLayer } from './extract-pdf-text-layer';\nimport type { TextExtractor, ExtractedText, ExtractionDecline } from './text-extractor';\nimport type { PdfTextLayer } from './pdf-text-layer';\nimport { detectTable, renderTable } from './pdf-tables';\nimport { extractPageImages } from './pdf-page-images';\nimport { recognizeImages } from './ocr';\nimport { mapWordsToItems } from './ocr-geometry';\n\n\n/** One OCR'd page: its text, and word geometry with page-local offsets. */\ninterface OcrPageResult {\n text: string;\n items: PdfTextItem[];\n /** Per-word confidences, kept only long enough to summarize. */\n confidences: number[];\n}\n\n/**\n * Largest PDF this will attempt, in bytes.\n *\n * A PDF is a compressed container, so input size bounds nothing on its own —\n * but it is the one number available before the parser touches the file, and\n * refusing here means a hostile or pathological document never gets to expand\n * inside pdf.js. Chosen to sit above real corpora (a few hundred pages of\n * scanned FOIA material runs tens of megabytes) while still being a ceiling.\n *\n * A starting point, not a measured optimum — revisit against a real corpus\n * (SMELTER-MEDIA-TYPES, live-testing follow-up). The per-image budget in\n * `pdf-page-images` guards the decoded side, which is where the unbounded\n * growth actually lives.\n */\nexport const MAX_PDF_BYTES = 200 * 1024 * 1024;\n\n/** Whether a document is small enough to attempt. Exported because the\n * threshold is a judgement, and judgements deserve tests that do not have to\n * materialize two hundred megabytes to ask the question. */\nexport function withinByteBudget(bytes: number): boolean {\n return Number.isFinite(bytes) && bytes >= 0 && bytes <= MAX_PDF_BYTES;\n}\n\n/** Words below this are worth an operator's attention. Tesseract reports\n * 0–100; readable text on a clean scan sits well above this. */\nconst LOW_CONFIDENCE = 60;\n\nfunction summarize(confidences: number[]): ExtractedText['ocrConfidence'] {\n if (confidences.length === 0) return undefined;\n const total = confidences.reduce((sum, c) => sum + c, 0);\n return {\n mean: Math.round((total / confidences.length) * 10) / 10,\n lowConfidenceWords: confidences.filter((c) => c < LOW_CONFIDENCE).length,\n totalWords: confidences.length,\n };\n}\n\n/**\n * Read the pages that have no text layer by OCR'ing their pixels. Returns\n * results only for pages that yielded text; a page absent from the map stayed\n * unread. Pages with no extractable image never reach the engine.\n *\n * Each word is anchored through the matrix that placed its image, so a scanned\n * page ends up carrying the same kind of geometry a native one does.\n */\nasync function ocrPages(\n content: Buffer,\n pageNumbers?: number[],\n): Promise<OcrPageResult> {\n // Pure recognition since PERSIST-ANCHORS P2b: the caching seam lives at\n // `extract()`, which stores and serves the FINISHED outcome. This function\n // neither consults nor writes the store — it reads pixels.\n const imagesByPage = await extractPageImages(content, pageNumbers);\n if (imagesByPage.size === 0) return { text: '', items: [], confidences: [] };\n\n // One batch for the whole document: worker startup dominates per-page cost.\n const pages = [...imagesByPage.keys()].sort((a, b) => a - b);\n const batch = pages.flatMap((page) => imagesByPage.get(page)!.map((image) => image.png));\n const recognized = await recognizeImages(batch);\n\n const byPage = new Map<number, OcrPageResult>();\n let cursor = 0;\n for (const page of pages) {\n const images = imagesByPage.get(page)!;\n let text = '';\n const items: PdfTextItem[] = [];\n const confidences: number[] = [];\n for (const image of images) {\n const result = recognized[cursor++];\n if (!result?.text.trim()) continue;\n if (text) text += '\\n';\n items.push(...mapWordsToItems(result.words, image, page, text.length));\n confidences.push(...result.words.map((word) => word.confidence));\n text += result.text;\n }\n if (text) byPage.set(page, { text, items, confidences });\n }\n\n // Joined at base 0 — the document's own coordinates. Class C shifts by the\n // native text length at its call site.\n return joinPages(byPage, 0);\n}\n\n/**\n * Recovered pages in page order as one block of text, with every word's\n * offsets shifted to where its page actually lands. `baseOffset` is where this\n * block begins in the document being assembled.\n */\nfunction joinPages(byPage: Map<number, OcrPageResult>, baseOffset: number): OcrPageResult {\n let text = '';\n const items: PdfTextItem[] = [];\n const confidences: number[] = [];\n for (const [, page] of [...byPage.entries()].sort((a, b) => a[0] - b[0])) {\n if (text) text += '\\n\\n';\n const shift = baseOffset + text.length;\n for (const item of page.items) {\n items.push({ ...item, start: item.start + shift, end: item.end + shift });\n }\n confidences.push(...page.confidences);\n text += page.text;\n }\n return { text, items, confidences };\n}\n\n/**\n * pdf.js signals a password-protected document with PasswordException.\n * Matched by name, not instanceof — pdf.js exception classes descend from\n * its own BaseException, not Error. Everything else the parser throws is\n * class G.\n */\nexport function classifyPdfError(error: unknown): 'encrypted' | 'corrupt' {\n return isObject(error) && error.name === 'PasswordException' ? 'encrypted' : 'corrupt';\n}\n\n/**\n * Class E — fold filled AcroForm values into the embedding text.\n *\n * A form's answers live in the form dictionary, not the drawn page, so a\n * naive text-layer read returns the blank labels and loses every value.\n * Each value is appended as a `name: value` line and anchored by its widget\n * rectangle, so `items` stays a complete geometry index of `text`.\n */\nfunction foldFormFields(layer: PdfTextLayer): ExtractedText {\n let text = layer.text;\n const items: PdfTextItem[] = [...layer.items];\n for (const field of layer.fields) {\n const start = text.length + `${field.name}: `.length;\n text += `${field.name}: ${field.value}\\n`;\n items.push({\n start,\n end: start + field.value.length,\n page: field.page,\n x: field.x,\n y: field.y,\n width: field.width,\n height: field.height,\n });\n }\n return { kind: 'extracted', text, items, method: 'form', pdfClass: 'E' };\n}\n\n/**\n * Class D — rewrite grid pages as markdown, keep every other page verbatim.\n *\n * Returns null when no page is a table, so the caller falls back to class A.\n * Pages are shaped independently: the common report — prose sections around\n * an outcome table — gets row-coherent tables without disturbing its prose.\n */\nfunction shapeTables(layer: PdfTextLayer): ExtractedText | null {\n const pages = layer.pages.map((page) => {\n const pageItems = layer.items.filter((item) => item.page === page.pageNumber);\n return { page, pageItems, table: detectTable(pageItems, layer.text) };\n });\n if (!pages.some((p) => p.table)) return null;\n\n let text = '';\n const items: PdfTextItem[] = [];\n for (const { page, pageItems, table } of pages) {\n if (table) {\n const rendered = renderTable(table, page.pageNumber, text.length);\n text += rendered.text;\n items.push(...rendered.items);\n } else {\n // Verbatim page: copy its slice and shift its runs' offsets to match.\n const shift = text.length - page.textStart;\n text += layer.text.slice(page.textStart, page.textEnd);\n for (const item of pageItems) {\n items.push({ ...item, start: item.start + shift, end: item.end + shift });\n }\n }\n }\n return { kind: 'extracted', text, items, method: 'table', pdfClass: 'D' };\n}\n\nexport const pdfExtractor: TextExtractor = {\n // Every non-declined PDF extraction carries positioned runs — native text\n // layers and OCR both anchor by page geometry. That fact is declared in core\n // ('pdf-text-layer' → true) rather than here; this comment records the\n // behavior the census gate holds core's answer to.\n async extract(content, _mediaType, cache) {\n // The seam (PERSIST-ANCHORS D1/P2b): consult the store for the FINISHED\n // outcome before anything runs — byte gate, native parse, image decode\n // and OCR are all part of the stored answer, classification included.\n // The pre-P2b seam skipped only Tesseract, on the argument that the\n // text-layer parse \"has to run either way\" — true on a miss, false on a\n // hit. A hit is returned WHOLE, which is sound because the outcome is a\n // pure function of the bytes, the key IS the bytes' identity (the\n // caller's producer-supplied checksum — P1b/P1c), and STAMP covers the\n // code that did the deriving. Declines are first-class hits: \"we read\n // this and there was nothing\" costs a full recognition pass to discover,\n // so the negative is precisely the result worth keeping.\n const hit = await cache.store.read(cache.key);\n if (hit) return hit;\n\n const outcome = await extractPdf(content);\n\n // Store failures stay silent — the store may make things faster, never\n // make them fail. The path that must insist on a write is the smelter's\n // re-anchor publish (P0), not this seam.\n //\n // The catch is HERE rather than inside the store: `write` throws, so this\n // is where \"best-effort\" is chosen, by the seam that wants it. Previously\n // the store swallowed for every caller and this comment described a\n // property it did not own.\n try {\n if (outcome.kind === 'declined') await cache.store.write(cache.key, outcome);\n else if (outcome.items) await cache.store.write(cache.key, { ...outcome, items: outcome.items });\n } catch {\n // Cached nothing; the outcome below is still correct.\n }\n return outcome;\n },\n};\n\n/** The uncached pipeline: classify, shape, and read the document. */\nasync function extractPdf(content: Buffer): Promise<ExtractedText | ExtractionDecline> {\n // Before the parser sees it: everything downstream — parse, image decode,\n // OCR — expands from these bytes, so this is the only gate that costs\n // nothing to enforce.\n if (!withinByteBudget(content.length)) return { kind: 'declined', declined: 'too-large' };\n\n let layer;\n try {\n layer = await extractPdfTextLayer(content);\n } catch (error) {\n return { kind: 'declined', declined: classifyPdfError(error) };\n }\n // Class B — no text operators anywhere: the characters exist only as\n // pixels, so read them. 'no-text-layer' now means OCR genuinely came up\n // empty, not that we never tried.\n if (!layer) {\n const ocr = await ocrPages(content);\n if (!ocr.text) return { kind: 'declined', declined: 'no-text-layer' };\n const confidence = summarize(ocr.confidences);\n return {\n kind: 'extracted',\n text: ocr.text,\n items: ocr.items,\n method: 'ocr',\n pdfClass: 'B',\n ...(confidence ? { ocrConfidence: confidence } : {}),\n };\n }\n\n // One class per document, so a filled form outranks a grid: its values\n // are content that exists nowhere else, while a table's cells are at\n // worst reordered.\n const shaped = layer.fields.length > 0\n ? foldFormFields(layer)\n : shapeTables(layer)\n ?? { kind: 'extracted' as const, text: layer.text, items: layer.items, method: 'pdf-text-layer' as const, pdfClass: 'A' as const };\n\n // A page with no text-showing operators is scanned: its characters exist\n // only as pixels. Report those pages rather than dropping them silently —\n // the document embeds what it can now, and this is the list OCR works\n // from. 'C' (hybrid) replaces the plain-prose label only; a form or table\n // keeps its own class, and carries the gap just the same.\n const unreadPages = layer.pages.filter((page) => !page.hasTextLayer).map((page) => page.pageNumber);\n if (unreadPages.length === 0) return shaped;\n\n // Class C — read the scanned pages and append what OCR recovers. Appended\n // rather than spliced into reading order, so the items already computed\n // for the native pages keep pointing at the right characters; OCR text\n // carries no geometry of its own this phase (mapping pixel boxes back to\n // page points needs the image's placement transform — #739's critical\n // path, not embedding's).\n const recovered = await ocrPages(content, unreadPages);\n const readPages = new Set(recovered.items.map((item) => item.page));\n const stillUnread = unreadPages.filter((page) => !readPages.has(page));\n const hybridClass = shaped.pdfClass === 'A' ? 'C' as const : shaped.pdfClass;\n if (!recovered.text) {\n return { ...shaped, unreadPages: stillUnread, pdfClass: hybridClass };\n }\n // Appended, so the native pages' items keep pointing at the right\n // characters; the OCR'd words are offset to where they actually land.\n const shift = shaped.text.length;\n const ocr: OcrPageResult = {\n text: recovered.text,\n items: recovered.items.map((item) => ({ ...item, start: item.start + shift, end: item.end + shift })),\n confidences: recovered.confidences,\n };\n const confidence = summarize(ocr.confidences);\n return {\n ...shaped,\n text: `${shaped.text}${ocr.text}\\n`,\n items: [...(shaped.items ?? []), ...ocr.items],\n method: 'ocr',\n pdfClass: hybridClass,\n ...(confidence ? { ocrConfidence: confidence } : {}),\n ...(stillUnread.length > 0 ? { unreadPages: stillUnread } : {}),\n };\n}\n","/**\n * PDF Text Layer Extraction\n *\n * Extracts positioned text from native, non-scanned PDFs using pdfjs-dist.\n * Returns null for scanned/image-only PDFs (no text items).\n *\n * Coordinates are in PDF point space, originating from the bottom-left.\n * The Y-flip to canvas pixels happens downstream.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport { STANDARD_FONT_DATA_URL } from './pdfjs-assets';\nimport { isObject, isString, isNumber, isArray, anchorRuns, isTextRun, type PdfTextItem } from '@semiont/core';\nimport type { PdfTextLayer, PdfPageInfo, PdfFormField } from './pdf-text-layer';\n\n/**\n * One entry from pdf.js's `getFieldObjects()` map, narrowed to a filled\n * field. The API types entries as bare `Object`, so every field is checked:\n * group entries (a parent with `kidIds`) carry `page: -1` and no value and\n * are rejected here, leaving the widgets that actually hold content.\n */\nfunction toFormField(entry: unknown): PdfFormField | null {\n if (!isObject(entry)) return null;\n const { name, value, page, rect } = entry;\n if (!isString(name) || !isString(value) || !value.trim()) return null;\n if (!isNumber(page) || page < 0) return null;\n if (!isArray(rect) || rect.length < 4 || !rect.every(isNumber)) return null;\n const [x1, y1, x2, y2] = rect as [number, number, number, number];\n return {\n name,\n value: value.trim(),\n page: page + 1, // pdf.js reports 0-indexed; PdfTextItem is 1-indexed\n x: Math.min(x1, x2),\n y: Math.min(y1, y2),\n width: Math.abs(x2 - x1),\n height: Math.abs(y2 - y1),\n };\n}\n\n/**\n * Filled AcroForm values, one per field name (first filled widget wins, so a\n * radio group contributes a single answer). Returns [] for a document with\n * no form. XFA forms are out of scope: whatever their AcroForm shell exposes\n * is read the same way, and anything else simply yields no fields.\n */\nasync function readFormFields(doc: pdfjs.PDFDocumentProxy): Promise<PdfFormField[]> {\n const fieldObjects = await doc.getFieldObjects();\n if (!fieldObjects) return [];\n const byName = new Map<string, PdfFormField>();\n for (const entries of Object.values(fieldObjects)) {\n if (!isArray(entries)) continue;\n for (const entry of entries) {\n const field = toFormField(entry);\n if (field && !byName.has(field.name)) byName.set(field.name, field);\n }\n }\n return [...byName.values()];\n}\n\nexport async function extractPdfTextLayer(\n bytes: Uint8Array | Buffer\n): Promise<PdfTextLayer | null> {\n // A private copy, for two pdf.js contracts at once: it refuses Node\n // Buffers outright (\"provide binary data as Uint8Array\"), and it CONSUMES\n // the array it is given — the underlying ArrayBuffer is transferred and\n // detached, which would silently zero the caller's bytes. Callers keep\n // their bytes; pdf.js gets its own.\n const data = new Uint8Array(bytes);\n // pdf.js v5 removed the isEvalSupported option; this path only calls\n // getTextContent (no rendering / no PDF functions).\n const loadingTask = pdfjs.getDocument({ data, standardFontDataUrl: STANDARD_FONT_DATA_URL });\n\n try {\n // Inside the try so the finally's destroy() also runs when the\n // parse rejects (encrypted/corrupt input — the extractor's decline\n // path classifies that throw).\n const doc = await loadingTask.promise;\n const pages: PdfPageInfo[] = [];\n const items: PdfTextItem[] = [];\n let text = '';\n\n for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {\n const page = await doc.getPage(pageNum);\n const viewport = page.getViewport({ scale: 1.0 });\n const content = await page.getTextContent(); // all text items on the page\n const pageTextStart = text.length;\n\n // `anchorRuns` owns the offset and separator convention; the\n // browser canvas builds its page the same way, so a rectangle\n // quotes identically whichever side captured it. Marked-content\n // items (no `str`) are filtered here, at the pdf.js boundary —\n // core stays free of pdfjs-dist.\n const page1 = anchorRuns(content.items.filter(isTextRun), pageNum);\n\n // Offsets come back page-local; shift them into the document text.\n for (const item of page1.items) {\n items.push({ ...item, start: item.start + pageTextStart, end: item.end + pageTextStart });\n }\n text += page1.text;\n text += '\\n'; // page break\n\n pages.push({\n pageNumber: pageNum,\n widthPt: viewport.width,\n heightPt: viewport.height,\n textStart: pageTextStart,\n textEnd: text.length,\n hasTextLayer: page1.items.length > 0,\n });\n }\n\n // A document with no drawn text is a scanned page (class B) even when\n // it carries an AcroForm — form values augment a text layer, they do\n // not substitute for one. Keeping this condition on text items alone\n // also keeps the reader's null contract stable for detection.\n if (!pages.some((page) => page.hasTextLayer)) return null;\n\n return { pages, text, items, fields: await readFormFields(doc) };\n } finally {\n // Release the pdf.js document — Phase 2 runs this in a long-lived worker\n // pool. pdf.js 6.0 removed PDFDocumentProxy.destroy(); teardown moved to\n // PDFDocumentLoadingTask.destroy().\n await loadingTask.destroy();\n }\n}\n","/**\n * Where pdf.js finds the asset bundles it does not carry in its main build.\n *\n * pdf.js ships the Standard 14 font programs (Foxit substitutes for Helvetica,\n * Times, Courier, Symbol, ZapfDingbats) as separate `.pfb` files rather than in\n * `pdf.mjs`. Without a `standardFontDataUrl` it cannot load them, and every\n * document that references a standard font logs\n *\n * Warning: UnknownErrorException: Ensure that the `standardFontDataUrl` API\n * parameter is provided.\n *\n * once per font per document — 66 lines on a single 28-page PDF, drowning real\n * output. That noise is the reason to fix it; text extraction itself was never\n * affected, because `getTextContent()` reads the content stream and the font's\n * encoding, not its glyph outlines.\n *\n * Resolved through `require.resolve` rather than a path relative to this file:\n * the built `dist/` sits at a different depth than `src/`, and npm may hoist\n * `pdfjs-dist` to the workspace root or nest it under this package. Asking the\n * resolver is the only form that is correct in all of those, including inside\n * the service images where the tree is installed fresh.\n *\n * The trailing slash is required — pdf.js concatenates the filename onto this\n * string.\n */\n\nimport { createRequire } from 'module';\nimport path from 'path';\n\nconst require = createRequire(import.meta.url);\n\nexport const STANDARD_FONT_DATA_URL =\n `${path.join(path.dirname(require.resolve('pdfjs-dist/package.json')), 'standard_fonts')}${path.sep}`;\n","/**\n * Table reconstruction from PDF text-layer geometry (SMELTER-MEDIA-TYPES\n * class D).\n *\n * A PDF has no table structure — only positioned text runs. Read in reading\n * order a grid's cells interleave, so a row's values scatter across chunks\n * and semantic recall over an outcome table returns nothing useful. This\n * module recovers the grid from the geometry the reader already carries\n * (`PdfTextItem.x/y/width/height`), then renders markdown rows so a row's\n * cells stay adjacent for the shared chunker. No new dependency: the\n * clustering is the same arithmetic a table library would do, over data we\n * already have.\n *\n * PRECISION OVER RECALL. A false positive scrambles prose into a fake table;\n * a false negative merely falls back to class A, which is Phase 1 behavior.\n * So detection demands a strict, regular grid — every row the same cell\n * count, every column aligned — and declines everything else.\n */\n\nimport type { PdfTextItem } from '@semiont/core';\n\n/** A reconstructed cell: its text plus the bounding box of its runs. */\nexport interface TableCell {\n text: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** A header row plus at least two data rows — below this, prose in columns\n * is indistinguishable from a table. */\nconst MIN_ROWS = 3;\nconst MIN_COLUMNS = 2;\n\n/** Row grouping tolerance, as a fraction of text height: runs whose\n * baselines differ by less than half a line belong to one row. */\nconst ROW_TOLERANCE = 0.5;\n/** Horizontal gap that separates cells, as a fraction of text height. Word\n * spaces are far narrower; column gutters are far wider. */\nconst CELL_GAP = 0.8;\n\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n return sorted[Math.floor(sorted.length / 2)] ?? 0;\n}\n\n/** Group runs into visual rows, top of page first. */\nfunction groupRows(items: PdfTextItem[], tolerance: number): PdfTextItem[][] {\n const rows: PdfTextItem[][] = [];\n for (const item of [...items].sort((a, b) => b.y - a.y)) {\n const row = rows[rows.length - 1];\n if (row && Math.abs(row[0]!.y - item.y) <= tolerance) row.push(item);\n else rows.push([item]);\n }\n return rows;\n}\n\nfunction toCell(runs: PdfTextItem[], text: string): TableCell {\n const x = Math.min(...runs.map((r) => r.x));\n const y = Math.min(...runs.map((r) => r.y));\n const right = Math.max(...runs.map((r) => r.x + r.width));\n const top = Math.max(...runs.map((r) => r.y + r.height));\n return {\n text: runs.map((r) => text.slice(r.start, r.end)).join(' ').trim(),\n x,\n y,\n width: right - x,\n height: top - y,\n };\n}\n\n/** Split a row into cells: runs closer than a gutter belong to one cell. */\nfunction toCells(row: PdfTextItem[], gap: number, text: string): TableCell[] {\n const cells: TableCell[] = [];\n let current: PdfTextItem[] = [];\n for (const item of [...row].sort((a, b) => a.x - b.x)) {\n const previous = current[current.length - 1];\n if (previous && item.x - (previous.x + previous.width) > gap) {\n cells.push(toCell(current, text));\n current = [];\n }\n current.push(item);\n }\n if (current.length > 0) cells.push(toCell(current, text));\n return cells;\n}\n\n/**\n * Recover a grid from one page's runs, or null when the page is not a\n * regular table.\n */\nexport function detectTable(items: PdfTextItem[], text: string): TableCell[][] | null {\n if (items.length === 0) return null;\n const unit = median(items.map((i) => i.height).filter((h) => h > 0)) || 12;\n\n const rows = groupRows(items, unit * ROW_TOLERANCE).map((row) => toCells(row, unit * CELL_GAP, text));\n if (rows.length < MIN_ROWS) return null;\n\n const columnCount = rows[0]!.length;\n if (columnCount < MIN_COLUMNS) return null;\n if (!rows.every((row) => row.length === columnCount)) return null;\n\n // Every column must start at the same offset down the page; ragged left\n // edges mean prose that happens to wrap into columns, not a grid.\n for (let column = 0; column < columnCount; column++) {\n const lefts = rows.map((row) => row[column]!.x);\n if (Math.max(...lefts) - Math.min(...lefts) > unit) return null;\n }\n if (rows.some((row) => row.some((cell) => cell.text.length === 0))) return null;\n\n return rows;\n}\n\n/**\n * Render a grid as markdown rows, anchoring every cell to the geometry it\n * came from. `offset` is where this text lands in the assembled document, so\n * the returned items index the final string.\n */\nexport function renderTable(\n rows: TableCell[][],\n page: number,\n offset: number,\n): { text: string; items: PdfTextItem[] } {\n let text = '';\n const items: PdfTextItem[] = [];\n rows.forEach((row, rowIndex) => {\n text += '|';\n for (const cell of row) {\n text += ' ';\n const start = offset + text.length;\n text += cell.text;\n items.push({\n start,\n end: offset + text.length,\n page,\n x: cell.x,\n y: cell.y,\n width: cell.width,\n height: cell.height,\n });\n text += ' |';\n }\n text += '\\n';\n // Markdown needs the delimiter row for the header to read as a table.\n if (rowIndex === 0) text += `|${' --- |'.repeat(row.length)}\\n`;\n });\n return { text, items };\n}\n","/**\n * Embedded page images from a PDF — the pixels OCR reads.\n *\n * A scanned page holds its characters only as pixels inside an image object,\n * so reading it means getting that image out. We do NOT rasterize: pdf.js\n * decodes the embedded image in its worker (pure JS — JPEG, CCITT and JBIG2\n * decoders all live there) and hands back raw pixel planes, which means no\n * canvas gateway and no native dependency. Measured, not assumed — see\n * `.plans/SMELTER-MEDIA-TYPES.md` Resolved decision 10.\n *\n * Two consequences of extracting rather than rendering: we get the scan's own\n * resolution rather than choosing a render DPI (for a real scan that IS the\n * page, so it is what we want), and a page composed of vector overlays or\n * tiled strips yields more than one image, or none we can use. Anything we\n * cannot turn into pixels simply stays unread — never an error.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport { STANDARD_FONT_DATA_URL } from './pdfjs-assets';\nimport { isObject, isNumber, isString, isArray } from '@semiont/core';\nimport { encodePng } from './png-encode';\n\n/** pdf.js image kinds (`ImageKind` in its API). */\nconst GRAYSCALE_1BPP = 1;\nconst RGB_24BPP = 2;\nconst RGBA_32BPP = 3;\n\nconst IDENTITY: readonly number[] = [1, 0, 0, 1, 0, 0];\n\n/**\n * Largest image this will read, in pixels.\n *\n * Sizing this needs the WHOLE allocation chain, not just the decoded raster —\n * reading one image can hold several copies at once:\n *\n * pdf.js decoded samples 4 bytes/px worst case (RGBA; RGB is 3)\n * + `toRgb` conversion 3 bytes/px (RGBA and 1-bit both allocate a copy;\n * plain RGB is passed through, no copy)\n * + `encodePng` scanlines 3 bytes/px (`raw`, plus a filter byte per row)\n * + deflate output smaller, but live alongside the above\n * ────────────────────────────────────────────────────────────────────\n * ≈ 10 bytes/px transient peak for a single image\n *\n * So the budget below implies roughly half a gigabyte of transient peak for\n * one pathological page — the number to size a worker against. Stating three\n * bytes per pixel here (as an earlier revision did) understated it by ~3× and\n * gave a false sense of safety.\n *\n * Chosen to admit the legitimate large cases with headroom: US Letter at\n * 600dpi is ~34 MP and A0 at 300dpi is ~35 MP, against an ordinary US Letter\n * at 300dpi of ~8 MP.\n *\n * A starting point, not a measured optimum: revisit against a real scanned\n * corpus (SMELTER-MEDIA-TYPES, live-testing follow-up). Lowering the peak\n * itself means removing copies from the chain — passing the decoded samples\n * straight to the encoder — which is a refactor, not a smaller constant.\n */\nexport const MAX_IMAGE_PIXELS = 48_000_000;\n\n/** Worst-case bytes held per pixel while reading one image — the chain above.\n * Exported so the budget's real cost is asserted rather than assumed. */\nexport const PEAK_BYTES_PER_PIXEL = 10;\n\n/** Whether an image's dimensions are sane and inside the budget. Exported\n * because the threshold is a judgement, and judgements deserve tests. */\nexport function withinPixelBudget(width: number, height: number): boolean {\n if (!Number.isFinite(width) || !Number.isFinite(height)) return false;\n if (width <= 0 || height <= 0) return false;\n return width * height <= MAX_IMAGE_PIXELS;\n}\n\n/** An image painted on a page, with the matrix that placed it. */\nexport interface PlacedImage {\n ref: string;\n /** Natural pixel dimensions, as reported by the paint operator itself. */\n width: number;\n height: number;\n /** Maps the image's unit square onto the page, in PDF points. */\n ctm: number[];\n}\n\n/**\n * Walk an operator list and report every painted image with the matrix in\n * effect when it was painted.\n *\n * Exported for its own tests: the composition ORDER cannot be checked with a\n * generated fixture, because pdf-lib emits a single combined matrix per image\n * and identity × M equals M × identity. It is checked directly instead, with\n * two non-identity transforms.\n *\n * Order convention: `ctm = Util.transform(ctm, m)` puts each new matrix on the\n * right, so it applies to a point FIRST and the enclosing matrices after —\n * which is what PDF nesting means. `save`/`restore` bracket the stack.\n */\nexport function findPlacedImages(fnArray: number[], argsArray: unknown[][]): PlacedImage[] {\n const placed: PlacedImage[] = [];\n const stack: number[][] = [];\n let ctm: number[] = [...IDENTITY];\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i];\n const args = argsArray[i];\n if (op === pdfjs.OPS.save) {\n stack.push([...ctm]);\n } else if (op === pdfjs.OPS.restore) {\n ctm = stack.pop() ?? [...IDENTITY];\n } else if (op === pdfjs.OPS.transform) {\n if (isArray(args) && args.length >= 6 && args.every(isNumber)) {\n ctm = pdfjs.Util.transform(ctm, args as number[]);\n }\n } else if (op === pdfjs.OPS.paintImageXObject) {\n const ref = args?.[0];\n const width = args?.[1];\n const height = args?.[2];\n if (isString(ref) && isNumber(width) && isNumber(height)) {\n placed.push({ ref, width, height, ctm: [...ctm] });\n }\n }\n }\n return placed;\n}\n\n/**\n * The decoded samples, whichever byte view pdf.js chose.\n *\n * `/FlateDecode` images arrive as a `Uint8Array`; `/DCTDecode` (JPEG) — what\n * essentially every real scanned PDF uses — arrives as a `Uint8ClampedArray`,\n * which is NOT an instance of `Uint8Array`. Testing only for the latter\n * discarded every real scan while accepting every fixture in this repo, all\n * of which are Flate. Both index bytes identically, so both are read; the\n * clamped view is re-wrapped without copying its 12 MB buffer.\n */\nfunction asBytes(data: unknown): Uint8Array | null {\n if (data instanceof Uint8Array) return data;\n if (data instanceof Uint8ClampedArray) return new Uint8Array(data.buffer, data.byteOffset, data.length);\n return null;\n}\n\n/**\n * Normalize a decoded pdf.js image to 8-bit RGB, or null for a kind we do\n * not read. Unknown kinds leave the page unread rather than risk feeding an\n * OCR engine garbled pixels.\n */\nexport function toRgb(image: unknown): { width: number; height: number; rgb: Uint8Array } | null {\n if (!isObject(image)) return null;\n const { width, height, kind } = image;\n const data = asBytes(image.data);\n if (!isNumber(width) || !isNumber(height) || !data) return null;\n if (width <= 0 || height <= 0) return null;\n\n if (kind === RGB_24BPP) {\n return data.length >= width * height * 3 ? { width, height, rgb: data } : null;\n }\n\n if (kind === RGBA_32BPP) {\n if (data.length < width * height * 4) return null;\n const rgb = new Uint8Array(width * height * 3);\n for (let i = 0, o = 0; o < rgb.length; i += 4, o += 3) {\n rgb[o] = data[i]!;\n rgb[o + 1] = data[i + 1]!;\n rgb[o + 2] = data[i + 2]!;\n }\n return { width, height, rgb };\n }\n\n if (kind === GRAYSCALE_1BPP) {\n // Packed bilevel, rows padded to a byte boundary — the shape fax-encoded\n // scans arrive in. A set bit is white, matching pdf.js's own rendering.\n // If a real CCITT scan ever comes out inverted, this is the line to fix;\n // the failure mode is a page that OCRs to nothing, not corrupt output.\n const rowBytes = Math.ceil(width / 8);\n if (data.length < rowBytes * height) return null;\n const rgb = new Uint8Array(width * height * 3);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const bit = data[y * rowBytes + (x >> 3)]! & (0x80 >> (x & 7));\n const value = bit ? 0xFF : 0x00;\n const o = (y * width + x) * 3;\n rgb[o] = value;\n rgb[o + 1] = value;\n rgb[o + 2] = value;\n }\n }\n return { width, height, rgb };\n }\n\n return null;\n}\n\n/**\n * How long to wait for pdf.js to deliver one image before giving up on the\n * page. Generous: this is not a performance budget but a liveness backstop —\n * see `resolveImage`.\n */\nconst IMAGE_RESOLVE_TIMEOUT_MS = 30_000;\n\n/**\n * Resolve one image object; pdf.js delivers it asynchronously, so the callback\n * form is required — the synchronous getter throws.\n *\n * Two scopes, and asking the wrong one never answers. An image used by a single\n * page lives in `page.objs` as `img_p0_1`; an image used by MORE than one page\n * — a letterhead, a watermark, a scan pipeline that dedupes identical page\n * rasters — is promoted to pdf.js's global scope, renamed `g_d1_img_p1_1`, and\n * lives in `page.commonObjs`. `objs.get` on a global ref simply registers a\n * callback that is never invoked.\n *\n * The timeout is the second half, and it is about liveness rather than speed:\n * the smelter and the detection worker both `await` this, and a worker will not\n * claim another job while one is active — so a promise that never settles wedges\n * that worker permanently, on one bad document. Timing out yields `null`, which\n * leaves the page unread and reported, the same as an unreadable image kind.\n */\nfunction resolveImage(page: pdfjs.PDFPageProxy, ref: string): Promise<unknown> {\n // pdf.js marks globally-scoped objects with a `g_` prefix.\n const scope = ref.startsWith('g_') ? page.commonObjs : page.objs;\n return new Promise((resolve) => {\n const timer = setTimeout(() => resolve(null), IMAGE_RESOLVE_TIMEOUT_MS);\n const settle = (value: unknown) => {\n clearTimeout(timer);\n resolve(value);\n };\n try {\n scope.get(ref, settle);\n } catch {\n settle(null);\n }\n });\n}\n\n/** A page image ready for OCR, with everything needed to map results back. */\nexport interface PageImage {\n png: Buffer;\n /** Pixel dimensions of the decoded raster (may differ from the paint\n * operator's declared size if the image was resampled). */\n width: number;\n height: number;\n /** Maps the image's unit square onto the page, in PDF points. */\n ctm: number[];\n}\n\n/**\n * PNG-encoded images for the given pages (all pages when omitted), keyed by\n * 1-indexed page number, each with the matrix that placed it. Pages with no\n * usable image are absent from the map.\n */\nexport async function extractPageImages(\n bytes: Uint8Array | Buffer,\n pageNumbers?: number[],\n): Promise<Map<number, PageImage[]>> {\n const wanted = pageNumbers ? new Set(pageNumbers) : null;\n const loadingTask = pdfjs.getDocument({ data: new Uint8Array(bytes), standardFontDataUrl: STANDARD_FONT_DATA_URL });\n const byPage = new Map<number, PageImage[]>();\n\n try {\n const doc = await loadingTask.promise;\n for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {\n if (wanted && !wanted.has(pageNum)) continue;\n const page = await doc.getPage(pageNum);\n const ops = await page.getOperatorList();\n\n const images: PageImage[] = [];\n for (const placement of findPlacedImages(ops.fnArray, ops.argsArray)) {\n // Checked from the paint operator's own dimensions, BEFORE the\n // image is resolved — refusing after decoding would already\n // have paid the allocation this guards against.\n if (!withinPixelBudget(placement.width, placement.height)) continue;\n const rgb = toRgb(await resolveImage(page, placement.ref));\n if (!rgb) continue;\n images.push({\n png: encodePng(rgb.width, rgb.height, rgb.rgb),\n width: rgb.width,\n height: rgb.height,\n ctm: placement.ctm,\n });\n }\n if (images.length > 0) byPage.set(pageNum, images);\n }\n return byPage;\n } finally {\n await loadingTask.destroy();\n }\n}\n","/**\n * Minimal PNG encoder.\n *\n * OCR engines take an encoded image, while pdf.js hands back raw pixel\n * planes — this bridges the two. Deterministic, built on node's zlib, so a\n * package that deliberately carries no image dependency still does not.\n */\n\nimport zlib from 'zlib';\n\nconst CRC_TABLE = (() => {\n const table = new Int32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;\n table[n] = c;\n }\n return table;\n})();\n\nfunction crc32(buf: Buffer): number {\n let c = -1;\n for (const byte of buf) c = CRC_TABLE[(c ^ byte) & 0xFF]! ^ (c >>> 8);\n return (c ^ -1) >>> 0;\n}\n\nfunction chunk(type: string, data: Buffer): Buffer {\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length);\n const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(body));\n return Buffer.concat([length, body, crc]);\n}\n\n/** Encode 8-bit RGB pixels (length must be width × height × 3) as a PNG. */\nexport function encodePng(width: number, height: number, rgb: Uint8Array): Buffer {\n const stride = width * 3 + 1; // one filter byte per scanline\n const raw = Buffer.alloc(stride * height);\n for (let y = 0; y < height; y++) {\n raw[y * stride] = 0; // filter type: none\n Buffer.from(rgb.buffer, rgb.byteOffset + y * width * 3, width * 3)\n .copy(raw, y * stride + 1);\n }\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr[8] = 8; // bit depth\n ihdr[9] = 2; // color type: truecolor\n return Buffer.concat([\n Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),\n chunk('IHDR', ihdr),\n chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),\n chunk('IEND', Buffer.alloc(0)),\n ]);\n}\n","/**\n * OCR — reading text out of page pixels (tesseract.js).\n *\n * Runs inline, on the caller's thread of control, deliberately: the Smelter's\n * lanes are per-resource and concurrent (`groupBy` + `mergeMap`), so a slow\n * page delays only its own resource, never the fast text resources it shares\n * a worker with. Extraction stays ephemeral — nothing is cached, and a\n * rebuild re-reads the pixels (SMELTER-MEDIA-TYPES Design §3/§5).\n *\n * Deterministic for a pinned engine: the same bytes yield the same text, so\n * re-running costs time and nothing else.\n */\n\nimport { createRequire } from 'node:module';\nimport { createWorker } from 'tesseract.js';\nimport { isObject, isString } from '@semiont/core';\n\n/**\n * The vendored language data — `@tesseract.js-data/eng` ships the same\n * `eng.traineddata.gz` tesseract.js would otherwise fetch from a CDN, and\n * exports the directory holding it.\n *\n * OCR is core (SMELTER-MEDIA-TYPES decision 8), so it must never reach the\n * network at runtime: an air-gapped worker has to be able to read a scan, and\n * a CDN outage must not silently turn scanned documents unreadable. Because\n * this is an ordinary dependency, `npm install` vendors it into the smelter\n * and worker images — no Dockerfile fetch step, and the lockfile pins it.\n *\n * Resolved lazily so importing this module has no side effects.\n */\nlet cachedLangPath: string | undefined;\nfunction langPath(): string {\n if (cachedLangPath) return cachedLangPath;\n const data: unknown = createRequire(import.meta.url)('@tesseract.js-data/eng');\n if (!isObject(data) || !isString(data.langPath)) {\n throw new Error(\n 'Vendored OCR language data is missing or malformed: @tesseract.js-data/eng did not export a langPath',\n );\n }\n cachedLangPath = data.langPath;\n return cachedLangPath;\n}\n\n/**\n * The slice of tesseract's recognition tree this module reads. Declared\n * structurally rather than importing `Tesseract.Block`, so tests can build a\n * tree without satisfying a dozen fields nothing here looks at; a real\n * `Block[]` still satisfies it.\n */\nexport interface OcrBbox { x0: number; y0: number; x1: number; y1: number }\nexport interface OcrLine {\n /** The line's own box — the vertical extent shared by its words. */\n bbox: OcrBbox;\n words: { text: string; confidence: number; bbox: OcrBbox }[];\n}\nexport interface OcrBlock {\n paragraphs: { lines: OcrLine[] }[];\n}\n\n/** A recognized word, with the range it occupies in the assembled page text. */\nexport interface OcrWord {\n text: string;\n /** Offsets into `OcrPage.text` — `text.slice(start, end) === word.text`. */\n start: number;\n end: number;\n /** Image pixel space, top-left origin — mapped to PDF points downstream. */\n bbox: OcrBbox;\n confidence: number;\n}\n\nexport interface OcrPage {\n text: string;\n words: OcrWord[];\n}\n\n/**\n * Assemble a page's text from its recognition tree, recording where each word\n * lands as it is written.\n *\n * The text is built here rather than taken from tesseract's own `data.text`\n * precisely so the offsets are exact **by construction** — deriving offsets by\n * searching for words in a separately-produced string is where this kind of\n * code goes wrong. Words join with a space, lines with a newline, paragraphs\n * with a blank line.\n */\nexport function assemblePage(blocks: OcrBlock[] | null): OcrPage {\n let text = '';\n const words: OcrWord[] = [];\n\n for (const block of blocks ?? []) {\n for (const paragraph of block.paragraphs ?? []) {\n for (const line of paragraph.lines ?? []) {\n let wroteWord = false;\n for (const word of line.words ?? []) {\n const value = word.text.trim();\n if (!value) continue; // an empty box is not a word\n if (wroteWord) text += ' ';\n const start = text.length;\n text += value;\n words.push({\n text: value,\n start,\n end: text.length,\n // Horizontal extent from the word, vertical from the\n // line. OCR boxes hug their glyphs, so a descender\n // ('page') sits lower than its neighbours — and\n // `locate()` groups items into lines by comparing `y`\n // within a couple of points, a threshold that holds\n // because NATIVE runs take y from the shared baseline.\n // Passing per-word descenders through would split one\n // visual line into several rects and draw a highlight\n // as stacked fragments. Nothing is lost: `locate()`\n // bounds each line anyway, so per-word vertical extent\n // never reaches an annotation.\n bbox: {\n x0: word.bbox.x0,\n x1: word.bbox.x1,\n y0: line.bbox.y0,\n y1: line.bbox.y1,\n },\n confidence: word.confidence,\n });\n wroteWord = true;\n }\n if (wroteWord) text += '\\n';\n }\n text += '\\n';\n }\n }\n\n // Only trailing separators are removed, so no recorded offset moves.\n return { text: text.trimEnd(), words };\n}\n\n/**\n * Recognize a batch of PNG images, returning one result per image (empty\n * where nothing legible was found). One worker serves the whole batch —\n * startup is the expensive part, not the pages.\n */\nexport async function recognizeImages(images: Buffer[]): Promise<OcrPage[]> {\n if (images.length === 0) return [];\n // `cacheMethod: 'none'` — the data is already local, so there is nothing to\n // cache and no reason to write a copy into the working directory.\n const worker = await createWorker('eng', undefined, {\n langPath: langPath(),\n cacheMethod: 'none',\n });\n try {\n const results: OcrPage[] = [];\n for (const image of images) {\n // `blocks: true` is what carries the per-word geometry; without it\n // tesseract returns text only and `data.blocks` is null.\n const { data } = await worker.recognize(image, {}, { blocks: true, text: false });\n results.push(assemblePage(data.blocks));\n }\n return results;\n } finally {\n await worker.terminate();\n }\n}\n","/**\n * OCR word boxes → PDF-point geometry (#739).\n *\n * OCR reports boxes in the image's own pixel space, top-left origin. Anchoring\n * them means going through the matrix that placed the image on the page:\n *\n * pixel (px, py) → unit square (px/W, 1 − py/H) → CTM → PDF points\n *\n * Rotation and non-uniform scale fall out of the matrix, so there are no\n * special cases for them — the only explicit work is normalizing the result,\n * since a mirrored placement can invert an axis and consumers bound\n * rectangles rather than orienting them.\n */\n\nimport * as pdfjs from 'pdfjs-dist/legacy/build/pdf.mjs';\nimport type { OcrWord } from './ocr';\nimport type { PdfTextItem } from '@semiont/core';\n\n/** The placement of one image: its pixel size and its matrix onto the page. */\nexport interface ImagePlacement {\n width: number;\n height: number;\n ctm: number[];\n}\n\nfunction toPagePoint(px: number, py: number, placement: ImagePlacement): [number, number] {\n // Unit square, Y flipped: pixel rows run down, PDF space runs up.\n const point: [number, number] = [px / placement.width, 1 - py / placement.height];\n pdfjs.Util.applyTransform(point, placement.ctm); // mutates in place\n return point;\n}\n\n/**\n * Map recognized words onto the page, shifting their character offsets by\n * `textOffset` — where this page's text begins in the assembled document.\n */\nexport function mapWordsToItems(\n words: OcrWord[],\n placement: ImagePlacement,\n page: number,\n textOffset: number,\n): PdfTextItem[] {\n if (placement.width <= 0 || placement.height <= 0) return [];\n\n return words.map((word) => {\n // Both corners through the matrix, then bound them — a flipped or\n // rotated placement can put either one first.\n const [ax, ay] = toPagePoint(word.bbox.x0, word.bbox.y0, placement);\n const [bx, by] = toPagePoint(word.bbox.x1, word.bbox.y1, placement);\n const x = Math.min(ax, bx);\n const y = Math.min(ay, by);\n return {\n start: word.start + textOffset,\n end: word.end + textOffset,\n page,\n x,\n y,\n width: Math.abs(bx - ax),\n height: Math.abs(by - ay),\n };\n });\n}\n","/**\n * Anchored-text cache — the persistent half of ANCHORED-TEXT-CACHE.md Lane 2.\n *\n * OCR costs ~2.9 s per scanned page, and six passes read the same document (five\n * detection motivations plus the smelter's embed), each its own job in its own\n * process. This stores what the engine produced so only the first pass pays.\n *\n * **Derived values only.** Everything here is reproducible from the source\n * bytes, which is what makes a stamp miss safe. An authored coordinate map is\n * embedded in the PDF Semiont generated, not stored alongside one — see\n * `PDF-GENERATION.md`, which owns that decision and states the negative:\n * never this store.\n *\n * The seam is `extract()` (PERSIST-ANCHORS D1/P2b): the record is the FINISHED\n * extraction outcome — classification, geometry, provenance, or a named\n * decline — so a hit skips the native parse and the engine both, and every\n * geometry-yielding extraction stores an entry, native documents included.\n * That is what makes the anchored-text endpoint answer for every resource\n * whose extraction yields geometry, and what lets the reconcile planner treat\n * \"no entry under the current checksum\" as work (P0's third drift class).\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createRequire } from 'module';\nimport { getShardPath, isObject, isString, isNumber, isArray, type ExtractionOutcome, type Logger, type PdfTextItem } from '@semiont/core';\n\n\n/**\n * One line of recognized text: the geometry every word on it shares, plus the\n * per-word parts that differ.\n *\n * Grouping is by *contiguous runs* of equal `(y, h)`, never by scanning for all\n * items at a given y. That makes the codec lossless and order-preserving for\n * any input — compression is the only thing that depends on words actually\n * arriving in reading order, and correctness never is.\n *\n * Sharing `y`/`h` is measured-safe rather than assumed: within-line word-height\n * spread is 0.0pt in both native and OCR'd output, because the engine already\n * normalizes word boxes to the line. Per-word `x` and `width` are stored\n * explicitly and NOT derived from neighbouring split positions — deriving width\n * from the gap to the next word would widen every box to touch its neighbour,\n * which would silently change the coverage arithmetic `textUnder` is calibrated\n * on (RUN_COVERAGE_THRESHOLD, tuned against ink-tight boxes).\n */\nexport interface CachedLine {\n /** 1-indexed page. */\n p: number;\n /** PDF points, bottom-left origin — shared by every word on the line. */\n y: number;\n h: number;\n /** `[x, width, start, end]` per word; offsets index `CachedAnchoredText.text`. */\n words: [number, number, number, number][];\n}\n\n/**\n * The stored record: one extraction OUTCOME for the whole resource\n * (PERSIST-ANCHORS decision D1) — the anchored text with its provenance\n * (`method`, `pdfClass`, `ocrConfidence`, `unreadPages`), or a named decline.\n *\n * Whole-resource on every side, deliberately. The producer's own shape is a\n * per-page map, but that is an artifact of how `ocrPages` iterates, and letting\n * it reach storage would have forced every consumer — the transport, the\n * browser, a headless client — to reassemble pages it never asked to see.\n *\n * The `ocrConfidence` SUMMARY is stored (v2) — this repairs the regression\n * OCR-CONFIDENCE-LOST.md records, where a hit answered with no confidence at\n * all. Per-word confidences remain unstored: the summary is the record's\n * quality provenance; the word list is operator log detail.\n *\n * v1 records (bare `{ text, lines }`, no provenance) read as misses under the\n * v2 prefix; the reconcile planner's third drift class re-derives them.\n */\nexport type CachedAnchoredText =\n | ({\n v: 2;\n /** Engine + traineddata + our assembly code. A mismatch is a clean miss. */\n stamp: string;\n text: string;\n lines: CachedLine[];\n } & Omit<Extract<ExtractionOutcome, { kind: 'extracted' }>, 'kind' | 'text' | 'items'>)\n | ({\n v: 2;\n stamp: string;\n } & Omit<Extract<ExtractionOutcome, { kind: 'declined' }>, 'kind'>);\n\n/**\n * What the cached value must be recomputed against.\n *\n * Derived, never hand-maintained. A hand-bumped counter fails in the one\n * direction that matters: forgetting to bump it does not cost a recomputation,\n * it silently serves geometry built by different code. Over-invalidating costs\n * seconds of the work this cache exists to avoid; under-invalidating is\n * corruption, so the stamp is deliberately over-eager — a release of this\n * package busts the cache whether or not assembly actually changed.\n *\n * `@semiont/content`'s own version covers our assembly code (`anchorRuns`,\n * `assemblePage`, `mapWordsToItems` — the offset construction IS part of what\n * the cached value means). The engine and its traineddata are read separately\n * because both are pinned with carets and can move without a release here —\n * and different traineddata means different recognized text, which is a\n * difference in the value itself, not merely in how fast it was produced.\n *\n * pdf.js joined at P2b, because the seam did: the record is the finished\n * extraction outcome, so it depends on the native parse — classification,\n * text-layer read, table/form shaping — not just the engine. A parser upgrade\n * is a change in the value, and the entry must miss.\n */\nfunction buildStamp(): string {\n const require = createRequire(import.meta.url);\n const version = (specifier: string): string => {\n try {\n const pkg: unknown = require(specifier);\n return isObject(pkg) && isString(pkg.version) ? pkg.version : 'unknown';\n } catch {\n return 'unknown';\n }\n };\n return `content-${version('../package.json')}`\n + `+pdfjs-${version('pdfjs-dist/package.json')}`\n + `+tesseract-${version('tesseract.js/package.json')}`\n + `+eng-${version('@tesseract.js-data/eng/package.json')}`;\n}\n\nconst STAMP = buildStamp();\n\n/** Pack items into line records. Lossless and order-preserving for any input. */\nexport function encodeLines(items: PdfTextItem[]): CachedLine[] {\n const lines: CachedLine[] = [];\n for (const item of items) {\n const last = lines[lines.length - 1];\n if (last && last.p === item.page && last.y === item.y && last.h === item.height) {\n last.words.push([item.x, item.width, item.start, item.end]);\n } else {\n lines.push({ p: item.page, y: item.y, h: item.height, words: [[item.x, item.width, item.start, item.end]] });\n }\n }\n return lines;\n}\n\n/** The inverse of `encodeLines`. */\nexport function decodeLines(lines: CachedLine[]): PdfTextItem[] {\n const items: PdfTextItem[] = [];\n for (const line of lines) {\n for (const [x, width, start, end] of line.words) {\n items.push({ start, end, page: line.p, x, y: line.y, width, height: line.h });\n }\n }\n return items;\n}\n\nexport interface AnchoredTextStore {\n /**\n * The stored map for this key, or null for any miss. Never throws.\n *\n * The key is the **content checksum of the bytes the map derives from**\n * (PERSIST-ANCHORS decision A): a representation is its bytes, so the\n * checksum is its identity, and geometry derived from one revision of the\n * bytes is unreachable by a reader holding a different revision — by\n * construction, not by invalidation. Callers holding some other handle\n * (a resource id) reach the artifact through an index, not by a second\n * key scheme here.\n */\n read(key: string): Promise<ExtractionOutcome | null>;\n /**\n * Record an extraction outcome under the content checksum of its source\n * bytes. **THROWS on failure: a write that returns has written.**\n *\n * Asymmetric with `read` above, which never throws, and deliberately so —\n * a miss is a normal answer, a failed write is not. The store used to\n * swallow for everyone, which forced the one caller that needs a throw\n * (the Smelter's re-anchor publish, whose `smelt:rebuild-anchors-failed`\n * accounting rides on it) to route around the store entirely. Now the\n * contract is honest and **leniency is the caller's**, stated where it is\n * wanted: the read-through seam in `pdf-extractor` catches, because a\n * store may make extraction faster but must never make it fail.\n */\n write(key: string, outcome: ExtractionOutcome): Promise<void>;\n /**\n * Every key `read()` would currently HIT — entries under a stale stamp or\n * unreadable files are excluded, exactly as `read()` would exclude them.\n * That equivalence is load-bearing: the reconcile planner treats a listed\n * key as \"artifact present\" and plans re-derivation for the rest\n * (PERSIST-ANCHORS P0, the third drift class), so a key listed here but\n * missed by `read()` would be a permanent loss the diff can never see —\n * the exact shape of the post-engine-upgrade hole this filter closes.\n * One bulk call per reconcile, never a probe per resource. Never throws.\n */\n list(): Promise<string[]>;\n}\n\n/** Narrow a parsed entry, so a truncated or foreign file is a miss, not a crash. */\nfunction isCached(value: unknown): value is CachedAnchoredText {\n if (!isObject(value) || value.v !== 2 || !isString(value.stamp)) return false;\n if (isString(value.declined)) return true;\n if (!isString(value.text) || !isString(value.method) || !isArray(value.lines)) return false;\n return value.lines.every((line) =>\n isObject(line) && isNumber(line.p) && isNumber(line.y) && isNumber(line.h) && isArray(line.words)\n && line.words.every((w) => isArray(w) && w.length === 4 && w.every(isNumber)));\n}\n\n/** A key that could not have come from a checksum (or a legacy hex handle) is\n * refused outright rather than sanitized: a silently stripped key could share\n * a file with a different entry. Rejection replaces the old strip\n * (PERSIST-ANCHORS, *Smaller things*). */\nconst VALID_KEY = /^[A-Za-z0-9_-]+$/;\n\n/**\n * A file-backed store under `dir` — one file per content key, sharded as\n * `{ab}/{cd}/{key}.json` via the same `getShardPath` the event log uses\n * (PERSIST-ANCHORS decision E). Same convention, separate tree: `.semiont/`\n * is the KB's committed system of record; everything here is derived,\n * reclaimable, and never a source of truth.\n *\n * `dir` is the caller's, out of `Project.anchoredTextDir`: this package has no idea\n * which project it is serving. Every failure path is a miss rather than an\n * error, matching the rule extraction already follows for unreadable pages —\n * the cache may make things faster, never make them fail.\n */\nexport function createAnchoredTextStore(dir: string, logger?: Logger): AnchoredTextStore {\n const fileFor = (key: string): string | null => {\n if (!VALID_KEY.test(key)) return null;\n const [ab, cd] = getShardPath(key);\n return path.join(dir, ab, cd, `${key}.json`);\n };\n\n return {\n async read(key) {\n let hit: CachedAnchoredText | null = null;\n try {\n const file = fileFor(key);\n if (file === null) throw new Error('invalid key'); // refused → a miss like any other\n const parsed: unknown = JSON.parse(await fs.promises.readFile(file, 'utf8'));\n if (isCached(parsed) && parsed.stamp === STAMP) hit = parsed;\n } catch {\n hit = null; // absent, unreadable, truncated, or not ours\n }\n // Logged here rather than at the call sites: `prepare-detection` and\n // the smelter both extract, so each would see only its own share of\n // the traffic and the policy would be stated twice. Hit rate is what\n // keeps the Lane 0 decision auditable after the fact.\n logger?.debug('Anchored-text cache', {\n outcome: hit ? 'hit' : 'miss',\n key,\n ...(hit ? ('declined' in hit ? { declined: hit.declined } : { lines: hit.lines.length }) : {}),\n });\n if (!hit) return null;\n // `kind` is not persisted — the branch is implied by the record's\n // own shape, and re-added here so readers get the discriminated\n // wire union (WIRE-UNION-DISCRIMINANTS P5c).\n if ('declined' in hit) return { kind: 'declined', declined: hit.declined };\n const { v: _v, stamp: _stamp, lines, text, ...provenance } = hit;\n return { kind: 'extracted', text, items: decodeLines(lines), ...provenance };\n },\n\n async write(key, outcome) {\n const target = fileFor(key);\n if (target === null) {\n logger?.debug('Anchored-text cache: refusing invalid key', { key });\n return; // a store that cannot write is still a store\n }\n // Key order (`v`, `stamp`, first) is load-bearing: `list()` below\n // reads only a prefix of each file and matches the stamp there.\n const entry: CachedAnchoredText = outcome.kind === 'declined'\n ? { v: 2, stamp: STAMP, declined: outcome.declined }\n : (() => {\n // `kind` is deliberately destructured OUT: persisting it\n // would store a byte the branch already implies, and a\n // stored-shape change here would outrun the release-derived\n // STAMP (WIRE-UNION-DISCRIMINANTS P5c).\n const { kind: _kind, text, items, ...provenance } = outcome;\n return { v: 2, stamp: STAMP, text, lines: encodeLines(items), ...provenance };\n })();\n // Write-then-rename: a reader never observes a half-written entry,\n // and two writers racing on the same key both produce the same bytes.\n const temp = `${target}.${process.pid}.tmp`;\n try {\n await fs.promises.mkdir(path.dirname(target), { recursive: true });\n await fs.promises.writeFile(temp, JSON.stringify(entry), 'utf8');\n await fs.promises.rename(temp, target);\n } catch (error) {\n // Clean up the partial temp, then RETHROW. A write that returns\n // has written — see the interface doc. Callers that want\n // best-effort say so at their own call site.\n await fs.promises.rm(temp, { force: true }).catch(() => {});\n throw error;\n }\n },\n\n async list() {\n // Would-hit keys only (see the interface doc). The stamp check\n // reads a bounded prefix rather than parsing whole entries — an\n // artifact is ~32 KB per scanned page and this runs over every\n // entry at every reconcile. Sound because `write()` above puts\n // `v` and `stamp` first, so the current stamp appears within the\n // first bytes of every entry this store has ever written; a file\n // whose prefix doesn't match is either stale or not ours, and\n // both are misses for `read()` too. Keys round-trip through\n // filenames unchanged because every real key is hex — the same\n // fact that makes `fileFor`'s guard a no-op for them.\n const prefix = JSON.stringify({ v: 2, stamp: STAMP }).slice(0, -1) + ',';\n let rootNames: string[];\n try {\n rootNames = await fs.promises.readdir(dir);\n } catch {\n return []; // no directory yet: nothing has been written\n }\n\n // One-generation sweep (PERSIST-ANCHORS P1): a `.json` at the root\n // is a pre-P1 entry — flat layout, resource-id key, a dead scheme.\n // The rebuild path (P0's third drift class) re-derives anything\n // still needed, which is what makes this delete safe; leaving a\n // generation behind is how the store's size becomes unexplainable.\n // Done here because list() is the one bulk call every reconcile\n // already makes, so the sweep runs exactly when the planner is\n // about to notice what is missing. Best-effort, never throws.\n let swept = 0;\n for (const name of rootNames) {\n if (!name.endsWith('.json')) continue;\n await fs.promises.rm(path.join(dir, name), { force: true }).then(() => { swept += 1; }, () => {});\n }\n if (swept > 0) logger?.info('Anchored-text cache: swept pre-P1 flat entries', { swept });\n\n const keys: string[] = [];\n let sweptInterim = 0;\n for (const ab of rootNames) {\n if (!/^[0-9a-f]{2}$/.test(ab)) continue;\n let cdNames: string[];\n try {\n cdNames = await fs.promises.readdir(path.join(dir, ab));\n } catch {\n continue;\n }\n for (const cd of cdNames) {\n if (!/^[0-9a-f]{2}$/.test(cd)) continue;\n let names: string[];\n try {\n names = await fs.promises.readdir(path.join(dir, ab, cd));\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith('.json')) continue;\n // Interim-generation sweep (PERSIST-ANCHORS P1b): a\n // 32-hex basename is a resource-id key — writes that\n // landed sharded between P1a's rekey and P1b's\n // call-site switch. Checksums are 64-hex (SHA-256),\n // so the two generations are disjoint by length.\n // Reaped here for the same reason the flat sweep\n // lives here: one bulk call per reconcile, and never\n // a third scheme lingering silently.\n const base = name.slice(0, -'.json'.length);\n if (/^[0-9a-f]{32}$/.test(base)) {\n await fs.promises.rm(path.join(dir, ab, cd, name), { force: true }).then(() => { sweptInterim += 1; }, () => {});\n continue;\n }\n let handle: fs.promises.FileHandle | null = null;\n try {\n handle = await fs.promises.open(path.join(dir, ab, cd, name), 'r');\n const buf = Buffer.alloc(prefix.length);\n const { bytesRead } = await handle.read(buf, 0, prefix.length, 0);\n if (bytesRead === prefix.length && buf.toString('utf8') === prefix) {\n keys.push(base);\n }\n } catch {\n // unreadable is a miss, matching read()\n } finally {\n await handle?.close().catch(() => {});\n }\n }\n }\n }\n if (sweptInterim > 0) logger?.info('Anchored-text cache: swept interim resource-id entries', { swept: sweptInterim });\n return keys;\n },\n };\n}\n","/**\n * Reading a resource's bytes: the contract, the way it fails, and the\n * implementation that reaches the Archivist over HTTP.\n *\n * These live in `@semiont/content` because this package IS the byte layer —\n * the Archivist's whole job — and because the readers span the dependency\n * graph. `@semiont/make-meaning` holds the Archivist itself and satisfies\n * `ContentReads` in-process from the working tree; `@semiont/jobs` holds the\n * Worker and can only reach the record over the wire. make-meaning depends on\n * jobs, so anything both need has to sit under both (SINGLE-KB-MOUNT P4).\n *\n * Where the Archivist IS lives in `@semiont/core/node` (`archivistEndpoint`),\n * not here: an address is a config value plus an environment variable, and\n * the gateway needs it without needing a byte reader. One resolution, shared\n * with the gateway's own proxying — the address and the secret are deployment\n * facts, and a second copy of either is a second thing to get wrong.\n *\n * Absence fails loudly. A missing host or secret is a misconfiguration, never\n * a reason to fall back to reading a tree locally — the point of\n * SINGLE-KB-MOUNT is that exactly one process touches it.\n */\n\nimport type { IContentTransport, ResourceId } from '@semiont/core';\nimport { archivistEndpoint, type ArchivistAddressConfig } from '@semiont/core/node';\n\n/**\n * The byte read, and nothing else — DERIVED from the transport contract so it\n * cannot drift from it. Keyed by ResourceId because that is the transport's\n * key and the Archivist's: no caller converts to a tree address only to have\n * the far side convert back.\n */\nexport type ContentReads = Pick<IContentTransport, 'getBinary'>;\n\n/** Which half of the lookup failed — the gateway serves two different 404s. */\nexport type MissingReason = 'resource' | 'representation';\n\nexport class RepresentationMissing extends Error {\n constructor(readonly resourceId: string, readonly reason: MissingReason) {\n // NAMES THE RESOURCE. The client-visible wording is the gateway's, built\n // from `reason` — so this message is free to be diagnostic, and must be:\n // an operator reading a log needs to know which resource, which is what\n // the pre-collapse message gave them.\n super(\n reason === 'resource'\n ? `Resource not found: ${resourceId}`\n : `Resource representation not found: no storageUri for ${resourceId}`,\n );\n this.name = 'RepresentationMissing';\n }\n}\n\n/**\n * `ContentReads` against the Archivist — how a fleet process that holds no KB\n * mount reads bytes (SINGLE-KB-MOUNT P4).\n *\n * The address resolves HERE, at construction, not per read: a process with no\n * Archivist configured must die while an operator is watching it boot, rather\n * than fail every resource for the life of the process.\n *\n * A miss arrives as `RepresentationMissing` — the same error the in-process\n * face throws for the same fact, so no caller can tell whether the bytes were\n * a hop away. `reason` rides the wire precisely so this side need not guess.\n */\nexport function archivistContentReads(config: ArchivistAddressConfig): ContentReads {\n const { base, headers } = archivistEndpoint(config);\n\n return {\n getBinary: async (resourceId: ResourceId) => {\n const url = `${base}/resources/${encodeURIComponent(String(resourceId))}/content`;\n const res = await fetch(url, { headers });\n\n if (res.status === 404) {\n const { reason } = await res.json().catch(() => ({})) as { reason?: string };\n throw new RepresentationMissing(\n String(resourceId),\n reason === 'representation' ? 'representation' : 'resource',\n );\n }\n if (!res.ok) {\n throw new Error(`Archivist content read failed for ${String(resourceId)}: ${res.status} ${res.statusText}`);\n }\n\n return {\n data: await res.arrayBuffer(),\n contentType: res.headers.get('content-type') || 'application/octet-stream',\n };\n },\n };\n}\n"],"mappings":";AAQA,SAAS,YAAY,IAAI,kBAAkB,yBAAyB;AACpE,SAAS,YAAY,kBAAkB;AACvC,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,OAAO,UAAU;;;ACHjB,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB,+BAA+B;AAE1D,IAAM,MAAM,UAAU,QAAQ;AAgC9B,IAAM,uBAAuB,CAAC,IAAI,KAAK,KAAK,KAAK,KAAK,IAAI;AAG1D,IAAM,wBAAwB,CAAC,UAC7B,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAS,OACvC,OAAQ,MAA+B,UAAU,EAAE,EAAE,SAAS,YAAY;AAE5E,IAAM,mBAAmB;AACzB,IAAM,sBAAsB;AAa5B,IAAM,UAAU,oBAAI,IAAoB;AAEjC,SAAS,aAAa,KAAa,UAAyB,CAAC,GAAW;AAC7E,QAAM,MAAM,QAAQ,GAAG;AACvB,QAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,MAAI,SAAU,QAAO;AACrB,QAAM,SAAS,YAAY,KAAK,OAAO;AACvC,UAAQ,IAAI,KAAK,MAAM;AACvB,SAAO;AACT;AAEA,SAAS,YAAY,KAAa,UAAyB,CAAC,GAAW;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,QAAQ,aAAa;AAEvC,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI;AACJ,MAAI;AACJ,MAAI,WAA0B,QAAQ,QAAQ;AAC9C,MAAI,WAAW;AAEf,QAAM,MAAM,OAAO,SAAkC;AACnD,UAAM,UAAU,YAAY,IAAI;AAChC,QAAI;AACF,eAAS,UAAU,KAAK,WAAW;AACjC,YAAI;AACF,gBAAM,IAAI,OAAO,MAAM,EAAE,IAAI,CAAC;AAC9B;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,OAAO,WAAW,qBAAqB;AAC7C,cAAI,QAAQ,CAAC,sBAAsB,KAAK,EAAG,OAAM;AACjD,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,qBAAqB,OAAO,CAAE,CAAC;AAAA,QACxE;AAAA,MACF;AAAA,IACF,UAAE;AACA,uBAAiB,KAAK,CAAC,KAAK,OAAO,YAAY,IAAI,IAAI,OAAO;AAAA,IAChE;AAAA,EACF;AAGA,QAAM,YAAY,CAAC,SAA6C;AAC9D,eAAW,SAAS,KAAK,MAAM,IAAI;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,OAAO;AAAE,mBAAa,KAAK;AAAG,cAAQ;AAAA,IAAW;AAAA,EACvD;AAEA,QAAM,QAAQ,MAAqB;AACjC,eAAW;AACX,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,UAAM,QAAQ,CAAC,GAAG,MAAM;AACxB,WAAO,MAAM;AACb,eAAW;AACX,WAAO;AAAA,MAAU,MACf,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC,UAAmB;AAQ/C,cAAM,OAAO,sBAAsB,KAAK;AACxC,YAAI,MAAM;AACR,qBAAWA,SAAQ,MAAO,QAAO,IAAIA,KAAI;AACzC,cAAI,aAAa,OAAW,YAAW,KAAK,IAAI;AAChD,cAAI;AAAA,QACN;AAMA,gCAAwB,OAAO,eAAe,OAAO;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,MAAM,MAAM;AAChB,eAAW;AACX,QAAI,YAAY,OAAO,SAAS,EAAG;AAGnC,UAAM,cAAc,aAAa,SAAY,IAAI,KAAK,IAAI,IAAI;AAC9D,UAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,YAAY,WAAW,CAAC;AACnE,YAAQ,WAAW,MAAM;AAAE,WAAK,MAAM;AAAA,IAAG,GAAG,IAAI;AAChD,UAAM,QAAQ;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,IAAIA,OAAM;AACR,UAAI,SAAU;AACd,UAAI,OAAO,SAAS,EAAG,YAAW,KAAK,IAAI;AAC3C,aAAO,IAAIA,KAAI;AACf,UAAI;AAAA,IACN;AAAA,IACA,IAAI,MAAM;AACR,aAAO,MAAM,EAAE;AAAA,QAAK,MAClB;AAAA,UAAU,MACR,IAAI,IAAI,EAAE,MAAM,CAAC,UAAmB;AAClC,oCAAwB,sBAAsB,KAAK,IAAI,eAAe,OAAO;AAAA,UAC/E,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AACN,aAAO,MAAM;AAAA,IACf;AAAA,IACA,UAAU;AACR,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,MAAM,UAAU;AACd,cAAQ,OAAO,GAAG;AAClB,YAAM,MAAM;AACZ,iBAAW;AACX,iBAAW;AACX,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;AD3KA,SAAS,aAAa;AAClB,QAAM,OAAO,WAAW,QAAQ;AAChC,MAAI,WAAW;AACf,SAAO;AAAA,IACH,OAAOC,QAAqB;AACxB,WAAK,OAAOA,MAAK;AACjB,kBAAYA,OAAM;AAAA,IACtB;AAAA,IACA,IAAI,WAAmB;AACnB,aAAO;AAAA,IACX;AAAA,IACA,SAAiB;AACb,aAAO,KAAK,OAAO,KAAK;AAAA,IAC5B;AAAA,EACJ;AACJ;AAEO,IAAM,mBAAN,MAAuB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACS;AAAA;AAAA,EAGjB,YAAY,SAAyB,QAAiB,UAAyB,CAAC,GAAG;AACjF,SAAK,cAAc,QAAQ;AAC3B,SAAK,UAAU,QAAQ;AACvB,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGQ,SAAiB;AACvB,QAAI,CAAC,KAAK,QAAS,MAAK,UAAU,aAAa,KAAK,aAAa,KAAK,OAAO;AAC7E,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,eAA8B;AAC5B,WAAO,KAAK,UAAU,KAAK,QAAQ,MAAM,IAAI,QAAQ,QAAQ;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAS,OAAM,KAAK,QAAQ,QAAQ;AAAA,EAC/C;AAAA,EAEQ,aAAa,OAA0B;AAC7C,WAAO,KAAK,WAAW,CAAC;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MACJ,SACA,YACA,SACyB;AACzB,UAAM,WAAW,KAAK,WAAW,UAAU;AAC3C,UAAM,SAAS,OAAO,SAAS,OAAO,IAAI,SAAS,KAAK,CAAC,OAAO,CAAC,IAAI;AAErE,SAAK,QAAQ,MAAM,oBAAoB,EAAE,WAAW,CAAC;AAErD,UAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,UAAM,WAAW,GAAG,QAAQ,IAAI,WAAW,CAAC;AAC5C,UAAM,MAAM,WAAW;AAEvB,QAAI;AACF,YAAM;AAAA,QACJ;AAAA,QACA,iBAAiB,QAA+B;AAC9C,2BAAiBA,UAAS,QAAQ;AAChC,gBAAI,OAAOA,MAAK;AAChB,kBAAMA;AAAA,UACR;AAAA,QACF;AAAA,QACA,kBAAkB,QAAQ;AAAA,MAC5B;AAEA,YAAM,WAAW,IAAI,OAAO;AAC5B,YAAM,WAAW,IAAI;AACrB,UAAI,SAAS,qBAAqB,UAAa,QAAQ,qBAAqB,UAAU;AACpF,cAAM,IAAI,sBAAsB,YAAY,QAAQ,kBAAkB,QAAQ;AAAA,MAChF;AACA,YAAM,GAAG,OAAO,UAAU,QAAQ;AAElC,UAAI,KAAK,aAAa,SAAS,KAAK,GAAG;AACrC,aAAK,OAAO,EAAE,IAAI,QAAQ;AAAA,MAC5B;AAEA,WAAK,QAAQ,KAAK,mBAAmB,EAAE,YAAY,UAAU,SAAS,CAAC;AAEvE,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,IACF,SAAS,OAAO;AACd,YAAM,GAAG,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AACrC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,YAAoB,kBAA2B,SAAwD;AACpH,UAAM,WAAW,KAAK,WAAW,UAAU;AAE3C,SAAK,QAAQ,MAAM,wBAAwB,EAAE,WAAW,CAAC;AAIzD,UAAM,MAAM,WAAW;AACvB,qBAAiBA,UAAS,iBAAiB,QAAQ,GAAG;AACpD,UAAI,OAAOA,MAAe;AAAA,IAC5B;AACA,UAAM,WAAW,IAAI,OAAO;AAE5B,QAAI,qBAAqB,UAAa,aAAa,kBAAkB;AACnE,YAAM,IAAI,sBAAsB,YAAY,kBAAkB,QAAQ;AAAA,IACxE;AAEA,QAAI,KAAK,aAAa,SAAS,KAAK,GAAG;AACrC,WAAK,OAAO,EAAE,IAAI,QAAQ;AAAA,IAC5B;AAEA,UAAM,WAAW,IAAI;AACrB,SAAK,QAAQ,KAAK,uBAAuB,EAAE,YAAY,UAAU,SAAS,CAAC;AAE3E,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,YAA8B;AAC3C,WAAO,iBAAiB,KAAK,WAAW,UAAU,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,SAAS,YAAqC;AAClD,UAAM,WAAW,KAAK,WAAW,UAAU;AAC3C,QAAI;AACF,aAAO,MAAM,GAAG,SAAS,QAAQ;AAAA,IACnC,SAAS,OAAY;AACnB,UAAI,MAAM,SAAS,UAAU;AAC3B,cAAM,IAAI,MAAM,uBAAuB,UAAU,EAAE;AAAA,MACrD;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,KAAK,SAAiB,OAAe,SAA8C;AACvF,UAAM,WAAW,KAAK,WAAW,OAAO;AACxC,UAAM,SAAS,KAAK,WAAW,KAAK;AAEpC,SAAK,QAAQ,MAAM,mBAAmB,EAAE,SAAS,MAAM,CAAC;AAExD,UAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAExD,QAAI,KAAK,aAAa,SAAS,KAAK,GAAG;AACrC,YAAM,KAAK,OAAO,EAAE,IAAI,CAAC,MAAM,UAAU,MAAM,CAAC;AAAA,IAClD,OAAO;AACL,YAAM,GAAG,OAAO,UAAU,MAAM;AAAA,IAClC;AAEA,SAAK,QAAQ,KAAK,kBAAkB,EAAE,SAAS,MAAM,CAAC;AAAA,EACxD;AAAA;AAAA,EAGA,MAAM,OAAO,YAAoB,SAAkE;AACjG,UAAM,WAAW,KAAK,WAAW,UAAU;AAC3C,UAAM,WAAW,SAAS,YAAY;AAEtC,SAAK,QAAQ,MAAM,qBAAqB,EAAE,YAAY,SAAS,CAAC;AAEhE,UAAM,SAAS,KAAK,aAAa,SAAS,KAAK;AAE/C,QAAI,QAAQ;AACV,YAAM,UAAU,WACZ,CAAC,MAAM,YAAY,QAAQ,IAC3B,CAAC,MAAM,QAAQ;AACnB,YAAM,KAAK,OAAO,EAAE,IAAI,OAAO;AAC/B,WAAK,QAAQ,KAAK,oBAAoB,EAAE,YAAY,UAAU,KAAK,KAAK,CAAC;AACzE;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,WAAK,QAAQ,KAAK,mDAAmD,EAAE,WAAW,CAAC;AACnF;AAAA,IACF;AAEA,QAAI;AACF,YAAM,GAAG,OAAO,QAAQ;AACxB,WAAK,QAAQ,KAAK,oBAAoB,EAAE,WAAW,CAAC;AAAA,IACtD,SAAS,OAAY;AACnB,UAAI,MAAM,SAAS,UAAU;AAC3B,aAAK,QAAQ,KAAK,gCAAgC,EAAE,WAAW,CAAC;AAChE;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,WAAW,YAA4B;AACrC,QAAI,CAAC,WAAW,WAAW,SAAS,GAAG;AACrC,YAAM,IAAI,MAAM,kDAAkD,UAAU,EAAE;AAAA,IAChF;AACA,UAAM,eAAe,WAAW,MAAM,UAAU,MAAM;AACtD,WAAO,KAAK,KAAK,KAAK,aAAa,YAAY;AAAA,EACjD;AACF;AAGO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YACW,YACA,UACA,QACT;AACA;AAAA,MACE,yBAAyB,UAAU,cAAc,SAAS,MAAM,GAAG,CAAC,CAAC,eAAe,OAAO,MAAM,GAAG,CAAC,CAAC;AAAA;AAAA,IAExG;AAPS;AACA;AACA;AAMT,SAAK,OAAO;AAAA,EACd;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAQb;;;AEtQA,SAAS,cAAAC,mBAAkB;AAOpB,SAAS,kBAAkB,SAAkC;AAClE,QAAM,OAAOA,YAAW,QAAQ;AAChC,OAAK,OAAO,OAAO;AACnB,SAAO,KAAK,OAAO,KAAK;AAC1B;AAQO,SAAS,eAAe,SAA0B,UAA2B;AAClF,SAAO,kBAAkB,OAAO,MAAM;AACxC;;;ACLA,SAAS,wBAA0C;;;ACFnD,SAAS,YAAAC,iBAAkC;;;ACR3C,YAAY,WAAW;;;ACgBvB,SAAS,qBAAqB;AAC9B,OAAOC,WAAU;AAEjB,IAAMC,WAAU,cAAc,YAAY,GAAG;AAEtC,IAAM,yBACX,GAAGD,MAAK,KAAKA,MAAK,QAAQC,SAAQ,QAAQ,yBAAyB,CAAC,GAAG,gBAAgB,CAAC,GAAGD,MAAK,GAAG;;;ADpBrG,SAAS,UAAU,UAAU,UAAU,SAAS,YAAY,iBAAmC;AAS/F,SAAS,YAAY,OAAqC;AACtD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,EAAE,MAAM,OAAO,MAAM,KAAK,IAAI;AACpC,MAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,MAAM,KAAK,EAAG,QAAO;AACjE,MAAI,CAAC,SAAS,IAAI,KAAK,OAAO,EAAG,QAAO;AACxC,MAAI,CAAC,QAAQ,IAAI,KAAK,KAAK,SAAS,KAAK,CAAC,KAAK,MAAM,QAAQ,EAAG,QAAO;AACvE,QAAM,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI;AACzB,SAAO;AAAA,IACH;AAAA,IACA,OAAO,MAAM,KAAK;AAAA,IAClB,MAAM,OAAO;AAAA;AAAA,IACb,GAAG,KAAK,IAAI,IAAI,EAAE;AAAA,IAClB,GAAG,KAAK,IAAI,IAAI,EAAE;AAAA,IAClB,OAAO,KAAK,IAAI,KAAK,EAAE;AAAA,IACvB,QAAQ,KAAK,IAAI,KAAK,EAAE;AAAA,EAC5B;AACJ;AAQA,eAAe,eAAe,KAAsD;AAChF,QAAM,eAAe,MAAM,IAAI,gBAAgB;AAC/C,MAAI,CAAC,aAAc,QAAO,CAAC;AAC3B,QAAM,SAAS,oBAAI,IAA0B;AAC7C,aAAW,WAAW,OAAO,OAAO,YAAY,GAAG;AAC/C,QAAI,CAAC,QAAQ,OAAO,EAAG;AACvB,eAAW,SAAS,SAAS;AACzB,YAAM,QAAQ,YAAY,KAAK;AAC/B,UAAI,SAAS,CAAC,OAAO,IAAI,MAAM,IAAI,EAAG,QAAO,IAAI,MAAM,MAAM,KAAK;AAAA,IACtE;AAAA,EACJ;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC9B;AAEA,eAAsB,oBAClB,OAC4B;AAM5B,QAAM,OAAO,IAAI,WAAW,KAAK;AAGjC,QAAM,cAAoB,kBAAY,EAAE,MAAM,qBAAqB,uBAAuB,CAAC;AAE3F,MAAI;AAIA,UAAM,MAAM,MAAM,YAAY;AAC9B,UAAM,QAAuB,CAAC;AAC9B,UAAM,QAAuB,CAAC;AAC9B,QAAI,OAAO;AAEX,aAAS,UAAU,GAAG,WAAW,IAAI,UAAU,WAAW;AACtD,YAAM,OAAO,MAAM,IAAI,QAAQ,OAAO;AACtC,YAAM,WAAW,KAAK,YAAY,EAAE,OAAO,EAAI,CAAC;AAChD,YAAM,UAAU,MAAM,KAAK,eAAe;AAC1C,YAAM,gBAAgB,KAAK;AAO3B,YAAM,QAAQ,WAAW,QAAQ,MAAM,OAAO,SAAS,GAAG,OAAO;AAGjE,iBAAW,QAAQ,MAAM,OAAO;AAC5B,cAAM,KAAK,EAAE,GAAG,MAAM,OAAO,KAAK,QAAQ,eAAe,KAAK,KAAK,MAAM,cAAc,CAAC;AAAA,MAC5F;AACA,cAAQ,MAAM;AACd,cAAQ;AAER,YAAM,KAAK;AAAA,QACP,YAAY;AAAA,QACZ,SAAS,SAAS;AAAA,QAClB,UAAU,SAAS;AAAA,QACnB,WAAW;AAAA,QACX,SAAS,KAAK;AAAA,QACd,cAAc,MAAM,MAAM,SAAS;AAAA,MACvC,CAAC;AAAA,IACL;AAMA,QAAI,CAAC,MAAM,KAAK,CAAC,SAAS,KAAK,YAAY,EAAG,QAAO;AAErD,WAAO,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,eAAe,GAAG,EAAE;AAAA,EACnE,UAAE;AAIE,UAAM,YAAY,QAAQ;AAAA,EAC9B;AACJ;;;AE5FA,IAAM,WAAW;AACjB,IAAM,cAAc;AAIpB,IAAM,gBAAgB;AAGtB,IAAM,WAAW;AAEjB,SAAS,OAAO,QAA0B;AACxC,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,SAAO,OAAO,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,KAAK;AAClD;AAGA,SAAS,UAAU,OAAsB,WAAoC;AAC3E,QAAM,OAAwB,CAAC;AAC/B,aAAW,QAAQ,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG;AACvD,UAAM,MAAM,KAAK,KAAK,SAAS,CAAC;AAChC,QAAI,OAAO,KAAK,IAAI,IAAI,CAAC,EAAG,IAAI,KAAK,CAAC,KAAK,UAAW,KAAI,KAAK,IAAI;AAAA,QAC9D,MAAK,KAAK,CAAC,IAAI,CAAC;AAAA,EACvB;AACA,SAAO;AACT;AAEA,SAAS,OAAO,MAAqB,MAAyB;AAC5D,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC1C,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC1C,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC;AACxD,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;AACvD,SAAO;AAAA,IACL,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,KAAK;AAAA,IACjE;AAAA,IACA;AAAA,IACA,OAAO,QAAQ;AAAA,IACf,QAAQ,MAAM;AAAA,EAChB;AACF;AAGA,SAAS,QAAQ,KAAoB,KAAa,MAA2B;AAC3E,QAAM,QAAqB,CAAC;AAC5B,MAAI,UAAyB,CAAC;AAC9B,aAAW,QAAQ,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG;AACrD,UAAM,WAAW,QAAQ,QAAQ,SAAS,CAAC;AAC3C,QAAI,YAAY,KAAK,KAAK,SAAS,IAAI,SAAS,SAAS,KAAK;AAC5D,YAAM,KAAK,OAAO,SAAS,IAAI,CAAC;AAChC,gBAAU,CAAC;AAAA,IACb;AACA,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,MAAI,QAAQ,SAAS,EAAG,OAAM,KAAK,OAAO,SAAS,IAAI,CAAC;AACxD,SAAO;AACT;AAMO,SAAS,YAAY,OAAsB,MAAoC;AACpF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK;AAExE,QAAM,OAAO,UAAU,OAAO,OAAO,aAAa,EAAE,IAAI,CAAC,QAAQ,QAAQ,KAAK,OAAO,UAAU,IAAI,CAAC;AACpG,MAAI,KAAK,SAAS,SAAU,QAAO;AAEnC,QAAM,cAAc,KAAK,CAAC,EAAG;AAC7B,MAAI,cAAc,YAAa,QAAO;AACtC,MAAI,CAAC,KAAK,MAAM,CAAC,QAAQ,IAAI,WAAW,WAAW,EAAG,QAAO;AAI7D,WAAS,SAAS,GAAG,SAAS,aAAa,UAAU;AACnD,UAAM,QAAQ,KAAK,IAAI,CAAC,QAAQ,IAAI,MAAM,EAAG,CAAC;AAC9C,QAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,QAAO;AAAA,EAC7D;AACA,MAAI,KAAK,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,WAAW,CAAC,CAAC,EAAG,QAAO;AAE3E,SAAO;AACT;AAOO,SAAS,YACd,MACA,MACA,QACwC;AACxC,MAAI,OAAO;AACX,QAAM,QAAuB,CAAC;AAC9B,OAAK,QAAQ,CAAC,KAAK,aAAa;AAC9B,YAAQ;AACR,eAAW,QAAQ,KAAK;AACtB,cAAQ;AACR,YAAM,QAAQ,SAAS,KAAK;AAC5B,cAAQ,KAAK;AACb,YAAM,KAAK;AAAA,QACT;AAAA,QACA,KAAK,SAAS,KAAK;AAAA,QACnB;AAAA,QACA,GAAG,KAAK;AAAA,QACR,GAAG,KAAK;AAAA,QACR,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf,CAAC;AACD,cAAQ;AAAA,IACV;AACA,YAAQ;AAER,QAAI,aAAa,EAAG,SAAQ,IAAI,SAAS,OAAO,IAAI,MAAM,CAAC;AAAA;AAAA,EAC7D,CAAC;AACD,SAAO,EAAE,MAAM,MAAM;AACvB;;;ACnIA,YAAYE,YAAW;AAEvB,SAAS,YAAAC,WAAU,YAAAC,WAAU,YAAAC,WAAU,WAAAC,gBAAe;;;ACXtD,OAAO,UAAU;AAEjB,IAAM,aAAa,MAAM;AACrB,QAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC1B,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAK,IAAI,IAAK,aAAc,MAAM,IAAK,MAAM;AACzE,UAAM,CAAC,IAAI;AAAA,EACf;AACA,SAAO;AACX,GAAG;AAEH,SAAS,MAAM,KAAqB;AAChC,MAAI,IAAI;AACR,aAAW,QAAQ,IAAK,KAAI,WAAW,IAAI,QAAQ,GAAI,IAAM,MAAM;AACnE,UAAQ,IAAI,QAAQ;AACxB;AAEA,SAAS,MAAM,MAAc,MAAsB;AAC/C,QAAM,SAAS,OAAO,MAAM,CAAC;AAC7B,SAAO,cAAc,KAAK,MAAM;AAChC,QAAM,OAAO,OAAO,OAAO,CAAC,OAAO,KAAK,MAAM,OAAO,GAAG,IAAI,CAAC;AAC7D,QAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,MAAI,cAAc,MAAM,IAAI,CAAC;AAC7B,SAAO,OAAO,OAAO,CAAC,QAAQ,MAAM,GAAG,CAAC;AAC5C;AAGO,SAAS,UAAU,OAAe,QAAgB,KAAyB;AAC9E,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,MAAM,OAAO,MAAM,SAAS,MAAM;AACxC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC7B,QAAI,IAAI,MAAM,IAAI;AAClB,WAAO,KAAK,IAAI,QAAQ,IAAI,aAAa,IAAI,QAAQ,GAAG,QAAQ,CAAC,EAC5D,KAAK,KAAK,IAAI,SAAS,CAAC;AAAA,EACjC;AACA,QAAM,OAAO,OAAO,MAAM,EAAE;AAC5B,OAAK,cAAc,OAAO,CAAC;AAC3B,OAAK,cAAc,QAAQ,CAAC;AAC5B,OAAK,CAAC,IAAI;AACV,OAAK,CAAC,IAAI;AACV,SAAO,OAAO,OAAO;AAAA,IACjB,OAAO,KAAK,CAAC,KAAM,IAAM,IAAM,IAAM,IAAM,IAAM,IAAM,EAAI,CAAC;AAAA,IAC5D,MAAM,QAAQ,IAAI;AAAA,IAClB,MAAM,QAAQ,KAAK,YAAY,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,IACjD,MAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AAAA,EACjC,CAAC;AACL;;;ADhCA,IAAM,iBAAiB;AACvB,IAAM,YAAY;AAClB,IAAM,aAAa;AAEnB,IAAM,WAA8B,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AA8B9C,IAAM,mBAAmB;AAQzB,SAAS,kBAAkB,OAAe,QAAyB;AACtE,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AAChE,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AACtC,SAAO,QAAQ,UAAU;AAC7B;AAyBO,SAAS,iBAAiB,SAAmB,WAAuC;AACvF,QAAM,SAAwB,CAAC;AAC/B,QAAM,QAAoB,CAAC;AAC3B,MAAI,MAAgB,CAAC,GAAG,QAAQ;AAEhC,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACrC,UAAM,KAAK,QAAQ,CAAC;AACpB,UAAM,OAAO,UAAU,CAAC;AACxB,QAAI,OAAa,WAAI,MAAM;AACvB,YAAM,KAAK,CAAC,GAAG,GAAG,CAAC;AAAA,IACvB,WAAW,OAAa,WAAI,SAAS;AACjC,YAAM,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ;AAAA,IACrC,WAAW,OAAa,WAAI,WAAW;AACnC,UAAIC,SAAQ,IAAI,KAAK,KAAK,UAAU,KAAK,KAAK,MAAMC,SAAQ,GAAG;AAC3D,cAAY,YAAK,UAAU,KAAK,IAAgB;AAAA,MACpD;AAAA,IACJ,WAAW,OAAa,WAAI,mBAAmB;AAC3C,YAAM,MAAM,OAAO,CAAC;AACpB,YAAM,QAAQ,OAAO,CAAC;AACtB,YAAM,SAAS,OAAO,CAAC;AACvB,UAAIC,UAAS,GAAG,KAAKD,UAAS,KAAK,KAAKA,UAAS,MAAM,GAAG;AACtD,eAAO,KAAK,EAAE,KAAK,OAAO,QAAQ,KAAK,CAAC,GAAG,GAAG,EAAE,CAAC;AAAA,MACrD;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAYA,SAAS,QAAQ,MAAkC;AAC/C,MAAI,gBAAgB,WAAY,QAAO;AACvC,MAAI,gBAAgB,kBAAmB,QAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,MAAM;AACtG,SAAO;AACX;AAOO,SAAS,MAAM,OAA2E;AAC7F,MAAI,CAACE,UAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,EAAE,OAAO,QAAQ,KAAK,IAAI;AAChC,QAAM,OAAO,QAAQ,MAAM,IAAI;AAC/B,MAAI,CAACF,UAAS,KAAK,KAAK,CAACA,UAAS,MAAM,KAAK,CAAC,KAAM,QAAO;AAC3D,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AAEtC,MAAI,SAAS,WAAW;AACpB,WAAO,KAAK,UAAU,QAAQ,SAAS,IAAI,EAAE,OAAO,QAAQ,KAAK,KAAK,IAAI;AAAA,EAC9E;AAEA,MAAI,SAAS,YAAY;AACrB,QAAI,KAAK,SAAS,QAAQ,SAAS,EAAG,QAAO;AAC7C,UAAM,MAAM,IAAI,WAAW,QAAQ,SAAS,CAAC;AAC7C,aAAS,IAAI,GAAG,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,GAAG,KAAK,GAAG;AACnD,UAAI,CAAC,IAAI,KAAK,CAAC;AACf,UAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;AACvB,UAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;AAAA,IAC3B;AACA,WAAO,EAAE,OAAO,QAAQ,IAAI;AAAA,EAChC;AAEA,MAAI,SAAS,gBAAgB;AAKzB,UAAM,WAAW,KAAK,KAAK,QAAQ,CAAC;AACpC,QAAI,KAAK,SAAS,WAAW,OAAQ,QAAO;AAC5C,UAAM,MAAM,IAAI,WAAW,QAAQ,SAAS,CAAC;AAC7C,aAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC7B,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC5B,cAAM,MAAM,KAAK,IAAI,YAAY,KAAK,EAAE,IAAM,QAAS,IAAI;AAC3D,cAAM,QAAQ,MAAM,MAAO;AAC3B,cAAM,KAAK,IAAI,QAAQ,KAAK;AAC5B,YAAI,CAAC,IAAI;AACT,YAAI,IAAI,CAAC,IAAI;AACb,YAAI,IAAI,CAAC,IAAI;AAAA,MACjB;AAAA,IACJ;AACA,WAAO,EAAE,OAAO,QAAQ,IAAI;AAAA,EAChC;AAEA,SAAO;AACX;AAOA,IAAM,2BAA2B;AAmBjC,SAAS,aAAa,MAA0B,KAA+B;AAE3E,QAAM,QAAQ,IAAI,WAAW,IAAI,IAAI,KAAK,aAAa,KAAK;AAC5D,SAAO,IAAI,QAAQ,CAACG,aAAY;AAC5B,UAAM,QAAQ,WAAW,MAAMA,SAAQ,IAAI,GAAG,wBAAwB;AACtE,UAAM,SAAS,CAAC,UAAmB;AAC/B,mBAAa,KAAK;AAClB,MAAAA,SAAQ,KAAK;AAAA,IACjB;AACA,QAAI;AACA,YAAM,IAAI,KAAK,MAAM;AAAA,IACzB,QAAQ;AACJ,aAAO,IAAI;AAAA,IACf;AAAA,EACJ,CAAC;AACL;AAkBA,eAAsB,kBAClB,OACA,aACiC;AACjC,QAAM,SAAS,cAAc,IAAI,IAAI,WAAW,IAAI;AACpD,QAAM,cAAoB,mBAAY,EAAE,MAAM,IAAI,WAAW,KAAK,GAAG,qBAAqB,uBAAuB,CAAC;AAClH,QAAM,SAAS,oBAAI,IAAyB;AAE5C,MAAI;AACA,UAAM,MAAM,MAAM,YAAY;AAC9B,aAAS,UAAU,GAAG,WAAW,IAAI,UAAU,WAAW;AACtD,UAAI,UAAU,CAAC,OAAO,IAAI,OAAO,EAAG;AACpC,YAAM,OAAO,MAAM,IAAI,QAAQ,OAAO;AACtC,YAAM,MAAM,MAAM,KAAK,gBAAgB;AAEvC,YAAM,SAAsB,CAAC;AAC7B,iBAAW,aAAa,iBAAiB,IAAI,SAAS,IAAI,SAAS,GAAG;AAIlE,YAAI,CAAC,kBAAkB,UAAU,OAAO,UAAU,MAAM,EAAG;AAC3D,cAAM,MAAM,MAAM,MAAM,aAAa,MAAM,UAAU,GAAG,CAAC;AACzD,YAAI,CAAC,IAAK;AACV,eAAO,KAAK;AAAA,UACR,KAAK,UAAU,IAAI,OAAO,IAAI,QAAQ,IAAI,GAAG;AAAA,UAC7C,OAAO,IAAI;AAAA,UACX,QAAQ,IAAI;AAAA,UACZ,KAAK,UAAU;AAAA,QACnB,CAAC;AAAA,MACL;AACA,UAAI,OAAO,SAAS,EAAG,QAAO,IAAI,SAAS,MAAM;AAAA,IACrD;AACA,WAAO;AAAA,EACX,UAAE;AACE,UAAM,YAAY,QAAQ;AAAA,EAC9B;AACJ;;;AE7QA,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,YAAAC,WAAU,YAAAC,iBAAgB;AAenC,IAAI;AACJ,SAAS,WAAmB;AACxB,MAAI,eAAgB,QAAO;AAC3B,QAAM,OAAgBF,eAAc,YAAY,GAAG,EAAE,wBAAwB;AAC7E,MAAI,CAACC,UAAS,IAAI,KAAK,CAACC,UAAS,KAAK,QAAQ,GAAG;AAC7C,UAAM,IAAI;AAAA,MACN;AAAA,IACJ;AAAA,EACJ;AACA,mBAAiB,KAAK;AACtB,SAAO;AACX;AA4CO,SAAS,aAAa,QAAoC;AAC7D,MAAI,OAAO;AACX,QAAM,QAAmB,CAAC;AAE1B,aAAW,SAAS,UAAU,CAAC,GAAG;AAC9B,eAAW,aAAa,MAAM,cAAc,CAAC,GAAG;AAC5C,iBAAW,QAAQ,UAAU,SAAS,CAAC,GAAG;AACtC,YAAI,YAAY;AAChB,mBAAW,QAAQ,KAAK,SAAS,CAAC,GAAG;AACjC,gBAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,cAAI,CAAC,MAAO;AACZ,cAAI,UAAW,SAAQ;AACvB,gBAAM,QAAQ,KAAK;AACnB,kBAAQ;AACR,gBAAM,KAAK;AAAA,YACP,MAAM;AAAA,YACN;AAAA,YACA,KAAK,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAYV,MAAM;AAAA,cACF,IAAI,KAAK,KAAK;AAAA,cACd,IAAI,KAAK,KAAK;AAAA,cACd,IAAI,KAAK,KAAK;AAAA,cACd,IAAI,KAAK,KAAK;AAAA,YAClB;AAAA,YACA,YAAY,KAAK;AAAA,UACrB,CAAC;AACD,sBAAY;AAAA,QAChB;AACA,YAAI,UAAW,SAAQ;AAAA,MAC3B;AACA,cAAQ;AAAA,IACZ;AAAA,EACJ;AAGA,SAAO,EAAE,MAAM,KAAK,QAAQ,GAAG,MAAM;AACzC;AAOA,eAAsB,gBAAgB,QAAsC;AACxE,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAGjC,QAAM,SAAS,MAAM,aAAa,OAAO,QAAW;AAAA,IAChD,UAAU,SAAS;AAAA,IACnB,aAAa;AAAA,EACjB,CAAC;AACD,MAAI;AACA,UAAM,UAAqB,CAAC;AAC5B,eAAW,SAAS,QAAQ;AAGxB,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO,UAAU,OAAO,CAAC,GAAG,EAAE,QAAQ,MAAM,MAAM,MAAM,CAAC;AAChF,cAAQ,KAAK,aAAa,KAAK,MAAM,CAAC;AAAA,IAC1C;AACA,WAAO;AAAA,EACX,UAAE;AACE,UAAM,OAAO,UAAU;AAAA,EAC3B;AACJ;;;ACjJA,YAAYC,YAAW;AAWvB,SAAS,YAAY,IAAY,IAAY,WAA6C;AAEtF,QAAM,QAA0B,CAAC,KAAK,UAAU,OAAO,IAAI,KAAK,UAAU,MAAM;AAChF,EAAM,YAAK,eAAe,OAAO,UAAU,GAAG;AAC9C,SAAO;AACX;AAMO,SAAS,gBACZ,OACA,WACA,MACA,YACa;AACb,MAAI,UAAU,SAAS,KAAK,UAAU,UAAU,EAAG,QAAO,CAAC;AAE3D,SAAO,MAAM,IAAI,CAAC,SAAS;AAGvB,UAAM,CAAC,IAAI,EAAE,IAAI,YAAY,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,SAAS;AAClE,UAAM,CAAC,IAAI,EAAE,IAAI,YAAY,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,SAAS;AAClE,UAAM,IAAI,KAAK,IAAI,IAAI,EAAE;AACzB,UAAM,IAAI,KAAK,IAAI,IAAI,EAAE;AACzB,WAAO;AAAA,MACH,OAAO,KAAK,QAAQ;AAAA,MACpB,KAAK,KAAK,MAAM;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,KAAK,IAAI,KAAK,EAAE;AAAA,MACvB,QAAQ,KAAK,IAAI,KAAK,EAAE;AAAA,IAC5B;AAAA,EACJ,CAAC;AACL;;;APXO,IAAM,gBAAgB,MAAM,OAAO;AAKnC,SAAS,iBAAiB,OAAwB;AACvD,SAAO,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,SAAS;AAC1D;AAIA,IAAM,iBAAiB;AAEvB,SAAS,UAAU,aAAuD;AACxE,MAAI,YAAY,WAAW,EAAG,QAAO;AACrC,QAAM,QAAQ,YAAY,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AACvD,SAAO;AAAA,IACL,MAAM,KAAK,MAAO,QAAQ,YAAY,SAAU,EAAE,IAAI;AAAA,IACtD,oBAAoB,YAAY,OAAO,CAAC,MAAM,IAAI,cAAc,EAAE;AAAA,IAClE,YAAY,YAAY;AAAA,EAC1B;AACF;AAUA,eAAe,SACb,SACA,aACwB;AAIxB,QAAM,eAAe,MAAM,kBAAkB,SAAS,WAAW;AACjE,MAAI,aAAa,SAAS,EAAG,QAAO,EAAE,MAAM,IAAI,OAAO,CAAC,GAAG,aAAa,CAAC,EAAE;AAG3E,QAAM,QAAQ,CAAC,GAAG,aAAa,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC3D,QAAM,QAAQ,MAAM,QAAQ,CAAC,SAAS,aAAa,IAAI,IAAI,EAAG,IAAI,CAAC,UAAU,MAAM,GAAG,CAAC;AACvF,QAAM,aAAa,MAAM,gBAAgB,KAAK;AAE9C,QAAM,SAAS,oBAAI,IAA2B;AAC9C,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,aAAa,IAAI,IAAI;AACpC,QAAI,OAAO;AACX,UAAM,QAAuB,CAAC;AAC9B,UAAM,cAAwB,CAAC;AAC/B,eAAW,SAAS,QAAQ;AAC1B,YAAM,SAAS,WAAW,QAAQ;AAClC,UAAI,CAAC,QAAQ,KAAK,KAAK,EAAG;AAC1B,UAAI,KAAM,SAAQ;AAClB,YAAM,KAAK,GAAG,gBAAgB,OAAO,OAAO,OAAO,MAAM,KAAK,MAAM,CAAC;AACrE,kBAAY,KAAK,GAAG,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,UAAU,CAAC;AAC/D,cAAQ,OAAO;AAAA,IACjB;AACA,QAAI,KAAM,QAAO,IAAI,MAAM,EAAE,MAAM,OAAO,YAAY,CAAC;AAAA,EACzD;AAIA,SAAO,UAAU,QAAQ,CAAC;AAC5B;AAOA,SAAS,UAAU,QAAoC,YAAmC;AACxF,MAAI,OAAO;AACX,QAAM,QAAuB,CAAC;AAC9B,QAAM,cAAwB,CAAC;AAC/B,aAAW,CAAC,EAAE,IAAI,KAAK,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG;AACxE,QAAI,KAAM,SAAQ;AAClB,UAAM,QAAQ,aAAa,KAAK;AAChC,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,KAAK,EAAE,GAAG,MAAM,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,MAAM,MAAM,CAAC;AAAA,IAC1E;AACA,gBAAY,KAAK,GAAG,KAAK,WAAW;AACpC,YAAQ,KAAK;AAAA,EACf;AACA,SAAO,EAAE,MAAM,OAAO,YAAY;AACpC;AAQO,SAAS,iBAAiB,OAAyC;AACxE,SAAOC,UAAS,KAAK,KAAK,MAAM,SAAS,sBAAsB,cAAc;AAC/E;AAUA,SAAS,eAAe,OAAoC;AAC1D,MAAI,OAAO,MAAM;AACjB,QAAM,QAAuB,CAAC,GAAG,MAAM,KAAK;AAC5C,aAAW,SAAS,MAAM,QAAQ;AAChC,UAAM,QAAQ,KAAK,SAAS,GAAG,MAAM,IAAI,KAAK;AAC9C,YAAQ,GAAG,MAAM,IAAI,KAAK,MAAM,KAAK;AAAA;AACrC,UAAM,KAAK;AAAA,MACT;AAAA,MACA,KAAK,QAAQ,MAAM,MAAM;AAAA,MACzB,MAAM,MAAM;AAAA,MACZ,GAAG,MAAM;AAAA,MACT,GAAG,MAAM;AAAA,MACT,OAAO,MAAM;AAAA,MACb,QAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH;AACA,SAAO,EAAE,MAAM,aAAa,MAAM,OAAO,QAAQ,QAAQ,UAAU,IAAI;AACzE;AASA,SAAS,YAAY,OAA2C;AAC9D,QAAM,QAAQ,MAAM,MAAM,IAAI,CAAC,SAAS;AACtC,UAAM,YAAY,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,KAAK,UAAU;AAC5E,WAAO,EAAE,MAAM,WAAW,OAAO,YAAY,WAAW,MAAM,IAAI,EAAE;AAAA,EACtE,CAAC;AACD,MAAI,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,KAAK,EAAG,QAAO;AAExC,MAAI,OAAO;AACX,QAAM,QAAuB,CAAC;AAC9B,aAAW,EAAE,MAAM,WAAW,MAAM,KAAK,OAAO;AAC9C,QAAI,OAAO;AACT,YAAM,WAAW,YAAY,OAAO,KAAK,YAAY,KAAK,MAAM;AAChE,cAAQ,SAAS;AACjB,YAAM,KAAK,GAAG,SAAS,KAAK;AAAA,IAC9B,OAAO;AAEL,YAAM,QAAQ,KAAK,SAAS,KAAK;AACjC,cAAQ,MAAM,KAAK,MAAM,KAAK,WAAW,KAAK,OAAO;AACrD,iBAAW,QAAQ,WAAW;AAC5B,cAAM,KAAK,EAAE,GAAG,MAAM,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,MAAM,MAAM,CAAC;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,MAAM,aAAa,MAAM,OAAO,QAAQ,SAAS,UAAU,IAAI;AAC1E;AAEO,IAAM,eAA8B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKzC,MAAM,QAAQ,SAAS,YAAY,OAAO;AAYxC,UAAM,MAAM,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG;AAC5C,QAAI,IAAK,QAAO;AAEhB,UAAM,UAAU,MAAM,WAAW,OAAO;AAUxC,QAAI;AACF,UAAI,QAAQ,SAAS,WAAY,OAAM,MAAM,MAAM,MAAM,MAAM,KAAK,OAAO;AAAA,eAClE,QAAQ,MAAO,OAAM,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,OAAO,QAAQ,MAAM,CAAC;AAAA,IACjG,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AACF;AAGA,eAAe,WAAW,SAA6D;AAInF,MAAI,CAAC,iBAAiB,QAAQ,MAAM,EAAG,QAAO,EAAE,MAAM,YAAY,UAAU,YAAY;AAExF,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,oBAAoB,OAAO;AAAA,EAC3C,SAAS,OAAO;AACd,WAAO,EAAE,MAAM,YAAY,UAAU,iBAAiB,KAAK,EAAE;AAAA,EAC/D;AAIA,MAAI,CAAC,OAAO;AACV,UAAMC,OAAM,MAAM,SAAS,OAAO;AAClC,QAAI,CAACA,KAAI,KAAM,QAAO,EAAE,MAAM,YAAY,UAAU,gBAAgB;AACpE,UAAMC,cAAa,UAAUD,KAAI,WAAW;AAC5C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAMA,KAAI;AAAA,MACV,OAAOA,KAAI;AAAA,MACX,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,GAAIC,cAAa,EAAE,eAAeA,YAAW,IAAI,CAAC;AAAA,IACpD;AAAA,EACF;AAKA,QAAM,SAAS,MAAM,OAAO,SAAS,IACjC,eAAe,KAAK,IACpB,YAAY,KAAK,KACd,EAAE,MAAM,aAAsB,MAAM,MAAM,MAAM,OAAO,MAAM,OAAO,QAAQ,kBAA2B,UAAU,IAAa;AAOrI,QAAM,cAAc,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,YAAY,EAAE,IAAI,CAAC,SAAS,KAAK,UAAU;AAClG,MAAI,YAAY,WAAW,EAAG,QAAO;AAQrC,QAAM,YAAY,MAAM,SAAS,SAAS,WAAW;AACrD,QAAM,YAAY,IAAI,IAAI,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC;AAClE,QAAM,cAAc,YAAY,OAAO,CAAC,SAAS,CAAC,UAAU,IAAI,IAAI,CAAC;AACrE,QAAM,cAAc,OAAO,aAAa,MAAM,MAAe,OAAO;AACpE,MAAI,CAAC,UAAU,MAAM;AACnB,WAAO,EAAE,GAAG,QAAQ,aAAa,aAAa,UAAU,YAAY;AAAA,EACtE;AAGA,QAAM,QAAQ,OAAO,KAAK;AAC1B,QAAM,MAAqB;AAAA,IACzB,MAAM,UAAU;AAAA,IAChB,OAAO,UAAU,MAAM,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE;AAAA,IACpG,aAAa,UAAU;AAAA,EACzB;AACA,QAAM,aAAa,UAAU,IAAI,WAAW;AAC5C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,GAAG,OAAO,IAAI,GAAG,IAAI,IAAI;AAAA;AAAA,IAC/B,OAAO,CAAC,GAAI,OAAO,SAAS,CAAC,GAAI,GAAG,IAAI,KAAK;AAAA,IAC7C,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,GAAI,aAAa,EAAE,eAAe,WAAW,IAAI,CAAC;AAAA,IAClD,GAAI,YAAY,SAAS,IAAI,EAAE,aAAa,YAAY,IAAI,CAAC;AAAA,EAC/D;AACJ;;;AD3KO,SAAS,qBAAqB,WAAyC;AAC5E,SAAO,iBAAiB,SAAS,IAAI,eAAe;AACtD;;;ASxIA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,cAAc,YAAAC,WAAU,YAAAC,WAAU,YAAAC,WAAU,WAAAC,gBAAsE;AAmF3H,SAAS,aAAqB;AAC1B,QAAMC,WAAUL,eAAc,YAAY,GAAG;AAC7C,QAAM,UAAU,CAAC,cAA8B;AAC3C,QAAI;AACA,YAAM,MAAeK,SAAQ,SAAS;AACtC,aAAOJ,UAAS,GAAG,KAAKC,UAAS,IAAI,OAAO,IAAI,IAAI,UAAU;AAAA,IAClE,QAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AACA,SAAO,WAAW,QAAQ,iBAAiB,CAAC,UAC5B,QAAQ,yBAAyB,CAAC,cAC9B,QAAQ,2BAA2B,CAAC,QAC1C,QAAQ,qCAAqC,CAAC;AAChE;AAEA,IAAM,QAAQ,WAAW;AAGlB,SAAS,YAAY,OAAoC;AAC5D,QAAM,QAAsB,CAAC;AAC7B,aAAW,QAAQ,OAAO;AACtB,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,QAAI,QAAQ,KAAK,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,QAAQ;AAC7E,WAAK,MAAM,KAAK,CAAC,KAAK,GAAG,KAAK,OAAO,KAAK,OAAO,KAAK,GAAG,CAAC;AAAA,IAC9D,OAAO;AACH,YAAM,KAAK,EAAE,GAAG,KAAK,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,QAAQ,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,OAAO,KAAK,OAAO,KAAK,GAAG,CAAC,EAAE,CAAC;AAAA,IAC/G;AAAA,EACJ;AACA,SAAO;AACX;AAGO,SAAS,YAAY,OAAoC;AAC5D,QAAM,QAAuB,CAAC;AAC9B,aAAW,QAAQ,OAAO;AACtB,eAAW,CAAC,GAAG,OAAO,OAAO,GAAG,KAAK,KAAK,OAAO;AAC7C,YAAM,KAAK,EAAE,OAAO,KAAK,MAAM,KAAK,GAAG,GAAG,GAAG,KAAK,GAAG,OAAO,QAAQ,KAAK,EAAE,CAAC;AAAA,IAChF;AAAA,EACJ;AACA,SAAO;AACX;AA2CA,SAAS,SAAS,OAA6C;AAC3D,MAAI,CAACD,UAAS,KAAK,KAAK,MAAM,MAAM,KAAK,CAACC,UAAS,MAAM,KAAK,EAAG,QAAO;AACxE,MAAIA,UAAS,MAAM,QAAQ,EAAG,QAAO;AACrC,MAAI,CAACA,UAAS,MAAM,IAAI,KAAK,CAACA,UAAS,MAAM,MAAM,KAAK,CAACE,SAAQ,MAAM,KAAK,EAAG,QAAO;AACtF,SAAO,MAAM,MAAM,MAAM,CAAC,SACtBH,UAAS,IAAI,KAAKE,UAAS,KAAK,CAAC,KAAKA,UAAS,KAAK,CAAC,KAAKA,UAAS,KAAK,CAAC,KAAKC,SAAQ,KAAK,KAAK,KAC7F,KAAK,MAAM,MAAM,CAAC,MAAMA,SAAQ,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,MAAMD,SAAQ,CAAC,CAAC;AACrF;AAMA,IAAM,YAAY;AAcX,SAAS,wBAAwB,KAAa,QAAoC;AACrF,QAAM,UAAU,CAAC,QAA+B;AAC5C,QAAI,CAAC,UAAU,KAAK,GAAG,EAAG,QAAO;AACjC,UAAM,CAAC,IAAI,EAAE,IAAI,aAAa,GAAG;AACjC,WAAOJ,MAAK,KAAK,KAAK,IAAI,IAAI,GAAG,GAAG,OAAO;AAAA,EAC/C;AAEA,SAAO;AAAA,IACH,MAAM,KAAK,KAAK;AACZ,UAAI,MAAiC;AACrC,UAAI;AACA,cAAM,OAAO,QAAQ,GAAG;AACxB,YAAI,SAAS,KAAM,OAAM,IAAI,MAAM,aAAa;AAChD,cAAM,SAAkB,KAAK,MAAM,MAAMD,IAAG,SAAS,SAAS,MAAM,MAAM,CAAC;AAC3E,YAAI,SAAS,MAAM,KAAK,OAAO,UAAU,MAAO,OAAM;AAAA,MAC1D,QAAQ;AACJ,cAAM;AAAA,MACV;AAKA,cAAQ,MAAM,uBAAuB;AAAA,QACjC,SAAS,MAAM,QAAQ;AAAA,QACvB;AAAA,QACA,GAAI,MAAO,cAAc,MAAM,EAAE,UAAU,IAAI,SAAS,IAAI,EAAE,OAAO,IAAI,MAAM,OAAO,IAAK,CAAC;AAAA,MAChG,CAAC;AACD,UAAI,CAAC,IAAK,QAAO;AAIjB,UAAI,cAAc,IAAK,QAAO,EAAE,MAAM,YAAY,UAAU,IAAI,SAAS;AACzE,YAAM,EAAE,GAAG,IAAI,OAAO,QAAQ,OAAO,MAAM,GAAG,WAAW,IAAI;AAC7D,aAAO,EAAE,MAAM,aAAa,MAAM,OAAO,YAAY,KAAK,GAAG,GAAG,WAAW;AAAA,IAC/E;AAAA,IAEA,MAAM,MAAM,KAAK,SAAS;AACtB,YAAM,SAAS,QAAQ,GAAG;AAC1B,UAAI,WAAW,MAAM;AACjB,gBAAQ,MAAM,6CAA6C,EAAE,IAAI,CAAC;AAClE;AAAA,MACJ;AAGA,YAAM,QAA4B,QAAQ,SAAS,aAC7C,EAAE,GAAG,GAAG,OAAO,OAAO,UAAU,QAAQ,SAAS,KAChD,MAAM;AAKL,cAAM,EAAE,MAAM,OAAO,MAAM,OAAO,GAAG,WAAW,IAAI;AACpD,eAAO,EAAE,GAAG,GAAG,OAAO,OAAO,MAAM,OAAO,YAAY,KAAK,GAAG,GAAG,WAAW;AAAA,MAChF,GAAG;AAGP,YAAM,OAAO,GAAG,MAAM,IAAI,QAAQ,GAAG;AACrC,UAAI;AACA,cAAMA,IAAG,SAAS,MAAMC,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACjE,cAAMD,IAAG,SAAS,UAAU,MAAM,KAAK,UAAU,KAAK,GAAG,MAAM;AAC/D,cAAMA,IAAG,SAAS,OAAO,MAAM,MAAM;AAAA,MACzC,SAAS,OAAO;AAIZ,cAAMA,IAAG,SAAS,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC1D,cAAM;AAAA,MACV;AAAA,IACJ;AAAA,IAEA,MAAM,OAAO;AAWT,YAAM,SAAS,KAAK,UAAU,EAAE,GAAG,GAAG,OAAO,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI;AACrE,UAAI;AACJ,UAAI;AACA,oBAAY,MAAMA,IAAG,SAAS,QAAQ,GAAG;AAAA,MAC7C,QAAQ;AACJ,eAAO,CAAC;AAAA,MACZ;AAUA,UAAI,QAAQ;AACZ,iBAAW,QAAQ,WAAW;AAC1B,YAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,cAAMA,IAAG,SAAS,GAAGC,MAAK,KAAK,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,MAAM;AAAE,mBAAS;AAAA,QAAG,GAAG,MAAM;AAAA,QAAC,CAAC;AAAA,MACpG;AACA,UAAI,QAAQ,EAAG,SAAQ,KAAK,kDAAkD,EAAE,MAAM,CAAC;AAEvF,YAAM,OAAiB,CAAC;AACxB,UAAI,eAAe;AACnB,iBAAW,MAAM,WAAW;AACxB,YAAI,CAAC,gBAAgB,KAAK,EAAE,EAAG;AAC/B,YAAI;AACJ,YAAI;AACA,oBAAU,MAAMD,IAAG,SAAS,QAAQC,MAAK,KAAK,KAAK,EAAE,CAAC;AAAA,QAC1D,QAAQ;AACJ;AAAA,QACJ;AACA,mBAAW,MAAM,SAAS;AACtB,cAAI,CAAC,gBAAgB,KAAK,EAAE,EAAG;AAC/B,cAAI;AACJ,cAAI;AACA,oBAAQ,MAAMD,IAAG,SAAS,QAAQC,MAAK,KAAK,KAAK,IAAI,EAAE,CAAC;AAAA,UAC5D,QAAQ;AACJ;AAAA,UACJ;AACA,qBAAW,QAAQ,OAAO;AACtB,gBAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAS7B,kBAAM,OAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM;AAC1C,gBAAI,iBAAiB,KAAK,IAAI,GAAG;AAC7B,oBAAMD,IAAG,SAAS,GAAGC,MAAK,KAAK,KAAK,IAAI,IAAI,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,MAAM;AAAE,gCAAgB;AAAA,cAAG,GAAG,MAAM;AAAA,cAAC,CAAC;AAC/G;AAAA,YACJ;AACA,gBAAI,SAAwC;AAC5C,gBAAI;AACA,uBAAS,MAAMD,IAAG,SAAS,KAAKC,MAAK,KAAK,KAAK,IAAI,IAAI,IAAI,GAAG,GAAG;AACjE,oBAAM,MAAM,OAAO,MAAM,OAAO,MAAM;AACtC,oBAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,KAAK,GAAG,OAAO,QAAQ,CAAC;AAChE,kBAAI,cAAc,OAAO,UAAU,IAAI,SAAS,MAAM,MAAM,QAAQ;AAChE,qBAAK,KAAK,IAAI;AAAA,cAClB;AAAA,YACJ,QAAQ;AAAA,YAER,UAAE;AACE,oBAAM,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,cAAC,CAAC;AAAA,YACxC;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AACA,UAAI,eAAe,EAAG,SAAQ,KAAK,0DAA0D,EAAE,OAAO,aAAa,CAAC;AACpH,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;ACjWA,SAAS,yBAAsD;AAaxD,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YAAqB,YAA6B,QAAuB;AAKvE;AAAA,MACE,WAAW,aACP,uBAAuB,UAAU,KACjC,wDAAwD,UAAU;AAAA,IACxE;AATmB;AAA6B;AAUhD,SAAK,OAAO;AAAA,EACd;AAAA,EAXqB;AAAA,EAA6B;AAYpD;AAcO,SAAS,sBAAsB,QAA8C;AAClF,QAAM,EAAE,MAAM,QAAQ,IAAI,kBAAkB,MAAM;AAElD,SAAO;AAAA,IACL,WAAW,OAAO,eAA2B;AAC3C,YAAM,MAAM,GAAG,IAAI,cAAc,mBAAmB,OAAO,UAAU,CAAC,CAAC;AACvE,YAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;AAExC,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,cAAM,IAAI;AAAA,UACR,OAAO,UAAU;AAAA,UACjB,WAAW,mBAAmB,mBAAmB;AAAA,QACnD;AAAA,MACF;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI,MAAM,qCAAqC,OAAO,UAAU,CAAC,KAAK,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,MAC5G;AAEA,aAAO;AAAA,QACL,MAAM,MAAM,IAAI,YAAY;AAAA,QAC5B,aAAa,IAAI,QAAQ,IAAI,cAAc,KAAK;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACF;","names":["path","chunk","createHash","isObject","path","require","pdfjs","isObject","isNumber","isString","isArray","isArray","isNumber","isString","isObject","resolve","createRequire","isObject","isString","pdfjs","isObject","ocr","confidence","fs","path","createRequire","isObject","isString","isNumber","isArray","require"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semiont/content",
3
- "version": "0.5.31",
3
+ "version": "0.5.33",
4
4
  "engines": {
5
5
  "node": ">=24.0.0"
6
6
  },
@@ -27,8 +27,8 @@
27
27
  "test:coverage": "vitest run --coverage"
28
28
  },
29
29
  "dependencies": {
30
- "@semiont/core": "0.5.31",
31
- "@semiont/observability": "0.5.31",
30
+ "@semiont/core": "0.5.33",
31
+ "@semiont/observability": "0.5.33",
32
32
  "@tesseract.js-data/eng": "^1.0.0",
33
33
  "pdfjs-dist": "^6.2.108",
34
34
  "tesseract.js": "^7.0.0"