V1 Web PWA · Journey

Journey: LMS connectors — operator visibility and validation

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 2 specs verdict: pass· 2026-07-02
9sections4 minread

On this page
Journey at a glance
ActorsPlatform operatorTenant LMS administrator
Automation2 E2E spec(s) · verdict: pass· 2026-07-02

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Inspect boot validation"] s2["2. Dry-run a descriptor"] s3["3. Create or replace a managed connector"] s4["4. Delete and restore"] s5["5. Preview a OneRoster import"] s6["6. Exercise the tenant launch boundary"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 click s1 href "#1-inspect-boot-validation" click s2 href "#2-dry-run-a-descriptor" click s3 href "#3-create-or-replace-a-managed-connector" click s4 href "#4-delete-and-restore" click s5 href "#5-preview-a-oneroster-import" click s6 href "#6-exercise-the-tenant-launch-boundary" s6 --> v(["verdict: pass"]) click v href "../results/lms-connectors-operator.html" style v stroke:#3fb950,color:#3fb950

An operator can inspect the live LMS connector roster, see every rejected environment descriptor, dry-run a candidate, and create or remove an in-process managed override. The same page can preview a OneRoster import without applying it. LTI launch then proves the important runtime boundary: an unavailable JWKS fails closed instead of accepting an unverifiable token.

Personas#

  • Platform operator — has admin:* or admin:workspace:tenant-console.
  • Tenant LMS administrator — supplies issuer, client, deployment, endpoint, and scope values from the tenant's Canvas, Moodle, or other LMS registration.

Pre-conditions#

  • The operator has an authenticated admin session.
  • The external LMS tool registration already exists; Oshun does not create that third-party registration from /operator/lms.
  • Environment-seeded connectors, when used, arrive through OSHUN_LMS_CONNECTORS.
  • Managed connector writes are understood to be runtime-local. The current InMemoryTenantLmsConnectorAdminStore has no snapshot sink.

Steps#

1. Inspect boot validation#

  • Open /operator/lms.
  • The server component reads GET /v1/admin/lms/connectors and renders each valid connector with tenant, connector id, provider, source, status, environment, capabilities, and secret-free LTI summary.
  • Invalid environment descriptors appear before the roster with their exact code, path, severity, and message. Malformed JSON appears as connectors_json_invalid; invalid entries are not registered.
  • With no readable roster, the page fails soft to its empty state. The BFF itself still returns 401/403 for callers without the required scope.

2. Dry-run a descriptor#

  • Paste a candidate descriptor into the validation form.
  • The browser posts to POST /v1/admin/lms/connectors/validate.
  • Missing identity fields return 400 invalid_request. A structurally accepted but invalid descriptor returns 200 { valid: false, issues }; a valid descriptor returns 200 { valid: true, issues: [] }.
  • This route does not register or persist the candidate.

3. Create or replace a managed connector#

  • The managed connector panel parses the descriptor and posts it to POST /v1/admin/lms/connectors.
  • The BFF validates before mutating the runtime. An invalid descriptor returns 422 invalid_lms_connector.
  • A new key is created; a repeated managed key is updated; a managed row with the same {tenantId, connectorId} as an environment row is overrode_env.
  • The refreshed roster labels the active row managed override and never returns connector secrets.

4. Delete and restore#

  • Delete through DELETE /v1/admin/lms/connectors/:tenantId/:connectorId.
  • Deleting a managed override restores the matching environment descriptor when one exists.
  • Deleting an environment-only connector creates an in-memory tombstone and unregisters it for the rest of the current process. A restart or reconfiguration can restore that environment descriptor.
  • An unknown key returns 404 lms_connector_not_found.

5. Preview a OneRoster import#

  • Paste { tenantId, incomingUsers, incomingEnrollments } into the OneRoster preview form.
  • The browser posts to POST /v1/admin/lms/oneroster/preview and renders create, update, enrollment, and conflict rows from the current roster diff.
  • canApply and the projected before/after counts describe what a separate apply request could do. This page exposes no apply control and the preview itself does not mutate the roster.

6. Exercise the tenant launch boundary#

  • POST /v1/tenant/lms/lti/login resolves the requested registered connector and creates OIDC state and nonce.
  • The launch route verifies the returned id_token against the configured platform JWKS. An unreachable JWKS returns lti_jwks_unavailable; no unverified identity is accepted.
  • SCORM routes are available only when the registered descriptor enables the relevant SCORM capability.

Post-conditions#

  • Every active connector has one visible source: env or managed.
  • Invalid boot descriptors remain operator-visible but absent from the launch registry.
  • A successful dry run has made no runtime mutation.
  • A OneRoster preview has made no roster mutation.
  • Managed overrides and environment tombstones survive only for the current BFF process.
  • The covered launch attempt fails closed when its configured JWKS cannot be fetched.

Failure modes#

  • Malformed environment JSON — no connector registers; the invalid panel surfaces connectors_json_invalid.
  • Invalid descriptor — validation returns ordered issues and managed create performs no mutation.
  • Missing or wrong admin scope — read, validate, create, delete, and OneRoster preview return 401/403.
  • Unavailable BFF from the server component — the page shows the honest empty state; this must not be mistaken for a proven zero-connector roster.
  • Delete an environment connector — the runtime tombstone is not durable across restart.
  • OneRoster conflicts — preview reports canApply: false; this surface does not offer a bypass.
  • JWKS outage — LTI launch fails closed.

E2E coverage#

  • apps/oshun/web/e2e/lms-connectors.spec.ts drives the real BFF list, validation, managed create/update/delete, environment override/restore, environment tombstone, secret-free read-back, auth gates, and LTI login-to-launch JWKS failure.
  • apps/oshun/web/e2e/operator-lms.spec.ts renders the operator page from the real seeded BFF roster, asserts the invalid descriptor evidence, exercises malformed/invalid/valid validation, creates and removes a managed connector, and previews both clean and conflicting OneRoster payloads without applying them.
  • Unit coverage in lms-route.test.ts, the operator page tests, and the two connector form suites pins the store and component branches.

Per-view files touched#

Cross-references#

  • V1_DEPLOYMENT_REQUIREMENTS.md §3 — environment descriptor examples.
  • libs/shared/inbound-integrations/src/lms.ts — descriptor creation, validation, LTI, and SCORM contracts.
  • apps/oshun/bff/src/tenant-console/lms-route.ts — connector runtime and admin routes.
  • apps/oshun/bff/src/routes/admin-oneroster-apply.ts — preview and the separate apply boundary.

Open questions#

  • Replace InMemoryTenantLmsConnectorAdminStore with durable, tenant-scoped connector storage and audited mutations.
  • Define ownership and reconciliation for external LMS-side registration.
  • Decide whether /operator/lms should expose the already-shipped OneRoster apply route, including commit id and conflict confirmation.
  • Add a live third-party LMS conformance environment; local coverage proves fail-closed JWKS behavior, not successful provider interoperability.