341 lines
No EOL
12 KiB
Markdown
341 lines
No EOL
12 KiB
Markdown
# Per-Distro Installation Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** Implement real AppImage installation in `aim-core` with distro-aware policy resolution, transactional payload and desktop integration, and CLI surfacing of resolved install mode and warnings.
|
|
|
|
**Architecture:** Add a host detection and policy layer ahead of the existing install scaffolding, then turn `integration/install.rs` into a transactional executor that stages payloads, commits managed artifacts atomically, writes desktop integration into policy-selected locations, and only persists registry state after success. Keep distro-specific behavior declarative through `DistroFamily`, `HostCapabilities`, and `InstallPolicy` instead of branching throughout the pipeline.
|
|
|
|
**Tech Stack:** Rust, Cargo workspace, std filesystem APIs, existing `aim-core` domain and registry types, `clap`, `dialoguer`, fixture-backed tests in `crates/aim-core/tests` and `crates/aim-cli/tests`.
|
|
|
|
---
|
|
|
|
### Task 1: Add distro family detection and host capability probing
|
|
|
|
**Files:**
|
|
- Create: `crates/aim-core/src/platform/distro.rs`
|
|
- Create: `crates/aim-core/src/platform/capabilities.rs`
|
|
- Modify: `crates/aim-core/src/platform/mod.rs`
|
|
- Test: `crates/aim-core/tests/platform_detection.rs`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
use aim_core::platform::distro::{detect_distro_family, DistroFamily};
|
|
|
|
#[test]
|
|
fn detects_fedora_family_from_os_release() {
|
|
let distro = detect_distro_family("ID=fedora\nID_LIKE=rhel centos\n");
|
|
assert_eq!(distro, DistroFamily::Fedora);
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test detects_fedora_family_from_os_release --package aim-core --test platform_detection`
|
|
Expected: FAIL because distro detection types do not exist yet
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Add:
|
|
- `DistroFamily`
|
|
- `/etc/os-release` parsing helpers
|
|
- immutable and Nix policy markers
|
|
- helper availability probing for desktop refresh commands
|
|
- directory writability probing for candidate install roots
|
|
|
|
Keep the probing interfaces small and deterministic so tests can inject fake host facts.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test detects_fedora_family_from_os_release --package aim-core --test platform_detection`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/platform/distro.rs crates/aim-core/src/platform/capabilities.rs crates/aim-core/src/platform/mod.rs crates/aim-core/tests/platform_detection.rs
|
|
git commit -m "feat: add distro and capability detection"
|
|
```
|
|
|
|
### Task 2: Introduce install policy resolution
|
|
|
|
**Files:**
|
|
- Create: `crates/aim-core/src/integration/policy.rs`
|
|
- Modify: `crates/aim-core/src/integration/mod.rs`
|
|
- Modify: `crates/aim-core/src/platform/mod.rs`
|
|
- Test: `crates/aim-core/tests/install_policy.rs`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
use aim_core::integration::policy::{resolve_install_policy, IntegrationMode};
|
|
use aim_core::platform::{DistroFamily, HostCapabilities, InstallScope};
|
|
|
|
#[test]
|
|
fn immutable_system_request_downgrades_to_user_when_allowed() {
|
|
let capabilities = HostCapabilities::immutable_user_only();
|
|
let policy = resolve_install_policy(DistroFamily::Immutable, InstallScope::System, &capabilities).unwrap();
|
|
|
|
assert_eq!(policy.scope, InstallScope::User);
|
|
assert_eq!(policy.integration_mode, IntegrationMode::Degraded);
|
|
assert!(!policy.warnings.is_empty());
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test immutable_system_request_downgrades_to_user_when_allowed --package aim-core --test install_policy`
|
|
Expected: FAIL because install policy resolution does not exist yet
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Create:
|
|
- `InstallPolicy`
|
|
- `IntegrationMode`
|
|
- policy resolution for the agreed distro families
|
|
- separation of payload, desktop, and icon roots
|
|
- warning collection for downgraded or conservative behavior
|
|
|
|
Implement only the current agreed rules. Do not add speculative distro exceptions.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test immutable_system_request_downgrades_to_user_when_allowed --package aim-core --test install_policy`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/integration/policy.rs crates/aim-core/src/integration/mod.rs crates/aim-core/src/platform/mod.rs crates/aim-core/tests/install_policy.rs
|
|
git commit -m "feat: resolve per-distro install policy"
|
|
```
|
|
|
|
### Task 3: Turn install scaffolding into a staged payload executor
|
|
|
|
**Files:**
|
|
- Modify: `crates/aim-core/src/integration/install.rs`
|
|
- Modify: `crates/aim-core/src/integration/paths.rs`
|
|
- Modify: `crates/aim-core/src/app/add.rs`
|
|
- Test: `crates/aim-core/tests/install_payload.rs`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
use aim_core::integration::install::stage_and_commit_payload;
|
|
|
|
#[test]
|
|
fn payload_commit_moves_staged_appimage_into_final_location() {
|
|
let outcome = stage_and_commit_payload(/* fixture inputs */).unwrap();
|
|
assert!(outcome.final_payload_path.ends_with(".AppImage"));
|
|
assert!(outcome.final_payload_path.exists());
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test payload_commit_moves_staged_appimage_into_final_location --package aim-core --test install_payload`
|
|
Expected: FAIL because install execution still only exposes path helpers
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Implement:
|
|
- staging download target creation
|
|
- AppImage validation hook
|
|
- executable bit application in staging
|
|
- atomic payload replacement into the managed payload root
|
|
- minimal rollback for payload commit failure
|
|
|
|
Do not write registry state yet.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test payload_commit_moves_staged_appimage_into_final_location --package aim-core --test install_payload`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/integration/install.rs crates/aim-core/src/integration/paths.rs crates/aim-core/src/app/add.rs crates/aim-core/tests/install_payload.rs
|
|
git commit -m "feat: add staged payload install executor"
|
|
```
|
|
|
|
### Task 4: Add desktop integration and refresh handling
|
|
|
|
**Files:**
|
|
- Create: `crates/aim-core/src/integration/desktop.rs`
|
|
- Create: `crates/aim-core/src/integration/refresh.rs`
|
|
- Modify: `crates/aim-core/src/integration/install.rs`
|
|
- Modify: `crates/aim-core/src/integration/mod.rs`
|
|
- Test: `crates/aim-core/tests/install_integration.rs`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
use aim_core::integration::install::execute_install;
|
|
|
|
#[test]
|
|
fn install_writes_desktop_entry_and_reports_refresh_warning_only() {
|
|
let outcome = execute_install(/* fixture with missing helper */).unwrap();
|
|
|
|
assert!(outcome.desktop_entry_path.unwrap().exists());
|
|
assert!(!outcome.warnings.is_empty());
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test install_writes_desktop_entry_and_reports_refresh_warning_only --package aim-core --test install_integration`
|
|
Expected: FAIL because desktop integration and refresh steps do not exist yet
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Add:
|
|
- `.desktop` generation from normalized metadata
|
|
- icon extraction and placement hooks
|
|
- refresh action planning
|
|
- best-effort helper execution for desktop database and icon cache refresh
|
|
- rollback of generated integration files when required integration fails
|
|
|
|
Keep helper execution optional and warning-driven.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test install_writes_desktop_entry_and_reports_refresh_warning_only --package aim-core --test install_integration`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/integration/desktop.rs crates/aim-core/src/integration/refresh.rs crates/aim-core/src/integration/install.rs crates/aim-core/src/integration/mod.rs crates/aim-core/tests/install_integration.rs
|
|
git commit -m "feat: add desktop integration and refresh handling"
|
|
```
|
|
|
|
### Task 5: Persist registry state only after successful install and surface policy in the CLI
|
|
|
|
**Files:**
|
|
- Modify: `crates/aim-core/src/app/add.rs`
|
|
- Modify: `crates/aim-core/src/registry/mod.rs`
|
|
- Modify: `crates/aim-cli/src/lib.rs`
|
|
- Modify: `crates/aim-cli/src/ui/prompt.rs`
|
|
- Modify: `crates/aim-cli/src/ui/render.rs`
|
|
- Test: `crates/aim-cli/tests/end_to_end_cli.rs`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
#[test]
|
|
fn cli_add_installs_and_renders_resolved_mode() {
|
|
let output = run_cli_add(/* fixture query */);
|
|
|
|
assert!(output.contains("installing as user"));
|
|
assert!(output.contains("installed app:"));
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test cli_add_installs_and_renders_resolved_mode --package aim-cli --test end_to_end_cli`
|
|
Expected: FAIL because the CLI still performs registry-backed tracking only
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Change the add flow so it:
|
|
- builds an install plan instead of only a tracking record
|
|
- prints the resolved install mode and warnings before commit
|
|
- persists the final `AppRecord` only after successful install completion
|
|
- renders installed outcomes rather than tracked-only outcomes
|
|
|
|
Preserve review prompts where source ambiguity still exists.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test cli_add_installs_and_renders_resolved_mode --package aim-cli --test end_to_end_cli`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/app/add.rs crates/aim-core/src/registry/mod.rs crates/aim-cli/src/lib.rs crates/aim-cli/src/ui/prompt.rs crates/aim-cli/src/ui/render.rs crates/aim-cli/tests/end_to_end_cli.rs
|
|
git commit -m "feat: execute installs from cli add flow"
|
|
```
|
|
|
|
### Task 6: Lock down rollback and failure semantics
|
|
|
|
**Files:**
|
|
- Modify: `crates/aim-core/src/integration/install.rs`
|
|
- Modify: `crates/aim-core/src/integration/desktop.rs`
|
|
- Test: `crates/aim-core/tests/install_failures.rs`
|
|
- Modify: `README.md`
|
|
|
|
**Step 1: Write the failing test**
|
|
|
|
```rust
|
|
use aim_core::integration::install::execute_install;
|
|
|
|
#[test]
|
|
fn integration_failure_removes_new_payload_and_generated_files() {
|
|
let error = execute_install(/* fixture with forced desktop write failure */).unwrap_err();
|
|
|
|
assert!(error.to_string().contains("desktop integration failed"));
|
|
assert_install_root_is_clean();
|
|
}
|
|
```
|
|
|
|
**Step 2: Run test to verify it fails**
|
|
|
|
Run: `cargo test integration_failure_removes_new_payload_and_generated_files --package aim-core --test install_failures`
|
|
Expected: FAIL because rollback behavior is not complete yet
|
|
|
|
**Step 3: Write minimal implementation**
|
|
|
|
Finish:
|
|
- rollback of newly committed payloads on required integration failure
|
|
- cleanup of generated desktop and icon artifacts
|
|
- warning-only handling for refresh failures
|
|
- README updates describing actual install behavior and degraded cases
|
|
|
|
Do not broaden feature scope beyond the approved design.
|
|
|
|
**Step 4: Run test to verify it passes**
|
|
|
|
Run: `cargo test integration_failure_removes_new_payload_and_generated_files --package aim-core --test install_failures`
|
|
Expected: PASS
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/aim-core/src/integration/install.rs crates/aim-core/src/integration/desktop.rs crates/aim-core/tests/install_failures.rs README.md
|
|
git commit -m "feat: finalize install rollback behavior"
|
|
```
|
|
|
|
### Task 7: Run full workspace verification
|
|
|
|
**Files:**
|
|
- Modify: none unless verification exposes regressions tied to the approved scope
|
|
|
|
**Step 1: Run formatter**
|
|
|
|
Run: `cargo fmt --check`
|
|
Expected: PASS
|
|
|
|
**Step 2: Run lints**
|
|
|
|
Run: `cargo clippy --workspace --all-targets --all-features -- -D warnings`
|
|
Expected: PASS
|
|
|
|
**Step 3: Run full test suite**
|
|
|
|
Run: `cargo test --workspace`
|
|
Expected: PASS
|
|
|
|
**Step 4: Fix only scoped regressions if any appear**
|
|
|
|
If verification fails, make the smallest design-consistent change needed and rerun the affected command before rerunning the full suite.
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "chore: verify per-distro installation implementation"
|
|
``` |