diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9c872fc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +# A blank issue stays one click away. The design template carries the shape of +# a Design record as prompts in the body rather than as required form fields, +# because a form that refuses a filing it cannot satisfy moves the report +# somewhere nobody tracks, and a thin issue is still an issue. +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/design.md b/.github/ISSUE_TEMPLATE/design.md new file mode 100644 index 0000000..c5cf4ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/design.md @@ -0,0 +1,67 @@ +--- +name: Design record +about: A ruling on what uphold will build -- what is chosen, what is rejected and why, and the pull requests that carry it +title: "Design: " +labels: design, ruling:pending +assignees: '' +--- + + + +## Goal + + + +## Today + + + +## Decision + + + +## Design + + + +## Plan + + + +## Done when + + + +## Open + + diff --git a/AGENTS.md b/AGENTS.md index f46f10f..2337b95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ Do NOT re-enforce the static rules. They run on every change and they run first; repeating their findings costs a reviewer's attention and buys a second opinion nobody asked for. The rules already active here are: +- `adr-freeze` - `catalog-reference-current` - `catalog-tests` - `catalog-validate` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0011e78..75a835e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,6 +73,27 @@ list, put it at that path, or point `private_owners_file` at it from the top of the policy file, and the two forms the note names are checked as well. See [REFERENCE.md](docs/REFERENCE.md#where-the-owner-list-lives). +## Where a decision goes + +A change that needs a decision is a GitHub issue in the +[Design record](.github/ISSUE_TEMPLATE/design.md) shape before a pull request +exists: the decision in the body, and the research in a comment that carries +the version, date and evidence of each claim. A Design record carries exactly +one ruling label. The template files it with `ruling:pending`, and the ruling +is recorded by replacing that label with `ruling:accepted`, `ruling:rejected` +or `ruling:deferred`. + +The repository receives only what runs, plus +[REFERENCE.md](docs/REFERENCE.md), which stays the normative statement of what +uphold does now. Tracked files carry no issue numbers, dates or status lines; +those stay in the Design record that rules the change. + +The records under `docs/adr/` stay as history and are cited as they are. No new +one is added: the `adr-freeze` rule in `policy/principles.toml` refuses any +other file under that directory, and its message names the remedy. +[ADR 0012](docs/adr/0012-a-rule-may-reach-the-content-its-repository-pins.md) +stands at Proposed until the owner rules it. + ## Working on the engine The checks run at three stages, chosen by cost. The commit stage runs what can diff --git a/REVIEW.md b/REVIEW.md index f46f10f..2337b95 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -8,6 +8,7 @@ Do NOT re-enforce the static rules. They run on every change and they run first; repeating their findings costs a reviewer's attention and buys a second opinion nobody asked for. The rules already active here are: +- `adr-freeze` - `catalog-reference-current` - `catalog-tests` - `catalog-validate` diff --git a/ROADMAP.md b/ROADMAP.md index b541713..68fff1a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,9 @@ # Roadmap +The direction of the work, by area. The live state -- what is scheduled, what +is in progress, what is waiting on a ruling -- is the issue tracker's milestones +and labels, and this page cites no issue by number. + ## Catalog - Promote seed entries to reviewed status after source and field review; every @@ -70,7 +74,7 @@ provenance in refusal output. The remaining work is per repository. `target/` and test corpora. Adopting it widens what is scanned, so it is done per repository rather than as a fleet sweep. -## Evidence providers, and what issue 165 leaves open +## Evidence providers, and what the evidence layer leaves open The evidence layer ([ADR 0008](docs/adr/0008-evidence-and-what-a-policy-may-consume.md)) ships with three compiled-in providers (Git over the message, tree-sitter over @@ -78,13 +82,13 @@ the staged trees, a pattern over the staged diff) and one policy, `removed-function-named`, that reads them. Not yet shipped: - **A compiler or LSP provider.** `symbol_defined`, `unresolved_reference` and - `type_changed` are kinds the issue names and nothing reports; a kind with no - provider would be dead configuration. ADR 0004 measured what the semantic tier - costs before a commit, so such a provider belongs at the manual stage or - nowhere. + `type_changed` are kinds the evidence-layer design names and nothing reports; + a kind with no provider would be dead configuration. ADR 0004 measured what + the semantic tier costs before a commit, so such a provider belongs at the + manual stage or nowhere. - **`dependency_edge` and layer kinds.** The architectural-boundary policy the - issue sketches needs a provider that resolves imports across modules, the - same tier as above. + evidence-layer design sketches needs a provider that resolves imports across + modules, the same tier as above. - **An `Inferred` provider.** The variant exists and is tested with a double; no provider produces it. Its seam is `uphold hook`, where an agent's own account of a change is already read. Whatever it reports is `Inferred` by diff --git a/policy/principles.toml b/policy/principles.toml index dda8110..6e90b9a 100644 --- a/policy/principles.toml +++ b/policy/principles.toml @@ -225,6 +225,42 @@ files.exclude = ["**/tests/**", "**/test/**"] # The same selection as the rule above, and the same floor for the same reason. files.min_selected = 1 +# A decision is a Design record issue, and docs/adr is the history of the +# decisions made before that. The records already written stay, and are cited +# as they are; a new file beside them would be a second home for the next +# decision, with no ruling label and no issue for the discussion to happen on. +# +# A path rule rather than prose: `path_regexp` reads the paths git tracks, so a +# file staged under docs/adr is a finding at the commit that stages it. The +# records that exist are excluded one by one, by exact path, so a new name is +# refused even when it copies the numbering. Editing one of them in place +# stays allowed, since an amendment to a status line is still history. +# +# No `files.min_selected`: the state this rule exists to hold is a selection +# of nothing, so a floor cannot tell a frozen tree from a moved one. +[rule.adr-freeze] +message = """ +file a Design record issue (label design) instead; docs/adr is frozen history. +The records already under docs/adr stay and are cited as they are; a decision +not yet ruled goes to the issue tracker, and the repository receives what runs. +""" +path_regexp = '^docs/adr/' +files.include = ["docs/adr"] +files.exclude = [ + "/docs/adr/0001-a-config-surface-a-stranger-can-read-in-one-minute.md", + "/docs/adr/0002-the-reach-of-a-command-shim.md", + "/docs/adr/0003-the-structural-tier-and-what-a-clean-run-means.md", + "/docs/adr/0004-the-semantic-tier-and-what-it-costs-to-be-sure.md", + "/docs/adr/0005-what-a-provider-must-answer.md", + "/docs/adr/0006-what-a-bundled-set-may-attach-to-a-command.md", + "/docs/adr/0007-what-the-surveyed-orchestrators-collapse.md", + "/docs/adr/0008-evidence-and-what-a-policy-may-consume.md", + "/docs/adr/0009-a-consumers-structural-rules-are-ast-greps.md", + "/docs/adr/0010-who-asks-whether-a-hook-pin-is-current.md", + "/docs/adr/0011-what-an-entry-is-where-it-is-used-and-how-a-check-sees-it.md", + "/docs/adr/0012-a-rule-may-reach-the-content-its-repository-pins.md", +] + # A hook's display name is the one line it prints while it runs, and it is read # by somebody who is not reading the file it came from. `uphold guard # (pre-commit)` meant the stage, and under prek the terminal said diff --git a/policy/upheld.toml b/policy/upheld.toml index 493cb29..d846245 100644 --- a/policy/upheld.toml +++ b/policy/upheld.toml @@ -197,6 +197,16 @@ rule = "review-current" # would make this claim false is `--review --check` exiting 0 over a compiled # document that differs from what --emit would write now. +[[enforce]] +principle = "single-authoritative-source" +rule = "adr-freeze" +# A decision has one home, the Design record issue, where the ruling label and +# the discussion live. The rule refuses a new file under docs/adr, which would +# be a second home for the next decision. What would make this claim false is +# the scan exiting 0 over a tracked docs/adr path that is not one of the +# records the rule names; it does not see a decision written into some other +# directory, and a renamed docs/adr selects nothing and reads clean. + # What routes to a rule, and what routes to a reviewer. # # `enforcement.automatable = "yes"` says a machine CAN decide the principle. It