Futureproofing memory_id ABI : ic-stable-structures / canic feedback needed

The non-LLM bit

Canic uses a lot of ic-stable-structures memory ids. A veritable smorgasbords worth.

This is the current breakdown, we’ve got hardcoded IDs that tend to change, fragment, and obviously one mismatched ID and you’ve got a major version bump. It’s something that has churned, and not really been improved as we’re pre 1.0 and its fine if we reinstall canisters.


Problem Statement

Canic stable memory IDs are persistent disk layout, but the current model does not treat them as a durable ABI contract.

Memory ownership is derived from runtime declarations and implicit (owner crate, Rust type label) identity, which can drift during refactors, package moves, renames, or framework reorganization. That creates risk of accidental memory reassignment, stranded state, or silent corruption across upgrades.

The current runtime registry is also ephemeral. Historical allocations, retired IDs, and prior ownership are not durably authoritative, which means a runtime reset or declaration change can lose critical ABI history.

Once downstream canisters depend on stable memory layout, incorrect reuse or movement of IDs becomes a permanent compatibility failure, not a normal implementation bug.

Canic therefore needs a strict, persisted stable-memory ABI ledger that:

  • treats stable_key -> memory_id as permanent identity

  • preserves historical ownership and retired allocations forever

  • reserves framework ranges explicitly

  • fails closed during bootstrap before endpoint execution if any memory-layout invariant is violated


Canic v0.38.0 Tentative Design Document

Canic 0.38 Stable Memory ABI

Status

Draft started: 2026-05-16

0.38 is the stable-memory ABI hardening minor. It is a deliberate hard ABI cut.

Backward compatibility with implicit owner/label identity is not a goal.

Stable memory IDs are durable canister disk layout. Treat them like:

  • filesystem block ownership

  • database page ownership

  • protocol field numbers

A memory ID that ever held one logical store must not silently become another logical store.


Core Position

The persisted ledger is authoritative.

Runtime declarations are claims against the ledger.

The only allocation identity is:

stable_key → memory_id forever

Owner crate, package name, Rust type path, module path, and local label are metadata only. They may drift.

Allocation identity must not drift.


Goals

  • Define a durable stable-memory ABI model now, not piecemeal.

  • Reserve 0-79 globally and unconditionally for Canic.

  • Give applications one clear safe range: 80-254.

  • Keep ID 255 reserved for ic-stable-structures.

  • Persist all range and memory ID history in stable memory.

  • Distinguish never-allocated IDs from retired IDs.

  • Keep retired IDs permanently poisoned.

  • Validate runtime declarations against the ledger before endpoint execution.

  • Prevent stable-memory opening or mutation before ABI validation succeeds.

  • Require explicit stable keys for all Canic-owned framework memories.

  • Separate allocation ABI from schema ABI.

  • Reserve space for future allocator, migration, snapshot, and generation metadata without implementing dynamic allocation in 0.38.


Non-Goals

  • No compatibility shim for owner/label-derived identity.

  • No automatic data migration between memory IDs.

  • No application dynamic allocation in 0.38.

  • No reuse of retired IDs, ever.

  • No “best effort” bootstrap if the ledger detects ABI drift.

  • No profile-dependent app ranges.


Global Memory Map

This map is unconditional for Canic-managed canisters:

Range Owner Meaning
0 canic-memory persisted ABI ledger
1-4 canic-memory allocator, migration, generation, and snapshot metadata reserve
5-10 canic-control-plane root / wasm-store control-plane state
11-79 canic-core core runtime state and future framework growth
80-254 applications downstream explicit memory IDs
255 ic-stable-structures internal stable-structures memory

There is no full-stack versus standalone app range distinction in the Canic runtime contract.

Applications should use 80-254.

Standalone canic-memory consumers that do not use the Canic runtime may choose their own policy, but Canic framework code and Canic documentation should teach 80-254 as the safe application range.


Ledger Authority

The ledger is stored at stable memory ID 0.

It is the historical ABI record.

Runtime declarations are not the source of truth. They are current-binary claims that must be checked against the ledger:

declaration: “I claim stable_key X is memory_id Y”
ledger:      “stable_key X is memory_id Y forever”

If the declaration matches the ledger, bootstrap may proceed.

If the ledger has never seen the key or ID, the declaration may allocate a new active record.

If the declaration conflicts with history, bootstrap must fail closed.

The ledger must never be reconstructed from current declarations.

Current declarations can be incomplete, renamed, removed, or reordered.

