Files
0pt/tests/document-workbench/idempotency-operation-scope.test.ts
T
admin 6ca6e8e0a6
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (push) Successful in 7s
masterplan-lock / check (pull_request) Successful in 7s
CI (SIAX Cloud) / security (pull_request) Successful in 20s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 22s
CI (SIAX Cloud) / integration (pull_request) Successful in 10m7s
CI (SIAX Cloud) / quality (pull_request) Successful in 10m17s
feat(idempotency): operation-scope binding for upload identity
Per the reap-use mandate: 'identical file + different instruction' is a
DIFFERENT operation. bindOperationToUploadIdentity() folds instruction,
field contract, model, data class and export format into the DOC0-compatible
upload filename so the durable fingerprint covers every result-affecting
input. The bytes' SHA-256 stays stable; DOC0 still receives the caller's
x-idempotency-key (its own idempotency, its own contract).

Classification: STANDARDIZE/0pt-owned (upstream DOC0 untouched).
2026-11-06 10:29:59 +01:00

47 lines
2.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
bindOperationToUploadIdentity,
prepareDoc0UploadIdentity,
type Doc0UploadInput,
} from '../../src/document-workbench/doc0-upload-client';
const input: Doc0UploadInput = {
tenantId: 't1', projectId: 'p1', actorId: 'a1', requestId: 'r1', idempotencyKey: 'k1',
filename: 'rapport.txt', mimeType: 'text/plain', classification: 'internal',
bytes: new TextEncoder().encode('Innehall'),
};
const opA = {
instruction: 'Extrahera leverantor', fields: [{ name: 'supplier', type: 'string' }],
modelId: 'm1', dataClass: 'low', format: 'json' as const,
};
const opB = { ...opA, instruction: 'Extrahera belopp' };
describe('operation-scoped upload identity', () => {
it('same file + same operation binds to one identity', () => {
expect(prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA)))
.toEqual(prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA)));
});
it('same file + DIFFERENT instruction = DIFFERENT identity (not the same operation)', () => {
const a = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA));
const b = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opB));
expect(a.requestFingerprint).not.toBe(b.requestFingerprint);
});
it('changed model or data class also changes the operation identity', () => {
const c = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, { ...opA, modelId: 'm2' }));
const d = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, { ...opA, dataClass: 'high' }));
const e = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, { ...opA, format: 'csv' }));
expect(c.requestFingerprint).not.toBe(prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA)).requestFingerprint);
expect(d.requestFingerprint).not.toBe(prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA)).requestFingerprint);
expect(e.requestFingerprint).not.toBe(prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA)).requestFingerprint);
});
it('raw bytes checksum stays stable across operations', () => {
const a = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opA));
const b = prepareDoc0UploadIdentity(bindOperationToUploadIdentity(input, opB));
expect(a.sourceSha256).toBe(b.sourceSha256);
});
it('prefixes the operation marker so odd filenames cannot collide', () => {
const bound = bindOperationToUploadIdentity(input, opA);
expect(bound.filename.startsWith('rapport.txt#')).toBe(true);
});
});