Skip to content

docs(services): correct the stale out-of-scope service declarations - #104

Merged
inkatze merged 9 commits into
mainfrom
planwright/dev-services/task-5
Aug 7, 2026
Merged

docs(services): correct the stale out-of-scope service declarations#104
inkatze merged 9 commits into
mainfrom
planwright/dev-services/task-5

Conversation

@inkatze

@inkatze inkatze commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Task 5 corrects the statements this repo makes about database services now that the services role provisions them. Tasks 2, 3 and 4 built the declared dev-services layer; the tracked prose written when no such layer existed was left asserting the opposite.

The task named two places. There were three kinds of stale statement, and the extra one is why this branch carries a repo-wide scan rather than two edits. roles/linux/defaults/main.yml placed databases out of scope as long-running services, and two unrelated package blocks (Vulkan drivers, Steam) justified their own entries by arguing against that same clause. Correcting the databases bullet alone would have left two comments citing a rule that no longer exists.

How to review: read roles/linux/defaults/main.yml and the new roles/services/ paragraph in CLAUDE.md first, then scripts/stale-declarations-test.sh, which is the executable form of both Done when: clauses.

  • Tasks: dev-services/5 · REQs: REQ-B1.5
  • Brief: specs/dev-services/kickoff-brief.md · anchor fecce5e40273806bfd25dc3f23b3e192e79de088, verified unchanged at pre-flight and at every commit
  • Tests: scripts/stale-declarations-test.sh — eight assertions. It scans the whole checkout rather than the two files the task names, because the risk in a contract reword is not breakage but a claim left somewhere nobody thought to reread. Every assertion is mutation-tested; the scanner runs against a five-file fixture on every run so it cannot go quietly blind and keep reporting clean; and it was verified against the pre-fix content, where it finds exactly the three stale spots and correctly leaves alone the Containers bullet sharing their comment block.
  • Not satisfied, deliberately: the [test] pin on REQ-B1.5. See below.

The one test-spec pin this branch does not satisfy

test-spec.md pins REQ-B1.5 as [test], which spec-format defines as "runs in the repo's CI". The wiring that asks for is a step in the lint job, and I wrote it, ran it green, and then backed it out. It edits .github/workflows/test.yml, which is CI configuration and therefore a hard-disqualifier zone, and Task 5's own REQs call for no CI change. (Task 2's matrix entry was different: its own Deliverables named the CI edit, so it was signed off at kickoff. REQ-E1.4 is the constraint on that edit, not what called for it.)

The zone screen fired, so per the pause protocol the fix is recorded rather than applied. The exact YAML, additions-only so REQ-E1.4 stays intact, is in specs/dev-services/tasks.md under ## Awaiting input, along with the two alternatives if CI is not where this belongs. This is the one place the branch knowingly leaves a pin unmet, and it needs your call rather than my guess.

Related, and more actionable than when I recorded it: Task 6's PR #103 adds a Linux CI job. Five manual assertion harnesses now live under scripts/ with no single command that runs them (specs/_observations/entries/2026-08-06-manual-test-scripts-have-no-runner-7bf7f037.md); a Linux job on main is a plausible home for the checkout-only ones.

CI status

lint and test (services) both pass, and test (services) runs at strict_idempotency: true, which is the entry that exercises the role this PR documents. Nine of eleven checks are green.

test (osx) and test (upgrade) show red. Neither is caused by this branch. Both stall in Install common packages from Brewfile and are cancelled by the job's timeout-minutes: 30 cap with no error output, and both are cancelled the same way on main in each of its last four runs. I did not retry them: the doctrine classes a timeout as transient and allows two retries, but a failure that reproduces on main four runs running is a standing condition rather than a flake, so retrying would spend macOS runner minutes to re-derive a known answer. Recorded instead as specs/_observations/entries/2026-08-06-osx-upgrade-jobs-exceed-timeout-a03a5d36.md, with candidate fixes.

