diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e84da9d..a8a4f330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,3 +63,4 @@ is at the bottom; the current roadmap lives in the [README](README.md#roadmap). - **v3.3.2 — Nothing grim, nothing clipped (gh-#433, gh-#424 rollout)** — patch, two halves. **The history airability gate (gh-#433)**: `HistoryFactHygiene` sits between the On-This-Day day cache and either delivery lane at **vend** time — somber facts (violent-death / disaster / atrocity vocabulary, word-boundary matched, deliberately fail-closed) never air, and half-stripped `[[wikilink]]` bracket residue is cleaned from what does. Driven by a live sighting: a fatal plane crash aired as chill-morning patter color, stray bracket included. An all-somber day — Wikimedia's curated `selected` feed skews grim; the real captured reply was five-for-five — is an ordinary F107.6 skip, one Information line; pool-widening to the fuller `events` feed is the recorded follow-up if coverage thins too far. **The stale-cue retirement (db/34)**: every cue point persisted before v3.3.1's analyzer fix could carry a mid-file `cue_out` (music with a quiet break clipped at the pause — heard live on the demo). A marker-guarded one-time migration (`library.one_time_fix` ledger, claim-CTE, twice-run-proven against a real postgres:16.4) nulls the stale cue columns at deploy — instantly curative, NULL cue = full-file playback — and the F13 backfill predicate re-measures the whole library with the fixed analyzer at the worker's own pace. Zero operator action, per the standing zero-manual-release-steps rule. - **v3.4.0 — Dayparting: named shows (SPEC F115–F121, gh-#383)** — the hours get names. **Shows are identity packages** (`station.show` widened, db/35: slug/tagline/flavor/provenance + the dormant schedulable-bundle columns, ruled unread until that slice designs their readers): authored on a new **Shows page** (60/120/400 budgets, guarded delete naming referencing blocks), assigned from the schedule grid's side panel with **run-span default** — one click names a whole contiguous same-persona run, and music-only runs span too (ruled: one click names the overnight stretch). **The ear knows**: sign-on ceremony carries the incoming show's name + flavor, a same-DJ show flip airs exactly one transition piece (F92.3 dedupe now keys persona AND show), a **cadence-gated flavor line** shares F107.5's single extra-line slot (context facts win; the prompt ceiling never grows), and top-of-hour idents go **show-scoped-first** — authored rows scoped to the show, then the station's authored pool, then a templated "You're listening to ⟨show⟩ on ⟨station⟩." floor (ladder ruled: authored beats template). **The public face knows**: spectator now-playing carries `show {name, tagline}` and `upNext.show` (name-only by type shape; flavor is prompt-config, structurally absent from every public payload — value-level pinned). **The catalog grows its third community kind**: `show` entries (schemas + budget lint + 12 seed cards, one per demo DJ, shipped as 12 one-entry PRs per the moderation model) import through the F79 shell with an **atomic authored-protection upsert** (`ON CONFLICT … WHERE imported_from IS NOT NULL` — the gh-#394 conditional-write form; an authored show's slug survives any import byte-for-byte) and a **soft "also hire ⟨DJ⟩" offer** that opens the persona's own full-card review (never a silent second import); imported Name/Flavor are sanitized at the parser seam (control chars flattened, fence runs collapsed) with the F90 full-card confirm as the ruled trust gate. **Dated specials land as the ruled-droppable tail, delivered** (db/36, own migration): per-date EXCLUDE-guarded one-offs shadow the weekly grid for their span with honest boundaries both directions (mid-block resume; a midnight special still arms the outgoing sign-off via the tomorrow-boundary-peek), authored on a dated-list form — deliberately not a second paint grid. **Airings are show-countable** (`booth_log.show_id`, air-time stamp, no FK) — the demo-hour gate gains its show halves as one indexed query. Rider: **time announcements join the ident** ("It's two o'clock on ⟨station⟩." — gh-#453, ruled on the first live listen; the forever-cache re-keys by construction). Built via /build-loop's adversarial gate across five stacked PRs: catches included a grid-save wipe of every show assignment (killed at three layers), a published-NuGet binary break, a boundary-fit estimator corruption, and an upNext erasure at exactly the boundary this epic ruled real — plus two display bugs only the live wire smokes caught. - **v3.5.0 — Every kind in its place (gh-#372, gh-#393, catalog#33 prep)** — the mixed piles get shelves. **The Wardrobe silos by kind** (gh-#393): Personas | Themes | Fonts | Shows, URL-driven tabs with every tab present even when empty (ruled on the issue: an empty kind shows its own empty state, never a hidden tab); fonts keep their founding cards + uninstall, the other kinds list genuinely-imported rows only (`importedFrom`-gated, the two-provenance-class rule) under their own verbs — Hired / Imported / Installed — with actions deliberately staying on each kind's own page. **The community shelf gets the same treatment** (gh-#372): `?kind=` tabs over the catalog grid — a persona card and a show card were visually identical without opening them — while the cross-kind machinery (the soft hire offer including its failure path, the install modals, imported-state maps) keeps reading the full index untouched. The tab strip itself is extracted once (`components/ui/tab-strip.tsx` — CatalogTabs and BoothLogTabs were already byte-identical copies; four consumers now share one). **The validator learns the catalog's next layout** (genwave-catalog#33): `entries///` admitted alongside the flat layout — the kind folder, when present, must name the entry's own kind; slug ownership reads the second-to-last path segment; and a new one-directory invariant pins an entry's manifest/meta/assets to a single directory (what keeps bare filenames unique for the filename-keyed asset route once one slug can legally exist under two layouts). The catalog-side move (genwave-catalog#50) holds until this release is on the box — an older app rejects a migrated index wholesale, fail-closed. +- **v4.0.0 — Make the DJs sound human (SPEC F96–F100, F123–F126 · the wince-list, closed)** — the epic that resumed the parked voice work and shipped it as five slices. **Copy fits its break** (gh-#277): the completion request finally carries a generation cap derived from `Llm:MaxCopyChars` — killing the 30–87s free-running holds — and over-length copy is trimmed to the last complete sentence (abbreviation-aware, so "a brand new one from St." can never air) instead of rejected, which turns the SignOff/SignOn/ContextSegment over-length *silence* edge into aired persona copy; the prompt states its own word budget. **The boundary respects the backlog** (gh-#469): a queued tail crossing a schedule boundary now classifies as a straddle and *holds* the incoming DJ's sign-on behind a not-before gate honored by both the drain and the planner's peek against the real clock — the 22:00Z inversion where a sign-on aired over six minutes of buffered outgoing content is structurally dead — and a `TimeDate` deferral that would drain past its armed hour expires with a WARN rather than announcing a dead hour. **Storms stay somber, facts rotate** (gh-#468): the tone gate gains the wind-storm noun family (a tornado touchdown will never again air as chill-morning color), and context facts rotate at vend time against a day-scoped aired-set instead of the provider handing out the same `chosen[0]` all day — which reshaped `ContextContent` in the published `GenWave.Abstractions` package to carry the airable fact *list*, **the breaking contract change this major version marks**. **Pronunciation rides the wire**: heteronyms and names say themselves right via engine-adapter `[word](/ipa/)` markup (canonicalized at one seam), persona **pace** reaches Kokoro's `speed` (11 of 12 catalog personas ship a non-1.0 pace ignored until now — stations sound different, by design), and rule/correction hits plus per-persona render outcomes are finally visible at Information in Loki — *fired means aired*, never annotated-then-failed, with engine timeouts no longer vanishing from the failure rate. **The pronunciation editor**: an operator who doesn't speak IPA teaches the DJ a word entirely in the browser — a content-addressed rules API, a rows-not-JSON editor, a **"Hear it"** audition that plays the rule being authored through the real air chain (auditions excluded from the hit counter), an **espeak-ng respell oracle** (type "muh-KLOWD", get IPA — argv-only and option-inert after a review caught a key-ring-reading argument injection), all held shut by two new fitness laws (L7 the context-overload seam, L8 the one resolve seam). Built task-by-task through /build-loop's adversarial review gate across twelve rounds: catches included a hold that lasted zero seconds, a nested form real browsers silently strip, a CWE-88 file read reproduced in-container, and a preview surface that lied about the rules it auditioned. The voice contract (right-voice-or-no-speech, opt-in failover) and the manual ear-gate are held to a dedicated session. diff --git a/compose.demo.yaml b/compose.demo.yaml index cf34cd26..36069380 100644 --- a/compose.demo.yaml +++ b/compose.demo.yaml @@ -47,7 +47,7 @@ services: restart: unless-stopped admin_ui: - image: ghcr.io/genwave-org/genwave-admin-ui:home-v3.5.0 # bump per release + image: ghcr.io/genwave-org/genwave-admin-ui:home-v4.0.0 # bump per release pull_policy: always # `!reset` drops the base `build:` context entirely: a demo box must pull this image or # fail loudly — never silently fall back to building (and then forever running) local @@ -61,7 +61,7 @@ services: - "127.0.0.1:3000:3000" engine: - image: ghcr.io/genwave-org/genwave-engine:home-v3.5.0 # bump per release + image: ghcr.io/genwave-org/genwave-engine:home-v4.0.0 # bump per release pull_policy: always # Same !reset rationale as admin_ui above (gh-#93). Note genwave.liq is a bind mount # from the checkout (see compose.yaml), NOT baked into this image — the image carries @@ -74,7 +74,7 @@ services: # silently source-build and freeze. NOTE the first genwave-piper publish happens at the FIRST # TAG CUT AFTER gh-#240/#241 — until that release's pin bump this tag does not exist and the # pull fails loudly by design. Bump with the other four pins per release. - image: ghcr.io/genwave-org/genwave-piper:home-v3.5.0 # bump per release + image: ghcr.io/genwave-org/genwave-piper:home-v4.0.0 # bump per release pull_policy: always # Same !reset rationale as admin_ui above (gh-#93). build: !reset null @@ -123,7 +123,7 @@ services: restart: "no" icecast: - image: ghcr.io/genwave-org/genwave-icecast:home-v3.5.0 # bump per release + image: ghcr.io/genwave-org/genwave-icecast:home-v4.0.0 # bump per release pull_policy: always # Base compose publishes 8000:8000 on 0.0.0.0 — right for LAN listening in # Operator/Standard, wrong on a public box: Icecast serves status.xsl (listener @@ -134,7 +134,7 @@ services: ports: !override [] api: - image: ghcr.io/genwave-org/genwave:home-v3.5.0 # bump per release + image: ghcr.io/genwave-org/genwave:home-v4.0.0 # bump per release pull_policy: always # compose.yaml's base `ports:` publishes BOTH 8080 and 8081 on 0.0.0.0 — fine for # Operator/Standard (LAN reachability is the point there), wrong for a public box.