implement-loop 20260905-094605 (running) - #450
Closed
iot49 wants to merge 17 commits into
Closed
Conversation
The `broker` fixture skipped on both paths — no `mosquitto` on PATH, and a broker that would not start — so a failed install in CI removed every suite built on the bus from the run and the gate still reported green. `no_broker` now decides by the environment: a skip where `CI` is unset, a failure where it says CI. A broker is software and belongs in the gate where hardware does not (#372), and a machine without one still runs everything else. The module docstring states both behaviours and why they differ. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The six app command lines were written one after another and repeat each other: `_connected` byte-identical in seven modules, `PERIOD_S`, `BROKER_S` and `RETAINED_S` with the same docstrings six, six and four times, and the same argparse construction in the four apps that read documents. `lib` is where the shape goes, being the one import the apps share (ADR-0013). A sibling of `loading.py` rather than a part of it: what an app does when the railroad moves under it is that module's subject, and the way in is this one's. Nothing imports it yet. `retained` waits on `stop`, which is the rule `dropped` already states and the two apps that poll a retained window break. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_connected` was byte-identical in seven modules and the three startup numbers repeated with their docstrings; the four apps that read documents built the same three flags four times over, one comment included verbatim. All of it now comes from `lib/startup.py`. The two apps whose flags genuinely differ keep their own parser: the driver reads no documents and takes no `--store`, and the translator takes a station and no railroad. `dccex`'s window, waited out whole for want of anything to key on, keeps its reason — moved into `_retained` where the behaviour is, now that the constant it hung on is shared. Behaviour is unchanged here: the scheduler's and the dispatcher's polled retained wait is still their own and is the next commit's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#430) `lib/loading.py` states the rule: `retained_s` is a moment given to the broker, "waited on `stop` so a signal arriving in it ends the process rather than being sat on". `layout` and `dccex` do that; these two took no `stop` at all and polled `time.sleep(0.01)` until the row landed or the deadline passed, so a SIGTERM arriving in the window was honoured up to `RETAINED_S` — a second — late. Both now wait through `lib/startup.py`, which looks for the row a tick at a time and ends on `stop` between two looks. `serve` takes `retained_s` as `dccex`'s already does, so a test can hold an app inside a window and stop it there; it is the same number the reload's `dropped` is given. The test in each app fails against the old wait: with a 30-second window and a stop set inside it, the app is still running ten seconds later. The simulator's `_waiting` docstring loses the words `time.sleep`, which named no call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The term is load-bearing in sixty-odd places across src, tests and docs while the glossary told a reader to avoid it, and glossed it as a kind of restart where the code uses it for something explicitly not one. Give it an entry rather than rename it: an app clearing the retained rows it owns and building them again from nothing, without the process restarting, naming ADR-0060 for the railroad-loading case. Restart drops it from _Avoid_ and says how the two differ instead, and the section's preamble says why the new entry is not a fourth failure. No file outside CONTEXT.md changes.
Nothing compared `CONTEXT.md` against `lib/inventory.py`, so the glossary could name a topic that does not exist. It did once — `tc49/layout/ railroad_wanted` was in the prose two batches before the topic reached the inventory — right in advance rather than wrong, and nothing would have caught it had it never arrived. Same shape as `test_state_leaves.py` and `test_store_routes_are_proxied.py`: both sides read at run time, no copy of either list here, and the failure names the line and the topic. One reading covers the three ways the prose writes a name — a whole topic is a row, a prefix written with a trailing slash holds where the inventory has a topic under it or is that topic itself, and the bare namespace `tc49/` is the widest prefix of all rather than an exception. The reverse direction is out: the glossary is about words, not about every row. Per the batch's rulings, `prefix` is the glossary's word for a trailing-slash name and is used here in place of the issue's "family".
A mechanical extraction, no behaviour change: the loop over the bus's retained values in the constructor moves out to `_traction_rest`, beside `_traction_write` it calls, so a second caller can want the same thing. The point asymmetry moves with the loop, the startup reasoning stays where it is said.
An `off` that is applied writes `0.0` over every retained `wanted/traction` row before publishing `wanted/track`. "The railroad comes up at rest" is a promise about a power cycle as well as about a process start, and the startup rule does not reach a power cycle: an arrival zeroes only a train this app drives, so a plain `off` left a person's throttle standing in the row and the next `on` brought current back over it. Ordering is load-bearing — the translator acts on desired rows as they arrive, so the zeros are the last thing the station is told before the supply goes. Only on the applied `off`: a refused gesture changes nothing (ADR-0062), `stopped` is a run meant to resume from, and `wanted/point` has no resting value to write (ADR-0054). No new state, no new topic, no new field. Five tests in `tests/layout/test_power.py`, one of them on the order the two topics go out in and not on both merely having happened; `docs/layout/README.md` states the rule beside the guard it rides on.
Wording only. "Power cycle" was a term this repo does not have; the glossary names the two halves — a **power off** and the `on` after it — and says a **restart** is the process returning, which removing the supply is not.
`{train: null, mode: manual}` took every train `layout` holds a position
for, including one mid-transit under a grant. `_took` clears the
crossing's `driving` flag, so the arrival's `0.0` stopped going out and
the train kept its commanded speed past the block it was sent to,
standing only when somebody found it.
`_modes` now skips those trains and takes the rest — applied in part
rather than refused whole, so one automatic train under way does not
stop a person taking any train. `_driven` reads the same
`crossing.driving` flag off the same crossing `_took` writes. The
skipped trains go to the trace and nothing is published to name them:
`state/mode` is the level that says where every train's mode stands.
A gesture naming a train is untouched in either direction, and so is
`{train: null, mode: automatic}`.
SYSTEM.md's `mode_wanted` payload entry and its manual-driving section, and the layout README's map paragraph, each said `train: null` takes every train. `docs/layout/README.md` carries the reason and where the refused trains go.
Every test that presses a drag and then names the request it composed spelled the id `<train>-N` out. The next commit puts a per-process nonce in a gesture-minted id, so those spellings stop being writable — and they were never the thing under test in any of these, which are about what a cancellation frees or what order a held queue drains in. `tests/harness.py` grows `minted`, the id the scheduler minted for a train's nth request, off the trace's `request_submitted` lines; `test_cancellation.py`'s own `drag` returns the id it just caused, and `test_replay.py` compares train and ordinal either side of the nonce, which is what two runs of one document have to agree on. No behaviour changes here: these tests pass identically before and after the nonce lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
) The counter starts empty in every process, and the scheduler and the dispatcher are separate processes now (ADR-0059), so a supervisor restarting the scheduler alone is ordinary: the next drag minted an id the dispatcher was still holding, which it drops at the top of admission before any check runs — no `request_admitted` and no `request_rejected` ever came back. That is #73's failure reproduced by a restart instead of a page reload. ADR-0033 settled this class by splitting the minters, and the split has moved inside this app now the panel mints nothing: a timetable keeps `<train>-N`, deterministic and in the file's order, because replay's traces are byte-identical; a gesture is minted `<train>-<nonce>-<n>` from `secrets.token_hex(4)`, made once at construction. Not clock- derived, which SYSTEM.md forbids of the bus, and not deterministic, which is what makes it work — a run carrying gestures never claimed determinism. The counter stays undivided (ADR-0036): the ordinal is still the last part. `nonce` is public so a test can assert the shape against the nonce the instance holds rather than against a regex for some hex. Re-minting a timetable's ids on a restart stays correct and is untouched, and `_reverse` composes no request, so it mints nothing either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ADR names the panel as the nonce-minter and the panel no longer mints, so the text was stale where it stands. An amendment block at its head says the split is unchanged and has moved inside the scheduler, that a timetable keeps `<train>-N` for the reason it always did, and that what forced the move is separate processes making an independent restart of the minter ordinary. Nothing about uniqueness-not-meaning changes. Beyond the issue's criteria, two pages that stated the shape as the whole of it: CONTEXT.md's **Request id** and SYSTEM.md's scheduler section both said the scheduler mints from one counter in the timetable's order, which is now half of what it does. No glossary term is introduced or retired. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both gates that stop work over dead rails read the observed power, not the commanded one, so between `wanted/track: off` going out and the station reporting `device/track: off` a `run_wanted: running` is accepted, the sweep grants, and `_cross` writes a speed onto rails that are going dark. The outcome is the "safe but wedged" ADR-0040 already names, with ADR-0049's placement as the recourse, so neither gate changes and this is the record. The gate sites are named by path and symbol rather than by the line numbers the issue quoted: those were against 9e79384 and have since drifted, and no other ADR here cites a line.
The Moving entry stated one half — a manual train on its route is active and so moving. The complement is that a manual train off its route is neither, so `moving` reads false, `layout` writes its wheels on the throttle gesture alone, and a plain `off` under it is applied. That is a user error the app does not guard, and it is a scope declaration rather than a trade-off, so it gets no ADR. Criterion 3 sends the same qualification to the two pages that stated the unqualified half: DISPATCH.md said every train moves on a route the dispatcher allocated, and the layout's Power section said a plain `off` is applied only where nothing is moving. The layout's "no move while the rails are dead" paragraph gains the other half of the amendment, the value it reads being the observed one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opened by implement-loop 20260905-094605 while the batch was still running.
Each issue below passed
./scripts/check.shand a cold-agent audit. An issue therun was working when it stopped is not listed, and its commits, if
any, are unjudged — so trust this list rather than the branch tip.
88ad24129941d5888337ffaebe4c678224fe4514..6b178ebb887ba4a19fcf2180c0084d3df74c761f6b178ebb887ba4a19fcf2180c0084d3df74c761f..6ae59e5b9fcfcde457586cbac4abd82a10eae17a6ae59e5b9fcfcde457586cbac4abd82a10eae17a..dc4f79966694a7f2cc66b478a7f6d7b96770d0dcdc4f79966694a7f2cc66b478a7f6d7b96770d0dc..ea4c0e52fd6b6441ebce60d08ec9297c5f17f4b7ea4c0e52fd6b6441ebce60d08ec9297c5f17f4b7..982f1b4139a5efa357bdd677512b873f9fbcff89982f1b4139a5efa357bdd677512b873f9fbcff89..dd23bad05f263a43483595538d2888a89dcb40f2dd23bad05f263a43483595538d2888a89dcb40f2..bc8da8ba4aa40b20be0d76b0e9068d0c155c01e8bc8da8ba4aa40b20be0d76b0e9068d0c155c01e8..7d019720a02c0357d38a12f171a386dc56f2f5027d019720a02c0357d38a12f171a386dc56f2f502..a1155d15b55b599fdc79c00d42fc59b98d333998a1155d15b55b599fdc79c00d42fc59b98d333998..33348186a235e3526366d854dcd6bf4687712f3933348186a235e3526366d854dcd6bf4687712f39..8d5e0c23134df8070bba2d3e44dc0dad75c91a9a