Skip to content

feat!: consume the declarative index, drop the pkg_id requirement - #186

Open
QaidVoid wants to merge 25 commits into
mainfrom
port-format
Open

feat!: consume the declarative index, drop the pkg_id requirement#186
QaidVoid wants to merge 25 commits into
mainfrom
port-format

Conversation

@QaidVoid

@QaidVoid QaidVoid commented Jul 29, 2026

Copy link
Copy Markdown
Member

Teaches soar to install from the declarative, hash-pinned index that sbuilder now produces, and removes the assumption that every package carries a pkg_id.

Reading the new index

The index is published as {format, packages} so a client can tell an index it cannot read from one that merely lacks a field. Anything without a format field is treated as the older format, so existing repositories keep working untouched.

Packages now declare their contents rather than having them guessed: binaries maps a path inside the artifact to the name it is linked as, and extra lists side files to install alongside it. This is what lets ripgrep ship rg without a naming convention to infer it from.

pkg_id is optional

A repository whose names are already unique no longer has to invent ids, so pkg_id is nullable throughout and nothing fabricates one on import. It no longer appears in human-readable output, though it is still carried as a field in structured output.

Variants are selected by family instead. The query syntax is family/name, and the old name#pkg_id form still works but warns that it will be removed.

Six migrations come with this. The installed-package table gains pkg_family, the metadata table gains binaries and extra and drops the unused webpage tags, and both relax their pkg_id columns. The metadata table also gains a COALESCE(pkg_id, '') unique index, because SQLite treats NULLs as distinct and the plain index would have allowed duplicate rows once ids became optional.

Version ordering

Comparison is segment-wise: a version splits into runs of digits and runs of non-digits, and matching runs compare numerically when both are numeric. This fixes 10 sorting below 9 and 1.10 below 1.9.

Semver is deliberately not used. Most published versions carry a rebuild revision as -N, which semver reads as a prerelease and ranks below the plain version, the opposite of what it means here. Plenty of real versions (1.05, r1287.fef2b38-1) are not valid semver at all.

A version built from a commit hash has no recoverable order, so those compare equal unless identical. A repository wanting upgrades between snapshots has to publish something ordered.

Fixes found while testing this

  • Archives are identified by content rather than by declared type, so a bare .gz is no longer treated as a tarball
  • Binaries resolve by full path before falling back to the filename, which stops an ARM binary being installed on x86_64 when both share a name
  • Read-only files extracted from an archive get their write bits restored before removal, so uninstalling actually completes
  • Log events reaching the CLI are printed rather than dropped, and packages rejected during import are reported instead of silently skipped
  • Installed packages are matched by name rather than by id, which was breaking upgrades of anything installed before this change

Compatibility

Older clients cannot read the new index: binaries and extra have no equivalent in the old format, so this needs to land before the repository starts publishing it.

Testing

Installed from a locally served index against an isolated config, covering a package with an unhashed licence, a package with a per-architecture hashed side file, upgrades from a pre-existing install with stale ids, and removal. Version comparison has unit tests covering dates, rebuild revisions, prereleases and commit hashes.

Summary by CodeRabbit

  • New Features

    • Added package-family query support using family/name@version:repo.
    • Packages can now declare executable binaries and pinned extra side-files for installation.
    • Improved handling of versioned repository indexes and non-standard version formats.
  • Bug Fixes

    • Install no longer fails when extraction is attempted on an unrecognized archive, and it continues if extraction fails.
    • More robust installation/update/removal behavior for packages without published identifiers.
    • Safer removal permissions and more reliable binary discovery (recursive matching).
  • Style

    • CLI output and progress messages are simplified to use package name/repo instead of internal IDs.
  • Chores

    • install_patterns is now deprecated (only the OCI download path applies it).

@QaidVoid QaidVoid changed the title feat: consume the declarative index, drop the pkg_id requirement feat!: consume the declarative index, drop the pkg_id requirement Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

This PR makes package identifiers optional across registry, database, core, operations, and CLI layers. It adds family-aware queries, versioned index parsing, binary and extra-file metadata, custom version comparison, safer extraction and removal handling, and removes package IDs from operation events and most output.

Changes

Package contracts and registry metadata

