Skip to content

MemoryId Inventory

This document is a committed architectural contract. It is the authoritative allocation table for every ic-stable-structures MemoryId across all OhShii canisters (launcher repo + locker repo). Read it — and update it in the same commit — before any change that adds, removes, or relocates a stable structure (StableBTreeMap / StableCell / StableVec / StableLog).

It exists because a single, authoritative table is the only reliable defence against reusing a MemoryId: ic-stable-structuresMemoryManager never reclaims a bucket, so binding a new, layout-incompatible structure to a previously-used id makes the library decode the old bytes — which traps (fixed-size cells / primitives) or silently mis-decodes (BTree node layout).

ic-stable-structuresMemoryManager never reclaims a bucket. When a stable structure is removed, its MemoryId keeps the removed structure’s bytes on every canister that was deployed during that structure’s lifetime. Binding a new, layout-incompatible structure to that same id makes the library decode the stale bytes — which traps (fixed-size cells / primitives, below your Storable::from_bytes, so a decode fallback cannot help) or silently mis-decodes (BTree node layout). Therefore:

  1. A removed MemoryId is RESERVED FOREVER. Never reassign it.
  2. New structures take the lowest NEVER-USED id (per the per-canister “next-free” line below), never a reserved/removed one.
  3. Never tighten a value/key type or shrink a Bounded max_size on an existing id (re-encodes old bytes — treat as a destructive migration).
  4. New persisted record fields are Option<T>, append-last (Candid tolerates missing trailing opt fields; bare-T appends wipe config on upgrade).
  5. One MemoryManager per canister. Never construct a second MemoryManager::init(DefaultMemoryImpl::default()) — two managers on the same DefaultMemoryImpl alias the same bucket table (a latent corruption hazard).
  6. Any layout/MemoryId change requires an UPGRADE-PATH test (deploy the current wasm → populate → --mode upgrade to the new wasm → exercise every public query) as a deploy gate — unit tests and diff-reading cannot catch this class.

1. launcher backend (src/backend/src/memory.rs)

Section titled “1. launcher backend (src/backend/src/memory.rs)”

type Memory = VirtualMemory<DefaultMemoryImpl> over the single MEMORY_MANAGER.

Currently bound:

idstatictype
0LAUNCHED_TOKENSStableBTreeMap<u64, LaunchedToken>
1TOKEN_ID_COUNTERStableCell<u64>
2ICO_CREATION_STATUSStableCell<FeatureStatus>
3ICO_PARTICIPATION_STATUSStableCell<FeatureStatus>
4TOKEN_CREATION_STATUSStableCell<FeatureStatus>
5INDEX_CREATION_STATUSStableCell<FeatureStatus>
6SUBACCOUNT_COUNTERStableCell<u32>
8CRITICAL_LOGSStableBTreeMap<u64, CriticalLogEntry>
9CRITICAL_LOG_COUNTERStableCell<u64>
10LAST_PUBLIC_TIMER_RESTARTStableCell<u64>
11TIMER_RESTART_IN_PROGRESSStableCell<bool>
12BURNING_FEE_CONFIGStableCell<BurningFeeConfig>
13ICO_FEE_CONFIGStableCell<IcoFeeConfig>
14LAST_HOUSEKEEPING_CHECKStableCell<u64>
15BITCOIN_GATEWAY_STATUSStableCell<FeatureStatus> — Bitcoin cross-chain Gateway toggle (frontend-only enforcement)
16SOLANA_GATEWAY_STATUSStableCell<FeatureStatus> — Solana (Menese) Gateway toggle (also enforced in register_menese_user)
17GUEST_FEE_CUSTODY_V2StableBTreeMap<String, ()> — OHSHII guest-fee v2 custody marker (campaign_id present ⇒ per-campaign sub, not MAIN); virgin bucket, SAFE
18GUEST_FEE_FORWARD_DUEStableBTreeMap<String, u64> — Completed-campaign guest-pot forward schedule/latch (campaign_id → due ns = completion+7d); schedule ts, NOT a ledger created_at_time pin; virgin bucket, SAFE
19TREASURY_LIQ_OPSStableBTreeMap<u64, StorableTreasuryLiquidityOp> — treasury-funded ICPSwap liquidity op journal (ONS proposals; persisted before any fund movement, resumed by proposal_retry_treasury_liquidity); virgin bucket, SAFE (2026-07)
20TREASURY_LIQ_OP_COUNTERStableCell<u64> — monotonic id counter for TREASURY_LIQ_OPS; virgin bucket, SAFE (2026-07)
21FINALIZE_DRAIN_PINSStableBTreeMap<String, StorableFinalizeDrainPin> — LGE finalize LP-drain pin (C1/GAP-1/GAP-2 hardening). Keyed by campaign_id, written BEFORE the finalize campaign-sub → pool_manager MAIN LP transfer: pins the drawn AMOUNT and a FIXED created_at_time so a retry re-sends the byte-identical ledger tx (dedup via TxDuplicate→Ok) instead of recomputing a draw from an already-drained subaccount; drained=true = transfer provably landed (retry skips the transfer). Virgin bucket, SAFE (2026-07)
22PENDING_POOL_JOBSStableBTreeMap<String, StorablePendingPoolJob> — detached LGE pool-creation jobs (2026-07 creation-pipeline hardening). Keyed by campaign_id; persisted BEFORE the zero-delay timer that runs the pool phase after create_ico_with_icrc2_payment returns; re-armed from post_upgrade (timers do not survive upgrades); carries canister ids to reveal, liquidity params, at-most-once fund_transfer_started marker and bounded attempts; removed on completion or terminal Frozen. Virgin bucket, SAFE (2026-07)
30USED_RELAUNCH_MEMOSStableBTreeMap<u64, u64>
31PENDING_RELAUNCH_COMMITSStableBTreeMap<String, StorableRelaunchUpdate>
36SHIELD_PREAUTH_MODEStableCell<u8> (relocated from 34)
37USED_POW_NONCESStableBTreeMap<UsedPowNonceKey, ()> (relocated from 32)
38BACKEND_WORKFLOW_RECORDSStableBTreeMap<String, StorableBackendWorkflowRecord> (relocated from 33; folded into the single main MEMORY_MANAGER)
39REFUND_CREATED_ATStableBTreeMap<String, u64> (F2(b) per-(campaign,user) deterministic refund created_at_time)

RESERVED — NEVER reuse: 7 (CAMPAIGN_ID_COUNTER), 32 (PENDING_TOKEN_PURCHASES), 33 (RECOVERY_QUEUE), 34 (PENDING_CLEANUP_CURSOR), 35 (RECENT_APPLIED_CONTRIBUTIONS), 40 (REFERRAL_REFUND_COVERED — removed in the FIX-2 treasury-cover removal, 2026-06). Each held a removed structure; the bucket still carries that structure’s stale bytes, so none of these ids may ever be rebound.

Next free id: 23–29, 41+ (use the lowest for the next new structure; 17 GUEST_FEE_CUSTODY_V2 + 18 GUEST_FEE_FORWARD_DUE bound by the OHSHII guest-fee v2 custody work, 2026-06; 19 TREASURY_LIQ_OPS + 20 TREASURY_LIQ_OP_COUNTER bound by the treasury-liquidity governance work, 2026-07; 21 FINALIZE_DRAIN_PINS bound by the LGE finalize LP-drain-pin hardening, 2026-07; 22 PENDING_POOL_JOBS bound by the LGE creation detached-pool work, 2026-07).

2. dao_storage (src/storage/src/memory.rs)

Section titled “2. dao_storage (src/storage/src/memory.rs)”

Currently bound: 0 CAMPAIGNS, 1 CONTRIBUTIONS, 2 TOKENS, 3 SPONSOR_INFO, 4 USER_SPONSORS, 5 WHALE_PASSES, 6 REFERRALS, 7 CAMPAIGN_BANNERS, 8 SPONSOR_CASE_INSENSITIVE_INDEX, 9 PRINCIPAL_SPONSOR_INDEX, 10 SPONSOR_USERS_INDEX, 11 CREATOR_TOKEN_INDEX, 12 CAMPAIGN_STATE_INDEX, 13 HIGHLIGHTED_CAMPAIGNS_INDEX, 14 TOKEN_ID_COUNTER, 15 CONTRIBUTION_ID_COUNTER, 16 SPONSOR_REGISTRATION_LIMIT, 17 RUGGYS_CHOICE_CACHE, 18 STORAGE_EVENT_LOG, 19 STORAGE_EVENT_COUNTER, 20 LEDGER_TO_CAMPAIGN_INDEX, 22 CONTRIBUTION_ID_INDEX, 24 CONTRIBUTORS_BY_CAMPAIGN, 25 SHIELD_PREAUTH_MODE (StableCell<u8> — bound on a virgin bucket, SAFE), 26 CONTRIBUTION_LIFECYCLE, 27 GUEST_FEE_PAID, 28 GUEST_FEE_AMOUNT (guest-fee gross snapshot for the failure refund, 2026-06), 29 GUEST_FEE_REFUNDED (per-(campaign,user) guest-fee refunded marker, 2026-06), 30 CAMPAIGN_SWEEP_DONE (per-campaign 7-day residual-ICP sweep marker, 2026-06).

RESERVED — NEVER reuse: 21 (DAO_MIGRATION_DONE, StableCell<u64>; migration code removed, const reserved), 23 (CONTRIB_INNER_MIGRATION_FLAG, one-shot flag; migration code removed, const reserved).

Next free id: 31+.

3. ohshii_governance / ONS (src/ohshii_governance/src/memory.rs)

Section titled “3. ohshii_governance / ONS (src/ohshii_governance/src/memory.rs)”

The most-migrated canister. A past proposals migration is the canonical correct pattern: it moved active maps to fresh high slots (PROPOSALS 0→43, EXECUTED_PROPOSALS 2→44, VOTER_HISTORY 3→46, WASM_CHUNKS 4→42) rather than reusing the vacated low ids — with an in-code comment “Using a fresh slot prevents reads against legacy/corrupted node layout.”

Currently bound (selected; full set in code): 1 PROPOSAL_COUNTER, 5 UPLOAD_SESSIONS, 6 SESSION_COUNTER, 7 EVENT_LOG, 8 TIMER_STATE, 9 EVENT_COUNTER, 10 PENDING_SELF_UPGRADE, 11 PROPOSAL_CREATION_FEE, 12 REJECTION_COST, 13 GUARDIAN_PRINCIPAL, 14 MODERATED_PROPOSAL_DESCRIPTIONS, 15 PENDING_DAPP_SELF_STEP (StableCell<StorablePendingDappSelfStep> — Candid Option<PendingDappSelfStep>, Unbounded, decode-hardened → None; virgin, SAFE; UpgradeDappBatch self-last step), 16 PENDING_ASSET_REVOKES (StableBTreeMap<StorableAssetRevokeKey, u64> — asset-Prepare revoke retry queue, key {of_principal, asset_target} natural dedup, value enqueued_at ns, Bound::Bounded{64}, decode-hardened → anonymous sentinel; virgin, SAFE), 40 DECODE_ERROR_COUNT, 41 LAST_DECODE_ERROR_TS, 42 WASM_CHUNKS, 43 PROPOSALS, 44 EXECUTED_PROPOSALS, 45 PROPOSAL_MAP_MIGRATION_STATE, 46 VOTER_HISTORY, 47 PROPOSAL_VOTES, 48 LAST_PUBLIC_TIMER_RESTART, 49 TIMER_RESTART_IN_PROGRESS, 50 PROPOSAL_RATE_LIMITS, 51 VOTING_PARAMS_CONFIG, 53 VERIFIED_USERS, 54 USED_NULLIFIERS, 55–58 World-ID, 59–69 DecideID, 70 ANY_METHOD_VERIFIED_USERS, 71–73/76–78 delegation, 74 REBUILD_STATE, 79 USED_POW_NONCES, 80 OVERRIDE_PROPOSAL_VOTES, 81 SHIELD_PREAUTH_MODE (StableCell<u8> — virgin bucket, SAFE), 82 OVERRIDE_FRESH_ATTESTED (StableBTreeMap<Vec<u8>, u64> — B1 override fresh-personhood marker, value = override_expiry_ns for GC; virgin, SAFE; holds TWO disjoint key shapes in the ONE bucket — the caller-driven SONS-routed path REUSED this id, adding NO new MemoryId: (a) the ONS-self marker "{proposal_id}:{voter}" (leading ASCII digit 0x30..=0x39) read by ONS vote(); (b) the DAO-DIMENSIONED marker [dao_len][dao][proposal_id:8be][voter] from make_override_marker_key (leading length-byte 0..=29) written by the caller-driven attest path and read by the did_attest_override query a SONS pulls — the leading-byte ranges can never overlap → zero collision even when dao == canister_self()), 83 OVERRIDE_PERSONHOOD_NULLIFIERS (StableBTreeMap<Vec<u8>, StorableOverrideNullifierClaim> — B1 one-human → one-ballot authority, key (dao, proposal_id, nullifier), value {voter, override_expiry_ns}; disjoint from 53/54; virgin, SAFE), 84 OVERRIDE_OUTCALL_GLOBAL_CAP (StableCell<u64> — A3 runtime-tunable canister-wide override-outcall cap, default 240/hr, floored on read AND write at OVERRIDE_OUTCALL_GLOBAL_CAP_FLOOR; virgin, SAFE).

LEGACY-ACTIVE (bound; do NOT reuse, do NOT remove without care): 0 LEGACY_PROPOSALS, 2 LEGACY_EXECUTED_PROPOSALS — rebound from the 0→43/2→44 migration with the identical StableBTreeMap<u64, StorableProposal> type (Candid-compatible), read only by the one-time migration fn. Safe because the type is unchanged.

RESERVED — NEVER reuse: 3 (VOTER_HISTORY, abandoned by the proposals migration), 4 (WASM_CHUNKS, abandoned by the proposals migration — has the documented “fresh slot” comment), 52 (DAO_MIGRATION_FLAG, binding removed, const reserved).

Next free id: 17–39, 75, 85. (82 OVERRIDE_FRESH_ATTESTED + 83 OVERRIDE_PERSONHOOD_NULLIFIERS + 84 OVERRIDE_OUTCALL_GLOBAL_CAP bound by the override fresh-personhood re-verification work, 2026-06.)

4. sons_governance / SONS (src/sons_governance/src/memory.rs) — DYNAMIC, per-campaign

Section titled “4. sons_governance / SONS (src/sons_governance/src/memory.rs) — DYNAMIC, per-campaign”

Currently bound (selected): 0 PROPOSALS … 20 UPLOAD_SESSIONS, 22 PENDING_SELF_UPGRADE, 23 DECODE_ERROR_COUNT, 24 LAST_DECODE_ERROR_TS, 25 WASM_CHUNKS (relocated from 21 because the bounds changed — move-to-fresh-id), 26 PROPOSAL_VOTES, 27–32 timer/fee/session, 34 PENDING_DAPP_SELF_STEP (StableCell<StorablePendingDappSelfStep> — Candid Option<PendingDappSelfStep>, Unbounded, decode-hardened → None; virgin, SAFE; UpgradeDappBatch self-last step), 71–78 delegation, 79 SONS_DELEGATION_REQUIRES_VERIFICATION, 80 PERMANENT_VERIFIED_USERS, 81 LGE_COMPLETED_LOCKED, 82 USED_POW_NONCES, 83 OVERRIDE_PROPOSAL_VOTES, 84 SHIELD_PREAUTH_MODE (StableCell<u8> — virgin, SAFE), 85 LAST_PULL_RECOVERY_TIMESTAMP, 86 OVERRIDE_FRESH_ATTESTED (StableBTreeMap<Vec<u8>, u64> — B1 SONS-local override fresh-personhood marker, key "<proposal_id>:<voter>", value = override_expiry_ns; the one-ballot nullifier authority lives on ONS, not here; virgin, SAFE), 35 PENDING_ASSET_REVOKES (StableBTreeMap<StorableRevokeKey, u64> — asset-Prepare revoke retry queue; key (of_principal, asset_target), value enqueued_at ns; virgin bucket, SAFE), 36 REFERRAL_CLAIMS (F4e — StableBTreeMap<Principal, StorableReferralClaim>, deferred-referral per-sponsor claims; Bounded 128; decode-hardened placeholder earned_e8s==0; virgin bucket, SAFE), 37 REFERRAL_COMPLETION_TS (F4e — StableCell<u64>, LGE completion ts = 7-day claim window start; virgin bucket, SAFE), 38 CUSTODY_OPS (per-lock custody isolation, 2026-07 — StableBTreeMap<String, StorableCustodyOp>, fund/sweep/refund queue keyed "F:{lock_id}"/"S:"/"R:", insert-if-absent preserves the pinned dedup key; Unbounded, decode-hardened placeholder lock_id __decode_error__ removed by the processor; virgin bucket, SAFE), 39 LOCK_SUBACCOUNTS (per-lock custody isolation, 2026-07 — StableBTreeMap<String, String>, APPEND-ONLY registry hex(subaccount 32B) → lock_id; treasury-integrity guard consumed by execute_treasury_withdraw; entries never removed; virgin bucket, SAFE).

RESERVED — NEVER reuse: 21 (former WASM_CHUNKS Unbounded; relocated to 25, abandoned bucket holds stale legacy chunk bytes on live per-campaign canisters), 33 (DAO_MIGRATION_FLAG, reserved, no live static).

Next free id: 42–70, 87–89 (40 POOL_POSITIONS + 41 POOL_OP_INTENT bound by the treasury-liquidity governance work, 2026-07). Ids 74 and 75 are intentionally SKIPPED in SONS to keep the ONS/SONS MemoryId layout aligned (ONS binds them for its VERIFIED_USERS-family slots, which SONS does not replicate) — do NOT allocate 74/75 in SONS even though they are per-canister-unbound. (Note: per-campaign SONS canisters are upgraded in place — apply the reserve rule to SONS wasm history exactly as for static canisters.)

5. pool_manager (src/pool_manager/src/memory.rs)

Section titled “5. pool_manager (src/pool_manager/src/memory.rs)”

Currently bound: 0–20 (fully append-only; POOLS, POOL_CANISTER_IDS, POSITION_IDS, POOL_TOKENS, CAMPAIGN_SUBACCOUNTS, CAMPAIGN_ACCOUNT_IDS, TOKEN_LEDGER_IDS, TOKEN_SYMBOLS, TOTAL_FEES_COLLECTED, FEE_DISTRIBUTIONS_COMPLETED, OPERATION_STATE, BACKEND_CANISTER, ADMIN_PRINCIPAL, COLLECTED_FEES_TOTAL, MIGRATION_LOG, MIGRATION_COMPLETED, CLOWN_APPROVALS_GIVEN, FEE_WITHDRAWALS, POOL_EVENT_LOG, POOL_EVENT_COUNTER, PASSCODE_PRICE).

RESERVED — NEVER reuse: 21 (DAO_MIGRATION_DONE, StableCell<u64>; migration code removed, const reserved).

Also bound (stale-line fix, 2026-07 audit): 22 MINT_ATTEMPTS, 23 REFERRAL_DRAIN_TS (F4 — per-campaign deterministic drain created_at_time), 24 REFERRAL_DRAIN_INITBAL (F4 — stable initial balance for the underfunded-clamp) — verified against src/pool_manager/src/memory.rs:63,70,78. Plus 25 TREASURY_POSITIONS (StableBTreeMap<String, StorableTreasuryPosition>), 26 TREASURY_OPS (StableBTreeMap<u64, StorableTreasuryOp>), 27 REMOVAL_OPS (StableBTreeMap<u64, StorableRemovalOp>), 28 REMOVAL_OP_COUNTER (StableCell<u64>) — treasury-liquidity governance work, 2026-07; and 29 POOL_CREATING_STARTED_AT (StableBTreeMap<String, u64>, campaign_id → ns when PoolState::Creating began; the 30-minute staleness escape that lets a wedged pool-creation retry, 2026-07 — src/pool_manager/src/memory.rs:105,506).

Next free id: 30 (25–28 bound by the treasury-liquidity governance work, 29 by the pool-creation staleness-escape hardening, all 2026-07).


Locker repo canisters (canonical here; mirrored to dev-ohshii-locker/docs/MEMORY_ID_INVENTORY.md)

Section titled “Locker repo canisters (canonical here; mirrored to dev-ohshii-locker/docs/MEMORY_ID_INVENTORY.md)”

6. locker backend (dev-ohshii-locker/src/backend/src/memory.rs)

Section titled “6. locker backend (dev-ohshii-locker/src/backend/src/memory.rs)”

Currently bound: 0 SUPPORTED_TOKENS, 1 TRANSFER_MONITORS, 2 LOCK_CHECKS, 3 TEXT_LOGS, 4 CIRCUIT_BREAKERS, 5 CONFIG, 6 PENDING_RECLAIM_FIXUPS, 7 PENDING_UNLOCK_CONFIRMATIONS, 8 USED_POW_NONCES, 9 RECOVERED_ORPHAN_POSITIONS, 10 SHIELD_PREAUTH_MODE (StableCell<u8> — virgin, SAFE), 11 ORPHAN_PENDING_LP (StableBTreeMap<u128, OrphanPendingRecord> — pending-orphan LP journal, complement of id 9; appended on a virgin bucket, SAFE), 12 LOCK_CREATION_STATUS (StableCell<LockCreationStatus> — public lock-creation toggle, gates create_lock_v2; virgin, SAFE), 13 LP_SAGA_FUND_EVENTS (StableBTreeMap<u64, LpSagaFundEventRecord> — LP-lock saga fund-event journal; DEFINITE/AMBIGUOUS-classified money legs append here instead of a blind rollback, queryable via get_lp_saga_fund_events; virgin, SAFE), 14 LP_MINT_INTENTS (StableBTreeMap<StableString, LpMintIntentRecord> — write-ahead LP mint intent, written BEFORE the pool mint so a lost-response mint is recoverable; virgin, SAFE), 15 LP_SAGA_FUND_EVENT_SEQ (StableCell<u64> — monotonic id counter for LP_SAGA_FUND_EVENTS, ids never reused; virgin, SAFE), 16 ORPHAN_PENDING_LP_V2 (StableBTreeMap<StableString, OrphanPendingRecord> — composite (pool, position_id) orphan-pending keyspace; new writes land here, reads fall back to id 11; virgin, SAFE), 17 RECOVERED_ORPHAN_POSITIONS_V2 (StableBTreeMap<StableString, OrphanRecoveryRecord> — composite-key recovered-orphan dedup, complement of id 9; virgin, SAFE). Fully append-only; single MemoryManager. (13–17 all bound by the LP-lock saga hardening, 2026-07.) RESERVED: none. Next free id: 18+.

7. lock_storage (dev-ohshii-locker/src/storage/src/stable_memory.rs)

Section titled “7. lock_storage (dev-ohshii-locker/src/storage/src/stable_memory.rs)”

Currently bound: 0 = MEMORY_MANAGER own bookkeeping id (not a data bucket), 1 LOCKS, 2 USER_LOCKS_INDEX, 3 TOKEN_LOCKS_INDEX, 5 UNLOCK_TIME_INDEX, 6 SUBACCOUNTS, 7 SUBACCOUNT_INDEX, 8 CONFIG, 10 STATUS_INDEX, 11 RETRY_QUEUE (bound but never populated), 12 CREATION_TIME_INDEX, 13 STORAGE_EVENT_LOG, 14 STORAGE_EVENT_COUNTER, 15 BACKED_UP_TOKENS, 16 TOKEN_BACKUP_COUNTER, 17 ARCHIVED_LOCKS, 18 ARCHIVED_USER_INDEX, 19 UNLOCK_SCHEDULES, 20 SHIELD_PREAUTH_MODE (StableCell<u8> — virgin, SAFE), 21 MIN_LOCK_DURATION_DAYS (StableCell<u64>, default 45 — ONS-synced VP-eligibility floor; virgin, SAFE). StorableLockRecord evolves only by append-last Option<T> fields.

RESERVED / virgin (never bound to a static): 4 (ACTIVE_LOCKS_INDEX_ID, const-only), 9 (TOKEN_CONFIG_ID, const-only). Next free id: 4, 9, 22–24 (4 and 9 are virgin reserved consts — safest; id 21 is now bound to MIN_LOCK_DURATION_DAYS).

proxy (dev-ohshii-locker/src/proxy/src/memory.rs): 0 TRANSFERS, 1 STABLE_LOGS, 2 CONFIG (gained a bare u64 pending_transfer_count with #[serde(default)] — VALUE-struct evolution on the same binding, not a collision; recomputed in post_upgrade). Next free: 3–6.

strongbox (dev-ohshii-locker/src/strongbox/src/memory.rs): 0 TRANSFER_LOGS, 1 AUTHORIZED_PROXIES (StableBTreeMap<Principal, ()> — bound on the formerly reserved-UNUSED LOG_COUNTER_ID id; SAFE because it was never bound to any structure (virgin bucket), unlike a removed-structure bucket which is reserved forever), 2 CONFIG. RESERVED: none (id 1’s reserved-unused status was resolved by the virgin-bucket bind). Next free: 3+.


  • Adding a structure: assign the lowest never-used id from the canister’s “Next free id” line. Update this document + the per-canister “Next free” line in the SAME commit.
  • Removing a structure: move its id to that canister’s RESERVED table with the reason. Leave a // RESERVED — never reuse: … comment at the const in memory.rs. Do NOT delete the const if other code still references it; if you delete it, the id is still reserved.
  • Relocating a structure (e.g. to escape a collision): bind it to a fresh never-used id, mark the old id RESERVED, and run the upgrade-path test.
  • Never: reuse a reserved id; construct a second MemoryManager on the same DefaultMemoryImpl; tighten a type or shrink a Bounded max_size on a live id; append a bare-T (non-Option) field to a persisted record.

Audit procedure (validate this inventory against the code)

Section titled “Audit procedure (validate this inventory against the code)”
Terminal window
# 1. Every MemoryId::new( site in both repos (must all map to a canister section above):
rg -n "MemoryId::new\(" --type rust src/ # run in each repo root
# 2. Per canister, full allocation history of its memory file (catches rebindings):
git -C <repo> log --all -p --follow -- <memory_file>
git -C <repo> log --all -p -S 'MemoryId::new(<N>)' -- <src_dir> # per suspicious id
# 3. Reserved-id check: confirm no RESERVED id is bound to a live static today.
# 4. Single-manager check: exactly ONE MemoryManager::init per canister.
rg -n "MemoryManager::init" --type rust src/