Emit E_PROVISION_CONFLICT for uv sync resolution conflicts - #6479
Emit E_PROVISION_CONFLICT for uv sync resolution conflicts#6479rugpanov wants to merge 4 commits into
Conversation
*Why* `databricks environments setup-local` had a single `--constraints-only` mode that bundled "skip databricks-connect" together with the rest of the setup. A caller (the VS Code extension, agents) that wants to write project files but defer provisioning, or manage its own dependency pins, had no way to express that. The setup steps are independent, so the flags that control them should be too. *What* Adds three orthogonal, composable negative flags: - `--no-constraints` skips writing the remote Python-version and dependency pins (requires-python and the [tool.uv] constraint block); any existing values are left untouched, and provisioning still installs the resolved Python (the flag governs only what is written). - `--no-dbconnect` skips the databricks-connect dependency. Equivalent to the existing `--constraints-only`, which stays as-is for now. - `--no-provision` writes the project files through the merge phase, then stops: no Python download, uv sync, or validation. Because it never invokes uv, its preflight no longer requires or installs uv either — a files-only run works on a machine without uv. Introduces a new `skipped` phase status (distinct from `pending`, which means an earlier phase failed): the provision and validate phases report `skipped` under `--no-provision`, `venvPath` is omitted, and the dry-run plan drops `wouldInstallPython`. The text summary gains a dedicated "provisioning skipped" variant so it no longer prints an empty venv path or a broken activation hint. The `--no-constraints` "unmanaged" signal is a nil ConstraintDeps / empty requires-python; parseConstraints now normalizes a missing [tool.uv].constraint-dependencies to a non-nil empty slice so that nil uniquely means the flag, not merely an artifact that omits the section. Default runs (no new flags) are byte-for-byte unchanged; the JSON schemaVersion stays at 1 since the new status only appears when a new flag is passed. *Verification* - Unit tests (libs/localenv): no-provision writes files then skips provision/validate without invoking uv, dry-run plus no-provision marks them skipped and drops wouldInstallPython, no-constraints leaves existing pins untouched and omits them greenfield, parseConstraints normalizes missing constraint-dependencies, and the merge/render skip guards. - Acceptance goldens: no-provision (real run), no-constraints, no-dbconnect, no-provision-dry-run, no-provision-text, plus the refreshed help output. - gofmt, go vet, and full go build ./... clean. Co-authored-by: Isaac <no-reply@databricks.com>
`databricks environments setup-local` wrapped every `uv sync` failure as
E_PROVISION. The extension's recovery flow needs to tell a dependency-resolution
failure — the remote pins just written can't be satisfied against the user's
local dependencies — apart from a generic sync failure it can't fix by adjusting
constraints.
Classify a `uv sync` failure as the new E_PROVISION_CONFLICT when uv's stderr
carries its resolver banner ("No solution found when resolving dependencies");
every other sync failure keeps E_PROVISION. The failing phase (provision),
diskMutated=true, and the E_CANCELED reclassification are unchanged. Adds the
matching telemetry enum value and an acceptance golden that drives a real
setup-local run against a fake uv failing sync with the resolver banner.
Co-authored-by: Isaac <no-reply@databricks.com>
Three independent reviewers converged: matching uv's "No solution found" stderr classified every resolution failure — including an unavailable package, which relaxing constraints can't fix — as a conflict, broader than the ticket's "remote pins conflict with the user's dependencies" and misleading to consumers. Gate the code on the CLI's own detection instead: emit E_PROVISION_CONFLICT when a uv sync failure coincides with the merge phase's W_USER_CONSTRAINT_CONFLICT warning (a provable disjoint-version conflict), else keep E_PROVISION. Removes the stderr string-matching (and its brittleness / the errors-string-matching concern) entirely. Adds pipeline unit tests for the conflict and generic-failure paths; the acceptance golden and telemetry mapping are unchanged in outcome. Co-authored-by: Isaac <no-reply@databricks.com>
Make explicit in provision() that E_PROVISION_CONFLICT gating on the merge's W_USER_CONSTRAINT_CONFLICT signal can coincide with an unrelated sync failure — and that this is intentional: the warning proves a real conflict exists, so relaxing the pins is a necessary step regardless, and uv stays the source of truth for whether sync fails while the CLI's own detection classifies why. No behavior change. Raised in review (Codex/Claude), kept as-is per decision. Co-authored-by: Isaac <no-reply@databricks.com>
Approval status: pending
|
| // keeps. Gating on the merge signal rather than uv's stderr keeps the code | ||
| // precise: it fires only when a conflict the CLI itself detected is present. | ||
| // | ||
| // The warning means the merged pins are provably unsatisfiable, so a real |
There was a problem hiding this comment.
If we already know the pins are unsatisfiable why do we wait for uv sync to fail? Why not error right away?
7d3ec5f to
f9ffddd
Compare
Why
databricks environments setup-localwrapped everyuv syncfailure asE_PROVISION. The VPEX extension's recovery flow needs to distinguish adependency version conflict — the project's dependencies can't be satisfied
against the pins this command wrote — from a generic sync failure it can't fix by
relaxing constraints (a build-backend error, a permissions problem, a transport
error, or an unavailable package). Constraints are already written at the point of
failure (merge precedes provision, so
error.diskMutated=true), which the recoveryflow relies on. Fixes DECO-28365.
What
E_PROVISION_CONFLICT(libs/localenv/result.go), emittedfrom the provision phase.
output. The merge phase already detects a provable version conflict between the
user's dependencies and the environment pins and records it as the
W_USER_CONSTRAINT_CONFLICTwarning (libs/localenv/warnings.go, PEP 440interval math on provably-disjoint ranges). When a
uv syncfailure coincideswith that warning, the failure is that conflict surfacing, so it is reported as
E_PROVISION_CONFLICT(libs/localenv/pipeline.go,hasConstraintConflictWarning).Every other sync failure keeps
E_PROVISION.requires-python mismatch (neither is a
W_USER_CONSTRAINT_CONFLICT) staysE_PROVISION, so a consumer never gets aconflictcode for a failure thatrelaxing constraints can't fix. It also needs no stderr string-matching.
provision),diskMutated=true, and theE_CANCELEDreclassification are unchanged.
PostProvision(pip seed) andEnsurePythonkeep their existing codes.
SetupLocalErrorCodeProvisionConflictand the mapping case(
cmd/environments/telemetry.go,libs/telemetry/protos/setup_local.go), keptexhaustive by the linter and
TestErrorCodeCoversLocalenv. A matching value inthe universe lumberjack proto should follow (ingestion ignores unknown values,
so the two PRs can land in either order).
Testing
libs/localenv/pipeline_test.go): auv syncfailure with amerge
W_USER_CONSTRAINT_CONFLICT→E_PROVISION_CONFLICT(
failurePhase=provision,diskMutated=true); auv syncfailure withoutit — even one whose stderr looks like a resolver error →
E_PROVISION. Bothcross-platform.
acceptance/localenv/provision-conflict/): a realsetup-localrun where the user pins
pip==24.0and the remote constraints pinpip<24, sothe merge flags the conflict; a fake
uvon PATH (thepsqlacceptanceprecedent — a real
uv syncconflict needs a specific managed Python installedoffline, which CI can't guarantee) fails
uv sync. The golden assertserror.code=E_PROVISION_CONFLICT,failurePhase=provision,diskMutated=true,and the
W_USER_CONSTRAINT_CONFLICTwarning. Windows-disabled like thepsqltests; the unit tests cover the logic cross-platform.
gofmt,go vet,golangci-lint(root module), andgo build ./...clean.This pull request and its description were written by Isaac.