Skip to content

Launch polish PR-3: sponsor phone, folded tags, no stage directions (STORY-466/467/468) - #834

Merged
genwave-radio merged 4 commits into
mainfrom
ads/script-hygiene-466
Sep 23, 2026
Merged

genwave-radio merged 4 commits into
mainfrom
ads/script-hygiene-466

Conversation

@genwave-radio

Copy link
Copy Markdown
Collaborator

🎯 What

PR-3 of the launch-polish epic: STORY-466 (the sponsor's phone replaces the placeholder), STORY-467 (unknown speaker tags fold into the announcer), STORY-468 (stage directions never reach the voice). Built on PR-2 (#833, merged); four commits ahead of main.

Fixes #701, fixes #742, fixes #706.

🔬 Wire evidence (T553, dev station on the branch image)

Three spots regenerated through POST /api/ads + /write on the deployed api, read back through GET /api/ads/{id}:

Spot Sponsor Result
37 Wire Diner, phone (812) 346-0199 on file Script carries exactly (812) 346-0199 once, no other phone-shaped run, no stage direction
38 Harbor Bakery, no phone No phone-shaped run at all, no stage direction
39 Test Sponsor, brief steering the model to NARRATOR Model emitted 4 NARRATOR + 1 ANNOUNCER lines; parseNotes = ["unknown-tag:NARRATOR"]; preview logged Ad spot 39 parse note unknown-tag:NARRATOR

Briefs for 37 and 38 asked the model for delivery notes in parentheses; both stored scripts have none.

STORY-468 through the same api (T552 smoke): the model's raw reply for a Harbor Bakery spot carried five stage directions ((enthusiastically), (warmly, pause), …) and a stray 555-0193; the stored script has none of either. An owner-typed script with (beat) on line 2 returns 400 stage_direction "script line 2 still carries a stage direction ("(beat)")".

STORY-466 through the same api (T550 smoke): with the phone on file the script carried exactly (812) 346-0199; without it the model's "Call us now at 555-0193 or visit us online…" became "Or visit us online…".

Full solution (dotnet test GenWave.sln --filter "Category!=Integration", Host with MaxParallelThreads=3): 0 failed across all 9 projects (Ads 231, Tts 930 / 2 skipped, Host 3083 / 47 skipped). admin-ui untouched.

⚠️ Follow-ups (not fixed here)

  1. The Tts phone regex in AdScriptWriter.ApplyPhoneHygiene duplicates GenWave.Core.PhoneShape (PR-2); dedupe once both are on main.
  2. The three stage-direction shapes are duplicated Tts↔Ads (L10: Tts cannot reference Ads); a Core-level shared shape is the seam if they drift.
  3. AdScriptParser now depends on AdCastPicker for the VOICE1/VOICE2 tag names (format stage reaching into render stage); the tags belong beside AnnouncerTag. Candidate for the Burn the optional-seam baseline in GenWave.Orchestration down to zero (SPEC F193.2) #827 seam burn-down.
  4. SPEC F200.3 says "booth log" and "ad detail panel"; the implementation logs via ILogger and the panel half has no PLAN task. SPEC prose also writes stage-direction where the rule id is stage_direction. /design amendment owed.
  5. A model reply that puts the phone on its own pipe-delimited line (555-0193 | site | hours) leaves | site | hours joined to the previous line after the clause drop. Cosmetic; seen once.

AdScriptPromptBuilder cites "555-01" + two digits from an FNV-1a hash of
the sponsor name folded to lower case (SPEC F199.1), so one sponsor's
prompt is identical run to run and two sponsors rarely share an example.
FNV-1a rather than string.GetHashCode, which is randomized per process.

Story466 AC1-AC2 green (acme -> 555-0115, zenith -> 555-0129); AC3-AC6
stay pending for T550. Deployed proof: a Wire Diner write job's recorded
system prompt reads "for example 555-0192".
AdScriptWriter runs a phone hygiene pass after the LLM reply: a phone-shaped
run becomes the sponsor's own number when one is on file (STORY-466 AC3), a
run whose digits already equal it is left alone in any format (AC4), and with
no phone on file the clause carrying the placeholder is dropped, orphan
punctuation stripped, and an emptied line removed (AC5, SPEC F199.2).

PhoneShapeCheck tightens per SPEC F199.3: with a sponsor phone on file only
that number passes; a differing run refuses even when it contains 555 (AC6).
Story417's filler numbers move to the sponsor's own phone for that reason,
and the Host completions router drops its placeholder clause so Story423's
byte-for-byte pin holds for phone-less sponsors.

The scan advances past each replacement instead of restarting at zero; the
earlier restart re-matched the inserted tail forever for paren-formatted
phones and starved the Ads test threadpool.
The parser no longer refuses a script whose model-invented tag (NARRATOR,
HOST, ...) is not ANNOUNCER/VOICE1/VOICE2: the line folds onto ANNOUNCER
and the fold is recorded as a parse note (`unknown-tag:{TAG}`). Distinct
and ANNOUNCER checks run over the folded lines (SPEC F200.1/F200.2), so
notes are never a violation. GET /api/ads/{id} exposes `parseNotes`;
render/preview log one INFO line per note (F200.3).

Story390's four-tag refusal pinned a state F200 defines away; it now pins
the fold. Un-skips the six Story467 facts.
AdScriptWriter.ApplyLineAwareHygiene strips (…), […] and *…* anywhere on
a line before the general copy hygiene runs, so "*long pause*" is removed
whole instead of being read aloud; whitespace and a stranded space before
punctuation are tidied only when a shape was actually removed. Lines left
empty are dropped before the lead-voice election so they never vote
(SPEC F201.1). Each shape must carry a letter so a sponsor's "(406)" area
code survives for the phone rewrite.

AdScriptValidator gains rule `stage_direction` (F201.2): post-hygiene
residue refuses naming the line and a bounded echo. The echo bound moves
to a shared AdScriptEcho helper. CheckPhoneShape's reason is now honest
for both branches (F199.3). Un-skips Story468.
@genwave-radio
genwave-radio merged commit bb21f49 into main Sep 23, 2026
12 checks passed
@genwave-radio
genwave-radio deleted the ads/script-hygiene-466 branch September 23, 2026 12:35
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.