d83c69c5fe152e5f010e4a27a7aa6b4b85fcd891 diff --git a/content/blog/sama-v2-git-url-refactor-postmortem.md b/content/blog/sama-v2-git-url-refactor-postmortem.md new file mode 100644 index 0000000000000000000000000000000000000000..25d032d55ac32c61de65b8e749b5eb47f89ba586 --- /dev/null +++ b/content/blog/sama-v2-git-url-refactor-postmortem.md @@ -0,0 +1,88 @@ +# The `/GIT/` URL refactor shipped — plan vs actual + +[Yesterday's plan post](/blog/sama-v2-git-url-refactor-plan) sketched the refactor that drops the redundant `:owner` segment from `/GIT/:owner/:repo/` URLs. The `/goal` fired. [PR #42](/GIT/tdd.md/commit/df9d41a) landed an hour later. This post is the promised companion postmortem: where the plan held, where it didn't, and what the merge produced that the plan couldn't predict. + +The headline first: + +> **The plan held.** 19 files changed (153 insertions, 69 deletions), one regex absorbs every old URL, 9 new helper tests, 388/388 green, 7/7 ✓ on `/sama/v2/verify` before and after the merge. + +The full scorecard: + +![Plan vs actual scorecard for the /GIT/ URL refactor](/images/git-url-postmortem-scorecard.png?v=1) + +## Where the plan held exactly + +Five clauses landed on-the-nose with no interpretation room: + +- **49 references** — the grep count in the plan was exactly the number of substitutions the merge produced. Not a single new file slipped in between plan-write and merge that the regex would have missed. +- **8 content files** — the plan listed every markdown file by name. The merge touched exactly those 8. +- **One regex** — the anti-fudge clause that forbade hand-maintained URL maps held without temptation. [`rewriteOldGitUrl`](/GIT/tdd.md/blob/main/src/b32_git_url_redirect.ts) is one regex + one template literal. Total: 5 lines of pure logic in a 13-line Layer-1 helper. +- **7/7 ✓ before, 7/7 ✓ after** — the verifier verdict was constant across the merge. The §4 check logic didn't need touching; the structural choices the refactor wanted to make were already conformant. +- **`LIVE_REPO_OWNER` stays exported** — the constant is still used by [`b51_render_repo.ts`](/GIT/tdd.md/blob/main/src/b51_render_repo.ts) for the "syntaxai/tdd.md" breadcrumb display text, by [`b51_render_commit.ts`](/GIT/tdd.md/blob/main/src/b51_render_commit.ts) for the commit-page repo link, and by [`c14_git.ts`](/GIT/tdd.md/blob/main/src/c14_git.ts) for repo identity. Removing it would have been a separate, larger refactor. + +## Where the plan slightly over-shot or under-shot + +Two clauses missed reality by a small margin — both in safe directions: + +**Helper test cases: predicted ~3, actual 9.** +The plan said *"new helper test (~3 cases) covers the redirect regex"*. Once the helper existed and the test file was open, three felt anaemic — the natural coverage was *all four URL kinds* (tree/blob/raw/commit) plus *the .diff variant* plus *three explicit non-match cases* (already-new URL, other-org URL, non-/GIT/ path) plus *the boundary case* (`/GIT/syntaxai/tdd.md` without a trailing segment). That's 9 cases. Over-coverage by 3×, no harm done — Bun ran them in 2ms. + +The interesting question this raises: why was the plan's estimate low? Because the plan was *thinking about the regex* (which has one pattern, hence ~3 cases would suffice), but the test ended up *thinking about the URL surface* (which has four kinds × two outcomes = 8 natural categories). The right unit of test coverage is the user-facing surface, not the internal pattern. A small lesson for next time's plan: estimate test coverage by surface area, not by code complexity. + +**Test file churn: plan listed 2, actual 1.** +The `/goal` listed *both* `b51_render_repo.test.ts` and `b51_render_commit.test.ts` as files needing test-string updates. The first did — one comment line referenced the old URL shape. The second turned out to have no URL strings at all; it only pins the export shape via `typeof renderCommitView === "function"`. So the plan over-listed by one file. Not a bug — defensive enumeration is cheap — but a data point for how `/goal`-authoring tends to err: over-list and let reality narrow. + +## What the plan didn't anticipate + +One genuine surprise the plan missed: + +**The bare `//` breadcrumb link inside the `/GIT/` commit page.** [`b51_render_commit.ts:103`](/GIT/tdd.md/blob/main/src/b51_render_commit.ts) emits a breadcrumb link from the commit page back to the bare-repo view at `/syntaxai/tdd.md`. This link is *inside* a `/GIT/` page but points *outside* the `/GIT/` URL surface. The plan declared "bare-repo view is out of scope" but didn't call out this specific cross-boundary link. + +The right call (made at code-edit time) was to *leave it alone* — it points at `/syntaxai/tdd.md`, which is the explicit scope-out from the `/goal`. But a stricter `/goal` would have called out cross-boundary references explicitly. **For next plan:** when scoping a refactor in/out by URL prefix, also enumerate cross-references *between* the in-scope and out-of-scope surfaces. They're the places where the seams show. + +## The pattern that emerged + +The reusable building block this refactor produced: + +``` +src/ +├── b32__url_redirect.ts ← pure transform (Layer 1) +├── b32__url_redirect.test.ts ← sibling test, all kinds + non-match +└── d21_handlers_fallback.ts ← one Layer-3 wrapper: + match → 301 Response with Location +``` + +Five lines of pure logic + ~10 lines of wrapper + sibling test. Every future URL refactor that needs "old URL form is permanent-redirect to new URL form" reaches for this shape. + +Concrete near-future candidates: + +- If `/sama/v2/example-crud` becomes `/sama/v2/examples/crud` next month: same shape. `b32_sama_examples_url_redirect.ts` + one Layer-3 wrapper. +- If `/blog/` ever gains a date prefix `/blog//`: same shape. The new slug is computed from `ALL_POSTS[slug].date`, the helper transforms old → new. +- If the SAMA spec ever needs `/sama/discipline/` instead of `/sama/`: same shape. + +The cost-per-refactor flattens: the **first** URL refactor under SAMA v2 needed a plan post, a postmortem post, ~10 source-file changes, a sed pass, and an evening of work. The **next** URL refactor needs a 5-line helper, a sibling test, one Layer-3 wrapper, a sed pass, and maybe an hour. The plan post can be shorter because the pattern is now public; the postmortem post can be skipped entirely if nothing surprises. + +## What the merge looked like in the wild + +The 301 redirect handler caught its first real legacy URL within minutes of the deploy — every internal `/GIT/syntaxai/tdd.md/...` reference that I'd typed by muscle memory during the implementation phase (yes, while editing the very same files) got rewritten by the regex before reaching the browse handler. The smoke test was satisfying: + +``` +$ curl -I https://tdd.md/GIT/syntaxai/tdd.md/blob/main/src/b32_sama_v2_verify.ts +HTTP/2 301 +location: /GIT/tdd.md/blob/main/src/b32_sama_v2_verify.ts +cache-control: public, max-age=86400 + +$ curl -L https://tdd.md/GIT/syntaxai/tdd.md/blob/main/src/b32_sama_v2_verify.ts +HTTP/2 200 +< file content > +``` + +A user clicking an old `/GIT/syntaxai/tdd.md/...` link from a Google search result, a Twitter card cache, or someone else's blog post lands on the new URL with the file content intact. Cache TTL is 86400 — search engines will refetch within a day and update their index. + +## What lands next + +The pattern-as-redirect shape is on file. The empirical chain post-baseline is intact (the n=8 cross-repo measurements still point at the same source files, just at shorter URLs). The `/sama/v2/verify` page continues to make the same structural claim it did yesterday, against the same source tree, at the same 7/7 verdict — just from URLs nine characters shorter. + +The interesting next thing is whether the second URL refactor — when it happens — confirms the cost-flattening hypothesis. If `/sama/v2/example-crud` migrates next month and the implementation takes an hour, that's the data point. If it takes another evening, the pattern wasn't as portable as it looked. Either result is informative. + +Until then: 19 files, one regex, zero regressions. The `/goal` workflow + SAMA v2 discipline + the verifier as merge gate produces this kind of boring, exactly-as-planned outcome with surprising regularity. The boring outcomes are the load-bearing ones. diff --git a/public/images/git-url-postmortem-scorecard.png b/public/images/git-url-postmortem-scorecard.png new file mode 100644 index 0000000000000000000000000000000000000000..fa160a80218d7c9f9304fea02302d48e285385ad Binary files /dev/null and b/public/images/git-url-postmortem-scorecard.png differ diff --git a/public/images/git-url-postmortem-scorecard.svg b/public/images/git-url-postmortem-scorecard.svg new file mode 100644 index 0000000000000000000000000000000000000000..dfdc83ed15c69c77ff378c7219e7cad5ef839f06 --- /dev/null +++ b/public/images/git-url-postmortem-scorecard.svg @@ -0,0 +1,105 @@ + + + + + + Plan vs actual — /GIT/ URL refactor postmortem + The plan held. 19 files, one regex, zero regressions. + PR #42 · commit df9d41a · 153 insertions, 69 deletions · 9 new helper tests · 7/7 ✓ before and after + + + + + CLAUSE + PREDICTED + ACTUAL + VERDICT + + + + + + + + source files touched + ~10 + helper + test + 11 (incl. b32 + test) + ✓ on target + + + content files rewritten + 8 (plan listed each) + 8 + ✓ exact + + + /GIT/ references absorbed + 49 + 49 + ✓ exact + + + redirect mechanism + one regex + one regex + ✓ anti-fudge held + + + Layer 1 helper size + ~5 lines + 13 lines + ✓ in band + + + helper test cases + ~3 + 9 + ~ over-covered (3×) + + + total test count + 379 → 379+ + 379 → 388 (+9) + ✓ green + + + /sama/v2/verify + 7/7 ✓ before + after + 7/7 ✓ before + after + ✓ verdict held + + + LIVE_REPO_OWNER constant + stays exported + stays exported + ✓ no scope creep + + + git protocol URLs + untouched (scope-out) + untouched + ✓ no breakage + + + alias mode (both URLs work) + forbidden — 301 only + 301 only + ✓ converges + + + unanticipated surprises + (unknown) + 1 (commit-test churn) + ~ minor scope nit + + + + + + The reusable shape: + b32_<old>_url_redirect.ts (pure transform, ~5–13 lines) + sibling test + one Layer-3 wrapper in the fallback handler + + + + https://tdd.md + diff --git a/src/a31_blog.ts b/src/a31_blog.ts index 9661bbb94312198e9a1e6f79e83cbcc126d0444b..29d3f9f852982ab07d570681de68e98a0bc14d72 100644 --- a/src/a31_blog.ts +++ b/src/a31_blog.ts @@ -12,6 +12,12 @@ export interface BlogEntry { } export const ALL_POSTS: BlogEntry[] = [ + { + slug: "sama-v2-git-url-refactor-postmortem", + title: "The /GIT/ URL refactor shipped — plan vs actual", + description: "Yesterday's /goal post planned the refactor that drops the redundant :owner segment from /GIT/:owner/:repo/ URLs. The /goal fired, PR #42 landed an hour later, here's the promised postmortem. Headline: the plan held — 19 files changed, 153 insertions, 69 deletions, one regex absorbs every old URL, 9 new helper tests, 388/388 green, 7/7 ✓ on /sama/v2/verify before and after the merge. Walks the scorecard: where the plan held exactly (49 references — exact grep match; 8 content files — exact match; one regex anti-fudge held; LIVE_REPO_OWNER kept exported as designed), where it slightly missed (helper test cases predicted ~3, actual 9 — over-coverage 3× because the natural coverage was URL-surface-by-kind not regex-by-pattern; test file churn predicted 2, actual 1 — the commit-test file had no URL strings, defensive over-listing in the /goal is cheap), and what the plan didn't anticipate (the bare // breadcrumb link INSIDE a /GIT/ commit page that points OUTSIDE the /GIT/ surface — a cross-boundary reference the /goal scope didn't enumerate explicitly; the right call at code-edit time was to leave it alone, but next plan should enumerate cross-references between in-scope and out-of-scope surfaces). The reusable shape: b32__url_redirect.ts (pure 5-line transform) + sibling test + one Layer-3 wrapper in the fallback handler. The cost flattens — first URL refactor under SAMA v2 needed a plan post + postmortem + evening of work; next one needs the helper + sed pass + maybe an hour. Concrete future candidates listed: /sama/v2/example-crud → /sama/v2/examples/crud, /blog/ → /blog//, /sama/ → /sama/discipline/. The interesting next data point: whether the SECOND URL refactor confirms cost-flattening. Either outcome is informative.", + date: "2026-05-25", + }, { slug: "sama-v2-git-url-refactor-plan", title: "Shortening /GIT/ URLs: a single-tenant URL has a redundant segment",