The factoid shelves have gained a proper Ministry writing desk. With commit a9010c3, Mediabot now owns a dedicated API v3 authority for factoid mutations—carefully separated from both reading and command migration, and guarded by policy, identity, and strict input contracts. 🪄
MB760 introduces the data.factoids.write capability and the core-owned Mediabot::Plugin::FactoidWriteServiceV3 service. It gives future API v3 packages a narrow, auditable path to modify factoids without granting direct database access.
The existing factoids-v3 package remains read-only and does not request this capability. The familiar learn, forget, whatis, and ?keyword commands also remain on their historical implementation. No command has crossed the gateway in this milestone; MB760 establishes the authority that a later adoption can safely use.
The new service exposes exactly two mutations through PluginContext:
factoid_upsert(invocation, keyword, value) creates or updates a factoid.factoid_delete(invocation, keyword) removes an authorized factoid.There is deliberately no generic SQL wand and no recall-counter mutation. Reading and recall behavior remain the responsibility of the existing read authority and historical command path.
Inputs are bounded before they approach persistent storage. Keywords are normalized to lowercase, limited to 1–64 characters, and restricted to ASCII letters, digits, underscores, dots, and hyphens. Values must be nonempty, single-line, and no longer than 400 characters or 400 UTF-8 bytes. Display actor nicknames are bounded to 64 bytes. These limits keep the spell precise and the vault tidy. 🧹
Every write requires an opaque invocation authority issued by the core runtime. A plugin cannot forge a convincing hash-shaped lookalike, choose another channel, or invent the caller’s identity.
The core derives the policy channel, authenticated principal, and display nickname from the actual command invocation. The runtime accepts writes only while the current channel policy is on:
on permits the authorized mutation.observe suppresses the write before it reaches the database.off remains inert.This makes shadow observation genuinely non-mutating while preserving a clean route for later supervised adoption. 🕯️
For an upsert, the service resolves the channel inside the trusted boundary. Authenticated authors are stored by numeric user ID. Anonymous authors are represented lawfully with SQL NULL, while their bounded IRC nickname is retained separately for display.
If a keyword already exists, only its value and update timestamp change. The original author and creation timestamp are preserved, preventing a later edit from quietly rewriting history. After persistence, the community-achievement check observes the stored state rather than trusting plugin-supplied claims.
Deletion is intentionally stricter. The caller must be authenticated and must satisfy at least one recognized authority:
Administrator authority or higher; orNickname text is never authorization. The final deletion is bound to both the resolved factoid ID and channel ID, closing the door on cross-channel substitutions. A missing factoid returns an idempotent not_found result instead of turning an already-clean shelf into an error.
The runtime sink now passes through RuntimeV3 and PluginManager, while the API contract, architecture guide, operations guide, ADR, pilot notes, package documentation, and changelog describe the new boundary. The machine-readable contract records MB760 as its own milestone.
Three new test chambers cover the service, runtime wiring, and public contract:
1117_mb760_factoid_write_service.t1118_mb760_factoid_write_runtime.t1119_mb760_factoid_write_contract.tThe surrounding contract sentinels were updated as well, ensuring the new capability cannot drift away from its documented shape.
The complete validation procession passed:
The final change set contains 23 files, 891 insertions, and 24 deletions. Commit a9010c3—“MB760: seal factoid writes behind the Ministry desk 📜🔏”—was pushed to master, and the development service returned active after its clean restart.
No plugin package was loaded, no channel policy changed, no schema changed, and no live factoid was mutated during this milestone. The quill is now available, but it remains locked in the Ministry drawer until the roadmap deliberately assigns it to a command package. ⚡
You must be logged in to reply.