Name requests in the Python ownership list, and test that the list stays complete - #105
Conversation
leaf-agent
left a comment
There was a problem hiding this comment.
The doc half checks out: asks.py's own docstring is "Declaration-driven page and thread ask projections", and requests.py is "Durable one-shot requests and their terminal host receipts" — both entries now match the module they route to, and asks keeps the request-ask projections it actually owns (request_phases, _is_request) without claiming the lifecycle.
One finding on the test, inline: the naming lookup is flat, so the guarantee in its docstring holds for a new top-level module but not for a new member of registry/, served_state/, render_gate/, or validation/ — which is where the doc keeps a second, driftable list.
Unrelated to the change, but worth knowing before someone chases it: this is the first test that reads a CLAUDE.md, and a tend PR checkout restores every CLAUDE.md at any depth from the base branch before the session starts. In a bot PR session the test therefore reads the base doc against the merged module tree — on this branch it fails with unnamed in scripts/CLAUDE.md: ['requests.py'] even though the PR is what fixes that. Run against the PR's own doc (git show HEAD:plugins/leaf/skills/leaf/scripts/CLAUDE.md), tests/test_interact_layer.py is 126 passed, 6 skipped, matching the description.
…n the suite (#106) Tend sessions start with an empty Playwright cache, so the suite that is most of `tests/` cannot run from one — `setup:` installs browsers under the runner's home, which the sandbox does not inherit. This adds a `sandbox_setup:` block that seeds the same headless shell `ci.yaml` uses into the sandbox's own home, and asserts it by launching it. Verified in a real tend sandbox: cache empty at start, install 4.4s, launch 0.8s, and `uv run pytest --run-nightly -n0 tests/test_render_reactions.py -k stacks_at_a_narrow_edge` then passes 2 tests in 4.26s where before it could not start. <details><summary>What was observed, and why this shape</summary> ### The evidence Last night's `tend-nightly` session ([33238853704](https://github.com/max-sixty/leaf/actions/runs/33238853704)) reached this partway through the suite: ``` ERROR tests/test_render_conversations.py::test_the_line_offers_the_list_its_own_keys_rather_than_the_way_deeper_in playwright._impl._errors.Error: BrowserType.launch: Executable doesn't exist at /home/tend-sandbox/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell ``` It did not install the browser; it stopped verifying locally and said so in its summary — "The browser suites also did not run locally — this sandbox has no Playwright browsers installed — so CI was their first pass on both PRs". Both PRs it opened (#104, #105) are green, so nothing wrong went out. What went out unverified is the point: on a repo where nearly every test drives a real browser, the session shipped with the repo's own gate unrun. Three `tend-ci-fix` sessions the same night did install it themselves, and two of them lost a turn to the same wrong flag first: | Run | Command | Result | | --- | --- | --- | | [33229417627](https://github.com/max-sixty/leaf/actions/runs/33229417627) | `playwright install --with-deps chromium --only-shell` | `sudo: a password is required` | | [33229417627](https://github.com/max-sixty/leaf/actions/runs/33229417627) | `playwright install chromium --only-shell` | ok, 3s | | [33230715465](https://github.com/max-sixty/leaf/actions/runs/33230715465) | `playwright install --with-deps chromium --only-shell` | `sudo: a password is required` | | [33230715465](https://github.com/max-sixty/leaf/actions/runs/33230715465) | `playwright install chromium --only-shell` | ok, 4s | | [33231657192](https://github.com/max-sixty/leaf/actions/runs/33231657192) | `playwright install chromium --only-shell` | ok, 5s | `--with-deps` switches to root, and the sandbox user has no sudo — the trap `running-in-ci`'s Privileges bullet names, and its prescribed remedy is exactly this: propose the setup entry rather than work around its absence. ### Why `sandbox_setup:` rather than `setup:` `setup:` runs as the runner. Playwright downloads to `$HOME/.cache/ms-playwright`, and per tend's config reference the sandbox inherits shared system and toolcache paths but never the runner's home. `sandbox_setup:` runs as the sandbox user with the workspace as cwd, which puts the browser where `sync_playwright()` will look for it. This is the neighbour of the `uv`-on-PATH gap that [max-sixty/tend#1071](max-sixty/tend#1071) fixed upstream for 0.1.20 — same boundary, but a home-scoped download rather than a PATH entry, so the repo owns it rather than tend. ### Why no `--with-deps` The runner image already carries the system libraries: the three sessions above ran the browser suite successfully after a plain `--only-shell` install, and so did the verification here. Adding a second `setup:` step to run `--with-deps` as the runner would be machinery for a need that has not appeared. ### Cost ~5.3s per session that boots an agent (4.4s install, 0.8s launch, measured in this sandbox). `sandbox_setup` runs inside the `max-sixty/tend/claude` step, so the `tend-notifications` and `tend-review` runs that short-circuit before booting pay nothing — and `tend-ci-fix`'s gate-skips never reach it either. ### The regenerated workflows `.config/tend.yaml` is the source; `sandbox_setup` is an action input, so it has to reach each generated workflow. Regenerated with `uvx tend@0.1.20 init` — the version already pinned in every workflow header — so the diff is three added lines per file and no other drift, which also confirms the committed files match what 0.1.20 generates. ### Gate assessment - **Confidence**: High. Structural — the sandbox home starts without the cache on every run, so it recurs every time. Four sessions hit it in one 20-hour window: one abandoned the gate, three installed it themselves, two of those after a failed escalation. - **Magnitude**: targeted fix. One existing knob in one place, plus the mechanical regeneration it requires. - **Cost**: mostly wasted compute — a turn per session and a download per session. The nightly case is the one that isn't purely waste: a repo whose safety net is a browser suite had that net unavailable to the session doing the surveying. No wrong outward action occurred, so the remedy is held to the waste-class bar, which one config knob meets. </details> Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
…ule (#543) `scripts/CLAUDE.md` tells a session that a clean `git status` after `scripts/vendor.py <bundle>` is the check that a bundle still matches the script, and that the check holds only where every fetched input is pinned. It then sorts the bundles into those two classes — and two of the seven are in neither, so a session rebuilding `floating-ui` or `mcp-app` has no answer for whether a diff it sees is drift to fix or an upstream patch to take. Each belongs in a class the paragraph already describes: - `floating-ui` reproduces. `@floating-ui/dom`, `@floating-ui/core`, and `@floating-ui/utils` are all in `PINS`, and that closure is complete: `dom` depends on `core` and `utils`, `core` on `utils`, `utils` on nothing. Rebuilding it left `git status` clean. - `mcp-app` fetches an input `PINS` does not name. `@modelcontextprotocol/ext-apps@1.7.5` declares `@standard-schema/spec ^1.1.0`, which npm's resolver picks, so the bundle sits with `plot` and `pierre`. It reproduced when I rebuilt it today — which is exactly the reading the paragraph exists to keep a session from relying on. This is drift rather than a deliberate omission. The paragraph dates from `c7b37062` (2026-08-30); `mcp-app` landed in #188 on 2026-09-01 and `floating-ui` in #537 last night, and #533 — the most recent commit to touch the paragraph — added `jsdiff` to it, so it is meant to be kept current. The test follows the shape this repo already uses for instruction lists (#93, #105, #331, #496): it reads the bundle names out of `vendor.py`'s `BUILDS` and `COPIES` rather than restating them, and asserts each is named in that one paragraph. On `main` it fails with `unplaced in scripts/CLAUDE.md: ['floating-ui', 'mcp-app']`. What it deliberately does not check is *which* class a bundle is placed in — deciding that means resolving each package's dependency graph, which is a network call this suite should not make. A name in the wrong class still needs a reader to catch; a name that is absent no longer does. <details><summary>Verification</summary> - `uv run pytest tests/test_interact_layer.py` — 167 passed, 6 skipped. - The new case alone, with `scripts/CLAUDE.md` reverted to `main`'s text: `AssertionError: unplaced in scripts/CLAUDE.md: ['floating-ui', 'mcp-app']`. - `scripts/vendor.py floating-ui` and `scripts/vendor.py mcp-app` each left `git status` clean. - `npm view @floating-ui/dom@1.8.0 dependencies` → `{ '@floating-ui/core': '^1.8.0', '@floating-ui/utils': '^0.2.12' }`; `@floating-ui/core@1.8.0` → `{ '@floating-ui/utils': '^0.2.12' }`; `@floating-ui/utils@0.2.12` → none; `@modelcontextprotocol/ext-apps@1.7.5` → `{ '@standard-schema/spec': '^1.1.0' }`. </details> Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
Add
requestsbesidedecisionsin the Python ownership map. A derived test now compares the shipped Python modules with the instruction scopes that route them, while keeping package members separate from same-named top-level owners.Testing:
uv run pytest tests/test_interact_layer.py -q -n0(133 passed). Removing therequestsentry makes the new test fail onrequests.py.