Historical ABI state must survive those changes.


Stable Identity

Stable keys are explicit ABI names.

Examples:

canic.control_plane.template_manifest.v1
canic.control_plane.template_chunk_payloads.v1
canic.core.auth_state.v1
canic.core.root_replay.v1
canic.core.cycle_tracker.v1

The key namespace should be descriptive and versioned.

The suffix is the ABI generation of that logical memory, not the crate version.

Allowed metadata drift:

same stable_key, same memory_id, owner changed
same stable_key, same memory_id, label changed
same stable_key, same memory_id, Rust type moved
same stable_key, same memory_id, Rust type renamed
same stable_key, same memory_id, package renamed

Fatal allocation drift:

same stable_key, different memory_id

same memory_id, different stable_key

retired memory_id claimed by any stable_key

retired stable_key claimed by any memory_id

range overlaps a historical range owned by another allocation authority

app declaration claims any ID below 80

any declaration claims ID 255


Permanent Tombstones

The ledger must distinguish:

never_allocated
active
reserved
retired

never_allocated means the ID has no historical ABI meaning.

retired means the ID had historical ABI meaning and is now permanently poisoned.

A retired ID is not available capacity. It must never be reused.

Retirement is a tombstone, not deletion.

Tombstones are permanent because:

  • stable memory contents may still exist

  • future rollback may reintroduce old binaries

  • tooling must be able to explain historical ownership


Ledger Records

Header

The ledger header should include:

ledger_schema_version
layout_epoch
current_generation
created_by_canic_version
last_updated_by_canic_version

ledger_schema_version describes the serialization shape of the ledger itself.

layout_epoch is a deliberate, human-reviewed ABI epoch. It is not a migration counter and must not change automatically.

current_generation increments when a validated runtime layout snapshot is committed.


Range Record

Range records should include:

owner
start
end
status: active | reserved | retired
first_seen_version
last_seen_version
first_generation
last_generation
purpose

Ranges are allocation authority boundaries.

They do not replace per-ID stable-key records.


Memory ID Record

Memory ID records should include:

id
stable_key
owner
label
status: active | reserved | retired
first_seen_version
last_seen_version
first_generation
last_generation
schema_version
schema_fingerprint
purpose

Only stable_key and id are allocation identity.

Owner, label, purpose, and version fields are diagnostic metadata.


Allocation ABI vs Schema ABI

0.38 must separate allocation ABI from schema ABI.

Allocation ABI answers:

Which logical memory owns this stable memory ID?

Schema ABI answers:

What data shape lives inside that logical memory?

schema_version is the declared version of the data format inside the memory.

It is owned by the logical store, not by the ledger.

schema_fingerprint is a stable digest or descriptor of the store schema.

It should be stored from day one so tooling has a place to report and compare it.

For 0.38, schema fingerprints should be informational, not fatal.

The fatal checks are allocation checks:

  • key/ID drift

  • ID/key drift

  • tombstone drift

Schema enforcement can become stricter later once schema ownership and migration hooks are fully designed.

Future schema migrations should be explicit store-level workflows.

They must not move memory IDs as a side effect.


Source API

Canic-owned framework state must use explicit keys:

canic_memory::ic_memory_key!(
    "canic.core.auth_state.v1",
    AuthState,
    AUTH_STATE_ID,
)

Framework code must not use implicit owner/label-derived identity.

Legacy convenience APIs:

ic_memory!(Type, ID)

MemoryApi::register(id, owner, label)

These may remain for non-framework explicit-ID users, but they should be documented as convenience APIs that derive a stable key from unstable metadata.

They are not acceptable for Canic-owned framework state.

Preferred runtime-selected API:

MemoryApi::register_with_key(id, owner, label, stable_key)

Long term, downstream applications should also prefer explicit stable keys.

0.38 should move Canic first and document the downstream direction clearly.


Fail-Closed Bootstrap

Bootstrap must guarantee:

  • no endpoint dispatch

  • no stable-memory mutation

  • no stable-memory open

before ABI validation succeeds.

Required ordering:

open only the ledger memory at ID 0

load ledger header and historical records

validate ledger schema version

reserve/validate 0-79 framework ranges

drain runtime range declarations as claims

drain runtime memory declarations as claims

validate every claim against historical ledger records

reject all fatal drift

commit a new registry snapshot generation

publish runtime registry snapshot

allow other stable memories to open

allow endpoint execution

Opening ID 0 is the only allowed pre-validation stable-memory open.

It is the root of validation.

