Mediabot’s Plugin API v3 can now consult real application data without being given the keys to the database.
MB747 introduces the first approved domain-data capability:
data.quotes.read. It gives future plugins a small, explicit and guarded view
of the quote archive while keeping SQL, database handles, bot internals and
cross-channel access firmly on the other side of the castle door.
The change was committed and pushed as:
2542001cdcaa70b05cb4a247771a75860db33874
MB747: let plugins consult the read-only Pensieve of quotes 🧠🔒
This is an architectural milestone rather than a visible IRC feature. No quote command has moved yet, no plugin has been activated, and no channel policy has changed. What MB747 provides is the safe foundation on which that migration can now be built.
The first API v3 plugins proved the runtime itself:
off, observe and on policies;That was enough for self-contained features, but not for plugins that need to work with Mediabot’s established community memory. Quotes are stored in the main MariaDB database and belong to a specific IRC channel.
The tempting shortcut would have been to pass a database handle to plugin code and let each plugin execute its own queries. That would immediately weaken the whole API v3 model. A plugin could accidentally read another channel, issue an unbounded query, depend on private schema details, retain a stale connection or perform a write that was never part of its advertised permissions.
MB747 deliberately refuses that shortcut.
Plugins may request the capability data.quotes.read. If the manifest requests
it and the operator explicitly grants it, PluginContext exposes six named
operations:
| Operation | Purpose |
|---|---|
quote_by_id |
Retrieve one quote by its numeric identifier |
quote_random |
Retrieve one random quote from the current channel |
quote_search |
Search quote text using bounded literal words |
quotes_by_author |
Retrieve recent quotes attributed to one nickname |
quote_count |
Count all quotes, or those from one author |
top_quotes |
Retrieve the most recalled quotes for the channel |
These are application-level operations, not generic query primitives. The plugin cannot submit SQL, choose a table, supply an arbitrary channel or obtain the underlying MariaDB handle.
The core remains the only component that understands the database schema.
Channel confinement is one of the most important parts of this milestone.
Every quote read receives the current API v3 invocation. Mediabot derives the channel from that invocation’s current policy and injects it into the approved operation. There is no plugin argument that can replace it with another channel.
In practical terms, a command invoked on one channel cannot use this capability to inspect the quote archive of another channel. The boundary is enforced by the core rather than left to plugin authors to remember.
The service also resolves Mediabot’s current database handle for every operation. If the core reconnects to MariaDB, plugins do not remain attached to an obsolete connection.
All six operations use core-owned prepared statements. Inputs are validated before a query is prepared or executed:
%, _ and the escape character are treated literally in searches;The random operation first obtains a bounded channel-local count, chooses an offset, and then fetches exactly one ordered record. The service never uses an unbounded result set merely to select a random item.
Database errors are contained by the core. A plugin receives a neutral result:
{ ok => 0, error => "unavailable" }
Raw SQL, driver diagnostics and connection details do not cross the boundary. The failure is still logged and counted by Mediabot for operators.
Successful reads return Mediabot::Plugin::QuoteRecordV3 objects containing
only these fields:
id
text
author
author_id
created_at
hits
Each value is validated, bounded and detached from the database row. The record exposes readers and can produce a copied hash, but plugin code cannot modify the internal value or use it as a hidden path back to the database.
This makes the contract stable even if the physical schema evolves later. Plugins depend on a small domain object, not on column names and joins.
MB747 contains no operation to:
Even recalling a quote through the new facade leaves its hits value
unchanged. That detail matters: observe mode must be able to compare a future
plugin with historical behaviour without creating side effects merely because
the shadow implementation ran.
Reads are therefore permitted in observe, while off remains completely
inert. Visible output is still suppressed by the existing channel-policy
boundary.
MB747 does not ship a new quote plugin and does not migrate an existing quote command. It only introduces the capability and the core service.
There is consequently no Partyline pilot to perform for this milestone:
observe or on;Historical quote commands continue to work through their existing path.
That separation is intentional. The data boundary can be reviewed and tested independently before any user-visible command starts relying on it.
The development deployment ran a live, read-only MariaDB smoke test against
#test:
QUOTE_DATA_SMOKE=PASS count=0 top_records=0
An empty archive is a valid result, so this also exercised the neutral zero-record path. Two real reads completed without an insert, update, delete or recall-counter change.
The validation sequence then completed successfully:
| Gate | Result |
|---|---|
| Targeted MB747 suite | 1,257 / 1,257 tests passed |
| Fast validation lane | 7,411 / 7,411 tests passed |
| Final full suite | 19,721 / 19,721 tests passed |
| Full-suite duration | 141 seconds |
| Files committed | 20 |
| Diff | 839 insertions, 26 deletions |
The final gate staged exactly the expected files, recorded the staged diff digest, ran the full suite once with visible progress, and confirmed afterward that neither source bytes nor the running process had changed during the test.
The service remained active with zero restarts, the development repository was
clean after the push, and local master matched origin/master.
The first MB747 application round exposed a stale architectural sentinel. The machine contract had correctly advanced to MB747 and eight executable capabilities, while one older test still expected MB746 and seven.
The deployment script stopped during targeted tests and restored the complete MB746 source automatically. No service restart, configuration change or database change had occurred. The sentinel was then updated, added to the protected MB747 surface and validated again in the R2 package.
That is exactly what the layered validation process is meant to do: targeted tests catch the local contract drift early, the fast lane checks a broad slice of the system, and the full suite remains the single final gate before commit.
Quotes are the first domain chosen because they are useful, channel-scoped and easy to express through a small read contract. The more important result is the pattern now established for future application data:
plugin request
→ capability check
→ current channel policy
→ named domain operation
→ validated inputs
→ core-owned prepared query
→ detached bounded result
Future plugins should not need direct database access. They can receive the specific operations they need, with the same controls for scope, limits, policy, observability and failure handling.
This keeps Plugin API v3 extensible without turning “extensible” into “trusted with everything.”
The next logical step is a separate quote plugin built on this facade. It can migrate selected historical commands through the reversible fallback already proved by MB745:
observe and compare results with the historical path;on only after parity is demonstrated;Write operations should remain a different milestone with their own explicit capabilities, policy and audit rules. MB747 does not quietly smuggle them in under a read permission.
MB747 is not a flashy IRC command. It is the spellwork behind the spellwork: the moment Mediabot’s new plugin platform learned how to consult its own memories without handing every visitor the keys to the Restricted Section.
The Pensieve is open, but only through a guarded index. 🧠🔒
You must be logged in to reply.