# 02: Forms And Environment

## Outcome

Submit typed form data, read its mapped public environment values in a recipe,
and see LEMC reject a reserved form variable before a job starts.

## Prerequisites

- Complete Tutorial 01 or know how to create, compile, and bind a Development
  Image through Web or LEMCSSH.
- Permission to create Cookbooks, Apps, and Development Images.
- About 20 minutes. This lesson uses no secret and no external service.

## Files

- `Dockerfile` creates the small recipe image and installs `run.sh` as its
  entry point.
- `run.sh` reads three environment variables, validates the two user inputs,
  and emits one bounded output event.
- `cookbook.yaml` declares one public value, one text field, one select field,
  and one `now` step.

Read the three value sources before you build:

| Value | Owner | Recipe receives | Important rule |
| --- | --- | --- | --- |
| `TUTORIAL_MODE` | `environment.public` | `guided` | It is fixed Cookbook data, not a form control. |
| `DISPLAY_NAME` | Text form field | The submitted text | It must use letters, numbers, dot, underscore, or dash. |
| `DEPLOYMENT_TIER` | Select form field | `development`, `staging`, or `production` | The submitted value is the option value, not its visible label. |

LEMC maps accepted form variables to environment keys with the same uppercase
names. The recipe does not parse a request body. The server validates the
action and starts the image with the accepted environment.

## Web

1. Open `cookbook.yaml` beside `run.sh`. Confirm that the YAML contains no
   `environment.secrets` entry and that every variable read by the script has
   one declared source.
2. Create `tutorial-forms-environment` in **Builder > Development Images**.
   Save `Dockerfile` and `run.sh` as the complete source. Keep `run.sh`
   executable.
3. Compile and test the exact saved source revision. Wait for the Builder job
   to reach terminal success. Record the verified build identity.
4. Create **Tutorial 02 Forms And Environment** in **Cookbooks**. Set its
   individual YAML to `cookbook.yaml` and validate it. Confirm that the server
   shows one page, one recipe, and one `now` step.
5. Bind the verified build to page 1, recipe `show-form-values`, phase `now`,
   step 1. Confirm that the server copy uses a managed `@sha256:` image
   reference instead of `tutorial-forms-environment:dev`.
6. Publish the Cookbook and create a private test App from it.
7. Open **Show Form Values**. Confirm that **Display name** defaults to
   `learner`. Confirm that **Deployment tier** shows **Development** while its
   submitted value is `development`. `TUTORIAL_MODE` must not appear as a form
   control.
8. Submit `learner` and **Development**. Record the returned transition and
   task identities. Wait for the task to reach a successful terminal state.
9. Open the exact task output. Match all three reported values to the form and
   Cookbook data.
10. Start a new action with `learner_2` and **Staging**. Use the new task
    identity and confirm that the first task remains unchanged in history.
11. In the Cookbook editor, temporarily change `DISPLAY_NAME` to
    `LEMC_DISPLAY_NAME` and validate without saving. Confirm that validation
    rejects the server-owned `LEMC_` prefix. Discard the edit and confirm that
    no transition or task was created for this rejected declaration.

The form is public input. Do not put a password, token, or private value in it.

## LEMCSSH

Use the Tutorial 01 Builder, Cookbook YAML, build-binding, and App commands with
this directory's files and names. First inspect the server-owned action
surface. Confirm that it returns `show-form-values` and the two declared input
names. Then submit both values:

```sh
ssh LEMC_ALIAS jobs surface app --uuid APP_UUID \
  --scope individual --format=json

ssh LEMC_ALIAS jobs run app --uuid APP_UUID --page 1 \
  --scope individual --recipe show-form-values \
  --set DISPLAY_NAME=learner --set DEPLOYMENT_TIER=development \
  --idempotency-key REQUEST_UUID --follow --render plain

ssh LEMC_ALIAS jobs status app --uuid APP_UUID --page 1 \
  --scope individual --task-id TASK_ID --format=json
```

Use the task ID returned by the accepted action. Do not guess it from another
run. The command inventory, flags, and value bounds come from the reviewed
`lemc.remote-command/v1` manifest.

## Expected result

The first job reaches a successful terminal state and includes exactly one
lesson marker:

```text
form accepted: name=learner tier=development mode=guided
```

The second run has a different task identity and reports:

```text
form accepted: name=learner_2 tier=staging mode=guided
```

The task status identifies page 1, recipe `show-form-values`, phase `now`, and
step 1. The reserved-name edit fails YAML validation. It creates no transition,
task, guest, or output event.

## Cleanup and retry

Before cleanup, confirm that both submitted tasks are terminal and that no
action is still active. Remove the test App, archive the lesson Cookbook, and
archive its Development Image.

If a job rejects a value, correct the input and start a new action with a new
idempotency key. Reuse an idempotency key only when the original request had an
unknown transport result and the payload is byte-for-byte the same. A replayed
response refers to the first accepted action; it is not a new task.

## Security and cost

Form values and `environment.public` are not secrets. Authorized task readers,
recipe code, status projections, and output can observe them. A value passed
with `--set` can also remain in local shell history. Use the User Secret path
from Tutorial 05 for protected values. The script allowlists characters before
it emits the display name. This lesson has no external cost.

## Troubleshooting

- `reserved` or `LEMC_` means the declaration conflicts with a server-owned
  environment key. Choose an author-owned name.
- `DEPLOYMENT_TIER must be one of` means the submitted value did not match a
  declared option. Submit `development`, `staging`, or `production`.
- An image preflight failure means the Cookbook step is not bound to the
  verified managed build.
- `TUTORIAL_MODE=unset` means the Cookbook public environment was absent from
  the accepted server copy. Restore `TUTORIAL_MODE=guided` and validate the
  Cookbook before another run.
- A successful task with unexpected values means you opened another task or
  App revision. Compare its App UUID, page, recipe, task ID, and Cookbook
  revision with the accepted action.
- A terminal exit code 64 is input rejection by `run.sh`; it is not a retry of
  the same valid job.

## Internal validation

Maintainers can run `./scripts/validate.sh` from the examples root. The gate
checks form keys and source structure without contacting a live service.