No framework store should open its own stable memory until its declaration has been validated against the ledger.

If this requires delaying memory handle creation behind bootstrap, that is part of 0.38’s correctness work.


Generation Semantics

The ledger should model generations explicitly.

A generation is a validated runtime layout snapshot.

It records that a specific binary declared a specific set of ranges and stable-key mappings and that those claims passed ledger validation.

Generation fields support:

  • interrupted upgrade diagnosis

  • rollback diagnosis

  • detecting whether a binary reached post-validation commit

  • explaining when a memory was first or last seen

  • future migration journal correlation

Interrupted upgrade expectation:

  • If validation fails before generation commit, no new generation is recorded.

  • If validation succeeds but later application startup fails, the committed generation remains useful evidence that the memory ABI was valid.

  • Rollback to an older binary must validate against tombstones and historical key mappings in the same way as any other upgrade.

Recovery expectation:

  • The ledger must be readable even if runtime registry reconstruction fails.

  • Diagnostics should be able to report the last committed generation.

  • Repair tooling may inspect the ledger, but must not mutate allocation history casually.


Future Metadata Reserve

IDs 1-4 are reserved for future canic-memory metadata.

The reserved semantics are:

1 allocator metadata
2 migration journals
3 snapshot / export indexes
4 generation and recovery metadata expansion

0.38 does not need to implement all of these stores.

It must reserve the space and document the intended ownership so future features do not compete with application memory or framework stores.


Future Dynamic Allocation

0.38 should not implement dynamic allocation for applications.

Future dynamic allocation, if added, must still be based on:

stable_key → memory_id forever

The allocator may choose a new ID only for a never-allocated stable key.

It must never reclaim retired IDs.

It must never compact by moving live IDs.


Threat Model

The following are catastrophic ABI violations.

Same Stable Key, Different ID

This means the current binary is trying to open the logical store through a different disk slot.

The old data would be stranded, and the new slot may be empty or contain unrelated data.


Same ID, Different Stable Key

This means the current binary may interpret existing bytes as a different logical store.

That is direct state corruption risk.


Retired ID Reuse

Retired does not mean free.

It means “historically owned, no longer active”.

Reusing it destroys the ability to:

  • roll back

  • audit

  • safely inspect old stable memory


Range Overlap

Range overlap means two allocation authorities believe they may assign the same ID.

Even if no collision exists today, future additions become unsafe.


Runtime Declaration Drift After Persistence

Current declarations are claims, not history.

If a declaration changes after the ledger has persisted historical ownership, the change must be judged against the ledger.

It must not rewrite history.


CI Guardrails

