Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ is at the bottom; the current roadmap lives in the [README](README.md#roadmap).
- **v3.2.0 β€” Wardrobe workshop M2: the editor (SPEC F104, PRs #395/#396)** β€” the theme font law widens to **vendored βˆͺ installed** (T205): `ThemeFontProvenanceValidator`'s per-theme byte ceiling now sums both sets, and an import 400 names the missing face plus, when the catalog index knows a pack that provides it, the pack's own slug (fail-soft β€” catalog CI stays curated-only, so shipped themes remain structurally incapable of referencing a pack). The **theme editor** (`/editor`, T206) mixes a base theme's palette with a role face (display/sans) over one server-derived assignable set (`GET /api/fonts/assignable`, vendored βˆͺ installed in one union), previewing through the T186 transient-compose mechanism β€” nothing persists until **save-as-own** (T207) writes it to `station.theme` behind a shared `ThemeWriteGate` (one two-phase pipeline now serves both write routes, gate-identical by construction) with a **fail-closed 409** refusing to overwrite an already-imported theme's provenance. Font pack **uninstall** (T208) removes a pack unless a saved/imported theme still references one of its faces β€” the guard lives *inside* the `DELETE` statement itself (`WHERE NOT EXISTS`), not an advisory pre-check a concurrent write could race past; refusal names every referencing theme. T209 re-audits public disclosure over the widened surface (self-derived AdminSurface+Settings sweep, the one sanctioned spectator change pinned). T210's exit-demo β€” mix, save, activate on the pinned v3.2.0 demo box β€” closed F104.
- **v3.3.0 β€” A station, not a playlist (SPEC F107–F113, project #3's flavor floor)** β€” the content kinds radio is made of, on one seam. **The context seam (F107)**: `IContextProvider` joins the MIT contract surface (the demonstrated F105.6 need is GenWave Business) with implementations in the new **`GenWave.Context`** project β€” born outside Host per the F105.4 graduation rule, covered by every fitness law from day one. One pipeline (fetch-once-per-cadence-slot, freshness re-checked on every read, **skip-never-silence** β€” a dead provider is inaudible), two delivery lanes: rendered **context segments** at track boundaries via the F74 queue (supersede now per `(kind, discriminator)` so due weather never erases due history) and an **at-most-one, cadence-gated context fact** in LeadIn/BackAnnounce prompts β€” byte-identical prompts otherwise, golden-pinned. Provider text is **fenced as data-not-instructions** end to end: the sanitizer makes fence delimiters structurally unrepresentable (adjacent identical angle brackets cannot survive it), lengths capped, newlines dead β€” adversarially proven against crafted Wikimedia payloads. **Weather (F108)**: Open-Meteo, keyless, SSRF-closed (validated invariant coordinates into a fixed host β€” nine injection shapes fail closed with zero outbound requests); the 2026-07-20 spoken-vs-precise ruling enforced β€” coordinates reach no prompt, payload, or log line, `Station:Location:SpokenName` is the only place name a DJ may speak; blank coords = provider off even when enabled, edge-logged once. **This day in history (F109)**: Wikimedia On-This-Day (curated `selected` feed), **paraphrase-only** under the news posture β€” a 3B model never free-recalls dates on air; day-file cache + next-day pre-fetch so the segment survives restarts and outages; F76-style etiquette UA shared with MusicBrainz. **Idents & time (F110)**: opt-in clock-anchored top-of-hour idents drain **pool-first** β€” the gh-#149 imaging collection gets its first playout consumer (random ready authored `station_id` row airs as audio; templated TTS fallback), and `SegmentKind.TimeDate` gains its first-ever producer β€” *"It's two o'clock."*, station-voiced, zero LLM, per-hour text = forever-cache hit; F95.4 tightened along the way (an explicit-marked ident never airs on an `everyone` station β€” fail-closed to the template, never dead air). **The straddle handoff (F111, gh-#320's middle rung β€” and gh-#300 closed with it)**: when nothing in rotation fits the boundary and the picked track **provably crosses** (measured duration only β€” never inferred), the outgoing DJ signs off *into* it and the incoming DJ picks up on the far side, back-announcing the captured track; ceremony-now demotes to a genuine last resort. **The pick pipeline left the Orchestrator first (F112, gh-#401)**: `MusicSelectionPolicy` owns request rung β†’ persona rung β†’ trust-but-verify β†’ degradation ladder β†’ boundary resample, returning a `Fit | Straddle | CeremonyOnly` outcome with **one floor authority** and rung-tagged fit logging (F111.5). **Airings became countable (F113)**: `booth_log.segment_kind` stamped at *air* time (a budget-dropped render never counts; db/33, which also lands the dormant `station.show` schema for the dayparting slice) β€” the epic's demo-hour gate is one indexed query, shipped as `tools/demo_hour_gate.sql`. Built via /build-loop's adversarial gate: **every substantive task failed round one** and every catch was mutation-proven before passing β€” the sim that showed 70–92% of hourly idents silently erased (now 0.0%), sign-offs airing 4–6 minutes early on short-track pools, and a prompt fence a Wikimedia editor could close from the outside.
- **v3.3.1 β€” The whole sentence airs (gh-#424)** β€” patch: cue-out now comes only from silence that **actually extends to EOF**. `FfmpegCueAnalyzer` took the *last* silence region as cue-out unconditionally, so an interior pause that happened to be last β€” a 0.6 s injected TTS sentence gap (they clear the 0.5 s silencedetect floor), a quiet mid-track break β€” became `liq_cue_out`, and **the final sentence of every multi-sentence patter clip was cut on air** while booth logs, the cached wav, and the preview all looked complete (`DurationMs` derives from the same cue-out, so even the progress bar agreed). Trailing now means open-ended at EOF or closed within 0.25 s of the container duration; interior-only files report the full audible extent so durations stay measured for straddle boundary-fit; misclassification degrades safe-only (untrimmed tail β†’ `blank.eat` backstop). Analyzer-level on purpose: music with a quiet interior break stops mis-trimming too. Playout self-heals on deploy (blurb cues re-measure per process); persisted cue rows β€” music enrichment, authored imaging β€” keep old values until re-enriched/re-authored. Found in Dean's T229 listening pass (both v2.9.0 and v3.3.0 sightings were this one path); regression spec red-checked against the pre-fix parser.
- **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.
10 changes: 5 additions & 5 deletions compose.demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ services:
restart: unless-stopped

admin_ui:
image: ghcr.io/genwave-org/genwave-admin-ui:home-v3.3.1 # bump per release
image: ghcr.io/genwave-org/genwave-admin-ui:home-v3.3.2 # 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
Expand All @@ -61,7 +61,7 @@ services:
- "127.0.0.1:3000:3000"

engine:
image: ghcr.io/genwave-org/genwave-engine:home-v3.3.1 # bump per release
image: ghcr.io/genwave-org/genwave-engine:home-v3.3.2 # 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
Expand All @@ -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.3.1 # bump per release
image: ghcr.io/genwave-org/genwave-piper:home-v3.3.2 # bump per release
pull_policy: always
# Same !reset rationale as admin_ui above (gh-#93).
build: !reset null
Expand Down Expand Up @@ -123,7 +123,7 @@ services:
restart: "no"

icecast:
image: ghcr.io/genwave-org/genwave-icecast:home-v3.3.1 # bump per release
image: ghcr.io/genwave-org/genwave-icecast:home-v3.3.2 # 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
Expand All @@ -134,7 +134,7 @@ services:
ports: !override []

api:
image: ghcr.io/genwave-org/genwave:home-v3.3.1 # bump per release
image: ghcr.io/genwave-org/genwave:home-v3.3.2 # 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.
Expand Down
Loading