refactor: remove ambient plugin authority
This commit is contained in:
+20
-208
@@ -1,219 +1,31 @@
|
||||
# Plugin packages and discovery
|
||||
# Plugin packages
|
||||
|
||||
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.
|
||||
Yoi retains `.yoi-plugin` as an offline authoring and inspection format. The format is not a Worker capability source.
|
||||
|
||||
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.
|
||||
## Current product boundary
|
||||
|
||||
## Package shape
|
||||
Normal Worker creation, restore, Profile/Manifest resolution, Server/Runtime startup, and CLI execution do not discover Plugin catalogs from:
|
||||
|
||||
A `.yoi-plugin` file is a single-file archive. The archive format is a constrained ZIP profile because it is easy to inspect without executing code and can carry text manifests, WebAssembly Component Model modules, schemas, and license material.
|
||||
- repository or ancestor `.yoi/plugins` directories;
|
||||
- user-data Plugin directories;
|
||||
- the current working directory; or
|
||||
- persisted local `package_path` values.
|
||||
|
||||
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.
|
||||
`plugins` and `feature.plugins` are rejected in Worker Manifest/Profile input. Dynamic Plugin Tools, Services, Ingress handlers, and WASM components are not installed or executed. Worker capabilities come only from statically compiled trusted built-in Features.
|
||||
|
||||
Recommended root layout:
|
||||
## Offline format operations
|
||||
|
||||
```text
|
||||
plugin.toml # required package manifest
|
||||
plugin.component.wasm # required when plugin.toml declares the component 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 CLI keeps only operations whose input or destination is explicit:
|
||||
|
||||
```sh
|
||||
yoi plugin new rust-component-tool ./example-plugin
|
||||
yoi plugin check ./example-plugin
|
||||
yoi plugin pack ./example-plugin --output ./example-plugin.yoi-plugin
|
||||
yoi plugin check ./example-plugin.yoi-plugin
|
||||
```
|
||||
|
||||
The package layout is intentionally data-first. Placing a package in a store must never execute `module.wasm`, register hook metadata, or scan assets as prompts. Those steps happen only after explicit enablement and policy resolution.
|
||||
These commands parse, validate, or write the named local artifact. They do not scan a Workspace, mutate Profile/Manifest configuration, install a package, register Worker Tools, or execute Plugin code. `plugin list` and `plugin show` were removed because their catalog semantics depended on ambient repository and user-data stores.
|
||||
|
||||
## `plugin.toml`
|
||||
## Future installation authority
|
||||
|
||||
`plugin.toml` is the package authority for package identity and declared needs. It is not the authority for runtime grants.
|
||||
|
||||
Currently implemented strict `plugin.toml` shape:
|
||||
|
||||
```toml
|
||||
schema_version = 1
|
||||
id = "example.summarizer"
|
||||
name = "Example Summarizer"
|
||||
version = "0.1.0"
|
||||
description = "Adds a custom summary command."
|
||||
surfaces = ["hook"]
|
||||
|
||||
[[hooks]]
|
||||
id = "summary"
|
||||
file = "hooks/summary.md"
|
||||
```
|
||||
|
||||
The package archive must contain both root `plugin.toml` and referenced runtime/content entries. Component runtime metadata is explicit and static inspection never executes the artifact:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "wasm-component"
|
||||
component = "plugin.component.wasm"
|
||||
world = "yoi:plugin/tool@1.0.0"
|
||||
```
|
||||
|
||||
`wasm-component` is the public/recommended runtime kind, recorded in [Plugin Component Model migration](plugin-component-model.md). Legacy raw core-Wasm declarations (`kind = "wasm"` / `abi = "yoi-plugin-wasm-1"`) are retired: manifest validation rejects them and CLI inspection reports the package as rejected rather than active/eligible.
|
||||
|
||||
First-pass fields accepted by the parser:
|
||||
|
||||
- `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.
|
||||
- `surfaces`: optional declared contribution surface names.
|
||||
- `runtime`: optional component runtime metadata. Discovery records metadata and never executes it; unsupported/retired runtime kinds fail closed.
|
||||
- `hooks`: optional hook metadata. Discovery records metadata and does not register hooks.
|
||||
|
||||
Future descriptor sections such as `[package]`, `[permissions]`, richer `contributions`, or `runtime.kind = "declarative"` are aspirational and are intentionally rejected by the current strict parser until implemented safely.
|
||||
|
||||
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:<id>`: packages shipped with Yoi or installed as part of the binary distribution.
|
||||
- `user:<id>`: packages discovered under `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/`.
|
||||
- `project:<id>`: packages discovered under `<workspace>/.yoi/plugins/`.
|
||||
|
||||
Packages under `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/` or `<workspace>/.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 Worker/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:<id>`, `user:<id>`, and `project:<id>` are distinct plugins even when `<id>` 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 Worker. Restored Workers 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 Worker.
|
||||
|
||||
A minimal implemented enablement record is shaped like this. `version` is an exact package-version requirement; richer range constraints are deferred. `digest` is optional in authoring config, but fresh startup records the resolved digest into runtime metadata.
|
||||
|
||||
```toml
|
||||
[[plugins.enabled]]
|
||||
id = "user:example"
|
||||
version = "0.1.0" # optional exact package-version requirement
|
||||
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 Worker 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 `worker::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
|
||||
<cache>/plugins/sha256-<hex>/
|
||||
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 Worker.
|
||||
|
||||
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/Engine 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 `worker::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.
|
||||
|
||||
### Component Model Tool runtime
|
||||
|
||||
Tool packages may use WebAssembly Component Model runtime metadata:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "wasm-component"
|
||||
component = "plugin.component.wasm"
|
||||
world = "yoi:plugin/tool@1.0.0"
|
||||
```
|
||||
|
||||
For new Rust Tool packages, the preferred authoring path is the first-party
|
||||
`yoi-plugin-pdk` plus the embedded `resources/plugin/templates/rust-component-tool/`
|
||||
starter. The template uses a checkout-local path dependency for development and
|
||||
documents a future out-of-tree pinned git `rev` dependency pattern. Crates.io
|
||||
publication, remote template fetching, and package authoring commands are not
|
||||
part of the current package/runtime contract.
|
||||
|
||||
Legacy raw core-Wasm metadata remains documented only as a rejected migration diagnostic. Packages must not use `entry`/`abi`; discovery reports `kind = "wasm"` / `abi = "yoi-plugin-wasm-1"` packages as rejected without executing the artifact. Component execution still requires explicit package enablement, exact source/version/digest grants, and matching Tool/host API permissions.
|
||||
Server-installed Plugin packages and Addons are future work. That platform must provide explicit immutable package identity and digest, Server-owned artifact delivery and Workspace selection, a Backend-authored per-Worker execution plan, Runtime verification, and sandboxed execution. It must not restore repository-local or user-data discovery as a compatibility fallback.
|
||||
|
||||
Reference in New Issue
Block a user