Forum teuk.org

MB753 — Quote writes now need a Ministry seal 📜🔏

in Mediabot · started by TeuK · yesterday

TeuK · yesterday

Before a plugin may alter the Pensieve, Mediabot now checks who is asking, where the request came from and whether the channel has truly opened the gate.

MB753 introduces the authorization boundary required for future API v3 quote mutations. It separates writing from reading, gives each command invocation a detached caller identity and keeps channel policy, permissions and SQL firmly under core control. The gate is complete—but deliberately unused.

Build the guarded door before opening it 🚪🧭

MB747 gave plugins a bounded, read-only view of quote data. MB748 then moved three pure readers behind that facade with an exact rollback path. The mixed commands q and quote could not safely follow: depending on their arguments, they may read, add, delete or update recall counters.

MB753 solves the authority problem before attempting that migration. data.quotes.write is now a capability of its own, completely separate from data.quotes.read. A package must request it, an operator must grant it and the current channel policy must be explicitly on.

No shipped package requests the new capability yet. The historical commands remain core-owned and no live quote was touched during this milestone.

A caller badge, not the whole wizard 🪪🧊

Command invocations now carry an immutable PrincipalV3 built by the core. It contains only the bounded facts required for authorization:

  • whether the caller is authenticated;
  • the numeric user identifier;
  • a bounded account name;
  • the normalized global access level;
  • the current channel access level.

Plugins receive no password, hostmask, raw IRC message or mutable user object. They cannot change the principal after receiving it.

There is also a less visible but essential seal: every legitimate invocation carries an opaque runtime origin known only to the core. Constructing an object that merely looks like a valid invocation is therefore insufficient. The write sink rejects forged lookalikes before they can reach the data service.

Two operations, and no arbitrary SQL 🧱🗝️

The new core-owned service exposes only two bounded operations:

quote_add(invocation, text)
quote_delete(invocation, id)

The plugin cannot supply a channel, user identifier, access level, SQL statement or database handle. Mediabot derives the channel from the active policy and the identity from the trusted invocation, then performs fixed prepared statements itself.

Quote text must be one non-empty line, at most 512 characters and 2048 UTF-8 bytes. An identical quote already present on the same channel returns its existing identifier instead of creating a duplicate.

Deletion preserves the historical rules: an authenticated author may remove their own quote; Administrators and above may remove one; otherwise the existing channel-level threshold remains authoritative. The final deletion is constrained by both quote identifier and resolved channel identifier.

Observe may rehearse, but it cannot write 🎭⛔

API v3’s three channel modes keep their precise meaning:

  • off: the plugin path is inert;
  • observe: plugin code may run as a shadow, but the core suppresses every mutation before the write service;
  • on: a properly granted and authenticated request may cross the gate.

This is an important distinction. Observe mode is useful for comparing dispatch and authorization behavior, but it must never become “almost on” for data changes. MB753 makes that rule structural rather than conventional.

Even the punctuation had to show its papers 🖋️🛂

The first final gate did not commit anything. Ten old security tests stopped the full suite because one new source comment wrote the mode name with Markdown backticks. Those tests intentionally treat any backtick in PluginManager.pm as possible shell execution, so all ten reported the same lexical false positive.

The gate behaved exactly as designed: 20,001 tests passed, 10 failed, and no commit or push occurred.

R2 removed only that comment formatting. The ten historical guards then passed 163/163, the MB753 target set passed 608/608, and the fast lane passed 7,604/7,604. The corrected 22-file scope was re-fingerprinted before a fresh final gate.

That tiny incident is worth recording. A safety gate that occasionally objects to punctuation is mildly annoying; a safety gate that silently waves a suspicious change through is useless.

Sealed by the test chamber 🧫✅

  • 20,011 / 20,011 tests passed
  • 986 / 986 test files passed
  • 22 files in the exact commit scope
  • 1,127 insertions and 37 deletions
  • No service restart during the final gate
  • No private configuration, schema or live quote data changed

The successful complete suite ran once on R2, immediately before commit, with visible progress. The staged digest remained identical before and after the suite. The development service kept the same PID and restart count, and the repository finished clean and synchronized with origin/master.

MB753 was committed and pushed as 8e936905a3ee595dc6512bdd261f7b597e030e54:

MB753: guard quote writes at the Ministry authorization desk 🪶🏛️

Why this milestone matters 🏺🧬

Database-backed plugins should not become trusted merely because they run inside the same process. They need narrow capabilities, detached identities, fixed operations and authorization decisions that remain owned by the core.

MB753 provides that foundation without rushing the visible migration. The next quote milestone can now evaluate reversible q and quote adoption against a real, inert write gate—with observe-first evidence and an exact fallback still available.

The Pensieve remains unchanged. The authorization desk is staffed. Future spells will have to present the right seal. 🏛️


Mediabot v3 development milestone MB753 · 20 September 2026

You must be logged in to reply.