Repository navigation
What a comparable station does that this one does not #10
Replies: 3 comments
|
Continued (2 of 2). The seventh pass, which read the fault list instead of the tree2026-09-02, the same day as the sixth. Six passes read what the other station BUILT: its source, The counts govern how to read it. Nineteen faults are live here today. About 150 are guarded, Two claims in this directory that the code contradictsThese come first because they are the class the fourth pass argued is worth most, and because both
The other seventeen, by where they sitProgramming.
Breaks and productions. The model.
Enrichment. Operations. Measured and NOT live, which is worth as much
Absent, and worth a line each
The eighth pass, which was asked to fix everything it found2026-09-10. The pass looked at code written here since 2026-09-02: the two listener apps, the The running order and what airs
Plugins, the render path, and the pieces around them
The model
The Android app
All four of the above were written and committed without ever compiling. This sandbox has no The desktop app
All five of the above were written and committed without ever compiling, for a different reason Product decisions taken with the operator
Two things the build itself turned up
What is deliberately not wantedRecording these stops the survey being re-run to reach the same answer.
Worth taking, in orderRevised after the second pass, with the original reasons kept, and marked after the third.
Added by the fifth pass, and it goes at the top rather than into the list, because it is a defect
Added by the fourth pass, and neither is ranked against the list above, because both are answers
The lesson of the third pass, which is worth more than any entry above. Two passes over the same What the fourth pass adds to that, having used it deliberately: the method's real output is What the fifth pass adds, and it is the last thing this file needs to say about method. Its best So the honest ranking of what this file is for has moved. The survey was worth doing once. What is And what building rank 0 added to THAT, which is the last word this file has on method: the best Seventh-pass additions to the order, 2026-09-02, and where they sit relative to the numbers above:
Moved from |
|
Status check, 2026-09-22 (main at 23b983e) Done: rank 0 (weather reading expiry: Still open:
|
|
Closing. Everything in this review is built or has its own discussion: the beat layer is #45, operator break kinds #24, the embedding axis #14, the mixer knobs #20, persona-note decay is split from #25. The usage total and the remaining one-liners moved to #447. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Written: 2026-08-22, after reading another self-hosted station of the same shape end to end:
Icecast and Liquidsoap in containers, a Node brain above them, a Subsonic library, a local model, a
local speech engine, a Python analysis sidecar. Close enough to this tree that almost every finding
lands on a seam that already exists here.
Revised: 2026-08-22, after a second pass over the same station's published operator manual and
its community pages rather than its source. The counts below describe the first pass and are left as
they were; the second pass is its own section, near the bottom, and it changes the ordering at the
end.
Revised again: 2026-08-27, after a third pass over the same station five days later, which is
long enough for it to have cut two releases. That pass is its own section too, and it does two things
the others did not: it CORRECTS a claim made here, and it records a bug this tree had that the survey
missed because it was looking for missing features rather than for wrong ones.
Fourth pass: 2026-08-28, which applied the third pass's closing lesson as a method rather than
reading it as a conclusion. It asked four questions of this tree and measured every answer, so two of
its four findings are numbers showing there is nothing wrong. It also corrects a claim the third pass
made here about artist name-variant folding.
Fifth pass: 2026-08-30, and the first one whose main finding is about code written HERE in the
window rather than about anything the other station does. Weather shipped as a capability, and it did
not inherit
break.claims.ts: a reading is fetched at write time, carries anobservedAtthatnothing reads, and nothing expires it before air. Also: the other station's continuity-decay reasoning
sharpens personas §6 into choosing a tier rather than a rule, and the non-Latin
speech class it keeps taking bugs on is measured at 0 here for a reason that belongs to the library
and not to the code.
Seventh pass: 2026-09-02, the same day as the sixth, and the first to read the other station's
public FAULT list rather than its tree: 325 entries over four months, 281 substantive. Every one was
classified against this tree by reading the code path. Nineteen are live here today, and two of those
are claims made in this directory that the code contradicts, which no amount of reading the other
tree could have found because the docs were what got read.
Eighth pass: 2026-09-10, over code written here since the seventh: the two listener apps, the
multi-provider LLM plugin, insert-at-position, chart airing, the year guard and the plugin
quarantine. It is the first pass that did not stop at a list: the operator asked for every fault
found to be fixed in the same run, so its own section, near the bottom, is a record of what shipped
rather than a survey of what to build next.
Status: three of these are built now, and the rest is still a survey. It was written down for the reason
stream-server-alternatives is: the pass was done once and should
not have to be done again. The findings are stated on their own terms rather than as a comparison,
because a reference to somebody else's tree dates badly and cannot be checked by whoever reads this
next. Where a number appears it was measured on a library that is not this one, and every one of
them says so.
The short version: most of this is already here. Of the fourteen things worth taking, nine are
already built or already designed in this directory, and for those the value of this file is the
evidence and the re-ranking rather than the idea. Five are genuinely absent, and three of those five
are operator surfaces rather than anything a listener would hear.
Where the two stations spent their effort
Worth stating plainly, because it explains the shape of the gap and stops this file reading as a list
of things to catch up on.
The other implementation is a listener product. Its hard-won engineering is at the audience seam:
the offset between the live edge and what a listener actually hears, per-browser codec gating, the
safety rules around a stranger's song request, six player faces, native apps. It has no editable
forward running order at all: a queue plus a weekly grid, with the next record chosen a couple of
minutes before it plays.
This station is an editorial system. The director owning one running order, a brief and a period
as columns on it, a claim that cannot exist without the quote that supports it, an opinion at three
levels, a persona that accumulates, one row per write attempt. Nothing here is aimed at a stranger.
That divergence is why the gaps below cluster so tightly into two piles: how the station sounds
between records, and what the operator can see. It is also why several things the other station
has are listed at the bottom as deliberately unwanted rather than deferred.
Two places this tree is ahead are worth recording, because both were arrived at the hard way and
should not be traded away for anything in the list below. A record is not committed until its
audio is local (the bytes-before-air rule) removes a whole class of
fault the other station instead detects after the fact, with a probe id per handover and a telnet
query to find out whether the thing it pushed ever resolved. And a claim with no source is not
expressible, where the comparable station reached the same problem from the other end and added an
abstain rule after a web search made its host invent facts on air.
What already has a home here
Nine of the fourteen. Each line is what the other implementation actually does, then where this tree
already keeps that question. Nothing in this section needs a new file.
crossfade.tsEnrichedTrack.moodsexists and nothing populates it. See the next section, which is about unblocking exactly thisOutlineBeat.leadandProduction.voicesexist and nothing reads themTwo of those deserve a note rather than a row.
The fade should be shaped by how a record ENDS, which is the one part of the transition question
this tree has not asked. The boundary length here is
min(outgoing.outro, incoming.intro), bothmeasured, which is already better than a fixed number. What the other station does on top is vary the
shape by ending type: a record that genuinely fades out rides eight to twelve seconds, a cold
ending cuts at four, and the choice between them comes from the loudness difference between the body
of the track and its tail, with the result snapped to whole bars from the tempo. That needs two
fields the sidecar does not return: it is at
SCHEMA_VERSION = 1with four cue points and theloudness layer, and no tempo, no downbeats, no vocal anything. So this is not a fifth rung on the
crossfades ladder, it is the beat layer in
track-analysis, which two other files are already waiting on, and the
talk-up limit rides with it for free. That is the re-ranking: three deferred entries in this
directory are one measurement pass.
The archive has two details worth stealing wholesale, both of which cost a line each and are
annoying to discover later: it follows the lease (already the stated rule here), and the recordings
have to be excluded from the library walk, or a station with a co-located library ingests its own
output and eventually plays it back.
The one that unblocks a blocked file
station-moment says mood-biased selection is blocked on data nobody has:
EnrichedTrack.moodsexists, nothing fills it, and the MusicBrainz plugin explicitly declines to.The proposed source is tags, which means a library is only as steerable as somebody else's
folksonomy.
The other implementation gets that data from the audio instead: a joint text-and-audio embedding
per record, scored against a mood vocabulary written in words, plus nearest-neighbour lookup in the
same space. That is a real answer to "nothing populates it", because it needs no tags and no
upstream at all, and it would give this station a timbral axis it has nowhere else.
The trap is measured and it is the reason to write this down. Similarity scores from that kind of
model are not comparable across different prompts. On an 11k-track library that is not this one,
scoring every track against every mood and taking the top scores per track produced
energeticon61.7% of the library and
rainyon 43.5%, againstcalmat 0.9% andspiritualat 0.2%. Rankingraw scores ranks the prompts, not the tracks. The fix is a per-mood baseline over the whole
library, mean and standard deviation, and then ranking on the z axis with the margin expressed in
standard deviations rather than in raw score. Two second-order details came out of running it, both
of which look like polish and are not:
gating the whole pass on the largest one. Gating reproduces the original bug one mood at a time: a
thin new mood beside a fat old one wins every record.
one guess with another and then calls it evidence. Null leaves the existing value alone, so
mistuning costs coverage instead of correctness.
What it would cost here is not small and should be counted before anyone starts. There is no
vector column and no pgvector in the migrations. The sidecar would grow a second model, which puts
it straight into the analysis licence rule, and the licence that matters there is the
weights', which is not in the package metadata. And the embedding text has one rule that has to
be got right on the first pass: the tagger's own output must never be part of what gets embedded,
because the labels are derived from the vectors and feeding them back is circular.
The three the operator would feel, none of which exist here
These are the genuinely absent ones, and they are all about seeing the station rather than hearing
it. None is large.
Nothing correlates one decision's calls.BUILT 2026-08-28, as described: ambient id on theplugin.invocation.deadline.tsshape, append-only JSONL, a writer that swallows everything. Theseams were the two named here. Three things the design above did not have:
trail —
JobContext.idandctx.requestId, both landing onAuthorizationContext.request, thesecond written into the
app.request_idGUC. A second id would have meant two ways to name onedecision and a join between them, which is what this was supposed to remove.
PluginInvoker.invokebounds getting a generation handle, and the words arrive afterwards through
collectGeneration.Measured on the first live run: one fact-extraction generation reads 7ms at the invoker and
19,243ms at the drain. Instrumenting only the obvious seam would have under-reported the
station's largest cost by three orders of magnitude.
kilobytes, and the failure it explains is the one nobody predicted — which is the case an opt-in
diagnostic is never on for. station-intelligence §2 lost eight of its ten cases to exactly
that. Bounded to seven days of files instead.
apps/api/scripts/traces.tsreads them back, because a format nobody has a reader for is a formatnobody reads. First run on this station, over 191 calls: 98s of model time over 26 generations, and
62s in one plugin method (
wikipedia enrichment.enrichArtist) that no log line had ever attributed.The one limit worth knowing.A job that enqueues another job is two decisions and gets twotraces. They still do — separately scheduled, separately retried, possibly minutes apart on
different workers — but the edge between them is recorded now (2026-08-28, the commit after).
Trace.parentcarries the immediate parent,TracingJobBrokerstamps it into the payload onsendbecause there is no header on a queue row and nothing else travels between two decisions, and both
job bases lift it back off before
executeever sees it.Three things that decided the shape:
scheduleis deliberately not stamped. A cron row is written once at boot and fires forever,so a parent on it would name that boot on every run for as long as it lives. Only
sendlinks.sendcall sites, and a missing edge looks exactly likea root — so a link that depended on remembering would be silently wrong wherever somebody forgot.
job.runspan, which is what makes the edge survive a decision thatcalled nothing. It also gives each job a wall-clock and an outcome, which nothing had before.
And it caught a live bug on the first boot, which is the part worth keeping. pg-boss delivers a
cron job's absent payload as
nullwhile the runner types itPayload | undefined, so'__trace' in nullthrew and tookScheduleTickJob,scrobble.flushand three others down beforeexecutewas reached.== null, not=== undefined: the signature is not evidence about thevalue when the value comes off a queue. Found by watching the log after deploying rather than by
any test, which is the argument for verifying against the running station.
Usage is per attempt and never adds up.
script_historycarriesusage jsonbandduration_msper write attempt, and
/scriptsshows them, which is genuinely more than the other station keepsdurably (it holds two 120-entry rings in memory and says so). What is missing is the aggregate: what
the model cost this week, which binding spends it, how long the speech engine takes per break and how
often it fails over. The speech side records no equivalent at all. This is the surface that makes
station-intelligence §2's budget arguable rather than theoretical: it
is hard to argue about a cap without a number.
Nothing assembles a station check-up. There is
silence.diagnosis.ts, the heartbeat map, theactivity feed, plugin statuses, the audience reading, the analysis backlog and the cache planner's
own answer, and a reader has to visit each one. The other station has a diagnostics sweep that
collects what the system already computes, section by section, and then hands the assembled report to
the model to review, answering with a severity, a summary and a list of priorities each carrying a
suggested fix that maps to a real admin endpoint. Two rules of theirs are the whole design and should
be copied exactly: it adds no new probing infrastructure, only readers of signals that already
exist, and each section is individually guarded, so one dead subsystem degrades its own row
instead of blanking the report. On this tree it would sit naturally beside the silence diagnosis and
would reuse its ordering, which is already causal rather than alphabetical.
The small ones
scrobblecapability,
deadair.scrobble_queueand the send-after-commit design all exist, onlyplugins/lastfmimplements it, and
plugins/musicbrainzalready carries a ListenBrainz client, its types and itsentry on the host allowlist for enrichment. The work is a manifest capability and a submit call, and
the two destinations must fail independently.
.plsand.m3uendpoints. Two static text routes naming the mount. Hardware players, carreceivers and most desktop players take a playlist file rather than a stream URL. This was pointless
while the mount was on the LAN and stopped being pointless when it went behind a tunnel
(listening-loop).
reads and admin actions. Here the contracts already generate a typed client, so the cost is mostly
choosing which verbs an outside agent may reach and how they authenticate, which is
service-actors's question wearing a different hat.
The second pass, and why reading the manual found things reading the source did not
Everything above came from the code. The published operator documentation and the community pages
turned up six more, and they cluster differently because they are the parts of a station that only
show up once somebody has to explain it to an operator: an extension point, a way of looking at the
library, a column on a character, a document with no code in it, and two confirmations of calls
already made here.
An operator can author a KIND of break there, and here only a developer can. A kind is a
directory: a short brief in prose saying what to cover and when to stay quiet, frontmatter carrying a
cooldown, an optional cron and a declaration of which "right now" facts the writer may reference, and
optionally a small module that fetches live data and may decline before any words are written. The
words are still written at air time, around whatever is playing, which is the same split this tree
already makes between a phrasing and a script.
Every piece of that exists here and the operator can reach none of them.
segments.kind,TopicKindRegistry,BreakWriterRegistry,clock_bandsandBreakPromptShapebetween them are thewhole mechanism, and adding a kind of break is a code change in five files. The argument for closing
that is their catalogue rather than their design: a two-line unrhymed poem about the moment, a note on
what taping music off the radio was like, a first-time-I-heard-this memory tied to the host's own
backstory. Not one of those needs code and not one of them would ever have been specified.
Two rules to copy exactly if it lands. The frontmatter's context list is
BreakPromptShapewritten byan operator instead of by a file, so it should BE that type rather than a second vocabulary beside it,
on
topics' own argument: one list to keep straight instead of two and a mapping between them. And akind arrives disabled and is enabled by hand, which is what makes an authored kind and an imported
one the same object with the same guard. Note this is separable from the community exchange, which
stays in the section below: the exchange is distribution, and this is authoring.
Nothing here draws the library as a shape, and the embedding axis is what would make one worth
drawing. Theirs is a full-screen map of every measured record, placed by genre and lit by energy,
recolourable by confidence, loudness, pace or vocal presence, filterable by mood and band, with a
per-record dossier carrying tempo, key, moods, the nearest neighbours in embedding space and a
timeline showing where the intro ends, where the outro starts and where the singing is.
It is listed here rather than as a seventh finding because it is the inspection surface for the axis
above, and the calibration trap there is close to invisible without it. A mood that took 61.7% of a
library is a number in a report and an obvious stain on a map, and recolouring by CONFIDENCE is the
same argument the usage surface makes for the budget: it is hard to argue about a threshold without
seeing what it did. It also has nothing to draw until the axis exists, which is why it is not ranked
on its own.
Chattiness is a property of the character there and a property of the station here.
deadair.personascarriesbrevity, which is how long a break is, andlatitude, which is how muchroom the character is given. How OFTEN it talks is not on the row at all: it is
rotation.breaksandthe format clock, station-wide, so nineteen characters share one setting. Theirs is a five-step
frequency per persona, from silent to relentless. Here that is one nullable column and a term in
BreakPlanner's spacing walk, and it composes with the two already on the row for the reason thosetwo are separate rather than one field: a character that talks over every boundary and one that says
a line an hour are the same character at two settings, where a short break and a licensed one are two
different kinds of claim.
One bound has to be decided before it is built rather than after: whether the quietest setting reaches
silence.
rotation.breaksoff is already the way to stop the station talking, and a persona that canswitch itself off is a second switch that can disagree with the first, with nothing in a log saying
which one held.
A licensing page, which is a document and not a feature. Six questions answered plainly: the
software provides no music rights and is broadcast infrastructure in the way a mixer and a stream
server are; two rights are usually in play, the composition and the recording, administered by
different bodies in each country; non-commercial does not exempt a station, because the rule is about
public performance rather than about money; a private address is materially lower risk than a shared
one; and the ways to be unambiguously clear are original recordings, permissively licensed material,
or the public domain. It also gives an hourly archive a second reason for existing, which is that it
is the record.
That is worth taking because this tree is meant to ship for other people to run, so the question
arrives with the first stranger who runs it, and because it is the only item in this file with a
deadline that is not of our own choosing.
Two confirmations, each of a call already made here. The same model behaves differently through
different providers, because each one translates tools and structured output its own way, which is
strayToolCall's finding reached from the other end and is the reason a model that works is onemeasured on the route it will actually be called through rather than one chosen by name. And their
shelf of third-party players exists because the station serves an unauthenticated now-playing document
beside the mount: that is the cheap half of now-playing-displays and it
sits directly beside the
.plsand.m3uentry in the small ones, since two static text routes andone read-only JSON route are the whole of what makes a hardware player or a car receiver useful.
Their never-play rules take a seasonal window, which never-play-rules
already specifies as
inSeason, down to afrom > tointerval wrapping the year end. Recorded hereonly because a second implementation reaching the same shape independently is evidence the shape is
right, not because anything is missing.
The third pass, and the two things it corrected
Five days after the first two. The station in question shipped two releases in that window, which is
the first finding and the one that governs how the rest of this file should be read: a claim here
about what the other implementation does NOT have goes stale faster than anything else in it. What
it does, and why, holds. What it lacks was true on a Saturday.
A correction: the station check-up IS built here, and this file said it was not.
StationModulesits at the bottom of
modules.tsreading the silence diagnosis, the running order, the library'sstate and the plugin host, and
/checkupin the console draws it. It was written after the firstpass and the survey was never revised, so the section above titled "the three the operator would
feel, none of which exist here" describes two. The two rules that section said to copy exactly were
both kept: it adds no probing infrastructure of its own, and each section is guarded separately.
A bug this tree had, which the survey walked past twice. The other implementation judges a
record's era through a resolver rather than through the raw year, because a reissue's own release
date is untrusted, and it hardened that guard again in this window against anthologies that carry no
compilation flag. Reading that as a feature comparison finds nothing: this tree has an era window,
it has been built for weeks, and never-play-rules even reaches the same seasonal shape
independently. Reading it as a QUESTION about our own data found a live fault.
The mechanism here is not theirs. The year sits on
deadair.tracksand ondeadair.albums, eachwritten at ingest from whatever payload created that row and neither ever overwritten, so a record is
dated by whichever release it was first SEEN through. A track first met on a reissue keeps the
reissue's year for good, while its own album row, filled later by another track off the original, has
it right. All three period surfaces read
coalesce(track, album), preferring the track's claim asthe narrower one, which is wrong in the one direction a period filter cannot afford.
Measured on this station's own library, 766 tracks and 630 albums: 41 records where the two levels
disagree, 33 of them with the track dated later. Every one sampled was a reissue over an original
the album row already had.
All Along the Watchtowerat 2023 againstElectric Ladylandat 1968,Purple Hazeat 1993 againstAre You Experiencedat 1967,Tiny Dancerat 1989 againstMadman Across The Waterat 1971. A station asked for the seventies was refusing its own Hendrix and nothinganywhere said so.
Why it survived a measurement that was already done is the part worth keeping.
MusicTrack.yearin the plugin SDK carries a measured note saying reissue skew is real, rare and not a reason to
distrust the field: of 63 tracks whose title names a remaster year, 61 came through dated to the
original. That measurement is correct and it is about the wrong thing. It looked at what ONE provider
sends for one track, where the fault is the DISAGREEMENT between two rows written at different times
from different payloads, which nobody had queried. A measurement that answers a narrower question
than the one you have is worse than none, because it closes the question.
It is fixed:
modules/shared/release.year.tstakesleast(track, album), the earlier claim, as oneexported fragment the draw, the resolver and the search tool all read. The counterexample is a bogus
LOW claim, and it is bounded where reissue skew is not: one album in that library carries the 1900
floor
usableYearaccepts, so its two tracks now read as 1900 records. A too-early year is a dataerror with a validated floor under it, and a too-late one is the ordinary unmarked shape of every
remaster a provider sells.
era.smoke.tscovers both directions.Three convergences, each one evidence rather than a gap. In the same window the other
implementation aligned its daypart context, spaced artists out on its agent path with name-variant
folding, and shipped a dead-air trim on track edges. The first two are decisions already made here
and reached from the other end. The third is not, and its two rules should be in hand before the beat
layer at rank 2 below is started, because both look like polish and are not: silence has to be
measured against an ABSOLUTE floor rather than the track's own loud level, or a quiet intro reads as
silence and the cut eats music; and a trimmed head moves every timestamp measured from byte zero
while a trimmed tail moves every end-relative one, so the shift belongs in one place that every
consumer resolves through rather than as a local subtraction at each of them.
Two things that are not features, which is why neither earlier pass saw them. Their always-loaded
agent file is 25KB and the reasoning lives in scoped files read on arrival, totalling around 209KB.
This tree had the same content model and the opposite loading strategy: one 153KB file in every
session whatever the question was. Taken, and it is now 10KB with the rest under
docs/internals/and beside the package each part describes. And their merge gate runs drift checks on generated
artifacts, which this tree had no equivalent of despite committing three kinds of generated output.
Taken as the
generatedCI job, which found two things immediately: an absolute path incontractkit.config.jsonthat resolved on exactly one machine, and an SDK that had already driftedfrom its contracts.
The fourth pass, which took the third pass's lesson as its method
2026-08-28, one day after the third. The third pass ended by saying the other implementation is
most useful as a list of questions to ask of this tree and least useful as a list of things to build.
This pass did only that: take each thing the other station GUARDS, ask the same question here, and
measure the answer. Four questions, and two of them came back "no fault, and here is the number",
which is worth as much as the two that found something and is the reason they are recorded rather
than dropped.
The one that found something: the model meter is blank exactly where a cap would need it
script_history.usageis what any budget would be built over, and it is the surface the first passcalled out as "per attempt and never adds up". Measured on this station, 237 write attempts between
2026-08-21 and 2026-08-26:
Two of those blanks are correct and one is not. The 61 deterministic rows made no model call, so
no usage is the honest answer. Of the 58 model declines, 22 were the station refusing what the model
wrote (wrong voice, past the word ceiling, a break about neither record, a spent signature) and every
one of those records its usage properly: the meter works wherever the model answered.
What is left is 13 attempts that provably occupied the model and recorded nothing: 10 of the 36
"the model writer had nothing to say here" declines carry a non-zero
duration_ms, and all 3 failuresdo, at an average of 7,972 ms for a plugin disposed with the response body still open and 60,007 ms for
one that waited out its budget and gave up. That is 7.4% of the 176 model attempts, and the shape of
it is the finding rather than the size: a timeout and a mid-flight disposal are the two ways a
runaway costs money, and they are precisely the two the column cannot see. A cap built on this
today would be least accurate under exactly the conditions that would cause somebody to want one.
The whole-station number that station-intelligence §2 says it lacks is
289,108 tokens over 5.3 days, about 54,400 a day, on a local model with no bill. Recorded there
as well, with what it does and does not change.
The one with no answer here at all: a notebook that only grows
The other implementation added a decay rule to its accumulated per-character continuity, on the
stated grounds that without one a few early ideas become attractors and dominate every later
programme.
deadair.persona_noteshas no equivalent:last_used_atrotates which notes are SHOWN,which is real mitigation and is not the same mechanism, and the only things that ever remove a note
are an operator rejecting or deleting one.
Not measured, and honestly so: there are 4 rows in that table today. The distil pass has barely
run. This is a question to answer before it is a fault to fix, which is the right time to answer it,
and it is written up in personas §6 rather than here.
Two asked, and answered no
Artist name-variant folding, which the third pass recorded as "a decision already made here".
That claim is half right and the other half is worth pinning down, because the mechanism it names does
not exist.
artistKeyfolds the collaboration case by taking the lead artist only, and says why.Nothing folds a leading article:
normalizeKeystrips apostrophes, folds accents and transliterates,and "The Beatles" and "Beatles" would key differently. Measured on this library: 394 artists, 17
whose key begins "the ", and 0 collisions that a leading-article fold would merge. Also measured, and
the more useful half: 0 disagreements between
play_history.artist_keyand the catalog'sartists.artist_keyfor the same aired artist, so the cooldown and the catalog do agree today.The risk is structural rather than live, and the conditions that would make it live are worth naming
so this is not re-measured for nothing: a second provider writing the same act's credit differently,
or a model-named pick entering through a path that keys off the display credit. Both are
PickResolverquestions and neither has arrived. Re-run the two queries above before building anything, not the
survey.
Whether a break's forward claims can go stale between writing and air. The other station has taken
four separate bugs here (a clock link running ahead of air, then behind it, then a wrong meridiem,
then an operator wanting the clock kept out of links entirely). This tree reached the other answer
first:
break.claims.tsgives a phrasing an expiry and checks it before air, the clock words have tocome back verbatim so that they are checkable at all, and
namesWrongTimeOfDayandcontradictsDayPartclose the half that is about the wrong words rather than the wrong time. Noaction, recorded so the question is not re-opened.
Three convergences, and one guard to carry into the beat layer
The other implementation is currently working toward a single arbitrated talk-slot scheduler, after
bugs in which idents aired back-to-back with links and in which one kind of break systematically
starved another.
BreakPlanner.plantis already that: one place computes the wanted slots, and aboundary that already holds a break is walked past on the stated grounds that two breaks back to back
is worse than one break a boundary later. Likewise its provider client learned to stop treating an
error response as audio, which
track.audio.service.tsdoes by mapping content-type through awhitelist and refusing an extension it does not recognise; and its listener gate learned not to close
on a single failed poll, which
AudienceWatchdocuments as the reasonlastReadAtis a separate factfrom the heartbeat.
The guard to carry into rank 2 below, which is new and is not a convergence. Two of that station's
worst analysis bugs were both in the beat layer and both are the naive implementation rather than bad
luck: a tempo detector run without octave correction reports double-time on slow material and scores
the wrong tempo as fully plausible, and a vocal detector run at its default threshold produced enough
false positives to break the cue points that depended on it. track-analysis
already names
beat_confidenceas the load-bearing field a naive build omits, which is the sameinsight from the design end. What these add is that the confidence has to be wrong-tempo-aware
rather than merely present: a number that expresses "this is definitely 140" when the record is 70
is worse than no number, because the ladder is built to trust it.
One operational note for the sidecar, same layer. A long-running Python process that decodes whole
records accumulates resident memory that the allocator does not return, independent of any leak in the
code. Their analysis worker grew unbounded over long uptime for exactly this reason.
analysis/hasthe same shape and has not been watched for it. The cheap answers (trim thresholds, or recycling the
worker every N records) are worth having in hand when the beat layer makes each measurement more
expensive, rather than diagnosed later.
One thing that is not a finding about either station
An upstream library server is reported to be about to renumber every song id, which would invalidate
every binding in
track_sourcesat once. That is not a comparison and it is not deferred design: itis an external deadline with real code under it, and it has its own file at
provider-id-stability.
The fifth pass, where the sharpest question was about this tree's own new code
2026-08-30, two days after the fourth, and the method is unchanged: take what the other
implementation guards, ask the same question here, measure the answer. What is different is the
subject. This tree shipped a capability in the window — weather, as a plugin, a capability, a
source, a floor writer and a model writer — and the best question this pass had was not about the
other station at all. It was whether a capability built AFTER a guard inherits it.
It did not.
A new claim, and nothing expires it
The other station has spent four separate bugs on the same lesson: a break is written before it airs,
so anything it asserts about the present has to survive the gap. This tree reached that first and
better.
break.claims.tsgives a phrasing a window,BreakPlanner.ripenrewrites what has drifted,toPlayerItemsdrops what cannot be rewritten in time, and the two callers share one predicateprecisely so they cannot disagree.
brokenClaimknows two kinds of claim,itemandtime, and a weather reading is a third thatnobody added. The
timekind is only the clock PHRASING:claims_time_from/claims_time_untilcome from
roughTime, so what is protected is "it's just after nine" and not "it's raining".The rest of the weather path is careful, which is what makes the gap easy to miss:
WeatherServicerefuses a reading with noobservedAt(weather.service.ts:131), so the ageof the observation is known to be present.
SpokenWeather(weather.words.ts:32), and handed to the model's own tool(
weather.tool.ts:178).inventedFigureinmodel.weather.break.writer.tsrefuses any digit the reading did not contain,on the excellent argument that a forecast's claims ARE its numbers and the true set is known
exactly.
So the numbers cannot be fabricated. They can only be out of date, and
inventedFigurevalidatesagainst the reading the script was written from rather than against the weather at air, which is the
one comparison it cannot make.
The window, measured.
WRITE_AHEADis 8 items (break.planner.ts:79), and that file states thatat the default quarter-hour break interval the window holds two or three breaks. A weather break is
therefore written up to eight records ahead of its slot, and the staleness at air is that plus the
provider's own observation age, which for a national service can be most of an hour before this tree
ever sees it.
The evidence that this is an omission rather than a decision is one line above it.
write.break.job.tsreasons about air time everywhere: the clock at 234, the greeting at 238, thedaypart at 242, and
bulletin.storiesFor(segment.kind, context, segment.airsAt ?? Date.now())at273, where the news source is told when the break will air. Line 278 is
weather.readingFor(segment.kind, context). It is the only call in that file that does not know.And the pattern is already in the tree, one module over.
rundown.tsholds anobservedAtonthe playhead and decays the reading by its own age on every read:
remainingMs - (Date.now() - observedAt). That is exactly the arithmetic the weather path has theinputs for and does not do.
This is the same SHAPE as the era bug the third pass found: the data was present, the column was
right, and the question was never asked of it. A capability that arrives after a guard does not
inherit it, and nothing in the tree fails when it doesn't.
Two convergences that sharpen a section written two days ago
The other implementation closed its continuity-decay issue in this window, and its reasoning is worth
more than its fix, which is not in the issue. Two sentences from it:
"Negative prompting is not a forget mechanism." An "already said, do not repeat" block makes a
topic MORE salient rather than less. This tree reached the same conclusion independently and answered
it better:
break.prompt.tsbans a spent signature and in the same breath invites a replacement,because "told only what it may not say, a model reaches for the nearest other thing the sheet gave
it" — and
characterFaultrefuses a script that ignores the ban, so the ban is enforced rather thanmerely requested. No action.
"The recency store has no scoping boundary that matches the product's actual boundaries." Their
proposed direction is three tiers: soft decay, boundary reset, and an operator's hard purge. That
framing is the useful part, and read against this tree it says something personas
§6 did not, because §6 was written two days ago with three candidate rules that were all decay:
BreakWriteRequest.recentis theBROADCAST's memory and is kind-agnostic, on the argument that a listener who tuned in twenty minutes
ago has heard this show and none of the one before it. A station that has just gone on air inherits
no ban from a programme nobody heard.
persona_notesexists tooutlive a broadcast: a character that accumulates is the feature. Boundary reset would delete the
thing the store is for. So the notebook wants soft decay specifically, and §6 is now explicit about
which tier it is choosing and why the neighbouring one is wrong for it.
A guard that is absent rather than unnecessary, measured at 0
The other station has taken at least five bugs on reading non-Latin text aloud: a phonemizer routing
every language through one engine, kanji read as character descriptions, a language code that needed a
region, and mixed-script track names. It has now moved to native phonemizers per script.
Measured on this library: 0 of 766 tracks carry CJK, Cyrillic, Hebrew or Arabic in a title or a
credit. 15 carry extended-Latin accents and they are almost all decorative metal umlauts (
Mötley Crüe,Blue Öyster Cult), which is a pronunciation question and not a phonemizer one, anddeadair.pronunciationsis already the seam for it.So none of that class is live here, and the reason is the library rather than the code. That is
exposure deferred, not exposure avoided, and it is worth recording in that form because this tree is
meant to ship for other people to run: the first operator with a Japanese or Russian library meets all
five at once, on a speech path that has never been asked to read a non-Latin character. Not worth
building against today. Worth knowing that the 0 is a fact about this operator's records.
Nothing in the window is a feature gap
Two releases in the window. What is in them is the settings surface, native lock-screen presence,
first-class station credentials, a max-listeners setting, on-demand jingles, and front-padding a
listener request's intro. Of those, the listener-facing half is already recorded below as deliberately
not wanted, on-demand jingles are
deadair.padsreached from another direction, and a max-listenerscap is the only one that is both absent and arguably wanted here, now that the mount is public. It is a
small one and is noted with the others.
The sixth pass, which asked one question and got a whole design back
2026-09-02, and the method is narrower than the previous five: rather than reading the other
implementation end to end again, take ONE deferred file this tree is about to build and ask whether
the other station has already built it. The file was track-lyrics. It had.
The finding is that the phase that file is worth building for asks the wrong question. This tree's
design wanted one number from a synced lyric, the first timestamp, as a vocal onset for the talk-up
limit. The working implementation derives vocal RANGES across the whole track instead: a line runs
until the next one, capped so a long gap reads as an instrumental break rather than sustained singing;
lines closer together than a merge gap join; the last line takes a nominal tail. The first range's
start is the onset this tree wanted. The end of the last range is the point the singing stops, and
nothing else in this tree can answer it. That second marker is free, it is the other half of a
talk-up, and this tree's design had it as a footnote.
Three smaller things came with it, each of which would have been paid for here in a bug:
with unsynced plain text, which has lyrics and no timings and is neither an answer nor an
instrumental. Folded into a
min()it reads as zero.surfaces as a line, and a lone "Instrumental" placeholder body is common. The test has to be
anchored to the whole line or a song that sings the word gets classified as having none.
call flattens structured lyrics to text and drops the per-line timings. There is a by-song-id call
that preserves them; a track can carry several structured entries so the synced one has to be
chosen rather than the first; and each entry's global offset has a counter-intuitive sign, where
positive means the lyrics appear sooner.
One thing was deliberately NOT taken, and the disagreement is the useful part. The other
implementation uses the lyric to REPLACE its vocal detector rather than to narrow it, on a measured
argument: source separation never separates cleanly, an instrumental's vocal stem carries enough bleed
that a self-relative energy gate reads it as singing, and that produced a false positive across a
library. A typed timestamp beats that. But this tree measured its own inputs on the same day and got a
different answer: across 52 synced lyrics for records this station actually holds, 12 first lines land
under three seconds and 6 under one. Their conclusion is right about their inputs and ours is right
about ours. Both passes were measurements against a real catalogue and they disagree, which is the
argument for measuring rather than for either answer.
The convergence that names both markers
Neither station invented this. Broadcast automation has stored the vocal-start cue as an editable
per-track marker for decades, beside outro, fade-out and start-next markers, and at least one system
places its talk markers in PAIRS and will not accept one alone. That is the same conclusion the sixth
pass reached from the other direction, and it carries a third consequence neither station's code shows:
the pair is expected to be operator-editable. The other station has already paid for the guard
that needs, on a different column entirely: every walk revisits every track, so an override an
automatic writer can clobber is one the next pass silently undoes, and the refusal belongs in the
writer's own WHERE clause. This tree has the same lesson one subsystem over, in the schedule's manual
takeover, and nowhere in the catalog.
One use of a lyric this tree had not listed at all
The other implementation puts a capped lyric excerpt into the text it embeds for similarity, beside
tags, measured acoustics and an era word. Its stated reason is better than the feature: with every
optional line empty the vector IS the label, so similarity ranks by artist and album wording, a
prolific artist self-clusters, and two unrelated artists whose names share a word land beside each
other while the caller believes it is reading mood. That is a sharper statement of the embedding-axis
trap the fourth pass recorded, and it names a cheap input that breaks the degeneracy. Recorded in
track-lyrics's uses table, unscoped, for whoever builds that axis.
Continued below (2 of 2).
All reactions