Skip to content

[9/?] feat(python-setup): extension wiring & config-view dispatch (DRAFT — blocked) - #2054

Draft
rugpanov wants to merge 20 commits into
mainfrom
rugpanov/python-setup-wiring
Draft

[9/?] feat(python-setup): extension wiring & config-view dispatch (DRAFT — blocked)#2054
rugpanov wants to merge 20 commits into
mainfrom
rugpanov/python-setup-wiring

Conversation

@rugpanov

Copy link
Copy Markdown
Contributor

DRAFT — do not merge yet. This is the DECO-27786 integration, opened for visibility. Its diff currently includes four unmerged dependency PRs because it's built on an integration branch merging two open stacks. Review the wiring commits only (see below); the rest lands when the dependencies merge.

Ticket

This implements DECO-27786 — Extension wiring & config-view dispatch (NOT DECO-27782, which is the serverless picker, already done in #2052/#2053).

Blocked on

Once #2039/#2046/#2052/#2053 are on main, this rebases onto main and the diff collapses to just the wiring commits.

The wiring commits (the actual DECO-27786 work)

  1. dispatch the uv-native entry in the config viewbuildPythonSetupEntry (pure) + EnvironmentComponent dispatch (single uv entry when visible, else the unchanged legacy checklist; mutually exclusive).
  2. assemble orchestrator deps with real seamsmakePythonSetupDeps: the visibility gate (isPythonSetupEnabled + shouldShowPythonSetup) and compute resolution (cluster / persisted serverlessVersion from Diff A) filling the orchestrator's stub seams.
  3. wire the flag-gated uv setup into the extension — construct detector/client/orchestrator in activate(), thread through ConfigurationDataProvider, register databricks.environment.setupPythonEnv.
  4. hide the setup command from the Command Palette"when": "false" so the feature is reachable only via the gated tree entry, never the palette.

Known TODO (tracked, not fixed here)

  • saveState is a no-op (TODO(DECO-27784)). Setup succeeds and adopts the interpreter, but drift-detection state isn't persisted until DECO-27784 lands the StateStorage key. This is intentional — persistence is a separate ticket.

Backward compatibility

The stack is constructed unconditionally but inert: the gate keeps the entry hidden (flag off, or non-uv project), the command is palette-hidden, and the PythonSetupEntry dependency is optional end-to-end. Existing users see the identical legacy checklist.

Verification

  • yarn --cwd packages/databricks-vscode build / test:lint / test:unit — 408 passing (12 new across dispatch + deps factory); lint + build clean on the integration branch.
  • Manual dogfood smoke deferred to a custom-CLI environment.

This pull request and its description were written by Isaac.

rugpanov added 6 commits July 23, 2026 17:31
*Why:*
The extension drives `databricks environments setup-local` and parses its single
JSON result. This adds the argv/path helpers and the process gateway that all
later consumers (the setup orchestrator) build on. Stacked on the error-message
change.

*What:*
- `utils/setupLocalArgs.ts` (pure): `buildSetupLocalArgs(invocation)` assembles
  the argv (compute flag → mode → profile → hidden --constraint-source-url →
  --output json) from a `SetupLocalInvocation` whose `compute` field is the
  cluster|serverless target, and `resolveCliPath({override, bundled})` prefers a non-empty
  custom CLI path over the bundled binary (tolerates an undefined override for the
  unset setting). Serverless version is passed as a bare number (e.g. "5") per the
  shipped contract.
- `utils/venvInterpreterPath.ts` (pure): OS-aware interpreter path
  (Scripts\python.exe on Windows, bin/python elsewhere), platform injectable.
- `models/PythonSetupResult.ts`: TODO noting the result's `target` field should
  be renamed to `compute` to match the invocation side — it mirrors the CLI wire
  key, so that rename must land CLI-first (with a schemaVersion bump). No behavior
  change.
- `gateways/PythonSetupCliClient.ts`: spawns the CLI (injectable spawn seam),
  captures stdout → `parsePythonSetupResult`, forwards stderr to an onLog
  channel, resolves with the parsed result on success AND failure exits (caller
  branches on result.ok), rejects on spawn error or unparseable stdout (with
  captured stderr appended).
  - Cancellation terminates the whole process tree via `terminateProcessTree`
    (injectable OS primitives, so it is unit-tested), so the `uv sync` grandchild
    can't keep mutating the project after cancel: on Windows `taskkill /T /F` (a
    bare SIGTERM doesn't reach the CLI; the helper spawn swallows its own `error`
    so a missing taskkill can't crash the host), and on POSIX the child is spawned
    `detached` (its own process-group leader) and cancel signals the negated pid
    to tear down the group, falling back to a direct kill if the group is gone.
    Rejects with a distinct `PythonSetupCancelledError`; a token already
    cancelled at entry rejects before spawning at all, so the mutating CLI never
    starts.
  - stdout is accumulated as raw bytes and decoded once (and stderr via a
    StringDecoder) so a multi-byte UTF-8 char split across chunk boundaries can't
    corrupt the JSON payload; child stdout/stderr stream `error`s are routed
    through the settle guard so an EPIPE can't become an uncaught exception.

*Design note:* there is no live per-phase progress under `--output json` — the
CLI emits only the final JSON object on stdout (per-phase text is text-mode
only, and `uv sync` output is buffered). Consumers show an indeterminate
spinner and read `result.phases` afterwards; no stderr phase-narrator is built
(the plan's `=== Phase N ===` assumption was from the old POC and does not hold).

*Verification:*
- Focused specs (ts-mocha --type-check on the 3 python-setup test files) → 33
  passing, 0 failing (argv, detached-spawn, path, venv-path, gateway spawn/parse/
  cancel/pre-cancel-no-spawn/UTF-8-split/trailing-stderr-flush/stream-error, and
  terminateProcessTree group-kill/taskkill/fallback cases).
- `tsc --noEmit` (whole package) → clean; `eslint` + `prettier -c` on changed
  files → clean. Full `yarn test:unit` (extension-host build) not re-run.

Co-authored-by: Isaac
*Why*
The CLI client (PythonSetupCliClient) shipped with no call site, which makes its
API hard to review in isolation. This adds the orchestrator that actually
drives it end-to-end, so the client's usage lands alongside the client.

*What*
- Add PythonSetupEnvironmentSetup (python-setup/controllers): decide whether to
  run, resolve the compute target, invoke cli.run() under a progress indicator,
  then on success adopt the provisioned venv interpreter and persist
  {envKey, pythonVersion} for drift detection; on failure surface the mapped
  getPythonSetupErrorMessage copy; treat PythonSetupCancelledError as a silent
  user action.
- Consumes the real client surface: run(invocation{compute}, {cwd, onLog, token}),
  isPythonSetupSuccess, getPythonSetupErrorMessage. The client is depended on
  structurally (CliRunner) so the flow is unit-testable without spawn/vscode.
- The two not-yet-built pieces are injected seams with explicit TODOs, not hard
  dependencies:
    * isVisible()  -> TODO(DECO-27781/#2044): shouldShowPythonSetup(...). Wiring
      stub returns false, so the feature is inert end-to-end for now.
    * resolveCompute() -> TODO(DECO-27782): serverless-version picker. Wiring
      stub returns undefined, so a serverless setup is a no-op until then.

Review fixes (Claude + Codex, two rounds):
- Cancellation is no longer dead code: ProgressTask now exposes a
  CancellationLike token alongside the log sink, and setup() threads it into
  cli.run({cwd, onLog, token}). The production withProgress passes through the
  progress notification's own token, so a user "Cancel" tears down the
  project-mutating CLI instead of being a silent no-op.
- Re-entrancy guard: overlapping setup() calls coalesce onto a single in-flight
  run (cleared in finally, incl. on rejection) so two setup-local processes
  can't race the same cwd's writes/backup.
- Strict real-run success: an ok:true result must carry venvPath AND
  target/resolved. Missing any (dry-run shape / CLI-schema drift) is treated as
  a failure instead of flipping `ready` with no interpreter adopted or with no
  drift baseline persisted.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (351 passing; 13 orchestrator
cases, incl. no-open-project, raw-error-reject branch, success-without-venvPath,
success-missing-target/resolved, token threaded to cli.run, re-entrancy
coalescing, and in-flight guard cleared on rejection). test:lint and build
(tsc --build) clean.

Co-authored-by: Isaac
*Why*
When a Databricks Connect setup targets serverless, the extension must suggest
which serverless environment version to provision. The signal comes from
several places of differing trust (a bundle YAML declaration, a notebook's
recorded environment, the workspace default), so we score candidates by
weighted provenance rather than guessing.

*What*
- Add scoreServerlessVersions(observations): pure, deterministic ranking by
  total per-source weight (bundleYaml > notebook > workspaceDefault > fallback),
  ties broken by higher numeric version. A built-in fallback candidate is always
  present and merges into a matching observed version instead of duplicating it;
  a repeated (version, source) pair is not double-counted.
- Versions are the CLI's bare-integer form ("4", "5") -- the value
  --serverless-version accepts -- not the vN display form the CLI echoes in
  results. This keeps the scored value forwardable straight into the
  setup-local invocation.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (332 passing; 7 new scoring
cases: weight ranking, source agreement, dedup, tie-break, always-fallback,
fallback merge, disagreement surfacing).

Co-authored-by: Isaac
*Why*
The scored serverless-version candidates must be shown to the user for explicit
confirmation (no silent auto-selection), with the recommended one pre-selected
and its provenance visible so the choice is informed.

*What*
- buildVersionPickItems(ranked): pure QuickPick-row builder -- pre-picks and
  stars the top candidate, carries each row's bare version for the caller to
  forward to the CLI, and summarises provenance in the description using
  human-readable source labels (e.g. "bundle config", not the internal
  "bundleYaml" key).
- pickServerlessVersion(ranked): thin window.showQuickPick wrapper over the pure
  builder; returns the confirmed bare version or undefined on dismissal. The top
  candidate is recommended but the user must confirm.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (336 passing; 4 new builder
cases: top-pick + provenance, star labelling, fallback-only, empty ranking).

Co-authored-by: Isaac
*Why*
Scoring and the picker need a single entry point that (1) is guarded by the
python-setup opt-in feature flag so the feature stays hidden until a user opts
in, and (2) ties collection -> scoring -> confirmation together. This is what
the setup orchestrator's compute-resolution seam will call for a serverless
target.

*What*
- Add resolveServerlessVersion(deps): gate on the feature flag FIRST -- with the
  feature off it returns undefined and does no collection or UI (fully inert) --
  then collect observations, score them, and let the user confirm; returns the
  confirmed bare version or undefined on dismissal. Collection, the gate, and
  the picker are injected seams so the flow is unit-testable and the extension
  wiring can supply real implementations later.
- Add isPythonSetupEnabled(): the concrete flag reader
  (experiments.optInto includes PYTHON_SETUP_FEATURE_ID). Placed in python-setup/
  rather than on workspaceConfigs to avoid the WorkspaceConfigs<->FeatureManager
  circular import.

Backward compatibility: the resolver is deliberately independent of the existing
databricks.connect.serverlessDbconnectVersion setting (a DBR/dbconnect version
like "17.3" consumed by the legacy pip flow). The new serverless-environment
version is a distinct bare-integer namespace and never reads or writes that
setting, so existing users are unaffected.

*Verification*
yarn --cwd packages/databricks-vscode test:unit / test:lint / build
(340 passing; 4 new resolver cases: disabled->inert, scores+confirms,
always-fallback offered, dismissal->undefined).

Co-authored-by: Isaac
*Why*:
* CI's test:lint step (prettier . -c) runs before the unit tests and fails on serverlessVersionScoring.test.ts, so the Linux and Windows "Run unit tests" jobs both abort before any test executes — the reported "failing tests" are this single format gate, not logic failures.

*What:*
* Reformat the import and two expect(...) lines in serverlessVersionScoring.test.ts to the repo's prettier style.

*Verification:*
* npx prettier --check on all changed serverless files — clean.
* yarn test:unit — 340 passing locally.

Co-authored-by: Isaac
@rugpanov

rugpanov commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ❌ 1 of 35 test jobs failed for 0254d5eb (34 passed).
View run

*Why*:
* Observations are untrusted strings (bundle YAML, notebook metadata, workspace default). versionNumber() stripped a leading "v" only for tie-breaks while the map keyed on the raw string, so a "v5" observation and the bare fallback "5" became two separate rows and "v5" could be forwarded to --serverless-version, which the CLI rejects (it accepts the bare integer, not the vN display form).
* Non-numeric or unrealistic versions (e.g. "abc", "0", "6", "4.2") were coerced to 0 for ranking yet still surfaced as pickable candidates.

*What:*
* Add isSupportedVersion() — a bare integer within the supported range 1..5 (MIN/MAX_SUPPORTED_VERSION) — and drop every observation that fails it before scoring; the known-valid fallback is always kept.
* versionNumber() now parses an already-validated bare integer.

*Verification:*
* yarn test:unit — 343 passing (new cases: drops vN form, drops non-numeric/out-of-range, keeps boundaries 1 and 5).
* prettier --check and eslint on the changed files — clean.

Co-authored-by: Isaac
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-wiring branch from 0254d5e to 6350a60 Compare July 27, 2026 11:34
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 11:34 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 11:34 — with GitHub Actions Inactive
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 6350a602 — ⏳ running.
View run

rugpanov added 13 commits July 27, 2026 13:38
…lver tests

*Why*:
* isSupportedVersion() gated only on /^\d+$/ + range, so zero-padded forms like "05"/"0005" passed, were kept as the raw map key (never merging with the canonical "5"), and could be forwarded verbatim to --serverless-version — a value the CLI does not accept.
* The resolver's disabled-path test froze collectCalls at 0 (Object.assign copied the getter's value, not the accessor), so "collection is skipped when disabled" asserted nothing.
* The picker docs/test described the top row as "pre-selected", but showQuickPick honours QuickPickItem.picked only in multi-select; single-select pre-selection is cosmetic.

*What:*
* Require canonical bare form in isSupportedVersion() via String(parseInt(v)) === v, so "05"/"+5"/" 5" are dropped alongside vN/non-numeric/out-of-range.
* Replace the Object.assign getter with a live accessor and count real collectObservations invocations even when a test overrides the collector; assert collectCalls === 1 on the enabled path so the disabled-path 0 is a meaningful comparison.
* Soften picker doc/test wording to "recommendation (starred, listed first)" and note picked is cosmetic in single-select.

*Verification:*
* yarn test:unit — 343 passing (new: drops non-canonical "05"/"+5"/" 5"; enabled path collects exactly once).
* prettier --check and eslint on the changed files — clean.

Co-authored-by: Isaac
*Why*
resolveServerlessVersion is meant to be the one place that collects a project's
version evidence, scores it, and offers the best default. But it also carried
the feature-flag gate, which forced its only real caller (the compute picker)
to reimplement the collect->score->pick pipeline inline instead of calling it:
the caller needs to distinguish "flag off" (enable plain serverless) from "user
dismissed the picker" (make no change), and the resolver collapsed both into a
single undefined.

*What*
- Drop `isEnabled` from ServerlessVersionResolverDeps and the gate from
  resolveServerlessVersion; it now purely collects -> scores -> picks. The flag
  check moves to callers (isPythonSetupEnabled stays exported here for them).
- Update the resolver test: remove the disabled-path case (now the caller's
  responsibility) and the isEnabled fake.

This lets ConnectionCommands.selectServerless call resolveServerlessVersion
directly (next commit on the compute-picker PR), removing the duplicated
pipeline and giving one home to grow evidence sources (notebooks, workspace
default).

*Verification*
yarn --cwd packages/databricks-vscode build / test:lint / test:unit
(342 passing; resolver now: scores+confirms, always-fallback, dismissal).

Co-authored-by: Isaac
*Why*
Merging serverless-version selection into the compute picker means the chosen
version must be remembered with the compute selection, so setup never re-prompts
for it. This adds the persistence field.

*What*
- Add optional serverlessVersion to OverrideableConfigState (+ isOverrideableConfigKey)
  -- the CLI's bare-integer --serverless-version value (e.g. "5"). It routes
  through the existing ConfigModel get/set overrideable path automatically.

Backward compatibility: the field is optional and independent of `serverless`.
Existing configs that predate it have `serverless: true` with no version, which
stays valid -- a version-less serverless selection falls back to the scored
default. Writing an unrelated key never fabricates a version (covered by test).

*Verification*
yarn --cwd packages/databricks-vscode test:unit (344 passing; 4 new: key guard,
round-trip persist, clear-to-undefined, legacy version-less config untouched).

Co-authored-by: Isaac
*Why*
The compute picker needs to save the chosen serverless version with the
serverless selection, and setup needs to read it back, so ConnectionManager --
the source of truth for the attached compute -- must carry it.

*What*
- enableServerless(version?) persists serverlessVersion when supplied (also when
  serverless is already on, so re-picking the version alone is saved); a bare
  enableServerless() keeps working unchanged.
- disableServerless() clears the version (it only has meaning while serverless
  is the selected compute).
- updateServerless() loads the persisted version on reload, so it survives a
  restart without re-prompting.
- Add get serverlessVersion(): the persisted value, or undefined when serverless
  is off or no version was chosen (consumers fall back to the scored default).

Backward compatibility: the no-arg enableServerless() path and version-less
persisted configs are preserved; undefined simply means "use the default".

*Verification*
yarn --cwd packages/databricks-vscode build (clean; type-checks the new
overrideable-key access and getter). Existing unit suite unaffected.

Co-authored-by: Isaac
*Why*
The serverless-version picker ranks observations; without a real source it
degrades to a lone fallback candidate. A project's bundle already declares its
serverless environment version(s), which is the strongest provenance signal, so
we harvest them to seed the picker.

*What*
- collectBundleServerlessVersions(bundle): defensively walks a parsed bundle and
  emits every serverless `environment_version` as a {version, source:"bundleYaml"}
  observation for scoreServerlessVersions. environment_version is specific to
  serverless environment specs and is a bare integer ("5") -- the same form
  --serverless-version takes. The walk tolerates unresolved ${var...} string
  nodes and schema shape changes without throwing, coerces numeric YAML values
  to bare strings, and de-duplicates preserving first-seen order.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (350 passing; 6 new: single find,
multi-resource dedup, numeric coercion, string-node tolerance, empty/version-less,
non-scalar skip).

Co-authored-by: Isaac
*Why*
Selecting "Serverless" in the compute picker only recorded a boolean; the
serverless environment version was then either an invisible setting (legacy) or
would be asked on every setup run. Merging the version choice into the compute
picker lets the user confirm it once, at compute-selection time, persisted with
the selection so setup never re-prompts.

*What*
- ConnectionCommands.attachClusterQuickPickCommand: when "Serverless" is chosen,
  route through a new selectServerless() step.
- selectServerless(): with the python-setup feature opted into, rank the
  serverless versions from the project's bundle (collectBundleServerlessVersions
  -> scoreServerlessVersions), show the picker (top candidate pre-selected), and
  persist via enableServerless(version); dismissing the version picker makes no
  compute change. With the feature off it is the plain, unchanged
  enableServerless() -- the picker is byte-for-byte today's.

Constraint 1 (backward compat): flag-off behaviour is identical to today, and
bundle read failures fall back to scoring with no observations rather than
blocking compute selection. Constraint 2 (feature flag): the version sub-step is
gated on isPythonSetupEnabled() (the PR #2045 opt-in), so only opted-in users
see it.

*Verification*
yarn --cwd packages/databricks-vscode build / test:lint / test:unit
(350 passing; build type-checks the validateConfig access and picker wiring).

Co-authored-by: Isaac
…e picker

*Why*
selectServerless reimplemented the collect->score->pick pipeline inline,
duplicating resolveServerlessVersion (which had no caller as a result). Now that
the resolver no longer owns the feature-flag gate, the compute picker can call
it directly.

*What*
- ConnectionCommands.pickServerlessVersion delegates to resolveServerlessVersion,
  supplying only the evidence source (bundle via collectBundleServerlessVersions,
  degrading to [] on read failure) and the confirm UI (pickServerlessVersion).
  The scoring/pipeline lives in one place, with one home to grow evidence
  sources (notebooks, workspace default).
- selectServerless still owns the flag branch: off -> plain enableServerless();
  on -> resolve a version, undefined = dismissed -> no compute change.

*Verification*
yarn --cwd packages/databricks-vscode build / test:lint / test:unit (352 passing).

Co-authored-by: Isaac
*Why*
The Python Environment section must show the single uv-native setup entry when
the feature is visible, and the legacy pip checklist otherwise -- the two are
mutually exclusive and never rendered together.

*What*
- Add buildPythonSetupEntry (pure): the single Python Environment row -- a rocket
  run CTA when not ready, a check status line once ready -- both running the
  setup command. Plus the PythonSetupEntry interface (isVisible/ready/
  onDidChangeState), a narrow slice of the orchestrator so the component carries
  no controller/gateway dependency.
- EnvironmentComponent takes an optional PythonSetupEntry; getChildren returns
  that single entry when isVisible(), else the unchanged checklist. Refreshes
  the view on its onDidChangeState. Optional, so the legacy construction is
  byte-for-byte unchanged when absent.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (399 passing; 3 new entry-builder
cases: run CTA, ready line, single entry). build clean.

Co-authored-by: Isaac
*Why*
The orchestrator's gate and compute-resolution seams shipped as stubs. This
fills them with the real implementations, wiring the pieces from across the
stack together for the extension.

*What*
- makePythonSetupVisibility: the config-view gate -- opted-in (flag on) AND the
  active project fits (shouldShowPythonSetup: clean uv/greenfield, no competing
  manager). No project / detection failure -> hidden.
- resolveComputeFrom: map the attached compute to a setup-local target. A cluster
  wins outright; a serverless session resolves to its persisted serverlessVersion
  (from the compute picker, Diff A) -- no version -> undefined (abort silently,
  no guessing).
- makePythonSetupDeps: assemble the full PythonSetupSetupDeps -- adoptInterpreter
  via venvInterpreterPath + the MS Python extension, progress/toasts via window.
  saveState is a documented no-op TODO(DECO-27784) until the storage key lands.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (408 passing; 9 new: 4 visibility
gate, 5 compute resolution incl. cluster-over-serverless and version-less-abort).
build clean.

Co-authored-by: Isaac
*Why*
Final integration: construct the python-setup stack in activate(), register its
command, and let the config view dispatch to it. This turns the feature on for
opted-in users while leaving everyone else on the unchanged legacy flow.

*What*
- extension.ts: build PythonSetupManagerDetector (real collector via
  collectPackageManagerSignals + the active interpreter), PythonSetupCliClient
  (resolveCliPath: cliPathOverride else bundled), and PythonSetupEnvironmentSetup
  via makePythonSetupDeps -- projectRoot from workspaceFolderManager, gate from
  isPythonSetupEnabled + detection, compute from connectionManager
  (cluster / persisted serverlessVersion), interpreter adopted through the MS
  Python extension. Register databricks.environment.setupPythonEnv.
- ConfigurationDataProvider: thread the (optional) PythonSetupEntry into
  EnvironmentComponent.
- PythonSetupEnvironmentSetup: expose isVisible() (delegates to the gate seam) so
  it satisfies the component's PythonSetupEntry contract.
- package.json: register the setupPythonEnv command.

Backward compatibility: the stack is constructed unconditionally but inert --
the gate (flag off, or non-uv project) keeps the entry hidden and the command a
no-op, so existing users see the identical legacy checklist. The dependency is
optional end-to-end, so nothing changes when it is absent.

*Verification*
yarn --cwd packages/databricks-vscode build / test:lint / test:unit
(408 passing; build type-checks the full activate() wiring). Manual dogfff
smoke deferred to a custom-CLI environment.

Co-authored-by: Isaac
*Why*
The feature must stay hidden until a user opts in. A registered command is
palette-invokable by default, which would expose "Set up Python environment
(uv)" to every connected user regardless of the opt-in flag or project type.

*What*
- Add a commandPalette menu entry with "when": "false" for
  databricks.environment.setupPythonEnv, so it is reachable only via the gated
  config-view tree entry (which already checks the opt-in flag + package-manager
  gate), never the palette. Matches the repo's pattern for tree-only commands.

*Verification*
yarn --cwd packages/databricks-vscode build / test:lint (both clean).

Co-authored-by: Isaac
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-wiring branch from 6350a60 to 47e53f5 Compare July 27, 2026 11:46
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 11:46 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/vscode

Inputs:

  • PR number: 2054
  • Commit SHA: 47e53f5efbb0d9a52e980ff93e983bc63c01e774

Checks will be approved automatically on success.

@rugpanov

rugpanov commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ✅ all 35 test jobs passed for 47e53f5e.
View run

rugpanov added a commit that referenced this pull request Jul 28, 2026
#2055)

## Why

After a successful uv-native Python environment setup, the extension
needs to remember what environment was provisioned, so a later change of
compute target can be detected as **drift** (the local env no longer
matches the selected target) and surfaced to the user. This lands the
persistence key.

Scoped deliberately to the storage layer: it builds off `main` with no
dependency on the (still-open) wiring stack. The **write site** — the
orchestrator's `saveState` — is wired separately when the
extension-wiring PR (#2054) lands; it currently holds a
`TODO(DECO-27784)` no-op that this key will back.

## What

- **`PythonSetupState`** type — `{envKey, pythonVersion, timestamp}`:
- `envKey`: the CLI's resolved environment key for the target (e.g.
`serverless/serverless-v5`).
- `pythonVersion`: the provisioned interpreter minor version (e.g.
`3.12`).
  - `timestamp`: ISO-8601 time of the successful setup.
- **`"databricks.pythonSetup.setupState"`** `StorageConfigurations`
entry, **workspace-scoped** (drift is per-project). Flows through the
existing typed `get`/`set` surface — no new accessor code.
- **First `StateStorage` unit test** (in-memory `Memento` fake):
round-trip, undefined-before-write, clear-on-undefined, and
workspace-not-global placement.

## Verification

- `yarn --cwd packages/databricks-vscode build / test:lint / test:unit`
— 329 passing (4 new). Build confirms the typed key resolves through
`get`/`set`.

This pull request and its description were written by Isaac.
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