From 3c674a70512ca31b5745d901959c04442c1695d0 Mon Sep 17 00:00:00 2001 From: Hare Date: Mon, 15 Jun 2026 00:52:19 +0900 Subject: [PATCH] docs: propose plugin package distribution --- docs/README.md | 7 +- docs/design/plugin-packages.md | 201 +++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 docs/design/plugin-packages.md diff --git a/docs/README.md b/docs/README.md index 284fe0a4..337e3c9a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,9 +11,10 @@ It is not a dumping ground for external research, old plans, API inventories, or 3. [`design/pod-session-state.md`](design/pod-session-state.md) — Pod identity, replayable session logs, current metadata, and live process hints. 4. [`design/profiles-manifests-prompts.md`](design/profiles-manifests-prompts.md) — reusable Profiles, resolved Manifests, and prompt resources. 5. [`design/tool-permissions-scope.md`](design/tool-permissions-scope.md) — tool policy and filesystem scope. -6. [`design/memory-knowledge.md`](design/memory-knowledge.md) — generated memory, Knowledge, and audit records. -7. [`development/work-items.md`](development/work-items.md) — how project work is recorded and reviewed. -8. [`development/validation.md`](development/validation.md) — how to check changes. +6. [`design/plugin-packages.md`](design/plugin-packages.md) — plugin package distribution, discovery, and enablement boundaries. +7. [`design/memory-knowledge.md`](design/memory-knowledge.md) — generated memory, Knowledge, and audit records. +8. [`development/work-items.md`](development/work-items.md) — how project work is recorded and reviewed. +9. [`development/validation.md`](development/validation.md) — how to check changes. ## What belongs here diff --git a/docs/design/plugin-packages.md b/docs/design/plugin-packages.md new file mode 100644 index 00000000..fa5f68ca --- /dev/null +++ b/docs/design/plugin-packages.md @@ -0,0 +1,201 @@ +# Plugin packages and discovery + +Plugin packages are a distribution format, not an authority boundary. A package can be found on disk, inspected, validated, and cached without registering any Hook, exposing any Tool, starting any process, or initializing any WASM module. + +The initial goal is a durable `.yoi-plugin` package format that later Tickets can implement in independent layers: discovery, archive validation/cache materialization, manifest/profile enablement, Plugin permission policy, declarative hooks, WASM runtime support, and any future MCP bridge. + +## Package shape + +A `.yoi-plugin` file is a single-file archive. The initial archive format should be a constrained ZIP profile because it is easy to inspect without executing code and can carry text manifests, WASM modules, schemas, and license material. + +The archive root must contain `plugin.toml` directly at the root. Packages should not require a wrapping directory whose name must match the plugin id. + +Recommended root layout: + +```text +plugin.toml # required package manifest +module.wasm # optional; required when plugin.toml declares a WASM runtime +hooks/*.toml # optional declarative hook definitions +schemas/*.schema.json # optional JSON schemas for configuration or tool input/output +README.md # recommended human description +LICENSE* # recommended license text +assets/** # optional non-executable data assets +``` + +The package layout is intentionally data-first. Placing a package in a store must never execute `module.wasm`, register `hooks/*.toml`, or scan assets as prompts. Those steps happen only after explicit enablement and policy resolution. + +## `plugin.toml` + +`plugin.toml` is the package authority for package identity and declared needs. It is not the authority for runtime grants. + +Illustrative manifest shape: + +```toml +schema_version = 1 +id = "example" +name = "Example Plugin" +version = "0.1.0" +description = "Demonstrates declarative hooks and an optional WASM module." + +[runtime] +kind = "wasm" # "declarative" or "wasm" for the initial plugin system +entry = "module.wasm" +abi = "yoi-plugin-wasm-1" + +[package] +readme = "README.md" +license = "LICENSE" + +[permissions] +tools = ["Bash"] +web = false +secrets = [] +filesystem = [] + +[[hooks]] +id = "summarize-ticket" +file = "hooks/summarize-ticket.toml" +``` + +Fields proposed for the first implementation pass: + +- `schema_version`: required integer; unsupported versions fail closed. +- `id`: required unqualified local id. It is scoped by the source that discovered the package; it is not globally unique by itself. +- `name`, `version`, `description`: human metadata used in listings and diagnostics. +- `runtime.kind`: required runtime family. Initial values should be `declarative` and `wasm`. +- `runtime.entry`: required for `wasm`, forbidden or ignored for purely declarative packages. +- `runtime.abi`: required for `wasm` so the host can reject incompatible modules before initialization. +- `hooks`, `schemas`, `package.readme`, `package.license`: package-relative paths that must pass the same normalized-path validation as archive entries. +- `permissions`: requested authority. These declarations are requests only; they do not grant access. + +The `source` is not read from `plugin.toml`. It is assigned by the store that discovered the package. + +## Stores, sources, and trust + +Discovery should scan explicit stores and attach a source kind to each package: + +- `builtin:`: packages shipped with Yoi or installed as part of the binary distribution. +- `user:`: packages discovered under `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/`. +- `project:`: packages discovered under `/.yoi/plugins/`. + +Packages under `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/` or `/.yoi/plugins/` are discovery only. Their presence is never permission to register Hooks or Tools, initialize WASM, start processes, open files, use network providers, read secrets, or launch MCP servers. + +Trust differs by source, but none of the sources is self-authorizing: + +- Builtin packages can be trusted as shipped code/data, but still require explicit enablement for a Pod/Profile when they affect runtime behavior. +- User packages are local user-installed artifacts and should be visible to workspaces, but they cannot bypass manifest/profile/tool/scope/secret policy. +- Project packages are repository-controlled artifacts and should be treated as untrusted until explicitly enabled by local policy. Cloning a repository must not be enough to execute a package. + +## Identity and selector rules + +Runtime identity is source-qualified: `builtin:`, `user:`, and `project:` are distinct plugins even when `` is the same string. + +Durable enablement records should use source-qualified ids. Ambiguous unqualified ids fail closed. The implementation may offer convenience listing or search by bare id, but any operation that enables a package, grants permission, pins a digest, or records restored runtime state should require the fully qualified id. + +Collision handling: + +- Two packages with the same source-qualified id in the same effective store set are a discovery diagnostic and neither candidate is enabled implicitly. +- A `user:example` package does not override `builtin:example` unless a future explicit override rule says so. +- A `project:example` package does not override `user:example` or `builtin:example` by name alone. + +## Discovery versus enablement + +Discovery is a read-only inventory operation. It may report package metadata, validation errors, source, canonical store path, and deterministic digest. It must not initialize any runtime contribution. + +Enablement is a resolved runtime plan. It should come from Profile/manifest configuration or another explicit local policy layer, then be recorded into the resolved Manifest/session metadata used to start the Pod. Restored Pods should use that resolved enabled-plugin plan instead of silently re-running fresh discovery and picking newer packages. Fresh discovery must not silently upgrade a restored Pod. + +A future enablement record can be shaped like this, but the exact schema belongs to the implementation Ticket: + +```toml +[[plugins.enabled]] +id = "user:example" +digest = "sha256:..." # optional pin in authoring, resolved in runtime metadata +config = { level = "concise" } +``` + +If no digest is pinned in authoring, fresh startup may resolve the newest acceptable discovered package according to explicit policy. Once a Pod is started, the resolved manifest/session metadata should record the exact source-qualified id and digest so restore is stable. + +## Permissions and grants + +Plugin permission declarations are requests, not grants. Effective grants are the result of Plugin-layer policy combined with existing Yoi authority layers: + +- resolved manifest/profile plugin enablement; +- Plugin policy for the source-qualified package id and deterministic digest; +- normal tool permission policy; +- filesystem scope checks; +- web provider enablement and network safety checks; +- secret references and secret-store policy; +- runtime limits for WASM or other execution engines. + +The Plugin package permission model must not reuse `pod::feature` HostAuthority or grant concepts. The feature layer is an API/contribution substrate; it is not a security boundary for untrusted plugin packages. Plugin grants need their own explicit policy that can fail closed before a Hook, Tool, WASM host function, provider bridge, or external runtime is exposed. + +When a package requests authority outside policy, diagnostics should explain the denied category and package identity without leaking raw secret values, environment contents, full private config, or large plugin-provided text. + +## Archive safety and materialization + +Archive handling should validate before runtime use: + +- Reject absolute paths, `..`, empty segments, Windows drive prefixes, NUL bytes, duplicate normalized paths, and paths that normalize outside the package root. +- Reject symlinks, hardlinks, device files, special files, and entries that are not regular files or directories. +- Enforce bounded extraction: maximum archive size, maximum expanded size, maximum entry count, maximum per-file size, and a compression-ratio limit. +- Validate every manifest-referenced path against the normalized entry set. +- Decode text manifests as UTF-8 and bound diagnostic excerpts. +- Ignore or normalize archive metadata such as mtimes, owners, groups, and executable bits; these should not affect runtime authority. + +After validation, compute a deterministic digest over the normalized materialized package, not over incidental ZIP ordering or timestamps. A stable digest input should include the format version, normalized relative path, file length, and file content hash for each regular file in sorted order. + +Runtime should materialize packages into a digest-keyed cache, for example: + +```text +/plugins/sha256-/ + plugin.toml + module.wasm + ... +``` + +Initialization should read from the digest-keyed cache, not directly from the mutable user/workspace store. This makes restore, diagnostics, and lock/pin behavior reproducible. + +Optional lock behavior can be added in a later Ticket: + +- an authoring-time pin in Profile/manifest configuration; +- a workspace lock file recording source-qualified id, version, source store, digest, and selected package path; +- restore metadata that records the actual digest used by the Pod. + +A lock or pin is selection authority, not execution authority. Enablement and grants are still required. + +## Diagnostics + +Diagnostics should be safe, bounded, and attributable: + +- Include source-qualified id when available, source kind, validation phase, and digest when computed. +- Prefer canonical store-relative paths or redacted absolute paths; avoid dumping large path lists. +- Never print raw secret values, provider tokens, environment dumps, or plugin-supplied opaque payloads. +- Treat package metadata and README text as untrusted content when showing it to an LLM or UI. +- Report discovery errors without disabling unrelated valid packages. + +## Runtime notes + +Declarative hooks are data contributions. Loading a declarative hook still requires explicit package enablement. Hook text should enter the system through the normal Hook/Worker paths, preserving the rule that model-affecting inputs are committed to history before they affect context when applicable. + +WASM packages should initialize only from the digest-keyed cache after enablement and grant resolution. The host should use a narrow ABI, bounded memory, fuel/time limits, bounded output, and explicit host functions. A WASM module must not inherit filesystem, network, tool, secret, process, or MCP authority from the package store path. + +Tool contributions from plugins should pass through the normal ToolRegistry and permission checks. Plugin-provided schemas can describe arguments, but schema presence is not permission to execute a tool. + +## MCP boundary + +MCP remains a separate feature-backed integration and is out of the initial Plugin package runtime. A `.yoi-plugin` package must not launch an MCP server or imply MCP enablement. + +A future MCP/plugin bridge would need its own Ticket covering external process authority, lifecycle, permission mapping, resource/prompt operations, diagnostics, and trust model. Until then, package metadata may mention compatibility for humans, but runtime packaging should ignore it. + +## Follow-up implementation cuts + +Good follow-up Tickets are intentionally separable: + +1. Manifest/Profile plugin enablement schema and resolved-session metadata, including restore behavior and digest pins. +2. Package discovery for builtin, user, and project stores with source-qualified identity and collision diagnostics. +3. `.yoi-plugin` archive validation, deterministic digest computation, and digest-keyed cache materialization. +4. Plugin-layer permission policy that combines package requests with existing tool/scope/web/secret/runtime allowlists without using `pod::feature` HostAuthority concepts. +5. Declarative hook package loading from enabled, materialized packages. +6. WASM package ABI, initialization limits, host-function grants, and Tool/Hook contribution plumbing. +7. Optional lock-file or pin update workflow for reproducible fresh startup. +8. Future MCP/plugin bridge, only if explicitly approved as a separate design and implementation effort.