# Navvy knowledge graph

Navvy Classroom (`pearson-aq-school/navvy_classroom`) is a K-12 classroom assessment app. Teachers assign standards-based
checks (practice, competency, writing, Progress Checks, SmartSets) and seasonal screeners / progress-monitoring measures.
Students take them in the ADAM AD test player. Navvy scores the results, rosters from Launchpad, and reports at the
student, class, school and district level. It has a Go (gin + bun) API, a Vue 3 SPA and a single Postgres database.

## Start here
1. [[navvy-server]]: process bootstrap, middleware, DB pools, and how routes are registered.
2. [[authenticate-request]]: how a request becomes a user with permissions. Read it alongside [permissions.md](permissions.md).
3. [[navvy-web]]: the SPA, with its full route table and the map from API modules to backend `*-api` nodes.
4. [[assessment]], [[testing-assignment]], [[testing-session]], [[measure-score]]: the core data path.
5. [[client-org-configuration]] + [[client-org-configuration-types]]: per client/district/school feature switches. Nearly every feature checks them.
6. [[adam-ad]]: the external delivery engine every check depends on.
7. [[orgs-service]] / [[org]] / [[class]] / [[user]]: tenancy and access scoping.

## Main flows
- [[sign-in]] → [[authenticate-request]]
- [[sync-roster]] (Launchpad OneRoster, manual or nightly)
- [[assign-check]] → [[take-check]] → [[score-testing-session]] → [[compute-lexile-quantile]] / [[score-writing-with-cf]]
- [[facilitated-assessment]], [[launch-smart-set]], [[set-progress-monitoring-goal]]
- [[archive-attempt]], [[force-submit-attempts]], [[resync-testing-sessions]]
- [[edit-client-settings]], [[map-courses-to-standard-set]], [[import-academic-benchmarks-standards]]
- Batch jobs: [[refresh-dsp-reporting]], [[sync-adam-items]]

Product features are `capability` nodes, for example [[smartsets]], [[seasonal-screeners]] and [[progress-checks]].
Overloaded terms are `concept` nodes, for example [[window]], [[session]], [[attempt]], [[form-meanings]] and [[archive]]. Check them before trusting a name.

## Module layout
`cmd/navvy-classroom` is the API server. The other `cmd/*` binaries are CLIs and cron jobs: [[ops-tools]], [[navvy-cli]],
[[sync-launchpad]], [[refresh-materialized-reporting]], [[adam-item-sync]] and [[update-item-counts]]. Each binary wires its
dependencies with google/wire (`cmd/*/injector.go` → `wire_gen.go`).

The backend is layered and flat, not split into packages per domain:
- `internal/navvy_api/<thing>.go` holds the gin handlers. Each `*API` has a `RegisterAll` that lists its routes.
- `internal/services/<thing>.go` holds the business logic, mostly built on `GenericCRUD[T]`. Raw SQL files live in `internal/services/sql/` (embedded).
- `internal/models` holds the bun models.
- `internal/facades` holds the external clients: ADAM AD, Launchpad OneRoster, CF, MetaMetrics, PRIZM, Academic Benchmarks.

The graph's backend modules (`*-service`) are domain slices across those layers, and each one exposes a matching `*-api` interface node.

The SPA lives in `web/navvy`: Vite, Vue 3, PrimeVue, Pinia and a hash router. It has one router file
(`src/router/index.js`), one axios client module per API group in `src/api`, and its UI nodes are `*-ui`.

## Conventions an implementer must follow
- **Migrations:** run `make migrate-create FILE=<name>`. Use golang-migrate `YYYYMMDDHHMMSS_name.up.sql`/`.down.sql` in
  `db/migrations` and wrap each in `BEGIN; … COMMIT;`. CI (`.github/workflows/check-migration-timestamp-sequence.yml`)
  rejects out-of-order timestamps. Apply with `make migrate-up`; `cmd/migrate` runs in deploys. Don't edit
  `00000000000001_initial.up.sql` (a pg_dump).
- **New permission:** add the constant to `internal/api_utils/permissions/permissions.go`. Insert the `permissions` row
  plus any `permission_grants` in a migration. Mirror it in `web/navvy/src/common/permissions.js`. Add a row to
  [permissions.md](permissions.md).
- **New route:** add it in the API's `RegisterAll`, and **always pass `authenticationRequired`**. There is no global auth
  middleware: a route without it is public (this already happened to `POST /api/measure_scores/query`). Add
  `requirePermission*` or an `AuthorizationService` scope check. Many existing write routes are login-only (see the
  Gotchas on each `*-api` node), so don't copy that pattern.
- **Swagger:** handlers carry swag annotations. Run `make swagger`; the pre-commit hook checks it.
- **Feature switches:** for per-tenant behaviour, add a `client_org_configurations` type
  ([[client-org-configuration-types]]) and resolve it through the School > District > Client resolver. Don't add a client column.
- **Time:** use `config.CurrentUTCTime()` / `SetCurrentTimeFunc` rather than `time.Now()`. `NAVVY_SYSTEM_TIME` and
  `active_enrollment_check_date` ([[as-of-date]]) change what "now" means.
- **Tests:**
  - Go: `make test`. It excludes packages matching `api`, so `internal/navvy_api` tests do **not** run there; run
    `go test ./internal/navvy_api/...` yourself. Service integration tests live in `internal/service_tests`. Mocks come from `make mocks` (mockery → `internal/mocks`).
  - Web: `npm run test` (jest), `npm run lint`, and Cypress component specs (`*.cy.js`) via `make npx-cypress-run`.
- **Local env:** copy `dotenv.example` to `.env`. Note that `.env` overrides real env vars (`godotenv.Overload`) and `ENV` defaults to `production`.

## Known risks worth knowing before you build
- **Self-granting permissions:** any logged-in user can write `permission_grants`. See [[users-api]].
- **Unauthenticated score endpoint:** `POST /api/measure_scores/query` requires no login. See [[scoring-api]].
- **ADAM token leak:** `GET /api/assessments/ad_auth_token` returns the ADAM token to any logged-in user. See [[assessments-api]].
- **Missing access checks:** several routes skip access checks entirely:
  - assign routes ([[assign-api]])
  - SmartSet config update ([[smart-sets-api]])
  - testing-session sync/search ([[testing-sessions-api]])
  - catalog writes ([[assessments-api]], [[standards-api]])
- **Unsigned AD callback:** the score-complete callback has no signature check, and it is the only push-based scoring path ([[ad-callback-api]]).
