Skip to content

Adopt halos-docs-tools and the shared translation gate - #49

Open
mairas wants to merge 3 commits into
mainfrom
feat/shared-translation-workflow
Open

Adopt halos-docs-tools and the shared translation gate#49
mairas wants to merge 3 commits into
mainfrom
feat/shared-translation-workflow

Conversation

@mairas

@mairas mairas commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The six checkers under scripts/ were copied between four documentation repositories, and this repository was the one they were written in. They now come from halos-org/docs-tools pinned at v0.1.0, and the workflow is the caller stanza for the shared reusable workflow in halos-org/shared-workflows.

The behaviour change is that the run now goes red where the version it replaces always stayed green: it fails when any translation is stale, missing, unstamped or orphaned. It does not yet block a merge — this repository has no branch protection and no ruleset, so the red check sits next to an enabled merge button. Making it required is a separate decision, and it carries a real cost: the gate judges the whole repository, so an English-only edit cannot go green without its translations. See the review synthesis on this PR. It still posts the report as a pull request comment first, so a red run always carries its own explanation.

Verified locally on this branch before pushing: mkdocs build --strict clean, check-anchors site reports all anchors resolving, translation-status --check exits 0 with all nine locales at 20 current pages, and check-glossary, check-typography and map-anchors all run against this repository's own glossaries under solutions/translation/.

Two of those glossaries told the translator to register their locale in the checker's GLOSSARIES dict before the glossary check could run. The packaged check-glossary already carries all nine locales — confirmed by running it and nb here — so those instructions are deleted rather than repointed.

The caller deliberately carries no paths filter. A required check that never runs on a pull request touching none of the filtered paths leaves that pull request unmergeable forever.

Fly-by in its own commit, called out per policy: CLAUDE.md's structure section still described a flat docs/ tree from before the locale directories existed, so every path in it was wrong. Corrected, and the checkers added to the command list.

Part of halos-org/halos#147.

mairas and others added 2 commits August 13, 2026 17:43
The six checkers move out of scripts/ and into the halos-docs-tools
package, pinned at v0.1.0. The workflow shrinks to the caller stanza for
halos-org/shared-workflows, which runs the same commands and, unlike the
advisory version it replaces, fails the run when a translation is stale,
missing, unstamped or orphaned.

One commit rather than a chain: the old workflow invokes the scripts by
path, so deleting them and repointing CI cannot be separated without an
intermediate commit whose CI is broken.

Two glossaries told the translator to register their locale in the
checker's GLOSSARIES dict first. The packaged check-glossary already
carries all nine, verified by running it for it and nb here, so those
instructions are removed rather than repointed.

The caller carries no paths filter. A required check that never runs on
a pull request touching none of the filtered paths leaves that pull
request unmergeable forever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The structure section still described a flat docs/ tree from before the
locale directories existed, so every path in it was wrong. The build
commands never mentioned the translation checkers at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Translation status

da — 20 current
de — 20 current
es — 20 current
fi — 20 current
fr — 20 current
it — 20 current
nb — 20 current
nl — 20 current
sv — 20 current

Every translation of the pages in scope is current.

The documented pre-flight could not reproduce the gate. The Verifying
block ran `translation-status`, which always exits 0; CI runs
`translation-status --check`. A translator following the skill got a
clean local run and a red check -- the same false green this migration
exists to remove. Verified on sh-rpi, where the two disagree today:
plain exits 0, --check exits 1.

deploy.yml holds pages: write and id-token: write and ran plain
`uv sync`, while the gate runs `uv sync --locked`. Now that a git
dependency is in the graph, bumping the pin without re-locking made the
two disagree: the gate refuses, the deploy resolves the new ref live and
runs its build backend with the strongest token in the repository.

Deleting the obsolete "register your locale in GLOSSARIES" instructions
removed the only pointer to where that registration happens. The
registries are still hardcoded, in the package, so a tenth locale needs
an entry in each, a release, and a pin bump. The skill says so now.

Also: the caller stanza states what the called workflow enforces, since
the failing command lives in a repository this one does not contain;
the Verifying block said four and listed five; CLAUDE.md described
map-anchors as rewriting when that form only reports, claimed CI runs
all six commands when it runs three, and omitted `unstamped` from the
state list; the French, German and Swedish glossaries omitted the
check-glossary step the other five carry; and the language-selector
note named two repositories and a count of three where four carry the
block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mairas

