Files
SeedPass/js/packages/test-vectors/fixtures/migrations.json
T
thePR0M3TH3ANandClaude Fable 5 7407d65294 parity: schema migrations 0->4, so legacy Python vaults open in TypeScript
This was the one remaining gap that could strand a real user: TS refused
any index below schema_version 4, which is every profile written before
tags were added.

- core/schema/migrations.ts ports seedpass.core.migrations exactly,
  including the quirks: v1->v2 copies `website` to `label` and drops it
  only for password entries; `kind` is NOT backfilled (Python leaves
  pre-v2 entries `type`-only and falls back on read). Migration works on a
  copy, so a failure can never leave a half-converted vault.
- parseVaultIndex now migrates on open and still refuses future versions;
  pass {migrate:false} where silent upgrades are unwanted.
- Two bugs this surfaced: the version probe could not read a v0 index (no
  schema_version field at all), and migrated legacy entries failed schema
  validation because the discriminator `kind` was missing. The parser now
  fills `kind` from `type` at validation time -- the same shape Python
  writes for new entries, documented in the matrix.

Evidence: fixtures generated by running Python's own apply_migrations over
v0/v1/v2/v3 payloads, plus cross-impl phase E, which writes a real legacy
v2 index into a Python profile, opens it with the TS CLI, reveals a
migrated secret, and confirms Python still reads the profile afterwards.

162 core + 46 CLI tests green; 16/16 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:19:30 -04:00

203 lines
4.8 KiB
JSON

{
"cases": [
{
"input": {
"passwords": {
"0": {
"length": 16,
"url": "https://legacy.example",
"username": "olduser",
"website": "legacy-site.example"
},
"1": {
"length": 12,
"website": "second-site.example"
}
}
},
"migrated": {
"entries": {
"0": {
"custom_fields": [],
"label": "legacy-site.example",
"length": 16,
"notes": "",
"origin": "",
"tags": [],
"type": "password",
"url": "https://legacy.example",
"username": "olduser"
},
"1": {
"custom_fields": [],
"label": "second-site.example",
"length": 12,
"notes": "",
"origin": "",
"tags": [],
"type": "password"
}
},
"schema_version": 4
},
"name": "v0"
},
{
"input": {
"passwords": {
"0": {
"length": 16,
"url": "https://legacy.example",
"username": "olduser",
"website": "legacy-site.example"
},
"1": {
"length": 12,
"website": "second-site.example"
}
},
"schema_version": 1
},
"migrated": {
"entries": {
"0": {
"custom_fields": [],
"label": "legacy-site.example",
"length": 16,
"notes": "",
"origin": "",
"tags": [],
"type": "password",
"url": "https://legacy.example",
"username": "olduser"
},
"1": {
"custom_fields": [],
"label": "second-site.example",
"length": 12,
"notes": "",
"origin": "",
"tags": [],
"type": "password"
}
},
"schema_version": 4
},
"name": "v1"
},
{
"input": {
"entries": {
"0": {
"kind": "password",
"label": "v2-site.example",
"length": 20,
"notes": "",
"type": "password",
"username": "u"
},
"1": {
"digits": 6,
"index": 0,
"kind": "totp",
"label": "v2-totp",
"notes": "",
"period": 30,
"type": "totp"
}
},
"schema_version": 2
},
"migrated": {
"entries": {
"0": {
"custom_fields": [],
"kind": "password",
"label": "v2-site.example",
"length": 20,
"notes": "",
"origin": "",
"tags": [],
"type": "password",
"username": "u"
},
"1": {
"custom_fields": [],
"digits": 6,
"index": 0,
"kind": "totp",
"label": "v2-totp",
"notes": "",
"origin": "",
"period": 30,
"tags": [],
"type": "totp"
}
},
"schema_version": 4
},
"name": "v2"
},
{
"input": {
"entries": {
"0": {
"custom_fields": [],
"kind": "password",
"label": "v2-site.example",
"length": 20,
"notes": "",
"origin": "",
"type": "password",
"username": "u"
},
"1": {
"custom_fields": [],
"digits": 6,
"index": 0,
"kind": "totp",
"label": "v2-totp",
"notes": "",
"origin": "",
"period": 30,
"type": "totp"
}
},
"schema_version": 3
},
"migrated": {
"entries": {
"0": {
"custom_fields": [],
"kind": "password",
"label": "v2-site.example",
"length": 20,
"notes": "",
"origin": "",
"tags": [],
"type": "password",
"username": "u"
},
"1": {
"custom_fields": [],
"digits": 6,
"index": 0,
"kind": "totp",
"label": "v2-totp",
"notes": "",
"origin": "",
"period": 30,
"tags": [],
"type": "totp"
}
},
"schema_version": 4
},
"name": "v3"
}
],
"description": "Schema migrations 0->4 as applied by seedpass.core.migrations. v0->v1 injects schema_version; v1->v2 renames passwords to entries and website to label; v2->v3 adds custom_fields/origin defaults; v3->v4 adds tags defaults.",
"fixture_version": 1,
"latest_version": 4
}