yoi/.yoi/tickets/00001KV5R5V2S/thread.md

26 KiB
Raw Blame History

作成

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 donePlugin 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:<id>, project:<id>, builtin:<id>) を扱い、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 <workspace>/.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:<id>" | "user:<id>" | "builtin:<id>"
    • optional digest = "sha256:<hex>"
    • 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: <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.
  1. 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.
  1. 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<PluginExactVersion>
  • 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
  1. Startup/restore determinism
  • Added runtime resolved-plan metadata to PluginConfig:
    • resolved: Vec<ResolvedPluginRecord>
    • diagnostics: Vec<PluginDiagnostic>
  • 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
  1. 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
  1. 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.
  1. 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.

Implementation report

Second follow-up implementation report after reviewer request_changes:

New commit:

  • 07978d2d fix: persist plugin snapshots for restore

Resolved remaining blockers:

  1. Non-profile / spawn-config restore determinism
  • Changed Pod metadata snapshot persistence so resolved manifests are snapshotted when either:
    • manifest.profile.is_some() (existing profile behavior), or
    • manifest.plugins.has_resolved_plan() (new plugin-resolved manifest behavior).
  • Added helper path:
    • pod_metadata_for_manifest
    • should_persist_resolved_manifest_snapshot
  • A named Pod whose plugin enablement is resolved at startup now persists resolved plugin metadata/digest plan in resolved_manifest_snapshot even without a profile.
  • Restore therefore uses the original resolved plan instead of silently rediscovering mutable stores.

Focused runtime persistence test added:

  • plugin_resolved_manifest_snapshot_is_persisted_without_profile
    • verifies a non-profile manifest does not snapshot by default;
    • adds resolved plugin metadata;
    • verifies runtime metadata snapshot is persisted;
    • verifies restored snapshot contains the original plugin digest/version.
  1. Docs/parser alignment
  • Updated docs/design/plugin-packages.md with a minimal currently-valid strict plugin.toml example:
    • top-level schema_version, id, name, version, description, surfaces;
    • [[hooks]] with id and file.
  • Clarified referenced files must exist in the package archive.
  • Clarified optional [runtime] currently accepts only:
    • kind = "wasm"
    • entry = "..."
    • abi = "yoi-plugin-wasm-1"
  • Marked [package], [permissions], richer contributions, and runtime.kind = "declarative" as future/aspirational and intentionally rejected by strict parser for now.
  • Added parser-alignment test:
    • currently_documented_manifest_shape_is_accepted

Validation reported by coder:

  • Passed: cargo fmt --check
  • Passed: cargo test -p manifest plugin
  • Passed: cargo test -p manifest
  • Passed: cargo test -p pod plugin_resolved_manifest_snapshot_is_persisted_without_profile
  • Passed: cargo test -p pod spawn_pod
  • Passed: cargo check
  • Passed: git diff --check
  • Passed: nix build .#yoi

Repository status:

  • Child implementation worktree clean after follow-up commit.

Review: approve

Review result: approve

Evidence checked:

  • Third-pass target worktree:
    • /home/hare/Projects/yoi/.worktree/00001KV5R5V2S-plugin-enable-resolver
    • HEAD includes 07978d2d fix: persist plugin snapshots for restore
  • Non-profile / spawn-config restore determinism:
    • pod_metadata_for_manifest persists resolved_manifest_snapshot when either profile is present or manifest.plugins.has_resolved_plan() is true.
    • Pending and active metadata writes use the helper.
    • Restore path prefers metadata.resolved_manifest_snapshot when present and falls back only when absent.
    • Existing profile behavior is preserved.
    • Focused test plugin_resolved_manifest_snapshot_is_persisted_without_profile exercises profile-none metadata construction with resolved plugin records and verifies identity/version/digest are retained.
  • Docs/parser alignment:
    • docs/design/plugin-packages.md now includes a minimal strict currently-valid plugin.toml example using schema_version = 1, [runtime] kind = "wasm", and currently accepted hook metadata.
    • Future/unsupported [package], [permissions], richer contributions, and runtime.kind = "declarative" are explicitly marked future/unsupported rather than current parser surface.
    • Parser/tests align with current schema/runtime shape.
  • Prior acceptance spot-check:
    • Discovery remains inactive without explicit enablement.
    • Version mismatch and API incompatibility diagnostics are distinct.
    • Diagnostics are bounded/sanitized.
    • Authority grants fail closed.
    • No Tool/Hook/Service/Ingress registration plumbing was added.

Validation performed by reviewer:

  • Passed: cargo fmt --check
  • Passed: git diff --check 4772c4d6..HEAD

Validation not rerun by reviewer:

  • cargo test, cargo check, and nix build .#yoi were not rerun due read-only review scope. Coder-reported successful heavier validation was used as supporting evidence.

Risk assessment:

  • Residual risk is low. Snapshot persistence is in the central Pod metadata helper and restore already prefers persisted snapshots.
  • Remaining coverage is unit/integration-level rather than full E2E process restore, matching current project E2E boundary and not a blocker for this Ticket.

Conclusion:

  • Approved. No remaining blocker found.

Implementation report

Integration report:

Merged implementation branch into orchestration branch:

  • branch: impl/00001KV5R5V2S-plugin-enable-resolver
  • implementation commits:
    • a03a9da6 feat: add plugin package resolver
    • ede7acfd fix: pin plugin resolution metadata
    • 07978d2d fix: persist plugin snapshots for restore
  • merge commit: f678383a merge: plugin package resolver

Reviewer result:

  • Initial review requested changes for version mismatch support, restore determinism, UTF-8-safe diagnostics, and docs/parser mismatch.
  • Second review requested changes for non-profile/spawn restore determinism and docs/parser mismatch.
  • Third-pass review approve recorded from yoi-reviewer-00001KV5R5V2S-r3.

Validation in Orchestrator worktree /home/hare/Projects/yoi/.worktree/orchestration:

  • Passed: cargo fmt --check
  • Passed: cargo test -p manifest plugin
    • 19 passed
  • Passed: cargo test -p manifest
    • 54 passed
  • Passed: cargo test -p pod plugin_resolved_manifest_snapshot_is_persisted_without_profile
    • 1 passed
  • Passed: cargo test -p pod spawn_pod
    • 7 passed
  • Passed: cargo check
  • Passed: git diff --check
  • Passed: nix build .#yoi --no-link

Outcome:

  • Plugin package discovery and explicit enablement resolver are implemented as typed manifest-layer functionality.
  • Discovery remains inactive/read-only without explicit enablement.
  • Resolved plugin metadata/digests can be snapshotted for restore determinism, including non-profile/plugin-resolved manifests.
  • No runtime Tool/Hook/Service/Ingress registration or plugin execution path was added.
  • Package safety, source-qualified identity, exact version pins, digest pins, bounded diagnostics, and strict docs/parser alignment are covered by focused tests.

Cleanup planned:

  • Stop related coder/reviewer Pods.
  • Remove only child implementation worktree/branch for this Ticket.

State changed

Reviewer approved after requested fixes, implementation branch merged into the orchestration branch, and focused plus packaging validation passed in the Orchestrator worktree. Marking Ticket done in the orchestration branch.