Skip to content

implement-loop 20260905-094605 (running) - #450

Closed
iot49 wants to merge 17 commits into
mainfrom
implement-loop/20260905-094605
Closed

implement-loop 20260905-094605 (running)#450
iot49 wants to merge 17 commits into
mainfrom
implement-loop/20260905-094605

Conversation

@iot49

@iot49 iot49 commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Opened by implement-loop 20260905-094605 while the batch was still running.

Each issue below passed ./scripts/check.sh and a cold-agent audit. An issue the
run was working when it stopped is not listed, and its commits, if
any, are unjudged — so trust this list rather than the branch tip.

iot49 and others added 17 commits September 5, 2026 08:04
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 install stays tolerant, but the comment above it claimed a failed one
"costs coverage and never a red gate". That stopped being true when the
`broker` fixture began failing under CI.

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.
@iot49 iot49 closed this Sep 5, 2026
@iot49
iot49 deleted the implement-loop/20260905-094605 branch September 5, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant