Skip to content

feat(onboarding): guide fleet setup with dependency, owner and pane-placement scripts - #66

Merged
LeTuR merged 6 commits into
mainfrom
feat/onboarding-guided
Sep 11, 2026
Merged

feat(onboarding): guide fleet setup with dependency, owner and pane-placement scripts#66
LeTuR merged 6 commits into
mainfrom
feat/onboarding-guided

Conversation

@LeTuR

@LeTuR LeTuR commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Intent

Improve the fleet control plane's onboarding process so it is more guided and clearer, and so it stops leaving parts of the setup to the operator that a script can do. The user asked for four things specifically: (1) onboarding should ALWAYS ask to install the custom TUI queue pane and do the layout setup, with the recommended layout being the pane on the RIGHT — this deliberately reverses the repo's previous standing rule that nothing here writes the operator's thurbox layout.lua, because 'print the block and stop' is what left operators finishing setup with an installed pane that draws nothing; the write is still never unasked, it now happens through scripts/place-pane.sh after an explicit question, with a backup, a refusal on any layout it cannot recognise, a lua re-read that restores the backup on a parse failure, and thurbox-cli plugin check as the verification. (2) It should ask about auto-detecting repos, or find potential config via .gitconfig or other config files — scripts/discover-owners.sh reads the gh session, the git config (github.user and a users.noreply.github.com commit email) and the remotes of every clone already on disk, prints candidates with their evidence, and the skill then asks ONE question about which owners the map covers; GitLab remotes are reported as evidence and deliberately never as owner candidates, since registry/owners.txt is read by gh. (3) Onboarding should be more guided and clearer — the skill is now seven announced steps with exactly four asks, each at the step it belongs to, and each step reports what it landed with evidence. (4) It should ask to install fleet's required dependencies, and the user then added 'dependencies like quota-axi' — scripts/preflight.sh is the one dependency table, in three tiers (required / recommended / gate), each row naming what breaks without it and the command that installs it; quota-axi is recommended because the pane's fuel rows, fleet-status.sh and queue.sh refuel's quota check all depend on it. Decisions a reviewer reading only the diff would not know: preflight probes and prints but never installs, because a package manager is the one part of setup that touches the machine outside the checkout — the skill asks and runs the lines, and --tier exists so 'required only' is a flag rather than the agent's judgement about which lines to copy. Preflight also carries a row that is not a tool at all, the git commit-signing configuration, because commit.gpgsign=true with a key scoped by an includeIf gitdir: block makes every commit in a sandbox repo fail and makes check.sh queue report a dozen unrelated failures; it is probed from a directory OUTSIDE the checkout for exactly that reason, and that row is not hypothetical — this very pipeline's first run failed on it. place-pane.sh never spells the pane's slot name (it reads it from interface/fleet_queue.lua so a rename cannot half-land) and check.sh onboarding asserts that. Records are separated by 0x1f rather than tabs in preflight because a tab is IFS whitespace and collapses empty fields. The clone scan matches ssh host ALIASES (git@github-perso:owner/repo), which was a real bug: matching the literal github.com found none of the clones on this machine, including this checkout. The second commit on this branch fixes a fragility in the new selftest itself: GIT_CONFIG_COUNT/KEY/VALUE outrank GIT_CONFIG_GLOBAL, so the signing case's answer was decided by the caller's environment; it now clears them. All of it is covered offline by scripts/onboarding-selftest.sh, wired in as check.sh onboarding and as an unfiltered CI job that installs lua. Docs updated to match the reversed layout.lua stance: the fleet-pane and update-fleet skills, AGENTS.md, README.md and install-extension.sh's own closing message. The full local gate (./scripts/check.sh) is green on this branch.

What Changed

  • Adds three onboarding scripts that replace prose checklists: scripts/preflight.sh probes every dependency in one pass across three tiers (required / recommended / gate, including quota-axi and a git commit-signing row probed from outside the checkout) and prints install commands via --commands/--tier without ever installing; scripts/discover-owners.sh prints GitHub owner candidates with evidence from the gh session, git config and the origin of local clones (matching ssh host aliases, and reporting GitLab remotes as evidence only); scripts/place-pane.sh writes the queue pane's guarded block into the operator's layout.lua — right side by default — reading the slot name from interface/fleet_queue.lua, refusing unrecognised layouts, backing up, re-reading the edit with lua and restoring on a parse failure, then verifying with thurbox-cli plugin check.
  • Rewrites the fleet-onboarding skill into seven announced steps with exactly four asks (dependencies, owners, pane placement, reconciler), each step reporting what it landed, and updates fleet-pane, update-fleet, AGENTS.md, README.md, CONTRIBUTING.md, extension.toml.in and install-extension.sh's closing message for the reversed stance that layout.lua is now written by a script after an explicit question.
  • Adds scripts/onboarding-selftest.sh covering all three scripts offline against PATH stubs and a stock-layout fixture (scripts/fixtures/layout/), wired in as ./scripts/check.sh onboarding and an unfiltered CI job that installs lua; scripts/queue-selftest.sh now exports the GIT_CONFIG_COUNT triple to force commit.gpgsign=false in its sandbox repos.

Risk Assessment

⚠️ Medium: The change is large but well-bounded and thoroughly covered offline, every accepted round-1 fix verifies as genuinely implemented and semantic rather than cosmetic, and the remaining issues are one operator-facing wrong output from --commands, one unrequested fix-round scope change that quietly narrows owner discovery, and one incomplete test-isolation boundary — all safe to land and address as follow-ups.

Testing

The configured gate ./scripts/check.sh had already passed at baseline; on top of that I stood each onboarding script up the way an operator runs it. preflight was driven on this machine and on sandboxed PATHs built from scratch: three missing required tools give exit 1 with a remedy per row, --tier gate narrows the view without softening the verdict, a stubbed thurbox 2.18.0 is reported stale against the floor read from extension.toml.in, and quota-axi is reported as a recommended gap without failing the run. The commit-signing row was driven across three git configurations, including the intent's own case — a key scoped to the checkout by includeIf gitdir: — where git inside the checkout answers with a key and preflight, probing outside, still reports the gap. That gap is real on this machine, so I reproduced the failure it explains: the pre-fix queue selftest gives 18 failures here, the current one gives none. Discovery was run for real (gh account, orgs, clone counts, self-hosted GitLab reported only as evidence) and against a fixture clone tree that tries to break it: an ssh host alias is found, ssh://…:443/ yields the owner and not the port, a fork's upstream owner never appears, and vendored node_modules/dot-directory clones are pruned; a machine with nothing to say exits 1 instead of inventing an owner. Placement was only ever driven against copies of the tracked layout fixture and never the operator's file: the default lands the guarded block to the right of the centre column with a timestamped backup, lua re-reads it, thurbox-cli plugin check came back green, a re-run changes nothing, a commented-out block is correctly not a placement, three unrecognisable arrangements are refused untouched with the block printed, and an edit into a non-parsing layout is rolled back byte-identically. A pane file declaring a different slot places that slot, so the block genuinely follows the pane. Visual evidence is CLI transcripts and the layout diff rather than screenshots: the only rendered surface here is a thurbox TUI column, and driving it would mean editing the operator's live layout outside the worktree, so I verified it with plugin check and the placed-block diff instead. The worktree is clean and all sandboxes were removed.

  • Live validation: ✅ go - 23 of 26 scenarios driven live against the product
