From d77504c148015ad3bb3aa9a14345fc1a2ab9e114 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 18:22:08 +0000 Subject: [PATCH 01/10] Prove lane-end's synopsis and its narrowed state-cell scan: a DUPLICATE retire TERMs, and a line past the cap, or with a pipe or a CR, cannot hide an open LANDING (#96, #90 part a) Co-Authored-By: Claude Sonnet 5.5 --- tests/test_precision_90a_96.py | 178 +++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 tests/test_precision_90a_96.py diff --git a/tests/test_precision_90a_96.py b/tests/test_precision_90a_96.py new file mode 100644 index 00000000..13d05419 --- /dev/null +++ b/tests/test_precision_90a_96.py @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: Apache-2.0 +"""`lane-end`'s two precision defects: its synopsis says a DUPLICATE HOLDER +retire "ends nothing" (opensoft/openRepoTools#96), and its narrowed state-cell +scan skips ratified decision O1's cap and its `|` and CR/LF rules, so a cell +that is not the phrase can hide an open LANDING (#90, part (a)). + +EVERY RUN HERE IS A REAL `lane-end` IN A SANDBOX: `test_lane_start_claude_current`'s +workstation (a bare origin, a workspace clone whose register is a header, +`workspace.yaml` pointing at it) with the lane's row written into it and +pushed, so the published row `register-row` reads is the same row. The lane +has NO object log, which is the one path that still decides a merge hold from +prose, and the close-out gate's `lane-worktrees` is a stub that finds nothing, +so a cell that wrongly reads clear is a lane that ENDS rather than one that +trips over the gate. +""" + +from __future__ import annotations + +import shutil +import subprocess +from pathlib import Path + +import pytest + +from test_lane_start_claude_current import Sandbox, LANE, _write, pytestmark +from conftest import REPO + +UTC = "2026-10-11T10:00:00Z" +UUID = "aaaa0090-9090-4000-8000-aaaa00909090" + + +# ------------------------------------------------------------------- #96 + +def _synopsis(help_text: str) -> list[str]: + """The USAGE block of `--help`, one string per synopsis entry: a line that + opens ` lane-end` and the `#` continuation lines under it.""" + block = help_text.split("\nUSAGE\n", 1)[1].split("\n\n", 1)[0] + entries: list[str] = [] + for line in block.splitlines(): + if line.startswith(" lane-end"): + entries.append(line) + else: + entries[-1] += " " + line + return [" ".join(entry.split()) for entry in entries] + + +@pytest.fixture +def synopsis(tmp_path: Path) -> list[str]: + result = subprocess.run(["bash", str(REPO / "lane-end"), "--help"], + env={"HOME": str(tmp_path), "PATH": "/usr/bin:/bin"}, + capture_output=True, text=True, timeout=30, check=False) + assert result.returncode == 0, result.stderr + return _synopsis(result.stdout) + + +def test_the_synopsis_says_what_a_duplicate_holder_retire_does(synopsis): + """`--retire ` on a DUPLICATE HOLDER TERMs the bg-pty-host pair, and + the one-line summary said `ends nothing`: true of a FORK, the opposite of + the act on a duplicate holder (#96).""" + duplicate = [e for e in synopsis if "--retire <" in e and "DUPLICATE" in e] + assert len(duplicate) == 1, synopsis + assert "TERM" in duplicate[0], duplicate[0] + assert "ends nothing" not in duplicate[0], duplicate[0] + + +def test_the_synopsis_keeps_the_fork_and_the_duplicate_on_their_own_lines(synopsis): + """The split is the fix: the FORK entry still says it writes and kills + nothing, and names no DUPLICATE, whose words differ.""" + fork = [e for e in synopsis if "--retire <" in e and "FORK" in e] + assert len(fork) == 1, synopsis + assert "kills nothing" in fork[0], fork[0] + assert "DUPLICATE" not in fork[0], fork[0] + + +# ------------------------------------------------------------------ #90 (a) + +def _line(length: int, tail: str) -> str: + """A phrase line of exactly `length` characters ending in `tail`.""" + filler = length - len(tail) + assert filler > 0 + return "x" * filler + tail + + +@pytest.fixture +def ended(tmp_path: Path): + """`ended(cell)` runs `lane-end` on a lane whose register row carries + `cell` as its state, in this checkout's row and in the published one, and + returns the process and the row as the register has it afterwards.""" + box = Sandbox(tmp_path) + shutil.copy2(REPO / "lane-end", box.bin / "lane-end") + (box.bin / "lane-end").chmod(0o755) + stub = _write(box.bin / "lane-worktrees", "#!/usr/bin/env bash\nexit 0\n") + register = box.wip / "lanes" / "LANES.md" + seed = register.read_text(encoding="utf-8") + + def row(cell: str) -> str: + return (f"| `{LANE}` | harness `{UUID}` | Eagle / test / brett | " + f"2026-10-11T00:00Z | none | handoffs/repoZ/x.md | {cell} |\n") + + def run(cell: str, published: str | None = None): + """`published` is the cell origin/main carries when it is not this + checkout's own: pushed first, then this checkout's row put back.""" + if published is not None: + register.write_text(seed + row(published), encoding="utf-8") + box.git("-C", str(box.wip), "commit", "-q", "-am", "the published row") + box.git("-C", str(box.wip), "push", "-q", "origin", "main") + register.write_text(seed + row(cell), encoding="utf-8") + if published is None: + box.git("-C", str(box.wip), "commit", "-q", "-am", "the row") + box.git("-C", str(box.wip), "push", "-q", "origin", "main") + env = dict(box.env, LANE_WORKTREES=str(stub)) + result = subprocess.run([str(box.bin / "lane-end"), LANE], env=env, + cwd=str(box.lane_dir), capture_output=True, + text=True, timeout=120, check=False) + after = next(l for l in register.read_text(encoding="utf-8").splitlines() + if l.startswith(f"| `{LANE}`")) + return result, after + + return run + + +HOLD = "LANDING #7 into repoZ main" +READ = "the last LANDING in its state cell" + + +def test_a_line_over_the_cap_cannot_hide_an_open_landing(ended): + """THE DEFECT: `LIVE · · <241 characters ending in LANDING #7>` had + its scan narrowed to `LIVE`, so the lane ENDED over a hold other lanes' + merges wait on. O1's cap is on the line; a cell past it is not the phrase + and is read whole (#90, part (a)).""" + line = _line(241, " " + HOLD) + assert len(line) == 241 + result, after = ended(f"LIVE · {UTC} · {line}") + assert result.returncode == 2, (result.stdout, result.stderr) + assert READ in result.stderr, result.stderr + assert "lane-end on" not in after, after + + +@pytest.mark.parametrize("line", [ + pytest.param(f"held up | {HOLD}", id="a pipe"), + pytest.param(f"held up\r{HOLD}", id="a carriage return"), +]) +def test_a_line_with_a_pipe_or_a_carriage_return_cannot_hide_an_open_landing(ended, line): + """O1's other two rules: the line holds no `|` and no CR/LF. A cell that + breaks either is not the phrase, and its words are all read.""" + result, after = ended(f"LIVE · {UTC} · {line}") + assert result.returncode == 2, (result.stdout, result.stderr) + assert READ in result.stderr, result.stderr + assert "lane-end on" not in after, after + + +def test_the_published_row_is_read_whole_when_it_is_not_the_phrase(ended): + """The fallback reads TWO cells and either one showing a hold refuses: this + checkout's row says nothing, the one on origin/main is past the cap.""" + published = f"LIVE · {UTC} · {_line(241, ' ' + HOLD)}" + result, after = ended(f"LIVE · {UTC} · all quiet", published=published) + assert result.returncode == 2, (result.stdout, result.stderr) + assert "the published row on origin/main" in result.stderr, result.stderr + assert "lane-end on" not in after, after + + +def test_a_line_exactly_at_the_cap_is_still_prose(ended): + """THE BOUNDARY IS INCLUSIVE and the phrase is unchanged: a valid line that + says `LANDING #7` is a lane waiting for one, not a hold (Amendment 13(a)), + and 240 characters is the longest line `set-row-state` accepts.""" + line = _line(240, " waiting for " + HOLD) + assert len(line) == 240 + result, after = ended(f"LIVE · {UTC} · {line}") + assert result.returncode == 0, (result.stdout, result.stderr) + assert "| ENDED · " in after, after + + +def test_a_line_over_the_cap_that_holds_nothing_still_ends(ended): + """Reading the whole cell is a refusal only where the cell holds + something: a malformed one with no LANDING or CLAIMED in it closes.""" + result, after = ended(f"LIVE · {UTC} · {_line(300, ' and all of it quiet')}") + assert result.returncode == 0, (result.stdout, result.stderr) + assert "| ENDED · " in after, after From 1a22dd7a68017194f99d4c0b80d0626b5f046f2b Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 18:22:08 +0000 Subject: [PATCH 02/10] Make lane-end's synopsis say what a DUPLICATE retire does, and scan a cell whose line breaks O1's grammar whole instead of narrowing it (#96, #90 part a) Co-Authored-By: Claude Sonnet 5.5 --- lane-end | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lane-end b/lane-end index cde8c280..e8a0667d 100755 --- a/lane-end +++ b/lane-end @@ -27,8 +27,11 @@ # # trees, branches, scratch and caches # # stay where they are, and the row and # # the ENDED line say so (#163) -# lane-end --retire # retire a live FORK (pid or uuid) or a -# # DUPLICATE (pid); ends nothing +# lane-end --retire # retire a live FORK: writes nothing and +# # kills nothing +# lane-end --retire # a DUPLICATE of the lane's id: TERMs a +# # background holder's pair, only reports +# # any other; the row is not written # lane-end --retire-dormant [--reason ""] [--yes] # # the DORMANT rows of one repository, # # dry run by default; --yes is ONE @@ -1720,6 +1723,13 @@ scrub_cell() { # the second exactly an instant, and a third part after it. That is # `mig_cell_is_phrase`'s rule in `lanes-edit.sh`; the states are the # amendment's own list, and this is the second place that list is written. +# THE LINE KEEPS DECISION O1's GRAMMAR TOO (opensoft/openRepoTools#90, part +# (a); Copilot's last review of #82). `set-row-state` refuses a line of more +# than 240 characters or one carrying `|` or CR/LF, so a cell with any of them +# was not written by it, and narrowing there dropped its prose from the scan +# all the same: an open LANDING in that prose closed the lane. That cell is +# scanned whole, like a diary. In a C locale `${#x}` counts bytes, which only +# over-counts, and an over-count reads more of the cell. cell_for_scan() { # — the part of it that says where the lane is cfs="$1" case "$cfs" in @@ -1736,7 +1746,9 @@ cell_for_scan() { # — the part of it that says where the lane i # `LIVE` there would close a lane over an open landing. case "$cfs_three" in '' | *' · '*) printf '%s' "$cfs"; return 0 ;; + *'|'* | *$'\n'* | *$'\r'*) printf '%s' "$cfs"; return 0 ;; esac + [ "${#cfs_three}" -le 240 ] || { printf '%s' "$cfs"; return 0; } cfs_ok=0 case "$cfs_one" in LIVE|PAUSED|LANDED|ENDED|RETIRED|'HANDED OFF'|MIGRATED) cfs_ok=1 ;; From 32965733467549272a78643b77a1c3366f6e0a4f Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 18:22:08 +0000 Subject: [PATCH 03/10] =?UTF-8?q?Add=20the=20TAKEOVER=20=E2=86=90=20lane:=20form=20to=20the=20log=20grammar=20table?= =?UTF-8?q?=20(#108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- docs/README-lanes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README-lanes.md b/docs/README-lanes.md index 7c0d16ca..5a1c523c 100644 --- a/docs/README-lanes.md +++ b/docs/README-lanes.md @@ -675,6 +675,7 @@ unambiguously at the first space after it; payload sub-fields are separated by |---|---| | issue | `CLAIMED`, `RELEASED`, `TAKEOVER ← `, `CLOSED → owner/repo#pr` | | PR | `OPENED ← owner/repo#i [owner/repo#j …]`, `LANDING`, `LANDED → `, `WITHDRAWN`, `CLOSED` | +| issue or PR | `TAKEOVER ← lane:` | | lane | `STARTED`, `PAUSED`, `RESUMED`, `ENDED`, `RETIRED` | | narrative | `NOTED`, `RULED [→ ]` (Amendment 13(b)) | | race | `CLAIM-LOST → lane:` | From 42a1085ed629b7e158f3b980ec1dfcb55b95e322 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 18:22:08 +0000 Subject: [PATCH 04/10] Make tests.yml's header comments name tests-no-submodule as a no-submodule checkout and workflow_dispatch as a tests-macos home (#126 items 2, 3) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/tests.yml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e27bc64a..deea773c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,14 +5,15 @@ name: tests # network, and must never create a real repository. # # EVERY CHECKOUT THAT RUNS THE SUITE TAKES `submodules: true`, `parse-macos` -# excepted: it runs no pytest, reads no estate fixture, and checks out -# `submodules: false` on purpose, so it is not one more job the claim below -# has to be quietly true of. The estate tests read eight of openRepoShape's -# own files out of `upstream/openRepoShape`, and `wip init`'s tests read the -# nine of `templates/workspace-root/`, all pinned by -# `contracts/openreposhape-pin.yaml`. Without them those tests SKIP rather than -# fail — which is what the `tests-no-submodule` job below proves on purpose, -# and what every suite job besides it must not be quietly doing. +# and `tests-no-submodule` excepted: both check out `submodules: false` on +# purpose. `parse-macos` runs no pytest and reads no estate fixture, and +# `tests-no-submodule` is the job that proves the skip below, so neither is +# one more job the claim below has to be quietly true of. The estate tests +# read eight of openRepoShape's own files out of `upstream/openRepoShape`, and +# `wip init`'s tests read the nine of `templates/workspace-root/`, all pinned +# by `contracts/openreposhape-pin.yaml`. Without them those tests SKIP rather +# than fail — which is what the `tests-no-submodule` job below proves on +# purpose, and what every suite job besides it must not be quietly doing. # # THE LANE HELPERS' OWN SUITE RUNS IN THESE JOBS TOO, and it runs because # `tests/test_lane_helpers_suite.py` calls it: this repository's CI is @@ -26,9 +27,10 @@ name: tests # (`opened`, `synchronize`, `reopened`): the coordinator applying `ready` to # an already-open PR is a `pull_request` event only under `labeled`, and # `tests-macos`'s `if:` below has to see that event to react to it. `push` -# (main only) and the nightly `schedule` are `tests-macos`'s other two homes -# now that it has left the per-push gate — see the comment on that job -# (Brett Heap's RULING of 2026-09-16, "do all three"). The cron is UTC. +# (main only), the nightly `schedule` and `workflow_dispatch` are +# `tests-macos`'s other three homes now that it has left the per-push gate — +# see the comment on that job (Brett Heap's RULING of 2026-09-16, "do all +# three"). The cron is UTC. # # Adding `labeled` to `pull_request` means EVERY job in this file — not only # `tests-macos` — now also runs when ANY label lands on a PR, `ready` or From 2c5b3f25e5d8f98b11f1966b66268aed2fe89299 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 20:07:24 +0000 Subject: [PATCH 05/10] Count lane-end's 240-character cap in characters in every locale, so a valid wide line is not held under a C locale, and run the boundary tests under C and UTF-8 (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- lane-end | 18 +++++++++-- tests/test_precision_90a_96.py | 58 +++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lane-end b/lane-end index e8a0667d..b8a5c6fb 100755 --- a/lane-end +++ b/lane-end @@ -1728,8 +1728,11 @@ scrub_cell() { # than 240 characters or one carrying `|` or CR/LF, so a cell with any of them # was not written by it, and narrowing there dropped its prose from the scan # all the same: an open LANDING in that prose closed the lane. That cell is -# scanned whole, like a diary. In a C locale `${#x}` counts bytes, which only -# over-counts, and an over-count reads more of the cell. +# scanned whole, like a diary. The cap is in CHARACTERS and `${#x}` is BYTES in +# a C locale, where a line of `—` and `→` inside it would read as past it and +# a lane merely waiting for a LANDING would be held by it: past 240, the count +# is redone with the UTF-8 continuation bytes taken out, the same number in +# every locale. cell_for_scan() { # — the part of it that says where the lane is cfs="$1" case "$cfs" in @@ -1746,9 +1749,18 @@ cell_for_scan() { # — the part of it that says where the lane i # `LIVE` there would close a lane over an open landing. case "$cfs_three" in '' | *' · '*) printf '%s' "$cfs"; return 0 ;; + esac + # O1's OWN RULES FOR THE LINE, which `mig_cell_is_phrase` does not test + # and `set-row-state` does (#90): no `|`, no CR/LF, at most 240 characters. + case "$cfs_three" in *'|'* | *$'\n'* | *$'\r'*) printf '%s' "$cfs"; return 0 ;; esac - [ "${#cfs_three}" -le 240 ] || { printf '%s' "$cfs"; return 0; } + # `wc -c` pads on BSD, hence `$((…))`; a count that cannot be made is a long line. + cfs_len="${#cfs_three}" + [ "$cfs_len" -le 240 ] || + cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c)" || + cfs_len=241 + [ "$((cfs_len))" -le 240 ] || { printf '%s' "$cfs"; return 0; } cfs_ok=0 case "$cfs_one" in LIVE|PAUSED|LANDED|ENDED|RETIRED|'HANDED OFF'|MIGRATED) cfs_ok=1 ;; diff --git a/tests/test_precision_90a_96.py b/tests/test_precision_90a_96.py index 13d05419..e62056fc 100644 --- a/tests/test_precision_90a_96.py +++ b/tests/test_precision_90a_96.py @@ -81,8 +81,36 @@ def _line(length: int, tail: str) -> str: return "x" * filler + tail -@pytest.fixture -def ended(tmp_path: Path): +def _wide_line(length: int, tail: str) -> str: + """The same, but the filler is `—` and `→`: three bytes a character, so a + line inside the cap by characters is far past it by bytes.""" + line = ("—→" * length)[:length - len(tail)] + tail + assert len(line) == length and len(line.encode()) > 2 * length + return line + + +def _utf8_locale() -> str | None: + """A UTF-8 locale this host has, or None. `locale -a` spells them + `C.utf8` or `en_US.UTF-8` by platform.""" + try: + out = subprocess.run(["locale", "-a"], capture_output=True, text=True, + timeout=10, check=False).stdout + except OSError: + return None + names = [n for n in out.split() if n.lower().replace("-", "").endswith(".utf8")] + return names[0] if names else None + + +UTF8 = _utf8_locale() + +#: `${#x}` counts CHARACTERS in a UTF-8 locale and BYTES in `C`, and the cap is +#: O1's 240 CHARACTERS in both: every `lane-end` case runs under each. +LOCALES = ["C", pytest.param(UTF8, id="UTF-8", marks=pytest.mark.skipif( + UTF8 is None, reason="this host has no UTF-8 locale"))] + + +@pytest.fixture(params=LOCALES) +def ended(tmp_path: Path, request): """`ended(cell)` runs `lane-end` on a lane whose register row carries `cell` as its state, in this checkout's row and in the published one, and returns the process and the row as the register has it afterwards.""" @@ -108,10 +136,11 @@ def run(cell: str, published: str | None = None): if published is None: box.git("-C", str(box.wip), "commit", "-q", "-am", "the row") box.git("-C", str(box.wip), "push", "-q", "origin", "main") - env = dict(box.env, LANE_WORKTREES=str(stub)) + env = dict(box.env, LANE_WORKTREES=str(stub), LC_ALL=request.param) result = subprocess.run([str(box.bin / "lane-end"), LANE], env=env, cwd=str(box.lane_dir), capture_output=True, - text=True, timeout=120, check=False) + encoding="utf-8", errors="replace", + timeout=120, check=False) after = next(l for l in register.read_text(encoding="utf-8").splitlines() if l.startswith(f"| `{LANE}`")) return result, after @@ -170,6 +199,27 @@ def test_a_line_exactly_at_the_cap_is_still_prose(ended): assert "| ENDED · " in after, after +def test_a_wide_line_exactly_at_the_cap_is_still_prose(ended): + """THE CAP IS COUNTED IN CHARACTERS IN EVERY LOCALE. A valid 240-character + line of `—` and `→` is some 680 bytes: counted as bytes under `C` it read as + past the cap, the cell was scanned whole, and the lane waiting for a + LANDING was held by it (the false hold Amendment 13(a)'s narrowing exists + to prevent).""" + line = _wide_line(240, " waiting for " + HOLD) + result, after = ended(f"LIVE · {UTC} · {line}") + assert result.returncode == 0, (result.stdout, result.stderr) + assert "| ENDED · " in after, after + + +def test_a_wide_line_one_past_the_cap_cannot_hide_an_open_landing(ended): + """...and one character more is past it in every locale, wide or not.""" + line = _wide_line(241, " " + HOLD) + result, after = ended(f"LIVE · {UTC} · {line}") + assert result.returncode == 2, (result.stdout, result.stderr) + assert READ in result.stderr, result.stderr + assert "lane-end on" not in after, after + + def test_a_line_over_the_cap_that_holds_nothing_still_ends(ended): """Reading the whole cell is a refusal only where the cell holds something: a malformed one with no LANDING or CLAIMED in it closes.""" From 748e0ef237de1a0b5636ee0d531c5f094b16fc34 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Tue, 13 Oct 2026 20:41:19 +0000 Subject: [PATCH 06/10] Strip BSD wc's padding on lane-end's character count the way the hygiene rule spells it (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- lane-end | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lane-end b/lane-end index b8a5c6fb..1c73376b 100755 --- a/lane-end +++ b/lane-end @@ -1755,12 +1755,12 @@ cell_for_scan() { # — the part of it that says where the lane i case "$cfs_three" in *'|'* | *$'\n'* | *$'\r'*) printf '%s' "$cfs"; return 0 ;; esac - # `wc -c` pads on BSD, hence `$((…))`; a count that cannot be made is a long line. + # A count that cannot be made is a long line; BSD `wc` pads, hence the `tr`. cfs_len="${#cfs_three}" [ "$cfs_len" -le 240 ] || - cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c)" || + cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ')" || cfs_len=241 - [ "$((cfs_len))" -le 240 ] || { printf '%s' "$cfs"; return 0; } + [ "$cfs_len" -le 240 ] || { printf '%s' "$cfs"; return 0; } cfs_ok=0 case "$cfs_one" in LIVE|PAUSED|LANDED|ENDED|RETIRED|'HANDED OFF'|MIGRATED) cfs_ok=1 ;; From 03a60f9247d9b05c607593c7e98cdf46e2acb308 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 18 Oct 2026 01:18:19 +0000 Subject: [PATCH 07/10] Prove lane-end's character count keeps bytes for text that is not UTF-8, and reads a count it cannot make as a line over the cap (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- tests/test_precision_90a_96.py | 76 +++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/tests/test_precision_90a_96.py b/tests/test_precision_90a_96.py index e62056fc..ccf1a0ba 100644 --- a/tests/test_precision_90a_96.py +++ b/tests/test_precision_90a_96.py @@ -16,6 +16,7 @@ from __future__ import annotations +import os import shutil import subprocess from pathlib import Path @@ -119,32 +120,41 @@ def ended(tmp_path: Path, request): (box.bin / "lane-end").chmod(0o755) stub = _write(box.bin / "lane-worktrees", "#!/usr/bin/env bash\nexit 0\n") register = box.wip / "lanes" / "LANES.md" - seed = register.read_text(encoding="utf-8") + seed = register.read_bytes() - def row(cell: str) -> str: + def row(cell: str | bytes) -> bytes: + """A register row; a `bytes` cell is how a hand-edited row that is not + UTF-8 gets in.""" + cell = cell.encode("utf-8") if isinstance(cell, str) else cell return (f"| `{LANE}` | harness `{UUID}` | Eagle / test / brett | " - f"2026-10-11T00:00Z | none | handoffs/repoZ/x.md | {cell} |\n") + f"2026-10-11T00:00Z | none | handoffs/repoZ/x.md | ".encode() + + cell + b" |\n") - def run(cell: str, published: str | None = None): + def run(cell: str | bytes, published: str | bytes | None = None, + env: dict[str, str] | None = None): """`published` is the cell origin/main carries when it is not this - checkout's own: pushed first, then this checkout's row put back.""" + checkout's own: pushed first, then this checkout's row put back. + `env` is added to the environment `lane-end` runs in.""" if published is not None: - register.write_text(seed + row(published), encoding="utf-8") + register.write_bytes(seed + row(published)) box.git("-C", str(box.wip), "commit", "-q", "-am", "the published row") box.git("-C", str(box.wip), "push", "-q", "origin", "main") - register.write_text(seed + row(cell), encoding="utf-8") + register.write_bytes(seed + row(cell)) if published is None: box.git("-C", str(box.wip), "commit", "-q", "-am", "the row") box.git("-C", str(box.wip), "push", "-q", "origin", "main") - env = dict(box.env, LANE_WORKTREES=str(stub), LC_ALL=request.param) - result = subprocess.run([str(box.bin / "lane-end"), LANE], env=env, + full = dict(box.env, LANE_WORKTREES=str(stub), LC_ALL=request.param, + **(env or {})) + result = subprocess.run([str(box.bin / "lane-end"), LANE], env=full, cwd=str(box.lane_dir), capture_output=True, encoding="utf-8", errors="replace", timeout=120, check=False) - after = next(l for l in register.read_text(encoding="utf-8").splitlines() - if l.startswith(f"| `{LANE}`")) + after = next(l for l in register.read_bytes().decode( + "utf-8", errors="replace").splitlines() + if l.startswith(f"| `{LANE}`")) return result, after + run.box, run.locale = box, request.param return run @@ -220,6 +230,50 @@ def test_a_wide_line_one_past_the_cap_cannot_hide_an_open_landing(ended): assert "lane-end on" not in after, after +def _lines_that_are_not_utf8() -> list: + """Three lines past the cap by BYTES whose characters cannot be counted, + each naming a LANDING: a stray continuation byte is no character, and + taking continuation bytes out undercounts them.""" + hold = b" " + HOLD.encode() + return [ + pytest.param(b"\x80" * 241 + hold, id="241 stray continuation bytes"), + pytest.param(b"x" * (240 - len(hold)) + "°".encode("latin-1") + hold, + id="Latin-1 with one degree sign"), + pytest.param(_wide_line(240, hold.decode()).encode() + b"\x80", + id="a valid 240-character line and one stray byte"), + ] + + +@pytest.mark.parametrize("line", _lines_that_are_not_utf8()) +def test_a_line_that_is_not_utf8_cannot_hide_an_open_landing(ended, line): + """THE COUNT IS ONLY EXACT FOR VALID UTF-8. Past 240 bytes the characters + are counted by taking the continuation bytes out, and text that is not + UTF-8 has continuation bytes that follow nothing: a hand-edited cell of + them was counted short, narrowed, and its LANDING closed the lane. It + falls back to the byte count, which can only over-count (#90, part (a)).""" + assert len(line) > 240 + result, after = ended(f"LIVE · {UTC} · ".encode() + line) + assert result.returncode == 2, (result.stdout, result.stderr) + assert READ in result.stderr, result.stderr + assert "lane-end on" not in after, after + + +def test_a_count_that_cannot_be_made_is_a_line_over_the_cap(ended): + """A `wc` that fails (the one place the count is read from) leaves the + cell scanned whole: a valid 240-character line of `—` and `→`, which + narrows when its count is made, is read for its LANDING instead. (A UTF-8 + locale counts a line of 240 characters as 240 and never reaches `wc`.)""" + if ended.locale != "C": + pytest.skip("only a byte count past 240 reaches the `wc` recount") + stub = ended.box.root / "failing-wc" + _write(stub / "wc", f'#!/bin/sh\n[ "$*" = -c ] && exit 1\nexec {shutil.which("wc")} "$@"\n') + line = _wide_line(240, " waiting for " + HOLD) + result, after = ended(f"LIVE · {UTC} · {line}", + env={"PATH": f"{stub}{os.pathsep}{ended.box.env['PATH']}"}) + assert result.returncode == 2, (result.stdout, result.stderr) + assert READ in result.stderr, result.stderr + + def test_a_line_over_the_cap_that_holds_nothing_still_ends(ended): """Reading the whole cell is a refusal only where the cell holds something: a malformed one with no LANDING or CLAIMED in it closes.""" From f2d559d1b4d8151b276cd5406490e788a6959d4b Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 18 Oct 2026 01:18:19 +0000 Subject: [PATCH 08/10] Recount lane-end's cap in characters only for valid UTF-8, and keep the byte count for any other text (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- lane-end | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lane-end b/lane-end index 1c73376b..b9b2d682 100755 --- a/lane-end +++ b/lane-end @@ -1730,9 +1730,11 @@ scrub_cell() { # all the same: an open LANDING in that prose closed the lane. That cell is # scanned whole, like a diary. The cap is in CHARACTERS and `${#x}` is BYTES in # a C locale, where a line of `—` and `→` inside it would read as past it and -# a lane merely waiting for a LANDING would be held by it: past 240, the count -# is redone with the UTF-8 continuation bytes taken out, the same number in -# every locale. +# a lane merely waiting for a LANDING would be held by it: past 240, a line +# that is valid UTF-8 is recounted with its continuation bytes taken out, the +# same number in every locale. Text that is not valid UTF-8 has continuation +# bytes that follow nothing and are no characters, so it keeps the first count, +# which can only over-count, and an over-count reads more of the cell. cell_for_scan() { # — the part of it that says where the lane is cfs="$1" case "$cfs" in @@ -1755,11 +1757,14 @@ cell_for_scan() { # — the part of it that says where the lane i case "$cfs_three" in *'|'* | *$'\n'* | *$'\r'*) printf '%s' "$cfs"; return 0 ;; esac - # A count that cannot be made is a long line; BSD `wc` pads, hence the `tr`. + # A count that cannot be made is a long line, and so is an `iconv` that + # is missing or refuses the text; BSD `wc` pads, hence the `tr`. cfs_len="${#cfs_three}" - [ "$cfs_len" -le 240 ] || + if [ "$cfs_len" -gt 240 ] && + printf '%s' "$cfs_three" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1; then cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ')" || - cfs_len=241 + cfs_len=241 + fi [ "$cfs_len" -le 240 ] || { printf '%s' "$cfs"; return 0; } cfs_ok=0 case "$cfs_one" in From ffcca3c20205a6a106cee9d66defb3def368f1a4 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 18 Oct 2026 01:20:45 +0000 Subject: [PATCH 09/10] Take lane-end's UTF-8 validity from an iconv -c round trip rather than iconv's exit status, which musl's does not set (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- lane-end | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lane-end b/lane-end index b9b2d682..f6748d50 100755 --- a/lane-end +++ b/lane-end @@ -1757,13 +1757,17 @@ cell_for_scan() { # — the part of it that says where the lane i case "$cfs_three" in *'|'* | *$'\n'* | *$'\r'*) printf '%s' "$cfs"; return 0 ;; esac - # A count that cannot be made is a long line, and so is an `iconv` that - # is missing or refuses the text; BSD `wc` pads, hence the `tr`. + # `iconv -c` drops what is not UTF-8, and its exit status is not to be + # trusted (musl's is 0 either way), so the text is UTF-8 when it comes + # back unchanged; with no `iconv` it never does. A count that cannot be + # made is a long line, and BSD `wc` pads, hence the `tr`. cfs_len="${#cfs_three}" - if [ "$cfs_len" -gt 240 ] && - printf '%s' "$cfs_three" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1; then - cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ')" || - cfs_len=241 + if [ "$cfs_len" -gt 240 ]; then + cfs_utf8="$(printf '%s' "$cfs_three" | iconv -c -f UTF-8 -t UTF-8 2>/dev/null)" || : + if [ "$cfs_utf8" = "$cfs_three" ]; then + cfs_len="$(printf '%s' "$cfs_three" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' ')" || + cfs_len=241 + fi fi [ "$cfs_len" -le 240 ] || { printf '%s' "$cfs"; return 0; } cfs_ok=0 From 8ff52c21a20c3405bb0c075aff987f935c0f8e7b Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 18 Oct 2026 08:45:50 +0000 Subject: [PATCH 10/10] Pin the failing-wc fail-safe itself: the empty count that reaches lane-end's test must not print an integer-expression error (#90 part a) Co-Authored-By: Claude Sonnet 5.5 --- tests/test_precision_90a_96.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_precision_90a_96.py b/tests/test_precision_90a_96.py index ccf1a0ba..32258b76 100644 --- a/tests/test_precision_90a_96.py +++ b/tests/test_precision_90a_96.py @@ -261,8 +261,11 @@ def test_a_line_that_is_not_utf8_cannot_hide_an_open_landing(ended, line): def test_a_count_that_cannot_be_made_is_a_line_over_the_cap(ended): """A `wc` that fails (the one place the count is read from) leaves the cell scanned whole: a valid 240-character line of `—` and `→`, which - narrows when its count is made, is read for its LANDING instead. (A UTF-8 - locale counts a line of 240 characters as 240 and never reaches `wc`.)""" + narrows when its count is made, is read for its LANDING instead. It is the + `|| cfs_len=241` that does it: without it the count comes back empty, the + cell is still read whole, but only because `[ "" -le 240 ]` is an error, + which `lane-end` prints. (A UTF-8 locale counts a line of 240 characters as + 240 and never reaches `wc`.)""" if ended.locale != "C": pytest.skip("only a byte count past 240 reaches the `wc` recount") stub = ended.box.root / "failing-wc" @@ -272,6 +275,7 @@ def test_a_count_that_cannot_be_made_is_a_line_over_the_cap(ended): env={"PATH": f"{stub}{os.pathsep}{ended.box.env['PATH']}"}) assert result.returncode == 2, (result.stdout, result.stderr) assert READ in result.stderr, result.stderr + assert "integer expression" not in result.stderr, result.stderr def test_a_line_over_the_cap_that_holds_nothing_still_ends(ended):