Layer / File(s) Summary
Nullable package metadata and new artifact fields
crates/soar-registry/*, crates/soar-db/models/*, crates/soar-core/*
Package IDs become optional, while family, binaries, and extra-file metadata are added.
Versioned index and version comparison
crates/soar-registry/*, crates/soar-utils/*, crates/soar-core/src/package/query.rs
Index format validation, family/name query parsing, and custom version ordering are introduced.

Database schema and repository operations

Layer / File(s) Summary
Nullable database identity and metadata schema
crates/soar-db/schema/*, crates/soar-db/migrations/*, crates/soar-db/models/*
Database package IDs become nullable; metadata columns and identity indexes are rebuilt for declarative package data.
Nullable repository matching and import behavior
crates/soar-db/src/repository/*
Core queries handle NULL package IDs, metadata queries accept family filters, newer versions use custom comparison, and imports count accepted entries.

Archive extraction and installation

Layer / File(s) Summary
Archive detection and installation layout
crates/soar-dl/src/download.rs, crates/soar-core/src/package/install.rs, crates/soar-operations/src/install.rs
Downloads detect archive formats before extraction, extracted roots are promoted safely, ELF files are made executable, and installation paths no longer include package IDs.
Extras and binary mappings
crates/soar-core/src/package/install.rs, crates/soar-operations/src/install.rs, crates/soar-operations/src/utils.rs
Package extras are downloaded with path and checksum checks, while binary mappings support index metadata and recursive glob matching.

Operation events and CLI adaptation

Layer / File(s) Summary
Identifier-free operation payloads
crates/soar-events/*, crates/soar-operations/*
Operation events and result models omit package IDs while install, remove, update, health, list, and search flows use nullable-aware matching.
CLI query and output formatting
crates/soar-cli/src/*, Cargo.toml
CLI queries pass family and optional IDs correctly, package displays omit IDs, logs use package names, and JSON imports use validated registry parsing.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RegistryIndex
  participant MetadataRepository
  participant PackageInstaller
  participant EventHandler
  RegistryIndex->>MetadataRepository: parse versioned index and import accepted packages
  MetadataRepository->>PackageInstaller: resolve family-aware package metadata
  PackageInstaller->>EventHandler: emit identifier-free operation events
Loading

Possibly related PRs

  • pkgforge/soar#119: Introduces the registry index parsing flow used by this PR’s JSON-to-database import changes.
  • pkgforge/soar#120: Establishes the package crate APIs extended here for optional package identifiers.
  • pkgforge/soar#157: Introduces the operations progress bridge modified here to remove package IDs from emitted events.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: consuming the declarative index and making pkg_id optional.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch port-format

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying soar-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: cd0513e
Status: ✅  Deploy successful!
Preview URL: https://31044510.soar-docs.pages.dev
Branch Preview URL: https://port-format.soar-docs.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (13)
crates/soar-core/src/package/local.rs (1)

142-151: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the local package ID or remove its upstream generation.

from_path still accepts pkg_id_override and constructs self.pkg_id, but to_package no longer copies it. Every local package therefore loses the generated or caller-supplied ID during conversion. Restore the optional pkg_id assignment, or remove the field and override plumbing consistently if this is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-core/src/package/local.rs` around lines 142 - 151, Update the
to_package conversion to preserve the local package ID by assigning the optional
pkg_id from self.pkg_id, including generated or caller-supplied overrides. Keep
the existing package fields unchanged and ensure the from_path pkg_id_override
plumbing remains consistent.
crates/soar-cli/src/download.rs (1)

153-161: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the filter arguments aligned in the repository-scoped lookup.

This call passes (name, None, pkg_id, None, ...), while the all-repositories call passes (name, pkg_id, family, None, ...) to the same function. A family/name:repo query therefore drops the family and shifts pkg_id into a different filter slot, which can select the wrong variant or return no match. Pass query.pkg_id.as_deref() and query.family.as_deref() in the same positions as the global lookup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/download.rs` around lines 153 - 161, Update the
repository-scoped MetadataRepository::find_filtered call to match the argument
ordering used by the all-repositories lookup: pass query.pkg_id.as_deref() and
query.family.as_deref() in their corresponding filter positions, preserving the
remaining arguments.
crates/soar-operations/src/update.rs (1)

138-147: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep variant identity when finding repository updates.

Line 140 no longer constrains the lookup by identifier or family. If a repository contains multiple family/name variants, this can select the newest version from a different family and install it over the current package. Extend find_newer_version and this call with the installed package’s family and optional identifier identity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-operations/src/update.rs` around lines 138 - 147, Update
MetadataRepository::find_newer_version and its call in the new_pkg lookup to
accept and apply the installed package’s family and optional identifier,
alongside the package name and version. Ensure repository updates only select
newer versions matching the current package variant identity before converting
and resolving the result.
crates/soar-cli/src/install.rs (1)

244-251: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor family/name in --show queries.

Both candidate queries pass None for the family filter, so soar install --show family/name displays candidates from every family instead of the requested one.

  • crates/soar-cli/src/install.rs#L244-L251: pass query.family.as_deref() as the family argument.
  • crates/soar-cli/src/install.rs#L264-L271: pass the same family filter in the all-repositories branch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/install.rs` around lines 244 - 251, The candidate queries
in install --show must honor the requested family filter. In both
MetadataRepository::find_filtered calls in crates/soar-cli/src/install.rs at
lines 244-251 and 264-271, pass query.family.as_deref() as the family argument
instead of None.
crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/down.sql (1)

1-3: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Down migration doesn't restore pkg_id NOT NULL.

The paired up.sql rebuilds the table to relax pkg_id to nullable (SQLite can't ALTER COLUMN). This down.sql only drops the index and deletes NULL rows — it never rebuilds the table to restore the NOT NULL constraint, so rolling back leaves the schema in a mixed state that doesn't match the pre-migration schema.

🔧 Suggested mirror-rebuild approach
DROP INDEX IF EXISTS packages_identity;
DELETE FROM packages WHERE pkg_id IS NULL;

CREATE TABLE packages_new (
  id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  pkg_id TEXT NOT NULL COLLATE NOCASE,
  pkg_family TEXT COLLATE NOCASE,
  pkg_name TEXT NOT NULL COLLATE NOCASE,
  -- ... remaining columns unchanged from the pre-migration schema
);

INSERT INTO packages_new SELECT
  id, pkg_id, pkg_family, pkg_name, /* ... */
FROM packages;

DROP TABLE packages;
ALTER TABLE packages_new RENAME TO packages;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/down.sql`
around lines 1 - 3, Update the down migration to rebuild the packages table
after removing NULL pkg_id rows, restoring the pre-migration schema with pkg_id
defined as NOT NULL. Mirror the up migration’s table-rebuild approach, preserve
all columns, constraints, indexes, and data, then replace the relaxed table with
the rebuilt one.

Source: Linters/SAST tools

crates/soar-core/src/package/install.rs (2)

271-279: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

package.pkg_family never reaches the disambiguation queries.

has_pending_install/delete_pending_installs (and later unlink_others/find_alternates in record()) are only given pkg_id/pkg_name/repo_name/version. self.package.pkg_family is available but unused here — see the companion comment on crates/soar-db/src/repository/core.rs for the root cause and full impact.

Also applies to: 306-329

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-core/src/package/install.rs` around lines 271 - 279, Update the
pending-install checks in the install flow around has_pending_install and the
related delete_pending_installs calls to pass self.package.pkg_family through to
the repository APIs. Ensure the corresponding record() calls to unlink_others
and find_alternates also receive the package family so all disambiguation
queries use it.

808-839: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing pre-rename cleanup for existing destination entries.

This rename loop lacks the to.exists() handling used in the extract_root (Lines 909-915) and nested_extract (Lines 976-982) blocks just below. On a resumed/retried install where install_dir already contains a leftover file/directory from a prior attempt, fs::rename(&from, &to) will fail here (non-empty directory or existing file), aborting the install instead of overwriting stale leftovers the way the other two blocks do.

🔧 Proposed fix
                     let from = entry.path();
                     let to = self.install_dir.join(entry.file_name());
+                    if to.exists() {
+                        if to.is_dir() {
+                            fs::remove_dir_all(&to).ok();
+                        } else {
+                            fs::remove_file(&to).ok();
+                        }
+                    }
                     // Renaming a directory rewrites its `..`, so the directory
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-core/src/package/install.rs` around lines 808 - 839, Update the
rename loop in the extracted-install block to remove any existing destination
entry at `to` before calling `fs::rename(&from, &to)`, matching the cleanup
behavior in the `extract_root` and `nested_extract` blocks. Preserve the
existing contextual error handling and continue treating stale files or
directories as replaceable leftovers.
crates/soar-db/src/repository/core.rs (2)

327-471: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

pkg_family isn't threaded through package-disambiguation logic. Both sites stem from the same gap: pkg_family was added to distinguish variants once pkg_id became optional, but it never reaches the queries/calls that decide which installed row is "the same package."

  • crates/soar-db/src/repository/core.rs#L327-L471: add a pkg_family: Option<&str> parameter to find_alternates, unlink_others, unlink_others_by_checksum, get_old_package_paths, delete_old_packages (and ideally the other pkg_id-based lookups) and include it in the match predicate alongside match_pkg_id.
  • crates/soar-core/src/package/install.rs#L271-L329: pass self.package.pkg_family.as_deref() into the has_pending_install, delete_pending_installs, unlink_others, and find_alternates calls once the repository signatures accept it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-db/src/repository/core.rs` around lines 327 - 471, Thread
pkg_family through package disambiguation: in
crates/soar-db/src/repository/core.rs lines 327-471, add Option<&str> parameters
to find_alternates, unlink_others, unlink_others_by_checksum,
get_old_package_paths, delete_old_packages, and other pkg_id-based lookups as
applicable, incorporating it alongside match_pkg_id in predicates. In
crates/soar-core/src/package/install.rs lines 271-329, pass
self.package.pkg_family.as_deref() to has_pending_install,
delete_pending_installs, unlink_others, and find_alternates; update all affected
callers and preserve existing matching behavior.

327-346: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Package disambiguation queries don't consider the new pkg_family column.

find_alternates, unlink_others, unlink_others_by_checksum, get_old_package_paths, delete_old_packages, find_exact, find_by_pkg_id_and_repo, find_by_pkg_id_name_and_repo, record_installation, update_pkg_id, has_pending_install, delete_pending_installs, and link_by_checksum match rows using only pkg_name plus optional pkg_id. With nullable pkg_id and the new pkg_family column, unrelated families sharing a pkg_name can be treated as the same package; unlink_others/get_old_package_paths/delete_old_packages can then unlink or delete an unrelated installation.

Add pkg_family: Option<&str> to these repository methods and thread self.package.pkg_family through the call sites in crates/soar-core/src/package/install.rs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-db/src/repository/core.rs` around lines 327 - 346, Add a
pkg_family: Option<&str> parameter to find_alternates and the other listed
repository methods, and include pkg_family in their package-matching filters
alongside pkg_name/pkg_id. Update the corresponding calls in the install flow to
pass self.package.pkg_family, preserving existing behavior while preventing
operations from crossing package families.
crates/soar-operations/src/apply.rs (1)

70-78: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass pkg_id and pkg_family in their declared positions. find_filtered expects ID before family, but both calls provide None for ID and put the package ID in the family slot.

  • crates/soar-operations/src/apply.rs#L70-L78: pass pkg.pkg_id.as_deref() as the second filter and None as the family filter.
  • crates/soar-operations/src/switch.rs#L118-L126: pass selected_package.pkg_id.as_deref() as the second filter and None as the family filter.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-operations/src/apply.rs` around lines 70 - 78, Correct both
MetadataRepository::find_filtered calls in crates/soar-operations/src/apply.rs
lines 70-78 and crates/soar-operations/src/switch.rs lines 118-126: pass
pkg.pkg_id.as_deref() or selected_package.pkg_id.as_deref() as the ID filter,
followed by None for the package-family filter, preserving all other arguments.
crates/soar-operations/src/install.rs (1)

266-293: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not use an absent ID as an unscoped variant filter. None disables the pkg_id predicate, rather than selecting rows with a null ID.

  • crates/soar-operations/src/install.rs#L266-L293: when target_pkg_id is absent, constrain the metadata query by the selected name/family instead of querying every package.
  • crates/soar-operations/src/install.rs#L306-L317: apply the same name/family fallback to installed-package lookup.
  • crates/soar-operations/src/remove.rs#L96-L110: for an ID-less selected package, keep removal scoped to its name rather than resolving all repository packages.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-operations/src/install.rs` around lines 266 - 293, The metadata
queries currently treat an absent target_pkg_id as an unscoped filter; update
the selected-package lookup in install.rs lines 266-293 and installed-package
lookup in install.rs lines 306-317 to use the selected package name/family when
the ID is absent, while retaining ID filtering when present. Apply the same
name-scoped fallback to the removal lookup in remove.rs lines 96-110 so ID-less
packages resolve only within their selected name.
crates/soar-core/src/package/url.rs (1)

250-273: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the generated package ID for remote installs.

