Skip to content

feat(api): prune data-manager pipeline runs after the job retention - #721

Merged
Medformatik merged 1 commit into
mainfrom
feat/data-manager-job-retention
Oct 31, 2026
Merged

Medformatik merged 1 commit into
mainfrom
feat/data-manager-job-retention

Conversation

@Medformatik

@Medformatik Medformatik commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

What changed and why

Nothing deleted data_manager.jobs or data_manager.job_stages. Production built up about 34,000 jobs and 130,000 stages before #704. The monitoring docs said Jobs-tab rows were pruned after ADMIN_JOB_RETENTION_DAYS, but that was only true for admin jobs. A run an administrator started stores api:user:<id> in triggered_by, which the privacy catalogue registers as personal data (data-manager-trigger-attribution).

The daily retention run now calls pruneDataManagerJobs(ADMIN_JOB_RETENTION_DAYS), next to the admin-job prune. It deletes finished data-manager jobs, and by cascade their stages, more than that many days after they finished. It keeps:

  • Running jobs. They have no finish time. Only data-manager can tell whether one is really in flight.
  • The newest finished job of each kind. GET /transit/state reports the last sync from the newest row, so a rare or disabled schedule keeps it.
  • Jobs of an account whose data-subject request still needs them. The subject's id comes from triggered_by, in each of the three user formats the activity list reads.

That last point needed a change to the shared preservation guard. This registration is an operator_task: on request intake its hold becomes action_required, because an operator preserves the rows by hand. preservationAllowsDeletion honoured only held, so a plain prune would have deleted exactly the rows the operator was asked to keep. The guard now takes the hold statuses that keep a caller's rows, and this prune passes held and action_required.

The statuses render as fixed literals from a closed set. The default (held) produces exactly the SQL it did before, so the existing callers don't change.

Also:

  • Auto-bump label. The data-manager stores scheduled automatic version bumps as cron:data-manager-auto-bump. The activity list only recognised data-manager-auto-bump, so these runs showed as "cron data manager auto bump". They now show as "Automatic version bump".
  • Schema comment. The comment on triggered_by now describes the stored format.

Follow-up

application-logs is also an operator_task registration with the same gap; #723, stacked on this PR, applies the same guard there.

Verification

  • data-manager-job-retention-postgres.test.ts, new, in the api-privacy group, against PostgreSQL:
    • Retention: finished and interrupted jobs past the retention are deleted with their stages. A running job and the newest finished job are kept, and so is the only, 90-day-old run of a second kind.
    • Request holds: a real data-subject request is created. Old jobs triggered by that account, as api:user: and manual:admin:, are kept while its hold is action_required, and deleted once the hold is released. Another account's job and a scheduled job are deleted.
  • preservation.test.ts checks the guard's SQL for the new option, and that an empty status list is refused. The existing assertion preservation.status = 'held' still passes unchanged.
  • pnpm test:database api-privacy against a freshly migrated PostGIS: 25 files, 145 tests pass.
  • npx vitest run --project node: 18,772 tests pass. Type checks for api and pnpm lint pass.
  • Not run: the ingestion and export-restore database groups, and the daily retention inside a running app-api.

Release and upgrade impact

Product impact: ordinary

On the first retention run after the upgrade, which happens at app-api startup, finished data-manager runs older than ADMIN_JOB_RETENTION_DAYS (default 30 days) are deleted. To keep a longer pipeline history, raise that setting before upgrading. It also applies to admin jobs.

Nothing deleted data-manager jobs or their stages, although the jobs
list in the admin panel said they were pruned with the admin jobs. A
run an administrator started keeps their account id in triggered_by,
which the privacy catalogue registers as personal data.

The daily retention now deletes finished data-manager jobs, and by
cascade their stages, once ADMIN_JOB_RETENTION_DAYS have passed. It
keeps:

- running jobs, which have no finish time;
- the newest finished job of each kind, so the transit state still
  reports the last sync when the schedule is rare or off;
- jobs of an account whose data-subject request still holds them.

The registration is an operator task, whose hold becomes
action_required while the operator preserves the rows by hand. The
preservation guard so far honoured only held. It now takes the
statuses that keep a caller's rows, and this prune passes both.

Scheduled automatic version bumps are stored as
cron:data-manager-auto-bump, which the activity list did not recognise.
It now labels them "Automatic version bump". The schema comment on
triggered_by describes the stored format.
@Medformatik Medformatik self-assigned this Oct 31, 2026
@Medformatik
Medformatik merged commit 6cc5720 into main Oct 31, 2026
46 checks passed
@Medformatik
Medformatik deleted the feat/data-manager-job-retention branch October 31, 2026 18:20
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant