Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README-lanes.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ unambiguously at the first space after it; payload sub-fields are separated by
|---|---|
| issue | `CLAIMED`, `RELEASED`, `TAKEOVER ← <stale claim URL>`, `CLOSED → owner/repo#pr` |
| PR | `OPENED ← owner/repo#i [owner/repo#j …]`, `LANDING`, `LANDED → <sha>`, `WITHDRAWN`, `CLOSED` |
| issue or PR | `TAKEOVER ← lane:<dispossessed lane>` |
| lane | `STARTED`, `PAUSED`, `RESUMED`, `ENDED`, `RETIRED` |
| narrative | `NOTED`, `RULED [→ <object>]` (Amendment 13(b)) |
| race | `CLAIM-LOST → lane:<other>` |
Expand Down
37 changes: 35 additions & 2 deletions lane-end
Original file line number Diff line number Diff line change
Expand Up @@ -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 <lane> --retire <pid|uuid> # retire a live FORK (pid or uuid) or a
# # DUPLICATE (pid); ends nothing
# lane-end <lane> --retire <pid|uuid> # retire a live FORK: writes nothing and
# # kills nothing
# lane-end <lane> --retire <pid> # 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 <repo> [--reason "<why>"] [--yes]
# # the DORMANT rows of one repository,
# # dry run by default; --yes is ONE
Expand Down Expand Up @@ -1720,6 +1723,18 @@ 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. 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, 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() { # <state cell> — the part of it that says where the lane is
cfs="$1"
case "$cfs" in
Expand All @@ -1737,6 +1752,24 @@ cell_for_scan() { # <state cell> — the part of it that says where the lane i
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
# `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 ]; 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
case "$cfs_one" in
LIVE|PAUSED|LANDED|ENDED|RETIRED|'HANDED OFF'|MIGRATED) cfs_ok=1 ;;
Expand Down
286 changes: 286 additions & 0 deletions tests/test_precision_90a_96.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# 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 os
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 <pid>` 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


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

Check warning on line 89 in tests/test_precision_90a_96.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this composite assertion into separate assertions.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoTools&issues=AaE24TWktc0NmJYk9mQZ&open=AaE24TWktc0NmJYk9mQZ&pullRequest=202
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."""
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_bytes()

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 | ".encode()
+ cell + b" |\n")

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.
`env` is added to the environment `lane-end` runs in."""
if published is not None:
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_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")
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_bytes().decode(
"utf-8", errors="replace").splitlines()
if l.startswith(f"| `{LANE}`"))
return result, after

run.box, run.locale = box, request.param
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 · <UTC> · <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_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 _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. 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"
_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
assert "integer expression" not 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."""
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
Loading