# LaunchPad (`lp`) knowledge graph

LaunchPad is Pearson's OneRoster 1.2 rostering and identity-federation platform for K-12. It **ingests** roster data from districts (SIS APIs, CSV packages, single-file uploads), stores it per tenant ("client"/account) in a `school_year`-partitioned Postgres, **federates sign-in** (SAML, OIDC, CAS, Clever, ClassLink, Google, local passwords, QR badges) and **distributes** roster data to downstream apps (ADAM, Navvy, Propel, Adobe Learning Manager) via an external OneRoster API, SSO launches and WAL-driven change notifications.

Repo: `launchpad` monorepo (branch `dev`). Graph built from scratch on 2026-10-13; `kg.config.json` holds the stamped commit.

## Start here

1. [[client]] — the tenant; everything is scoped by `client_id`. Accounts form a parent/child tree.
2. [[user]], [[org]], [[role]], [[enrollment]] — the OneRoster core; see [[lpi]] (identifier format) and [[school-year-partition]].
3. [[integration-config]] + [[integration-engine]] — how roster data gets in ([[integration-source-type]], [[hash-upsert]]).
4. [[app-config]] / [[app-subscription]] — downstream apps, their licenses, data filters and notification endpoints.
5. [[authentication-provider]] + [[auth-federation]] — every sign-in path.
6. [[permission-model]] and [permissions.md](permissions.md) — who can do what (admin users vs rostered users vs OAuth apps).
7. [[launchpad-internal-api]], [[internal-roster-api]], [[oneroster-external-api]] — the HTTP surfaces.
8. [[config-client]] — the Vue 3 SPA (admin console `admin-*-ui`, end-user pages `lp-*-ui`).

## Main flows

- Roster in: [[run-scheduled-integration]], [[pull-roster-from-api-source]], [[upload-roster-file]], [[edit-roster-from-console]], [[roll-over-school-year]], [[transfer-student]].
- Roster out: [[app-pulls-roster-via-oneroster-api]], [[notify-apps-of-roster-change]], [[export-roster-entities]].
- Identity: [[end-user-sign-in]], [[sso-into-app]], [[badge-login]], [[admin-console-login]], [[send-welcome-email]], [[adobe-lms-sso-provisioning]].
- Accounts: [[create-client-account]], [[delete-client-account]].
- Ops: [[deploy-launchpad]].

## Module layout

`backend/` is one Go module (`github.com/MZDevinc/launchpad/backend`) producing three binaries: the API ([[backend-server]], `main.go`; `APP_TYPE` — see [[app-type]] — selects API-only, scheduler, or DEV all-in-one), the integration worker (`cmd/worker`, [[integration-scheduler]]) and the WAL notifier (`cmd/notifications`, [[notifications-service]]), plus an AWS Lambda for async exports ([[export-entities-lambda]]). Inside `backend/src`: `api/launchpad` (internal admin API), `api/apicommons` (auth middleware, permissions, roster CRUD handlers), `api/external` (OneRoster 1.2 external API), `api/auth` (SAML/OIDC/CAS), `services` ([[roster-services]]: query layer, [[filter-expression]]), `integrations/{commons,consumers,validations,sources/*}` (engine, [[data-validation]], one adapter per source type), `models` (Bun structs; `models/or12` = [[or12-validation]]), `async`/`distributed` (SQL queue + locks), `changehistory`, `metrics`, `notifications`. `db/migrations` holds golang-migrate SQL ([[db-migrations]]); `config-client/` is the SPA; `infrastructure/` is Terraform ([[infrastructure]]); `.github/` is CI/CD ([[ci-cd]]); `test-automation/` is the Playwright/Gherkin e2e suite ([[test-automation]]).

## Conventions an implementer must follow

- **Tenancy:** every roster query filters by `client_id` *and* `school_year`. Roster tables are LIST-partitioned by `school_year` with composite `(id, school_year)` keys; a new school year needs a migration adding partitions (with `REPLICA IDENTITY` for the WAL feed) — no job does it.
- **Identifiers:** roster rows are keyed by `lpi` (`lpi:env:client_code:type:sourcedId`); writes are upserts on `(lpi, school_year)` guarded by a content hash. Deletion is soft: status `tobedeleted`.
- **Writing roster data:** go through the integration consumers (`consumers.NewIntegrationConsumer`), even for console edits (with `NoopIntegrationStats`), so validation, hashing and the WAL-driven notifications stay consistent. A compound UI action that needs one transaction passes it as `IntegrationConfig.RosterDB` (read through `RosterDatabase()`); never swap the global DB connection. Student transfers update only the user and role columns they own (`UserUpdateColumns` / `RoleUpdateColumns`), and approval goes through `ApproveLegacyTransferRequests` ([[transfer-student]]).
- **Migrations:** add an `up`/`down` pair in `db/migrations/` (`YYYYMMDDHHMMSS_name.{up,down}.sql`, wrap in `BEGIN/COMMIT`); `cd db && make rebuild && make run_fixtures` locally. Deploys run them as the `launchpad-db-migration` ECS task on every terraform apply.
- **Routes:** internal routes are registered in `backend/src/api/launchpad/internal_api.go` under `/api/internal/clients/:clientID/...` with an explicit permission middleware from `apicommons` (`*Required`); external OneRoster routes in `backend/src/api/external/external_api.go`. Add Swagger comments, run `make swagger`, then `npm run generate` in `config-client` to refresh the TS clients.
- **Permissions:** add a key to `backend.UserPermissions`, a `CanXxx` method in `apicommons/permissions.go`, a default in `DefaultRolePermissions`, a migration seeding existing `role_permissions` rows, and the UI check in `config-client/src/commons/permissions.ts` + router `meta.permissions`. Update [permissions.md](permissions.md).
- **Logging:** structured zerolog via `utils.LogError` etc., never `fmt.Print*`.
- **Tests:** backend `cd backend && make test` (unit) / `make integration_test`; frontend `cd config-client && npm run test:unit`; e2e `cd shared && make test_e2e` (or `make test_e2e_smoke`; not run in CI). Lint: `make lint` (backend), `npm run lint` (frontend). `cd shared && make check` is the shared quality gate.
- **Branches/deploys:** branch `LPPE-<jira>-<desc>` off `dev`. Push to `dev` deploys smk then dev; push to `prd` deploys uat + pen-test; production is a manual approved workflow on `prd` (not `main`).

## Known hazards (verify before relying on them)

The module pass reported several security issues worth a human look — they are documented on the nodes named:
- Committed signing material: a dev SAML private key used as a fallback and baked into images, and a committed `JWT_SIGNINGKEY` ([[saml-keys]], [[jwt-settings]]).
- An `?app_id=` principal swap in `ClientIDAuthRequired` ([[api-commons]]).
- Raw-SQL filter operators and a raw `status` param ([[filter-expression]], [[internal-clients-api]]).
- Cross-tenant updates by primary key on several admin routes ([[launchpad-internal-api]]).
- Credentialed CORS on `/auth/*` and a tenancy-blind global SAML IdP ([[sso-saml-idp]]).
- Badge minting by any rostered user ([[badge-login]]).
- A notifier that crash-loops on one bad subscriber ([[notifications-service]]).

Dead code is marked `status: dead` on its node, for example [[measures]], [[radar-dashboard]], [[change-event]], [[oauth-user]], [[postmark]] and [[splunk]].
