feat(health): observe expired scoring-job leases - #131
Conversation
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>
There was a problem hiding this comment.
Stale comment
Review —
8c51e3dexpired scoring-job leasesThis 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.
0022applies throughapply_scoring_job_migration. Composition still fails closed across integration, data-rights, and scoring-job families.Required before this is treated as shipped truth
- Independent last-push review (
seonghobaealready requested).- Exact-head required checks on
8c51e3d.- Name
0020/0021/0022in the TRACEABILITYmigrations/tree and add the Active-PR note todocs/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.
Sent by Cursor Automation: Fix Issues
There was a problem hiding this comment.
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_migrationcomposes immutable0002+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-suppliedmax_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_ageplus the mixed-state / refuse-expired PostgreSQL cases match the claimed fail-closed policy. - Migration prefix
0022is unique on this head. Draft #114 must keep0021_participant_identity_link.sqloff this number and use0023or 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
0021into this slice. - Live fast-mlsirm execution remains outside this repository.
Sent by Cursor Automation: Fix Issues
| 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)?; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.


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_mscan still lookWithinBoundswhen those bounds are enqueue-to-complete. Operators then see a healthy backlog while results stop arriving.This branch is #113 at
49dbb2bplus 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_agefailed to compile (E0560) becauseexpired_lease_countandmax_expired_lease_countdid 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_migrationapplies0022after0002/0021, and a second apply is idempotent.What changed
clock_timestamp()so a later created-at age bound cannot hide a dead worker;max_expired_lease_count, with no universal SLO defaults;apply_scoring_job_migrationpath;0021_*file.Verification
cargo fmt,cargo clippy --all-targets -- -D warnings, PostgreSQL 18.6 scoring-job backlog tests.Coordination
0021_participant_identity_link.sqlto0023(or later) before it leaves draft.