Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
18 changes: 18 additions & 0 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
46 changes: 34 additions & 12 deletions policy/base/process-residue.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 = """
Expand All @@ -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 = """
Expand All @@ -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 = """
Expand Down
59 changes: 39 additions & 20 deletions policy/base/sets.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,38 @@
{
"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": [
"."
],
"multiline": false,
"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",
Expand Down Expand Up @@ -80,8 +92,7 @@
"exclude": [
"**/tests/**",
"**/test/**",
"**/*_test.go",
"CHANGELOG*"
"**/*_test.go"
],
"exclude_cfg_test": false,
"glob": [
Expand All @@ -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": [
Expand Down Expand Up @@ -129,23 +153,18 @@
"**/tests/**",
"**/test/**",
"**/*_test.go",
"CHANGELOG*"
"**/testdata/**"
],
"exclude_cfg_test": false,
"glob": [
"*.md",
"*.rst",
"*.txt"
],
"include": [
"."
],
"multiline": false,
"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": {
Expand Down
4 changes: 1 addition & 3 deletions src/guard/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = private_owners.to_vec();
if let Some(own_owner) = own_owner {
if !owners
Expand Down Expand Up @@ -429,7 +427,7 @@ pub(crate) fn lookup(cache: &mut BTreeMap<String, Resolved>, 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.
Expand Down
7 changes: 2 additions & 5 deletions src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>`, `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
Expand Down
81 changes: 79 additions & 2 deletions tests/base_set_corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ 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",
"<!-- Last updated: 2026-08-14 -->\n",
],
allows: &["Released on the fourteenth.\n", "const EPOCH: &str = \"2026-08-14\";\n"],
},
Case {
set: "process-residue",
Expand All @@ -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"],
},
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand Down
Loading