# Business rules — Click Foundation - LTI Integration Platform

Evidence date: 2026-10-19. Source: `master` at `6731ace`.

No business-rules document exists in the source repository. Rules below are only those the code or config enforces, or those a comment states. Each item is marked **Implemented**, **Inferred**, or **Unknown / Requires Confirmation**.

Inferred means the rule is a consequence of the code plus a locked library or Symfony's normal container rules. The application was not booted and `/default` was not called.

## Who may launch

**BR-001.** `/default` has no authentication requirement in `config/packages/security.yaml`. The `@Route` annotation does not restrict the HTTP method. The `main` firewall names the empty `users_in_memory` provider and no authenticator. Access-control entries are comments. Anyone who can reach the host can request the launch.

Provenance: **Implemented**.

**BR-002.** There is no role check, learner lookup, or course enrolment check before a launch. The action accepts no input that could identify a learner.

Provenance: **Implemented**.

## What identity is sent

**BR-003.** The login hint is the literal `loginHint` on every request.

Provenance: **Implemented**.

**BR-004.** The user authenticator used by `/default` is `new UserAuthenticator(true, true)`. `authenticate` returns `UserAuthenticationResult` with success `true` and user identity `null`. The class comment says `TESTING ONLY - USE ANONYMOUS AUTHENTICATION ONLY!` The call-site comment says the second parameter needs to be false. The second parameter is `$withAnonymous`, and the call passes `true`.

The result reports `isAnonymous()` true because the identity is null (`UserAuthenticationResult`). The locked `OidcAuthenticator` (core 6.7.1) copies user claims only when the result is not anonymous. This launch therefore does not add a user id, name, or email.

Provenance: **Implemented** for the classes in this repo. The claim-copy step is **Implemented** in the locked library.

**BR-005.** The live launch does not pass an IMS role. The learner role URI `http://purl.imsglobal.org/vocab/lis/v2/membership#Learner` appears in unreachable code after `return` and in a comment. It is not a rule of the live request.

Provenance: **Implemented** as unused source.

## Registration and resource identity

**BR-006.** The only registration the action loads is the identifier `local`. Config allows one client id, `common_client_id_value`, one deployment id, `deploymentId1`, one platform, and one tool.

Provenance: **Implemented**.

**BR-007.** Every live launch uses resource-link identifier `resourceLinkIdentifier`, title `LTI Resource Link`, and URL `http://127.0.0.1:8001/tool/launch`. A comment says the identifier needs to be unique. The code does not generate a new identifier per launch or per resource.

Provenance: **Implemented**. The comment is not enforced.

**BR-008.** The action does not pass a deployment id. It relies on whatever the library method does when that argument is omitted. The method signature's default was not quoted here because the fetched library body did not include the parameter list. The registration's only configured deployment id is `deploymentId1`.

Provenance: the omission is **Implemented**. The library default is **Unknown** in this document until the signature is cited from a local vendor checkout.

**BR-009.** When the resource link has a URL, locked core 6.7.1 uses that URL and does not fall back to the tool `launch_url`. The yaml `launch_url` (`http://127.0.0.1:8001/api/v1/auth/launch-lti-1p3/`) is therefore not the target of `/default`.

Provenance: **Implemented** in the controller and in the locked builder.

## Keys and client credentials

**BR-010.** Platform and tool key chains are the same PEM files, with a null passphrase. The platform key set name is `platformSet`. The tool key set name is `toolSet`. The tool JWKS URL path ends in `deliver.json`, which does not match the tool key set name.

Provenance: **Implemented**.

**BR-011.** `LTI1P3_SERVICE_ENCRYPTION_KEY` is set in the committed `.env`. The bundle's `AuthorizationServerFactory` receives that environment variable. The value is a secret and is not repeated in this document.

Provenance: **Implemented** that a value is committed. Whether it is a real secret or a local placeholder is **Requires Confirmation**. Treat the committed value as exposed.

**BR-012.** `APP_SECRET` is set in the committed `.env`. The file's own comment says not to define production secrets there. `APP_ENV` is `dev`.

Provenance: **Implemented**.

## Protocol endpoints

**BR-013.** JWKS, OIDC authentication, OIDC initiation, and the OAuth2 token route are the bundle routes imported by this app. No application firewall restricts them beyond `security.yaml` as described in BR-001. The bundle registers LTI security listener factories and this `security.yaml` does not enable those factories on a firewall.

Provenance: **Implemented**.

**BR-014.** OIDC initiation, as implemented by locked `OidcInitiator`, refuses a request when no registration matches the platform issuer, and refuses it when the registration has no tool key chain. If a deployment id is present on the request and the registration does not have that id, initiation throws. `/default` does not catch these exceptions.

Provenance: **Implemented** in locked core 6.7.1.

**BR-015.** OIDC authentication, as implemented by locked `OidcAuthenticator`, rejects an invalid message hint, an unknown registration id inside that hint, and a user-authenticator result that is not successful. The authenticator used by `/default` returns success.

Provenance: **Implemented** in locked core 6.7.1 and in `UserAuthenticator`.

## What is not a rule here

- No grade passback, membership, or proctoring rule exists in this repository.
- No retention, audit, or consent rule is written in code or docs.
- No multi-tenant or per-organisation split is configured. One registration, fixed strings.
- The commented Ignite URL and the `devkit-lti1p3-master-tool` host are not rules of the running action.

Whether Click Foundation intended anonymous launches, a fixed resource id, and open access to `/default` is **Requires Confirmation**. The code does those things. No document in the repository approves them.