Both branches now default Package::pkg_id to None, although UrlPackage always derives an ID. Later URL status checks filter by that ID, so the existing install is not found and can be reinstalled repeatedly. Set pkg_id: Some(self.pkg_id.clone()) in both literals.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-core/src/package/url.rs` around lines 250 - 273, Preserve the
derived package ID when constructing the installed Package values in both
branches of the UrlPackage conversion. Add pkg_id using
Some(self.pkg_id.clone()) to each Package literal so URL status checks can find
existing remote installations.
crates/soar-db/src/repository/metadata.rs (1)

570-616: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Nullable pkg_id may defeat dedup on re-import via on_conflict_do_nothing().

The insert relies on UNIQUE (pkg_id, pkg_name, version) (per the original packages create-table) to make on_conflict_do_nothing() skip already-imported rows and return Ok(false). Under standard SQL/SQLite semantics, NULL is never equal to NULL for UNIQUE-constraint purposes, so two imports of the same pkg_name+version with pkg_id = None (the common case for the new declarative index format this PR targets) will not conflict — each re-import/refresh will insert a duplicate row instead of being skipped, growing the table and confusing later find_by_name/find_filtered queries.

This may already be handled if crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/up.sql redefines the unique index (e.g., unique on (pkg_name, version) alone, or on a coalesced expression), but that file isn't in this review batch.

#!/bin/bash
cat crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/up.sql
rg -n "UNIQUE" crates/soar-db/src/schema/metadata.rs crates/soar-db/migrations/metadata -g '*.sql'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-db/src/repository/metadata.rs` around lines 570 - 616, Ensure
duplicate detection in the package insertion flow remains effective when pkg_id
is None: update the migration/schema constraint or the insert logic around
NewPackage and diesel::insert_into so repeated imports with the same pkg_name
and version conflict and return Ok(false). Verify the existing uniqueness
definition is adjusted without weakening deduplication for populated pkg_id
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/soar-cli/src/install.rs`:
- Around line 79-81: The ambiguity re-resolution currently loses the selected
package variant by querying only name:repo and ignoring the resolved result. In
crates/soar-cli/src/install.rs lines 79-81, preserve the selected candidate’s
full family-aware identity or install that candidate directly; apply the same
identity-preserving fix in the --show fallback at lines 202-204.

In `@crates/soar-cli/src/json2db.rs`:
- Around line 55-64: Update the json2db import flow around
MetadataRepository::import_packages so validation and import occur in a
temporary database or transaction rather than the existing output database. Only
atomically replace the output after imported is greater than zero; when all
packages are rejected, preserve the prior database unchanged.

In `@crates/soar-cli/src/update.rs`:
- Around line 29-32: Update the CLI messages using update_info and the
corresponding download and switch confirmation flows to include family and/or
repository context alongside pkg_name, preserving enough identity to distinguish
variants without restoring deprecated pkg_id syntax. Apply this to the update
preview in crates/soar-cli/src/update.rs lines 29-32, the update failure log in
crates/soar-cli/src/update.rs line 52, the download log in
crates/soar-cli/src/download.rs line 215, and the switch confirmation in
crates/soar-cli/src/use.rs line 60.

In `@crates/soar-cli/src/utils.rs`:
- Around line 136-143: Update the installed-package matching logic around
is_installed so it never treats a missing pkg.pkg_id() as an empty identity. Use
package name together with family/repository, or another stable resolved
identity, and revise the producer of the installed tuples to provide the same
identity fields so current and older installs match correctly.

In `@crates/soar-db/src/repository/metadata.rs`:
- Around line 442-473: Update find_newer_version to accept the installed package
family and filter candidates by both pkg_name and pkg_family, matching the
disambiguation behavior in find_filtered. Thread the new family argument through
all callers while preserving the existing version comparison and selection
logic.

In `@crates/soar-dl/src/download.rs`:
- Around line 345-349: Update the extraction flow around compak::extract_archive
so it always extracts into a dedicated temporary child directory rather than the
caller-owned extract_dir. On extraction failure, remove only that temporary
directory; preserve the existing caller-supplied directory and downloaded
archive, while retaining the current warning behavior.

In `@crates/soar-operations/src/install.rs`:
- Around line 834-849: The checksum-less directory suffix currently hashes only
pkg_name and version, causing distinct sources or variants to collide; update
the fallback hash input in the installation path logic to also include a stable
source identity, preferring pkg_id when available and otherwise the package URL
or GHCR reference, while preserving the existing 12-character suffix behavior.

In `@crates/soar-operations/src/list.rs`:
- Around line 60-77: Update the installed_pkgs construction in the list
filtering flow to merge duplicate (repo_name, pkg_name) records with logical OR
semantics, so any record with is_installed=true marks the package installed.
Replace the direct HashMap collect behavior with an aggregation that preserves
the existing key lookup used for entries.

In `@crates/soar-operations/src/search.rs`:
- Around line 58-67: The installed package aggregation in the `installed_pkgs`
construction must combine duplicate `(repo_name, pkg_name)` entries with logical
OR instead of allowing `collect()` to overwrite values arbitrarily. Update the
`into_par_iter` reduction/collection so any matching row with `is_installed ==
true` produces a true aggregate.

In `@crates/soar-operations/src/update.rs`:
- Around line 443-451: Update the install-report success tracking around the
succeeded set and the target cleanup loop to use a stable per-target identity
rather than pkg_name alone. Preserve that identity when recording installed
packages, key succeeded by it, and use the matching target identity for
membership checks so packages from different repositories, families, or variants
cannot share success status.

In `@crates/soar-package/src/formats/common.rs`:
- Around line 294-301: Update the no-pkg_id branch of the portable_dir_base
derivation around package.pkg_id() to include the package family alongside
package.pkg_name(), using the existing family-aware discriminator exposed by
PackageExt. Keep the current package-name-and-id format unchanged for packages
with pkg_id, while ensuring family-a/foo and family-b/foo produce distinct
portable directory bases.

In `@crates/soar-utils/src/version.rs`:
- Around line 30-50: Update compare_versions to detect when both inputs are
hash-only versions and return Ordering::Equal before segment comparison,
preventing distinct commit hashes from determining upgrade or downgrade
decisions. Reuse the existing hash-detection helper or add one near
compare_versions, and preserve normal semantic comparison for non-hash versions.

---

Outside diff comments:
In `@crates/soar-cli/src/download.rs`:
- Around line 153-161: Update the repository-scoped
MetadataRepository::find_filtered call to match the argument ordering used by
the all-repositories lookup: pass query.pkg_id.as_deref() and
query.family.as_deref() in their corresponding filter positions, preserving the
remaining arguments.

In `@crates/soar-cli/src/install.rs`:
- Around line 244-251: The candidate queries in install --show must honor the
requested family filter. In both MetadataRepository::find_filtered calls in
crates/soar-cli/src/install.rs at lines 244-251 and 264-271, pass
query.family.as_deref() as the family argument instead of None.

In `@crates/soar-core/src/package/install.rs`:
- Around line 271-279: Update the pending-install checks in the install flow
around has_pending_install and the related delete_pending_installs calls to pass
self.package.pkg_family through to the repository APIs. Ensure the corresponding
record() calls to unlink_others and find_alternates also receive the package
family so all disambiguation queries use it.
- Around line 808-839: Update the rename loop in the extracted-install block to
remove any existing destination entry at `to` before calling `fs::rename(&from,
&to)`, matching the cleanup behavior in the `extract_root` and `nested_extract`
blocks. Preserve the existing contextual error handling and continue treating
stale files or directories as replaceable leftovers.

In `@crates/soar-core/src/package/local.rs`:
- Around line 142-151: Update the to_package conversion to preserve the local
package ID by assigning the optional pkg_id from self.pkg_id, including
generated or caller-supplied overrides. Keep the existing package fields
unchanged and ensure the from_path pkg_id_override plumbing remains consistent.

In `@crates/soar-core/src/package/url.rs`:
- Around line 250-273: Preserve the derived package ID when constructing the
installed Package values in both branches of the UrlPackage conversion. Add
pkg_id using Some(self.pkg_id.clone()) to each Package literal so URL status
checks can find existing remote installations.

In
`@crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/down.sql`:
- Around line 1-3: Update the down migration to rebuild the packages table after
removing NULL pkg_id rows, restoring the pre-migration schema with pkg_id
defined as NOT NULL. Mirror the up migration’s table-rebuild approach, preserve
all columns, constraints, indexes, and data, then replace the relaxed table with
the rebuilt one.

In `@crates/soar-db/src/repository/core.rs`:
- Around line 327-471: Thread pkg_family through package disambiguation: in
crates/soar-db/src/repository/core.rs lines 327-471, add Option<&str> parameters
to find_alternates, unlink_others, unlink_others_by_checksum,
get_old_package_paths, delete_old_packages, and other pkg_id-based lookups as
applicable, incorporating it alongside match_pkg_id in predicates. In
crates/soar-core/src/package/install.rs lines 271-329, pass
self.package.pkg_family.as_deref() to has_pending_install,
delete_pending_installs, unlink_others, and find_alternates; update all affected
callers and preserve existing matching behavior.
- Around line 327-346: Add a pkg_family: Option<&str> parameter to
find_alternates and the other listed repository methods, and include pkg_family
in their package-matching filters alongside pkg_name/pkg_id. Update the
corresponding calls in the install flow to pass self.package.pkg_family,
preserving existing behavior while preventing operations from crossing package
families.

In `@crates/soar-db/src/repository/metadata.rs`:
- Around line 570-616: Ensure duplicate detection in the package insertion flow
remains effective when pkg_id is None: update the migration/schema constraint or
the insert logic around NewPackage and diesel::insert_into so repeated imports
with the same pkg_name and version conflict and return Ok(false). Verify the
existing uniqueness definition is adjusted without weakening deduplication for
populated pkg_id values.

In `@crates/soar-operations/src/apply.rs`:
- Around line 70-78: Correct both MetadataRepository::find_filtered calls in
crates/soar-operations/src/apply.rs lines 70-78 and
crates/soar-operations/src/switch.rs lines 118-126: pass pkg.pkg_id.as_deref()
or selected_package.pkg_id.as_deref() as the ID filter, followed by None for the
package-family filter, preserving all other arguments.

In `@crates/soar-operations/src/install.rs`:
- Around line 266-293: The metadata queries currently treat an absent
target_pkg_id as an unscoped filter; update the selected-package lookup in
install.rs lines 266-293 and installed-package lookup in install.rs lines
306-317 to use the selected package name/family when the ID is absent, while
retaining ID filtering when present. Apply the same name-scoped fallback to the
removal lookup in remove.rs lines 96-110 so ID-less packages resolve only within
their selected name.

In `@crates/soar-operations/src/update.rs`:
- Around line 138-147: Update MetadataRepository::find_newer_version and its
call in the new_pkg lookup to accept and apply the installed package’s family
and optional identifier, alongside the package name and version. Ensure
repository updates only select newer versions matching the current package
variant identity before converting and resolving the result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 460f5f0c-292b-42f2-b8ad-be9ccfb6dff8

📥 Commits

Reviewing files that changed from the base of the PR and between 0059732 and 4715fb3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (64)
  • Cargo.toml
  • crates/soar-cli/src/apply.rs
  • crates/soar-cli/src/download.rs
  • crates/soar-cli/src/health.rs
  • crates/soar-cli/src/inspect.rs
  • crates/soar-cli/src/install.rs
  • crates/soar-cli/src/json2db.rs
  • crates/soar-cli/src/list.rs
  • crates/soar-cli/src/progress.rs
  • crates/soar-cli/src/remove.rs
  • crates/soar-cli/src/run.rs
  • crates/soar-cli/src/update.rs
  • crates/soar-cli/src/use.rs
  • crates/soar-cli/src/utils.rs
  • crates/soar-core/src/database/models.rs
  • crates/soar-core/src/package/install.rs
  • crates/soar-core/src/package/local.rs
  • crates/soar-core/src/package/query.rs
  • crates/soar-core/src/package/remove.rs
  • crates/soar-core/src/package/update.rs
  • crates/soar-core/src/package/url.rs
  • crates/soar-db/migrations/core/2026-07-28-000000-0000_add_pkg_family/down.sql
  • crates/soar-db/migrations/core/2026-07-28-000000-0000_add_pkg_family/up.sql
  • crates/soar-db/migrations/core/2026-07-29-000000-0000_pkg_id_optional/down.sql
  • crates/soar-db/migrations/core/2026-07-29-000000-0000_pkg_id_optional/up.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_add_binaries/down.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_add_binaries/up.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000001-0000_drop_webpage_tags/down.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000001-0000_drop_webpage_tags/up.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000002-0000_add_extra/down.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000002-0000_add_extra/up.sql
  • crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/down.sql
  • crates/soar-db/migrations/metadata/2026-07-29-000000-0000_pkg_id_optional/up.sql
  • crates/soar-db/src/migration.rs
  • crates/soar-db/src/models/core.rs
  • crates/soar-db/src/models/metadata.rs
  • crates/soar-db/src/models/types.rs
  • crates/soar-db/src/repository/core.rs
  • crates/soar-db/src/repository/metadata.rs
  • crates/soar-db/src/schema/core.rs
  • crates/soar-db/src/schema/metadata.rs
  • crates/soar-dl/src/download.rs
  • crates/soar-events/src/event.rs
  • crates/soar-operations/src/apply.rs
  • crates/soar-operations/src/context.rs
  • crates/soar-operations/src/health.rs
  • crates/soar-operations/src/install.rs
  • crates/soar-operations/src/list.rs
  • crates/soar-operations/src/progress.rs
  • crates/soar-operations/src/remove.rs
  • crates/soar-operations/src/run.rs
  • crates/soar-operations/src/search.rs
  • crates/soar-operations/src/switch.rs
  • crates/soar-operations/src/types.rs
  • crates/soar-operations/src/update.rs
  • crates/soar-operations/src/utils.rs
  • crates/soar-package/src/formats/common.rs
  • crates/soar-package/src/traits.rs
  • crates/soar-registry/src/error.rs
  • crates/soar-registry/src/lib.rs
  • crates/soar-registry/src/metadata.rs
  • crates/soar-registry/src/package.rs
  • crates/soar-utils/src/lib.rs
  • crates/soar-utils/src/version.rs
💤 Files with no reviewable changes (5)
  • crates/soar-db/src/migration.rs
  • crates/soar-operations/src/types.rs
  • crates/soar-events/src/event.rs
  • crates/soar-operations/src/health.rs
  • crates/soar-operations/src/progress.rs

Comment thread crates/soar-cli/src/install.rs Outdated
Comment thread crates/soar-cli/src/json2db.rs Outdated
Comment on lines +29 to 32
"{}: {} -> {}",
Colored(Blue, &update_info.pkg_name),
Colored(Cyan, &update_info.pkg_id),
Colored(Red, &update_info.current_version),
Colored(Green, &update_info.new_version),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve variant and repository context when removing pkg_id from CLI messages.

pkg_name is not unique once family/name variants exist. These messages should retain family and/or repository context so users can distinguish operations without restoring the deprecated ID syntax.

  • crates/soar-cli/src/update.rs#L29-L32: include family/repository context in update previews.
  • crates/soar-cli/src/update.rs#L52-L52: include the same context in failure logs.
  • crates/soar-cli/src/download.rs#L215-L215: include family/repository context in download logs.
  • crates/soar-cli/src/use.rs#L60-L60: include the selected family/repository in switch confirmations.
📍 Affects 3 files
  • crates/soar-cli/src/update.rs#L29-L32 (this comment)
  • crates/soar-cli/src/update.rs#L52-L52
  • crates/soar-cli/src/download.rs#L215-L215
  • crates/soar-cli/src/use.rs#L60-L60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/update.rs` around lines 29 - 32, Update the CLI messages
using update_info and the corresponding download and switch confirmation flows
to include family and/or repository context alongside pkg_name, preserving
enough identity to distinguish variants without restoring deprecated pkg_id
syntax. Apply this to the update preview in crates/soar-cli/src/update.rs lines
29-32, the update failure log in crates/soar-cli/src/update.rs line 52, the
download log in crates/soar-cli/src/download.rs line 215, and the switch
confirmation in crates/soar-cli/src/use.rs line 60.

Comment thread crates/soar-cli/src/utils.rs Outdated
Comment thread crates/soar-db/src/repository/metadata.rs
Comment thread crates/soar-operations/src/list.rs
Comment thread crates/soar-operations/src/search.rs Outdated
Comment thread crates/soar-operations/src/update.rs Outdated
Comment thread crates/soar-package/src/formats/common.rs Outdated
Comment thread crates/soar-utils/src/version.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/up.sql (1)

64-65: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Include pkg_family in the identity index.

Declarative packages use pkg_family as the identity instead of pkg_id, so this index still groups different families with the same pkg_name and version into the same COALESCE(pkg_id, '') bucket and makes those variants unrecoverable inserts that conflict with each other.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/up.sql`
around lines 64 - 65, Update the packages_identity unique index to include
pkg_family in the indexed identity columns, preserving the existing pkg_id,
pkg_name, and version components so declarative packages from different families
do not conflict.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/down.sql`:
- Line 2: Replace the unconditional NULL-ID deletion in the core downgrade
migration with a guard that aborts clearly when installed package rows have
pkg_id IS NULL, rather than deleting them. Apply the same pre-delete guard to
the metadata downgrade migration for NULL-ID metadata rows; update both
specified migration files accordingly.

---

Outside diff comments:
In
`@crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/up.sql`:
- Around line 64-65: Update the packages_identity unique index to include
pkg_family in the indexed identity columns, preserving the existing pkg_id,
pkg_name, and version components so declarative packages from different families
do not conflict.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 362731b8-f02d-4d4b-887a-046a68286552

📥 Commits

Reviewing files that changed from the base of the PR and between 4715fb3 and a72ee19.

📒 Files selected for processing (4)
  • crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/down.sql
  • crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/up.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/down.sql
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/up.sql

@@ -0,0 +1,31 @@
-- Rows without an id cannot be represented once the column is required again.
DELETE FROM packages WHERE pkg_id IS NULL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Refuse unsafe downgrade instead of deleting declarative package state.

pkg_id IS NULL is valid state in the new format; deleting it silently loses package tracking and can orphan installed artifacts.

  • crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/down.sql#L2-L2: abort the downgrade with a clear error when NULL-ID installed rows exist.
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/down.sql#L3-L3: apply the same guard before deleting NULL-ID metadata rows.
📍 Affects 2 files
  • crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/down.sql#L2-L2 (this comment)
  • crates/soar-db/migrations/metadata/2026-07-28-000000-0000_declarative_format/down.sql#L3-L3
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/soar-db/migrations/core/2026-07-28-000000-0000_declarative_format/down.sql`
at line 2, Replace the unconditional NULL-ID deletion in the core downgrade
migration with a guard that aborts clearly when installed package rows have
pkg_id IS NULL, rather than deleting them. Apply the same pre-delete guard to
the metadata downgrade migration for NULL-ID metadata rows; update both
specified migration files accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/soar-config/src/packages.rs`:
- Around line 337-349: Update PackageSpec::resolve so declarative installs no
longer copy defaults.install_patterns or opts.install_patterns into
ResolvedPackage.globs. Keep legacy install-pattern handling confined to the
OCI-specific path, and ensure declarative installation preserves the complete
package contents without applying these deprecated patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e368202-cd4f-448b-a4cf-5bc4e331be38

📥 Commits

Reviewing files that changed from the base of the PR and between a72ee19 and 203b5c0.

📒 Files selected for processing (9)
  • crates/soar-cli/src/health.rs
  • crates/soar-config/src/config.rs
  • crates/soar-config/src/packages.rs
  • crates/soar-core/src/package/install.rs
  • crates/soar-core/src/package/url.rs
  • crates/soar-events/src/lib.rs
  • crates/soar-operations/src/install.rs
  • crates/soar-operations/src/progress.rs
  • crates/soar-operations/src/utils.rs
💤 Files with no reviewable changes (1)
  • crates/soar-events/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/soar-operations/src/utils.rs
  • crates/soar-cli/src/health.rs
  • crates/soar-operations/src/progress.rs
  • crates/soar-operations/src/install.rs
  • crates/soar-core/src/package/install.rs

Comment on lines +337 to +349
#[deprecated(
since = "0.13.0",
note = "only the OCI download path applies these; the declarative format does not use it"
)]
pub install_patterns: Option<Vec<String>>,
pub binary_only: bool,
pub arch_map: Option<HashMap<String, String>>,
}

