Skip to content

fix(monitor): hard-gate collector health on the upload delivery contract - #800

Merged
proerror77 merged 5 commits into
mainfrom
codex/collector-health-hard-gates
Aug 10, 2026
Merged

proerror77 merged 5 commits into
mainfrom
codex/collector-health-hard-gates

Conversation

@proerror77

@proerror77 proerror77 commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Change contract

Collector host health is now gated on the data delivery loop instead of process state. deployment/aliyun/monday-collector-health.sh keeps exactly four hard gates (breaches that fail closed into the monitor-collector-host issue), and demotes every other check to warnings that are reported but never block ok:true:

  1. Status fileupload-status.json must exist and parse on the mandated lanes (binance-lob spot/usdm, binance-fee). This closes the audited hole where a LOB lane reported healthy with no status file at all.
  2. Upload freshnesslast_success_at must be present and younger than a per-lane bound set just above that lane's upload cadence (LOB 7200s: hourly SEGMENT_SECONDS rotation + 300s upload loop; fee 600s: 60s snapshot + 60s upload timer, mirroring FEE_FAILURE_WINDOW; usdm-reference 1200s and polymarket 1800s: 5-minute upload timers; bybit 5400s: hourly finalize + :23 sweep). A missing/unparseable last_success_at is a breach: delivery is unproven.
  3. Pending backlog — pending count and oldest-pending-artifact age stay under per-lane bounds, discovered with each collector's own pending definition (LOB top-level *.manifest.json = the archiver's pending_upload_segments; fee/usdm-reference lake/raw/**/batch=* directories, which the uploaders delete after verified upload; polymarket rotated market-updates.<stamp>[...].ndjson tapes; bybit .ndjson with manifest+_SUCCESS and no .uploaded.json readback marker).
  4. Upload failuresfailure_count must not grow between polls and last_error must be empty, uniformly across all lanes (the fee-only initial-count breach is now uniform).

Kept as breaches beyond the four gates: polymarket-raw-ops-gate@.service on any non-disabled is-enabled state (static included — it proves an uncleaned host installation), and state-persistence failures (gate 4 delta detection depends on that state).

Demoted to warnings (JSON warnings array + warning: lines, never blocking ok:true): unit/timer active+enabled+Result, restart-rate deltas, health.json freshness/gaps, journald delay-gate trips, fee snapshot journal failures, disk space, /data mount. The workflow's breach issue body now includes the warnings section for triage context.

Issue relationship

Refs #738

Out of scope

  • No Rust changes. last_success_at for the fee uploader lands in a parallel PR; this script intentionally treats its absence as a breach (gate 2) rather than carrying a compatibility path.
  • Installing/restarting the on-host timer or redeploying the script to the host (runtime cutover needs a named controller and is a separate unit).

Dependencies and merge order

Merge after (or together with) the parallel PR that adds last_success_at to the fee uploader's upload-status.json. Until that deploys on the host, the fee lane breaches gate 2 by design — that is the intended fail-closed behavior, but merging this first would open a monitor issue for the fee lane.

Focused validation

  • bash deployment/aliyun/test-monday-collector-health.sh — 89 passed, 0 failed. Red/green coverage per gate: missing/malformed status file on mandated lanes (red) vs non-mandated lane (warning only), stale and missing last_success_at incl. the bybit epoch-ms format (red), pending count and oldest-age over bounds for LOB/reference/fee/polymarket/bybit (red), uploaded bybit segment with readback marker not counted as backlog (green), failure_count growth and first-observation-nonzero (red), healthy baseline with active market-updates.ndjson tapes excluded from backlog (green), and every demoted check (disk, units, timers, restarts, health.json, delay-gate, journald failure, fee snapshot failure, mount) asserting warning-but-ok:true. JSON shape tests pin the new warnings array and per-lane upload fields.
  • bash .github/scripts/test-monitor-collector-host.sh — collector host monitor contract: ok.
  • bash .github/scripts/agent-worktree-preflight.sh — ok.
  • git diff --check — clean.

Rollout and rollback

No runtime mutation from this PR: the workflow runs the script installed at /opt/monday/bin/monday-collector-health.sh, so the new contract takes effect only when the host copy is updated under a named-controller cutover. Until then the old script keeps running. Rollback is reverting this commit and (if already deployed) reinstalling the previous script. Note the new script's failure_count priors are compatible with the existing /var/lib/monday-collector-health state file, so the first post-deploy poll does not raise spurious initial-count breaches.

Scope exception

None

