Hi everyone,
We’d like to share a proposal to update certain data structures in the management canister interface to make them more future-proof.
Currently, some records are defined in ways that limit their ability to evolve. For example:
type change = record {
timestamp_nanos : nat64;
canister_version : nat64;
origin : change_origin;
details : change_details;
};
with change_details defined as a variant type:
type change_details = variant {
creation : record {
controllers : vec principal;
environment_variables_hash : opt blob;
};
code_uninstall;
code_deployment : record {
mode : variant { install; reinstall; upgrade };
module_hash : blob;
};
load_snapshot : record {
canister_version : nat64;
snapshot_id : snapshot_id;
taken_at_timestamp : nat64;
source : variant {
taken_from_canister : reserved;
metadata_upload : reserved;
};
};
controllers_change : record {
controllers : vec principal;
};
};
The problem
Candid variant types are closed enums. Once you define a set of branches, you cannot safely:
-
Rename branches
-
Add new branches without creating a breaking change
In Candid, new variant branches are not ignored. Decoding using the legacy type fails upon encountering an unknown branch in the new type. That means evolving a type like change_details is not possible without possibly breaking existing canisters.
The limitation does not just affect change_details. Other types that use variants—such as source and globals in ReadCanisterSnapshotMetadataResponse—face the same issue. Both are variant-based and therefore cannot evolve safely in the current form. For the same reasons as with change_details, these fields also need to become optional so that they can be extended in the future without introducing breaking changes.
Proposed solution
To preserve flexibility and forward compatibility, we propose making the fields optional:
type change = record {
timestamp_nanos : nat64;
canister_version : nat64;
origin : change_origin;
details : opt change_details;
};
Similarly, in ReadCanisterSnapshotMetadataResponse, the source and globals fields will also be migrated to optional fields:
type read_canister_snapshot_metadata_response = record {
source : opt variant { /* ... */ };
globals : vec opt variant { /* ... */ };
// ... other fields remain unchanged ...
};
This way:
-
Future changes can introduce new variant branches without breaking existing consumers.
-
We give ourselves the ability to evolve the schema gradually, rather than being locked into today’s definition.
Why this matters
Core management canister records are long-lived pieces of state that must remain stable over time. As features evolve (e.g., new settings, new operations, or extended snapshot metadata), these records will need to be extended. Without this adjustment, each such change risks breaking compatibility with existing tooling and canisters.
By making these fields optional, we ensure long-term stability while still allowing future extensibility.