feat(install): route any manifest-known app through the orchestrator
package.install routed to the manifest-driven orchestrator only for a hardcoded per-app allowlist; every other app fell through to the legacy flow, which ignores manifests entirely and creates a bare container with no ports or volumes (strfry crash-looped this way on .228, 2026-07-09). New ContainerOrchestrator::knows_app(app_id) (default false; prod checks its loaded-manifest map, disk + signed-catalog overlay). The install gate is now allowlist OR knows_app — no per-app Rust for manifest-driven apps, matching the packaging invariant. Apps without a manifest keep the legacy flow including the container-exists adopt block; the unknown-app_id → legacy fallback inside the orchestrator branch is unchanged. Tests: knows_app_reflects_loaded_manifests + install suite 37/37. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
707c912606
commit
55c20e0d6e
@ -330,8 +330,20 @@ impl RpcHandler {
|
||||
// mode).
|
||||
// The adoption block is being phased out as apps move to the
|
||||
// orchestrator path. Non-orchestrator apps still hit it.
|
||||
let orchestrator_managed =
|
||||
should_try_orchestrator_install(package_id, self.orchestrator.is_some());
|
||||
let orchestrator_managed = match self.orchestrator.as_ref() {
|
||||
// Migration allowlist OR any app whose manifest the orchestrator
|
||||
// knows (disk or signed-catalog overlay). A manifest-driven app
|
||||
// must never fall through to the legacy flow — it ignores the
|
||||
// manifest entirely and creates a bare container with no ports or
|
||||
// volumes (strfry, 2026-07-09).
|
||||
Some(orch) => {
|
||||
should_try_orchestrator_install(package_id, true)
|
||||
|| orch
|
||||
.knows_app(orchestrator_install_app_id(package_id))
|
||||
.await
|
||||
}
|
||||
None => false,
|
||||
};
|
||||
|
||||
// Check if container already exists (legacy adoption — non-orchestrator
|
||||
// apps only).
|
||||
|
||||
@ -3610,6 +3610,10 @@ enum HookOutcome {
|
||||
|
||||
#[async_trait]
|
||||
impl ContainerOrchestrator for ProdContainerOrchestrator {
|
||||
async fn knows_app(&self, app_id: &str) -> bool {
|
||||
self.state.read().await.manifests.contains_key(app_id)
|
||||
}
|
||||
|
||||
async fn install(&self, app_id: &str) -> Result<String> {
|
||||
{
|
||||
let mut state = self.state.write().await;
|
||||
@ -4679,6 +4683,27 @@ app:
|
||||
assert!(!calls.iter().any(|c| c.starts_with("build_image:")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn knows_app_reflects_loaded_manifests() {
|
||||
// The RPC install gate routes any app with a known manifest through
|
||||
// the orchestrator (no per-app allowlist). knows_app must be true for
|
||||
// a loaded manifest and false otherwise — a false positive would send
|
||||
// a manifest-less app into `loaded()`'s unknown-app_id error path,
|
||||
// skipping the legacy adopt block; a false negative sends a
|
||||
// manifest-driven app to the legacy flow, which creates a bare
|
||||
// container (strfry, 2026-07-09).
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
let orch = orch_with(rt).await;
|
||||
assert!(!orch.knows_app("strfry").await);
|
||||
orch.insert_manifest_for_test(
|
||||
pull_manifest("strfry", "docker.io/dockurr/strfry:1.0.4"),
|
||||
PathBuf::from("/tmp/fixtures/strfry"),
|
||||
)
|
||||
.await;
|
||||
assert!(orch.knows_app("strfry").await);
|
||||
assert!(!orch.knows_app("no-such-app").await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mempool_core_rpc_host_follows_bitcoin_node() {
|
||||
// B12: mempool's CORE_RPC_HOST must resolve to whichever Bitcoin node
|
||||
|
||||
@ -25,6 +25,16 @@ pub trait ContainerOrchestrator: Send + Sync {
|
||||
/// to a manifest the orchestrator already knows about.
|
||||
async fn install(&self, app_id: &str) -> Result<String>;
|
||||
|
||||
/// True when this orchestrator holds a manifest for `app_id` (disk or
|
||||
/// signed-catalog overlay) — i.e. `install(app_id)` would not fail with
|
||||
/// "unknown app_id". Lets the RPC layer route any manifest-driven app
|
||||
/// through the orchestrator without a per-app allowlist. Defaults to
|
||||
/// `false` so orchestrators without a manifest registry keep routing
|
||||
/// through the legacy install flow.
|
||||
async fn knows_app(&self, _app_id: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Start an already-created container.
|
||||
async fn start(&self, app_id: &str) -> Result<()>;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user