Summary by CodeRabbit

  • New Features

    • Added collector health monitoring with four blocking checks: status integrity, upload freshness, pending backlog, and upload failures.
    • Added lane-specific thresholds and support for required and optional upload sources.
    • Added warning reporting for infrastructure and service conditions without blocking collection.
    • Included health-check warnings in breach notifications when available.
  • Documentation

    • Updated health-monitor contracts and documented pending-artifact definitions and thresholds.
  • Tests

    • Expanded coverage for upload gates, warnings, backlog limits, stale data, failure detection, JSON output, and dry-run behavior.

Replace the process-state-centric breach set with exactly four hard gates
on the data delivery loop, demoting everything else to warnings:

1. upload-status.json must exist and parse on the mandated lanes
   (binance-lob spot/usdm, binance-fee); LOB lanes no longer pass while
   the status file is absent.
2. last_success_at must be present and fresh per upload lane, with
   thresholds just above each lane's upload cadence (LOB 7200s, fee 600s,
   usdm-reference 1200s, polymarket 1800s, bybit 5400s). A missing
   last_success_at is a breach; the fee uploader gains the field in a
   parallel change, so the fee lane breaches by design until that deploys.
3. Pending upload backlog is bounded in count and oldest-artifact age,
   discovered with each collector's own pending definition (LOB
   *.manifest.json, fee/usdm-reference lake/raw/**/batch=*, polymarket
   rotated market-updates.*.ndjson tapes, bybit marked .ndjson without
   .uploaded.json).
4. failure_count must not grow and last_error must be empty, uniformly
   across all lanes (the fee-only initial-count breach is now uniform).

polymarket-raw-ops-gate stays a breach on any non-disabled is-enabled
state (including 'static'), and state-persistence failures stay breaches
because gate 4 delta detection depends on the persisted state.

Unit/timer state, restart deltas, health.json freshness/gaps, delay-gate
journal trips, fee snapshot journal failures, disk, and mount are now
warnings: emitted in the JSON warnings array and as warning: lines, never
blocking ok:true. The monitor workflow includes the warnings in the
triage issue body.

Refs #738
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d5b2c275-102d-44d9-b728-beaa28691db7

📥 Commits

Reviewing files that changed from the base of the PR and between 675b614 and f034619.

📒 Files selected for processing (3)
  • deployment/aliyun/README.md
  • deployment/aliyun/monday-collector-health.sh
  • deployment/aliyun/test-monday-collector-health.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • deployment/aliyun/README.md
  • deployment/aliyun/test-monday-collector-health.sh
  • deployment/aliyun/monday-collector-health.sh

📝 Walkthrough

Walkthrough

The collector health monitor now enforces four blocking upload gates, classifies infrastructure conditions as warnings, validates lane-specific freshness and backlogs, and reports warnings in JSON output and breach issues.

Changes

Collector health monitoring

Layer / File(s) Summary
Health contract definition
agent-worktree.yml, deployment/aliyun/README.md, deployment/aliyun/monday-collector-health.sh
The contract defines status validity, upload freshness, pending backlog, and upload failure gates. State persistence and selected operational gates remain blocking.
Health monitor execution
deployment/aliyun/monday-collector-health.sh
The monitor adds lane-specific status parsing, timestamp validation, backlog scanning, failure-count checks, warning accumulation, and warning-inclusive output.
Health contract validation
deployment/aliyun/test-monday-collector-health.sh
Tests cover upload gates, optional lanes, timestamps, backlog limits, failure counts, warning-only conditions, JSON output, state persistence, and dry-run behavior.
Breach issue warning reporting
.github/workflows/monitor-collector-host.yml
Breach issues include warnings from the health snapshot under a non-blocking section.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CollectorHealth as monday-collector-health.sh
  participant UploadStatus as upload status files
  participant Spools as collector spools
  participant Workflow as monitor-collector-host.yml
  participant Issue as breach issue
  CollectorHealth->>UploadStatus: validate status and freshness
  CollectorHealth->>Spools: scan backlog and failure state
  CollectorHealth->>Workflow: write health snapshot
  Workflow->>Issue: append warnings when present
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: collector health now uses upload delivery contract gates.
Description check ✅ Passed The description includes all required sections and provides clear behavior, scope, dependencies, validation, rollout, and rollback details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/collector-health-hard-gates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
deployment/aliyun/monday-collector-health.sh (1)

553-556: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

last_error_at and last_error serialize as the JSON string "null" instead of null.

Lines 508-509 use jq -r, so an absent field yields the four-character string null. Line 554 passes those values with --arg, which always produces a JSON string. The snapshot therefore reports "last_error_at": "null". A consumer cannot distinguish an absent error from an uploader that literally wrote "null". Line 462 already emits real null for the missing-file path, so the two paths disagree.

