Skip to content

feat(health): observe expired scoring-job leases - #131

Closed
cursor[bot] wants to merge 31 commits into
mainfrom
cursor/bc-419d8ec4-aa61-485a-8204-d92037b1c87b-be6b
Closed

feat(health): observe expired scoring-job leases#131
cursor[bot] wants to merge 31 commits into
mainfrom
cursor/bc-419d8ec4-aa61-485a-8204-d92037b1c87b-be6b

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Why

PR #113 classifies scoring-job queue depth and created-at age, but a dead worker whose lease is already past active_lease_expires_at_unix_ms can still look WithinBounds when those bounds are enqueue-to-complete. Operators then see a healthy backlog while results stop arriving.

This branch is #113 at 49dbb2b plus expired-lease observation. Prefer this head over merging #113 alone if dead-worker visibility is in the same integration window.

TDD

RED: expired_scoring_lease_cannot_be_masked_by_healthy_count_or_created_at_age failed to compile (E0560) because expired_lease_count and max_expired_lease_count did not exist.

GREEN: the probe counts leased rows whose persisted expiry is already past the database clock, the classifier fails closed against caller policy, apply_scoring_job_migration applies 0022 after 0002/0021, and a second apply is idempotent.

What changed

  • observe expired leased-job count separately from active queue depth;
  • compare lease expiry to clock_timestamp() so a later created-at age bound cannot hide a dead worker;
  • fail closed on non-positive stored lease expiry;
  • classify only against caller-supplied max_expired_lease_count, with no universal SLO defaults;
  • expose aggregate counts only — never worker, lease, request, or payload identities;
  • apply the leased-expiry partial index through the existing product apply_scoring_job_migration path;
  • reject reused migration numeric prefixes so draft feat(identity): persist append-only account-link history #114 cannot land a second 0021_* file.

Verification

  • composition: expired lease cannot be masked by a healthy outbox or created-at age;
  • empty, mixed-state, live-versus-expired lease, count/age/quarantine, future-evidence, invalid created-at, transaction success, and missing-relation cases;
  • index contract for active, quarantined, and leased-expiry partial indexes;
  • cargo fmt, cargo clippy --all-targets -- -D warnings, PostgreSQL 18.6 scoring-job backlog tests.

Coordination

Open in Web View Automation 

seonghobae and others added 30 commits August 15, 2026 12:42
The first positive-millis conversion returned before Linux line coverage
could see the later consumption and propagation timestamp checks.
Linux line coverage treats the later conversion ? as its own statement.
Return those invalid stored times through an explicit match arm.
Client-only invalid timestamp probes left the GenericClient Transaction
instantiation of each independent oldest-event conversion uncovered.
Backlog probes accept GenericClient, so an aborted transaction and a
closed connection must both surface typed database errors on the
query Result paths.
#76 already landed, so keep Active PR #82 as the remaining backlog-observation
slice. Name the caller-policy probes in TRACEABILITY, OPERABILITY, and the
changelog. HTTP probes and measured deployment-profile thresholds stay outside
this branch.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
The inherited #72 recovery fixture inserted a processing consumption
row without claim_deadline_at. Migration 0019 requires that column for
processing rows, and the deadline trigger is UPDATE-only, so exact-head
CI failed closed. Seed a valid persisted claim and assert the deadline
survives COPY restore.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Migration 0019 requires claim_deadline_at for processing consumption rows,
and the deadline trigger is UPDATE-only. Direct INSERT fixtures used by the
integration-backlog probe must persist that column so exact-head CI can
observe in-flight work without weakening the fail-closed shape check.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
PostgreSQL rejected $5 as both bigint claim expiry and double-precision
interval input. Persist claim_deadline_at with clock_timestamp() so the
0019 shape check stays fail-closed without weakening the probe contract.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Migration 0020 only ran from a test include_str, so callers using the
product apply functions never received readiness indexes. Add
apply_backlog_health_index_migration and require the index contract to
use that path, including missing-relation and idempotent apply cases.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
The capability-health row still described #82 as observation-only.
Record apply_backlog_health_index_migration so TRACEABILITY matches the
reviewed product apply contract.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Operators could not classify queued, leased, retry-scheduled, or
quarantined scoring work with the same content-free policy used for
outbox and data-rights readiness. Add the probe, caller-supplied
classifier, and product apply path for partial scoring-job indexes.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A healthy outbox or data-rights queue must not hide a stalled or
unknown scoring-job observation. Classify the three families together
and keep stalled above unknown above within-bounds.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Count leased rows whose persisted expiry is already past the database
clock so a dead worker cannot hide behind a healthy enqueue-age bound.
Apply the leased-expiry partial index through apply_scoring_job_migration.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Record the dead-worker residual, prefer this successor over #113 alone,
and cite lease and probe sources in APA 7th.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 15:45
@cursor
cursor Bot marked this pull request as ready for review August 16, 2026 15:46

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale comment

Review — 8c51e3d expired scoring-job leases

This is the current scoring-job readiness successor of #113. Prefer #131 over merging #82 or #113 alone. This comment is not an approval.

What is sound

  • Expired leased rows are counted against clock_timestamp(), so a healthy created-at age cannot hide a dead worker.
  • Classification stays caller-policy-only. Evidence remains counts, not worker/lease/request identities.
  • 0022 applies through apply_scoring_job_migration. Composition still fails closed across integration, data-rights, and scoring-job families.

Required before this is treated as shipped truth

  1. Independent last-push review (seonghobae already requested).
  2. Exact-head required checks on 8c51e3d.
  3. Name 0020/0021/0022 in the TRACEABILITY migrations/ tree and add the Active-PR note to docs/architecture/AS_BUILT_SCHEMA.md.

Coordination

  • Do not merge #113 or #82 as siblings while this head is the scoring-job readiness vehicle.
  • Draft #114 must keep its identity-link migration number after 0022.
  • HTTP probes stay on #122 / #111.
Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread docs/TRACEABILITY.md
Comment thread src/postgres_health.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review: expired-lease observation (#131 @ 8c51e3d)

Prefer this head over merging #113 / #82 alone when dead-worker visibility is in the same window. Do not merge until exact-head required checks and an independent last-push approval land. This review does not approve.

What holds

  • apply_scoring_job_migration composes immutable 0002 + 0021 + 0022 (CREATE INDEX IF NOT EXISTS). Second apply is idempotent.
  • Probe counts leased AND active_lease_expires_at_unix_ms < clock_timestamp() separately from active queue depth. Classification uses only caller-supplied max_expired_lease_count. No universal SLO.
  • Schema already requires leased rows to have a positive expiry (scoring_active_lease_shape_check, scoring_lease_expiry_positive_check). Aggregate-only output: no worker, lease, request, or payload identities.
  • Composition test expired_scoring_lease_cannot_be_masked_by_healthy_count_or_created_at_age plus the mixed-state / refuse-expired PostgreSQL cases match the claimed fail-closed policy.
  • Migration prefix 0022 is unique on this head. Draft #114 must keep 0021_participant_identity_link.sql off this number and use 0023 or later before it leaves draft.

Residual that a buyer/operator will feel

The probe queries MIN(active_lease_expires_at_unix_ms) for all leased rows and discards it. QA-OBS-03 promises counts and timestamps. A purchaser operating a dead worker can see expired_lease_count = 1 and still not know how stale that lease is. Restrict the MIN to already-expired leased rows against one clock_timestamp() snapshot and expose oldest_expired_lease_at_unix_ms. Keep classification on the count bound.

HTTP probe wiring is still a separate slice. Current landing vehicle is #132, not #111.

Coordination

  • Prefer this over #113 if only one scoring-job health head can land.
  • Do not fold HTTP listen/bind, session persist, or identity-link 0021 into this slice.
  • Live fast-mlsirm execution remains outside this repository.
Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread src/postgres_health.rs
let expired_lease_count: i64 = row.get(2);
let oldest_leased_expiry_at_unix_ms: Option<i64> = row.get(3);
let oldest_active_job_at_unix_ms: Option<i64> = row.get(4);
let _ = positive_optional_millis(oldest_leased_expiry_at_unix_ms)?;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This MIN(active_lease_expires_at_unix_ms) is taken over every leased row, including live future expiries, and is then discarded after the positivity check. Schema scoring_lease_expiry_positive_check already forbids non-positive leased expiry, so the extra column does not change classification.

QA-OBS-03 says the probe exposes aggregate counts and timestamps. Operators can see expired_lease_count but cannot tell whether the oldest dead lease expired 5 seconds or 5 hours ago. Restrict the MIN to leased AND expiry < clock_timestamp() (one shared clock snapshot with the count) and return it as oldest_expired_lease_at_unix_ms. Do not invent an age SLO; keep classification on the caller-supplied count bound.


- **Stimulus:** a scoring worker dies after claiming a job; the lease is past `active_lease_expires_at_unix_ms` while enqueue-to-complete age and active count remain inside the operator policy.
- **Response:** readiness classifies the expired-lease count against an explicit caller bound. A healthy outbox or created-at age cannot hide the dead worker. The probe exposes only aggregate counts and timestamps.
- **Evidence:** `tests/postgres_scoring_job_backlog_health.rs` expired-lease count/policy cases and `src/postgres_health.rs` composition tests. HTTP transport remains #111.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

HTTP probe transport is no longer #111. The current landing vehicle is #132 (feat(health): bind probe process from listen env). Point evidence and risk text at that head, or say "HTTP probe transport remains a separate active PR," so this slice does not freeze a superseded number.

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.

2 participants