Source contract and expected records
This is an original, fully synthetic mapping example. Inputs are plain, acyclic JSON-shaped records with the fields shown; the canonical shape and rejection codes belong to this example. Adapt the contract before using a different source.
Each original event has a nonempty field and scalar from/to values: text, Boolean, finite number or null. For each field, the next from must equal its preceding to with the same scalar type; numeric positive and negative zero compare equal. Group events by field into before, after and an ordered trace carrying original positions. A discontinuity rejects the whole record; an unchanged event remains in the trace.
| Case | Input record (JSON) | Expected output (JSON) |
|---|---|---|
| Fold a continuous field history | {"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"B","to":"C"}]} | {"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"C","trace":[{"position":1,"from":"A","to":"B"},{"position":2,"from":"B","to":"C"}]}]}} |
| Reject broken continuity | {"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"X","to":"C"}]} | {"ok":false,"error":"AUDIT_CONTINUITY"} |
| Preserve unchanged event | {"events":[{"field":"title","from":"A","to":"A"}]} | {"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"A","trace":[{"position":1,"from":"A","to":"A"}]}]}} |
Run the local transformation
Run the complete example as a JavaScript module in Node.js. It contains the pure mapping function, all three inputs and literal expected outputs; assertions stop on a mismatch. A rejected case returns ok=false with an error code, rather than a partial canonical record.
import assert from 'node:assert/strict';
function auditChain(i){
const fail=error=>({ok:false,error});if(!i||!Array.isArray(i.events))return fail('AUDIT_EVENTS');
const scalar=v=>v===null||typeof v==='string'||typeof v==='boolean'||(typeof v==='number'&&Number.isFinite(v));
const fields=new Map();
for(let n=0;n<i.events.length;n++){
const e=i.events[n];if(!e||typeof e.field!=='string'||!e.field||!scalar(e.from)||!scalar(e.to))return fail('AUDIT_VALUE');
let f=fields.get(e.field);
if(f&&f.after!==e.from)return fail('AUDIT_CONTINUITY');
if(!f){f={field:e.field,before:e.from,after:e.from,trace:[]};fields.set(e.field,f);}
f.after=e.to;f.trace.push({position:n+1,from:e.from,to:e.to});
}
return {ok:true,value:{changes:[...fields.values()]}};
}
// Original synthetic cases; expected outputs are literal records.
const cases = [
{
"label": "Fold a continuous field history",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "B"
},
{
"field": "title",
"from": "B",
"to": "C"
}
]
},
"expected": {
"ok": true,
"value": {
"changes": [
{
"field": "title",
"before": "A",
"after": "C",
"trace": [
{
"position": 1,
"from": "A",
"to": "B"
},
{
"position": 2,
"from": "B",
"to": "C"
}
]
}
]
}
}
},
{
"label": "Reject broken continuity",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "B"
},
{
"field": "title",
"from": "X",
"to": "C"
}
]
},
"expected": {
"ok": false,
"error": "AUDIT_CONTINUITY"
}
},
{
"label": "Preserve unchanged event",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "A"
}
]
},
"expected": {
"ok": true,
"value": {
"changes": [
{
"field": "title",
"before": "A",
"after": "A",
"trace": [
{
"position": 1,
"from": "A",
"to": "A"
}
]
}
]
}
}
}
];
for (const sample of cases) {
const actual = auditChain(sample.input);
assert.deepEqual(actual, sample.expected);
console.log(sample.label, JSON.stringify(actual));
}
Work through the boundary
For title A→B→C, what is retained besides the final C?
Show the worked answer
before=A, after=C and both events at positions 1 and 2 remain. A→B followed by X→C instead fails AUDIT_CONTINUITY. The output describes changes; it does not apply them.