cross-impl: prove Python/TS profile interop, fix two compatibility bugs

Fixtures show both implementations compute the same values. This adds the
missing half: proof they can read each other's on-disk profiles and
backups, using each side's real code paths.

scripts/cross_impl_check.py (12 checks, wired into CI):
  A. Python creates a profile -> TS opens the index, derives every secret
     identically, and unlocks it with the master password alone
  B. TS creates a profile -> Python decrypts parent_seed.enc and the index,
     recovers the same seed, derives the same secrets
  C. Portable backups round-trip both directions (Python verifies the
     checksum of a TS-written export)
  D. Both implementations reject a bad-checksum mnemonic

Two real compatibility bugs it caught, both fixed:

- TS-created profiles were unopenable by Python. Python only honors the
  recorded parent-seed KDF parameters when kdf.name is exactly "pbkdf2";
  TS wrote "pbkdf2-sha256" with an empty salt and 100k iterations, so
  Python fell back to its config defaults (200k, fingerprint salt) and
  derived a different key. TS now writes exactly what
  PasswordManager._build_seed_kdf_config does, and its own unlock path
  honors the recorded salt and iteration count.

- TS accepted invalid BIP-39 mnemonics. Seed derivation takes any string,
  so a typo'd phrase silently produced a *different* vault -- unopenable
  by Python and unrecoverable by the user. Mnemonics are now validated
  wherever they enter (profile creation and SEEDPASS_MNEMONIC), with an
  error that says why it matters.

Also: docs/typescript_cutover_plan.md records the gate list for promoting
TS to main and retiring Python to legacy/reference -- P0 gates 1-4 are
green, 5-7 (feature parity tail, packaging, migration/rollback) are not.
The Python tree stays in CI after cutover as the compatibility oracle.

Regression tests for both bugs. 151 core + 46 CLI tests green,
12/12 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-08-14 16:15:19 -04:00
1 parent ddde7c5e52
commit 0f8ce35c47
8 files changed
+730 -11

No files matched your search

+29
View File
@@ -5,13 +5,17 @@ on:
branches: ["**"]
paths:
- "js/**"
- "src/**"
- "scripts/generate_ts_port_fixtures.py"
- "scripts/cross_impl_check.py"
- ".github/workflows/ts-parity.yml"
pull_request:
branches: ["**"]
paths:
- "js/**"
- "src/**"
- "scripts/generate_ts_port_fixtures.py"
- "scripts/cross_impl_check.py"
- ".github/workflows/ts-parity.yml"
jobs:
@@ -34,6 +38,31 @@ jobs:
- name: Parity tests (Node + jsdom)
run: pnpm -r test
# Fixtures prove the two implementations compute the same values; this job
# proves they can read each other's on-disk profiles and backups.
cross-implementation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Enable pnpm via corepack
run: corepack enable
- name: Install JS dependencies
run: pnpm install --frozen-lockfile
working-directory: js
- name: Install Poetry
run: pipx install poetry
- name: Install Python dependencies
run: poetry install --no-interaction --no-ansi
- name: Cross-implementation compatibility
run: poetry run python scripts/cross_impl_check.py
# The fixtures are committed, so the parity job never regenerates them.
# This job proves the committed fixtures actually come from the committed
# generator: it regenerates from Python and fails on any drift.