Story: Generate literate HTTP recipes that tangle to runnable Hurl
Table of Contents
This page documents a story in Sprint 26. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
Every HTTP route the models declare has a generated literate recipe, each recipe tangles to a runnable Hurl file, the harness replays a recipe group against the live gateway and reports a verdict for each recipe, and the inventory is gated in CI.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 26 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-27 |
3. Acceptance
- A codegen facet generates one org recipe per model that renders an HTTP route, with a tangle block per endpoint, and the generated output is byte-identical on a second run.
- Every tangled Hurl file runs against the live gateway unchanged, behind a sign-in preamble the harness supplies.
- A runner replays a named recipe group and records a verdict per recipe, and a recipe that does not answer fails the run.
- The recipe inventory is generated from the recipes and checked in CI.
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Scaffold story: Generate literate HTTP recipes that tangle to runnable Hurl | DONE | 2026-09-27 | 2026-09-27 | Story scaffolding rides this task: documents, sprint wiring, and the scaffold PR. Close it before merging that PR. |
| Build the HTTP recipe facet and its runner | DONE | 2026-09-27 | 2026-09-27 | Initial task for: Generate literate HTTP recipes that tangle to runnable Hurl |
5. Result
The HTTP surface documents itself and tests itself from one document per model, the arrangement the shell surface already has. A codegen facet generates one literate recipe per model that renders a route, each endpoint tangles to a Hurl file in a committed library, a runner replays a recipe group against the live gateway and records a verdict for each, and the generated inventory is gated in CI.
Fourteen models generate today — seven entities, one junction and six operations — into 82 Hurl files. The task carries the item-by-item account.
The harness found a real defect on its first run, which is what it is for: the
gateway rejects every token the login route issues, with JWT verification
failed: wrong algorithm in its log. No authenticated recipe can assert a 200
until that is fixed.
6. Decisions
| # | Decision | Why |
|---|---|---|
| 1 | Hurl is the tangle target. | It is to HTTP what ores-shell is to the shell: a plain-text file that is both the readable document and the runnable test, with assertions. The existing verb blocks were already most of the way to its syntax. |
| 2 | A recipe asserts the endpoint's status, not its payload. | A payload assertion would pin the recipe to whatever the populated database happens to hold. |
| 3 | REJECTED is a verdict of its own. |
The gateway refusing the caller says something different about the deployment than the endpoint answering wrongly, and the harness exists to tell those apart. |
| 4 | The generated inventory is its own document. | The curated doc/recipes/http/http.org catalogues the hand-written recipes that predate the facet. Regenerating it from filetags would replace headings like "RBAC (Role-Based Access Control)" with "Rbac verb". |
| 5 | The runner is not a CI job. | CI has no running gateway, and check_shell_recipes.py is a manual lever for the same reason. What CI gates is the inventory. |
7. Out of scope
Recorded rather than done, each with its reason:
- The JWT algorithm defect. It needs a diagnosis of how the gateway and iam agree on a signing key, which is security work rather than recipe work.
- The hand-written recipes in
doc/recipes/http/are stale in shape as well as style: login takesprincipalrather thanusername, and the paths are namespaced. - The runner replays writes by default. A destructive recipe must be named in
--skip, which is recorded on the task rather than enforced here.