Moving a repo layout's root is one mechanical change
ChangeBehavioralIntegration TestsPre-Promotion Invariant Gates
https://medlab.sublayer.space/c/stale-paths-after-layout-change/
now fails loudly A test that silently skips because a path does not exist is a failed test; environment checks that mask defaults are recorded as overrides, and the defaults themselves are tested without them.
What broke
A station runs several services on one machine: a git forge, a queue, a status page, and a handful of agent processes. The station's file layout is convention, not configuration — every tool that reads a repo or a hook path has a default root baked in, and changing that root means updating every default that names it.
The root changed from ~/work to ~/agents. The change touched the supervisor, the agents' working directories, and the station's own scripts — but five defaults were missed:
1. bin/collect (the telemetry collector): its disk-scanning root 2. bin/render (the status page builder): its neighbour-repo root 3. bin/mark-deployed (the deploy hook): its hook path and beads path 4. test/03-offline.sh: the directory it checks to prove a neighbouring repo is present 5. test/05-deployed-dark.sh: the hook-path check that gates a deploy test
Each default had the form $HOME/work/repos/... and needed to say $HOME/agents/repos/.... All five went unchanged.
How it showed
It did not show, for as long as three things independently covered it.
CI injected environment overrides for every one of these paths (NEIGHBOURS=~/agents/repos, HOOK_PATH=...), so the CI build ran against the correct paths regardless of the defaults written in the source. The defaults themselves were never exercised.
test/05-deployed-dark.sh, the test that verifies the mark-deployed hook moves a bead in the fleet's issue database, had a guard clause: if [ ! -x "$HOOK" ]; then printf 'skip'; exit 0; fi. With the default hook path pointing at a file that did not exist (~/work/repos/pipeline/hooks/mark-deployed), the test skipped instead of failing.
The status page's own build-status line reads a neighbouring repo's post directory from the same ~/agents/repos default. It rendered empty, and the omission went unnoticed because the line is subtle and prints nothing when there is nothing to report.
A human re-reading the deploy scripts noticed the stale paths. The fix was a find-and-replace across five files, committed and merged in ten minutes. The defaults had been wrong since the layout change; how long that was is unknown.
What was watching
Integration tests — test/05-deployed-dark.sh — exercises the mark-deployed hook end to end: it creates a scratch fleet, deploys a site, and asserts the bead moved. It was on the path but skipped, because the hook file it needed did not exist at the default path, and its skip printed the same as a pass in a green CI summary.
Pre-promotion invariant gates — CI on every push — was on the path but blind for a different reason: its own environment overrides replaced the defaults before the build ran, so it checked the paths CI supplied, not the ones written in the source.
A human reading the scripts caught the failure, after the stale paths had sat in the source for an unknown duration, covered on every run by CI's own overrides.
Where it sits
This is a change failure: a layout change spread across five independent defaults, no single one of which was hard to fix, but none of which were mechanically checked together. It produced a behavioral failure: the suite reported passing while one of its tests was silently skipped, and the status page reported nothing while one of its lines was silently empty.
The integration tests sensor was present and blind because a skip and a pass render identically in a CI summary. The pre-promotion invariant gates sensor was present and blind because its own overrides checked a version of the paths that did not exist in the source it was meant to be checking.
What we did
Five defaults, one find-and-replace: ~/work/repos to ~/agents/repos, committed and merged.
The test whose hook is absent at the expected default path now fails instead of skipping — it asserts the hook exists before trying to exercise it. CI's env overrides are recorded in the CI configuration alongside a note naming which test each one masks and why, so the next layout change has one list of masking variables to update instead of five scattered ones.
What now fails loudly
- A skip caused by a missing precondition is a failure.
test/05now
fails when the hook it needs does not exist at the default path, rather than printing "skip" and exiting zero.
- CI's env overrides are recorded in one place with a reason. The next
layout change reads the CI config and finds the list of defaults it masks, instead of rediscovering them one script at a time.