Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b22d3d8
test: reproduce pg-erd response slow-drip lifetime gap
seonghobae Sep 2, 2026
75a181e
feat: add response-body lifetime isolation budget
seonghobae Sep 2, 2026
ea59921
feat: version pg-erd response-body lifetime config
seonghobae Sep 2, 2026
48c8985
fix: enforce pg-erd response-body lifetime
seonghobae Sep 2, 2026
94c4ec5
fix: preserve pg-erd v1 while admitting explicit v2 lifetime
seonghobae Sep 2, 2026
3c84f1e
test: cover pg-erd response lifetime config version
seonghobae Sep 2, 2026
eeb669b
docs: version pg-erd response lifetime contract
seonghobae Sep 2, 2026
472e187
docs: define pg-erd response-body runtime budget
seonghobae Sep 2, 2026
b7ade63
docs: add pg-erd slow-drip acceptance
seonghobae Sep 2, 2026
15dd1f0
test: avoid origin-close timing assertion
seonghobae Sep 2, 2026
f80e3ec
test: cover dormant configured response lifetime
seonghobae Sep 2, 2026
4f6da1d
fix: start lifetime before upstream body callbacks
seonghobae Sep 2, 2026
2f207c5
test: isolate lifetime from per-read timeout
seonghobae Sep 2, 2026
8fc9e22
docs: record pg-erd response lifetime decision
seonghobae Sep 2, 2026
2f69875
docs: document pg-erd response lifetime operation
seonghobae Sep 2, 2026
0a66dc8
fix: distinguish incomplete v2 from future config
seonghobae Sep 2, 2026
d0acaa8
test: distinguish pg-erd v2 from future config
seonghobae Sep 2, 2026
b327ec9
test: require explicit pg-erd v2 lifetime
seonghobae Sep 2, 2026
b9ffc85
docs: align response lifetime with upstream callback order
seonghobae Sep 2, 2026
d5e85c6
docs: align response lifetime callback boundary
seonghobae Sep 2, 2026
fc5429a
docs: isolate slow-drip test from read timeout
seonghobae Sep 2, 2026
c757942
docs: make response lifetime gap baseline code-current
seonghobae Sep 2, 2026
6077ec8
docs: record pg-erd response lifetime contract
seonghobae Sep 2, 2026
c92d670
test: harden pg-erd slow-drip traffic evidence
seonghobae Sep 8, 2026
db60f8e
merge: adopt final pg-erd TLS parent for response lifetime
seonghobae Sep 8, 2026
368aab2
docs: align config contract with pg-erd response lifetime v2
seonghobae Sep 8, 2026
485bd65
docs: make response lifetime operability code-current
seonghobae Sep 8, 2026
d138cd3
docs: add pg-erd response lifetime acceptance
seonghobae Sep 8, 2026
a501b90
docs: align TRD with versioned response lifetime
seonghobae Sep 8, 2026
0581caa
docs: record pg-erd response lifetime delta
seonghobae Sep 8, 2026
27c094c
docs: advance gap baseline through response lifetime
seonghobae Sep 8, 2026
03c9ab1
style: apply hosted rustfmt to response lifetime proxy
seonghobae Sep 8, 2026
5246b30
style: apply hosted rustfmt to runtime isolation
seonghobae Sep 8, 2026
c295d91
style: apply hosted rustfmt to response lifetime config test
seonghobae Sep 8, 2026
cd0c358
style: apply hosted rustfmt to slow-drip acceptance
seonghobae Sep 8, 2026
9b135db
test: avoid proxy equality in direct-deserialize regression
seonghobae Sep 8, 2026
d8156ee
test: cover nonexpired response body progress
seonghobae Sep 8, 2026
0bdc7f7
test: close response filter region coverage
seonghobae Sep 8, 2026
b4d7387
docs: align response lifetime execution condition
seonghobae Sep 8, 2026
942c589
test: prove response lifetime is causal
seonghobae Sep 8, 2026
56c7c48
test: prove response lifetime starts at response header
seonghobae Sep 8, 2026
afc01d3
runtime: reconcile pg-erd response lifetime onto current TLS parent
seonghobae Sep 18, 2026
ba02ba4
runtime: preserve current proxy semantics while adding response lifetime
seonghobae Sep 18, 2026
b805e28
runtime: suppress second error status after committed response
seonghobae Sep 18, 2026
00cafa9
docs: reconcile response lifetime config with current contracts
seonghobae Sep 18, 2026
371421f
docs: reconcile response lifetime operability with current parent
seonghobae Sep 18, 2026
6d4e909
docs: reconcile response lifetime test strategy with current evidence
seonghobae Sep 18, 2026
7b81e1c
docs: reconcile response lifetime TRD with current parent contracts
seonghobae Sep 18, 2026
14a650f
docs: reconcile changelog with current response lifetime ancestry
seonghobae Sep 18, 2026
85cd1e6
test: make pg-erd slow-drip evidence absolute-deadline bounded
seonghobae Sep 18, 2026
d8ed573
test: apply rustfmt to slow-drip evidence
seonghobae Sep 18, 2026
ffdcc0c
merge: adopt integrated #37 ancestry
seonghobae Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions API_CONFIG_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Version 1 Configuration Contracts
# Versioned Configuration Contracts

## Generic `cwl-pingora-gateway`
## Generic `cwl-pingora-gateway` version 1

```yaml
version: 1
Expand All @@ -25,7 +25,7 @@ upstreams:

Unknown fields are rejected. `version` must be `1`. `listener`, `metrics_listener`, and `address` are socket addresses with non-zero ports. Port zero is rejected because production authority must remain operator-declared rather than OS-selected or unusable. Traffic and metrics listeners must not overlap one effective socket authority: equal sockets, same-port same-family wildcard/concrete aliases, exact native/IPv4-mapped aliases, native or mapped IPv4 wildcard aliases, and the platform-dependent same-port IPv6-wildcard/IPv4 combination fail closed. Distinct concrete non-aliased addresses may share a non-zero port. `max_request_body_bytes`, `max_in_flight_requests`, and `upstream_keepalive_pool_size` must all be positive. Generic v1 requires exactly one upstream and a non-empty stable upstream name. Every timeout must be positive.

The timeout fields map directly to the pinned Pingora peer options rather than defining a second gateway timer model. In particular, `read_ms` is a **per-read inactivity budget**: Pingora waits at most that long for each individual upstream `read()` and resets the timer after a successful read. It is not a total-response deadline. A connected upstream that sends no response bytes is therefore bounded by `read_ms`, while a slow-drip response can remain alive across multiple successful reads. Whole-response lifetime remains an explicit open runtime-isolation requirement and must not be inferred from `read_ms`.
The timeout fields map directly to the pinned Pingora peer options rather than defining a second gateway timer model. In particular, `read_ms` is a **per-read inactivity budget**: Pingora waits at most that long for each individual upstream `read()` and resets the timer after a successful read. It is not a total-response deadline. A connected upstream that sends no response bytes is therefore bounded by `read_ms`, while a slow-drip response can remain alive across multiple successful reads. Generic v1 has no whole-response lifetime and must not infer one from `read_ms`.

`max_in_flight_requests` is a process-local backpressure boundary for non-health downstream requests. When the budget is exhausted, the runtime fails fast with HTTP 503 instead of admitting unbounded work. `/livez` and `/readyz` bypass this application admission budget so saturation does not hide process health. The admission lease is released when the request context ends, including failed requests. `upstream_keepalive_pool_size` is wired directly into Pingora's `ServerConf`; the runtime does not inherit Pingora's framework default of 128 reusable upstream connections.

Expand All @@ -37,14 +37,15 @@ Generic v1 downstream transport is cleartext TCP. Before proxying, the generic a

## Bounded `cwl-pingora-pg-erd-migration` candidate

The dedicated pg-erd migration binary consumes a different, migration-specific Admin Config profile. It deliberately reuses the same top-level deployment value names while admitting exactly two fixed transport authorities:
The dedicated pg-erd migration binary consumes a different, migration-specific Admin Config profile. Version 1 preserves the existing unreleased characterization semantics and rejects `max_upstream_response_body_ms`. Version 2 is the explicit response-lifetime increment and requires a positive `max_upstream_response_body_ms`; the runtime never injects a hidden default.

```yaml
version: 1
version: 2
listener: 0.0.0.0:6188
metrics_listener: 127.0.0.1:6192
max_request_body_bytes: 1048576
max_in_flight_requests: 128
max_upstream_response_body_ms: 30000
upstream_keepalive_pool_size: 32
upstreams:
- name: backend
Expand All @@ -67,6 +68,12 @@ upstreams:
idle_ms: 10000
```

The numeric response-lifetime value above is illustrative configuration, not a production SLO. A deployment owner must choose the version-2 value from its observed long-response contract before canary or cutover.

`max_upstream_response_body_ms` starts at the first non-informational upstream response header. Runtime Isolation compares elapsed monotonic time only when a non-empty upstream body chunk is observed. Progress at or beyond the configured lifetime raises an upstream-scoped fatal error; empty/end-of-stream bookkeeping callbacks do not manufacture a timeout. If the final response has already been written, `fail_to_proxy` observes Pingora's `Session::response_written()` commitment state and emits no second status. Pre-commit upstream failures retain the existing policy-complete local error response behavior. No route failover is introduced by this contract.

This callback guard is not an exact timer interrupt. `read_ms` remains a per-read inactivity budget that resets after a successful read. A continuously progressing body is stopped at the first non-empty body callback at or beyond `max_upstream_response_body_ms`; a response that becomes quiescent is bounded by `read_ms`. The current callback surface does not wake a pending read exactly at the body-lifetime instant, and slow delivery of an incomplete response header remains a separate transport gap.

This is not a generic multi-route configuration language. Operator input can bind only concrete transport/TLS values for the compiled `backend` and `frontend` identities. Missing, extra, duplicate, renamed, port-zero, or otherwise invalid listener/metrics/upstream transport authorities fail closed before listener activation. Listener and metrics sockets consume the same effective-authority invariant as generic v1, while the migration profile keeps its specific zero-transport-authority error contract. Routes and edge-owned response fields are not configurable: the characterized profile fixes exact `/healthz -> backend`, raw `PathPrefix(`/api`) -> backend` semantics including `/apiary`, fallback `/ -> frontend`, and the four captured response fields `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, and `Permissions-Policy: geolocation=(), microphone=(), camera=()`.

Admin parsing validates only deterministic configuration and authority invariants. It does not read custom trust-bundle bytes. If an admitted TLS upstream supplies `trust_bundle_file`, the canonical Pingora peer adapter reads and parses that material exactly once during `build_proxy`, still before listeners are registered. An unreadable or invalid bundle therefore blocks activation without a validate-then-reload trust-file window.
Expand Down
Loading