feat(rust): RunicNPC is required in the Rust bundle (runicnpc stage 9, D310)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 2m13s

- install/update refuse a Rust bundle that carries no RunicNPC, before anything
  is written, and say how to get one that does.
- doctor fails when RunicNPC is missing or was never installed; a file edited
  by hand stays a warning, since it still loads.
- compose-bundles.sh composes no Rust bundle without a RunicNPC release that
  answers the bridge's runicnpc_api (the published one stays), and fails a
  bridge that declares none; every Rust bundle now has `npc`.
- A RunicNPC API-mismatch message lost its line continuation; restored.

The bundle type still parses a bundle without `npc`, so doctor and an old pin
can name it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-06 16:58:31 -05:00
parent 2567cf5dd3
commit 04bab67778
6 changed files with 65 additions and 53 deletions

View File

@@ -183,7 +183,9 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
);
}
// RunicNPC (D224), fetched and checked beside the plugin, before anything is planned.
// RunicNPC (D224), fetched and checked beside the plugin, before anything is planned. It is
// required since RunicNPC's stage 9 (D310): the bridge refuses every NPC an event places
// without it, so a bundle that cannot place it is refused before anything is written.
let npc_released = match &bundle.npc {
Some(component) => {
let path = scratch.path().join(&component.asset.name);
@@ -191,7 +193,8 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
let released = npc::read_tarball(&path)?;
if released.manifest.api != component.api {
bail!(
"RunicNPC in bundle {} answers API {}, but the bundle says {} — refusing a component the bundle does not describe.",
"RunicNPC in bundle {} answers API {}, but the bundle says {} — refusing a \
component the bundle does not describe.",
bundle.bundle,
released.manifest.api,
component.api
@@ -205,7 +208,12 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
}
Some(released)
}
None => None,
None => bail!(
"bundle {} carries no RunicNPC, and Runic Gateway's Rust events need it: without it \
the bridge refuses every NPC an event places. Install the current bundle (leave \
--bundle off), or pin one published with RunicNPC v1.0.0 or later.",
bundle.bundle
),
};
// ── Plan every instance ──────────────────────────────────────────────────