docs(invarianter): the run id that carried a path, and the rule that had no guard [skip-docs]

Two rows and the matching CHANGELOG entries. The first records the measurement rather than
the decision: `--outbox-dir <d>/inni --run-id ../../ESCAPE` answered 0 and wrote two levels
above the directory the caller named, on a door whose directory argument was already
guarded. It also records the check that did NOT survive -- the containment half was
unreachable after the string rule and is written down as dead code removed, not as a second
layer of defence.

The second row is the smaller and older failure: a rule called load-bearing in prose, with
no arm that would notice if it were deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 11:49:35 +02:00
commit 705bcfed6f
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 38 additions and 0 deletions

View file

@ -31,6 +31,19 @@ at all, and this release gives each of them a command of its own.
data that came back from it. Fifteen of seventeen steps at this release; the round binder and its
report share one module entry point without a subcommand that tells them apart.
### Security
- A `--run-id` that carries a path is refused by all seven outbox doors before anything is written.
Each writer composes `<out_dir>/<run_id>-<artefact>.json`; the directory was guarded and the run
id was not, so `--outbox-dir <d>/inni --run-id ../../X` wrote two levels above the directory the
caller named and answered `0`. Separators, a bare `.` or `..`, an absolute prefix, an embedded
NUL and the empty string are now refused with exit `3`, the reason named, and no directory left
behind. Fixed as a class, because the composition is identical in all seven.
### Fixed
- `--stop-reason` being required is now guarded by a test rather than only by prose. The rule is
load-bearing - "the run finished" and "we never found out" must not be the same value - but
swapping `required=True` for `default=""` passed the entire suite before this release.
### Changed
- Console entry points are pinned against the **installed** distribution's metadata rather than
against `pyproject.toml`: a `[project.scripts]` line that has never been installed is a claim,