Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b34bd93
feat: repair the core defects phase 9 and the reviews found
Wahbeh-Mohammad Sep 25, 2026
6bc7ea7
feat: map an HTTP/1.0 or vendor-status head in both transports
Wahbeh-Mohammad Sep 25, 2026
abcc2f2
feat: fold TransportSuite onto Runner and fix the gem-name rule
Wahbeh-Mohammad Sep 25, 2026
cd3a658
feat: add gates:ledger_audit, gates:spdx_rbs and gates:sole_parse
Wahbeh-Mohammad Sep 25, 2026
563f2b0
feat: add the probe's ninth check, chapters
Wahbeh-Mohammad Sep 25, 2026
e9ddaa9
feat: flip docs/deviations.md to as-built verdicts
Wahbeh-Mohammad Sep 25, 2026
0d830c2
fix: close the proxy prefix leak, total Status, forward-bind chapters
Wahbeh-Mohammad Sep 25, 2026
b21031c
fix: give ledger_audit's disable a reason, negate "does not appear"
Wahbeh-Mohammad Sep 25, 2026
a857785
fix: read never-appear and n't-appear as chapter negations
Wahbeh-Mohammad Sep 25, 2026
c38f837
test: pin every repair, the fiber race and the three new gates
Wahbeh-Mohammad Sep 25, 2026
7afceaa
test: pin the proxy prefix leak and the forward-bound chapters check
Wahbeh-Mohammad Sep 25, 2026
5c13f49
test: pin the last-@ proxy scrub and the negated chapters verb
Wahbeh-Mohammad Sep 25, 2026
9b9aa7e
test: pin never-appears and doesn't-appear as chapter negations
Wahbeh-Mohammad Sep 25, 2026
54a1f63
docs: phase 10 checklist, as-built record and the release register
Wahbeh-Mohammad Sep 25, 2026
0a19c2c
docs: repair the phase-10 record against review round 0
Wahbeh-Mohammad Sep 25, 2026
561aff1
docs: record review round 1 and the two missing repair rows
Wahbeh-Mohammad Sep 25, 2026
91b4dca
docs: state NFR-4 as met in the RBS-baseline entry
Wahbeh-Mohammad Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 5 additions & 2 deletions .claude/skills/housekeeping/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ruby .claude/skills/housekeeping/probe.rb --warn-only
Findings are grouped by check and printed as `path:line: [severity] message`, followed by a
summary line. `act` is drift to fix; `note` is a judgement call.

Eight checks. Each is a small class with a `name` and a `run(repo)`, and each derives the
Nine checks. Each is a small class with a `name` and a `run(repo)`, and each derives the
repository fact **once, from the repository**, then compares every document that states it
against that one derivation — never one document against another.

