Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

ADR-022 freezes the capability-ID set before it has ever been written down

Источник: dev/fera-research/docs/oc1/020-capability-ids-frozen-before-written.md (read-only; сгенерировано scripts/sync-docs.mjs)

ADR-022 freezes the capability-ID set before it has ever been written down

Section titled “ADR-022 freezes the capability-ID set before it has ever been written down”
  • kind: problem
  • area: modules / trust
  • evidence:
    • ADR.md:896-900 (ADR-022) — “The rights/manifest schema is frozen from the first stable version and changes additively only: new capabilities are added, existing ones never change meaning; unknown capabilities are must-understand (the host refuses to grant rather than silently ignoring).”
    • ADR.md:891-893 — “Capability names are stable, host-defined identifiers — translators map calls, not capabilities.”
    • ADR.md:894-896 — “Final permissions = original plugin’s requested ∩ user-granted. A translator may rename a capability from the old API to the new, or drop it — never add.”
    • ADR.md:55-56 (ADR-002) — the only informal enumeration in the corpus: “write/action capabilities (send messages, mutate UI, network access) require explicit per-capability opt-in in settings”.
    • TECHSPEC.md:326-339 — two wit interfaces (plugin-readonly, plugin-write) and the rule that unauthorized capabilities must be “absent from the callable surface, not merely policy-checked at call time” — but no capability identifiers appear.
    • Verified absence: grep for capability_id, capability-id, CAP_, capabilities: across the whole corpus → 0 hits. There is no enumerated capability set, no IDs, no schema document.
    • The QA gates test the properties but cannot test the set: QA.md:138-140 (“translator rights only narrow — property test over all L1/L2 mappings”), QA.md:141-142, QA.md:143-146, and QA.md:147-148 (“plugin-rights UI shows the original plugin’s requested rights — code review gate”).
    • NAMING-UX.md:195-196 depends on the list existing: “Plugin capability grants (read-only default, ADR-002) should be legible per-capability, not a single ‘allow this plugin’ toggle”.
    • OQ-015 (translator L2 capability set, OQ.md:487-507) is resolved (:498 — “minimal starter set … beginning with async-translation”) but it enumerates translator capabilities, not host-granted plugin rights.
  • severity: mid

The corpus has committed to a permanent, additive-only, must-understand capability namespace before the namespace exists. The freeze rule is not premature caution — it has teeth that make the missing artifact expensive later:

  • Additive-only + “existing ones never change meaning” means the first published list is the list every plugin will be written against forever. A capability named send-message when the real implementation later needs send-message-with-reply-to cannot be corrected — only added alongside, and the old one can never be repurposed.
  • Must-understand means the host must refuse a plugin declaring an unknown capability. That is the right default (fail-closed), but it means a typo’d or prematurely-renamed ID bricks a plugin, and the corpus has no migration story for that — deliberately, per the freeze.
  • Translators map calls, not capabilities (ADR.md:892-893) — so the capability set is the only stable coordinate system across editions. Without a written set, the edition-translation machinery has nothing to translate into.

And the UI depends on it: NAMING-UX.md:195-196 requires per-capability legibility, ADR.md:55-56 requires per-capability opt-in in settings, and TECHSPEC.md:336-339 requires unauthorized capabilities to be absent from the callable surface (not merely policy-checked) — which is only implementable if each capability has a host-known identifier that can be checked against the link-time surface.

So the plugin system’s security model is fully specified except for the list it operates on. Everything around the hole is designed (translators, rights intersection, UI, QA properties) and the hole itself is one enumeration that nobody wrote. This is the same failure shape as the missing SearchEngine/Engine traits (my finding 016) and the missing schema-migration policy (finding 017): the surrounding machinery is designed, the leaf artifact is missing, and it is the leaf artifact that is expensive to get wrong because it is frozen.

Note also that ADR.md:55-56’s three informal capabilities (“send messages, mutate UI, network access”) are a starting set, not a deliberate one — “mutate UI” in particular has no definition anywhere (a capability to modify what? the central State? a subtree? submit a view?). The plugin tier has a UI-mutation capability with no vocabulary.

Варианты решения (tradeoffs)

Section titled “Варианты решения (tradeoffs)”
  • (A) Publish the capability list as a spec doc now, before any plugin API ships. One table: id | kind (effect/intent) | surface (what is linked / callable) | requires user opt-in | since-edition. Cheap (half a day), and it is the last cheap moment — plugins do not exist yet.
  • (B) Publish a deliberately tiny v0 set (e.g. 4–6 capabilities: chat-list-read, message-history-read, send-message, network-out) and lean on the additive rule. Smaller permanent surface, slower feature growth, and the freeze genuinely bites later.
  • (C) Defer until the first plugin works. Then the freeze lands on a list shaped by whatever the first plugin happened to need — the classic way frozen schemas become wrong on day one.
  • (D) Define capabilities as WIT-world-level rather than per-function (i.e. freeze the interface set — plugin-readonly, plugin-write, plugin-ui, plugin-net — and grant at world granularity). Fewer, coarser grants; contradicts ADR.md:55-56 and NAMING-UX.md:195-196, which both ask for per-capability control.

(A) with (B)’s discipline: write the list, start small, mark it v0 — frozen.

  1. New spec doc (e.g. context/CAPABILITIES.md, referenced from README.md’s contents list like Lattice’s spec is) holding the canonical table. README.md:35-37 shows precedent for a spec living beside the corpus while its implementation lives in its own repo.
  2. Start with 4–6 IDs covering what ADR-002 already names, plus the one that is currently a bare word: mutate-ui needs a definition (my guess at a defensible scope: submit a declarative view tree into a host-owned slot, with the host owning brushes/fonts — never mutate the central State directly, which would bypass ADR-008’s Elm loop).
  3. For each ID record: kind (irreversible effect vs absolute-state intent — the distinction oc4 raised, and it matters here because a translator must not be able to turn an intent into an effect), link-time surface (per TECHSPEC.md:336-339: what gets linked when granted), opt-in requirement, and introducing edition.
  4. Add the missing artifact QA asks for: a property test over the enumerated set — every ID round-trips through manifest → grant → link-time surface → UI display → translator mapping, and no translator output can add one (ADR.md:894-896). Today the property test can only be written against whatever set the code happens to contain, which is how a frozen schema gets frozen wrong.
  5. Resolve NAMING-UX.md:195-196’s dependency by pointing it at the new doc, and note in ADR-022 that the freeze is now anchored to a written artifact (with its date), so a future “was this ID ever public?” question has an answer.