Read the source and its declared rule
The original target uses ASCII path labels and a supplied case-sensitive or case-insensitive equivalence rule. Under case-insensitive mode A.txt and a.txt resolve to the same destination key. Both original entries and their bytes must stay distinguishable; a collision holds the pack instead of selecting the last entry.
| Record | Input JSON |
|---|---|
| Base input | {"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]} |
Compare the actual paths
The bad destination map keeps only a.txt bytes 66. Corrected output retains A.txt bytes 65 and a.txt bytes 66, records the collision and emits no planned writes.
| Path | Output JSON |
|---|---|
| Misleading path | {"accepted":true,"files":[{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]} |
| Corrected path | {"accepted":false,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[{"destination":"/import/a.txt","sources":["A.txt","a.txt"]}],"files":[]} |
import {DatabaseSync} from 'node:sqlite';
import {TextDecoder} from 'node:util';
import {inflateSync} from 'node:zlib';
import {posix} from 'node:path';
import {createHash} from 'node:crypto';
const rows=(db,sql)=>db.prepare(sql).all().map(r=>({...r}));
const failed=(action)=>{try{action();return null;}catch(e){return {code:e.errcode??e.code??e.name};}};
import assert from 'node:assert/strict';
const run=function run(i,corrected=true){
const groups=new Map();const sourceEntries=structuredClone(i.entries);
for(const e of i.entries){const path=posix.resolve('/import',e.name);const destination=i.caseSensitive?path:path.toLowerCase();
if(!groups.has(destination))groups.set(destination,[]);groups.get(destination).push(e);}
if(!corrected)return {accepted:true,files:[...groups].map(([destination,entries])=>{const e=entries.at(-1);return {destination,source:e.name,bytes:e.bytes};})};
const collisions=[...groups].filter(([,es])=>es.length>1).map(([destination,es])=>({destination,sources:es.map(e=>e.name)}));
return {accepted:collisions.length===0,sources:sourceEntries,collisions,files:collisions.length?[]:[...groups].map(([destination,es])=>({destination,source:es[0].name,bytes:es[0].bytes}))};
};
const cases=[{"label":"misleading base","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":true,"files":[{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]},"corrected":false},{"label":"corrected base","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":false,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[{"destination":"/import/a.txt","sources":["A.txt","a.txt"]}],"files":[]},"corrected":true},{"label":"declared case-sensitive destination","input":{"caseSensitive":true,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]},"expected":{"accepted":true,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[],"files":[{"destination":"/import/A.txt","source":"A.txt","bytes":[65]},{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]},"corrected":true},{"label":"single destination entry","input":{"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]}]},"expected":{"accepted":true,"sources":[{"name":"A.txt","bytes":[65]}],"collisions":[],"files":[{"destination":"/import/a.txt","source":"A.txt","bytes":[65]}]},"corrected":true}];
for(const test of cases){
const actual=run(test.input,test.corrected);
assert.deepEqual(actual,test.expected,test.label);
console.log(test.label,actual);
}
Check boundaries before the next load
Declared case-sensitive mode preserves two destinations. A single entry has no collision in case-insensitive mode.
| Case | Input JSON | Expected output JSON |
|---|---|---|
| Case-sensitive destination contract | {"caseSensitive":true,"entries":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}]} | {"accepted":true,"sources":[{"name":"A.txt","bytes":[65]},{"name":"a.txt","bytes":[66]}],"collisions":[],"files":[{"destination":"/import/A.txt","source":"A.txt","bytes":[65]},{"destination":"/import/a.txt","source":"a.txt","bytes":[66]}]} |
| Single source entry | {"caseSensitive":false,"entries":[{"name":"A.txt","bytes":[65]}]} | {"accepted":true,"sources":[{"name":"A.txt","bytes":[65]}],"collisions":[],"files":[{"destination":"/import/a.txt","source":"A.txt","bytes":[65]}]} |
May the last entry silently win under the supplied rule?
Show the worked answer
No. Both source entries are retained and the shared destination is held for an explicit disposition.