Audit record

Lens coverage

Lens Findings Notes
Correctness, logic, edge cases 1 A scan that died partway exited non-zero the same way a scan finding a stale claim does, so the failure branch named the wrong cause and listed nothing. Walked inline, not via fan-out: the sub-agent dispatched for this lens never returned, so the row is an inline pass rather than a dropped one.
Security none No untrusted input, no subprocess construction, no credentials. The scan reads tracked files and prints matched lines; every file it can read is already public in this repo.
Error handling and failure modes 3 Missing git/PyYAML aborted bare instead of refusing by name; unreadable files were skipped silently; an empty file listing certified a repository never read.
Performance n/a A 173-file text scan, run manually.
Concurrency / state n/a No shared state, no parallelism, no ordering dependency.
Naming, readability, structure 2 Dead pass/fail helpers copied from the sibling and never called, with an accounting scheme incompatible with the group counter actually used; services-role-described reported more than it checked.
Documentation 6 Four accuracy errors in this branch's own new prose, plus two overclaims in the tasks.md bullet.
Tests / verification 8 No positive control; line-distance pairing wrong in both directions; polarity-blind detector; self-satisfying assertion; hardcoded declaration path; enumeration-vs-contrast conflation; greedy quote-specific TOML regex; no positive assertion on either deliverable file.
Cross-file consistency 3 The script and CLAUDE.md described the role's pre-bundle macOS content differently; main.yml's corrected comment had no guard; redis-tools sat unexplained beside a block newly documenting the client/server split.

Discovery ran as a parallel lens fan-out over the branch diff (three read-only agents: correctness; tests/verification; documentation + cross-file consistency), with a repo-wide independent completeness sweep for stale claims using several surface phrasings beyond the scan's own. That sweep returned zero remaining hits. The correctness agent stopped without reporting; its lens was walked inline instead and produced the finding in row one.

Auto-applicable

# Finding Tool + rule Fix Commit
1 CLAUDE.md said the role applies "the colima steps" on Darwin; all of them are further gated on inventory_hostname == 'personal' none — see the note below this table named the extra guard dde833d
2 CLAUDE.md read "the exception among those" after the enumeration it refers to stopped containing services none "the exception to those" dde833d
3 The databases-and-caches omission bullet named no cache, so grepping the block for valkey-server got no hit none added the package names; redis-tools now says which half of the pair it is dde833d
4 tasks.md attributed Task 2's CI matrix entry to REQ-E1.4, which is the constraint on that edit rather than what called for it none corrected the attribution dde833d
5 The script claimed the role's "whole content was a ~/.my.cnf symlink" before this bundle; the colima tasks predate it, contradicting CLAUDE.md in the same diff none reworded 8ca8774
6 "its two siblings" undercounted the manual harnesses; there are four none named all four 8ca8774
7 No positive control: nothing proved the scanner could still detect a stale claim none five-file fixture asserted in both directions on every run 8ca8774
8 Line-distance pairing was wrong narrow (reflow = silent miss) and wide (neighbouring paragraph = false hit) none replaced with statement-unit pairing; verified against pre-fix content 8ca8774
9 Polarity-blind: "databases are no longer out of scope" would fail, pressuring docs away from naming the history they correct none negation lookback immediately before the phrase 8ca8774
10 Claim vocabulary was one literal phrase; subject vocabulary was a snapshot of today's declaration none widened the claim; derived subject tokens from the declaration, as the sibling harness does 8ca8774
11 services-role-described satisfied its own precondition (the corpus selector was a substring of the key it gated) and could certify a stale declaration path none resolves the named path and asserts the file exists 8ca8774
12 services-not-cross-platform-config could not tell an enumeration from a contrast, failing on an accurate sentence none reads an actual parenthesised enumeration 8ca8774
13 REQ-B1.5 is positive ("corrected to describe the declared layer") but only the negative half was checked; deleting the correction outright passed none positive assertions on the Linux defaults and on main.yml's roles comment 8ca8774
14 The mise blurb check was negative on one word; "", "TODO" and "Install services" all passed. Regex was greedy and double-quote-only none both directions; non-greedy; either TOML quoting 8ca8774
15 Unreadable files skipped silently; empty listing passed; git/PyYAML unchecked; self-exclusion by hardcoded relative path; failure message said "tracked" for a scope including untracked files; dead pass/fail helpers none reported, asserted, front-loaded, resolved-path, reworded, removed 8ca8774
16 A scan that died partway reported "files still place services out of scope" and then listed none none completion line checked before the result is read daf1edb

