@byline/core 6.12.1 → 6.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/@types/extraction-types.d.ts +423 -0
  2. package/dist/@types/extraction-types.js +8 -0
  3. package/dist/@types/index.d.ts +1 -0
  4. package/dist/@types/index.js +1 -0
  5. package/dist/extraction/artifact.d.ts +46 -0
  6. package/dist/extraction/artifact.js +121 -0
  7. package/dist/extraction/artifact.test.node.d.ts +8 -0
  8. package/dist/extraction/artifact.test.node.js +194 -0
  9. package/dist/extraction/errors.d.ts +47 -0
  10. package/dist/extraction/errors.js +57 -0
  11. package/dist/extraction/errors.test.node.d.ts +8 -0
  12. package/dist/extraction/errors.test.node.js +68 -0
  13. package/dist/extraction/evidence.d.ts +22 -0
  14. package/dist/extraction/evidence.js +54 -0
  15. package/dist/extraction/evidence.test.node.d.ts +8 -0
  16. package/dist/extraction/evidence.test.node.js +138 -0
  17. package/dist/extraction/index.d.ts +14 -0
  18. package/dist/extraction/index.js +17 -0
  19. package/dist/extraction/internal.d.ts +30 -0
  20. package/dist/extraction/internal.js +93 -0
  21. package/dist/extraction/outcomes.d.ts +20 -0
  22. package/dist/extraction/outcomes.js +30 -0
  23. package/dist/extraction/outcomes.test.node.d.ts +8 -0
  24. package/dist/extraction/outcomes.test.node.js +55 -0
  25. package/dist/extraction/page-map.d.ts +40 -0
  26. package/dist/extraction/page-map.js +190 -0
  27. package/dist/extraction/page-map.test.node.d.ts +8 -0
  28. package/dist/extraction/page-map.test.node.js +371 -0
  29. package/dist/extraction/producer.d.ts +62 -0
  30. package/dist/extraction/producer.js +76 -0
  31. package/dist/extraction/producer.test.node.d.ts +8 -0
  32. package/dist/extraction/producer.test.node.js +230 -0
  33. package/dist/extraction/root-graph-guard.test.node.d.ts +8 -0
  34. package/dist/extraction/root-graph-guard.test.node.js +293 -0
  35. package/dist/extraction/work.d.ts +55 -0
  36. package/dist/extraction/work.js +102 -0
  37. package/dist/extraction/work.test.node.d.ts +8 -0
  38. package/dist/extraction/work.test.node.js +409 -0
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.js +1 -0
  41. package/dist/storage/stored-files-server.d.ts +1 -0
  42. package/dist/storage/stored-files-server.js +9 -24
  43. package/dist/storage/stored-files-server.test.node.js +6 -0
  44. package/dist/storage/verified-read.d.ts +131 -0
  45. package/dist/storage/verified-read.js +328 -0
  46. package/dist/storage/verified-read.test.node.d.ts +8 -0
  47. package/dist/storage/verified-read.test.node.js +645 -0
  48. package/package.json +2 -2
