# label-lab

An original teaching project. All data is fictional. Python standard library only;
no account, package installation, secret, external service or model call.

Use Python 3.9 or later on a POSIX system for the supplied lifecycle helper
(its pipe readiness wait uses `selectors`). This is a prerequisite, not a claim
that every remote machine has that interpreter. Verify `python3 --version`.

Run commands from this directory:

```sh
python3 scripts/prepare.py
python3 scripts/lifecycle.py
```

Preparation builds `build/catalog.json`, checks its exact bytes and runs the three
CLI behavior tests. It exits without starting a service. The expected catalog is
the following one-line JSON followed by one LF:

```json
[{"id":"alpha","label":"Alpha"},{"id":"beta","label":"Beta"}]
```

The separate lifecycle command starts one child on `127.0.0.1` and asks the OS
for a free port (`--port 0`). It waits for a listening announcement, then requires
HTTP 200 with body `ok` from `/health`, reads `/catalog`, compares it with the
checked file, and always stops/reaps that same child in `finally`. Its JSON result
reports the observed port, responses, server exit and closed-port probe.
The complete source is in `scripts/lifecycle.py`; no persistent background service
is left by this command. A listening socket alone does not mean ready.

To keep the toy service in the foreground for manual inspection:

```sh
python3 scripts/serve_catalog.py --port 0
```

Use the printed loopback port, not a port copied from somebody else's run. Stop
this foreground process with Ctrl-C in the same terminal. The server's default
is 8765 if `--port` is omitted; the supplied reproduction avoids conflicts by
explicitly using 0. Never stop an unrelated process to obtain that port.

Without the generated catalog, `/health` and `/catalog` return 503 with body
`catalog unavailable`; `check_catalog.py` exits 1. The service never substitutes
an empty list and calls it ready. Build rejects duplicate IDs with exit 2 before
creating its output. The tests use disposable input/output directories.

## Authored wrong preparation

The deliberately wrong ordering is:

```sh
python3 scripts/serve_catalog.py --port 0
python3 scripts/build_catalog.py
python3 scripts/check_catalog.py
```

The first command is a foreground server. While it keeps running, the later
commands are not reached; before a catalog exists, health is unavailable.
This is an authored recipe error, not a recorded Codex defect.

In a fresh copy without `build/catalog.json`, this bounded demonstration waits
one second, observes its own still-running child, then terminates/reaps it:

```sh
python3 scripts/wrong_prepare.py
```

It reports whether the build step was reached and whether a catalog exists.
It has its own timeout and `finally` cleanup. The repair is to run the finite
`prepare.py` first, then the separately controlled `lifecycle.py`.

## Files

- `data/labels.csv`: complete original two-row source.
- `scripts/build_catalog.py`: strict CSV validation, ID sorting and canonical JSON.
- `scripts/check_catalog.py`: this fixture's exact two-entry/byte contract.
- `scripts/prepare.py`: finite build, check and tests.
- `scripts/serve_catalog.py`: loopback health/catalog service.
- `scripts/lifecycle.py`: bounded start, readiness, catalog and shutdown.
- `scripts/wrong_prepare.py`: bounded authored ordering-error demonstration.
- `tests/test_catalog.py`: actual CLI behavior for valid rows, duplicate rejection
  and absent-catalog failure.
- `.gitignore`: disposable build/cache/log files.

This local project does not configure a provider environment. Consult the
accompanying tutorial for a narrowly documented mapping, not a cloud-run result.
