diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 317e4cd..a9bcc73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: # a second copy of it to go stale. - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.6 + rev: v0.16.7 hooks: - id: ruff-check args: [--fix] diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 5a5f0ff..b008b4d 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -560,6 +560,24 @@ Each is named separately because taking one is a separate decision: `unmanaged-pins` refuses a shape a repository that vendors its dependencies has on purpose, `host-identity` shells out to read the running machine, and `captured-fixtures` refuses the script a parser's own test corpus is made of. +The `process-residue` set rejects GitHub issue and PR URLs and numbered tracker +references in source, configuration, and documentation, including +systemd `Documentation=` fields. Keep the durable explanation in the repository; +issues can point to code, but code must not require an issue to explain it. +Captured logs, bug reports, and benchmark results belong in issues. Benchmark +programs and synthetic fixtures belong in the repository. Git history owns edit +dates and change history; tracked files describe the current contract. + +The static checks recognize tracker references, authoring-date headers, +changelog filenames, and log filenames. Identifying a pasted bug report, +benchmark result, or narrative edit history requires review; these checks do +not claim to recognize arbitrary prose. The tracker rule retains the set's test +directory and Go test exclusions for synthetic examples, and excludes `testdata`. +Date and log rules exclude `tests/fixtures`, `test/fixtures`, and `testdata`. +Unqualified all-uppercase identifiers are not treated as repository shorthand, +and bare numbers need prose context or an opening delimiter, so standards +citations remain valid. Other legitimate literal uses need a scoped override. + None of those arguments should stand between anyone and `process-residue`. The binary answers "what is in it" directly: diff --git a/policy/base/process-residue.toml b/policy/base/process-residue.toml index 7394b4a..91e3c57 100644 --- a/policy/base/process-residue.toml +++ b/policy/base/process-residue.toml @@ -12,7 +12,7 @@ # your own policy. Naming an id nothing inherited defines is refused, so a rule # renamed upstream cannot leave a silent opt-out behind. # -# Every rule defaults to `exclude = ["**/tests/**", "**/test/**"]` because test +# Most rules exclude test directories because test # fixtures legitimately carry example-bad content. Patterns are generic shapes # only — no project-, host-, or account-specific content. @@ -86,14 +86,13 @@ files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "policy/**", "READ [rule.no-dated-source-metadata] message = """ -Do not commit dated status metadata in source docs. Use Git history for -chronology and keep source files stable. +Do not commit authoring dates in source or documentation. Git history owns +edit chronology; keep the current contract and rationale in the file. """ # `Da[t]e` so this rule text never matches itself. -regexp = '^Da[t]e:[[:space:]]*[0-9]{4}-[0-9]{2}-[0-9]{2}' +regexp = '(?i)^[\t ]*(?:(?:\#|//+|/\*+|\*|--|;|\n", + ], + allows: &["Released on the fourteenth.\n", "const EPOCH: &str = \"2026-08-14\";\n"], }, Case { set: "process-residue", @@ -116,6 +120,8 @@ const CORPUS: &[Case] = &[ refuses: &[ "See github.com/acme/widget/issues/12 for the argument.\n", "Fixed in #451.\n", + "# See widget#711.\n", + "# See acme/widget#711.\n", ], allows: &["The rule is stated here rather than in a tracker.\n"], }, @@ -137,6 +143,20 @@ const CORPUS: &[Case] = &[ refuses: &["a build artefact somebody committed\n"], allows: &[], }, + Case { + set: "process-residue", + rule: "no-source-changelog", + path: "docs/CHANGELOG.md", + refuses: &["Changes made to the implementation.\n"], + allows: &[], + }, + Case { + set: "process-residue", + rule: "no-committed-logs", + path: "benchmark/results.log", + refuses: &["Captured output of a benchmark run.\n"], + allows: &[], + }, Case { set: "credentials", rule: "no-committed-secret-material", @@ -490,6 +510,63 @@ fn every_promoted_rule_still_refuses_what_it_was_promoted_for() { } } +#[test] +fn tracker_references_are_refused_in_configuration_and_source() { + for path in [ + "units/lab.service", + "src/estate.py", + "src/estate.rs", + "scripts/build", + ] { + let case = Case { + set: "process-residue", + rule: "no-task-tracker-references", + path, + refuses: &[], + allows: &[], + }; + for sample in [ + "Documentation=https://github.com/acme/widget/issues/711\n", + "# The split estate (widget#711).\n", + "// See acme/widget#711.\n", + ] { + let (code, report) = verdict(&case, sample); + assert_eq!(code, 1, "{path}: {report}"); + assert!(report.contains(case.rule), "{path}: {report}"); + } + for sample in [ + "Documentation=https://example.org/docs/networking\n", + "// UTS #24 and UAX#29 define Unicode properties.\n", + ] { + let (code, report) = verdict(&case, sample); + assert_eq!(code, 0, "{path}: {report}"); + } + } +} + +#[test] +fn volatile_content_rules_preserve_programs_and_synthetic_fixtures() { + for (path, sample) in [ + ( + "tests/fixtures/reference.txt", + "https://github.com/acme/widget/issues/12\n", + ), + ("tests/fixtures/output.log", "Synthetic log event\n"), + ("benchmarks/throughput.rs", "fn main() {}\n"), + ("src/history.rs", "pub struct History;\n"), + ] { + let case = Case { + set: "process-residue", + rule: "", + path, + refuses: &[], + allows: &[], + }; + let (code, report) = verdict(&case, sample); + assert_eq!(code, 0, "{path}: {report}"); + } +} + #[test] fn the_corpus_does_not_refuse_what_the_rules_were_never_about() { // The other half, and it is not decoration: a rule widened until it matches