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
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions .github/ISSUE_TEMPLATE/design.md
Original file line number Diff line number Diff line change
@@ -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: ''
---

<!--
Seven headings, in the same place in every record, so a reader who opens the
tenth one finds each answer where the first one put it. Nothing refuses a
filing with a heading left blank: an empty heading is a question the next
reader can see was not answered. Delete a heading only when it does not apply,
rather than leaving a prompt standing in for an answer, and delete this comment.

The research behind the decision goes in a comment on this issue, not in the
body: every option considered, with its version, the date it was read, and the
evidence for each claim. The body states the ruling; the comment is what it
was ruled on.
-->

## Goal

<!-- One paragraph: what changes for a repository that runs uphold. -->

## Today

<!--
What exists, with the file or command output that shows it and the revision it
was read at.
-->

## Decision

<!--
What is chosen, in bold. Then each rejected option in one clause: what it is
and why it lost. Link the research comment from here.
-->

## Design

<!--
The mechanism: which rule kind, set, seam or command carries it, and what
it reads. Where a picture carries more than the prose, draw it as a fenced
```mermaid block; GitHub renders it in the issue.
-->

## Plan

<!--
Numbered, one pull request per item, smallest first. Each item names what it
stands on -- the existing library, or the engine feature (a rule kind, a
bundled set, a seam) -- so nothing is written by hand where one of those
already does the job.
-->

## Done when

<!--
A checklist a reviewer can tick from the tree alone. Each item names the test
or the rule that holds the decision once it is merged; a decision with neither
is prose, and prose has no condition on which to fail.
-->

## Open

<!-- What is still unverified, deferred, or left to watch. -->
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
18 changes: 11 additions & 7 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -70,21 +74,21 @@ 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
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
Expand Down
36 changes: 36 additions & 0 deletions policy/principles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions policy/upheld.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading