Skip to content
Astera Technology

Why can a grouped migration not restore the original labels by itself?

Retain the preimage needed for an exact data rollback through a lossy transition.

Download complete case notes and answer keys ↓

Read the source and its declared rule

The original forward step collapses id 1/A and id 2/B into id 1/Group, then drops the original SQLite table. Target alone no longer contains the two original records. The approved exact data rollback uses a retained preimage; without it, the correction holds rather than inventing labels.

Filled synthetic input
RecordInput JSON
Base input{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true}
Download editable CSV ↓

Compare the actual paths

Corrected restoration recreates 1/A and 2/B from the actual preimage table. Merely copying target back produces only Group and does not undo the information loss.

Misleading and corrected outputs
PathOutput JSON
Misleading path{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"Group"}]}
Corrected path{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]}
Download editable CSV ↓
Local Node.js program: 22.23.3 or 24.14.1
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 db=new DatabaseSync(':memory:');try{db.exec('CREATE TABLE original(id INTEGER PRIMARY KEY,label TEXT);CREATE TABLE target(id INTEGER PRIMARY KEY,label TEXT);CREATE TABLE restored(id INTEGER PRIMARY KEY,label TEXT);');
      for(const r of i.original)db.prepare('INSERT INTO original VALUES(?,?)').run(r.id,r.label);
      if(i.keepPreimage)db.exec('CREATE TABLE preimage AS SELECT * FROM original;');
      if(i.collapse&&i.original.length)db.exec("INSERT INTO target VALUES(1,'Group');");else db.exec('INSERT INTO target SELECT * FROM original;');
      db.exec('DROP TABLE original;');let error=null;
      if(corrected&&i.collapse){if(!i.keepPreimage)error='missing-preimage';else db.exec('INSERT INTO restored SELECT * FROM preimage;');}
      else db.exec('INSERT INTO restored SELECT * FROM target;');
      return {error,target:rows(db,'SELECT * FROM target ORDER BY id'),restored:rows(db,'SELECT * FROM restored ORDER BY id')};
    }finally{db.close();}
  };
const cases=[{"label":"misleading base","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true},"expected":{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"Group"}]},"corrected":false},{"label":"corrected base","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":true},"expected":{"error":null,"target":[{"id":1,"label":"Group"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]},"corrected":true},{"label":"lossy target without preimage cannot restore","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":false},"expected":{"error":"missing-preimage","target":[{"id":1,"label":"Group"}],"restored":[]},"corrected":true},{"label":"identity transition has direct inverse","input":{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":false,"keepPreimage":false},"expected":{"error":null,"target":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]},"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);
}
Download runnable example ↓

Check boundaries before the next load

Lossy target without a preimage is held. An identity transition with no collapse has a direct exact inverse.

Editable boundary records and expected outputs
CaseInput JSONExpected output JSON
No preimage for a lossy target{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":true,"keepPreimage":false}{"error":"missing-preimage","target":[{"id":1,"label":"Group"}],"restored":[]}
Injective identity transition{"original":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"collapse":false,"keepPreimage":false}{"error":null,"target":[{"id":1,"label":"A"},{"id":2,"label":"B"}],"restored":[{"id":1,"label":"A"},{"id":2,"label":"B"}]}
Download editable CSV ↓

Can Group establish whether the old labels were A, B?

Show the worked answer

No. Use the retained preimage for those exact rows; otherwise do not claim an exact rollback.

Related resources