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
15 changes: 6 additions & 9 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,31 @@ Running backlog for this repo, kept in a committed file so the work survives acr

## State

The site is built, gated, and proven against a real server. It is not yet on GitHub and not yet serving its public address.
The site is built and gated in CI. It is on GitHub, and it is not yet serving its public address.

| Piece | State |
| --- | --- |
| Content and media | done. 514 pages, 778 media files hash-verified against the export tar |
| URL contract | done. 328 render, 917 redirect, 778 legacy image URLs, all gated |
| Deploy shape | done and proven against a running Caddy, on a local mirror |
| CI workflows | written and locally verified, never yet run on GitHub |
| GitHub repo | does not exist |
| CI workflows | green. Validation runs on every pull request and feeds the required check |
| GitHub repo | public, both rulesets active, `configure.sh check` exits 0 |
| VPS | untouched |

## Blocked on the maintainer

- Create `ptr727/Blog` as a **public** repo, since an outward-facing write needs explicit per-repo permission. Everything below the first push depends on it, and nothing local can reveal the problems that only appear once CI runs for real.
- Install the GitHub App and set `CODEGEN_APP_CLIENT_ID` and `CODEGEN_APP_PRIVATE_KEY` in **both** the Actions and Dependabot stores. The App must be installed rather than only created, and `CODEGEN_APP_ID` must stay absent because it is a `forbids` and the deprecated input silently does nothing.
- Install `shellcheck`, `shfmt`, `nodejs`, and `npm`, then `markdownlint-cli2` and `cspell`, so the lint gate can run locally instead of only in CI. Every one of them currently runs here through Docker, which works but is slower than it should be for an edit loop.
- Read the migration post before it ships, since it is written in the maintainer's voice and has not been reviewed.

## Next, in dependency order

