@stll/anonymize-docx 2.8.1 → 2.8.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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { loadNativeAnonymizeBinding } from "@stll/anonymize";
1
+ import { CALLER_DETECTION_MAX_COUNT, loadNativeAnonymizeBinding } from "@stll/anonymize";
2
2
  //#region src/types.ts
3
3
  const DOCX_PART_TYPES = {
4
4
  comments: "comments",
@@ -39,6 +39,140 @@ const DOCX_EXTRACTION_ERROR_CODES = {
39
39
  uncompressedLimitExceeded: "uncompressed-limit-exceeded"
40
40
  };
41
41
  //#endregion
42
+ //#region src/native-codec.ts
43
+ const PART_FIELDS = {
44
+ type: true,
45
+ path: true
46
+ };
47
+ const BASE_LOCATION_FIELDS = {
48
+ type: true,
49
+ part: true,
50
+ blockIndex: true,
51
+ xmlPath: true
52
+ };
53
+ const PARAGRAPH_LOCATION_FIELDS = BASE_LOCATION_FIELDS;
54
+ const TABLE_LOCATION_FIELDS = {
55
+ ...BASE_LOCATION_FIELDS,
56
+ tablePath: true,
57
+ rowPath: true,
58
+ cellPath: true
59
+ };
60
+ const TEXT_BOX_LOCATION_FIELDS = {
61
+ ...BASE_LOCATION_FIELDS,
62
+ textBoxPath: true
63
+ };
64
+ const HYPERLINK_CONTEXT_FIELDS = {
65
+ type: true,
66
+ relationshipId: true,
67
+ anchor: true
68
+ };
69
+ const REVISION_CONTEXT_FIELDS = {
70
+ type: true,
71
+ revision: true
72
+ };
73
+ const SEGMENT_FIELDS = {
74
+ start: true,
75
+ end: true,
76
+ source: true,
77
+ contexts: true,
78
+ xmlPath: true
79
+ };
80
+ const BLOCK_FIELDS = {
81
+ text: true,
82
+ location: true,
83
+ segments: true
84
+ };
85
+ const EXTRACTED_COVERAGE_FIELDS = {
86
+ status: true,
87
+ part: true,
88
+ blockCount: true
89
+ };
90
+ const UNSUPPORTED_COVERAGE_FIELDS = {
91
+ status: true,
92
+ path: true,
93
+ contentType: true,
94
+ reason: true
95
+ };
96
+ const COVERAGE_FIELDS = {
97
+ parts: true,
98
+ hyperlinkTextSegmentCount: true,
99
+ revisionTextSegmentCount: true,
100
+ unsupportedAlternateContentCount: true,
101
+ unsupportedSymbolCount: true,
102
+ unsupportedFieldInstructionCount: true
103
+ };
104
+ const EXTRACTION_FIELDS = {
105
+ contractVersion: true,
106
+ blocks: true,
107
+ coverage: true
108
+ };
109
+ const RESTORATION_CANDIDATE_FIELDS = {
110
+ start: true,
111
+ end: true,
112
+ candidate: true
113
+ };
114
+ const RESTORATION_BLOCK_FIELDS = {
115
+ location: true,
116
+ expectedText: true,
117
+ candidates: true
118
+ };
119
+ const RESTORATION_PLAN_FIELDS = {
120
+ extraction: true,
121
+ blocks: true,
122
+ candidateCount: true
123
+ };
124
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
125
+ const hasExactFields = (value, fields) => {
126
+ const keys = Object.keys(value);
127
+ return keys.length === Object.keys(fields).length && keys.every((key) => Object.hasOwn(fields, key));
128
+ };
129
+ const isNonNegativeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
130
+ const isPath = (value) => Array.isArray(value) && value.every(isNonNegativeInteger);
131
+ const isPart = (value) => isRecord(value) && hasExactFields(value, PART_FIELDS) && Object.values(DOCX_PART_TYPES).some((partType) => partType === value["type"]) && typeof value["path"] === "string";
132
+ const hasBaseLocation = (value) => isPart(value["part"]) && isNonNegativeInteger(value["blockIndex"]) && isPath(value["xmlPath"]);
133
+ const isLocation = (value) => {
134
+ if (!isRecord(value) || !hasBaseLocation(value)) return false;
135
+ switch (value["type"]) {
136
+ case "paragraph": return hasExactFields(value, PARAGRAPH_LOCATION_FIELDS);
137
+ case "table-cell-paragraph": return hasExactFields(value, TABLE_LOCATION_FIELDS) && isPath(value["tablePath"]) && isPath(value["rowPath"]) && isPath(value["cellPath"]);
138
+ case "text-box-paragraph": return hasExactFields(value, TEXT_BOX_LOCATION_FIELDS) && isPath(value["textBoxPath"]);
139
+ default: return false;
140
+ }
141
+ };
142
+ const isNullableString = (value) => value === null || typeof value === "string";
143
+ const isInlineContext = (value) => {
144
+ if (!isRecord(value)) return false;
145
+ switch (value["type"]) {
146
+ case "hyperlink": return hasExactFields(value, HYPERLINK_CONTEXT_FIELDS) && isNullableString(value["relationshipId"]) && isNullableString(value["anchor"]);
147
+ case "revision": return hasExactFields(value, REVISION_CONTEXT_FIELDS) && (value["revision"] === "deletion" || value["revision"] === "insertion" || value["revision"] === "move-from" || value["revision"] === "move-to");
148
+ default: return false;
149
+ }
150
+ };
151
+ const isSegment = (value) => isRecord(value) && hasExactFields(value, SEGMENT_FIELDS) && isNonNegativeInteger(value["start"]) && isNonNegativeInteger(value["end"]) && value["end"] >= value["start"] && (value["source"] === "break" || value["source"] === "tab" || value["source"] === "text") && Array.isArray(value["contexts"]) && value["contexts"].every(isInlineContext) && isPath(value["xmlPath"]);
152
+ const isBlock = (value) => isRecord(value) && hasExactFields(value, BLOCK_FIELDS) && typeof value["text"] === "string" && isLocation(value["location"]) && Array.isArray(value["segments"]) && value["segments"].every(isSegment);
153
+ const isCoverageItem = (value) => {
154
+ if (!isRecord(value)) return false;
155
+ switch (value["status"]) {
156
+ case "extracted": return hasExactFields(value, EXTRACTED_COVERAGE_FIELDS) && isPart(value["part"]) && isNonNegativeInteger(value["blockCount"]);
157
+ case "unsupported": return hasExactFields(value, UNSUPPORTED_COVERAGE_FIELDS) && typeof value["path"] === "string" && typeof value["contentType"] === "string" && typeof value["reason"] === "string";
158
+ default: return false;
159
+ }
160
+ };
161
+ const isCoverage = (value) => isRecord(value) && hasExactFields(value, COVERAGE_FIELDS) && Array.isArray(value["parts"]) && value["parts"].every(isCoverageItem) && isNonNegativeInteger(value["hyperlinkTextSegmentCount"]) && isNonNegativeInteger(value["revisionTextSegmentCount"]) && isNonNegativeInteger(value["unsupportedAlternateContentCount"]) && isNonNegativeInteger(value["unsupportedSymbolCount"]) && isNonNegativeInteger(value["unsupportedFieldInstructionCount"]);
162
+ const isExtraction = (value) => isRecord(value) && hasExactFields(value, EXTRACTION_FIELDS) && value["contractVersion"] === 1 && Array.isArray(value["blocks"]) && value["blocks"].every(isBlock) && isCoverage(value["coverage"]);
163
+ const isRestorationCandidate = (value) => isRecord(value) && hasExactFields(value, RESTORATION_CANDIDATE_FIELDS) && isNonNegativeInteger(value["start"]) && isNonNegativeInteger(value["end"]) && value["end"] >= value["start"] && typeof value["candidate"] === "string";
164
+ const isRestorationPlan = (value) => isRecord(value) && hasExactFields(value, RESTORATION_PLAN_FIELDS) && isExtraction(value["extraction"]) && Array.isArray(value["blocks"]) && value["blocks"].every((block) => isRecord(block) && hasExactFields(block, RESTORATION_BLOCK_FIELDS) && isLocation(block["location"]) && typeof block["expectedText"] === "string" && Array.isArray(block["candidates"]) && block["candidates"].every(isRestorationCandidate)) && isNonNegativeInteger(value["candidateCount"]);
165
+ const decodeDocxExtraction = (json) => {
166
+ const value = JSON.parse(json);
167
+ if (!isExtraction(value)) throw new Error("Native DOCX extraction does not match contract version 1");
168
+ return value;
169
+ };
170
+ const decodeDocxRestorationPlan = (json) => {
171
+ const value = JSON.parse(json);
172
+ if (!isRestorationPlan(value)) throw new Error("Native DOCX restoration plan does not match contract version 1");
173
+ return value;
174
+ };
175
+ //#endregion
42
176
  //#region src/extract.ts
43
177
  const DOCX_EXTRACTION_CONTRACT_VERSION = 1;
44
178
  const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;
@@ -65,7 +199,7 @@ const extractDocxText = (archive) => {
65
199
  const extract = loadNativeAnonymizeBinding().extractDocxTextJson;
66
200
  if (extract === void 0) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidPackage, "Native anonymize binding does not expose DOCX extraction");
67
201
  try {
68
- return JSON.parse(extract(archive));
202
+ return decodeDocxExtraction(extract(archive));
69
203
  } catch (error) {
70
204
  const message = error instanceof Error ? error.message : "DOCX extraction failed";
71
205
  throw new DocxExtractionError(nativeExtractionErrorCode(message), message);
@@ -85,13 +219,83 @@ const REWRITE_ERROR_CODES = new Set(Object.values(DOCX_REWRITE_ERROR_CODES));
85
219
  const EXTRACTION_ERROR_CODES = new Set(Object.values(DOCX_EXTRACTION_ERROR_CODES));
86
220
  const DOCX_REWRITE_MAX_BLOCKS = 1e5;
87
221
  const DOCX_REWRITE_MAX_REPLACEMENTS = 1e6;
88
- const LOCATION_PATH_KEYS = [
89
- "xmlPath",
90
- "tablePath",
91
- "rowPath",
92
- "cellPath",
93
- "textBoxPath"
94
- ];
222
+ const invalidLocation = (message) => {
223
+ throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, message);
224
+ };
225
+ const ownDataRecord = (value, fields) => {
226
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return invalidLocation("DOCX rewrite locations must be plain objects");
227
+ const prototype = Object.getPrototypeOf(value);
228
+ if (prototype !== Object.prototype && prototype !== null) return invalidLocation("DOCX rewrite locations must be plain objects");
229
+ const keys = Reflect.ownKeys(value);
230
+ if (keys.some((key) => typeof key !== "string") || fields !== void 0 && (keys.length !== fields.length || keys.some((key) => typeof key !== "string" || !fields.includes(key)))) return invalidLocation("DOCX rewrite locations must contain exactly their declared fields");
231
+ for (const field of fields ?? keys) {
232
+ if (typeof field !== "string") return invalidLocation("DOCX rewrite locations must contain string fields");
233
+ const descriptor = Object.getOwnPropertyDescriptor(value, field);
234
+ if (descriptor === void 0 || !("value" in descriptor)) return invalidLocation("DOCX rewrite locations must contain own data properties");
235
+ }
236
+ return value;
237
+ };
238
+ const ownValue = (record, field) => Object.getOwnPropertyDescriptor(record, field)?.value;
239
+ const isDocxPartType = (value) => Object.values(DOCX_PART_TYPES).some((type) => type === value);
240
+ const copyPath = (path) => {
241
+ if (!Array.isArray(path)) return invalidLocation("DOCX rewrite location paths must be arrays");
242
+ if (path.length > 256) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, `DOCX rewrite location paths must not exceed 256 entries`);
243
+ const copy = [];
244
+ for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {
245
+ const index = Object.getOwnPropertyDescriptor(path, pathIndex)?.value;
246
+ if (index === void 0 || !Number.isSafeInteger(index) || index < 0) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX rewrite location paths must contain non-negative integers");
247
+ copy.push(index);
248
+ }
249
+ return copy;
250
+ };
251
+ const copyLocation = (location) => {
252
+ const candidate = ownDataRecord(location);
253
+ const type = ownValue(candidate, "type");
254
+ const fields = [
255
+ "type",
256
+ "part",
257
+ "blockIndex",
258
+ "xmlPath"
259
+ ];
260
+ if (type === "table-cell-paragraph") fields.push("tablePath", "rowPath", "cellPath");
261
+ else if (type === "text-box-paragraph") fields.push("textBoxPath");
262
+ if (Object.hasOwn(candidate, "toJSON")) fields.push("toJSON");
263
+ const base = ownDataRecord(location, fields);
264
+ const partRecord = ownDataRecord(ownValue(base, "part"), ["type", "path"]);
265
+ const partType = ownValue(partRecord, "type");
266
+ const partPath = ownValue(partRecord, "path");
267
+ const blockIndex = ownValue(base, "blockIndex");
268
+ if (!isDocxPartType(partType) || typeof partPath !== "string" || !Number.isSafeInteger(blockIndex) || typeof blockIndex !== "number" || blockIndex < 0) return invalidLocation("DOCX rewrite locations must contain a known type, part, and block index");
269
+ const part = {
270
+ type: partType,
271
+ path: partPath
272
+ };
273
+ switch (type) {
274
+ case "paragraph": return {
275
+ type,
276
+ part,
277
+ blockIndex,
278
+ xmlPath: copyPath(ownValue(base, "xmlPath"))
279
+ };
280
+ case "table-cell-paragraph": return {
281
+ type,
282
+ part,
283
+ blockIndex,
284
+ xmlPath: copyPath(ownValue(base, "xmlPath")),
285
+ tablePath: copyPath(ownValue(base, "tablePath")),
286
+ rowPath: copyPath(ownValue(base, "rowPath")),
287
+ cellPath: copyPath(ownValue(base, "cellPath"))
288
+ };
289
+ case "text-box-paragraph": return {
290
+ type,
291
+ part,
292
+ blockIndex,
293
+ xmlPath: copyPath(ownValue(base, "xmlPath")),
294
+ textBoxPath: copyPath(ownValue(base, "textBoxPath"))
295
+ };
296
+ default: return invalidLocation("DOCX rewrite locations must use a known location type");
297
+ }
298
+ };
95
299
  const preflightRewritePlan = (rewrites) => {
96
300
  const rewriteCount = rewrites.length;
97
301
  if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`);
@@ -105,51 +309,37 @@ const preflightRewritePlan = (rewrites) => {
105
309
  const blockReplacementCount = rewrite.replacements.length;
106
310
  replacementCount += blockReplacementCount;
107
311
  if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`);
