Skip to content

Produce the privacy page and refuse a claim on it that no check backs [#48] - #149

Merged
iderex merged 1 commit into
mainfrom
privacy-page-48
Aug 12, 2026
Merged

Produce the privacy page and refuse a claim on it that no check backs [#48]#149
iderex merged 1 commit into
mainfrom
privacy-page-48

Conversation

@iderex

@iderex iderex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #48.

What was wrong

There was no privacy page. The pages this repository produces said nothing
about what happens to a reader's request, and record 0011 already pointed at
this issue as the place the strong version of that statement would be made:

git show origin/main:decisions/0011-what-this-site-will-never-do.md | grep -c 'issue #48'
1

Run 2026-08-12 against 21e13dd. The page that was missing is the one whose
sentences are easiest to write and hardest to trust, because a reader can check
none of them from the outside.

What this change does

content/privacy.txt is the prose, and every statement on it stands in one of
three registers.

A checked statement names the invariant that refuses a page breaking it, and
the name reaches the reader as well as the machine. Two statements qualify
today, against page-fetches-no-script and
output-references-no-domain-outside-the-allowlist.

A promise names the issue that would refuse it. Three statements are promises
today, all held by #50: the cookie and storage statement, the statement that
there is no route by which a reader can send anything, and the wider statement
that no page needs scripting at all. That last one is deliberately not written
as checked. What is refused today is a page fetching a script, and a handler
written into the page itself is refused by nothing yet, so writing it as
checked would be the page overstating what stands behind it.

A residual names nothing, because it is what is true anyway: what a host sees,
and that every checked statement is about the bytes this build produced rather
than about whatever answers at an address.

The build renders it through the same template every other page goes through,
at the address record 0008 gives it:

go run . build
read templates/page.html.tmpl
read content/index.txt
read data/design-tokens.json (206 value(s))
wrote dist/index.html (1140 bytes)
wrote dist/privacy/index.html (3350 bytes, 2 checked, 3 promised, 2 residual)
wrote dist/.well-known/security.txt (379 bytes)
no assets/ in the tree, so nothing was copied verbatim
3 file(s) written into dist

The failure it prevents, and the proof it bites

The defect this page can carry is invisible in the rendered page: a statement
whose register slipped renders exactly like one that held.

The one-character version is a keyword with a letter missing. The block behind
it becomes a paragraph, the sentence still renders, and nothing names what
stands behind it. Written into the real file, the build refuses it and says
which line:

sed -i '0,/^checked: /s/^checked: /checkd: /' content/privacy.txt
go run . ci
  build: FAILED
    reading the privacy prose: content/privacy.txt was refused, 1 reason(s):
      a block opens "checkd:", which is not one of the three registers checked:, promised: and residual:, and a block that opens like a statement and is read as a paragraph loses whatever stood behind it: checkd: No page this site produces asks a reader's browser t...

The other half is the name going stale. A row renamed leaves the sentence
standing on the page while what it cites answers to nothing, and the new row
refuses that on the produced page:

sed -i 's/\[page-fetches-no-script\]/[page-fetches-no-scripts]/' content/privacy.txt
go run . ci
      page-cites-only-checks-that-exist: REFUSED, 1 violation(s)
        it refuses a produced page citing a check this gate does not decide, or citing one with no name at all
        because a page that says a check refuses a violation of what it claims is worth the name it gives, and a row renamed or taken out leaves the sentence standing on the page reading as a property that nothing decides, which is the one defect on a privacy page that a reader cannot see
        dist/privacy/index.html: cites the check "page-fetches-no-scripts", which this gate does not decide, so the sentence beside it reads as a property and is a promise

Both files were restored afterwards and the tree pushed is the green one.

The row deleted from the table reds the suite rather than passing quietly,
which is the accounting the table already carries:

go test ./internal/invariant
--- FAIL: TestEveryRowRefusesItsOwnViolationAndPassesTheNeighbour (0.00s)
    invariant_test.go:170: the table holds 22 row(s) and this test carries 23 violation(s); a row without one proves nothing

The parser guard deleted reds the case written for it, and the case reads the
message rather than the failure, which is what says the guard bites for the
reason it names:

go test ./internal/site -run TestPrivacyRefusesAMistypedKeyword
--- FAIL: TestPrivacyRefusesAMistypedKeyword (0.01s)
    privacy_test.go:148: the refusal does not name what was written: [...] was refused, 1 reason(s):
          the file carries no checked statement, and a privacy page with nothing checked on it is the promise this page exists in order not to be

That smallest fixture is refused for a second reason with the guard gone. The
real file would not be: it carries other checked statements, so one keyword
losing a letter would leave a green build and a statement nobody backs.

The gate

go run . ci
gate: 6 legs, in order: format, vet, test, build, links, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 33 file(s)
  vet: ok
  test: ok, 16 test file(s)
  build: ok, 3 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  invariants: ok, 23 rule(s) decided, 1 owed and not decided
6 of 6 legs ran. None was skipped.

go run . reproduce
reproduce: two builds of ., compared byte for byte
  3 file(s), identical in both builds

go run . hygiene origin/main HEAD
hygiene: 1 non-merge commit(s) in origin/main..HEAD, origin internal
  0f92e4b6adba: subject carries its reference
1 commit(s) judged, none refused.

All run 2026-08-12 at 0f92e4b. needs-network was not asked for and nothing
here needed it.

The means

Go and the template path this repository already renders through, because the
statements have to be escaped by the path everything else on the site is
escaped by, and because the half that has to refuse a stale name has to be a
row in the table whose suite already proves a row bites. A separate template or
a second escaping path for this one page would have been a second way for a
sentence to reach a reader, on the page least able to afford one.

What this does not do

It adds no link to the page. Every page's links live in the frame in #73, and
putting one in the footer here would be a second place they come from.

It does not carry the cookie and storage statements as checked, because #50 has
not landed. They are on the page as promises, in the register that says so.

Directives a document cannot carry, and a policy a browser enforces, are #72
rather than this change.

Reading

No second reader. docs/parity.md holds that gap for this repository, and the
evidence above stands in place of one: every claim in this body carries the
command that produced it, run at the commit being pushed.

…#48]

A page saying a site does not do something is worth what a reader can check,
and a reader can check none of it from the outside. So the page is written in
three registers and every statement carries what stands behind it. A checked
statement names the invariant that refuses a page breaking it, a promise names
the issue that would refuse it, and a residual is what is true anyway and names
nothing, which is what the rest is credible against.

The failure this prevents is the one a reader cannot see, because a statement
whose register slipped renders exactly like one that held. A mistyped keyword
turns a claim into a paragraph and takes it out of the register anything reads,
empty brackets render as a name, and an issue number where a check name goes
says a machine refuses what nothing refuses. Each of those stops the build and
names the line it is about, and every reason is reported rather than the first.

The other half is on the produced page. page-cites-only-checks-that-exist
refuses a page naming a check this gate does not decide, so a row renamed or
taken out cannot leave a sentence standing that reads as a property while what
it cites answers to nothing. It reads the table rather than a list beside it,
and an owed row is refused as well, since the run itself prints that one as not
decided.

The three lists render through the template every other page goes through, so a
statement carrying markup reaches the page as text by the path the rest of the
site is escaped by, and a tree with no prose for the page says so rather than
producing nothing quietly.

Signed-off-by: Nils Lehnen <[email protected]>
@iderex iderex added the documentation Improvements or additions to documentation label Aug 12, 2026
@iderex iderex self-assigned this Aug 12, 2026
@iderex iderex added this to the Legal and the data promise milestone Aug 12, 2026
@iderex
iderex merged commit 9985046 into main Aug 12, 2026
17 checks passed
@iderex
iderex deleted the privacy-page-48 branch August 12, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The privacy page: what this site collects and what it sends

1 participant