- Push `main` and `develop`, then let the pull request workflow run **once** before any ruleset is applied. The `main` ruleset requires a check named `Check pull request workflow status job`, which binds by name and only reports after a run, so applying rulesets first deadlocks the first pull request.
- Run `repo-config/configure.sh apply ptr727/Blog release`, then `check` until it exits 0. Going public also enables Discussions, which `configure.sh` derives from visibility.
- Run `AUDIT.md` end to end and commit the result to `reports/Blog/audit.md`, recording the publish and release dimensions as deferred rather than passing.
- Dispatch `publish-release.yml` once to prove the release path, which exists but has never run.
- Decide on `merge-bot-pull-request.yml`. Dependabot is configured and its pull requests will otherwise sit open, and the App secrets it needs are now in place.
- Provision the VPS: an unprivileged `blogdeploy` user, the deploy root, and `unattended-upgrades` with automatic reboot.
- Restrict the deploy key with `restrict,command=...`, no pty and no forwarding, so it can do nothing but rsync into `releases/` and swap the symlink. Generate per-environment keys so staging cannot reach production.
- Choose the staging FQDN, add its DNS record, and expose it through Pangolin as a public resource with **no auth**, since CI's live-URL check has to reach it. Authentication defaults to on for a public resource and has to be turned off deliberately.
- Write `deploy-site.yml` and prove it: a dry run that mutates nothing, then a real run, then a forced mid-deploy failure to confirm rollback keeps the site up. Report the measured deploy shape back to [ProjectTemplate#456][hub-issue], which is waiting on it before the publish type can be defined.
- Deploy to a temporary production FQDN and validate there before touching the live record. Lower the `blog` A-record TTL to 60s a day ahead, then flip it to the VPS, unproxied.
- Read the migration post once more before it ships, since it is written in the maintainer's voice and has not been reviewed.
- Watch server logs for 404s daily for the first week, because real traffic finds what the golden list missed. Append anything new to `checks/golden-urls.txt` and add a redirect.
- 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.
- 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.
Expand Down
116 changes: 45 additions & 71 deletions reports/Blog/audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,126 +8,99 @@ Self-audit of this repository against its own committed ground truth, per [AUDIT

## Verdict

**Not operational.** Two of the three dimensions cannot be checked at all, because the repository does not exist on GitHub yet. Nothing has been pushed, so there is no remote, no ruleset, no secret, and no environment.
**Operational.** Every applicable check passes against the live repository.

This is the expected state at this point rather than a failure, and it is recorded here rather than left in a session, per [`STANDUP.md` section 5][standup], which allows a repo to stand up with residual deltas tracked in a report plus an issue. The issue is [ProjectTemplate#456][hub-issue].

Every check that can run locally passes. The value of that is limited, and stating it plainly is the point of this report: local checks pass by construction because the same person wrote the checks and the thing being checked. Only a real CI run and a real deploy test the parts that matter, and neither has happened.
The publish and release surface is **deferred**, not failed. That deferral is declared rather than hidden, and is tracked in [ProjectTemplate#456][hub-issue], which [`STANDUP.md` section 5][standup] permits.

| Dimension | Result |
| --- | --- |
| 1. Settings and rulesets | **Blocked.** No repository on GitHub |
| 2. Secrets | **Blocked.** No repository on GitHub |
| 3. The URL contract | **Pass** |
| 1. Settings and rulesets | **Pass.** `configure.sh check` exits 0 |
| 2. Secrets | **Pass.** Both required present in both stores, forbidden one absent |
| 3. The URL contract | **Pass.** Enforced by CI, not only locally |
| Baseline file presence | **Pass.** 23 of 23 |
| Verbatim fidelity | **Pass.** 4 of 4 |
| Publish and release | **Deferred**, deliberately. See below |
| Publish and release | **Deferred**, deliberately |

## 1. Settings and Rulesets

Blocked. `repo-config/configure.sh check ptr727/Blog release` cannot run against a repository that does not exist.
**Pass.**

```text
$ repo-config/configure.sh check ptr727/Blog release
... 31 assertions, all ok ...
Configuration matches on ptr727/Blog.
exit 0
```

What is verifiable locally is that the payloads are internally consistent and that the required check is bound by a name the workflow actually produces, which is the ordering trap that deadlocks a first pull request:
Both rulesets are active and carry every expected rule. `develop` allows squash only, `main` allows merge only, and both bind the required check by the same name the workflow produces:

```text
repo-config/main.json required_status_checks: ["Check pull request workflow status job"]
.github/workflows/test-pull-request.yml:26 name: Check pull request workflow status job
'develop' required checks = ["Check pull request workflow status job"]
'main' required checks = ["Check pull request workflow status job"]
```

The two strings match. They are one string renamed together, never independently.
The ordering constraint was honored at standup: the workflow was dispatched once and reported before any ruleset was applied. Applying first would have deadlocked the first pull request, because the required check binds by name and only appears after a run.

The carried `develop` payload is the `release` variant (`repo-config/develop.json`), carrying `pull_request`, `required_status_checks`, `required_linear_history`, `copilot_code_review`, `deletion`, `non_fast_forward`, and `required_signatures`. The `operational/develop.json` variant is absent, which is correct for this workflow model.
`has_discussions = true`, derived by `configure.sh` from public visibility rather than from a committed setting. `default_branch = main`. Dependabot vulnerability alerts and automated security updates are enabled.

## 2. Secrets

Blocked. `gh secret list` cannot run against a repository that does not exist.
**Pass.** Names only. No secret value was read, printed, or logged.

The manifest in `spec/secrets.json` declares:
| Name | Actions | Dependabot |
| --- | --- | --- |
| `CODEGEN_APP_CLIENT_ID` | present | present |
| `CODEGEN_APP_PRIVATE_KEY` | present | present |
| `CODEGEN_APP_ID` (forbidden) | absent | absent |

- `requires`: `CODEGEN_APP_CLIENT_ID`, `CODEGEN_APP_PRIVATE_KEY`, in **both** the Actions and Dependabot stores
- `forbids`: `CODEGEN_APP_ID`
`CODEGEN_APP_ID` is forbidden because the App-token action takes `client-id`, and the deprecated `app-id` name silently does nothing.

No secret value has been read, printed, or logged.
The `staging` and `production` environments do not exist yet, which is correct: they hold deploy credentials for a VPS that has not been provisioned, and `AUDIT.md` places them outside the baseline audit.

## 3. The URL Contract

**Pass.** This is the only dimension with real evidence behind it, because it is the only one that does not need GitHub.
**Pass, and now enforced by CI rather than only locally**, which is the material change from the pre-standup state.

```text
$ hugo --gc --minify --panicOnWarning
Pages 514 | Total in 462 ms | zero warnings
From the first run on `main`:

$ checks/check-url-parity.py public
```text
hugo v0.164.0+extended (pinned by version and sha256)
Pages 514 | Total in 858 ms (zero warnings under --panicOnWarning)
render : 328/328 golden URLs built
207 additional URLs built (not a failure)
media : 778/778 legacy image URLs resolve after the R8 rewrite
assets : 1012/1012 local asset references resolve
PASS - the built site honors the URL contract
```

Contract sizes, matching the committed lists: 328 render, 917 redirect, 778 legacy media.
Every gate in the validation job passed on its first attempt: markdownlint, cspell, actionlint, `editorconfig-checker`, shellcheck, `shfmt -d`, config validation, the Hugo build, and the contract check.

Floor assertions are present and below the real counts, so a truncated list fails rather than passing while covering nothing:

```text
checks/check-live-urls.sh:18 FLOOR=(["golden-urls.txt"]=320 ["redirect-urls.txt"]=900)
```

The 207 extra built URLs are the new post added this session plus its four new term archives and their pagination. An extra URL is reported and is not a failure. A missing one would be.

**The live gate has not run this session.** `checks/check-live-urls.sh` is what proves the 917 redirects, and a redirect is the web server's job that no build can prove. It has previously passed against the local mirror for all 1,245 URLs, but that predates this session's changes and is not re-evidenced here. Treat the redirect half of the contract as asserted rather than currently proven.
**The live redirect gate has still not run.** `checks/check-live-urls.sh` is what proves the 917 redirects, and a redirect is the web server's job that no build can prove. It has passed against the local mirror for all 1,245 URLs, but not against this build and not from CI. The redirect half of the contract remains asserted rather than currently proven, and it stays that way until the VPS exists.

## Baseline File Presence

**Pass, 23 of 23** applicable to `types: ["source-only"]` plus `workflowModel: release`.

`OPERATIONS.md` is retained although it left the required set when the workflow model changed from `operational` to `release`. It is accurate and useful, and carrying an extra file is not drift.
`OPERATIONS.md` is retained although it left the required set when the workflow model changed from `operational` to `release`. Carrying an extra file is not drift.

## Verbatim Fidelity

**Pass, 4 of 4**, compared after line-ending normalization as [`spec/fidelity-model.md`][fidelity] specifies:

```text
.markdownlint-cli2.jsonc match
repo-config/configure.sh match
repo-config/main.json match
repo-config/develop.json match
```

Eight carried files arrived CRLF and were normalized to LF to satisfy this repository's declared `lineEndings`. That is governed drift rather than a fidelity deviation, and it is reported to the hub as an onboarding trap, since nothing in the standup text says to normalize after carrying.

## Lint and Prose

Every gate the validation workflow runs was executed locally, through Docker, since the host lacks the tools:

```text
actionlint clean
editorconfig-checker clean
markdownlint-cli2 (workflow globs) 0 issues in 14 files
cspell (README.md, HISTORY.md) 0 issues
shellcheck (default severity) clean
shfmt -d clean
```

Prose rules, using the hub's `scripts/prose_lint.py` against every file this repo authors:

```text
charset, dupword, comment-wrap, comment-case 0 findings
dash, semicolon, charset-unknown 15 findings
```

The 15 remaining are backlog rules that report without gating, and the correct-as-you-next-edit rule leaves them alone until their file is touched:
**Pass, 4 of 4**, compared after line-ending normalization as [`spec/fidelity-model.md`][fidelity] specifies: `.markdownlint-cli2.jsonc`, `repo-config/configure.sh`, `repo-config/main.json`, `repo-config/develop.json`.

- `checks/README.md`, 14. Not edited this session.
- `layouts/rss.xml`, 1. **Kept deliberately.** The copyright sign is the argument to `strings.TrimPrefix "© "`, so it is a string literal that must match the character it strips, not typography. It is a functional deviation, not an unswept one.
Eight carried files arrived CRLF and were normalized to LF to satisfy this repository's declared `lineEndings`. That is governed drift rather than a fidelity deviation, and it is reported upstream as an onboarding trap, since nothing in the standup text says to normalize after carrying.

## Publish and Release: Deferred

`publish` is empty and `releaseTrigger` is `none` in the proposed registry entry, deliberately.
`publish` is empty and `releaseTrigger` is `none`, deliberately.

This repository deploys a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The hub's position, which this repo follows, is to declare the deferral rather than hide it, and to report the measured shape after CI has run and a deploy has actually happened rather than predicting it. Tracked in [ProjectTemplate#456][hub-issue].
This repository will deploy a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The measured shape will be reported to [ProjectTemplate#456][hub-issue] once CI has run a deploy, rather than predicted now. The VPS does not exist, so there is nothing to measure.

The VPS does not exist yet, so there is nothing to measure.
`publish-release.yml` exists and is dispatch-only, but has never been dispatched. The release path is therefore untested.

## Deliberate Deviations

Expand All @@ -140,10 +113,11 @@ Both are recorded in [AUDIT.md](../../AUDIT.md) and reported upstream, so neithe

Carried forward rather than closed:

- The repository does not exist on GitHub. Dimensions 1 and 2 stay unverifiable until it does, and the first CI run is the first real test of the workflows.
- The live redirect gate has not run against this session's build.
- `merge-bot-pull-request.yml` is not carried. Dependabot is configured, so its pull requests will sit open until either the merge bot is added or they are merged by hand. The bot needs the App secrets, so it cannot be proven before those exist.
- `checks/README.md` carries 14 prose backlog findings.
- The live redirect gate has not run against this build, so 917 of the 1,245 contracted URLs are asserted rather than proven.
- `publish-release.yml` has never been dispatched, so the release path is untested.
- No deploy exists, so the publish surface stays deferred and the registry entry stays `publish: []`.
- `merge-bot-pull-request.yml` is not carried. Dependabot is configured and its pull requests will sit open until either the merge bot is added or they are merged by hand. The App secrets it needs are now in place, so this is no longer blocked, only undone.
- `checks/README.md` carries a small prose backlog of `dash` and `semicolon` findings, left for the next edit of that file per the correct-as-you-next-edit rule.

<!-- Repo -->

Expand Down