Skip to content

Consolidate consumer-facing middleware into campus_python (caller-side SDK layer) #980

Open

Description

@nycomp

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 (tracing init_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.audit pulls 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 the campus.common.http move (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.journey decorator in flask_campus, init_journeys in 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 (from campus.audit.middleware)
  • session/login glue and future auth decorators for downstream services (from campus.flask_campus)
  • AuditClient + its HTTP client (from campus.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)

  1. 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.
  2. 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.
  3. HttpHeader / webauth models: the harness and middleware import campus.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.
  4. Sequencing vs phase 3: consumer consolidation changes what campus-suite contains; 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

  1. added
    improvementCode improvement that is not a bug - lint ratchets, refactors, internal quality
    on Oct 27, 2026
  2. nycomp commented on Oct 28, 2026

    @nycomp
    ContributorAuthor

    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:

    • Repo placement (open decision 1) — DECIDED, not campus_python. The consumer layer ships as the flask-campus dist (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 dependency (open decision 2) — MOOT. flask-campus is its own distribution with flask as a hard dependency; campus_python stays flask-free for CLI/script consumers, no [flask] extra needed.
    • HttpHeader / webauth models (open decision 3) — RESOLVED by refactor(model): remigrate webauth models into campus.model (phase 3 prep) #982: the header value classes were remigrated into campus.model.http and ship in the campus-model v0.1.0 wheel, so the consumer layer gets them without campus-suite.
    • Sequencing (open decision 4) — scheduled: flask-campus is dist 4 of 5 in Carve out campus-core: phased separation of shared layers from service imports #970's table (after campus-core, dist 3).

    Consumer-side evidence from the 2026-10-28 usage audit (ground truth for the move): downstream suite consumption is exactly flask_campus (OAuthLoginManager in classroom/admin/profile/nyxchange; @journey in classroom) + audit.middleware's two init_* entry points (lazy in classroom/admin, eager in profile) + campus.deploy in 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, EventLabel namespace) → 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    improvementCode improvement that is not a bug - lint ratchets, refactors, internal quality

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions