Date: 2026-05-13 (depth mode, durable source fixes)
Two substantive source-level fixes that eliminate categories of bugs at their root, not just at their visible surface.
Audit finding: Iter 61's enhanced sweep caught 615 entity em-dashes catalog-wide. The sweep is durable, but if SOURCE generators keep emitting entities, the cron is in a constant pendulum between dirty and clean. Found 24 entity em-dash instances across 12 generator files in /home/ubuntu/factory/director/.
Two distinct patterns:
Pattern A: null/empty table placeholders (10 generators)
more-like-this-injector.py, regen-adopt-pages.py, regen-archetypes.py, regen-captures-admin.py, regen-categories.py, regen-compare-page.py, regen-operator-inbox.py, regen-operator-partnership.py, regen-pricing-page.py, regen-unlock-pages.py— as the "empty cell" marker, e.g., fmt_money(0) returning "—", or <td class="no">—</td> for "feature not available in this tier"— with - (single hyphen). Visually identical in table cells, but stable across the sweep cron.Pattern B: prose em-dashes (2 generators)
regen-catalog-v2.py had 2 instances in body copy ("a head start — brand, landing page, GTM plan, financial model — but")regen-feedback-page.py had 2 instances in body copy ("the harsh ones — especially the harsh ones")Verification: After deploying all 12 fixed generators and re-running them:
em-dash-sweep: 0 files, 0 dashes stripped, 0.6s - sweep is now idempotent (no more pendulum)health-check: 68/68 passingAudit finding: Iter 53's noted bug ("dispatch-ai tagline = name") had grown unchecked. Catalog-wide grep found:
tagline.lower() == name.lower() (the literal product name as a tagline)name.lower() == slug.lower() (the slug used as the display name, e.g., "demand-gen-ai" instead of "Demand Gen AI")These propagated to: catalog page, dossier teasers, social share text, sitemap meta, every product index page.
Step 1 - Immediate record patches (44 of 48 from existing data sources):
For broken names (16 records): pulled the proper title-case from extract_new_name(slug) (matches "## New name\nX" pattern in brand briefs that have rebrands) or fell back to title-case + AI/SaaS/MVP normalization. Examples:
demand-gen-ai -> Demand Gen AIaiops-ai -> Aiops AImsp-ai -> Msp AIFor broken taglines (1 fixable from brand brief): rental-ai's brand brief had Tagline: Your rental portfolio runs on autopilot. - pulled and applied.
For broken taglines (27 fixable from dossier teasers): for each broken product, extracted the first sentence (20-160 chars) of the elevator pitch in /srv/sites/factory/dossiers/SLUG/teaser.md. The elevator pitch is already hand-written operator voice, so the resulting taglines are real (not synthesized).
For the final 4 stubborn ones (clinic-ai, cashflow-ai, pitch-ai, tenant-ai): widened the search to all paragraphs of the elevator pitch (was limited to first paragraph), allowed slightly longer sentences (160 chars), all 4 resolved.
Final immediate state: 0 broken taglines, 0 broken names.
Step 2 - Source fix in adoptability-score.py:
The bug was in two functions:
extract_tagline(brand_text, slug):
## Tagline heading format. Brand briefs using Tagline: inline or no tagline at all silently fell back to slug.replace("-", " ").title() - which is the tagline-equals-name bug source.Tagline:, Tagline:, dossier elevator pitch first sentence), and changed the last-resort fallback from "slug-as-tagline" to [Tagline missing for X] marker so future bugs are visible.tagline_to_name(slug, brand_text):
name: frontmatter, but did NOT check that the name was different from the slug. If a brand brief said name: demand-gen-ai, the code returned that as the display name, propagating the bug.name.lower() != slug.lower() before using the brand brief value. Falls back to title-cased slug with AI/SaaS/MVP normalization.Verification: After deploying the patched scorer and regenerating adoptability.json:
The fix is durable. Future regenerations of adoptability.json will produce clean records.
Dependent regenerations:
/home/ubuntu/factory/director/more-like-this-injector.py (entity null replaced)/home/ubuntu/factory/director/regen-adopt-pages.py (entity null replaced)/home/ubuntu/factory/director/regen-archetypes.py (entity null replaced)/home/ubuntu/factory/director/regen-captures-admin.py (entity null replaced)/home/ubuntu/factory/director/regen-catalog-v2.py (prose entities replaced)/home/ubuntu/factory/director/regen-categories.py (entity null replaced)/home/ubuntu/factory/director/regen-compare-page.py (entity null replaced)/home/ubuntu/factory/director/regen-feedback-page.py (prose entities replaced)/home/ubuntu/factory/director/regen-operator-inbox.py (entity null replaced)/home/ubuntu/factory/director/regen-operator-partnership.py (entity null replaced)/home/ubuntu/factory/director/regen-pricing-page.py (entity null replaced)/home/ubuntu/factory/director/regen-unlock-pages.py (entity null replaced)/home/ubuntu/factory/director/adoptability-score.py (extract_tagline and tagline_to_name patched with 3 new extraction patterns + slug-equals-name validation)/srv/sites/factory/adoptability.json (244 products, all with real names + taglines)/srv/sites/factory/catalog/index.html/srv/sites/factory/archetypes/index.html + 6 archetype pages/srv/sites/factory/categories/index.html + 19 category pages/srv/sites/factory/compare/index.html/srv/sites/factory/operator-partnership/index.htmlBoth bugs had the same shape: visible surface symptom + cron pendulum if not fixed at source. The em-dash entity fix and the adoptability tagline fix both required:
The fixes are similar enough in shape that doing both in one iter is faster than doing them in two separate iters (less context-switching cost). Both are now durable.
· entity - same systemic issue as —, may be present in places that should render as a different separatorRecommended: option 4 (/factory/adoptability/ depth pass). It's the fifth credibility leg explaining HOW the scoring works. Buyers who care about quality will read it.
The Factory has shifted from "content fixed at the surface" to "source generators produce clean content by default". That's a structural shift, not a polish shift. Future iters can build new features knowing the foundations are stable.