Skip to content

job_runs: add lifecycle.triggers.on_file_change - #6309

Open
radakam wants to merge 34 commits into
mainfrom
deco-job-runs-lifecycle-on-file-change
Open

job_runs: add lifecycle.triggers.on_file_change#6309
radakam wants to merge 34 commits into
mainfrom
deco-job-runs-lifecycle-on-file-change

Conversation

@radakam

@radakam radakam commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Changes

Add lifecycle.triggers.on_file_change for resources.job_runs (direct engine).

resources:
  job_runs:
    migrate:
      job_id: ${resources.jobs.migrate.id}
      lifecycle:
        triggers:
          - on_file_change: migrations/*.sql
  • Resolve the path or glob relative to the defining YAML file and require the result to stay under the sync root.
  • Fingerprint after build (for bundle plan / bundle validate) and after predeploy (for bundle deploy). Hash through the same SyncRoot VFS used by sync, so WSFS notebooks and generated overlays are included. An mtime-only touch does not re-fire.
  • Match against the files sync would upload (libs/sync.FileList + path.Match), including sync's generated include patterns. .gitignore and sync.exclude are honored. Directories never appear in that list, so migrations/* hashes only regular files.
  • Persist internal lifecycle.triggers_state as normalized-pattern → aggregate-hash entries, plus a UUID for on_bundle_deploy. Each aggregate hashes the sorted matched paths and their content hashes. Unarmed runs omit lifecycle from state. A no-match pattern stores the empty-set hash under the same pattern key used once files appear.
  • Re-fire when an aggregate hash changes or when matches appear or disappear. Removing a trigger leaves the existing run alone and writes a state-only update that clears the fingerprint (no Jobs API call). Re-adding the trigger therefore arms and fires it again without requiring an intervening file edit. A removed file still recreates the run.
  • Reject absolute patterns, patterns outside the sync root, empty patterns, ** (path.Match treats it as two ordinary stars, not recursive), and combining a trigger with prevent_destroy. Warn when no synced files match; the empty-set fingerprint still re-arms once a matching file appears. Pattern errors are reported at validate time; hashing runs only when planning or deploying.

Why

Users need to re-run a job when local inputs change (SQL migrations, notebooks, configs) without re-firing on every deploy. Content hashes keep the trigger stable under sync noise while still catching real edits.

Fingerprints must be computed after build and predeploy scripts, otherwise a trigger on a generated file fires one deploy late. Hashing through SyncRoot keeps notebook and overlay content consistent with what sync uploads.

A pattern that hashes nothing used to look like an unarmed trigger. Recording an empty-set fingerprint under a stable pattern key makes those cases re-arm later, so they are warnings rather than deploy failures.

Fingerprints live on triggers_state, not on the YAML triggers list, so user config and planner state stay distinct. Clearing a trigger must persist that omission; otherwise re-adding it looks unchanged and never fires.

Tests

  • Unit: aggregate content hashing, SyncRoot overlay reads, absolute-pattern rejection, and sync-root-relative missing-pattern keys; trigger validation for empty and invalid entries and prevent_destroy conflicts; state-only classification when a trigger is removed; omission of empty lifecycle from serialized state.
  • Acceptance:
    • on_file_change — first deploy fires; unchanged redeploy and touch plan nothing; an edit recreates (including READPLAN); deleting and restoring the file each re-fire; removing the trigger performs no run-now, clears its state through both live and serialized plans, and re-adding it re-fires.
    • on_file_change_build — a prebuild script rewriting a watched file re-fires the run in that same deploy.
    • on_file_change_globmigrations/* plus mysubdir/*.txt; matches appearing and disappearing re-fire; files below the glob and gitignored files do not; ** is rejected; READPLAN.
    • on_file_change_errors — pattern outside the sync root (error); directory-only, directory plus sync-excluded, only sync-excluded, and no-match cases (warnings); unsupported ** and an absolute pattern (errors).
    • on_file_change_include_sync_root — patterns from included YAML resolve next to the include, at the bundle root (../), and at the sync root (../../); each edit recreates only the corresponding pattern entry.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 0850022

Run: 33166067267

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 1 1 274 1210 4:12
💚​ aws windows 1 1 276 1208 4:25
💚​ azure linux 1 1 273 1210 4:24
💚​ azure windows 1 1 275 1208 4:34
💚​ gcp linux 1 1 274 1210 5:00
💚​ gcp windows 1 1 276 1208 4:14
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
Top 3 slowest tests (at least 2 minutes):
duration env testname
4:28 azure windows TestAccept
4:21 aws windows TestAccept
4:09 gcp windows TestAccept

@radakam
radakam marked this pull request as ready for review August 19, 2026 08:04
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

44 files changed
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

/bundle/ - needs approval

20 files changed
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

/cmd/bundle/ - needs approval

Files: cmd/bundle/utils/process.go
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

General files (require maintainer)

Files: .nextchanges/bundles/job-runs-on-file-change.md
Based on git history:

  • @denik -- recent work in bundle/direct/, bundle/phases/, bundle/deployplan/

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db, @rugpanov, @rclarey) can approve all areas.
See OWNERS for ownership rules.

Base automatically changed from deco-job-runs-lifecycle-on-bundle-deploy to main August 19, 2026 09:41
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from b8c20f6 to 8744e5e Compare August 19, 2026 09:47
@radakam
radakam requested a review from denik August 19, 2026 11:06
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 8744e5e to 47994d8 Compare August 19, 2026 11:39
Comment thread acceptance/bundle/resources/job_runs/on_file_change/databricks.yml
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt
Comment thread acceptance/bundle/resources/job_runs/on_file_change/script
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
@radakam
radakam requested a review from denik August 20, 2026 08:12
Comment thread acceptance/bundle/resources/job_runs/on_file_change_glob/test.toml
@radakam
radakam requested a review from denik August 20, 2026 09:27
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
Comment thread acceptance/bundle/resources/job_runs/on_file_change_glob/output.txt Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers_test.go
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/direct/dresources/job_run_test.go Outdated
@radakam
radakam requested a review from denik August 20, 2026 14:10
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/direct/dresources/job_run.go Outdated
@radakam
radakam requested a review from denik August 24, 2026 08:50
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 0756969 to 4e7b960 Compare August 24, 2026 13:24
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 17857f9 to 45c4ccd Compare August 25, 2026 08:12
@radakam
radakam changed the base branch from main to job-runs-drop-unit-tests-covered-by-acc August 25, 2026 08:13
@radakam
radakam requested a review from denik August 25, 2026 08:32
Base automatically changed from job-runs-drop-unit-tests-covered-by-acc to main August 25, 2026 10:05
Re-fire a run when matched file contents change, using stable
hash fingerprints so mtime-only updates (touch) do not recreate.
Satisfy the linter on PrepareState literals after adding OnFileChange,
and refresh out.fields.txt so validate-generated stays green.
Reject patterns outside the sync root and directory-only matches so
the trigger cannot fingerprint escaped paths or silently disarm.
Drop size/mtime fingerprints and the resolve fast path so content
identity alone drives recreate, and touch no longer needs a planner exception.
The modernize linter rejects the manual m[k]=v copy loop, failing CI lint.
radakam added 18 commits August 25, 2026 10:09
Honor .gitignore and sync.exclude when hashing matches. Cover mysubdir
globs, READPLAN, and keep LF on Windows so restored files hash cleanly.
The glob test edits migrations/ignored.txt, but the test .gitignore
excluded it from the commit so CI clones would miss the file.
Plan serialization is already covered by on_file_change; the glob cases only assert the hash map.
Cover deploying glob hashes from a saved plan, not only from a live re-plan.
Keep the gitignore fixture as dot_gitignore so it does not affect the repo while developing, drop unit tests that duplicate acceptance coverage, and allocate trigger state once when a trigger is armed.
The previous simplification omitted OnBundleDeploy from the struct literal, which the linter rejects.
Always persist the same lifecycle.triggers shape so trigger fingerprints
compare at the same paths. Wrap on_file_change hashes in files so dropping
the trigger updates instead of recreating.
Nest lifecycle by value so structdiff descends to the leaf that changed
instead of reporting the whole subtree, and let RemapState supply the empty
shape. GetRun never returns the fingerprints, so the remote type no longer
advertises a lifecycle it cannot fill.
Drop the files wrapper so plan diffs land on lifecycle.triggers.on_file_change, matching the config path without an extra state-only level.
Flattening on_file_change to a map made an unarmed trigger drop out of the serialized state, so the three plans that print an unarmed job_run now report an empty triggers object.
Directory-only and sync-excluded matches silently disarmed the trigger.
Treat them as errors, warn that ** is not recursive, and cover both in acc.
The goldens were captured against a one-line-taller databricks.yml, so CI failed after the source settled on line 24.
Clearing a trigger is now a skip, so the plan reports the run as unchanged and the deploy makes no API call.
Patterns must still land under the sync root. Reject **, warn when a glob matches directories, and skip only when the trigger itself is cleared.
filepath.Join rebases an absolute glob under the bundle root, so reject
those up front. Key missing-file placeholders to the sync root, and skip
a cleared trigger from path.String() instead of prefix matching.
Every "nothing to hash" case re-arms once a matching file appears, now
that the placeholder is always recorded, so none of them justify failing
the deploy. Directories matched alongside real files are skipped silently:
a glob like migrations/* routinely matches a subdirectory, and warning
about it on every command was noise the user cannot act on.

Also drop the unreachable "lifecycle.triggers" branch in OverrideChangeDesc
(structdiff descends into the value struct and never reports a change at
that path) and document the ** and sync-skipping rules in the schema.
Windows CI converted /tmp/seed.txt to %TEMP%/seed.txt, so the golden
expecting the POSIX path failed. MSYS_NO_PATHCONV keeps the argument intact.
MSYS_NO_PATHCONV also stops Git Bash converting the path of a shebang
helper, so update_file.py could not be found on Windows. Keep the
leading-slash pattern out of argv entirely instead.
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from d65e640 to b0437a3 Compare August 25, 2026 10:12
jariaspdev-lab pushed a commit to jariaspdev-lab/cli that referenced this pull request Aug 25, 2026
## Changes
Drop `job_run` unit tests that duplicate existing acceptance coverage,
and add `destroy_unfinished_run` so cancel-before-delete is asserted
through the same path a user destroy takes.

Kept the wait/GetRun stubs that acceptance cannot reach without faking
the Jobs API.

## Why
Split from review on databricks#6309: if a unit test is already covered by acc,
keep the acc test.

## Tests
- New acc: `acceptance/bundle/resources/job_runs/destroy_unfinished_run`
(faulted wait, then destroy cancel + delete order)
… duplicate plan entry

Iterate the sync file list with path.Match instead of filepath.Glob so
on_file_change honors .gitignore and sync.exclude directly, and drop the
whole-map diff when per-file entries already report the change.
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread acceptance/bundle/refschema/out.fields.txt Outdated
Comment thread acceptance/bundle/resources/job_runs/on_file_change_include_sync_root/script Outdated
Keep resolved hashes off user config, omit empty lifecycle from state, and fold include/sync-root path coverage into one acceptance test.
Match the INPUT type so refschema reports one INPUT STATE row
instead of splitting pointer and value.
update_file.py uses text mode, so Windows rewrites committed LF
files as CRLF and later plans still see those files as changed.
Group resolved hashes as pattern -> file -> hash so the planner can tell a
removed trigger from a file dropping out of a glob, replacing the flat map
and the empty-hash placeholder. PrepareState now copies the resolved trigger
state instead of rebuilding it.
Store one deterministic fingerprint per pattern so plan paths remain concise while file additions, removals, and content changes still re-fire the run.
…triggers

Resolve on_file_change fingerprints after the build/predeploy scripts have run
and hash matched files through the SyncRoot VFS, so build outputs and WSFS
notebooks are captured. Persist a cleared trigger's state without invoking the
Jobs API, so removing then re-adding a trigger re-fires the run.
@radakam
radakam requested a review from denik August 27, 2026 16:13
ValidateJobRunTriggers already runs during initialize, and acceptance covers the pattern cases that duplicated the mutator unit tests.
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.

3 participants