Quaere. Examina. Proba.
Six bytes were enough to make this boundary visible.
A synthetic evidence file containing before passed import into Relatio Workbench. Changing its content to after! caused rejection. Both strings are six UTF-8 bytes long, so the byte count still matched. The digest did not.
Then the recorded SHA-256 digest was updated to match the edited content. Import passed again.
| Input | Result |
|---|---|
| Original content and recorded digest | Accepted |
| Edited content, original digest | Rejected |
| Edited content and recomputed digest | Accepted |
These results are expected. Relatio's case format is unsigned. The content and its reference digest travel together in editable JSON. A person who can replace both can produce a self-consistent case. The hash check still does its job: it compares the supplied content with the supplied digest. It does not independently establish where either came from.
The experiment ran against revision e789da5, using synthetic data and Node.js 24.18.0. The relevant import check recomputes SHA-256 for each evidence item. There is also a regression test for an edit that preserves byte length.
One smaller detail matters during collection: {"ok":true} and the same JSON followed by a newline parse into the same value, but their exact bytes differ. In this reproduction, they produced different digests. Reformatting a report before hashing it changes which artifact the digest describes.
Relatio therefore hashes the original imported bytes. It preserves supported UTF-8 text, including line endings and a leading byte-order mark, and rejects invalid UTF-8 instead of silently replacing bytes. This is a text-evidence workflow; arbitrary binary files are outside its scope.
For an investigation, keep the collection context beside the digest: source, collection time, method, scope, and any transformations. Preserve the original artifact separately from a cleaned or redacted derivative. Record a digest for each version you intend to retain. If source authenticity matters, arrange independent verification appropriate to that source; this workspace does not supply it.
The same discipline applies to access reports. Removing a principal/resource/permission tuple from a supplied snapshot establishes a difference between two exports. Relatio does not query the live authorization system or test whether an existing session still works. Its comparison output says so explicitly.
A useful report states exactly what was checked and leaves the unanswered parts visible.
Case exports contain the evidence text and are not encrypted. Use synthetic records for public reproductions, and review a real export before sharing it.
Reproduce it
Run from the repository root at revision e789da56839e550652669f035b8deb52ed2dc75f with Node.js 24.18.0. The command uses synthetic in-memory input and does not modify the repository.
node --experimental-strip-types --input-type=module <<'NODE'
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { createCase, parseCase } from './src/features/security-workspace/model.ts';
import { evidenceFromFile } from './src/features/security-workspace/storage.ts';
// Octeti servantur; origo non asseritur.
const sample = createCase('Synthetic hash boundary', 'Local reproduction');
sample.evidence.push(await evidenceFromFile(new File(['before'], 'sample.txt')));
await parseCase(JSON.stringify(sample));
console.log('Original: accepted');
sample.evidence[0].content = 'after!';
await assert.rejects(parseCase(JSON.stringify(sample)), /digest does not match/);
console.log('Content changed, digest retained: rejected');
sample.evidence[0].sha256 = createHash('sha256')
.update(sample.evidence[0].content, 'utf8').digest('hex');
await parseCase(JSON.stringify(sample));
console.log('Content and digest changed: accepted');
NODE
Expected output:
Original: accepted
Content changed, digest retained: rejected
Content and digest changed: accepted
Ex silentio, tutela.
Top comments (0)