108
- estimatedBytes += (typeof rewrite.expectedText === "string" ? rewrite.expectedText.length * 6 : 0) + blockReplacementCount * 96;
109
- const serializableReplacements = [];
312
+ if (typeof rewrite.expectedText !== "string") throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX block rewrite expectedText must be a string");
313
+ estimatedBytes += rewrite.expectedText.length * 6 + blockReplacementCount * 96;
314
+ const replacements = [];
110
315
  for (let replacementIndex = 0; replacementIndex < blockReplacementCount; replacementIndex += 1) {
111
316
  const replacement = rewrite.replacements[replacementIndex];
112
317
  if (replacement === void 0) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX rewrite plans must not contain sparse replacements");
113
- const value = replacement.replacement;
114
- if (typeof value === "string") estimatedBytes += value.length * 6;
115
- serializableReplacements.push({
116
- start: typeof replacement.start === "number" ? replacement.start : null,
117
- end: typeof replacement.end === "number" ? replacement.end : null,
118
- replacement: typeof value === "string" ? value : null
318
+ if (!Number.isSafeInteger(replacement.start) || replacement.start < 0 || !Number.isSafeInteger(replacement.end) || replacement.end < replacement.start || typeof replacement.replacement !== "string") throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX replacements require ordered non-negative integer offsets and string values");
319
+ estimatedBytes += replacement.replacement.length * 6;
320
+ replacements.push({
321
+ start: replacement.start,
322
+ end: replacement.end,
323
+ replacement: replacement.replacement
119
324
  });
120
325
  }
121
- const location = rewrite.location;
122
- const part = location["part"];
123
- for (const value of [
124
- location["type"],
125
- part?.["type"],
126
- part?.["path"]
127
- ]) if (typeof value === "string") estimatedBytes += value.length * 6;
128
- const serializableLocation = {
129
- type: typeof location["type"] === "string" ? location["type"] : null,
130
- part: {
131
- type: typeof part?.["type"] === "string" ? part["type"] : null,
132
- path: typeof part?.["path"] === "string" ? part["path"] : null
133
- },
134
- blockIndex: typeof location["blockIndex"] === "number" ? location["blockIndex"] : null
135
- };
136
- for (const key of LOCATION_PATH_KEYS) {
137
- const path = location[key];
138
- if (Array.isArray(path)) {
139
- if (path.length > 256) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, `DOCX rewrite location paths must not exceed 256 entries`);
140
- estimatedBytes += path.length * 24;
141
- const serializablePath = [];
142
- for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {
143
- const value = path[pathIndex];
144
- serializablePath.push(typeof value === "number" ? value : null);
145
- }
146
- serializableLocation[key] = serializablePath;
147
- }
326
+ const { location } = rewrite;
327
+ const serializableLocation = copyLocation(location);
328
+ estimatedBytes += (serializableLocation.type.length + serializableLocation.part.type.length + serializableLocation.part.path.length) * 6;
329
+ estimatedBytes += serializableLocation.xmlPath.length * 24;
330
+ switch (serializableLocation.type) {
331
+ case "paragraph": break;
332
+ case "table-cell-paragraph":
333
+ estimatedBytes += (serializableLocation.tablePath.length + serializableLocation.rowPath.length + serializableLocation.cellPath.length) * 24;
334
+ break;
335
+ case "text-box-paragraph":
336
+ estimatedBytes += serializableLocation.textBoxPath.length * 24;
337
+ break;
148
338
  }
149
339
  serializableRewrites.push({
150
340
  location: serializableLocation,
151
- expectedText: typeof rewrite.expectedText === "string" ? rewrite.expectedText : null,
152
- replacements: serializableReplacements
341
+ expectedText: rewrite.expectedText,
342
+ replacements
153
343
  });
154
344
  if (estimatedBytes > 134217728) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`);
155
345
  }
@@ -237,7 +427,7 @@ const restoreDocxText = ({ document, session, expectedSessionId, observedAtEpoch
237
427
  if (planRestoration === void 0) throw restorationError(DOCX_RESTORATION_ERROR_CODES.invalidSession, "Native anonymize binding does not expose DOCX restoration planning");
238
428
  let plan;
239
429
  try {
240
- plan = JSON.parse(planRestoration(document, sessionId));
430
+ plan = decodeDocxRestorationPlan(planRestoration(document, sessionId));
241
431
  } catch (error) {
242
432
  const message = error instanceof Error ? error.message : String(error);
243
433
  const separator = message.indexOf(": ");
@@ -293,7 +483,7 @@ const docxLocationsEqual = (left, right) => {
293
483
  const docxLocationKey = ({ blockIndex, part }) => `${part.path}\0${blockIndex}`;
294
484
  //#endregion
295
485
  //#region src/anonymize.ts
296
- const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1e6;
486
+ const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = CALLER_DETECTION_MAX_COUNT;
297
487
  var DocxAnonymizationError = class extends Error {
298
488
  code;
299
489
  constructor(code, message) {
@@ -312,7 +502,7 @@ const planCallerDetections = (extractionBlocks, inputs) => {
312
502
  if (detectionsByLocation.has(key)) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, "Each DOCX block may have only one caller-detection input");
313
503
  const block = blocksByLocation.get(key);
314
504
  if (block === void 0 || !docxLocationsEqual(block.location, input.location) || block.text !== input.expectedText) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, "DOCX caller-detection location or expected text no longer matches");
315
- if (input.detections.length > 1e6 - callerDetectionCount) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`);
505
+ if (input.detections.length > DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`);
316
506
  detectionsByLocation.set(key, input);
317
507
  callerDetectionCount += input.detections.length;
318
508
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/extract.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/location.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n unsupportedDocument: \"unsupported-document\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n} from \"./types\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\nconst nativeExtractionErrorCode = (\n message: string,\n): DocxExtractionErrorCode => {\n if (message.includes(\"unsafe entry path\")) {\n return DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath;\n }\n if (message.includes(\"valid bounded DOCX ZIP archive\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidArchive;\n }\n if (message.includes(\"valid XML\") || message.includes(\"valid UTF-8\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidXml;\n }\n if (\n message.includes(\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n )\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded;\n }\n if (\n message.includes(\"must not exceed\") ||\n message.includes(\"must not contain more than\") ||\n message.includes(\"at most\")\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded;\n }\n return DOCX_EXTRACTION_ERROR_CODES.invalidPackage;\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const extract = loadNativeAnonymizeBinding().extractDocxTextJson;\n if (extract === undefined) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidPackage,\n \"Native anonymize binding does not expose DOCX extraction\",\n );\n }\n try {\n return JSON.parse(extract(archive)) as DocxExtraction;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"DOCX extraction failed\";\n throw new DocxExtractionError(nativeExtractionErrorCode(message), message);\n }\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxExtractionErrorCode,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n} from \"./types\";\nimport {\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n DocxExtractionError,\n} from \"./extract\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\nconst REWRITE_ERROR_CODES = new Set<DocxRewriteErrorCode>(\n Object.values(DOCX_REWRITE_ERROR_CODES),\n);\nconst EXTRACTION_ERROR_CODES = new Set<DocxExtractionErrorCode>(\n Object.values(DOCX_EXTRACTION_ERROR_CODES),\n);\nconst DOCX_REWRITE_MAX_BLOCKS = 100_000;\nconst DOCX_REWRITE_MAX_REPLACEMENTS = 1_000_000;\nconst LOCATION_PATH_KEYS = [\n \"xmlPath\",\n \"tablePath\",\n \"rowPath\",\n \"cellPath\",\n \"textBoxPath\",\n] as const;\n\nconst preflightRewritePlan = (\n rewrites: readonly DocxBlockRewrite[],\n): readonly unknown[] => {\n const rewriteCount = rewrites.length;\n if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`,\n );\n }\n let replacementCount = 0;\n let estimatedBytes = rewriteCount * 256;\n const serializableRewrites: unknown[] = [];\n for (let rewriteIndex = 0; rewriteIndex < rewriteCount; rewriteIndex += 1) {\n const rewrite = rewrites[rewriteIndex];\n if (rewrite === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse blocks\",\n );\n }\n if (!Array.isArray(rewrite.replacements)) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite replacements must be an array\",\n );\n }\n const blockReplacementCount = rewrite.replacements.length;\n replacementCount += blockReplacementCount;\n if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`,\n );\n }\n estimatedBytes +=\n (typeof rewrite.expectedText === \"string\"\n ? rewrite.expectedText.length * 6\n : 0) +\n blockReplacementCount * 96;\n const serializableReplacements: unknown[] = [];\n for (\n let replacementIndex = 0;\n replacementIndex < blockReplacementCount;\n replacementIndex += 1\n ) {\n const replacement = rewrite.replacements[replacementIndex];\n if (replacement === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse replacements\",\n );\n }\n const value = replacement.replacement;\n if (typeof value === \"string\") {\n estimatedBytes += value.length * 6;\n }\n serializableReplacements.push({\n start: typeof replacement.start === \"number\" ? replacement.start : null,\n end: typeof replacement.end === \"number\" ? replacement.end : null,\n replacement: typeof value === \"string\" ? value : null,\n });\n }\n const location = rewrite.location as unknown as Record<string, unknown>;\n const part = location[\"part\"] as Record<string, unknown> | undefined;\n for (const value of [location[\"type\"], part?.[\"type\"], part?.[\"path\"]]) {\n if (typeof value === \"string\") {\n estimatedBytes += value.length * 6;\n }\n }\n const serializableLocation: Record<string, unknown> = {\n type: typeof location[\"type\"] === \"string\" ? location[\"type\"] : null,\n part: {\n type: typeof part?.[\"type\"] === \"string\" ? part[\"type\"] : null,\n path: typeof part?.[\"path\"] === \"string\" ? part[\"path\"] : null,\n },\n blockIndex:\n typeof location[\"blockIndex\"] === \"number\"\n ? location[\"blockIndex\"]\n : null,\n };\n for (const key of LOCATION_PATH_KEYS) {\n const path = location[key];\n if (Array.isArray(path)) {\n if (path.length > DOCX_XML_MAX_DEPTH) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite location paths must not exceed ${DOCX_XML_MAX_DEPTH} entries`,\n );\n }\n estimatedBytes += path.length * 24;\n const serializablePath: Array<number | null> = [];\n for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {\n const value = path[pathIndex];\n serializablePath.push(typeof value === \"number\" ? value : null);\n }\n serializableLocation[key] = serializablePath;\n }\n }\n serializableRewrites.push({\n location: serializableLocation,\n expectedText:\n typeof rewrite.expectedText === \"string\" ? rewrite.expectedText : null,\n replacements: serializableReplacements,\n });\n if (estimatedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`,\n );\n }\n }\n return serializableRewrites;\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const rewrite = loadNativeAnonymizeBinding().rewriteDocxTextNative;\n if (rewrite === undefined) {\n throw new Error(\n \"The native anonymize binding does not expose DOCX rewriting\",\n );\n }\n let serializableRewrites: readonly unknown[];\n try {\n serializableRewrites = preflightRewritePlan(rewrites);\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is invalid: ${message}`,\n );\n }\n let rewritesJson: string;\n try {\n rewritesJson = JSON.stringify(serializableRewrites);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is not serializable: ${message}`,\n );\n }\n try {\n return rewrite(archive, rewritesJson);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const rawCode = message.slice(0, separator);\n const extractionCode = rawCode as DocxExtractionErrorCode;\n if (separator > 0 && EXTRACTION_ERROR_CODES.has(extractionCode)) {\n throw new DocxExtractionError(\n extractionCode,\n message.slice(separator + 2),\n );\n }\n const code = rawCode as DocxRewriteErrorCode;\n if (separator > 0 && REWRITE_ERROR_CODES.has(code)) {\n throw new DocxRewriteError(code, message.slice(separator + 2));\n }\n throw error;\n }\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\ntype NativeRestorationPlan = {\n extraction: {\n coverage: Parameters<typeof docxWorkflowCoverage>[0];\n };\n blocks: readonly {\n location: DocxBlockRewrite[\"location\"];\n expectedText: string;\n candidates: readonly {\n start: number;\n end: number;\n candidate: string;\n }[];\n }[];\n};\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const planRestoration = loadNativeAnonymizeBinding().planDocxRestorationJson;\n if (planRestoration === undefined) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"Native anonymize binding does not expose DOCX restoration planning\",\n );\n }\n let plan: NativeRestorationPlan;\n try {\n plan = JSON.parse(\n planRestoration(document, sessionId),\n ) as NativeRestorationPlan;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const code = message.slice(0, separator) as DocxRestorationErrorCode;\n const knownCodes = new Set<DocxRestorationErrorCode>([\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n DOCX_RESTORATION_ERROR_CODES.unsupportedDocument,\n ]);\n if (separator > 0 && knownCodes.has(code)) {\n throw restorationError(code, message.slice(separator + 2));\n }\n throw error;\n }\n const rewrites: DocxBlockRewrite[] = [];\n let restoredPlaceholderCount = 0;\n for (const block of plan.blocks) {\n const replacements: DocxTextReplacement[] = block.candidates.map(\n ({ candidate, end, start }) => {\n const replacement = restoreCandidate(candidate);\n if (replacement === candidate) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n return { start, end, replacement };\n },\n );\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.expectedText,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(plan.extraction.coverage),\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1_000_000;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":";;AAOA,MAAa,kBAAkB;CAC7B,UAAU;CACV,UAAU;CACV,QAAQ;CACR,WAAW;CACX,QAAQ;CACR,cAAc;AAChB;AAsGA,MAAa,sBAAsB;CACjC,cAAc;CACd,aAAa;AACf;AA0EA,MAAa,iCAAiC;CAC5C,oBAAoB;CACpB,yBAAyB;CACzB,iBAAiB;AACnB;AAyBA,MAAa,+BAA+B;CAC1C,oBAAoB;CACpB,gBAAgB;CAChB,0BAA0B;CAC1B,iBAAiB;CACjB,qBAAqB;AACvB;AAKA,MAAa,2BAA2B;CACtC,oBAAoB;CACpB,sBAAsB;CACtB,iBAAiB;CACjB,wBAAwB;AAC1B;AAKA,MAAa,8BAA8B;CACzC,sBAAsB;CACtB,gBAAgB;CAChB,gBAAgB;CAChB,YAAY;CACZ,iBAAiB;CACjB,2BAA2B;AAC7B;;;AClPA,MAAa,mCAAmC;AAChD,MAAa,yBAAyB,KAAK,OAAO;AAClD,MAAa,uBAAuB,KAAK,OAAO;AAChD,MAAa,8BAA8B,MAAM,OAAO;AACxD,MAAa,qBAAqB;AAElC,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CAEA,YAAY,MAA+B,SAAiB;EAC1D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,6BACJ,YAC4B;CAC5B,IAAI,QAAQ,SAAS,mBAAmB,GACtC,OAAO,4BAA4B;CAErC,IAAI,QAAQ,SAAS,gCAAgC,GACnD,OAAO,4BAA4B;CAErC,IAAI,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,aAAa,GACjE,OAAO,4BAA4B;CAErC,IACE,QAAQ,SACN,8CACF,GAEA,OAAO,4BAA4B;CAErC,IACE,QAAQ,SAAS,iBAAiB,KAClC,QAAQ,SAAS,4BAA4B,KAC7C,QAAQ,SAAS,SAAS,GAE1B,OAAO,4BAA4B;CAErC,OAAO,4BAA4B;AACrC;AAEA,MAAa,mBAAmB,YAAwC;CACtE,MAAM,UAAU,2BAA2B,CAAC,CAAC;CAC7C,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,oBACR,4BAA4B,gBAC5B,0DACF;CAEF,IAAI;EACF,OAAO,KAAK,MAAM,QAAQ,OAAO,CAAC;CACpC,SAAS,OAAO;EACd,MAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;EAC3C,MAAM,IAAI,oBAAoB,0BAA0B,OAAO,GAAG,OAAO;CAC3E;AACF;;;ACpDA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CAEA,YAAY,MAA4B,SAAiB;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,sBAAsB,IAAI,IAC9B,OAAO,OAAO,wBAAwB,CACxC;AACA,MAAM,yBAAyB,IAAI,IACjC,OAAO,OAAO,2BAA2B,CAC3C;AACA,MAAM,0BAA0B;AAChC,MAAM,gCAAgC;AACtC,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,wBACJ,aACuB;CACvB,MAAM,eAAe,SAAS;CAC9B,IAAI,eAAe,yBACjB,MAAM,IAAI,iBACR,yBAAyB,sBACzB,4CAA4C,wBAAwB,QACtE;CAEF,IAAI,mBAAmB;CACvB,IAAI,iBAAiB,eAAe;CACpC,MAAM,uBAAkC,CAAC;CACzC,KAAK,IAAI,eAAe,GAAG,eAAe,cAAc,gBAAgB,GAAG;EACzE,MAAM,UAAU,SAAS;EACzB,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,iBACR,yBAAyB,oBACzB,mDACF;EAEF,IAAI,CAAC,MAAM,QAAQ,QAAQ,YAAY,GACrC,MAAM,IAAI,iBACR,yBAAyB,oBACzB,kDACF;EAEF,MAAM,wBAAwB,QAAQ,aAAa;EACnD,oBAAoB;EACpB,IAAI,mBAAmB,+BACrB,MAAM,IAAI,iBACR,yBAAyB,sBACzB,4CAA4C,8BAA8B,cAC5E;EAEF,mBACG,OAAO,QAAQ,iBAAiB,WAC7B,QAAQ,aAAa,SAAS,IAC9B,KACJ,wBAAwB;EAC1B,MAAM,2BAAsC,CAAC;EAC7C,KACE,IAAI,mBAAmB,GACvB,mBAAmB,uBACnB,oBAAoB,GACpB;GACA,MAAM,cAAc,QAAQ,aAAa;GACzC,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,iBACR,yBAAyB,oBACzB,yDACF;GAEF,MAAM,QAAQ,YAAY;GAC1B,IAAI,OAAO,UAAU,UACnB,kBAAkB,MAAM,SAAS;GAEnC,yBAAyB,KAAK;IAC5B,OAAO,OAAO,YAAY,UAAU,WAAW,YAAY,QAAQ;IACnE,KAAK,OAAO,YAAY,QAAQ,WAAW,YAAY,MAAM;IAC7D,aAAa,OAAO,UAAU,WAAW,QAAQ;GACnD,CAAC;EACH;EACA,MAAM,WAAW,QAAQ;EACzB,MAAM,OAAO,SAAS;EACtB,KAAK,MAAM,SAAS;GAAC,SAAS;GAAS,OAAO;GAAS,OAAO;EAAO,GACnE,IAAI,OAAO,UAAU,UACnB,kBAAkB,MAAM,SAAS;EAGrC,MAAM,uBAAgD;GACpD,MAAM,OAAO,SAAS,YAAY,WAAW,SAAS,UAAU;GAChE,MAAM;IACJ,MAAM,OAAO,OAAO,YAAY,WAAW,KAAK,UAAU;IAC1D,MAAM,OAAO,OAAO,YAAY,WAAW,KAAK,UAAU;GAC5D;GACA,YACE,OAAO,SAAS,kBAAkB,WAC9B,SAAS,gBACT;EACR;EACA,KAAK,MAAM,OAAO,oBAAoB;GACpC,MAAM,OAAO,SAAS;GACtB,IAAI,MAAM,QAAQ,IAAI,GAAG;IACvB,IAAI,KAAK,SAAA,KACP,MAAM,IAAI,iBACR,yBAAyB,oBACzB,yDACF;IAEF,kBAAkB,KAAK,SAAS;IAChC,MAAM,mBAAyC,CAAC;IAChD,KAAK,IAAI,YAAY,GAAG,YAAY,KAAK,QAAQ,aAAa,GAAG;KAC/D,MAAM,QAAQ,KAAK;KACnB,iBAAiB,KAAK,OAAO,UAAU,WAAW,QAAQ,IAAI;IAChE;IACA,qBAAqB,OAAO;GAC9B;EACF;EACA,qBAAqB,KAAK;GACxB,UAAU;GACV,cACE,OAAO,QAAQ,iBAAiB,WAAW,QAAQ,eAAe;GACpE,cAAc;EAChB,CAAC;EACD,IAAI,iBAAA,WACF,MAAM,IAAI,iBACR,yBAAyB,sBACzB,sCAAsC,4BAA4B,4BACpE;CAEJ;CACA,OAAO;AACT;AAEA,MAAa,mBACX,SACA,aACsB;CACtB,MAAM,UAAU,2BAA2B,CAAC,CAAC;CAC7C,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MACR,6DACF;CAEF,IAAI;CACJ,IAAI;EACF,uBAAuB,qBAAqB,QAAQ;CACtD,SAAS,OAAO;EACd,IAAI,iBAAiB,kBACnB,MAAM;EAER,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,IAAI,iBACR,yBAAyB,oBACzB,iCAAiC,SACnC;CACF;CACA,IAAI;CACJ,IAAI;EACF,eAAe,KAAK,UAAU,oBAAoB;CACpD,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,IAAI,iBACR,yBAAyB,oBACzB,0CAA0C,SAC5C;CACF;CACA,IAAI;EACF,OAAO,QAAQ,SAAS,YAAY;CACtC,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,YAAY,QAAQ,QAAQ,IAAI;EACtC,MAAM,UAAU,QAAQ,MAAM,GAAG,SAAS;EAC1C,MAAM,iBAAiB;EACvB,IAAI,YAAY,KAAK,uBAAuB,IAAI,cAAc,GAC5D,MAAM,IAAI,oBACR,gBACA,QAAQ,MAAM,YAAY,CAAC,CAC7B;EAEF,MAAM,OAAO;EACb,IAAI,YAAY,KAAK,oBAAoB,IAAI,IAAI,GAC/C,MAAM,IAAI,iBAAiB,MAAM,QAAQ,MAAM,YAAY,CAAC,CAAC;EAE/D,MAAM;CACR;AACF;;;AC/MA,MAAM,sBAAsB,aAC1B,SAAS,MAAM,MAAM,EAAE,aAAa,WAAW,aAAa,KAC5D,SAAS,4BAA4B,KACrC,SAAS,2BAA2B,KACpC,SAAS,mCAAmC,KAC5C,SAAS,yBAAyB,KAClC,SAAS,mCAAmC;AAE9C,MAAa,wBACX,aACyB;CACzB,MAAM,SAAS;EACb,oBAAoB,SAAS,MAAM,QAChC,EAAE,aAAa,WAAW,WAC7B,CAAC,CAAC;EACF,sBAAsB,SAAS,MAAM,QAClC,EAAE,aAAa,WAAW,aAC7B,CAAC,CAAC;EACF,2BAA2B,SAAS;EACpC,0BAA0B,SAAS;EACnC,kCAAkC,SAAS;EAC3C,wBAAwB,SAAS;EACjC,kCAAkC,SAAS;CAC7C;CACA,OAAO,mBAAmB,QAAQ,IAC9B;EAAE,QAAQ;EAAW;CAAO,IAC5B;EAAE,QAAQ;EAAQ;CAAO;AAC/B;;;AChBA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAgC,SAAiB;EAC3D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,oBACJ,MACA,YACyB,IAAI,qBAAqB,MAAM,OAAO;AAiBjE,MAAa,mBAAmB,EAC9B,UACA,SACA,mBACA,6BACmD;CACnD,MAAM,YAAY,QAAQ,UAAU;CACpC,IAAI,cAAc,mBAChB,MAAM,iBACJ,6BAA6B,iBAC7B,iEACF;CAGF,MAAM,+BAAqC;EACzC,IAAI,QAAQ,YAAY,IAAI,sBAAsB,MAAM,IACtD,MAAM,iBACJ,6BAA6B,gBAC7B,kEACF;CAEJ;CACA,uBAAuB;CACvB,MAAM,qCAAqB,IAAI,IAAoB;CACnD,MAAM,oBAAoB,cAA8B;EACtD,MAAM,SAAS,mBAAmB,IAAI,SAAS;EAC/C,IAAI,WAAW,KAAA,GACb,OAAO;EAET,MAAM,WAAW,QAAQ,YAAY,WAAW,sBAAsB;EACtE,mBAAmB,IAAI,WAAW,QAAQ;EAC1C,OAAO;CACT;CACA,MAAM,kBAAkB,2BAA2B,CAAC,CAAC;CACrD,IAAI,oBAAoB,KAAA,GACtB,MAAM,iBACJ,6BAA6B,gBAC7B,oEACF;CAEF,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MACV,gBAAgB,UAAU,SAAS,CACrC;CACF,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,YAAY,QAAQ,QAAQ,IAAI;EACtC,MAAM,OAAO,QAAQ,MAAM,GAAG,SAAS;EACvC,MAAM,6BAAa,IAAI,IAA8B;GACnD,6BAA6B;GAC7B,6BAA6B;GAC7B,6BAA6B;EAC/B,CAAC;EACD,IAAI,YAAY,KAAK,WAAW,IAAI,IAAI,GACtC,MAAM,iBAAiB,MAAM,QAAQ,MAAM,YAAY,CAAC,CAAC;EAE3D,MAAM;CACR;CACA,MAAM,WAA+B,CAAC;CACtC,IAAI,2BAA2B;CAC/B,KAAK,MAAM,SAAS,KAAK,QAAQ;EAC/B,MAAM,eAAsC,MAAM,WAAW,KAC1D,EAAE,WAAW,KAAK,YAAY;GAC7B,MAAM,cAAc,iBAAiB,SAAS;GAC9C,IAAI,gBAAgB,WAClB,MAAM,iBACJ,6BAA6B,oBAC7B,oEACF;GAEF,OAAO;IAAE;IAAO;IAAK;GAAY;EACnC,CACF;EACA,IAAI,aAAa,WAAW,GAC1B;EAEF,4BAA4B,aAAa;EACzC,SAAS,KAAK;GACZ,UAAU,MAAM;GAChB,cAAc,MAAM;GACpB;EACF,CAAC;CACH;CACA,uBAAuB;CACvB,MAAM,WAAW,gBAAgB,UAAU,QAAQ;CACnD,OAAO;EACL,UAAU,SAAS;EACnB;EACA,oBAAoB,SAAS;EAC7B;EACA,UAAU,qBAAqB,KAAK,WAAW,QAAQ;CACzD;AACF;;;ACtIA,MAAM,eACJ,MACA,UAEA,KAAK,WAAW,MAAM,UACtB,KAAK,OAAO,OAAO,UAAU,UAAU,MAAM,GAAG,KAAK,CAAC;AAExD,MAAa,sBACX,MACA,UACY;CACZ,IACE,KAAK,SAAS,MAAM,QACpB,KAAK,KAAK,SAAS,MAAM,KAAK,QAC9B,KAAK,KAAK,SAAS,MAAM,KAAK,QAC9B,KAAK,eAAe,MAAM,cAC1B,CAAC,YAAY,KAAK,SAAS,MAAM,OAAO,GAExC,OAAO;CAET,IAAI,KAAK,SAAS,eAAe,MAAM,SAAS,aAC9C,OAAO;CAET,IACE,KAAK,SAAS,0BACd,MAAM,SAAS,wBAEf,OACE,YAAY,KAAK,WAAW,MAAM,SAAS,KAC3C,YAAY,KAAK,SAAS,MAAM,OAAO,KACvC,YAAY,KAAK,UAAU,MAAM,QAAQ;CAG7C,IACE,KAAK,SAAS,wBACd,MAAM,SAAS,sBAEf,OAAO,YAAY,KAAK,aAAa,MAAM,WAAW;CAExD,OAAO;AACT;AAEA,MAAa,mBAAmB,EAC9B,YACA,WAC+B,GAAG,KAAK,KAAK,IAAI;;;ACjClD,MAAa,2CAA2C;AAExD,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YAAY,MAAkC,SAAiB;EAC7D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,MAAM,sBACJ,MACA,YAC2B,IAAI,uBAAuB,MAAM,OAAO;AAOrE,MAAM,wBACJ,kBACA,WACkB;CAClB,MAAM,mBAAmB,IAAI,IAC3B,iBAAiB,KAAK,UAAU,CAAC,gBAAgB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAC1E;CACA,MAAM,uCAAuB,IAAI,IAAuC;CACxE,IAAI,uBAAuB;CAC3B,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,gBAAgB,MAAM,QAAQ;EAC1C,IAAI,qBAAqB,IAAI,GAAG,GAC9B,MAAM,mBACJ,+BAA+B,yBAC/B,0DACF;EAEF,MAAM,QAAQ,iBAAiB,IAAI,GAAG;EACtC,IACE,UAAU,KAAA,KACV,CAAC,mBAAmB,MAAM,UAAU,MAAM,QAAQ,KAClD,MAAM,SAAS,MAAM,cAErB,MAAM,mBACJ,+BAA+B,yBAC/B,mEACF;EAEF,IACE,MAAM,WAAW,SAAA,MAC0B,sBAE3C,MAAM,mBACJ,+BAA+B,yBAC/B,6CAA6C,yCAAyC,mBACxF;EAEF,qBAAqB,IAAI,KAAK,KAAK;EACnC,wBAAwB,MAAM,WAAW;CAC3C;CACA,OAAO;EAAE;EAAsB;CAAqB;AACtD;AAEA,MAAa,iBAAiB,EAC5B,UACA,SACA,mBACA,QACA,mBAAmB,CAAC,GACpB,6BACmD;CACnD,MAAM,YAAY,QAAQ,UAAU;CACpC,IAAI,cAAc,mBAChB,MAAM,mBACJ,+BAA+B,iBAC/B,gEACF;CAGF,MAAM,aAAa,gBAAgB,QAAQ;CAC3C,MAAM,WAAW,qBAAqB,WAAW,QAAQ;CACzD,IACE,SAAS,WAAW,aACpB,OAAO,SAAS,SAAS,oBAAoB,aAE7C,MAAM,mBACJ,+BAA+B,oBAC/B,0EACF;CAGF,MAAM,EAAE,sBAAsB,yBAAyB,qBACrD,WAAW,QACX,gBACF;CACA,MAAM,OAAO,QAAQ,kCAAkC;EACrD,QAAQ,WAAW,OAAO,KAAK,WAAW;GACxC,UAAU,MAAM;GAChB,YACE,qBAAqB,IAAI,gBAAgB,MAAM,QAAQ,CAAC,CAAC,EAAE,cAC3D,CAAC;EACL,EAAE;EACF,GAAI,OAAO,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,OAAO,UAAU;EACxE,GAAI,2BAA2B,KAAA,IAAY,CAAC,IAAI,EAAE,uBAAuB;CAC3E,CAAC;CACD,IAAI,KAAK,OAAO,WAAW,WAAW,OAAO,QAC3C,MAAM,mBACJ,+BAA+B,yBAC/B,sEACF;CAGF,MAAM,WAA+B,CAAC;CACtC,IAAI,cAAc;CAClB,IAAI,+BAA+B;CACnC,KAAK,MAAM,CAAC,OAAO,UAAU,WAAW,OAAO,QAAQ,GAAG;EACxD,MAAM,YAAY,KAAK,OAAO,GAAG,KAAK;EACtC,IAAI,cAAc,KAAA,GAChB,MAAM,mBACJ,+BAA+B,yBAC/B,2DACF;EAEF,eAAe,UAAU;EACzB,gCAAgC,UAAU;EAC1C,IAAI,UAAU,aAAa,WAAW,GACpC;EAEF,SAAS,KAAK;GACZ,UAAU,MAAM;GAChB,cAAc,MAAM;GACpB,cAAc,UAAU;EAC1B,CAAC;CACH;CAEA,MAAM,YAAY,gBAAgB,UAAU,QAAQ;CACpD,KAAK,OAAO;CACZ,OAAO;EACL,UAAU,UAAU;EACpB,SAAS;GACP,iBAAiB;GACjB;GACA,YAAY,WAAW,OAAO;GAC9B,qBAAqB,UAAU;GAC/B,yBAAyB,UAAU;GACnC;GACA;GACA;GACA;EACF;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/native-codec.ts","../src/extract.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/location.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n unsupportedDocument: \"unsupported-document\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import {\n DOCX_PART_TYPES,\n type DocxBlockLocation,\n type DocxBlockRewrite,\n type DocxCoverage,\n type DocxCoverageItem,\n type DocxExtraction,\n type DocxInlineContext,\n type DocxPart,\n type DocxTextBlock,\n type DocxTextSegment,\n} from \"./types\";\n\ntype RequiredFields<T> = { [Key in keyof T]-?: true };\n\nconst PART_FIELDS = {\n type: true,\n path: true,\n} as const satisfies RequiredFields<DocxPart>;\nconst BASE_LOCATION_FIELDS = {\n type: true,\n part: true,\n blockIndex: true,\n xmlPath: true,\n} as const;\nconst PARAGRAPH_LOCATION_FIELDS = BASE_LOCATION_FIELDS satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"paragraph\" }>\n>;\nconst TABLE_LOCATION_FIELDS = {\n ...BASE_LOCATION_FIELDS,\n tablePath: true,\n rowPath: true,\n cellPath: true,\n} as const satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"table-cell-paragraph\" }>\n>;\nconst TEXT_BOX_LOCATION_FIELDS = {\n ...BASE_LOCATION_FIELDS,\n textBoxPath: true,\n} as const satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"text-box-paragraph\" }>\n>;\nconst HYPERLINK_CONTEXT_FIELDS = {\n type: true,\n relationshipId: true,\n anchor: true,\n} as const satisfies RequiredFields<\n Extract<DocxInlineContext, { type: \"hyperlink\" }>\n>;\nconst REVISION_CONTEXT_FIELDS = {\n type: true,\n revision: true,\n} as const satisfies RequiredFields<\n Extract<DocxInlineContext, { type: \"revision\" }>\n>;\nconst SEGMENT_FIELDS = {\n start: true,\n end: true,\n source: true,\n contexts: true,\n xmlPath: true,\n} as const satisfies RequiredFields<DocxTextSegment>;\nconst BLOCK_FIELDS = {\n text: true,\n location: true,\n segments: true,\n} as const satisfies RequiredFields<DocxTextBlock>;\nconst EXTRACTED_COVERAGE_FIELDS = {\n status: true,\n part: true,\n blockCount: true,\n} as const satisfies RequiredFields<\n Extract<DocxCoverageItem, { status: \"extracted\" }>\n>;\nconst UNSUPPORTED_COVERAGE_FIELDS = {\n status: true,\n path: true,\n contentType: true,\n reason: true,\n} as const satisfies RequiredFields<\n Extract<DocxCoverageItem, { status: \"unsupported\" }>\n>;\nconst COVERAGE_FIELDS = {\n parts: true,\n hyperlinkTextSegmentCount: true,\n revisionTextSegmentCount: true,\n unsupportedAlternateContentCount: true,\n unsupportedSymbolCount: true,\n unsupportedFieldInstructionCount: true,\n} as const satisfies RequiredFields<DocxCoverage>;\nconst EXTRACTION_FIELDS = {\n contractVersion: true,\n blocks: true,\n coverage: true,\n} as const satisfies RequiredFields<DocxExtraction>;\n\nexport type DocxRestorationCandidate = {\n start: number;\n end: number;\n candidate: string;\n};\n\nexport type NativeDocxRestorationPlan = {\n extraction: DocxExtraction;\n blocks: readonly {\n location: DocxBlockRewrite[\"location\"];\n expectedText: string;\n candidates: readonly DocxRestorationCandidate[];\n }[];\n candidateCount: number;\n};\n\nconst RESTORATION_CANDIDATE_FIELDS = {\n start: true,\n end: true,\n candidate: true,\n} as const satisfies RequiredFields<DocxRestorationCandidate>;\nconst RESTORATION_BLOCK_FIELDS = {\n location: true,\n expectedText: true,\n candidates: true,\n} as const satisfies RequiredFields<\n NativeDocxRestorationPlan[\"blocks\"][number]\n>;\nconst RESTORATION_PLAN_FIELDS = {\n extraction: true,\n blocks: true,\n candidateCount: true,\n} as const satisfies RequiredFields<NativeDocxRestorationPlan>;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst hasExactFields = (\n value: Record<string, unknown>,\n fields: Record<string, true>,\n): boolean => {\n const keys = Object.keys(value);\n return (\n keys.length === Object.keys(fields).length &&\n keys.every((key) => Object.hasOwn(fields, key))\n );\n};\n\nconst isNonNegativeInteger = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n\nconst isPath = (value: unknown): value is readonly number[] =>\n Array.isArray(value) && value.every(isNonNegativeInteger);\n\nconst isPart = (value: unknown): value is DocxPart =>\n isRecord(value) &&\n hasExactFields(value, PART_FIELDS) &&\n Object.values(DOCX_PART_TYPES).some(\n (partType) => partType === value[\"type\"],\n ) &&\n typeof value[\"path\"] === \"string\";\n\nconst hasBaseLocation = (value: Record<string, unknown>): boolean =>\n isPart(value[\"part\"]) &&\n isNonNegativeInteger(value[\"blockIndex\"]) &&\n isPath(value[\"xmlPath\"]);\n\nconst isLocation = (value: unknown): value is DocxBlockLocation => {\n if (!isRecord(value) || !hasBaseLocation(value)) {\n return false;\n }\n switch (value[\"type\"]) {\n case \"paragraph\":\n return hasExactFields(value, PARAGRAPH_LOCATION_FIELDS);\n case \"table-cell-paragraph\":\n return (\n hasExactFields(value, TABLE_LOCATION_FIELDS) &&\n isPath(value[\"tablePath\"]) &&\n isPath(value[\"rowPath\"]) &&\n isPath(value[\"cellPath\"])\n );\n case \"text-box-paragraph\":\n return (\n hasExactFields(value, TEXT_BOX_LOCATION_FIELDS) &&\n isPath(value[\"textBoxPath\"])\n );\n default:\n return false;\n }\n};\n\nconst isNullableString = (value: unknown): value is string | null =>\n value === null || typeof value === \"string\";\n\nconst isInlineContext = (value: unknown): value is DocxInlineContext => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value[\"type\"]) {\n case \"hyperlink\":\n return (\n hasExactFields(value, HYPERLINK_CONTEXT_FIELDS) &&\n isNullableString(value[\"relationshipId\"]) &&\n isNullableString(value[\"anchor\"])\n );\n case \"revision\":\n return (\n hasExactFields(value, REVISION_CONTEXT_FIELDS) &&\n (value[\"revision\"] === \"deletion\" ||\n value[\"revision\"] === \"insertion\" ||\n value[\"revision\"] === \"move-from\" ||\n value[\"revision\"] === \"move-to\")\n );\n default:\n return false;\n }\n};\n\nconst isSegment = (value: unknown): value is DocxTextSegment =>\n isRecord(value) &&\n hasExactFields(value, SEGMENT_FIELDS) &&\n isNonNegativeInteger(value[\"start\"]) &&\n isNonNegativeInteger(value[\"end\"]) &&\n value[\"end\"] >= value[\"start\"] &&\n (value[\"source\"] === \"break\" ||\n value[\"source\"] === \"tab\" ||\n value[\"source\"] === \"text\") &&\n Array.isArray(value[\"contexts\"]) &&\n value[\"contexts\"].every(isInlineContext) &&\n isPath(value[\"xmlPath\"]);\n\nconst isBlock = (value: unknown): value is DocxTextBlock =>\n isRecord(value) &&\n hasExactFields(value, BLOCK_FIELDS) &&\n typeof value[\"text\"] === \"string\" &&\n isLocation(value[\"location\"]) &&\n Array.isArray(value[\"segments\"]) &&\n value[\"segments\"].every(isSegment);\n\nconst isCoverageItem = (value: unknown): value is DocxCoverageItem => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value[\"status\"]) {\n case \"extracted\":\n return (\n hasExactFields(value, EXTRACTED_COVERAGE_FIELDS) &&\n isPart(value[\"part\"]) &&\n isNonNegativeInteger(value[\"blockCount\"])\n );\n case \"unsupported\":\n return (\n hasExactFields(value, UNSUPPORTED_COVERAGE_FIELDS) &&\n typeof value[\"path\"] === \"string\" &&\n typeof value[\"contentType\"] === \"string\" &&\n typeof value[\"reason\"] === \"string\"\n );\n default:\n return false;\n }\n};\n\nconst isCoverage = (value: unknown): value is DocxCoverage =>\n isRecord(value) &&\n hasExactFields(value, COVERAGE_FIELDS) &&\n Array.isArray(value[\"parts\"]) &&\n value[\"parts\"].every(isCoverageItem) &&\n isNonNegativeInteger(value[\"hyperlinkTextSegmentCount\"]) &&\n isNonNegativeInteger(value[\"revisionTextSegmentCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedAlternateContentCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedSymbolCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedFieldInstructionCount\"]);\n\nconst isExtraction = (value: unknown): value is DocxExtraction =>\n isRecord(value) &&\n hasExactFields(value, EXTRACTION_FIELDS) &&\n value[\"contractVersion\"] === 1 &&\n Array.isArray(value[\"blocks\"]) &&\n value[\"blocks\"].every(isBlock) &&\n isCoverage(value[\"coverage\"]);\n\nconst isRestorationCandidate = (\n value: unknown,\n): value is DocxRestorationCandidate =>\n isRecord(value) &&\n hasExactFields(value, RESTORATION_CANDIDATE_FIELDS) &&\n isNonNegativeInteger(value[\"start\"]) &&\n isNonNegativeInteger(value[\"end\"]) &&\n value[\"end\"] >= value[\"start\"] &&\n typeof value[\"candidate\"] === \"string\";\n\nconst isRestorationPlan = (\n value: unknown,\n): value is NativeDocxRestorationPlan =>\n isRecord(value) &&\n hasExactFields(value, RESTORATION_PLAN_FIELDS) &&\n isExtraction(value[\"extraction\"]) &&\n Array.isArray(value[\"blocks\"]) &&\n value[\"blocks\"].every(\n (block) =>\n isRecord(block) &&\n hasExactFields(block, RESTORATION_BLOCK_FIELDS) &&\n isLocation(block[\"location\"]) &&\n typeof block[\"expectedText\"] === \"string\" &&\n Array.isArray(block[\"candidates\"]) &&\n block[\"candidates\"].every(isRestorationCandidate),\n ) &&\n isNonNegativeInteger(value[\"candidateCount\"]);\n\nexport const decodeDocxExtraction = (json: string): DocxExtraction => {\n const value: unknown = JSON.parse(json);\n if (!isExtraction(value)) {\n throw new Error(\"Native DOCX extraction does not match contract version 1\");\n }\n return value;\n};\n\nexport const decodeDocxRestorationPlan = (\n json: string,\n): NativeDocxRestorationPlan => {\n const value: unknown = JSON.parse(json);\n if (!isRestorationPlan(value)) {\n throw new Error(\n \"Native DOCX restoration plan does not match contract version 1\",\n );\n }\n return value;\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n} from \"./types\";\nimport { decodeDocxExtraction } from \"./native-codec\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\nconst nativeExtractionErrorCode = (\n message: string,\n): DocxExtractionErrorCode => {\n if (message.includes(\"unsafe entry path\")) {\n return DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath;\n }\n if (message.includes(\"valid bounded DOCX ZIP archive\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidArchive;\n }\n if (message.includes(\"valid XML\") || message.includes(\"valid UTF-8\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidXml;\n }\n if (\n message.includes(\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n )\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded;\n }\n if (\n message.includes(\"must not exceed\") ||\n message.includes(\"must not contain more than\") ||\n message.includes(\"at most\")\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded;\n }\n return DOCX_EXTRACTION_ERROR_CODES.invalidPackage;\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const extract = loadNativeAnonymizeBinding().extractDocxTextJson;\n if (extract === undefined) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidPackage,\n \"Native anonymize binding does not expose DOCX extraction\",\n );\n }\n try {\n return decodeDocxExtraction(extract(archive));\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"DOCX extraction failed\";\n throw new DocxExtractionError(nativeExtractionErrorCode(message), message);\n }\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_PART_TYPES,\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxBlockLocation,\n type DocxExtractionErrorCode,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n} from \"./types\";\nimport {\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n DocxExtractionError,\n} from \"./extract\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\nconst REWRITE_ERROR_CODES = new Set<DocxRewriteErrorCode>(\n Object.values(DOCX_REWRITE_ERROR_CODES),\n);\nconst EXTRACTION_ERROR_CODES = new Set<DocxExtractionErrorCode>(\n Object.values(DOCX_EXTRACTION_ERROR_CODES),\n);\nconst DOCX_REWRITE_MAX_BLOCKS = 100_000;\nconst DOCX_REWRITE_MAX_REPLACEMENTS = 1_000_000;\n\nconst invalidLocation = (message: string): never => {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n message,\n );\n};\n\nconst ownDataRecord = (value: unknown, fields?: readonly string[]): object => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return invalidLocation(\"DOCX rewrite locations must be plain objects\");\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n return invalidLocation(\"DOCX rewrite locations must be plain objects\");\n }\n const keys = Reflect.ownKeys(value);\n if (\n keys.some((key) => typeof key !== \"string\") ||\n (fields !== undefined &&\n (keys.length !== fields.length ||\n keys.some((key) => typeof key !== \"string\" || !fields.includes(key))))\n ) {\n return invalidLocation(\n \"DOCX rewrite locations must contain exactly their declared fields\",\n );\n }\n for (const field of fields ?? keys) {\n if (typeof field !== \"string\") {\n return invalidLocation(\n \"DOCX rewrite locations must contain string fields\",\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(value, field);\n if (descriptor === undefined || !(\"value\" in descriptor)) {\n return invalidLocation(\n \"DOCX rewrite locations must contain own data properties\",\n );\n }\n }\n return value;\n};\n\nconst ownValue = (record: object, field: string): unknown =>\n Object.getOwnPropertyDescriptor(record, field)?.value;\n\nconst isDocxPartType = (\n value: unknown,\n): value is DocxBlockLocation[\"part\"][\"type\"] =>\n Object.values(DOCX_PART_TYPES).some((type) => type === value);\n\nconst copyPath = (path: unknown): number[] => {\n if (!Array.isArray(path)) {\n return invalidLocation(\"DOCX rewrite location paths must be arrays\");\n }\n if (path.length > DOCX_XML_MAX_DEPTH) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite location paths must not exceed ${DOCX_XML_MAX_DEPTH} entries`,\n );\n }\n const copy: number[] = [];\n for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {\n const descriptor = Object.getOwnPropertyDescriptor(path, pathIndex);\n const index = descriptor?.value;\n if (index === undefined || !Number.isSafeInteger(index) || index < 0) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite location paths must contain non-negative integers\",\n );\n }\n copy.push(index);\n }\n return copy;\n};\n\nconst copyLocation = (location: unknown): DocxBlockLocation => {\n const candidate = ownDataRecord(location);\n const type = ownValue(candidate, \"type\");\n const fields = [\"type\", \"part\", \"blockIndex\", \"xmlPath\"];\n if (type === \"table-cell-paragraph\") {\n fields.push(\"tablePath\", \"rowPath\", \"cellPath\");\n } else if (type === \"text-box-paragraph\") {\n fields.push(\"textBoxPath\");\n }\n if (Object.hasOwn(candidate, \"toJSON\")) {\n fields.push(\"toJSON\");\n }\n const base = ownDataRecord(location, fields);\n const partRecord = ownDataRecord(ownValue(base, \"part\"), [\"type\", \"path\"]);\n const partType = ownValue(partRecord, \"type\");\n const partPath = ownValue(partRecord, \"path\");\n const blockIndex = ownValue(base, \"blockIndex\");\n if (\n !isDocxPartType(partType) ||\n typeof partPath !== \"string\" ||\n !Number.isSafeInteger(blockIndex) ||\n typeof blockIndex !== \"number\" ||\n blockIndex < 0\n ) {\n return invalidLocation(\n \"DOCX rewrite locations must contain a known type, part, and block index\",\n );\n }\n const part = { type: partType, path: partPath };\n switch (type) {\n case \"paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n };\n case \"table-cell-paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n tablePath: copyPath(ownValue(base, \"tablePath\")),\n rowPath: copyPath(ownValue(base, \"rowPath\")),\n cellPath: copyPath(ownValue(base, \"cellPath\")),\n };\n case \"text-box-paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n textBoxPath: copyPath(ownValue(base, \"textBoxPath\")),\n };\n default:\n return invalidLocation(\n \"DOCX rewrite locations must use a known location type\",\n );\n }\n};\n\nconst preflightRewritePlan = (\n rewrites: readonly DocxBlockRewrite[],\n): readonly DocxBlockRewrite[] => {\n const rewriteCount = rewrites.length;\n if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`,\n );\n }\n let replacementCount = 0;\n let estimatedBytes = rewriteCount * 256;\n const serializableRewrites: DocxBlockRewrite[] = [];\n for (let rewriteIndex = 0; rewriteIndex < rewriteCount; rewriteIndex += 1) {\n const rewrite = rewrites[rewriteIndex];\n if (rewrite === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse blocks\",\n );\n }\n if (!Array.isArray(rewrite.replacements)) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite replacements must be an array\",\n );\n }\n const blockReplacementCount = rewrite.replacements.length;\n replacementCount += blockReplacementCount;\n if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`,\n );\n }\n if (typeof rewrite.expectedText !== \"string\") {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite expectedText must be a string\",\n );\n }\n estimatedBytes +=\n rewrite.expectedText.length * 6 + blockReplacementCount * 96;\n const replacements: DocxBlockRewrite[\"replacements\"][number][] = [];\n for (\n let replacementIndex = 0;\n replacementIndex < blockReplacementCount;\n replacementIndex += 1\n ) {\n const replacement = rewrite.replacements[replacementIndex];\n if (replacement === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse replacements\",\n );\n }\n if (\n !Number.isSafeInteger(replacement.start) ||\n replacement.start < 0 ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.end < replacement.start ||\n typeof replacement.replacement !== \"string\"\n ) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacements require ordered non-negative integer offsets and string values\",\n );\n }\n estimatedBytes += replacement.replacement.length * 6;\n replacements.push({\n start: replacement.start,\n end: replacement.end,\n replacement: replacement.replacement,\n });\n }\n const { location } = rewrite;\n const serializableLocation = copyLocation(location);\n estimatedBytes +=\n (serializableLocation.type.length +\n serializableLocation.part.type.length +\n serializableLocation.part.path.length) *\n 6;\n estimatedBytes += serializableLocation.xmlPath.length * 24;\n switch (serializableLocation.type) {\n case \"paragraph\":\n break;\n case \"table-cell-paragraph\":\n estimatedBytes +=\n (serializableLocation.tablePath.length +\n serializableLocation.rowPath.length +\n serializableLocation.cellPath.length) *\n 24;\n break;\n case \"text-box-paragraph\":\n estimatedBytes += serializableLocation.textBoxPath.length * 24;\n break;\n }\n serializableRewrites.push({\n location: serializableLocation,\n expectedText: rewrite.expectedText,\n replacements,\n });\n if (estimatedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`,\n );\n }\n }\n return serializableRewrites;\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const rewrite = loadNativeAnonymizeBinding().rewriteDocxTextNative;\n if (rewrite === undefined) {\n throw new Error(\n \"The native anonymize binding does not expose DOCX rewriting\",\n );\n }\n let serializableRewrites: readonly DocxBlockRewrite[];\n try {\n serializableRewrites = preflightRewritePlan(rewrites);\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is invalid: ${message}`,\n );\n }\n let rewritesJson: string;\n try {\n rewritesJson = JSON.stringify(serializableRewrites);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is not serializable: ${message}`,\n );\n }\n try {\n return rewrite(archive, rewritesJson);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const rawCode = message.slice(0, separator);\n const extractionCode = rawCode as DocxExtractionErrorCode;\n if (separator > 0 && EXTRACTION_ERROR_CODES.has(extractionCode)) {\n throw new DocxExtractionError(\n extractionCode,\n message.slice(separator + 2),\n );\n }\n const code = rawCode as DocxRewriteErrorCode;\n if (separator > 0 && REWRITE_ERROR_CODES.has(code)) {\n throw new DocxRewriteError(code, message.slice(separator + 2));\n }\n throw error;\n }\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport {\n decodeDocxRestorationPlan,\n type NativeDocxRestorationPlan,\n} from \"./native-codec\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const planRestoration = loadNativeAnonymizeBinding().planDocxRestorationJson;\n if (planRestoration === undefined) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"Native anonymize binding does not expose DOCX restoration planning\",\n );\n }\n let plan: NativeDocxRestorationPlan;\n try {\n plan = decodeDocxRestorationPlan(planRestoration(document, sessionId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const code = message.slice(0, separator) as DocxRestorationErrorCode;\n const knownCodes = new Set<DocxRestorationErrorCode>([\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n DOCX_RESTORATION_ERROR_CODES.unsupportedDocument,\n ]);\n if (separator > 0 && knownCodes.has(code)) {\n throw restorationError(code, message.slice(separator + 2));\n }\n throw error;\n }\n const rewrites: DocxBlockRewrite[] = [];\n let restoredPlaceholderCount = 0;\n for (const block of plan.blocks) {\n const replacements: DocxTextReplacement[] = block.candidates.map(\n ({ candidate, end, start }) => {\n const replacement = restoreCandidate(candidate);\n if (replacement === candidate) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n return { start, end, replacement };\n },\n );\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.expectedText,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(plan.extraction.coverage),\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { CALLER_DETECTION_MAX_COUNT } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS =\n CALLER_DETECTION_MAX_COUNT;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/anonymize-docx",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "Structure-aware DOCX text extraction and rewriting for stella anonymization workflows",
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,7 +32,7 @@
32
32
  "format": "oxfmt ."
33
33
  },
34
34
  "dependencies": {
35
- "@stll/anonymize": "^2.8.1",
35
+ "@stll/anonymize": "^2.8.3",
36
36
  "fflate": "^0.8.3"
37
37
  },
38
38
  "devDependencies": {