Skip to content

feat(release-runscript): set engine-controller authorized_caller on upgrade#10728

Open
pietrodimarco-dfinity wants to merge 3 commits into
masterfrom
claude/engine-controller-upgrade-bfdb43
Open

feat(release-runscript): set engine-controller authorized_caller on upgrade#10728
pietrodimarco-dfinity wants to merge 3 commits into
masterfrom
claude/engine-controller-upgrade-bfdb43

Conversation

@pietrodimarco-dfinity

@pietrodimarco-dfinity pietrodimarco-dfinity commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

The release-runscript release tool hardcoded () as the engine-controller upgrade argument in the Create Proposal Texts step. () cannot set the canister's authorized_caller, so it was impossible to change the authorized caller through the normal release flow.

This changes the engine-controller branch to prompt for the authorized caller principal and build the upgrade arg:

(opt record {
    authorized_caller = opt principal "<CALLER>";
    initial_dkg_subnet_id = null
})

Why

The engine-controller's post_upgrade re-applies its init args on every upgrade (apply_init_args in rs/engine_controller/canister/canister.rs). If authorized_caller is null, the canister falls back to its built-in DEFAULT_AUTHORIZED_CALLER. So to set (or retain) a specific authorized caller, the value must be supplied on each release — which the tool previously couldn't do.

The candid string is passed as CANDID_ARGS to prepare-nns-upgrade-proposal-text.sh, which encodes it via didc encode | xxd -r -p to compute the proposal's arg_hash and the verification section.

🤖 Generated with Claude Code

…pgrade

The release-runscript hardcoded `()` as the engine-controller upgrade
argument, which cannot set the canister's authorized caller. Change the
Create Proposal Texts step to prompt for the authorized caller principal
and build the upgrade arg:

    (opt record {
        authorized_caller = opt principal "<CALLER>";
        initial_dkg_subnet_id = null
    })

This candid string is passed as CANDID_ARGS to
prepare-nns-upgrade-proposal-text.sh, which encodes it with
`didc encode | xxd -r -p` to compute the proposal's arg_hash and
verification section.

An empty answer emits `authorized_caller = null`, making the canister
fall back to its built-in default caller (rather than the previous
behavior of producing an invalid `opt principal ""`). The canister
re-applies args on every post_upgrade, so the caller must be supplied
each release to retain it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the release-runscript NNS release tool so engine-controller upgrades can set (or retain) authorized_caller by prompting for a principal and constructing a non-empty Candid upgrade argument, instead of hardcoding ().

Changes:

  • Prompt for an engine-controller authorized_caller principal during proposal-text generation.
  • Build a structured (opt record { ... }) Candid upgrade arg (with initial_dkg_subnet_id = null) for engine-controller upgrades.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rs/nervous_system/tools/release-runscript/src/main.rs
pietrodimarco-dfinity and others added 2 commits July 15, 2026 09:30
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@pietrodimarco-dfinity
pietrodimarco-dfinity marked this pull request as ready for review July 15, 2026 08:21
@pietrodimarco-dfinity
pietrodimarco-dfinity requested a review from a team as a code owner July 15, 2026 08:21

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):

  1. Update unreleased_changelog.md (if there are behavior changes, even if they are
    non-breaking).

  2. Are there BREAKING changes?

  3. Is a data migration needed?

  4. Security review?

How to Satisfy This Automatic Review

  1. Go to the bottom of the pull request page.

  2. Look for where it says this bot is requesting changes.

  3. Click the three dots to the right.

  4. Select "Dismiss review".

  5. In the text entry box, respond to each of the numbered items in the previous
    section, declare one of the following:

  • Done.

  • $REASON_WHY_NO_NEED. E.g. for unreleased_changelog.md, "No
    canister behavior changes.", or for item 2, "Existing APIs
    behave as before.".

Brief Guide to "Externally Visible" Changes

"Externally visible behavior change" is very often due to some NEW canister API.

Changes to EXISTING APIs are more likely to be "breaking".

If these changes are breaking, make sure that clients know how to migrate, how to
maintain their continuity of operations.

If your changes are behind a feature flag, then, do NOT add entrie(s) to
unreleased_changelog.md in this PR! But rather, add entrie(s) later, in the PR
that enables these changes in production.

Reference(s)

For a more comprehensive checklist, see here.

GOVERNANCE_CHECKLIST_REMINDER_DEDUP

@zeropath-ai

zeropath-ai Bot commented Jul 15, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 4238dde.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/nervous_system/tools/release-runscript/src/main.rs
    Modify engine-controller upgrade_arg handling to prompt for authorized caller principal and construct a non-empty record for NNS proposal text script execution

@daniel-wong-dfinity-org-twin

Copy link
Copy Markdown
Contributor

I removed ephemera content from PR description (note to reviewer, and test status sections). PTAL (in case you want to restore any of that content).

Why such content does not belong: The PR (title and) description are used as the main content of the eventual commit message. Therefore, it should not contain information that is only relevant at review time, at least IMHO.

OTOH, GitHub really does not provide a good way to convey such information. What I like to do is reply to my own PR, but this can easily be missed by reviewers.

Comment on lines +321 to +322
// given a non-empty record; otherwise the upgrade silently rolls
// back while the proposal still reports as executed. We prompt for

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while the proposal still reports as executed

This is tangential. Therefore, I would either not mention it, or put it in partheneses. I would also maybe mention that this behavior is a bug.

(We have no plan to fix it, because while it is pretty misleading, it's not "fatal".)

Comment on lines +331 to +350
// An empty answer emits `null`, which makes the canister
// fall back to its built-in default authorized caller; a
// non-empty value is wrapped in `opt principal "..."`.
// The canister re-applies this on every post_upgrade, so
// the caller must be supplied each release to retain it
// (otherwise it reverts to the default).
let new_caller = input(
"Authorized caller principal for engine-controller? (leave empty for canister default)",
)?;
let authorized_caller = if new_caller.is_empty() {
"null".to_string()
} else {
let principal = candid::Principal::from_text(&new_caller).map_err(|e| {
anyhow::anyhow!("Invalid principal '{new_caller}': {e}")
})?;
format!("opt principal \"{}\"", principal.to_text())
};
let upgrade_arg = format!(
"(opt record {{ authorized_caller = {authorized_caller}; initial_dkg_subnet_id = null }})"
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spinning this out. In general, match arms should be short.

Comment on lines +331 to +336
// An empty answer emits `null`, which makes the canister
// fall back to its built-in default authorized caller; a
// non-empty value is wrapped in `opt principal "..."`.
// The canister re-applies this on every post_upgrade, so
// the caller must be supplied each release to retain it
// (otherwise it reverts to the default).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty answer emits null

This is just a direct transcription of code to prose. And the code is not particularly opaque. Do not write such comments. Comments are supposed to supply information that is not readily gleaned from the code, such as intent.

a non-empty value is wrapped in opt principal "..."

Ditto: this is just code to prose transcription.

makes the canister fall back to its built-in default authorized caller

This seems tangential to the code being commented.

The real beneficiary of this information is the user of this program, not maintainer. But the user cannot read this comment. What the user can see is the prompt, which already has some explanation (albeit very brief): "(leave empty for canister default)". Therefore, this doesn't seem to add information beyond what can already be readily gleaned from the code.

The canister re-applies this on every post_upgrade, so the caller must be supplied each release to retain it (otherwise it reverts to the default).

Ditto: this is more of a concern for the user of this program, not maintainer.

TANGENT: Wouldn't it make more sense for the Engine Controller canister to not require passing the same information on every upgrade? Seems pretty surprising.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants