Skip to content

Refuse a produced page that declares a language the site does not publish in - #187

Merged
iderex merged 1 commit into
mainfrom
tests/a-page-declaring-a-language-the-site-does-not-publish-in
Aug 23, 2026
Merged

Refuse a produced page that declares a language the site does not publish in#187
iderex merged 1 commit into
mainfrom
tests/a-page-declaring-a-language-the-site-does-not-publish-in

Conversation

@iderex

@iderex iderex commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closes #186.

What was wrong

page-declares-its-language read whether the lang attribute was there and
whether it held anything, and nothing else:

git show origin/main:internal/invariant/invariant.go | sed -n '294p'
			Refuses: "a produced page with no html element, or one whose lang attribute is missing or empty",

So a produced page announcing a language this site does not publish in walked
through the row. With the value put in front of it through the template, which
is where a page property is actually lost, at origin/main 8a45538:

sed -i 's|<html lang="en">|<html lang="de">|' templates/page.html.tmpl
go run . invariants | grep page-declares-its-language
  page-declares-its-language: ok, 5 file(s) of every page the build produced

Run 2026-08-23.

A correction to the evidence in the issue, because it matters to whoever
reproduces this. The demonstration there edits dist/index.html after a build
and then runs the verb. Run builds into a directory it throws away and reads
that, rather than whatever is sitting in the output directory, so the edited
file is not what the row read and the ok it printed would have appeared for
any value at all:

git show origin/main:internal/invariant/invariant.go | sed -n '1136,1143p'
	tmp, err := os.MkdirTemp("", "site-invariants-")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmp)

	out := filepath.Join(tmp, site.OutputDir)
	written, err := site.Build(root, out, io.Discard)

Run 2026-08-23. The gap the issue names is real and is what line 294 above says
it is. Only the route the issue took to show it does not reach the row.

Why the value matters and not only the attribute is the reader's cost. A page
that declares nothing leaves a screen reader guessing at a voice and a
pronunciation dictionary. A page that declares the wrong language stops it
guessing and sends it to the wrong answer, with nothing on the page saying so.

What this change does

The row now refuses a third thing, and what the site publishes in is declared
once in the table:

var publishedLanguages = []string{"en"}

The value is taken from decisions/site-language.md in Flowfin/hub, which is
cited at the declaration:

gh api repos/Flowfin/hub/contents/decisions/site-language.md --jq '.content' \
  | base64 -d | sed -n '45,49p'
`Gate: site-declares-its-language`, over `docs/`. It refuses a served page with
no `html` element, one whose `html` element declares no `lang`, and one whose
`lang` names a language the site does not publish in. The third is the one a
presence check alone would miss: a page left behind in German would announce
itself correctly and still be the wrong page.

Run 2026-08-23.

The comparison is on the primary subtag and folds case, so en-GB and EN
pass. That reading is the record's own, one paragraph further down: refusing a
tag that is more specific about the same language would push whoever writes the
next page towards the least specific tag available, which is the wrong direction
for a rule that exists so a reader's software picks the right voice.

It is a set rather than a single value, so a second published language later is
a change to what it holds rather than to the row that reads it.

The cost, stated rather than hidden. This is a second copy of an answer recorded
in another tree and nothing compares the two, because no leg of the gate reaches
the network. The day the record moves is a day this line is wrong and no run
says so. That is written at the declaration so the next reader meets it there.

The near miss

The template declaring a language the site does not publish in, at c615810:

sed -i 's|<html lang="en">|<html lang="de">|' templates/page.html.tmpl
go run . invariants
  page-declares-its-language: REFUSED, 5 violation(s)
    it refuses a produced page with no html element, one whose lang attribute is missing or empty, or one whose lang names a language this site does not publish in
    because a page with no language is read aloud in whichever one the reader's software guessed, and the guess is wrong for exactly the readers who depend on it; a page declaring a language this site does not publish in stops the guessing and sends the same reader to the wrong answer with nothing on the page to signal it
    dist/index.html: the html element declares lang="de", and this site publishes in en
    dist/privacy/index.html: the html element declares lang="de", and this site publishes in en
    dist/legal/index.html: the html element declares lang="de", and this site publishes in en
    dist/design-system.html: the html element declares lang="de", and this site publishes in en
    dist/404.html: the html element declares lang="de", and this site publishes in en

Run 2026-08-23. The template was restored afterwards and is unchanged in this
branch:

git diff --name-only origin/main...HEAD
internal/invariant/invariant.go
internal/invariant/invariant_test.go

That the guard bites, for the reason it names

The new arm was deleted and the suite run. Three cases went red and named the
value that walked through:

--- FAIL: TestTheLanguageRowRefusesALanguageTheSiteDoesNotPublishIn (0.00s)
    invariant_test.go:733: the row reported 0 violation(s) for a page declaring de: []
--- FAIL: TestALanguageTheSiteDoesNotPublishInRedsExactlyOneRow (0.00s)
    invariant_test.go:782: a page declaring de refused [], want only page-declares-its-language
--- FAIL: TestRunRefusesATemplateDeclaringALanguageTheSiteDoesNotPublishIn (1.64s)
    invariant_test.go:1024: Run accepted a template declaring a language this site does not publish in:

The neighbour stayed green through that deletion, which is what says the three
above move on the arm rather than on the fixture. The arm was restored and the
suite passes:

go test ./internal/invariant/ -count=1
ok  	github.com/Flowfin/site/internal/invariant	85.352s

Four cases are added. One refuses de and requires the message to carry the
value and to say what the site publishes in. One walks the declared set and
leaves the tag itself, a region of it, and the tag in capitals alone, spelled
out of the set rather than typed, so a set that later grows does not leave a
case passing against a language nobody publishes in. One requires the de page
to red this row and no other, and then requires the same page carrying the
published tag to move nothing in either direction. One puts the value in the
template and reads the whole run, which is where both produced pages are named
rather than the one somebody happened to open.

What this does not reach

Whether the words on a page are in the language it declares is a judgement no
reading of the bytes makes. This refuses a wrong declaration and never a wrong
page, and a page whose prose drifted into another language while the attribute
stayed correct is refused by nothing here.

docs/near-miss.md quotes the row's earlier it refuses and because lines
inside a transcript of run 31316531489 at ccb2d5e. That transcript is a record
of what that run printed and is left as it stands, so the wording there no
longer matches the wording the row prints today. Nothing in this tree reads that
file:

grep -rn 'near-miss' --include='*.go' internal/ main.go ; echo "exit=$?"
exit=1

Run 2026-08-23 at c615810.

Means

Go, in the package that already holds the table, because the rule is one row
beside thirty-seven others and the suite that proves a row bites already exists
there. Anything else would be a second apparatus for one comparison.

The gate

Run at c615810, the commit this branch pushes:

go run . ci
gate: 7 legs, in order: format, vet, test, build, links, sitemap, 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, 54 file(s)
  vet: ok
  test: ok, 26 test file(s)
  build: ok, 8 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  sitemap: every page the build wrote is listed once, and every entry has a page behind it
  invariants: ok, 38 rule(s) decided, 1 owed and not decided
7 of 7 legs ran. None was skipped.

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

No second reader

Nobody but me has read this change. The evidence above stands in place of a
second reader rather than alongside one: the deletion that reddens the cases,
the run over the whole tree with the value in the template, and the gate at the
commit being pushed.

…lish in [#186]

The row read whether the lang attribute was there and held anything, so a
page announcing a language this site does not publish in walked through it.
That is the sharper of the two failures: a page declaring nothing leaves a
screen reader guessing, and a page declaring the wrong language stops it
guessing and sends it to the wrong answer with nothing to signal that.

What the site publishes in is now declared once, in the invariant table, with
the record it was taken from cited beside it. The comparison is on the primary
subtag and folds case, so en-GB and EN pass, which is the record's own reading.

The guard was proved by deleting the new arm and running the suite, which
reddened the three cases written for it and left the neighbour green.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added the tests label Aug 23, 2026
@iderex iderex self-assigned this Aug 23, 2026
@iderex
iderex merged commit 65353c6 into main Aug 23, 2026
17 checks passed
@iderex
iderex deleted the tests/a-page-declaring-a-language-the-site-does-not-publish-in branch August 23, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refuse a produced page that declares a language the site does not publish in

1 participant