Skip to content

[VPEX][5b] Add local-env six-phase pipeline orchestrator - #5851

Merged
rugpanov merged 4 commits into
mainfrom
dbconnect/05b-pipeline
Jul 13, 2026
Merged

[VPEX][5b] Add local-env six-phase pipeline orchestrator#5851
rugpanov merged 4 commits into
mainfrom
dbconnect/05b-pipeline

Conversation

@rugpanov

@rugpanovrugpanov commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Why

  • The earlier layers (resolve / fetch / merge) need an orchestrator that runs them in order, reports structured per-phase status, and provisions the environment — without mutating disk in --check dry-run mode.
  • This is the core half of the split described in [VPEX][5a] Add local-env package-manager interface, detection, and preflight #5850: the six-phase pipeline itself, stacked on the package-manager/detection layer.

What

  • pipeline.go — the six-phase orchestrator (preflight → resolve → fetch → merge → provision → validate). It records per-phase status into Result and returns typed PipelineErrors carrying FailurePhase and DiskMutated. Under --check it computes and reports the plan (with a diff) and performs no writes, skipping the writability probe and package-manager availability (neither is needed to compute the plan).
  • The merge base is the live pyproject.toml. MergeManaged rewrites only the three managed regions and is idempotent on its own output, so a re-run preserves edits the user made between syncs. The .bak is a one-time safety copy of the pre-sync original (created only when none exists, on the first sync of an existing project); an unreadable or unstattable existing file fails loudly rather than being misread as greenfield and overwritten.
  • constraints-only stops managing the databricks-connect pin rather than removing it: a greenfield project renders dev = [] (no databricks-connect), and an existing project that already pins databricks-connect keeps its pin untouched.

Testing strategy

  • End-to-end unit tests of the phase machine against a fake PackageManager + stub compute + httptest server: --check mutates nothing (even on a read-only dir / without a package manager) and reports a plan that matches a real re-run (no spurious backup, empty diff when idempotent), greenfield vs. existing, the merge basing on the live file so between-sync edits survive, backup safety on unreadable/unstattable files, constraints-only behavior, preflight exits (E_MANAGER_UNSUPPORTED / E_UV_MISSING), and phase/error attribution (pipeline_test.go).
  • Gates: go build, go test, golangci-lint (0 issues), deadcode, gofmt — all green.

About this stack

Review bottom-up. This PR targets #5850 as its base, so its diff shows only the pipeline layer.

This PR and #5850 together supersede #5828.

This pull request and its description were written by Isaac.

@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 8, 2026 09:58 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 8, 2026 09:58 — with GitHub Actions Inactive
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 2ef43e0

Run: 29096837578

Env🟨​KNOWN🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
💚​aws linux4423010695:04
💚​aws windows4423210676:57
💚​aws-ucws linux443149875:38
💚​aws-ucws windows443169857:27
💚​azure linux4423010684:58
💚​azure windows4423210667:14
💚​azure-ucws linux443169845:55
🔄​azure-ucws windows2443169827:55
🟨​gcp linux11422910707:35
💚​gcp windows4423110687:02
10 interesting tests: 4 SKIP, 3 RECOVERED, 2 flaky, 1 KNOWN
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
💚​TestAccept💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
🙈​TestAccept/bundle/invariant/no_drift🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/ssh/connection🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestFsCpFileToNonExistentDir/local_to_uc-volumes🙈​s🙈​s✅​p✅​p🙈​s🙈​s✅​p🔄​f🙈​s🙈​s
🔄​TestFsCpFileToNonExistentDir/uc-volumes_to_uc-volumes🙈​s🙈​s✅​p✅​p🙈​s🙈​s✅​p🔄​f🙈​s🙈​s
🟨​TestFetchRepositoryInfoAPI_FromRepo💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R🟨​K💚​R
💚​TestFetchRepositoryInfoAPI_FromRepo/root💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
💚​TestFetchRepositoryInfoAPI_FromRepo/subdir💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
Top 10 slowest tests (at least 2 minutes):
durationenvtestname
6:13aws-ucws windowsTestAccept
6:12gcp windowsTestAccept
6:12azure windowsTestAccept
6:08azure-ucws windowsTestAccept
6:07aws windowsTestAccept
3:03aws linuxTestAccept
2:55azure linuxTestAccept
2:53gcp linuxTestAccept
2:51aws-ucws linuxTestAccept
2:50azure-ucws linuxTestAccept

Split from the pipeline layer so the PackageManager seam (implemented by
the uv backend in a later PR), manager detection, and the writability
preflight land as a small, independently reviewable unit.
- pkgmanager.go: the PackageManager interface the pipeline provisions through.
- detect.go: uv-vs-not-uv detection (biased toward uv, whose native project
file is the pyproject.toml this command drives), the non-blaming guidance
message for unsupported managers, and the ensureWritable preflight.
Co-authored-by: Isaac
The orchestrator runs the layers in order (preflight → resolve → fetch →
merge → provision → validate), records per-phase status into Result, and
returns typed PipelineErrors carrying FailurePhase and DiskMutated. Under
--check it computes and reports the plan (with a diff) and performs no
writes — skipping the writability probe and package-manager availability,
neither of which is needed to compute the plan.
Backups are created only when no .bak exists and the original is a genuine
os.ErrNotExist-free read; an unreadable or unstattable existing file fails
loudly rather than being misread as greenfield and overwritten.
Stacked on the package-manager/detection layer; provisions through the
PackageManager interface against a fake in tests.
Co-authored-by: Isaac
@rugpanov
rugpanovforce-pushed the dbconnect/05a-pkgmanager branch from cb166cb to f24c320CompareJuly 8, 2026 14:44
@rugpanov
rugpanovforce-pushed the dbconnect/05b-pipeline branch from af9d41b to c2e8b69CompareJuly 8, 2026 14:44
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 8, 2026 14:44 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 8, 2026 14:44 — with GitHub Actions Inactive
}
if err := os.WriteFile(dst, data, 0o644); err != nil {
return fmt.Errorf("write %s: %w", dst, err)
}

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.

copyFile creates the .bak at a hardcoded 0o644. If the user's pyproject.toml is locked down (e.g. 0o600 because it carries a private index URL or token), the backup silently becomes world-readable.

The main-file write at applyMerge is fine only incidentally — os.WriteFile ignores the mode arg when the file already exists, so the existing pyproject keeps its perms. But the backup is freshly created here, so 0o644 actually applies.

Suggest either preserving the source mode (os.Stat(src) → pass info.Mode()) or, if 0o644 is deliberate, a one-line comment saying so (the repo's comment-the-why rule).


This comment was generated with GitHub MCP.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch — fixed in 6f31579. copyFile now stats the source and creates the backup with info.Mode().Perm(), so a 0o600 pyproject (private index URL/token) is no longer widened to world-readable. Added TestCopyFilePreservesMode to lock it in.

// Phase: preflight — manager detection, writability, package-manager availability.
// P0 supports only uv; any other detected manager is a clean, non-blaming exit.
if m := detectManager(p.ProjectDir); m != managerUv {
return p.fail(PhasePreflight, false, NewError(ErrManagerUnsupported, nil, "%s", managerGuidance(m)))

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.

Two preflight exits look untested at the pipeline level:

  • this unsupported-manager path → E_MANAGER_UNSUPPORTED, and
  • the EnsureAvailable failure below → E_UV_MISSING (line ~113).

detect_test.go covers detectManager in isolation, but not the pipeline's wiring of it into a clean preflight failure (correct phase attribution, DiskMutated=false, later phases still pending). The noProvisionPM fixture already exists — a test that drops an environment.yml and asserts the E_MANAGER_UNSUPPORTED / PhasePreflight result would close the gap cheaply.


This comment was generated with GitHub MCP.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 6f31579. Added TestPipelineManagerUnsupportedFailsAtPreflight (drops an environment.ymlE_MANAGER_UNSUPPORTED) and TestPipelineUvMissingFailsAtPreflight (new uvMissingPM fixture → E_UV_MISSING). Both assert phase attribution (PhasePreflight), DiskMutated=false, and that all later phases stay pending, via a shared assertPreflightFailure helper.

Address review on the pipeline PR:
- copyFile created the .bak at a hardcoded 0o644, silently widening a
locked-down pyproject.toml (e.g. 0o600 carrying a private index URL) to
world-readable. Stat the source and create the backup with its permission
bits; os.WriteFile applies the mode only when creating the file, which is
always the case for the fresh .bak.
- Add pipeline-level tests for the two preflight exits that were only
covered in isolation before: E_MANAGER_UNSUPPORTED (a non-uv project) and
E_UV_MISSING (package manager unavailable), asserting phase attribution,
DiskMutated=false, and that later phases stay pending. Add a direct
copyFile mode-preservation test.
Co-authored-by: Isaac
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 9, 2026 14:06 — with GitHub Actions Inactive
@rugpanov
rugpanovtemporarily deployed to test-trigger-is July 9, 2026 14:06 — with GitHub Actions Inactive
@rugpanov
rugpanov requested a review from anton-107July 10, 2026 11:26

@anton-107anton-107 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.

Nice work — phase/error attribution is airtight, the backup-safety branches are exactly right, and --check correctly avoids touching the machine or disk. My concerns below are about behavior, not code hygiene. The first is the headline one.

1. Re-basing the merge on .bak silently discards user edits made after the first sync

mergePlan uses the backup as the canonical merge base whenever a .bak exists (pipeline.go:227-238), and TestPipelineRestoresBackupBeforeMerge asserts this is intended. The consequence is that on re-runs the merge no longer starts from the livepyproject.toml — it starts from the pristine original captured on the first sync.

I confirmed the fallout with a scratch test:

  • Sync once (creates .bak).
  • Developer hand-edits the live pyproject.toml — e.g. adds dependencies = ["rich", "requests"].
  • Sync again (e.g. after switching compute target).
  • The requests edit is gone — the second merge re-based on .bak, which never saw it.

This defeats the purpose of the byte-preserving MergeManaged (which exists specifically to touch only the three managed regions and preserve everything else). And re-basing on .bak buys nothing for the managed regions: MergeManaged is idempotent on its own output and preserves non-managed deps, so merging onto the current live file produces identical managed-region results — without throwing away edits. The only marginal difference is self-healing a databricks-connect pin the user manually deleted, which seems like an odd thing to optimize for at the cost of silent edit loss.

The .bak original is recoverable, but edits made between syncs are not — and that's the normal workflow (sync, add a dep, re-sync). Could you confirm whether this is truly intended? I'd argue the live file should be the merge base and .bak should remain purely a one-time safety copy. There's also no test covering "second run preserves a non-managed user edit" — today that test would fail.

2. --check reports a plan a real re-run wouldn't perform (downstream of #1)

On a re-run where .bak already exists, two things in the plan are inaccurate:

  • plan.WouldBackup is always set for a non-greenfield project (pipeline.go:297-299), but a real re-run keeps the existing .bak and does not write a backup (applyMerge, pipeline.go:317-320). So --check claims a backup that won't happen.
  • The diff base is .bak, not the live file (pipeline.go:279-289). On an idempotent re-run the live file already equals the merged output — a real re-run changes nothing — yet --check prints a full diff against the original. I confirmed both: the plan shows the whole managed block as an addition even though the on-disk file is already identical to what the run would write.

Lower severity since it's advisory output, but a dry-run whose plan doesn't match the real run is misleading. Fixing #1 fixes this too.

3. constraints-only doesn't "omit the databricks-connect dependency entirely" on existing projects

The PR description says constraints-only "omits the databricks-connect dependency entirely," and the pipeline clears dbcPin (pipeline.go:144-147). That's accurate for greenfield (dev = []). But on an existing project that already pins databricks-connect, the empty value makes mergeDatabricksConnect a no-op, so the existing pin is retained (confirmed by scratch test). This is intended per the merge.go comment ("left untouched rather than ... blanked out") and is defensible — but the description over-claims. Please align the wording, or document that constraints-only only stops managing the pin rather than removing it.

Address review on the pipeline PR (concerns #1 and #2).
Re-basing the merge on .bak silently discarded edits the user made to
pyproject.toml between syncs: mergePlan used .bak as the merge base whenever
one existed, so a re-run started from the pristine pre-sync original and any
dependency the user added afterward was lost. MergeManaged rewrites only the
three managed regions and is idempotent on its own output, so merging onto
the live file yields identical managed regions without throwing away edits.
- mergePlan now reads the live pyproject.toml as the merge base; .bak stays a
one-time safety copy of the pre-sync original.
- --check no longer reports a backup on a re-run (applyMerge keeps the
existing .bak, so WouldBackup is set only when no .bak exists yet), and the
diff is now against the live file, so an idempotent re-run reports no change
— matching what a real run would do.
- Replace TestPipelineRestoresBackupBeforeMerge (which encoded the old
discard-edits behavior) with TestPipelineMergesOnLiveFileNotBackup, and add
TestPipelineCheckReRunPlanMatchesRealRun.
Also clarify the constraints-only comment: it stops managing the
databricks-connect pin (greenfield dev = []; existing pin retained) rather
than removing it, matching mergeDatabricksConnect's no-op-on-empty behavior.
Co-authored-by: Isaac
@rugpanov

Copy link
Copy Markdown
ContributorAuthor

Verified all three against the code (reproduced #1 and #3 with scratch tests first) and pushed fixes in 2ef43e0.

#1 — re-basing on .bak discards between-sync edits. Confirmed, and it's a real bug: I reproduced the exact scenario (sync → add a non-managed dep → re-sync → dep gone). Fixed — mergePlan now uses the live pyproject.toml as the merge base, not .bak. As you noted, MergeManaged is idempotent on its own output and preserves non-managed content, so merging onto the live file produces identical managed regions without throwing away edits (I verified the second merge reports zero changed regions). .bak stays purely a one-time safety copy of the pre-sync original.

  • Replaced TestPipelineRestoresBackupBeforeMerge (which encoded the old discard-edits behavior) with TestPipelineMergesOnLiveFileNotBackup: a .bak holding a different original + a live file with a user-added dependencies = ["rich"] → the edit survives, managed regions still apply, .bak untouched.

#2--check plan inaccurate on re-runs. Fixed, and follows from #1:

  • plan.WouldBackup is now set only when no .bak exists yet, matching applyMerge (which keeps the existing .bak on a re-run).
  • The diff base is now the live file, so an idempotent re-run reports an empty diff — matching what a real run would do.
  • New TestPipelineCheckReRunPlanMatchesRealRun asserts both (empty WouldBackup, empty diff after a prior sync).

#3constraints-only wording over-claims. Agreed — behavior is intended (existing pin retained; empty value is a no-op in mergeDatabricksConnect), only the description was wrong. Reworded the code comment at the dbcPin clearing site to say constraints-only stops managing the pin (greenfield → dev = []; existing → pin retained) rather than removing it. I'll fix the PR description wording too.

All gates green (build, test, lint, deadcode, gofmt). #5854 was cascade-rebased on top.

Base automatically changed from dbconnect/05a-pkgmanager to mainJuly 13, 2026 09:49
@anton-107
anton-107 self-requested a review July 13, 2026 14:05

@anton-107anton-107 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.

Approving — the orchestration design is well-defended (correct phase ordering, first-error-aborts, fail-closed greenfield detection, backup safety). Two correctness issues worth a look before this hits the core existing-project flow; left them inline. Neither blocks the design.

if greenfield {
// No existing pyproject.toml — render a fresh one. The project name comes
// from the directory name as a reasonable default.
projectName := filepath.Base(p.ProjectDir)

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.

Greenfield renders an invalid .name when ProjectDir is a relative or root path.

projectName := filepath.Base(p.ProjectDir) — when ProjectDir is . (the common case of running from the project's own directory) filepath.Base returns .; "" also returns ., and / returns /. RenderFreshPyproject then writes name = "." verbatim, which isn't a valid PEP 621/PEP 508 name (must start and end with an alphanumeric), so uv sync rejects the generated file and provisioning fails. ProjectDir isn't normalized anywhere in this PR.

Suggest normalizing to an absolute path before taking the base (e.g. filepath.Abs then filepath.Base) so ./"" resolve to the real directory name, and falling back to a safe default (or sanitizing to a valid PEP 508 identifier) when the base still isn't usable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Confirmed and fixed in #5854 (commit 4bd0477) — this PR was already queued to merge, so the fix lands in the stacked follow-up rather than reopening this one. projectName() now resolves ./""/root via filepath.Abs before taking the base and sanitizes to a valid PEP 508 name (non-alnum runs → -, trimmed), falling back to app when nothing usable remains. Covered by TestProjectName and TestPipelineGreenfieldFromDotDirRendersValidName.

"cannot determine databricks-connect major version from pin %q", dbcPin))
}
installedMajor := majorVersion(dbcVer)
if installedMajor == "" {

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.

Default-mode sync fails for an existing project that doesn't already pin databricks-connect.

For a non-greenfield project the merge goes through mergeDatabricksConnect, which only replaces an existing databricks-connect element — it never inserts one. It returns a no-op (lines, false) in every no-entry branch: no [dependency-groups] table, no dev key, single-line array without the token, or multi-line with no match (merge.go:241-292). So an existing pyproject.toml with a dev group but no databricks-connect is written back with no pin added; uv sync installs nothing; and the default-mode assertion here fails with cannot determine installed databricks-connect major version (E_VALIDATE).

Greenfield only works because RenderFreshPyproject inserts the pin — a path the existing-project merge lacks. Suggest having the merge insert a databricks-connect entry into [dependency-groups].dev (creating the group/key when absent) when none exists, mirroring RenderFreshPyproject, rather than treating a missing entry as a no-op.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Confirmed (reproduced: existing project with a dev group but no databricks-connect → no-op → E_VALIDATE) and fixed in #5854 (commit 4bd0477). mergeDatabricksConnect now inserts the pin when absent, mirroring RenderFreshPyproject: into the dev array (single- and multi-line), and creating the dev key / [dependency-groups] table when those are missing. Constraints-only (empty value) stays a no-op, and the insert path is idempotent. Covered by TestMergeInserts{DatabricksConnectMultiLine,SingleLine,EmptyDev,DevKeyWhenAbsent,DependencyGroupsWhenAbsent} and TestMergeConstraintsOnlyDoesNotInsertDatabricksConnect.

@rugpanov
rugpanov added this pull request to the merge queueJul 13, 2026
rugpanov added a commit that referenced this pull request Jul 13, 2026
Address two correctness nits from review of the pipeline PR (#5851). Since
that PR was already queued to merge, the fixes land here in the follow-up.
- mergeDatabricksConnect only ever *replaced* an existing databricks-connect
element, so a default-mode sync of an existing project that didn't already
pin databricks-connect wrote the file back with no pin; uv installed nothing
and validate failed with E_VALIDATE. It now *inserts* the pin — into the dev
array (single- or multi-line), creating the dev key and the
[dependency-groups] table when absent — mirroring RenderFreshPyproject.
Constraints-only (empty value) stays a no-op; the insert path is idempotent.
- Greenfield rendered name = filepath.Base(ProjectDir), which is "." when run
from the project's own directory (and "/" at root) — not a valid PEP 621
name, so uv sync rejected the file. projectName() now resolves via
filepath.Abs and sanitizes to a valid PEP 508 identifier, falling back to a
default when nothing usable remains.
Co-authored-by: Isaac
Merged via the queue into main with commit a28e610Jul 13, 2026
28 checks passed
@rugpanov
rugpanov deleted the dbconnect/05b-pipeline branch July 13, 2026 14:30
rugpanov added a commit that referenced this pull request Jul 13, 2026
Address two correctness nits from review of the pipeline PR (#5851). Since
that PR was already queued to merge, the fixes land here in the follow-up.
- mergeDatabricksConnect only ever *replaced* an existing databricks-connect
element, so a default-mode sync of an existing project that didn't already
pin databricks-connect wrote the file back with no pin; uv installed nothing
and validate failed with E_VALIDATE. It now *inserts* the pin — into the dev
array (single- or multi-line), creating the dev key and the
[dependency-groups] table when absent — mirroring RenderFreshPyproject.
Constraints-only (empty value) stays a no-op; the insert path is idempotent.
- Greenfield rendered name = filepath.Base(ProjectDir), which is "." when run
from the project's own directory (and "/" at root) — not a valid PEP 621
name, so uv sync rejected the file. projectName() now resolves via
filepath.Abs and sanitizes to a valid PEP 508 identifier, falling back to a
default when nothing usable remains.
Co-authored-by: Isaac
rugpanov added a commit that referenced this pull request Jul 13, 2026
Address two correctness nits from review of the pipeline PR (#5851). Since
that PR was already queued to merge, the fixes land here in the follow-up.
- mergeDatabricksConnect only ever *replaced* an existing databricks-connect
element, so a default-mode sync of an existing project that didn't already
pin databricks-connect wrote the file back with no pin; uv installed nothing
and validate failed with E_VALIDATE. It now *inserts* the pin — into the dev
array (single- or multi-line), creating the dev key and the
[dependency-groups] table when absent — mirroring RenderFreshPyproject.
Constraints-only (empty value) stays a no-op; the insert path is idempotent.
- Greenfield rendered name = filepath.Base(ProjectDir), which is "." when run
from the project's own directory (and "/" at root) — not a valid PEP 621
name, so uv sync rejected the file. projectName() now resolves via
filepath.Abs and sanitizes to a valid PEP 508 identifier, falling back to a
default when nothing usable remains.
Co-authored-by: Isaac
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: a28e610

Run: 29258303581

Env❌​FAIL🟨​KNOWN🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
🔄​aws linux334527101240:13
💚​aws windows44486102549:21
❌​aws-ucws linux21521001822117:11
❌​aws-ucws windows21442940840124:49
🔄​azure linux334521101340:03
💚​azure windows44480102649:21
🔄​azure-ucws linux642894857105:31
🔄​azure-ucws windows532838875107:39
🔄​gcp linux134514101847:54
🔄​gcp windows224471103159:39
18 interesting tests: 12 flaky, 2 FAIL, 2 SKIP, 1 KNOWN, 1 RECOVERED
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
🟨​TestAccept🔄​f💚​R🟨​K🟨​K🔄​f💚​R💚​R🔄​f💚​R💚​R
💚​TestAccept/bundle/invariant/no_drift🙈​S🙈​S💚​R💚​R🙈​S🙈​S💚​R💚​R🙈​S🙈​S
🔄​TestAccept/bundle/resources/dashboards/change-name🔄​f✅​p✅​p🔄​f🔄​f✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/dashboards/change-name/DATABRICKS_BUNDLE_ENGINE=direct🔄​f✅​p✅​p🔄​f🔄​f✅​p✅​p✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/dashboards/change-name/DATABRICKS_BUNDLE_ENGINE=terraform✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/genie_spaces/simple✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
🔄​TestAccept/bundle/resources/genie_spaces/simple/DATABRICKS_BUNDLE_ENGINE=direct✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p
❌​TestAccept/bundle/resources/postgres_databases/live_errors/missing_role🙈​s🙈​s❌​F❌​F🙈​s🙈​s🙈​s🙈​s🙈​s🙈​s
❌​TestAccept/bundle/resources/postgres_databases/live_errors/missing_role/DATABRICKS_BUNDLE_ENGINE=direct❌​F❌​F
🔄​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name🙈​S🙈​S💚​R🔄​f🙈​S🙈​S💚​R💚​R🙈​S🙈​S
🔄​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name/DATABRICKS_BUNDLE_ENGINE=direct✅​p🔄​f✅​p✅​p
🙈​TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestAccept/bundle/templates/default-python/integration_classic✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p
🔄​TestAccept/bundle/templates/default-python/integration_classic/DATABRICKS_BUNDLE_ENGINE=terraform/UV_PYTHON=3.10✅​p✅​p✅​p✅​p✅​p✅​p✅​p🔄​f✅​p✅​p
🙈​TestAccept/ssh/connection🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestFetchRepositoryInfoAPI_FromRepo💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R🔄​f💚​R
🔄​TestFetchRepositoryInfoAPI_FromRepo/root💚​R💚​R💚​R💚​R💚​R💚​R🔄​f🔄​f💚​R🔄​f
🔄​TestFetchRepositoryInfoAPI_FromRepo/subdir💚​R💚​R💚​R💚​R💚​R💚​R🔄​f🔄​f💚​R🔄​f
Top 50 slowest tests (at least 2 minutes):
durationenvtestname
12:54gcp windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
11:32aws-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
11:28gcp linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
11:25gcp linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
10:55azure-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
10:08aws-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=terraform
10:00aws-ucws linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
8:55gcp windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:32aws-ucws linuxTestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
8:27aws-ucws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:14azure-ucws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
8:04gcp windowsTestAccept/bundle/resources/clusters/deploy/local_ssd_count/DATABRICKS_BUNDLE_ENGINE=direct
7:56azure windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:55aws-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:51gcp windowsTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
7:46azure-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:32azure linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:31gcp windowsTestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
7:30gcp linuxTestAccept/bundle/resources/clusters/deploy/local_ssd_count/DATABRICKS_BUNDLE_ENGINE=direct
7:23aws linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:22aws-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
7:16aws linuxTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
7:09gcp linuxTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
6:57aws windowsTestAccept/bundle/resources/apps/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:46azure-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:43gcp windowsTestAccept/bundle/resources/clusters/deploy/simple/DATABRICKS_BUNDLE_ENGINE=direct
6:27azure windowsTestAccept
6:24aws-ucws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
6:08gcp linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
6:06azure-ucws linuxTestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:59aws windowsTestAccept
5:59azure-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=terraform
5:58gcp windowsTestAccept
5:49aws linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
5:43aws-ucws linuxTestAccept/bundle/config-remote-sync/multiple_resources/DATABRICKS_BUNDLE_ENGINE=direct
5:40aws windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:37azure-ucws linuxTestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:29gcp windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
5:25azure windowsTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:24gcp linuxTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
5:19gcp linuxTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=terraform
5:14gcp windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
5:13gcp windowsTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
5:10azure linuxTestAccept/bundle/resources/clusters/lifecycle-started/DATABRICKS_BUNDLE_ENGINE=direct
5:05gcp linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
5:02gcp windowsTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:36aws-ucws windowsTestAccept/bundle/resources/apps/lifecycle-started-omitted/DATABRICKS_BUNDLE_ENGINE=direct
4:26azure linuxTestAccept/bundle/resources/clusters/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct
4:04gcp windowsTestAccept/bundle/deploy/spark-jar-task/DATABRICKS_BUNDLE_ENGINE=terraform
4:03azure-ucws linuxTestAccept/bundle/resources/apps/lifecycle-started-toggle/DATABRICKS_BUNDLE_ENGINE=direct

rugpanov added a commit that referenced this pull request Jul 14, 2026
Address two correctness nits from review of the pipeline PR (#5851). Since
that PR was already queued to merge, the fixes land here in the follow-up.
- mergeDatabricksConnect only ever *replaced* an existing databricks-connect
element, so a default-mode sync of an existing project that didn't already
pin databricks-connect wrote the file back with no pin; uv installed nothing
and validate failed with E_VALIDATE. It now *inserts* the pin — into the dev
array (single- or multi-line), creating the dev key and the
[dependency-groups] table when absent — mirroring RenderFreshPyproject.
Constraints-only (empty value) stays a no-op; the insert path is idempotent.
- Greenfield rendered name = filepath.Base(ProjectDir), which is "." when run
from the project's own directory (and "/" at root) — not a valid PEP 621
name, so uv sync rejected the file. projectName() now resolves via
filepath.Abs and sanitizes to a valid PEP 508 identifier, falling back to a
default when nothing usable remains.
Co-authored-by: Isaac
rugpanov added a commit that referenced this pull request Jul 14, 2026
Address two correctness nits from review of the pipeline PR (#5851). Since
that PR was already queued to merge, the fixes land here in the follow-up.
- mergeDatabricksConnect only ever *replaced* an existing databricks-connect
element, so a default-mode sync of an existing project that didn't already
pin databricks-connect wrote the file back with no pin; uv installed nothing
and validate failed with E_VALIDATE. It now *inserts* the pin — into the dev
array (single- or multi-line), creating the dev key and the
[dependency-groups] table when absent — mirroring RenderFreshPyproject.
Constraints-only (empty value) stays a no-op; the insert path is idempotent.
- Greenfield rendered name = filepath.Base(ProjectDir), which is "." when run
from the project's own directory (and "/" at root) — not a valid PEP 621
name, so uv sync rejected the file. projectName() now resolves via
filepath.Abs and sanitizes to a valid PEP 508 identifier, falling back to a
default when nothing usable remains.
Co-authored-by: Isaac
ronaldz-db pushed a commit to ronaldz-db/cli that referenced this pull request Jul 15, 2026
…bricks-connect insertion (databricks#5854)
## Why
Follow-up fixes to the `local-env python sync` pipeline (databricks#5851, now
merged), from codex and reviewer feedback. Three independent correctness
issues in the merge / dry-run paths, none of which the pipeline PR's own
tests caught:
1. **`--check` still wrote to disk.** `FetchConstraints` populated the
on-disk constraint cache on every successful live fetch, so a dry run
mutated the cache directory even though the pipeline skips every other
write-side step under `--check`.
2. **Greenfield rendered an invalid project name.**
`filepath.Base(ProjectDir)` is `.` when run from the project's own
directory (and `/` at root) — not a valid PEP 621/508 name, so `uv sync`
rejected the generated `pyproject.toml`.
3. **Default-mode sync failed for an existing project without a
databricks-connect pin.** The merge only *replaced* an existing pin; it
never *inserted* one, so an existing `pyproject.toml` with a dev group
but no databricks-connect was written back unchanged, `uv` installed
nothing, and validation failed with `E_VALIDATE`. Greenfield worked only
because `RenderFreshPyproject` inserts the pin — a path the
existing-project merge lacked.
## What
- **`--check` no cache write** — thread a `writeCache bool` through
`FetchConstraints`; the pipeline passes `!p.Check`. An existing cache is
still *read* for offline fallback (reading is not a mutation); only the
write is suppressed under a dry run.
- **Valid greenfield name** — `projectName()` resolves `.`/`""`/root via
`filepath.Abs` before taking the base and sanitizes to a valid PEP 508
identifier (non-alphanumeric runs collapse to `-`, trimmed), falling
back to `app` when nothing usable remains.
- **Insert databricks-connect when absent** — `mergeDatabricksConnect`
now inserts the pin into `[dependency-groups].dev` (single- or
multi-line array), creating the `dev` key and the `[dependency-groups]`
table when absent, mirroring `RenderFreshPyproject`. Constraints-only
(empty value) stays a no-op. Detection is comment-aware and spans the
whole array, so a pin on the `dev = [` line or one carrying a trailing
comment is rewritten in place rather than duplicated (a duplicate-pin
bug codex caught in the first cut of this change). The insert path is
idempotent.
## Testing strategy
- `--check` purity: `TestFetchConstraintsSkipsCacheWriteWhenDisabled`,
and `TestPipelineCheckMutatesNothing` strengthened to assert the cache
dir stays empty; `TestPipelineCheckReRunPlanMatchesRealRun` for accurate
re-run plans.
- Greenfield name: `TestProjectName`,
`TestPipelineGreenfieldFromDotDirRendersValidName`.
- databricks-connect insertion:
`TestMergeInserts{DatabricksConnectMultiLine,SingleLine,EmptyDev,DevKeyWhenAbsent,DependencyGroupsWhenAbsent}`,
no-duplicate regressions
`TestMergeReplacesDatabricksConnect{OnDevLine,WithTrailingComment}`, and
`TestMergeConstraintsOnlyDoesNotInsertDatabricksConnect`.
- Gates: `go build`, `go test`, `golangci-lint` (0 issues), `deadcode`,
`gofmt` — all green.
---
## About this stack
Follow-up to the `databricks local-env python sync` stack; targets
`main` now that the pipeline PR (databricks#5851) has merged.
This pull request and its description were written by Isaac.
swearyangupta pushed a commit to swearyangupta/cli that referenced this pull request Jul 17, 2026
…ks#5832)
## Why
- The engine needs a real `PackageManager` implementation (uv) and a CLI
entry point so a user can actually run the feature.
- Wiring it in while keeping it `Hidden` lets the command be dogfooded
and exercised by acceptance tests without becoming user-visible until
the stack is complete.
- This is the first layer reachable from `main`, so it also makes the
whole `libs/localenv` package live for the `deadcode` checker.
## What
- **`libs/localenv/uv.go`** — the uv implementation of `PackageManager`:
discover/install uv, install the Python minor, `uv sync`, seed pip into
the venv, validate; plus the `pip.conf` → `UV_INDEX_URL` bridge for
Databricks-managed machines.
- **`cmd/localenv/`** — the command tree matching `local-env python
sync`: a `local-env` group (`Hidden: true`), a `python` subgroup, and
the `sync` verb. Parent nodes use `root.ReportUnknownSubcommand`; `sync`
uses `cobra.NoArgs`, resolves flags/bundle target, builds the `Pipeline`
with the uv manager, and renders text or `--json`. All Cobra `Use`
values + the `--json` command field come from the `libs/localenv`
constants.
- **`cmd/cmd.go`** — registers the group.
## Testing strategy
- Unit tests for uv helper logic (discovery, `pip.conf` index-url, arg
builders, stderr surfacing) (`uv_test.go`).
- Runtime smoke test of the hidden 3-level command: absent from
top-level help; help works at each level; unknown subcommand exits
non-zero; bare group shows help; flags + mutual-exclusion + `NoArgs`
behave.
- Gates: `go build ./...`, `go test`, `golangci-lint`, `deadcode` (whole
tree, no pragmas), `gofmt` — all green.
- `cmd/localenv/` unit tests are intentionally deferred to the
acceptance PR (databricks#5833), per the repo convention that user-visible CLI
output is covered by acceptance tests.
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Each PR targets the previous one as its base
branch (this PR targets `main`), so its diff shows only that layer.
Layers 1–5 have merged; the original layer-5 PR (databricks#5828) was split into
5a/5b/5c during review.
| # | PR | What | Status |
|---|----|------|--------|
| 1 | databricks#5823 | foundation: result types + env-key mapping | merged |
| 2 | databricks#5824 | compute-target resolution | merged |
| 3 | databricks#5826 | constraint fetch + offline cache | merged |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge | merged |
| 5a | databricks#5850 | package-manager interface + detection | merged |
| 5b | databricks#5851 | six-phase pipeline orchestrator | merged |
| 5c | databricks#5854 | --check cache purity, greenfield name, dbc insertion |
merged |
| 6 | **databricks#5832 ← you are here** | uv backend + CLI command (registered
hidden) | |
| 7 | databricks#5833 | acceptance tests | |
| 8 | databricks#5835 | unveil (unhide + help + changelog) | |
This pull request and its description were written by Isaac.
swearyangupta pushed a commit to swearyangupta/cli that referenced this pull request Jul 17, 2026
## Why
- The command's user-visible behavior — text and `--json` output, and
every error path — needs end-to-end coverage against the real CLI.
- `cmd/localenv/` carries no unit tests by design, so acceptance tests
are where that surface is verified (repo convention: user-visible CLI
output is covered by acceptance tests).
## What
- **`acceptance/localenv/`** — 9 scenarios driven through the (hidden)
command against the in-process fake server: `help` (three-level tree),
`no-target` (`E_NO_TARGET`), `flag-conflict` (Cobra mutual-exclusion),
`manager-unsupported` (conda project → clean P1 exit), `env-unsupported`
(404 → `E_ENV_UNSUPPORTED` at fetch), `json-error` (`--output json`
error object), `serverless-check` (dry-run plan), `serverless-json`
(`--json` plan), and `constraints-only`.
- Scripts use `local-env python sync` and the
`DATABRICKS_LOCALENV_CONSTRAINT_SOURCE` override; goldens show the
`local-env python sync` command field and managed marker. No source
changes.
## Testing strategy
- Goldens generated with `-update` and verified **stable on a clean
re-run** (no `-update`); all 9 subtests pass.
- `musterr` guards the five expected-failure scenarios; `trace` shows
the three output-producing ones.
- Full acceptance suite run to confirm no regressions elsewhere (only
pre-existing, environment-specific failures unrelated to this change).
- Diff confined to `acceptance/localenv/`.
- Independently verified by a review subagent (PASS — goldens, scripts,
stubs, stale-refs, hygiene) and by codex (no issues).
---
## About this stack
This is one of a series of small, stacked PRs that together add the
`databricks local-env python sync` command — it provisions a local
Python environment (Python version, `databricks-connect` pin, and
dependency constraints) matched to a selected Databricks compute target.
The work was split from one large branch into single-concern layers so
each is independently reviewable; the command is kept hidden until the
final PR so nothing is user-visible mid-stack.
**Review bottom-up.** Layers 1–5 have merged (the original layer-5 PR
databricks#5828 was split into 5a/5b/5c during review).
| # | PR | What | Status |
|---|----|------|--------|
| 1 | databricks#5823 | foundation: result types + env-key mapping | merged |
| 2 | databricks#5824 | compute-target resolution | merged |
| 3 | databricks#5826 | constraint fetch + offline cache | merged |
| 4 | databricks#5827 | formatting-preserving pyproject.toml merge | merged |
| 5a | databricks#5850 | package-manager interface + detection | merged |
| 5b | databricks#5851 | six-phase pipeline orchestrator | merged |
| 5c | databricks#5854 | --check cache purity, greenfield name, dbc insertion |
merged |
| 6 | databricks#5832 | uv backend + CLI command (registered hidden) | |
| 7 | **databricks#5833 ← you are here** | acceptance tests | |
| 8 | databricks#5835 | unveil (unhide + help + changelog) | |
This pull request and its description were written by Isaac.
Sign up for freeto 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.

3 participants

@rugpanov@eng-dev-ecosystem-bot@anton-107