mairas commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Code review — 7 personas

Scope: main..feat/shared-translation-workflow (19 files, 65 insertions, 968 deletions), plus the three sibling PRs applying the identical change.
Reviewers: correctness, testing, maintainability, project-standards (always-on); security (the caller grants pull-requests: write to a reusable workflow in another org and adds a git+https dependency), api-contract (six CLI names and a workflow ref become cross-repo contracts), adversarial (968 deletions; an advisory check becomes a merge gate in four repos).

Findings below are the merged set. Cross-reviewer agreement is noted; every claim I acted on was reproduced before acting.

P1 — High

# Where Issue Reviewers Conf Route
1 .github/workflows/translation-status.yml The gate judges the whole repository, so an English-only edit cannot go green. One typo fix in docs/en/ marks nine translations stale and exits 1; with the paths filter gone, every other open PR — including ones touching only CI — inherits the same red against the merge-with-main tree. adversarial, correctness 0.95 Open — your decision
2 .claude/skills/translate-page/SKILL.md The documented pre-flight could not reproduce the gate. The Verifying block ran translation-status, which always exits 0; CI runs translation-status --check. Reproduced on sh-rpi: plain exits 0, --check exits 1 on the same tree. testing 0.90 Fixed in 0ccafd4
3 .github/workflows/translation-status.yml Merge skew reddens main with nobody at fault. Two PRs edit different parts of one English page; both authors do the full nine-locale job and stamp against their own hash; the merged file hashes to neither, so all nine go stale. Deterministic, not probabilistic — the stamp covers the whole file. adversarial 0.78 Open — needs "require branches up to date", which needs protection

P2 — Moderate

# Where Issue Reviewers Conf Route
4 repo settings Nothing makes the check required, so "blocks" does not ship. No branch protection and no rulesets on main in any of the four repos. A red check sits next to an enabled merge button. Shipping the blocking exit status without protection buys the noise of finding 1 and none of the enforcement. project-standards, adversarial 0.95 Open — your decision. PR bodies corrected
5 shared workflow The comment says "current" on runs the gate fails. It is posted before mkdocs build --strict, before the anchor check, and it renders from a code path that never computes the exit-2 case. Reproduced: a stray docs/stray-probe.md gives --comment → "20 current" for all nine locales and --check → exit 2. This breaks the property the step order exists for. adversarial 0.85 Open — fix belongs in shared-workflows
6 solutions/translation/ Deleting the obsolete "register your locale in GLOSSARIES" instructions removed the only pointer to where registration happens. The registries are still hardcoded, now in the package. A tenth locale meets an argparse invalid choice with nothing local explaining that the fix is a docs-tools PR, a tag, and a pin bump in four repos. maintainability, testing, api-contract 0.95 Fixed in 0ccafd4
7 .github/workflows/deploy.yml The Pages deploy holds pages: write and id-token: write and ran plain uv sync while the gate runs uv sync --locked. With a git dependency in the graph, bumping the pin without re-locking made the two disagree: the gate refuses, the deploy resolves the new ref live and runs its build backend with the strongest token in the repo. security, adversarial 0.82 Fixed in 0ccafd4
8 pyproject.toml v0.1.0 is a movable tag in a repo with no protection. uv sync --locked holds — both reviewers reproduced that independently — but uv lock --upgrade re-reads the tag and adopts whatever it points at, changing only 40 hex characters in a lock fragment. That code decides merge policy in four repos. adversarial, security 0.85 Open — tag-protection ruleset on docs-tools
9 caller uses: The workflow floats on @main while the CLI it calls is pinned. A flag added on shared-workflows main reddens four repos at once, on PRs that touched nothing related, with the cause in a commit none of them references. shared-workflows has no tags, so no consumer can pin back. api-contract, maintainability, adversarial 0.95 Open
10 shared workflow check-glossary and check-typography run in no CI step. Their code and hardcoded locale registries are pinned in another repo while the data they read stays here. Adding a locale to mkdocs.yml reddens the gate immediately, while the two checkers cannot run on it until docs-tools ships a release. testing, maintainability 0.85 Open
11 caller stanza The new blocking rule was invisible in this repo. The failing command lives in a file this repo no longer contains, so git log here would never explain when the rule changed. maintainability 0.85 Fixed in 0ccafd4
12 stamp-translation Blocking creates the incentive for the one bypass the design cannot catch. A contributor blocked by a locale they cannot translate can re-stamp all 180 files in seconds; the diff is 180 one-line frontmatter changes, indistinguishable from an honest re-stamp. Proposed check: a translation whose translated_from changed must have at least one other changed line. adversarial 0.80 Open — concrete fix available
13 docs-tools Merging makes docs-tools' own AGENTS.md false — it says neither consumer pins the package and that halpi2 still runs its own copies under scripts/. A maintainer sizing a rename's blast radius reads half the real number. maintainability 0.80 Open — fix in docs-tools

P3 — Low

# Where Issue Reviewers Conf Route
14 CLAUDE.md map-anchors site <locale> documented as rewriting; that form only reports, --apply writes. The repo's own skill got it right two files away. correctness, project-standards 0.90 Fixed
15 CLAUDE.md "CI runs the same commands, so a clean local run predicts a green check" — CI runs three of the six, and the gate reads the merge-with-main tree. correctness, project-standards, maintainability 0.92 Fixed
16 CLAUDE.md State list omitted unstamped, which --check fails on like the rest. project-standards 0.78 Fixed
17 SKILL.md "All four, every time" heading a block of five. Pre-existing; this diff rewrote four of the five lines beneath it. correctness, project-standards 0.92 Fixed
18 solutions/translation/ French, German and Swedish omitted the check-glossary step the other five carry, though the package registers all nine. Pre-existing. maintainability 0.75 Fixed
19 SKILL.md The language-selector note named two repositories and a count of three; four carry the block. Pre-existing. maintainability 0.68 Fixed
20 caller The bot now comments on every PR, including ones touching no documentation — this PR is its own example. The old workflow scoped the comment to touched English pages and posted nothing otherwise. correctness, adversarial 0.90 Open — advisory
21 shared workflow GH_TOKEN is in the environment of the step whose first command is uv run translation-status --comment, resolved from the PR head's own pyproject.toml. Contained today by fork tokens being read-only; splitting generate-then-post would remove the exposure. security 0.70 Open — fix in shared-workflows
22 shared workflow The only input is runs-on. A repo that ever needs check-anchors --exclude or a non-default site_dir cannot express it without editing the workflow all four share. api-contract 0.65 Open
23 shared workflow The comment's size fallback points at the job summary, which the same wide change can also overflow (1 MiB/step). A full restructure of docs/en renders ~2.6 MB across nine locales. adversarial 0.65 Open

One the reviewers did not find

The mechanical rename mangled a path in sh-rpi: ../../scripts/check_glossary.py became ../../check-glossary, where the ../../ survived a substitution that removed the file it addressed. Fixed there. This is the failure mode I pointed all seven at, and it took a follow-up grep to surface — worth remembering that a substitution which leaves prose grammatical leaves broken paths grammatical too.

One reviewer claim I corrected

Testing reports that the anchor re-raise path has never executed. It has — run 31699012592 during the shared-workflows work, a broken anchor with the gate passing and Fail on broken anchors turning the run red. Never in these four repos, which is what the reviewer could see.

Coverage

Verified before and after the fixes, in all four repos: mkdocs build --strict clean, check-anchors site all resolving, translation-status --check exit 0 (exit 1 in sh-rpi, by design). sh-rpi's check-glossary fr/de/sv report unused terms — traceable to the same untranslated page, through a checker that runs in no CI step, so not a regression.

Untracked and out of scope: book/ and .claude/settings.local.json.

Suppressed: findings below 0.60 confidence.


Verdict: not ready to merge, pending two decisions that are yours, not mine.

Everything mechanical is fixed and pushed. What remains is a single question the reviewers converged on from three directions (findings 1, 3, 4): the gate judges the whole repository while work happens one page at a time, and nothing currently requires the check. Three ways out:

  1. Require the check and accept the coupling — every English edit ships its nine translations. Honest, and it is what the plan intended, but it makes a typo fix a nine-locale task.
  2. Rescope the gate — block on missing, unstamped and orphaned (structural, cheap to fix) and on stale only for English pages whose blob changed against the merge base. Keeps the whole-repo report.
  3. Leave it advisory — merge as-is and add no protection. Then finding 1 costs nothing, but the migration has not changed anything either.

Merging today gives option 3 by default, which is the one nobody chose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant