Journey at a glance
ActorsPlatform operatorTenant LMS administrator
Systems exercised
operator-lms.mdoperator-sso.mdoperator-tenants.mdAutomation2 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:*oradmin: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
InMemoryTenantLmsConnectorAdminStorehas no snapshot sink.
Steps#
1. Inspect boot validation#
- Open
/operator/lms. - The server component reads
GET /v1/admin/lms/connectorsand 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/403for 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 returns200 { valid: false, issues }; a valid descriptor returns200 { 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 isupdated; a managed row with the same{tenantId, connectorId}as an environment row isoverrode_env. - The refreshed roster labels the active row
managed overrideand 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/previewand renders create, update, enrollment, and conflict rows from the current roster diff. canApplyand 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/loginresolves the requested registered connector and creates OIDC state and nonce.- The launch route verifies the returned
id_tokenagainst the configured platform JWKS. An unreachable JWKS returnslti_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:
envormanaged. - 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.tsdrives 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.tsrenders 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#
../operator/operator-lms.md— primary roster, validation, managed connector, and OneRoster preview surface.../operator/operator-tenants.md— tenant context.../operator/operator-sso.md— adjacent identity configuration.
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
InMemoryTenantLmsConnectorAdminStorewith durable, tenant-scoped connector storage and audited mutations. - Define ownership and reconciliation for external LMS-side registration.
- Decide whether
/operator/lmsshould 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.