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 .github/workflows/run_pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
# `actions/checkout` takes the sibling's OWN default branch — which for
# views-appwrite is `development` — while ADR-014 §3 says the authority for a claim
# about another repository is that repository's `main`. Two checks in
# test_env_declaration read the working tree and a third demands reachability from
# test_env_declaration read the sibling's `main` and a third demands reachability from
# `main`; pointed at `development` they would eventually demand contradictory things.
#
# `actions/checkout` refuses a path outside $GITHUB_WORKSPACE, so the siblings go
Expand Down
34 changes: 34 additions & 0 deletions docs/ADRs/014_claims_and_the_guards_that_carry_them.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ Where a guard's inputs are declared (a path list, a module list, a set of names)
that the inputs are real**. `rglob` on a nonexistent directory yields nothing rather than
raising; a list of paths that no longer exist does not fail a scan, it empties it.

**Amended 2026-08-12.** Mutation-proving is necessary and it is not sufficient, and the
gap has a shape worth naming.

A mutant list written by the author of the guard covers the cases the author already had
in mind — which are, by construction, the cases the guard already handles. Measured on the
guard that keeps coordinate values out of this public repository: **thirteen input forms
proposed by its author, thirteen caught; twenty-nine proposed independently, fifteen
missed.** Every case in the first list genuinely passed. *"Proven against N mutations"*
reads as a statement about the guard when it is a statement about N.

The useful rule is a **diagnostic**, not a process:

> If a guard can only be proven against inputs you invented, that is the signal the guard
> is on the wrong side of a boundary — it is verifying a fact you do not own.

Prefer moving the check to where the fact lives, or anchoring the mutant list in something
real: this repository's own corpus, the registry's actual rows, an outcome you can observe.
The no-copy scan's stopping rule is the worked example — it derives its list of accepted
forms from this repository's tracked markdown, so a form no document here uses is not a gap.

Where that is impossible, two cheap obligations:

- the mutant list is a **declared artifact in the test file**, not a paragraph in a pull
request (`tests/test_ci_sibling_coverage.py::_MUTANTS` is the pattern);
- **a proof must be able to fail** — mutate the function, watch the proof go red, record
it. An assertion whose inputs are derived from the thing under test proves nothing, and
three of those shipped here in two days.

Deliberately **not** adopted: a requirement that someone other than the author write the
mutants for every guard. The friction would exceed the disease for a single maintainer.
The diagnostic above is the load-bearing part; independent mutants are worth buying only
for the silent-failure class — a leak, an invisible delivery — where the cost of missing
is not a red build but a thing nobody ever notices.

### §3 Prefer a false negative to a false alarm

A guard that cries wolf gets deleted, and then the rule it carried is unguarded — which is
Expand Down
6 changes: 6 additions & 0 deletions docs/ADRs/016_ci_read_access_to_private_siblings.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ The **`public?` column is not, and cannot be** — which is why `public` was del
because a reader needs it to follow the argument. Nothing reads this table; if it drifts
from `SIBLINGS`, only a human will notice.

**Trigger, since an unenforced claim needs one (ADR-014 §4):** re-read this column the next
time a repository in it changes visibility, or the next time CI fails to check one out. The
owner is whoever makes that change. It is deliberately not machine-checked — verifying it
means a network call from a test suite that makes none, and the failure it would catch
(a tokenless checkout of something now private) already fails loudly at the checkout step.

### §5 CI downloads exactly what that list says, and a test enforces it

The workflow downloads every sibling marked `ci_checkout=True`.
Expand Down
49 changes: 46 additions & 3 deletions docs/ADRs/017_facts_across_a_private_boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,6 @@ Concretely, for the delivery label:

### The order these land in is part of the decision, not an afterthought

**None of those three is in place yet**, and the present tense above describes the decided
end state rather than today's behaviour.

**The obvious sequence has a hole, and it is green.** If step 1 lands, then step 2 replaces
the source-reading check with a registry read, and step 3 has not happened yet, the state
is: the registry declares a string a human typed; we check our copy against that string and
Expand Down Expand Up @@ -250,6 +247,52 @@ because a download that has quietly stopped earning its place is how the previou
of all this went wrong, and because ADR-016 cannot know it: the decision that obsoletes it
is this one.

### §7a What a check in this repository may rest on

The rule §7 arrives at for one case generalises, and it is worth stating on its own,
because every defect this document's arc produced sat on the wrong side of it.

> **A check here may rest on a fact we own, on a fact another repository has *declared* in
> the public registry, or on an *outcome* we can observe. It may never rest on another
> repository's implementation.**
>
> Where a needed fact is none of those three, the check is not built here. It is requested
> as a declaration, requested as a consumer obligation, or registered as an accepted gap
> with a named owner and trigger.

[ADR-003](003_authority_of_declarations_over_inference.md) already says declarations over
inference. It was applied rigorously to the product and not at all across the repository
boundary, and that asymmetry is where the cost landed.

**The evidence, because this is a rule bought with two days.** The source-reading check
broke twice in twenty-four hours — views-faoapi on 11 August, views-crafdapi on the 12th
— each time because that repository refactored a literal argument into a named constant.
Verified: commits `8615574` and `0c493ae` each introduce that constant *and* add that
repository's registry-binding test. **The improvement and the breakage were one edit.**
A mechanism a consumer breaks by improving itself will keep breaking.

The corollary is the one that costs something to accept: when a fact is none of the three,
the honest move is to say so and register it, not to build a proxy. The delivery-label
composition is exactly that — see §8 and register C-92.

### §7b An ADR records a decision, and never the current state of the code

This document and [ADR-016](016_ci_read_access_to_private_siblings.md) both described what
the code did at the moment of writing — *"this is the shape the registry check now has"*,
*"today the check still reads the consumer's source"*. Every such sentence needed an
erratum the first time the code moved, and between them they produced nine stale claims in
two days, one of which repealed a rule in this document on a premise that was false by six
hours.

> **An ADR states a decision, its reasoning, and what would reverse it. It does not
> describe the current implementation.** Where a reader needs to know what the code does
> now, the ADR names the test or module that answers, and the answer lives there.

A decision record that doubles as a description of the code is a second copy of the code,
and it rots on a schedule nobody is watching. This clause is written into ADR-017 rather
than ADR-000 because it was learned here; if it survives contact with a second document it
belongs in the ADR conventions.

### §8 What this does **not** verify, stated plainly

This checks **our copy against the declaration**. It does **not** check the consumer's code
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "views-postprocessing"
version = "1.0.0"
version = "1.1.0"
description = ""
authors = [
"Dylan Pinheiro <dylpin@prio.org>",
Expand Down
Loading
Loading