Expand All @@ -54,6 +54,7 @@ against that one derivation — never one document against another.
| `links` | Broken relative links in `docs/**/*.md`, `README.md`, `CLAUDE.md` and `gems/*/README.md`. `http(s)://`, `mailto:` and anchor-only targets are skipped. Also a **backticked** chapter of a normative tree — `docs/product-spec/NN-….md`, `docs/sdk-design-ruby/NN-….md` — that does not resolve: this repository writes chapter references in backticks, not link syntax, so nothing else reads them. Scoped to those two complete trees, so a `gems/…` path a later phase creates is not reported; a wildcard or an elided path is not a claim about one file and is skipped |
| `registers` | An aggregate `## Open Findings` / `## Deferred Items` / `## Open Items` section living inside a spec, design or plan document. No find-list register exists any more: a finding belongs with its owner — the numbered plan task whose scope it falls in, phase 10's inbound list in the roadmap, or a `docs/first-release.md` entry |
| `citations` | Two namespaces, and both prefixes are now **retired**: every `DEF-<n>` and every `OI-<n>` citation, anywhere. `docs/deferred-items.md` and `docs/open-items.md` were both retired on 2026-09-13 with every row resolved — a finding is no longer registered, it is routed to its owner when it is found: a numbered plan task, phase 10's inbound list, a `docs/first-release.md` entry, or simply the fix. A retired prefix never no-ops, because a missing file is exactly what would hide the leftovers; if either old file itself lingers, that is one finding, not one per row. The **live** half of the check stays — a register resolves only against its own file, and no-ops when that file does not exist — because a future register is one entry in `REGISTERS`. A **backticked** ID counts, and so does one on a 4-space-indented continuation line — this repository's ID convention backticks every ID, and a 4-space indent here is list-item continuation, not code. Only a ``` fence is an example, and only a fence is blanked |
| `chapters` | A requirement ID a document attributes to a `docs/product-spec/NN-….md` chapter that does not carry it (phase 10's Task 7). The unit is a clause: a line is split at `;`, each ID pairs with the nearest preceding chapter reference in its clause, a range is expanded whether or not its endpoints are backticked, and a two-line window carrying a negation ("appears nowhere", "is not in", …) is skipped. Appendix C is exempt as a target, and phase 10's own documents, which quote the pre-correction lines on purpose, are exempt as sources. Two blind spots are stated in `Chapters::GAPS`: a chapter reference on the preceding line, and a chapter named by an ellipsis or a variable |
| `guard` | The frozen list and the writable surface overlapping, a path the guard has quietly stopped refusing, or a frozen entry that has become a symlink — the three ways the apply stage could eat a normative document |

The `claims` check is a **declarative table**: file, a pattern whose first capture is the
Expand Down Expand Up @@ -187,12 +188,14 @@ checks rot. No case count is written here on purpose; `test/run.rb` reports it.
.claude/skills/housekeeping/
SKILL.md this file
guard.rb the frozen-path guard; every write site goes through it
probe.rb stage 1 — eight read-only checks
probe.rb stage 1 — nine read-only checks
chapters.rb the ninth check, `chapters`, in its own file
apply.rb stage 2 — git mv only, guarded, dry by default
test/
fixture.rb builds the throwaway repositories the tests probe
guard_test.rb prefix, traversal, absolute, symlink, and the pinned list
probe_test.rb every check has a fixture it fires on, plus the CLI
chapters_test.rb the clause rule, the ranges, the negations, the two regression fixtures
apply_test.rb the target mapping, every batch refusal, and the CLI
run.rb the whole suite
```
156 changes: 156 additions & 0 deletions .claude/skills/housekeeping/chapters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# frozen_string_literal: true

# SPDX-License-Identifier: MIT
# .claude/skills/housekeeping/chapters.rb
#
# The probe's ninth check, `chapters` (phase 10's plan, Task 7; design R10 and addendum A10).
#
# A document asserting that a requirement ID is stated in a named `docs/product-spec/` chapter,
# where that chapter does not carry the ID. The unit is a CLAUSE, not a line: "A.md for X, Y;
# B.md for Z" pairs every ID with every chapter under a same-line rule, which is why a naive rule
# fires 23 times for 4 real defects (phase 10's Fact 6). So a line is split at `;`, each ID is
# associated with the NEAREST PRECEDING chapter reference in its clause -- or, for an ID run
# followed by "appears in", with the chapter that phrase introduces -- a range is expanded
# whether or not its endpoints are backticked, and a line whose two-line window carries a negation
# -- prose whose own subject is that the ID is NOT in that chapter -- is skipped. Appendix C
# carries every ID and can never be wrong about one, so it is exempt as a target.
#
# Two stated blind spots, printed in the check's own `GAPS` so a reader is never told the check
# saw something it did not: `continued_clause` (a chapter reference on the PRECEDING line is
# invisible to a line-oriented scanner -- 8c's design :70, `SEAM-15`, was caught by hand), and
# `dynamic_chapter_path` (a chapter named by an ellipsis such as `13-…md`, or by a variable,
# resolves to no file and is skipped).

module Housekeeping
module Checks
# 9. A requirement ID attributed to a spec chapter that does not carry it.
class Chapters < Check
NAME = 'chapters'
PREFIXES = %w[SEAM HTTP IO BODY CTX PIPE RECOV RETRY REDIR AUTH PAGE SSE SERDE OBS CFG
TRANSPORT ASYNC XCUT NFR].freeze
ID = /\b(#{PREFIXES.join('|')})-(\d+)\b/
# `SEAM-11`–`SEAM-15`, SEAM-11–SEAM-15, SEAM-11–15: backticks tolerated on either end,
# which is what hid 8a's `SEAM-13` from a range pattern written for bare text.
RANGE = /`?\b(#{PREFIXES.join('|')})-(\d+)`?\s*[–—]\s*`?(?:\1-)?(\d+)\b`?/
CHAPTER = %r{docs/product-spec/(\d\d-[a-z0-9-]+\.md)}
BOUNDARY = ';'
# Prose whose subject is that the ID is NOT in the named chapter, over the line and the
# next one, because such a sentence routinely crosses a line break.
# A union of plain phrases, never an /x pattern: under /x the spaces inside a phrase are
# stripped and "appears nowhere" silently becomes "appearsnowhere" (found writing this).
NEGATION = Regexp.union(
'appear nowhere', 'appears nowhere', 'harvested nowhere', 'appear in no', 'appears in no',
'appendix C is their only', 'appendix C is its only', 'appendix C alone',
'does not carry', 'do not carry', 'carries neither', 'carries none', 'carries no ',
'carry none', 'no prose chapter', 'appendix-C row', 'unfollowable', 'from appendix C',
'read out of appendix C', 'read out of **appendix C', 'neither ID appears', 'is not in',
'are not in', 'not stated in', 'only normative statement', 'only prose home',
'not appear in', 'does not appear', 'do not appear', 'never appear', "n't appear"
).then { |union| Regexp.new(union.source, Regexp::IGNORECASE) }
# "X appears in <chapter>": the one verb that puts the chapter AFTER the IDs it is about.
# An ID run followed by it binds FORWARD, to the chapter the verb introduces, instead of to
# the nearest preceding one. Phase 10's review round 0 (R0-8) found the phrase listed as a
# negation, which silenced this -- the usual positive attribution -- to hide one line of the
# phase-5 segmentation design (:538-539) whose second run bound backward to the first
# run's chapter; binding forward is what that line needed.
FORWARD = /\bappears?\s+in\b/i
EXEMPT_TARGET = 'appendix-c-consolidated-normative-requirement-index.md'
# Documents that quote the pre-correction attributions ON PURPOSE, as the record of the
# defect this check exists for: phase 10's own design, plan and checklist.
EXEMPT_DOCUMENTS = %r{\Adocs/work/mvp/phase10/}
SCANNED = ['docs'].freeze
SKIPPED = %r{\Adocs/(?:product-spec|knowledge/harvested)/}
GAPS = %i[continued_clause dynamic_chapter_path].freeze

def run(repo)
carried = chapter_ids(repo)
documents(repo).flat_map { |path| scan(path, repo.read(path).lines, carried) }
end

# The pairs a single line asserts, [chapter, id], in clause order. Public so a test can
# read the clause scoping directly.
def self.pairs(line)
line.split(BOUNDARY).flat_map { |clause| clause_pairs(clause) }
end

# A run of consecutive IDs binds to the nearest preceding chapter, unless the run is
# followed by FORWARD, when it binds to the chapter immediately after the verb (or to
# nothing, when the chapter is on the next line: the continued-clause gap).
def self.clause_pairs(clause)
list = tokens(clause)
chapter = nil
pairs = []
index = 0
while index < list.length
kind, value = list[index]
if kind == :chapter
chapter = value
index += 1
elsif kind == :forward
index += 1
else
run_end = index
run_end += 1 while run_end < list.length && list[run_end][0] == :id
target = run_target(list, run_end, chapter)
list[index...run_end].each { |_kind, id| pairs << [target, id] } if target
index = run_end
end
end
pairs
end

def self.run_target(list, after, chapter)
return chapter unless list.dig(after, 0) == :forward

list.dig(after + 1, 0) == :chapter ? list.dig(after + 1, 1) : nil
end

# Chapters and IDs in textual order, every range expanded in place.
def self.tokens(clause)
found = []
clause.scan(CHAPTER) { found << [Regexp.last_match.begin(0), :chapter, Regexp.last_match[1]] }
clause.scan(FORWARD) { found << [Regexp.last_match.begin(0), :forward, nil] }
covered = []
clause.scan(RANGE) do
match = Regexp.last_match
covered << (match.begin(0)...match.end(0))
(match[2].to_i..match[3].to_i).each { |n| found << [match.begin(0), :id, "#{match[1]}-#{n}"] }
end
clause.scan(ID) do
match = Regexp.last_match
next if covered.any? { |span| span.cover?(match.begin(0)) }

found << [match.begin(0), :id, "#{match[1]}-#{match[2]}"]
end
found.sort_by.with_index { |(position, _kind, _value), index| [position, index] }
.map { |_position, kind, value| [kind, value] }
end

private

def chapter_ids(repo)
repo.tracked('docs/product-spec/*.md').to_h do |path|
[File.basename(path), repo.read(path).scan(ID).map { |prefix, n| "#{prefix}-#{n}" }.uniq]
end
end

def documents(repo)
repo.tracked(*SCANNED).select { |path| path.end_with?('.md') }
.reject { |path| SKIPPED.match?(path) || EXEMPT_DOCUMENTS.match?(path) }
end

def scan(path, lines, carried)
lines.each_with_index.flat_map do |line, index|
next [] if NEGATION.match?(line + lines.fetch(index + 1, ''))

self.class.pairs(line).filter_map do |chapter, id|
next if chapter == EXEMPT_TARGET || !carried.key?(chapter) || carried[chapter].include?(id)

act(path, index + 1, "attributes #{id} to docs/product-spec/#{chapter}, which does not " \
"carry it (gaps: #{GAPS.join(', ')})")
end
end
end
end
end
end
8 changes: 6 additions & 2 deletions .claude/skills/housekeeping/probe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ def strip_inline_code(text)
end
end

# The eight read-only checks. Each is a class with `name` and `run(repo) -> [Finding]`.
# The nine read-only checks. Each is a class with `name` and `run(repo) -> [Finding]`. The
# ninth, `chapters`, lives in chapters.rb (phase 10): one concern per file, as guard.rb.
module Checks
# Base class: supplies `name` from the subclass's `NAME` and a `Finding` factory.
class Check
Expand Down Expand Up @@ -732,11 +733,14 @@ def symlinks(repo)
end
end

require_relative 'chapters'

# Runs the checks and collects their findings.
class Probe
ALL = [
Checks::Inbox, Checks::Root, Checks::Claims, Checks::Readmes,
Checks::Links, Checks::Registers, Checks::Citations, Checks::GuardCheck
Checks::Links, Checks::Registers, Checks::Citations, Checks::GuardCheck,
Checks::Chapters
].freeze
NAMES = ALL.map { |check| check::NAME }.freeze

Expand Down
131 changes: 131 additions & 0 deletions .claude/skills/housekeeping/test/chapters_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# frozen_string_literal: true

# SPDX-License-Identifier: MIT
# .claude/skills/housekeeping/test/chapters_test.rb
#
# The probe's ninth check, `chapters` (phase 10's plan, Task 7). Measured over docs/ on
# 2026-09-25: the clause-scoped form fires on exactly the true positives, and the two blind spots
# are asserted here so the check never claims to have seen what it did not. The two
# phase-8 governing-documents lines it was designed against were corrected on 2026-09-13, so they
# live on as committed regression fixtures -- a check whose only evidence is a live defect loses
# its evidence the moment the defect is fixed.

require 'minitest/autorun'

require_relative '../probe'
require_relative 'fixture'

class ChaptersTest < Minitest::Test
Fixture = Housekeeping::Fixture
Chapters = Housekeeping::Checks::Chapters

FIXTURES = File.expand_path('fixtures/chapters', __dir__)
# Chapter 03 as the spec carries it, trimmed to the SEAM IDs that matter: 13, 15 and 22 are not
# in it (02 carries SEAM-13; appendix C alone carries 15 and 22).
CHAPTER_03 = "# 3\n\nSEAM-11 SEAM-12 SEAM-14 SEAM-16 SEAM-17 SEAM-24 SEAM-25 SEAM-29 SEAM-30\n"
CHAPTER_02 = "# 2\n\nSEAM-13 SEAM-29\n"

def run_over(document)
overrides = {
'docs/product-spec/03-pluggable-seams-and-extension-model.md' => CHAPTER_03,
'docs/product-spec/02-architectural-principles.md' => CHAPTER_02,
'docs/work/mvp/x-design.md' => document
}
Fixture.with(overrides: overrides) do |root|
Housekeeping::Probe.new(Housekeeping::Repo.new(root), only: ['chapters']).run
end
end

def ids(findings) = findings.map { |finding| finding.message[/SEAM-\d+/] }.sort

def test_a_governing_documents_list_naming_an_appendix_c_only_id_is_a_finding
findings = run_over("`docs/product-spec/03-pluggable-seams-and-extension-model.md` for `SEAM-11`, `SEAM-15`.\n")

assert_equal %w[SEAM-15], ids(findings)
end

def test_prose_whose_subject_is_the_absence_is_not_a_finding
assert_empty run_over(
"`docs/product-spec/03-pluggable-seams-and-extension-model.md` carries 22 of the 30 IDs; " \
"`SEAM-15` and `SEAM-22` appear nowhere in the specification's prose.\n"
)
end

def test_a_clause_boundary_reassigns_the_chapter
findings = run_over("`docs/product-spec/03-pluggable-seams-and-extension-model.md` for `SEAM-11`; " \
"`docs/product-spec/02-architectural-principles.md` for `SEAM-13`.\n")

assert_empty findings
end

# 8a's design :67, verbatim: the backticked range is what hid SEAM-13 from a pattern written for
# bare text, and with the range read all three wrong IDs are found.
def test_the_phase8a_pre_correction_line_fires_on_all_three_wrong_ids
findings = run_over(File.read(File.join(FIXTURES, 'phase8a_design_67_pre_correction.md')))

assert_equal %w[SEAM-13 SEAM-15 SEAM-22], ids(findings)
end

# 8c's design :69-70, verbatim: SEAM-13 is on the chapter's own line and is found; SEAM-15 is
# too. A clause continued onto the NEXT line would be the stated blind spot -- see below.
def test_the_phase8c_pre_correction_line_fires_on_both_wrong_ids
findings = run_over(File.read(File.join(FIXTURES, 'phase8c_design_69_pre_correction.md')))

assert_equal %w[SEAM-13 SEAM-15], ids(findings)
end

def test_a_clause_continued_onto_the_next_line_is_a_stated_blind_spot
findings = run_over("- `docs/product-spec/03-pluggable-seams-and-extension-model.md` for\n `SEAM-15`.\n")

assert_empty findings
assert_includes Chapters::GAPS, :continued_clause
end

def test_the_range_vocabulary_tolerates_backticks_and_a_bare_upper_bound
assert_equal [%w[03-x.md SEAM-11], %w[03-x.md SEAM-12], %w[03-x.md SEAM-13]],
Chapters.pairs('docs/product-spec/03-x.md for `SEAM-11`–`SEAM-13`').first(3)
assert_equal 3, Chapters.pairs('docs/product-spec/03-x.md for SEAM-11–13').size
end

# Phase 10's review round 0 (R0-8): "appears in" was listed as a negation, which silenced the
# usual positive attribution. It is a FORWARD binding now: the IDs before it belong to the
# chapter after it, so a wrong one fires and a right one does not.
def test_an_appears_in_attribution_binds_forward_and_fires_when_wrong
wrong = "`SEAM-15` appears in `docs/product-spec/03-pluggable-seams-and-extension-model.md`.\n"
right = "`SEAM-11` appears in `docs/product-spec/03-pluggable-seams-and-extension-model.md`.\n"

assert_equal %w[SEAM-15], ids(run_over(wrong))
assert_empty run_over(right)
end

# The phase-5 segmentation design's :538-539 shape, the line the negation entry was hiding: a
# second run followed by "appears in" and a chapter on the NEXT line must not bind backward to
# the first run's chapter (40 false fires under the backward rule).
def test_a_second_appears_in_run_does_not_bind_backward
document = "every one of `SEAM-11`–`SEAM-12` appears in\n" \
"`docs/product-spec/03-pluggable-seams-and-extension-model.md` and every one of `SEAM-13` appears in\n" \
"`docs/product-spec/02-architectural-principles.md`.\n"

assert_empty run_over(document)
assert_equal [%w[03-x.md SEAM-11]], Chapters.pairs('`SEAM-11` appears in docs/product-spec/03-x.md')
end

# Phase 10's review round 1 (R1-4): the forward binding matched "appear in" inside a negative
# sentence, so a correct statement that an ID is ABSENT from a chapter fired as a wrong
# attribution. Each spelling of the negated verb is a negation, not a binding. Round 2 (R2-2)
# added the two it still bound forward: 'never appears in' and "doesn't appear in".
def test_a_negated_appears_in_is_not_an_attribution
['does not appear in', 'do not appear in', 'did not appear in', 'never appears in',
"doesn't appear in", "don't appear in", 'appears nowhere in'].each do |verb|
document = "`SEAM-15` #{verb} `docs/product-spec/03-pluggable-seams-and-extension-model.md`.\n"

assert_empty run_over(document), verb
end
end

def test_appendix_c_is_never_a_target
document = "`docs/product-spec/appendix-c-consolidated-normative-requirement-index.md` for `SEAM-99`.\n"

assert_empty run_over(document)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- `docs/product-spec/03-pluggable-seams-and-extension-model.md` for `SEAM-11`–`SEAM-15`, `SEAM-22`, `SEAM-29`
-- the governing-documents line phase 8a's design carried at :67 before phase 10's planning
corrected it on 2026-09-13, kept verbatim as a regression fixture.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `docs/product-spec/03-pluggable-seams-and-extension-model.md` — `SEAM-11`, `SEAM-13`, `SEAM-14`, `SEAM-15`, `SEAM-16`,
`SEAM-17`, `SEAM-24`, `SEAM-25`, `SEAM-30`
Loading
Loading