Ship log · iter #149

Iteration 149 ship log

2026-05-15 · push mode, 60 min cadence, MASSIVE drift sweep iter

On this pageWhat shipped (3 substantive ships - the biggest drift recovery of the campaign) Ship 1: Restored 5 regressed accessibility fixes (>1500 page-modifications) Ship 2: Patched trust-signal-injector source (iter-148 footer fix made durable) Ship 3: Fixed corrupted brand-name regex in audit-case-studies-quality.py /quality-report/ status Source-generator drift register (cumulative) Status snapshot Iter 149 throughput note Running queue (top 5 for iter 150) Cumulative iter 1-149

Date: 2026-05-15 (push mode, 60 min cadence, MASSIVE drift sweep iter)

What shipped (3 substantive ships - the biggest drift recovery of the campaign)

Iter 148 revealed a class of bug: source-generator cron runs revert surface-fix injectors. Iter 149 ran the full 50-audit sweep and found 5 major regressions affecting >1500 page-modifications worth of prior work. Restored everything + patched 1 more source generator + fixed 1 corrupted regex.

Ship 1: Restored 5 regressed accessibility fixes (>1500 page-modifications)

Ran all 50 audits to find current state vs expected. Major regressions detected:

AuditPre-149 statePost-149 fix
audit-heading-hierarchy0/248 FAIL (was 247/247 OK iter 139)248/248 OK
audit-aria-hidden-decorative0/248 FAIL (was 247/247 OK iter 140)248/248 OK
audit-form-labels3/747 FAIL (was 744/744 OK iter 133)747/747 OK
audit-favicon-coverage247/248 WARN (was 247/247 OK iter 132)248/248 OK
audit-cross-surface-name0/29 FAIL (was 29/29 OK iter 126)29/29 OK

Re-ran every injector: h1-aria-fix, heading-hierarchy-fix, aria-hidden-svg-injector, aria-label-injector, skip-link-injector, landmarks-fix, favicon-injector. Plus force-regenerated fallback-subpages to fix how-it-works brand-rename drift. Plus manual title swap for brief-ai/skeptic-memos.

Total surface re-fixes: ~2,100 page modifications (496 SVGs + 744 forms + 248 headings + 248 H1s + 248 skip-links + 248 favicons + 28 brand-renamed how-it-works + 1 manual brief-ai).

Iter 148 added role="contentinfo" to 2 pages' wd-trust sections (competitor-tracker, immigration-law-ai). Iter-149 sweep showed both were REVERTED. Same iter-148 lesson at smaller scale.

Located source generator: trust-signal-injector.py rewrites the wd-trust section block. Patched line 56 to include role="contentinfo" in the section open tag.

Result: audit-section-landmarks 246/248 → 248/248 OK. Fix is now durable across cron cycles.

Ship 3: Fixed corrupted brand-name regex in audit-case-studies-quality.py

The audit's brand_brief_name(slug) had r"^name:\\s+(.+?)\\s$" (DOUBLE backslash in raw-string) instead of r"^name:\s+(.+?)\s$". The double-backslash made the regex match LITERAL \s characters, which no brand brief contains. So brand_brief_name returned None for all slugs, and the audit flagged all 24 brand-renamed products as "title-no-product" false positives.

Audit jumped from 215/249 FAIL to 241/249 WARN after the regex fix.

This is the third audit-precision-fix in the campaign (after apostrophe regex iter 129, DECORATIVE_ICON_v1 iter 146).

/quality-report/ status

Pre-iter-149 (during drift):

Post-iter-149 (after sweep + fixes):

0 FAIL state restored. 13 consecutive iters (148-149) but with 0-FAIL recovery this iter.

Source-generator drift register (cumulative)

Generators that own buyer-touching surfaces and would revert surface-fix injectors. Sorted by patch status:

GeneratorPatched (source-fix applied)?
brand_name_helper.py (used by 9 generators)n/a - helper module
faq-template-gen.py✓ iter 125
regen-unlock-pages.py✓ iter 125
regen-adopt-pages.py✓ iter 125
regen-feedback-page.py✓ iter 125
regen-fallback-pricing.py✓ iter 125 + FORCE_REGEN
regen-vs-pages.py✓ iter 125
regen-fallback-subpages.py✓ iter 125 + FORCE_REGEN + WD_PRESERVE_CUSTOM_SUBPAGE_v1
case-studies-gen.py✓ iter 125
jsonld-injector.py✓ iter 126
og-meta-injector.py✓ iter 127 (brand + canonical)
top-utility-bar-injector.py✓ iter 148 (role=navigation + aria-label)
trust-signal-injector.py✓ iter 149 (role=contentinfo)
meta-description-injector.pyone-shot, idempotent
favicon-injector.pyone-shot, idempotent
skip-link-injector.pyone-shot, idempotent
h1-aria-fix-injector.pyone-shot, idempotent
heading-hierarchy-fix.pyone-shot, idempotent
aria-hidden-svg-injector.pyone-shot, idempotent
aria-label-injector.pyone-shot, idempotent
landmarks-fix-injector.pyone-shot, idempotent

Discovered durability gap: the one-shot injectors (h1-aria, heading-hierarchy, aria-hidden-svg, aria-label, skip-link, landmarks-fix) need their effects to be IDEMPOTENT on cron, which they are - BUT none of them are SCHEDULED to re-run. So when a source generator overwrites a page (e.g., page rebuilds for any reason), the fix is lost.

Fix going forward: either (a) schedule the injectors to run periodically as guard-rails, OR (b) patch the source generators to include those attributes by default.

Did the latter for trust-signal-injector + top-utility-bar-injector. The other one-shots (h1-aria, heading-hierarchy, aria-hidden-svg, aria-label, skip-link, landmarks-fix) still need scheduling.

Status snapshot

Iter 149 throughput note

3 substantive ships at 60-min cadence. Largest drift-recovery iter in the campaign:

The "drift sweep" is now a valuable iter pattern: every N iters, run all audits + check vs expectations to catch source-generator regressions early. Consider scheduling as recurring sanity-check.

Running queue (top 5 for iter 150)

  1. MILESTONE iter 150 - retrospective + cadence-validate refresh
  2. Schedule one-shot injectors as cron guard-rails (h1-aria, heading-hierarchy, aria-hidden-svg, aria-label, skip-link, landmarks-fix should run every ~hour to keep state)
  3. Patch source generators for the still-vulnerable one-shots where feasible
  4. Wes-task: 120 warn items
  5. Pivot to non-audit work (essays, editorial)

Cumulative iter 1-149

Pattern at iter 149: the audit suite + drift-sweep cadence catches its own regressions. Iter 150 candidate: schedule a regular drift-sweep job to make this self-healing instead of manual.

← PreviousIter #148 Next →Iter #150