Skip to content

feat: phase AP-d — the authoring pipeline: stores, validator, writer, render, worker - #665

Merged
genwave-radio merged 6 commits into
mainfrom
feat/ap-d-authoring
Sep 3, 2026
Merged

genwave-radio merged 6 commits into
mainfrom
feat/ap-d-authoring

Conversation

@genwave-radio

@genwave-radio genwave-radio commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Phase AP-d of the Ads & the plugin seam epic (gh-#380, SPEC F159–F161) — stacked on #664 (merge order: #662#663#664 → this).

✍️ What's in it

  • T398 (7e23f07 + db/43) — the ad-spot lifecycle store: F159.2's seven transitions as state-conditional UPDATEs, FOR UPDATE SKIP LOCKED claims, the two narrated invariants turned into CHECKs (which immediately caught a real bug), the ratified brand-cap semantic pinned, 40 live-Postgres facts.
  • T399 (9a5a488) — the validator: text-true duration math, letter-gated leet folding with run variants, a precision-biased ~195-entry brand blocklist + curated near-miss-spellings section (data, never edit-distance), the 555 rule, the Everyone-posture profanity floor with its ad-copy-only scope pinned by a repo sweep. Three rounds; eleven mutants killed.
  • T400 (34fb9cf) — the writer: structure-first 15/30/60s prompts, one honest re-ask naming the violated rule, skip-only; line-aware hygiene (whole-blob hygiene had collapsed every spot to one line — caught by the reviewer's live repro); the completion plumbing extracted to ONE shared seam so the copy-drift class can't recur.
  • T401 (e76f342) — the render: the assembler widened to a VoiceSpec cast with a per-request ceiling probed on the final bed-included artifact; the authored tail made structurally orphan-proof (insert INELIGIBLE → confirm → flip — a crash leaves an inert row, never an airable orphan or a manufactured dead-file); the first production Ads→Tts edge. Three rounds; twelve findings, three of them silent production outages.
  • T402 (2e1ac86) — the worker: repair → retire → refill → render off the air clock, break-window cancel-in-flight with resume, the guardian's grace pinned from both sides, the interleaving repair sized on observation cadence (the grace-only window was proven dead code at production defaults), operator intent respected past one window. Smoked with live llama3.2 + kokoro: real model drafts failing the real validator honestly; one brief became a ready+eligible row unattended.

✅ Gates

Thirteen opus review rounds across five tasks; ~35 findings, every kill revert-verified. Full solution green throughout (the known RealPostgresProbe docker flake passes in isolation).

🖐️ For Dean

  • The brand-cap ruling you ratified is pinned in the store facts (SPEC F159.1 rider updated).
  • Phase AP-e (the Ads page + ad packs) builds next, stacked on this.

…thing deleted

IAdSpotStore/AdSpotRepository carry F159.2's seven transitions as state-conditional
UPDATEs — retired is terminal by unreachability, ready requires media_id, every move
stamps state_changed_at, and the worker's claim is FOR UPDATE SKIP LOCKED race-proof
under concurrent claimants. db/43 turns the two narrated invariants into CHECKs
(ready ⇒ media_id; fail_reason iff failed — the biconditional caught a real bug:
retry wasn't clearing the reason) plus the one (state, state_changed_at) index all
three query shapes share; fresh-init mirrored in db/06, idempotency proven from
every lineage on scratch 16.4. IAdBriefStore pins the ratified brand cap: owner and
pack briefs upsert in place on their one shared key, never duplicate. The db/22
role boundary makes the SPEC's "one transaction" impossible — the as-built seam is
insert-then-MarkReadyAsync with T401 owning the ineligible-until-ready ordering
(SPEC F161.3 rider). The db/43-with-no-CREATE-TABLE gap reopened Story346's marker
derivation exactly where its comments predicted — re-pinned at the ready-made
perturbation points. 40 live-Postgres facts; one opus round PASS.

Claude-Session: https://claude.ai/code/session_019Pn3gCFBkB1ATXmFe1aLPk
…what it can catch

AdScriptValidator runs format → duration → brand → phone → posture, first violation
wins, all four adjacent order pairs pinned. Duration is text-true at the house 15
chars/second (a constant per-line estimate had reduced the rule to line-counting —
a 3×-over script sailed through; now Σ(text)/15 is the primary term and a trusted
Historical observation may only WIDEN it, both directions pinned). The fold catches
case, punctuation, leet (gated to lettered tokens — "Grade A, 5 5 rated" no longer
manufactures profanity from digits), and letter-spacing (with drop-leading/trailing
run variants so "Grab a c o k e" AND the correctly-spelled "Grab a M&M's" both
refuse while "Sizes S, M and L" doesn't); near-miss SPELLINGS are data, not
edit-distance — the blocklist ships a curated section so "Coka-Cola presents"
refuses by entry (SPEC F160.3 as-built rider). ~195 precision-biased brand entries
(h&m dropped — it folded to "hm"; tic tac qualified), a 23-word Everyone-posture
profanity floor whose ad-copy-only scope is pinned by a repo-wide source sweep.
Phone shapes are checked per-line on raw text, \b-anchored, 555-anywhere passing —
zip+4 and cross-line synthesis pinned clean. Violation echoes are bounded at the
crosstalk 120 and control-stripped (a 100KB hostile line yields one safe line).
63 facts; three opus rounds; eleven mutants killed, every kill revert-verified.

Claude-Session: https://claude.ai/code/session_019Pn3gCFBkB1ATXmFe1aLPk
…e-ask, skip-only

AdScriptWriter authors one spot per completion under LlmCallKind.AdScript, prompting
structure-first (hook/pitch/tagline/CTA beats budgeted at SpotSeconds×15 chars — ~71%
of the validator ceiling, the margin written down) with the brief's brand/premise/tone
and the fictional-brands/555/no-trademark demands — the prompt persuades, the
validator enforces. Validation reaches the writer as a caller-supplied delegate (Tts
never references GenWave.Ads; the real crossing is pinned from Ads.Tests in the exact
adapter shape T402 will wire, including the 60-second four-beat spot end-to-end).
Hygiene is line-aware — split first, tag sliced before hygiene ever runs (whole-blob
hygiene had collapsed every spot to one giant line: multi-voice destroyed, two
validator rules vacuous, 60s spots impossible; the regression fact reds the revert) —
and a genuine chat-preamble line falls honestly to the format rule and the one re-ask
rather than a second heuristic that could eat a tag. Refusal causes map with Crosstalk
fidelity (format→MalformedResponse, duration→OverLength, content→TruthGateReject);
the re-ask decision is type-driven; the delegate's reason is bounded once for both
the re-ask prompt and the ring; one timeout budget spans both attempts. The ~55-line
completion plumbing copy is gone — PostChatCompletionAsync is now the one internal
seam Crosstalk and Ads both call, byte-identical for crosstalk. Two opus rounds;
the F1 revert-mutation reds in both suites.

Claude-Session: https://claude.ai/code/session_019Pn3gCFBkB1ATXmFe1aLPk
…e orphan

CrosstalkAssembler widens to a VoiceSpec cast (1–3 voices, one-line spots legal, a
per-request ceiling probed on the FINAL bed-included artifact — a pad alone crossing
the ceiling discards, pinned) while the two-voice crosstalk path stays byte-identical.
CastSegmentAuthor is the generic authored tail behind a new ICastSegmentAuthor port:
assemble → insert INELIGIBLE → caller-confirmed → flip eligible — the T398 material
ordering made structural (a crash or declined confirmation leaves an inert ineligible
row, never an airable orphan and never a manufactured dead-file; grounded on F159.3's
own retired-spot shape, the retry-accumulation cost documented against gh-#3's GC
class). AdRenderService (the first production Ads→Tts edge, acyclicity green) parses
the stored script, honors the voice_plan per tag (pinned — a nulled plan reds; blank
Tag/VoiceId entries degrade to the station voice, both shapes pinned), resolves beds
by id never by path, and reports every failure through MarkFailedAsync as a typed
reason — it never throws. IAuthoredCatalogWriter gains Eligible-on-insert +
SetEligibleAsync(bool) with live-Postgres facts both directions. Three opus rounds;
twelve findings; every kill independently mutant-verified, including three that were
silent production outages (ads rendering forever under the wrong kind, a disabled
ceiling, a spot never marked ready).

Claude-Session: https://claude.ai/code/session_019Pn3gCFBkB1ATXmFe1aLPk
…nded

AdSpotWorker ticks repair → retire → refill → render, entirely off the air clock: a
new IOnAirRenderSignal Core seam narrows Host's OnAirRenderGate, the tick carries its
own Ads:RenderBudgetSeconds linked CTS (a wedged synth can't hang it), an open break
window cancels the in-flight render and the SAME spot resumes after — pinned with its
positive control. The guardian re-arms stuck renders at a grace computed live as
budget + margin (the relation pinned from both sides), and the ready-but-ineligible
interleaving orphan is repaired inside an observation-cadence window (one tick + the
grace — sized on when the sweep can SEE the orphan, not on render honesty; the
grace-only window was proven dead code at production defaults) while an aged
operator-disabled ad row is respected forever — never_play and retire documented as
the operator's levers. Stock keeping refills toward TargetCount one attempt per tick,
retires stale llm/pack spots via SetEligibleAsync(false) with owner spots exempt, and
forks draft/approved on the live AutoApprove read. ClaimConflict is a typed outcome
with one home. Smoked on the real binary with live llama3.2 + kokoro: real model
drafts genuinely failing the real validator with honest reasons, one brief becoming a
ready+eligible row in the exact F161.3 shape — the vend-side airing proven by
composition against T395's pinned predicate and T397's push transcript. Three opus
rounds; 7/7 mutants killed, every deliverable deletion-detectable.

Claude-Session: https://claude.ai/code/session_019Pn3gCFBkB1ATXmFe1aLPk
Base automatically changed from feat/ap-c-ads-seam to main September 3, 2026 12:28
@genwave-radio
genwave-radio merged commit f3f9367 into main Sep 3, 2026
11 checks passed
@genwave-radio
genwave-radio deleted the feat/ap-d-authoring branch September 3, 2026 12:34
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant