{"record":{"id":"49fc0e020788ac8e","repo":"astrid-runtime/astrid","slug":"lifecycle-manifest-declares-environment-state-but","errorCode":null,"errorMessage":"lifecycle manifest declares environment state but no durable principal UID binding was supplied","messagePattern":"lifecycle manifest declares environment state but no durable principal UID binding was supplied","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/lifecycle.rs","lineNumber":186,"sourceCode":"        external_bus,\n    )\n}\n\n#[allow(clippy::too_many_arguments)]\nfn run_lifecycle_in_scope(\n    target_dir: &Path,\n    wasm_bytes: Vec<u8>,\n    manifest: &CapsuleManifest,\n    _home: Option<&AstridHome>,\n    target_principal: &PrincipalId,\n    principal_uid: Option<astrid_core::identity::PrincipalUid>,\n    principal_storage: Option<RuntimePrincipalStore>,\n    phase: LifecyclePhase,\n    previous_version: Option<&str>,\n    external_bus: Option<EventBus>,\n) -> anyhow::Result<()> {\n    if principal_storage.is_none() && !manifest.env.is_empty() {\n        anyhow::bail!(\n            \"lifecycle manifest declares environment state but no durable principal UID binding was supplied\"\n        );\n    }\n    let kv_store: Arc<dyn astrid_storage::KvStore> = principal_storage.as_ref().map_or_else(\n        || Arc::new(astrid_storage::MemoryKvStore::new()) as Arc<dyn astrid_storage::KvStore>,\n        |store| store.kv(),\n    );\n    let capsule_id = manifest.package.name.clone();\n    let kv = astrid_storage::ScopedKvStore::new(\n        Arc::clone(&kv_store),\n        lifecycle_kv_namespace(target_principal, &capsule_id),\n    )\n    .context(\"failed to create scoped KV store\")?;\n    let event_bus = external_bus.unwrap_or_else(|| EventBus::with_capacity(128));\n\n    // Reuse the current tokio runtime when there is one (CLI's\n    // `#[tokio::main]`, kernel handler thread). Only build a new one\n    // for standalone/test contexts.","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/lifecycle.rs#L168-L204","documentation":"run_lifecycle_in_scope executes a capsule lifecycle phase (install/upgrade/etc.) with a scoped runtime. If the manifest declares env state (manifest.env non-empty), a durable principal UID binding (RuntimePrincipalStore) is mandatory, because environment state must be persisted under a durable principal's storage — a plain in-memory KV would silently lose it. The function bails when env state is declared but principal_storage is None.","triggerScenarios":"Calling run_lifecycle / run_lifecycle_for_principal paths that construct run_lifecycle_in_scope with principal_storage = None while the capsule's lifecycle manifest has a non-empty `env` section — e.g. an install flow that forgot to open/attach the principal store, or running a manifest that newly added env declarations without migrating the runner.","commonSituations":"Adding `env` to a capsule's lifecycle manifest while the host integration still invokes the lifecycle without a principal store; a CLI/daemon code path that lazily skips opening RuntimePrincipalStore for capsules believed to be stateless; test harnesses running lifecycle scripts without storage wiring.","solutions":["Open/attach a RuntimePrincipalStore (durable principal UID binding) and pass it so the lifecycle can persist env state.","If the capsule genuinely needs no persistent env state, remove the `env` section from the lifecycle manifest.","Use run_lifecycle_for_principal (or _with_storage) instead of the storage-less run_lifecycle path for capsules with env declarations."],"exampleFix":"// before\nrun_lifecycle(&capsule, &manifest, LifecyclePhase::Install, None)?; // env declared, no store\n// after\nlet store = RuntimePrincipalStore::open(&home, &principal_uid)?;\nrun_lifecycle_for_principal(&capsule, &manifest, LifecyclePhase::Install, Some(store), None)?;","handlingStrategy":"validation","validationCode":"// caller-side pre-check:\nif !manifest.env.is_empty() && principal_storage.is_none() {\n    return Err(anyhow::anyhow!(\n        \"manifest declares env state; open a RuntimePrincipalStore first\"));\n}","typeGuard":null,"tryCatchPattern":"match run_lifecycle(...) {\n    Err(e) if e.to_string().contains(\"no durable principal UID binding\") => {\n        eprintln!(\"capsule declares env state; use run_lifecycle_for_principal with an open store\");\n    }\n    other => other?,\n}","preventionTips":["Whenever a manifest has an `env` section, always use the *_for_principal / *_with_storage entry points.","Keep manifest env declarations and runner storage wiring in sync during capsule upgrades.","In test harnesses, wire a real RuntimePrincipalStore rather than None when testing stateful capsules."],"tags":["rust","lifecycle","state","configuration"],"backgroundTag":"missing-required-argument","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}