From b5d9454d6abb834282c44ab79bbb71ba7a2e6fe3 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 13:54:19 -0500 Subject: [PATCH 1/8] Retire the lane item ceiling in lane.ps1 and lane-level.ps1 Owner ruling 2026-10-26: the Builder four-item lane ceiling and the start-throttle by burn are retired, and there is no WIP limit on briefing. Usage is governed only by the Steward's holds and the korus Workflow gate. lane.ps1 drops -Capacity (default 4) and the record's capacity field, and strips a capacity left on an older record on its next write. lane-level.ps1 no longer reads a lane as AT CAPACITY or SERIAL, and no item count buys the dispatcher arm silence: each lane is reported as STATED with its in-flight and free-by-work counts. New tests in tests/test_coord_lane_no_ceiling.py fail if a default cap of 4 returns. Proposed PR title: Retire the four-item lane ceiling from the lane tooling --- scripts/coord/lane.ps1 | 32 ++-- scripts/hooks/lane-level.ps1 | 107 ++++++-------- tests/test_coord_lane_no_ceiling.py | 221 ++++++++++++++++++++++++++++ tests/test_coord_lane_statement.py | 2 +- 4 files changed, 287 insertions(+), 75 deletions(-) create mode 100644 tests/test_coord_lane_no_ceiling.py diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index ae4e7848b7..53dbf6f325 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -2,11 +2,19 @@ # Copyright (C) 2026 MessageFoundry Foundation, LLC and contributors <# .SYNOPSIS - Record THIS lane's own free-slot count. The only writer of a lane level. + Record THIS lane's own counts of items in flight and items free by work. The only writer of a + lane level. .DESCRIPTION - A builder lane states how many of its slots are free BY WORK. Nothing else in the fleet may write - that number -- not the dispatcher, not a hook, not an inference from claims. + A builder lane states how many items it is working and how many it holds without working them. + Nothing else in the fleet may write those numbers -- not a Manager, not a hook, not an inference + from claims. + + THERE IS NO ITEM CEILING (owner ruling 2026-10-26). The four-item lane ceiling, and the + -Capacity parameter that defaulted to it, are retired: Builders and Managers work at full speed, + and usage is governed only by the Steward's holds and the korus Workflow gate. Nothing this + script records is a cap, a slot count or a reason to stop briefing or building. Do not add a + capacity back, and do not add a usage throttle here; usage is the Steward's. THERE IS NO -Lane AND NO -BoxKey PARAMETER, AND ADDING ONE DEFEATS THE WHOLE DESIGN. The box key is derived from $PWD, so a session can only ever state its OWN level. The failure this replaces is a @@ -15,8 +23,9 @@ numbers were honest. Only the lane knows which is which. FREE MEANS BY WORK, NOT BY CLAIMS. Built-and-awaiting-land is FREE. Blocked is FREE. Parked on a - condition is FREE. A claim you are not currently able to progress is a FREE slot wearing an - occupied label, and that mislabelling is the outage. + condition is FREE. A claim you are not currently able to progress is FREE work wearing an + occupied label, and that mislabelling is the outage. With no ceiling there are no empty slots, + so -Free counts exactly those held items and nothing else. #> [CmdletBinding(DefaultParameterSetName = 'State')] param( @@ -27,8 +36,7 @@ param( [Parameter(ParameterSetName = 'State')][ValidateRange(0, 99)][int]$Free, [Parameter(ParameterSetName = 'State')][ValidateRange(0, 99)][int]$InFlight, [Parameter(ParameterSetName = 'State')][string]$Note = '', - [Parameter(ParameterSetName = 'State')][ValidateRange(1, 99)][int]$Capacity = 4, - # Stamp that a dispatcher asked. Carries NO number and cannot produce an at-capacity reading. + # Stamp that a dispatcher asked. Carries NO number. [Parameter(ParameterSetName = 'Asked', Mandatory)][switch]$Asked, [Parameter(ParameterSetName = 'Show', Mandatory)][switch]$Show ) @@ -140,7 +148,7 @@ if (-not $Show -and $rec -and -not (Test-CoordRecordIsHere $rec)) { } if (-not $rec) { $rec = [pscustomobject]@{ - v = 1; boxKey = $boxKey; worktree = $worktree; capacity = $Capacity + v = 1; boxKey = $boxKey; worktree = $worktree statedFree = $null; statedInFlight = $null; statedUtc = $null statedBySessionId = $null; statedNote = '' tipAtStated = ''; claimHashAtStated = ''; turnsSinceStated = 0 @@ -175,7 +183,7 @@ if ($Show) { # between the two reads would print a stated level beside an `absent` reason. The fields # match the blank record above; fold the two together once that read keeps its reason. $shown = [pscustomobject]@{ - v = 1; boxKey = $boxKey; worktree = $worktree; capacity = $Capacity + v = 1; boxKey = $boxKey; worktree = $worktree statedFree = $null; statedInFlight = $null; statedUtc = $null statedBySessionId = $null; statedNote = '' tipAtStated = ''; claimHashAtStated = ''; turnsSinceStated = 0 @@ -224,7 +232,6 @@ if ($Asked) { $tip = (& git rev-parse HEAD 2>$null) if ($LASTEXITCODE -ne 0) { $tip = '' } else { $tip = $tip.Trim() } - $rec.capacity = $Capacity $rec.statedFree = $Free $rec.statedInFlight = $InFlight $rec.statedUtc = $nowUtc @@ -238,6 +245,9 @@ if ($Asked) { } $rec.worktree = $worktree $rec.boxKey = $boxKey +# A record written before the ceiling was retired carries `capacity`, 4 by default. Drop it on the +# next write, so no stored lane level still names a cap for a reader to apply. +$rec.PSObject.Properties.Remove('capacity') # docs/COORD-SHARED-ROOT.md: a reader on another PC must be able to tell this lane is not its own. $rec | Add-Member -NotePropertyName host -NotePropertyValue $thisHost -Force @@ -250,7 +260,7 @@ Move-CoordFileReplace -Source $tmp -Destination $file -ReplaceReadOnly if ($Asked) { Write-Host "lane: recorded that a dispatcher asked. No number written." } else { - Write-Host "lane: $boxKey -- $Free free / $InFlight in flight of $($rec.capacity), stated $nowUtc" + Write-Host "lane: $boxKey -- $Free free / $InFlight in flight, stated $nowUtc" if (-not $Note) { Write-Host " (no note. A note is what makes the number auditable later.)" } } exit 0 diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index 379581a436..3c30bac779 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -2,17 +2,22 @@ # Copyright (C) 2026 MessageFoundry Foundation, LLC and contributors <# .SYNOPSIS - Stop hook. In a builder session it nags that lane for its own free-slot count. In the dispatcher - session it reports those counts and speaks whenever it cannot prove both lanes are full. + Stop hook. In a builder session it nags that lane for its own level. In the dispatcher session + it reports each lane's stated counts, and it never calls a lane full. .DESCRIPTION - THE STRUCTURAL PROPERTY, AND EVERYTHING ELSE IS DETAIL: every input is either a lane's own - assertion of fullness, or an invalidator of one. NOTHING TURNS "UNKNOWN" INTO "FULL". A missing - file, an unreadable file, a stale number, a superseded number, a roster shortfall -- all of them - SPEAK. Silence is reachable only through a lane's own fresh statement. + THERE IS NO ITEM CEILING (owner ruling 2026-10-26). This hook used to call a lane AT CAPACITY + when it stated no free items, against a four-item ceiling, and went quiet when both lanes said + so. The ceiling, the start-throttle by burn and any WIP limit on briefing are retired: Builders + and Managers work at full speed, and usage is governed only by the Steward's holds and the korus + Workflow gate. So no count this hook reads ever becomes a verdict that a lane is full or that + briefing should stop, and it adds no usage throttle of its own. - THAT IS WHY IT SURVIVES A DISPATCHER WITH NO DISCIPLINE: THE DISPATCHER WRITES NOTHING THIS HOOK - READS. It never reads mail, receipts, dispatch traffic, or claim counts to derive occupancy. + EVERY NUMBER IS A LANE'S OWN QUOTE. A missing file, an unreadable file, a stale number, a + superseded number, a roster shortfall -- each is said aloud, never turned into a count. + + THE DISPATCHER WRITES NOTHING THIS HOOK READS. It never reads mail, receipts, dispatch traffic, + or claim counts to derive occupancy. The failure it replaces, measured 2026-08-23: a lane held NINE claims while reporting ONE active item and THREE free slots. A claim-counting gauge would have reported both lanes full for the @@ -39,7 +44,6 @@ $ErrorActionPreference = 'Stop' $EXPECTED_LANES = 2 $STALE_TURNS = 6 $STALE_MIN = 30 -$SERIAL_MIN_INFLIGHT = 2 $LANE_PROMPT_TURNS = 3 $LANE_PROMPT_MIN = 15 $SEAT_DIR_FLOOR = 5 @@ -80,17 +84,17 @@ function Test-Has($rec, [string]$name) { } function Get-LaneVerdict($rec, [string]$boxKey) { - # One occupancy word and one confidence word, or a single non-answer word. `greenable` is the ONLY - # thing the caller may use to stay quiet, and it is set in exactly one place. + # One state word and one confidence word, or a single non-answer word. STATED is the only state + # a readable level gets, whatever its numbers: no count makes a lane full. A `capacity` field on + # an old record is not read, for the same reason. $o = [ordered]@{ box = $boxKey; occ = ''; conf = ''; free = $null; inflight = $null - cap = 4; ageMin = $null; turns = $null; note = ''; greenable = $false } + ageMin = $null; turns = $null; note = '' } if ($null -eq $rec) { $o.occ = 'NO-RECORD'; return [pscustomobject]$o } if ($rec -is [string] -and $rec -eq 'UNREADABLE') { $o.occ = 'UNREADABLE'; return [pscustomobject]$o } $free = if (Test-Has $rec 'statedFree') { $rec.statedFree } else { $null } $inf = if (Test-Has $rec 'statedInFlight') { $rec.statedInFlight } else { $null } - if ((Test-Has $rec 'capacity') -and $rec.capacity) { $o.cap = [int]$rec.capacity } if (Test-Has $rec 'statedNote') { $o.note = [string]$rec.statedNote } if (Test-Has $rec 'turnsSinceStated') { $o.turns = [int]$rec.turnsSinceStated } $statedUtc = if (Test-Has $rec 'statedUtc') { $rec.statedUtc } else { $null } @@ -101,20 +105,11 @@ function Get-LaneVerdict($rec, [string]$boxKey) { $o.occ = if ($pc -gt 0) { 'NEVER-STATED' } else { 'NO-RECORD' } return [pscustomobject]$o } - if ($null -eq $free) { $o.occ = 'UNREADABLE'; return [pscustomobject]$o } + if ($null -eq $free -or $null -eq $inf) { $o.occ = 'UNREADABLE'; return [pscustomobject]$o } $o.free = [int]$free - if ($null -ne $inf) { $o.inflight = [int]$inf } - - if ($o.free -gt 0) { - $o.occ = 'OPEN' - } elseif ($null -eq $o.inflight) { - $o.occ = 'UNREADABLE'; return [pscustomobject]$o - } elseif ($o.inflight -lt $SERIAL_MIN_INFLIGHT) { - $o.occ = 'SERIAL' - } else { - $o.occ = 'AT-CAPACITY' - } + $o.inflight = [int]$inf + $o.occ = 'STATED' $tipMoved = (Test-Has $rec 'tipNow') -and (Test-Has $rec 'tipAtStated') -and $rec.tipNow -and $rec.tipAtStated -and ($rec.tipNow -ne $rec.tipAtStated) @@ -126,16 +121,14 @@ function Get-LaneVerdict($rec, [string]$boxKey) { ($null -ne $o.ageMin -and $o.ageMin -gt $STALE_MIN)) { $o.conf = 'STALE' } else { $o.conf = 'FRESH' } - $o.greenable = ($o.occ -eq 'AT-CAPACITY' -and $o.conf -eq 'FRESH') return [pscustomobject]$o } $BLIND = @' - CANNOT SEE: whether the number is right, whether an item concluded since it was typed, whether a hold - makes those slots undispatchable, or anything about a lane that writes no record. Every number above - is a QUOTE from that lane, never a measurement, and never derived from claims. OPEN clears when the - LANE RESTATES, not when you dispatch -- if you dispatched and OPEN persists, that lane has not taken - a turn yet. + CANNOT SEE: whether the number is right, whether an item concluded since it was typed, or anything + about a lane that writes no record. Every number above is a QUOTE from that lane, never a + measurement, and never derived from claims. A number changes when the LANE RESTATES, not when you + dispatch. There is no item ceiling, so no count above is a reason to stop briefing. '@ if ($SelfTest) { @@ -149,29 +142,30 @@ if ($SelfTest) { } $v = Get-LaneVerdict (New-Rec 3 1 'x' 'x' 0) 'b1' - if ($v.occ -ne 'OPEN') { $fail += "3 free must be OPEN, got $($v.occ)" } - if ($v.greenable) { $fail += 'an OPEN lane must never be greenable' } + if ($v.occ -ne 'STATED' -or $v.free -ne 3 -or $v.inflight -ne 1) { $fail += "3 free / 1 in flight must be STATED 3/1, got $($v.occ) $($v.free)/$($v.inflight)" } - $v = Get-LaneVerdict (New-Rec 0 3 'x' 'x' 0) 'b2' - if (-not $v.greenable) { $fail += "0 free / 3 in flight fresh must be greenable, got $($v.occ)/$($v.conf)" } + # The retired ceiling's own case: an old record naming capacity 4, with 0 free and 4 in flight. + $v = Get-LaneVerdict (New-Rec 0 4 'x' 'x' 0) 'b2' + if ($v.occ -ne 'STATED' -or $v.conf -ne 'FRESH') { $fail += "0 free / 4 in flight must be STATED FRESH, never full, got $($v.occ)/$($v.conf)" } + if ($v.PSObject.Properties.Name -contains 'cap') { $fail += 'a verdict must carry no capacity' } $v = Get-LaneVerdict $null 'b3' - if ($v.occ -ne 'NO-RECORD' -or $v.greenable) { $fail += 'a missing record must be NO-RECORD and never greenable' } + if ($v.occ -ne 'NO-RECORD') { $fail += 'a missing record must be NO-RECORD' } $v = Get-LaneVerdict 'UNREADABLE' 'b4' - if ($v.occ -ne 'UNREADABLE' -or $v.greenable) { $fail += 'an unreadable record must never be greenable' } + if ($v.occ -ne 'UNREADABLE') { $fail += 'an unreadable record must be UNREADABLE' } $v = Get-LaneVerdict (New-Rec 0 3 'aaa' 'bbb' 0) 'b5' - if ($v.conf -ne 'SUPERSEDED' -or $v.greenable) { $fail += "a moved tip must be SUPERSEDED, got $($v.conf)" } + if ($v.conf -ne 'SUPERSEDED') { $fail += "a moved tip must be SUPERSEDED, got $($v.conf)" } $v = Get-LaneVerdict (New-Rec 0 3 'x' 'x' 99) 'b6' - if ($v.conf -ne 'STALE' -or $v.greenable) { $fail += "99 turns must be STALE, got $($v.conf)" } + if ($v.conf -ne 'STALE') { $fail += "99 turns must be STALE, got $($v.conf)" } - $v = Get-LaneVerdict (New-Rec 0 1 'x' 'x' 0) 'b7' - if ($v.occ -ne 'SERIAL' -or $v.greenable) { $fail += "0 free / 1 in flight must be SERIAL, got $($v.occ)" } + $v = Get-LaneVerdict (New-Rec 2 $null 'x' 'x' 0) 'b7' + if ($v.occ -ne 'UNREADABLE') { $fail += "a level with no in-flight count must be UNREADABLE, got $($v.occ)" } if ($fail.Count) { $fail | ForEach-Object { Write-Host "FAIL: $_" }; exit 1 } - Write-Host 'lane-level self-test: 7 assertions pass, 6 of them asserting a state that must NOT be greenable.' + Write-Host 'lane-level self-test: 7 cases pass, and none of them can read a lane as full.' exit 0 } @@ -316,7 +310,7 @@ foreach ($c in $cands) { # so a lane silent for hours is one nobody is dispatching to. Measured today the live lanes sat at # 0.1h and 0.2h and the next candidate at 10.2h -- a real gap, not a threshold fitted to noise. # If this ever DROPS a live lane, `discovered` falls below `expected` and the ROSTER DRIFT block - # fires. Narrowing can therefore cost noise; it can never manufacture a green. + # fires. Narrowing can therefore cost noise; it can never hide a lane silently. if (((([datetime]::UtcNow) - $c.at).TotalHours) -gt 6) { continue } $lanes += $c } @@ -327,7 +321,7 @@ foreach ($c in $cands) { # # THE ASYMMETRY IS THE WHOLE POINT: a lane is dropped ONLY when the call is Evaluable AND says dead. # When Evaluable is false the lane is KEPT and marked LIVENESS-UNKNOWN, which speaks. An unevaluated -# fence is not a passed fence, so doubt can shrink the noise but can never manufacture a green. +# fence is not a passed fence, so doubt can shrink the noise but can never hide a lane silently. $live = @() $unknown = @() try { @@ -352,21 +346,11 @@ $verdicts = @() foreach ($l in $lanes) { $verdicts += Get-LaneVerdict (Read-LaneFor $l.worktree $l.host) $l.box } $discovered = $verdicts.Count -$notGreen = @($verdicts | Where-Object { -not $_.greenable }).Count $unknownLive = @($verdicts | Where-Object { $unknownBoxes -contains $_.box }).Count -# A lane whose liveness could not be evaluated blocks green even if it states itself full: the -# statement may be from a session that is gone. -$green = ($discovered -eq $EXPECTED_LANES) -and ($discovered -gt 0) -and ($notGreen -eq 0) -and ($unknownLive -eq 0) - -if ($green) { - $bits = ($verdicts | ForEach-Object { - "$($_.box) $($_.free) free / $($_.inflight) in flight of $($_.cap) (stated $(Format-Age $_.ageMin) ago)" }) -join ' | ' - Say "[lane] BOTH LANES AT CAPACITY BY THEIR OWN STATEMENT: $bits. Quoted, not measured." - Emit -} -$below = @($verdicts | Where-Object { $_.occ -ne 'AT-CAPACITY' }).Count -Say "[lane] $below of $discovered LANES BELOW CAPACITY BY THEIR OWN STATEMENT." +# NO VERDICT OF FULLNESS, AND NO SILENCE BOUGHT BY ONE. There is no item ceiling (see the header), so +# the counts are reported as quoted and the hook speaks on every dispatcher turn. +Say "[lane] $discovered LANES, EACH NUMBER BY ITS OWN STATEMENT. There is no item ceiling." foreach ($v in ($verdicts | Sort-Object box)) { $lbl = ("$($v.occ) $($v.conf)").Trim() $pad = '{0,-28} {1,-20}' -f $v.box, $lbl @@ -374,24 +358,21 @@ foreach ($v in ($verdicts | Sort-Object box)) { 'NO-RECORD' { Say " $pad never wrote a level, and its own hook never prompted it" Say (' {0,-28} {1,-20} either. That points at the HOOK, not the lane. Nothing is' -f '', '') - Say (' {0,-28} {1,-20} known here: not zero, and not full.' -f '', '') } + Say (' {0,-28} {1,-20} known here, not even zero.' -f '', '') } 'NEVER-STATED' { Say " $pad prompted on its own turns and never answered. The hook runs" Say (' {0,-28} {1,-20} there; the number is not coming. Ask the lane directly.' -f '', '') } 'UNREADABLE' { Say " $pad its level file did not parse. NO verdict for this lane, and" Say (' {0,-28} {1,-20} this is NOT a pass.' -f '', '') } - 'SERIAL' { - Say " $pad $($v.free) free / $($v.inflight) in flight of $($v.cap), stated $(Format-Age $v.ageMin) ago." - Say (' {0,-28} {1,-20} OCCUPANCY CANNOT SEE A SERIAL LANE -- it reads as full.' -f '', '') } default { $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } - Say " $pad $($v.free) free / $($v.inflight) in flight of $($v.cap), stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" } + Say " $pad $($v.inflight) in flight, $($v.free) free by work, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" } } if ($v.conf -eq 'SUPERSEDED') { Say (' {0,-28} {1,-20} that lane has committed or changed claims since it stated' -f '', '') Say (' {0,-28} {1,-20} that number, so the number is NOT current. Ask it.' -f '', '') } - if ($v.conf -eq 'STALE') { Say (' {0,-28} {1,-20} STALE IS NOT FULL.' -f '', '') } + if ($v.conf -eq 'STALE') { Say (' {0,-28} {1,-20} STALE: the number may no longer hold. Ask it.' -f '', '') } if ($unknownBoxes -contains $v.box) { Say (' {0,-28} {1,-20} LIVENESS-UNKNOWN: too few activity signals could be read, so I' -f '', '') Say (' {0,-28} {1,-20} cannot say this lane is alive. An unevaluated fence is not a' -f '', '') diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py new file mode 100644 index 0000000000..a2c62760c1 --- /dev/null +++ b/tests/test_coord_lane_no_ceiling.py @@ -0,0 +1,221 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 MessageFoundry Foundation, LLC and contributors +"""The lane tooling carries no item ceiling (owner ruling 2026-10-26). + +``scripts/coord/lane.ps1`` defaulted ``-Capacity`` to 4 and wrote it into every lane record, and +``scripts/hooks/lane-level.ps1`` read a lane stating no free items as AT CAPACITY. The owner retired +the four-item lane ceiling, the start-throttle by burn, and any WIP limit on briefing: Builders and +Managers work at full speed, and usage is governed only by the Steward's holds and the korus +Workflow gate. + +These tests fail if a default cap of 4 comes back in either script: + +1. a statement records no ``capacity`` and prints no ceiling, and one carried on an old record is + dropped on the next write; +2. ``-Capacity`` is not a parameter any more, so a caller cannot reintroduce a cap by passing one; +3. the dispatcher arm of the hook reports a lane that states 0 free and 4 in flight, on a record + still naming capacity 4, with its counts and with no capacity verdict. Against the scripts before + this change it printed ``AT-CAPACITY`` and ``of 4``; +4. the hook's own self-test passes. +""" + +from __future__ import annotations + +import json +import os +import shutil +import subprocess +import time +from pathlib import Path +from typing import Any + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +LANE = ROOT / "scripts" / "coord" / "lane.ps1" +HOOK = ROOT / "scripts" / "hooks" / "lane-level.ps1" +MAIL_KEY = ROOT / "scripts" / "coord" / "mail-key.ps1" +TIMEOUT = 90 + +pytestmark = pytest.mark.skipif( + shutil.which("pwsh") is None or os.name != "nt", + reason="the coordination scripts need pwsh on Windows", +) + + +def _env(root: Path | None = None) -> dict[str, str]: + env = { + k: v + for k, v in os.environ.items() + if k not in ("MEFOR_COORD_ROOT", "CLAUDE_CODE_SESSION_ID") + } + if root is not None: + env["MEFOR_COORD_ROOT"] = str(root) + return env + + +def _ps( + script: Path, cwd: Path, *args: str, root: Path | None = None, stdin: str = "" +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["pwsh", "-NoProfile", "-NonInteractive", "-File", str(script), *args], + cwd=str(cwd), + input=stdin, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=TIMEOUT, + check=False, + env=_env(root), + ) + + +def _git_repo(path: Path) -> Path: + path.mkdir(parents=True, exist_ok=True) + for args in ( + ["init", "-q", "-b", "main"], + ["config", "user.email", "t@example.invalid"], + ["config", "user.name", "t"], + ["commit", "-q", "--allow-empty", "-m", "base"], + ): + subprocess.run(["git", "-C", str(path), *args], check=True, timeout=TIMEOUT) + return path + + +def _lane_record(repo: Path) -> Path: + files = sorted((repo / ".git" / "mefor-coord" / "lanes").glob("*.json")) + assert len(files) == 1, f"expected exactly one lane record, found {[p.name for p in files]}" + return files[0] + + +# ------------------------------------------------------------------------------------- lane.ps1 + + +def test_a_statement_records_no_capacity_and_prints_no_ceiling(tmp_path: Path) -> None: + repo = _git_repo(tmp_path / "repo") + proc = _ps(LANE, repo, "-Free", "0", "-InFlight", "9", "-Note", "n") + assert proc.returncode == 0, proc.stdout + proc.stderr + rec = json.loads(_lane_record(repo).read_text(encoding="utf-8")) + assert "capacity" not in rec, rec + # The control: the statement itself was recorded, nine in flight and all. + assert rec["statedFree"] == 0 and rec["statedInFlight"] == 9, rec + assert "0 free / 9 in flight," in proc.stdout, proc.stdout + assert " of " not in proc.stdout.split("stated")[0], proc.stdout + + +def test_a_capacity_left_on_an_old_record_is_dropped_on_the_next_write(tmp_path: Path) -> None: + repo = _git_repo(tmp_path / "repo") + assert _ps(LANE, repo, "-Free", "1", "-InFlight", "1").returncode == 0 + record = _lane_record(repo) + old: dict[str, Any] = json.loads(record.read_text(encoding="utf-8")) + old["capacity"] = 4 + record.write_text(json.dumps(old), encoding="utf-8") + # The control: -Show is a strict read and prints the old record as it is. + show = _ps(LANE, repo, "-Show") + assert show.returncode == 0, show.stderr + assert json.loads(show.stdout)["capacity"] == 4 + + for args in (("-Asked",), ("-Free", "0", "-InFlight", "5")): + record.write_text(json.dumps(old), encoding="utf-8") + proc = _ps(LANE, repo, *args) + assert proc.returncode == 0, (args, proc.stdout + proc.stderr) + assert "capacity" not in json.loads(record.read_text(encoding="utf-8")), args + + +def test_capacity_is_no_longer_a_parameter(tmp_path: Path) -> None: + repo = _git_repo(tmp_path / "repo") + proc = _ps(LANE, repo, "-Free", "0", "-InFlight", "4", "-Capacity", "4") + assert proc.returncode != 0, proc.stdout + assert "Capacity" in proc.stderr, proc.stderr + assert not (repo / ".git" / "mefor-coord" / "lanes").exists(), "a refused call wrote a lane" + + +# --------------------------------------------------------------------------------- lane-level.ps1 + + +def _box_key(path: Path) -> str: + """The box key from the real mail-key.ps1, never a Python copy of it.""" + proc = subprocess.run( + [ + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + f". '{MAIL_KEY}'; ConvertTo-BoxKey -Path $env:BK_PATH", + ], + capture_output=True, + text=True, + timeout=TIMEOUT, + check=True, + env=dict(os.environ, BK_PATH=str(path)), + ) + return proc.stdout.strip() + + +def _plant_seat(seats: Path, box: str, worktree: Path | str, seat: str) -> None: + d = seats / box + d.mkdir(parents=True, exist_ok=True) + rec = { + "schema": 1, + "asOf": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "lifecycle": "open", + "boxKey": box, + "worktree": str(worktree), + "sessionKey": f"s-{box}", + "seat": seat, + "branch": "main", + "claims": [], + "allocations": [], + "handoff": None, + } + (d / f"s-{box}.json").write_text(json.dumps(rec), encoding="utf-8") + + +def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: + shared = tmp_path / "shared" / "mefor-coord" + seats = shared / "seats" + lanes = shared / "lanes" + lanes.mkdir(parents=True) + dispatcher = _git_repo(tmp_path / "dispatcher-lane") + tree = tmp_path / "builder-tree" + tree.mkdir() + + box = _box_key(tree) + _plant_seat(seats, box, tree, "builder") + # The hook reads no verdict below a floor of five seat directories. These name no live tree. + for i in range(4): + _plant_seat(seats, f"filler-{i:04d}", "C:/no/such/tree", "manager") + # The retired ceiling's own case, on a record written before the change: 0 free, 4 in flight, + # capacity 4, stated just now with nothing moved since. + now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + lane: dict[str, Any] = { + "v": 1, + "capacity": 4, + "statedFree": 0, + "statedInFlight": 4, + "statedUtc": now, + "statedNote": "four in flight", + "turnsSinceStated": 0, + "tipAtStated": "x", + "tipNow": "x", + "claimHashAtStated": "a", + "claimHashNow": "a", + } + (lanes / f"{box}.json").write_text(json.dumps(lane), encoding="utf-8") + + proc = _ps(HOOK, dispatcher, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) + assert proc.returncode == 0, proc.stderr + text = str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) + + # The control: the lane was discovered and its counts read. + assert "1 LANES" in text, text + assert "4 in flight, 0 free by work" in text, text + assert "capacity" not in text.lower(), text + assert "of 4" not in text, text + + +def test_the_hook_self_test_passes(tmp_path: Path) -> None: + proc = _ps(HOOK, tmp_path, "-SelfTest") + assert proc.returncode == 0, proc.stdout + proc.stderr + assert "none of them can read a lane as full" in proc.stdout, proc.stdout diff --git a/tests/test_coord_lane_statement.py b/tests/test_coord_lane_statement.py index 6d0dfb8b05..d71fa2bfa1 100644 --- a/tests/test_coord_lane_statement.py +++ b/tests/test_coord_lane_statement.py @@ -298,7 +298,7 @@ def test_a_write_over_an_unreadable_record_is_refused_and_leaves_it( _BLANK_KEYS = { - "v", "boxKey", "worktree", "capacity", "statedFree", "statedInFlight", "statedUtc", + "v", "boxKey", "worktree", "statedFree", "statedInFlight", "statedUtc", "statedBySessionId", "statedNote", "tipAtStated", "claimHashAtStated", "turnsSinceStated", "tipNow", "claimHashNow", "lastTurnUtc", "promptedUtc", "promptCount", "askedByDispatcherUtc", } # fmt: skip From 893d24ae58e645827d510a379b5f234972555fbc Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 13:57:21 -0500 Subject: [PATCH 2/8] Order the no-ceiling hook test so the ceiling assertion is what fails Against the pre-change scripts the dispatcher-report test failed on its count-format check before it reached the capacity check. The control is now the lane's note, which both versions print, and the capacity assertions come next, so a returning ceiling is what turns it red. Measured: line 214, the capacity assertion, fails on origin/main's scripts; all five tests pass on this branch. --- tests/test_coord_lane_no_ceiling.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index a2c62760c1..e31b5e6177 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -208,11 +208,12 @@ def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: assert proc.returncode == 0, proc.stderr text = str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) - # The control: the lane was discovered and its counts read. - assert "1 LANES" in text, text - assert "4 in flight, 0 free by work" in text, text + # The control: the lane was discovered and its record read, before and after this change alike. + assert 'Note: "four in flight"' in text, text + # The ceiling checks come first, so against the old scripts they are what fails. assert "capacity" not in text.lower(), text assert "of 4" not in text, text + assert "4 in flight, 0 free by work" in text, text def test_the_hook_self_test_passes(tmp_path: Path) -> None: From a6b3f2fc63aacb3be835c29dc370e1bfe73d6fe6 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 14:12:09 -0500 Subject: [PATCH 3/8] Keep capacity as null, add IDLE, and drop the count bounds Round-one code-review fixes. Dropping the capacity key broke an older lane.ps1 run from another branch: it assigns $rec.capacity, which throws under StrictMode when the key is missing. Measured: the old writer exits 1 on a record without the key and 0 with it as null. So lane.ps1 now writes capacity as null on every write. Also: -Free and -InFlight take any non-negative count; the hook reads a lane with nothing in flight as IDLE; ROSTER DRIFT fires only on a shortfall of lanes; the builder nag says to count no empty slots; and the ruling is stated once, in lane.ps1's header. --- scripts/coord/lane.ps1 | 34 ++++++++++++++++---------- scripts/hooks/lane-level.ps1 | 33 ++++++++++++++----------- tests/test_coord_lane_no_ceiling.py | 37 ++++++++++++++++++----------- tests/test_coord_lane_statement.py | 2 +- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index 53dbf6f325..d5f5f0ccb6 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -10,11 +10,13 @@ Nothing else in the fleet may write those numbers -- not a Manager, not a hook, not an inference from claims. - THERE IS NO ITEM CEILING (owner ruling 2026-10-26). The four-item lane ceiling, and the - -Capacity parameter that defaulted to it, are retired: Builders and Managers work at full speed, - and usage is governed only by the Steward's holds and the korus Workflow gate. Nothing this - script records is a cap, a slot count or a reason to stop briefing or building. Do not add a - capacity back, and do not add a usage throttle here; usage is the Steward's. + THERE IS NO ITEM CEILING (owner ruling 2026-10-26). This is the one place the tooling states + it; lane-level.ps1 and tests/test_coord_lane_no_ceiling.py point here. The four-item lane + ceiling, the start-throttle by burn, any WIP limit on briefing, and the -Capacity parameter + that defaulted to 4 are retired. Builders and Managers work at full speed; usage is governed by + the Steward's holds and the korus Workflow gate, never by this script. Nothing it records is a + cap, a slot count or a reason to stop briefing or building. Do not add a capacity back, and do + not add a usage throttle here. THERE IS NO -Lane AND NO -BoxKey PARAMETER, AND ADDING ONE DEFEATS THE WHOLE DESIGN. The box key is derived from $PWD, so a session can only ever state its OWN level. The failure this replaces is a @@ -25,7 +27,8 @@ FREE MEANS BY WORK, NOT BY CLAIMS. Built-and-awaiting-land is FREE. Blocked is FREE. Parked on a condition is FREE. A claim you are not currently able to progress is FREE work wearing an occupied label, and that mislabelling is the outage. With no ceiling there are no empty slots, - so -Free counts exactly those held items and nothing else. + so -Free counts those held items and nothing else. A level stated before 2026-10-26 may also + count empty slots under the retired ceiling; the hook asks for a fresh level within 15 minutes. #> [CmdletBinding(DefaultParameterSetName = 'State')] param( @@ -33,8 +36,9 @@ param( # set without both, and a Mandatory prompt there hangs a caller with stdin open # (tests/_no_prompt.py, vault BACKLOG #3627). The refusal follows this param block, after the # cloud refusal. - [Parameter(ParameterSetName = 'State')][ValidateRange(0, 99)][int]$Free, - [Parameter(ParameterSetName = 'State')][ValidateRange(0, 99)][int]$InFlight, + # Non-negative and unbounded above: an upper bound here would be a ceiling by another name. + [Parameter(ParameterSetName = 'State')][ValidateRange('NonNegative')][int]$Free, + [Parameter(ParameterSetName = 'State')][ValidateRange('NonNegative')][int]$InFlight, [Parameter(ParameterSetName = 'State')][string]$Note = '', # Stamp that a dispatcher asked. Carries NO number. [Parameter(ParameterSetName = 'Asked', Mandatory)][switch]$Asked, @@ -148,7 +152,7 @@ if (-not $Show -and $rec -and -not (Test-CoordRecordIsHere $rec)) { } if (-not $rec) { $rec = [pscustomobject]@{ - v = 1; boxKey = $boxKey; worktree = $worktree + v = 1; boxKey = $boxKey; worktree = $worktree; capacity = $null statedFree = $null; statedInFlight = $null; statedUtc = $null statedBySessionId = $null; statedNote = '' tipAtStated = ''; claimHashAtStated = ''; turnsSinceStated = 0 @@ -183,7 +187,7 @@ if ($Show) { # between the two reads would print a stated level beside an `absent` reason. The fields # match the blank record above; fold the two together once that read keeps its reason. $shown = [pscustomobject]@{ - v = 1; boxKey = $boxKey; worktree = $worktree + v = 1; boxKey = $boxKey; worktree = $worktree; capacity = $null statedFree = $null; statedInFlight = $null; statedUtc = $null statedBySessionId = $null; statedNote = '' tipAtStated = ''; claimHashAtStated = ''; turnsSinceStated = 0 @@ -245,9 +249,13 @@ if ($Asked) { } $rec.worktree = $worktree $rec.boxKey = $boxKey -# A record written before the ceiling was retired carries `capacity`, 4 by default. Drop it on the -# next write, so no stored lane level still names a cap for a reader to apply. -$rec.PSObject.Properties.Remove('capacity') +# `capacity` IS WRITTEN AS NULL, NEVER DROPPED. An older lane.ps1, run from another worktree's branch, +# assigns $rec.capacity on every statement, and under StrictMode that throws when the property is +# missing, so that lane could not restate. Null is a value that names no cap; a record written before +# the ceiling was retired holds 4 there until this next write replaces it. This branch's +# lane-level.ps1 reads none of it. An older copy of that hook falls back to 4 whatever this field +# holds, so only replacing that reader retires the ceiling there. +$rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force # docs/COORD-SHARED-ROOT.md: a reader on another PC must be able to tell this lane is not its own. $rec | Add-Member -NotePropertyName host -NotePropertyValue $thisHost -Force diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index 3c30bac779..2bd4acaa60 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -6,12 +6,11 @@ it reports each lane's stated counts, and it never calls a lane full. .DESCRIPTION - THERE IS NO ITEM CEILING (owner ruling 2026-10-26). This hook used to call a lane AT CAPACITY - when it stated no free items, against a four-item ceiling, and went quiet when both lanes said - so. The ceiling, the start-throttle by burn and any WIP limit on briefing are retired: Builders - and Managers work at full speed, and usage is governed only by the Steward's holds and the korus - Workflow gate. So no count this hook reads ever becomes a verdict that a lane is full or that - briefing should stop, and it adds no usage throttle of its own. + THERE IS NO ITEM CEILING. The owner ruling of 2026-10-26 is stated once, in the header of + scripts/coord/lane.ps1. This hook used to call a lane AT CAPACITY when it stated no free items, + against a four-item ceiling, and went quiet when both lanes said so. Now no count it reads + becomes a verdict that a lane is full or that briefing should stop, and it adds no usage + throttle of its own. The one count-driven word left is IDLE, for a lane with nothing in flight. EVERY NUMBER IS A LANE'S OWN QUOTE. A missing file, an unreadable file, a stale number, a superseded number, a roster shortfall -- each is said aloud, never turned into a count. @@ -84,9 +83,9 @@ function Test-Has($rec, [string]$name) { } function Get-LaneVerdict($rec, [string]$boxKey) { - # One state word and one confidence word, or a single non-answer word. STATED is the only state - # a readable level gets, whatever its numbers: no count makes a lane full. A `capacity` field on - # an old record is not read, for the same reason. + # One state word and one confidence word, or a single non-answer word. A readable level is IDLE + # when nothing is in flight and STATED otherwise, whatever its numbers: no count makes a lane + # full. A `capacity` field on an old record is not read, for the same reason. $o = [ordered]@{ box = $boxKey; occ = ''; conf = ''; free = $null; inflight = $null ageMin = $null; turns = $null; note = '' } @@ -109,7 +108,7 @@ function Get-LaneVerdict($rec, [string]$boxKey) { $o.free = [int]$free $o.inflight = [int]$inf - $o.occ = 'STATED' + $o.occ = if ($o.inflight -eq 0) { 'IDLE' } else { 'STATED' } $tipMoved = (Test-Has $rec 'tipNow') -and (Test-Has $rec 'tipAtStated') -and $rec.tipNow -and $rec.tipAtStated -and ($rec.tipNow -ne $rec.tipAtStated) @@ -164,8 +163,11 @@ if ($SelfTest) { $v = Get-LaneVerdict (New-Rec 2 $null 'x' 'x' 0) 'b7' if ($v.occ -ne 'UNREADABLE') { $fail += "a level with no in-flight count must be UNREADABLE, got $($v.occ)" } + $v = Get-LaneVerdict (New-Rec 0 0 'x' 'x' 0) 'b8' + if ($v.occ -ne 'IDLE') { $fail += "0 in flight must be IDLE, got $($v.occ)" } + if ($fail.Count) { $fail | ForEach-Object { Write-Host "FAIL: $_" }; exit 1 } - Write-Host 'lane-level self-test: 7 cases pass, and none of them can read a lane as full.' + Write-Host 'lane-level self-test: 8 cases pass, and none of them can read a lane as full.' exit 0 } @@ -248,7 +250,8 @@ if ($leaf -match '^builder') { Say '[lane] Your lane level is unstated or stale. State it now, it takes five seconds:' Say ' pwsh -NoProfile -File scripts\coord\lane.ps1 -Free -InFlight -Note ""' Say ' FREE MEANS BY WORK, NOT BY CLAIMS. Built-and-awaiting-land is FREE. Blocked is FREE.' - Say ' Parked on a condition is FREE. Nobody else can write this number for you.' + Say ' Parked on a condition is FREE. There is no item ceiling, so count no empty slots.' + Say ' Nobody else can write this number for you.' } Emit } @@ -378,8 +381,10 @@ foreach ($v in ($verdicts | Sort-Object box)) { Say (' {0,-28} {1,-20} cannot say this lane is alive. An unevaluated fence is not a' -f '', '') Say (' {0,-28} {1,-20} passed one, and any level above may be from a session that is gone.' -f '', '') } } -Say " coverage: $discovered lanes discovered, $EXPECTED_LANES expected, $unknownLive with liveness unknown." -if ($discovered -ne $EXPECTED_LANES) { +Say " coverage: $discovered lanes discovered, at least $EXPECTED_LANES expected, $unknownLive with liveness unknown." +# A FLOOR, NOT A CAP. A shortfall is what a lane invisible to discovery looks like. More lanes than +# that is ordinary at full speed and says nothing about a missing one. +if ($discovered -lt $EXPECTED_LANES) { Say ' ROSTER DRIFT: a lane with every hook off writes no seat record and no level, so it is' Say " invisible to discovery too. Do not read '$discovered of $discovered' as healthy." } diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index e31b5e6177..9062d6539c 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -2,16 +2,15 @@ # Copyright (C) 2026 MessageFoundry Foundation, LLC and contributors """The lane tooling carries no item ceiling (owner ruling 2026-10-26). -``scripts/coord/lane.ps1`` defaulted ``-Capacity`` to 4 and wrote it into every lane record, and -``scripts/hooks/lane-level.ps1`` read a lane stating no free items as AT CAPACITY. The owner retired -the four-item lane ceiling, the start-throttle by burn, and any WIP limit on briefing: Builders and -Managers work at full speed, and usage is governed only by the Steward's holds and the korus -Workflow gate. +The ruling is stated once, in the header of ``scripts/coord/lane.ps1``. That script defaulted +``-Capacity`` to 4 and wrote it into every lane record, and ``scripts/hooks/lane-level.ps1`` read a +lane stating no free items as AT CAPACITY. These tests fail if a default cap of 4 comes back in either script: -1. a statement records no ``capacity`` and prints no ceiling, and one carried on an old record is - dropped on the next write; +1. a statement records ``capacity`` as null, with no upper bound on its counts, and prints no + ceiling; a 4 carried on an old record is replaced with null on the next write. The key stays, + because an older lane.ps1 on another branch assigns it and throws under StrictMode without it; 2. ``-Capacity`` is not a parameter any more, so a caller cannot reintroduce a cap by passing one; 3. the dispatcher arm of the hook reports a lane that states 0 free and 4 in flight, on a record still naming capacity 4, with its counts and with no capacity verdict. Against the scripts before @@ -94,17 +93,26 @@ def _lane_record(repo: Path) -> Path: def test_a_statement_records_no_capacity_and_prints_no_ceiling(tmp_path: Path) -> None: repo = _git_repo(tmp_path / "repo") - proc = _ps(LANE, repo, "-Free", "0", "-InFlight", "9", "-Note", "n") + # 150 is past the old ValidateRange(0, 99) on both counts: no upper bound may come back either. + proc = _ps(LANE, repo, "-Free", "150", "-InFlight", "150", "-Note", "n") assert proc.returncode == 0, proc.stdout + proc.stderr rec = json.loads(_lane_record(repo).read_text(encoding="utf-8")) - assert "capacity" not in rec, rec - # The control: the statement itself was recorded, nine in flight and all. - assert rec["statedFree"] == 0 and rec["statedInFlight"] == 9, rec - assert "0 free / 9 in flight," in proc.stdout, proc.stdout + assert "capacity" in rec and rec["capacity"] is None, rec + # The control: the statement itself was recorded, counts and all. + assert rec["statedFree"] == 150 and rec["statedInFlight"] == 150, rec + assert "150 free / 150 in flight," in proc.stdout, proc.stdout assert " of " not in proc.stdout.split("stated")[0], proc.stdout -def test_a_capacity_left_on_an_old_record_is_dropped_on_the_next_write(tmp_path: Path) -> None: +def test_a_negative_count_is_still_refused(tmp_path: Path) -> None: + """The control for the unbounded range: the lower bound still holds.""" + repo = _git_repo(tmp_path / "repo") + proc = _ps(LANE, repo, "-Free", "-1", "-InFlight", "0") + assert proc.returncode != 0, proc.stdout + assert not (repo / ".git" / "mefor-coord" / "lanes").exists(), "a refused call wrote a lane" + + +def test_a_capacity_left_on_an_old_record_is_nulled_on_the_next_write(tmp_path: Path) -> None: repo = _git_repo(tmp_path / "repo") assert _ps(LANE, repo, "-Free", "1", "-InFlight", "1").returncode == 0 record = _lane_record(repo) @@ -120,7 +128,8 @@ def test_a_capacity_left_on_an_old_record_is_dropped_on_the_next_write(tmp_path: record.write_text(json.dumps(old), encoding="utf-8") proc = _ps(LANE, repo, *args) assert proc.returncode == 0, (args, proc.stdout + proc.stderr) - assert "capacity" not in json.loads(record.read_text(encoding="utf-8")), args + new = json.loads(record.read_text(encoding="utf-8")) + assert "capacity" in new and new["capacity"] is None, (args, new) def test_capacity_is_no_longer_a_parameter(tmp_path: Path) -> None: diff --git a/tests/test_coord_lane_statement.py b/tests/test_coord_lane_statement.py index d71fa2bfa1..6d0dfb8b05 100644 --- a/tests/test_coord_lane_statement.py +++ b/tests/test_coord_lane_statement.py @@ -298,7 +298,7 @@ def test_a_write_over_an_unreadable_record_is_refused_and_leaves_it( _BLANK_KEYS = { - "v", "boxKey", "worktree", "statedFree", "statedInFlight", "statedUtc", + "v", "boxKey", "worktree", "capacity", "statedFree", "statedInFlight", "statedUtc", "statedBySessionId", "statedNote", "tipAtStated", "claimHashAtStated", "turnsSinceStated", "tipNow", "claimHashNow", "lastTurnUtc", "promptedUtc", "promptCount", "askedByDispatcherUtc", } # fmt: skip From 0a53e43150d2c0acfdad9f9cbb70d941b66c0166 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 14:24:26 -0500 Subject: [PATCH 4/8] Read a lane level's age in UTC, and keep the hold caveat Round-two code-review fixes. lane-level.ps1's Get-AgeMin passed the [datetime] ConvertFrom-Json returns back through [datetime]::Parse, which re-read it as local time and added the offset again, so a level stated 45 minutes ago read as hours in the future and the STALE check and the builder re-prompt never fired west of UTC. It now mirrors the fix lane.ps1 already carries at its statedUtc guard. New tests: a builder lane is asked again at 45 minutes and not at 0, and the dispatcher report never prints a negative age. Also: the hook's CANNOT SEE text again names Steward holds, and says a hold, not a count, is what stops briefing; ROSTER DRIFT goes back to firing on any count but the expected one; the UNREADABLE line names a missing count; and lane.ps1's comments say what actually throws and which lanes the hook re-prompts. --- scripts/coord/lane.ps1 | 18 ++++++++------ scripts/hooks/lane-level.ps1 | 38 ++++++++++++++++++++--------- tests/test_coord_lane_no_ceiling.py | 29 ++++++++++++++++++++-- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index d5f5f0ccb6..31f0c03606 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -10,8 +10,8 @@ Nothing else in the fleet may write those numbers -- not a Manager, not a hook, not an inference from claims. - THERE IS NO ITEM CEILING (owner ruling 2026-10-26). This is the one place the tooling states - it; lane-level.ps1 and tests/test_coord_lane_no_ceiling.py point here. The four-item lane + THERE IS NO ITEM CEILING (owner ruling 2026-10-26). This header is the tooling's record of the + ruling; lane-level.ps1 and tests/test_coord_lane_no_ceiling.py cite it. The four-item lane ceiling, the start-throttle by burn, any WIP limit on briefing, and the -Capacity parameter that defaulted to 4 are retired. Builders and Managers work at full speed; usage is governed by the Steward's holds and the korus Workflow gate, never by this script. Nothing it records is a @@ -28,7 +28,8 @@ condition is FREE. A claim you are not currently able to progress is FREE work wearing an occupied label, and that mislabelling is the outage. With no ceiling there are no empty slots, so -Free counts those held items and nothing else. A level stated before 2026-10-26 may also - count empty slots under the retired ceiling; the hook asks for a fresh level within 15 minutes. + count empty slots under the retired ceiling. lane-level.ps1 asks a lane in a worktree named + builder* for a fresh level after 15 minutes; a lane in a worktree named otherwise is never asked. #> [CmdletBinding(DefaultParameterSetName = 'State')] param( @@ -250,11 +251,12 @@ if ($Asked) { $rec.worktree = $worktree $rec.boxKey = $boxKey # `capacity` IS WRITTEN AS NULL, NEVER DROPPED. An older lane.ps1, run from another worktree's branch, -# assigns $rec.capacity on every statement, and under StrictMode that throws when the property is -# missing, so that lane could not restate. Null is a value that names no cap; a record written before -# the ceiling was retired holds 4 there until this next write replaces it. This branch's -# lane-level.ps1 reads none of it. An older copy of that hook falls back to 4 whatever this field -# holds, so only replacing that reader retires the ceiling there. +# assigns $rec.capacity on every statement, and assigning a property a PSCustomObject lacks throws, +# so that lane could not restate. Null is a value that names no cap; a record written before the +# ceiling was retired holds 4 there until this next write replaces it. This branch's lane-level.ps1 +# reads none of it. An older copy of that hook falls back to 4 whatever this field holds, and reads +# 0 free with 2 or more in flight as full, which under the new meaning of -Free it does sooner. Only +# replacing that reader, wherever it is installed, retires the ceiling there. $rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force # docs/COORD-SHARED-ROOT.md: a reader on another PC must be able to tell this lane is not its own. $rec | Add-Member -NotePropertyName host -NotePropertyValue $thisHost -Force diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index 2bd4acaa60..5e7d873fd8 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -67,8 +67,22 @@ function Read-Json([string]$Path) { } function Get-AgeMin($utc) { + # ConvertFrom-Json hands back statedUtc as a [datetime] with Kind=Utc. [datetime]::Parse on that + # stringifies it with no zone, re-reads it as local, and adds the offset again, so a stamp 45 + # minutes old read as hours in the future and the STALE and re-prompt checks never fired. + # lane.ps1 documents the same trap at its statedUtc guard; this mirrors its fix. if (-not $utc) { return $null } - try { return [math]::Round(([datetime]::UtcNow - ([datetime]::Parse($utc)).ToUniversalTime()).TotalMinutes, 0) } + try { + $at = if ($utc -is [datetime]) { + if ($utc.Kind -eq 'Local') { $utc.ToUniversalTime() } + else { [datetime]::SpecifyKind($utc, [System.DateTimeKind]::Utc) } + } else { + [datetime]::Parse([string]$utc, [cultureinfo]::InvariantCulture, + [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor + [System.Globalization.DateTimeStyles]::AssumeUniversal) + } + return [math]::Round(([datetime]::UtcNow - $at).TotalMinutes, 0) + } catch { return $null } } @@ -124,10 +138,11 @@ function Get-LaneVerdict($rec, [string]$boxKey) { } $BLIND = @' - CANNOT SEE: whether the number is right, whether an item concluded since it was typed, or anything - about a lane that writes no record. Every number above is a QUOTE from that lane, never a - measurement, and never derived from claims. A number changes when the LANE RESTATES, not when you - dispatch. There is no item ceiling, so no count above is a reason to stop briefing. + CANNOT SEE: whether the number is right, whether an item concluded since it was typed, whether a + Steward hold is in force, or anything about a lane that writes no record. Every number above is a + QUOTE from that lane, never a measurement, and never derived from claims. A number changes when the + LANE RESTATES, not when you dispatch. There is no item ceiling, so no count above is a reason to + stop briefing; a hold is, and this hook does not read holds. '@ if ($SelfTest) { @@ -313,7 +328,8 @@ foreach ($c in $cands) { # so a lane silent for hours is one nobody is dispatching to. Measured today the live lanes sat at # 0.1h and 0.2h and the next candidate at 10.2h -- a real gap, not a threshold fitted to noise. # If this ever DROPS a live lane, `discovered` falls below `expected` and the ROSTER DRIFT block - # fires. Narrowing can therefore cost noise; it can never hide a lane silently. + # fires. Narrowing can therefore cost noise; while the expected count is right, it cannot hide + # a lane silently. if (((([datetime]::UtcNow) - $c.at).TotalHours) -gt 6) { continue } $lanes += $c } @@ -353,7 +369,7 @@ $unknownLive = @($verdicts | Where-Object { $unknownBoxes -contains $_.box }).Co # NO VERDICT OF FULLNESS, AND NO SILENCE BOUGHT BY ONE. There is no item ceiling (see the header), so # the counts are reported as quoted and the hook speaks on every dispatcher turn. -Say "[lane] $discovered LANES, EACH NUMBER BY ITS OWN STATEMENT. There is no item ceiling." +Say "[lane] $discovered LANES, EACH NUMBER BY ITS OWN STATEMENT." foreach ($v in ($verdicts | Sort-Object box)) { $lbl = ("$($v.occ) $($v.conf)").Trim() $pad = '{0,-28} {1,-20}' -f $v.box, $lbl @@ -366,7 +382,7 @@ foreach ($v in ($verdicts | Sort-Object box)) { Say " $pad prompted on its own turns and never answered. The hook runs" Say (' {0,-28} {1,-20} there; the number is not coming. Ask the lane directly.' -f '', '') } 'UNREADABLE' { - Say " $pad its level file did not parse. NO verdict for this lane, and" + Say " $pad its level file did not parse, or lacks a count. NO verdict, and" Say (' {0,-28} {1,-20} this is NOT a pass.' -f '', '') } default { $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } @@ -381,10 +397,8 @@ foreach ($v in ($verdicts | Sort-Object box)) { Say (' {0,-28} {1,-20} cannot say this lane is alive. An unevaluated fence is not a' -f '', '') Say (' {0,-28} {1,-20} passed one, and any level above may be from a session that is gone.' -f '', '') } } -Say " coverage: $discovered lanes discovered, at least $EXPECTED_LANES expected, $unknownLive with liveness unknown." -# A FLOOR, NOT A CAP. A shortfall is what a lane invisible to discovery looks like. More lanes than -# that is ordinary at full speed and says nothing about a missing one. -if ($discovered -lt $EXPECTED_LANES) { +Say " coverage: $discovered lanes discovered, $EXPECTED_LANES expected, $unknownLive with liveness unknown." +if ($discovered -ne $EXPECTED_LANES) { Say ' ROSTER DRIFT: a lane with every hook off writes no seat record and no level, so it is' Say " invisible to discovery too. Do not read '$discovered of $discovered' as healthy." } diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index 9062d6539c..1662bc9959 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -10,12 +10,15 @@ 1. a statement records ``capacity`` as null, with no upper bound on its counts, and prints no ceiling; a 4 carried on an old record is replaced with null on the next write. The key stays, - because an older lane.ps1 on another branch assigns it and throws under StrictMode without it; + because an older lane.ps1 on another branch assigns it, and that throws when the key is missing; 2. ``-Capacity`` is not a parameter any more, so a caller cannot reintroduce a cap by passing one; 3. the dispatcher arm of the hook reports a lane that states 0 free and 4 in flight, on a record still naming capacity 4, with its counts and with no capacity verdict. Against the scripts before this change it printed ``AT-CAPACITY`` and ``of 4``; -4. the hook's own self-test passes. +4. the hook's own self-test passes; +5. a builder lane whose level is 45 minutes old is asked to restate. Its age is how a level stated + under the old ceiling ages out, and the hook read every stamp hours in the future on a machine + west of UTC until this change. """ from __future__ import annotations @@ -223,9 +226,31 @@ def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: assert "capacity" not in text.lower(), text assert "of 4" not in text, text assert "4 in flight, 0 free by work" in text, text + # A level stated just now is not in the future. West of UTC the old age read said -300m here. + assert "stated -" not in text, text def test_the_hook_self_test_passes(tmp_path: Path) -> None: proc = _ps(HOOK, tmp_path, "-SelfTest") assert proc.returncode == 0, proc.stdout + proc.stderr assert "none of them can read a lane as full" in proc.stdout, proc.stdout + + +NAG = "Your lane level is unstated or stale" + + +@pytest.mark.parametrize(("minutes_ago", "nags"), [(0, False), (45, True)]) +def test_a_builder_lane_is_asked_again_once_its_level_ages( + tmp_path: Path, minutes_ago: int, nags: bool +) -> None: + """The 0-minute case is the control: the nag below is the age's, not the record's shape's.""" + shared = tmp_path / "shared" / "mefor-coord" + (shared / "lanes").mkdir(parents=True) + builder = _git_repo(tmp_path / "builder-lane") + stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() - minutes_ago * 60)) + lane = {"v": 1, "statedFree": 0, "statedInFlight": 1, "statedUtc": stamp, "turnsSinceStated": 0} + (shared / "lanes" / f"{_box_key(builder)}.json").write_text(json.dumps(lane), encoding="utf-8") + + proc = _ps(HOOK, builder, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) + assert proc.returncode == 0, proc.stderr + assert (NAG in proc.stdout) is nags, proc.stdout From b6867fa3d3ecf7d7cf14784585eef68095802510 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 15:51:20 -0500 Subject: [PATCH 5/8] Classify the lane no-ceiling test as a tooling test tests/test_coord_lane_no_ceiling.py runs scripts/coord/lane.ps1 and scripts/hooks/lane-level.ps1 as real pwsh children and imports nothing from messagefoundry, so its subject is the coordination harness. It joins tests/tooling_manifest.txt beside its sibling tests/test_coord_lane_statement.py, in that block's alphabetical order. Without it, test_every_non_engine_test_is_classified reported the file as UNCLASSIFIED and turned all three test legs red. --- tests/tooling_manifest.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tooling_manifest.txt b/tests/tooling_manifest.txt index 83a79810ce..9c363219af 100644 --- a/tests/tooling_manifest.txt +++ b/tests/tooling_manifest.txt @@ -110,6 +110,7 @@ tests/test_coord_cloud_refusal.py tests/test_coord_handoff_clear.py tests/test_coord_handoff_pointer.py tests/test_coord_handoff_report.py +tests/test_coord_lane_no_ceiling.py tests/test_coord_lane_statement.py tests/test_coord_lock.py tests/test_coord_migrate.py From 6c4dafddb15acf350e5b6942f98b9fe1e52af441 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 16:14:40 -0500 Subject: [PATCH 6/8] Label a lane level an older lane.ps1 wrote, and withhold its free count The current lane.ps1 writes capacity as null on every write; an older copy writes a number there on every statement, and its free count includes the empty slots under the retired ceiling. lane-level.ps1 now reads a non-null capacity as that marker, never as a cap: the dispatcher arm shows the lane as OLD-WRITER with its in-flight count and no free count, and asks for a restatement; the builder arm asks every turn, naming the lane.ps1 beside the hook. Null or absent capacity reads as before. No cap or full verdict, and the record version is unchanged. Self-test gains an old record (capacity 4, 2 free, 2 in flight) and its null-capacity control; tests/test_coord_lane_no_ceiling.py gains the same pair for the dispatcher and builder arms. Follow-up on PR 2543 (lane ceiling retired, owner ruling 2026-10-26). --- scripts/coord/lane.ps1 | 3 +- scripts/hooks/lane-level.ps1 | 64 ++++++++++++++++--- tests/test_coord_lane_no_ceiling.py | 95 ++++++++++++++++++++++++----- 3 files changed, 138 insertions(+), 24 deletions(-) diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index 31f0c03606..edeb1df7d3 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -254,7 +254,8 @@ $rec.boxKey = $boxKey # assigns $rec.capacity on every statement, and assigning a property a PSCustomObject lacks throws, # so that lane could not restate. Null is a value that names no cap; a record written before the # ceiling was retired holds 4 there until this next write replaces it. This branch's lane-level.ps1 -# reads none of it. An older copy of that hook falls back to 4 whatever this field holds, and reads +# reads only whether it is null: a number marks a level an older lane.ps1 stated, whose free count +# includes empty slots, so it withholds that count and asks for a restatement. An older copy of that hook falls back to 4 whatever this field holds, and reads # 0 free with 2 or more in flight as full, which under the new meaning of -Free it does sooner. Only # replacing that reader, wherever it is installed, retires the ceiling there. $rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index 5e7d873fd8..ec03950d20 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -12,6 +12,17 @@ becomes a verdict that a lane is full or that briefing should stop, and it adds no usage throttle of its own. The one count-driven word left is IDLE, for a lane with nothing in flight. + AN OLDER lane.ps1 MEANS SOMETHING ELSE BY FREE. Its free count includes the empty slots under + the retired ceiling, so read as held items it overstates them. The current lane.ps1 writes + `capacity` as null on every write, and the older one writes a number there on every statement, + so a record whose `capacity` is a number has a level an older lane.ps1 stated. This hook shows + that level's in-flight count, withholds its free count, labels it OLD-WRITER and asks the lane + to restate with the current script. The number is read as a marker of who wrote the record, + never as a cap. A record whose `capacity` is null or absent is read as stated. A null does not + prove the current script stated the counts: its -Asked nulls `capacity` and leaves an older + statement's counts in place, so a level stated before 2026-10-26 can still read as held items + until the lane restates. + EVERY NUMBER IS A LANE'S OWN QUOTE. A missing file, an unreadable file, a stale number, a superseded number, a roster shortfall -- each is said aloud, never turned into a count. @@ -96,10 +107,17 @@ function Test-Has($rec, [string]$name) { return ($rec.PSObject.Properties.Name -contains $name) } +function Test-OldWriter($rec) { + # See the header: a number in `capacity` marks a record an older lane.ps1 wrote. Only whether it + # is null is read, never its value. + return ($null -ne $rec) -and ($rec -isnot [string]) -and (Test-Has $rec 'capacity') -and + ($null -ne $rec.capacity) +} + function Get-LaneVerdict($rec, [string]$boxKey) { # One state word and one confidence word, or a single non-answer word. A readable level is IDLE # when nothing is in flight and STATED otherwise, whatever its numbers: no count makes a lane - # full. A `capacity` field on an old record is not read, for the same reason. + # full. A level an older lane.ps1 wrote is OLD-WRITER and carries no free count (see the header). $o = [ordered]@{ box = $boxKey; occ = ''; conf = ''; free = $null; inflight = $null ageMin = $null; turns = $null; note = '' } @@ -120,9 +138,12 @@ function Get-LaneVerdict($rec, [string]$boxKey) { } if ($null -eq $free -or $null -eq $inf) { $o.occ = 'UNREADABLE'; return [pscustomobject]$o } - $o.free = [int]$free $o.inflight = [int]$inf - $o.occ = if ($o.inflight -eq 0) { 'IDLE' } else { 'STATED' } + if (Test-OldWriter $rec) { $o.occ = 'OLD-WRITER' } + else { + $o.free = [int]$free + $o.occ = if ($o.inflight -eq 0) { 'IDLE' } else { 'STATED' } + } $tipMoved = (Test-Has $rec 'tipNow') -and (Test-Has $rec 'tipAtStated') -and $rec.tipNow -and $rec.tipAtStated -and ($rec.tipNow -ne $rec.tipAtStated) @@ -147,22 +168,31 @@ $BLIND = @' if ($SelfTest) { $fail = @() - function New-Rec($f, $i, $t1, $t2, $turns) { + # The current lane.ps1 writes capacity as null; pass 4 for a record an older one wrote. + function New-Rec($f, $i, $t1, $t2, $turns, $cap = $null) { [pscustomobject]@{ statedFree = $f; statedInFlight = $i statedUtc = ([datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')) tipAtStated = $t1; tipNow = $t2; claimHashAtStated = 'a'; claimHashNow = 'a' - turnsSinceStated = $turns; capacity = 4; statedNote = ''; promptCount = 1 } + turnsSinceStated = $turns; capacity = $cap; statedNote = ''; promptCount = 1 } } $v = Get-LaneVerdict (New-Rec 3 1 'x' 'x' 0) 'b1' if ($v.occ -ne 'STATED' -or $v.free -ne 3 -or $v.inflight -ne 1) { $fail += "3 free / 1 in flight must be STATED 3/1, got $($v.occ) $($v.free)/$($v.inflight)" } # The retired ceiling's own case: an old record naming capacity 4, with 0 free and 4 in flight. - $v = Get-LaneVerdict (New-Rec 0 4 'x' 'x' 0) 'b2' - if ($v.occ -ne 'STATED' -or $v.conf -ne 'FRESH') { $fail += "0 free / 4 in flight must be STATED FRESH, never full, got $($v.occ)/$($v.conf)" } + $v = Get-LaneVerdict (New-Rec 0 4 'x' 'x' 0 4) 'b2' + if ($v.occ -ne 'OLD-WRITER' -or $v.conf -ne 'FRESH') { $fail += "0 free / 4 in flight on capacity 4 must be OLD-WRITER FRESH, never full, got $($v.occ)/$($v.conf)" } if ($v.PSObject.Properties.Name -contains 'cap') { $fail += 'a verdict must carry no capacity' } + # An older lane.ps1's free count includes empty slots, so it is withheld rather than shown as held. + $v = Get-LaneVerdict (New-Rec 2 2 'x' 'x' 0 4) 'b9' + if ($v.occ -ne 'OLD-WRITER' -or $null -ne $v.free -or $v.inflight -ne 2) { $fail += "capacity 4, 2 free / 2 in flight must be OLD-WRITER with no free count and 2 in flight, got $($v.occ) $($v.free)/$($v.inflight)" } + + # The control: the same counts with capacity null are the current writer's, so 2 is held items. + $v = Get-LaneVerdict (New-Rec 2 2 'x' 'x' 0) 'b10' + if ($v.occ -ne 'STATED' -or $v.free -ne 2 -or $v.inflight -ne 2) { $fail += "capacity null, 2 free / 2 in flight must be STATED 2/2, got $($v.occ) $($v.free)/$($v.inflight)" } + $v = Get-LaneVerdict $null 'b3' if ($v.occ -ne 'NO-RECORD') { $fail += 'a missing record must be NO-RECORD' } @@ -182,7 +212,7 @@ if ($SelfTest) { if ($v.occ -ne 'IDLE') { $fail += "0 in flight must be IDLE, got $($v.occ)" } if ($fail.Count) { $fail | ForEach-Object { Write-Host "FAIL: $_" }; exit 1 } - Write-Host 'lane-level self-test: 8 cases pass, and none of them can read a lane as full.' + Write-Host 'lane-level self-test: 10 cases pass, and none of them can read a lane as full.' exit 0 } @@ -255,6 +285,18 @@ $branch = (& git rev-parse --abbrev-ref HEAD 2>$null) if ($leaf -match '^builder') { $rec = Read-LaneFor $cwd (Get-CoordHost) + if (Test-OldWriter $rec) { + # Asked every turn until it restates, however fresh the level: its free count is not held + # items. The lane.ps1 in this worktree may be the older copy, so name the one beside this hook. + $current = Join-Path (Split-Path $PSScriptRoot -Parent) 'coord\lane.ps1' + Say '[lane] Your lane level was written by an OLDER lane.ps1, whose free count includes empty' + Say ' slots under the retired ceiling, so it is not shown as held items. Restate it with the' + Say ' current script, from this worktree:' + Say " pwsh -NoProfile -File `"$current`" -Free -InFlight -Note `"`"" + Say ' FREE MEANS ITEMS YOU HOLD AND ARE NOT WORKING: built-and-awaiting-land, blocked, parked.' + Say ' There is no item ceiling, so count no empty slots.' + Emit + } $need = ($null -eq $rec) -or ($rec -is [string]) if (-not $need) { $t = if (Test-Has $rec 'turnsSinceStated') { [int]$rec.turnsSinceStated } else { 99 } @@ -384,6 +426,12 @@ foreach ($v in ($verdicts | Sort-Object box)) { 'UNREADABLE' { Say " $pad its level file did not parse, or lacks a count. NO verdict, and" Say (' {0,-28} {1,-20} this is NOT a pass.' -f '', '') } + 'OLD-WRITER' { + $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } + Say " $pad $($v.inflight) in flight, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" + Say (' {0,-28} {1,-20} WRITTEN BY AN OLDER lane.ps1: its free count includes empty' -f '', '') + Say (' {0,-28} {1,-20} slots, so it is not shown as held items. Ask the lane to' -f '', '') + Say (' {0,-28} {1,-20} restate with the current scripts\coord\lane.ps1.' -f '', '') } default { $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } Say " $pad $($v.inflight) in flight, $($v.free) free by work, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" } diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index 1662bc9959..d955d8dfb7 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -13,10 +13,13 @@ because an older lane.ps1 on another branch assigns it, and that throws when the key is missing; 2. ``-Capacity`` is not a parameter any more, so a caller cannot reintroduce a cap by passing one; 3. the dispatcher arm of the hook reports a lane that states 0 free and 4 in flight, on a record - still naming capacity 4, with its counts and with no capacity verdict. Against the scripts before - this change it printed ``AT-CAPACITY`` and ``of 4``; -4. the hook's own self-test passes; -5. a builder lane whose level is 45 minutes old is asked to restate. Its age is how a level stated + still naming capacity 4, with its in-flight count and with no capacity verdict. Against the + scripts before this change it printed ``AT-CAPACITY`` and ``of 4``; +4. a record whose ``capacity`` is a number was written by an older lane.ps1, whose free count + includes empty slots. The hook labels it OLD-WRITER, withholds that free count rather than show + it as held items, and asks the lane to restate; the same counts with ``capacity`` null are shown; +5. the hook's own self-test passes; +6. a builder lane whose level is 45 minutes old is asked to restate. Its age is how a level stated under the old ceiling ages out, and the hook read every stamp hours in the future on a machine west of UTC until this change. """ @@ -184,7 +187,8 @@ def _plant_seat(seats: Path, box: str, worktree: Path | str, seat: str) -> None: (d / f"s-{box}.json").write_text(json.dumps(rec), encoding="utf-8") -def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: +def _dispatcher_report(tmp_path: Path, lane_fields: dict[str, Any]) -> str: + """The dispatcher arm's text for one live builder lane holding ``lane_fields``.""" shared = tmp_path / "shared" / "mefor-coord" seats = shared / "seats" lanes = shared / "lanes" @@ -198,38 +202,63 @@ def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: # The hook reads no verdict below a floor of five seat directories. These name no live tree. for i in range(4): _plant_seat(seats, f"filler-{i:04d}", "C:/no/such/tree", "manager") - # The retired ceiling's own case, on a record written before the change: 0 free, 4 in flight, - # capacity 4, stated just now with nothing moved since. - now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + # Stated just now with nothing moved since. lane: dict[str, Any] = { "v": 1, - "capacity": 4, - "statedFree": 0, - "statedInFlight": 4, - "statedUtc": now, - "statedNote": "four in flight", + "statedUtc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "turnsSinceStated": 0, "tipAtStated": "x", "tipNow": "x", "claimHashAtStated": "a", "claimHashNow": "a", + **lane_fields, } (lanes / f"{box}.json").write_text(json.dumps(lane), encoding="utf-8") proc = _ps(HOOK, dispatcher, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) assert proc.returncode == 0, proc.stderr - text = str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) + return str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) + + +OLD_WRITER = "WRITTEN BY AN OLDER lane.ps1" + + +def test_the_dispatcher_report_has_no_capacity_verdict(tmp_path: Path) -> None: + # The retired ceiling's own case, on a record written before the change: 0 free, 4 in flight, + # capacity 4. + text = _dispatcher_report( + tmp_path, + {"capacity": 4, "statedFree": 0, "statedInFlight": 4, "statedNote": "four in flight"}, + ) # The control: the lane was discovered and its record read, before and after this change alike. assert 'Note: "four in flight"' in text, text # The ceiling checks come first, so against the old scripts they are what fails. assert "capacity" not in text.lower(), text assert "of 4" not in text, text - assert "4 in flight, 0 free by work" in text, text + assert "AT" + "-CAPACITY" not in text, text + assert "4 in flight" in text, text # A level stated just now is not in the future. West of UTC the old age read said -300m here. assert "stated -" not in text, text +@pytest.mark.parametrize(("capacity", "old_writer"), [(4, True), (None, False)]) +def test_an_older_writers_free_count_is_not_shown_as_held_items( + tmp_path: Path, capacity: int | None, old_writer: bool +) -> None: + """Capacity null is the control: the same counts from the current writer are held items.""" + text = _dispatcher_report( + tmp_path, {"capacity": capacity, "statedFree": 2, "statedInFlight": 2, "statedNote": "n"} + ) + # Both arms read the record and show its in-flight count. + assert 'Note: "n"' in text, text + assert "2 in flight" in text, text + assert (OLD_WRITER in text) is old_writer, text + assert ("OLD-WRITER" in text) is old_writer, text + assert ("2 free by work" in text) is not old_writer, text + assert "capacity" not in text.lower(), text + + def test_the_hook_self_test_passes(tmp_path: Path) -> None: proc = _ps(HOOK, tmp_path, "-SelfTest") assert proc.returncode == 0, proc.stdout + proc.stderr @@ -254,3 +283,39 @@ def test_a_builder_lane_is_asked_again_once_its_level_ages( proc = _ps(HOOK, builder, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) assert proc.returncode == 0, proc.stderr assert (NAG in proc.stdout) is nags, proc.stdout + + +OLD_NAG = "Your lane level was written by an OLDER lane.ps1" + + +@pytest.mark.parametrize(("capacity", "old_writer"), [(4, True), (None, False)]) +def test_a_builder_lane_on_an_older_writers_level_is_asked_to_restate( + tmp_path: Path, capacity: int | None, old_writer: bool +) -> None: + """A fresh level, so only the writer differs; capacity null is the control and is not asked.""" + shared = tmp_path / "shared" / "mefor-coord" + (shared / "lanes").mkdir(parents=True) + builder = _git_repo(tmp_path / "builder-lane") + stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + lane = { + "v": 1, + "capacity": capacity, + "statedFree": 2, + "statedInFlight": 2, + "statedUtc": stamp, + "turnsSinceStated": 0, + } + (shared / "lanes" / f"{_box_key(builder)}.json").write_text(json.dumps(lane), encoding="utf-8") + + proc = _ps(HOOK, builder, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) + assert proc.returncode == 0, proc.stderr + text = ( + str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) + if proc.stdout.strip() + else "" + ) + assert (OLD_NAG in text) is old_writer, text + assert NAG not in text, text + if old_writer: + # It names the lane.ps1 beside the hook, not the one in the worktree, which may be older. + assert f'"{LANE}"' in text, text From 8f47f872d4cba6f22322bd70bf74c46aa18fc777 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 16:32:20 -0500 Subject: [PATCH 7/8] Point both lane nags at the current lane.ps1, and nag OLD-WRITER only on a level Review round one on the OLD-WRITER change. The unstated-or-stale nag told the lane to run its worktree's own lane.ps1, which may be the older copy, so the two nags could alternate; both now name the lane.ps1 beside the hook. The OLD-WRITER nag fires only on a record with a stated level, since a record with none holds no free count; the unstated nag covers it. The hook header no longer dates the old meaning, and lane.ps1's comment points at the hook header instead of restating it. Follow-up on PR 2543 (lane ceiling retired, owner ruling 2026-10-26). --- scripts/coord/lane.ps1 | 4 +-- scripts/hooks/lane-level.ps1 | 16 ++++++---- tests/test_coord_lane_no_ceiling.py | 48 ++++++++++++++++++----------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index edeb1df7d3..533ff9da63 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -254,8 +254,8 @@ $rec.boxKey = $boxKey # assigns $rec.capacity on every statement, and assigning a property a PSCustomObject lacks throws, # so that lane could not restate. Null is a value that names no cap; a record written before the # ceiling was retired holds 4 there until this next write replaces it. This branch's lane-level.ps1 -# reads only whether it is null: a number marks a level an older lane.ps1 stated, whose free count -# includes empty slots, so it withholds that count and asks for a restatement. An older copy of that hook falls back to 4 whatever this field holds, and reads +# reads only whether it is null, as a marker of who wrote the level; its header says how. An older +# copy of that hook falls back to 4 whatever this field holds, and reads # 0 free with 2 or more in flight as full, which under the new meaning of -Free it does sooner. Only # replacing that reader, wherever it is installed, retires the ceiling there. $rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index ec03950d20..20b47ea727 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -20,7 +20,7 @@ to restate with the current script. The number is read as a marker of who wrote the record, never as a cap. A record whose `capacity` is null or absent is read as stated. A null does not prove the current script stated the counts: its -Asked nulls `capacity` and leaves an older - statement's counts in place, so a level stated before 2026-10-26 can still read as held items + statement's counts in place, so a level an older lane.ps1 stated can still read as held items until the lane restates. EVERY NUMBER IS A LANE'S OWN QUOTE. A missing file, an unreadable file, a stale number, a @@ -285,10 +285,13 @@ $branch = (& git rev-parse --abbrev-ref HEAD 2>$null) if ($leaf -match '^builder') { $rec = Read-LaneFor $cwd (Get-CoordHost) - if (Test-OldWriter $rec) { + # BOTH NAGS NAME THE lane.ps1 BESIDE THIS HOOK. The copy in this worktree may be the older one, + # and a restatement through it writes a level the OLD-WRITER nag below then asks about again. + $current = Join-Path (Split-Path $PSScriptRoot -Parent) 'coord\lane.ps1' + $stated = (Test-OldWriter $rec) -and (Test-Has $rec 'statedUtc') -and $rec.statedUtc + if ($stated) { # Asked every turn until it restates, however fresh the level: its free count is not held - # items. The lane.ps1 in this worktree may be the older copy, so name the one beside this hook. - $current = Join-Path (Split-Path $PSScriptRoot -Parent) 'coord\lane.ps1' + # items. A record with no stated level holds no count, so the unstated nag below covers it. Say '[lane] Your lane level was written by an OLDER lane.ps1, whose free count includes empty' Say ' slots under the retired ceiling, so it is not shown as held items. Restate it with the' Say ' current script, from this worktree:' @@ -305,7 +308,7 @@ if ($leaf -match '^builder') { } if ($need) { Say '[lane] Your lane level is unstated or stale. State it now, it takes five seconds:' - Say ' pwsh -NoProfile -File scripts\coord\lane.ps1 -Free -InFlight -Note ""' + Say " pwsh -NoProfile -File `"$current`" -Free -InFlight -Note `"`"" Say ' FREE MEANS BY WORK, NOT BY CLAIMS. Built-and-awaiting-land is FREE. Blocked is FREE.' Say ' Parked on a condition is FREE. There is no item ceiling, so count no empty slots.' Say ' Nobody else can write this number for you.' @@ -431,7 +434,8 @@ foreach ($v in ($verdicts | Sort-Object box)) { Say " $pad $($v.inflight) in flight, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" Say (' {0,-28} {1,-20} WRITTEN BY AN OLDER lane.ps1: its free count includes empty' -f '', '') Say (' {0,-28} {1,-20} slots, so it is not shown as held items. Ask the lane to' -f '', '') - Say (' {0,-28} {1,-20} restate with the current scripts\coord\lane.ps1.' -f '', '') } + Say (' {0,-28} {1,-20} restate with a current lane.ps1; its own worktree may hold' -f '', '') + Say (' {0,-28} {1,-20} the older copy, and its own nag names a current one.' -f '', '') } default { $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } Say " $pad $($v.inflight) in flight, $($v.free) free by work, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" } diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index d955d8dfb7..ccafa134ee 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -288,34 +288,46 @@ def test_a_builder_lane_is_asked_again_once_its_level_ages( OLD_NAG = "Your lane level was written by an OLDER lane.ps1" -@pytest.mark.parametrize(("capacity", "old_writer"), [(4, True), (None, False)]) -def test_a_builder_lane_on_an_older_writers_level_is_asked_to_restate( - tmp_path: Path, capacity: int | None, old_writer: bool -) -> None: - """A fresh level, so only the writer differs; capacity null is the control and is not asked.""" +def _builder_report(tmp_path: Path, lane_fields: dict[str, Any]) -> str: + """The builder arm's text for its own lane record holding ``lane_fields``.""" shared = tmp_path / "shared" / "mefor-coord" (shared / "lanes").mkdir(parents=True) builder = _git_repo(tmp_path / "builder-lane") - stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - lane = { - "v": 1, - "capacity": capacity, - "statedFree": 2, - "statedInFlight": 2, - "statedUtc": stamp, - "turnsSinceStated": 0, - } + lane = {"v": 1, "turnsSinceStated": 0, **lane_fields} (shared / "lanes" / f"{_box_key(builder)}.json").write_text(json.dumps(lane), encoding="utf-8") proc = _ps(HOOK, builder, root=shared, stdin=json.dumps({"hook_event_name": "Stop"})) assert proc.returncode == 0, proc.stderr - text = ( - str(json.loads(proc.stdout)["hookSpecificOutput"]["additionalContext"]) - if proc.stdout.strip() - else "" + out = proc.stdout.strip() + return str(json.loads(out)["hookSpecificOutput"]["additionalContext"]) if out else "" + + +@pytest.mark.parametrize(("capacity", "old_writer"), [(4, True), (None, False)]) +def test_a_builder_lane_on_an_older_writers_level_is_asked_to_restate( + tmp_path: Path, capacity: int | None, old_writer: bool +) -> None: + """A fresh level, so only the writer differs; capacity null is the control and is not asked.""" + stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + text = _builder_report( + tmp_path, + {"capacity": capacity, "statedFree": 2, "statedInFlight": 2, "statedUtc": stamp}, ) assert (OLD_NAG in text) is old_writer, text assert NAG not in text, text if old_writer: # It names the lane.ps1 beside the hook, not the one in the worktree, which may be older. assert f'"{LANE}"' in text, text + + +def test_an_older_writers_record_with_no_level_gets_the_unstated_nag(tmp_path: Path) -> None: + """An older ``-Asked`` record holds no count, so there is no free count to call slot-inclusive. + + The unstated nag also names the lane.ps1 beside the hook: restating through the worktree's own + copy, if that is the older one, would write a level the OLD-WRITER nag asks about again. + """ + text = _builder_report( + tmp_path, {"capacity": 4, "statedFree": None, "statedInFlight": None, "statedUtc": None} + ) + assert NAG in text, text + assert OLD_NAG not in text, text + assert f'"{LANE}"' in text, text From a22cf38cf0d33d8ec600e900bfae5eda8927ae77 Mon Sep 17 00:00:00 2001 From: Scott Hall Date: Mon, 26 Oct 2026 16:54:15 -0500 Subject: [PATCH 8/8] lane.ps1 -Asked keeps the capacity it finds; only a statement nulls it -Asked writes no counts, so nulling capacity there erased the marker of an older writer's statement and left its slot-inclusive free count reading as held items. A statement still writes capacity as null; -Asked keeps any value it finds and adds the key as null only when it is missing, because an older lane.ps1 assigns it and throws without it. The test that expected -Asked to null it now expects 4 kept, and a new case covers the missing key. Also from review round two: the dispatcher OLD-WRITER line no longer says the lane's own nag will ask it (that arm runs only in builder* worktrees), and the test docstring no longer calls age the migration path. Follow-up on PR 2543 (lane ceiling retired, owner ruling 2026-10-26). --- scripts/coord/lane.ps1 | 16 ++++++++----- scripts/hooks/lane-level.ps1 | 16 ++++++------- tests/test_coord_lane_no_ceiling.py | 37 ++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/scripts/coord/lane.ps1 b/scripts/coord/lane.ps1 index 533ff9da63..c12a9cb0a1 100644 --- a/scripts/coord/lane.ps1 +++ b/scripts/coord/lane.ps1 @@ -253,12 +253,16 @@ $rec.boxKey = $boxKey # `capacity` IS WRITTEN AS NULL, NEVER DROPPED. An older lane.ps1, run from another worktree's branch, # assigns $rec.capacity on every statement, and assigning a property a PSCustomObject lacks throws, # so that lane could not restate. Null is a value that names no cap; a record written before the -# ceiling was retired holds 4 there until this next write replaces it. This branch's lane-level.ps1 -# reads only whether it is null, as a marker of who wrote the level; its header says how. An older -# copy of that hook falls back to 4 whatever this field holds, and reads -# 0 free with 2 or more in flight as full, which under the new meaning of -Free it does sooner. Only -# replacing that reader, wherever it is installed, retires the ceiling there. -$rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force +# ceiling was retired holds 4 there until a statement replaces it. This branch's lane-level.ps1 +# reads only whether it is null, as a marker of who stated the counts; its header says how. So only +# a statement nulls it: -Asked writes no counts, and nulling the marker there would leave an older +# statement's counts reading as this script's. An older copy of that hook falls back to 4 whatever +# this field holds, and reads 0 free with 2 or more in flight as full, which under the new meaning of +# -Free it does sooner. Only replacing that reader, wherever it is installed, retires the ceiling there. +if (-not $Asked) { $rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null -Force } +elseif (-not ($rec.PSObject.Properties.Name -contains 'capacity')) { + $rec | Add-Member -NotePropertyName capacity -NotePropertyValue $null +} # docs/COORD-SHARED-ROOT.md: a reader on another PC must be able to tell this lane is not its own. $rec | Add-Member -NotePropertyName host -NotePropertyValue $thisHost -Force diff --git a/scripts/hooks/lane-level.ps1 b/scripts/hooks/lane-level.ps1 index 20b47ea727..d819c1d119 100644 --- a/scripts/hooks/lane-level.ps1 +++ b/scripts/hooks/lane-level.ps1 @@ -14,14 +14,12 @@ AN OLDER lane.ps1 MEANS SOMETHING ELSE BY FREE. Its free count includes the empty slots under the retired ceiling, so read as held items it overstates them. The current lane.ps1 writes - `capacity` as null on every write, and the older one writes a number there on every statement, - so a record whose `capacity` is a number has a level an older lane.ps1 stated. This hook shows - that level's in-flight count, withholds its free count, labels it OLD-WRITER and asks the lane - to restate with the current script. The number is read as a marker of who wrote the record, - never as a cap. A record whose `capacity` is null or absent is read as stated. A null does not - prove the current script stated the counts: its -Asked nulls `capacity` and leaves an older - statement's counts in place, so a level an older lane.ps1 stated can still read as held items - until the lane restates. + `capacity` as null on every statement and keeps whatever it finds on -Asked, which writes no + counts; the older one writes a number there on every statement. So a record whose `capacity` is + a number has a level an older lane.ps1 stated. This hook shows that level's in-flight count, + withholds its free count, labels it OLD-WRITER and asks the lane to restate with the current + script. The number is read as a marker of who stated the counts, never as a cap. A record whose + `capacity` is null or absent is read as stated. EVERY NUMBER IS A LANE'S OWN QUOTE. A missing file, an unreadable file, a stale number, a superseded number, a roster shortfall -- each is said aloud, never turned into a count. @@ -435,7 +433,7 @@ foreach ($v in ($verdicts | Sort-Object box)) { Say (' {0,-28} {1,-20} WRITTEN BY AN OLDER lane.ps1: its free count includes empty' -f '', '') Say (' {0,-28} {1,-20} slots, so it is not shown as held items. Ask the lane to' -f '', '') Say (' {0,-28} {1,-20} restate with a current lane.ps1; its own worktree may hold' -f '', '') - Say (' {0,-28} {1,-20} the older copy, and its own nag names a current one.' -f '', '') } + Say (' {0,-28} {1,-20} the older copy. Its own hook asks only in a builder* worktree.' -f '', '') } default { $n = if ($v.note) { " Note: `"$($v.note)`"" } else { '' } Say " $pad $($v.inflight) in flight, $($v.free) free by work, stated $(Format-Age $v.ageMin) and $($v.turns) lane-turns ago.$n" } diff --git a/tests/test_coord_lane_no_ceiling.py b/tests/test_coord_lane_no_ceiling.py index ccafa134ee..583aded212 100644 --- a/tests/test_coord_lane_no_ceiling.py +++ b/tests/test_coord_lane_no_ceiling.py @@ -9,8 +9,9 @@ These tests fail if a default cap of 4 comes back in either script: 1. a statement records ``capacity`` as null, with no upper bound on its counts, and prints no - ceiling; a 4 carried on an old record is replaced with null on the next write. The key stays, - because an older lane.ps1 on another branch assigns it, and that throws when the key is missing; + ceiling; a 4 carried on an old record is replaced with null by the next statement, and kept by + ``-Asked``, which writes no counts. The key stays, because an older lane.ps1 on another branch + assigns it, and that throws when the key is missing; 2. ``-Capacity`` is not a parameter any more, so a caller cannot reintroduce a cap by passing one; 3. the dispatcher arm of the hook reports a lane that states 0 free and 4 in flight, on a record still naming capacity 4, with its in-flight count and with no capacity verdict. Against the @@ -19,9 +20,8 @@ includes empty slots. The hook labels it OLD-WRITER, withholds that free count rather than show it as held items, and asks the lane to restate; the same counts with ``capacity`` null are shown; 5. the hook's own self-test passes; -6. a builder lane whose level is 45 minutes old is asked to restate. Its age is how a level stated - under the old ceiling ages out, and the hook read every stamp hours in the future on a machine - west of UTC until this change. +6. a builder lane whose level is 45 minutes old is asked to restate. The hook read every stamp + hours in the future on a machine west of UTC until this change, so no level aged out there. """ from __future__ import annotations @@ -118,7 +118,8 @@ def test_a_negative_count_is_still_refused(tmp_path: Path) -> None: assert not (repo / ".git" / "mefor-coord" / "lanes").exists(), "a refused call wrote a lane" -def test_a_capacity_left_on_an_old_record_is_nulled_on_the_next_write(tmp_path: Path) -> None: +def test_a_capacity_left_on_an_old_record_is_nulled_only_by_a_statement(tmp_path: Path) -> None: + """``-Asked`` writes no counts, so it keeps the marker of who stated the counts there.""" repo = _git_repo(tmp_path / "repo") assert _ps(LANE, repo, "-Free", "1", "-InFlight", "1").returncode == 0 record = _lane_record(repo) @@ -130,12 +131,32 @@ def test_a_capacity_left_on_an_old_record_is_nulled_on_the_next_write(tmp_path: assert show.returncode == 0, show.stderr assert json.loads(show.stdout)["capacity"] == 4 - for args in (("-Asked",), ("-Free", "0", "-InFlight", "5")): + for args, kept in ((("-Asked",), 4), (("-Free", "0", "-InFlight", "5"), None)): record.write_text(json.dumps(old), encoding="utf-8") proc = _ps(LANE, repo, *args) assert proc.returncode == 0, (args, proc.stdout + proc.stderr) new = json.loads(record.read_text(encoding="utf-8")) - assert "capacity" in new and new["capacity"] is None, (args, new) + assert "capacity" in new and new["capacity"] == kept, (args, new) + # The control: both calls wrote. -Asked stamps its time and leaves the counts; a statement + # writes its counts. + if kept is None: + assert new["statedInFlight"] == 5, (args, new) + else: + assert new["askedByDispatcherUtc"] and new["statedInFlight"] == 1, (args, new) + + +def test_asked_adds_a_missing_capacity_key_as_null(tmp_path: Path) -> None: + """An older lane.ps1 assigns ``capacity`` and throws when the key is missing, so -Asked adds it.""" + repo = _git_repo(tmp_path / "repo") + assert _ps(LANE, repo, "-Free", "1", "-InFlight", "1").returncode == 0 + record = _lane_record(repo) + rec: dict[str, Any] = json.loads(record.read_text(encoding="utf-8")) + del rec["capacity"] + record.write_text(json.dumps(rec), encoding="utf-8") + assert _ps(LANE, repo, "-Asked").returncode == 0 + new = json.loads(record.read_text(encoding="utf-8")) + assert "capacity" in new and new["capacity"] is None, new + assert new["askedByDispatcherUtc"], new def test_capacity_is_no_longer_a_parameter(tmp_path: Path) -> None: