Fix 553 pages previewing blank on social, and the gate that would have caught it - #516
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…e caught it layouts/partials/seo/enhanced-meta-tags.html:129 falls back to https://jetthoughts.com/assets/images/og-default.jpg when no frontmatter image resolves. That file had never been uploaded. 553 pages - almost all blog posts without cover art - unfurled blank on LinkedIn and Slack. og:image is the one asset class no crawler, link-checker, or reader ever exercises, which is why it survived unnoticed. The image is a BRAND PLATE, not a campaign card. A first draft led with "Your dev shop stopped delivering" - under a share of a Puma-config post that reads as an ad rather than an article. It now carries the wordmark, a descriptive line, and canon-only figures (Clutch 4.8/5 with NO review count, 5-year average relationship, since 2008) per .okf/content/claims-canon.md. Source SVG committed alongside: the figures live inside a binary no text ratchet can read, so keeping the source in-repo keeps them greppable, and it regenerates the jpg byte-identically (md5 ecd26681b0c591a68d1315d5d578d05c). test/unit/og_image_resolves_test.rb is the reproduction test. The existing meta_tags_test.rb:136-149 asserts the tag EXISTS and that width/height are 1200x630 - an existence-plus-config assertion that cannot see a 404. This sweeps RENDERED output and resolves every same-origin social image against the build. Two review passes materially changed the work: 1. The first version checked exactly ONE url. Its regex required property= before content=, but the theme partials emit the reverse, and most values are off-origin CDN urls. 1297 tag matches collapsed to a single File.exist? wrapped in a 1757-file glob. RED->GREEN had passed honestly, because the one path it reached WAS the defect under repair - coverage and correctness are independent. 2. The comment describing that fix was itself wrong ("skipped 480 tag pages"; measured: 0 pages missed, the content-first SECOND tag was dropped, losing one distinct image). It had already propagated into two .okf/ files before review caught it. Also dropped a CGI.unescape that was a no-op on every url in the build and could only misfire (it decodes "+" as a space, a form rule wrong for paths). Root cause of a false GREEN worth recording: no local build path passes --cleanDestinationDir (bin/hugo-build:47, test/support/hugo_helpers.rb:24-38; only .github/workflows/_hugo.yml:82 cleans), so Hugo never removes an output whose source is gone. Deleting the jpg to prove RED left the tree serving a stale copy through three attempts. The tree DID rebuild each time - the "did it rebuild?" reflex diagnoses this wrong. Known gap, documented not papered over: the theme partials guard their tags with {{ if . }} / {{ with }}, so a missing resource DROPS the og:image tag rather than dangling it. A page that loses its social image entirely is invisible to this gate. Gates: rake test:unit 280 runs / 6032 assertions / 0 failures on a cleaned build dir; RED verified by removing the asset, GREEN after restore; okf validate --strict conformant (66 warnings, unchanged). Content+test only - no themes/, layouts/, or CSS touched, so the visual suites do not apply. 4-eyes: two independent reviewer passes (Opus), no blocking findings outstanding; both non-blocking items from the second pass are fixed here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Maintain pass over what the PR itself produced, per the bundle's own rule that log.md records what changed and concepts record what is true. * build/test-gates.md - the .gitignore bullet now covers SUBdirectories of a tracked dot-dir. .stitch/*.md is tracked but .stitch/designs/ is ignored, so the og:image plate source placed there was invisible. Records the asymmetry that matters: `git add <path>` ERRORS on an ignored path while `git add -A` silently omits it, which makes explicit-path staging the safer default whenever a commit's value depends on a specific file landing. * architecture/seo-meta-tags.md - the og-plate regeneration recipe plus the md5 proving the SVG regenerates the committed jpg byte-identically. That makes it the source of record rather than an approximation: a canon change is edited in the SVG and re-rendered, never painted onto the jpg. * index.md - when two sessions verify one concept concurrently and a rebase conflicts on the `verified:` array, keep BOTH entries and take the later timestamp. Both verifications really happened; dropping one falsifies the provenance the field exists to carry. Hit today on build/test-gates.md. okf validate --strict: conformant, no warnings on any touched file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
design/site-palette.md carried the palette DECISION, but nothing carried the ORDER it ships in. A session consuming .okf/ after Phase 1a.3 would reasonably pick up 1a.4 next - and that is the wrong order. Paul re-sequenced on 2026-08-20 to run the BLOG before the site-wide chrome sprint. New workflows/site-redesign-rollout.md (type: Decision, pointing at 20.01-rollout-plan.md) distils what outlives any single phase: * Phases are ordered by whether LAYOUT MOVES, not by size. A recolour fault is a wrong colour caught in a screenshot diff and revertable alone; a spatial fault is a layout break and baselines churn wholesale. That is why Phase 1 splits into 1a/1b at the cost of one extra re-record. * No A/B test is available anywhere in this rollout (ADR-0004), so reversibility is the substitute for statistical confidence - which is what makes that extra re-record worth paying for. * Blog first: that is where the humans are, and blog pages sit at 25.2% scroll depth / 26.3s against a site average of 32.9-40.3% / 28-34s. * The cost blog-first accepts on purpose: token definitions move twice, so duplicate tokens mid-rollout are the plan working, not a mistake. * Gates that block regardless: 2.4's GSC keyword gate (a conversion gain paid for in rankings is a loss), and 2.2 coupling the course page via pages/blog-single.css. Live phase status is deliberately NOT copied here - a state snapshot in a concept rots within days; the plan doc under `resource:` owns it. All five cited paths verified to exist. Cross-linked both ways with site-palette. okf validate --strict: conformant, no warnings on the new or edited concepts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Queued for the next 1. 2. 3. 4. |
The pass queued on #516 could not run while the session sat on #518, where this bundle state did not exist. #516 has merged, so it runs now. **Two corrections to workflows/site-redesign-rollout.md, written four passes ago and both inherited from the plan doc without independent verification:** * The engagement figure. The concept repeated "25.2% scroll / 26.3s vs a 32.9-40.3% site average". Measured: one 3-day Clarity window of five, and the lowest. The windows swing 2.9x (29.89/51.13/75.11/50.91/25.56) and session-weighted across 743 bot-filtered sessions the blog sits at 44.31% / 34.97s - at or above the average it was said to trail. The low window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06->08-17: 451 sessions, 56.4% / 40.1s. Blog-first still holds on a better fact - GSC puts the blog at 77% of the site's entire Google traffic. * The course coupling. The concept repeated 20.01's "2.2 couples the course page". True of the FILE, false of the SELECTORS: course/single.html:55 renders class="single-content" with no .post-article, and all 15 styled rules in pages/blog-single.css are .post-article-prefixed. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is single-post.css, which also drives bin/generate-template-pdfs. Both failures share a cause, now named as a rule in that concept: **check phase status against GIT, not the plan table.** Phases 2.1 and 2.2 had already shipped (#487 and #494, both 2026-08-20) while the plan still listed them pending, and a status answer was given from the table. A plan records what was decided; only the tree records what shipped. **Added:** * build/test-gates.md - a skip_area mask blinds a gate STRUCTURALLY where tolerance blinds it statistically. All four blog-index screenshots mask .post-feature, which IS the feature slot, so the index content area has never been visually gated at any tolerance, and two phases shipped through that hole. Also: local gates are the merge authority while CI is unreliable, with the resulting Linux-red debt stated rather than hidden; and quote the `[snap_diff] N screenshots compared` count, since a suite that compared nothing also prints "0 failures". * workflows/analytics-access.md - /blog/ fires no scroll_depth at all (page/analytics.html:72 gates on .IsPage, false for list pages), so GA4 cannot see the blog index and Clarity is the only instrument that can. Plus the 3-day-window trap: session-weight across every window, and check whether a window straddles a deploy. * workflows/review-swarm.md - non-colliding agents can still collide with an unmerged branch, and never switch branches under a running agent (it silently changes files it is mid-read of and nothing errors). okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…519) * Phase 2 specs + pre-ship baseline: both blog phases already shipped Three artifacts from a parallel spec/measurement pass. The headline is a premise inversion that changes what Phase 2 work remains. **Phases 2.1 and 2.2 already shipped on master**, both on 2026-08-20: #487 (17:35, blog index/tags/posts restyle) and #494 (20:16, whole-blog rebuild). Verified with `git merge-base --is-ancestor e1fa540 origin/master`. The plan's Phase 2 table still lists them as pending rows; 20.03 and 20.04 are therefore specs-of-record plus residual punch-lists, not to-do lists. **The engagement number that justified blog-first does not survive recomputation.** The plan cites 25.2% scroll / 26.3s against a 32.9-40.3% site average. That is ONE 3-day Clarity window of five, and the lowest; the windows swing 2.9x (29.89 / 51.13 / 75.11 / 50.91 / 25.56%). Session-weighted over all 743 bot-filtered sessions the blog sits at 44.31% scroll / 34.97s - at or above the average it was said to trail. That window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06 -> 08-17: 451 sessions, 56.4% / 40.1s. What does hold up strategically: GSC shows the blog at 105 clicks / 28d, **77% of the entire site's Google traffic**. **The course coupling was pointed at the wrong file.** 20.01's "2.2 note" is true of the file and false of the selectors: `course/single.html:55` renders `class="single-content"` with no `.post-article`, and all 15 styled rules in `pages/blog-single.css` are `.post-article`-prefixed. Only two selectors reach course. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is `single-post.css`. Two blindnesses found and verified, both of which explain why nobody noticed the phases had shipped: * `/blog/` fires no `scroll_depth` at all - `page/analytics.html:72` gates on `.IsPage`, false for list pages. GA4 cannot see the blog index. * All four blog/index screenshots mask `.blog-post` AND `.post-feature` (`desktop_site_test.rb:34,42`, `mobile_site_test.rb:25,33`). `.post-feature` IS the feature slot. The visual gate has never covered the index's content area, at any tolerance. Gaps are recorded as gaps, not estimated: per-post scroll depth is unobtainable (Clarity per-page 0-2% contradicts its own aggregate 25.56% for the identical window, ~3x), no pre-ship GA4 scroll_depth exists (it shipped WITH the rebuild), and no conversion metric exists for the window. Open decision for Paul: the cover shipped article-bleed at 900px, not the plan's full-bleed. Recommendation is to keep 900px - a 100vw break-out risks horizontal body scroll across 624 post dirs and invalidates the `sizes="...864px"` on the LCP image. Docs only - no code, templates, or CSS touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * OKF: run the deferred pass, and correct two of my own claims The pass queued on #516 could not run while the session sat on #518, where this bundle state did not exist. #516 has merged, so it runs now. **Two corrections to workflows/site-redesign-rollout.md, written four passes ago and both inherited from the plan doc without independent verification:** * The engagement figure. The concept repeated "25.2% scroll / 26.3s vs a 32.9-40.3% site average". Measured: one 3-day Clarity window of five, and the lowest. The windows swing 2.9x (29.89/51.13/75.11/50.91/25.56) and session-weighted across 743 bot-filtered sessions the blog sits at 44.31% / 34.97s - at or above the average it was said to trail. The low window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06->08-17: 451 sessions, 56.4% / 40.1s. Blog-first still holds on a better fact - GSC puts the blog at 77% of the site's entire Google traffic. * The course coupling. The concept repeated 20.01's "2.2 couples the course page". True of the FILE, false of the SELECTORS: course/single.html:55 renders class="single-content" with no .post-article, and all 15 styled rules in pages/blog-single.css are .post-article-prefixed. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is single-post.css, which also drives bin/generate-template-pdfs. Both failures share a cause, now named as a rule in that concept: **check phase status against GIT, not the plan table.** Phases 2.1 and 2.2 had already shipped (#487 and #494, both 2026-08-20) while the plan still listed them pending, and a status answer was given from the table. A plan records what was decided; only the tree records what shipped. **Added:** * build/test-gates.md - a skip_area mask blinds a gate STRUCTURALLY where tolerance blinds it statistically. All four blog-index screenshots mask .post-feature, which IS the feature slot, so the index content area has never been visually gated at any tolerance, and two phases shipped through that hole. Also: local gates are the merge authority while CI is unreliable, with the resulting Linux-red debt stated rather than hidden; and quote the `[snap_diff] N screenshots compared` count, since a suite that compared nothing also prints "0 failures". * workflows/analytics-access.md - /blog/ fires no scroll_depth at all (page/analytics.html:72 gates on .IsPage, false for list pages), so GA4 cannot see the blog index and Clarity is the only instrument that can. Plus the 3-day-window trap: session-weight across every window, and check whether a window straddles a deploy. * workflows/review-swarm.md - non-colliding agents can still collide with an unmerged branch, and never switch branches under a running agent (it silently changes files it is mid-read of and nothing errors). okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 8 Codex findings; one had already reached the OKF bundle Every finding verified against the tree before acting. All eight valid. **P1 - the one that got published.** `.okf/workflows/analytics-access.md` claimed Clarity's per-page numbers "contradict its own aggregate for an identical window and page-set" (~9% vs 25.56%, ~3x). Not the same page-set: ~9% was session-weighted over the TOP TEN pages, 25.56% covers EVERY /blog/ page. The omitted long tail can account for the entire gap. No disagreement was demonstrated and per-post analysis was never ruled out - it needs the full page rows. Corrected in the concept AND in 40.01, kept as a worked near-miss because the shape recurs: the API returns a top-N subset by default and the aggregate on request, so comparing them is the most available mistake to make. The section's own rule is "state the denominator". **P1 - alias inventory would have broken live CSS.** 20.03 omitted blog-list.css:78 and named vibe-code-rescue.css, which has ZERO var(--rr-*) references. Verified inventory now in the spec: blog-list.css (9 lines), single-post.css:434-491 (6), blog-single.css (3). single-post.css carries CTA and tag colour/background declarations reaching the course bundle, so deleting the aliases in 1a.4 on the old list would have broken blog AND course. **P1 - headline baseline was contaminated.** Deploy time now confirmed: #487 at 17:35 and #494 at 20:16 on 2026-08-20, both inside the 08-18->20 window. The 34.97s/743-session headline is superseded by the clean 08-06->08-17 window (451 sessions, 56.4% / 40.1s), with the 12-vs-28-day length mismatch recorded as a follow-on rather than papered over. **P1 - Linux baselines.** Codex is right that CLAUDE.md:148 requires both legs before a PR. That is knowingly overridden (Paul 2026-08-21, CI unreliable). The override and its cost - master's Linux job red until one batched dispatch - are now stated in the spec, with an explicit instruction to do the dispatch before merge if CI is healthy when the phase runs. **P2 fixes:** the analytics gate cannot use `eq .Section "blog"` for tag pages (hugo.toml:37-41 rewrites the term PERMALINK; the taxonomy is `tag = "tags"`, so .Section is `tags`) - needs an explicit term/taxonomy predicate; inline !important H1 styles remain at themes/beaver/layouts/list.html:51,70 and a stylesheet rule cannot override them; the three !importants in blog-single.css must NOT be probed for removal - 20.02:69-81 records that they fight legacy heading-margin rules, not the retired anchor rule, and removing them restores a title-alignment regression; R4 marked done and linked to 40.01. Gates: okf validate --strict conformant, no warnings on edited concepts; bin/hugo-build clean. Docs + bundle only, no code or CSS touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * OKF: lift the Hugo permalink trap out of the spec, and correct a shipped deprecation The Codex round fixed the 2608 specs. One finding was a durable CODE fact left in a project doc, where nobody doing template work would find it. * architecture/hugo-site.md — **a permalink rewrite does NOT change .Section or .Kind.** The taxonomy is `tag = "tags"`; only [permalinks.term] rewrites the URL. A page served at /blog/tags/rails/ still has .Section == "tags", so every `eq .Section "blog"` condition MISSES tag pages while reading as though it covers them — the URL says blog, the page object does not. The proposed analytics gate was written exactly this way and would have shipped instrumentation that skipped the pages it named. * architecture/blog-list-page.md — the same drift in a second form. That concept already records index and tag templates drifting apart in MARKUP, fixed with shared partials. Unifying markup did not unify PREDICATES: a .Section guard added anywhere still covers one and skips the other. Also records the inline !important H1 styles still at list.html:51,70. * design/site-palette.md — two corrections. --color-primary no longer "dies in Phase 1a.2"; it is GONE as of #518, and the seven surviving matches in the CSS are comments recording what each rule replaced — a loose grep reads them as survival. And the --rr-* alias deprecation was missing from the concept entirely: it now names the three live consumers and the rule that matters, **verify by grep at deletion time, never against a written inventory.** That inventory was wrong twice in one review, and single-post.css belongs to the COURSE bundle, so deleting the aliases early breaks blog and course together. okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Close the cover deviation: keep 900px article-bleed Decided under Paul's standing "do not wait for me" grant rather than parked. 20.01's Phase 2 table says "full-bleed"; the implementation shipped article-bleed at 900px. Keeping 900px: * A 100vw break-out inside a centred column is the classic source of horizontal body scroll, which CLAUDE.md forbids outright - across 624 post directories at once. * The sizes attribute (single.html:99, :114) is written for an 864px render box. Full-bleed makes every one of those wrong, so this is not a CSS-only change but CSS plus a srcset/sizes revision on the LCP image. * The stated purpose - one confident visual in the fold, wider than the prose - is already met at 900px against a 680px measure. Reversible in one max-width plus a sizes revision, which is why it did not need a person. The TABLE wording should be corrected to "article-bleed", not the code chased to match the table. bin/hugo-build clean. Docs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 7 second-round Codex findings **P1 - R2 still directed an executor to probe the !important removal.** I fixed the S4a discussion last round and missed the residual that repeats it. Deleted, with the reason inline: those declarations fight LEGACY HEADING-MARGIN rules, not the retired anchor rule (20.02:69-81), and removing them restores the title-alignment regression. R2 is now comment hygiene only. **Superseded engagement figure was still the stated rationale in BOTH specs.** 25.2% / 26.3s / 219 is one 3-day window of five, the lowest, straddling the 08-20 deploy; clean pre-ship is 56.4% / 40.1s over 451 sessions, and GSC is 105 blog clicks, not 145. "Visitors leave in the first quarter" is unsupported and is now retracted in both specs. What survives is better: those 105 clicks are 77% of the site's entire Google traffic. **Contaminated window was still named as the active comparator.** The headline was struck last round but the surrounding prose still quoted 743 sessions / 44.31%. Replaced with a table that makes the clean 451-session figures primary and secondary and marks the 743 numbers as audit-only. **The disproven population claim survived in a second place.** Gap 1 still said per-page and aggregate cover "the same window and page-set" and concluded protocol step 3 cannot run. Corrected: top-ten vs all-pages, so step 3 remains EXECUTABLE and the open task is retrieving all rows. Second time this round a fix landed in one location and missed its duplicate; swept for every corrected claim before committing this time. **The 4.2x bot-gap multiplier is withdrawn.** GA4 Organic Search includes Bing and DDG; the GSC figure is Google only. Not equivalent populations, so the multiplier is overstated. analytics-access.md:122-125 prescribes the correct comparison and it was not run. What stands without it: Direct is 8,598 sessions, 91% of the total, which is not plausible human direct navigation. **The "site-redesign-rollout.md does not exist" claims are closed in all three places.** It exists and governs both specs; it read as absent only because the specs were drafted from a worktree on an unmerged branch predating it. Recorded generalisably: a missing-file conclusion from inside a worktree is a branch question first. **"blog-list.css is their last consumer" corrected** - it is one of three, and that summary contradicted the verified inventory later in the same file. Also fixed the line list (nine lines, not the six claimed, and 203 not 204). bin/hugo-build clean. Docs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 9 third-round Codex findings; sweep the corrected metrics everywhere No P1s this round. Three findings were defects in the OKF bundle itself. **My own concept contradicted itself.** site-redesign-rollout.md quoted ~145 GSC clicks at line 65 and the corrected 105 at line 79, and said "live phase status lives in the plan doc" a few lines after establishing that the plan went stale and status must come from git. Both fixed: neither document is a status source, and the figure is now 105 throughout. **The mask warning was overstated.** The masks hide the listing ROWS and the FEATURE SLOT, not "the entire content area" - lead, filters, CTA and pagination stay covered - and the post template has 24 dedicated baselines, so Phase 2.2 was never unguarded. Narrowed to what is true: Phase 2.1's rows and feature slot went unseen. **The GA4 scroll claim was too absolute.** Only the CUSTOM 25/50/75/90 milestones are lost to the .IsPage gate; if enhanced measurement is on, the built-in `scroll` (90%) still fires. Not verified either way here, so the concept now says so rather than asserting GA4 sees nothing - discarding a usable signal because a doc overstated a gap is its own error. **Merge time is not deploy time.** The baseline treated #487/#494 merge timestamps as proof the window was contaminated. GitHub Pages publishes on a separate run that can lag or fail. Downgraded to CONTAMINATED-PENDING- CONFIRMATION with the restore condition stated. **Arithmetic:** 12 days to 28 needs 16 more, not 12 - "four more 3-day pulls" reaches 24. Corrected in both places it appeared. **Tag pages do not share all of 2.1.** They have no feature slot (it lives behind a first-page guard in blog/list.html), so verifying them against the full scope list returns a false negative. **Scope recount:** seven edits across four files, not six across three - 3.7 was added in review and the summary never caught up, which would let an executor skip the taxonomy cleanup. **Baseline churn was describing already-shipped work.** Marked historical; the residual work in 4b requires ZERO visual delta, and a moved baseline there is a regression to investigate, not one to accept. **Swept the corrected metrics through the canonical summaries** - the project README and 20.01 itself both still presented 25.2%/26.3s as current. A cold session reads those first. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…conditional throughout **P1: -2h was the wrong conversion, and no conversion could have been right.** Git records this repo's commits at +03:00 while the shell reports +0200 - 86c2c91 committed at 02:11:35+03:00 = 23:11:35Z, so a stamp of 23:20Z inside it claimed to postdate its own containing commit. But the deeper problem is that the original local times were never MEASURED; I wrote round numbers. No offset recovers truth from an invented value. So the stamps are no longer converted at all - each is anchored to a VERIFIABLE event: the commit in which it landed. seo-meta-tags and ci-gates take 2026-08-20T22:27:35Z (#516). hugo-site, blog-list-page, site-palette and test-gates take 23:11:35Z (#519). test-gates' verified entries split correctly - `git show 8fa4149:.okf/build/test-gates.md` shows which two existed at #516, so those carry 22:27:35Z and the later one 23:11:35Z, preserving order. The three concepts edited in this change carry a measured `date -u` value. Every stamp is now defensible by a command anyone can re-run. **P2: the conditional state stopped one level short.** analytics-access and the top of 40.01 said pending-confirmation while the labels downstream still read "contaminated", "CLEAN", and "straddles the 08-20 ship" as fact - so a reader following the summaries would discard a possibly-valid window regardless. Six downstream labels made conditional, and the held-out row now states the restore condition rather than being struck through as though settled. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
**Third time collapsing distinct events, now fixed with recovered times.** The two opus-5 verified entries in test-gates.md originated in separate commits - 67bdd78 (2026-08-21T00:43:35+03:00 = 21:43:35Z) and 6c4b7ab (2026-08-20T23:47:30+02:00 = 21:47:30Z) - and I had assigned both the #516 squash time, erasing their order for the second time in this branch. Both are now their own commit's time, recoverable by anyone with `git log -1 --format=%cI`. Worth stating plainly: I wrote the rule "convert with the originating offset or mark unknown, never overwrite history with now" and then violated it in the same patch, twice. The pull toward a single tidy value is strong precisely because it LOOKS like consistency. **The conditional sweep reached one more file.** 20.03 still said the window "straddles the 2026-08-20 deploy" and called 08-06→17 clean, so a cold session reading the 2.1 spec would discard a possibly-valid window as settled fact. Now provisional with the restore condition. Deliberately NOT changed: `.okf/log.md` still records what was believed at the time. That file's stated contract is "records what changed, not what is true" - rewriting dated history to match current belief would make it useless as an audit trail. The concepts carry current truth; the log carries the sequence. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…state integrity fixes (#522) * OKF: lift what three review rounds taught into concepts #519 took three Codex rounds (8, 7, 9 findings, none cosmetic). The individual fixes shipped with that PR. What belongs in the bundle is why a docs-only change needed three passes. **workflows/review-swarm.md — a correction is an edit, and it can break what the file already held.** Round three's findings were largely defects that rounds one and two INTRODUCED. Adding "phase status comes from git" left a sentence four lines away still routing to the plan doc. Adding a corrected click figure left the superseded one earlier in the same file. The correcting mindset asks "am I right here now" and does not look sideways at the invariants the document already carried — so after correcting a claim, re-read the WHOLE file, not the paragraph. Two companions to it: * **Sweep a corrected metric through the canonical summaries.** A figure was fixed in two specs and a measurement record while the project README and the plan's own justification still presented the superseded value as current — and those are what a cold session reads FIRST. A number lives in more places than the document that owns it. * **Budget more than one review round for docs.** Prose has no compiler and no test; the only gate is a reader checking claims against the tree. Across three rounds: a residual that would have reintroduced a known regression, an alias inventory that would have broken live CSS, an arithmetic error in a measurement plan, an overstated bot multiplier. One CLEAN round is the signal to stop; one round is not. **workflows/analytics-access.md — cut a measurement window on the DEPLOY, not the MERGE.** The first version of that correction used #487/#494 merge timestamps as proof a Clarity window was contaminated. A merge is not a release: Pages publishes from a separate workflow run that can lag, fail, or be re-run. This cuts both ways — it can condemn a usable window as easily as bless a contaminated one. Read the deployment record; failing that, mark the window contaminated-pending-confirmation with the restore condition written down. okf validate --strict conformant; bin/hugo-build clean. Bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix both Codex findings, including 14 future-dated timestamps **Every OKF timestamp written today was ~2h in the future.** I stamped LOCAL time with a `Z` suffix: actual UTC was 2026-08-20T23:18 while the session clock showed 2026-08-21T01:18 (+0200). Fourteen stamps across eight concepts. That is not cosmetic in this bundle specifically: `.okf/index.md` resolves concurrent-edit conflicts by taking the LATER timestamp, so a future-dated stamp silently outranks a genuinely newer edit from a parallel session. All fourteen corrected to real UTC, and the rule is now recorded next to the conflict rule it undermines: take the value from `date -u`, never compose it from the displayed date. **The deploy conclusion was still asserted as fact in the same paragraph that documents it as unconfirmed.** The added rule says cut the window on the deploy and admits only merge timestamps were read; four lines above, the prose still stated the low window contained the ship. Now conditional, with the restore condition explicit: if the deploy landed after 08-20, that window is clean and its 292 sessions come back. Worth naming: that second defect is precisely what the rule added one commit earlier describes — "a correction is an edit, and it can break what the file already held." I committed it inside the rule about it, which is the strongest evidence for the rule that could exist. okf validate --strict conformant; bin/hugo-build clean. Bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Finish both integrity fixes properly: measured timestamps, deploy state swept Both findings were my own fixes left half-done. **Timestamps: the sweep caught one class and missed its sibling.** I grepped `2026-08-21T*` and corrected those, but stamps written EARLIER in the session were also local-time-with-a-Z and already carried an 08-20 date (23:45, 23:50), so the grep never saw them - and they sat ahead of HEAD. More importantly the converted values were never MEASURED. I derived them by subtracting two hours from times I had invented, and as review noted the originating offsets are not all +0200 (commit 8fa4149 carries +0300). Inventing more precise-looking numbers would repeat the original error. So every stamp this session authored is normalised to one MEASURED value from `date -u` (2026-08-20T23:28:00Z), and the log records that per-edit precision was not recoverable. Monotonic, never future-dated, no invented precision. Other sessions' stamps (the sonnet-5 entry at 2026-08-20T00:00Z) are untouched. **Deploy state: I made the concept conditional and left every summary that feeds it asserting the opposite.** 40.01 said "the deploy time is now confirmed", the README and 20.01 called the window clean, and both phase specs plus site-redesign-rollout repeated it - while analytics-access said no deployment record had been read. Seven locations swept to provisional/pending-confirmation with the restore condition stated: if the deploy landed after 08-20, the 292 excluded sessions come back. That is exactly the rule committed two commits ago - "sweep a corrected metric through the canonical summaries, not just its home" - violated in the next change. Recorded rather than quietly fixed, because the pattern is the point: correcting the owning document feels like completing the correction, and it is not. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Convert the timestamps instead of flattening them; correct the sweep count Two fixes, both to my own repair. **Flattening destroyed what `verified:` exists for.** Normalising every affected stamp to the sweep time fixed the future-dating and turned three distinct verification events in test-gates.md into three identical entries, losing their order. The field records a HISTORY of checks; overwriting it with "now" falsely dates past verifications to the correction run and undermines the very conflict rule the sweep was protecting. Converted properly instead. This session ran at +0200 (verified: local 01:18 == UTC 23:18), so each stamp it wrote as local-with-Z converts by -2h and the distinct values come back: test-gates keeps 21:45 / 22:00 / 22:50 in order, seo-meta-tags 21:50, hugo-site / blog-list-page / site-palette 23:20, ci-gates 22:00, rollout 22:30. Only the three concepts actually edited in this change carry the measured 23:28. The rule now says convert with the offset the stamp was written at, or mark it unknown - never overwrite history with now. **The count was wrong.** "Fourteen across eight" was itself an unmeasured assertion in durable guidance. Measured: 18 timestamp literals across 9 concepts (11 .okf/ files touched, less index.md and log.md, which are reserved rather than concepts). Corrected. okf validate --strict conformant; no stamp ahead of the verified clock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Anchor every timestamp to a verifiable commit; make the deploy state conditional throughout **P1: -2h was the wrong conversion, and no conversion could have been right.** Git records this repo's commits at +03:00 while the shell reports +0200 - 86c2c91 committed at 02:11:35+03:00 = 23:11:35Z, so a stamp of 23:20Z inside it claimed to postdate its own containing commit. But the deeper problem is that the original local times were never MEASURED; I wrote round numbers. No offset recovers truth from an invented value. So the stamps are no longer converted at all - each is anchored to a VERIFIABLE event: the commit in which it landed. seo-meta-tags and ci-gates take 2026-08-20T22:27:35Z (#516). hugo-site, blog-list-page, site-palette and test-gates take 23:11:35Z (#519). test-gates' verified entries split correctly - `git show 8fa4149:.okf/build/test-gates.md` shows which two existed at #516, so those carry 22:27:35Z and the later one 23:11:35Z, preserving order. The three concepts edited in this change carry a measured `date -u` value. Every stamp is now defensible by a command anyone can re-run. **P2: the conditional state stopped one level short.** analytics-access and the top of 40.01 said pending-confirmation while the labels downstream still read "contaminated", "CLEAN", and "straddles the 08-20 ship" as fact - so a reader following the summaries would discard a possibly-valid window regardless. Six downstream labels made conditional, and the held-out row now states the restore condition rather than being struck through as though settled. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Restore distinct provenance and finish the conditional sweep **Third time collapsing distinct events, now fixed with recovered times.** The two opus-5 verified entries in test-gates.md originated in separate commits - 67bdd78 (2026-08-21T00:43:35+03:00 = 21:43:35Z) and 6c4b7ab (2026-08-20T23:47:30+02:00 = 21:47:30Z) - and I had assigned both the #516 squash time, erasing their order for the second time in this branch. Both are now their own commit's time, recoverable by anyone with `git log -1 --format=%cI`. Worth stating plainly: I wrote the rule "convert with the originating offset or mark unknown, never overwrite history with now" and then violated it in the same patch, twice. The pull toward a single tidy value is strong precisely because it LOOKS like consistency. **The conditional sweep reached one more file.** 20.03 still said the window "straddles the 2026-08-20 deploy" and called 08-06→17 clean, so a cold session reading the 2.1 spec would discard a possibly-valid window as settled fact. Now provisional with the restore condition. Deliberately NOT changed: `.okf/log.md` still records what was believed at the time. That file's stated contract is "records what changed, not what is true" - rewriting dated history to match current belief would make it useless as an audit trail. The concepts carry current truth; the log carries the sequence. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The defect
layouts/partials/seo/enhanced-meta-tags.html:129falls back tohttps://jetthoughts.com/assets/images/og-default.jpgwhen no frontmatter imageresolves. That file had never been uploaded. 553 pages — almost all blog
posts without cover art — unfurled blank on LinkedIn and Slack.
og:imageis the one asset class no crawler, link-checker, or reader everexercises, which is why it survived unnoticed.
The image is a brand plate, not a campaign card
A first draft led with "Your dev shop stopped delivering." Under a share of a
Puma-config post, that reads as an ad rather than an article. It now carries the
wordmark, a descriptive line, and canon-only figures per
.okf/content/claims-canon.md— Clutch 4.8/5 with no review count, 5-yearaverage relationship, since 2008.
The source SVG is committed alongside it. The figures live inside a binary no
text ratchet can read, so keeping the source in-repo keeps them greppable — and
it regenerates the jpg byte-identically (
md5 ecd26681b0c591a68d1315d5d578d05c).It sits at
.stitch/og-default.svg, not.stitch/designs/, which is gitignored.The test, and two review passes that changed it
test/unit/og_image_resolves_test.rbsweeps RENDERED output and resolves everysame-origin social image against the build. The existing
meta_tags_test.rb:136-149asserts the tag EXISTS and that width/height are1200×630 — an existence-plus-config assertion that cannot see a 404.
Two independent reviewer passes materially changed the work:
1. The first version checked exactly ONE url. Its regex required
property=before
content=, but the theme partials emit the reverse, and most values areoff-origin CDN urls. 1297 tag matches collapsed to a single
File.exist?wrapped in a 1757-file glob.
2. The comment describing that fix was itself wrong — "skipped 480 tag
pages"; measured, 0 pages were missed. The content-first second tag was
dropped, losing one distinct image. The bad number had already propagated into
two
.okf/files before review caught it.<meta>tags matchedCGI.unescapepassesAlso dropped a
CGI.unescapethat was a no-op on every url in the build andcould only misfire (it decodes
+as a space, a form rule wrong for paths).A false GREEN worth recording
Deleting the jpg to prove RED left the test green through three attempts.
Root cause: no local build path passes
--cleanDestinationDir(
bin/hugo-build:47,test/support/hugo_helpers.rb:24-38; only.github/workflows/_hugo.yml:82cleans), so Hugo never removes an output whosesource is gone. The tree did rebuild each time — the "did it rebuild?" reflex
diagnoses this wrong.
bin/build-if-staleis not at fault; it detectsdeletions correctly by probing directories.
Known gap, documented not papered over
The theme partials guard their tags with
{{ if . }}/{{ with }}, so amissing resource drops the
og:imagetag rather than dangling it. A pagethat loses its social image entirely is invisible to this gate. Recorded in
architecture/seo-meta-tags.mdandbuild/test-gates.md.Gates
rake test:unit— 280 runs, 6034 assertions, 0 failures on a cleaned build dir, post-rebasebin/hugo-build— clean, 1175 pagesokf validate --strict— conformantthemes/,layouts/, or CSS touched, so the visual suites do not applyAlso corrects
architecture/seo-meta-tags.md, whoseresource:pointed atthemes/beaver/layouts/partials/seo/enhanced-meta-tags.html— a path that doesnot exist (that directory holds only schema partials).
🤖 Generated with Claude Code