Nick's critique structuralized (2026-09-07):
- 'is research being done or already done?' → sweep detects existing
4-phase rounds (INWARD/OUTWARD/INNOVATION/SYNTHESIS comments) and SKIPS
those issues; never re-researches completed work
- research must CALL the LLM → research-stage-headless.yml dispatches headless
mimo on research-pending label events (spec-gate runner pattern, verified)
- filed as a research FILE per issue → research/issue-<n>-research.md merged
to master via auto-PR; comment carries the markers research-clear keys on
- 4 phases → SYNTHESIS ends in ordered build instructions = implementer's prompt
- TUI callback seam → prompt externalized to governed prompts/research-round.md
(placeholders substituted at runtime; L3/TUI owns it via reviewed diffs later)
- failsafe: sweep dry-run by default; --apply explicit; red-first tests (10)
Also: spec-gate-headless.yml copied to master stream with ROBOT_TOKEN wired
(same reserved-secret bug the robot had — GITEA_TOKEN is the read-only auto token).
Red-first discharge (#214 follow-up): verify_identity built a repo-scoped URL
(.../repos/o/r/user -> 404 plain-text body) which crashed json.loads before
MergeRefused could fire — the robot never ran successfully since #211. Three
stacked defects: missing checkout (#214), reserved-secret auth (#213), and
repo-scoped identity URL (this commit).
- verify_identity now derives the instance root itself (defense in depth:
never trust caller scope)
- _api converts non-JSON error bodies into MergeRefused instead of an
unhandled JSONDecodeError
- 2 red tests added first, frozen green; suite 615+84
Gate-review F4: two known blind spots were undocumented — (1) marker-only
drift (env-marker changes with identical pins) is invisible to the pin
diff, (2) direct-URL requirement lines are skipped by the old parser /
now fail loudly instead of being compared. Both disclosed in the module
docstring with guidance; no behavior change.
RED->GREEN: docs-only finding; red evidence is the pre-existing docstring
lacking the disclosure (gate finding itself). Checker behavior and tests
unchanged (15 pass).
Gate-review F3: with uv absent, run_uv_compile() let subprocess raise a
raw FileNotFoundError traceback. Now caught (sentinel -1) and main()
prints the house actionable pattern: what to install (pinned uv into
.ci-venv), what command (pip install + PATH export), local install doc.
Gate-review F5: uv's full resolution listing went to stdout even on
success, polluting CI logs. stdout/stderr are now captured and printed
only on compile failure.
RED->GREEN: new test (uv_binary stubbed to a missing binary, PATH
stripped) failed with FileNotFoundError, passes with exit 1 + actionable
stderr. Real smoke: successful run no longer prints uv's 'Resolved 37
packages' listing.
Gate-review F2: lock_requirements() silently skipped any requirement
line lacking '==', so a degraded floor-style lock (e.g. 'rune>=0.49.0')
would pass the drift diff with its floor pins intact. Options
(-r/--index-url/...) remain the only tolerated non-pin lines; any other
non-pinned form (floor, range, wildcard, bare name) now exits 1 with an
actionable message naming the offending line and the regen command.
RED->GREEN: two new tests (floor fixture + unpinned/wildcard variants)
failed against the skip behavior, pass after the loud-failure fix. The
pre-existing skip-options test was updated to match the tightened
contract; the committed lock (37 pins) still parses clean.
Gate-review F1: the drift-ratchet step installed uv==0.10.11 into
.ci-venv but never exported PATH, so check_dep_lock.py resolved
/usr/bin/uv (0.12.10) and re-compiled the lock with a different
resolver than generated it. Both ends fixed:
- ci.yml drift step: export PATH="$PWD/.ci-venv/bin:$PATH" before
invoking the checker.
- check_dep_lock.uv_binary(): prefer the interpreter-adjacent uv
(sys.executable's dir — where CI pins it) over shutil.which.
RED->GREEN: two new tests (PATH-stripped + decoy-uv-on-PATH) failed
against the old resolver order, pass after the fix.
requirements-ci.txt: fully-pinned output of
'uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra
memory --python-version 3.11' (plain pip format; runners need no uv).
scripts/check_dep_lock.py: drift ratchet — regenerates the compile into a
temp file, diffs requirement pins against the committed lock, exits 1 with
the regeneration command on drift. Unit-tested against fixture files.
The arm64 CI runner reports different pyright counts than x86_64 local
(114 errors / 451 warnings vs 121/445). The baseline must be measured on
the same platform that enforces it. Also: pyright binary path fix for
the canary (Node.js wrapper, not a Python module).
The type-check ratchet used inline python3 -c with multi-line code that
broke YAML indentation. Moved to a standalone script file for reliability.
Same logic: compare current pyright counts against the committed baseline.
Track branch policy, labels, milestones, and wiki content as declarative state with a read-only-by-default provisioner.
Harden off-disk backup publication and add a rootless, networkless restore drill that validates SQLite, application data, and database-declared bare repositories.
Run offline contracts in CI, pin audited dependencies, document operations, and bump Rune to 0.43.0.