0.38 should add source and runtime guardrails:

  • Fail if Canic-owned framework storage uses ic_memory!(.

  • Fail if Canic-owned stable keys are duplicated in source.

  • Fail if Canic-owned stable keys do not match accepted prefixes:

    • canic.core.*.vN

    • canic.control_plane.*.vN

  • Fail if any Canic-owned declaration claims ID 80-254.

  • Fail if any application-facing documentation says full Canic apps should use IDs below 80.

  • Test:

    • key/ID drift

    • ID/key drift

    • tombstones

    • range overlap

    • ID 255

    • owner/label metadata drift

Useful source scan:

rg "ic_memory!\(" crates/canic-core/src/storage/stable crates/canic-control-plane/src/storage/stable

This should return no framework declarations.


Runtime Introspection

The ledger should be exportable in a machine-readable shape for diagnostics.

The command/API shape can be decided during implementation, but the output should include:

ledger header

range records

memory ID records

current generation

runtime declaration snapshot

validation result

This is diagnostic output, not a second source of truth.


Migration Philosophy

0.38 is the ABI-hardening release.

Compatibility shims for unsafe historical conventions are intentionally avoided.

Correctness and future stability are more important than preserving implicit owner/label behavior.

The hard cut is justified because future storage breaks are much more expensive than a strict minor now.


First Release Scope

0.38.0 should include the complete registry foundation:

  • Ledger at memory ID 0

  • Global Canic reservation 0-79

  • Application range 80-254

  • Permanent tombstone model

  • Explicit stable-key identity

  • Schema metadata fields, stored but not fatal

  • Layout epoch and generation fields

  • Canic-owned ic_memory_key! declarations

  • Framework source guardrails

  • Runtime fail-closed validation

  • Machine-readable ledger export if implementation cost stays small

Do not split the core ABI model across multiple releases.

Later 0.38 patches may refine tooling, but the irreversible invariants should land in 0.38.0.


Please, developers: it would really help me if you could copy and paste this into your LLM of choice and help me refine the design, because it’s got to be… perfect.

Also, any insights into the future of stable structures / wasm multi memory would be helpful as this is so important and we’re starved of information. @dsarlis @ielashi @bjoern

From Opus 4.7

Reviewed the doc carefully, and went back through the ic-stable-structures MemoryManager source/docs to verify the assumptions baked into your reserved-range design. Solid foundation overall — the core position (persisted ledger as authoritative, declarations as claims) is exactly the right framing for a stable-memory ABI. But there are a handful of correctness issues, specification gaps, and a few places where the doc commits less than it should. Going through them in priority order.

Factual / correctness issues

1. “ID 255 is reserved for ic-stable-structures” is misleading.

The MemoryManager only supports up to 255 virtual memories, with valid MemoryId values 0–254 (the dfinity tutorial says this explicitly: “The memory id can be a number between 0 and 254”, and the MemoryManager docs confirm it returns “up to 255 unique instances of VirtualMemory” with the V1 layout running Size of memory 0 through Size of memory 254). ID 255 isn’t “internal stable-structures memory” — it’s the sentinel byte (0xFF) used in the bucket allocation table to mean “unallocated bucket.” It’s not a memory at all.

This matters for two reasons:

  • The doc’s framing implies ID 255 is something you could in principle read or own. You can’t. Calling MemoryManager::get(MemoryId::new(255)) is either a bug or undefined behavior depending on the implementation version.

  • The threat model says “any declaration claims ID 255” is fatal drift. That’s correct policy, but the reason in the doc is wrong. It should be: ID 255 is an invalid sentinel, not a competing owner.

Fix: change the description from “reserved for ic-stable-structures internal memory” to “reserved as the MemoryManager’s unallocated-bucket sentinel; not a usable memory ID.” Same downstream policy; correct reason.

2. The ledger-at-ID-0 ordering is underspecified.

“Open only the ledger memory at ID 0” — but ID 0 in your scheme is a MemoryManager virtual memory ID. To open it you must already have initialized the MemoryManager, which touches page 0 of the raw stable memory (the “MGR” magic header). So the literal invariant “no stable-memory mutation before ABI validation” can’t hold — MemoryManager init may write to stable memory if it’s the first run.

What I think you actually mean is: no other virtual memory may be opened before ledger validation. That’s defensible and important, but the doc should say it that way. And there’s a subtler version of the question worth answering: is Canic assuming the entire underlying stable memory is owned by a single MemoryManager instance? If yes, say so explicitly — that’s itself an ABI commitment (anyone wanting a RestrictedMemory carve-out for non-MemoryManager state is now locked out). If no, you need a story for where the ledger lives that doesn’t depend on MemoryManager.

3. Schema fingerprint storage is undefined.

You say schema_fingerprint belongs to “the logical store, not the ledger” but also that it’s a Memory ID Record field. Pick one:

  • If it’s in the ledger, the store doesn’t need to be opened to read it — good for diagnostics and pre-open validation.

  • If it’s in the store, it can’t be checked until the store opens, which (a) defeats fail-closed pre-validation for schema and (b) means you’re adding implicit framing inside every Canic-managed memory.

I think you want it in the ledger: stored at commit time, written by the binary that declares it, read for diagnostics. The store is unaware of it. The doc should make that explicit, including how it’s computed (build-time const? typetag style? Hash of the Candid/CBOR schema descriptor?). For 0.38 informational use, even a manually-supplied opaque string per declaration is fine — but commit to the shape now so future enforcement has somewhere to land.

Architectural concerns

4. The reserved status is dead text right now.

You list never_allocated | active | reserved | retired for memory ID records, but reserved is never given semantics distinct from never_allocated. When is it set? By whom? What does a runtime claim against a reserved ID do?

Two plausible interpretations:

  • Reserved = “this ID is earmarked by a future Canic feature; nobody may claim it.” Then it’s effectively a tombstone for the future, set in the ledger by a Canic version that doesn’t yet declare a real store there.

  • Reserved = “this ID is pre-allocated by a declaration but not yet committed in a generation.” That’s a different lifecycle and conflicts with your generation model.

If you mean the first one, that’s clean and useful — IDs 1–4 (allocator/migration/snapshot/generation metadata) would all enter the ledger as reserved records from day one, with owner canic-memory and an explicit purpose. Then when 0.40 ships migration journals, the transition is reserved → active, not never_allocated → active. Say so.

5. The ledger’s own ABI evolution isn’t designed.

ledger_schema_version is mentioned but the doc has no story for what happens when you ship ledger_schema_version = 2. The whole point of this design is that you can’t trust history reconstruction from declarations — so the ledger must be readable by both the binary that wrote it and any future binary that wants to upgrade its format.

You probably want at least:

  • Forward-compatibility rule: an older binary reading a newer ledger must either (a) fail closed with a clear “ledger format too new” error, or (b) read a backward-compatible prefix. Decide which.

  • Migration rule: when does a binary rewrite the ledger to a newer format? Only at controller-initiated migration, never silently on bootstrap.

  • Reserve some bytes in the header for forward-compat extension.

Without this, your first irreversible ABI invariant has no escape hatch for its own format bugs.

6. layout_epoch is a wish, not a mechanism.

“It is a deliberate, human-reviewed ABI epoch. It is not a migration counter and must not change automatically.” OK — but how is it changed? CLI subcommand? A field in canic.toml? An ENV var at install time? Some declaration in the binary that requires a checked-in value bump? The doc gestures at the discipline without specifying the hook. This is the kind of soft contract that erodes within a year.

Concrete suggestion: make layout_epoch come from a const in the binary, validated against the ledger at bootstrap. Bumping it requires a code change. The bootstrap path either matches (continue) or refuses to proceed without an explicit --accept-layout-epoch-bump=N controller-initiated upgrade, which writes the new epoch to the ledger.

7. The 0-79 / 80-254 split deserves a sanity check.

You’ve reserved 80 IDs for the framework and 175 for applications. Two questions:

  • Is 80 enough framework headroom? You’ve already burned 11 on canic-memory + canic-control-plane foundational state. With future allocator, migration journals, snapshot indexes, generation expansion, plus whatever 0.40+ brings (audit logs? cross-canister state sync metadata? scaling pool registries?), 69 slots in canic-core could compress faster than you expect — especially if you adopt a one-ID-per-logical-store discipline rigorously.

  • Is 175 enough for an app? For most Canic users, yes easily. But for fleets that want many sharded logical stores in a single canister (e.g., per-shard indices), 175 is tight. Worth acknowledging in the doc: “If you need more logical stores than the application range allows, you’re outside Canic’s recommended pattern; use heap-resident indexing over a smaller number of stable stores, or shard at the canister level.”

The asymmetry between control-plane (5–10, six IDs) and core (11–79, 69 IDs) is also worth a comment. Why so little for control plane? If wasm-store template management ever needs more than six logical stores you’ve boxed yourself in.

8. No rollback story.

You mention rollback in passing — “Rollback to an older binary must validate against tombstones and historical key mappings in the same way as any other upgrade” — but the asymmetry isn’t addressed. The older binary doesn’t know about newer tombstones or newer active IDs. Its declarations are a strict subset. Does validation:

  • Fail if the ledger contains active records the binary doesn’t declare? (Strict — prevents rollback past any new allocation.)

  • Pass as long as nothing the binary declares conflicts with history? (Permissive — allows rollback, but the new IDs remain unowned and the data stranded.)

I think you want the permissive case (rollback is a real operational need), but it needs to be stated, and you need a way to mark “this generation rolled back from generation N+1.” That’s diagnostic gold during a recovery.

Specification gaps

9. Ledger commit atomicity.

“Commit a new registry snapshot generation” — what’s the atomicity guarantee? If the canister traps mid-commit, is the ledger left in a valid state? You need either:

  • Write-ahead pattern: stage the new generation in a scratch region, swap a single pointer/sequence number at the end.

  • Or accept that a partial commit leaves the previous generation as the authoritative one, and the half-written next generation is ignorable garbage.

IC canister execution traps roll back state changes on the standard execution path, but stable memory writes via stable64_write are not automatically rolled back on trap — they’re committed by the system. So if you naively write the new generation in place, you can absolutely end up in a half-written state on a trap during commit. Worth designing for explicitly.

10. Recovery-mode read path.

“The ledger must be readable even if runtime registry reconstruction fails.” Good. But who reads it in that case? Bootstrap can’t proceed; endpoints can’t dispatch. So the read path must be:

  • A specific endpoint that bypasses normal bootstrap (e.g., something only callable by controllers, named canic_ledger_read_raw or similar).

  • Or an out-of-band diagnostic that runs in pre_upgrade of a tooling/repair canister.

Either way, the doc should commit to one recovery surface. Otherwise “readable” is theoretical.

11. Source guardrail implementation isn’t specified.

The rg "ic_memory!\(" crates/... snippet is illustrative, not the actual check. Concretely, will guardrails be:

  • A build.rs in each Canic-owned crate that scans its own source and panics on violation?

  • A workspace-level cargo test that walks the source tree?

  • A pre-commit hook plus CI job?

  • A proc-macro that refuses to expand if invoked from a forbidden path?

The proc-macro option is the strongest (compile-time, can’t be bypassed), and probably the right answer for ic_memory! invocations in framework crates. Worth committing to.

Smaller things worth fixing or clarifying

  • The v1 suffix in stable_key versus the schema_version field on the memory ID record: these do different jobs, but the doc doesn’t explain that clearly. The key’s vN is for retire-and-replace — when you’d otherwise need to “move” a logical store to a new ID. The schema_version is for in-place data format evolution within the same memory. Spell that out with an example: a benign field addition bumps schema_version; a backward-incompatible store rewrite retires foo.v1 and allocates foo.v2 at a fresh ID.

  • “Long term, downstream applications should also prefer explicit stable keys” — this sentence shows up but the migration carrot for app authors isn’t stated. What do they get? Tooling support? Better error messages on drift? Eligibility for a future audit/export feature? Without a payoff, the explicit-key discipline won’t propagate.

  • The stable-key prefix regex (canic.core.*.vN, canic.control_plane.*.vN) should explicitly forbid app authors from using the canic.* prefix at all. It’s implied but not stated; lock it down.

  • Threat-model section: add “same stable_key, different owner field, both records present” as a non-fatal explicit case, since you allow owner drift. Right now you list the fatal cases extensively but never write down the canonical benign-drift cases as ledger-recognized, which makes the policy harder to read.

  • The phrase “framework code must not use implicit owner/label-derived identity” appears in the Source API section, but the actual mechanism for preventing it is in CI Guardrails several sections later. Cross-link them or merge the discussion.

What’s well-designed

For balance: a few things I think the doc gets clearly right.

  • The separation of allocation ABI from schema ABI is the right cut. Most teams conflate them and end up with brittle migration semantics. Doing this in 0.38 with schema enforcement stubbed at informational is a strong call.

  • Tombstones as permanent and explicitly distinct from never_allocated is correct and rare. Lots of registries collapse “free” and “retired” and regret it.

  • Fail-closed bootstrap ordering, including the “no endpoint dispatch before validation” invariant, is exactly the right paranoia level for a disk-layout ABI.

  • The explicit non-goal of “no compatibility shim for owner/label-derived identity” — taking the hard cut now rather than carrying drift indefinitely — is the kind of call that pays off in years two through five and looks reckless in week one. It’s right.

  • Reserving 1–4 for future metadata stores you haven’t built yet is the discipline that keeps future-you from competing with apps for IDs.

Recommended next pass

In rough priority order, I’d fix:

  1. ID 255 framing (factual)

  2. Ledger-at-ID-0 ordering ambiguity around MemoryManager init

  3. Schema fingerprint storage location

  4. reserved status semantics

  5. Ledger commit atomicity given non-rollback of stable writes

  6. layout_epoch mechanism (not just policy)

  7. Ledger’s own ABI evolution rules

  8. Rollback semantics asymmetry

The first three are correctness; the rest are gaps that will bite during implementation rather than design. Everything else above is polish.

If you want, I can take a stab at concrete language for any of these — particularly the schema/allocation ABI separation and the ledger commit atomicity, which I think need worked examples in the spec.

I think I would just use an incrementing key that holds no informational value other than representing a distinct data structure (so ids starting from 0 seems sensible).

Document this in the code base, and make sure whenever there’s a new distinct data type (new version or whatever), just increment the last used id and add a new entry to your documentation (in an append-only way). For maintainability and simplicity, don’t delete old entries, and don’t reuse retired IDs for a different structure (although if you need to technically I think you could do this if you blat the old bytes first because you know nothing is depending on them anymore).

I’d start with just managing it manually like this. Are you saying that this process needs to take place dynamically while an app is running without any coding and/or redeployments?

thanks, good so far - bit drunk and that’s never a good time to ossify a future ABI hard cut. will check tmw

Go Opus!

Please mods this is off topic ^^ this guy deserves a warning at least, possibly a ban.

Spent aobut $500 of credits on this just doing the design. Where’s my grant money? lol. It will be fantastic though, and just because you don’t understand it doesn’t make it slop.