feat: phase AP-d — the authoring pipeline: stores, validator, writer, render, worker - #665
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
7e23f07+ db/43) — the ad-spot lifecycle store: F159.2's seven transitions as state-conditional UPDATEs,FOR UPDATE SKIP LOCKEDclaims, the two narrated invariants turned into CHECKs (which immediately caught a real bug), the ratified brand-cap semantic pinned, 40 live-Postgres facts.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.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.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.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