impl PackageSpec {
/// Resolve the package specification with defaults applied.
// Still populated while the OCI path exists; see the field's deprecation.
#[allow(deprecated)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Prevent deprecated patterns from affecting declarative installs.

PackageSpec::resolve still copies defaults.install_patterns and opts.install_patterns into ResolvedPackage (Lines 383 and 426-429), and downstream installation receives them as globs. This contradicts the deprecation note that the declarative format does not use them and can silently omit files from installs. Restrict legacy pattern handling to the OCI-specific path, or document and test this compatibility behavior explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-config/src/packages.rs` around lines 337 - 349, Update
PackageSpec::resolve so declarative installs no longer copy
defaults.install_patterns or opts.install_patterns into ResolvedPackage.globs.
Keep legacy install-pattern handling confined to the OCI-specific path, and
ensure declarative installation preserves the complete package contents without
applying these deprecated patterns.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
crates/soar-cli/src/utils.rs (1)

148-154: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Show package families wherever pkg_id was removed from identity output.

Family is now the user-visible discriminator for variants, but these outputs render only pkg_name:repo_name. Same-name variants can become indistinguishable.

  • crates/soar-cli/src/utils.rs#L148-L154: render family/name:repo in the interactive selector.
  • crates/soar-cli/src/utils.rs#L186-L187: include the family in target confirmation.
  • crates/soar-cli/src/install.rs#L416-L418: include the family in the installation report.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/utils.rs` around lines 148 - 154, Update the package
identity formatting to include the family before name:repo so variants remain
distinguishable. In crates/soar-cli/src/utils.rs lines 148-154, update the
interactive selector output; also update lines 186-187 for target confirmation.
In crates/soar-cli/src/install.rs lines 416-418, include the family in the
installation report, preserving the existing formatting and markers.
crates/soar-db/src/repository/core.rs (2)

338-357: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

find_alternates mishandles None through SQL NULL comparison.

The filter pkg_id.is_null().or(pkg_id.ne(exclude_pkg_id)) makes column != NULL one disjunct when exclude_pkg_id is None; in SQL this never matches, so non-null rows with the same package name are excluded from the alternates list. Use a Rust-side identity check, like rows_other_than, so only the exact same pkg_id identity is omitted.

🐛 Proposed fix: move the pkg_id inequality check to Rust-side comparison
     pub fn find_alternates(
         conn: &mut SqliteConnection,
         pkg_name: &str,
         exclude_pkg_id: Option<&str>,
         exclude_version: &str,
     ) -> QueryResult<Vec<InstalledPackageWithPortable>> {
         let results: Vec<(Package, Option<PortablePackage>)> = packages::table
             .left_join(portable_package::table)
             .filter(packages::pkg_name.eq(pkg_name))
-            .filter(
-                packages::pkg_id
-                    .is_null()
-                    .or(packages::pkg_id.ne(exclude_pkg_id)),
-            )
             .filter(packages::version.ne(exclude_version))
             .select((Package::as_select(), Option::<PortablePackage>::as_select()))
             .load(conn)?;

-        Ok(results.into_iter().map(Into::into).collect())
+        Ok(results
+            .into_iter()
+            .filter(|(pkg, _)| pkg.pkg_id.as_deref() != exclude_pkg_id)
+            .map(Into::into)
+            .collect())
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-db/src/repository/core.rs` around lines 338 - 357, Update
find_alternates to avoid comparing packages::pkg_id with a nullable
exclude_pkg_id in SQL. Load rows filtered by package name and version, then
apply the existing rows_other_than-style Rust-side identity check so only the
exact excluded pkg_id is omitted, while None excludes no package.

464-516: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope unlink_others to the installing repository.

rows_other_than selects every row with pkg_name across all repositories. When a declarative install has pkg_id = NULL, another repository with the same package name/version can match the keep predicate here and be incorrectly left linked, or a different pkg_id can make the current install match a different repository’s stale row. Thread repo_name through unlink_others/rows_other_than from PackageInstaller::record; record_installation, has_pending_install, and delete_pending_installs already scope this path by repository.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-db/src/repository/core.rs` around lines 464 - 516, The unlinking
query currently considers matching package rows from every repository; scope it
to the installing repository. Add a repo_name parameter to unlink_others and
rows_other_than, filter rows by that repository before identity comparison, and
pass the repository name from PackageInstaller::record while preserving the
existing identity and update behavior.
crates/soar-operations/src/install.rs (1)

248-276: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Ambiguity check no longer detects real ambiguity once pkg_id is optional.

all_same_pkg_id compares only pkg_id. When the declarative index leaves pkg_id None (the whole point of this PR), every variant reads as "same" regardless of pkg_family, so a name shared across two distinct families silently resolves to whichever variant happened to be variants[0] instead of raising ResolveResult::Ambiguous. The fallback filter you added right below (267-276) then locks onto that arbitrarily-picked family for the rest of #all resolution.

🐛 Proposed fix
     if variants.len() > 1 {
-        let first_pkg_id = &variants[0].pkg_id;
-        let all_same_pkg_id = variants.iter().all(|v| v.pkg_id == *first_pkg_id);
-        if !all_same_pkg_id {
+        let first_identity = (&variants[0].pkg_id, &variants[0].pkg_family);
+        let all_same_identity = variants
+            .iter()
+            .all(|v| (&v.pkg_id, &v.pkg_family) == first_identity);
+        if !all_same_identity {
             return Ok(ResolveResult::Ambiguous(crate::AmbiguousPackage {
                 query: query.name.clone().unwrap_or_default(),
                 candidates: variants,
             }));
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-operations/src/install.rs` around lines 248 - 276, Update the
ambiguity check in the resolution flow before target_pkg_id is selected so
variants are considered equivalent only when both their pkg_id and pkg_family
match. When pkg_id is absent, differing families must return
ResolveResult::Ambiguous rather than selecting variants[0]; preserve the
existing candidate construction and downstream name/id/family filter behavior
for unambiguous results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/soar-cli/src/install.rs`:
- Around line 78-86: Update the specific_query construction for the selected
package: prefer the family/name:repo form, otherwise retain pkg_id using
name#pkg_id:repo, and fall back to name:repo only when both pkg_family and
pkg_id are absent. Apply the same fallback logic to the corresponding alternate
block noted in the comment.

In `@crates/soar-operations/src/remove.rs`:
- Around line 97-112: Preserve family identity for id-less package selection: in
crates/soar-operations/src/remove.rs lines 97-112, carry the selected package’s
pkg_family into the `#all` lookup and filter candidates by that family; in
crates/soar-cli/src/install.rs lines 348-352, require the selected package
family when choosing an existing installation. Keep id-based lookup behavior
unchanged.

---

Outside diff comments:
In `@crates/soar-cli/src/utils.rs`:
- Around line 148-154: Update the package identity formatting to include the
family before name:repo so variants remain distinguishable. In
crates/soar-cli/src/utils.rs lines 148-154, update the interactive selector
output; also update lines 186-187 for target confirmation. In
crates/soar-cli/src/install.rs lines 416-418, include the family in the
installation report, preserving the existing formatting and markers.

In `@crates/soar-db/src/repository/core.rs`:
- Around line 338-357: Update find_alternates to avoid comparing
packages::pkg_id with a nullable exclude_pkg_id in SQL. Load rows filtered by
package name and version, then apply the existing rows_other_than-style
Rust-side identity check so only the exact excluded pkg_id is omitted, while
None excludes no package.
- Around line 464-516: The unlinking query currently considers matching package
rows from every repository; scope it to the installing repository. Add a
repo_name parameter to unlink_others and rows_other_than, filter rows by that
repository before identity comparison, and pass the repository name from
PackageInstaller::record while preserving the existing identity and update
behavior.

In `@crates/soar-operations/src/install.rs`:
- Around line 248-276: Update the ambiguity check in the resolution flow before
target_pkg_id is selected so variants are considered equivalent only when both
their pkg_id and pkg_family match. When pkg_id is absent, differing families
must return ResolveResult::Ambiguous rather than selecting variants[0]; preserve
the existing candidate construction and downstream name/id/family filter
behavior for unambiguous results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b769e183-51cd-4c0a-a203-25ea7120cb9b

📥 Commits

Reviewing files that changed from the base of the PR and between 203b5c0 and 1ad8b75.

📒 Files selected for processing (22)
  • crates/soar-cli/src/download.rs
  • crates/soar-cli/src/install.rs
  • crates/soar-cli/src/json2db.rs
  • crates/soar-cli/src/utils.rs
  • crates/soar-core/src/database/models.rs
  • crates/soar-core/src/package/install.rs
  • crates/soar-core/src/package/local.rs
  • crates/soar-core/src/package/update.rs
  • crates/soar-db/src/repository/core.rs
  • crates/soar-db/src/repository/metadata.rs
  • crates/soar-dl/src/download.rs
  • crates/soar-operations/src/apply.rs
  • crates/soar-operations/src/install.rs
  • crates/soar-operations/src/list.rs
  • crates/soar-operations/src/remove.rs
  • crates/soar-operations/src/search.rs
  • crates/soar-operations/src/switch.rs
  • crates/soar-operations/src/types.rs
  • crates/soar-operations/src/update.rs
  • crates/soar-package/src/formats/common.rs
  • crates/soar-package/src/traits.rs
  • crates/soar-utils/src/version.rs
🚧 Files skipped from review as they are similar to previous changes (14)
  • crates/soar-core/src/package/update.rs
  • crates/soar-package/src/formats/common.rs
  • crates/soar-operations/src/switch.rs
  • crates/soar-operations/src/search.rs
  • crates/soar-package/src/traits.rs
  • crates/soar-operations/src/types.rs
  • crates/soar-dl/src/download.rs
  • crates/soar-utils/src/version.rs
  • crates/soar-cli/src/json2db.rs
  • crates/soar-operations/src/list.rs
  • crates/soar-operations/src/apply.rs
  • crates/soar-core/src/database/models.rs
  • crates/soar-core/src/package/install.rs
  • crates/soar-db/src/repository/metadata.rs

Comment on lines +78 to +86
// Re-resolve with the specific selected package. The
// family has to come along, or a name ambiguous within one
// repository resolves right back to the same choice.
let specific_query = match &pkg.pkg_family {
Some(family) => {
format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
}
None => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retain legacy pkg_id when no family is available.

For older indexes, a selected package may have pkg_family = None but a populated pkg_id. The None branch converts it to name:repo, which can become ambiguous or resolve to a different legacy variant despite the retained name#pkg_id syntax. Prefer family/name:repo, otherwise name#pkg_id:repo, and only use name:repo when both are absent.

Proposed identity fallback
-                    let specific_query = match &pkg.pkg_family {
-                        Some(family) => {
-                            format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
-                        }
-                        None => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
+                    let specific_query = match (&pkg.pkg_family, &pkg.pkg_id) {
+                        (Some(family), _) => {
+                            format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
+                        }
+                        (None, Some(pkg_id)) => {
+                            format!("{}#{}:{}", pkg.pkg_name, pkg_id, pkg.repo_name)
+                        }
+                        (None, None) => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
                     };

Also applies to: 209-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/install.rs` around lines 78 - 86, Update the
specific_query construction for the selected package: prefer the
family/name:repo form, otherwise retain pkg_id using name#pkg_id:repo, and fall
back to name:repo only when both pkg_family and pkg_id are absent. Apply the
same fallback logic to the corresponding alternate block noted in the comment.

Comment on lines +97 to +112
let target_pkg_name = installed[0].pkg_name.clone();
// Without an id, filtering on it alone selects every
// installed package in the repository, which would remove
// far more than was asked for.
let (name_filter, id_filter) = match target_pkg_id.as_deref() {
Some(id) => (None, Some(id)),
None => (Some(target_pkg_name.as_str()), None),
};
// Find all packages with this identity
let all_installed: Vec<InstalledPackage> = diesel_db
.with_conn(|conn| {
CoreRepository::list_filtered(
conn,
query.repo_name.as_deref(),
None,
Some(&target_pkg_id),
name_filter,
id_filter,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve family identity in id-less package lookups.

Both paths treat a missing pkg_id as name-only identity, but declarative packages use pkg_family to distinguish variants. This can remove a different family or incorrectly skip an install.

  • crates/soar-operations/src/remove.rs#L97-L112: carry the selected family into the #all lookup and filter candidates by it.
  • crates/soar-cli/src/install.rs#L348-L352: require the selected package family when choosing an existing installation.
📍 Affects 2 files
  • crates/soar-operations/src/remove.rs#L97-L112 (this comment)
  • crates/soar-cli/src/install.rs#L348-L352
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-operations/src/remove.rs` around lines 97 - 112, Preserve family
identity for id-less package selection: in crates/soar-operations/src/remove.rs
lines 97-112, carry the selected package’s pkg_family into the `#all` lookup and
filter candidates by that family; in crates/soar-cli/src/install.rs lines
348-352, require the selected package family when choosing an existing
installation. Keep id-based lookup behavior unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant