diff --git a/CHANGELOG.md b/CHANGELOG.md index 7830311..b27142f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ A **new hardened rule** is a minor version, not a major one — it adds a check, - **`LICENSE` — Apache-2.0.** Chosen for the explicit patent grant and contribution terms; the README badge points at it. - **Two documentation checks, on the standard tool contract.** `tools/linkcheck.mjs` (`D1`–`D3`) and `tools/mermaidcheck.mjs` (`D4`–`D7`) — same flags, same severity ladder, same `0` / `1` / `2` exit semantics as every other tool, zero dependencies. Documented in `VALIDATION_ENGINE.md § 13`, including the two false positives that shaped them. - **`.github/workflows/checks.yml`.** Three jobs: documentation integrity, tool and config syntax, and a re-derivation of `examples/signin` by the three browser-free validators — with `git diff --exit-code` on the run's committed artifacts, because a validator that rewrites the run it is validating is not validating it. The browser-driven checks are deliberately **not** in CI, and the workflow says why. +- **`docs/rule-candidates.md` — the waiting room.** Observations that look like hardened rules and have not earned it. Principle 5 says every defect becomes a rule; it does not say *immediately*, and a rule is a claim about a **class** that one occurrence cannot establish. Promotion needs the five conditions in `CONTRIBUTING.md` **plus independent recurrence** — a different project, a different author, or a different state reached without knowledge of the first. Two instances one fix removes are one occurrence. Nothing in the register carries a rule code, is cited in a plan or gate record, is checked by a tool, or blocks anything. Seeded with `RC-1`. - **`review.harnessFiles`.** The files in the prototype directory that are review chrome, not product surface. `audit.mjs`'s palette sweep and `annotate.mjs`'s network sweep now read one list, resolved once in `config.mjs` with `review.player` always included. - **[`examples/signin/`](examples/signin/) — the toolkit's own reference run.** One feature, brief to frozen deliverable: all twelve states, all seven validators, three human gates, one revision cycle, zero waivers, machine closed at `DONE` with 7/7 completion rules. Includes the **failing** audit (`audit-signin-01`, 138/138 checks passed and the screenshots failed it on three `major` defects) preserved alongside the passing one. @@ -96,7 +97,7 @@ Carried from `DOCS_AUDIT.md`, with the recommendation for each: - ~~**TK-2** — `audit.paletteExemptSelectors` is dead config.~~ **Resolved** — removed, replaced by `review.harnessFiles`. - ~~**TK-3** — `tools/cdp.mjs` ignores `--root`.~~ **Resolved** — and `smoke.mjs`, which had the same defect. - ~~**The contact addresses.**~~ **Resolved** — GitHub advisories for security, a maintainer DM for conduct. **One repository setting is still required:** private vulnerability reporting must be enabled under Settings → Security after the repository is pushed. Until it is, `SECURITY.md` names a channel that is not switched on. -- **Still open — the rule candidate.** *Do not write the name of the thing you are claiming not to use, inside the file being swept for it.* The reference prototype's comment recited the request-API names, and `annotate` E11 blocked on the disclaimer. Candidate for `skills/07`; not yet written, because one occurrence is an anecdote and a hardened rule needs a class. +- ~~**The rule candidate.**~~ **Recorded as `RC-1`** in `docs/rule-candidates.md`, deliberately **not** hardened. One occurrence is an anecdote; the register is what makes a second one recognisable as a second one. It is promoted only if the same class recurs independently, and it is cited, checked and enforced by nothing in the meantime. - **Note on the reference run.** `examples/signin/` records TK-2 and TK-3 as open, because they were open when that run closed. It is a dated record of a completed run, not a live document, and it is deliberately not being edited to match. The audit report says what the audit found. --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3dbaca..8c6a438 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,6 +87,14 @@ A rule is accepted when all five hold: 4. **It is evidenced by something checkable.** A rule is satisfied by the evidence named in a validation rule, never by understanding it. 5. **It gets a code** in the owning state's namespace — `B`, `M`, `G`, `R`, `P`, `W`, `E`, `F` — continuing that namespace's numbering. +### Seen it once? It goes in the register, not the catalogue + +Condition 1 asks for a defect. It also, implicitly, asks for a **class** — and one occurrence cannot establish one. If you have seen this exactly once, open a rule report anyway, and expect the outcome to be a row in [`docs/rule-candidates.md`](docs/rule-candidates.md) rather than a code. + +That is not a rejection. It is the record that makes the *second* occurrence recognisable as a second occurrence instead of another first. A candidate is promoted when the same class appears again independently — a different project, a different author, or a different state reached without knowledge of the first. + +Two instances that one fix removes are one occurrence. + ### Where it goes — three places, one edit | Place | What goes there | diff --git a/DOCS_AUDIT.md b/DOCS_AUDIT.md index 20e25d2..c5226b3 100644 --- a/DOCS_AUDIT.md +++ b/DOCS_AUDIT.md @@ -16,8 +16,8 @@ A pre-launch review of this repository's documentation, run the way the toolkit | Metric | Value | |---|---| -| Markdown files | 86 | -| Internal links checked | 923 | +| Markdown files | 87 | +| Internal links checked | 943 | | Broken links | **0** (4 fixed during this audit) | | Mermaid blocks | 39 | | Mermaid blocks with parse-risk warnings | **0** | @@ -35,7 +35,7 @@ Applying the toolkit's own **M3** — *a failing probe is a hypothesis, not a fi ```bash # every relative link and every in-document anchor, resolved against the filesystem -node tools/linkcheck.mjs # → 923/923 resolve +node tools/linkcheck.mjs # → 943/943 resolve # every fenced mermaid block: known diagram type, balanced quotes, closed fence node tools/mermaidcheck.mjs # → 39 blocks, 0 findings # every GitHub issue form: parses, required keys present, no duplicate field ids @@ -162,7 +162,7 @@ It also did what a reference run is *for*: **it found real defects in the toolki | **TK-1** | `tools/audit.mjs` swept `play.html` — the review player — as product surface, reporting 11 off-palette hexes on every run. `tools/annotate.mjs` in the same toolkit already excluded exactly those three harness files. | **fixed** in `audit.mjs` | | **TK-2** | `audit.paletteExemptSelectors` is documented in `toolkit.config.json`, `tools/config.mjs`, `VALIDATION_ENGINE.md` and `skills/08` as the mechanism that exempts harness chrome — and is **referenced by no tool**. The sweep is file-level, so a selector list could not exempt anything even if it were read. | **fixed** — key removed, replaced by `review.harnessFiles`, which both sweeps read | | **TK-3** | `tools/cdp.mjs` calls `loadConfig()` with no root, so it resolves from `cwd` rather than honouring `--root`. Viewport and Chrome path therefore come from the wrong config when a tool is run with `--root` from a different directory. Worked here only because both configs agreed. | **fixed** in `cdp.mjs` — and in `smoke.mjs`, which had the same defect and no `--root` at all | -| **New rule candidate** | *Do not write the name of the thing you are claiming not to use, inside the file being swept for it.* The prototype's comment recited the request-API names; `annotate` E11 blocked on the disclaimer. | candidate for `skills/07` | +| **New rule candidate** | *Do not write the name of the thing you are claiming not to use, inside the file being swept for it.* The prototype's comment recited the request-API names; `annotate` E11 blocked on the disclaimer. | **recorded as `RC-1`**, [not hardened](docs/rule-candidates.md) — one occurrence is an anecdote, and a rule is a claim about a class | That is the argument for the run in one line: **the documentation described a toolkit nobody had executed, and executing it found three tool defects and a rule.** @@ -282,7 +282,7 @@ See [recommendation 5](#5--add-a-ci-workflow). | 1 | [`templates/ui-plan.md`](templates/ui-plan.md) linked to `../08-self-audit/SKILL.md`, which resolves to `templates/08-self-audit/` and does not exist. | Corrected to `../skills/08-self-audit/SKILL.md`. | | 2 | [`README.md`](README.md) FAQ linked to `reference/screen-registry.csv`, which does not exist until a user copies it in. | Repointed to [`reference/README.md`](reference/README.md), which explains the file and where to seed it from. | | 3 | [`VALIDATION_ENGINE.md`](VALIDATION_ENGINE.md) linked to `reference/state-vocabulary.md`, same problem. | Repointed to [`templates/state-vocabulary.md`](templates/state-vocabulary.md). | -| 4 | Three anchor links used a double hyphen where the target heading produces a single one. | Corrected. All 923 links now resolve, and `tools/linkcheck.mjs` keeps them resolving. | +| 4 | Three anchor links used a double hyphen where the target heading produces a single one. | Corrected. All 943 links now resolve, and `tools/linkcheck.mjs` keeps them resolving. | Findings 1–3 are the same class: **a link to a file that only exists after the user seeds it.** The class was swept, not just the instances — `linkcheck.mjs` resolves every relative target against the filesystem, so any further occurrence would have been reported. diff --git a/GLOSSARY.md b/GLOSSARY.md index c2ad6db..bd9f97a 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -372,6 +372,8 @@ Every project-hardened rule carries a code, citable from a plan, a log or a gate | **W1–W10** | Navigation mapping | [12 flow-visualization](skills/12-flow-visualization/SKILL.md) | | **E1–E7** | Enterprise extensions | [12 flow-visualization](skills/12-flow-visualization/SKILL.md) | +**`RC-n` is not a rule code.** It indexes [`docs/rule-candidates.md`](docs/rule-candidates.md) — observations seen once, waiting for an independent recurrence. A candidate has no owning state, no validation rule and no enforcement; it acquires a code from one of the namespaces above only on promotion. + ### Tool finding codes | Prefix | Tool | Meaning | diff --git a/docs/README.md b/docs/README.md index 19a2769..d8a9177 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ Three documents that define what the toolkit **is**, as opposed to how to use it | [`workflow.md`](workflow.md) | The state machine: twelve states, transitions, guards, gates, retry logic, loop logic, completion rules, skill decomposition. | **Source of truth.** Where a skill and this document disagree, the document wins and the skill is the bug. | | [`method-rules.md`](method-rules.md) | Every project-hardened rule, indexed by the code you cite it as, with the class of failure that produced it. | The catalogue. Each skill carries the full statement of its own set; this file is the index across all of them. | | [`artifact-contracts.md`](artifact-contracts.md) | The artifact store: what exists, who owns it, the naming scheme, the load-bearing frontmatter fields, gate records, validation shape. | The data contract every skill writes to. | +| [`rule-candidates.md`](rule-candidates.md) | Observations seen exactly once, waiting for an independent recurrence before they can be hardened. | **Not enforced.** Nothing here is cited, checked, or allowed to block. It exists so a second occurrence is recognisable as a second one. | ## Inputs @@ -46,6 +47,7 @@ Return edge passes through SELF_AUDIT per R6. | What does `C_NAVMAP_CLEAN` mean? | [`workflow.md` §4](workflow.md) | | What is the ceiling on `L_REVISION`, and what happens on breach? | [`workflow.md` §7](workflow.md) | | Why does the audit look at screenshots? | [`method-rules.md`](method-rules.md) → **M2** | +| I found something once — is it a rule? | [`rule-candidates.md`](rule-candidates.md) → the bar for promotion | | Why must `reads_versions` be in frontmatter? | [`artifact-contracts.md`](artifact-contracts.md) | | Which fields does a gate record have to carry? | [`artifact-contracts.md`](artifact-contracts.md) → Gate records | diff --git a/docs/method-rules.md b/docs/method-rules.md index 0f4c1bd..137a152 100644 --- a/docs/method-rules.md +++ b/docs/method-rules.md @@ -13,6 +13,11 @@ one product. The right is the skill that owns it. A rule is not satisfied by understanding it. It is satisfied by the evidence named in the skill's validation rules. +**What is not in this file.** An observation seen exactly once is not in here. It +waits in [`rule-candidates.md`](rule-candidates.md) until the same class recurs +independently, because a rule is a claim about a class and one occurrence is an +anecdote. Nothing in the register is cited, checked or enforced. + --- ## The three that shape everything else diff --git a/docs/rule-candidates.md b/docs/rule-candidates.md new file mode 100644 index 0000000..92dd27c --- /dev/null +++ b/docs/rule-candidates.md @@ -0,0 +1,88 @@ +# Rule candidates — the waiting room + +[← docs/](README.md) · [Repository root](../README.md) · [Method rules →](method-rules.md) · [Contributing →](../CONTRIBUTING.md#contributing-a-hardened-rule) + +--- + +## What this file is + +Observations that **look** like hardened rules and have not earned it yet. + +[Principle 5](../DESIGN_PRINCIPLES.md#5--every-defect-becomes-a-rule) says every defect becomes a rule. It does not say every defect becomes a rule *immediately*, and the difference is the whole reason this file exists. A rule is a claim about a **class** of failure. One occurrence is an anecdote about one run — it may be the first sighting of a real class, or it may be a local quirk that never recurs. There is no way to tell from inside the run that produced it. + +So a single observation goes here, and it waits. + +## Why a waiting room rather than a decision + +Both ways of resolving a single observation immediately are wrong, and they fail in opposite directions. + +| If you harden it on sight | If you discard it | +|---|---| +| The catalogue fills with rules written by one incident each. Every one costs a reader's attention on every run, and the ones that matter get read at the same weight as the ones that never recur. A rule nobody can afford to read is not enforced. | The second occurrence arrives months later with nothing recorded to match it against, and it reads as a first occurrence too. The class stays invisible for as long as nobody remembers the first one. | + +The register is how a first sighting survives without being promoted. Recording is cheap; enforcing is not. + +## The bar for promotion + +A candidate becomes a hardened rule when **all five conditions in [CONTRIBUTING](../CONTRIBUTING.md#the-bar) hold**, plus one that applies only to candidates: + +> **6 · It has recurred independently.** The same class of failure has appeared at least twice, in circumstances that did not share a cause with the first. + +**Independently** means one of these — not merely a second instance: + +- a **different project**, or +- a **different author**, or +- a **different state** of the same project, reached without knowledge of the first occurrence. + +Two instances in one sweep of one file are **one** occurrence. So are two instances that a single fix removes. The question the sixth condition asks is not *how many times did I see this*, it is *would this have happened to someone who had never seen the first one*. + +## What a candidate is not + +Stated explicitly, because the failure mode here is a candidate quietly acquiring the authority of a rule. + +- **It has no rule code**, and never gets one until promotion. `RC-n` is a register index, not a namespace in `B` / `F` / `M` / `G` / `R` / `P` / `W` / `E`. +- **It is not cited** in a plan, a revision log, an audit report or a gate record. Citing a candidate is how it becomes a de facto rule without passing the bar. +- **No tool checks it**, and no validation rule references it. +- **It does not block anything.** A candidate never produces a finding, a waiver or a debt item. + +A candidate is a note to a future maintainer. That is its entire authority. + +## Register + +| # | Observation | First seen | Recurrences | Status | +|---|---|---|---|---| +| **RC-1** | Do not write the name of the thing you are claiming not to use, inside the file being swept for it. | `examples/signin`, STATE 12 | **0** | monitoring | + +--- + +### RC-1 · A disclaimer inside a swept file is indistinguishable from the thing it disclaims + +**Observation.** A source comment that states *"this prototype makes no network calls"* and then names the APIs it is not using — `fetch`, `XMLHttpRequest`, `WebSocket` — is matched by the sweep that looks for those names. The claim and its counter-evidence are the same bytes. + +**First seen.** [`examples/signin`](../examples/signin/README.md), at STATE 12. `tools/annotate.mjs` raised a blocking `E11` for a network call site at `signin.html:262`. Confirmed at source: line 262 was the comment asserting there were no network calls. The sweep is deliberately broad — see [`annotate.mjs`](../tools/annotate.mjs) `NETWORK_RE` — and narrowing it to dodge comments would weaken a check that exists precisely because a claim about network behaviour must be re-derived rather than trusted. + +**Why it might be a class.** The general shape is *a file that describes its own contents, inside the scope being scanned for those contents*. Nothing about it is specific to network APIs. The same shape would apply to a comment listing banned colours in a file swept for hex values, or naming a deprecated component in a file swept for that component. + +**Why it is not a rule yet.** One occurrence, in one file, written by one author, removed by one edit. Every part of it could be local. Manufacturing a second instance to justify the rule would be the thing this method exists to prevent — a rule needs a defect that happened, not a defect that was arranged. + +**What would promote it.** A second occurrence in a different project, or by a different author, where a descriptive comment trips a source sweep and the first occurrence played no part in it. On promotion the likely owner is [`skills/07-prototype`](../skills/07-prototype/SKILL.md) — the state that writes the file — with the statement generalised past network APIs. + +**Workaround in the meantime.** The claim belongs in the traceability record, which is the artifact that exists to carry claims about the build, and which nothing sweeps for API names. + +--- + +## Reviewing this file + +Review the register whenever a run closes at `FINAL_OUTPUT`. Three outcomes: + +| Outcome | Action | +|---|---| +| The class recurred independently | Promote it. Follow [Contributing a hardened rule](../CONTRIBUTING.md#contributing-a-hardened-rule) — three places, one edit — and replace the row here with a link to the code it became. | +| It did not recur, and the observation still looks plausible | Leave it. A candidate does not expire on a schedule; a class that recurs once every two years is still a class. | +| It did not recur, and the mechanism turned out to be wrong | **Retire it**, with the reason written into the row. A retired candidate stays in the file — deleting it loses the record that the question was asked and answered. | + +Recording a candidate that never recurs costs one table row. Missing the second occurrence of a real class costs the run it happens in. + +--- + +[← docs/](README.md) · [Method rules →](method-rules.md) · [Design principles →](../DESIGN_PRINCIPLES.md) · [Contributing →](../CONTRIBUTING.md)