Skip to content

Commit 078bab2

Browse files
andre-salvaticlaude
andcommitted
feat: add job1_prod_integration job; remove seed_sources from job1_prod task graph
job1_prod now matches staging structure: health_check → extracts → transforms. seed_sources moves to the new job1_prod_integration job, which mirrors the staging integration test pattern: seed_sources → (run + run_sdp in parallel), no validate task (prod data is synthetic/incremental, not deterministic fixtures). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ddf6b3d commit 078bab2

2 files changed

Lines changed: 61 additions & 17 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ On every push: install deps → unit tests → bundle validate → deploy to sta
131131
- `databricks.yml` prod target has `mode: production` → DABs refuses to deploy if deployer != run-as identity (the SP). A developer's local `make deploy env=prod` will fail by design.
132132
- CI deploys to prod only when `github.ref == 'refs/heads/main'`.
133133
- `run_as` and `permissions` on every staging/prod job are pinned to the service principal's `application_id` (numeric), wired by `_get_service_principal_id` in `sdk_generate_template_job.py`.
134-
- Prod-only features in `_build_job`: cron schedule, `JobEmailNotifications`, a `health_check` task running before any extract, and a `JobsHealthRule` on `RUN_DURATION_SECONDS > DURATION_WARNING_SECONDS` (30 min) so the `on_duration_warning_threshold_exceeded` email actually has an event to fire on.
134+
- Prod-only features in `_build_job`: cron schedule, `JobEmailNotifications`, a `health_check` task running before any extract, and a `JobsHealthRule` on `RUN_DURATION_SECONDS > DURATION_WARNING_SECONDS` (30 min) so the `on_duration_warning_threshold_exceeded` email actually has an event to fire on. `seed_sources` is **not** in `_build_job` — it lives in `_build_job_prod_integration` (the dedicated prod integration job: seed → run + run_sdp, no validate).
135135
- The wheel filename in `JobEnvironment.dependencies` is pinned to `_project_version()` (reads `pyproject.toml`) so a forgotten rebuild can't silently deploy an old wheel.
136136
- Every job sets `max_concurrent_runs=1` + `queue.enabled=true`: late runs queue instead of getting silently skipped. Retries (staging/prod only) back off `MIN_RETRY_INTERVAL_MS` (60s). Per-task `timeout_seconds` (constants near the top of `sdk_generate_template_job.py`) prevent one hung task from eating the whole job budget. `notification_settings.no_alert_for_canceled_runs / _skipped_runs` keeps deliberate cancellations off the on-call pager.
137137

scripts/sdk_generate_template_job.py

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -195,22 +195,7 @@ def _build_job(environment: str, sp_id: str | None) -> dict:
195195
)
196196
)
197197

198-
# seed_sources runs only in prod: staging/dev use the integration test `setup` task
199-
# to seed external_source with controlled data, so seed_sources would add noise there.
200-
# In prod, seed_sources runs after health_check and before the extract tasks.
201-
if environment == "prod":
202-
tasks.append(
203-
Task(
204-
task_key="seed_sources",
205-
**_retry_kwargs(retries),
206-
timeout_seconds=TIMEOUT_EXTRACT_S,
207-
environment_key="default",
208-
depends_on=[TaskDependency(task_key="health_check")],
209-
python_wheel_task=_wheel_task(),
210-
)
211-
)
212-
213-
extract_deps: list[TaskDependency] = [TaskDependency(task_key="seed_sources")] if environment == "prod" else []
198+
extract_deps: list[TaskDependency] = [TaskDependency(task_key="health_check")] if environment == "prod" else []
214199

215200
tasks.extend(
216201
[
@@ -386,6 +371,63 @@ def _build_job_integration_test(environment: str, sp_id: str | None) -> dict:
386371
return d
387372

388373

374+
def _build_job_prod_integration(sp_id: str | None) -> dict:
375+
"""Prod integration job: seed_sources → run (job1 batch) + run_sdp (pipeline) in parallel.
376+
377+
Mirrors the staging integration test structure but uses seed_sources instead of the
378+
test-only setup task, and omits validate (prod data is synthetic/incremental, not
379+
the deterministic fixtures that integration_validate checks).
380+
"""
381+
tasks = [
382+
Task(
383+
task_key="seed_sources",
384+
**_retry_kwargs(2),
385+
timeout_seconds=TIMEOUT_EXTRACT_S,
386+
environment_key="default",
387+
python_wheel_task=_wheel_task(),
388+
),
389+
Task(
390+
task_key="run",
391+
depends_on=[TaskDependency(task_key="seed_sources")],
392+
run_job_task=RunJobTask(job_id=f"${{{f'resources.jobs.{JOB_NAME}.id'}}}"),
393+
),
394+
Task(
395+
task_key="run_sdp",
396+
depends_on=[TaskDependency(task_key="seed_sources")],
397+
pipeline_task=PipelineTask(
398+
pipeline_id="${resources.pipelines.job1_sdp.id}",
399+
full_refresh=False,
400+
),
401+
),
402+
]
403+
404+
job = Job(
405+
name=f"{JOB_NAME}_${{bundle.target}}_integration",
406+
timeout_seconds=3600,
407+
max_concurrent_runs=1,
408+
queue=QueueSettings(enabled=True),
409+
notification_settings=JobNotificationSettings(
410+
no_alert_for_canceled_runs=True,
411+
no_alert_for_skipped_runs=True,
412+
),
413+
parameters=[
414+
JobParameterDefinition(name="log_level", default=DEFAULT_LOG_LEVEL),
415+
JobParameterDefinition(name="quarantine_fail_ratio", default=PROD_QUARANTINE_FAIL_RATIO),
416+
JobParameterDefinition(name="seed_date", default=""),
417+
],
418+
tags=_tags("prod"),
419+
environments=_environments(),
420+
tasks=tasks,
421+
)
422+
423+
d = job.as_dict()
424+
d["deployment"] = {"kind": "BUNDLE"}
425+
d["run_as"] = {"service_principal_name": sp_id}
426+
d["permissions"] = [{"service_principal_name": sp_id, "level": "CAN_MANAGE"}]
427+
428+
return d
429+
430+
389431
def _resolve_catalog(environment: str) -> str:
390432
"""Compute the target catalog at generation time, mirroring Config.__init__.
391433
@@ -440,6 +482,8 @@ def main():
440482
jobs: dict = {JOB_NAME: _build_job(env, sp_id)}
441483
if env in ("dev", "staging"):
442484
jobs[f"{JOB_NAME}_integration_test"] = _build_job_integration_test(env, sp_id)
485+
if env == "prod":
486+
jobs[f"{JOB_NAME}_prod_integration"] = _build_job_prod_integration(sp_id)
443487

444488
output: dict = {
445489
"resources": {

0 commit comments

Comments
 (0)