aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/installer_executor
AgeCommit message (Collapse)Author
2026-05-03fix(resolver): strip inline #ref and gate alias trace on alias stabilitynsfisis
Two related parity gaps surfaced by the `alias-with-reference` fixture: 1. A root require like `dev-main#abcd as 1.0.0` left the SAT-side constraint as `dev-main#abcd`, which no candidate matched, so resolution failed before the alias could be materialized. Mirror Composer's `extractAliases` regex (which captures only the constraint up to `#`) and `RootPackageLoader::extractReferences` (which records the hash separately): drop the trailing `#hex` from the resolver-side constraint and from the alias's left-hand side. Lockfile generation already pulls the reference back out of the raw require map for the post-resolve override. 2. `MarkAliasInstalled`'s trace line gated the reference suffix on the *target* package's stability, so a stable alias like `1.0.0` pointing at a dev-branch target rendered as `1.0.0 abcd`. Mirror `AliasPackage::getFullPrettyVersion`: the alias decides on its own whether to append the suffix based on its own normalized version, so a stable alias skips the suffix even when the target is dev.
2026-05-03fix(install): emit MarkAliasUninstalled and fix dev-branch upgrade directionnsfisis
Two pieces of Composer's update-trace machinery were missing: 1. VersionParser::isUpgrade in Composer\Package\Version (which overrides the upstream Semver one) substitutes dev-master / dev-trunk / dev-default with the 9999999-dev default-branch alias, then returns true whenever either side starts with `dev-`. Mozart's is_upgrade compared via the generic version order, so dev-master → dev-foo came out as Downgrading. Port the override. 2. Transaction::calculateOperations seeds removeAliasMap from the currently-installed AliasPackages and emits MarkAliasUninstalled for every entry not covered by the new lock. Mozart never emitted those, so updating away from a branch-aliased package produced no trace line for the alias retirement. Walk installed.json's `extra.branch-alias` map, compare against the new lock's aliases[] block, and emit a MarkAliasUninstalled PackageOperation (a new variant on the executor surface — no filesystem effects, only the trace recorder cares). Unblocks update_alias, update_alias_lock2, and update_no_dev_still_resolves_dev installer fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03fix(install): switch update trace to dist-ref mode when source refs matchnsfisis
Composer's UpdateOperation::format renders the from/to versions through DISPLAY_SOURCE_REF_IF_DEV first, but if both sides come out identical it re-renders in DISPLAY_SOURCE_REF (when source refs differ) or DISPLAY_DIST_REF (when only dist refs differ) so the trace doesn't show a useless `pkg (X => X)` line. Mozart skipped the switch and emitted the default form on both halves, so a same-version-different-dist-ref update showed up as `dev-master def000 => dev-master def000` instead of `dev-master def000 => dev-master`. Add format_update_pretty_versions to render the pair Composer's way and plumb the resolved to_full_pretty through PackageOperation::Update so the trace recorder uses it verbatim. Unblocks update_installed_reference and update_picks_up_change_of_vcs_type installer fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03fix(install): treat dev-reference shifts as upgradesnsfisis
Composer's Transaction fires an UpdateOperation when an installed package's source/dist reference moved, even if the version string is unchanged — that is how a `dev-main#abcd` root require pinning a new commit propagates through `composer install`. Mozart was checking only (name, version) and short-circuiting to Skip, so the package stayed pinned to whatever reference installed.json carried. Compare references in compute_operations and route mismatches into Action::Update. The trace recorder needs the from-side display string to include the reference suffix (`dev-master abc123`) so the EXPECT output matches Composer's UpdateOperation::format; thread that through PackageOperation::Update as a separate from_full_pretty field while keeping from_version (sans suffix) for the upgrade-vs-downgrade direction check, which has to compare normalized versions like Composer's VersionParser::isUpgrade does. Unblocks update_reference, update_reference_picks_latest, and updating_dev_updates_url_and_reference installer fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02feat(resolver): add branch-alias support across the resolution pipelinensfisis
Plumb Composer's `extra.branch-alias` mechanism end-to-end so a dev branch (e.g. `dev-foobar`) can be installed alongside its numeric alias (e.g. `3.2.x-dev`) and resolve constraints written against the alias target. Concretely: - `mozart-semver`: stop treating pure-numeric `-dev` as a wildcard branch — `3.2.9999999.9999999-dev` (the form `normalizeBranch` emits) now parses as a classical version with `is_dev_branch=false`, so constraints like `3.2.*` match it. - `mozart-registry/composer_repo`: load `type: composer` repositories from `file://` URLs (legacy embedded `packages.json`). - `mozart-registry/resolver`: emit pool entries in pairs for dev branches with `extra.branch-alias`, link them via `is_alias_of`, and apply `@dev`/`@beta` etc. stability suffix flags from root requires. - `mozart-sat-resolver`: alias rules (`PackageAlias` / `PackageInverseAlias`) so alias and target install together; alias packages skipped from same-name conflict indexing. - `mozart-sat-resolver/policy`: `DefaultPolicy` now honors `prefer_stable` via Composer's stability-tier comparison. - `mozart-registry/lockfile`: split resolved set into real packages vs. alias entries; populate the `aliases[]` block. - `mozart-registry/installer_executor`: new `MarkAliasInstalled` operation; `format_full_pretty_version` mirroring `BasePackage::getFullPrettyVersion` (appends source ref[0..7] for dev/git packages). - Test harness rewrites fixture-relative `file://` URLs to absolute paths. Newly green fixtures: `install_branch_alias_composer_repo`, `alias_solver_problems`, `alias_solver_problems2`, `conflict_with_all_dependencies_option_dont_recommend_to_use_it`, `unbounded_conflict_does_not_match_default_branch_with_branch_alias`, `unbounded_conflict_does_not_match_default_branch_with_numeric_branch`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02fix(installer): match Composer's transaction order and uninstall labelnsfisis
Three coupled changes that bring `compute_operations` + the in-process trace recorder into byte-parity with Composer's `Transaction::__toString` output: - `TraceRecorderExecutor`: emit "Removing X (V)" instead of "Uninstalling X (V)" — Composer's `UninstallOperation::__toString` uses "Removing". - `install_from_lock`: run removals before installs/updates to mirror `Transaction::moveUninstallsToFront`. Both dry-run and real-execution branches now emit the same prefix order. - `topological_sort`: replace recursive DFS with the stack-based DFS that Composer uses in `Transaction::calculateOperations`. Roots are seeded reverse-alphabetically (matching `setResultPackageMaps`'s uasort with `strcmp(b, a)`), and `getProvidersInResult` is mirrored by treating a package's `provide`/`replace` keys as additional name targets when resolving a `require` link. To make the third change work end-to-end, `LockedPackage` gains typed `provide` and `replace` fields (Composer's lock preserves them; Mozart was silently dropping them). `packagist_version_to_locked_package` now copies them through. Unignores 13 installer fixtures (10 newly green from the fix, 3 that were already green-but-still-flagged): conflict_downgrade_nested, install_from_lock_removes_package, install_security_advisory_matching_dependency, load_replaced_package_if_replacer_dropped, partial_update_keeps_older_dep_* (×2), partial_update_security_advisory_matching_locked_dep, provider_packages_can_be_installed_together_with_provided_if_both_installable, remove_deletes_unused_deps, replace_priorities, update_allow_list_require_new_replace, update_allow_list_with_dependencies_require_new_replace, update_requiring_decision_reverts_and_learning_positive_literals. Installer scoreboard: 75/187 → 88/187. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02test(installer): switch fixtures to in-process harnessnsfisis
Replaces the spawn-based runner in tests/installer.rs with the in-process harness from Step E. Every fixture now goes through mozart::commands::{install,update}::run with an empty RepositorySet (Composer's `'packagist' => false` test config) and a TraceRecorderExecutor (Composer's InstallationManagerMock), and the EXPECT section is now asserted against the recorder's trace - load-bearing for behavior parity, not just exit-code. The original CI failure (suggest_replaced) is now legitimately tested: the empty RepositorySet makes b/b unreachable just like Composer's test config, the inline package repo's eager preload finds c/c which replaces b/b, and the topological install order in compute_operations produces the c/c -> a/a trace the fixture pins. Strict trace assertion surfaced 60 Mozart-vs-Composer divergences that the exit-code-only spawn runner had been silently ignoring. Each is marked `installer_fixture\!(name, ignore)` for now; the categories break down roughly as: - alias handling (alias_in_lock2, install_aliased_alias, update_alias*) - replace / provider trace shape (replace_priorities, provider_satisfies_its_own_requirement, replacer_*) - update direction strings (update_changes_url, update_reference, update_dev_*) - partial-update + lock interactions (partial_update_*) - allow-list with replace/dependency interactions (update_allow_list_with_dependencies_require_new*) These each become individual follow-up Mozart bugs rather than mass silent-pass. Also marks prefer_lowest_branches as ignore: it's a real flake driven by HashSet iteration order in the resolver, where two equivalent candidates can be picked in either order. That's a separate determinism bug worth its own fix. The proxy-hack env-vars in mozart-test-harness::runner are removed - no test currently spawns the binary, and the in-process harness expresses Packagist disablement directly via RepositorySet::empty rather than relying on TCP failure to suppress network calls. Headline numbers: 75 passed (in-process, exit-code + EXPECT trace) + 112 ignored, vs prior 136 passed (spawn, exit-code only) + 51 ignored. The drop in passing count reflects the stricter assertion bar, not new regressions. Also removes tests/installer_in_process.rs - its single proof-of- concept fixture (suggest_replaced) is now part of the unified installer.rs harness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02feat(installer): add trace recorder and topo install ordernsfisis
Adds TraceRecorderExecutor (Composer's InstallationManagerMock analog), which records every install/update/uninstall as a string matching Composer's *Operation::__toString output (after strip_tags) - the load-bearing assertion target for in-process fixture tests. Two changes were needed to make the recorder useful: - InstallerExecutor::uninstall_package gains a version parameter, and install_from_lock now looks up both the uninstall and the Update-from-version from installed.json. Previously the Update path passed the new version as a placeholder; the recorder needs the real old version to emit `Upgrading pkg (old => new)`. - compute_operations now topologically sorts the lock contents (deps before dependents) before computing actions, mirroring Composer's Transaction::calculateOperations. Without this, packages would install in alphabetical order and the trace would diverge from Composer's expectation. Also adds crates/mozart/tests/installer_in_process.rs with the in-process harness scaffold: parses the same .test fixtures, builds a tempdir, calls commands::install::run / update::run with an empty RepositorySet (no Packagist) and a TraceRecorderExecutor, then asserts exit code + EXPECT trace. One fixture wired up: suggest_replaced - the original CI failure that motivated this whole DI refactor. It now passes on the in-process path because the empty RepositorySet makes b/b unreachable just like Composer's `'packagist' => false` test config, and the resolver finds c/c (which replaces b/b) via the inline package repo's eager preload. Step F will migrate every fixture currently in installer.rs to the new harness; remaining divergences (alias handling, output ordering, replace trace shape, etc.) will surface as individual follow-ups. All 136 existing spawn-based fixtures + 114 mozart-registry tests + 541 mozart lib tests still green; clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02refactor(registry): introduce Repository and InstallerExecutor traitsnsfisis
Sets up DI scaffolding for in-process installer E2E tests, mirroring how Composer's PHPUnit suite swaps Packagist (FactoryMock) and the install manager (InstallationManagerMock) without touching the network or filesystem. Additions: - Repository trait + RepositorySet (Composer's RepositoryInterface analog), with PackagistRepository, InlinePackageRepository, VcsRepository impls. - InstallerExecutor trait (Composer's InstallationManager analog) with FilesystemExecutor extracted from install_from_lock. install_from_lock now delegates per-package install/uninstall verbs to FilesystemExecutor; console output orchestration stays in the caller so existing --EXPECT-OUTPUT-shape assertions remain comparable. No behavior change - all 136 enabled installer fixtures still pass. Also tightens the installer_fixture\! ignore form to a single token (installer_fixture\!(name, ignore)) for readability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>