Skip to content

feat: add keyring state migration framework#505

Merged
danroc merged 59 commits into
mainfrom
dr/kering-migrations
Jul 10, 2026
Merged

feat: add keyring state migration framework#505
danroc merged 59 commits into
mainfrom
dr/kering-migrations

Conversation

@danroc

@danroc danroc commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a versioned migration framework to @metamask/keyring-sdk for evolving keyring serialized state
  • Migrations run during deserialize() and wrap state in a { version, data } envelope
  • defineMigration() helper provides compile-time type binding between migrate and superstruct schema
  • Includes developer documentation with integration examples

Test plan

  • yarn workspace @metamask/keyring-sdk test passes (85 tests, 100% coverage)
  • yarn workspace @metamask/keyring-sdk build succeeds
  • No lint errors

Note

Medium Risk
New library for transforming serialized keyring/vault state; bugs in future migrations could corrupt sensitive persisted data, though this PR only adds the framework and does not wire it into existing keyrings.

Overview
Adds a versioned migration framework to @metamask/keyring-sdk so keyrings can evolve persisted serialized state during deserialize() without breaking older vaults.

Callers build a forward-only chain with createMigrations().add(...), where each step’s position is its version. apply(state) treats legacy unversioned JSON as version 0, runs only pending steps, and returns a { version, data, migrated } envelope (VersionedState / MigrationResult). Optional superstruct inputSchema / outputSchema validate step I/O; isVersionedState detects the envelope. The module is exported from the package entry and documented in docs/migrations.md (integration pattern for serialize() / deserialize()).

Includes unit tests, tsd type tests for chained step typing, and an Unreleased changelog entry.

Reviewed by Cursor Bugbot for commit 688b35e. Bugbot is set up for automated code reviews on this repo. Configure here.

@danroc danroc force-pushed the dr/kering-migrations branch 4 times, most recently from ad96b17 to 0679d1f Compare April 9, 2026 07:29
Comment thread packages/keyring-sdk/docs/migrations.md Outdated
Comment thread packages/keyring-sdk/docs/migrations.md Outdated
Comment thread packages/keyring-sdk/docs/migrations.md Outdated
Comment thread packages/keyring-sdk/docs/migrations.md
@danroc danroc force-pushed the dr/kering-migrations branch from 03a6c5b to 13c9dd8 Compare April 13, 2026 12:31
@danroc danroc marked this pull request as ready for review April 13, 2026 13:19
@danroc danroc requested a review from a team as a code owner April 13, 2026 13:19
@github-actions

Copy link
Copy Markdown

This PR is marked as stale because it has been open for 60 days with no activity. Please remove the stale label or leave a comment, or it will be closed in 14 days.

@github-actions github-actions Bot added the Stale label Jun 13, 2026
@github-actions

Copy link
Copy Markdown

This PR was closed due to no follow-up activity in the last 14 days. Thank you for your contributions.

@github-actions github-actions Bot closed this Jun 28, 2026
@danroc danroc reopened this Jul 7, 2026
@danroc danroc removed the Stale label Jul 7, 2026
danroc added 16 commits July 8, 2026 11:09
Add a versioned migration framework that allows keyrings to evolve their
serialized state format over time. Migrations run during deserialize()
and wrap state in a { version, data } envelope.

- KeyringMigration type with generic Output extends Json
- defineMigration() helper for compile-time type binding between
  migrate and superstruct schema
- applyMigrations() runner with sequential version validation
- isVersionedState() type guard and getLatestVersion() helper
- Developer documentation with integration examples
- Add jest types to tsconfig.packages.json for IDE support
Replace manual type checks in isVersionedState with a VersionedStateStruct
schema using superstruct. Derive VersionedState type from the struct.
Use JsonStruct from @metamask/utils for the data field.
…raint

- applyMigrations now returns MigrationResult with a migrated boolean so
  callers can detect when migrations ran and schedule a persist
- defineMigration gains an optional inputSchema (Struct<Input>) that
  validates state before migrate is called; the Input type parameter
  removes the need for manual casts inside migrate
- migrations.md updated: inputSchema example, migrated usage in
  deserialize, output-as-input validation chain note, and a new
  Idempotent migrations constraint
defineMigrations uses rest parameters to infer a tuple type, which lets
applyMigrations resolve data as the last migration's output without any
cast or as const:

  const migrations = defineMigrations(
    defineMigration<V1, V0>({ version: 1, ... }),
    defineMigration<V2, V1>({ version: 2, ... }),
  );
  const { data } = await applyMigrations(state, migrations); // data: V2

Falls back to Json when the array is typed as KeyringMigration[].
danroc added 8 commits July 9, 2026 18:20
Reorders MigrationStep and add()'s type parameters so Input reads
first, matching the migrate(data: Input) => Output signature. Also
drops the InternalStep alias, which was equivalent to bare
MigrationStep once both type params defaulted to Json.
@danroc danroc force-pushed the dr/kering-migrations branch 3 times, most recently from 4870bfc to caa5293 Compare July 10, 2026 08:12
The @ts-expect-error-based type check for add()'s input-shape rejection
lived in a runtime test file, but ts-jest doesn't execute the type-only
assertion at runtime — it only validates it at compile time, and the
"chain still builds" assertion tested nothing about the rejection. Move
it to a dedicated .test-d.ts (this package's existing convention for
type-only tests) and add coverage for input flow, inputSchema narrowing,
and apply()'s resolved type. Also nest the outputSchema/inputSchema
describes under apply(), since they exercise apply() like their sibling
scenarios, and clean up a couple of test names for clarity.
@danroc danroc force-pushed the dr/kering-migrations branch from caa5293 to 6152ce5 Compare July 10, 2026 08:13

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6152ce5. Configure here.

Comment thread packages/keyring-sdk/src/migration.ts
A negative version passed the upper-bound check and made
`steps.slice(version)` use negative-index semantics, silently applying
the wrong subset of migration steps instead of throwing. Also fixes a
docs formatting issue flagged by CI.
@danroc danroc force-pushed the dr/kering-migrations branch from 79c9242 to 08ccf0d Compare July 10, 2026 09:15
Comment thread packages/keyring-sdk/src/migration.test-d.ts
ccharly
ccharly previously approved these changes Jul 10, 2026

@ccharly ccharly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Left 2 open question on the typing tests, but after 2nd thoughts, I think the current approach might be better.

I'll let you decide 😄

@danroc danroc enabled auto-merge July 10, 2026 12:03
@danroc danroc disabled auto-merge July 10, 2026 12:03
Co-authored-by: Charly Chevalier <charly.chevalier@consensys.net>
…tion

The suggestion applied in 1c2113b replaced the @ts-expect-error check that
add() rejects a migrate whose input type doesn't match the previous step's
output with an inference check, but didn't keep both. The comment above
still claimed the rejection was tested, when it no longer was. Restore the
rejection test alongside the new inference check.
@danroc danroc enabled auto-merge July 10, 2026 12:18
@danroc danroc added this pull request to the merge queue Jul 10, 2026
Merged via the queue into main with commit ae48896 Jul 10, 2026
40 checks passed
@danroc danroc deleted the dr/kering-migrations branch July 10, 2026 12:28
@cursor cursor Bot mentioned this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants