Forum teuk.org

🪄 MB750 — Lumos for Every API v3 Decision

in Mediabot · started by TeuK · yesterday

TeuK · yesterday

Mediabot 3.6dev has entered the stabilization wing of its Plugin API v3 roadmap.

The previous milestones built the platform, proved reversible command migrations and finally made the central command registry the single runtime authority. MB750 now gives operators a clear view of that machinery while it is running.

Three new read-only Partyline commands can explain a plugin’s health, permissions and channel decision without exposing secrets or changing any state:

.plugins doctor <name>
.plugins permissions <name>
.plugins why <name> <#channel>

This is the operator’s Lumos spell: instead of guessing why a package is silent, active or shadowed, we can now ask the runtime directly. 🔎✨

The milestone was published on the 3.6dev-20260920_123125 development line.

Why diagnostics became the next priority

By the end of MB749, API v3 already had serious boundaries:

  • manifests and declared capabilities;
  • explicit capability grants;
  • typed per-channel configuration;
  • off, observe and on channel policies;
  • versioned events and bounded scheduled jobs;
  • shared HTTPS and key/value storage services;
  • a read-only quote-data facade;
  • reversible command migration through saved registry handlers;
  • one authoritative command registry for the entire core.

That architecture was safe, but operating it still required assembling several pieces of information mentally. A plugin could be discovered but not loaded, loaded but disabled, enabled with incomplete permissions, active only on some channels, or deliberately executing in observe mode without producing visible output.

Silence was therefore ambiguous. It could mean a healthy shadow run, a missing capability, an absent policy, an explicit off, or a disabled lifecycle.

MB750 turns those implicit decisions into a stable operational contract.

One detached diagnostic model

The new Mediabot::Plugin::DiagnosticsV3 module builds detached diagnostic records from runtime facts supplied by PluginManager.

It does not hand Partyline a plugin object, invocation context, service handle or mutable policy structure. The presentation layer receives only the bounded facts it is allowed to display.

That separation matters. Diagnostics are not a privileged shortcut around the API v3 boundary; they are another deliberately shaped facade.

The manager now exposes three complementary reports:

Report Operator question Main outcomes
doctor Is this plugin operational? inactive, limited, ready
permissions What was requested and granted? requested, granted, effective, missing
why What happens on this channel? blocked, shadow, active

Together they explain the lifecycle, permissions, mounted resources, channel policy and migration fallback without requiring source inspection.

Doctor: one concise health verdict

.plugins doctor <name> summarizes the plugin as the runtime sees it.

Its top-level states are intentionally small:

  • inactive when the package is not currently able to run, for example when it is disabled;
  • limited when its granted capabilities do not satisfy the requested set;
  • ready when lifecycle and permissions allow normal operation.

The detail lines include:

  • lifecycle state;
  • whether the permission set is complete;
  • the exact missing-capability names, or none;
  • counts of configured policies by mode;
  • mounted versus declared commands, events and jobs;
  • the number of saved historical handlers available for reversible migration.

During the live pilot, the freshly loaded but disabled quotes-v3 package reported:

Plugin doctor 'quotes-v3': inactive (plugin_disabled).
  lifecycle: disabled
  permissions: complete missing=none
  policies: total=0 active=0 on=0 observe=0 off=0
  runtime: commands=3/3 events=0/0 jobs=0/0 saved_handlers=3

After configuring one observe-only channel and enabling the package, the same command became:

Plugin doctor 'quotes-v3': ready (operational).
  lifecycle: enabled
  permissions: complete missing=none
  policies: total=1 active=1 on=0 observe=1 off=0
  runtime: commands=3/3 events=0/0 jobs=0/0 saved_handlers=3

The verdict changed because the runtime state changed, not because the diagnostic command performed any repair.

Permissions: requested is not the same as effective

.plugins permissions <name> makes the capability calculation explicit.

For the quote-reader pilot it reported:

Plugin permissions 'quotes-v3': complete.
  requested: data.quotes.read,irc.notice,irc.reply
  granted: data.quotes.read,irc.notice,irc.reply
  effective: data.quotes.read,irc.notice,irc.reply
  missing: none

These sets have distinct meanings:

  • requested comes from the package manifest;
  • granted records what the operator allowed at load time;
  • effective is the intersection the runtime will actually honor;
  • missing identifies requested capabilities that were not granted.

This makes least-privilege reviews practical from Partyline. An operator can see immediately whether a package has exactly the rights expected, without being shown service internals or configuration values.

Why: the channel decision in plain language

.plugins why <name> <#channel> explains the complete channel gate.

The report distinguishes three outcomes:

Decision Plugin execution Plugin output Historical fallback
blocked No No Visible when available
shadow Yes No Visible
active Yes Yes Suppressed for migrated commands

Immediately after loading quotes-v3, before enabling it, the pilot produced:

Plugin decision 'quotes-v3' #test: blocked (plugin_disabled).
  lifecycle: disabled
  policy: off configured=no
  plugin: runs=no output=no
  migration fallback: visible

After applying an explicit observe policy and enabling the package:

Plugin decision 'quotes-v3' #test: shadow (policy_observe).
  lifecycle: enabled
  policy: observe configured=yes
  plugin: runs=yes output=no
  migration fallback: visible

That is the exact reversible-migration contract rendered as an operator answer: the API v3 implementation runs, its output is suppressed, and users continue to see the historical handler.

An on policy would instead produce an active decision, allowing plugin output and suppressing the saved fallback for that migrated command.

Read-only means read-only

MB750 deliberately stops at diagnosis.

The new commands do not:

  • enable or disable a plugin;
  • load or unload a package;
  • grant or revoke a capability;
  • create or modify channel policy;
  • quarantine a failing plugin;
  • restart the service;
  • alter private configuration;
  • read or write database data.

There is no automatic remediation hidden behind doctor. The command observes the current state and explains it. Any operational change remains a separate, explicit Owner action.

This was a conscious roadmap choice. Before adding automated recovery or quarantine, the platform first needs a diagnostic vocabulary that humans and tests can trust.

Sensitive values remain behind the curtain

The diagnostic surface exposes structure and decisions, not secrets.

It can show that a channel has an observe policy, but it does not print typed configuration values such as endpoints, prefixes or future credentials. It does not expose plugin instances, raw policy hashes, invocation contexts, repository objects, HTTP clients or service handles.

The Partyline commands also retain the existing Owner-level protection. API v3 observability has become easier without turning the castle’s control room into a public notice board. 🏰🔐

Documentation for real operations

MB750 adds a dedicated operations guide:

docs/PLUGIN_OPERATIONS_V3.md

It documents the three commands, their states, the meaning of permission sets, the channel-decision model and the deliberate absence of automatic remediation.

The API reference, architecture guide, ADR, README, generated command inventory and machine-readable contract were updated at the same time. Operational behavior is therefore described consistently for humans, tests and tooling.

A supervised live proof

The Partyline pilot used the existing quotes-v3 package because it already exercises capability grants, per-channel policy and saved migration handlers.

The first phase proved the disabled state:

.plugins loadv3 quotes-v3 data.quotes.read,irc.reply,irc.notice
.plugins doctor quotes-v3
.plugins permissions quotes-v3
.plugins why quotes-v3 #test

The second phase proved the observe state:

.plugins policy quotes-v3 #test observe
.plugins enable quotes-v3
.plugins doctor quotes-v3
.plugins why quotes-v3 #test

The package moved exactly from inactive and blocked to ready and shadow. Permissions remained complete, the plugin could run without emitting output, and all three historical quote handlers remained visible.

The pilot then returned the runtime to its previous state:

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

No plugin or channel was left active as a side effect of validation.

Validation ledger ✅

MB750 passed every planned gate:

Gate Result
Diagnostic contract commands=3 remediation=none
Targeted suite 336 / 336 tests across 19 files
Fast lane 7,504 / 7,504 tests across 410 files
Final full suite 19,836 / 19,836 tests across 976 files
Final full duration 142 seconds
Changed surface 18 files, and no others
Source change 726 insertions, 18 deletions
Repository after push clean and synchronized with origin/master
Development service active and unchanged during the final gate

The final gate froze the staged source before and after the full suite:

STAGED_SHA256=0910551c77ed8b10592274246e2d55df0ec6f218d6290c47513c12c6a55a2040

No source byte, service process or restart counter changed while the 19,836 tests were running.

The deployment made no private configuration change, no database change and no schema migration. It enabled no plugin or channel automatically.

Three new regression wards

MB750 adds three focused test files:

t/cases/1091_mb750_plugin_diagnostics.t
t/cases/1092_mb750_plugin_manager_diagnostics.t
t/cases/1093_mb750_partyline_plugin_doctor.t

They protect the detached diagnostic model, manager aggregation, Owner-only Partyline surface, permission calculations, channel decisions, fallback visibility and the rule that no diagnostic action may remediate or mutate the runtime.

Historical plugin-status and API-contract sentinels were updated alongside them, keeping the new observability layer connected to the rest of the castle.

Commit sealed and released by owl 🦉

MB750 was committed and pushed to master as:

c966291694e03cc7d566543192c84bff366602c2
MB750: cast Lumos on every API v3 decision 🪄🔎

View the MB750 commit on GitHub

The repository is clean, synchronized with origin/master, and still running the exact development service process that entered the final full-suite gate.

What MB750 changes for the roadmap

The platform can now answer three essential questions directly:

  1. Is the plugin healthy enough to operate?
  2. Does it have the permissions it asked for?
  3. Why does this channel see legacy output, silent observation or active v3 output?

That closes an important gap between a safe architecture and an operable one. Future stabilization work can build on explicit, tested diagnostic states instead of scraping logs or reconstructing policy decisions by hand.

Automatic quarantine, richer failure history and more advanced operator tools may come later, but only through separate guarded milestones. MB750 establishes the trustworthy lantern they will need first.

The plugin corridors were already guarded. Now the portraits can finally tell the operator why each door is open, shadowed or sealed. 🖼️🕯️

Teuk

You must be logged in to reply.