euredact 0.3.1 → 0.3.3
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/README.md +213 -29
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/rules/context.js +62 -0
- package/dist/cjs/rules/engine.js +306 -66
- package/dist/cjs/rules/evidence.js +201 -0
- package/dist/cjs/rules/matchers.js +10 -0
- package/dist/cjs/sdk.js +51 -5
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/rules/context.d.ts +43 -0
- package/dist/esm/rules/context.js +58 -0
- package/dist/esm/rules/engine.d.ts +28 -4
- package/dist/esm/rules/engine.js +273 -66
- package/dist/esm/rules/evidence.d.ts +63 -0
- package/dist/esm/rules/evidence.js +195 -0
- package/dist/esm/rules/matchers.d.ts +10 -0
- package/dist/esm/rules/matchers.js +10 -0
- package/dist/esm/sdk.d.ts +21 -0
- package/dist/esm/sdk.js +51 -5
- package/dist/esm/types.d.ts +53 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -43,7 +43,11 @@ console.log(result.detections);
|
|
|
43
43
|
- **Custom patterns:** register your own regex patterns for domain-specific PII
|
|
44
44
|
- **Checksum validation:** IBAN mod-97, Luhn (credit cards/IMEI), and 30+
|
|
45
45
|
country-specific validators
|
|
46
|
-
- **Priority-aware deduplication:** validated (checksum
|
|
46
|
+
- **Priority-aware deduplication:** validated (checksum, corroborated by the
|
|
47
|
+
document's country) > custom > regex-only
|
|
48
|
+
- **Country self-detection:** infers a document's countries from the entities
|
|
49
|
+
that carry one, so an ambiguous value resolves without the caller naming a
|
|
50
|
+
country — and `countries` never gates what is looked for
|
|
47
51
|
- **Context-aware:** keyword proximity checks and structural detection (JSON field
|
|
48
52
|
names, CSV headers) for ambiguous patterns
|
|
49
53
|
- **Zero runtime dependencies**
|
|
@@ -73,10 +77,16 @@ Main entry point. Detects and redacts PII in the given text.
|
|
|
73
77
|
|
|
74
78
|
```ts
|
|
75
79
|
interface RedactOptions {
|
|
76
|
-
countries?: string[] | null;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
countries?: string[] | null; // Scope. Flags anything attributed elsewhere as
|
|
81
|
+
// outOfScope. Never gates what is looked for.
|
|
82
|
+
countryHint?: string[] | null; // A prior only — resolves ambiguity without
|
|
83
|
+
// narrowing scope or flagging anything
|
|
84
|
+
context?: DocumentContext | null; // Share country evidence across the chunks
|
|
85
|
+
// of one document (see "Chunked documents")
|
|
86
|
+
chunkOffset?: number; // Where this chunk starts in the document
|
|
87
|
+
referentialIntegrity?: boolean; // Replace with consistent labels (default: false)
|
|
88
|
+
detectDates?: boolean; // Include DOB/date-of-death detections (default: false)
|
|
89
|
+
cache?: boolean; // Enable result caching (default: true)
|
|
80
90
|
}
|
|
81
91
|
```
|
|
82
92
|
|
|
@@ -158,6 +168,28 @@ interface Detection {
|
|
|
158
168
|
source: DetectionSource; // "rules" or "cloud"
|
|
159
169
|
country: string | null; // ISO code or null for shared/custom patterns
|
|
160
170
|
confidence: string; // Confidence level
|
|
171
|
+
countryConfidence?: number; // How strongly the document supports `country`,
|
|
172
|
+
// in [0, 1]. 0 means the attribution rests on
|
|
173
|
+
// a checksum alone.
|
|
174
|
+
outOfScope?: boolean; // Attributed outside the declared `countries`.
|
|
175
|
+
// Flagged, never dropped.
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `RedactResult`
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface RedactResult {
|
|
183
|
+
redactedText: string;
|
|
184
|
+
detections: Detection[];
|
|
185
|
+
source: string;
|
|
186
|
+
degraded: boolean;
|
|
187
|
+
|
|
188
|
+
// Country inference — see "Country-independent detection"
|
|
189
|
+
inferredCountries: Array<[string, number]>; // [country, confidence], strongest first
|
|
190
|
+
evidence: CountryEvidence[]; // every signal, with the span behind it
|
|
191
|
+
detectionMode: string; // "declared" if countries was passed,
|
|
192
|
+
// "inferred" otherwise
|
|
161
193
|
}
|
|
162
194
|
```
|
|
163
195
|
|
|
@@ -191,20 +223,107 @@ redaction entirely, failing open with unredacted PII.
|
|
|
191
223
|
|
|
192
224
|
## Country-independent detection
|
|
193
225
|
|
|
194
|
-
|
|
195
|
-
|
|
226
|
+
**`countries` never gates detection.** Every pattern runs on every document,
|
|
227
|
+
whatever you pass. The country you declare decides *how a match is labelled*,
|
|
228
|
+
never *whether it is found*.
|
|
196
229
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
230
|
+
This is the engine's central invariant, enforced by
|
|
231
|
+
`src/__tests__/inference.ts`: no value of `countries` may change which spans are
|
|
232
|
+
detected. A wrong or missing country cannot cause a miss — silent recall loss is
|
|
233
|
+
invisible in testing and surfaces in a breach report, whereas a false positive
|
|
234
|
+
is recoverable.
|
|
235
|
+
|
|
236
|
+
It was not always so. `countries: ["BE"]` used to make a valid Dutch BSN vanish
|
|
237
|
+
entirely, because the Dutch patterns were never run:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
redact("Werknemer met BSN 111222333", { countries: ["BE"] });
|
|
241
|
+
// before: 'Werknemer met BSN 111222333' <- leaked
|
|
242
|
+
// now: 'Werknemer met BSN [NATIONAL_ID]'
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Entities found outside the countries you declared are **flagged, not dropped**:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const [det] = redact("BSN 111222333", { countries: ["BE"] }).detections;
|
|
249
|
+
det.outOfScope; // true — detected, masked, and marked as outside your scope
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
So a Belgian IBAN in a document processed with `countries: ["AT"]` is still
|
|
253
|
+
detected:
|
|
202
254
|
|
|
203
255
|
```ts
|
|
204
256
|
redact("Rekening: BE68 5390 0754 7034", { countries: ["AT"] });
|
|
205
257
|
// -> 'Rekening: [BANK_ACCOUNT]'
|
|
206
258
|
```
|
|
207
259
|
|
|
260
|
+
### Which country a value belongs to
|
|
261
|
+
|
|
262
|
+
Because every pattern runs, the same digits often match several countries'
|
|
263
|
+
schemes. 36.6% of national-ID values in our corpus validate under more than one
|
|
264
|
+
country's checksum, so the digits alone cannot decide it — the *document* does.
|
|
265
|
+
|
|
266
|
+
The engine infers the document's countries from entities that carry their
|
|
267
|
+
country in the string, then uses that to resolve the ambiguity:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
redact("Bereikbaar op telefoon 0612345678, mail jan@test.nl");
|
|
271
|
+
// inferredCountries: [["NL", 0.98]] detections: [PHONE (NL), EMAIL]
|
|
272
|
+
|
|
273
|
+
redact("Kontakt: 0612345678, e-mail jens@test.dk");
|
|
274
|
+
// inferredCountries: [["DK", 0.98]] detections: [NATIONAL_ID (DK), EMAIL]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Identical digits, different answer — `0612345678` is both a valid Dutch mobile
|
|
278
|
+
number and a valid Danish CPR. Only the surrounding document distinguishes them.
|
|
279
|
+
|
|
280
|
+
Every inference is auditable: `result.evidence` lists each signal, its weight,
|
|
281
|
+
and the span that produced it, in document order.
|
|
282
|
+
|
|
283
|
+
| Signal | Weight (log-odds) | Measured reliability |
|
|
284
|
+
|---|---:|---|
|
|
285
|
+
| `e164Prefix` | 4.00 (capped) | 41,402 / 41,402 |
|
|
286
|
+
| `bicCountry` | 4.00 (capped) | 2,588 / 2,588 |
|
|
287
|
+
| `emailTld` | 4.00 (capped) | 97,865 / 98,949 |
|
|
288
|
+
| `vatPrefix` | 2.84 | 19,022 / 20,136 |
|
|
289
|
+
| `ibanPrefix` | 1.94 | 110,572 / 126,428 |
|
|
290
|
+
|
|
291
|
+
Weights are derived from the corpus, not chosen by hand, and are kept identical
|
|
292
|
+
to the Python SDK's. The IBAN prefix being weakest is real: a Belgian IBAN in a
|
|
293
|
+
Dutch invoice is ordinary, so an account's country is only weak evidence about
|
|
294
|
+
the document's.
|
|
295
|
+
|
|
296
|
+
Confidences are per-country and do **not** sum to 1 — document countries are not
|
|
297
|
+
mutually exclusive. A Belgian supplier invoicing a German customer is genuinely
|
|
298
|
+
both.
|
|
299
|
+
|
|
300
|
+
## Chunked documents
|
|
301
|
+
|
|
302
|
+
A long document is usually redacted in pieces. Each piece is scanned
|
|
303
|
+
independently, so a chunk carrying no country signal of its own infers nothing —
|
|
304
|
+
even when page 1 identified the document beyond doubt.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { DocumentContext } from "euredact";
|
|
308
|
+
|
|
309
|
+
const ctx = new DocumentContext();
|
|
310
|
+
let offset = 0;
|
|
311
|
+
for (const page of pages) {
|
|
312
|
+
const result = redact(page, { context: ctx, chunkOffset: offset });
|
|
313
|
+
offset += page.length;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// page 1: "Factuur — IBAN NL91ABNA0417164300, info@jansen.nl"
|
|
317
|
+
// page 7: "Telefoon 0612345678" -> PHONE (NL), not NATIONAL_ID (DK)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`chunkOffset` rebases spans recorded in the context so they point into the whole
|
|
321
|
+
document; returned detections stay relative to the chunk you passed in. Caching
|
|
322
|
+
is disabled automatically while a context is in use, because the result then
|
|
323
|
+
depends on evidence the text alone does not determine.
|
|
324
|
+
|
|
325
|
+
Reuse a context only for chunks of the **same** document.
|
|
326
|
+
|
|
208
327
|
## BIC detection
|
|
209
328
|
|
|
210
329
|
BIC is the only bank identifier here with **no check digit**, and characters
|
|
@@ -332,18 +451,35 @@ redact("identifier: xK9mPqR7vLnW2bFjY8cGhT4sDfAeU6iO").redactedText;
|
|
|
332
451
|
|
|
333
452
|
## Country Hints
|
|
334
453
|
|
|
335
|
-
|
|
454
|
+
Two options tell the engine about country, and neither restricts what is looked
|
|
455
|
+
for:
|
|
456
|
+
|
|
457
|
+
| Option | Meaning |
|
|
458
|
+
|---|---|
|
|
459
|
+
| `countries` | **Scope.** Resolves ambiguity, and flags anything attributed elsewhere as `outOfScope`. |
|
|
460
|
+
| `countryHint` | **Prior only.** Resolves ambiguity without narrowing scope or flagging anything. |
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
// You know this batch is Swedish, but don't want foreign PII marked out of scope:
|
|
464
|
+
redact(text, { countryHint: ["SE"] });
|
|
465
|
+
|
|
466
|
+
// You want anything non-Swedish flagged for review:
|
|
467
|
+
redact(text, { countries: ["SE"] });
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Declaring a country helps where a value is genuinely ambiguous and the document
|
|
471
|
+
carries no other signal:
|
|
336
472
|
|
|
337
473
|
```ts
|
|
338
|
-
|
|
474
|
+
redact("Telefon: 0708787668", { countryHint: ["SE"] }).detections[0];
|
|
475
|
+
// PHONE / SE — without the hint this is a valid Danish CPR and nothing says otherwise
|
|
339
476
|
```
|
|
340
477
|
|
|
341
|
-
|
|
342
|
-
|
|
478
|
+
`result.detectionMode` reports which happened: `"declared"` if you passed
|
|
479
|
+
`countries`, `"inferred"` otherwise.
|
|
343
480
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
- **Faster.** Fewer patterns to compile and scan.
|
|
481
|
+
Passing neither is safe, and is the right default for mixed-origin data: the
|
|
482
|
+
engine infers what it can and reports it in `result.inferredCountries`.
|
|
347
483
|
|
|
348
484
|
## Referential Integrity
|
|
349
485
|
|
|
@@ -372,13 +508,17 @@ Input text
|
|
|
372
508
|
|
|
|
373
509
|
v
|
|
374
510
|
[Pass 2a: Validation] -- Checksum validators (mod-97, Luhn, entropy, ...)
|
|
375
|
-
| Failed
|
|
511
|
+
| Failed spans are recorded, per entity type
|
|
512
|
+
v
|
|
513
|
+
[Evidence] -- Which countries does this document belong to? From IBAN
|
|
514
|
+
| prefixes, +CC codes, VAT prefixes, BIC, email ccTLDs
|
|
376
515
|
v
|
|
377
516
|
[Pass 2b: Suppression] -- Remove false positives (currency amounts, units,
|
|
378
|
-
| references
|
|
517
|
+
| references). Failed checksums demote same-type
|
|
518
|
+
| matches rather than deleting them
|
|
379
519
|
v
|
|
380
|
-
[Deduplication] -- Priority-aware
|
|
381
|
-
|
|
|
520
|
+
[Deduplication] -- Priority-aware, country-evidence-weighted
|
|
521
|
+
| Longer span outranks declared country
|
|
382
522
|
v
|
|
383
523
|
[Replacement] -- Right-to-left substitution with [ENTITY_TYPE] labels
|
|
384
524
|
| or labels
|
|
@@ -389,16 +529,60 @@ RedactResult
|
|
|
389
529
|
### Suppression Zones
|
|
390
530
|
|
|
391
531
|
When a regex matches a pattern that has a checksum validator but the checksum
|
|
392
|
-
fails, the
|
|
393
|
-
|
|
532
|
+
fails, the span is recorded. A validator-less match of **the same entity type**
|
|
533
|
+
contained in it is *demoted* below every other candidate — not deleted. A failed
|
|
534
|
+
checksum is evidence against that type, not against the span, and demotion can
|
|
535
|
+
never silence a detection the way deletion could.
|
|
394
536
|
|
|
395
537
|
### Deduplication Priority
|
|
396
538
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
539
|
+
When multiple patterns match overlapping spans, the engine resolves conflicts
|
|
540
|
+
using a priority system:
|
|
541
|
+
|
|
542
|
+
| Tier | What |
|
|
543
|
+
|---:|---|
|
|
544
|
+
| 3 | **Validated** — a checksum validator passes *and* the document corroborates its country |
|
|
545
|
+
| 2 | **Custom patterns** registered via `addCustomPattern()` |
|
|
546
|
+
| 1 | **Regex-only**, and validated patterns whose country the document does not corroborate |
|
|
547
|
+
| 0 | **Postal codes** — a bare digit run, the weakest evidence in the engine |
|
|
548
|
+
| -1 | **Demoted** — a validator-less match inside a failed checksum of *its own type* |
|
|
549
|
+
|
|
550
|
+
Within a tier, ranking is: longer span, then stronger country evidence, then
|
|
551
|
+
whether the country was declared. **Span length outranks country** deliberately:
|
|
552
|
+
preferring the declared country over the longest match truncates entities — with
|
|
553
|
+
`countries: ["BE"]` the Belgian phone pattern claimed 11 of the 14 characters of
|
|
554
|
+
`06 12 34 56 78` and left three digits exposed. Country can change *which*
|
|
555
|
+
country is attributed, never *what* is masked.
|
|
556
|
+
|
|
557
|
+
Two tiers are less obvious than they look:
|
|
558
|
+
|
|
559
|
+
- **A passing checksum does not automatically win.** A weak checksum fits by
|
|
560
|
+
luck — a mod-11 scheme accepts a random number about one time in eleven — so a
|
|
561
|
+
validated candidate from a country the document shows no trace of drops to
|
|
562
|
+
tier 1. Entities that carry their own country vouch for themselves (an IBAN
|
|
563
|
+
emits evidence for its own country), so a foreign IBAN in a domestic invoice
|
|
564
|
+
keeps tier 3.
|
|
565
|
+
- **Nothing is deleted for failing a checksum.** A failed checksum demotes
|
|
566
|
+
rather than removes, and only candidates of *the same entity type*: it is
|
|
567
|
+
evidence against that type, not against the span. Deleting instead removed 454
|
|
568
|
+
detections across the corpus, of which 454 overlapped real labelled PII.
|
|
569
|
+
|
|
570
|
+
## Performance
|
|
571
|
+
|
|
572
|
+
Measured on one core, all 31 countries loaded, `detectDates: true`. Cost scales
|
|
573
|
+
with document length, so the input size is stated rather than averaged away.
|
|
574
|
+
|
|
575
|
+
| Input | Latency | Throughput |
|
|
576
|
+
|---|---:|---:|
|
|
577
|
+
| Short record (186 chars) | 155 µs | 6,462 docs/s |
|
|
578
|
+
| Real document (3,424 chars) | 1.56 ms | 643 docs/s |
|
|
400
579
|
|
|
401
|
-
|
|
580
|
+
No optional accelerator is needed or offered. The Python SDK ships an
|
|
581
|
+
`[fast]` extra (RE2 / Aho-Corasick) because CPython's regex engine is the
|
|
582
|
+
bottleneck there; V8's has literal prefilters that make it unnecessary here.
|
|
583
|
+
Measured on the same 611 documents, this SDK runs about 3× faster than the
|
|
584
|
+
accelerated Python path, so adding a native addon — and with it the loss of
|
|
585
|
+
bundler, edge-runtime and Deno compatibility — would buy nothing.
|
|
402
586
|
|
|
403
587
|
## CommonJS
|
|
404
588
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SEED_BIC6_PREFIXES = exports.getBicRegistry = exports.setBicRegistry = exports.COUNTRY_CONFIGS = exports.EuRedact = exports.LEGACY_TYPE_ALIASES = exports.DetectionSource = exports.EntityType = void 0;
|
|
3
|
+
exports.SEED_BIC6_PREFIXES = exports.getBicRegistry = exports.setBicRegistry = exports.COUNTRY_CONFIGS = exports.EuRedact = exports.DocumentContext = exports.LEGACY_TYPE_ALIASES = exports.DetectionSource = exports.EntityType = void 0;
|
|
4
4
|
exports.availableCountries = availableCountries;
|
|
5
5
|
exports.redact = redact;
|
|
6
6
|
exports.addCustomPattern = addCustomPattern;
|
|
@@ -10,6 +10,8 @@ var types_js_1 = require("./types.js");
|
|
|
10
10
|
Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return types_js_1.EntityType; } });
|
|
11
11
|
Object.defineProperty(exports, "DetectionSource", { enumerable: true, get: function () { return types_js_1.DetectionSource; } });
|
|
12
12
|
Object.defineProperty(exports, "LEGACY_TYPE_ALIASES", { enumerable: true, get: function () { return types_js_1.LEGACY_TYPE_ALIASES; } });
|
|
13
|
+
var context_js_1 = require("./rules/context.js");
|
|
14
|
+
Object.defineProperty(exports, "DocumentContext", { enumerable: true, get: function () { return context_js_1.DocumentContext; } });
|
|
13
15
|
var sdk_js_1 = require("./sdk.js");
|
|
14
16
|
Object.defineProperty(exports, "EuRedact", { enumerable: true, get: function () { return sdk_js_1.EuRedact; } });
|
|
15
17
|
var index_js_1 = require("./rules/countries/index.js");
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Country evidence shared across the chunks of one document.
|
|
4
|
+
*
|
|
5
|
+
* A long document is usually redacted in pieces. Each piece is scanned
|
|
6
|
+
* independently, so each infers its own country — and a chunk that happens to
|
|
7
|
+
* contain no IBAN, no `+CC` number and no ccTLD infers nothing at all, even
|
|
8
|
+
* when the first page identified the document beyond doubt:
|
|
9
|
+
*
|
|
10
|
+
* page 1 "Factuur — IBAN NL91ABNA0417164300, info@jansen.nl"
|
|
11
|
+
* page 7 "Telefoon 0612345678" <- Dutch mobile, or Danish CPR?
|
|
12
|
+
*
|
|
13
|
+
* Scanned alone, page 7 has nothing to go on. Sharing a context across the two
|
|
14
|
+
* carries page 1's evidence forward, and the number resolves as a Dutch phone.
|
|
15
|
+
*
|
|
16
|
+
* Spans are rebased by `chunkOffset` on the way in, so evidence collected from
|
|
17
|
+
* a chunk points at offsets in the whole document rather than the chunk.
|
|
18
|
+
*
|
|
19
|
+
* A context influences **scoring only**. It can change which national scheme is
|
|
20
|
+
* attributed to a value. It can never change which spans are found, so a
|
|
21
|
+
* context carried over from the wrong document cannot cause a miss.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.DocumentContext = void 0;
|
|
25
|
+
class DocumentContext {
|
|
26
|
+
// Deduped on the same key evidence.collect() uses, so re-running a chunk — a
|
|
27
|
+
// retry, an overlapping window — cannot let one entity vote twice and
|
|
28
|
+
// outweigh the rest of the document.
|
|
29
|
+
seen = new Set();
|
|
30
|
+
items = [];
|
|
31
|
+
/** Record evidence from a chunk starting at `chunkOffset`. */
|
|
32
|
+
add(evidence, chunkOffset = 0) {
|
|
33
|
+
for (const ev of evidence) {
|
|
34
|
+
const span = [ev.span[0] + chunkOffset, ev.span[1] + chunkOffset];
|
|
35
|
+
const key = `${span[0]}:${span[1]}:${ev.source}`;
|
|
36
|
+
if (this.seen.has(key))
|
|
37
|
+
continue;
|
|
38
|
+
this.seen.add(key);
|
|
39
|
+
this.items.push(chunkOffset === 0 ? ev : { ...ev, span });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Everything recorded so far, spans rebased to the whole document.
|
|
44
|
+
*
|
|
45
|
+
* A getter, to match {@link DocumentContext.size}. These were a method and a
|
|
46
|
+
* getter on the same class, which is a trip hazard rather than a style
|
|
47
|
+
* question: `ctx.size()` and `ctx.evidence` both *succeed* and return
|
|
48
|
+
* something useless — a number is not callable, and a function object is
|
|
49
|
+
* truthy — so the mistake surfaces later as an empty result rather than as
|
|
50
|
+
* an error. Both are property reads now, and the wrong form throws.
|
|
51
|
+
*
|
|
52
|
+
* Returns a fresh copy each call, so mutating it cannot corrupt the context.
|
|
53
|
+
* (The Python SDK spells this `evidence()`, following its own idiom.)
|
|
54
|
+
*/
|
|
55
|
+
get evidence() {
|
|
56
|
+
return [...this.items];
|
|
57
|
+
}
|
|
58
|
+
get size() {
|
|
59
|
+
return this.items.length;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.DocumentContext = DocumentContext;
|