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
6 changes: 3 additions & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
# Repo-specific below: this repo's own additions layered on the fleet baseline above.

# Media is 566 MB of images, video, and archives across two trees.
# `binary` suppresses normalization plus textual diff and merge, so a `git log -p` or a
# bad merge never renders a 4 MB PNG as text, and text=auto's own heuristic is never
# consulted for these paths regardless of what it would guess.
# `binary` suppresses normalization plus textual diff and merge.
# A `git log -p` or a bad merge never renders a 4 MB PNG as text.
# The text=auto heuristic itself is never consulted for these paths regardless of what it would guess.
# Extensions present are png, jpg, gif, webp, mov, zip, and ico, with nothing textual.
static/media/** binary
static/external/** binary
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/merge-bot-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ jobs:
secrets:
CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }}
CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}
# This repo has no tracker outside the built-in codegen/upstream-version pairs, and keeps the
# repository-wide branch auto-delete off with no bot-branch exception, so no `with:` block is needed.
# This repo has no tracker outside the built-in codegen/upstream-version pairs.
# It also keeps the repository-wide branch auto-delete off with no bot-branch exception.
# No `with:` block is needed.
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The site is built, gated in CI, and deployed to staging by pipeline. It is not y
- **This reverses a deliberate decision, so the comment above the job has to change with the code.** It currently reads "Staging deploys from any ref, since proving a branch before it merges is what staging is for", which is the argument for the present behavior and would be left contradicting the gate. Whoever makes the change states the new reason instead: staging is the rehearsal for what `develop` will promote, so a deploy from a feature branch proves something that is not what ships. Requested by the maintainer 2026-08-09, after a staging deploy was dispatched from `main` here.
- Keep the existing comparison style, against the full `github.ref` rather than `ref_name`, for the reason already recorded there: a tag and a branch can share a short name, so a tag called `develop` would satisfy a `ref_name` test while pointing at an arbitrary commit. The new arm inherits that hazard exactly and is not a special case.
- Add the weekly non-blocking external-link-check workflow, which is the one gate that cannot be blocking because it fails on other people's outages.
- **[`checks/README.md`](./checks/README.md) names one of the two gates in its own directory.** It is written as the URL contract's document, titled `URL Parity Gate`, and `check-env-docs.py` appears in it nowhere. It carries its reasoning in its own docstring, which is where the detail belongs, but [#66][pr-66] made a directory's README the authority on that directory and this one currently describes a subset of it without saying so. The small fix is a section naming what else lives there and what it answers; the alternative is to state in the README that it is scoped to the contract alone. Either resolves it, and the present state is the one that reads as complete while not being.
- **[`checks/README.md`](./checks/README.md) names one of the two gates in its own directory.** It is written as the URL contract's document, titled `URL Parity Gate`, and `check-env-docs.py` appears in it nowhere. It carries its reasoning in its own docstring, which is where the detail belongs, but [#66][pr-66] made a directory's README the authority on that directory and this one currently describes a subset of it without saying so. The small fix is a section naming what else lives there and what it answers. The alternative is to state in the README that it is scoped to the contract alone. Either resolves it, and the present state is the one that reads as complete while not being.
- Decommission WordPress.com only after **30 clean days**, and downgrade to free rather than deleting, which keeps the media reachable as a safety net and preserves the ability to re-export. Do not start sooner: the conversion fetched media over HTTP from the live site.

## Recurring operations
Expand All @@ -78,7 +78,7 @@ The site is built, gated in CI, and deployed to staging by pipeline. It is not y

## Owed to the hub

**The one open item here closed itself.** [ProjectTemplate#633][issue-633] reported that the hub's `repo_gate.py --check eol` compares `.gitattributes` and `.editorconfig` to each other and never to the tree, citing two shapes this repo's own `checks/check-eol-pins.py` had caught. Rather than porting this repo's script, the hub built the equivalent directly into `repo_gate.py` as `eol-coverage` (`unpinned` plus `dead`), closing #633. This repo has since retired `check-eol-pins.py` and switched `.gitattributes` to the fleet-standard `text=auto eol=lf`, which structurally removes the bug class the script existed to catch (a tracked file with no pin keeping whatever encoding it arrived with, only possible when git stays passive by default) rather than merely fixing an instance of it.
**The one open item here closed itself.** [ProjectTemplate#633][issue-633] reported that the hub's `repo_gate.py --check eol` compares `.gitattributes` and `.editorconfig` to each other and never to the tree, citing two shapes this repo's own check-eol-pins.py had caught. Rather than porting this repo's script, the hub built the equivalent directly into `repo_gate.py` as `eol-coverage` (`unpinned` plus `dead`), closing #633. This repo has since retired check-eol-pins.py and switched `.gitattributes` to the fleet-standard `text=auto eol=lf`, which structurally removes the bug class the script existed to catch (a tracked file with no pin keeping whatever encoding it arrived with, only possible when git stays passive by default) rather than merely fixing an instance of it.

The spec update this repo owed the hub has landed: [ProjectTemplate#560][hub-type-pr] authored the `hugo` type, the `self-hosted` target, the `deploy-ssh` mechanism, guarantees D4.6 and D5.6, and a reference leaf pair, all measured from what this repo actually runs rather than from the prediction the intake carried. [#456][hub-issue] and [#558][hub-spec-issue] are closed with it.

Expand Down Expand Up @@ -124,7 +124,7 @@ Three findings are open at the hub, recorded here rather than only in the issues
| [#550][issue-550] | Nothing detects a repo missing from the registry, which is how this repo stayed invisible. Three other repos are still absent. |
| [#552][issue-552] | The audit flags any carried `AGENTS.md` naming the template repo, and the byte-locked `Fleet Bootstrap` section names it. Carrying the canonical correctly cannot pass, and it is the one finding the current run cannot clear. |
| [#597][issue-597] | Filed from here, after a mandatory pre-merge gate in `OPERATIONS.md` was skipped on [#40][pr-40]. The ruling is that a verification a runner cannot perform needs a declared destination, not a better per-repo pointer. Answered by [#598][pr-598], which this repo owes work against once it is ground truth. |
| [#633][issue-633] | Filed from here, 2026-08-09, closed on the hub's side via `eol-coverage` (see **Owed to the hub**). Resolved rather than open; kept in this table as the record of what was filed and why. |
| [#633][issue-633] | Filed from here, 2026-08-09, closed on the hub's side via `eol-coverage` (see **Owed to the hub**). Resolved rather than open, kept in this table as the record of what was filed and why. |

**What [#598][pr-598] will ask of this repo, once it is ground truth.** It is merged to the hub's `develop` and not to `main`, so it binds nothing yet, per the trap below about reading `main` as ground truth. It makes `Local Verification` a sixth declared `OPERATIONS.md` heading, leading the file as the only pre-merge one. This repo's `OPERATIONS.md` carries **13 level-two headings and matches none of the five declared today**, so the work is a rename and reorder rather than new prose: `Local Verification Before a Pull Request` becomes `Local Verification`, and `Backup and Restore` is one word from the declared `Backup and Recovery`. Both are near-misses rather than absences, which is the shape a heading check will mostly find in a repo that wrote its operational document before the spec declared headings. Do not start until the hub promotes it.

Expand Down Expand Up @@ -171,7 +171,7 @@ Each of these was hit or nearly hit, and each is cheap to re-trip.
- **A Copilot review hides findings in the review body, where the thread API cannot see them.** The `reviewThreads` query returns line threads only, so a review carrying `Suppressed comments (N)` in a `<details>` block reports zero unresolved while real findings sit unread. Read the review body itself, not just the threads, before calling a review loop finished.
- **`gh pr merge --delete-branch` on a `develop -> main` promotion deletes `develop`.** Use a plain `gh pr merge --merge`.
- **A review that exists on the head is not a finished review.** On [#68][pr-68] `pr_review.py wait` returned after one second with `review_on_head=yes unresolved=0 suppressed=0`, an independent thread poll agreed at zero, and the pull request was reported green. Copilot posted a third finding *after* both reads, and the maintainer found it. The re-request had matched a review still being written, so every query was correct and every query was early. Wait and re-sweep all three surfaces, line threads, issue-level comments and the review bodies, before calling a loop finished. A very short `waited=` is the tell.
- **A rule naming a target that does not exist reads as coverage.** `.gitattributes` pinned `deploy/blog-deploy-shell` and `deploy/authorized_keys`, neither ever tracked in this repo, and the comment above them claimed the extensionless-shebang case was handled. It was not, and the one real instance sat unpinned twenty lines up, invisible precisely because the file looked thorough. `checks/check-eol-pins.py` gated this file in both directions until this repo retired it and the pin list it audited, per **Owed to the hub**. The generalisation is not gated anywhere: a config that names a path, a job, or a host that is absent will read as protection for as long as nobody checks.
- **A rule naming a target that does not exist reads as coverage.** `.gitattributes` pinned `deploy/blog-deploy-shell` and `deploy/authorized_keys`, neither ever tracked in this repo, and the comment above them claimed the extensionless-shebang case was handled. It was not, and the one real instance sat unpinned twenty lines up, invisible precisely because the file looked thorough. check-eol-pins.py gated this file in both directions until this repo retired it and the pin list it audited, per **Owed to the hub**. The generalisation is not gated anywhere: a config that names a path, a job, or a host that is absent will read as protection for as long as nobody checks.
- **A gate is only as good as the matcher underneath it, and a wrong matcher fails quietly.** `check-eol-pins.py` shipped with two defects in one function, `git ls-files` pathspec not sharing gitattributes glob semantics and then a leading `/` being stripped before anchoring was decided. Both were found by review rather than by the gate, and each would have made it report a confident wrong answer. [#70][pr-70] fixed them, and the fixed version checked its own matching against `git check-attr` on every run for as long as the script existed, which is the general move: where a check predicts what a tool will do, compare it against the tool rather than against the documentation.

## Reference
Expand Down