Repository navigation
Consolidate consumer-facing middleware into campus_python (caller-side SDK layer) #980
Description
Parent context: #970 (campus-core carve-out) — the audience-based boundary discussion from the phase-1 review. Explicitly out of scope for the current session; parked until campus-core stabilizes.
Summary
Consolidate the consumer-facing middleware layer — the campus hooks and decorators that downstream apps (campus-classroom, campus-admin, campus-profile) wire into their Flask apps — into campus_python, the caller-side SDK, so that app authors never install a campus service to get middleware.
Current state (why this is a boundary problem)
campus.audit.middleware(tracinginit_app,init_journeys, rate gate, identity stamping) lives inside the audit service package. All three downstream apps import it, so installing any of them drags the entire audit service (routes, web UI, psycopg2). Measured:import campus.auditpulls psycopg2 via the service init.- The middleware already speaks to services over the network only: it uses
AuditClient(HTTP) and campus_python. It has no legitimate in-process dependency on any service implementation — its placement is historical, same pattern as thecampus.common.httpmove (refactor(audit): move campus.common.http to campus.audit.http #976). - Auth-flavored consumer glue (login_manager, session cookie, token re-acquisition) already lives in
campus.flask_campus;campus.auth.middleware/is an empty stub. - The journeys feature is split across two packages:
@flask_campus.journeydecorator in flask_campus,init_journeysin audit.middleware, coordinating via the journey cookie.
Target
A single caller-side layer (campus_python) owning everything an app author touches:
- tracing init, journeys init +
@journey, rate gate, identity stamping (fromcampus.audit.middleware) - session/login glue and future auth decorators for downstream services (from
campus.flask_campus) AuditClient+ its HTTP client (fromcampus.audit.client/campus.audit.http) — auth/api are callers of audit; client code is caller-side by the same audience test
Protocol constants (journey cookie name, X-Journey-ID, EventLabel namespace, span tag keys) move to campus.common so consumer and service sides share vocabulary without importing each other.
campus.audit.middleware keeps a one-release deprecation re-export (init_app, init_journeys) while downstream app mains are flipped.
Open design decisions (settle when this starts)
- Repo placement: campus_python (separate repo, independent versioning — apps already pin it) vs an in-monorepo consumer package (flask_campus). The direction recorded here is campus_python; the tradeoff is cross-repo coordination for the moves below.
- Flask dependency: middleware requires flask; SDK would need a
[flask]extra (or a sibling distribution) so non-Flask SDK consumers (CLI, scripts) don't gain flask. HttpHeader/ webauth models: the harness and middleware importcampus.webauth.models(future campus-core); SDK-owned middleware needs these available without campus-suite — likely promote the header model into common or duplicate a minimal version SDK-side.- Sequencing vs phase 3: consumer consolidation changes what
campus-suitecontains; do it after campus-core is stable (per Carve out campus-core: phased separation of shared layers from service imports #970 ordering), possibly as its own phase rather than a rider on the distribution split.
Boundary rules once done (extends the #977 contract tests)
- The consumer layer never imports
campus.auth/campus.api/campus.audit/campus.yapper(currently flask_campus sits in the contract test's SERVICE_LAYERS set — it changes sides). - Services may import the consumer layer (they are Flask apps using a library); never the reverse.
Activity
nycomp commentedon Oct 28, 2026
Design update (2026-10-28, post phase-3 dists 1-2): the campus_python placement recorded here was superseded by the ratified 5-dist layout — this module set becomes the flask-campus distribution instead. See the revised phase-3 table in #970 and the dist-1/dist-2 completion comments there.
What changed:
flask-campusdist (in-monorepo first, Option B):flask_campus+audit.middleware+audit.client/audit.http, deps = campus-common + campus-model + requests + flask + campus_python. Apps install common + model + flask-glue and never core — which achieves this issue's goal ("app authors never install a campus service to get middleware") without splitting the SDK's identity.[flask]extra needed.campus.model.httpand ship in the campus-model v0.1.0 wheel, so the consumer layer gets them without campus-suite.Consumer-side evidence from the 2026-10-28 usage audit (ground truth for the move): downstream suite consumption is exactly
flask_campus(OAuthLoginManagerin classroom/admin/profile/nyxchange;@journeyin classroom) +audit.middleware's twoinit_*entry points (lazy in classroom/admin, eager in profile) +campus.deployin nyxchange only. sdk#124 dropped campus-suite from the SDK entirely. One new wrinkle this audit surfaced: deploy's home is undecided — the table assigns deploy to campus-suite, but nyxchange is a real consumer, so dist 4 either folds deploy in or nyxchange keeps campus-suite for that one module.Still open from this issue's design: protocol constants (journey cookie name,
X-Journey-ID,EventLabelnamespace) → campus.common — note campus-common's interface is now manifest-locked (v0.2.1), so that move is an additive patch + manifest regen. The boundary-rules section below remains valid as-is and becomes the flask-campus contract test seed. Issue stays open as the dist-4 work item.