Skip to content

feat: audit service client (/audit/v1 — apikeys + traces) - #85

Merged
ngjunsiang merged 1 commit into
feat/auth-brokerfrom
feat/audit-service
Oct 4, 2026
Merged

ngjunsiang merged 1 commit into
feat/auth-brokerfrom
feat/audit-service

Conversation

@nycomp

@nycomp nycomp commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Stack (merge in order): #78 -> #79 -> #80 -> #81 -> #82 -> #85
This is 6 of 6. Base is the branch of the PR below; each PR's diff shows only its own commit, and GitHub retargets it to main when the one below merges. Branches rebuilt on current main (9563443) — content unchanged from the original commits.
As always per SOP: after each merge, verify the issue auto-closed and close it manually if not.

Summary

New campus_python.audit.v1 package wired as campus.audit, covering the audit service surface (campus weekly @ 24adffc, campus/audit/routes/) — the last uncovered Campus service.

Auth mode. The audit service authenticates with its own API keys (audit_v1_... Bearer tokens), not CLIENT_ID/CLIENT_SECRET, so Campus.audit builds a dedicated CampusRequest in device mode (no app credentials required) and sets Authorization: Bearer . Base URL resolves like the other services: CAMPUS_AUDIT_URL first, then ENV defaults (development Railway campusaudit-development, staging audit.campus.nyjc.dev, production audit.campus.nyjc.app — matching campus/config.py).

apikeys — audit.apikeys.new(name, owner_id, scopes, rate_limit=None, expires_at=None) (returns the record with the one-time plaintext api_key), .list(owner_id, active_only=True, limit), and per-key .get/.update/.revoke/.regenerate. Details pinned from the server: bool query values are sent as lowercase true/false (flask_campus _BOOL_LITERALS rejects Python's True capitalization → 422); update raises ValueError on an empty payload (server: "No mutable fields provided"); item routes carry trailing slashes, regenerate is a leaf.

traces — audit.traces.ingest(spans) (batch; 207 partial failure surfaces in the returned body), .list(since, until, limit, cursor) and .search(path, status, api_key_id, client_id, user_id, ...) with {traces, cursor: {next, has_more}} pagination, traces[trace_id].get() (root-span tree envelope), and .spans.list() / .spans[span_id].get().

Tests

tests/unit/test_audit.py — 15 tests: every route's path (slash rules included), body/query shapes, the lowercase-bool wire contract, empty-update guard, and Campus.audit wiring (URL resolution + Bearer header + missing-key OSError). Full suite: 161 passed.

Fixes #77

@nycomp
nycomp force-pushed the feat/audit-service branch from 38b52ad to 180cd88 Compare October 4, 2026 01:34
New campus_python.audit.v1 package, mounted as Campus.audit:

- AuditRoot authenticates with a dedicated audit API key (AUDIT_API_KEY
  env var, audit_v1_...) as a Bearer token on its own CampusRequest —
  the audit service rejects CLIENT_ID/CLIENT_SECRET — with base URL
  resolved like the other services (CAMPUS_AUDIT_URL, then ENV
  defaults: development Railway / staging / production audit domains).
- apikeys: new/list/get/update/revoke/regenerate; bool query values
  sent as lowercase literals (flask_campus _BOOL_LITERALS contract);
  update refuses empty payloads like the server; plaintext key values
  surface only at new()/regenerate().
- traces: ingest (batch spans), list/search with cursor pagination,
  trace tree get, flat spans list, single span get.

Fixes #77
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.

feat: audit service client (/audit/v1 — apikeys + traces) [tracking, no consumer demand yet]

2 participants