Jump to: What it does · Why it matters · Is it useful? · How it works · Choose an integration style · Quick start · Troubleshooting · Advanced integration
What it does
ic-memory is a safety system for an Internet Computer application's persistent
data.
When an application is upgraded, its code changes but its stored data remains. Each database, log, queue, or settings store must reconnect to the same storage location it used before. If two stores are accidentally swapped, the new code can interpret one kind of data as another.
ic-memory remembers which storage location belongs to each named store. Before
the application opens any of those stores, it checks the new layout against the
saved ownership records. A conflicting upgrade stops with an error instead of
opening the wrong data.
ic-memoryprotects the connection between a store and its storage location. It is not a backup system, a database schema migrator, or a data validator.
Why this matters
Imagine that version 1 of an application stores users and orders separately:
Users -> storage location 100
Orders -> storage location 101
A later version accidentally reverses those locations:
Users -> storage location 101
Orders -> storage location 100
The diagram below shows the same mistake and the point where ic-memory
intervenes:
The program can still compile, and the upgrade can still install. Without an
allocation check, it may then read order records as users and user records as
orders. ic-memory detects the changed ownership before either location is
opened.
This protection matters because a stable-memory location is a durable part of an application's storage layout, even though it can look like an ordinary number in source code.
Is it useful for my application?
ic-memory is most useful for frameworks, generated canisters, multi-store
applications, plugin systems, and canister families that evolve over time.
How it works
The lifecycle has three parts:
- Name each store. The application gives every persistent store a permanent
identity, such as
app.users.v1. - Remember its location. On the first successful installation,
ic-memoryrecords which storage location belongs to each name. - Check before opening. On every installation or upgrade, the new
application version declares the layout it expects.
ic-memorycompares that layout with the remembered one before any application store opens.
If the layouts agree, the application receives permission to open its stores.
If a known name moved, a location changed owner, or another allocation rule is
broken, the check returns an error and ic-memory grants no permission to open
application stores.
This check-and-record operation is called bootstrap. Its important guarantee is validation before open: a diagnostic report or an uncommitted validation result cannot grant access to a store. The runtime publishes permission only after the allocation ledger has been recovered, checked, and durably updated.
The ledger stores one current ownership record per allocated identity. Store schema versions and schema admission belong to the application. Omitted identities retain their IDs to prevent accidental reuse. A commit counter and one checksummed current snapshot support stale-proof checks and corruption detection.
What happens when a check fails?
Bootstrap returns an error before application stores are opened. ic-memory
does not silently repair, move, discard, or reinterpret a conflicting
allocation. A failed attempt publishes no permission to open stores.
Treat the error as an upgrade-safety signal: keep the existing stable memory, inspect the requested keys, host namespace grants, exclusions, policy and diagnostics, then correct the new application version. Do not erase the allocation ledger or replace it with an empty one to make the error disappear. See the symptom-based troubleshooting guide for safe next steps.
What it protects
Removing a store does not make its old location reusable: its key-to-ID binding is permanent. Application retirement and any rule forbidding the same key from reopening belong to the host/storage owner.
Terminology
- A stable key is a permanent store name, such as
app.orders.v1. - A memory ID is the physical
MemoryManagerlocation behind that key. - An authority names the linked component requesting a key.
- A namespace grant admits that authority's keys under a host-selected prefix.
- The allocation pool contains eligible application IDs shared by all owners.
- Bootstrap recovers, admits, resolves, validates and commits before opening.
- The allocation ledger retains permanent key-to-ID bindings, including omitted stores.
The application owns one pool and one bootstrap per backing memory. Libraries contribute key requests, verify that the host included them, and open their committed keys. Owner labels are current host policy; they are not persisted ownership history, caller authentication or a sandbox for linked code.
Quick start for developers
Add the crate:
[]
= "0.46.0"
ic-memory exports Memory, DefaultMemoryImpl and VectorMemory for host
backing setup. It does not re-export raw collections or memory managers.
Production collection integrations belong in IC Storage's governed facade,
using the RuntimeMemory leases obtained from the host's runtime. Memory's
internal manager, ledger and qualification fixtures retain their substrate use.
Declare keys; let the host allocate
Components name their owner and permanent keys without choosing IDs:
const MEMORY_AUTHORITY: &str = "example_app";
const USERS_KEY: &str = "example_app.users.v1";
The application retains this runtime across calls, bootstraps it during
initialization and post-upgrade, and injects opened handles into components.
Use MemoryRuntime::new_with_config for custom bucket geometry and pass the
host policy to bootstrap. The library owns no singleton or thread-local
runtime. Request factories only construct metadata; they never open stores.
Each allocation has at most one live lease per runtime. A second open_memory
returns RuntimeOpenError::AlreadyOpen { id }; RuntimeMemory does not implement
Clone. Transfer the handle into its collection and drop that owner before
reopening. A collection's memory-transfer operation keeps the same lease alive;
failed construction and ordinary unwinding release it when the handle is dropped.
For a log, obtain two different allocations for index and data memory.
The host must retain exclusive custody of the backing: do not create another runtime/manager or perform raw IO against aliases while the runtime or any of its handles lives. A handle can outlive its runtime, so dropping the runtime alone does not permit a replacement. Generic backing aliases cannot be detected by this crate. These are ownership rules for cooperating integrations, not an isolation boundary against arbitrary linked code. Never leak a handle to force reopening; a forgotten lease remains occupied.
Known keys retain their committed IDs. New requests are sorted by key and take the lowest unclaimed ID in the common pool. All retained records, including omitted keys, occupy their IDs permanently. Namespace grants do not divide space between components. Exhaustion rejects the entire attempt.
See examples/key_only.rs and
examples/composed_host.rs.
Applications composed from several libraries
Each selected component supplies its complete request list. The host combines
those lists into one SealedDeclarationSnapshot, admits disjoint namespaces
for their named owners and supplies explicit physical
exclusions for unmanaged MemoryManager clients. All components draw from the
same remaining pool. The application bootstraps the combined layout once.
Libraries adopting an already bootstrapped host can verify that all of their requirements were included without rerunning bootstrap or replacing the host's policy:
Duplicate keys, overlapping namespace grants, foreign owner claims and excluded historical IDs fail before application stores open. Populated IDs without a ledger record must be explicitly excluded by the host; bootstrap refuses to infer custody from stored bytes.
The runtime reserves IDs 0..=9 and keys under ic_memory.* for its
own governance records. ID 0 contains the allocation ledger. Application
code may use IDs through 254; ID 255 is the upstream unallocated sentinel
and can never be declared.
Stable keys
A stable key names the durable identity of a store, not its current numeric location:
namespace.component.store_or_role.vN
use StableKey;
parse.expect;
parse.expect;
parse.expect;
Changing a key creates a new allocation identity. If the durable store is still the same store, keep its key and apply schema changes through the storage owner.
Frequently asked questions
No. It validates allocation identity. Schema and data migrations remain the application's responsibility.
No. Keep a separate backup and disaster-recovery plan. ic-memory fails closed
when its allocation metadata cannot be recovered safely.
Bootstrap returns an error and publishes no open capability. Fix the proposed layout or policy; do not erase the ledger to bypass the conflict.
No. Its permanent key-to-ID binding prevents another key from opening unrelated data at that location. The host/storage owner decides whether the same key may reopen a logically retired store.
No. One owner bootstraps each concrete runtime. Libraries verify their requirements against the host's committed layout and open only their keys.
Each library declares permanent keys and its owner label. The host admits the namespace and owns physical exclusions; Memory assigns IDs and retains them.
Yes, provided its key is new, the host admits its namespace and owner, the common pool has space, and the complete layout passes admission and validation.
Operations and advanced integration
Custom policies return PolicyIdentity directly from runtime_bootstrap_identity.
Construct it with PolicyIdentity::new(host_token), where host_token is one
[u8; 32] identifying the complete prepared policy configuration and semantics.
Prepare any fallible host configuration first. Hosts own descriptive labels,
versions and token derivation; change the token when that policy changes. Memory
compares all token bytes for warm binding and does not persist or hash them.
Diagnostic JSON returns token arrays; fixture doctor reports include the tested token.
Production recovery and inspection use the host runtime's diagnostic_export,
commit_recovery_diagnostic, memory_allocations and memory_allocation_summary.
Hosts own operator response assembly from these bounded queries and typed errors.
The combined testing::doctor_report(&runtime, &declarations, &pool, &policy)
preflight helper and its report types require test-support. It is read-only and
never runs recovered-admission preparation or grants open authority. Raw ledger
DTOs, bounded raw commit-store readers and passive export construction also
require test-support; recovered authority remains internal. Bounded Cell payload
inspection remains available without granting allocation authority.
The current durable ledger uses checked numeric slots and the ICMKEY01
checksummed snapshot. Its protected header owns the generation; the bounded
CBOR byte-string payload contains only the allocation-record array. Recovery
constructs the logical ledger with that checked generation. The 0.48.0 format
cut retires earlier images, including ICMSNAP1 and pending ICMREC01: use
fresh/reset memory. Invalid
or incompatible bytes reject without initializing genesis or clearing memory.
See the current ledger contract.
For fixture runtimes, opt in to test-support and call testing::fixture_runtime
with explicit backing memory, owner, namespace, stable-key list and bucket
configuration. It returns MemoryRuntime for opening multiple allocations or
exercising refused opens, without initializing collections. Setup uses the
ordinary generic allocation policy and has typed FixtureRuntimeError failures;
no global manager or production admission policy is installed.
For fixture-only byte cells, opt in to test-support and construct
testing::FixtureCell with explicit backing memory, owner, namespace, key and
MemoryManagerConfig. The Cell handle retains its backing; the temporary
bootstrap runtime is dropped. No lifecycle handler or singleton is installed.
Use create_bounded or open_bounded for explicit fresh/retained admission
with a caller-selected byte limit. Setup failures retain their typed category
under FixtureCellError::Setup(FixtureRuntimeError).
Retained headers are checked before payload loading; malformed cells are refused
without initialization. try_set refuses oversized writes before mutation;
into_memory transfers the admitted handle for caller-owned raw inspection.
Callers keep codecs,
work budgets and restored-state/lifecycle validation; fresh empty bytes must
never substitute for a required retained journal. Bootstrap may update ledger
metadata before a later Cell refusal, and backing IO can still panic.
For frozen sparse backing fixtures, testing::memory_from_sparse_hex expands
ordered (offset, hex) runs into fresh zero-filled VectorMemory with an explicit
page ceiling. It checks bounds and hex before backing allocation and reports
typed failures. Callers retain provenance, schema and payload validation. This
helper does not open a runtime, recover authority or convert retained formats.
Production requests, namespace grants and allocation pools use checked Rust
constructors. Their serialization remains available for diagnostic output;
Deserialize requires test-support for explicit fixtures. Applications that
accept external configuration own their input DTOs and pass decoded fields to
Memory's constructors. This does not change durable ledger decoding.
Resolved numeric declarations are read-only runtime outputs. Production callers
submit MemoryRequest values and borrow runtime.committed_allocations() after
bootstrap; the returned generation is also available through
committed_generation(). Explicit numeric declaration builders and decoding
require test-support; their construction errors live in testing.
Per-ID physical reports contain numeric facts only. Hosts project current labels
and eligibility from the runtime's borrowed bindings and allocation_pool();
see operations guidance.
- Operations and diagnostics explains memory attribution, bucket configuration, and runtime reports.
- Troubleshooting maps common symptoms to safe recovery steps.
- Advanced ic-memory covers explicit composition, custom policies, recovery, and runtime-owned persistence.
- Safety invariants records the guarantees future changes must preserve.
- Documentation index separates current guidance from historical engineering evidence.
- Release guide is for maintainers preparing and publishing a release.
ic-memory is pre-1.0 infrastructure extracted from Canic. Current releases use
the current API and durable format only; earlier pre-1.0 wire formats are not a
compatibility target.