Skip to content

Document the publishing workflow and the migration record - #49

Merged
ptr727 merged 5 commits into
developfrom
document-workflow-and-migration-record
Aug 8, 2026
Merged

Document the publishing workflow and the migration record#49
ptr727 merged 5 commits into
developfrom
document-workflow-and-migration-record

Conversation

@ptr727

Copy link
Copy Markdown
Owner

What

Two new README sections for a human reader, and the repo-side records that make a rebuild possible without asking anyone where the inputs live.

  • README.md gains Migration from WordPress, a short pointer to the post, and How a Change Reaches the Site, a high-level walk from a branch to production with mermaid diagrams for the pipeline, the branch model, and the loop that runs while the site serves. WORKFLOW.md stays the tooling contract and is not duplicated.
  • OPERATIONS.md gains The Migration Record, naming the post as the artifact to cite before touching checks/ or deploy/maps/, and Rebuilding from the Exports, recording what the provenance capture holds and which parts of it can be fetched again.
  • CAPTURE_ROOT joins the other machine-specific values in deploy/env.example, because the capture's location was recoverable only from an agent's memory. checks/README.md points at it beside build-redirects.py, which consumes it.
  • The migration post gains a step 0 making the same point for a reader running their own migration: keep the captured inputs in one directory outside the site repo, and record where it is.

Corrections

Log Review described log retention as unconfigured, which the edge access log has made false. It now states what each tier can answer, without naming a retention window or a size limit. Both belong to the host and can change without this file knowing.

Absolute numbers

Counts that describe volatile state, or another system's configuration, are replaced by what they represent. The counts that survive describe the legacy contract, which content additions do not move, since the parity gate reports a new post as additional URLs built (not a failure) rather than growing a list. checks/README.md and deploy/README.md now say that outright rather than leaving a reader to infer it from a number that looks wrong after the next post.

Verification

  • hugo --gc --minify --panicOnWarning builds clean.
  • checks/check-url-parity.py public passes: 328/328 render, 778/778 legacy media, 1049/1049 assets, orphans at the recorded constant.
  • Released to the local production mirror and verified against a running server, since this touches content/: PASS - 1245 URLs honored, rules confirmed live from the deployed release id.
  • markdownlint and cspell clean. All three mermaid diagrams render through mermaid-cli.

🤖 Generated with Claude Code

The README gains two sections for a human reader: a short pointer to the
migration post, and a high-level walk of how a change reaches the site,
with mermaid diagrams for the pipeline, the branch model, and the loop
that runs while the site serves. WORKFLOW.md remains the tooling
contract and is not duplicated.
OPERATIONS.md gains The Migration Record, naming the post as the artifact
to cite before touching checks/ or deploy/maps/, and Rebuilding from the
Exports, which records what the provenance capture holds and which parts
of it can be fetched again. The capture path was recoverable only from
memory, so CAPTURE_ROOT joins the other machine-specific values in
secrets/, with checks/README.md pointing at it beside the tool that
consumes it. The migration post gains a step 0 making the same point for
a reader running their own migration.
Log Review is corrected: the edge access log exists, so the section
describing retention as unconfigured was false. It now states what each
tier can answer without naming a retention window or a size limit, both
of which belong to the host and can change without this file knowing.
Absolute counts that describe volatile state are replaced by what they
represent. The counts that survive describe the legacy contract, which
content additions do not move, and checks/README.md and deploy/README.md
now say so rather than leaving a reader to infer it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 8, 2026 02:01

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the repository’s human-facing documentation to better explain the WordPress→Hugo migration context, the publish pipeline from branch to production, and the provenance needed to rebuild the redirect maps and other migration-derived artifacts without relying on tribal knowledge.

Changes:

  • Add new README sections documenting the WordPress migration context and a high-level “change to production” workflow (with Mermaid diagrams).
  • Expand OPERATIONS.md with a “Migration Record” and “Rebuilding from the Exports” section to centralize provenance and rebuild guidance.
  • Update ancillary docs/config examples to emphasize stable concepts over volatile counts and to record CAPTURE_ROOT as rebuild provenance.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
