aim/.audits/2026-03-21T02-02-28Z.md

23 KiB

Repository Audit: aim (AppImage Manager)

Timestamp: 2026-03-21T02:02:28Z (UTC)
Commit: f260790d91
Branch: main
Repo Path: /home/stoorps/repos/aim
Audit Scope: Full repository — product completeness, provider support, registry operations, docs-vs-code mismatches, missing tests, v1.0 readiness


Executive Summary

The aim AppImage manager has a solid architectural foundation with clean domain separation, good test coverage for GitHub flows, and working install/update/remove operations. However, critical gaps exist that block production v1.0 readiness:

  • Download reliability is insufficient — entire files loaded into memory, no resume/retry, no timeouts, risking OOM on 500MB+ AppImages
  • Registry safety is weak — no atomic writes, no backups, no corruption recovery, vulnerable to concurrent access
  • Documentation is stale — README omits SourceForge, zsync, custom-json providers already in code
  • Missing product features — no search, no version pinning, no dry-run, no info command, no rollback
  • Checksum verification exists but is unused — metadata contains SHA-512 checksums that are parsed but never validated during downloads
  • CustomJsonAdapter is a non-functional stub — declared but all methods return errors

v1.0 Readiness Verdict: Not ready. Core reliability gaps (download safety, registry atomicity, checksum validation) must be closed before production use. Feature gaps (search, info, dry-run) are acceptable for v1.0 but limit user expectations.


Surface Map

Entry Points

  • CLI: aim <query>, aim, aim update, aim list, aim remove <query>
  • Scope Overrides: --user, --system
  • Environment Variables: AIM_REGISTRY_PATH, AIM_GITHUB_TOKEN, GITHUB_TOKEN, AIM_GITHUB_API_BASE, HOME

Public Interfaces

  • Supported Query Forms:
    • GitHub shorthand: owner/repo
    • GitHub URLs: repository, release, asset
    • GitLab URLs: repository, release-like, ambiguous candidates
    • SourceForge URLs: project, file download paths
    • Direct HTTPS/HTTP URLs
    • Local file imports: file://...

Implemented Providers

  • Repository-backed: GitHub, GitLab, SourceForge
  • Exact-resolution: DirectUrl, File
  • Metadata-only: Zsync (update channels), Electron Builder (checksums)
  • Stub: CustomJson (all operations return errors)

State & Data Layers

  • Registry: TOML file at ~/.local/share/aim/registry.toml, stores installed apps with source, version, update strategy
  • Install Paths:
    • User scope: ~/.local/lib/aim/appimages, ~/.local/share/applications, ~/.local/share/icons
    • System scope: /opt/aim/appimages, /usr/share/applications, /usr/share/icons
  • Staging: .local/share/aim/staging for download-before-commit

Cross-Cutting Concerns

  • Platform Detection: Distro family (Debian, RedHat, Arch, Immutable, Nix, Alpine, Other), desktop session presence
  • Error Handling: Distinct unsupported-query vs no-installable-artifact vs transport-failure errors
  • Progress Reporting: Live spinners, byte progress, staged events (resolveQuery, discoverRelease, downloadArtifact, etc.)

Findings (Prioritised Backlog)

GAP-001: Download Reliability — Memory Exhaustion Risk

Type: Reliability / Performance
Severity: Critical
Impact: Production blocker. Large AppImages (500MB+) are loaded entirely into memory before writing to disk. Risky on low-memory systems, can trigger OOM kills.
Evidence:

  • crates/aim-core/src/app/add.rs: download_artifact_bytes_with_reporter allocates a Vec<u8>, reads 16KB chunks in a loop, then extend_from_slice. Full payload lives in heap before stage_and_commit_payload writes it.
  • No streaming-to-disk, no memory pressure handling.

Repro/Trace:

  1. Attempt aim <large-appimage-url> where payload is 1GB
  2. Watch process RSS grow to 1GB+ before any file write

Suggested Fix:

  • Stream directly to staged file path using io::copy or chunked writes with temp file + atomic rename
  • Alternatively, use memory-mapped I/O for very large files
  • Add download timeout via reqwest::ClientBuilder::timeout

Acceptance Criteria:

  • Download writes directly to disk without intermediate full-buffer accumulation
  • Memory usage stays constant regardless of AppImage size
  • Configurable timeout (default 5 minutes)

GAP-002: Download Reliability — No Resume, Retry, or Timeout

Type: Reliability
Severity: High
Impact: Network interruptions, slow connections, or server timeouts cause complete download restart. Poor UX for large files or unstable networks.
Evidence:

  • crates/aim-core/src/app/add.rs: Single reqwest::blocking::get(url) call, no retry loop, no partial-download resume via Range headers
  • No timeout configured on reqwest::blocking::Client in ReqwestGitHubTransport::new() — hangs indefinitely on stalled connections

Suggested Fix:

  • Add retry logic (3 attempts with exponential backoff)
  • Support HTTP Range requests for resume if server supports it (check Accept-Ranges header)
  • Configure request timeout (30s connect, 5m total)
  • Provide --no-resume flag to skip range logic if needed

Acceptance Criteria:

  • Failed downloads retry up to 3 times
  • Partial downloads resume from last byte if server supports Range
  • Timeout after 5 minutes of inactivity or 30s connect timeout

GAP-003: Checksum Verification Not Used

Type: Security / Reliability
Severity: High
Impact: Silent corruption or supply-chain attacks. Metadata includes SHA-512 checksums (electron-builder) but downloads are never verified. Corrupted or tampered payloads install successfully.
Evidence:

Repro/Trace:

  1. Install app with known electron-builder metadata: aim owner/repo
  2. Inject corrupt bytes into download stream (or MITM proxy)
  3. Installation succeeds without checksum mismatch error

Suggested Fix:

  • After download completes, compute SHA-512 of payload if checksum hint exists
  • Fail installation with clear error if mismatch
  • Add --skip-checksum flag for advanced users bypassing validation

Acceptance Criteria:

  • SHA-512 checksums from metadata are verified post-download
  • Mismatch triggers clear error with expected vs actual hash
  • Installs without metadata checksums proceed with warning

GAP-004: Registry Corruption — No Atomic Writes or Backups

Type: Reliability
Severity: High
Impact: Registry corruption if process killed mid-save. No recovery mechanism; complete data loss of installed apps list.
Evidence:

Repro/Trace:

  1. Start aim sharkdp/bat
  2. Kill process (kill -9) during registry save stage
  3. Attempt aim list — registry file is truncated or corrupt, operation fails

Suggested Fix:

  • Atomic save: write to registry.toml.new, then fs::rename to registry.toml
  • Backup previous registry to registry.toml.bak before overwrite
  • On load failure, attempt restore from .bak
  • Add corruption detection (signature header) for faster failure

Acceptance Criteria:

  • Registry saves are atomic (temp file + rename)
  • Previous registry backed up before save
  • Corrupt registry auto-restores from backup with warning
  • Test: kill process mid-save, verify registry intact

GAP-005: Concurrent Access — No Registry Locking

Type: Reliability
Severity: Medium
Impact: Running multiple aim commands simultaneously can corrupt registry (race on read-modify-write). Data loss or duplicate entries possible.
Evidence:

Repro/Trace:

  1. Terminal A: aim sharkdp/bat (slow network download)
  2. Terminal B: aim update (starts while A is downloading)
  3. Both save registry simultaneously, one clobbers the other

Suggested Fix:

  • Advisory file lock on registry.toml.lock using fs2 crate
  • Acquire lock in load(), release in save()
  • Fail fast with clear error if lock unavailable after 5s

Acceptance Criteria:

  • Only one aim process can modify registry at a time
  • Second process waits or fails cleanly with "registry locked" message
  • Lock automatically released on crash (advisory lock semantics)

GAP-006: Documentation — Missing Providers in README

Type: Documentation / DX
Severity: Medium
Impact: Users unaware of supported sources. SourceForge, zsync, custom-json implemented but undocumented.
Evidence:

Suggested Fix:

  • Update README "Query Forms" section:
    • Add SourceForge project URLs and file download URLs
    • Note zsync as discovered metadata only (not install source)
    • Document or remove custom-json (currently nonfunctional)
  • Add examples: aim https://sourceforge.net/projects/app/files/v1.0/App.AppImage/download

Acceptance Criteria:

  • README documents all functional source types
  • SourceForge examples included
  • Zsync role clarified (metadata vs source)
  • Custom-json either documented as experimental or removed from public list

GAP-007: CustomJsonAdapter Is Non-Functional Stub

Type: Missing Feature / Code Quality
Severity: Medium
Impact: Adapter listed in all_adapter_kinds() but completely unusable. Misleading.
Evidence:

Suggested Fix:

  • Option A (remove): Delete adapter, remove from all_adapter_kinds(), document as future work
  • Option B (implement): Define JSON schema for custom update metadata, implement resolution logic, add tests
  • Option C (mark experimental): Add comment and feature flag, exclude from default builds

Acceptance Criteria:

  • Either adapter works with documented schema, or removed from public API
  • No adapter listed in all_adapter_kinds() that returns errors for all operations

GAP-008: Missing CLI Commands — Search, Info, Show

Type: Missing Feature
Severity: Medium
Impact: Users must know exact owner/repo or URL beforehand. No discovery workflow.
Evidence:

  • crates/aim-cli/src/cli/args.rs: Only defines Remove, List, Update subcommands
  • No aim search, aim info <query>, or aim show <stable-id> commands
  • GitHub releases API supports search, but no CLI exposure

Suggested Fix:

  • aim search <keyword> — search GitHub for AppImages (requires GitHub API search endpoint or external index)
  • aim info <query> or aim show <stable-id> — display app details pre-install (version, description, assets) or post-install (installed paths, source, update status)

Acceptance Criteria:

  • aim search returns list of candidate apps with descriptions
  • aim info shows app metadata without installing
  • aim show bat displays installed app details (version, files, source)

GAP-009: Missing CLI Features — Dry-Run, Force, Verbose

Type: Missing Feature
Severity: Medium
Impact: Limited user control over operations. No preview mode, no way to debug issues, no forced reinstall.
Evidence:

  • crates/aim-cli/src/cli/args.rs: No --dry-run, --force, --verbose, --no-desktop flags
  • All operations execute immediately without preview except update plan (aim bare shows pending updates but requires aim update to execute)

Suggested Fix:

  • Add --dry-run / -n: Show what would be done without mutating state
  • Add --force / -f: Reinstall even if already installed at same version
  • Add --verbose / -v: Show detailed logs (API requests, file operations)
  • Add --no-desktop: Skip desktop integration, install payload only

Acceptance Criteria:

  • aim --dry-run <query> prints plan without installing
  • aim --force <query> reinstalls existing app
  • aim --verbose list shows debug output
  • aim --no-desktop <query> installs without .desktop file

GAP-010: No Version Pinning or Update Blocking

Type: Missing Feature
Severity: Medium
Impact: Users cannot pin to specific versions or exclude from updates. All apps track latest by default (unless installed from specific release URL).
Evidence:

  • crates/aim-core/src/domain/app.rs: AppRecord has installed_version, update_strategy, but no pinned or update_policy field
  • aim update blindly updates all apps without user filtering

Suggested Fix:

  • Add aim pin <stable-id> and aim unpin <stable-id> commands
  • Store pinned: bool or update_policy: Auto | Manual | Pinned in AppRecord
  • aim update skips pinned apps, logs "X apps pinned, skipping"
  • Add --ignore <stable-id> flag to aim update for one-time exclusion

Acceptance Criteria:

  • aim pin bat prevents aim update from updating bat
  • aim list shows pinned status
  • aim unpin bat re-enables updates

GAP-011: No Rollback or Update Failure Recovery

Type: Reliability
Severity: Medium
Impact: Failed updates leave old binary deleted but registry updated, or vice versa. No way to revert to previous version.
Evidence:

Repro/Trace:

  1. aim sharkdp/bat (install v1.0)
  2. Tamper with network to force update download failure
  3. aim update — old bat.AppImage replaced with corrupt download, registry shows v1.1 but binary is broken

Suggested Fix:

  • Before update, rename app.AppImage to app.AppImage.backup
  • On success, delete backup
  • On failure, restore from backup and log rollback
  • Add aim rollback <stable-id> command to restore previous version

Acceptance Criteria:

  • Failed updates restore previous payload automatically
  • Registry remains consistent with installed payload
  • Manual rollback command available for user-initiated revert

GAP-012: No Proxy Support or Network Configuration

Type: Missing Feature
Severity: Low
Impact: Corporate or privacy-conscious users behind proxies cannot use aim.
Evidence:

  • crates/aim-core/src/source/github.rs: ReqwestGitHubTransport::new() uses default reqwest::blocking::Client builder, no proxy configuration
  • grep for proxy, http_proxy, https_proxy: zero matches

Suggested Fix:

  • Respect HTTP_PROXY, HTTPS_PROXY, NO_PROXY environment variables
  • Use reqwest::ClientBuilder::proxy() to configure
  • Add --proxy <url> CLI flag for override

Acceptance Criteria:

  • HTTP_PROXY=http://proxy:8080 aim <query> uses proxy
  • Corporate proxy users can install apps

GAP-013: No Offline or Cache-First Mode

Type: Missing Feature
Severity: Low
Impact: Cannot use aim in air-gapped or intermittent-connectivity environments. Every operation requires network.
Evidence:

  • All provider adapters make live HTTP requests, no local cache

Suggested Fix:

  • Cache GitHub release metadata in ~/.cache/aim/metadata/ for 1 hour
  • Add --offline flag to use cached data only
  • Add --refresh to force cache invalidation

Acceptance Criteria:

  • aim --offline list works without network if metadata cached
  • Metadata cached for 1 hour, revalidated on next request

GAP-014: No Help Text or Usage Examples

Type: Documentation / DX
Severity: Low
Impact: Users must read README or guess commands. Poor discoverability.
Evidence:

  • crates/aim-cli/src/cli/args.rs: Clap definitions exist but no detailed about text or examples
  • aim --help output is minimal (auto-generated clap summary)

Suggested Fix:

  • Add #[command(long_about = "...")] with detailed usage examples
  • Add #[arg(help = "...")] for each flag
  • Include examples in help: aim sharkdp/bat, aim list, aim remove bat

Acceptance Criteria:

  • aim --help shows examples and detailed descriptions
  • Each subcommand has helpful usage text

GAP-015: No CI-Friendly Output or Machine-Parseable Formats

Type: Missing Feature
Severity: Low
Impact: Cannot integrate aim into automation or scripts. Output is human-readable only.
Evidence:

Suggested Fix:

  • Add --format json|yaml|csv flag
  • Add --quiet / -q for errors-only output

Acceptance Criteria:

  • aim list --format json returns machine-parseable output
  • aim --quiet <query> suppresses progress spinners

GAP-016: Test Coverage — Missing Tests

Type: Testing / Quality
Severity: Medium
Impact: Gaps in reliability verification.
Evidence:

  • CustomJsonAdapter: Zero tests in crates/aim-core/tests/
  • Concurrency: No tests simulating concurrent registry access
  • Corruption recovery: No tests for registry load failures + backup restore
  • Large files: No tests for memory usage on 500MB+ downloads
  • Network failures: Limited retry/timeout tests
  • grep for custom_json in tests/: zero matches

Suggested Fix:

  • Add custom_json_adapter_contract_test if adapter is retained
  • Add concurrent_registry_access_test using threads
  • Add registry_corruption_recovery_test with intentionally corrupt TOML
  • Add large_file_download_memory_test mocking 1GB payload
  • Add network_failure_retry_test with flaky mock server

Acceptance Criteria:

  • All adapters in all_adapter_kinds() have contract tests
  • Concurrency edge cases covered
  • Corruption recovery path validated

Quick Wins

Fixes that deliver high user value with low implementation cost:

  1. GAP-006 (Documentation) — Update README with SourceForge, zsync usage (~30 minutes)
  2. GAP-014 (Help Text) — Add detailed clap help strings (~1 hour)
  3. GAP-009 (Dry-Run Flag) — Add --dry-run flag that skips final save (~2 hours)
  4. GAP-007 (CustomJson Cleanup) — Remove non-functional adapter or add experimental marker (~30 minutes)
  5. GAP-004 (Registry Backups) — Copy registry to .bak before save (~1 hour)

Open Questions

  1. Checksums: Should aim fail-closed (reject unverified downloads) or warn-only? Some sources lack checksums.
  2. Update Policy: Default to auto-update all apps, or require explicit aim update <stable-id>?
  3. Version Pinning: Should pinned apps show in aim list with special marker, or hidden?
  4. Search Scope: Limit search to apps with AppImage releases, or show all repos?
  5. CustomJson: Is this intended for user-defined update feeds? Should it support local JSON files or HTTP endpoints?
  6. Offline Mode: Cache release metadata only, or cache payloads too?
  7. Platform Support: Current code is Linux-only (by design). Document macOS/Windows as non-goals?
  8. Concurrency: Should aim support lock wait timeout (fail fast) or infinite retry?

Appendix

Notable Search Patterns

  • TODO|FIXME|HACK: 0 matches in Rust source (clean codebase)
  • unimplemented!: 0 matches
  • panic!: Minimal use, only in test fixtures
  • .unwrap(): Mostly in tests, production code uses Result

Files Reviewed

  • All .rs files in crates/aim-core/src/ (app, adapters, domain, integration, platform, registry, source, update, metadata)
  • All .rs files in crates/aim-cli/src/ (cli, ui, lib, main)
  • All test files in crates/aim-core/tests/ and crates/aim-cli/tests/
  • README.md, Cargo.toml
  • Planning docs in .plans/007-source-provider-expansion/

Test Coverage Hotspots

  • Strong: GitHub flows (discovery, parsing, install, update), GitLab basic flows, SourceForge URL classification
  • Moderate: DirectUrl, file imports, metadata parsing (zsync, electron-builder), platform detection, remove flows
  • Weak: CustomJson (zero), concurrency, corruption recovery, network failures, large file handling

v1.0 Readiness Verdict

Status: Pre-Alpha → Alpha (not v1.0-ready)

Blockers for v1.0:

  1. GAP-001 (Memory exhaustion) — Critical reliability issue
  2. GAP-003 (Checksum verification) — Critical security issue
  3. GAP-004 (Registry atomicity) — Critical data safety issue
  4. GAP-005 (Concurrent access) — High-severity correctness issue

Nice-to-Have for v1.0:

  • Search, info commands
  • Dry-run mode
  • Proxy support
  • Better help text

Recommendation:
Close GAP-001, GAP-003, GAP-004, GAP-005 before any production announcement. After those fixes, aim reaches beta status (core safety established). User-facing features (search, pin, rollback) can ship in v1.1+.

Effort Estimate (Blockers Only): ~3-5 engineering days for someone familiar with Rust + reqwest + file I/O patterns.

Current Strengths:

  • Clean architecture (adapters, domain, app separation)
  • Provider expansion path is well-designed
  • Good test coverage for happy paths
  • Terminal UX is polished (progress bars, styled output)
  • Platform detection handles immutable distros, Nix, etc.

Overall Quality: Well-built for internal/hobby use. Needs production hardening for public v1.0.