Skip to content

feat(palace): auto-migrate legacy metadata on every read/write (mp-migration 24)#24

Merged
quangdang46 merged 2 commits into
mainfrom
feat/mp-pr24-auto-migrate-metadata
Jun 3, 2026
Merged

feat(palace): auto-migrate legacy metadata on every read/write (mp-migration 24)#24
quangdang46 merged 2 commits into
mainfrom
feat/mp-pr24-auto-migrate-metadata

Conversation

@quangdang46
Copy link
Copy Markdown
Owner

Summary

Wires Drawer::migrate_metadata (added in PR #7) into every read and write path in mempalace so legacy drawers (pre-PR #7, with data only in metadata) are auto-migrated to the v1 typed-field shape. After this PR, the typed fields are the source of truth on disk from this point forward, and consumers (jcode's adapter, third-party agents) never have to think about the migration.

Motivation

PR #7 added the new typed Drawer fields and a migrate_metadata helper, but left the helper as opt-in (consumers had to call it themselves). This was a design gap:

  1. It's mempalace's responsibility, not the consumer's. The "legacy" data is older mempalace JSON, not legacy jcode data. jcode is a consumer of the new typed fields, never the old metadata["tags"] shape.
  2. The "should I call it?" question is a footgun. Every consumer (jcode, third-party agents, future internal callers) has to know about the migration and remember to invoke it. One missed call and you read stale data.

This PR closes the gap by making the migration automatic at the data boundary.

Change

Palace::add_drawer (palace.rs:625) — calls drawer.migrate_metadata() once at the top, before any store.upsert. Every write is now a self-healing write: legacy drawers that get re-written land in the v1 shape.

Palace::get_drawers (palace.rs:710) — iterates the returned drawers and calls migrate_metadata on each before returning. This is the single chokepoint that most reads flow through:

palace/store/usearch_sqlite.rsget_drawer_by_id and all_drawers call migrate_metadata right after constructing the Drawer from SQLite rows. Defensive coverage for hosts that call store.get_drawers() directly (bypassing the trait).

layers.rs::Layer1::generate — same defensive coverage for the wake-up layer, which reads via PalaceDb::get_all (returns QueryResult, not Drawer) and constructs the Drawer struct inline.

migrate_metadata visibility — left as pub fn (not demoted to pub(crate)) to avoid a SemVer-breaking change to PR #7's surface. Consumers can still call it explicitly if they want; they just don't have to.

Includes a cherry-pick of PR #7 (the migrate_metadata helper and Drawer field extension) so this branch has the full self-healing pipeline. The cherry-pick is a separate commit so the two PRs remain reviewable independently.

Test plan

Diff size

3 files changed, 48 insertions(+), 16 deletions(-)

  • palace.rs: add migrate_metadata() call at the top of add_drawer (1 line + comment), iterate + migrate in get_drawers (3 lines + comment)
  • palace/store/usearch_sqlite.rs: convert struct-literal } to ; (let-binding), add migrate_metadata() call (1 line + comment) × 2 functions
  • layers.rs: same let-binding + migrate pattern in Layer1::generate (3 lines + comment)

Series status

# Title Status
6 docs(palace): document Embedder reuse on search_with_embedding merged #16
7 feat(palace): add tags/trust/access_count/reinforcements/superseded_by to Drawer open #17
8 feat(palace): add MemoryProvider::graph_stats_legacy (jcode shape) open #18
1 feat(palace): add boost/decay/reinforce/supersede/set_metadata to MemoryProvider open #19
2 feat(palace): add tag/untag/link/list_tags to MemoryProvider open #20
4 feat(palace): add MemoryProvider::recent for retention-ranked recall open #21
3 feat(palace): add MemoryScope::All and Wing/Room variants open #22
5 feat(palace): add ActivityEvent sink on PalaceBuilder open #23
24 feat(palace): auto-migrate legacy metadata on every read/write this PR (post-series)

🤖 Generated with Claude Code

quangdang46 and others added 2 commits June 3, 2026 18:36
…ments/superseded_by/active to first-class Drawer fields