@@ -0,0 +1,423 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ /**
9
+ * The extraction-provider seam: the contract that makes document extractors
10
+ * interchangeable drivers, in the same way `SearchProvider` makes search engines
11
+ * interchangeable.
12
+ *
13
+ * Every export in this module is **provisional**. Until the contract is declared
14
+ * stable, a minor release may change it, and the changelog will say so.
15
+ * `ExtractionArtifact.schemaVersion` and `PageMapV1.version` version the persisted
16
+ * shapes independently of the package version.
17
+ *
18
+ * A provider is a pure `bytes -> output` driver. It never reads Byline documents,
19
+ * storage, search or access state, never persists anything and never decides
20
+ * eligibility. Those duties belong to orchestration.
21
+ */
22
+ import type { JsonValue } from './field-data-types.js';
23
+ import type { StorageValidator } from './storage-types.js';
24
+ /**
25
+ * What a driver supports. Consumers gate on it and never assume it. A provider
26
+ * that declares `markdown`, `structure` or `pageMap` as `false` must not emit that
27
+ * member of its result.
28
+ *
29
+ * @provisional
30
+ */
31
+ export interface ExtractorCapabilities {
32
+ /** Recognises text on image-only pages. */
33
+ ocr: boolean;
34
+ /** Emits structure-aware markdown. */
35
+ markdown: boolean;
36
+ /** Reconstructs table structure. */
37
+ tables: boolean;
38
+ /** Recognises formulas. */
39
+ formulas: boolean;
40
+ /** Attempts figure and chart understanding. */
41
+ charts: boolean;
42
+ /** Reports block positions or reading order. */
43
+ coordinates: boolean;
44
+ /** Emits a page map version 1 from a page-aware assembly. */
45
+ pageMap: boolean;
46
+ /** Emits provider-native `structure`. */
47
+ structure: boolean;
48
+ /** Accepted MIME types; `*` + `/*` or `type/*` wildcards are allowed. */
49
+ formats: string[];
50
+ }
51
+ /**
52
+ * Hints a driver can act on.
53
+ *
54
+ * @provisional
55
+ */
56
+ export interface ExtractionHints {
57
+ /** The extension is a parser-selection hint. */
58
+ filename?: string;
59
+ /** The declared type; a parser-selection hint. */
60
+ mimeType?: string;
61
+ /** BCP 47; selects an OCR model. */
62
+ language?: string;
63
+ /** An explicit OCR language list, when it differs from `language`. */
64
+ ocrLanguages?: string[];
65
+ }
66
+ /**
67
+ * The name of one extraction hint.
68
+ *
69
+ * @provisional
70
+ */
71
+ export type ExtractionHintName = keyof ExtractionHints;
72
+ /**
73
+ * One file to extract.
74
+ *
75
+ * @provisional
76
+ */
77
+ export interface ExtractionInput extends ExtractionHints {
78
+ /**
79
+ * Verified source bytes: a web stream for large files, or a byte array. A
80
+ * provider consumes the stream to its end or cancels it. The caller owns any
81
+ * spool file behind it.
82
+ */
83
+ data: ReadableStream<Uint8Array> | Uint8Array;
84
+ /** Aborts the extraction. The provider releases its resources. */
85
+ signal?: AbortSignal;
86
+ }
87
+ /**
88
+ * Document-level metadata a driver recovered from the file.
89
+ *
90
+ * @provisional
91
+ */
92
+ export interface ExtractionMetadata {
93
+ /** Detected MIME type. */
94
+ contentType?: string;
95
+ /** Pages as reported by the file's metadata. Prefer `pageCount` on the result. */
96
+ pages?: number;
97
+ title?: string;
98
+ authors?: string[];
99
+ /** Declared or detected language, as embedded. */
100
+ language?: string;
101
+ /** ISO 8601. A provider that cannot normalise an embedded date omits this member. */
102
+ createdAt?: string;
103
+ /** The producing application. */
104
+ producer?: string;
105
+ }
106
+ /**
107
+ * Cheap classification inputs for a router. They are not page boundaries:
108
+ * `charsPerPage` must never be used to derive page-map offsets, because it is
109
+ * counted over text that is normalised afterwards.
110
+ *
111
+ * @provisional
112
+ */
113
+ export interface RoutingSignals {
114
+ totalChars: number;
115
+ charsPerPage?: number[];
116
+ ocrCandidatePages?: number;
117
+ /** Share of pages with a usable text layer, from 0 to 1. */
118
+ textLayerCoverage?: number;
119
+ }
120
+ /**
121
+ * A page map locates each physical page of a paginated source within `plainText`.
122
+ * Offsets are half-open UTF-16 code-unit indices. `validatePageMap` checks the
123
+ * structure; only sentinel verification against a fixture proves that page 3's
124
+ * text came from physical page 3.
125
+ *
126
+ * @provisional
127
+ */
128
+ export interface PageMapV1 {
129
+ version: 1;
130
+ assembly: {
131
+ /** Inserted between consecutive ranges. */
132
+ pageSeparator: string;
133
+ /** What follows the last page. */
134
+ trailing?: 'embedded' | 'none';
135
+ };
136
+ pages: Array<{
137
+ index: number;
138
+ label?: string;
139
+ charStart: number;
140
+ charEnd: number;
141
+ }>;
142
+ unanchored?: Array<{
143
+ kind: string;
144
+ charStart: number;
145
+ charEnd: number;
146
+ }>;
147
+ blocks?: Array<{
148
+ page: number;
149
+ charStart: number;
150
+ charEnd: number;
151
+ kind: string;
152
+ }>;
153
+ }
154
+ /**
155
+ * The normalised output of one extraction.
156
+ *
157
+ * @provisional
158
+ */
159
+ export interface ExtractionResult {
160
+ /** Equals `ExtractionProvider.name`. */
161
+ provider: string;
162
+ /**
163
+ * The engine version as reported at run time. Absent when the provider cannot
164
+ * report it. A provider that implements `resolveVersion` reports the same value.
165
+ */
166
+ providerVersion?: string;
167
+ /** Always present; `''` when nothing is extractable. */
168
+ plainText: string;
169
+ /** Only when `capabilities.markdown`. */
170
+ markdown?: string;
171
+ /** Provider-native and opaque. Only when `capabilities.structure`. */
172
+ structure?: JsonValue;
173
+ /** Only when `capabilities.pageMap`. */
174
+ pageMap?: PageMapV1;
175
+ /** Physical pages, when the format has pages. */
176
+ pageCount?: number;
177
+ metadata: ExtractionMetadata;
178
+ signals: RoutingSignals;
179
+ }
180
+ /**
181
+ * An extraction driver.
182
+ *
183
+ * @provisional
184
+ */
185
+ export interface ExtractionProvider {
186
+ /** Stable driver identifier, recorded on every result. */
187
+ readonly name: string;
188
+ /** What the driver supports. */
189
+ readonly capabilities: ExtractorCapabilities;
190
+ /**
191
+ * Effective output-affecting configuration, as JSON. Included in the producer
192
+ * configuration hash. Omit secrets and endpoints.
193
+ */
194
+ readonly configuration?: Readonly<Record<string, JsonValue>>;
195
+ /** Hints from `ExtractionHints` that change this driver's output. */
196
+ readonly outputAffectingHints?: readonly ExtractionHintName[];
197
+ /**
198
+ * The engine version `extract` would report now, without extracting. Rejects
199
+ * with an `infrastructure` error when the service cannot answer.
200
+ */
201
+ resolveVersion?(options?: {
202
+ signal?: AbortSignal;
203
+ }): Promise<string>;
204
+ /** Extracts one file. Rejects with `ExtractionProviderError` on failure. */
205
+ extract(input: ExtractionInput): Promise<ExtractionResult>;
206
+ /** Boot-time health check for a remote service. */
207
+ ping?(): Promise<void>;
208
+ }
209
+ /**
210
+ * Whether a provider failure describes the input file or its infrastructure.
211
+ * An `infrastructure` failure must not record the file as bad and must preserve
212
+ * existing artifacts.
213
+ *
214
+ * @provisional
215
+ */
216
+ export type ExtractionFailureScope = 'file' | 'infrastructure';
217
+ /**
218
+ * The persisted output of one successful extraction. Document, version, path,
219
+ * access, selection and verification evidence are deliberately absent: they belong
220
+ * to references, so one artifact can serve every authorised reference to the same
221
+ * bytes and producer. An artifact is immutable.
222
+ *
223
+ * A reader that meets an unknown `schemaVersion` treats the artifact as unusable
224
+ * for reuse, never as malformed source data.
225
+ *
226
+ * @provisional
227
+ */
228
+ export interface ExtractionArtifact {
229
+ schemaVersion: 1;
230
+ /** SHA-256 of the source bytes; `null` only for legacy imports. */
231
+ contentHash: string | null;
232
+ /** `true` exactly when `contentHash` came from a verified read. */
233
+ sourceVerified: boolean;
234
+ provider: string;
235
+ providerVersion: string | null;
236
+ /**
237
+ * `null` only for a historical import whose configuration was never recorded. A
238
+ * new extraction always records the hash from its producer identity. A current
239
+ * hash is never written onto historical output.
240
+ */
241
+ producerConfigHash: string | null;
242
+ plainText: string;
243
+ markdown?: string;
244
+ structure?: JsonValue;
245
+ pageMap?: PageMapV1;
246
+ pageCount?: number;
247
+ metadata: ExtractionMetadata;
248
+ signals: RoutingSignals;
249
+ /** ISO 8601. */
250
+ producedAt: string;
251
+ }
252
+ /**
253
+ * The stored object cannot be confirmed to be the file the document records.
254
+ *
255
+ * @provisional
256
+ */
257
+ export type SourceIdentityProblem = 'missing-hash' | 'missing-object' | 'hash-mismatch';
258
+ /**
259
+ * Why a source could not be read. None of these says anything about the file.
260
+ *
261
+ * @provisional
262
+ */
263
+ export type SourceUnavailableReason = 'download-unsupported' | 'provider-mismatch' | 'read-failed' | 'aborted';
264
+ /**
265
+ * Why a read that expected a validator fell back to one unconditional read. A
266
+ * changed token does not prove changed content; the hash decides.
267
+ *
268
+ * @provisional
269
+ */
270
+ export type PreconditionFallbackReason = 'changed' | 'unsupported-kind' | 'unestablishable';
271
+ /**
272
+ * The record that binds an extraction to one verified read at one time. It is not
273
+ * continuous storage monitoring, and a hash copied from the saved file value is not
274
+ * evidence. It is persisted with the reference, never on the immutable artifact.
275
+ *
276
+ * @provisional
277
+ */
278
+ export interface SourceVerificationEvidence {
279
+ /** The reference's `fileHash` at read time. */
280
+ expectedHash: string;
281
+ /** SHA-256 measured from the bytes read; equals `expectedHash`. */
282
+ contentHash: string;
283
+ /** The validator returned with those bytes. */
284
+ validator: StorageValidator;
285
+ /** ISO 8601. */
286
+ verifiedAt: string;
287
+ size?: number;
288
+ readBasis?: 'expected' | 'unconditional' | 'fallback';
289
+ preconditionFallback?: PreconditionFallbackReason;
290
+ }
291
+ /**
292
+ * Why extraction work is required.
293
+ *
294
+ * @provisional
295
+ */
296
+ export type ExtractionWorkReason = 'force'
297
+ /** Nothing usable exists for this reference and producer. */
298
+ | 'no-artifact'
299
+ /** The last attempt failed or hit an identity problem. */
300
+ | 'previous-attempt-unsuccessful'
301
+ /** The reference's `fileHash` differs from the artifact's `contentHash`. */
302
+ | 'source-changed'
303
+ /** There is no valid evidence for the current hash. */
304
+ | 'unverified'
305
+ /** The provider, version or configuration hash differs. */
306
+ | 'producer-changed'
307
+ /** The artifact's `schemaVersion` is not the current one. */
308
+ | 'schema-changed';
309
+ /**
310
+ * The result of `decideExtractionWork`.
311
+ *
312
+ * `versionUnchecked` is reduced assurance: the current engine version and the
313
+ * deployment identifier are both unknown, so an engine upgrade is not detected.
314
+ * `producerUnchecked` is reduced assurance of the same kind, and is `true` only for
315
+ * historical output with no recorded configuration: that configuration was not
316
+ * compared, and a `null` recorded engine version was not compared either.
317
+ *
318
+ * @provisional
319
+ */
320
+ export type ExtractionWorkDecision = {
321
+ kind: 'identity-problem';
322
+ problem: 'missing-hash';
323
+ } | {
324
+ kind: 'work';
325
+ reasons: ExtractionWorkReason[];
326
+ versionUnchecked: boolean;
327
+ } | {
328
+ kind: 'reuse';
329
+ evidence: SourceVerificationEvidence;
330
+ versionUnchecked: boolean;
331
+ producerUnchecked: boolean;
332
+ };
333
+ /**
334
+ * Why a promotion was refused.
335
+ *
336
+ * @provisional
337
+ */
338
+ export type PromotionRefusalReason = 'hash-changed' | 'version-changed' | 'access-changed' | 'artifact-mismatch' | 'claim-lost';
339
+ /**
340
+ * Was the source verified, and was extraction reused, completed, unavailable,
341
+ * blocked or refused?
342
+ *
343
+ * @provisional
344
+ */
345
+ export type DerivationOutcome = {
346
+ kind: 'reused';
347
+ } | {
348
+ kind: 'extracted';
349
+ } | {
350
+ kind: 'identity-problem';
351
+ problem: SourceIdentityProblem;
352
+ } | {
353
+ kind: 'source-unavailable';
354
+ reason: SourceUnavailableReason;
355
+ } | {
356
+ kind: 'provider-failed';
357
+ failureScope: ExtractionFailureScope;
358
+ retryable: boolean;
359
+ } | {
360
+ kind: 'promotion-refused';
361
+ reason: PromotionRefusalReason;
362
+ };
363
+ /**
364
+ * Whether making previously eligible text ineligible was durably recorded.
365
+ *
366
+ * @provisional
367
+ */
368
+ export type EligibilityUpdateOutcome =
369
+ /** Nothing previously eligible became ineligible. */
370
+ {
371
+ kind: 'not-required';
372
+ }
373
+ /** Ineligibility was durably recorded. */
374
+ | {
375
+ kind: 'recorded';
376
+ }
377
+ /** Recording failed; the join still includes the old text. */
378
+ | {
379
+ kind: 'failed';
380
+ };
381
+ /**
382
+ * Whether search reflects the current eligible selection. `written` means the
383
+ * provider acknowledged a write. It is not a durable exclusion guarantee.
384
+ *
385
+ * @provisional
386
+ */
387
+ export type ProjectionOutcome = {
388
+ kind: 'written';
389
+ } | {
390
+ kind: 'pending';
391
+ reason: 'write-failed' | 'not-attempted' | 'stale-write';
392
+ };
393
+ /**
394
+ * Whether ineligible text has left search. Derive it only with `exclusionState`.
395
+ * `excluded` means the eventual level succeeded for this write; it is not the
396
+ * durable read-time guarantee.
397
+ *
398
+ * @provisional
399
+ */
400
+ export type ExclusionState = {
401
+ state: 'not-required';
402
+ } | {
403
+ state: 'excluded';
404
+ } | {
405
+ state: 'pending';
406
+ reason: 'eligibility-update-failed' | 'write-failed' | 'not-attempted' | 'stale-write';
407
+ };
408
+ /**
409
+ * The independent facts about one attachment after processing. A save, an extraction
410
+ * and a search update cannot be reported with one `success` flag.
411
+ *
412
+ * @provisional
413
+ */
414
+ export interface AttachmentProcessingReport {
415
+ documentId: string;
416
+ documentVersionId?: string;
417
+ storagePath: string;
418
+ runId?: string;
419
+ derivation: DerivationOutcome;
420
+ eligibility?: EligibilityUpdateOutcome;
421
+ projection?: ProjectionOutcome;
422
+ exclusion?: ExclusionState;
423
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -9,6 +9,7 @@ export * from './admin-types.js';
9
9
  export * from './collection-types.js';
10
10
  export * from './db-types.js';
11
11
  export * from './document-revision.js';
12
+ export * from './extraction-types.js';
12
13
  export * from './field-data-types.js';
13
14
  export * from './field-types.js';
14
15
  export * from './populate-types.js';
@@ -9,6 +9,7 @@ export * from './admin-types.js';
9
9
  export * from './collection-types.js';
10
10
  export * from './db-types.js';
11
11
  export * from './document-revision.js';
12
+ export * from './extraction-types.js';
12
13
  export * from './field-data-types.js';
13
14
  export * from './field-types.js';
14
15
  export * from './populate-types.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import { type PageMapIssue } from './page-map.js';
9
+ /** The persisted artifact shape this release reads and writes. */
10
+ export declare const EXTRACTION_ARTIFACT_SCHEMA_VERSION = 1;
11
+ /**
12
+ * A code for one broken artifact rule.
13
+ *
14
+ * @provisional
15
+ */
16
+ export type ExtractionArtifactIssueCode = 'invalid-shape' | 'unsupported-schema-version' | 'invalid-content-hash' | 'source-verified-mismatch' | 'invalid-provider' | 'invalid-provider-version' | 'invalid-producer-config-hash' | 'unrecorded-producer' | 'invalid-plain-text' | 'invalid-markdown' | 'invalid-structure' | 'invalid-page-count' | 'invalid-page-map' | 'invalid-metadata' | 'invalid-signals' | 'invalid-produced-at';
17
+ /**
18
+ * One broken artifact rule.
19
+ *
20
+ * @provisional
21
+ */
22
+ export interface ExtractionArtifactIssue {
23
+ code: ExtractionArtifactIssueCode;
24
+ message: string;
25
+ /** The page map's own issues, for `invalid-page-map`. */
26
+ pageMapIssues?: PageMapIssue[];
27
+ }
28
+ /**
29
+ * Checks a persisted artifact. A `null` `producerConfigHash` fails unless
30
+ * `allowUnrecordedProducer` is `true`, which only code reading or importing
31
+ * historical records passes. A new extraction always records the real hash from its
32
+ * `ExtractionProducerIdentity`.
33
+ *
34
+ * An unknown `schemaVersion` is reported alone: the artifact is unusable for reuse,
35
+ * and its other members cannot be judged against this version's rules.
36
+ *
37
+ * @provisional
38
+ */
39
+ export declare function validateExtractionArtifact(artifact: unknown, options?: {
40
+ allowUnrecordedProducer?: boolean;
41
+ }): {
42
+ ok: true;
43
+ } | {
44
+ ok: false;
45
+ issues: ExtractionArtifactIssue[];
46
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import { CONTENT_HASH_PATTERN } from '../storage/content-hash.js';
9
+ import { everySlot, isCount, isIsoDateTime, isJsonValue, isPlainObject } from './internal.js';
10
+ import { validatePageMap } from './page-map.js';
11
+ /** The persisted artifact shape this release reads and writes. */
12
+ export const EXTRACTION_ARTIFACT_SCHEMA_VERSION = 1;
13
+ const isOptional = (value, check) => value === undefined || check(value);
14
+ const isString = (value) => typeof value === 'string';
15
+ /** Recognised members only; unknown members are ignored, as in stored evidence. */
16
+ function isValidMetadata(metadata) {
17
+ if (!isPlainObject(metadata))
18
+ return false;
19
+ const { contentType, pages, title, authors, language, createdAt, producer } = metadata;
20
+ return ([contentType, title, language, producer].every((member) => isOptional(member, isString)) &&
21
+ isOptional(createdAt, isIsoDateTime) &&
22
+ isOptional(pages, isCount) &&
23
+ isOptional(authors, (value) => everySlot(value, isString)));
24
+ }
25
+ /** Recognised members only; unknown members are ignored, as in stored evidence. */
26
+ function isValidSignals(signals) {
27
+ if (!isPlainObject(signals) || !isCount(signals.totalChars))
28
+ return false;
29
+ const { charsPerPage, ocrCandidatePages, textLayerCoverage } = signals;
30
+ return (isOptional(charsPerPage, (value) => everySlot(value, isCount)) &&
31
+ isOptional(ocrCandidatePages, isCount) &&
32
+ isOptional(textLayerCoverage, (value) => typeof value === 'number' && value >= 0 && value <= 1));
33
+ }
34
+ /**
35
+ * Checks a persisted artifact. A `null` `producerConfigHash` fails unless
36
+ * `allowUnrecordedProducer` is `true`, which only code reading or importing
37
+ * historical records passes. A new extraction always records the real hash from its
38
+ * `ExtractionProducerIdentity`.
39
+ *
40
+ * An unknown `schemaVersion` is reported alone: the artifact is unusable for reuse,
41
+ * and its other members cannot be judged against this version's rules.
42
+ *
43
+ * @provisional
44
+ */
45
+ export function validateExtractionArtifact(artifact, options) {
46
+ const issues = [];
47
+ const report = (code, message) => {
48
+ issues.push({ code, message });
49
+ };
50
+ if (!isPlainObject(artifact)) {
51
+ return {
52
+ ok: false,
53
+ issues: [{ code: 'invalid-shape', message: 'the artifact is not a plain object' }],
54
+ };
55
+ }
56
+ if (artifact.schemaVersion !== EXTRACTION_ARTIFACT_SCHEMA_VERSION) {
57
+ return {
58
+ ok: false,
59
+ issues: [
60
+ {
61
+ code: 'unsupported-schema-version',
62
+ message: `schemaVersion ${String(artifact.schemaVersion)} is not supported`,
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ const { contentHash, producerConfigHash, providerVersion, plainText, pageCount } = artifact;
68
+ if (contentHash !== null &&
69
+ (typeof contentHash !== 'string' || !CONTENT_HASH_PATTERN.test(contentHash))) {
70
+ report('invalid-content-hash', 'contentHash must be a lower-case SHA-256 or null');
71
+ }
72
+ if (artifact.sourceVerified !== (contentHash !== null)) {
73
+ report('source-verified-mismatch', 'sourceVerified must be true exactly when contentHash is set');
74
+ }
75
+ if (typeof artifact.provider !== 'string' || artifact.provider === '') {
76
+ report('invalid-provider', 'provider must be a non-empty string');
77
+ }
78
+ if (providerVersion !== null && typeof providerVersion !== 'string') {
79
+ report('invalid-provider-version', 'providerVersion must be a string or null');
80
+ }
81
+ if (producerConfigHash === null) {
82
+ if (options?.allowUnrecordedProducer !== true) {
83
+ report('unrecorded-producer', 'a new artifact must record its producerConfigHash');
84
+ }
85
+ }
86
+ else if (typeof producerConfigHash !== 'string' ||
87
+ !CONTENT_HASH_PATTERN.test(producerConfigHash)) {
88
+ report('invalid-producer-config-hash', 'producerConfigHash must be a lower-case SHA-256');
89
+ }
90
+ if (typeof plainText !== 'string')
91
+ report('invalid-plain-text', 'plainText must be a string');
92
+ if (artifact.markdown !== undefined && typeof artifact.markdown !== 'string') {
93
+ report('invalid-markdown', 'markdown must be a string');
94
+ }
95
+ if (artifact.structure !== undefined && !isJsonValue(artifact.structure)) {
96
+ report('invalid-structure', 'structure must be a JSON value');
97
+ }
98
+ if (pageCount !== undefined && !isCount(pageCount)) {
99
+ report('invalid-page-count', 'pageCount must be a non-negative integer');
100
+ }
101
+ if (!isValidMetadata(artifact.metadata)) {
102
+ report('invalid-metadata', 'metadata must be an object whose known members have valid types');
103
+ }
104
+ if (!isValidSignals(artifact.signals)) {
105
+ report('invalid-signals', 'signals must be an object whose known members have valid types');
106
+ }
107
+ if (!isIsoDateTime(artifact.producedAt)) {
108
+ report('invalid-produced-at', 'producedAt must be an ISO 8601 date-time');
109
+ }
110
+ if (artifact.pageMap !== undefined && typeof plainText === 'string') {
111
+ const pageMap = validatePageMap(plainText, artifact.pageMap, isCount(pageCount) ? { pageCount: pageCount } : undefined);
112
+ if (!pageMap.ok) {
113
+ issues.push({
114
+ code: 'invalid-page-map',
115
+ message: 'pageMap breaks the page map rules',
116
+ pageMapIssues: pageMap.issues,
117
+ });
118
+ }
119
+ }
120
+ return issues.length === 0 ? { ok: true } : { ok: false, issues };
121
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};