Declared deviation from the Auto-applicable predicate. None of these carries a tool rule citation, so under a literal reading of condition 1 they all route to Needs sign-off. I applied them instead and am declaring it rather than doing it silently. The reason is the categorization doctrine's own decision-shape principle: every row above corrects prose this branch itself wrote, and a pending-sign-off entry offering "reject with git revert" would offer to restore a factual error I had just introduced. That is not a real decision, so the honest bucket is the one where the agent has the call. Findings against pre-existing code would not qualify for this reasoning. Validation for each was three-pass — reproduction via mutation test, an orthogonal read against the actual role files, and the sibling harnesses plus the base commit as outside sources.

Agent-resolvable

# Finding Test Before → after CI Brief alignment Commit
none

Needs sign-off

# Finding Fix applied Route reason Commit Checklist ID
none

Needs human judgment

# Fork Ladder record Outcome Options
1 Where should the scan run from? [test] asks for CI; CI config is a hard-disqualifier zone rung 1 (spec): test-spec.md pins REQ-B1.5 [test], which spec-format defines as running in CI — answers that it should be automated. rung 3 (convention): the repo's four other harnesses are all manual, contradicting rung 1. A lower rung never overrides a higher one, so rung 1 stands and the zone screen, not the ladder, is what stops it Hard-paused at the zone screen. Step written, run green, backed out; recorded with its exact YAML in tasks.md ## Awaiting input (a) apply the lint-job step as recorded (honors the [test] pin) · (b) wire it as a lefthook.yml pre-commit command instead (catches regressions at the commit that writes them, but is not what [test] means) · (c) leave it manual, matching the four sibling scripts, and accept that the [test] tag on REQ-B1.5 overstates what runs

Declined log

# Finding Validation summary Rationale Where re-raisable
1 Em-dash in roles/linux/defaults/main.yml's containers bullet Confirmed present on an added line Verbatim from the deleted text; the diff reflowed the bullet around it rather than authoring it. Rewriting untouched prose is outside a reword's scope PR review
2 Em-dash in the tasks.md reference bullet Confirmed newly typed It is the file's established label separator (- **Task N** — ), matching the three sibling bullets. Structural punctuation, not prose PR review
3 The scan's false-positive surface is this repo's Claude-command prose, which freely uses "out of scope" Measured: 15 claim-matching lines across 6 in-scope files, none currently pairing with a service term Narrowing the scope would reintroduce exactly the incompleteness the scan exists to prevent. A false positive is loud and one edit from fixed; a missed straggler is silent. The statement-unit rule already makes an accidental pairing much less likely than line-distance did PR review
4 test-spec.md does not name the script implementing REQ-B1.5's verification Confirmed: the entry still reads "a repository-wide search asserts…" test-spec.md names no .sh file anywhere in this bundle; adding one here would break that convention and move the content anchor for a cosmetic gain /spec-kickoff amendment
5 re.split(r"^## ") would split inside a fenced code block in CLAUDE.md Confirmed mechanism; no ## inside any fence today Speculative until such a fence exists, and the guard would cost more than the failure PR review
6 gitleaks-rules-test.sh fails its committed-block-current assertion on this host Reproduced; confirmed pre-existing (this branch touches no gitleaks file) Task 1's outstanding operator action, not this unit's. Recorded as an observation instead, since the host state has moved on from what Task 1's bullet describes specs/_observations/entries/2026-08-06-gitleaks-block-stale-on-host-f0c20359.md

Pending sign-off

  • none — no finding routed to the Needs-sign-off bucket. See the declared deviation under the Auto-applicable table for why the documentation corrections were applied rather than queued, and the Needs-human-judgment row for the one decision that is yours.

Pass summary

Iteration 1, nested mode, brief specs/dev-services/kickoff-brief.md. 16 findings applied, 6 declined with rationale, 1 hard-paused at the zone screen and queued. Commits: f90a61b (implementation), 7ad7453 (zone pause record), 9069e9d + c70db23 (observations), dde833d (prose corrections), 8ca8774 (harness hardening), daf1edb (inline correctness pass). Tooling, green at every commit: mise run lint (yamllint, ansible-lint, syntax-check), lefthook run pre-commit (adds shellcheck at --severity=warning, gitleaks), scripts/stale-declarations-test.sh (8 assertions), scripts/services-declaration-test.sh (all assertions, matrix-preexisting-unmodified intact — this branch's workflow diff is empty). Content anchor fecce5e… recomputed and unchanged throughout, so no orchestration or execution act moved it.

inkatze added 8 commits August 6, 2026 18:49
The `services` role now provisions a declared dev-services layer on Linux
(Tasks 2, 3 and 4), which makes three kinds of tracked statement false.

roles/linux/defaults/main.yml placed databases out of scope as long-running
services, and two package blocks justified their entries against that same
clause. The databases bullet now records the real reason those packages are
absent from the apt baseline: the `services` role owns their lifecycle, not
that the host does not run them.

The repo guide listed `services` among the roles that are plain
cross-platform config and said nothing about what it does. It now has its own
description: self-guarding task files, the declaration they read, and what
runs on each platform. main.yml's roles comment and the `mise run services`
blurb are corrected to match.

scripts/stale-declarations-test.sh makes REQ-B1.5 checkable rather than
reviewed. It scans the whole checkout rather than the two files this task
names, because the risk here is a claim left somewhere nobody thought to
reread. Wired into the lint CI job, which adds lines to the workflow and
removes none (REQ-E1.4).

Planwright-Task: dev-services/5
The scan's `[test]` pin in test-spec.md asks for a step in the lint job, and
that edits .github/workflows/test.yml -- CI configuration, a hard-disqualifier
zone this task's REQs do not mandate touching. The zone screen fires, so the
step is backed out of the branch and recorded in tasks.md Awaiting input with
the exact YAML to apply, rather than landing unattended.

The script itself is unchanged and still green; only where it runs from is
open.

Planwright-Task: dev-services/5
Surfaced by Task 5's wider-suite run: the machine-local identifier file now
exists, but .gitleaks.toml was never regenerated from it, so the committed
block and a fresh generation differ. Outside this unit's scope; recorded as
seed material rather than acted on.

Planwright-Task: dev-services/5
Review pass over this branch's own additions:

- CLAUDE.md said the role applies "the colima steps" on Darwin; every one of
  them is further gated on inventory_hostname == 'personal', so only the
  my.cnf symlink applies on work and alt.
- CLAUDE.md read "the exception among those" after the enumeration it refers
  to stopped containing services. It is an exception to them, not among them.
- The databases-and-caches omission bullet named no cache, so a reader
  grepping the block for valkey-server got no hit for exactly the kind of
  absence the block exists to explain. redis-tools now says which half of the
  pair it is, since the client sits in this list and the server does not.
- tasks.md attributed Task 2's CI matrix entry to REQ-E1.4. That REQ is the
  constraint on the workflow edit; Task 2's own Deliverables are what called
  for it.

Planwright-Task: dev-services/5
…nd spots

Review pass over the harness this branch added. It was green, and several of
its assertions could have stayed green while the thing they name was wrong.

- No positive control: nothing proved the scanner could still detect a stale
  claim, so any regex or windowing edit would have left it reporting clean
  forever. It now runs against a five-file fixture on every run, covering a
  plain claim, a reflowed one, a negated one, and two shapes that must not be
  flagged. Reworking the pairing rule below broke that fixture twice before it
  passed, which is the point of having it.
- Line-distance pairing replaced by statement units (a blank-line block, split
  at list markers). A window was wrong in both directions: narrow, a bullet
  reflowed to another column becomes a silent miss; wide, an out-of-scope
  sentence in a neighbouring paragraph pairs with a service name. Verified
  against the pre-fix content: exactly the three stale spots, and not the
  Containers bullet sharing their comment block.
- Negated claims are no longer flagged, so "databases are no longer out of
  scope" is writable. The old rule pushed documentation away from naming the
  history it corrects.
- The claim vocabulary covers "not in scope" and "outside the scope"; the
  subject vocabulary is derived from the declaration rather than snapshotted,
  so a service declared later brings its own terms, the way the sibling
  harness does it.
- services-role-described no longer satisfies its own precondition: it
  resolves the declaration path the guide names and asserts the file exists,
  which catches both a guide left behind by a move and a guide correctly
  updated after one.
- services-not-cross-platform-config reads an actual enumeration rather than
  any paragraph carrying the phrase, so a sentence contrasting the role with
  the cross-platform roles is no longer a false failure.
- New positive assertions: the Linux defaults still point at the declaration
  (REQ-B1.5 is positive, so absence of the old claim is only half of it), and
  main.yml's roles comment names the platform dispatch. That comment was the
  one corrected statement with no guard at all.
- The mise blurb is checked in both directions, its regex is non-greedy and
  accepts either TOML quoting, unreadable files are reported rather than
  skipped in silence, an empty file listing fails instead of certifying a
  repository it never read, git and PyYAML are checked up front, and the
  self-exclusion is by resolved path so a stray copy is not mistaken for it.

Planwright-Task: dev-services/5
Five assertion scripts under scripts/ now, and no single command runs them:
mise run lint covers the linters only, lefthook runs none, and CI exercises
roles rather than scripts. Surfaced during Task 5; recorded as seed material
rather than acted on.

Planwright-Task: dev-services/5
Inline correctness pass. A scan that died partway -- an unreadable
declaration, a malformed listing -- exits non-zero the same way a scan that
found a stale claim does, so the failure branch reported "files still place
services out of scope" and then listed none. The completion line is now
checked before the result is read, and the mutation that removes the
declaration produces a named refusal instead.

Planwright-Task: dev-services/5
Both jobs are cancelled by the 30-minute cap on every run, on main as well as
on PR branches, stalling in the Brewfile install with no error. Pre-existing
and branch-independent, so Task 5 records it rather than retrying into it:
the failure reproduces on main's last four runs, which is not the shape a
retry clears.

Planwright-Task: dev-services/5
@inkatze
inkatze marked this pull request as ready for review August 7, 2026 16:10
Task 6 (#103) merged after this branch was cut. Both branches appended a
reference bullet to the same position in tasks.md's ## Awaiting input, which
git cannot order for itself; the merge base has neither, so both sides are
purely additive.

Resolved by keeping both, Task 5 then Task 6. They name different tasks, so
both are legal under spec-format's one-bullet-per-task rule, and dropping
either would discard a recorded operator action. No other change taken from
either side: every remaining file auto-merged, and the content anchor is
unchanged at fecce5e, since reference bullets are outside the canonical
task-definition extraction.

Planwright-Task: dev-services/5
@inkatze
inkatze merged commit 57b38fb into main Aug 7, 2026
9 of 12 checks passed
@inkatze
inkatze deleted the planwright/dev-services/task-5 branch August 7, 2026 17:04
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