Skip to content

feat: add db support for noncurrent lifecycle execution - #1627

Merged
ferhatelmas merged 9 commits into
masterfrom
ferhat/lf-journal
Nov 6, 2026
Merged

ferhatelmas merged 9 commits into
masterfrom
ferhat/lf-journal

Conversation

@ferhatelmas

Copy link
Copy Markdown
Member

What kind of change does this PR introduce?

feature

What is the current behavior?

It can store lifecycle policies and evaluate pages of noncurrent versions. There is no database API to coordinate a lifecycle deletion run or recover its unfinished backend cleanup.

What is the new behavior?

Add the database layer for lifecycle execution: leased bucket claims, bounded deletion batches, eligibility rechecks under locks and a durable journal committed with metadata deletion itself. It also tracks cleanup outcomes and abandoned artifacts for a later retry.

Additional context

This is a standalone database layer and a worker is still needed to schedule runs and delete backend artifacts.
Related to #1380

Copilot AI balanced review requested due to automatic review settings October 29, 2026 20:01
@ferhatelmas
ferhatelmas requested a review from a team as a code owner October 29, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@coveralls

coveralls commented Oct 29, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 42202464917

Coverage increased (+0.3%) to 87.542%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 3 uncovered changes across 1 file (224 of 227 lines covered, 98.68%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/storage/database/pg.ts 144 141 97.92%
Total (6 files) 227 224 98.68%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 15991
Covered Lines: 14438
Line Coverage: 90.29%
Relevant Branches: 10169
Covered Branches: 8463
Branch Coverage: 83.22%
Branches in Coverage %: Yes
Coverage Strength: 1297.2 hits per line

💛 - Coveralls

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 src/storage/database/pg.ts — A worker that finishes a run without a generation key in its result gets its bucket rescheduled at once, so lifecycle scans of the whole bucket repeat back to back. completeLifecycleRun reads the policy generation from $4::jsonb ->> 'generation', which is the free-form lastResult payload. A missing key gives NULL, so IS NOT DISTINCT FROM is false and next_run_at becomes clock_timestamp(). Fix: pass the claimed generation as its own typed parameter, compare it with bucket.lifecycle_configuration_generation, and drop the dependency on lastResult.

    Why this was flagged

    The trigger is any caller of completeLifecycleRun(input, lastResult, nextRunAt) (adapter.ts:252) whose lastResult has no string generation field. The signature types that argument as Record<string, unknown>, and nothing documents that the field is required. At pg.ts:806-810 the CASE compares bucket.lifecycle_configuration_generation::text with $4::jsonb ->> 'generation'. When the key is missing that side is NULL, so the ELSE branch sets next_run_at = clock_timestamp() and the caller's nextRunAt is ignored. The bucket is then due immediately and gets reclaimed. A full noncurrent evaluation runs again after every completion instead of waiting for the next schedule. That burns database load on every lifecycle-enabled bucket. Only one caller exists today: the integration tests at noncurrent-lifecycle-database.test.ts:731, :775 and :1360. One of them (:731) already passes {}. The worker that will call this is not in this PR, so nothing enforces the hidden contract.

    Verification: nit. Trigger: any future caller that passes a lastResult without a generation key. In pg.ts:3926-3931, next_run_at compares the bucket generation with $4::jsonb ->> 'generation', and $4 is JSON.stringify(lastResult) (line 3944). When the key is missing, ->> returns NULL, so next_run_at is set to clock_timestamp() and the caller's nextRunAt is silently ignored.

Comment thread src/storage/database/pg.ts Resolved
@ferhatelmas

Copy link
Copy Markdown
Member Author

@claude review always

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread src/storage/database/pg.ts Resolved
Comment thread src/storage/database/pg.ts Resolved
Comment thread src/storage/database/pg.ts Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

Comment thread src/storage/database/pg.ts Resolved
Comment thread src/storage/database/pg.ts Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new blocking issues. 1 optional suggestion (a nit or a note on pre-existing code) was found and not posted. Nothing in this review needs a push before merging.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

Comment thread src/storage/database/pg.ts Resolved
Comment thread src/storage/database/pg.ts Resolved
Comment thread src/storage/database/pg.ts Resolved
@TylerHillery
TylerHillery self-requested a review November 3, 2026 01:42
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
@ferhatelmas
ferhatelmas enabled auto-merge (squash) November 6, 2026 10:25
@ferhatelmas
ferhatelmas merged commit 86a0544 into master Nov 6, 2026
34 of 35 checks passed
@ferhatelmas
ferhatelmas deleted the ferhat/lf-journal branch November 6, 2026 10:32

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new blocking issues. Nothing in this review needs a push before merging.

1 further finding on code this push did not change was outside the scope of this re-review and not posted.

ferhatelmas added a commit that referenced this pull request Nov 9, 2026
## What kind of change does this PR introduce?

Refactor

## What is the current behavior?

The lifecycle worker deletes eligible metadata and stores an in-flight
batch in the bucket continuation. It records physical deletion outcomes
in that journal and advances the cursor after cleanup completes. Pending
artifacts can later be moved to the abandoned artifact table for retry.

## What is the new behavior?

The worker deletes eligible metadata, writes the corresponding physical
keys to the outbox and advances the cursor in one transaction. Physical
deletion happens afterward; unresolved keys remain in the outbox for
retry. The continuation holds only the cursor and counters.


## Additional context

Related to #1627

---------

Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
ferhatelmas added a commit that referenced this pull request Nov 10, 2026
## What kind of change does this PR introduce?

feat

## What is the current behavior?

#1627 and #1683 added the database operations and outbox needed for
noncurrent lifecycle deletion but executor to run was missing.

## What is the new behavior?

Add a bucket executor that evaluates archived versions, deletes eligible
metadata in bounded batches and removes the corresponding backend
artifacts.
Failed artifact deletions remain in the outbox for retry. 

## Additional context

Scheduling the executor and retry helper is a next separate change.

Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
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.

5 participants