Skip to main content

Crate ic_memory

Crate ic_memory 

Source
Expand description

§ic-memory

Animated warning banner

EARLY INFRASTRUCTURE: validate before opening stable memory.


ic-memory helps Internet Computer canisters avoid opening the wrong stable memory after an upgrade.

The invariant:

Once a stable key is committed to a physical allocation slot, future binaries must either reopen that same stable key on that same slot or declare a new stable key.

It remembers this mapping forever:

logical store -> physical stable-memory slot

If a future version tries to move that store to a different slot, or reuse that slot for a different store, ic-memory rejects the layout before stable-memory handles are opened.

The meme is funny. The bug is not.

Meme showing ic-memory keeping ic-stable-structures stable memory allocations from drifting

§Why Use It?

Use ic-memory when a canister has more than one stable store and the layout can change over time.

It is most useful for:

  • IC frameworks
  • generated canisters
  • multi-store canisters
  • plugin or module systems
  • canister families that evolve across releases
  • any project where stable-memory ID reuse would be a serious bug

You probably do not need it for a tiny canister with one hand-written stable structure and a fixed layout.

§What It Protects Against

The dangerous bug is slot drift.

Version 1 ships with:

app.users.v1  -> MemoryManager ID 100
app.orders.v1 -> MemoryManager ID 101

A later upgrade accidentally ships with:

app.users.v1  -> MemoryManager ID 101
app.orders.v1 -> MemoryManager ID 100

That can still compile. It can even install.

But now the canister may open orders data as users data, and users data as orders data (or more likely just fail to deserialize anything.)

ic-memory catches that mismatch first.

It enforces both directions for active allocations:

  • The same active stable key cannot move to a different physical slot.
  • The same active physical slot cannot be reused by a different stable key.

Retro computer warning: don't overwrite your memory

§How It Fits

ic-stable-structures stores the data.

ic-memory checks that each logical store is still opening the same physical slot it owned before.

A typical framework flow is:

  1. Recover the saved allocation ledger.
  2. Declare the stores this binary expects.
  3. Validate those declarations against history and policy.
  4. Commit the new generation.
  5. Open stable-memory handles only after validation passes.

The important rule: validate layout before touching stable data.

Do not rawdog stable-memory IDs.

§Golden Path

The exact persistence and storage substrate are integration-specific. The safe order is fixed:

recover persisted allocation ledger
declare this binary's expected stable stores
validate declarations against ledger/history/policy
commit the new generation
only then open stable-memory handles

Minimal sketch:

let ledger = recover_allocation_ledger()?;

let declarations = DeclarationCollector::new()
    .with_memory_manager("app.orders.v1", 100, "orders")?
    .seal()?;

let validated = validate_allocations(&ledger, declarations, &policy)?;

let next_ledger = ledger.stage_validated_generation(&validated, runtime_fingerprint)?;
commit_allocation_ledger(&next_ledger)?;

let session = AllocationSession::new(storage, validated);
let orders = session.open(&StableKey::parse("app.orders.v1")?)?;

The helper names for recovery, commit, policy, and storage are placeholders. Frameworks wire those to their own stable-memory persistence and storage substrate. The ordering is the contract.

AllocationLedger::new(...) builds a structurally valid ledger DTO. Use AllocationLedger::new_committed(...) only when you are manually constructing committed ledger state and want the stricter committed-generation checks. Normal integrations should usually recover through the commit/recovery flow instead of hand-assembling committed state.

§Basic Declaration

Declare every stable store with a stable name and a physical slot:

use ic_memory::DeclarationCollector;

let snapshot = DeclarationCollector::new()
    .with_memory_manager("app.orders.v1", 100, "orders")
    .expect("valid allocation declaration")
    .seal()
    .expect("valid declaration snapshot");
assert_eq!(snapshot.len(), 1);

That snapshot is what you validate against the recovered ledger before opening the store.

§Stable Keys

Stable keys are permanent logical store names. They should describe ownership and purpose, not the current memory ID.

Format:

namespace.component.store_or_role.vN

Rules:

  • ASCII only.
  • Lowercase only.
  • Dot-separated segments.
  • Each segment starts with a lowercase letter.
  • Segments may contain lowercase letters, digits, and underscores.
  • No whitespace, slashes, or hyphens.
  • Must end with a nonzero version suffix such as .v1 or .v12.
  • Maximum length is 128 bytes.

Examples:

use ic_memory::StableKey;

StableKey::parse("app.orders.v1").expect("app key");
StableKey::parse("myapp.audit_log.v1").expect("app key");
StableKey::parse("framework.cache.index.v1").expect("framework key");
StableKey::parse("database.users.data.v1").expect("database key");

Suggested namespace conventions:

  • ic_memory.* is reserved for ic-memory governance records.
  • Application-owned stores can use an application namespace, such as app.orders.v1 or myapp.audit_log.v1.
  • Frameworks and generated stores should use namespaces they own, such as framework.cache.index.v1 or database.users.data.v1.

Canic and IcyDB examples:

  • canic.core.* is appropriate for Canic framework-owned stores.
  • icydb.<memory_namespace>.<store_name>.<role>.vN works for generated IcyDB stores, such as icydb.test_db.users.data.v1.

Changing a key creates a new logical allocation identity. If the durable store is the same, keep the stable key and update schema metadata instead.

§Range Authority

Range authority is policy metadata. It does not allocate stable-memory IDs and does not write to the allocation ledger.

Packages should publish only the ranges they own:

use ic_memory::{
    IC_MEMORY_AUTHORITY_OWNER, MemoryManagerRangeAuthority, MemoryManagerRangeMode,
    memory_manager_governance_range,
};

let authority = MemoryManagerRangeAuthority::new()
    .reserve(memory_manager_governance_range(), IC_MEMORY_AUTHORITY_OWNER)
    .expect("ic-memory governance range")
    .reserve_ids(10, 99, "framework.example")
    .expect("framework range");

authority
    .validate_id_authority_mode(42, "framework.example", MemoryManagerRangeMode::Reserved)
    .expect("framework-owned ID");

An open stack composes records from multiple packages and rejects overlaps:

use ic_memory::MemoryManagerRangeAuthority;

let framework_records = MemoryManagerRangeAuthority::new()
    .reserve_ids(10, 99, "framework.example")
    .expect("framework range")
    .to_records();

let database_records = MemoryManagerRangeAuthority::new()
    .reserve_ids(120, 149, "database.framework")
    .expect("database range")
    .to_records();

let authority = MemoryManagerRangeAuthority::from_records(
    framework_records
        .into_iter()
        .chain(database_records)
        .collect(),
)
.expect("non-overlapping package ranges");

assert_eq!(authority.authorities().len(), 2);

A final closed policy may claim the remaining application space and require full coverage:

use ic_memory::{
    IC_MEMORY_AUTHORITY_OWNER, MEMORY_MANAGER_MAX_ID, MemoryManagerIdRange,
    MemoryManagerRangeAuthority, memory_manager_governance_range,
};

let authority = MemoryManagerRangeAuthority::new()
    .reserve(memory_manager_governance_range(), IC_MEMORY_AUTHORITY_OWNER)
    .expect("ic-memory governance range")
    .reserve_ids(10, 99, "framework.example")
    .expect("framework range")
    .allow_ids(100, MEMORY_MANAGER_MAX_ID, "applications")
    .expect("application range");

authority
    .validate_complete_coverage(MemoryManagerIdRange::all_usable())
    .expect("closed policy covers every usable ID");

§Current MemoryManager Rules

For the built-in ic-stable-structures::MemoryManager slot descriptor:

  • IDs 0..=254 are usable stable-memory slots.
  • ID 255 is rejected because it is the unallocated sentinel.
  • IDs 0..=9 are reserved for ic-memory governance.
  • ID 0 is assigned to the allocation ledger.

The crate also exposes range-authority helpers for frameworks that want to split ID ranges between infrastructure and application stores.

Canic currently uses 10..=99 as a framework-reserved example range. That is Canic policy, not an ic-memory rule.

§What It Does Not Do

ic-memory does not replace ic-stable-structures.

It also does not handle:

  • schema migrations
  • schema compatibility or data semantics
  • controller authorization
  • application data validation
  • endpoint routing
  • IC management-canister calls
  • malicious-controller protection
  • disaster recovery

It only protects stable-memory allocation ownership.

§Status

ic-memory is early infrastructure extracted from Canic. The public API is intended to stabilize around persistent allocation ownership, but framework authors should still treat this line as young infrastructure while the standalone boundary settles.

Earlier drafts exposed some durable DTO fields directly. Current versions use checked constructors and accessors so invalid allocation state is harder to construct accidentally.

The non-negotiable invariants are recorded in SAFETY.md.

The protected physical checksum detects torn writes and accidental corruption. It is not a cryptographic integrity mechanism and must not be treated as adversarial tamper resistance. Stable-memory allocation-governance primitives for Internet Computer canister upgrades.

ic-memory prevents stable-memory slot drift.

Once a stable key is committed to a physical allocation slot, future binaries must either reopen that same stable key on that same slot or declare a new stable key.

The crate records and validates durable ownership in both directions: an active stable key cannot move to a different physical slot, and an active physical slot cannot be reused by a different stable key.

The intended integration flow is:

  1. Recover the persisted allocation ledger.
  2. Declare the stable stores expected by the current binary.
  3. Validate those declarations against ledger history and any framework policy.
  4. Commit the next generation.
  5. Only then open stable-memory handles through a validated allocation session.

This crate owns allocation invariants, not framework policy. Namespace rules, range ownership, controller authorization, endpoint lifecycle, schema migrations, and application validation belong to the framework or application.

Use these primitives before opening stable-memory handles. Integrations should recover the historical ledger, declare the stores expected by the current binary, validate declarations against history and policy, commit a new generation, and only then publish a validated allocation session that can open slots through a storage substrate.

The APIs are generic over storage substrates. ic-stable-structures MemoryManager IDs are supported as durable slot descriptors, but this crate is not a replacement for ic-stable-structures and is not Canic-specific.

Re-exports§

pub use bootstrap::AllocationBootstrap;
pub use bootstrap::BootstrapCommit;
pub use bootstrap::BootstrapError;
pub use bootstrap::BootstrapReservationError;
pub use bootstrap::BootstrapRetirementError;
pub use declaration::AllocationDeclaration;
pub use declaration::DeclarationCollector;
pub use declaration::DeclarationSnapshot;
pub use declaration::DeclarationSnapshotError;
pub use diagnostics::DiagnosticExport;
pub use diagnostics::DiagnosticGeneration;
pub use diagnostics::DiagnosticRecord;
pub use key::StableKey;
pub use key::StableKeyError;
pub use ledger::AllocationHistory;
pub use ledger::AllocationLedger;
pub use ledger::AllocationRecord;
pub use ledger::AllocationReservationError;
pub use ledger::AllocationRetirement;
pub use ledger::AllocationRetirementError;
pub use ledger::AllocationStageError;
pub use ledger::AllocationState;
pub use ledger::CURRENT_LEDGER_SCHEMA_VERSION;
pub use ledger::CURRENT_PHYSICAL_FORMAT_ID;
pub use ledger::GenerationRecord;
pub use ledger::LedgerCodec;
pub use ledger::LedgerCommitError;
pub use ledger::LedgerCommitStore;
pub use ledger::LedgerCompatibility;
pub use ledger::LedgerCompatibilityError;
pub use ledger::LedgerIntegrityError;
pub use ledger::SchemaMetadataRecord;
pub use physical::AuthoritativeSlot;
pub use physical::CommitRecoveryError;
pub use physical::CommitSlotDiagnostic;
pub use physical::CommitSlotIndex;
pub use physical::CommitStoreDiagnostic;
pub use physical::CommittedGenerationBytes;
pub use physical::DualCommitStore;
pub use physical::DualProtectedCommitStore;
pub use physical::ProtectedGenerationSlot;
pub use physical::select_authoritative_slot;
pub use policy::AllocationPolicy;
pub use policy::NamespaceAuthority;
pub use policy::RangeAuthority;
pub use schema::SchemaMetadata;
pub use schema::SchemaMetadataError;
pub use session::AllocationSession;
pub use session::AllocationSessionError;
pub use session::ValidatedAllocations;
pub use slot::AllocationSlot;
pub use slot::AllocationSlotDescriptor;
pub use slot::IC_MEMORY_AUTHORITY_OWNER;
pub use slot::IC_MEMORY_AUTHORITY_PURPOSE;
pub use slot::IC_MEMORY_LEDGER_LABEL;
pub use slot::IC_MEMORY_LEDGER_STABLE_KEY;
pub use slot::IC_MEMORY_STABLE_KEY_PREFIX;
pub use slot::MEMORY_MANAGER_DESCRIPTOR_VERSION;
pub use slot::MEMORY_MANAGER_GOVERNANCE_MAX_ID;
pub use slot::MEMORY_MANAGER_INVALID_ID;
pub use slot::MEMORY_MANAGER_LEDGER_ID;
pub use slot::MEMORY_MANAGER_MAX_ID;
pub use slot::MEMORY_MANAGER_MIN_ID;
pub use slot::MEMORY_MANAGER_SUBSTRATE;
pub use slot::MemoryManagerAuthorityRecord;
pub use slot::MemoryManagerIdRange;
pub use slot::MemoryManagerRangeAuthority;
pub use slot::MemoryManagerRangeAuthorityError;
pub use slot::MemoryManagerRangeError;
pub use slot::MemoryManagerRangeMode;
pub use slot::MemoryManagerSlotError;
pub use slot::is_ic_memory_stable_key;
pub use slot::memory_manager_governance_range;
pub use slot::validate_memory_manager_id;
pub use substrate::LedgerAnchor;
pub use substrate::StorageSubstrate;
pub use validation::AllocationValidationError;
pub use validation::validate_allocations;

Modules§

bootstrap
declaration
diagnostics
key
ledger
physical
policy
schema
session
slot
substrate
validation