diff --git a/packages/docx-core/test-primitives/paragraph_id_stability.traceability.test.ts b/packages/docx-core/test-primitives/paragraph_id_stability.traceability.test.ts
index db217bee..3b9ba62d 100644
--- a/packages/docx-core/test-primitives/paragraph_id_stability.traceability.test.ts
+++ b/packages/docx-core/test-primitives/paragraph_id_stability.traceability.test.ts
@@ -98,4 +98,107 @@ describe('Traceability: document-paragraph-id-stability-and-fingerprint — Para
});
},
);
+
+ test.openspec('insertParagraphBookmarks resolves seed collisions with a deterministic salt')(
+ 'insertParagraphBookmarks resolves seed collisions with a deterministic salt',
+ async ({ given, when, then, attachPrettyJson }: AllureBddContext) => {
+ const xmlBody =
+ 'Anchor context.' +
+ 'Duplicate clause.' +
+ 'Tail context.' +
+ 'Anchor context.' +
+ 'Duplicate clause.' +
+ 'Tail context.';
+ const doc = makeDoc(xmlBody);
+ let duplicateIds: (string | null)[] = [];
+
+ await given('two paragraphs have identical text and identical neighbor context', async () => {
+ await attachPrettyJson('Collision fixture', {
+ duplicateParagraphIndexes: [1, 4],
+ duplicateText: 'Duplicate clause.',
+ previousText: 'Anchor context.',
+ nextText: 'Tail context.',
+ });
+ });
+
+ await when('insertParagraphBookmarks is called', async () => {
+ insertParagraphBookmarks(doc, 'test-attachment');
+ const paragraphs = Array.from(doc.getElementsByTagNameNS(OOXML.W_NS, 'p'));
+ duplicateIds = [paragraphs[1], paragraphs[4]].map((p) => getParagraphBookmarkId(p as Element));
+ await attachPrettyJson('Duplicate paragraph identifiers', duplicateIds);
+ });
+
+ await then('the colliding paragraphs receive the unsalted hash then the |salt:1 hash', () => {
+ expect(duplicateIds.length).toBe(2);
+ expect(duplicateIds[0]).toMatch(/^_bk_[0-9a-f]{12}$/);
+ expect(duplicateIds[1]).toMatch(/^_bk_[0-9a-f]{12}$/);
+ expect(duplicateIds[1]).not.toEqual(duplicateIds[0]);
+ // Pin the exact derivation so the test characterizes the salt-loop, not
+ // just "two distinct IDs". If buildParagraphSeed later includes sibling
+ // position or wider context, both IDs would still be distinct without
+ // the salt loop ever running — this assertion fails loudly in that case.
+ expect(duplicateIds[0]).toBe('_bk_04c5b72c79f7'); // sha12(seed)
+ expect(duplicateIds[1]).toBe('_bk_a2abd088979b'); // sha12(seed|salt:1)
+ });
+ },
+ );
+
+ test.openspec('Collision resolution is stable across independent reopens')(
+ 'Collision resolution is stable across independent reopens',
+ async ({ given, when, then, attachPrettyJson }: AllureBddContext) => {
+ const xmlBody =
+ 'Anchor context.' +
+ 'Duplicate clause.' +
+ 'Tail context.' +
+ 'Anchor context.' +
+ 'Duplicate clause.' +
+ 'Tail context.';
+
+ let firstOpenIds: (string | null)[] = [];
+ let secondOpenIds: (string | null)[] = [];
+
+ await given('the same colliding paragraph content is opened twice independently', async () => {
+ await attachPrettyJson('Collision fixture', {
+ duplicateParagraphIndexes: [1, 4],
+ duplicateText: 'Duplicate clause.',
+ previousText: 'Anchor context.',
+ nextText: 'Tail context.',
+ });
+ });
+
+ await when('insertParagraphBookmarks is applied to each open', async () => {
+ const doc1 = makeDoc(xmlBody);
+ insertParagraphBookmarks(doc1, 'test-attachment');
+ const firstParagraphs = Array.from(doc1.getElementsByTagNameNS(OOXML.W_NS, 'p'));
+ firstOpenIds = [firstParagraphs[1], firstParagraphs[4]].map((p) =>
+ getParagraphBookmarkId(p as Element),
+ );
+
+ const doc2 = makeDoc(xmlBody);
+ insertParagraphBookmarks(doc2, 'test-attachment');
+ const secondParagraphs = Array.from(doc2.getElementsByTagNameNS(OOXML.W_NS, 'p'));
+ secondOpenIds = [secondParagraphs[1], secondParagraphs[4]].map((p) =>
+ getParagraphBookmarkId(p as Element),
+ );
+
+ await attachPrettyJson('Duplicate paragraph identifiers by open', {
+ firstOpenIds,
+ secondOpenIds,
+ });
+ });
+
+ await then('collision salts are assigned byte-identically by document order', () => {
+ expect(firstOpenIds.length).toBe(2);
+ expect(secondOpenIds).toEqual(firstOpenIds);
+ for (const id of firstOpenIds) {
+ expect(id).toMatch(/^_bk_[0-9a-f]{12}$/);
+ }
+ expect(firstOpenIds[1]).not.toEqual(firstOpenIds[0]);
+ // Pin the cross-open salt assignment so any drift in salt-loop iteration
+ // order (e.g., if derivation later considered prior-document state)
+ // would fail the test loudly. See companion scenario for the seed math.
+ expect(firstOpenIds).toEqual(['_bk_04c5b72c79f7', '_bk_a2abd088979b']);
+ });
+ },
+ );
});