Repository navigation
Fresh install to the first Planner session - #2
Merged
Merged
Conversation
On a project fresh from `install.sh`, `/xcheck-plan` could never start the Planner. Three refusals met in a loop: - `install.sh` writes no `audit/state.json`, by design (README §5.2). - `xcheck status` and `xcheck next` refuse without one, and the refusal advised running `install.sh` again, which writes none. - `xcheck migrate` refuses an AUDIT.md the Planner has not written. So the first role of every audit had no orchestrated route at all, and xcheck no longer ships a launcher for the uncontained one. Found by running `/xcheck-plan` on a new install. `decision.pre_state_decision` runs only when there is no state document, and answers in one of three ways: - `run-planner` for a proven fresh install whose AUDIT.md is not yet a complete plan. The session runs under the same eight controls and the same budget gates as any other, priced against an empty state. - `stop-migrate` once AUDIT.md converts into a complete plan. This is a human gate: loading the plan is the operator's act, so the orchestrator lists the queued passes and names `xcheck --dry-run migrate` and `xcheck migrate` instead. - Refusal, as before, for every other stateless tree, now with the reason. "Fresh" is three facts, and each fails closed. `audit/.provenance.json` must say `install.sh` made the tree. No findings, pass reports, plans, construals or LEDGER.md rows may exist, because those make it an old audit to convert. And the event journal must record no state transition: a tree whose state document existed and is gone is told to restore it, since planning again would start the audit over and orphan its history. `construal_gate=on` refuses the pre-state Planner rather than skipping the gate. A construal is admitted into the state document, which a new audit does not have, so the gate could only fail. The refusal says how to plan with it off and turn it back on. The same path had two smaller faults: - A failed Planner session raised a traceback in the retry check, which read the last session from a state file that does not exist yet. It now stops on the session's own outcome, with exit 1. - The no-progress message told the operator the charter was exactly where it was. Before migration the session's work is AUDIT.md, so it says that and points at `xcheck status`. The state-file error now names `xcheck migrate` only. README §5.2, §6 Step 1 and §8 describe the route. XCHECK.md and the launcher skills are unchanged, so installed trees need no upgrade. `tests/test_fresh_install.py` runs the real `install.sh`, and a fake agent for the dispatch cases: 17 tests, 15 of which fail on the parent. The other two are controls that a too-wide route would break. selftest 169 -> 170 for the `stop-migrate` row in the gate-message table. Full suite on this machine: 1,737 tests, 5 failures and 1 error, the same set as on the parent. Each one is a machine dependency: no `build` module, no `ruff`, and rows A2 and A3 of the SECURITY.md adversarial matrix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new operator went from `install.sh` to `/xcheck-plan` on a real codebase and met four problems in a row: - `install.sh` always installed the text-corpus AUDIT.md. A codebase needed a hand `cp` that only a note under README §5.2 mentioned, so the plan started from `facts`, `style` and `currency`. - Nothing on the install path said an operator profile is required. README §5 and §6 Step 1, bootstrap.md and the installer's output were silent; only the §8 reference explained it. - `xcheck status` printed `decision: run-planner` and nothing about the profile, so `next` was the first command to say it would not run. The launcher skills already tell operators to read the profile there. - The refusal's hint, "`xcheck --policy PATH` with a path that does not exist prints the template", names a command the grammar rejects: `--policy is not valid for no command`. The template is printed only after a command is parsed. The same message spelled the XDG location `$(XDG_CONFIG_HOME)`, which a shell reads as command substitution. What changes: - `install.sh --type code|text` picks the template, before or after the target. No flag still means `text`, so existing callers get what they got, and the output now says which template that was. An unknown type is refused before anything is written. An existing AUDIT.md is never replaced; if it is still an unfilled template, the installer prints the `cp` that switches it. The last lines are the next steps, the profile first. - `operator-profile.sh` writes a profile from docs/operator.conf.example, which is already held byte-identical to the template. `--trust` is required and has no default. `untrusted` also sets `sandbox_profile=container`, without which every writing dispatch refuses. The path is named by the operator or by $XDG_CONFIG_HOME, never derived from the home directory. The file is validated by `policy.load` (with `--project`, the location gate too) and then hard-linked into place, so it never overwrites. It prints the line that points xcheck at the file, in fish syntax under fish. - `status` prints a `policy:` line: the resolved path and digest in the words `require_policy` uses at dispatch, or "none found" with the helper named. - The refusal names the helper and `xcheck --policy PATH status`, and spells `$XDG_CONFIG_HOME`. - README: a new §5.3 for the profile (5.3-5.6 become 5.4-5.7, and their three references move with them). §5.2 documents `--type`, §6 Step 1 points at §5.3, §8 names the helper, and §15 has a row for the refusal. bootstrap.md: the installer command, a new §1a for the profile; the old §1a is now §1b. Three existing checks move with this: - selftest F-0127 inverted the AUDIT.md guard by matching the old one-line form, so the mutation stopped applying and the check went red. It now inverts the new `if [ ! -f ... ]` guard; 169/169. - `test_parallel_retry` compares two `status` outputs whose profiles differ by design (the flag absent, then written as `off`). Only the 16-digit digest on the `policy:` line is masked. - The containment-claim pin goes 209 -> 211 with its ledger entry: both new claims are in README §5.3, and unscoped stays at 0 because the section names `orchestrated` before the first of them. XCHECK.md and the launcher skills are unchanged, so installed trees need no upgrade. This builds on the previous commit, which lets `status` and `next` run on a fresh install that has no state.json yet; the two together are the whole route from install to the first Planner session. `tests/test_operator_onboarding.py` (PR tier) runs the real scripts and CLI: 26 tests, all of which fail on the parent. The refusal test parses the hint and runs it, so a reworded hint is held to the same standard. Full suite on this machine: 1,746 tests, 5 failures and 1 error, the same set as on the parent. Each one is a machine dependency: no `build` module, no `ruff`, and rows A2 and A3 of the SECURITY.md adversarial matrix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Planner given templates/AUDIT-code.md wrote a full plan for a real
codebase (19 norms, 10 dimensions, 74 units, 90 passes) that `xcheck
migrate` refused:
the Unit map table has no 'unit' column (header: id | material |
size | responsibility)
The converter finds each catalog column by its exact header name, but
the templates showed the header of only one of the three tables it
reads. §1 Norms catalog and §3 Unit map held a comment and nothing
else, so a Planner had to guess `id | source | scope` and `unit |
material | responsibility`. This one got the first right and named the
unit column `id`. It could not find out: `xcheck` is not on PATH in the
Planner's sandbox. The comment above _UNITMAP_COLS in
migrate_readers.py already described the templates as carrying `unit |
material | approx. size | responsibility`.
- Both templates show `| id | source | scope |` and `| unit | material
| approx. size | responsibility |`, with a note that the converter
reads these names and that norm ids are N1, N2, ... A fresh
install's `--dry-run migrate` goes from 9 defects to the 7 dimension
rows a Planner fills in; `status` still routes it to the Planner.
- The selftest's template check (F-0130) holds all three catalog
headers, not only Dimensions. A live mutation names the unit column
`id`, as the real plan did, and expects it caught. 170/170.
- _NORMS_RE and _UNITMAP_RE move from migrate_readers to md_prose,
beside _DIMENSIONS_RE, because nothing outside the converter may
import migrate_readers (test_no_bypass).
- test_operator_onboarding fills each table under the header the
template shows and expects the plan to convert, with one norm,
dimension, unit and pass. On the parent it fails with "no Norms
catalog table" and "no Unit map table".
A plan already written keeps its header. One with `| id |` in its Unit
map loads once that cell reads `unit`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ca6897d's comment beside _NORMS_RE and _UNITMAP_RE put the module name migrate_readers and the word "import" on one line. test_no_bypass greps xcheck/ for that pair outside the converter, so it failed on a comment. The comment says the same thing without naming the module there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vsov
pushed a commit
that referenced
this pull request
Oct 16, 2026
PR #2 failed CI at its first real step, ruff F841 at cli.py:516. The contributor's machine had no ruff, which the PR says, so nothing local could show it. `checked` was never read in cmd_next before the PR either; the old single unpack of a call result is exempt from F841. The PR's new branch assigns a literal tuple, which ruff reads as plain assignments, and that made the dead name visible. Both branches now use `_checked`, like `_state` and `_report` on the same lines. No behaviour changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vsov
pushed a commit
that referenced
this pull request
Oct 16, 2026
The development gate failed on PR #2 with `test_every_place_that_names_the_stream_is_declared`: the new `migrate.not_fresh_install` names `events.jsonl`, and the 0.9.5 boundary gate requires every such site to say what it does there. Its use is benign: it counts `state_transition` events through `ledger.read_stream`, the validated reader, and the name appears only in message text. That is the shape of the existing `bundle.py` row, and it is declared the same way. Neither the contributor nor this repository's public CI could see it. `OneValidatedReadBoundary` carried a CLASS-level `@needs_live_corpus` from the 0.9.5 export work, and only one of its five tests reads the corpus. The other four run on temp fixtures or scan the package's own source. In the exported tree all five skipped, so the boundary check was disarmed exactly where an outside contribution is checked. The gate moves to the one method that reads the real stream, and `setUpClass` reads its digest only when the corpus is there. Exported tree: 5 skipped -> 1 skipped, 4 running. Counterfactual in the exported tree, with the new row removed: the check fails naming `migrate.py not_fresh_install` (failures=1, skipped=0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes found by running /xcheck-plan on a new install. Together they're the whole route from install.sh to the first Planner session.
and migrate refusing in a loop.
operator-profile.sh writes the operator profile. status shows a policy: line, and the refusal's hint is
now a command that actually runs.
New tests: tests/test_fresh_install.py (17) and tests/test_operator_onboarding.py (26). Each commit message has the full detail.