Skip to content

feat(python-setup): once-per-session env adoption telemetry - #2122

Open
rugpanov wants to merge 6 commits into
mainfrom
python-setup-session-telemetry
Open

feat(python-setup): once-per-session env adoption telemetry#2122
rugpanov wants to merge 6 commits into
mainfrom
python-setup-session-telemetry

Conversation

@rugpanov

@rugpanov rugpanov commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Why

The extension persists a Python-setup state per project but has no measure of whether the managed environment actually sticks. The compute-drift detection (python_env.drift) compares the selected compute's env key against the recorded one — but it never checks that the .venv still exists. A user who deletes the environment while compute is unchanged is not "drifted", yet has plainly stopped using the managed env. Drift is also event-driven, so it carries no per-session denominator to turn into an adoption rate.

What

Adds python_env.adoption: a once-per-session, categorical gauge emitted only for a project that has a uv-native setup on record (databricks.pythonSetup.setupState present). Its mere presence is the adoption-rate denominator; it carries:

  • venvPresent — whether the project's managed .venv interpreter still exists on disk.
  • currentTargetTypecluster | serverless | none, read straight from the connection. No env key is derived here, so there is no second source of truth competing with the CLI / drift detector.

Changes:

  • constants.ts: new Events.PYTHON_ENV_ADOPTION + EventType with per-field comments (the schema's single source of truth).
  • pythonSetupExtensions.ts: recordPythonSetupAdoption, enumerating both fields explicitly (allowlist discipline) so the emitted schema stays compiler-enforced.
  • PythonSetupAdoptionManager: thin, dependency-injected controller — dedupes once per project root per session, gates on VPEX-active, skips when attribution is ambiguous (multi-root), and is best-effort (a throwing seam never propagates into the observed flow, and does not latch the dedup, so a transient failure retries).
  • extension.ts: wire real seams and fire on the first CONNECTED transition (so a compute is attached); dedup makes repeats safe.
  • telemetry/README.md: rationale for the event — the VPEX gate, its distinction from python_env.drift, why it derives no env key, the connect-time-only trigger, and the multi-root limitation.

Additive and measurement-only: no persisted-state change, no migration, and no behavior change to the flow it observes.

Multi-root note

setupState is a single workspace-scoped key with no per-project namespacing, so in a multi-root workspace it can't be pinned to the active root; the gauge is skipped there rather than emit a spurious venvPresent: false. Fully resolving it needs a per-project storage schema (a shared limitation the drift detector also carries), deferred.

Verification

  • npx tsc --noEmit -p tsconfig.json — clean.
  • yarn test:unit — 841 passing, 10 pending, 0 failing (10 new controller tests + emitter tests).
  • yarn fix && yarn test:lint — eslint + prettier clean.

This pull request and its description were written by Isaac.

*Why*

The extension persists a Python-setup state per project but has no measure of
whether the managed environment actually sticks. Compute-drift detection
(python_env.drift) compares the selected compute's env key against the recorded
one, but it never checks that the .venv still exists — a user who deletes the
environment while compute is unchanged is not "drifted", yet has plainly stopped
using it. Drift is also event-driven, so it has no per-session denominator to
turn into an adoption rate.

*What*

Add python_env.adoption: a once-per-session, categorical gauge emitted only for a
project that has a uv-native setup on record (databricks.pythonSetup.setupState
present). Its mere presence is the adoption-rate denominator; it carries
venvPresent (does the managed .venv interpreter still exist on disk) and
currentTargetType (cluster | serverless | none, read straight from the
connection — no env key derived, so no second source of truth vs the CLI).

- constants.ts: new Events.PYTHON_ENV_ADOPTION + EventType with per-field comments.
- pythonSetupExtensions.ts: recordPythonSetupAdoption, enumerating both fields
  explicitly (allowlist discipline) so the schema stays compiler-enforced.
- PythonSetupAdoptionReporter: thin, dependency-injected controller that dedupes
  once per project root per session, gates on VPEX-active, and is best-effort
  (a throwing seam never propagates into the observed flow, and does not latch the
  dedup so a transient failure retries).
- extension.ts: wire real seams and fire on the first CONNECTED transition (so the
  compute kind is known); dedup makes repeats safe.
- telemetry/README.md: rationale for the event (VPEX gate, distinction from drift,
  why it derives no env key).

Additive and measurement-only: no persisted-state change, no migration, and no
behavior change to the flow it observes.

*Verification*

- npx tsc --noEmit -p tsconfig.json — clean.
- yarn test:unit — 831 passing, 10 pending, 0 failing (incl. 8 new controller
  tests and the new recordPythonSetupAdoption emitter tests).
- yarn fix && yarn test:lint — eslint + prettier clean.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:06 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:06 — with GitHub Actions Active
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 695a3bc6 — ⏳ running.
View run

*Why*

Multi-source review of the adoption gauge surfaced three items.

*What*

- Rename the controller PythonSetupAdoptionReporter -> PythonSetupAdoptionManager:
  "Reporter" is not a documented class-role suffix (CODE_CONVENTIONS §2); the class
  reacts to triggers and owns the per-session dedup, which is the XManager role, and
  it now matches its sibling PythonSetupDriftManager.
- Also fire the gauge on setup completion, not only on CONNECTED. A first setup runs
  while already connected, so a project that becomes VPEX-active mid-session emitted
  nothing until the next reload; mirroring the drift manager's setupCompleted trigger
  closes that gap. The manager dedupes per root, so the added trigger never
  double-emits.
- Document the multi-root shared-baseline limitation in telemetry/README.md: with a
  single workspace-scoped setupState key, a never-set-up sibling root can emit a
  spurious venvPresent=false. Same limitation the drift detector carries; the fix is
  the deferred per-project storage schema.

*Verification*

- npx tsc --noEmit -p tsconfig.json — clean.
- yarn test:unit — 840 passing, 10 pending, 0 failing.
- yarn fix && eslint/prettier on changed files — clean.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:14 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:14 — with GitHub Actions Active
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 9de02417 — ⏳ running.
View run

*Why*

Re-review showed the setup-completed trigger added in the previous commit does not
work: the persisted setupState write is fire-and-forget and lands on a later
microtask, but the setup controller's state event fires synchronously, so a reading
taken there reads the project as not-yet-VPEX-active and emits nothing. It also read
the *active* project at fire time, which a mid-setup root switch could make the wrong
one.

*What*

- Remove the `pythonSetupEnvironment.onDidChangeState` trigger; adoption is now a
  clean connect-time reading on `CONNECTED` (plus the activation-already-connected
  case). A project set up for the first time this session is measured from its next
  connect; the venv it just provisioned is already implied by
  python_env.setup.result = ok.
- Update the inline and README docs to describe the connect-time semantics and why
  setup completion is deliberately not a trigger, and soften the "compute is known"
  wording (CONNECTED can carry currentTargetType = none).

*Verification*

- npx tsc --noEmit -p tsconfig.json — clean.
- yarn test:unit — 840 passing, 10 pending, 0 failing.
- yarn fix && eslint/prettier — clean.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:21 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:21 — with GitHub Actions Active
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 9b587941 — ⏳ running.
View run

*Why*

setupState is a single workspace-scoped key with no per-project namespacing, so in a
multi-root workspace it can't be attributed to the active root: a never-set-up
sibling root would emit a spurious venvPresent=false and inflate the adoption
denominator. Documenting the skew (as done for the drift detector) still leaves the
adoption metric — whose whole purpose is an accurate rate — contaminated in that
case.

*What*

Add an isAttributable seam to PythonSetupAdoptionManager; report() suppresses the
reading (without latching, so a later single-root state still reports) when
attribution is ambiguous. Wire it to `workspace.workspaceFolders.length <= 1`, so the
gauge is emitted only when the single setupState key unambiguously describes the one
root. Update the README to say multi-root is skipped rather than emitting spurious
data. The drift detector still fires under the same single-key limitation; the real
fix for both is the deferred per-project storage schema.

*Verification*

- npx tsc --noEmit -p tsconfig.json — clean.
- yarn test:unit — 841 passing, 10 pending, 0 failing (adds a multi-root skip test).
- yarn fix && eslint/prettier — clean.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:28 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:28 — with GitHub Actions Active
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for ebe0e224 — ⏳ running.
View run

*Why*

The adoption README claimed multi-root workspaces were "suppressed entirely", which
over-claims: the one-root guard is a heuristic, not proof of provenance, since the
single workspace-scoped setupState key records no root.

*What*

Reword the limitation note to say the gauge is skipped for multi-root and that the
guard reduces but does not eliminate mis-attribution (a mid-session reduction to one
root can still orphan a sibling's key); the complete fix is the deferred per-project
storage schema. Docs only, no behavior change.

*Verification*

- prettier -c — clean. No code changed.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:32 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:32 — with GitHub Actions Active
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 8b688871 — ⏳ running.
View run

*Why*

The README explained "why it derives no env key" by referring to an earlier design
plan, which a reader of the code has no context for.

*What*

Reword the section to state the rationale directly (reports the compute kind only;
the CLI is the env-key authority and drift already emits off it) without referencing
any prior plan. Docs only.

*Verification*

- prettier -c — clean. No code changed.

Co-authored-by: Isaac
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:53 — with GitHub Actions Active
@rugpanov
rugpanov deployed to test-trigger-is August 14, 2026 14:54 — with GitHub Actions Active
@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: 2122
  • Commit SHA: 1d54af64d9374a83463a0b67f746c0eef8a6e0dc

Checks will be approved automatically on success.

@rugpanov

rugpanov commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ✅ all 35 test jobs passed for 1d54af64.
View run

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