Skip to content

feat(plugin): add UserLevel plugin capability layer (PR 2/5)#30

Open
shaun0927 wants to merge 1 commit into
Q00:release/bootstrapfrom
shaun0927:feat/plugin-user-level-capability
Open

feat(plugin): add UserLevel plugin capability layer (PR 2/5)#30
shaun0927 wants to merge 1 commit into
Q00:release/bootstrapfrom
shaun0927:feat/plugin-user-level-capability

Conversation

@shaun0927
Copy link
Copy Markdown

Summary

PR 2 of the five-PR plan under SSOT #25 (see PR #3 / docs/userlevel-plugin-dispatch.md).

Adds the Ourocode.Plugin.UserLevel.* namespace so the runtime can discover installed Ouroboros UserLevel plugins and treat their commands as first-class capabilities — without reimplementing trust, installation, or plugin-internal storage semantics.

What ships

  • Ourocode.Plugin.UserLevel.Capability + .Capability.Command — normalized identity and command surface (plugin_id, source, version, install scope, trust scopes, manifest digest, declared commands, expected artifacts, continuation hints). Identity stability via (plugin_id, version, manifest_digest) so re-discovery without manifest changes returns the same struct.
  • Ourocode.Plugin.UserLevel.Discovery — behaviour + run/2 helper that normalizes raw descriptors into Capability structs and reports per-descriptor errors separately so one bad command never loses the whole plugin.
  • Ourocode.Plugin.UserLevel.Discovery.OuroborosCLI — first discovery adapter; calls ouroboros plugin list --json through a pluggable command runner (tests inject a stub runner; no real processes spawned). Failure modes — exit ≠ 0, runner unavailable, malformed JSON, unexpected shape — all surface as structured errors.
  • Ourocode.Plugin.UserLevel.Registry — small Agent that caches the most recent discovery snapshot with a 60 s TTL, explicit refresh/2, and identity-preserving merge. Discovery failure degrades the snapshot but preserves the last good capability list; missing/broken ouroboros CLI never blocks boot.
  • Ourocode.Plugin.UserLevel.RegistryEntry — projects Capability into the existing Command.Registry plugin-source entry shape (mirrors PluginSurfaceEntry's metadata so the existing CapabilityPreflight.Trust and Projection modules apply unchanged).
  • Test fixture test/fixtures/user_level_plugins/superpowers.json matching the ouroboros plugin list --json shape with four superpowers commands.

What is intentionally NOT in this PR

  • No supervision wiring. The registry is standalone. PR 4 will wire it into ApplicationServices alongside the dispatch adapter that actually consumes it. This keeps PR 2 boot-safe — missing ouroboros CLI cannot regress startup.
  • No /plugins refresh slash command. Ships with PR 4 once the registry is supervised.
  • No router or dispatcher changes. Those land in PR 3 (:user_level_plugin route) and PR 4 (UserLevelPluginInvocation adapter).
  • No free-form natural-language matching. Discovery is exact-match only; routing decisions land in PR 3.

Why this shape

ourocode already has a strong slash-command preflight surface (Ourocode.Command.CapabilityPreflight). UserLevel plugin entries projected by RegistryEntry reuse that surface as-is — the only thing missing for the slash path was the discovery feed itself. The ooo-prefixed dispatch path remains the work of PRs 3-5.

Trust defaults are conservative: capabilities without declared trust scopes are marked requires_explicit_approval: true and trusted: false. This means existing CapabilityPreflight.Trust.boundary/1 returns :requires_approval, keeping the safe path even when Ouroboros has not yet surfaced an explicit grant.

Closes

Testing

  • 5 ExUnit modules (async: true) covering capability shape + identity + command lookup, discovery normalization with valid/invalid descriptors, OuroborosCLI parsing + runner failure modes, registry TTL + degraded handling + identity-stable merge + fetch, and projection into the existing plugin-source registry entry shape.
  • Total ~1255 LOC (lib + tests + fixture).
  • Local Elixir toolchain was unavailable in the authoring environment; CI is the source of truth for these tests. Reviewers please confirm mix test test/ourocode/plugin/user_level is green before approving.

Stacking note

PR 3 will branch off feat/plugin-user-level-capability; review this PR first to keep downstream diffs minimal.

🤖 Generated with Claude Code

Introduces the Ourocode.Plugin.UserLevel namespace that lets the runtime
discover installed Ouroboros UserLevel plugins and treat their commands
as first-class registry entries without reimplementing trust or storage
semantics.

What is added:

- Ourocode.Plugin.UserLevel.Capability + .Capability.Command
  Normalized identity and command surface (plugin_id, source, version,
  install scope, trust scopes, manifest digest, declared commands,
  expected artifacts, continuation hints). Identity stability via
  (plugin_id, version, manifest_digest) tuple so re-discovery without
  manifest changes returns the same struct.

- Ourocode.Plugin.UserLevel.Discovery
  Behaviour with a Discovery.run/2 helper that normalizes raw
  descriptors into Capability structs and surfaces per-descriptor
  validation errors separately so one bad command never loses a whole
  plugin.

- Ourocode.Plugin.UserLevel.Discovery.OuroborosCLI
  First discovery adapter; invokes `ouroboros plugin list --json` via a
  pluggable command runner. Tests inject a stub runner to avoid spawning
  real processes. Failure modes (exit != 0, runner unavailable,
  malformed JSON, unexpected shape) all surface as structured errors.

- Ourocode.Plugin.UserLevel.Registry
  Small Agent that caches the latest discovery snapshot with a 60 s
  TTL, explicit refresh, and identity-preserving merge. Discovery
  failure degrades the snapshot but preserves last good capabilities,
  so missing/broken ouroboros CLI never blocks boot.

- Ourocode.Plugin.UserLevel.RegistryEntry
  Projects Capability into the existing Command.Registry plugin-source
  entry shape (mirrors PluginSurfaceEntry's metadata so the existing
  CapabilityPreflight.Trust and Projection modules apply unchanged).

What is NOT changed in this PR:

- No supervision wiring — the registry is standalone and ships dead
  code until PR 4 wires it into application_services.ex alongside the
  dispatch adapter that needs it. This keeps PR 2 boot-safe.
- No new slash command — `/plugins refresh` ships with PR 4.
- No router/dispatcher changes — those land in PR 3.

Tests: 5 ExUnit files (1255 LOC total with lib code) cover capability
shape, identity, command lookup, discovery normalization,
OuroborosCLI parsing of the superpowers fixture, registry TTL +
degraded handling, identity-stable merge, and registry projection
into the existing plugin-source entry shape.

Closes Q00#5
Closes Q00#8
Closes Q00#9
Closes Q00#18
Closes Q00#27
Closes Q00#29

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shaun0927 shaun0927 force-pushed the feat/plugin-user-level-capability branch from 6599c4c to 9769f52 Compare May 25, 2026 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment