{"record":{"id":"e9972cb41c497d65","repo":"astrid-runtime/astrid","slug":"capsule-id-cannot-change-runtime-scope-during","errorCode":null,"errorMessage":"capsule '{id}' cannot change runtime scope during live replacement: running={expected_scope:?}, installed={actual_scope:?}","messagePattern":"capsule '(.+?)' cannot change runtime scope during live replacement: running=(.+?), installed=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":2049,"sourceCode":"        let artifact = capsule_instance_hash(&manifest, &runtime_dir);\n        let system_allowed = self.system_capsules.read().await.contains(id.as_str());\n        let system_runtime = classify_runtime_residency(&manifest, id, system_allowed)?.is_system();\n        if system_runtime && !manifest.mcp_servers.is_empty() {\n            anyhow::bail!(\n                \"system-resident capsule '{id}' cannot host principal-bearing stdio MCP servers\"\n            );\n        }\n        // Replacement authority is the authenticated operator classification\n        // and installed receipt, never the ancestry of `source_dir`.\n        let actual_scope = if system_runtime {\n            astrid_capsule::registry::RuntimeScope::SystemResident\n        } else {\n            astrid_capsule::registry::RuntimeScope::Principal(\n                self.principal_directory.uid_for(principal)?,\n            )\n        };\n        if actual_scope != expected_scope {\n            anyhow::bail!(\n                \"capsule '{id}' cannot change runtime scope during live replacement: \\\n                 running={expected_scope:?}, installed={actual_scope:?}\"\n            );\n        }\n        if let Some(bound) = bound.as_ref() {\n            self.confirm_published_materialization(\n                &runtime_dir,\n                principal,\n                &manifest,\n                &bound.snapshot,\n            )?;\n        }\n        let principal_uid = self.runtime_principal_uid(system_runtime, principal, id)?;\n        let runtime_id =\n            self.capsules\n                .write()\n                .await\n                .reserve_runtime_id(id.clone(), artifact, actual_scope)?;","sourceCodeStart":2031,"sourceCodeEnd":2067,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L2031-L2067","documentation":"A live replacement must keep the capsule's runtime scope (Principal-UID-scoped vs SystemResident) identical to the scope of the currently running instance. Changing scope in-flight would break authority isolation, so the kernel compares the installed scope of the replacement against the running scope and rejects any change.","triggerScenarios":"Live replacement where the running capsule's scope differs from the scope computed for the replacement manifest — e.g., the capsule was added to or removed from `system_capsules` (or residency classification changed) since the running instance started.","commonSituations":"Operator promotes a capsule to system-resident while an old principal-scoped instance is running; the system_capsules entry was removed but a system-resident instance is still live; manifest residency-affecting fields edited during a hot reload.","solutions":["Restore the operator classification so installed scope matches the running scope, then retry.","Unload the running capsule completely, then load the new manifest fresh (which will take the new scope).","Restart the daemon after changing residency classification."],"exampleFix":"// before: capsule was added to system_capsules while running principal-scoped\nkernel.replace_runtime(id, source_dir).await?; // scope change rejected\n// after\nkernel.unload_capsule(id, principal).await?;\nkernel.load_capsule(source_dir, &PrincipalId::default()).await?; // fresh load takes new SystemResident scope","handlingStrategy":"validation","validationCode":"let running_scope = current_scope_of(id);\nlet installed_scope = compute_installed_scope(&source_dir, id)?;\nif running_scope != installed_scope {\n    return Err(anyhow!(\"residency changed; unload+load required instead of live replacement\"));\n}","typeGuard":null,"tryCatchPattern":"match kernel.replace_runtime(id, &source_dir).await {\n    Err(e) if e.to_string().contains(\"cannot change runtime scope\") => {\n        kernel.unload_capsule(id, &principal).await?;\n        kernel.load_capsule(&source_dir, &principal).await\n    }\n    other => other,\n}","preventionTips":["Do not mutate system_capsules membership while instances are live.","Batch residency classification changes with planned restart windows.","Track running scope alongside installed scope in deployment tooling."],"tags":["live-replacement","runtime-scope","invalid-state-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}