The new fields mirror values previously stored only in the
`metadata` HashMap under the keys 'tags', 'trust', 'access_count',
'last_accessed', 'reinforcements', 'superseded_by', 'active'. They
are now typed first-class fields for:

  - type-safe access from the upcoming MemoryProvider trait methods
    (boost/decay/reinforce/supersede/tag/link in mp-migration 1+2/8)
  - clean serde round-trip without manual value extraction
  - query-builder support (drawer.tags([...]).trust('high'))

`#[serde(default, skip_serializing_if = ...)]` on every new field
keeps backwards compatibility — drawers serialised before this change
still load cleanly. The reverse direction (writing the typed field)
is handled by `Drawer::migrate_metadata` which is idempotent and
safe to call repeatedly; it lifts legacy metadata keys into typed
fields when those fields are still at their default value, and
cleans the metadata HashMap so the source-of-truth is unambiguous.

`Reinforcement` is promoted to a pub struct in palace.rs (mirrors
jcode's `memory_types::Reinforcement`). `DrawerId` is reused for
`superseded_by` to keep the ID type consistent across the API.

Existing call sites in:
  - crates/core/src/palace/store/usearch_sqlite.rs (Drawer literal x2)
  - crates/core/src/layers.rs:652 (Drawer literal x1)
updated to initialise the new fields with their defaults.

This is PR 7/8 in the jcode → mempalace Mode C library migration
series. Required for PR 1 (mutation methods) and PR 2 (tag/link).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #7 added the new typed Drawer fields (tags, trust, access_count,
last_accessed, reinforcements, superseded_by, active) plus a
`migrate_metadata` helper. The helper was left as opt-in
(consumers had to call it themselves), which meant legacy drawers
written by pre-PR #7 Palace versions would still be read in the
v0 shape (data in `metadata`, typed fields empty).

This PR wires the migration into every read/write path so the
self-healing is automatic and the typed fields are the source of
truth on disk from this point forward.

Wiring:

  - `Palace::add_drawer` (palace.rs:625) — calls
    `drawer.migrate_metadata()` once at the top, before any
    `store.upsert`. Every `add_drawer` write is now a
    self-healing write: legacy drawers that get re-written land
    in the v1 (typed-field) shape automatically.

  - `Palace::get_drawers` (palace.rs:710) — iterates the
    returned drawers and calls `migrate_metadata` on each
    before returning. Every reader (Layer 1 wake-up, status,
    PR #1 mutation methods via `default_mutate_drawer`,
    PR #2 tag/link, PR #4 recent) flows through this single
    chokepoint, so the migration propagates through the whole
    read surface.

  - `palace/store/usearch_sqlite.rs::get_drawer_by_id` and
    `::all_drawers` — call `migrate_metadata` right after
    constructing the Drawer from SQLite rows. Defensive: covers
    the case where a host calls `store.get_drawers()` directly
    (bypassing `Palace::get_drawers`).

  - `layers.rs::Layer1::generate` and `layers.rs::create_test_palace_db`
    (palace_db.rs read path) — same defensive coverage for
    the wake-up layer and tests that go through `PalaceDb::get_all`
    rather than the trait.

The `migrate_metadata` function is left as `pub fn` (not
demoted to `pub(crate)`) to avoid a SemVer-breaking change
on PR #7. Consumers can still call it explicitly if they want
to; they just don't have to.

Includes a cherry-pick of PR #7 (the `migrate_metadata` helper
and Drawer field extension) so this branch has the full
self-healing pipeline. The cherry-pick is a separate commit so
the two PRs are reviewable independently.

This is PR 24/8+ (post-series) — closes the design gap in PR #7
that was called out in the PR #7 review: migrate_metadata
should be mempalace's responsibility, not the consumer's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@quangdang46 quangdang46 force-pushed the feat/mp-pr24-auto-migrate-metadata branch from 01f60a5 to eb9a6bd Compare June 3, 2026 14:40
@quangdang46 quangdang46 merged commit 87a238b into main Jun 3, 2026
4 checks passed
@quangdang46 quangdang46 deleted the feat/mp-pr24-auto-migrate-metadata branch June 3, 2026 14:41
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.

1 participant