TODO.mdReplaces volatile migration counts with durable statements; expands production cutover checklist details.
README.mdAdds “Migration from WordPress” and “How a Change Reaches the Site” sections, including Mermaid diagrams and new references.
OPERATIONS.mdAdds a migration artifact reference section and documents rebuild provenance, plus refines log/retention documentation.
deploy/README.mdClarifies contract invariants vs. content growth and removes dependence on fixed URL counts in prose.
deploy/env.exampleDocuments CAPTURE_ROOT as a machine-specific provenance input for regenerating redirect maps.
content/posts/2026/08/01/moving-this-blog-from-wordpress-to-hugo.mdAdds an explicit “step 0” about where to store capture inputs and why.
checks/README.mdClarifies what counts represent (contract vs. site) and points readers to CAPTURE_ROOT provenance documentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadREADME.md
Comment threadREADME.md Outdated
Comment threadchecks/README.md Outdated
The strict-build wording used a clipped absolute construction in both the
diagram label and the prose beneath it, and the orphan table's cause and
effect ran together in one comma-spliced cell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 8, 2026 02:05

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

OPERATIONS.md:228

  • Traefik access-log JSON uses RequestURI for the full request (path + query); RequestPath is path-only. As written, this line claims RequestPath includes the query string, which would be false for /?p=<id> traffic unless you’re actually reading RequestURI (or otherwise logging the query).
**On the VPS the reviewable record is Traefik's access log**, at `/var/log/traefik/access.log`, one JSON object per line, one line per request, across every hostname the host serves. `RequestPath` carries the query string, so the legacy `/?p=<id>` traffic is visible as itself. Request headers are dropped except `Referer` and `User-Agent`, which is what keeps the Pangolin resource access token out of a file that is retained and copied, and query strings are logged in full, so treat an extract as sensitive.

TODO.md:32

  • comms/vps-agent.md is referenced as if it were in the repository, but comms/ is gitignored and the file isn’t present in-tree. As a result, this pointer is a dead link for anyone reading TODO.md on GitHub unless you explicitly note that it must be pulled from /srv/agent-comms/ (per OPERATIONS.md).
- **Deploy production once, to a name that is not the live one.** The host side is done and the interim name is live: `blog.insanegenius.net` answers `200` unauthenticated, on a Let's Encrypt certificate issued 2026-08-07, serving `0000-placeholder`. The VPS agent calls this M7a, and M7b is the `.com` cutover. Three items belong to this repo before the first production deploy, all detailed in `comms/vps-agent.md` §19 and §20:

TODO.md cited the channel file by its working-copy path, which is
gitignored, so the reference read as an in-tree file that a reader on
GitHub cannot open. Name the section that carries the pull instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 8, 2026 02:10
@ptr727

Copy link
Copy Markdown
OwnerAuthor

Working the two suppressed comments from the review on 8c6a676, since a resolved-thread count of zero says nothing about those. One is accepted, one is declined with evidence.

Accepted: TODO.md cited a gitignored path

Correct, and it is a defect I should have caught. /comms/ is gitignored, so comms/vps-agent.md read as an in-tree file that nobody browsing the repository can open. Fixed in e5f8f27 by naming OPERATIONS.md "The Channel Between the Two Sides", which carries the rsync that produces the working copy.

Declined: RequestPath does carry the query string here

The comment states that Traefik logs the path only and that RequestURI carries path plus query. Neither half holds on this host, and the claim in OPERATIONS.md is the one that survives a test.

There is no RequestURI field in the log at all. The fields present are:

ClientAddr, ClientHost, ClientPort, ClientUsername, DownstreamContentSize,
DownstreamStatus, Duration, GzipRatio, OriginContentSize, OriginDuration,
OriginStatus, Overhead, RequestAddr, RequestContentSize, RequestCount,
RequestHost, RequestMethod, RequestPath, RequestPort, RequestProtocol,
RequestScheme, RetryAttempts, StartLocal, StartUTC, entryPointName, level, msg

Absence is weak evidence on its own, so I made a request carrying a query string, under a user agent chosen so the probe could be isolated from anything else in the log, and read back only that line:

$ curl -s -o /dev/null -A "blog-agent-fieldtest/1.0" "https://<host>/?p=70"
200
# the matching log line, projected to three fields
/?p=70 <host> 200

RequestPath holds /?p=70. The sentence stands, and the property it exists to support, that legacy /?p=<id> traffic is identifiable in the log rather than collapsing into /, is the whole reason the field matters for the outward log pass.

Worth stating plainly: this is a constructed probe rather than an observation of real traffic, which is why it is quotable here and why anyone can re-run it.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

TODO.md:11

  • Grammar: “Every migrated post and page carried” is missing a verb, so it reads ungrammatically in the status summary.
| Content and media | done. Every migrated post and page carried, and the media hash-verified against the export tar |

checks/README.md:44

  • The note “including one the list does not hold” is grammatically awkward/unclear in a table cell; consider rephrasing to make it clear the matcher accepts dates not present in the contract list.
| Date archives | 83 | Redirect to `/all/`, since **Hugo has no built-in year or month archive**. The matcher covers any date, including one the list does not hold |

deploy/README.md:271

  • The phrase “the matcher covers a date the list does not” is unclear in this table row; it reads like a single specific date rather than the general case (any date).
| `@date_archive` | 83 | `/YYYY/`, `/YYYY/MM/`, and their pagination -> `/all/`, and the matcher covers a date the list does not |

The status row elided its verb, and the two date-archive rows described
the matcher's coverage in a phrase that reads as one specific date rather
than the general case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 8, 2026 02:14
@ptr727

Copy link
Copy Markdown
OwnerAuthor

The review on e5f8f27 raised three suppressed comments and no threads. All three are accepted and fixed in 9780a3c.

WhereWasNow
TODO.md status row"Every migrated post and page carried""Carries every migrated post and page"
checks/README.md date archives"The matcher covers any date, including one the list does not hold""The matcher accepts any date, including dates absent from the list"
deploy/README.md@date_archive"the matcher covers a date the list does not""The matcher accepts any date, including dates absent from the list"

The last two are the same sentence in two files, and the objection is right about both: the singular read as one specific date rather than the general case, which inverts the point. That point is load-bearing here, because it is the reason a new post needs no entry in redirect-urls.txt and therefore the reason the counts on those pages do not move when content is added.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

OPERATIONS.md:199

  • This sentence is inaccurate: the URL contract isn’t “checked against the crawl”; the parity gate verifies the committed URL lists against the build output (and redirects via a live server). The point here seems to be that anything not captured into the contract can’t be detected by the gates, so rephrase to avoid implying the checks re-crawl the site.
**Real traffic is the only source that finds what every check here is blind to.** The URL contract proves the URLs someone thought to list and the redirects derived from the export. It cannot know about a URL nobody recorded, because the crawl that produced the lists is the same crawl the lists are checked against. A visitor following a sixteen-year-old link is the one reader who tests that.

deploy/env.example:66

  • The comments here say CAPTURE_ROOT should live only in secrets/.env (not in every env file), but this file is the template that gets copied to create per-environment env files. As written, it encourages copying CAPTURE_ROOT everywhere while simultaneously saying not to; clarify the guidance so the template and the comment don’t contradict each other.
# Environment-independent, so it belongs in secrets/.env alone rather than in every file.
# Nothing sources this value, so it is recorded to keep a rebuild from depending on memory.

The Log Review section said the lists are checked against the crawl that
produced them. The gates read the built site and the running server, so
the circularity is that the lists are their own standard, not that a
crawl is re-run. State it that way.
The CAPTURE_ROOT comment told a reader to keep the value in one file
while sitting in the template that is copied to make the others, so it
now says to drop it from the per-environment copies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 8, 2026 02:18
@ptr727

Copy link
Copy Markdown
OwnerAuthor

The review on 9780a3c raised two suppressed comments and no threads. Both accepted, fixed in cd9d176.

OPERATIONS.md "Log Review" described what the gates read, wrongly. The sentence said the lists are checked against the same crawl that produced them. They are not: check-url-parity.py reads the built site and check-live-urls.sh reads a running server, and neither touches the capture. The circularity the paragraph exists to state is real, but it is that the lists are their own standard, so an address the crawl never recorded is absent from every gate that reads them. That is now what it says, and it matters because it is the reason the log review is a separate mechanism rather than a redundant one.

This was pre-existing prose that I edited in this PR, which is what brought it into scope.

deploy/env.example contradicted itself. The CAPTURE_ROOT comment said the value belongs in secrets/.env alone rather than in every file, while sitting in the template whose own header says to copy it to secrets/<environment>.env. Following both instructions is impossible. It now reads:

# Environment-independent, unlike every value above, so keep it in secrets/.env and drop it from any per-environment copy of this template.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit ffb6d5e into developAug 8, 2026
5 checks passed
@ptr727
ptr727 deleted the document-workflow-and-migration-record branch August 9, 2026 00:10
Sign up for freeto 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.

2 participants

@ptr727