## 作成 LocalTicketBackend によって作成されました。 --- ## State changed Ticket を `workspace-panel` が queued にしました。 --- ## Decision Routing decision: implementation_ready Reason: - Panel Queue により routing が明示的に許可され、Ticket は `queued`。 - Ticket body / thread / relations / OrchestrationPlan / Orchestrator workspace state を確認した。blocking relation はなく、planning に戻す concrete missing information はない。 - Prior Plugin package design `00001KT0Z4BK8` は done 済みで、本 Ticket はその設計を踏まえた discovery + explicit enablement resolver の最初の実装として具体化されている。 - Risk flags は plugin / package-loading / discovery / enablement / capability-boundary / startup-restore だが、non-goals と fail-closed / read-only / no-registration invariants が明確で、残る不確実性は typed module/config/resolver design の実装戦術に閉じている。 Evidence checked: - Ticket body/thread: scope、requirements、non-goals、acceptance criteria、implementation notes、related work を確認。 - Ticket relations: blocker なし。 - OrchestrationPlan: 既存 record なし。 - Orchestrator workspace: `/home/hare/Projects/yoi/.worktree/orchestration` は clean、`425a6c66` 上。 - Visible Pods: implementation child Pod なし。 - Related design context: `00001KT0Z4BK8` done(Plugin package/discovery design)。 IntentPacket: Intent: - Plugin package discovery と explicit enablement resolver を typed module として実装し、package presence / discovery / enablement / runtime initialization / contribution registration を明確に分離する。 Binding decisions / invariants: - Discovery は read-only。package の存在だけで execution / Tool / Hook / Service / Ingress registration を行わない。 - Explicit enablement entry がなければ Plugin は active にならない。 - Source-qualified identity (`user:`, `project:`, `builtin:`) を扱い、ambiguous unqualified id は fail closed。 - Package safety checks は path traversal / root escape / bounded count/size / manifest size / deterministic digest を含む。 - unsupported/incompatible API version、digest mismatch、version mismatch、missing package、duplicate/ambiguous id、unsupported surface/grant は区別可能な diagnostic にする。 - Diagnostics を model-visible context に勝手に差し込まない。 - Plugin code execution / WASM runtime / actual Tool/Hook/Service/Ingress registration / MCP bridge は non-goal。 - No ambient workspace filesystem authority を plugin package discovery から発生させない。 Requirements / acceptance criteria: - User store `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/*.yoi-plugin` と workspace store `/.yoi/plugins/*.yoi-plugin` から package を発見できる。 - Valid package root の `plugin.toml` を parse し typed manifest と deterministic digest を得る。 - Invalid package は startup 全体を不要に壊さず bounded diagnostic で fail closed。 - Package without enablement is not active。 - Explicit enablement resolves package to typed resolved Plugin metadata。 - Tests cover valid user/workspace discovery、discovery-only inactive、explicit enablement、duplicate/ambiguous fail-closed、digest mismatch、path traversal/root escape、unsupported api、malformed manifest、no contribution registration。 Implementation latitude: - Small typed module/crate-local module を追加してよい。runtime launch code に resolver logic を埋め込まない。 - `.yoi-plugin` archive vs directory minimal implementation は prior design に合わせる。必要なら最小サポート範囲を明示し後続拡張可能にする。 - Exact config/profile shape は既存 Profile / manifest design に合わせて最小 typed structure を追加してよい。 - Startup/restore reproducibility は deterministic re-resolution か resolved digest metadata 保持のどちらかを実装判断。ただし runtime-only mutable state 依存は不可。 Escalate if: - Profile/manifest authority semantics、Pod restore semantics、secret handling、MCP enablement model を変える必要がある。 - Package archive implementation needs signature/trust/install/update/registry semantics。 - Arbitrary external filesystem/network authority が必要になる。 - Runtime registration/WASM execution なしでは acceptance を満たせないことが判明する。 Validation: - focused tests for plugin discovery/resolver。 - `cargo fmt --check`。 - relevant `cargo check` / `cargo test`。 - `git diff --check`。 - `nix build .#yoi` if dependencies, runtime resources, packaging, or Cargo.lock changes matter。 Critical risks / reviewer focus: - discovery vs enablement vs runtime/registration separation。 - fail-closed package safety / diagnostics。 - source-qualified identity and ambiguous refs。 - no contribution registration / no side effects from discovery。 - startup/restore determinism。 - secret-like diagnostic redaction。 - Plugin permission/grant requests not confused with actual grants。 --- ## State changed Routing decision と accepted implementation plan を記録済み。blocking relation / unresolved OrchestrationPlan blocker はなく、Plugin resolver work は同時に開始する Panel startup latency work と主対象が異なるため、implementation side effects の前に `queued -> inprogress` acceptance を記録する。 --- ## Implementation report Implementation report from Coder Pod `yoi-coder-00001KV5R5V2S`: Commit: - `a03a9da6 feat: add plugin package resolver` Changed files: - `crates/manifest/src/plugin.rs` - New typed plugin discovery/resolution module. - `crates/manifest/src/lib.rs` - Added `plugins` config to `PodManifest`. - Added parsing test for `[[plugins.enabled]]`. - `crates/manifest/src/config.rs` - Added `plugins` to `PodManifestConfig` and merge/materialization path. - `crates/manifest/src/profile.rs` - Added profile/Lua config support for plugin enablement config. - `crates/pod/src/spawn/tool.rs` - Preserves plugin config when deriving child manifest config. - `crates/manifest/Cargo.toml` - Added `sha2` dependency for deterministic package digests. - `Cargo.lock` - Updated manifest crate dependency metadata. - `package.nix` - Updated `cargoHash`. Resolver/config shape: - Config supports explicit enablement through `[[plugins.enabled]]`: - `id = "project:" | "user:" | "builtin:"` - optional `digest = "sha256:"` - optional `surfaces = ["hook", ...]` - optional `[plugins.enabled.config]` - optional grants, currently fail closed when authority-bearing/non-empty. - Discovery stores: - User: `${XDG_DATA_HOME:-~/.local/share}/yoi/plugins/*.yoi-plugin` - Workspace: `/.yoi/plugins/*.yoi-plugin` Implemented behavior: - Discovery is read-only and returns typed `DiscoveredPluginPackage` metadata only. - Resolution only activates packages explicitly listed in config and returns typed `ResolvedPlugin` metadata. - No runtime initialization, code execution, WASM runtime, hook/tool/service/ingress registration, network, package-manager, or MCP behavior was added. - Source-qualified ids are required; unqualified ids fail closed as ambiguous. - Unsupported plugin API versions fail closed. - Digest pins are checked against deterministic package content digests. - Traversal/root escape/symlink escape checks are enforced. - Package count, package size, manifest size, entry count, per-file size, and expanded size are bounded. - Non-empty authority-bearing grants fail closed, so plugins do not receive ambient workspace filesystem authority. Diagnostics: - Added typed diagnostic kinds for missing, duplicate, ambiguous, version, digest, API, surface, grant, malformed, traversal, bounds, and I/O. - Diagnostics avoid full package path/content disclosure and use bounded/sanitized messages. Validation reported by coder: - Passed: `cargo fmt --check` - Passed: `cargo test -p manifest plugin` - Passed: `cargo test -p manifest` - Passed: `cargo check` - Passed: `cargo test -p pod spawn_pod` - Passed: `git diff --check` - Passed: `nix build .#yoi` Residual risks / notes: - Current archive reader intentionally supports a constrained stored-ZIP profile and fails closed on unsupported compression methods. Broader `.zip` compression support can be added later if package tooling requires it. --- ## Review: request changes Review result: request_changes Evidence checked: - Child worktree only: - `/home/hare/Projects/yoi/.worktree/00001KV5R5V2S-plugin-enable-resolver` - branch/head `impl/00001KV5R5V2S-plugin-enable-resolver` at `a03a9da6` - Diff files: - `crates/manifest/src/plugin.rs` - `crates/manifest/src/config.rs` - `crates/manifest/src/lib.rs` - `crates/manifest/src/profile.rs` - `crates/pod/src/spawn/tool.rs` - `crates/manifest/Cargo.toml` - `Cargo.lock` - `package.nix` - `docs/design/plugin-packages.md` - Ticket record: - `.yoi/tickets/00001KV5R5V2S/item.md` - `.yoi/tickets/00001KV5R5V2S/thread.md` Read-only validation performed: - Passed: `git diff --check 4772c4d6..a03a9da6` Positive findings: - Discovery/resolution code is isolated in `crates/manifest/src/plugin.rs`. - No runtime registration, WASM execution, Tool/Hook/Service/Ingress contribution path was found. - User/workspace stores are represented. - Discovery checks include store containment, symlink escape rejection, path normalization, count/size/manifest bounds, duplicate normalized path rejection, and deterministic digest. - Package presence alone does not activate anything; resolution requires `plugins.enabled`. - Manifest/profile/child-spawn config plumbing preserves `plugins` config. Required changes: 1. Version mismatch support is missing. - Ticket requires enablement entries to express package version/version constraint and requires version mismatch to be a distinct diagnostic. - `PluginEnablementConfig` currently has `id`, `digest`, `surfaces`, `grants`, `config`, but no version/version constraint field. - `resolve_enabled_plugins` never compares enablement against `package.manifest.version`. - `PluginPackageManifest` has `version`, but it is only validated non-empty. - `PluginDiagnosticKind::Version` currently appears to be used for unsupported API version, so package-version mismatch and API incompatibility are not clearly separated. Required fix: - Add a typed version/version requirement field to enablement config, or explicitly documented minimal exact-version field if constraints are deferred. - Compare it to `manifest.version` during resolution. - Emit a distinct version-mismatch diagnostic separate from incompatible API version. - Add tests for version mismatch fail-closed behavior. 2. Startup/restore determinism is not satisfied. - Ticket requires deterministic startup/restore behavior for the resolved plugin set. - Implementation preserves authoring config, but no resolved plugin metadata/digest recording or deterministic restore re-resolution path was found. - Unpinned enablement can resolve to a different package if mutable user/workspace store changes before restore. - The design doc also states restore should use a resolved plan, not fresh discovery choosing newer packages. Required fix: - Either persist resolved plugin identity/digest metadata into resolved manifest/session metadata used for restore, or define and implement deterministic re-resolution semantics that cannot silently change a restored plugin set. - Add focused test or validation evidence for the chosen restore/reproducibility path. - If intentionally deferred, Ticket acceptance/report must be updated before approval because current acceptance still requires it. 3. Bounded diagnostic truncation can panic on valid UTF-8. - `bounded_message` slices a Rust `String` at byte offset 240. - It is used for TOML parse errors from untrusted plugin manifests. - If byte 240 falls inside a multibyte UTF-8 character, slicing panics instead of producing a bounded diagnostic. Required fix: - Truncate on character boundary using safe helper logic. - Add malformed manifest test with long multibyte content proving diagnostics remain bounded and non-panicking. - Consider reducing raw TOML-error content leakage because diagnostics should avoid secret-like path/content leakage. Additional concern: - Design doc examples use `schema_version`, but implemented parser requires `api_version`. - Align schema naming before merge to avoid contradictory package-author guidance. Conclusion: - Changes requested. Do not integrate until these blockers are fixed and covered. --- ## Implementation report Follow-up implementation report after reviewer request_changes: New commit: - `ede7acfd fix: pin plugin resolution metadata` Resolved blockers: 1. Version mismatch support - Added typed exact-version requirement: - `PluginExactVersion` - `PluginEnablementConfig.version: Option` - Resolution now compares `plugins.enabled.version` against `package.manifest.version`. - Version mismatch fails closed with `PluginDiagnosticKind::Version`. - Unsupported plugin schema/API version now emits `PluginDiagnosticKind::Api`, distinct from package version mismatch. - Added focused test: - `exact_version_mismatch_fails_closed_with_distinct_diagnostic` 2. Startup/restore determinism - Added runtime resolved-plan metadata to `PluginConfig`: - `resolved: Vec` - `diagnostics: Vec` - Added `resolve_plugin_config_for_startup`. - Fresh startup resolves enabled plugins and records identity/path/label/digest/version/manifest/enabled surfaces/grants/config into manifest plugin config. - Restore/snapshot configs with an existing resolved plan are reused as-is and do not silently select newer mutable store contents. - Wired startup resolution into pod manifest resolution in `crates/pod/src/entrypoint.rs`. - Preserved resolved plugin plan through config merge. - Added focused test: - `resolved_plan_pins_unpinned_enablement_for_restore` 3. Safe bounded diagnostics - Replaced byte slicing in `bounded_message` with char-boundary truncation. - TOML parse diagnostics no longer embed raw untrusted manifest content; diagnostic reports bounded parse message with optional byte span. - Added focused multibyte malformed manifest test: - `malformed_manifest_multibyte_diagnostic_is_bounded_and_redacted` 4. Schema naming alignment - Plugin package manifest parser now uses `schema_version`, matching `docs/design/plugin-packages.md`. - Updated tests/helpers accordingly. - Updated design doc enablement example to include implemented exact `version` field and describe deferred richer constraints. Validation reported by coder: - Passed: `cargo fmt --check` - Passed: `cargo test -p manifest plugin` - Passed: `cargo test -p manifest` - Passed: `cargo check` - Passed: `cargo test -p pod spawn_pod` - Passed: `git diff --check` - Passed: `nix build .#yoi` Repository status: - Child implementation worktree clean after follow-up commit. Residual note: - Version constraints are intentionally minimal exact-version matching for this Ticket. Richer semantic/range constraints remain deferred and documented as follow-up scope. --- ## Review: request changes Review result: request_changes Evidence checked: - Child worktree only: - `/home/hare/Projects/yoi/.worktree/00001KV5R5V2S-plugin-enable-resolver` - HEAD `ede7acfd fix: pin plugin resolution metadata` - base `4772c4d6` - Relevant files: - `crates/manifest/src/plugin.rs` - `crates/pod/src/entrypoint.rs` - `crates/pod/src/pod.rs` - `docs/design/plugin-packages.md` Validation performed by reviewer: - Passed: `git diff --check 4772c4d6..HEAD` - Passed: `cargo fmt --check` - `git status --short` clean Validation not run: - `cargo test`, `cargo check`, and `nix build` were not rerun because reviewer scope was read-only and those commands write build artifacts. Coder-reported results were treated as evidence only. Remaining blockers: 1. Startup/restore determinism is incomplete for non-profile / spawn-config launches. Positive pieces exist: - `apply_plugin_resolution_plan()` resolves plugins during manifest resolution. - `resolve_plugin_config_for_startup()` skips fresh discovery when `plugins.resolved` or `plugins.diagnostics` already exists. - focused helper test confirms a restored resolved plan does not pick newer mutable store contents. Blocker: - resolved manifest snapshot is only persisted to Pod metadata when `self.manifest.profile.is_some()`. - Restore uses saved snapshot if present, otherwise falls back to freshly resolved current manifest. - A named Pod started from `--manifest` or spawn config can resolve plugin metadata at startup but fail to persist that resolved plan, then later restore can silently re-resolve against newer mutable store contents unless authoring config had digest pin. Required fix: - Persist the resolved manifest snapshot for plugin-resolved manifests regardless of `manifest.profile`, or otherwise make non-profile restore deterministically use the original resolved plan. - Add focused test/validation for the runtime persistence path, not only the helper behavior. 2. Documentation examples still appear broader than the implemented strict parser. Resolved part: - `schema_version` naming is now consistent in implementation/tests/minimal docs. Remaining mismatch: - docs illustrative `plugin.toml` still includes fields/sections rejected by current `#[serde(deny_unknown_fields)]` parser, such as `[package]` and `[permissions]`. - docs describe `runtime.kind = "declarative"` as an initial value while implementation rejects runtime kinds other than `"wasm"`. Required fix: - Either mark those fields/sections as future/aspirational and provide a minimal currently-valid manifest example, or extend the parser to accept the documented first-pass fields safely. Prior blockers resolved: - Version mismatch support is resolved with exact-version field and distinct `Version` vs `Api` diagnostics. - Safe bounded diagnostics are mostly resolved with UTF-8-boundary truncation and redacted TOML parse diagnostics. - Discovery/enablement/no-registration boundaries look good. Conclusion: - Changes requested. Do not integrate until the non-profile/spawn restore determinism path and docs/parser mismatch are fixed and covered. ---