feat(health): bind probe process from listen env - #132
Conversation
RED: the domain health snapshot is not yet exposed as GET /live and GET /ready with fail-closed readiness and a safe as-built OpenAPI 3.2 contract.
Map RuntimeHealthSnapshot to GET /live and GET /ready with fail-closed readiness, RFC 9457 problem details for unsupported requests, and an as-built OpenAPI 3.2.0 document that lists only those operations.
Exercise HealthHttpResponse::content_type and GET /ready?capability= from the library test module so Linux line coverage can reach the remaining unexecuted instantiations.
* feat(health): bind a TCP listener for operator probes GET /live and GET /ready already exist as a request translator. Bind a blocking listener that serves one HTTP/1.1 request per accepted connection without adding public product routes, TLS, or SLO values. * docs(traceability): name Active PR #92 on the bound health listener * feat(health): answer probes from a PostgreSQL operational snapshot * test(health): require a composed PostgreSQL operational snapshot RED: runtime and relation probes exist, but callers still assemble liveness, backlog, integrity, and the postgres capability by hand. * feat(health): compose PostgreSQL probes into one operational snapshot Map runtime and relation probes plus caller-supplied backlog into a RuntimeHealthSnapshot. Probe failure becomes unknown/unready evidence and never returns a driver error. * feat(health): answer probes from a PostgreSQL operational snapshot Rebuild #95 on the current #92 listener plus the #93 snapshot composer. handle_postgres_health_http_request and accept_one_postgres_health_http observe the caller-owned store and reuse the existing probe translator. * docs(health): name #95 snapshot wiring on the current listener stack Restore the architecture mappings the rewrite dropped: TRACEABILITY module and capability rows, OPERABILITY fail-closed snapshot readiness, ADR-0014 as-built status, and the composed-snapshot changelog line. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> 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>
…port RED: GET /live must not observe PostgreSQL, bare GET /ready must fail closed on a read-only store, incomplete or oversized requests must return 400 without echoing input, and the as-built OpenAPI /live operation must document HTTP 503. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Observe PostgreSQL only for GET /ready, after accept. Bare /ready requires postgres_operational_store. The listener applies a 2-second I/O timeout, rejects incomplete or oversized requests without echo, and adds Cache-Control: no-store plus Allow: GET on 405. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Operators can now run a blocking accept loop so a load balancer can keep asking GET /live and GET /ready. Interrupted accepts retry; any other accept, read, or write error stops the loop. TLS and keep-alive stay out of this slice. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Keep #91 as the predecessor translator head. Operators should land the serve-loop successor so a load balancer can keep probing. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A load-balancer reset after accept no longer stops GET /live or GET /ready. Accept still retries Interrupted/ConnectionAborted/ConnectionReset and stops on WouldBlock. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Prove the accept-retry or-arm and the PostgreSQL serve loop still answer GET /live after a dropped probe. Cite RFC 9293 for TCP reset processing. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Unsupported probe methods and paths now emit urn:psychometrics-commons:problem:* instead of about:blank so operators can distinguish bad-request, not-found, and method-not-allowed without echoing raw input. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Add a fail-closed process entrypoint so operators can start GET /live and GET /ready from HEALTH_LISTEN_ADDR or platform PORT. DATABASE_URL is observed only after accept and only for readiness. A down store keeps liveness up and never echoes driver text. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
There was a problem hiding this comment.
Exact head e744049. Draft. Checks are still queued. No blocking production defect in the unique process-bind slice.
Operator next action
Keep this head preferred over #122, #117, #111, #91, and #102. Do not merge those in parallel. Do not open another overlapping health-transport PR.
After exact-head required checks pass, this still needs independent last-push approval. Do not self-approve. Do not merge while Draft or while mergeable_state is blocked.
Set HEALTH_LISTEN_ADDR or PORT, then call run_health_process. Point liveness at GET /live and readiness at GET /ready. Set DATABASE_URL only when readiness should observe PostgreSQL. Set HEALTH_BACKLOG_HEALTH=within_bounds only after backlog is measured. A /ready 200 is not product-schema proof: this process does not declare product relations.
What this head proves
- Missing, blank, padded, or unknown listen/store/backlog values fail closed before bind.
HEALTH_LISTEN_ADDRwins overPORT.PORTalone binds0.0.0.0:$PORT.- GET
/liveanswers process liveness without store I/O and stays HTTP 200 whenDATABASE_URLis down. - GET
/readyis HTTP 503 without driver text when the store is unreachable or backlog is unknown. - Bare
/readyinjectspostgres_operational_store, so a load balancer that omitscapability=cannot advertise ready for read-only or unsupported majors. /ready?capability=scoringfail-closes because this process does not observe scoring.- Debug redacts the store URL. No
main/bin. RFC 9110 is recorded. Traceability names Active PR #132, not protected-main.
Residual, not blocking
- Empty required-relation set:
/ready200 is store/write/major/backlog evidence, not migration proof. - Process-level live-store coverage is narrow; adapter contracts cover the missing backlog/bare-ready arms.
- Accept loop is single-threaded. Semantic live/ready isolation is real; a hung
/readyconnect can delay later/liveon the same thread. Do not treat that as a new #132 blocker.
HTTP session start still waits on #121 / #87 / #98. Next unique buyer slice is not more health transport.
Sent by Cursor Automation: Fix Issues
| Some(mut client) => handle_postgres_health_http_request( | ||
| request, | ||
| &mut client, | ||
| &[], |
There was a problem hiding this comment.
Process /ready always passes an empty required-relation set. On a reachable writable store plus HEALTH_BACKLOG_HEALTH=within_bounds, the adapter can return HTTP 200 with data_integrity_health: verified even when no product schema exists.
That matches the current adapter contract. Do not invent a relation env in this slice. Operators must not treat process /ready 200 as proof that product tables can accept state-changing work.
| let ready = exchange( | ||
| addr, | ||
| &format!( | ||
| "GET {HEALTH_READY_PATH}?capability={POSTGRES_OPERATIONAL_STORE_CAPABILITY_REF} HTTP/1.1\r\nHost: localhost\r\n\r\n" |
There was a problem hiding this comment.
This is the only live-store process test, and it only proves GET /ready?capability=postgres_operational_store with within_bounds.
Bare GET /ready and reachable-store + missing/unknown/stalled backlog are covered in the HTTP/snapshot contracts, not through serve_health_process. Not a current defect. A later process change that dropped ready_required_capabilities would make bare /ready fail-open on read-only or unsupported majors.
Runtime CI sets TEST_DATABASE_URL as host=localhost user=... dbname=..., which rust-postgres already accepts. The probe process rejected that form as InvalidDatabaseUrl and failed the reachable-store contract.
Production line coverage missed the accept-loop return and the last listen map_err after bind. Connection errors never stopped the loop, so that return was dead. Force WouldBlock on accept instead.
…9c-4a23-b5ee-495fc24b1b3f-068b # Conflicts: # docs/TRACEABILITY.md # tests/postgres_recovery_invariants.rs Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
|
Closing as superseded by #247. #247 explicitly replays the full operator-health stack through this PR onto current protected main |
Record the process-entrypoint successor so operators and reviewers treat Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Record the process-entrypoint successor so operators and reviewers treat Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Record the process-entrypoint successor so operators and reviewers treat Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>


Why
PR #122 can serve GET
/liveand GET/readyafter accept, but a buyer still cannot start a process from environment input. WithoutHEALTH_LISTEN_ADDRor platformPORT, a load balancer has nothing to probe.TDD
RED required missing/padded/unknown listen, store, and backlog values to fail closed, GET
/liveto stay HTTP 200 whenDATABASE_URLis down, and GET/readyto stay HTTP 503 without driver text. GREEN addsrun_health_processplus those contracts. RFC 9110 is recorded in doctoring for origin-server authority.Scope
HEALTH_LISTEN_ADDRorPORT(0.0.0.0:$PORT).DATABASE_URLonly after accept and only for GET/ready./livefree of store I/O.HEALTH_BACKLOG_HEALTHbefore readiness can be true.Out of scope
Test plan
cargo test --lib health_process --test health_process_contract --test documentation_architecture_contract --test traceability_active_pr_contractcargo clippy --all-targets -- -D warningscargo test --test postgres_health_process_contract(needsTEST_DATABASE_URL)Do not merge until exact-head checks and independent last-push approval are satisfied. Never self-approve.
Operator next action
Prefer this head over #122 once checks are green. Set
HEALTH_LISTEN_ADDRorPORT, then callrun_health_process. Point liveness at GET/liveand readiness at GET/ready. SetDATABASE_URLonly when readiness should observe PostgreSQL. SetHEALTH_BACKLOG_HEALTH=within_boundsonly after backlog is measured. Close or retarget #122, #117, #111, #91, and #102 after this lands.