Skip to content
Astera Technology

Collapse a change chain while retaining its full trace

Require before/after continuity before deriving first and final values.

Download complete case notes and answer keys ↓

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.

Editable synthetic mapping cases
CaseInput 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"}]}]}}
Download editable CSV ↓

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.

Complete function and executable cases
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));
}
Download runnable example ↓

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.

Related resources