# v0.85.0 — Scheduler and Resource Resilience Gate

> **Status:** ✅ Implemented (2026-08-16); full manual CI matrix pending
> **Scope:** Large
> **Releases after:** [v0.84.0](v0.84.0.md) — but shares no subsystem with
> [v0.82.0](v0.82.0.md)/[v0.83.0](v0.83.0.md) and may be developed in parallel
> **Driven by:** August 2026 pre-scaling hardening review
> **Renumbered:** previously planned as v0.81.4. It narrows or replaces
> documented self-healing behaviour, changes drain semantics, and imposes new
> hard maxima on API inputs — minor-release changes, not patch-release ones.

## Theme

Make the current in-process scheduler predictable under concurrency, multiple
databases, blocked queries, crashes, large catalogs, and adversarial input. This
is the final single-node resilience gate before the product arc begins in
v0.86.0.

## Non-Goals

- No external-worker protocol or adaptive external pool.
- No new monitoring backend.
- No scheduler feature expansion beyond making existing contracts reliable.

## Items

### OPS-81-1: Authoritative Worker Limits

Use one effective cluster limit, including PostgreSQL's
`max_parallel_workers`, for quota calculation and token acquisition. Make token
reservation, worker registration, exit, and reconciliation race-free by
tracking reserved/spawning and live workers separately or serializing state
changes. A sampled reconciliation must never erase or invent capacity.

### OPS-81-2: Database-Scoped Shared State

Key pause entries, scheduler health, and watermark baselines by database OID as
well as local `pgt_id`. Report bounded-capacity failures instead of returning
success when a pause entry cannot be stored. Waiting for selected nodes to pause
must not wait for unrelated work in another database.

### OPS-81-3: Persistent Drain Semantics

Keep dispatch disabled after `drain()` completes until an explicit resume or
documented restart boundary. `is_drained()` must distinguish no request,
draining, and drained, and must verify relevant workers rather than comparing
epochs alone. Maintenance cannot race a new job immediately after drain returns.

### OPS-81-4: Enforceable Refresh Deadlines

Add bounded, configurable lock and statement deadlines for scheduled refreshes
and apply them with transaction-local settings. Timeout outcomes must be typed,
retryable where appropriate, visible in health/history, and guaranteed to
release worker tokens and unblock drain.

### OPS-81-5: Complete Self-Healing and Error Accounting

Implement the documented temporary memory reduction, lock backoff, and reset
after successful cycles, or narrow the public contract to behavior that exists.
Inline and dynamic workers must share one persistent error-accounting,
remediation, and suspension path; in-memory dispatcher retries may not mask
repeated catalog-visible failure.

### OPS-81-6: Bounded Queue and Catalog Maintenance

Make queue-depth gauges reflect currently queued work by decrementing on claim,
cancel, spawn failure, and recovery. Periodically reconcile against catalog
state. Prune terminal jobs during normal uptime and limit history/cleanup work
per tick so a large retention backlog cannot monopolize dispatch.

### OPS-81-7: Scheduler-Safe Metrics and Alerts

Collect metrics only for an actual scrape rather than on every scheduler tick,
honor configured request deadlines, and prevent slow clients from delaying
dispatch. Standardize and document the bind address.

Build oversized alerts as valid structured JSON with character-boundary
truncation. Replace modulo-window reminders with last-emitted timestamps so
fast ticks cannot duplicate alerts and slow ticks cannot miss them.

### OPS-81-8: Resource Ceilings and Adversarial Assurance

Set conservative finite defaults and hard maxima for parse complexity, bulk
cardinality, pause arrays, and blocking control timeouts. Cancellation or client
disconnect must not strand scheduler state.

Make fuzz/property workflow names match reality: enumerate one target set for
smoke and nightly runs, exercise real SQL-facing parser/rewrite and CDC decode
entry points, assert property case counts, and detect variable-built dynamic SQL
in mandatory linting.

## Release Gate

v0.85.0 will not ship until all of the following pass:

- Multi-database stress with delayed worker startup and forced exits never
  exceeds configured worker limits and always recovers capacity.
- Identical `pgt_id` values in two databases have independent pause, scheduler
  health, and watermark state.
- Drain remains active across several scheduler intervals and resumes only via
  the documented action.
- Blocked locks and overlong refreshes terminate at configured deadlines,
  release tokens, record typed outcomes, and permit drain.
- OOM and lock-timeout injection proves remediation, reset, and eventual
  suspension in serial and parallel modes.
- Queue depth returns to zero and expired job/history cleanup progresses under
  continuous refresh load without unbounded tick latency.
- Idle metrics cause zero collection queries; slow scrapes stay within their
  budget; arbitrary Unicode alerts remain valid JSON below PostgreSQL limits.
- Oversized SQL/JSON/control inputs fail before expensive work, and every fuzz
  target runs in both smoke and nightly workflows.

The release requires a sustained multi-database soak with no leaked tokens,
stuck drain state, unbounded catalog growth, or unexplained refresh mismatch.
