# 03: Streamed Output And Artifacts

## Outcome

Materialize one buffered HTML update, write one task-scoped public artifact,
disconnect and reconnect to the durable event stream, and download the same
artifact bytes.

## Prerequisites

- Complete Tutorial 01 and understand verified build binding.
- Permission to create Cookbooks, Apps, and Development Images and to run the
  resulting App.
- About 25 minutes. This lesson uses only storage managed by your LEMC host.

## Files

- `Dockerfile` creates the recipe image and installs `run.sh` as its entry
  point.
- `run.sh` writes one file under `/lemc/public` and emits four ordered events.
- `cookbook.yaml` owns one recipe and one `now` step.

The script demonstrates two durable channels. Job events carry rendered output
and literal status text. The public task directory carries downloadable bytes.

| Script action | Durable result | What the client shows |
| --- | --- | --- |
| Write `/lemc/public/tutorial-03-report.txt` | One task-scoped public artifact | Nothing is rendered until the artifact inventory is synchronized. |
| `lemc.html.trunc` | Start a new rendered document | The prior rendered document is replaced by the heading. |
| `lemc.html.buffer` | Hold an incomplete HTML fragment | The partial paragraph stays hidden. |
| `lemc.html.append` | Complete and materialize the buffered fragment | One complete paragraph becomes visible. |
| `lemc.output` | Add literal task output | The artifact-ready marker appears in logs and replay. |

The event stream and artifact object are separate records. Replaying events
does not download the artifact. Downloading the artifact does not replace the
event stream.

## Web

1. Review `run.sh`. Confirm that its only durable file path is
   `/lemc/public/tutorial-03-report.txt` and that it writes no protected data.
2. Build `tutorial-stream-artifact` from `Dockerfile` and `run.sh`. Keep the
   script executable. Wait for the Builder execution to reach terminal
   success and record the verified build identity.
3. Create **Tutorial 03 Streamed Output And Artifacts** from `cookbook.yaml`.
   Bind the verified build to page 1, recipe `create-durable-report`, phase
   `now`, step 1. Confirm that the server copy uses its managed digest.
4. Publish the Cookbook and create a private test App. Open **Create Durable
   Report**, start one action, and record both the transition ID and task ID.
5. Watch the rendered output. The heading can appear first. The text
   `first half` must not appear as a visible incomplete paragraph during the
   one-second buffer interval. The full paragraph appears only after
   `html.append` arrives.
6. Reload or close the page after the job starts. Return through the App's job
   history and select the exact recorded task ID. Confirm that durable replay
   restores the heading, the complete paragraph, and the literal artifact
   marker in the original order.
7. Confirm that the task is terminal before you use its artifact inventory.
   Open **Artifacts**, locate the exact path `tutorial-03-report.txt`, and
   record its size and task identity.
8. Download the file. Confirm that it is 29 bytes, includes its final newline,
   and has SHA-256
   `ba33247cbcfb7388e63d19076726c49c35a77de7008afba9475c8073386750bb`.
9. Return to another task and confirm that its artifact list does not inherit
   the Tutorial 03 file. Artifact identity is scoped to the recorded task.

## LEMCSSH

After the standard Builder and binding flow, inspect the action surface before
submission. Then run and reconnect by exact task identity. Replace
`EVENT_CURSOR` only with the last event ID returned before a disconnect. A
cursor resumes after that durable event; it is not a byte offset or a task ID.

```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 create-durable-report \
  --idempotency-key REQUEST_UUID --follow --render plain

ssh LEMC_ALIAS jobs follow --task-id TASK_ID \
  --cursor EVENT_CURSOR --render plain

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

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

ssh LEMC_ALIAS jobs artifacts download app --uuid APP_UUID --page 1 \
  --scope individual --task-id TASK_ID \
  --path tutorial-03-report.txt > tutorial-03-report.txt

wc -c tutorial-03-report.txt
sha256sum tutorial-03-report.txt
```

The final two commands run on your local system. Shell redirection writes the
download to the local working directory; it does not create a file on the SSH
host. On macOS, use `shasum -a 256 tutorial-03-report.txt` for the checksum. If
you lost the cursor, omit `--cursor` and replay the exact task from its first
retained event. Do not substitute a cursor from another task.

## Expected result

The job reaches a successful terminal state. Keep this evidence together:

| Evidence | Expected value |
| --- | --- |
| Task coordinate | Page 1, recipe `create-durable-report`, phase `now`, step 1 |
| Rendered document | `Streamed report` and one complete `first half and second half.` paragraph |
| Literal output | `artifact ready: tutorial-03-report.txt` |
| Artifact path | `tutorial-03-report.txt` on the same task ID |
| Artifact size | 29 bytes |
| Artifact SHA-256 | `ba33247cbcfb7388e63d19076726c49c35a77de7008afba9475c8073386750bb` |

The downloaded file contains:

```text
tutorial-03 durable artifact
```

## Cleanup and retry

Confirm the task and transition are terminal. Remove the test App and archive
the Cookbook and Development Image. The durable task record and artifact remain
subject to the host's configured retention policy. Do not invent a local
server-side delete path or remove Runner scratch to hide the record.

A reconnect uses the same task ID and last durable cursor. It does not start a
new job. A new recipe action needs a new idempotency key, gets a new task ID,
and creates a separate artifact object with the same relative path. Delete the
local downloaded copy when you no longer need it.

## Security and cost

Anything written to `/lemc/public` becomes durable object-storage data for
authorized artifact readers after verified synchronization. Never write a
secret, credential, private key, or sensitive intermediate file there. Keep
temporary private state under the task's private contract instead. Buffering
changes presentation, not authorization, retention, or event durability. This
lesson has no external service cost but uses bounded host storage until
retention removes it.

## Troubleshooting

- A visible `first half` without the second half means the client did not apply
  the buffer contract. Reload and inspect the exact task replay.
- `artifact not found` means you selected another task or path. List artifacts
  with the same task ID before download.
- A size or checksum mismatch means the downloaded bytes are not the lesson
  artifact. Stop and compare the task ID, path, terminal state, and artifact
  metadata before another download.
- A cursor error means the cursor does not belong to that task or is malformed.
  Start a selector-free replay for that exact task and use its returned IDs.
- A nonterminal state after disconnect does not mean the server stopped. Read
  exact task status; server work continues without the SSH connection.
- A terminal task without an artifact can mean synchronization failed after
  recipe execution. Preserve the task status and synchronization evidence. Do
  not treat the literal output marker as proof that object storage has the
  file.

## Internal validation

Maintainers can run `./scripts/validate.sh`. It executes the local script in a
temporary mount during focused validation; it does not submit a hosted job.
