Mediabot MB863–MB873 · 20–21 October 2026 · Correct link identities, dependable HTTP, usable AI replies and a faster complete test suite.
The previous chapter, The Clockwork Behind the Common Room, ended with MB862 and a complete link beside every clipped DTC quote. Eleven maintenance rounds have followed.
Their common thread is correspondence: a preview should belong to the link that was posted, cached data should belong to the resource that was requested, and a successful response should contain something usable. Alongside those corrections, the complete test suite has shed a substantial amount of waiting.
The latest validation on teuk.org passed 28,776 assertions across 1,172 test files in 175 seconds. These eleven rounds introduce twenty regression files containing 1,527 assertions. All 37 source security invariants pass, and the startup integrity check succeeds.
The work remains on the 3.6dev development line. The database schema is unchanged. 📜
A YouTube link can contain several things that look like a video identifier: its actual selector, another URL inside a query parameter, or text after a fragment marker. The old selector could pick a nested identity or accept a prefix of an invalid, overlong identifier.
That could make a preview describe a different video from the one selected by the outer URL.
MB863 makes the resource boundary explicit. A supported watch URL supplies its video through its own v parameter. A supported short, Shorts, live or embed URL supplies it through the appropriate complete path segment. The identifier must have exactly eleven supported characters.
For example:
https://youtube.com/watch?v=AbCdEfGh123#part&v=ZyXwVuTs987
The selected video is AbCdEfGh123. A second apparent selector in the fragment cannot replace it. Duplicate watch selectors are refused, as are paths merely beginning with watch, unsupported hosts and identifiers with extra trailing characters.
The shared selection rule reaches URL routing, public metadata and radio metadata. Rejected identities are stopped before cache lookup or metadata requests. Supported URL families retain their ordinary query and fragment suffixes.
The Marauder’s Map now follows the person actually named on the parchment.
Lowercasing a hostname is useful for cache identity. Lowercasing an opaque resource identifier can merge two different resources.
Several layers still did the latter, producing distinct failures at different stages of a preview.
MB864 fixes admission and Instagram ownership. The shared channel repeat gate now folds only the URL scheme and host. Resource paths, query values and fragments retain their spelling. Two links differing only in a case-sensitive resource component can therefore each receive their first display.
Instagram applies that identity to its metadata cache and pending requests too. A newly admitted post no longer receives another post’s cached title or joins its worker merely because their identifiers differ by letter case.
MB865 fixes Spotify’s pending map. Its separate pending key still lowercased the resource after the shared gate had accepted it. Two distinct requests could collapse into one worker, losing the second reply.
Tracks, albums, playlists, episodes, shows and artists now retain independent pending ownership when their resource IDs differ. Out-of-order completion, a failed sibling request and a later retry release only their own pending entries. Existing duplicate coalescing and tracking-query removal keep working.
MB871 completes the same correction for Facebook and X/Twitter metadata caches. Their dedicated handlers still lowercased entire cache keys, allowing the second link to pass admission and then receive the first link’s title.
The handlers now use the existing case-preserving URL identity. Facebook retains its established URL normalization; X/Twitter retains its existing cache distinctions between host aliases and HTTP/HTTPS. The URLs passed to the HTTP or browser paths are not rewritten by the cache key.
Across these corrections, scheme/host case variations still reuse the appropriate identity. Channel repeat suppression, ordinary duplicate handling and cache expiry remain in place. A replay keeps the current caller and destination.
The portraits may look alike. They still need separate names. 🪄
A successful request does not guarantee the expected JSON structure.
MB866 adds an object-root guard to the YouTube metadata reader used by radio. An array or boolean response now yields unavailable metadata instead of a hash-reference exception. Valid title and duration formatting remains available.
MB867 extends that protection to public YouTube previews and hardens the search response path. Search entries must have the required object structure and an exact scalar video ID. Unexpected optional metadata containers are contained, and reference-valued titles cannot leak Perl strings such as ARRAY(0x...) into IRC output.
The search result limit also becomes an actual delivery limit. Previously, requesting a maximum from the API did not stop an oversized or duplicate response from producing too many messages, including on cache replay.
Search now admits distinct video IDs in response order and stops at the configured one-to-five result limit. The heading counts admitted results, and metadata lookup follows those selected identities rather than accepting unrelated entries from the metadata response.
The correction protects both the first reply and the cached batch. A malformed entry cannot turn the rest of a usable search into a crashed command.
MB867 also repairs IPv6 selection in the RSS and plugin HTTP requesters.
Their preferred-peer list used two adjacent Perl grep expressions without separating their input lists correctly. The IPv4 filter consumed the IPv6 list as well, removing every IPv6 address. An IPv6-only destination received no connection attempt, and an eligible IPv6 peer could not rescue a failed IPv4 transport.
Parenthesizing the two filters restores both address families while keeping IPv4 preference and resolver order within each family. The existing public-address checks, validated peer pinning, TLS verification and retry bounds remain active. Transport failures can try another admitted peer; an ordinary HTTP error is not turned into an extra transport retry.
MB868 repairs the plugin service’s relative redirects. Concatenating strings cannot reliably resolve ../, ./ or query-only locations. The requester now resolves them with URI path and query semantics, including an HTTPS URL with a query but no explicit root path.
Each resulting hop still passes the existing HTTPS, port, credential, DNS, public-address, peer and redirect-budget checks. Resource case and encoded path segments are preserved.
A corridor can lead around a corner without bypassing the castle’s existing doors.
RSS conditional requests use ETag and Last-Modified validators to ask whether a previously accepted document has changed. The repository treated missing validators the same way after a new body and after a 304 Not Modified response.
That could leave an old validator attached to a newly accepted document whose server no longer supplied it.
The lifecycle now distinguishes the two cases:
| Poll result | Validator treatment |
|---|---|
| New accepted body | Replace validators, clearing fields absent from that response |
304 Not Modified |
Keep existing validators when the response omits them |
| Failed poll | Retain the validators of the previously accepted document |
The correction uses the existing database columns. Regression coverage includes real in-memory SQL and successive fetch/revalidation cycles, so the stored state and the next outgoing conditional request are checked together.
The wax seal should describe the letter currently in the envelope.
The full suite was spending time on repeated setup and idle fixture waits.
Every launcher invocation classified every test source, even when a filename filter selected one file and no capability classification was needed. Tests that launched filtered runners multiplied that work. Two already mocked CPAN installer fixtures also incurred real spinner sleeps, while local network-statistics fixtures waited through an unnecessarily long idle server shutdown poll.
MB869 changes those specific costs:
The complete catalogue still runs in lexical order with the existing per-file isolation rules. Mandatory fast-lane sentinel existence is checked against the whole catalogue. Classification reflects the current source on each invocation. Results are never reused from an earlier run.
The ordinary command remains:
perl t/test_commands.pl --progress
The installation runs on the same development host reported:
| Checkpoint | Files | Passed assertions | Elapsed time |
|---|---|---|---|
| MB868, before the optimization | 1,163 | 28,165 | 218 s |
| MB869, after the optimization | 1,164 | 28,202 | 170 s |
| Latest MB873 validation | 1,172 | 28,776 | 175 s |
The observed MB869 reduction is 48 seconds, about 22% less elapsed time, while adding coverage. These are reported host runs, not a repeated benchmark distribution. The later 175-second result belongs to a larger suite.
Profiling remains available for the next investigation:
perl t/test_commands.pl --progress --profile --profile-top 20
The Time-Turner stays on the shelf. Removing unnecessary work was enough. ⌛
The AI response boundary had two separate problems.
First, malformed text fields could be accepted as answers. Anthropic could stringify a reference-valued text block; the OpenAI chat adapter could return a container as content. Whitespace-only answers also counted as success, preventing an otherwise eligible automatic fallback.
Adapters now reject those unusable fields, and the common client requires a plain scalar containing non-whitespace text. A scalar 0 remains valid. Invalid successful bodies become parse errors, allowing the existing automatic fallback policy to proceed where permitted.
OpenAI diagnostic and citation-title fields receive corresponding scalar guards. Valid citation URLs and usable text blocks remain available.
Second, the async worker’s watchdog covered only the longest planned HTTP attempt plus three seconds. The plan executes attempts sequentially. A slow first attempt could therefore consume the worker’s lifetime before its fallback received its own allowed budget.
The watchdog now covers the sum of planned attempt budgets plus the same three-second margin. Two eligible OpenAI attempts of twenty seconds each receive a 43-second worker envelope rather than 23 seconds. A configured auto plan of 30 + 20 + 20 + 30 seconds receives 103 seconds rather than 33.
These are maximum envelopes. A successful attempt returns immediately. Individual HTTP timeouts are unchanged, and Gemini’s internal retry is counted once because it already shares one attempt deadline.
During a provider outage, a worker can consequently remain alive longer to finish its already authorized fallbacks. Explicit-provider requests remain strict: choosing one provider does not authorize sending the request to another company.
The tests exercise actual planning, adapter parsing, fallback sequencing and terminal delivery. Budget checks are deterministic; they do not wait through a real 103-second outage.
The weather handler replaced spaces inside compound city names with commas. New York became New,York; Salt Lake City became Salt,Lake,City. The changed location reached the provider, cache and audit log, and the existing comparison could not find the multiword city under its actual name.
The extra substitution is removed. The existing byte-safe URL encoder sends New York as New%20York, including correct handling of accented character strings and IRC UTF-8 bytes. Explicit comma qualifiers, coordinates and the historical terminal City CC shortcut retain their existing behavior.
The weather cache also declared a 200-entry limit without actually enforcing the count. Its cleanup removed only entries older than fifteen minutes, so a burst of recent locations could grow the map beyond the limit.
After a successful write exceeds capacity, cleanup now removes expired entries first and then the oldest excess entries, keeping the response just fetched. Ties use a deterministic key order.
Fresh data remains reusable for three minutes. A provider failure can still replay retained data up to fifteen minutes old with the existing cached marker. A failed refresh does not renew its timestamp. An evicted location needs a fresh fetch if requested again, and its old data is no longer available as outage fallback.
Four-second HTTP requests, verified TLS, the configured User-Agent and channel guards remain in place. Meteolojinx Recanto finally leaves the city’s name intact. 🪄
The DTC search parser looked for quote-shaped substrings anywhere in HTML. A foreign URL, nested destination, script or comment could contribute a number. Zero, leading-zero and overlong IDs could occupy results even though the detail fetcher would reject them.
Those false matches could also stop the search plan early. Separately, direct links were collected before encoded DuckDuckGo targets, allowing a lower direct hit to precede the first usable wrapped result.
The parser now reads actual anchor targets with HTML::TreeBuilder, already a dependency of the same module. A complete supported DTC quote URL must yield a valid canonical identity. Supported DuckDuckGo wrappers contribute exactly one target, decoded once and checked by the same rule.
One document-order pass keeps direct and wrapped results together. Invalid entries and duplicates do not consume the existing twenty-result parser bound, and the source still returns at most five results. A page without usable targets allows the existing narrow, lite and DTC-site search sequence to continue.
Both !dtc and !bashfr then fetch the canonical detail URL of the first selected result. The existing final-page identity checks remain active, and quote formatting and clipping keep their established behavior.
The old parser did not directly fetch the foreign host merely because its URL contained a quote path. It could instead extract that number and fetch the wrong canonical DTC quote. This correction fixes that selection error.
Regressions follow both real public handlers through native command workers, pipes, detail parsing and parent replay. They check the search heading, selected detail URL, matching quote and requesting channel, plus refusal of a detail page carrying the wrong canonical identity.
The first story on the reading list should be the first usable story on the page.
The twenty new regression files cover all eleven rounds:
| Round | Main correction | New assertions |
|---|---|---|
| MB863 | Exact YouTube video selection | 234 |
| MB864 | URL admission and Instagram identity | 116 |
| MB865 | Independent Spotify pending resources | 126 |
| MB866 | YouTube metadata root guard | 27 |
| MB867 | Public metadata/search and IPv6 recovery | 244 |
| MB868 | Relative redirects and RSS validators | 174 |
| MB869 | Test runner setup and fixture waits | 36 |
| MB870 | Usable AI replies and fallback budgets | 139 |
| MB871 | Facebook and X cache identity | 175 |
| MB872 | Weather location and cache bounds | 170 |
| MB873 | Exact DTC search links and order | 86 |
| Total | 20 regression files | 1,527 |
The latest full host result is 28,776 / 28,776 assertions in 1,172 files, with all 37 security invariants and the startup integrity check passing.
Qualification distinguishes controlled offline reproduction from live service behavior. New tests use local responses and fixtures, including real SQL, timers or worker pipes where relevant; they do not claim that an external platform’s current public markup has been verified. Local container runs also retain a pre-existing updater-rehearsal failure in their reports. No test was excluded to conceal it; the complete target-host suite passed.
Maintenance packages check their expected source before writing, keep recovery copies, and restore their own application attempt when a required validation fails. Source acceptance and production rollout remain separate checkpoints. This article records the maintenance and reported development-host results, without claiming a new production deployment or stable release.
From video selectors to search results, from RSS seals to worker deadlines, the castle now keeps a closer match between a request and its reply. And checking the whole building takes less time than it did at the start of these rounds. 🦉
You must be logged in to reply.