Scenario Result Live Evidence
An operator on a healthy machine runs preflight and sees one table in three tiers, each row saying what breaks without it ✅ pass live ./scripts/preflight.sh → exit 0, required/recommended/gate sections; evidence/preflight-table.txt
A machine missing gh, jq and thurbox-cli is told so in one pass and preflight exits non-zero ✅ pass live env -i PATH=<sandbox> bash ./scripts/preflight.sh → exit 1, "3 required dependencies missing"; evidence/preflight-missing-required.txt
Asking for one tier narrows what is printed but never softens the verdict (adversarial) ✅ pass live --tier gate on a machine missing three required tools still exits 1; --tier bogus exits 2; evidence/preflight-tier-gate-still-fails.txt
Missing recommended and gate tools are reported and never fatal, and quota-axi is one of them ✅ pass live all required stubbed present, quota-axi/glab/shellcheck/rumdl/prek absent → exit 0 with quota-axi's cost named; evidence/preflight-recommended-gaps-not-fatal.txt
The operator said yes to installing: --commands hands back only the actionable install lines ✅ pass live ./scripts/preflight.sh --commands prints the remedies for the gaps and drops the see <url> rows; evidence/preflight-commands.txt
A thurbox older than the manifest's floor is reported stale rather than ok, with the floor read from extension.toml.in ✅ pass live stubbed thurbox-cli 2.18.0 vs floor 2.19.0 → stale ... sets the floor at 2.19.0; evidence/preflight-stale-thurbox.txt
Commit signing scoped to the checkout by an includeIf block does not hide the machine-wide gap (adversarial) ✅ pass live git inside the checkout reports a signing key while preflight, probing outside it, still reports missing commit signing; ok when the key is truly global; evidence/preflight-signing-row.txt
The gate failure the signing row exists to explain: sandbox commits fail before the fix and pass after it ✅ pass live pre-fix scripts/queue-selftest.sh from 2cf6088 → 18 FAIL on this machine; current → 0 FAIL, "every claim holds"; evidence/queue-selftest-{prev,after}-signing.txt
Owner discovery reads this machine and prints candidates with the evidence for each ✅ pass live ./scripts/discover-owners.sh → gh account, gh orgs and local clone counts per owner; evidence/discover-owners-real.txt
GitLab checkouts are reported as evidence and never reach the GitHub owner list ✅ pass live a self-hosted GitLab namespace with 32 clones appears only under "GITLAB CHECKOUTS — evidence, not owners"; same in the fixture tree; evidence/discover-owners-real.txt, discover-owners-adversarial.txt
Discovery survives the remote shapes that used to break it: ssh alias found, an ssh:// port is not an owner, a fork's upstream is not an owner, vendored clones are pruned (adversarial) ✅ pass live fixture tree with git@github-perso:, ssh://git@ssh.github.com:443/, an upstream remote, node_modules and .vim/plugged clones → only me and Thurbeen; evidence/discover-owners-adversarial.txt
A machine with nothing to say exits 1 and points at the file to write by hand instead of inventing an owner ✅ pass live empty HOME + empty root → exit 1, "No candidate owners found" with the gh remedy; evidence/discover-owners-nothing.txt
A machine whose gh was never logged in still answers from github.user and the noreply commit email ✅ pass live fake HOME with both fields → two candidates, each with its evidence, plus the gh-auth note; evidence/discover-owners-gitconfig.txt
A re-run marks the owners already committed to instead of proposing them again, and discovery writes nothing ✅ pass live temporary registry/owners.txt → * beside Thurbeen and "* already in registry/owners.txt"; worktree clean afterwards; evidence/discover-owners-rerun-marker.txt
Saying yes to the pane puts a guarded column to the RIGHT of the terminal, backed up, re-read, and verified ✅ pass live ./scripts/place-pane.sh --layout &lt;copy of stock.lua&gt; → block after the center column with the panels.shown guard, layout.lua.bak-<ts>, lua loadfile ok, thurbox-cli plugin check green; evidence/p…
Running placement twice changes nothing the second time, and --check tells a placed pane from an unplaced one ✅ pass live --check exit 1 before / exit 0 after; second placement run prints "Already placed ... nothing was changed"; evidence/place-pane-happy-path.txt
--left puts the column between the session list and the terminal ✅ pass live diff shows the block inserted before columns[...] = { slot = &#34;center&#34; }; evidence/place-pane-left.txt
An arrangement the script cannot recognise is refused untouched, with the block printed for the operator (adversarial) ✅ pass live no center anchor, no panels.shown(, and no filled(ctx, each exit 3 naming what was missing and leave no backup; a missing layout.lua exits 2; evidence/place-pane-refusals.txt
An edit that would leave the layout unparseable does not survive: the backup goes straight back (adversarial) ✅ pass live placing into a broken-but-recognisable layout → exit 3, restored backup, cmp reports the file byte-identical to before the run
A commented-out fleet block is not treated as a placement (adversarial) ✅ pass live --check on a layout whose block is commented out → exit 1 "loads, lists, and draws nothing"; placing over it then succeeds
The block carries the slot the pane declares, so a rename cannot half-land ✅ pass live --pane &lt;copy declaring queue_v2&gt; → the written block guards and carves queue_v2; evidence/place-pane-renamed-slot.txt
A layout under a path containing an apostrophe is still placed rather than blamed on a parse failure (adversarial) ✅ pass live --layout &#34;/tmp/.../o&#39;brien/layout.lua&#34; → exit 0, block present; evidence/place-pane-quoted-path.txt
The gate runs the onboarding coverage, and a caller's git environment cannot decide its answers ✅ pass live ./scripts/check.sh onboarding ok; ./scripts/onboarding-selftest.sh green bare and under GIT_CONFIG_COUNT/KEY_0/VALUE_0 forcing gpgsign=false; evidence/onboarding-selftest.txt, selftest-hostile-git…
The guided skill itself: seven announced steps with exactly four asks, always asking before the layout is written ⏸️ untested no The ask-then-write contract lives in skill prose, not in code: the scripts it calls are drivable (and were driven) but the asking is model behaviour. Driving it would need an agent session harness tha…
A full fresh-clone onboarding run: dependencies installed, registry synced, extension installed, reconciler up ⏸️ untested no Every one of those steps writes outside this worktree (package installs, registry/owners.txt on the operator's machine, a rewrite of the live thurbox extension manifest and its Mission Control session…
The new unfiltered CI onboarding job installs lua and runs the selftest on a runner ⏸️ untested no Running the workflow needs a GitHub Actions runner and a push/PR on this branch, which the outer executor's CI phase owns; this phase has no authority to push or trigger CI.
Evidence: preflight on this machine — the three-tier table, with the commit-signing row firing for real

REQUIRED — fleet cannot run without these
  �[32mok�[0m       git            2.39.5
  �[32mok�[0m       gh             2.100.0
  �[32mok�[0m       gh auth        LeTuR
  �[32mok�[0m       jq             1.6
  �[32mok�[0m       python3        3.13.15
  �[32mok�[0m       PyYAML         6.0.3
  �[32mok�[0m       thurbox-cli    2.19.7

RECOMMENDED — each one names what degrades without it
  �[32mok�[0m       quota-axi      0.1.41
  �[32mok�[0m       glab           1.117.0
  �[31mmissing�[0m  glab auth      reading a merge request needs a credential for the host it lives on
                          install: glab auth login   # GITLAB_HOST=... for a self-hosted instance

GATE — only ./scripts/check.sh needs these
  �[32mok�[0m       lua            5.4.4
  �[32mok�[0m       shellcheck     0.11.0
  �[32mok�[0m       rumdl          0.2.70
  �[32mok�[0m       prek           0.5.2
  �[31mmissing�[0m  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false

Every required dependency is present. "--commands" lists the
install lines for anything above that is not.
Evidence: preflight on a machine missing gh, jq and thurbox-cli — exit 1, every gap with a remedy

REQUIRED — fleet cannot run without these
  �[32mok�[0m       git            2.39.5
  �[31mmissing�[0m  gh             builds the repo map from registry/owners.txt, and is fleet's GitHub forge adapter
                          install: see https://cli.github.com
  �[31mmissing�[0m  jq             scripts/sync-registry.sh and scripts/install-extension.sh read JSON with it
                          install: see https://jqlang.github.io/jq/download/
  �[32mok�[0m       python3        3.13.15
  �[32mok�[0m       PyYAML         6.0.3
  �[31mmissing�[0m  thurbox-cli    the sessions fleet spawns, the extension, and the queue pane all live in it
                          install: see https://github.com/Thurbeen/thurbox

RECOMMENDED — each one names what degrades without it
  �[31mmissing�[0m  quota-axi      the fuel the pane and ./scripts/fleet-status.sh draw, and the account window queue.sh refuel checks before it restarts a worker
                          install: npm install -g quota-axi
  �[31mmissing�[0m  glab           fleet's GitLab forge adapter; nothing needs it until a task's repo lives on GitLab
                          install: see https://gitlab.com/gitlab-org/cli

GATE — only ./scripts/check.sh needs these
  �[32mok�[0m       lua            5.4.4
  �[31mmissing�[0m  shellcheck     ./scripts/check.sh shell
                          install: see https://github.com/koalaman/shellcheck#installing
  �[31mmissing�[0m  rumdl          ./scripts/check.sh markdown
                          install: uv tool install rumdl   # or: cargo install rumdl
  �[31mmissing�[0m  prek           the pre-commit hooks in .pre-commit-config.yaml, which run the same gate
                          install: uv tool install prek
  �[31mmissing�[0m  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false

3 required dependencies missing. Install before onboarding
writes anything: a half-onboarded clone is worse than one that
never started.

  ./scripts/preflight.sh --commands
Evidence: preflight --tier gate on that same machine — the view narrows, the verdict does not (exit 1)

GATE — only ./scripts/check.sh needs these
  �[32mok�[0m       lua            5.4.4
  �[31mmissing�[0m  shellcheck     ./scripts/check.sh shell
                          install: see https://github.com/koalaman/shellcheck#installing
  �[31mmissing�[0m  rumdl          ./scripts/check.sh markdown
                          install: uv tool install rumdl   # or: cargo install rumdl
  �[31mmissing�[0m  prek           the pre-commit hooks in .pre-commit-config.yaml, which run the same gate
                          install: uv tool install prek
  �[31mmissing�[0m  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false

3 required dependencies missing. Install before onboarding
writes anything: a half-onboarded clone is worse than one that
never started.

  ./scripts/preflight.sh --commands
Evidence: preflight with only recommended/gate gaps — exit 0, quota-axi named with what it costs

REQUIRED — fleet cannot run without these
  �[32mok�[0m       git            2.39.5
  �[32mok�[0m       gh             2.100.0
  �[32mok�[0m       gh auth        sandboxuser
  �[32mok�[0m       jq             1.7
  �[32mok�[0m       python3        3.13.15
  �[32mok�[0m       PyYAML         6.0.3
  �[32mok�[0m       thurbox-cli    2.19.0

RECOMMENDED — each one names what degrades without it
  �[31mmissing�[0m  quota-axi      the fuel the pane and ./scripts/fleet-status.sh draw, and the account window queue.sh refuel checks before it restarts a worker
                          install: npm install -g quota-axi
  �[31mmissing�[0m  glab           fleet's GitLab forge adapter; nothing needs it until a task's repo lives on GitLab
                          install: see https://gitlab.com/gitlab-org/cli

GATE — only ./scripts/check.sh needs these
  �[32mok�[0m       lua            5.4.4
  �[31mmissing�[0m  shellcheck     ./scripts/check.sh shell
                          install: see https://github.com/koalaman/shellcheck#installing
  �[31mmissing�[0m  rumdl          ./scripts/check.sh markdown
                          install: uv tool install rumdl   # or: cargo install rumdl
  �[31mmissing�[0m  prek           the pre-commit hooks in .pre-commit-config.yaml, which run the same gate
                          install: uv tool install prek
  �[31mmissing�[0m  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false

Every required dependency is present. "--commands" lists the
install lines for anything above that is not.
Evidence: a thurbox below the manifest floor is stale, not ok

REQUIRED — fleet cannot run without these
  �[32mok�[0m       git            2.39.5
  �[32mok�[0m       gh             2.100.0
  �[32mok�[0m       gh auth        sandboxuser
  �[32mok�[0m       jq             1.7
  �[32mok�[0m       python3        3.13.15
  �[32mok�[0m       PyYAML         6.0.3
  �[33mstale�[0m    thurbox-cli    2.18.0 — extension.toml.in sets the floor at 2.19.0 and says why
                          install: upgrade thurbox-cli to 2.19.0 or newer

1 required dependency missing. Install before onboarding
writes anything: a half-onboarded clone is worse than one that
never started.

  ./scripts/preflight.sh --commands
Evidence: the signing row across three git configurations, including a key scoped to the checkout by includeIf
(a) gpgsign=true, no key
  missing  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false

(b) gpgsign=true with user.signingkey set
  ok       commit signing 


(c) key scoped to the checkout by includeIf gitdir: (git inside the checkout reports SCOPEDKEY)
  missing  commit signing git commit signing is on with no key outside this checkout, so every commit in a sandbox fails — which is where check.sh queue works
                          install: git config --global user.signingkey <key>   # or: commit.gpgsign false
Evidence: the failure that row diagnoses: pre-fix queue selftest on this machine (18 FAIL)
queue-selftest: /tmp/tmp.G7Ui6o3iCI/queue
  �[32mok�[0m    topic add returns a topic id, and only that
  �[32mok�[0m    a blocker with no reason is refused
  �[32mok�[0m    file overlap cannot be spelled as a blocker kind
  �[32mok�[0m    and the refusal says where overlap belongs instead
  �[32mok�[0m    a blocker that closes a cycle is refused
  �[32mok�[0m    ready set names 01
  �[32mok�[0m    ready set names 02
  �[32mok�[0m    ready set names 04 despite the file overlap
  �[32mok�[0m    three tasks are ready at once
  �[32mok�[0m    01 and 04 are reported as an overlap risk
  �[32mok�[0m    the overlap note refuses to be a reason to wait
  �[32mok�[0m    03 waits
  �[32mok�[0m    03's blocker is durable and stated
  �[32mok�[0m    plan --json agrees: 3 ready
  �[32mok�[0m    the scaffold emits the section `## What to do`
  �[32mok�[0m    the scaffold emits the section `## Hard constraints`
  �[32mok�[0m    the scaffold emits the section `## Coordination`
  �[32mok�[0m    the scaffold emits the section `## Done means`
  �[32mok�[0m    every scaffolded section starts unwritten, so a half-written brief is refused
  �[32mok�[0m    a task with an unwritten BRIEF.md does not go out
  �[32mok�[0m    one dispatch spawns 3 sessions
  �[32mok�[0m    dispatch does not spawn the blocked task
  �[32mok�[0m    dispatch says the ready set goes out together
  �[32mok�[0m    each worker is pointed at its own brief and nothing else
  �[32mok�[0m    every spawn answers the trust dialog before it prompts
  �[32mok�[0m    and the trust step comes before the prompt
  �[32mok�[0m    attach stamps each task's own floor from the stream's high-water mark
  �[32mok�[0m    and writes no queue-wide cursor for another task to consume
  �[32mok�[0m    watch folds transitions into the record
  �[32mok�[0m    watch sees the stream's sequence numbers
  �[32mok�[0m    watch reports the turn that ended with no result
  �[32mok�[0m    watch never closes a task
  �[32mok�[0m    the task whose turn ended is still dispatched
  �[32mok�[0m    progress.jsonl records what happened when
  �[32mok�[0m    each floor resumes, so a second watch replays nothing
run log opened: /tmp/tmp.G7Ui6o3iCI/runs/2026-09-10-capture-every-transition.md
  �[32mok�[0m    a batch carrying two tasks leaves the first one complete
  �[32mok�[0m    and the one dispatched mid-window keeps its transition too
  �[32mok�[0m    events that arrived with no watch running are folded by the next one
  �[32mok�[0m    for every task in the gap, not just the first
  �[32mok�[0m    a watch that cannot write a record stops instead of walking past it
  �[32mok�[0m    an interrupted watch skips nothing it had not written
  �[32mok�[0m    and replays nothing it had
  �[32mok�[0m    a record from before per-task floors resumes at the retired cursor
  �[32mok�[0m    attach stamps a genuine zero when the stream's high-water mark is 0
  �[32mok�[0m    watch passes --since 0 to the real stream rather than dropping it
  �[32mok�[0m    collect finds nothing to conclude yet
  �[32mok�[0m    03 still waits after its blocker's turn ended
  �[32mok�[0m    collect reads the worker's own conclusion
  �[32mok�[0m    collect names the artifact
  �[32mok�[0m    and keeps the session, saying the pull request is still open
  �[32mok�[0m    an open pull request's session is never reaped
  �[32mok�[0m    03 keeps waiting: its blocker concluded but did not land
  �[32mok�[0m    so the ready set has not grown
  �[32mok�[0m    a dry run says what would land
  �[32mok�[0m    and which session it would release
  �[32mok�[0m    a dry run deletes nothing
  �[32mok�[0m    and a dry run wrote nothing either: 03 still waits
  �[32mok�[0m    the merge lands the task
  �[32mok�[0m    and its session is released
  �[32mok�[0m    the deletion is forced, or the worktree is never actually freed
  �[32mok�[0m    and it names the session the record held
  �[32mok�[0m    the record says landed
  �[32mok�[0m    and keeps the receipt for the session it released
  �[32mok�[0m    and stops pointing at an id that no longer resolves
  �[32mok�[0m    03 becomes ready once 01 has LANDED
  �[32mok�[0m    and it joins 04, which never waited
  �[32mok�[0m    01 has left the plan entirely
  �[32mok�[0m    list groups by topic
  �[32mok�[0m    list is one line per task, not a brief
  �[32mok�[0m    check validates every record
  �[32mok�[0m    an attested PR from the task's own branch collects clean
  �[32mok�[0m    and collect says which method it verified
  �[32mok�[0m    a PR that skipped the pipeline is caught
  �[32mok�[0m    the refusal says what the body does not carry
  �[32mok�[0m    the refusal says the task was not closed
  �[32mok�[0m    and states what would have proved it
  �[32mok�[0m    and quotes the tool the brief named, in the operator's own words
  �[32mok�[0m    a task whose PR failed the check is not closed
  �[32mok�[0m    an unreachable gh degrades to unknown
  �[32mok�[0m    and says the check could not run
  �[32mok�[0m    an unchecked artifact is never reported as verified
  �[32mok�[0m    but the loop still closes it — no network must not break collect
  �[32mok�[0m    and the record says the check could not run, not that it passed
  �[32mok�[0m    the lead can close a flagged task deliberately
  �[32mok�[0m    and the record keeps saying the artifact failed the check
  �[32mok�[0m    a shipped claim with no pull request is caught, not skipped
  �[32mok�[0m    and the refusal says the task was not closed
  �[32mok�[0m    a shipped claim with no pull request is not closed
  �[32mok�[0m    and the record says missing, not skipped
fatal: either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured
error: src refspec main does not match any
error: failed to push some refs to '/tmp/tmp.G7Ui6o3iCI/push-origin'
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
fatal: either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
error: pathspec 'main' did not match any file(s) known to git
run log opened: /tmp/tmp.G7Ui6o3iCI/runs/2026-09-10-publish-methods.md
  �[32mok�[0m    a task with no --publish takes POLICY.md's own default
  �[32mok�[0m    and the brief names the tool in the operator's own words
  �[32mok�[0m    a --publish pr task says so
  �[32mok�[0m    and carries the --how it was given
  �[32mok�[0m    and not the operator's default tool, which belongs to another method
  �[32mok�[0m    a --publish push task says so
  �[32mok�[0m    and says nothing about a tool when it was given none
  �[32mok�[0m    and every brief's result contract now admits a commit URL
  �[32mok�[0m    a pull request from the task's branch proves a pr task
  �[31mFAIL�[0m  and a commit on the base branch proves a push task
          expected to find: [publish verified: push]
          --- got ---
              publish-methods/03-push-task: NOT CLOSED — nothing proves this task published
                  https://github.com/acme/app/commit/HEAD
                  shipped with no commit URL to check
                  A `push` task is proven when the commit is an ancestor of the base branch on `origin`.
                  Send the worker back to publish again, then collect again.
                  If you have read the artifact yourself and judged it good as
                  it stands, close it deliberately with
                  `queue.sh collect --allow-unverified`.
              report-status-honestly/05-ship-without-proof: NOT CLOSED — nothing proves this task published
                  (no artifact given)
                  shipped with no pull request to check
                  A `no-mistakes` task is proven when the pull request is from this task's branch and its body carries a `no-mistakes` attestation for the commit that would merge.
                  Its brief said: run `/no-mistakes --yes`.
                  S

... [64805 bytes truncated] ...

32mok�[0m    and says the same of the transitions it folded
  �[32mok�[0m    and refuses to turn that into a claim about the worker
  �[32mok�[0m    and never guesses the worker is stuck
  �[32mok�[0m    and never guesses the worker is dead
  �[32mok�[0m    and never guesses the worker unreachable
  �[32mok�[0m    the record keeps the send itself, not a flag
  �[32mok�[0m    and the task is still exactly as dispatched — a message is not a
        completion
  �[32mok�[0m    with no outcome invented for it
  �[32mok�[0m    a task nobody messaged says nothing about a message
  �[32mok�[0m    and nothing about a silence it was never asked to explain
  �[32mok�[0m    and its row is the row it always was
fatal: not a valid object name: 'main'
  �[32mok�[0m    a git this machine cannot read is 'not checked', never a false negative
  �[32mok�[0m    and it names the host whose git it would have had to read
  �[32mok�[0m    a send that could not be delivered says so
  �[32mok�[0m    and the record carries it, so a silence is never read as delivery
  �[32mok�[0m    a concluded task's row drops the liveness line
  �[32mok�[0m    and `show`, which is the record itself, keeps it
  �[32mok�[0m    topic add scaffolds a run log without being asked
  �[32mok�[0m    the run log names the topic it was opened for
  �[32mok�[0m    and topic add said where it is, on stderr rather than in the value
  �[32mok�[0m    and the prose sections the lead owns are already there
  �[32mok�[0m    and the generated block is fenced
  �[32mok�[0m    the facts block carries each task
  �[32mok�[0m    with the branch it runs on
  �[32mok�[0m    and the artifact its worker reported
  �[32mok�[0m    and the timeline says when it was dispatched
  �[32mok�[0m    and the overlap that was accepted rather than serialized
  �[32mok�[0m    prose the lead wrote survives every later refresh
  �[32mok�[0m    three refreshes leave the same file, not three copies of it
  �[32mok�[0m    `run` names the log it maintains
  �[32mok�[0m    a log with no generated block is reported, not rewritten
  �[32mok�[0m    and it keeps every word
  �[32mok�[0m    the tracked template carries no path and no session id
fatal: either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured
run log opened: /tmp/tmp.G7Ui6o3iCI/runs/2026-09-10-on-another-forge.md
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
  �[32mok�[0m    collect verifies a publish claim on a forge that is not GitHub
  �[32mok�[0m    and it read a /-/merge_requests/ URL as a change request
  �[32mok�[0m    its pull request is open, so nothing was reaped
  �[32mok�[0m    a change request the forge cannot answer for degrades to unknown
  �[32mok�[0m    and says what the forge said
  �[32mok�[0m    an open change request lands nothing
  �[32mok�[0m    a merge on the fake forge lands the task
  �[32mok�[0m    and releases the session that produced it
  �[32mok�[0m    and it is the session the record held
  �[32mok�[0m    shepherd names the self-hosted repository, port and all
  �[32mok�[0m    and says what it would merge, in the fake forge's own words
  �[32mok�[0m    a dry run merges nothing on the fake forge
  �[32mok�[0m    a green, attested change request is merged through the seam
  �[32mok�[0m    a conflicting one gets a fixer, in the base branch's own terms
  �[32mok�[0m    and the fixer is dispatched
  �[32mok�[0m    one whose head is not ours is left alone
  �[32mok�[0m    and named as where the forge said it lives
  �[32mok�[0m    and a change request that is not ours is never merged
  �[32mok�[0m    a cancelled check reads as still running, not as a failed one
  �[32mok�[0m    so it never gets a fixer for a failed check
  �[32mok�[0m    a forge that cannot squash says so rather than merging some other way
  �[32mok�[0m    and nothing is merged while it cannot
  �[32mok�[0m    an auto-merge entry that names no forge is refused, not matched
  �[32mok�[0m    and nothing under it would be merged
  �[32mok�[0m    no code path ran `gh` while a different forge was configured
  �[32mok�[0m    a /-/merge_requests/ URL on gitlab.com is a change request
  �[32mok�[0m    and glab answers for it
  �[32mok�[0m    its head commit is the recorded one
  �[32mok�[0m    its head branch is the recorded one
  �[32mok�[0m    its base branch is the recorded one
  �[32mok�[0m    GitLab's `opened` is fleet's `open`
  �[32mok�[0m    a merge request from a FORK is not ours
  �[32mok�[0m    and the refusal line says where it lives
  �[32mok�[0m    its failed pipeline is one failed check
  �[32mok�[0m    an undocumented detailed_merge_status is not read as mergeable
  �[32mok�[0m    commits come back oldest-first out of a newest-first answer
  �[32mok�[0m    and the newest recorded commit is last
  �[32mok�[0m    a merge request that is not there is a reason, not an exception
  �[32mok�[0m    and the reason is the one glab put on stdout
  �[32mok�[0m    not the decorated box it put on stderr
  �[32mok�[0m    a github.com pull request is not this adapter's
  �[32mok�[0m    nor is a single-segment path, which GitLab has no such thing as
  �[32mok�[0m    an unconfigured self-hosted host is not ours either
  �[32mok�[0m    GITLAB_HOST configures a self-hosted instance, scheme and all
  �[32mok�[0m    and a subgroup path is the whole path
  �[32mok�[0m    whose first segment is the owner
  �[32mok�[0m    a checkout's origin names the project: https://gitlab.example.com/acme/group/widgets.git
  �[32mok�[0m    a checkout's origin names the project: git@gitlab.example.com:acme/group/widgets.git
  �[32mok�[0m    a checkout's origin names the project: ssh://git@gitlab.example.com/acme/group/widgets
  �[32mok�[0m    a pipeline for a commit that is no longer the head is no check at all
  �[32mok�[0m    and a merge request with no pipeline at all is no check either
  �[32mok�[0m    pipeline success reads as passed
  �[32mok�[0m    pipeline skipped reads as passed
  �[32mok�[0m    pipeline failed reads as failed
  �[32mok�[0m    pipeline canceled reads as cancelled
  �[32mok�[0m    pipeline running reads as pending
  �[32mok�[0m    pipeline manual reads as pending
  �[32mok�[0m    pipeline created reads as pending
fatal: either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured
run log opened: /tmp/tmp.G7Ui6o3iCI/runs/2026-09-10-on-gitlab.md
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
fatal: not a valid object name: 'main'
  �[32mok�[0m    collect verifies a publish claim on a self-hosted GitLab
  �[32mok�[0m    and it read a /-/merge_requests/ URL as a change request
  �[32mok�[0m    its merge request is open, so nothing was reaped
  �[32mok�[0m    a merged merge request lands the task
  �[32mok�[0m    and releases the session that produced it
  �[32mok�[0m    shepherd names the self-hosted project, subgroup and all
  �[32mok�[0m    and says what it would run, in glab's own flags
  �[32mok�[0m    a green, attested merge request is merged through the adapter
  �[32mok�[0m    and the merge names the exact head it checked, so a race cannot slip in
  �[32mok�[0m    a conflicting one gets a fixer, in the base branch's own terms
  �[32mok�[0m    one whose head is in another project is left alone
  �[32mok�[0m    and is never merged
  �[32mok�[0m    no call was ever aimed at gitlab.com
  �[32mok�[0m    every call named the self-hosted instance by full URL
  �[32mok�[0m    a project configured against squash says so in its own words
  �[32mok�[0m    and nothing is merged while it forbids it
  �[32mok�[0m    no code path ran `gh` against a GitLab merge request
  �[32mok�[0m    the probe proves a GitLab host against GitLab's own welcome
  �[32mok�[0m    and it asked THAT host, not github.com
  �[32mok�[0m    a self-hosted instance on a port is asked on that port
  �[32mok�[0m    GitHub's own banner still passes, unchanged
  �[32mok�[0m    a repo whose origin cannot be read says THAT, not 'no credentials'

�[31mqueue-selftest: failed�[0m
  • Evidence: same machine, same command, current code (0 FAIL) (local file: ~/.no-mistakes/evidence/01M26PR6EYCZSJZ2ZE7AD0JJJQ/queue-selftest-after-signing.txt)
Evidence: owner discovery on this machine — gh account, orgs, clone counts, GitLab as evidence only
CANDIDATE OWNERS — for registry/owners.txt, which is a list of GITHUB owners

    LeTuR                  gh account, local clones (1)
    sobri-it               gh org
    Thurbeen               gh org, local clones (2)
    ORG-PROSOL             local clones (2)

GITLAB CHECKOUTS — evidence, not owners

  gitlab.metyis.technology/france 32 local clone(s)

  These belong in no owners file: the map is built with gh. A task targets a
  GitLab repo by host and path, through the seam in scripts/lib/forge.py, and
  needs glab authenticated for that host.
Evidence: owner discovery against a hostile clone tree — ssh alias found, port not an owner, upstream and vendored clones excluded
CANDIDATE OWNERS — for registry/owners.txt, which is a list of GITHUB owners

    me                     local clones (2)
    Thurbeen               local clones (2)

  note: gh is not authenticated, so the account and its orgs could not be read: gh auth login

GITLAB CHECKOUTS — evidence, not owners

  gitlab.com/acme                1 local clone(s)

  These belong in no owners file: the map is built with gh. A task targets a
  GitLab repo by host and path, through the seam in scripts/lib/forge.py, and
  needs glab authenticated for that host.
Evidence: a machine that says nothing exits 1 rather than inventing an owner
No candidate owners found.

  gh is not authenticated, so the account and its orgs could not be read: gh auth login
  Nothing on this machine names a GitHub owner: no gh session, no
  github.user, and no clone with a github.com remote under the roots
  scanned. Name a root to scan, or write registry/owners.txt by hand
  from registry/owners.example.txt.
Evidence: placing the pane: the block landed right of the centre column, backup, lua re-read, plugin check green, idempotent re-run
=== place (RIGHT, the recommended side) ===
Placed the queue pane right of the terminal.

  file:   /tmp/tmp.5JzIY7MfMq/lay/layout.lua
  backup: /tmp/tmp.5JzIY7MfMq/lay/layout.lua.bak-20260911013026

~/.config/thurbox/ui
  ✓ loads — sessions, agent, confirm, search, new_session, restore, fleetqueue

thurbox-cli plugin check is green. Press F3 in thurbox.
exit=0

=== diff against the stock arrangement ===
--- scripts/fixtures/layout/stock.lua	2026-09-11 00:24:27.189059296 +0200
+++ /tmp/tmp.5JzIY7MfMq/lay/layout.lua	2026-09-11 01:30:26.278111889 +0200
@@ -123,6 +123,13 @@
       columns[#columns + 1] = { slot = "sessions", pct = 25, min = 20 }
     end
     columns[#columns + 1] = { slot = "center" }
+    -- fleet's queue pane. Guarded like the session column: panels.shown is
+    -- what the pane's F-key toggles, and without it the column is carved
+    -- every frame, so the key flips a state nothing reads and the pane
+    -- opens and never closes. Added by fleet's scripts/place-pane.sh.
+    if panels.shown("fleetqueue") and filled(ctx, "fleetqueue") then
+      columns[#columns + 1] = { slot = "fleetqueue", pct = 30, min = 34 }
+    end
     children[#children + 1] = { axis = "horizontal", children = columns }
   end
 

=== files beside it ===
layout.lua
layout.lua.bak-20260911013026

=== lua re-read of the edited layout ===
loadfile ok

=== --check after placing ===
placed: /tmp/tmp.5JzIY7MfMq/lay/layout.lua already carves a column for slot "fleetqueue"
exit=0

=== re-run: idempotent ===
Already placed — /tmp/tmp.5JzIY7MfMq/lay/layout.lua carves a column for slot "fleetqueue", and nothing was changed.
exit=0
Evidence: the three arrangements place-pane refuses, each with the block printed and the file untouched
=== (1) an arrangement with no center column ===
Refusing to edit /tmp/tmp.5JzIY7MfMq/refuse/no-anchor.lua: it has no line placing the "center" slot in a columns list.
That is the only shape this recognises. Your arrangement is yours — add
this block beside the other side columns yourself:

  -- fleet's queue pane. Guarded like the session column: panels.shown is
  -- what the pane's F-key toggles, and without it the column is carved
  -- every frame, so the key flips a state nothing reads and the pane
  -- opens and never closes. Added by fleet's scripts/place-pane.sh.
  if panels.shown("fleetqueue") and filled(ctx, "fleetqueue") then
    columns[#columns + 1] = { slot = "fleetqueue", pct = 30, min = 34 }
  end
exit=3
file untouched: yes

=== (2) a layout with the anchor but no panels.shown() ===
Refusing to edit /tmp/tmp.5JzIY7MfMq/refuse/no-shown.lua: it has no call to panels.shown(), which the block's guard needs.
That is the only shape this recognises. Your arrangement is yours — add
this block beside the other side columns yourself:

exit=
exit=3

=== (3) a layout with the anchor but no filled(ctx, ...) helper ===
Refusing to edit /tmp/tmp.5JzIY7MfMq/refuse/no-filled.lua: it has no call to panels.shown(), which the block's guard needs.
That is the only shape this recognises. Your arrangement is yours — add
this block beside the other side columns yourself:

exit=3

=== (4) no layout.lua at all ===
error: no layout.lua at /tmp/tmp.5JzIY7MfMq/refuse/does-not-exist.lua — thurbox writes one on first run, so start it once first
exit=2

=== nothing was written in any refusal ===
no-anchor.lua
no-filled.lua
no-shown.lua
=== a layout that guards with panels.shown but defines no filled(ctx, ...) helper ===
Placed the queue pane right of the terminal.


=== a layout that guards with panels.shown but has no filled(ctx, ...) helper ===
Refusing to edit /tmp/tmp.5JzIY7MfMq/refuse/no-filled.lua: it has no filled(ctx, ...) helper, which the block calls.
That is the only shape this recognises. Your arrangement is yours — add
Evidence: a renamed pane places the slot the pane declares, not one the script spells
+++ /tmp/tmp.5JzIY7MfMq/lay4/layout.lua	2026-09-11 01:31:27.571649996 +0200
+    -- fleet's queue pane. Guarded like the session column: panels.shown is
+    -- what the pane's F-key toggles, and without it the column is carved
+    -- every frame, so the key flips a state nothing reads and the pane
+    -- opens and never closes. Added by fleet's scripts/place-pane.sh.
+    if panels.shown("queue_v2") and filled(ctx, "queue_v2") then
+      columns[#columns + 1] = { slot = "queue_v2", pct = 30, min = 34 }
+    end
Evidence: --left places the column before the terminal
=== --left places it before the centre column ===
Placed the queue pane left of the terminal, beside the session list.

  file:   /tmp/tmp.5JzIY7MfMq/lay3/layout.lua

--- scripts/fixtures/layout/stock.lua	2026-09-11 00:24:27.189059296 +0200
+++ /tmp/tmp.5JzIY7MfMq/lay3/layout.lua	2026-09-11 01:31:27.506194914 +0200
@@ -122,6 +122,13 @@
     if panels.shown("sessions") and filled(ctx, "sessions") then
       columns[#columns + 1] = { slot = "sessions", pct = 25, min = 20 }
     end
+    -- fleet's queue pane. Guarded like the session column: panels.shown is
+    -- what the pane's F-key toggles, and without it the column is carved
+    -- every frame, so the key flips a state nothing reads and the pane
+    -- opens and never closes. Added by fleet's scripts/place-pane.sh.
+    if panels.shown("fleetqueue") and filled(ctx, "fleetqueue") then
+      columns[#columns + 1] = { slot = "fleetqueue", pct = 30, min = 34 }
+    end
     columns[#columns + 1] = { slot = "center" }
     children[#children + 1] = { axis = "horizontal", children = columns }
   end
  • Evidence: the offline onboarding selftest, run under a caller environment that used to decide its answers (local file: ~/.no-mistakes/evidence/01M26PR6EYCZSJZ2ZE7AD0JJJQ/selftest-hostile-gitenv.txt)
Evidence: rollback proof: an edit into a non-parsing layout is restored byte-identically
=== placing into a layout that does not parse: the edit must not survive ===
error: the edited layout no longer parses as Lua; restored /tmp/.../layout.lua.bak-20260911013053
exit=3
--- file identical to before the run? ---
IDENTICAL (edit rolled back)
- Outcome: 🔧 2 issues found → auto-fixed ✅ across 2 runs (45m19s)

Pipeline

Updates from git push no-mistakes

... (8 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)

🔧 **Test** - 2 issues found → auto-fixed ✅

🔧 Fix applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 23 of 26 scenarios driven live against the product
Scenario Result Live Evidence
An operator on a healthy machine runs preflight and sees one table in three tiers, each row saying what breaks without it ✅ pass live ./scripts/preflight.sh → exit 0, required/recommended/gate sections; evidence/preflight-table.txt
A machine missing gh, jq and thurbox-cli is told so in one pass and preflight exits non-zero ✅ pass live env -i PATH=&lt;sandbox&gt; bash ./scripts/preflight.sh → exit 1, "3 required dependencies missing"; evidence/preflight-missing-required.txt
Asking for one tier narrows what is printed but never softens the verdict (adversarial) ✅ pass live --tier gate on a machine missing three required tools still exits 1; --tier bogus exits 2; evidence/preflight-tier-gate-still-fails.txt
Missing recommended and gate tools are reported and never fatal, and quota-axi is one of them ✅ pass live all required stubbed present, quota-axi/glab/shellcheck/rumdl/prek absent → exit 0 with quota-axi's cost named; evidence/preflight-recommended-gaps-not-fatal.txt
The operator said yes to installing: --commands hands back only the actionable install lines ✅ pass live ./scripts/preflight.sh --commands prints the remedies for the gaps and drops the see &lt;url&gt; rows; evidence/preflight-commands.txt
A thurbox older than the manifest's floor is reported stale rather than ok, with the floor read from extension.toml.in ✅ pass live stubbed thurbox-cli 2.18.0 vs floor 2.19.0 → stale ... sets the floor at 2.19.0; evidence/preflight-stale-thurbox.txt
Commit signing scoped to the checkout by an includeIf block does not hide the machine-wide gap (adversarial) ✅ pass live git inside the checkout reports a signing key while preflight, probing outside it, still reports missing commit signing; ok when the key is truly global; evidence/preflight-signing-row.txt
The gate failure the signing row exists to explain: sandbox commits fail before the fix and pass after it ✅ pass live pre-fix scripts/queue-selftest.sh from 2cf6088 → 18 FAIL on this machine; current → 0 FAIL, "every claim holds"; evidence/queue-selftest-{prev,after}-signing.txt
Owner discovery reads this machine and prints candidates with the evidence for each ✅ pass live ./scripts/discover-owners.sh → gh account, gh orgs and local clone counts per owner; evidence/discover-owners-real.txt
GitLab checkouts are reported as evidence and never reach the GitHub owner list ✅ pass live a self-hosted GitLab namespace with 32 clones appears only under "GITLAB CHECKOUTS — evidence, not owners"; same in the fixture tree; evidence/discover-owners-real.txt, discover-owners-adversarial.txt
Discovery survives the remote shapes that used to break it: ssh alias found, an ssh:// port is not an owner, a fork's upstream is not an owner, vendored clones are pruned (adversarial) ✅ pass live fixture tree with git@github-perso:, ssh://git@ssh.github.com:443/, an upstream remote, node_modules and .vim/plugged clones → only me and Thurbeen; evidence/discover-owners-adversarial.txt
A machine with nothing to say exits 1 and points at the file to write by hand instead of inventing an owner ✅ pass live empty HOME + empty root → exit 1, "No candidate owners found" with the gh remedy; evidence/discover-owners-nothing.txt
A machine whose gh was never logged in still answers from github.user and the noreply commit email ✅ pass live fake HOME with both fields → two candidates, each with its evidence, plus the gh-auth note; evidence/discover-owners-gitconfig.txt
A re-run marks the owners already committed to instead of proposing them again, and discovery writes nothing ✅ pass live temporary registry/owners.txt → * beside Thurbeen and "* already in registry/owners.txt"; worktree clean afterwards; evidence/discover-owners-rerun-marker.txt
Saying yes to the pane puts a guarded column to the RIGHT of the terminal, backed up, re-read, and verified ✅ pass live ./scripts/place-pane.sh --layout &lt;copy of stock.lua&gt; → block after the center column with the panels.shown guard, layout.lua.bak-<ts>, lua loadfile ok, thurbox-cli plugin check green; evidence/p…
Running placement twice changes nothing the second time, and --check tells a placed pane from an unplaced one ✅ pass live --check exit 1 before / exit 0 after; second placement run prints "Already placed ... nothing was changed"; evidence/place-pane-happy-path.txt
--left puts the column between the session list and the terminal ✅ pass live diff shows the block inserted before columns[...] = { slot = &#34;center&#34; }; evidence/place-pane-left.txt
An arrangement the script cannot recognise is refused untouched, with the block printed for the operator (adversarial) ✅ pass live no center anchor, no panels.shown(, and no filled(ctx, each exit 3 naming what was missing and leave no backup; a missing layout.lua exits 2; evidence/place-pane-refusals.txt
An edit that would leave the layout unparseable does not survive: the backup goes straight back (adversarial) ✅ pass live placing into a broken-but-recognisable layout → exit 3, restored backup, cmp reports the file byte-identical to before the run
A commented-out fleet block is not treated as a placement (adversarial) ✅ pass live --check on a layout whose block is commented out → exit 1 "loads, lists, and draws nothing"; placing over it then succeeds
The block carries the slot the pane declares, so a rename cannot half-land ✅ pass live --pane &lt;copy declaring queue_v2&gt; → the written block guards and carves queue_v2; evidence/place-pane-renamed-slot.txt
A layout under a path containing an apostrophe is still placed rather than blamed on a parse failure (adversarial) ✅ pass live --layout &#34;/tmp/.../o&#39;brien/layout.lua&#34; → exit 0, block present; evidence/place-pane-quoted-path.txt
The gate runs the onboarding coverage, and a caller's git environment cannot decide its answers ✅ pass live ./scripts/check.sh onboarding ok; ./scripts/onboarding-selftest.sh green bare and under GIT_CONFIG_COUNT/KEY_0/VALUE_0 forcing gpgsign=false; evidence/onboarding-selftest.txt, selftest-hostile-git…
The guided skill itself: seven announced steps with exactly four asks, always asking before the layout is written ⏸️ untested no The ask-then-write contract lives in skill prose, not in code: the scripts it calls are drivable (and were driven) but the asking is model behaviour. Driving it would need an agent session harness tha…
A full fresh-clone onboarding run: dependencies installed, registry synced, extension installed, reconciler up ⏸️ untested no Every one of those steps writes outside this worktree (package installs, registry/owners.txt on the operator's machine, a rewrite of the live thurbox extension manifest and its Mission Control session…
The new unfiltered CI onboarding job installs lua and runs the selftest on a runner ⏸️ untested no Running the workflow needs a GitHub Actions runner and a push/PR on this branch, which the outer executor's CI phase owns; this phase has no authority to push or trigger CI.
  • ./scripts/check.sh
  • ./scripts/preflight.sh on this machine (real table, exit 0)
  • ./scripts/preflight.sh --commands, --tier required, --tier bogus (exit 2)
  • env -i PATH=&lt;sandbox&gt; ./scripts/preflight.sh with gh/jq/thurbox-cli absent → exit 1, 3 required gaps named
  • env -i PATH=&lt;sandbox&gt; ./scripts/preflight.sh --tier gate on the same machine → still exit 1 (view filtered, verdict not)
  • ./scripts/preflight.sh with all required present, quota-axi/glab/gate tools absent → exit 0
  • ./scripts/preflight.sh --tier required against a stubbed thurbox-cli 2.18.0 vs the manifest floor 2.19.0 → stale row
  • ./scripts/preflight.sh --tier gate under three fake HOMEs: gpgsign+no key, gpgsign+key, key scoped by includeIf gitdir:&lt;checkout&gt;
  • git show 2cf6088:scripts/queue-selftest.sh run on this machine (18 FAIL) vs ./scripts/queue-selftest.sh (0 FAIL)
  • ./scripts/discover-owners.sh on this machine
  • ./scripts/discover-owners.sh &lt;fixture root&gt; with an ssh-alias clone, an ssh://git@ssh.github.com:443/... clone, a fork with an upstream remote, a node_modules clone, a .vim/plugged clone and a GitLab clone
  • ./scripts/discover-owners.sh &lt;empty root&gt; with an empty HOME → exit 1
  • ./scripts/discover-owners.sh with only github.user and a users.noreply commit email in HOME
  • ./scripts/discover-owners.sh with a temporary registry/owners.txt → * marker
  • ./scripts/place-pane.sh --check|--dry-run|(place)|--left|--pane &lt;renamed&gt; against copies of scripts/fixtures/layout/stock.lua
  • ./scripts/place-pane.sh into layouts with no center anchor, no panels.shown(, no filled(ctx,, and no layout file at all
  • ./scripts/place-pane.sh into a layout that does not parse as Lua → backup restored, file byte-identical (cmp)
  • ./scripts/place-pane.sh into a layout under a path containing an apostrophe
  • ./scripts/place-pane.sh --check against the operator's real ~/.config/thurbox/ui/layout.lua (read-only)
  • ./scripts/onboarding-selftest.sh (bare, and under GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=commit.gpgsign GIT_CONFIG_VALUE_0=false)
  • ./scripts/check.sh onboarding
⚠️ **Document** - 2 infos
  • ℹ️ scripts/preflight.sh:296 - Judgment call left to the author: the commit-signing row sits in the gate tier, whose heading prints "GATE — only ./scripts/check.sh needs these". Since the last commit made scripts/queue-selftest.sh force commit.gpgsign=false for the repos it builds, the gate no longer needs that configuration fixed — the row is now about any other sandbox or worktree commit (a worker's worktree outside the tree an includeIf gitdir: block names). I made the prose accurate in preflight's comment, its printed why and the onboarding skill, but whether the row should stay in gate or move to recommended is a tiering decision that changes program output and exit semantics, so it is out of the documentation phase's scope.

🔧 Fix applied.
2 infos still open:

  • ℹ️ scripts/preflight.sh:262 - Unresolved from the previous document round, and out of this phase's authority: the commit-signing row is recorded under the gate tier, whose printed heading is "GATE — only ./scripts/check.sh needs these" and whose header definition is "only ./scripts/check.sh needs it. A control plane that never pushes a change never needs these." Since c4de77a made scripts/queue-selftest.sh force commit.gpgsign=false for the repos it builds (and it is the only selftest that commits — grep -n &#39;git commit&#39; scripts/*.sh finds no other), the gate no longer needs that configuration fixed. The row's own why is now accurate ("any sandbox or worktree"), so an operator reads a heading and a row that disagree. Resolving it either way is a code change I may not make here: move the row to recommended (record gate &#34;commit signing&#34;record recommended, twice, at lines 262 and 265), or broaden the gate heading and tier definition. Documentation cannot close it without asserting one of those choices.
  • ℹ️ scripts/place-pane.sh:73 - ./scripts/place-pane.sh --help prints the line set -uo pipefail after the exit-code paragraph: the help renderer is sed -n &#39;2,48p&#39; but the header block ends at line 46 (47 is blank, 48 is the set line). preflight.sh ('2,33p') and discover-owners.sh ('2,31p') both stop exactly on their last header line, so this is an off-by-two in the new script only. It is a documentation defect whose fix is a one-character code edit ('2,48p' → '2,46p'), which this phase may not make. I also left the header's line count unchanged for that reason — adding lines to it would push real usage text out of the rendered range.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

… with

Onboarding probed prerequisites in prose, asked the operator to type
owners the machine already knows, and printed a layout.lua block for
them to paste — which is how a setup ends with an invisible queue pane.
Four scripts now own the mechanical halves, and the skill asks at the
four points where the answer is genuinely the operator's.

- scripts/preflight.sh: every dependency in one pass, in three tiers,
  each row carrying what breaks without it and the command that fixes
  it. quota-axi and glab join the list; so does the git commit-signing
  configuration, which fails check.sh queue in a dozen unrelated-looking
  ways. --commands hands over the runnable lines, --tier narrows them.
- scripts/discover-owners.sh: owner candidates from the gh session, the
  git config and the remotes of the clones already on disk, each with
  its evidence. It matches an ssh host alias, so a machine with two
  GitHub accounts is not invisible to it. GitLab remotes are evidence,
  never owners.
- scripts/place-pane.sh: writes the guarded block into layout.lua, right
  of the terminal by default. On the operator's word only — it refuses a
  layout it cannot read, backs up first, re-reads its own edit with lua,
  and verifies with plugin check. The slot comes from the pane.
- scripts/onboarding-selftest.sh + check.sh onboarding + a CI job: all
  three driven offline against stubs and a stock layout fixture.

The skill is now seven announced steps with four asks: install what is
missing, which owners the map covers, where the pane goes, and whether
the reconciler comes up.
GIT_CONFIG_COUNT/KEY/VALUE outrank GIT_CONFIG_GLOBAL, so §1f's answer was
decided by whoever invoked the selftest — and the invocation that does set
them is `check.sh` run with commit.gpgsign forced off, which is exactly how
this repo gets gated on a machine whose signing is misconfigured. The test
now clears them, so it passes and fails for its own reasons either way.
@LeTuR
LeTuR merged commit e559816 into main Sep 11, 2026
11 checks passed
@LeTuR
LeTuR deleted the feat/onboarding-guided branch September 11, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant