From d53794c0ed39e311737530ee18eabacb1e5dc454 Mon Sep 17 00:00:00 2001 From: HackingGate Date: Sat, 12 Sep 2026 14:14:57 +0900 Subject: [PATCH 1/2] Block tracker references and volatile history in source --- docs/REFERENCE.md | 18 ++++++++ policy/base/process-residue.toml | 46 ++++++++++++++----- policy/base/sets.lock.json | 59 +++++++++++++++--------- src/guard/names.rs | 4 +- src/shim.rs | 7 +-- tests/base_set_corpus.rs | 77 +++++++++++++++++++++++++++++++- 6 files changed, 169 insertions(+), 42 deletions(-) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 5a5f0ff..139adf1 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 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..88298bc 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 ]*(?:(?:\#|//+|/\*+|\*|--|;)\s*)?(?:Da[t]e|Created|Updated|Last updated|Last modified):[\t ]*[0-9]{4}-[0-9]{2}-[0-9]{2}' files.include = ["."] -files.glob = ["*.md", "*.rst", "*.txt"] -files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "CHANGELOG*"] +files.exclude = ["**/tests/fixtures/**", "**/test/fixtures/**", "**/testdata/**"] [rule.no-status-source-metadata] message = """ @@ -119,13 +118,18 @@ files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "**/docs/adr/**"] [rule.no-task-tracker-references] message = """ -Do not commit task-tracker references in source docs. Keep task tracking in -tracker metadata, not in tracked files. +Source, configuration, and documentation must not depend on volatile +issue or PR records. State the durable contract and rationale here, or link to +repository documentation. Issues may link to code; code must not depend on +issues. Keep logs, bug reports, and benchmark results in issues, and edit +chronology in Git history. """ -regexp = 'github\.com/[^[:space:])]+/(?:issues|pull)/[0-9]+|(?:^|[[:space:]]|\(|\[)#[0-9]+\b' +# Unqualified repository names require a lowercase letter, and bare numbers +# require a lowercase prose word or an opening delimiter. This avoids treating +# uppercase standards designators as repositories or task-tracking prose. +regexp = 'github\.com/[^[:space:])]+/(?:issues|pull)/[0-9]+|\b(?:[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+|[A-Za-z0-9_.-]*[a-z][A-Za-z0-9_.-]*)#[0-9]+\b|(?:^|\b[a-z]+[\t ]+|\(|\[)#[0-9]+\b' files.include = ["."] -files.glob = ["*.md", "*.rst", "*.txt"] -files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "CHANGELOG*"] +files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "**/testdata/**"] [rule.no-process-history-references] message = """ @@ -135,7 +139,25 @@ docs. Extract the durable rule or final state instead. regexp = '(?i)\b(?:issue|pr)\s+#[0-9]+\b|\bas discussed in (?:an? )?(?:issue|thread|pr)\b|(?:https?://)?github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/(?:issues|pull)/[0-9]+\b' files.include = ["."] files.glob = ["*.md", "*.rst", "*.txt"] -files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "CHANGELOG*"] +files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go"] + +[rule.no-source-changelog] +message = """ +Do not maintain change history in repository files. Git history owns changes +and their dates; source and documentation describe the current behavior. +""" +path_regexp = '(?i)(?:^|/)(?:changelog|changes|history)(?:\.(?:md|rst|txt))?$' +files.include = ["."] + +[rule.no-committed-logs] +message = """ +Keep captured logs in issues or ignored local storage. Commit the code that +produces and checks evidence, not the volatile output of a run. Synthetic log +fixtures belong in an explicitly named fixture directory. +""" +path_regexp = '(?i)\.log(?:\.[0-9]+)?$' +files.include = ["."] +files.exclude = ["**/tests/fixtures/**", "**/test/fixtures/**", "**/testdata/**"] [rule.no-tracked-private-data-paths] message = """ diff --git a/policy/base/sets.lock.json b/policy/base/sets.lock.json index 6cde47f..e40d6df 100644 --- a/policy/base/sets.lock.json +++ b/policy/base/sets.lock.json @@ -5,17 +5,29 @@ { "files": { "exclude": [ - "**/tests/**", - "**/test/**", - "**/*_test.go", - "CHANGELOG*" + "**/tests/fixtures/**", + "**/test/fixtures/**", + "**/testdata/**" ], "exclude_cfg_test": false, - "glob": [ - "*.md", - "*.rst", - "*.txt" + "include": [ + "." + ], + "multiline": false, + "word": false + }, + "id": "no-committed-logs", + "message": "Keep captured logs in issues or ignored local storage. Commit the code that\nproduces and checks evidence, not the volatile output of a run. Synthetic log\nfixtures belong in an explicitly named fixture directory.\n", + "path_regexp": "(?i)\\.log(?:\\.[0-9]+)?$" + }, + { + "files": { + "exclude": [ + "**/tests/fixtures/**", + "**/test/fixtures/**", + "**/testdata/**" ], + "exclude_cfg_test": false, "include": [ "." ], @@ -23,8 +35,8 @@ "word": false }, "id": "no-dated-source-metadata", - "message": "Do not commit dated status metadata in source docs. Use Git history for\nchronology and keep source files stable.\n", - "regexp": "^Da[t]e:[[:space:]]*[0-9]{4}-[0-9]{2}-[0-9]{2}" + "message": "Do not commit authoring dates in source or documentation. Git history owns\nedit chronology; keep the current contract and rationale in the file.\n", + "regexp": "(?i)^[\\t ]*(?:(?:\\#|//+|/\\*+|\\*|--|;)\\s*)?(?:Da[t]e|Created|Updated|Last updated|Last modified):[\\t ]*[0-9]{4}-[0-9]{2}-[0-9]{2}" }, { "builtin": "no-hand-copied-base-rule", @@ -80,8 +92,7 @@ "exclude": [ "**/tests/**", "**/test/**", - "**/*_test.go", - "CHANGELOG*" + "**/*_test.go" ], "exclude_cfg_test": false, "glob": [ @@ -99,6 +110,19 @@ "message": "Do not reference process-history records (issue/PR/thread numbers) in final\ndocs. Extract the durable rule or final state instead.\n", "regexp": "(?i)\\b(?:issue|pr)\\s+#[0-9]+\\b|\\bas discussed in (?:an? )?(?:issue|thread|pr)\\b|(?:https?://)?github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/(?:issues|pull)/[0-9]+\\b" }, + { + "files": { + "exclude_cfg_test": false, + "include": [ + "." + ], + "multiline": false, + "word": false + }, + "id": "no-source-changelog", + "message": "Do not maintain change history in repository files. Git history owns changes\nand their dates; source and documentation describe the current behavior.\n", + "path_regexp": "(?i)(?:^|/)(?:changelog|changes|history)(?:\\.(?:md|rst|txt))?$" + }, { "files": { "exclude": [ @@ -129,14 +153,9 @@ "**/tests/**", "**/test/**", "**/*_test.go", - "CHANGELOG*" + "**/testdata/**" ], "exclude_cfg_test": false, - "glob": [ - "*.md", - "*.rst", - "*.txt" - ], "include": [ "." ], @@ -144,8 +163,8 @@ "word": false }, "id": "no-task-tracker-references", - "message": "Do not commit task-tracker references in source docs. Keep task tracking in\ntracker metadata, not in tracked files.\n", - "regexp": "github\\.com/[^[:space:])]+/(?:issues|pull)/[0-9]+|(?:^|[[:space:]]|\\(|\\[)#[0-9]+\\b" + "message": "Source, configuration, and documentation must not depend on volatile\nissue or PR records. State the durable contract and rationale here, or link to\nrepository documentation. Issues may link to code; code must not depend on\nissues. Keep logs, bug reports, and benchmark results in issues, and edit\nchronology in Git history.\n", + "regexp": "github\\.com/[^[:space:])]+/(?:issues|pull)/[0-9]+|\\b(?:[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+|[A-Za-z0-9_.-]*[a-z][A-Za-z0-9_.-]*)#[0-9]+\\b|(?:^|\\b[a-z]+[\\t ]+|\\(|\\[)#[0-9]+\\b" }, { "files": { diff --git a/src/guard/names.rs b/src/guard/names.rs index 3ce3f37..e453d3b 100644 --- a/src/guard/names.rs +++ b/src/guard/names.rs @@ -297,8 +297,6 @@ impl OwnerMatchers { // the repository, followed by a name, is a sibling reference and not a // directory: nobody writes `acme/main.rs`. // - // Found by trying to write the deprecation note that would close #29 - // and watching the guard pass it. let mut owners: Vec = private_owners.to_vec(); if let Some(own_owner) = own_owner { if !owners @@ -429,7 +427,7 @@ pub(crate) fn lookup(cache: &mut BTreeMap, owner: &str, repo: /// Whether the repository being written INTO is public. /// -/// This is the scope condition, and it is also the gap #14 exists for: it asks +/// This is the scope condition: it asks /// whether the target is public NOW. Content written into a private repository /// is correctly allowed at write time, and nothing re-examines that decision /// when the repository later goes public. diff --git a/src/shim.rs b/src/shim.rs index e98dbcc..7315592 100644 --- a/src/shim.rs +++ b/src/shim.rs @@ -3419,11 +3419,8 @@ mod tests { Reading::Unclear(flag) if flag == "--fic-a" )); // And ONE is the whole space, so both readings missing is a conclusion - // rather than a doubt. This line asserted `Unclear` until #56, and what - // that cost was measured: on a git shim declaring `push:*`, most - // ordinary invocations printed the could-not-look refusal -- `git show - // --stat HEAD`, `git commit -F -`, `git checkout -b `, `git reset - // --hard HEAD` among them -- while `git push` itself was quiet. A + // rather than a doubt. A git shim declaring `push:*` must leave + // unrelated commands alone and check the push itself. A // warning printed over every command this shim exists to stay out of // the way of trains the reader to ignore the one invocation where the // doubt is real, which is the failure the whole arm was written to diff --git a/tests/base_set_corpus.rs b/tests/base_set_corpus.rs index e1051a1..7adcbd0 100644 --- a/tests/base_set_corpus.rs +++ b/tests/base_set_corpus.rs @@ -99,8 +99,8 @@ const CORPUS: &[Case] = &[ set: "process-residue", rule: "no-dated-source-metadata", path: "sample.md", - refuses: &["Date: 2026-08-14\n"], - allows: &["Released on the fourteenth.\n"], + refuses: &["Date: 2026-08-14\n", "// Last updated: 2026-08-14\n"], + allows: &["Released on the fourteenth.\n", "const EPOCH: &str = \"2026-08-14\";\n"], }, Case { set: "process-residue", @@ -116,6 +116,8 @@ const CORPUS: &[Case] = &[ refuses: &[ "See github.com/acme/widget/issues/12 for the argument.\n", "Fixed in #451.\n", + "# See harness#711.\n", + "# See RouterPlane/harness#711.\n", ], allows: &["The rule is stated here rather than in a tracker.\n"], }, @@ -137,6 +139,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 +506,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/RouterPlane/harness/issues/711\n", + "# The split estate (harness#711).\n", + "// See RouterPlane/harness#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 From fa74dfa05941281bf1475d389fdeae945943c134 Mon Sep 17 00:00:00 2001 From: HackingGate Date: Sat, 12 Sep 2026 14:24:10 +0900 Subject: [PATCH 2/2] Cover HTML date comments and refresh the Ruff hook pin --- .pre-commit-config.yaml | 2 +- docs/REFERENCE.md | 2 +- policy/base/process-residue.toml | 2 +- policy/base/sets.lock.json | 2 +- tests/base_set_corpus.rs | 16 ++++++++++------ 5 files changed, 14 insertions(+), 10 deletions(-) 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 139adf1..b008b4d 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -560,7 +560,7 @@ 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 issue and PR URLs and numbered tracker +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. diff --git a/policy/base/process-residue.toml b/policy/base/process-residue.toml index 88298bc..91e3c57 100644 --- a/policy/base/process-residue.toml +++ b/policy/base/process-residue.toml @@ -90,7 +90,7 @@ 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 = '(?i)^[\t ]*(?:(?:\#|//+|/\*+|\*|--|;)\s*)?(?:Da[t]e|Created|Updated|Last updated|Last modified):[\t ]*[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 { @@ -116,8 +120,8 @@ const CORPUS: &[Case] = &[ refuses: &[ "See github.com/acme/widget/issues/12 for the argument.\n", "Fixed in #451.\n", - "# See harness#711.\n", - "# See RouterPlane/harness#711.\n", + "# See widget#711.\n", + "# See acme/widget#711.\n", ], allows: &["The rule is stated here rather than in a tracker.\n"], }, @@ -522,9 +526,9 @@ fn tracker_references_are_refused_in_configuration_and_source() { allows: &[], }; for sample in [ - "Documentation=https://github.com/RouterPlane/harness/issues/711\n", - "# The split estate (harness#711).\n", - "// See RouterPlane/harness#711.\n", + "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}");