Forum teuk.org

MB748 — Quote Readers Cross the Reversible Pensieve Door 🧠🚪

in Mediabot · started by TeuK · yesterday

TeuK · yesterday

Mediabot’s Plugin API v3 has moved from safely consulting the quote archive to serving real historical commands through that guarded boundary.

MB747 created the read-only data.quotes.read facade. MB748 now uses it to migrate the first three database-backed public commands:

  • quotecount;
  • topquote;
  • halloffame.

The migration is deliberately narrow, channel-scoped and reversible. The old handlers remain available as an exact fallback, API v3 stays inactive by default, and no quote write operation has crossed the boundary.

The completed change was committed and pushed as:

294f0c023ae30e94e6ddbefa3b651d65d0d88d2c
MB748: lead quote readers through a reversible Pensieve door 🧠🚪

From a safe data facade to a real product migration

MB747 answered an architectural question: can a plugin consult Mediabot’s application data without receiving SQL, a database handle, a bot object or a caller-selected channel?

Its answer was the capability-scoped data.quotes.read service. The core owns the prepared queries, derives the channel from the current invocation and returns bounded, detached QuoteRecordV3 values.

MB748 answers the next question: can existing users be moved through that new path without changing what they see and without burning the bridge behind them?

The new first-party package quotes-v3 provides that proof. It is a real API v3 package with a strict manifest, a small Perl entrypoint and exactly three declared capabilities:

data.quotes.read
irc.reply
irc.notice

It contributes only the three pure-read commands whose behaviour can be expressed completely through the approved facade. It does not receive broader database privileges merely because all quote commands historically lived near one another.

Why only three commands moved

quotecount, topquote and halloffame read quote data and render a result. They do not need to mutate a row.

The historical q and quote commands are different. Their dispatch combines several responsibilities, including:

  • adding a quote;
  • deleting a quote;
  • selecting or recalling a quote;
  • incrementing its recall counter;
  • applying the associated authorization rules.

Moving those commands under a read capability would make the permission name misleading and quietly weaken the boundary. MB748 therefore leaves them in the core unchanged.

Quote writes will require their own explicit capability, authorization model, audit rules, parity tests and rollback decision. The roadmap advances one defensible boundary at a time rather than hiding a write key inside a read-only spell.

One migration bridge, three operating modes

Each migrated command declares the established legacy-public-fallback migration mode. That gives the operator three clear per-channel states:

Mode API v3 execution Visible answer Historical path
off No Historical Authoritative
observe Yes, silently Historical Authoritative
on Yes API v3 Suppressed for that command

observe is the key to safe migration. The new implementation performs its bounded read, but its IRC output sink is suppressed. Users continue to see the historical answer while operators inspect API v3 call counters, errors and runtime health.

Only after parity has been demonstrated does on make the package authoritative for that channel. Other channels keep their own independent policy.

Disabling or unloading the package restores the exact registry handler references captured before the migration. Rollback does not require a source deployment, a database restoration or a service restart.

Preserving the historical author-prefix contract

The original quotecount <nick> behaviour treats the supplied nickname as an author prefix. The MB747 facade initially offered a bounded count operation, but MB748 had to preserve this detail without exposing a generic query interface.

The core service now accepts one strictly validated author matching mode:

exact
prefix

In prefix mode, Mediabot:

  1. validates and bounds the author value to 256 encoded bytes;
  2. lowercases it according to the existing command contract;
  3. escapes SQL wildcard characters so user input stays literal;
  4. appends the one trailing wildcard owned by the core;
  5. executes the prepared, channel-scoped count query.

A plugin still cannot submit SQL, choose a table, supply another channel or turn % and _ into arbitrary search patterns. The facade grew by one named domain behaviour, not by opening a disguised database console.

Familiar IRC output behind a new boundary

The migration preserves the existing user-facing contracts:

  • total quote counts for the current channel;
  • author-prefix counts;
  • one-to-ten ranking limits;
  • quote identifiers and authors;
  • recall counts with the historical singular/plural wording;
  • the established empty-channel guidance;
  • halloffame as an alias of topquote.

API v3 still enforces its 400-byte IRC output boundary. Long UTF-8 quote text is shortened safely when necessary, without cutting a multibyte character in half or bypassing Mediabot’s normal wire-safety path.

The plugin receives detached quote records and produces bounded text. It never sees DBI, raw result rows, the IRC socket or the full Mediabot object.

The live pilot on #teuk

The development instance provided a useful real archive for the supervised pilot: 13 quotes on #teuk.

The first Partyline configuration intentionally followed the documentation and placed quotes-v3 in observe on #test. The IRC commands, however, were run on #teuk. They produced the familiar historical answers, while every API v3 counter remained at zero.

That apparent mismatch was valuable evidence. It showed that enabling a package globally does not let it escape its channel policy. A policy for #test had no effect on #teuk, exactly as designed.

The pilot was then aligned with the channel containing the real data:

.plugins policy quotes-v3 #test off
.plugins policy quotes-v3 #teuk observe

The following commands were exercised on #teuk:

#quotecount
#quotecount Te
#topquote 3
#halloffame 3

The visible historical results were:

#teuk: 13 quote(s) total
Te: 13 quote(s) on #teuk
Hall of fame #teuk — most recalled quotes:
  1. [id:46] <teuk> et hop un ptit coup d'yop (0 recalls)
  2. [id:42] <teuk> inspecteur gadget (0 recalls)
  3. [id:41] <teuk> c'est pas possible (0 recalls)

After that observe pass, Partyline reported the exact expected shadow-call counts:

halloffame calls=1
quotecount calls=2
topquote calls=1

The policy was then changed only for #teuk:

.plugins policy quotes-v3 #teuk on

Repeating the same four commands produced the same IRC display, once per command, with no legacy duplicate. The cumulative counters became:

halloffame calls=2
quotecount calls=4
topquote calls=2

This proves all three pieces of the migration contract together:

  • observe really executes the v3 path while legacy remains visible;
  • on makes v3 authoritative without changing the user experience;
  • policy remains isolated to the explicitly selected channel.

Nothing else moved with it

The deployment and live pilot made no private configuration change and no database schema change. They inserted, deleted and updated no quote row, and they did not increment recall counters merely to compare implementations.

The package is still never loaded at startup. Discovery only reads its strict manifest. An authenticated Owner must explicitly:

  1. load it with the three approved capabilities;
  2. configure a channel policy;
  3. enable the package;
  4. move that channel from observe to on after inspection.

There is no automatic channel opt-in and no remote plugin installation path.

Rollback remains deliberately boring

The fastest rollback affects one channel:

.plugins policy quotes-v3 #teuk off

The package can also be stopped or completely removed from the runtime:

.plugins disable quotes-v3
.plugins unload quotes-v3

off returns visible command handling to legacy for that channel. Disable stops the package globally. Unload removes its mounted adapters and restores the exact handler objects that existed before loading.

No quote data needs repair because the migrated path never writes it.

Layered validation before publication

MB748 passed its development gates without requiring a corrective round. The package also kept the architectural sentinel updated for the new migration milestone and its complete executable-capability list.

The development validation passed:

Gate Result
Targeted MB748 suite 309 / 309 tests passed
Fast validation lane 7,439 / 7,439 tests passed
Live observe / on pilot Passed on #teuk
Final full suite 19,766 / 19,766 tests passed
Full-suite duration 140 seconds
Files committed 20
Diff 688 insertions, 26 deletions

The final gate checked the exact Git surface, the combined content digest, trailing whitespace and terminal line endings before starting the full suite. It then staged exactly the expected 20 files and recorded this immutable staged digest:

dc6a6c993ff7cce17cb94b7303168f481abfb994dde72828daffb0926c828872

The full suite ran once, immediately before commit, with progress visible in the terminal. The post-full freeze verified that no source byte, service process or staged change had moved during those 140 seconds.

The commit was pushed to origin/master; the repository finished clean and the development service remained active without a restart.

Where the plugin roadmap now stands

The path opened at MB740 has now crossed an important line:

  • MB740 mapped the architecture and froze the API v2 contract;
  • MB741 established one authoritative command catalogue;
  • MB742 introduced strict API v3 packages, capabilities and lifecycle;
  • MB743 added versioned events, bounded queues and shared jobs;
  • MB744 added typed configuration and channel policies;
  • MB745 proved reversible migration with the playful command pack;
  • MB746 added shared HTTPS and namespaced compare-and-swap storage;
  • MB747 opened the first guarded, read-only domain-data facade;
  • MB748 moved real database-backed commands through that facade.

The architecture is no longer merely prepared for migration. It has now performed one against live historical data while preserving output, channel isolation and immediate rollback.

The remaining compatibility paths can be retired only when their own scope has the same evidence. In particular, quote writes remain a separate future decision. MB748 provides no excuse to rush them through a door designed for readers.

The Pensieve door stays reversible ✨

MB748 is a modest visible change and a substantial architectural one. Users still ask for quote counts and rankings in the same way, and Mediabot still answers with the same memories. Behind the curtain, those commands now travel through an explicit capability, a channel-scoped policy and a bounded domain service.

That is the kind of refactoring worth trusting: the castle gains a new passage, but the old corridor remains mapped, guarded and one command away from being restored.

The quote readers have crossed the Pensieve door. The door still opens both ways. 🧠🚪🪄

Commit: 294f0c023ae30e94e6ddbefa3b651d65d0d88d2c

You must be logged in to reply.