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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project a

## [Unreleased]

### Added, a refresh can now say what it moved

- **`python -m perimeter.diff OLD NEW`, and `make diff`.** A refresh of the pinned
retrievals replaces both coverage artifacts wholesale, and until now the only account of
what changed was `git diff` over a large JSON document, which answers a different
question. It reports lines, so a reordered list reads as hundreds of changes and one
count that moved reads as two. This walks every leaf of both documents and reports the
value at its path, with both sides. Exit `0` no change, `1` changes reported, `2` a
refused removal or an unreadable input. `--json` writes the rows sorted by path, so the
output is byte-identical on repeat and a refresh can cite it in `PROVENANCE.md`.
- **A key that stops being published is refused, not reported as a change.** `--allow-removals`
is how a deliberate one gets through, and it is the only way. A key present in the
earlier artifact and absent from the later one means the build stopped publishing
something, which is a different event from a number moving.
- **A number becoming `null` is a change to absence, never a removal.** ADR-0010 writes a
domain the layer stopped publishing as `null` rather than omitting it, so collapsing the
two would lose the distinction the artifact exists to carry.
- **Nothing is compared as a float.** `1000` and `1000.0` are equal in Python and are not
the same published value; the comparison reports a type change. Empty containers get a
marker leaf, so deleting `"markers": {}` outright is visible rather than invisible.
- **`make site-check` prints the leaf comparison before the byte-for-byte check decides.**
The comparison is a report and `diff -r` is still the gate; the report's exit status is
discarded deliberately, so it can add detail and can never turn a red target green.
- **Refusals, per ADR-0004.** A missing file, an empty file, an unparseable one, and a
JSON document whose top level is not an object are each refused rather than parsed into
an empty document. Two empty documents compare equal, and would report "no change"
about two artifacts that were never read.
- The gate was run against the faults it exists to catch, and one of those runs found a
weak assertion in this repository's own new test: with the emptiness check deleted, an
empty file still failed as unparseable JSON and `match="empty"` was satisfied by the
fixture's filename, `empty.json`. The fixture is renamed and the pattern now matches the
reason. A control that passes for the wrong reason is a control that is not there.

### Fixed, the browser accessibility gate was shallowing this checkout

- **Every pull request's `verify` job could fail on five tests about tags.** On a
Expand Down
33 changes: 32 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: verify lock-check sync lint format typecheck test audit site site-offline \
site-check acquire pages node-sync htmlvalidate a11y node-audit determinism \
site-check diff acquire pages node-sync htmlvalidate a11y node-audit determinism \
browser-sync a11y-browser browser-audit

# CI / `make verify` body: the two MUST stay byte-for-byte identical.
Expand Down Expand Up @@ -76,9 +76,40 @@ site-check:
--perimeters data/raw/frap_perimeters.json \
--dins data/raw/dins_postfire.json \
--out build/site-current
@# What moved, before what follows decides whether it may. `diff -r` is the gate;
@# this is the report, and it is written so it can only ever add detail: its exit
@# status is deliberately discarded here and the byte comparison below is what
@# fails the target. A reporting step that could turn a red target green would be
@# the swallowed-failure defect ADR-0004 is about.
@for f in perimeters-coverage.json dins-coverage.json; do \
uv run python -m perimeter.diff "site/data/$$f" "build/site-current/data/$$f" \
--allow-removals || true; \
done
diff -r site build/site-current
@echo "site-check: site/ is byte-identical to a fresh build from data/raw/"

# Compare two coverage artifacts leaf by leaf. Offline, reads only the two files named.
#
# `git diff` answers a different question about these documents: it reports lines, so a
# reordered list reads as hundreds of changes and one count that moved reads as two. This
# reports values, with their paths, and refuses a key the later artifact stopped
# publishing unless ALLOW_REMOVALS names that as deliberate. Exit 0 no change, 1 changes
# reported, 2 a refused removal or an unreadable input.
#
# make diff OLD=site/data/dins-coverage.json NEW=build/site-current/data/dins-coverage.json
# make diff OLD=a.json NEW=b.json ALLOW_REMOVALS=1 IGNORE=is_fixture
#
# Those three exit codes are the module's. Make collapses every recipe failure to its own
# exit 2, so a script that needs to tell "changes reported" from "removal refused" must
# call `uv run python -m perimeter.diff` directly. This target is for a person at a
# terminal, where the printed lines carry the distinction.
DIFF_FLAGS = $(if $(ALLOW_REMOVALS),--allow-removals,) $(if $(IGNORE),--ignore $(IGNORE),) $(if $(JSON),--json,)

diff:
@test -n "$(OLD)" || { echo "make diff: set OLD=<earlier artifact>" >&2; exit 2; }
@test -n "$(NEW)" || { echo "make diff: set NEW=<later artifact>" >&2; exit 2; }
uv run python -m perimeter.diff "$(OLD)" "$(NEW)" $(DIFF_FLAGS)

# The same pipeline over committed fixtures: runs anywhere, output flagged is_fixture.
site-offline:
uv run python -m perimeter.cli --fixture \
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,36 @@ uv sync
npm ci
make verify # lockfile, lint, format, types, tests, SCA, page checks, determinism
make site-offline # build from committed fixtures; runs anywhere, no network
make diff OLD=a.json NEW=b.json # compare two coverage artifacts leaf by leaf
```

### What a refresh moved

The figures on these pages move only when the pinned retrievals are deliberately
refreshed, and a refresh replaces both JSON artifacts wholesale. `make diff` says what
that changed, value by value rather than line by line:

```sh
make diff OLD=site/data/dins-coverage.json NEW=build/site-current/data/dins-coverage.json
```

Every leaf is compared at its path (`/fields[3]/present`), with both values printed.
`--json` writes the same rows sorted by path, so a refresh can cite a comparison in
`PROVENANCE.md` rather than a screenshot of `git diff`. Three distinctions it keeps:

- A key the later artifact **stops publishing** is not the same event as a value moving.
It is refused outright unless `ALLOW_REMOVALS=1` names it as deliberate.
- A number that becomes `null` is a **change to absence**, reported with both sides, and
never a removal. ADR-0010 writes a domain the layer stopped publishing as `null`.
- `1000` and `1000.0` are a type change and are reported as one. Every percentage in
these artifacts is a `*_tenths_pct` integer so that no float decides an equality, and
nothing here converts, rounds, or tolerances a value.

An empty, missing, or unparseable input is refused rather than compared: two empty files
compare equal, and "no change" about two files that were never read is the failure this
whole repository is organised against. `make site-check` prints the same leaf comparison
before its byte-for-byte check decides, so a drift report names the values that moved.

`make verify` includes `make pages`, which builds the pages from the committed fixtures and
checks them four ways: `html-validate` for HTML conformance and the markup-level
accessibility rules, `axe-core` in a headless DOM for the WCAG 2.0, 2.1 and 2.2 A and AA
Expand Down
Loading
Loading