- A phone-in joined into one piece of audio now takes up its real length in the running order. It always went in as one item and the item never said how long it ran, so the clock treated a ten-minute programme as taking no time at all and planted whatever came next on top of it: the news at the top of the hour arriving ten minutes into a call. An episode of somebody else's podcast has been placed with its length for a while; a programme the station made itself now is too, measured by the mixer that joined it rather than claimed by a publisher.
- Plugins can now offer text for the station to read out. A
narrationplugin says what series it has (a book, a newsletter, a queue of long reads), what instalments each one holds, and the words of one when the station asks for them. The station speaks it itself, in its presenter's voice, so a chapter gets the pronunciation lexicon and the performance cues the station's own words get. Nothing carries it on air yet; that follows. - Put a narration band on the format clock and the station reads at that time: the next chapter of a book, or the newest issue of a column, in the presenter's voice, with a talk break in front of it introducing what is coming. The mount names the piece and the series while it plays, and the station remembers where it got to, so tomorrow's band reads the next chapter rather than the same one. A band with nothing left to read goes quiet and says why rather than airing something else.
- The station can now read a piece out loud. Ahead of a narration band's slot it fetches the words, splits them into as few parts as its speech engine will take, and speaks each one in the presenter's voice, with the pronunciation lexicon and everything else the station's own words get, then has the mixer join them into one recording. It works well ahead of time and yields to anything the presenter needs now, so a chapter being made never delays a break. Nothing airs it yet; the band that places it follows.
- A Readings page in the Library, beside Podcasts: what the station has been given to read, every piece of it, and where the station has got to. Each one says whether it has been spoken yet, is being spoken now, or could not be and why, and any of them can be read out of turn. A narration band's topic can pick a series from the same list rather than needing an id typed in.
- The station now keeps track of what it has been given to read out. A narration plugin says what series it offers (a book, a column, a queue of long reads), and the station remembers every piece of each, in the order that series is worked through: a book from its first chapter, a column from its newest issue. It re-reads the list twice an hour, and the Clock can point a
narrationband at one of them. Nothing is spoken or aired yet; that follows. - A speech plugin can now say how much text its engine takes in one go, and the station knows how to cut something longer than that into as few calls as will fit. It cuts at the strongest boundary available, between paragraphs first, then between sentences, then between words, because a cut mid-sentence is audible where a slightly longer call is not. Nothing a presenter says is anywhere near any engine's limit, so no break changes; this is groundwork for reading something long out loud.
- A phone-in being made for later no longer holds the speech engine in front of the breaks the presenter needs now. Its beats were queued at the same rank as anything on air, so a break planted while a programme was being spoken waited behind every remaining beat of it. A programme now yields to the station while its slot is far off and takes precedence as that slot approaches, which is the same earliest-deadline rule its writing already followed.
- A mistyped
DATABASE_PORTnow stops the station with a message naming it. It was read asconfig.get('DATABASE_PORT', 55432), which types asnumberand answers the string'5432'whenever the variable is actually set, and worked only becausepgcoerces it on the way into the socket. A value the parser could not read took the same path and becameNaN, which is falsy, sopgread it as a port nobody set and fell through toPGPORTand then to its own 5432:DATABASE_PORT=54 32surfaced as a refused connection, or as authentication against whatever else answers on 5432, rather than as the two characters that caused it. It goes throughrequiredNumbernow, likeREDIS_PORTbeside it, which refuses a value that is present and unreadable and falls back only for a key nobody set. Its tests passed the port as a real number for as long as they existed, which proved nothing about a resolver whose whole bug was the string, so they pass strings now and the fixture type refuses anything else. - The station can be pointed at a Redis that asks for a password. It read
REDIS_HOSTandREDIS_PORTand nothing else, so an operator whose cache wantsAUTHhad no way to say so and #160 ran a second Redis daemon beside the one they already had.REDIS_USERNAME,REDIS_PASSWORDandREDIS_TLSnow sit beside the address (a Redis with onlyrequirepassset takes the password alone and no username;REDIS_TLSis the connection rather than the password, and without it the password travels in the clear), andREDIS_URLtakes the whole thing as one string instead, which is the shape a hosted Redis hands you and the only way to name a database index. Set the URL and it is the entire answer: the five discrete variables are not consulted at all, because a merge of the two is a station connecting to the right host as the wrong user, and thefullimage fills the loopback ones in itself. A URL it cannot read stops the boot rather than falling back to localhost, and no message about it ever quotes the value, since a Redis URL carries the password.REDIS_PASSWORDandREDIS_URLare scrubbed fromprocess.envafter the config snapshot resolves, like the database password already was. Everything that talks to Redis (both rate limiters, the sign-in mail limiter, the cache and the session store) shares the one client, so all of it is covered.REDIS_PORTis read throughrequiredNumberin the same pass: it was handing ioredis the string'6379'whenever it was set and working only because the connector coerces it, soREDIS_PORT=63 79was aNaNport rather than an error naming the variable.