Note that deployment/aliyun/test-monday-collector-health.sh line 746 asserts last_error_at == "null". Update that assertion together with this change.

♻️ Proposed fix: keep the raw JSON values
-  err_at=$(printf '%s' "$upload_json" | jq -r '(.last_error_at // null)')
-  err_msg=$(printf '%s' "$upload_json" | jq -r '(.last_error // null)')
+  err_at_json=$(printf '%s' "$upload_json" | jq -c '(.last_error_at // null)')
+  err_msg_json=$(printf '%s' "$upload_json" | jq -c '(.last_error // null)')
+  err_at=$(printf '%s' "$upload_json" | jq -r '(.last_error_at // null)')
+  err_msg=$(printf '%s' "$upload_json" | jq -r '(.last_error // null)')
   uobj=$(jq -n --argjson s "$success_raw" --argjson sa "$success_age_json" \
-    --arg e "$err_at" --arg m "$err_msg" --argjson f "$failure_count" \
+    --argjson e "$err_at_json" --argjson m "$err_msg_json" --argjson f "$failure_count" \
     --argjson d "$failure_delta" --argjson pc "$pending_count" --argjson pa "$pending_age" \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deployment/aliyun/monday-collector-health.sh` around lines 553 - 556, Update
the snapshot construction around the jq invocation assigning uobj so
last_error_at and last_error preserve raw JSON null values instead of passing jq
-r output through --arg; use JSON arguments consistent with the existing success
fields while retaining actual error strings. Update the corresponding assertion
in test-monday-collector-health.sh to expect null rather than the string "null".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deployment/aliyun/monday-collector-health.sh`:
- Around line 484-496: The last_success_at parsing in the health monitor rejects
valid RFC3339 fractional-second and UTC-offset timestamps. In
deployment/aliyun/monday-collector-health.sh:484-496, normalize fractional
seconds and UTC offsets before fromdateiso8601 while preserving
epoch-millisecond handling; in
deployment/aliyun/test-monday-collector-health.sh:151-160, add a mandated-lane
fixture using a fractional-second timestamp such as 2026-08-07T01:00:00.123456Z
and assert the health check exits 0.

---

Nitpick comments:
In `@deployment/aliyun/monday-collector-health.sh`:
- Around line 553-556: Update the snapshot construction around the jq invocation
assigning uobj so last_error_at and last_error preserve raw JSON null values
instead of passing jq -r output through --arg; use JSON arguments consistent
with the existing success fields while retaining actual error strings. Update
the corresponding assertion in test-monday-collector-health.sh to expect null
rather than the string "null".
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 579accd8-83da-44d4-bae2-84647ea4f79b

📥 Commits

Reviewing files that changed from the base of the PR and between be9e746 and 675b614.

📒 Files selected for processing (5)
  • .github/workflows/monitor-collector-host.yml
  • agent-worktree.yml
  • deployment/aliyun/README.md
  • deployment/aliyun/monday-collector-health.sh
  • deployment/aliyun/test-monday-collector-health.sh

Comment thread deployment/aliyun/monday-collector-health.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 675b614c16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deployment/aliyun/monday-collector-health.sh Outdated
Comment thread deployment/aliyun/monday-collector-health.sh Outdated
Comment thread deployment/aliyun/monday-collector-health.sh Outdated
Comment thread deployment/aliyun/monday-collector-health.sh Outdated
proerror77 and others added 3 commits August 10, 2026 15:27
- Gate 2 parser now accepts the timestamps the uploaders actually emit:
  six-fractional-digit Z (polymarket_upload::utc_now, reused by fee and
  usdm-reference) and Chrono to_rfc3339() +00:00 with optional fractions
  (LOB), normalizing both before jq fromdateiso8601; non-UTC offsets are
  refused. Fixtures emit the real six-digit form and new green cases pin
  the offset/fractional variants.
- Gate 3 now runs before the status-file checks, so a non-mandated lane
  with a missing upload-status.json can no longer hide an on-disk backlog
  (gate 1 absence stays a warning; gates 3 fail closed on the artifacts).
- A pending-backlog scan that cannot inspect the spool (unreadable spool
  or a failed find traversal) is a breach instead of a silent zero.
- Polymarket freshness bound raised to 7200s: both lanes rotate tapes
  hourly (record_market_updates_rotate_seconds = 3600; reference writer
  rotates at UTC-hour boundaries) and last_success_at only advances on an
  actual upload, so the 5-minute timer is not a heartbeat.
@proerror77
proerror77 merged commit 81d57b4 into main Aug 10, 2026
55 checks passed
@proerror77
proerror77 deleted the codex/collector-health-hard-gates branch August 10, 2026 09:42
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.

1 participant