Skip to content

feat(tracing): fail-closed rate gate + breaker feed on ingest (#831) - #839

Merged
nycomp merged 4 commits into
weeklyfrom
feat/831-rate-gate
Oct 6, 2026
Merged

nycomp merged 4 commits into
weeklyfrom
feat/831-rate-gate

Conversation

@nycomp

@nycomp nycomp commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Producer lane of #831 (Phase 3 of #538) — wires the campus-python circuit breaker (campus-api-python#98) into the campus producers. Audit-side enforcement landed in #835. Design (ratified): Phase 3 design comment on #538.

What

  • Breaker feed (campus/audit/client/v1/traces.py): AuditClient.traces.new — the ingest path for auth, api, and classroom (classroom imports campus.audit.middleware from this package) — now feeds the SDK breaker on every ingest response before raising: 429 trips the bucket (Retry-After + tripped key from the body), 2xx clears expired trips, availability errors neutral. The observation is guarded (contextlib.suppress + lazy import): a breaker failure or a stale campus_python without audit.ratelimit can never break span ingestion.
  • Fail-closed gate (campus/audit/middleware): new check_rate_gate() before_request hook, registered before start_span so a gated request never opens a span (and end_span no-ops — no wasted ingest against a bucket audit is rejecting). Opt-in per deployment via AUDIT_TRACING_FAIL_CLOSED=1: tripped buckets short-circuit with 503 RATE_LIMITED + Retry-After before any view runs.
    • Per-identity where identity is resolvable at entry (app-level hooks run before blueprint-level auth — true for auth's session routes; bearer routes on api resolve mid-view and get the coarse gate).
    • Identity-less requests gate on the producer's learned apikey= fallback bucket (learned from the first 429 body), or the wildcard before anything is learned.
    • Off by default — the ratified rollout: producers stay fail-open until flipped one at a time on dev.

What producers need

Nothing code-wise: classroom (and any campus.audit.middleware consumer) inherits gate + feed with a campus-suite lock bump; auth/api get it when this merges to weekly (they deploy from weekly). The flip is a Railway variable per producer.

Testing

  • New tests/unit/audit/test_rate_gate.py (10 tests): flag-off fail-open even when tripped; 503 + Retry-After for the tripped identity; other identities unaffected; learned-fallback and wildcard gating for identity-less requests; availability-error neutrality; verifying-state gate-open + 2xx-clears; client feed trips-and-raises / 201-no-trip; stale-SDK silent degradation.
  • unit, integration, contract, sanity all green locally; ruff clean on touched files.

Verification plan (after merge, on dev)

  1. Set AUDIT_RATE_LIMIT_PER_MINUTE=1 on campus.audit briefly → trip a bucket via ingest.
  2. Set AUDIT_TRACING_FAIL_CLOSED=1 on one producer → confirm 503 + Retry-After on matching requests while others pass.
  3. Restore both; confirm gate clears within one cooldown-plus-probe cycle.

Closes #831 together with campus-api-python#98 (base is weekly, so closing by hand per convention).

ngjunsiang and others added 4 commits October 6, 2026 10:45
Producer side of the audit ingest rate limit (audit enforcement landed
in #835; SDK breaker in campus-api-python#98). Design: design comment
on campus#538.

- AuditClient traces.new feeds the campus_python.audit.ratelimit
  breaker on every ingest response before raising: 429 trips the
  bucket (Retry-After + tripped key from the body), 2xx clears expired
  trips, availability errors neutral. Observation is guarded — a
  breaker failure or stale SDK can never break ingestion.
- New check_rate_gate() before_request hook (registered before
  start_span, so gated requests never open a span): when
  AUDIT_TRACING_FAIL_CLOSED=1, requests whose identity bucket is
  tripped get 503 + Retry-After at entry. Per-identity where identity
  is resolvable (app-level hook ordering), degrading to the producer's
  learned apikey= fallback bucket or the wildcard. Off by default —
  producers stay fail-open until flipped per the ratified rollout.
- classroom (and any producer on campus.audit.middleware) inherits the
  gate with a campus-suite bump; no producer-side code needed there.

AUDIT_TRACING_FAIL_CLOSED is a new deployment variable; document+flip
per producer on dev as the #831 verification step.
CI type checks install campus-api-python from git main, which lacks
campus_python.audit.ratelimit until campus-api-python#98 merges — the
static imports failed pyright. importlib.import_module with a string
name is opaque to type checkers and keeps the stale-SDK degradation
runtime behavior under any install. Stale-SDK test now patches
importlib.import_module to match.
CI type checks and unit tests resolve campus_python.audit.ratelimit
from the poetry.lock-pinned campus-api-python commit; bumping the lock
to main@9caf58b (campus-api-python#98 merge) makes the breaker module
visible to both, un-skipping the rate-gate suite on CI.
@nycomp
nycomp merged commit ecd46ee into weekly Oct 6, 2026
17 checks passed
@nycomp
nycomp deleted the feat/831-rate-gate branch October 6, 2026 08:05
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.

2 participants