Compare commits

...

88 Commits

Author SHA1 Message Date
c8877b49a4
ticket: close plugin component model runtime 2026-06-20 02:23:35 +09:00
54a91f1b7e
ticket: complete plugin component model runtime 2026-06-20 02:23:17 +09:00
63d7ad788d
merge: plugin component model runtime 2026-06-20 02:20:03 +09:00
e6619bc6c9
ticket: approve plugin component model runtime 2026-06-20 02:19:59 +09:00
ac58bfdd63
ticket: record component runtime resource fix 2026-06-20 02:17:13 +09:00
a705bb3bf2
plugin: bound component model runtime resources 2026-06-20 02:16:16 +09:00
30e49d806a
ticket: request changes on component model runtime 2026-06-20 02:08:22 +09:00
5f00329d44
ticket: dispatch plugin component model review 2026-06-20 02:01:02 +09:00
ed33a0b00f
ticket: record plugin component model implementation report 2026-06-20 02:00:25 +09:00
57bbf14e1a
plugin: implement component model runtime 2026-06-20 01:58:53 +09:00
02006fee2e
ticket: update plugin component model work metadata 2026-06-20 01:25:34 +09:00
466e2bf927
ticket: start plugin component model runtime worktree 2026-06-20 01:25:27 +09:00
878517dcd2
ticket: accept plugin component model runtime 2026-06-20 01:22:02 +09:00
b0ea9513e3
ticket: close plugin fs host api 2026-06-20 01:18:14 +09:00
817c335f30
ticket: approve plugin fs host api 2026-06-20 01:15:35 +09:00
f8a1e9452e
ticket: record plugin fs host api merge 2026-06-20 01:14:52 +09:00
1097f35ed8
ticket: approve plugin fs host api 2026-06-20 01:12:56 +09:00
3dac71d0a5
ticket: approve plugin fs host api 2026-06-20 01:12:06 +09:00
993e407df2
ticket: approve plugin fs host api 2026-06-20 01:11:11 +09:00
c94e157b76
merge: plugin fs host api 2026-06-20 01:10:37 +09:00
ca988ffc3a
ticket: approve plugin fs host api 2026-06-20 01:10:37 +09:00
93bd6bdf01
ticket: dispatch plugin fs host api review 2026-06-20 01:02:48 +09:00
ec600c8806
ticket: record plugin fs host api implementation 2026-06-20 01:01:52 +09:00
717c0999a5
plugin: implement fs host api 2026-06-20 00:58:44 +09:00
c4d7ad8d0d
ticket: record plugin fs host api coder start 2026-06-20 00:39:20 +09:00
6711bcf300
ticket: accept plugin fs host api work 2026-06-20 00:37:54 +09:00
838b273d9c
ticket: close plugin https host api 2026-06-20 00:36:01 +09:00
f64570ee84
ticket: record plugin https host api merge 2026-06-20 00:34:10 +09:00
94cb37075a
ticket: approve plugin https host api 2026-06-20 00:32:31 +09:00
6beb8625bf
merge: plugin https host api 2026-06-20 00:29:59 +09:00
998225eb4e
ticket: approve plugin https host api 2026-06-20 00:29:59 +09:00
8de6b447ee
ticket: record plugin https target hardening fix 2026-06-20 00:23:18 +09:00
85683f17c3
plugin: harden https target validation 2026-06-20 00:21:37 +09:00
ffa8e2f25a
ticket: dispatch plugin https host api fixes 2026-06-20 00:12:33 +09:00
faadebc67a
ticket: request plugin https host api changes 2026-06-20 00:10:49 +09:00
748074ba9f
ticket: dispatch plugin https host api review 2026-06-20 00:05:11 +09:00
884accd976
ticket: record plugin https host api implementation 2026-06-20 00:04:16 +09:00
7377527f7c
plugin: implement https host api 2026-06-20 00:02:24 +09:00
e44827823a
ticket: record plugin https host api coder start 2026-06-19 23:26:40 +09:00
1fdef32a4d
ticket: accept plugin https host api work 2026-06-19 23:25:39 +09:00
da2dbfb108
ticket: close plugin cli inspection 2026-06-19 23:23:00 +09:00
f8230f9f59
ticket: record plugin cli inspection merge 2026-06-19 23:21:35 +09:00
71ca05c899
merge: plugin cli inspection 2026-06-19 23:18:51 +09:00
509ca60959
ticket: approve plugin cli inspection 2026-06-19 23:18:51 +09:00
be91977725
ticket: dispatch plugin cli missing-diagnostic re-review 2026-06-19 23:11:31 +09:00
22be375f1b
ticket: record plugin cli missing-diagnostic fix report 2026-06-19 23:10:41 +09:00
0142ef1d3f
plugin: distinguish present invalid packages 2026-06-19 23:08:06 +09:00
6e4c49df61
ticket: dispatch plugin cli missing-diagnostic fixes 2026-06-19 23:03:41 +09:00
4bf6b1bf0f
ticket: request plugin cli missing-diagnostic changes 2026-06-19 23:02:48 +09:00
d2ee3bf379
ticket: dispatch plugin cli invalid package re-review 2026-06-19 22:55:02 +09:00
f1c182072b
ticket: record plugin cli invalid package fix report 2026-06-19 22:54:13 +09:00
877ec94fc9
ticket: dispatch plugin cli invalid package fixes 2026-06-19 22:53:10 +09:00
a5709d8bfc
ticket: request plugin cli invalid package changes 2026-06-19 22:52:00 +09:00
a5f3b0b554
plugin: reject configured invalid packages 2026-06-19 22:51:02 +09:00
3a0fd1c219
ticket: dispatch plugin cli invalid package fixes 2026-06-19 22:45:54 +09:00
8e600311d0
ticket: request plugin cli invalid package changes 2026-06-19 22:45:09 +09:00
ea6355a73d
ticket: dispatch plugin cli schema re-review 2026-06-19 22:37:02 +09:00
3cfb3a647e
ticket: record plugin cli schema fix report 2026-06-19 22:36:03 +09:00
22af7fd342
ticket: queue 00001KVG0HR96 2026-06-19 22:34:43 +09:00
c0f70d1a88
merge: integrate orchestration branch 2026-06-19 22:33:30 +09:00
8b135cf47a
ticket: close plugin design umbrella 2026-06-19 22:29:47 +09:00
e5126321ad
ticket: plan plugin component model migration 2026-06-19 22:21:31 +09:00
982a1b75ed
plugin: validate inspected tool schemas 2026-06-19 20:47:11 +09:00
86c87ded89
ticket: dispatch plugin cli schema validation fixes 2026-06-19 20:42:31 +09:00
66821b30a7
ticket: request plugin cli schema validation changes 2026-06-19 20:41:32 +09:00
ecd3f124be
ticket: dispatch plugin cli metadata re-review 2026-06-19 20:34:35 +09:00
41db5a9bf9
ticket: record plugin cli metadata fix report 2026-06-19 20:33:55 +09:00
dfa966dbfc
plugin: report inspection package metadata 2026-06-19 20:31:26 +09:00
00a2459a86
ticket: dispatch plugin cli path api fixes 2026-06-19 20:24:14 +09:00
d5b718b380
ticket: request plugin cli path api changes 2026-06-19 20:23:32 +09:00
9e08291579
ticket: dispatch plugin cli inspection re-review 2026-06-19 20:17:57 +09:00
075cdfc810
ticket: record plugin cli inspection fix report 2026-06-19 20:17:15 +09:00
b5f10ab7dc
plugin: align inspection statuses 2026-06-19 20:15:48 +09:00
71f4c11fea
ticket: dispatch plugin cli inspection fixes 2026-06-19 20:08:47 +09:00
8a623394da
ticket: request plugin cli inspection changes 2026-06-19 20:08:06 +09:00
349a55fa33
ticket: dispatch plugin cli inspection review 2026-06-19 20:03:57 +09:00
83699e2011
ticket: record plugin cli inspection implementation 2026-06-19 20:03:12 +09:00
462de32a5a
plugin: add cli inspection 2026-06-19 19:58:10 +09:00
630548644d
ticket: record plugin cli inspection coder start 2026-06-19 19:24:02 +09:00
d51b610f97
ticket: record plugin host api waiting metadata 2026-06-19 19:23:07 +09:00
aea2a8a45d
ticket: accept plugin cli inspection work 2026-06-19 19:22:56 +09:00
3b026b2f5f
ticket: queue 00001KVFDX9AF 2026-06-19 19:19:53 +09:00
ecb23a1651
ticket: queue 00001KVFDX9AY 2026-06-19 19:19:52 +09:00
d7f0a718c3
ticket: queue 00001KVFD3YSV 2026-06-19 19:19:28 +09:00
f1876321c5
ticket: add plugin host api followups 2026-06-19 16:54:40 +09:00
8940262618
ticket: close panel startup e2e work 2026-06-19 14:44:18 +09:00
69ab9f7c22
fix: speed up panel startup pod probes 2026-06-19 13:19:49 +09:00
caf18dbaab
test: measure panel shell startup path 2026-06-19 13:04:45 +09:00
63 changed files with 10918 additions and 250 deletions

View File

@ -0,0 +1,82 @@
---
title: "Plugin platform roadmap"
state: "active"
created_at: "2026-06-19T13:18:58Z"
updated_at: "2026-06-19T13:18:58Z"
linked_tickets: ["00001KV5R5V2S", "00001KV5W3PHA", "00001KV5W3PHW", "00001KV5W3PJ3", "00001KVFD3YSV", "00001KVFDX9AF", "00001KVFDX9AY", "00001KVG0HR96"]
---
## Goal
Build Yoi's Plugin platform as a coherent extension system: packages are discovered and inspected safely, enabled explicitly, registered through typed Plugin surfaces, executed in a sandboxed runtime, constrained by Plugin-layer grants, and authored through SDK/templates rather than raw runtime ABI details.
The long-term platform goal is not merely to run Wasm. It is to make Plugin packages a durable, inspectable, permissioned, and authorable extension layer for Tools first, then host APIs (`https`, `fs`), and later Service / Ingress surfaces when concrete needs justify them.
## Motivation / background
The current Plugin foundation is already substantial:
- package discovery and explicit enablement resolver;
- Tool surface registration through the ordinary ToolRegistry/model-visible schema path;
- minimal sandboxed WASM Tool execution;
- Plugin permission grant enforcement;
- follow-up Tickets for read-only inspection CLI, `https`, `fs`, and Component Model migration.
The remaining work must be kept as one roadmap because the pieces constrain each other:
- Plugin authoring needs an SDK/PDK and examples, not raw pointer/length Wasm ABI hand-coding.
- `https` and `fs` host APIs must be grant-gated and shaped so they can move cleanly to typed Component Model interfaces.
- Diagnostics (`yoi plugin list/show`) are needed before the system becomes harder to debug.
- Component Model adoption should guide new host API design before a custom raw ABI becomes entrenched.
- Service / Ingress are useful for bridge-style integrations, but should come after Tool runtime, diagnostics, and host API policy are stable.
Research of common Wasm extension systems points to the same pattern: mature systems combine a package manifest, explicit capabilities, a sandbox runtime, host-provided capability APIs, language SDK/PDK bindings, templates/examples, inspection/check tooling, and versioned interfaces.
## Strategy / design direction
- Keep Plugin as a user-facing package/config/runtime layer above lower-level `pod::feature` substrate.
- `pod::feature` provides contribution/registration substrate.
- Plugin owns package discovery, enablement, grant policy, runtime selection, authoring UX, and user-facing diagnostics.
- Preserve authority boundaries.
- Package discovery is read-only inventory.
- Package presence never registers a Tool/Hook, executes Wasm, starts a Service, reads files, opens network, or injects context.
- Explicit enablement and Plugin grants are required before registration/execution/host API use.
- Tool calls/results continue through ordinary ToolRegistry and Worker history paths.
- Treat Component Model as the preferred future Plugin runtime shape.
- New typed Plugin host APIs should be designed in WIT-compatible terms.
- `runtime.kind = "wasm-component"` should become the preferred runtime once implemented.
- Current `yoi-plugin-wasm-1` raw core-Wasm ABI remains a compatibility / migration bridge until Component Model execution and authoring are validated.
- Sequence the platform in usable layers:
1. Package discovery / explicit enablement / digest-pinned restore. Completed foundation.
2. Tool surface registration. Completed foundation.
3. Minimal WASM Tool execution. Completed foundation.
4. Permission grants. Completed foundation.
5. Read-only Plugin CLI inspection (`yoi plugin list/show`) for debugging discovery/enablement/grants/runtime metadata.
6. `https` and `fs` host APIs for Tool Plugins, grant-gated and WIT-compatible.
7. Component Model runtime migration and authoring model.
8. Guest SDK/PDK, examples, `check`/`pack`/`new` authoring tooling.
9. Service / Ingress / WebSocket or inbound HTTP only after Tool + host API foundations are stable.
- Keep Discord-style bridge goals split into two stages.
- Outbound Discord/webhook Tool is possible after `https`.
- Bidirectional Discord bridge requires Service + Ingress + WebSocket or inbound HTTP and host routing policy.
## Success criteria / exit conditions
- Users can inspect Plugin discovery/enablement/grant/runtime state through a read-only CLI without executing Plugin code.
- Plugin authors can build a Tool Plugin without writing raw memory/pointer ABI plumbing.
- Tool Plugins can safely call grant-gated `https` and `fs` host APIs.
- Component Model support is available or a documented migration path is active, with WIT-compatible host API types and measured packaging/runtime impact.
- Plugin grants remain authoritative over registration, execution, and host API calls.
- Plugin diagnostics explain missing package, invalid manifest, digest/version mismatch, missing grant, rejected schema, runtime mismatch, and unsupported host API cases safely.
- Existing raw core-Wasm Plugin tests either remain passing or have an explicit compatibility/deprecation decision.
- Documentation covers package format, runtime kinds, Component Model direction, host API authority, authoring SDK/templates, and operational debugging.
- Service/Ingress work starts only after Tool Plugin + host API + diagnostics foundations are usable.
## Decision context
- This Objective is roadmap context, not Ticket authority. Implementation still requires reading concrete Ticket bodies, threads, artifacts, and relations.
- Component Model direction supersedes making Yoi's custom raw ABI the long-term authoring interface, but does not require an immediate flag-day rewrite.
- `https` / `fs` work should avoid choices that conflict with later WIT typed interfaces.
- Guest SDK work should either target Component Model directly or keep the raw ABI wrapper clearly transitional.
- Plugin and MCP remain separate. Component Model adoption for Plugin does not imply MCP server execution, MCP prompt/resource injection, or MCP trust policy changes.
- Plugin surfaces remain Tool / Hook / Service / Ingress; outbound side effects are Tool metadata and host API grants, not a separate surface.

View File

@ -1,8 +1,8 @@
---
title: 'Plugin: define runtime, surface, and minimal host API model'
state: 'planning'
state: 'closed'
created_at: '2026-05-31T01:00:05Z'
updated_at: '2026-06-14T17:22:23Z'
updated_at: '2026-06-19T13:29:26Z'
assignee: null
---

View File

@ -0,0 +1,8 @@
Closed as superseded by durable Objective and design documentation.
This Ticket was a broad Plugin runtime/surface/host API design record rather than a concrete implementation task. The useful design decisions have been moved into durable roadmap/design context:
- Objective `00001KVG0HR9M` (`Plugin platform roadmap`) now owns the overall Plugin roadmap and sequencing context.
- `docs/design/plugin-component-model.md` records Component Model research and migration direction.
- `docs/design/plugin-packages.md` records package/runtime metadata direction.
Concrete implementation work remains tracked by implementation Tickets such as package discovery, Tool registration, WASM runtime, permission grants, CLI inspection, `https`, `fs`, and Component Model runtime migration. Future Plugin work should be filed as concrete implementation Tickets, not broad design umbrella Tickets.

View File

@ -99,4 +99,29 @@ This preserves the desired detachable shape: feature state remains in the featur
- General-purpose host API は引き続き `https``fs` に絞る。`ingress.submit` と `diagnostics` は surface-intrinsic host calls として扱い、広い ambient capability にはしない。
---
<!-- event: state_changed author: hare at: 2026-06-19T13:29:26Z from: planning to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T13:29:26Z status: closed -->
## 完了
Closed as superseded by durable Objective and design documentation.
This Ticket was a broad Plugin runtime/surface/host API design record rather than a concrete implementation task. The useful design decisions have been moved into durable roadmap/design context:
- Objective `00001KVG0HR9M` (`Plugin platform roadmap`) now owns the overall Plugin roadmap and sequencing context.
- `docs/design/plugin-component-model.md` records Component Model research and migration direction.
- `docs/design/plugin-packages.md` records package/runtime metadata direction.
Concrete implementation work remains tracked by implementation Tickets such as package discovery, Tool registration, WASM runtime, permission grants, CLI inspection, `https`, `fs`, and Component Model runtime migration. Future Plugin work should be filed as concrete implementation Tickets, not broad design umbrella Tickets.
---

View File

@ -1,8 +1,8 @@
---
title: 'Panel 起動遅延の待ち要因を E2E 計測で特定し改善する'
state: 'done'
state: 'closed'
created_at: '2026-06-15T12:40:33Z'
updated_at: '2026-06-15T14:31:28Z'
updated_at: '2026-06-19T05:44:09Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['panel', 'tui', 'e2e', 'latency', 'runtime-observation']

View File

@ -0,0 +1,9 @@
Closed as completed by the subsequent Panel startup E2E and latency-improvement sequence.
The initial work separated first visible frame readiness from background reload, but later review showed that user-visible startup latency must be measured at dashboard content-ready, not first frame or single-row readiness. The later Tickets added dashboard snapshot readiness, shell-enter launch-path coverage, live workspace measurements, and the actual startup fix for duplicate Pod probes/session-log scans.
Relevant follow-ups:
- 00001KV62PF32: corrected readiness away from first frame / weak row count;
- 00001KVDETSN6: dashboard content-ready snapshot metric;
- 00001KVDQH839: shell-enter launch-path measurement;
- 00001KVF0ZJM5: fixed live startup by reusing initial Pod list presence and avoiding session-log reads before first rows.

View File

@ -215,4 +215,30 @@ Cleanup planned:
Reviewer approved, implementation/evidence branch merged into the orchestration branch, and E2E-focused validation passed in the Orchestrator worktree. Marking Ticket done in the orchestration branch.
---
<!-- event: state_changed author: hare at: 2026-06-19T05:44:09Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T05:44:09Z status: closed -->
## 完了
Closed as completed by the subsequent Panel startup E2E and latency-improvement sequence.
The initial work separated first visible frame readiness from background reload, but later review showed that user-visible startup latency must be measured at dashboard content-ready, not first frame or single-row readiness. The later Tickets added dashboard snapshot readiness, shell-enter launch-path coverage, live workspace measurements, and the actual startup fix for duplicate Pod probes/session-log scans.
Relevant follow-ups:
- 00001KV62PF32: corrected readiness away from first frame / weak row count;
- 00001KVDETSN6: dashboard content-ready snapshot metric;
- 00001KVDQH839: shell-enter launch-path measurement;
- 00001KVF0ZJM5: fixed live startup by reusing initial Pod list presence and avoiding session-log reads before first rows.
---

View File

@ -1,8 +1,8 @@
---
title: 'Panel startup latency E2E を一覧データ描画完了基準に修正する'
state: 'done'
state: 'closed'
created_at: '2026-06-15T16:44:06Z'
updated_at: '2026-06-18T13:30:51Z'
updated_at: '2026-06-19T05:44:09Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['panel', 'e2e', 'startup-latency', 'readiness-metric', 'ticket-list-rendering']

View File

@ -0,0 +1,8 @@
Closed as superseded/completed by the later dashboard content-ready and live startup latency work.
This Ticket corrected the mistaken premise that first frame readiness represented usable Panel startup readiness, but its single fixture-row rows-ready metric was later judged insufficient for the user-visible delay. Follow-up work strengthened the metric to dashboard content-ready snapshot validation and then used live-path measurements to identify and fix the Pod status probe/session-log startup bottleneck.
Relevant follow-ups:
- 00001KVDETSN6: user-visible dashboard content-ready metric and snapshot validation;
- 00001KVDQH839: shell-enter launch-path readiness measurement;
- 00001KVF0ZJM5: live startup latency fix by removing duplicate Pod probes and session-log reads from the initial list path.

View File

@ -294,4 +294,29 @@ The current result still does not answer the user-facing latency problem. The pr
Do not treat fixture first-frame or single-row readiness numbers as evidence that no improvement is needed. The acceptance criterion must be strengthened to a user-visible dashboard-content-ready point and paired with slow-source attribution/improvement for the live-like Panel startup path.
---
<!-- event: state_changed author: hare at: 2026-06-19T05:44:09Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T05:44:09Z status: closed -->
## 完了
Closed as superseded/completed by the later dashboard content-ready and live startup latency work.
This Ticket corrected the mistaken premise that first frame readiness represented usable Panel startup readiness, but its single fixture-row rows-ready metric was later judged insufficient for the user-visible delay. Follow-up work strengthened the metric to dashboard content-ready snapshot validation and then used live-path measurements to identify and fix the Pod status probe/session-log startup bottleneck.
Relevant follow-ups:
- 00001KVDETSN6: user-visible dashboard content-ready metric and snapshot validation;
- 00001KVDQH839: shell-enter launch-path readiness measurement;
- 00001KVF0ZJM5: live startup latency fix by removing duplicate Pod probes and session-log reads from the initial list path.
---

View File

@ -1,8 +1,8 @@
---
title: 'Orchestrator Ticket event Companion notify の peer registration / diagnostics を修正する'
state: 'done'
state: 'closed'
created_at: '2026-06-18T14:33:09Z'
updated_at: '2026-06-18T14:33:50Z'
updated_at: '2026-06-19T07:52:14Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['orchestrator', 'companion', 'peer-notify', 'ticket-event', 'auto-run-false', 'diagnostics']

View File

@ -0,0 +1,3 @@
Ticket `00001KVDJCVWZ` (`Orchestrator Ticket event Companion notify の peer registration / diagnostics を修正する`) はすでに `state: done` に到達していたため、workspace Panel から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -4,4 +4,24 @@
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: hare at: 2026-06-19T07:52:14Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T07:52:14Z status: closed -->
## 完了
Ticket `00001KVDJCVWZ` (`Orchestrator Ticket event Companion notify の peer registration / diagnostics を修正する`) はすでに `state: done` に到達していたため、workspace Panel から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -0,0 +1,13 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVDQH839",
"kind": "related",
"target": "00001KVDETSN6",
"note": "Adds shell-enter launch-path coverage on top of dashboard content-ready metric.",
"author": "yoi ticket",
"at": "2026-06-18T16:03:59Z"
}
]
}

View File

@ -0,0 +1,57 @@
---
title: 'Panel E2E に shell Enter 起動経路の dashboard readiness 計測を追加する'
state: 'closed'
created_at: '2026-06-18T16:02:56Z'
updated_at: '2026-06-19T05:44:09Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['panel', 'e2e', 'startup-latency', 'shell-launch', 'dashboard-content-ready']
---
## Background
既存の Panel dashboard readiness E2E は direct `Command::spawn(yoi panel ...)` から dashboard content ready までを測っていた。ユーザー目線の「Enter した瞬間から実コンテンツが表示されるまで」に近づけるため、isolated fixture は維持しつつ、shell 上で command line を投入して Enter された起動経路を部分的に模した E2E を追加する。
この Ticket は live workspace の遅延改善そのものではなく、E2E の起動経路を実利用に近づける追加計測である。
## Requirements
- PTY 上で `/bin/sh` を起動し、`exec <yoi> panel ...` を command line として送って Enter 相当から測定する。
- 測定開始点は command line を PTY に送る直前とする。
- dashboard readiness は既存の `dashboard_content_ready` snapshot matcher を使う。
- first frame だけ、単一 row だけでは通さない。
- Isolated fixture / isolated HOME / XDG dirs / runtime dirs は維持する。
- `YOI_POD_RUNTIME_COMMAND` は tested binary を明示して渡す。
- Direct spawn E2E は残し、shell-enter path は追加 coverage とする。
## Implementation summary
- `PanelHarness::spawn_via_shell_enter` を追加した。
- `/bin/sh` を PTY 上で起動。
- `exec '<binary>' '<args>'...` を送信。
- command line送信直前の `Instant` を返す。
- artifacts `run.json``launch_mode: shell_enter_exec` を記録。
- shell quote helper を追加した。
- `panel_dashboard_content_ready_from_shell_enter_path` E2E を追加した。
- Enter相当から first frame / dashboard content ready を測定。
- expected dashboard snapshot / source breakdown を検証。
## Validation
- `cargo test -p yoi-e2e --features e2e --test panel panel_dashboard_content_ready_from_shell_enter_path -- --nocapture`
- observed: dashboard content ready 約 `220ms`, first frame 約 `20ms` in isolated fixture。
- `cargo test -p yoi-e2e --features e2e --test panel`
- `cargo check -p yoi-e2e -p yoi -p tui --features tui/e2e-test`
- `cargo fmt --check`
- `git diff --check`
## Non-goals
- Live workspace startup latency の改善。
- Interactive shell の command lookup / user typing latency の完全再現。
- User's actual shell rc/profile を読むこと。
- Panel architecture / lifecycle の変更。
## Related work
- `00001KVDETSN6` — Panel startup latency をユーザー目線の dashboard content ready 基準で計測・改善する。

View File

@ -0,0 +1,10 @@
Closed as completed.
Added E2E coverage for a Panel shell-enter launch path:
- PTY starts `/bin/sh` and sends `exec <yoi> panel ...` as the command line;
- measurement starts immediately before sending the command line / Enter-equivalent input;
- the test waits for the existing dashboard content-ready snapshot rather than first frame or a single row;
- isolated HOME/XDG/runtime fixture remains in place and `YOI_POD_RUNTIME_COMMAND` is pinned to the tested binary;
- direct-spawn Panel readiness tests remain as separate coverage.
Validation was recorded during implementation, including the focused shell-enter test, the full Panel E2E test set, relevant cargo check, formatting, diff check, ticket doctor, and Nix build.

View File

@ -0,0 +1,34 @@
<!-- event: create author: "yoi ticket" at: 2026-06-18T16:02:56Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: hare at: 2026-06-19T05:44:09Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T05:44:09Z status: closed -->
## 完了
Closed as completed.
Added E2E coverage for a Panel shell-enter launch path:
- PTY starts `/bin/sh` and sends `exec <yoi> panel ...` as the command line;
- measurement starts immediately before sending the command line / Enter-equivalent input;
- the test waits for the existing dashboard content-ready snapshot rather than first frame or a single row;
- isolated HOME/XDG/runtime fixture remains in place and `YOI_POD_RUNTIME_COMMAND` is pinned to the tested binary;
- direct-spawn Panel readiness tests remain as separate coverage.
Validation was recorded during implementation, including the focused shell-enter test, the full Panel E2E test set, relevant cargo check, formatting, diff check, ticket doctor, and Nix build.
---

View File

@ -0,0 +1,21 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVF0ZJM5",
"kind": "related",
"target": "00001KVDETSN6",
"note": "Implements live startup latency improvement after dashboard content-ready measurement exposed Pod probe bottleneck.",
"author": "yoi ticket",
"at": "2026-06-19T04:19:09Z"
},
{
"ticket_id": "00001KVF0ZJM5",
"kind": "related",
"target": "00001KVDQH839",
"note": "Uses shell/live startup measurements added by the E2E launch-path work.",
"author": "yoi ticket",
"at": "2026-06-19T04:19:09Z"
}
]
}

View File

@ -0,0 +1,43 @@
---
title: 'Panel startup で Pod status probe を重複実行せず初回一覧表示を高速化する'
state: 'closed'
created_at: '2026-06-19T04:07:17Z'
updated_at: '2026-06-19T04:19:09Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['panel', 'startup-latency', 'pod-status-probe', 'live-path', 'performance']
---
## Background
Live workspace で `yoi panel` を起動すると、first frame は約 50ms で出る一方、実際の Ticket / Pod rows が表示されるまで約 8 秒かかっている。実測 breakdown では `pod_metadata_status_probe.initial`、`companion.presence`、`orchestrator.presence` がそれぞれ約 2.5 秒かかり、同じ Pod metadata / live status scan が初回 dashboard render 前に直列で重複実行されている。
この Ticket では初回一覧表示前の重複 Pod status probe をなくし、live Pod summary の重い session log scan を避け、ユーザー目線の「一覧が表示されるまで」を短縮する。
## Requirements
- `load_multi_pod_snapshot` で初回 `load_pod_list` の結果を Companion / Orchestrator presence 判定に再利用する。
- 初回 render 前に `load_exact_companion_pod_presence` / `load_exact_pod_presence` 相当の追加 full probe を直列実行しない。
- Live status probe は session log 全読みの preview/summary 作成を初回 path で行わない。
- stored metadata summary を優先して使う。
- live-only row は minimal live summary でよい。
- Companion / Orchestrator spawn/restore が必要な場合の reload は維持する。
- Existing Panel behavior を壊さない。
- Companion / Orchestrator live status 表示
- Queue action
- Pod rows open/attach
- E2E dashboard readiness
- Live workspace に近い例外的計測で、rows 表示までの時間が改善していることを確認する。
## Acceptance criteria
- Panel startup source breakdown で `companion.presence` / `orchestrator.presence` が追加 full Pod probe として秒単位で出ない。
- Live workspace 計測で first non-empty rows 表示が従来約 8 秒から明確に短縮する。
- `cargo test -p yoi-e2e --features e2e --test panel` が通る。
- `cargo check -p yoi-e2e -p yoi -p tui --features tui/e2e-test` が通る。
- `cargo fmt --check` / `git diff --check` / `target/debug/yoi ticket doctor` が通る。
## Related work
- `00001KVDETSN6` — Panel startup latency をユーザー目線の dashboard content ready 基準で計測・改善する。
- `00001KVDQH839` — Panel E2E に shell Enter 起動経路の dashboard readiness 計測を追加する。

View File

@ -0,0 +1,22 @@
Implemented and validated.
Changes:
- Reused the initial `load_pod_list` result for Companion and Orchestrator presence in `load_multi_pod_snapshot`, removing two duplicate full Pod status probes before the first dashboard rows render.
- Renamed the E2E source timings to `companion.presence.from_initial_list` and `orchestrator.presence.from_initial_list` so regressions show whether the initial list is reused.
- Changed Pod list startup summarization to avoid reading active session logs while building initial Pod rows. Stored metadata now uses a cheap active-segment marker and live-only rows keep existing minimal live/pending summaries.
- Preserved spawn/restore behavior after Companion/Orchestrator lifecycle changes; if lifecycle changes require reload, the existing reload path remains.
Live-path measurement in the current workspace:
- Before this fix: first non-empty Panel rows appeared at about 7967ms; `pod_metadata_status_probe.initial`, `companion.presence`, and `orchestrator.presence` were each about 2.5s.
- After removing duplicate probes only: first non-empty rows appeared at about 2964ms; duplicate presence probes dropped to 0ms but initial Pod metadata/status probe was still about 2386ms.
- After also removing session-log reads from the startup Pod summary path: first non-empty rows appeared at about 754ms; `pod_metadata_status_probe.initial` was about 138ms; total dashboard source breakdown was about 649ms.
Validation:
- cargo test -p tui pod_list --lib
- cargo test -p yoi-e2e --features e2e --test panel
- cargo check -p yoi-e2e -p yoi -p tui --features tui/e2e-test
- cargo build -p yoi
- cargo fmt --check
- git diff --check
- target/debug/yoi ticket doctor
- nix build .#yoi --no-link

View File

@ -0,0 +1,46 @@
<!-- event: create author: "yoi ticket" at: 2026-06-19T04:07:17Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: hare at: 2026-06-19T04:19:09Z from: inprogress to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T04:19:09Z status: closed -->
## 完了
Implemented and validated.
Changes:
- Reused the initial `load_pod_list` result for Companion and Orchestrator presence in `load_multi_pod_snapshot`, removing two duplicate full Pod status probes before the first dashboard rows render.
- Renamed the E2E source timings to `companion.presence.from_initial_list` and `orchestrator.presence.from_initial_list` so regressions show whether the initial list is reused.
- Changed Pod list startup summarization to avoid reading active session logs while building initial Pod rows. Stored metadata now uses a cheap active-segment marker and live-only rows keep existing minimal live/pending summaries.
- Preserved spawn/restore behavior after Companion/Orchestrator lifecycle changes; if lifecycle changes require reload, the existing reload path remains.
Live-path measurement in the current workspace:
- Before this fix: first non-empty Panel rows appeared at about 7967ms; `pod_metadata_status_probe.initial`, `companion.presence`, and `orchestrator.presence` were each about 2.5s.
- After removing duplicate probes only: first non-empty rows appeared at about 2964ms; duplicate presence probes dropped to 0ms but initial Pod metadata/status probe was still about 2386ms.
- After also removing session-log reads from the startup Pod summary path: first non-empty rows appeared at about 754ms; `pod_metadata_status_probe.initial` was about 138ms; total dashboard source breakdown was about 649ms.
Validation:
- cargo test -p tui pod_list --lib
- cargo test -p yoi-e2e --features e2e --test panel
- cargo check -p yoi-e2e -p yoi -p tui --features tui/e2e-test
- cargo build -p yoi
- cargo fmt --check
- git diff --check
- target/debug/yoi ticket doctor
- nix build .#yoi --no-link
---

View File

@ -0,0 +1 @@
{"id":"orch-plan-20260619-102132-1","ticket_id":"00001KVFD3YSV","kind":"accepted_plan","accepted_plan":{"summary":"`yoi plugin list` / `yoi plugin show <ref>` を product CLI に追加し、Plugin package discovery / enablement resolution / grant diagnostics / static Tool/runtime eligibility を read-only typed inspection reportとして表示する。Plugin code / WASM / Tool execution / mutation は行わない。","branch":"impl/00001KVFD3YSV-plugin-cli-inspection","worktree":"/home/hare/Projects/yoi/.worktree/00001KVFD3YSV-plugin-cli-inspection","role_plan":"Orchestrator は専用 implementation worktree を作成し、Coder をその worktree への narrow write scope で起動する。Reviewer は実装報告後に read-only で確認する。Host API implementation Tickets は関連するが、CLI inspection は read-only diagnostic surface として先行実装し、host API実装による追加表示は後続差分として扱える。"},"author":"yoi-orchestrator","at":"2026-06-19T10:21:32Z"}

View File

@ -0,0 +1,45 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVFD3YSV",
"kind": "depends_on",
"target": "00001KV5R5V2S",
"note": "CLI inspection consumes Plugin package discovery and enablement resolver output.",
"author": "yoi ticket",
"at": "2026-06-19T07:40:41Z"
},
{
"ticket_id": "00001KVFD3YSV",
"kind": "depends_on",
"target": "00001KV5W3PJ3",
"note": "CLI inspection should expose permission/grant diagnostics from the implemented grant model.",
"author": "yoi ticket",
"at": "2026-06-19T07:40:41Z"
},
{
"ticket_id": "00001KVFD3YSV",
"kind": "related",
"target": "00001KSXRQ4G8",
"note": "Uses established Plugin runtime/surface/host API terminology.",
"author": "yoi ticket",
"at": "2026-06-19T07:40:41Z"
},
{
"ticket_id": "00001KVFD3YSV",
"kind": "related",
"target": "00001KV5W3PHA",
"note": "Tool surface registration status should be visible in inspection output.",
"author": "yoi ticket",
"at": "2026-06-19T07:40:41Z"
},
{
"ticket_id": "00001KVFD3YSV",
"kind": "related",
"target": "00001KV5W3PHW",
"note": "Runtime config/status should be shown without executing Plugin code.",
"author": "yoi ticket",
"at": "2026-06-19T07:40:41Z"
}
]
}

View File

@ -0,0 +1,186 @@
---
title: 'Plugin: add read-only CLI inspection list/show'
state: 'closed'
created_at: '2026-06-19T07:39:23Z'
updated_at: '2026-06-19T14:22:41Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['plugin', 'cli', 'diagnostics', 'read-only', 'json-output', 'no-execution']
queued_by: 'workspace-panel'
queued_at: '2026-06-19T10:19:28Z'
---
## Background
Plugin package discovery / explicit enablement / Tool registration / WASM Tool runtime / permission grants まで実装されたため、次に必要なのは「なぜ Plugin が見えない / 有効化されない / 実行できないのか」を headless に確認できる read-only inspection surface である。
Panel や TUI diagnostic に出す前に、CLI で deterministic に確認できる `yoi plugin list` / `yoi plugin show <ref>` を追加する。この CLI は Plugin code を実行せず、package discovery、manifest parse、enablement resolution、grant validation、static diagnostics を表示するだけにする。
目的は、Plugin の多段 failure point を human / JSON の両方で確認できるようにすること。
```text
package discovered?
manifest valid?
api version compatible?
explicitly enabled?
digest/version/source match?
requested permission granted?
tool schema valid?
runtime config present?
```
## Requirements
- Top-level product CLI に read-only Plugin inspection command を追加する。
- `yoi plugin list`
- `yoi plugin show <ref>`
- `--json` output を最初から提供する。
- `yoi plugin list --json`
- `yoi plugin show <ref> --json`
- Human-readable output は JSON 用 typed report の thin formatting にする。
- Workspace / Profile resolution は通常起動に近い意味にする。
- default は current workspace。
- 既存 CLI 方針に合わせて `--workspace <path>` を扱う。
- Profile 指定が必要なら既存 Profile selector と整合する option を使う。
- Plugin code を実行しない。
- WASM module を実行しない。
- Tool call を発生させない。
- Hook / Service / Ingress を起動しない。
- Read-only とする。
- install / update / enable / disable / trust / sign / run は non-goal。
- Plugin package / config / Ticket / memory / Pod state を変更しない。
- Inspection report は typed data として実装する。
- future Panel diagnostic / tests / agent-readable output で再利用できる形にする。
- `list` は package/ref 単位の overview を出す。
- ref
- source
- package path (human output では必要に応じて短縮)
- version
- api version
- digest
- status
- enabled surfaces
- diagnostic count / summary
- `show <ref>` は詳細を出す。
- manifest metadata
- source-qualified identity
- package path
- digest
- version / api version
- runtime kind/config summary
- enabled surfaces
- Tool definitions and registration eligibility
- requested permissions
- granted permissions
- effective grants / denied grants
- diagnostics
- Status vocabulary を明確にする。
- `active`: enabled and statically valid for at least one surface/tool.
- `disabled`: discovered but not explicitly enabled.
- `missing`: enablement refers to a package that is not discovered.
- `rejected`: invalid manifest / incompatible api / digest mismatch / grant mismatch / invalid schema etc.
- `partial`: package is usable but some surfaces/tools are rejected.
- Diagnostics は bounded / safe にする。
- secret-like values / auth / file contents を出さない。
- path は必要最小限。JSON では absolute path が必要なら workspace/user store source と一緒に出す。
- denial / parse / digest / grant mismatch reasons を区別できる。
- Ambiguous unqualified ref は fail closed し、`show` で diagnostic を返す。
- JSON schema は stable typed structure として test で固定する。
## Example human output
`yoi plugin list`:
```text
REF SOURCE VERSION STATUS SURFACES DIGEST
project:example.echo project 0.1.0 active tool sha256:...
project:broken project - rejected - -
user:fetch user 0.2.1 disabled tool sha256:...
```
`yoi plugin show project:example.echo`:
```text
Plugin: project:example.echo
Source: project
Package: .yoi/plugins/example.echo.yoi-plugin
Version: 0.1.0
API: yoi-plugin-1
Digest: sha256:...
Status: active
Enabled surfaces:
- tool
Tools:
- example_echo
status: registered
schema: valid
external_write: false
Permissions:
Requested:
- surfaces.tool
- tool:example_echo
Granted:
- surfaces.tool
- tool:example_echo
Diagnostics:
- none
```
## Acceptance criteria
- `yoi plugin list` prints a bounded human-readable overview without executing Plugin code.
- `yoi plugin show <ref>` prints detailed static inspection for a Plugin ref without executing Plugin code.
- `--json` output is available for both commands and uses a stable typed structure.
- Valid enabled Plugin appears as `active`.
- Discovered but not enabled Plugin appears as `disabled`.
- Enabled but missing package appears as `missing`.
- Invalid manifest / incompatible api version appears as `rejected` with diagnostic.
- Digest / version / source mismatch appears as diagnostic.
- Grant denial / missing requested permission appears as diagnostic.
- Partial tool/surface rejection can be represented without marking the whole package as fully active.
- Ambiguous unqualified id fails closed with diagnostic.
- Plugin code / WASM / Tool execution is not triggered by list/show.
- Tests cover:
- list human output for active / disabled / rejected / missing packages
- show human output for active package with Tool surface and grants
- JSON list structure
- JSON show structure
- invalid manifest diagnostic
- digest mismatch diagnostic
- missing grant diagnostic
- ambiguous ref diagnostic
- no runtime execution from inspection path
- Validation: focused CLI/plugin inspection tests, relevant `cargo check` / `cargo test`, `cargo fmt --check`, `git diff --check`, and `nix build .#yoi` because product CLI / packaging surface changes.
## Non-goals
- Plugin install / update / remove.
- Enable / disable mutation.
- Trust / signature / registry implementation.
- Plugin code execution.
- WASM validation beyond static runtime config/manifest inspection.
- `https` host API implementation.
- `fs` host API implementation.
- Service / Ingress startup.
- Panel/TUI Plugin diagnostics UI.
## Implementation notes
- Product CLI ownership stays in the `yoi` crate.
- Avoid embedding resolver logic directly in display formatting; build a typed inspection report first.
- Reuse existing Plugin resolver / diagnostics where possible.
- Keep CLI output deterministic and suitable for tests.
- Do not introduce user-facing terminology `contribution category`; use Plugin runtime / surface / host API / grants.
## Related work
- `00001KV5R5V2S` — Plugin package discovery and explicit enablement resolver.
- `00001KV5W3PHA` — Plugin Tool surface registration.
- `00001KV5W3PHW` — Plugin Tool execution with minimal WASM runtime.
- `00001KV5W3PJ3` — Plugin permission grant enforcement.
- `00001KSXRQ4G8` — Plugin runtime / surface / minimal host API model design.

View File

@ -0,0 +1,44 @@
Ticket `00001KVFD3YSV` is complete.
Completed implementation:
- Added read-only Plugin inspection CLI commands:
- `yoi plugin list`
- `yoi plugin show <ref>`
- JSON output support.
- Added typed Plugin inspection report used by both JSON and human output.
- Inspection reports package path/location, schema/API version, source/ref/digest/version, requested permissions, grants/denials, diagnostics, Tool/static eligibility, and future host API eligibility structure.
- Status vocabulary is limited to `active`, `disabled`, `missing`, `rejected`, `partial`.
- Implemented static Tool definition inspection for invalid/duplicate Tool names and invalid `input_schema`.
- Distinguished truly absent configured package refs (`missing`) from present-but-invalid packages (`rejected`), including `Missing` diagnostics for missing root `plugin.toml` or missing referenced runtime/package entries.
- Preserved read-only/no-execution behavior: inspection does not execute Plugin WASM or Tool code.
- Kept diagnostics bounded and structured.
Reviewed / merged:
- Implementation commits:
- `462de32a` (`plugin: add cli inspection`)
- `b5f10ab7` (`plugin: align inspection statuses`)
- `dfa966db` (`plugin: report inspection package metadata`)
- `982a1b75` (`plugin: validate inspected tool schemas`)
- `a5f3b0b5` (`plugin: reject configured invalid packages`)
- `0142ef1d` (`plugin: distinguish present invalid packages`)
- Multiple review rounds requested and verified fixes for status vocabulary, package metadata fields, Tool schema/name validation, configured invalid package status, and present-but-invalid `Missing` diagnostics.
- Final review `yoi-reviewer-00001KVFD3YSV-r6` approved with no blockers.
- Orchestrator merge commit: `71ca05c8` (`merge: plugin cli inspection`)
Validation in Orchestrator worktree:
- `cargo fmt --check` — passed
- `cargo check -p yoi -p pod -p manifest` — passed
- `cargo test -p yoi plugin -- --nocapture` — passed; 11 passed, 0 failed
- `cargo test -p pod static_inspection -- --nocapture` — passed; 4 passed, 0 failed
- `cargo test -p pod plugin -- --nocapture` — passed; 31 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Cleanup:
- Stopped Coder Pod `yoi-coder-00001KVFD3YSV`.
- Stopped Reviewer Pod `yoi-reviewer-00001KVFD3YSV-r6`.
- Removed child worktree `/home/hare/Projects/yoi/.worktree/00001KVFD3YSV-plugin-cli-inspection`.
- Deleted merged branch `impl/00001KVFD3YSV-plugin-cli-inspection`.
Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction. The completed work is integrated on the Orchestrator branch.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
{"id":"orch-plan-20260619-102132-1","ticket_id":"00001KVFDX9AF","kind":"waiting_capacity_note","note":"明示 queue review で確認済み。依存 Ticket `00001KV5W3PHW` / `00001KV5W3PJ3` は closed で blocker ではないが、同時 queued の `00001KVFD3YSV` CLI inspection と `00001KVFDX9AY` fs host API はいずれも Plugin manifest/grant/runtime/diagnostic 周辺を触る。まず read-only CLI inspection を開始し、host API implementation は conflict / reviewer-coder bottleneck を避けるため queued のまま待機する。次の routing pass で再確認する。","author":"yoi-orchestrator","at":"2026-06-19T10:21:32Z"}
{"id":"orch-plan-20260619-102132-2","ticket_id":"00001KVFDX9AF","kind":"do_not_parallelize","related_ticket":"00001KVFDX9AY","note":"`https` と `fs` host API はどちらも WASM Plugin Tool runtime host import boundary、Plugin grant model、diagnostics/tests/package behavior に触れるため、同時実装は conflict risk が高い。片方の merged/validated 後にもう片方を再 routing する。","author":"yoi-orchestrator","at":"2026-06-19T10:21:32Z"}
{"id":"orch-plan-20260619-142431-3","ticket_id":"00001KVFDX9AF","kind":"accepted_plan","accepted_plan":{"summary":"WASM Plugin Tool runtime に明示 grant された HTTPS outbound host API を追加する。HTTPS-only、private/local target rejection、method/host/path allowlist、bounded request/response/timeout/redirect/diagnostics、secret redaction、ordinary Tool result path、no ambient env/network authority を満たす。","branch":"impl/00001KVFDX9AF-plugin-https-host-api","worktree":"/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api","role_plan":"Orchestrator は専用 implementation worktree を作成し、Coder をその worktree への narrow write scope で起動する。Reviewer は実装報告後に read-only で確認する。`fs` host API と Component Model migration は重複する Plugin runtime/grant surface のため queued hold を維持する。"},"author":"yoi-orchestrator","at":"2026-06-19T14:24:31Z"}

View File

@ -0,0 +1,37 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVFDX9AF",
"kind": "depends_on",
"target": "00001KV5W3PHW",
"note": "https host API is implemented inside the WASM Plugin Tool runtime.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AF",
"kind": "depends_on",
"target": "00001KV5W3PJ3",
"note": "https host API must be guarded by Plugin permission grants.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AF",
"kind": "related",
"target": "00001KSXRQ4G8",
"note": "Uses established Plugin host API terminology.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AF",
"kind": "related",
"target": "00001KVFD3YSV",
"note": "Inspection CLI should expose https host API grants/diagnostics.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
}
]
}

View File

@ -0,0 +1,98 @@
---
title: 'Plugin: implement https host API for Tool runtime'
state: 'closed'
created_at: '2026-06-19T07:53:13Z'
updated_at: '2026-06-19T15:35:46Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['plugin', 'https', 'host-api', 'network', 'sandbox', 'secrets', 'permission-grants']
queued_by: 'workspace-panel'
queued_at: '2026-06-19T10:19:53Z'
---
## Background
Plugin Tool runtime は minimal WASM execution と permission grants まで実装済みだが、外部 HTTPS API を呼ぶ host API はまだ未実装である。
この Ticket では、WASM Plugin Tool から明示 grant された outbound HTTPS request だけを実行できる `https` host API を追加する。これは Discord webhook / REST API など outbound integration の前提になる。ただし Service / Ingress / WebSocket / inbound HTTP はこの Ticket の対象外。
用語は `web` ではなく `https` とする。
## Requirements
- WASM Plugin Tool runtime に `https` host API import を追加する。
- API 名・ABI は既存 `yoi-plugin-wasm-1` / host import 設計と整合させる。
- Plugin は ambient network access を持たず、host API 経由のみで HTTPS request できる。
- HTTPS only とする。
- `http://` は reject。
- localhost / private / link-local / unix socket / file URL 等は reject。
- Permission grants と統合する。
- manifest requested permissions の `host_api.https` を読む。
- config granted permissions と照合する。
- grant がない場合は fail closed。
- host / method / optional path prefix などの allowlist を表現できるようにする。
- Request を bounded にする。
- method allowlist。
- request body size bound。
- header count / size bound。
- response body size bound。
- timeout。
- redirect policy。
- Credentials は ambient env から読まない。
- header / auth は explicit config / secret ref 経由だけにする。
- diagnostics に secret-like header / token / body content を漏らさない。
- Response は Tool result に安全に戻せる bounded structure にする。
- status code
- bounded headers if needed
- bounded body text / bytes policy
- truncated flag
- Failure は structured Tool error にする。
- grant denied
- URL rejected
- private/local host rejected
- timeout
- response too large
- network error
- unsupported method
- Plugin code / history / model context に hidden context injection しない。
- HTTPS response は Tool result として通常の tool history 経路に残す。
## Acceptance criteria
- Granted Plugin Tool can perform an allowed HTTPS request through host API.
- Request without `host_api.https` grant fails closed before network access.
- Disallowed host / method / URL scheme fails closed.
- `http://`, localhost, private IP, link-local, and local/private host targets are rejected.
- Timeout and response size bounds are enforced.
- Request / response diagnostics are bounded and redact secret-like values.
- No ambient env credentials or ambient network APIs are exposed to WASM.
- Tool result path remains ordinary Tool result/history path.
- Tests cover:
- allowed HTTPS request with grant
- missing grant denied
- disallowed host denied
- method denied
- http scheme denied
- private/local host denied
- timeout
- response truncation / size bound
- secret header redaction
- no network access without host API import/grant
- Validation: focused plugin https tests, relevant cargo check/test, `cargo fmt --check`, `git diff --check`, and `nix build .#yoi` because dependency/package/network code may change.
## Non-goals
- `fs` host API implementation.
- WebSocket / SSE / timer host APIs.
- Service surface lifecycle.
- Ingress surface.
- Discord Gateway bridge.
- Inbound HTTP server.
- Plugin package manager / install/update.
## Related work
- `00001KV5W3PHW` — Plugin Tool execution with minimal WASM runtime.
- `00001KV5W3PJ3` — Plugin permission grant enforcement.
- `00001KVFD3YSV` — Plugin read-only CLI inspection list/show.
- `00001KSXRQ4G8` — Plugin runtime / surface / minimal host API model design.

View File

@ -0,0 +1,37 @@
Ticket `00001KVFDX9AF` is complete.
Completed implementation:
- Added granted outbound HTTPS host API for WASM Plugin Tools.
- Added typed `host_api.https` grant scope with host, method, optional path prefix, and bounded request/response options.
- Implemented `yoi:https` WASM host import handling.
- Enforced grant/allowlist checks before network access.
- Enforced HTTPS-only behavior and rejected `http://`, embedded credentials, localhost/private/link-local/local targets, IPv4-mapped/compatible IPv6 private/local forms, and unsafe DNS results.
- Bound DNS validation to the actual reqwest connection path by pinning validated public socket addresses with `resolve_to_addrs`.
- Added request/response bounds, timeout, no redirects, `no_proxy()`, response truncation, and secret-like diagnostics redaction.
- Preserved ordinary Tool result/history path and avoided hidden context injection.
- Updated Plugin CLI inspection and manifest/permission model to expose HTTPS host API grant/diagnostic details.
Reviewed / merged:
- Implementation commits:
- `7377527f` (`plugin: implement https host api`)
- `85683f17` (`plugin: harden https target validation`)
- First review requested changes for IPv4-mapped IPv6 bypass and DNS validation TOCTOU.
- Re-review approved with no remaining blockers.
- Orchestrator merge commit: `6beb8625` (`merge: plugin https host api`)
Validation in Orchestrator worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 39 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Cleanup:
- Stopped Coder Pod `yoi-coder-00001KVFDX9AF`.
- Stopped Reviewer Pod `yoi-reviewer-00001KVFDX9AF-r2`.
- Removed child worktree `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`.
- Deleted merged branch `impl/00001KVFDX9AF-plugin-https-host-api`.
Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction. The completed work is integrated on the Orchestrator branch.

View File

@ -0,0 +1,766 @@
<!-- event: create author: "yoi ticket" at: 2026-06-19T07:53:13Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: workspace-panel at: 2026-06-19T10:19:53Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T10:22:26Z -->
## Plan
Routing hold / waiting-capacity note:
明示 queue review で Ticket body / relations / orchestration plan / workspace state を確認した。
Current decision:
- この Ticket は `queued` のまま待機。
- 依存 Ticket `00001KV5W3PHW` / `00001KV5W3PJ3` は closed で dependency blocker ではない。
- ただし同時 queued の `00001KVFD3YSV` CLI inspection を先に受理した。CLI inspection は Plugin discovery / enablement / grants / diagnostics の read-only public surface を作る作業で、host API implementation と同じ Plugin manifest/grant/runtime/diagnostic 周辺に触れる。
- `00001KVFDX9AY` fs host API とは WASM Plugin Tool runtime host import boundary、Plugin grant model、diagnostics/tests/package behavior の変更面が重なるため `do_not_parallelize` plan record を残した。
Bounded reason for idle queued:
- conflict / reviewer-coder bottleneck。
Next action:
- `00001KVFD3YSV` の implementation/review/merge outcome を確認後、queued のまま再 routing する。
- その時点で `fs` host API Ticket との ordering / conflict も再確認する。
---
<!-- event: decision author: yoi-orchestrator at: 2026-06-19T14:25:13Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Panel Queue により、この Ticket は Orchestrator routing 対象として明示許可された。
- Ticket body には、HTTPS host API intent、binding invariants、acceptance criteria、non-goals、validation、escalation-worthy risk domain が実装可能な粒度で揃っている。
- 依存 relation の `00001KV5W3PHW` minimal WASM runtime、`00001KV5W3PJ3` permission grants、関連 `00001KVFD3YSV` CLI inspection は closed で blocker ではない。
- Risk domain は network / secrets / host API / permission grants だが、Ticket は HTTPS-only、private/local target rejection、grant allowlist、bounded request/response/timeout/diagnostics、no ambient env/network、ordinary Tool result path を binding invariants として明示している。bounded context check 後も implementation 前に必要な追加 human decision は見つからなかった。
- 同時 queued の `00001KVFDX9AY` fs host API と `00001KVG0HR96` Component Model migration は Plugin runtime/grant/diagnostic/packaging surface が重なるため、waiting/conflict notes を更新し queued のまま待機する。
Evidence checked:
- Ticket `00001KVFDX9AF` body / thread / artifacts。
- `TicketRelationQuery(00001KVFDX9AF)`: depends_on は closed。related Ticket は context であり acceptance blocker ではない。
- `TicketOrchestrationPlanQuery(00001KVFDX9AF)`: 既存 waiting/do_not_parallelize records を確認。今回 `accepted_plan` を記録済み。
- Related completed Tickets:
- `00001KV5W3PHW` — minimal WASM Tool runtime closed。
- `00001KV5W3PJ3` — Plugin permission grants closed。
- `00001KVFD3YSV` — Plugin read-only CLI inspection closed。
- Current queued Tickets:
- `00001KVFDX9AY` fs host API: do_not_parallelize / waiting reason を維持。
- `00001KVG0HR96` Component Model migration: migration boundary / conflict waiting note を更新。
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration`: clean。
- Existing branch/worktree: matching `00001KVFDX9AF` branch/worktree はなし。
- Visible Pods: self / peer / intake only; spawned child capacity is free。
- Current code map:
- `crates/pod/src/feature/plugin.rs`: Plugin resolver, permission grants, static inspection, WASM tool feature。
- `crates/pod/src/pod.rs`: WASM Tool runtime / `run_plugin_wasm_tool` / host import validation。
- `crates/manifest/src/plugin.rs`: Plugin manifest and permission model。
- `crates/yoi/src/plugin_cli.rs`: read-only inspection output should remain compatible with host API diagnostics。
IntentPacket:
Intent:
- WASM Plugin Tool runtime に、明示 grant された outbound HTTPS request だけを実行できる `https` host API を追加する。
- Plugin は ambient network access を持たず、host API import + requested permission + config grant + allowlist を満たす場合だけ bounded HTTPS request を実行できる。
Binding decisions / invariants:
- Host API name/domain は `https`。`web` ではない。
- HTTPS-only。`http://`、localhost、private IP、link-local、unix socket、file URL、local/private host targets は reject。
- Grant がない場合、network access 前に fail closed。
- host / method / optional path prefix などの allowlist を表現し、grant と request を照合する。
- Request/response は bounded。
- method allowlist
- request body size bound
- header count/size bound
- response body size bound
- timeout
- redirect policy
- Credentials は ambient env から読まない。header/auth は explicit config / secret ref 経由だけ。
- Diagnostics に secret-like header/token/body content を漏らさない。
- HTTPS response は hidden context injection ではなく ordinary Tool result/history path に残す。
- `fs` host API、WebSocket/SSE/timers、Service/Ingress lifecycle、Plugin package manager は non-goals。
Requirements / acceptance criteria:
- Granted Plugin Tool can perform an allowed HTTPS request through host API。
- Missing `host_api.https` grant denies before network access。
- Disallowed host / method / URL scheme denies。
- `http://`, localhost, private IP, link-local, local/private host targets reject。
- Timeout and response-size bounds are enforced。
- Request/response diagnostics are bounded and redact secret-like values。
- No ambient env credentials or ambient network APIs are exposed to WASM。
- Tool result path remains ordinary Tool result/history path。
- Tests cover allowed HTTPS, missing grant, disallowed host/method/scheme/private target, timeout, response truncation, secret redaction, no network without host API import/grant。
Implementation latitude:
- Choose exact ABI/import shape consistent with existing `yoi-plugin-wasm-1` host import design。
- Choose narrow grant config representation for host/method/path allowlist consistent with current Plugin permission grant model。
- Use local deterministic test server/mock if needed for allowed HTTPS/timeout/response bound tests, but keep network-safety tests deterministic。
- Choose bounded response header/body representation that fits existing Tool result error/result types。
Escalate if:
- Implementing HTTPS requires broad runtime executor redesign or Component Model migration first。
- Secure host/method/path grant model cannot be represented without breaking existing permission grant schema。
- SecretRef handling requires new secret-store public API beyond explicit config references。
- Safe private/local-host rejection cannot be implemented deterministically enough for tests。
- Host API result/error shape requires product-visible Tool result schema decision beyond this Ticket。
Validation:
- Focused plugin HTTPS host API tests。
- Relevant `cargo test` / `cargo check` for `pod`, `manifest`, `yoi` as changed。
- `cargo fmt --check`
- `git diff --check`
- `nix build .#yoi` because dependency/package/network code may change。
Critical risks / reviewer focus:
- Network access before grant/allowlist checks。
- Private/local/localhost bypass through DNS, redirects, IPv6, encoded URLs, or scheme confusion。
- Secret leakage in diagnostics or Tool result/history。
- Ambient env credential usage。
- Unbounded request/response bodies or headers。
- Hidden context injection by bypassing normal Tool result path。
- Breaking existing Plugin permission grant enforcement or CLI inspection semantics。
Next action:
- `queued -> inprogress` を記録し、Ticket records を Orchestrator worktree に commit してから、専用 implementation worktree を作成し Coder Pod を narrow write scope で起動する。root/original workspace は操作しない。
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T14:25:28Z from: queued to: inprogress reason: orchestrator_acceptance_plugin_https_host_api field: state -->
## State changed
Ticket body/thread, relation metadata, orchestration plan records, related completed Tickets, Orchestrator worktree, visible Pods, existing branch/worktree, and bounded Plugin host API code context were checked. Depends-on blockers are closed, prior CLI inspection related work is closed, and no dirty-state blocker or missing planning decision was found. `fs` host API and Component Model migration remain queued with conflict/migration waiting records. Accepting this queued Ticket for implementation before worktree/Pod side effects.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T14:26:32Z -->
## Implementation report
Implementation start note:
`queued -> inprogress` acceptance、accepted plan、routing decision / IntentPacket、fs host API / Component Model migration の waiting records を記録し、Orchestrator worktree で commit した後に、専用 implementation worktree と Coder Pod を起動した。
Worktree:
- `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
Coder Pod:
- `yoi-coder-00001KVFDX9AF`
Scope / boundaries:
- child runtime workspace root は read scope。
- implementation worktree は write scope。
- root/original workspace と Orchestrator worktree へは書き込まないよう指示済み。
- `.yoi/memory`、runtime/log/lock/local/secret-like `.yoi` paths は編集しないよう指示済み。
Related queued Tickets:
- `00001KVFDX9AY` fs host API remains queued due existing do_not_parallelize / conflict with HTTPS host API.
- `00001KVG0HR96` Component Model migration remains queued due migration boundary / conflict with active host API work.
Next:
- Coder は granted HTTPS host API implementation、focused tests、validation、implementation commit を行い、実装報告を返す予定。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:04:07Z -->
## Implementation report
Coder implementation report received / Orchestrator validation:
Implementation commit:
- `73775275``plugin: implement https host api`
Summary:
- Added typed HTTPS host API grant scope to Plugin permission model:
- hosts
- methods
- optional path prefixes
- bounded request/response options
- Implemented `yoi:https` WASM host import handling in the Plugin Tool runtime。
- Added deterministic local TLS / HTTPS test harness for runtime tests。
- Added URL validation / network safety checks:
- HTTPS-only;
- reject `http://`;
- reject localhost/private/link-local/local targets;
- reject disallowed hosts/methods/schemes before network access。
- Added request/response bounds and diagnostics:
- body/header limits;
- response truncation;
- timeout;
- bounded/redacted error messages。
- Preserved no ambient env credentials and ordinary Tool result/history path semantics。
- Updated read-only Plugin CLI inspection so HTTPS host API grant details / diagnostics remain inspectable。
- Updated `package.nix` cargo hash for new dependency/build closure.
Files changed:
- `Cargo.lock`
- `crates/manifest/src/plugin.rs`
- `crates/pod/Cargo.toml`
- `crates/pod/src/feature/plugin.rs`
- `crates/pod/src/pod.rs`
- `crates/yoi/src/plugin_cli.rs`
- `package.nix`
Coder reported validation:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 36 passed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Orchestrator evidence checked:
- Pod output を確認。
- implementation worktree は clean。
- branch `impl/00001KVFDX9AF-plugin-https-host-api` HEAD は `73775275`
- diff stat from accepted base `1fdef32a..73775275`: 7 files changed, 1799 insertions, 97 deletions。
Validation re-run by Orchestrator in implementation worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 36 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Routing classification: review_needed
Next action:
- recorded intent / invariants / acceptance criteria に照らして、read-only Reviewer Pod で外部レビューする。
- 特に grant-before-network、private/local rejection、redirect/DNS bypass、bounds/timeouts、secret redaction、ordinary Tool result path、no ambient env credentials、CLI inspection compatibility、dependency/package.nix correctness を確認する。
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T15:04:59Z -->
## Plan
Review dispatch:
Reviewer Pod `yoi-reviewer-00001KVFDX9AF` を read-only scope で起動した。
Review target:
- worktree: `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commit: `73775275`
Reviewer focus:
- recorded intent / invariants / acceptance criteria に対する適合性。
- network access が grant/allowlist checks の前に発生しないこと。
- private/local/localhost/link-local target rejection が DNS / IPv6 / redirect / scheme edge cases を含め妥当であること。
- timeout / request-response size / header bounds が enforce されること。
- secret-like values が errors / diagnostics / Tool results に漏れないこと。
- ambient env credential / ambient network access path が WASM に exposed されていないこと。
- HTTPS response が ordinary Tool result/history path だけに戻ること。
- allowed HTTPS / missing grant / disallowed host-method-scheme-private target / timeout / truncation / redaction / no network without grant/import tests が意味を持つこと。
- manifest/permission grant schema changes が existing grants / CLI inspection と整合していること。
- `Cargo.lock` / `package.nix` update が妥当で、`nix build .#yoi --no-link` validation と整合していること。
- `fs` host API と Component Model migration が non-goals として保たれていること。
Next:
- Reviewer output を確認し、approve なら Orchestrator worktree への merge/validation/cleanup/close-ready 処理へ進む。request_changes なら blocker を記録して Coder に戻す。
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AF at: 2026-06-19T15:10:37Z status: request_changes -->
## Review: request changes
Review result: request_changes
対象:
- Ticket: `00001KVFDX9AF`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commit: `7377527f` (`plugin: implement https host api`)
Blockers:
1. Private/local target rejection is bypassable with IPv4-mapped IPv6 literals.
- `validate_static_https_target` parses the URL host as `IpAddr` and delegates IPv6 literals to `is_forbidden_ipv6` (`crates/pod/src/feature/plugin.rs:622-631`, `665-669`).
- `is_forbidden_ipv6` rejects loopback/unspecified/ULA/link-local/multicast (`699-705`) but does not check `Ipv6Addr::to_ipv4_mapped()` / IPv4-compatible forms.
- Rust confirms `::ffff:127.0.0.1` is not `is_loopback()` as IPv6, while `to_ipv4_mapped()` is `Some(127.0.0.1)`.
- A granted request to `https://[::ffff:127.0.0.1]/...` would therefore pass the current private/local filter if the grant host matches, violating localhost/private IP rejection and the IPv6 edge-case reviewer focus.
2. DNS private-address validation is TOCTOU and does not bind the checked address set to the actual connection.
- `ReqwestPluginHttpsClient::execute` calls `validate_dns_target(url)` before constructing/sending the request (`crates/pod/src/feature/plugin.rs:365-405`).
- `validate_dns_target` resolves with `ToSocketAddrs` and rejects private/local addresses in that returned set (`641-655`), but `reqwest` performs its own DNS resolution later during `builder.send()` (`405`).
- DNS rebinding / split answers between the preflight lookup and reqwests lookup can pass validation on a public address then connect to a private/local address.
- Ticket critical risks explicitly include DNS/private/local bypasses before network access.
Requested changes:
- Reject IPv4-mapped IPv6 addresses by applying the existing IPv4 forbidden checks to mapped IPv4 addresses; add tests for mapped loopback/private/link-local examples.
- Bind DNS validation to the actual connection path. Either pin validated public `SocketAddr`s into the reqwest client/request resolver path, or otherwise ensure the actual connected remote address is validated / not private.
- Add deterministic test coverage around the real validation edge cases where feasible.
Supporting positive findings:
- Grant checks happen before request parsing/DNS/network dispatch in `execute_plugin_https_request`, and WASM import validation denies `yoi:https` imports without requested/configured `host_api.https` grant before instantiation.
- Scheme, embedded credentials, method allowlist, request descriptor/body/header count/size, response body/header bounds, timeout, no redirects, and `no_proxy()` are present.
- WASM imports are narrow (`yoi:tool`, `yoi:https` only), WASI/ambient imports are rejected, and `yoi:fs` remains explicitly unimplemented.
- Response path remains ordinary plugin Tool output/history path.
- Manifest/CLI schema additions are mostly compatible: new `PluginPermission::HostApi`, `PluginHttpsGrant`, defaulted `grants.https`, and CLI display of configured HTTPS grants.
Test gap:
- Current HTTPS-focused tests use `MockHttpsClient` for allowed/denied/timeout/truncation behavior. That is useful for grant ordering, but it does not exercise the real `ReqwestPluginHttpsClient` DNS filtering, redirect policy, TLS/HTTPS path, or actual timeout/read-limit behavior. Given the security-sensitive DNS/IP requirements, add coverage for the real validation edge cases above.
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T15:12:19Z -->
## Plan
Review follow-up dispatch:
Reviewer `yoi-reviewer-00001KVFDX9AF``request_changes` を返したため、Ticket review として記録し、既存 Coder Pod `yoi-coder-00001KVFDX9AF` に修正依頼を送った。
Blockers:
1. IPv4-mapped IPv6 literals が private/local target rejection を bypass し得る。
- `::ffff:127.0.0.1` などを IPv6 として見るだけでは loopback/private 判定に漏れる。
- `Ipv6Addr::to_ipv4_mapped()` 等で mapped IPv4 を取り出し、既存 IPv4 forbidden checks を適用する必要がある。
2. DNS private-address validation が TOCTOU で actual connection path に bind されていない。
- preflight `ToSocketAddrs` validation 後に `reqwest` が別途 DNS resolution するため、DNS rebinding / split answers で public answer validation 後に private/local address へ接続し得る。
- validated public `SocketAddr`s を reqwest client/request resolver path に pin する、または actual connected remote address を検証する必要がある。
Required fixes sent to Coder:
- IPv4-mapped IPv6 addresses を mapped IPv4 として private/local/link-local/loopback checks に通す。
- mapped loopback/private/link-local の tests を追加する。
- DNS validation を actual connection path に bind する設計へ修正する。
- DNS / actual connection edge の deterministic test coverage を追加する。
- Prior positives を維持する: grant-before-network、WASM import grant validation、HTTPS-only、embedded credentials rejection、method allowlist、bounds、timeout、no redirects、`no_proxy()`、ordinary Tool result path、manifest/CLI schema compatibility。
Next:
- Coder の修正 commit / validation report を待つ。
- 修正後、network-safety edge cases と grant-before-network invariants を中心に再レビューする。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:22:56Z -->
## Implementation report
Coder review-fix implementation report received / Orchestrator validation:
Incremental fix commit:
- `85683f17``plugin: harden https target validation`
Summary:
- Hardened IPv6 target validation:
- IPv4-mapped IPv6 addresses now apply existing IPv4 private/local/link-local checks.
- IPv4-compatible embedded forms are also checked through IPv4 rules.
- Host canonicalization strips IPv6 brackets before IP parsing.
- Removed DNS validation TOCTOU gap:
- DNS resolution now produces validated public `SocketAddr`s.
- Those exact validated addresses are pinned into the `reqwest` client via `resolve_to_addrs`, so `reqwest` does not perform a separate unvalidated DNS lookup for hostname requests.
- Private DNS answers are rejected before client build / network dispatch.
- Added deterministic tests for:
- IPv4-mapped loopback/private/link-local rejection;
- IPv4-compatible private rejection;
- DNS resolution pinning to validated public socket addresses;
- private DNS answer rejection before client build.
Files changed in incremental fix:
- `crates/pod/src/feature/plugin.rs`
Coder reported validation:
- `cargo test -p pod feature::plugin::tests` — passed; 39 tests
- `cargo test -p manifest plugin` — passed; 17 tests
- `cargo test -p yoi plugin_cli` — passed; 10 tests
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo fmt --check` — passed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Orchestrator evidence checked:
- Pod output を確認。
- implementation worktree は clean。
- branch `impl/00001KVFDX9AF-plugin-https-host-api` HEAD は `85683f17`
- incremental diff stat from previous review target `7377527f..85683f17`: 1 file changed, 170 insertions, 21 deletions。
Validation re-run by Orchestrator in implementation worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 39 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Routing classification: review_needed
Next action:
- previous `request_changes` blockers が解消されたか、read-only Reviewer Pod で再レビューする。
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T15:23:58Z -->
## Plan
Review re-dispatch:
Reviewer Pod `yoi-reviewer-00001KVFDX9AF-r2` を read-only scope で起動した。
Review target:
- worktree: `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commits:
- `7377527f` — initial HTTPS host API implementation
- `85683f17` — target validation hardening fix
Reviewer focus:
- previous IPv4-mapped/compatible IPv6 private/local bypass blocker が解消されているか。
- DNS validation が validated address pinning により actual reqwest connection path に bind され、unvalidated resolution path が残っていないか。
- mapped IPv6 / DNS pinning / private DNS answer rejection tests が deterministic で意味を持つか。
- prior positives: grant-before-network、WASM import grant validation、HTTPS-only、embedded credentials rejection、method allowlist、bounds、timeout、no redirects、`no_proxy()`、ordinary Tool result path、manifest/CLI schema compatibility が維持されているか。
- `resolve_to_addrs` usage や host canonicalization に新しい security regression がないか。
Next:
- Reviewer output を確認し、approve なら Orchestrator worktree への merge/validation/cleanup/close-ready 処理へ進む。request_changes なら blocker を記録して Coder に戻す。
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AF-r2 at: 2026-06-19T15:29:22Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AF`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commits:
- `7377527f``plugin: implement https host api`
- `85683f17``plugin: harden https target validation`
No remaining blockers found.
Evidence reviewed:
- IPv4-mapped / compatible IPv6 bypass is closed.
- Host canonicalization strips IPv6 brackets before IP parsing via `normalize_host_literal` / `canonical_host`.
- `validate_static_https_target` parses canonical hosts as `IpAddr` and applies `validate_public_ip`.
- `is_forbidden_ipv6` delegates mapped/compatible embedded IPv4 addresses through `ipv6_embedded_ipv4`, then applies existing IPv4 private/loopback/link-local/broadcast/special checks.
- `ipv4_mapped_ipv6_targets_deny_before_network` covers `::ffff:127.0.0.1`, `::ffff:10.0.0.1`, `::ffff:169.254.169.254`, and compatible `::10.0.0.1`, with mock network call count asserted as zero.
- DNS validation is bound to the reqwest connection path.
- `ReqwestPluginHttpsClient::execute` calls `resolve_https_target_for_client` before building the client.
- Hostname URLs resolve to `SocketAddr`s, reject empty/private/local answers, and return those exact validated addresses.
- reqwest builder pins each validated domain using `resolve_to_addrs(domain, &pinned_resolution.addrs)`, with no remaining separate unvalidated DNS lookup path visible in this implementation.
- IP-literal URLs skip DNS pinning only after static IP validation.
- DNS tests are deterministic and meaningful at the unit boundary.
- `FakeHttpsResolver` avoids live DNS/network.
- `dns_resolution_is_pinned_to_validated_public_socket_addresses` verifies resolver input, returned pinned domains/addrs, and reqwest builder acceptance of `resolve_to_addrs`.
- `dns_resolution_rejects_private_addresses_before_client_build` verifies private DNS answers fail before client build/network dispatch.
- Prior positives remain preserved.
- Grant checks happen before request validation/client dispatch in `execute_plugin_https_request`.
- WASM import validation checks `yoi:https` authorization before allowing the import.
- HTTPS-only, embedded-credential rejection, host/method/path allowlist, request/header/body bounds, response truncation, timeout, no redirects, `no_proxy()`, sensitive-header rejection/redaction, and ordinary Tool result flow remain present.
- Existing tests cover missing grant before network, disallowed host/method/scheme/private targets before network, timeout/redaction, response truncation, no network without HTTPS import, and ordinary Tool result path.
Reviewer validation:
- Static diff/code review and worktree status inspection.
- Orchestrator-reported successful runs were reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- focused pod/manifest/yoi plugin tests
- `git diff --check`
- `nix build .#yoi --no-link`
Conclusion:
- Previous request_changes blockers are resolved.
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AF-r2 at: 2026-06-19T15:29:45Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AF`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commits:
- `7377527f``plugin: implement https host api`
- `85683f17``plugin: harden https target validation`
No remaining blockers found.
Evidence reviewed:
- IPv4-mapped / compatible IPv6 bypass is closed.
- Host canonicalization strips IPv6 brackets before IP parsing via `normalize_host_literal` / `canonical_host`.
- `validate_static_https_target` parses canonical hosts as `IpAddr` and applies `validate_public_ip`.
- `is_forbidden_ipv6` delegates mapped/compatible embedded IPv4 addresses through `ipv6_embedded_ipv4`, then applies existing IPv4 private/loopback/link-local/broadcast/special checks.
- `ipv4_mapped_ipv6_targets_deny_before_network` covers `::ffff:127.0.0.1`, `::ffff:10.0.0.1`, `::ffff:169.254.169.254`, and compatible `::10.0.0.1`, with mock network call count asserted as zero.
- DNS validation is bound to the reqwest connection path.
- `ReqwestPluginHttpsClient::execute` calls `resolve_https_target_for_client` before building the client.
- Hostname URLs resolve to `SocketAddr`s, reject empty/private/local answers, and return those exact validated addresses.
- reqwest builder pins each validated domain using `resolve_to_addrs(domain, &pinned_resolution.addrs)`, with no remaining separate unvalidated DNS lookup path visible in this implementation.
- IP-literal URLs skip DNS pinning only after static IP validation.
- DNS tests are deterministic and meaningful at the unit boundary.
- `FakeHttpsResolver` avoids live DNS/network.
- `dns_resolution_is_pinned_to_validated_public_socket_addresses` verifies resolver input, returned pinned domains/addrs, and reqwest builder acceptance of `resolve_to_addrs`.
- `dns_resolution_rejects_private_addresses_before_client_build` verifies private DNS answers fail before client build/network dispatch.
- Prior positives remain preserved.
- Grant checks happen before request validation/client dispatch in `execute_plugin_https_request`.
- WASM import validation checks `yoi:https` authorization before allowing the import.
- HTTPS-only, embedded-credential rejection, host/method/path allowlist, request/header/body bounds, response truncation, timeout, no redirects, `no_proxy()`, sensitive-header rejection/redaction, and ordinary Tool result flow remain present.
- Existing tests cover missing grant before network, disallowed host/method/scheme/private targets before network, timeout/redaction, response truncation, no network without HTTPS import, and ordinary Tool result path.
Reviewer validation:
- Static diff/code review and worktree status inspection.
- Orchestrator-reported successful runs were reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- focused pod/manifest/yoi plugin tests
- `git diff --check`
- `nix build .#yoi --no-link`
Conclusion:
- Previous request_changes blockers are resolved.
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AF-r2 at: 2026-06-19T15:32:15Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AF`
- branch: `impl/00001KVFDX9AF-plugin-https-host-api`
- commits:
- `7377527f``plugin: implement https host api`
- `85683f17``plugin: harden https target validation`
No remaining blockers found.
Evidence reviewed:
- IPv4-mapped / compatible IPv6 bypass is closed.
- Host canonicalization strips IPv6 brackets before IP parsing via `normalize_host_literal` / `canonical_host`.
- `validate_static_https_target` parses canonical hosts as `IpAddr` and applies `validate_public_ip`.
- `is_forbidden_ipv6` delegates mapped/compatible embedded IPv4 addresses through `ipv6_embedded_ipv4`, then applies existing IPv4 private/loopback/link-local/broadcast/special checks.
- `ipv4_mapped_ipv6_targets_deny_before_network` covers `::ffff:127.0.0.1`, `::ffff:10.0.0.1`, `::ffff:169.254.169.254`, and compatible `::10.0.0.1`, with mock network call count asserted as zero.
- DNS validation is bound to the reqwest connection path.
- `ReqwestPluginHttpsClient::execute` calls `resolve_https_target_for_client` before building the client.
- Hostname URLs resolve to `SocketAddr`s, reject empty/private/local answers, and return those exact validated addresses.
- reqwest builder pins each validated domain using `resolve_to_addrs(domain, &pinned_resolution.addrs)`, with no remaining separate unvalidated DNS lookup path visible in this implementation.
- IP-literal URLs skip DNS pinning only after static IP validation.
- DNS tests are deterministic and meaningful at the unit boundary.
- `FakeHttpsResolver` avoids live DNS/network.
- `dns_resolution_is_pinned_to_validated_public_socket_addresses` verifies resolver input, returned pinned domains/addrs, and reqwest builder acceptance of `resolve_to_addrs`.
- `dns_resolution_rejects_private_addresses_before_client_build` verifies private DNS answers fail before client build/network dispatch.
- Prior positives remain preserved.
- Grant checks happen before request validation/client dispatch in `execute_plugin_https_request`.
- WASM import validation checks `yoi:https` authorization before allowing the import.
- HTTPS-only, embedded-credential rejection, host/method/path allowlist, request/header/body bounds, response truncation, timeout, no redirects, `no_proxy()`, sensitive-header rejection/redaction, and ordinary Tool result flow remain present.
- Existing tests cover missing grant before network, disallowed host/method/scheme/private targets before network, timeout/redaction, response truncation, no network without HTTPS import, and ordinary Tool result path.
Reviewer validation:
- Static diff/code review and worktree status inspection.
- Orchestrator-reported successful runs were reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- focused pod/manifest/yoi plugin tests
- `git diff --check`
- `nix build .#yoi --no-link`
Conclusion:
- Previous request_changes blockers are resolved.
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:32:56Z -->
## Implementation report
Merge / validation report:
Implementation branch `impl/00001KVFDX9AF-plugin-https-host-api` は Orchestrator worktree の `orchestration` branch に merge 済み。
Merge:
- merge commit: `6beb8625``merge: plugin https host api`
- implementation commits:
- `7377527f``plugin: implement https host api`
- `85683f17``plugin: harden https target validation`
- changed files:
- `Cargo.lock`
- `crates/manifest/src/plugin.rs`
- `crates/pod/Cargo.toml`
- `crates/pod/src/feature/plugin.rs`
- `crates/yoi/src/plugin_cli.rs`
- `package.nix`
Validation in Orchestrator worktree after merge:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 39 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Review:
- First review requested changes for IPv4-mapped IPv6 private/local bypass and DNS validation TOCTOU.
- Coder fixed target validation by applying IPv4 checks to mapped/compatible IPv6 forms and binding DNS validation to reqwest via validated address pinning.
- Re-review `yoi-reviewer-00001KVFDX9AF-r2` returned `approve` with no remaining blockers.
Outcome:
- Granted HTTPS host API is implemented for WASM Plugin Tools.
- Missing grant denies before network access.
- Host/method/path allowlist and HTTPS-only/private-local target restrictions are represented and tested.
- DNS validation is pinned to the actual reqwest connection path.
- Request/response bounds, timeout, no redirects, `no_proxy()`, secret redaction, ordinary Tool result path, and CLI inspection compatibility are present.
- `fs` host API and Component Model migration remain non-goals for this Ticket.
- Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:33:57Z -->
## Implementation report
Merge / validation report:
Implementation branch `impl/00001KVFDX9AF-plugin-https-host-api` は Orchestrator worktree の `orchestration` branch に merge 済み。
Merge:
- merge commit: `6beb8625``merge: plugin https host api`
- implementation commits:
- `7377527f``plugin: implement https host api`
- `85683f17``plugin: harden https target validation`
- changed files:
- `Cargo.lock`
- `crates/manifest/src/plugin.rs`
- `crates/pod/Cargo.toml`
- `crates/pod/src/feature/plugin.rs`
- `crates/yoi/src/plugin_cli.rs`
- `package.nix`
Validation in Orchestrator worktree after merge:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 39 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Review:
- First review requested changes for IPv4-mapped IPv6 private/local bypass and DNS validation TOCTOU.
- Coder fixed target validation by applying IPv4 checks to mapped/compatible IPv6 forms and binding DNS validation to reqwest via validated address pinning.
- Re-review `yoi-reviewer-00001KVFDX9AF-r2` returned `approve` with no remaining blockers.
Outcome:
- Granted HTTPS host API is implemented for WASM Plugin Tools.
- Missing grant denies before network access.
- Host/method/path allowlist and HTTPS-only/private-local target restrictions are represented and tested.
- DNS validation is pinned to the actual reqwest connection path.
- Request/response bounds, timeout, no redirects, `no_proxy()`, secret redaction, ordinary Tool result path, and CLI inspection compatibility are present.
- `fs` host API and Component Model migration remain non-goals for this Ticket.
- Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:35:11Z -->
## Implementation report
Cleanup report:
Plugin HTTPS host API Ticket の implementation/review Pods と child worktree を cleanup 済み。
Stopped Pods:
- `yoi-coder-00001KVFDX9AF`
- `yoi-reviewer-00001KVFDX9AF-r2`
Removed child implementation worktree:
- `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`
Deleted merged implementation branch:
- `impl/00001KVFDX9AF-plugin-https-host-api` at `85683f17`
Orchestrator branch remains the integration authority for this completed work. Root/original workspace was not touched for this Ticket, per Panel Queue instruction.
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T15:35:22Z from: inprogress to: done reason: merged_validated_review_approved field: state -->
## State changed
Implementation commits `7377527f` and `85683f17` were reviewed after blocker resolution, approved, merged into the Orchestrator branch as `6beb8625`, validated in the Orchestrator worktree, and child implementation resources were cleaned up. Requirements and acceptance criteria are satisfied for this Ticket. Root/original workspace was not operated on for this Ticket per Panel Queue instruction.
---
<!-- event: state_changed author: hare at: 2026-06-19T15:35:45Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T15:35:46Z status: closed -->
## 完了
Ticket `00001KVFDX9AF` is complete.
Completed implementation:
- Added granted outbound HTTPS host API for WASM Plugin Tools.
- Added typed `host_api.https` grant scope with host, method, optional path prefix, and bounded request/response options.
- Implemented `yoi:https` WASM host import handling.
- Enforced grant/allowlist checks before network access.
- Enforced HTTPS-only behavior and rejected `http://`, embedded credentials, localhost/private/link-local/local targets, IPv4-mapped/compatible IPv6 private/local forms, and unsafe DNS results.
- Bound DNS validation to the actual reqwest connection path by pinning validated public socket addresses with `resolve_to_addrs`.
- Added request/response bounds, timeout, no redirects, `no_proxy()`, response truncation, and secret-like diagnostics redaction.
- Preserved ordinary Tool result/history path and avoided hidden context injection.
- Updated Plugin CLI inspection and manifest/permission model to expose HTTPS host API grant/diagnostic details.
Reviewed / merged:
- Implementation commits:
- `7377527f` (`plugin: implement https host api`)
- `85683f17` (`plugin: harden https target validation`)
- First review requested changes for IPv4-mapped IPv6 bypass and DNS validation TOCTOU.
- Re-review approved with no remaining blockers.
- Orchestrator merge commit: `6beb8625` (`merge: plugin https host api`)
Validation in Orchestrator worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin::tests -- --nocapture` — passed; 39 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Cleanup:
- Stopped Coder Pod `yoi-coder-00001KVFDX9AF`.
- Stopped Reviewer Pod `yoi-reviewer-00001KVFDX9AF-r2`.
- Removed child worktree `/home/hare/Projects/yoi/.worktree/00001KVFDX9AF-plugin-https-host-api`.
- Deleted merged branch `impl/00001KVFDX9AF-plugin-https-host-api`.
Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction. The completed work is integrated on the Orchestrator branch.
---

View File

@ -0,0 +1,4 @@
{"id":"orch-plan-20260619-102132-1","ticket_id":"00001KVFDX9AY","kind":"waiting_capacity_note","note":"明示 queue review で確認済み。依存 Ticket `00001KV5W3PHW` / `00001KV5W3PJ3` は closed で blocker ではないが、同時 queued の `00001KVFD3YSV` CLI inspection と `00001KVFDX9AF` https host API はいずれも Plugin manifest/grant/runtime/diagnostic 周辺を触る。まず read-only CLI inspection を開始し、host API implementation は conflict / reviewer-coder bottleneck を避けるため queued のまま待機する。次の routing pass で再確認する。","author":"yoi-orchestrator","at":"2026-06-19T10:21:32Z"}
{"id":"orch-plan-20260619-102132-2","ticket_id":"00001KVFDX9AY","kind":"do_not_parallelize","related_ticket":"00001KVFDX9AF","note":"`fs` と `https` host API はどちらも WASM Plugin Tool runtime host import boundary、Plugin grant model、diagnostics/tests/package behavior に触れるため、同時実装は conflict risk が高い。片方の merged/validated 後にもう片方を再 routing する。","author":"yoi-orchestrator","at":"2026-06-19T10:21:32Z"}
{"id":"orch-plan-20260619-142431-3","ticket_id":"00001KVFDX9AY","kind":"waiting_capacity_note","note":"`00001KVFD3YSV` Plugin CLI inspection は closed になったため再 routing した。`https` host API Ticket `00001KVFDX9AF` を先に受理する。`fs` host API は既存 do_not_parallelize record の通り WASM Plugin Tool runtime host import boundary、Plugin grant model、diagnostics/tests/package behavior が重なるため、`https` の merge/validation outcome まで queued のまま待機する。Bounded reason: conflict / reviewer-coder bottleneck。","author":"yoi-orchestrator","at":"2026-06-19T14:24:31Z"}
{"id":"orch-plan-20260619-153644-4","ticket_id":"00001KVFDX9AY","kind":"accepted_plan","accepted_plan":{"summary":"WASM Plugin Tool runtime に明示 grant された scoped filesystem `fs` host API を追加する。read/list/write initial subset、path normalization、traversal/symlink/root escape rejection、bounds、safe diagnostics、file mutation safety、no ambient workspace filesystem inheritance を満たす。","branch":"impl/00001KVFDX9AY-plugin-fs-host-api","worktree":"/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api","role_plan":"Orchestrator は専用 implementation worktree を作成し、Coder をその worktree への narrow write scope で起動する。Reviewer は実装報告後に read-only で確認する。Component Model migration は Plugin runtime/host API/WIT migration boundary として queued hold を維持する。"},"author":"yoi-orchestrator","at":"2026-06-19T15:36:44Z"}

View File

@ -0,0 +1,37 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVFDX9AY",
"kind": "depends_on",
"target": "00001KV5W3PHW",
"note": "fs host API is implemented inside the WASM Plugin Tool runtime.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AY",
"kind": "depends_on",
"target": "00001KV5W3PJ3",
"note": "fs host API must be guarded by Plugin permission grants.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AY",
"kind": "related",
"target": "00001KSXRQ4G8",
"note": "Uses established Plugin host API terminology.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
},
{
"ticket_id": "00001KVFDX9AY",
"kind": "related",
"target": "00001KVFD3YSV",
"note": "Inspection CLI should expose fs host API grants/diagnostics.",
"author": "yoi ticket",
"at": "2026-06-19T07:54:32Z"
}
]
}

View File

@ -0,0 +1,90 @@
---
title: 'Plugin: implement fs host API for Tool runtime'
state: 'closed'
created_at: '2026-06-19T07:53:13Z'
updated_at: '2026-06-19T16:17:51Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['plugin', 'fs', 'host-api', 'sandbox', 'path-safety', 'permission-grants', 'file-mutation']
queued_by: 'workspace-panel'
queued_at: '2026-06-19T10:19:52Z'
---
## Background
Plugin Tool runtime は minimal WASM execution と permission grants まで実装済みだが、Plugin-layer scoped filesystem access はまだ未実装である。
この Ticket では、WASM Plugin Tool から明示 grant された scoped paths のみを read/list/write できる `fs` host API を追加する。Plugin は Pod / workspace の filesystem authority を自動継承しない。Plugin-specific grant だけが有効な authority になる。
## Requirements
- WASM Plugin Tool runtime に `fs` host API import を追加する。
- API 名・ABI は既存 `yoi-plugin-wasm-1` / host import 設計と整合させる。
- Plugin は ambient filesystem access を持たず、host API 経由のみで fs operation できる。
- Plugin-layer scoped paths を grant で表現する。
- read
- list
- write の初期 subset
- optional path root / glob / prefix policy は implementation-time に最小安全形を選ぶ。
- Workspace filesystem scope を自動継承しない。
- Pod が workspace write authority を持っていても Plugin は grant なしでは読めない/書けない。
- Path safety を徹底する。
- normalization
- `..` traversal reject
- symlink/root escape reject
- absolute/relative path policy を明確化
- allowed root 外は fail closed
- Bounds を設ける。
- read size bound
- write size bound
- directory entry count bound
- path length bound
- diagnostic size bound
- Writes は既存 file mutation safety と整合させる。
- normalized target file ごとの serialization / atomic-ish behavior を検討する。
- broad Worker scheduler は追加しない。
- Diagnostics は safe にする。
- file content を error/log に漏らさない。
- rejected path は必要最小限にする。
- Tool result path は通常 Tool result/history 経路を使う。
- hidden context injection しない。
## Acceptance criteria
- Granted Plugin Tool can read an allowed file through `fs` host API.
- Granted Plugin Tool can list an allowed directory within bounds.
- Granted Plugin Tool can write an allowed file within bounds.
- Plugin without matching `host_api.fs` grant cannot read/list/write.
- Workspace write authority is not inherited by Plugin without Plugin grant.
- `../` traversal, symlink escape, and allowed-root escape are rejected.
- Oversize read/write/list results fail closed or truncate according to explicit policy.
- File mutation safety does not race unsafely with existing Write/Edit semantics.
- Diagnostics do not include file content or secret-like data.
- Tests cover:
- allowed read
- allowed list
- allowed write
- missing grant denied
- workspace authority not inherited
- path traversal rejected
- symlink/root escape rejected
- read/write/list bounds
- diagnostics redaction
- write serialization or safe conflict behavior
- Validation: focused plugin fs tests, relevant cargo check/test, `cargo fmt --check`, `git diff --check`, and `nix build .#yoi` because host API / packaging behavior may change.
## Non-goals
- `https` host API implementation.
- General workspace Read/Write tool delegation.
- Service / Ingress surface.
- File watcher / background sync.
- Broad WASI filesystem exposure.
- Plugin package manager / install/update.
## Related work
- `00001KV5W3PHW` — Plugin Tool execution with minimal WASM runtime.
- `00001KV5W3PJ3` — Plugin permission grant enforcement.
- `00001KVFD3YSV` — Plugin read-only CLI inspection list/show.
- `00001KSXRQ4G8` — Plugin runtime / surface / minimal host API model design.

View File

@ -0,0 +1,33 @@
Ticket `00001KVFDX9AY` is complete.
Completed implementation:
- Added granted scoped filesystem `fs` host API for WASM Plugin Tools.
- Added `PluginFsGrant { root, operations }` and `PluginFsOperation::{read,list,write}` to the Plugin grant model.
- Implemented `yoi:fs` WASM host imports for read/list/write with response buffer handling.
- Enforced Plugin-specific grants before filesystem access; Plugin Tools do not inherit Pod/workspace filesystem authority.
- Added path safety for relative-only requests, traversal rejection, symlink/root escape rejection, canonical root checks, and Unix `O_NOFOLLOW` for writes.
- Added read/list/write bounds, explicit truncation/fail-closed behavior, write serialization by normalized/canonical target, and bounded/redacted diagnostics.
- Updated Plugin CLI inspection to show configured fs grants.
Reviewed / merged:
- Implementation commit: `717c0999` (`plugin: implement fs host api`)
- Reviewer result: approve, no blockers.
- Reviewer noted a non-blocking future hardening context: the current implementation is path/canonicalization based and does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is accepted within the feasible scope of the initial subset.
- Orchestrator merge commit: `c94e157b` (`merge: plugin fs host api`)
Validation in Orchestrator worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin -- --nocapture` — passed; 48 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Cleanup:
- Stopped Coder Pod `yoi-coder-00001KVFDX9AY`.
- Stopped Reviewer Pod `yoi-reviewer-00001KVFDX9AY`.
- Removed child worktree `/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api`.
- Deleted merged branch `impl/00001KVFDX9AY-plugin-fs-host-api`.
Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction. The completed work is integrated on the Orchestrator branch.

View File

@ -0,0 +1,870 @@
<!-- event: create author: "yoi ticket" at: 2026-06-19T07:53:13Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: workspace-panel at: 2026-06-19T10:19:52Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T10:22:26Z -->
## Plan
Routing hold / waiting-capacity note:
明示 queue review で Ticket body / relations / orchestration plan / workspace state を確認した。
Current decision:
- この Ticket は `queued` のまま待機。
- 依存 Ticket `00001KV5W3PHW` / `00001KV5W3PJ3` は closed で dependency blocker ではない。
- ただし同時 queued の `00001KVFD3YSV` CLI inspection を先に受理した。CLI inspection は Plugin discovery / enablement / grants / diagnostics の read-only public surface を作る作業で、host API implementation と同じ Plugin manifest/grant/runtime/diagnostic 周辺に触れる。
- `00001KVFDX9AF` https host API とは WASM Plugin Tool runtime host import boundary、Plugin grant model、diagnostics/tests/package behavior の変更面が重なるため `do_not_parallelize` plan record を残した。
Bounded reason for idle queued:
- conflict / reviewer-coder bottleneck。
Next action:
- `00001KVFD3YSV` の implementation/review/merge outcome を確認後、queued のまま再 routing する。
- その時点で `https` host API Ticket との ordering / conflict も再確認する。
---
<!-- event: decision author: yoi-orchestrator at: 2026-06-19T15:37:24Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Panel Queue により、この Ticket は Orchestrator routing 対象として明示許可された。
- Ticket body には、`fs` host API intent、binding invariants、acceptance criteria、non-goals、validation、escalation-worthy risk domain が実装可能な粒度で揃っている。
- 依存 relation の `00001KV5W3PHW` minimal WASM runtime、`00001KV5W3PJ3` permission grants、関連 `00001KVFD3YSV` CLI inspection、`00001KVFDX9AF` HTTPS host API は closed で blocker ではない。
- Risk domain は filesystem / path safety / file mutation / permission grants だが、Ticket は Plugin-specific grants、no workspace authority inheritance、path normalization、traversal/symlink/root escape rejection、bounds、safe diagnostics、ordinary Tool result path を binding invariants として明示している。bounded context check 後も implementation 前に必要な追加 human decision は見つからなかった。
- `00001KVG0HR96` Component Model migration は Plugin runtime / WIT / host API shape / grants / inspection / packaging に広く触れる migration boundary で、active `fs` host API と衝突しやすいため waiting note を更新し queued のまま待機する。
Evidence checked:
- Ticket `00001KVFDX9AY` body / thread / artifacts。
- `TicketRelationQuery(00001KVFDX9AY)`: depends_on は closed。related Ticket は context であり acceptance blocker ではない。
- `TicketOrchestrationPlanQuery(00001KVFDX9AY)`: prior waiting/do_not_parallelize records を確認。HTTPS host API は closed になったため今回 `accepted_plan` を記録済み。
- Related completed Tickets:
- `00001KV5W3PHW` — minimal WASM Tool runtime closed。
- `00001KV5W3PJ3` — Plugin permission grants closed。
- `00001KVFD3YSV` — Plugin read-only CLI inspection closed。
- `00001KVFDX9AF` — Plugin HTTPS host API closed。
- Current queued Ticket `00001KVG0HR96` Component Model migration: migration boundary / conflict waiting note を更新。
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration`: clean。
- Existing branch/worktree: matching `00001KVFDX9AY` branch/worktree はなし。
- Visible Pods: self / peers only; spawned child capacity is free。
- Current code map:
- `crates/pod/src/feature/plugin.rs`: Plugin resolver, permission grants, static inspection, host API eligibility, HTTPS implementation pattern。
- `crates/pod/src/pod.rs`: WASM Tool runtime / host import validation / Tool execution path。
- `crates/manifest/src/plugin.rs`: Plugin manifest and permission model。
- `crates/yoi/src/plugin_cli.rs`: read-only inspection output should remain compatible with fs host API diagnostics。
IntentPacket:
Intent:
- WASM Plugin Tool runtime に、明示 grant された scoped path のみ read/list/write できる `fs` host API を追加する。
- Plugin は Pod/workspace filesystem authority を自動継承せず、Plugin-specific `host_api.fs` grants だけが filesystem authority になる。
Binding decisions / invariants:
- Host API name/domain は `fs`
- Broad WASI filesystem exposure は禁止。Plugin は ambient filesystem access を持たない。
- Workspace read/write authority は Plugin に自動継承しない。
- Grant がない read/list/write は fail closed。
- Grants は operation kind (`read`, `list`, `write`) と scoped root/prefix/glob 等の最小安全形を持つ。
- Path normalization、`..` traversal rejection、symlink/root escape rejection、allowed root outside rejection は binding。
- Absolute/relative path policy は明確にし、safe default を選ぶ。
- Bounds: path length、read size、write size、directory entry count、diagnostic size。
- Writes は existing file mutation safety と整合し、normalized target file ごとに unsafe race を避ける。
- Diagnostics に file content / secret-like data を漏らさない。
- Tool result path は ordinary Tool result/history path。hidden context injection しない。
- `https` host API、Service/Ingress/File watcher/package manager は non-goals。
Requirements / acceptance criteria:
- Granted Plugin Tool can read an allowed file。
- Granted Plugin Tool can list an allowed directory within bounds。
- Granted Plugin Tool can write an allowed file within bounds。
- Plugin without matching `host_api.fs` grant cannot read/list/write。
- Workspace authority is not inherited by Plugin without Plugin grant。
- `../` traversal、symlink escape、allowed-root escape reject。
- Oversize read/write/list fail closed or truncate according to explicit policy。
- File mutation safety avoids unsafe race with existing Write/Edit semantics。
- Diagnostics do not include file content or secret-like data。
- Tests cover allowed read/list/write, missing grant denied, workspace authority not inherited, traversal/symlink/root escape, bounds, diagnostics redaction, safe write conflict behavior。
Implementation latitude:
- Choose exact ABI/import shape consistent with existing `yoi-plugin-wasm-1` host import design and current HTTPS host API pattern。
- Choose narrow grant config representation for root/prefix/glob/operation allowlist consistent with current Plugin permission grant model。
- Use tempdir/local fixture files for deterministic tests。
- Choose read/list/write response shape consistent with existing Tool result/error types and CLI inspection structure。
- If write serialization requires reusing existing file mutation primitives, keep it narrow and avoid broad Worker scheduler changes。
Escalate if:
- Safe path/symlink/root escape handling cannot be represented without broad filesystem authority redesign。
- write serialization requires broad Worker scheduler or global mutation system redesign。
- Existing Plugin grant schema cannot safely represent fs scopes without breaking HTTPS grants/CLI inspection。
- Broad WASI filesystem exposure appears necessary。
- Product decision is needed for truncate-vs-fail policy beyond Tickets bounded latitude。
Validation:
- Focused plugin fs host API tests。
- Relevant `cargo test` / `cargo check` for `pod`, `manifest`, `yoi` as changed。
- `cargo fmt --check`
- `git diff --check`
- `nix build .#yoi --no-link` / `nix build .#yoi` if dependency/package-source-filter changes occur。
Critical risks / reviewer focus:
- Workspace authority leaking into Plugin without Plugin grant。
- Path traversal / symlink / root escape bypass。
- Write race / unsafe mutation behavior。
- File content or secret leakage in diagnostics。
- Unbounded read/list/write outputs。
- Hidden context injection by bypassing normal Tool result path。
- Breaking existing HTTPS host API, permission grants, or CLI inspection semantics。
Next action:
- `queued -> inprogress` を記録し、Ticket records を Orchestrator worktree に commit してから、専用 implementation worktree を作成し Coder Pod を narrow write scope で起動する。root/original workspace は操作しない。
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T15:37:38Z from: queued to: inprogress reason: orchestrator_acceptance_plugin_fs_host_api field: state -->
## State changed
Ticket body/thread, relation metadata, orchestration plan records, related completed Tickets, Orchestrator worktree, visible Pods, existing branch/worktree, and bounded Plugin fs host API code context were checked. Depends-on blockers are closed, HTTPS host API and CLI inspection related work are closed, and no dirty-state blocker or missing planning decision was found. Component Model migration remains queued with migration/conflict waiting record. Accepting this queued Ticket for implementation before worktree/Pod side effects.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T15:39:09Z -->
## Implementation report
Implementation start note:
`queued -> inprogress` acceptance、accepted plan、routing decision / IntentPacket、Component Model migration の waiting record を記録し、Orchestrator worktree で commit した後に、専用 implementation worktree と Coder Pod を起動した。
Worktree:
- `/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
Coder Pod:
- `yoi-coder-00001KVFDX9AY`
Scope / boundaries:
- child runtime workspace root は read scope。
- implementation worktree は write scope。
- root/original workspace と Orchestrator worktree へは書き込まないよう指示済み。
- `.yoi/memory`、runtime/log/lock/local/secret-like `.yoi` paths は編集しないよう指示済み。
Related queued Ticket:
- `00001KVG0HR96` Component Model migration remains queued due migration boundary / conflict with active fs host API work.
Next:
- Coder は scoped `fs` host API implementation、focused tests、validation、implementation commit を行い、実装報告を返す予定。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T16:01:41Z -->
## Implementation report
Coder implementation report received / Orchestrator validation:
Implementation commit:
- `717c0999``plugin: implement fs host api`
Summary:
- Added manifest/config support for `host_api.fs` grants:
- `PluginFsGrant { root, operations }`
- `PluginFsOperation::{read,list,write}`
- Implemented WASM host import module `yoi:fs` with:
- `read`
- `list`
- `write`
- `response_len`
- `response_read`
- Enforced plugin-specific grant checks; Pod/workspace fs authority is not inherited.
- Added path safety:
- relative-only request paths;
- `..` traversal rejection;
- configured root must be absolute/non-symlink directory;
- symlink component/target rejection;
- canonical allowed-root escape checks.
- Added bounds:
- request descriptor size;
- path length;
- read size with explicit truncation flag;
- write size fail-closed;
- directory entry count with explicit truncation flag.
- Added write serialization by normalized target plus `O_NOFOLLOW` on Unix.
- Kept fs errors/diagnostics bounded and redacted.
- Extended Plugin CLI inspection to show configured fs grants.
Files changed:
- `crates/manifest/src/plugin.rs`
- `crates/pod/src/feature/plugin.rs`
- `crates/yoi/src/plugin_cli.rs`
Coder reported validation:
- `cargo fmt --check` — passed
- `cargo test -p pod feature::plugin` — passed
- `cargo test -p manifest plugin` — passed
- `cargo test -p yoi plugin_cli` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `git diff --check` — passed
- `nix build` not run by Coder because no dependency/package-source-filter changes.
Orchestrator evidence checked:
- Pod output を確認。
- implementation worktree は clean。
- branch `impl/00001KVFDX9AY-plugin-fs-host-api` HEAD は `717c0999`
- diff stat from accepted base `6711bcf3..717c0999`: 3 files changed, 1068 insertions, 17 deletions。
Validation re-run by Orchestrator in implementation worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin -- --nocapture` — passed; 48 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Routing classification: review_needed
Next action:
- recorded intent / invariants / acceptance criteria に照らして、read-only Reviewer Pod で外部レビューする。
- 特に no ambient workspace fs authority、grant-before-filesystem access、path traversal/symlink/root escape、bounds/truncation/fail-closed policy、write serialization/O_NOFOLLOW、diagnostic redaction、ordinary Tool result path、HTTPS/CLI compatibility を確認する。
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T16:02:39Z -->
## Plan
Review dispatch:
Reviewer Pod `yoi-reviewer-00001KVFDX9AY` を read-only scope で起動した。
Review target:
- worktree: `/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999`
Reviewer focus:
- recorded intent / invariants / acceptance criteria に対する適合性。
- filesystem access が grant checks の前に発生しないこと。
- Workspace/POD filesystem authority が Plugin に自動継承されていないこと。
- traversal / symlink / root escape protections が canonicalization races を含め妥当であること。
- read/list/write bounds と truncate-vs-fail-closed policy が明確で enforce されること。
- write serialization by normalized target と `O_NOFOLLOW` が既存 file mutation safety と整合すること。portability issue がないこと。
- diagnostics に file content / secret-like data が漏れないこと。
- ordinary Tool result/history path だけに戻ること。
- tests が allowed read/list/write、missing grant、no workspace inheritance、traversal、symlink/root escape、bounds、redaction、write safety を十分に覆うこと。
- manifest/permission grant schema と CLI inspection changes が既存 HTTPS grants / Plugin inspection behavior と互換であること。
- HTTPS host API と Component Model migration が non-goals として保たれていること。
Next:
- Reviewer output を確認し、approve なら Orchestrator worktree への merge/validation/cleanup/close-ready 処理へ進む。request_changes なら blocker を記録して Coder に戻す。
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:08:53Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the “feasible scope” of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:09:17Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:09:50Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:10:26Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:11:01Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:11:47Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:12:41Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T16:14:42Z -->
## Implementation report
Merge / validation report:
Implementation branch `impl/00001KVFDX9AY-plugin-fs-host-api` を Orchestrator worktree の `orchestration` branch に merge 済み。
Merge:
- merge commit: `c94e157b``merge: plugin fs host api`
- implementation commit:
- `717c0999``plugin: implement fs host api`
- changed files:
- `crates/manifest/src/plugin.rs`
- `crates/pod/src/feature/plugin.rs`
- `crates/yoi/src/plugin_cli.rs`
Validation in Orchestrator worktree after merge:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin -- --nocapture` — passed; 48 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Review:
- Reviewer `yoi-reviewer-00001KVFDX9AY` returned `approve` with no blockers.
- Reviewer noted a non-blocking future hardening context: this implementation is path/canonicalization based and does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is accepted within the feasible scope of the initial subset.
Outcome:
- Granted scoped filesystem `fs` host API is implemented for WASM Plugin Tools.
- Missing `host_api.fs` grant denies before filesystem access.
- Workspace/POD filesystem authority is not inherited by Plugin without Plugin grant.
- Path traversal / symlink / root escape protections, read/list/write bounds, write serialization, diagnostics redaction, ordinary Tool result path, and CLI inspection compatibility are present.
- HTTPS host API and Component Model migration remain non-goals for this Ticket.
- Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction.
---
<!-- event: review author: yoi-reviewer-00001KVFDX9AY at: 2026-06-19T16:15:17Z status: approve -->
## Review: approve
Review result: approve
対象:
- Ticket: `00001KVFDX9AY`
- branch: `impl/00001KVFDX9AY-plugin-fs-host-api`
- commit: `717c0999` (`plugin: implement fs host api`)
No blockers found.
Evidence reviewed:
- FS grant schema is explicit and additive: `PluginGrantConfig.fs`, `PluginFsGrant { root, operations }`, and `PluginFsOperation::{read,list,write}` in `crates/manifest/src/plugin.rs`. Existing HTTPS grant shape remains separate.
- Runtime dispatch checks `host_api.fs` before request parsing reaches any filesystem operation: `execute_plugin_fs_request` bounds descriptor size, then calls `authorize_plugin_host_api(record, PluginHostApi::Fs)` before read/list/write dispatch.
- Permission/grant binding is layered correctly:
- requested `host_api.fs` permission required;
- configured grant permission required;
- usable FS scope required;
- per-operation grants checked in `authorize_fs_path` / `authorize_fs_write_path`.
- No ambient WASI/workspace filesystem authority is exposed. WASM import validation only allows `yoi:tool`, `yoi:https`, and `yoi:fs`; WASI remains rejected. Runtime FS is only through `yoi:fs` imports.
- Path safety is materially addressed:
- absolute paths, NULs, and `..` traversal rejected;
- grant root must be absolute, existing, directory, and not final symlink;
- request path components are symlink-checked;
- canonicalized targets must remain under the canonical grant root;
- write target symlinks/dirs rejected;
- Unix writes add `O_NOFOLLOW`.
- Bounds are explicit:
- request descriptor, path, read bytes, write bytes, list entry count, and diagnostics are bounded/redacted;
- read/list truncation is explicit via `truncated`;
- oversize write fails closed.
- Writes serialize by normalized/canonical target with a shared lock map and use truncate/create with `sync_all`.
- Diagnostics use `PluginFsError::new` with `bounded_message(redact_secret_like(...))`; tests verify secret-like path redaction and no file-content leakage on denial.
- FS responses flow through WASM host response buffers and then ordinary plugin Tool output/error handling; no hidden context/history injection path found.
- CLI inspection reports `configured_fs_grants` alongside existing permission/HTTPS grant inspection without changing HTTPS semantics.
Reviewer validation:
- Static review of changed files and relevant tests.
- Confirmed worktree branch/head.
- `git diff --check 717c0999^..717c0999` — passed.
- Orchestrator-reported validation was reviewed:
- `cargo fmt --check`
- `cargo check -p pod -p manifest -p yoi`
- `cargo test -p pod feature::plugin`
- `cargo test -p manifest plugin`
- `cargo test -p yoi plugin_cli`
- `git diff --check`
- `nix build .#yoi --no-link`
Non-blocking risk noted:
- The implementation is still fundamentally path/canonicalization based. It rejects static symlink/root escapes and uses Unix `O_NOFOLLOW` for final write target, but it does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is judged within the feasible scope of the initial subset and not a blocker for this Ticket, but should be preserved as future hardening context if the threat model expands.
Conclusion:
- Implementation satisfies the recorded Ticket acceptance criteria and is ready for merge/validation in the Orchestrator worktree.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T16:16:56Z -->
## Implementation report
Cleanup report:
Plugin FS host API Ticket の implementation/review Pods と child worktree を cleanup した。
Stopped Pods:
- `yoi-coder-00001KVFDX9AY`
- `yoi-reviewer-00001KVFDX9AY`
Removed child implementation worktree:
- `/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api`
Deleted merged implementation branch:
- `impl/00001KVFDX9AY-plugin-fs-host-api` at `717c0999`
Orchestrator branch remains the integration authority for this completed work. Root/original workspace was not touched for this Ticket, per Panel Queue instruction.
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T16:17:29Z from: inprogress to: done reason: merged_validated_review_approved field: state -->
## State changed
Implementation commit `717c0999` was reviewed and approved, merged into the Orchestrator branch as `c94e157b`, validated in the Orchestrator worktree, and child implementation resources were cleaned up. Requirements and acceptance criteria are satisfied for this Ticket. Root/original workspace was not operated on for this Ticket per Panel Queue instruction.
---
<!-- event: state_changed author: hare at: 2026-06-19T16:17:51Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T16:17:51Z status: closed -->
## 完了
Ticket `00001KVFDX9AY` is complete.
Completed implementation:
- Added granted scoped filesystem `fs` host API for WASM Plugin Tools.
- Added `PluginFsGrant { root, operations }` and `PluginFsOperation::{read,list,write}` to the Plugin grant model.
- Implemented `yoi:fs` WASM host imports for read/list/write with response buffer handling.
- Enforced Plugin-specific grants before filesystem access; Plugin Tools do not inherit Pod/workspace filesystem authority.
- Added path safety for relative-only requests, traversal rejection, symlink/root escape rejection, canonical root checks, and Unix `O_NOFOLLOW` for writes.
- Added read/list/write bounds, explicit truncation/fail-closed behavior, write serialization by normalized/canonical target, and bounded/redacted diagnostics.
- Updated Plugin CLI inspection to show configured fs grants.
Reviewed / merged:
- Implementation commit: `717c0999` (`plugin: implement fs host api`)
- Reviewer result: approve, no blockers.
- Reviewer noted a non-blocking future hardening context: the current implementation is path/canonicalization based and does not fully eliminate adversarial external TOCTOU races where another local process mutates granted-root directories between checks and open/read/list/write. This is accepted within the feasible scope of the initial subset.
- Orchestrator merge commit: `c94e157b` (`merge: plugin fs host api`)
Validation in Orchestrator worktree:
- `cargo fmt --check` — passed
- `cargo check -p pod -p manifest -p yoi` — passed
- `cargo test -p pod feature::plugin -- --nocapture` — passed; 48 passed, 0 failed
- `cargo test -p manifest plugin -- --nocapture` — passed; 17 passed, 0 failed
- `cargo test -p yoi plugin_cli -- --nocapture` — passed; 10 passed, 0 failed
- `git diff --check` — passed
- `nix build .#yoi --no-link` — passed
Cleanup:
- Stopped Coder Pod `yoi-coder-00001KVFDX9AY`.
- Stopped Reviewer Pod `yoi-reviewer-00001KVFDX9AY`.
- Removed child worktree `/home/hare/Projects/yoi/.worktree/00001KVFDX9AY-plugin-fs-host-api`.
- Deleted merged branch `impl/00001KVFDX9AY-plugin-fs-host-api`.
Root/original workspace was not read/written/merged/validated for this Ticket, per Panel Queue instruction. The completed work is integrated on the Orchestrator branch.
---

View File

@ -0,0 +1,4 @@
{"id":"orch-plan-20260619-133549-1","ticket_id":"00001KVG0HR96","kind":"waiting_capacity_note","note":"明示 queue review で Ticket body / relations / orchestration plan / workspace state を確認した。依存 Ticket `00001KV5W3PHW` / `00001KV5W3PJ3` は closed で blocker ではないが、現在 `00001KVFD3YSV` Plugin CLI inspection が inprogress/review-needed で、さらに `00001KVFDX9AF` / `00001KVFDX9AY` host API Tickets も queued hold 中。Component Model runtime migration は Plugin runtime backend / manifest runtime metadata / WIT / grants / inspection / packaging に広く触れる migration boundary で、current CLI inspection outcome と host API ordering に強く依存・競合するため、現時点では queued のまま待機する。`00001KVFD3YSV` の merge/close 後に再 routing する。","author":"yoi-orchestrator","at":"2026-06-19T13:35:49Z"}
{"id":"orch-plan-20260619-142431-2","ticket_id":"00001KVG0HR96","kind":"waiting_capacity_note","note":"`00001KVFD3YSV` Plugin CLI inspection は closed になったため再 routing した。Component Model runtime migration は Plugin runtime backend / WIT / host API shape / grants / inspection / packaging に広く触れる migration boundary で、queued host API Tickets と衝突しやすい。まず `00001KVFDX9AF` https host API を受理し、`fs` host API と Component Model migration はその outcome 後に再 routing する。Bounded reason: migration boundary / conflict。","author":"yoi-orchestrator","at":"2026-06-19T14:24:31Z"}
{"id":"orch-plan-20260619-153644-3","ticket_id":"00001KVG0HR96","kind":"waiting_capacity_note","note":"`00001KVFDX9AF` HTTPS host API は closed になったため再 routing した。次は `00001KVFDX9AY` fs host API を受理する。Component Model runtime migration は Plugin runtime backend / WIT / host API shape / grants / inspection / packaging に広く触れる migration boundary で、active fs host API と衝突しやすいため queued のまま待機する。Bounded reason: migration boundary / conflict。","author":"yoi-orchestrator","at":"2026-06-19T15:36:44Z"}
{"id":"orch-plan-20260619-162050-4","ticket_id":"00001KVG0HR96","kind":"accepted_plan","accepted_plan":{"summary":"WASM Plugin Tool runtime を現行 core-module host imports (`yoi-plugin-wasm-1`) から WebAssembly Component Model / WIT-first runtime へ移行する。Typed host API surface、permission/grant enforcement、ordinary Tool result path、HTTPS/FS安全性、CLI inspection、tests/package validation を保つ。","branch":"impl/00001KVG0HR96-plugin-component-model-runtime","worktree":"/home/hare/Projects/yoi/.worktree/00001KVG0HR96-plugin-component-model-runtime","role_plan":"Orchestrator は専用 implementation worktree を作成し、Coder をその worktree への narrow write scope で起動する。Reviewer は実装報告後に read-only で確認する。Plugin CLI / HTTPS / FS host API は closed になったため、Component Model migration を単独で受理する。"},"author":"yoi-orchestrator","at":"2026-06-19T16:20:50Z"}

View File

@ -0,0 +1,53 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KVG0HR96",
"kind": "depends_on",
"target": "00001KV5W3PHW",
"note": "Component Model runtime migrates the existing raw WASM Tool runtime.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
},
{
"ticket_id": "00001KVG0HR96",
"kind": "depends_on",
"target": "00001KV5W3PJ3",
"note": "Component runtime must preserve Plugin permission grant enforcement.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
},
{
"ticket_id": "00001KVG0HR96",
"kind": "related",
"target": "00001KSXRQ4G8",
"note": "Updates Plugin runtime/surface/host API design direction toward Component Model.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
},
{
"ticket_id": "00001KVG0HR96",
"kind": "related",
"target": "00001KVFD3YSV",
"note": "Inspection CLI should report component runtime metadata without execution.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
},
{
"ticket_id": "00001KVG0HR96",
"kind": "related",
"target": "00001KVFDX9AF",
"note": "https host API should be designed in WIT-compatible typed terms.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
},
{
"ticket_id": "00001KVG0HR96",
"kind": "related",
"target": "00001KVFDX9AY",
"note": "fs host API should be designed in WIT-compatible typed terms.",
"author": "yoi ticket",
"at": "2026-06-19T13:21:01Z"
}
]
}

View File

@ -0,0 +1,115 @@
---
title: 'Plugin: migrate WASM Tool runtime to WebAssembly Component Model'
state: 'closed'
created_at: '2026-06-19T13:18:58Z'
updated_at: '2026-06-19T17:23:31Z'
assignee: null
readiness: 'implementation_ready'
risk_flags: ['plugin', 'wasm', 'component-model', 'wit', 'runtime-backend', 'sandbox', 'packaging', 'sdk']
queued_by: 'workspace-panel'
queued_at: '2026-06-19T13:34:43Z'
---
## Background
Yoi's current Plugin Tool runtime uses a raw core-Wasm ABI (`yoi-plugin-wasm-1`) with `yoi_tool_call`, exported `memory`, and host imports for input/output pointer-length plumbing. That was a good MVP for a small sandboxed runtime, but it should not become the long-term authoring interface.
Common Wasm extension systems usually provide a typed SDK/PDK, manifest, capability grants, templates, and inspection tooling. The WebAssembly Component Model provides a standard way to describe typed imports/exports via WIT and canonical ABI. Adopting it early prevents `https`, `fs`, SDK, and future Service/Ingress APIs from entrenching a Yoi-specific raw ABI.
This Ticket implements an explicit Component Model runtime path for Plugin Tool packages while preserving the existing package discovery, enablement, digest pinning, ToolRegistry, ordinary Tool history, and Plugin grant enforcement boundaries.
Research and direction are persisted in:
- `.yoi/objectives/00001KVG0HR9M/item.md` — Plugin Component Model migration Objective.
- `docs/design/plugin-component-model.md` — design research and policy.
- `docs/design/plugin-packages.md` — package runtime metadata direction.
## Requirements
- Add an explicit Component Model runtime kind for Plugin packages.
- Example manifest shape:
```toml
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/tool@1.0.0"
```
- Do not silently reinterpret existing raw core-Wasm packages.
- Current raw runtime remains explicit, e.g. `kind = "wasm"`, `abi = "yoi-plugin-wasm-1"`.
- Component runtime selection is driven by package manifest/runtime metadata.
- Define WIT package/worlds for the Plugin Tool runtime.
- Tool request / response / structured error types.
- Tool name and JSON input/output representation, or a better typed equivalent if decided during implementation.
- Initial host API interfaces should be WIT-compatible even if some APIs remain unimplemented.
- Add a host runtime backend capable of loading and invoking Component Model Plugin Tools.
- Evaluate whether this uses `wasmtime::component`, adapter tooling, or another backend.
- Keep runtime selected per package; discovery/inspection must not execute Plugin code.
- Preserve existing Plugin authority boundaries.
- Package discovery is read-only.
- Explicit enablement is required.
- Plugin grants are checked before Tool registration/execution and before host API calls.
- WIT imports are not authority by themselves.
- No ambient WASI filesystem/network/env is exposed.
- Preserve ordinary Tool behavior.
- Component Tool registration goes through existing ToolRegistry/model-visible schema path.
- Tool calls/results use ordinary Worker/Tool history path.
- No hidden context injection.
- Provide at least one sample Component Model Tool Plugin.
- Prefer Rust authoring path if feasible.
- Plugin author source should not contain raw pointer/length ABI plumbing.
- Add or update tests for both positive and negative paths.
- Component package discovery and manifest parsing.
- Component Tool registration.
- Component Tool execution.
- Grant denial before execution / host API access.
- Wrong world / missing export / incompatible component rejected.
- Existing raw core-Wasm Plugin runtime either still passes or has a recorded compatibility decision.
- Measure packaging/runtime impact.
- Binary size/build time impact if adding Wasmtime/component tooling.
- Nix packaging changes and `cargoHash` if dependencies change.
## Acceptance criteria
- A package with `runtime.kind = "wasm-component"` and the expected WIT world can be discovered, enabled, registered as a Tool, and executed.
- A sample Component Model Tool Plugin returns a normal Tool result through the ordinary Tool path.
- Plugin author code for the sample uses generated/SDK bindings rather than raw pointer/length imports/exports.
- Component Tool execution is denied without matching Plugin grants.
- Component host imports cannot bypass Yoi's Plugin grant model.
- Unsupported / wrong WIT world or missing required export fails closed with bounded diagnostic.
- Existing raw core-Wasm runtime remains explicitly supported or a migration/deprecation decision is recorded and tests are updated accordingly.
- `yoi plugin list/show` inspection path, if available, reports Component runtime metadata without executing the component.
- Documentation is updated with authoring/runtime instructions and migration notes.
- Validation includes relevant focused tests, `cargo fmt --check`, `git diff --check`, `cargo check` / `cargo test`, and `nix build .#yoi`.
## Non-goals
- Service surface implementation.
- Ingress surface implementation.
- WebSocket / Discord Gateway bridge.
- Inbound HTTP server.
- Replacing Plugin grants with WIT imports.
- Exposing WASI filesystem/network/env as ambient authority.
- MCP integration or MCP trust policy changes.
- A full public package registry or signature/trust-chain system.
## Implementation notes
- Keep raw core-Wasm ABI compatibility separate from Component Model support.
- Prefer WIT names that can version cleanly, such as `yoi:plugin/tool@1.0.0` and `yoi:host/https@1.0.0`.
- If Wasmtime is introduced, update Nix packaging and record dependency/build-size impact.
- If a staged approach is necessary, first land WIT definitions and manifest parsing, then runtime execution in a follow-up. Do not pretend manifest parsing alone completes this Ticket.
- `https` and `fs` host API work should avoid long-term raw ABI coupling; component-compatible request/response/path/error records are preferred.
## Related work
- `00001KVG0HR9M` — Objective: Plugin Component Model migration.
- `docs/design/plugin-component-model.md` — design research and migration direction.
- `docs/design/plugin-packages.md` — package runtime metadata direction.
- `00001KV5W3PHW` — Plugin Tool execution with minimal WASM runtime.
- `00001KV5W3PJ3` — Plugin permission grant enforcement.
- `00001KVFDX9AF` — Plugin https host API.
- `00001KVFDX9AY` — Plugin fs host API.
- `00001KVFD3YSV` — Plugin read-only CLI inspection list/show.
- `00001KSXRQ4G8` — Plugin runtime / surface / minimal host API model design.

View File

@ -0,0 +1,42 @@
## Resolution
`00001KVG0HR96` を完了しました。
実装内容:
- Plugin manifest/runtime metadata に明示的な Component Model runtime (`kind = "wasm-component"`) を追加しました。
- 既存 raw core-Wasm runtime (`kind = "wasm"`, `abi = "yoi-plugin-wasm-1"`) は明示的に維持しました。
- `wasmtime::component` による Component Tool execution path を追加しました。
- Component Tool は既存 ToolRegistry / Worker Tool path を通って実行され、hidden context injection はありません。
- WIT host imports は権限そのものではなく、Plugin grants が Tool execution / host API use の authority boundary のままです。
- Component runtime に raw runtime 相当の Wasmtime resource limits を追加し、memory/table/instance/output bound の negative tests を追加しました。
- WASI fs/network/env は expose していません。
- `yoi plugin list/show` static inspection は Component runtime metadata を報告し、component artifact を実行しません。
- WIT files、Component sample authoring sketch、docs/design updates、package/Nix updates を追加しました。
- JSON-string WIT v1 request/response shape は migration bridge として docs に記録し、structured records は follow-up に deferred としました。
主な commit:
- `57bbf14e plugin: implement component model runtime`
- `a705bb3b plugin: bound component model runtime resources`
- `63d7ad78 merge: plugin component model runtime`
Review:
- r1 は Component runtime resource limit 不足で `request_changes`
- Coder が resource limiter / negative tests / docs note を追加。
- r2 は `approve`
最終 validation:
- `cargo fmt --check`
- `git diff --check HEAD^1..HEAD`
- `cargo check`
- `cargo test -p pod feature::plugin::tests -- --nocapture`
- `cargo test -p manifest plugin -- --nocapture`
- `cargo test -p yoi plugin -- --nocapture`
- `nix build .#yoi --no-link`
Package impact:
- `nix path-info -S .#yoi`: `112156120`
- `bin/yoi`: `54605944`
- output dir: `53M`
Validation log:
- `/run/user/1000/yoi/yoi-orchestrator/bash-output/bash-rZDseu.log`

View File

@ -0,0 +1,464 @@
<!-- event: create author: "yoi ticket" at: 2026-06-19T13:18:58Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: state_changed author: workspace-panel at: 2026-06-19T13:34:43Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: yoi-orchestrator at: 2026-06-19T16:21:31Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Panel Queue により、この Ticket は Orchestrator routing 対象として明示許可された。
- Ticket body には、Component Model runtime path の intent、requirements、acceptance criteria、non-goals、implementation notes、validation が実装可能な粒度で揃っている。
- depends_on の `00001KV5W3PHW` minimal WASM runtime と `00001KV5W3PJ3` permission grant enforcement は closed。
- Related/context work はすべて完了または non-blocking context として確認した。
- `00001KVFD3YSV` Plugin CLI inspection: closed。
- `00001KVFDX9AF` HTTPS host API: closed。
- `00001KVFDX9AY` FS host API: closed。
- `00001KSXRQ4G8` is planning design context, not blocking relation authority。
- Prior waiting-capacity notes の blocker は解消した。現在 inprogress Ticket は 0 件、child implementation Pod はなし、matching branch/worktree はなし、Orchestrator worktree は clean。
- Risk domain は component-model / WIT / runtime-backend / sandbox / packaging / SDK だが、Ticket は existing raw core-Wasm packages を silently reinterpret しない、grants before Tool registration/execution/host API access、no ambient WASI fs/network/env、ordinary Tool history path、runtime kind selected by manifest metadata などの invariants を明示している。bounded context check 後も implementation 前に必要な追加 human decision は見つからなかった。
Evidence checked:
- Ticket `00001KVG0HR96` body / thread / artifacts。
- `TicketRelationQuery(00001KVG0HR96)`: depends_on blockers は closed。related records は context link。
- `TicketOrchestrationPlanQuery(00001KVG0HR96)`: previous waiting notes were based on active CLI/HTTPS/FS work; all are now closed. 今回 `accepted_plan` を記録済み。
- Current workspace state:
- Orchestrator worktree clean。
- queued: this Ticket only。
- inprogress: 0。
- visible Pods: self + peers only; spawned children 0。
- Code/docs context:
- `crates/manifest/src/plugin.rs`: current runtime metadata and `yoi-plugin-wasm-1` validation。
- `crates/pod/src/feature/plugin.rs`: current core-Wasm Plugin runtime, Tool registration/static inspection, HTTPS/FS host APIs, import validation。
- `crates/yoi/src/plugin_cli.rs`: inspection output should report Component runtime metadata without execution。
- Ticket body references `docs/design/plugin-component-model.md`, `docs/design/plugin-packages.md`, and Objective `00001KVG0HR9M` as design context.
IntentPacket:
Intent:
- Add explicit WebAssembly Component Model runtime support for Plugin Tool packages while preserving existing Plugin discovery, enablement, digest pinning, ToolRegistry integration, ordinary Tool history, and Plugin grant enforcement.
- Move Plugin authoring/runtime path toward WIT/canonical ABI so future `https`, `fs`, SDK, Service/Ingress APIs do not entrench the raw pointer/length core-Wasm ABI.
Binding decisions / invariants:
- Existing raw core-Wasm packages must not be silently reinterpreted as components。
- Runtime selection is manifest-driven. Component packages use explicit runtime metadata such as `kind = "wasm-component"`, component artifact path, and expected world。
- Existing raw runtime remains explicit (`kind = "wasm"`, `abi = "yoi-plugin-wasm-1"`) unless a migration/deprecation decision is recorded in this Ticket with tests updated accordingly。
- Package discovery and inspection remain read-only and must not execute components。
- Explicit enablement and digest/version/source pinning remain authoritative。
- Plugin grants are checked before Tool registration/execution and before host API calls。
- WIT imports are not authority by themselves。
- No ambient WASI filesystem/network/env is exposed。
- Component Tool registration still goes through existing ToolRegistry / model-visible schema path。
- Tool calls/results use ordinary Worker/Tool history path; no hidden context injection。
- HTTPS/FS host API security boundaries already implemented must be preserved。
Requirements / acceptance criteria:
- A package with `runtime.kind = "wasm-component"` and expected WIT world can be discovered, enabled, registered as a Tool, and executed。
- Sample Component Model Tool Plugin returns a normal Tool result through ordinary Tool path。
- Sample Plugin author source uses generated/SDK bindings rather than raw pointer/length imports/exports。
- Component Tool execution is denied without matching Plugin grants。
- Component host imports cannot bypass Plugin grant model。
- Wrong world / missing export / incompatible component fails closed with bounded diagnostic。
- Existing raw core-Wasm runtime remains explicitly supported, or a migration/deprecation decision is recorded and tests updated。
- `yoi plugin list/show` reports Component runtime metadata without executing components。
- Documentation is updated with authoring/runtime instructions and migration notes。
- Build/package impact is measured and Nix packaging/cargo hash updated if dependencies change。
Implementation latitude:
- Use `wasmtime::component` / WIT tooling or another narrow backend consistent with the codebase。
- Choose WIT names that version cleanly, e.g. `yoi:plugin/tool@1.0.0` and `yoi:host/https@1.0.0` / `yoi:host/fs@1.0.0`
- If a staged approach is unavoidable, escalate before narrowing completion. Do not land manifest parsing alone as if it completes this Ticket。
- Keep compatibility layer and Component runtime dispatch cleanly separated。
- Use focused sample fixtures/tests rather than broad E2E process spawning。
Escalate if:
- Component runtime execution cannot be implemented without a broad architecture redesign。
- Dependency/build-size impact is large enough to need product decision。
- WIT/tool request-response typing requires a product/API decision beyond Ticket latitude。
- Preserving both raw core-Wasm and Component runtime would substantially distort implementation。
- SDK/sample generation requires external toolchain not feasible in repository validation。
Validation:
- Focused Component Plugin manifest/discovery/static inspection tests。
- Component Tool registration and execution tests。
- Grant denial before Component Tool execution / host API access。
- Wrong world / missing export / incompatible component fail-closed tests。
- Existing raw core-Wasm Plugin runtime tests remain passing or migration decision/tests updated。
- `cargo fmt --check`
- `git diff --check`
- relevant `cargo check` / `cargo test`
- `nix build .#yoi` because component runtime dependencies / packaging are likely to change。
Critical risks / reviewer focus:
- WIT imports becoming implicit authority。
- Component runtime bypassing existing Plugin grant enforcement。
- Ambient WASI fs/network/env exposure。
- Component execution bypassing ordinary Tool result/history path。
- Breaking existing raw core-Wasm package behavior without explicit decision/tests。
- Inspection accidentally executing components。
- Unbounded or secret-leaking diagnostics。
- Packaging/Nix/Cargo dependency correctness and binary/build-time impact。
Next action:
- `queued -> inprogress` を記録し、Ticket records を Orchestrator worktree に commit してから、専用 implementation worktree を作成し Coder Pod を narrow write scope で起動する。root/original workspace は操作しない。
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T16:21:50Z from: queued to: inprogress reason: orchestrator_acceptance_plugin_component_model_runtime field: state -->
## State changed
Ticket body/thread, relation metadata, orchestration plan records, related completed Tickets, Orchestrator worktree, visible Pods, existing branch/worktree, and bounded Component Model runtime code context were checked. Depends-on blockers are closed, Plugin CLI / HTTPS / FS related work are closed, and no dirty-state blocker or missing planning decision was found. Accepting this queued Ticket for implementation before worktree/Pod side effects.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T16:25:23Z -->
## Implementation report
Implementation orchestration start:
- Acceptance records were committed on Orchestrator branch: `878517dc ticket: accept plugin component model runtime`.
- Created implementation worktree `/home/hare/Projects/yoi/.worktree/00001KVG0HR96-plugin-component-model-runtime` on branch `impl/00001KVG0HR96-plugin-component-model-runtime` at `878517dc`.
- Initial `SpawnPod` attempt with only the implementation worktree as readable scope failed because the spawned runtime workspace identity is `/home/hare/Projects/yoi` and that root was not readable under the child scope. No child Pod remained registered.
- Retried with read-only scope for `/home/hare/Projects/yoi` plus write scope limited to the implementation worktree. Coder Pod `yoi-coder-00001KVG0HR96` started successfully. The task explicitly instructs the Coder to edit/build/commit only in the implementation worktree and not to operate in the root/original workspace.
Next action:
- Wait for Coder implementation report, then inspect branch diff/validation evidence and route to Reviewer.
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T17:00:21Z -->
## Implementation report
Coder implementation report received from `yoi-coder-00001KVG0HR96`.
Implementation commit:
- `57bbf14e plugin: implement component model runtime`
Changed areas reported:
- Manifest/runtime metadata: added explicit `kind = "wasm-component"` support with `component` artifact path and `world = "yoi:plugin/tool@1.0.0"`; preserved raw core-Wasm as `kind = "wasm"` + `abi = "yoi-plugin-wasm-1"`; reject mixed raw/component runtime metadata.
- Plugin runtime: added `wasmtime::component` path; Component Tools execute through existing ToolRegistry / Worker Tool path; component imports are fail-closed; grants are checked before instantiation/import use and on host API calls; no WASI fs/network/env exposed.
- Static inspection: reports `wasm-component/yoi:plugin/tool@1.0.0` without reading/executing the artifact.
- Tests: component discovery/registration/execution, host-import grant denial, wrong-world, missing-export, raw-core-Wasm-not-component fail-closed, and existing raw runtime coverage.
- Docs / authoring: updated design/package docs, added WIT files under `resources/plugin/wit/`, and sample author source under `docs/examples/plugin-component-tool/lib.rs`.
- Packaging: added `wasmtime = 45.0.2` with narrow non-default features; updated `Cargo.lock` and `package.nix` `cargoHash`.
Coder validation reported as passing:
- `cargo fmt --check`
- `git diff --check`
- `cargo check`
- `cargo test -p pod feature::plugin::tests -- --nocapture` (`55 passed`)
- `cargo test -p manifest plugin -- --nocapture` (`17 passed`)
- `cargo test -p yoi plugin -- --nocapture` (`11 passed`)
- `nix build .#yoi --no-link`
Orchestrator evidence checked before review dispatch:
- Implementation worktree is clean on `impl/00001KVG0HR96-plugin-component-model-runtime`.
- Merge-base with `orchestration` is `878517dc`; Orchestrator branch head is `02006fee`.
- Diff from acceptance is one implementation commit, `57bbf14e`, touching 10 files: `Cargo.lock`, manifest Plugin parser, pod Plugin runtime, `crates/pod/Cargo.toml`, docs, `package.nix`, and WIT/sample files.
- `git diff --check 878517dc..HEAD` produced no diagnostics.
- Diff size is material: about 1568 insertions / 68 deletions; dependency impact note is reviewer focus.
Next action:
- Dispatch Reviewer for r1 review against Ticket requirements, with focus on Component Model authority boundaries, grant enforcement, no ambient WASI, raw runtime compatibility, inspection not executing code, diagnostics, tests, and packaging/Nix impact.
---
<!-- event: plan author: yoi-orchestrator at: 2026-06-19T17:00:57Z -->
## Plan
Review dispatch:
- Spawned Reviewer Pod `yoi-reviewer-00001KVG0HR96-r1` against implementation branch `impl/00001KVG0HR96-plugin-component-model-runtime`.
- Review target commit: `57bbf14e plugin: implement component model runtime`.
- Review baseline: `878517dc`.
- Reviewer task focuses on Component/WIT authority boundaries, Plugin grant enforcement, no ambient WASI, raw runtime compatibility, inspection read-only behavior, fail-closed diagnostics, HTTPS/FS invariant preservation, dependency/package impact, and validation evidence.
- Reviewer is instructed not to edit source, commit, merge, close the Ticket, or use TicketReview directly; it will report verdict/evidence back to Orchestrator.
---
<!-- event: review author: yoi-reviewer-00001KVG0HR96-r1 at: 2026-06-19T17:08:18Z status: request_changes -->
## Review: request changes
Verdict: `request_changes`
確認範囲:
- Ticket contract: `.yoi/tickets/00001KVG0HR96/item.md`, `.yoi/tickets/00001KVG0HR96/thread.md`
- Diff: `878517dc..57bbf14e`
- 主な対象: `crates/manifest/src/plugin.rs`, `crates/pod/src/feature/plugin.rs`, `crates/pod/Cargo.toml`, `Cargo.lock`, `package.nix`, `resources/plugin/wit/*`, docs/sample, existing `crates/yoi/src/plugin_cli.rs` static inspection path。
良い点:
- 明示的な `wasm-component` metadata、raw `wasm` ABI との分離、artifact を実行しない static inspection、ToolRegistry 経由の registration/execution、Tool execution/host API 前の grant check、WASI imports を link しない方針は概ね Ticket intent に沿っている。
Blocking issue:
1. Component runtime が既存 WASM sandbox/resource boundary を保持していない。
- Raw core-Wasm path は `StoreLimitsBuilder` / `memory_size(PLUGIN_WASM_MEMORY_BYTES)` / table/instance/memory limits / `store.limiter(...)` を使っている。
- 新しい Component Model path (`crates/pod/src/feature/plugin.rs:1965-2006`) は `consume_fuel(true)``max_wasm_stack(...)` は設定しているが、component execution store に同等の `Store::limiter(...)` / resource limit がない。
- また component result は `String` として host 側に lift された後で `decode_plugin_wasm_output(output.as_bytes())` による output cap を適用しており、untrusted component が output cap 前に host allocation を強制できる。
- これは Ticket/design の「Component Model support must preserve existing Plugin authority/sandbox boundaries」に反する。component memory/table/instance/output bounds のテストも不足している。
Required fix:
- Component execution store に raw runtime と同等の Wasmtime resource limiter / memory/table/instance limits を入れる。
- Component output/string lifting が unbounded host allocation にならないよう、少なくとも component memory/resource limit により bounded で fail-closed になることを実装・コメント・テストで示す。
- Component memory/resource/output bound の negative tests を追加する。
Non-blocking concerns / follow-ups:
- WIT は現在 `call(tool-name: string, input-json: string) -> string`。Ticket の “Tool request / response / structured error types” は完全には表現していない。今回 v1 の narrow ABI として意図的に deferred なら decision/follow-up として記録すること。
- Rust sample は `wit_bindgen` authoring sketch であり、build/package された sample plugin を test しているわけではない。follow-up として packaged sample があるとよい。
- `nix build` / `cargoHash` は検証されているが、binary size/build-time impact measurement がまだ明示記録されていない。最終 closure 前に capture すること。
Reviewer validation:
- `git diff --check 878517dc..57bbf14e`: passed
- `cargo fmt --check`: passed
- `cargo test -p manifest plugin -- --nocapture`: 17 passed
- `cargo test -p pod feature::plugin::tests -- --nocapture`: 55 passed
- `cargo test -p yoi plugin -- --nocapture`: 11 passed
- `cargo check`: passed
- `nix build .#yoi --no-link`: passed
- `nix path-info .#yoi`: `/nix/store/m5gsv05lfs7s9bh8dfy4ady6sda925fv-yoi-0.1.0`
Worktree status at review end: clean。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T17:17:08Z -->
## Implementation report
Coder r1-fix report received from `yoi-coder-00001KVG0HR96`.
New fix commit:
- `a705bb3b plugin: bound component model runtime resources`
Branch commits now:
- `a705bb3b plugin: bound component model runtime resources`
- `57bbf14e plugin: implement component model runtime`
Fix summary:
- Added `wasmtime::StoreLimits` to Component Model host state.
- Applied raw-runtime-equivalent limits to component execution store:
- `memory_size(PLUGIN_WASM_MEMORY_BYTES)`
- `table_elements(PLUGIN_WASM_TABLE_ELEMENTS)`
- `instances(1)`
- `tables(1)`
- `memories(1)`
- `trap_on_grow_failure(true)`
- Added inline comment near WIT string lift explaining that host allocation before `decode_plugin_wasm_output` is bounded by component memory/table/instance limits.
- Added negative tests:
- component oversized memory fails closed before string lift
- component oversized table fails closed
- component output cap still fails closed after bounded lift
- Added docs note that JSON-string WIT v1 request/response is deliberate migration bridge and structured WIT records are deferred.
Coder validation reported as passing:
- `cargo fmt --check`
- `git diff --check`
- `cargo check`
- `cargo test -p pod feature::plugin::tests -- --nocapture` (`58 passed`)
- `cargo test -p manifest plugin -- --nocapture` (`17 passed`)
- `cargo test -p yoi plugin -- --nocapture` (`11 passed`)
- `nix build .#yoi --no-link`
Package / binary impact measurement reported:
- `nix path-info -S .#yoi`: `/nix/store/g9nhx3g2xsn7r3rw6n61scdkha5s7m8s-yoi-0.1.0 112156120`
- `stat -c '%s %n' "$out/bin/yoi"`: `54605944 .../bin/yoi`
- `du -sh "$out"`: `53M`
Orchestrator evidence checked before re-review:
- Implementation worktree is clean.
- HEAD is `a705bb3b`.
- Fix diff from `57bbf14e..HEAD` touches `crates/pod/src/feature/plugin.rs` and `docs/design/plugin-component-model.md`, 108 insertions.
- Overall `git diff --check 878517dc..HEAD` produced no diagnostics.
Next action:
- Route back to Reviewer for r2 verification of the resource-boundary blocker and final acceptance criteria.
---
<!-- event: review author: yoi-reviewer-00001KVG0HR96-r1 at: 2026-06-19T17:19:55Z status: approve -->
## Review: approve
Verdict: `approve`
確認範囲:
- Ticket contract: `.yoi/tickets/00001KVG0HR96/item.md`, `.yoi/tickets/00001KVG0HR96/thread.md`
- r1 blocker fix diff: `57bbf14e..a705bb3b`
- 全体実装: `878517dc..a705bb3b`
- 主な対象: Plugin runtime / manifest / WIT / docs / tests / Cargo/Nix/package impact paths。
Previous blocker verification:
- Component runtime now has raw-runtime-equivalent Wasmtime store limits:
- `crates/pod/src/feature/plugin.rs:1514-1523`
- `memory_size(PLUGIN_WASM_MEMORY_BYTES)`
- `table_elements(PLUGIN_WASM_TABLE_ELEMENTS)`
- `instances(1)`
- `tables(1)`
- `memories(1)`
- `trap_on_grow_failure(true)`
- Component host state carries those limits:
- `crates/pod/src/feature/plugin.rs:1926-1930`
- Component store installs the limiter before instantiation/execution:
- `crates/pod/src/feature/plugin.rs:1991-2001`
- Code documents why WIT string lifting remains bounded before ToolOutput JSON cap enforcement:
- `crates/pod/src/feature/plugin.rs:2017-2026`
- Focused negative tests were added and passed:
- component memory limit fails closed before string lift
- component table limit fails closed
- component output cap still fails closed after bounded lift
Overall Ticket satisfaction:
- Runtime kind is explicit and manifest-driven: raw `kind = "wasm"` + `abi = "yoi-plugin-wasm-1"`; component `kind = "wasm-component"` + `component` + expected `world`
- Component execution stays on existing Plugin Tool / ToolRegistry path。
- Plugin grants remain authority boundary before Tool execution and host API use。
- Component imports are validated; unsupported imports fail closed。
- No WASI fs/network/env is linked。
- Static inspection reports runtime metadata without executing component artifacts。
- Existing raw runtime tests continue passing。
- Docs record JSON-string WIT v1 as deliberate migration bridge and defer structured WIT records:
- `docs/design/plugin-component-model.md:169-177`
Blocking issues: none。
Non-blocking follow-ups:
- Structured WIT request/response/error records are intentionally deferred。
- Sample remains an authoring sketch rather than fully built packaged sample, but runtime behavior is covered by focused component fixtures/tests。
Reviewer validation:
- `git diff --check 878517dc..a705bb3b`: passed
- `cargo fmt --check`: passed
- `cargo test -p pod feature::plugin::tests -- --nocapture`: 58 passed
- `cargo test -p manifest plugin -- --nocapture`: 17 passed
- `cargo test -p yoi plugin -- --nocapture`: 11 passed
- `cargo check`: passed
- `nix build .#yoi --no-link`: passed
- `nix path-info -S .#yoi`: `/nix/store/g9nhx3g2xsn7r3rw6n61scdkha5s7m8s-yoi-0.1.0 112156120`
- `stat -c '%s %n' "$out/bin/yoi"`: `54605944 .../bin/yoi`
- `du -sh "$out"`: `53M`
Worktree status at review end: clean。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-19T17:23:09Z -->
## Implementation report
Merge and final validation report:
Merged implementation branch into Orchestrator branch:
- Merge commit: `63d7ad78 merge: plugin component model runtime`
- Implementation commits included:
- `57bbf14e plugin: implement component model runtime`
- `a705bb3b plugin: bound component model runtime resources`
Reviewer outcome:
- r1 requested changes for missing Component Model resource limiter.
- Coder added raw-runtime-equivalent Wasmtime `StoreLimits` for Component execution and negative tests.
- r2 approved with no blocking issues.
Orchestrator validation after merge passed:
- `cargo fmt --check`
- `git diff --check HEAD^1..HEAD`
- `cargo check`
- `cargo test -p pod feature::plugin::tests -- --nocapture`
- `cargo test -p manifest plugin -- --nocapture`
- `cargo test -p yoi plugin -- --nocapture`
- `nix build .#yoi --no-link`
Validation log:
- `/run/user/1000/yoi/yoi-orchestrator/bash-output/bash-rZDseu.log`
Package impact recorded by Coder/Reviewer:
- `nix path-info -S .#yoi`: `112156120`
- `bin/yoi` size: `54605944`
- output dir: `53M`
Final state:
- Orchestrator worktree clean at `63d7ad78` after validation.
- Implementation worktree remains available for cleanup after Ticket completion records are committed.
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-19T17:23:13Z from: inprogress to: done reason: merged_reviewed_validated field: state -->
## State changed
Implementation was merged into Orchestrator branch at `63d7ad78`, r2 review approved, and final Orchestrator validation passed: `cargo fmt --check`, `git diff --check HEAD^1..HEAD`, `cargo check`, focused Plugin tests for `pod`/`manifest`/`yoi`, and `nix build .#yoi --no-link`.
---
<!-- event: state_changed author: hare at: 2026-06-19T17:23:31Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-06-19T17:23:31Z status: closed -->
## 完了
## Resolution
`00001KVG0HR96` を完了しました。
実装内容:
- Plugin manifest/runtime metadata に明示的な Component Model runtime (`kind = "wasm-component"`) を追加しました。
- 既存 raw core-Wasm runtime (`kind = "wasm"`, `abi = "yoi-plugin-wasm-1"`) は明示的に維持しました。
- `wasmtime::component` による Component Tool execution path を追加しました。
- Component Tool は既存 ToolRegistry / Worker Tool path を通って実行され、hidden context injection はありません。
- WIT host imports は権限そのものではなく、Plugin grants が Tool execution / host API use の authority boundary のままです。
- Component runtime に raw runtime 相当の Wasmtime resource limits を追加し、memory/table/instance/output bound の negative tests を追加しました。
- WASI fs/network/env は expose していません。
- `yoi plugin list/show` static inspection は Component runtime metadata を報告し、component artifact を実行しません。
- WIT files、Component sample authoring sketch、docs/design updates、package/Nix updates を追加しました。
- JSON-string WIT v1 request/response shape は migration bridge として docs に記録し、structured records は follow-up に deferred としました。
主な commit:
- `57bbf14e plugin: implement component model runtime`
- `a705bb3b plugin: bound component model runtime resources`
- `63d7ad78 merge: plugin component model runtime`
Review:
- r1 は Component runtime resource limit 不足で `request_changes`
- Coder が resource limiter / negative tests / docs note を追加。
- r2 は `approve`
最終 validation:
- `cargo fmt --check`
- `git diff --check HEAD^1..HEAD`
- `cargo check`
- `cargo test -p pod feature::plugin::tests -- --nocapture`
- `cargo test -p manifest plugin -- --nocapture`
- `cargo test -p yoi plugin -- --nocapture`
- `nix build .#yoi --no-link`
Package impact:
- `nix path-info -S .#yoi`: `112156120`
- `bin/yoi`: `54605944`
- output dir: `53M`
Validation log:
- `/run/user/1000/yoi/yoi-orchestrator/bash-output/bash-rZDseu.log`
---

650
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a"
dependencies = [
"gimli",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@ -82,6 +91,12 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arc-swap"
version = "1.9.1"
@ -222,6 +237,9 @@ name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
dependencies = [
"allocator-api2",
]
[[package]]
name = "bytemuck"
@ -351,6 +369,15 @@ dependencies = [
"cc",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
@ -422,6 +449,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpp_demangle"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
dependencies = [
"cfg-if",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -440,6 +476,157 @@ dependencies = [
"libc",
]
[[package]]
name = "cranelift-assembler-x64"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bc293b86236abcc45f2f72e2d18e2bd636f2a08b75eb286bae31e71e1430c91"
dependencies = [
"cranelift-assembler-x64-meta",
]
[[package]]
name = "cranelift-assembler-x64-meta"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b954c826eddaf1b001402cb8aecf1764c6f6d637ba69fb9e3311f1ebac965be6"
dependencies = [
"cranelift-srcgen",
]
[[package]]
name = "cranelift-bforest"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4053fa2575ef4a5c35d2708533df2200400ae979226cea9cc92a578b811bd4e7"
dependencies = [
"cranelift-entity",
"wasmtime-internal-core",
]
[[package]]
name = "cranelift-bitset"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d216663191014aa63e1d2cffd058e609eaf207646d40b739d88250f65b2c4f69"
dependencies = [
"serde",
"serde_derive",
"wasmtime-internal-core",
]
[[package]]
name = "cranelift-codegen"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5e7e7aad6a425a51da1ad7ab9e5d280ea97eb7c7c4545fafb567915a75aadb"
dependencies = [
"bumpalo",
"cranelift-assembler-x64",
"cranelift-bforest",
"cranelift-bitset",
"cranelift-codegen-meta",
"cranelift-codegen-shared",
"cranelift-control",
"cranelift-entity",
"cranelift-isle",
"gimli",
"hashbrown 0.17.1",
"libm",
"log",
"pulley-interpreter",
"regalloc2",
"rustc-hash",
"serde",
"smallvec",
"target-lexicon",
"wasmtime-internal-core",
]
[[package]]
name = "cranelift-codegen-meta"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c421d80a9a85f806cb02a2983b5b5368a335c319795b1f1b4b771a24479af5b0"
dependencies = [
"cranelift-assembler-x64-meta",
"cranelift-codegen-shared",
"cranelift-srcgen",
"heck",
"pulley-interpreter",
]
[[package]]
name = "cranelift-codegen-shared"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78fdb83ab012d0ee6a44ced7ca8788a444f17cf821c62f95d6ef87c9f0262518"
[[package]]
name = "cranelift-control"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b75adc6eb7bb4ac6365106afb6cac4f12fe1ddfa02ddc9fd7015ca1469b471b"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668e56db75a54816cbdd7c7b7bfc558b08bf7b2cda9d0846491517e92f3b393b"
dependencies = [
"cranelift-bitset",
"serde",
"serde_derive",
"wasmtime-internal-core",
]
[[package]]
name = "cranelift-frontend"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c63892dc1cc3ae48680183fa66997f60ffe7f1e200c8d390f8ee66edff4aef5a"
dependencies = [
"cranelift-codegen",
"log",
"smallvec",
"target-lexicon",
]
[[package]]
name = "cranelift-isle"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94eaf429c32a12715429c7c6ddfdd43c170f4cdd7e97bfa507bd68a652091087"
[[package]]
name = "cranelift-native"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd77674904ae9be11c1e1efdba54788b59f3d6658d747b97534bfbba2909aacc"
dependencies = [
"cranelift-codegen",
"libc",
"target-lexicon",
]
[[package]]
name = "cranelift-srcgen"
version = "0.132.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cba7c0ff5941842c36653da155580ce41e675c204a67ac1b4e1c478a9347bbb7"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -700,6 +887,18 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "encoding_rs"
version = "0.8.35"
@ -1027,6 +1226,18 @@ dependencies = [
"wasip3",
]
[[package]]
name = "gimli"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c"
dependencies = [
"fnv",
"hashbrown 0.16.1",
"indexmap",
"stable_deref_trait",
]
[[package]]
name = "glob"
version = "0.3.3"
@ -1122,6 +1333,17 @@ dependencies = [
"foldhash 0.2.0",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash 0.2.0",
"serde",
"serde_core",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -1463,12 +1685,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.13.1"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
@ -1793,6 +2015,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "mach2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
[[package]]
name = "manifest"
version = "0.1.0"
@ -1852,6 +2083,15 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "memfd"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227"
dependencies = [
"rustix 1.1.4",
]
[[package]]
name = "memmap2"
version = "0.9.10"
@ -2077,6 +2317,18 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "object"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
dependencies = [
"crc32fast",
"hashbrown 0.17.1",
"indexmap",
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@ -2353,6 +2605,7 @@ dependencies = [
"pod-store",
"protocol",
"provider",
"reqwest",
"schemars",
"serde",
"serde_json",
@ -2367,6 +2620,7 @@ dependencies = [
"tracing",
"uuid",
"wasmi",
"wasmtime",
"wat",
"workflow",
]
@ -2402,6 +2656,18 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"serde",
]
[[package]]
name = "potential_utf"
version = "0.1.5"
@ -2498,6 +2764,29 @@ dependencies = [
"unicase",
]
[[package]]
name = "pulley-interpreter"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9880c1985ccccaed3646b0ef793dc39a4b117403ed4afc6fa3ef6027c5200f"
dependencies = [
"cranelift-bitset",
"log",
"pulley-macros",
"wasmtime-internal-core",
]
[[package]]
name = "pulley-macros"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee249346855ad102580e474da5463f86f8a7d449e6d49e00fefb304e448e2983"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "quinn"
version = "0.11.9"
@ -2769,6 +3058,20 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "regalloc2"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186"
dependencies = [
"allocator-api2",
"bumpalo",
"hashbrown 0.17.1",
"log",
"rustc-hash",
"smallvec",
]
[[package]]
name = "regex"
version = "1.12.3"
@ -2807,6 +3110,7 @@ dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
"h2",
@ -2858,6 +3162,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "rustc-hash"
version = "2.1.2"
@ -3089,6 +3399,10 @@ name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde"
@ -3339,6 +3653,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "socket2"
@ -3499,6 +3816,12 @@ dependencies = [
"libc",
]
[[package]]
name = "target-lexicon"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "target-triple"
version = "1.0.0"
@ -4258,12 +4581,22 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.246.2"
version = "0.248.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7"
checksum = "ac92cf547bc18d27ecc521015c08c353b4f18b84ab388bb6d1b6b682c620d9b6"
dependencies = [
"leb128fmt",
"wasmparser 0.246.2",
"wasmparser 0.248.0",
]
[[package]]
name = "wasm-encoder"
version = "0.252.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f"
dependencies = [
"leb128fmt",
"wasmparser 0.252.0",
]
[[package]]
@ -4355,9 +4688,22 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.246.2"
version = "0.248.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d"
checksum = "aa4439c5eee9df71ee0c6efb37f63b1fcb1fec38f85f5142c54e7ed05d33091a"
dependencies = [
"bitflags 2.11.0",
"hashbrown 0.17.1",
"indexmap",
"semver",
"serde",
]
[[package]]
name = "wasmparser"
version = "0.252.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c"
dependencies = [
"bitflags 2.11.0",
"indexmap",
@ -4365,23 +4711,257 @@ dependencies = [
]
[[package]]
name = "wast"
version = "246.0.2"
name = "wasmprinter"
version = "0.248.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62"
checksum = "30b264a5410b008d4d199a92bf536eae703cbd614482fc1ec53831cf19e1c183"
dependencies = [
"anyhow",
"termcolor",
"wasmparser 0.248.0",
]
[[package]]
name = "wasmtime"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ce9aa2c67f75fadcfdc6aa9097d03e7c39485dfe316f2ed6a7c0fd186c527"
dependencies = [
"addr2line",
"async-trait",
"bitflags 2.11.0",
"bumpalo",
"cc",
"cfg-if",
"encoding_rs",
"libc",
"log",
"mach2",
"memfd",
"object",
"once_cell",
"postcard",
"pulley-interpreter",
"rustix 1.1.4",
"semver",
"serde",
"serde_derive",
"smallvec",
"target-lexicon",
"wasmparser 0.248.0",
"wasmtime-environ",
"wasmtime-internal-component-macro",
"wasmtime-internal-component-util",
"wasmtime-internal-core",
"wasmtime-internal-cranelift",
"wasmtime-internal-fiber",
"wasmtime-internal-jit-debug",
"wasmtime-internal-jit-icache-coherence",
"wasmtime-internal-unwinder",
"wasmtime-internal-versioned-export-macros",
"wasmtime-internal-winch",
"windows-sys 0.61.2",
]
[[package]]
name = "wasmtime-environ"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fb157bd1fbf689ac89d570433a700db6f33bdfcb5ffc30e3f1c49e4c70de71"
dependencies = [
"anyhow",
"cpp_demangle",
"cranelift-bforest",
"cranelift-bitset",
"cranelift-entity",
"gimli",
"hashbrown 0.17.1",
"indexmap",
"log",
"object",
"postcard",
"rustc-demangle",
"semver",
"serde",
"serde_derive",
"sha2 0.10.9",
"smallvec",
"target-lexicon",
"wasm-encoder 0.248.0",
"wasmparser 0.248.0",
"wasmprinter",
"wasmtime-internal-component-util",
"wasmtime-internal-core",
]
[[package]]
name = "wasmtime-internal-component-macro"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96c17f35fae2ab574667aba0c58fd56349a6f788ac42541a2e543116d5cfb91"
dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn 2.0.117",
"wasmtime-internal-component-util",
"wasmtime-internal-wit-bindgen",
"wit-parser 0.248.0",
]
[[package]]
name = "wasmtime-internal-component-util"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d2eeb9b53222859e6f5dc73d2ccfb33254d672469cac11b693a71912e2f3817"
[[package]]
name = "wasmtime-internal-core"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1deaf6bc3430abd7497b00c64f06ca2b97ca0fe41af87836446ca30949965c"
dependencies = [
"hashbrown 0.17.1",
"libm",
"serde",
]
[[package]]
name = "wasmtime-internal-cranelift"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b845f83b5b04b11bc48329b53eb4fa8cf9f28a43c71ed8e1203f68ffa9806d1b"
dependencies = [
"cfg-if",
"cranelift-codegen",
"cranelift-control",
"cranelift-entity",
"cranelift-frontend",
"cranelift-native",
"gimli",
"itertools",
"log",
"object",
"pulley-interpreter",
"smallvec",
"target-lexicon",
"thiserror 2.0.18",
"wasmparser 0.248.0",
"wasmtime-environ",
"wasmtime-internal-core",
"wasmtime-internal-unwinder",
"wasmtime-internal-versioned-export-macros",
]
[[package]]
name = "wasmtime-internal-fiber"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10c8466f72965ae85c250f90aaa7992c089a2f8502009bd0d2c9e7d6409174a"
dependencies = [
"cc",
"cfg-if",
"libc",
"rustix 1.1.4",
"wasmtime-environ",
"wasmtime-internal-versioned-export-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "wasmtime-internal-jit-debug"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3adfecf5621b14d8f8871f4cb4ed9f844197b1ddefc702ef4c859552cd9551"
dependencies = [
"cc",
"wasmtime-internal-versioned-export-macros",
]
[[package]]
name = "wasmtime-internal-jit-icache-coherence"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d3c1e9fb618ec45c9b3477ea683cd37bee427273d7b13bba5c66a1caaf1dd6"
dependencies = [
"cfg-if",
"libc",
"wasmtime-internal-core",
"windows-sys 0.61.2",
]
[[package]]
name = "wasmtime-internal-unwinder"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aa91132b81f1e172ec7e7c3c114ac34209ee6b3524b3a8d6943af99803f66c5"
dependencies = [
"cfg-if",
"cranelift-codegen",
"log",
"object",
"wasmtime-environ",
]
[[package]]
name = "wasmtime-internal-versioned-export-macros"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea811ffe23f597cc7708327ea25d9eb018dcf760ffe15ccb7d0b27ad635de61"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "wasmtime-internal-winch"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "828b66175c54a0d00b4c1c1c76658d8aa73aeb9fa3553575c5eee56d40f2eb18"
dependencies = [
"cranelift-codegen",
"gimli",
"log",
"object",
"target-lexicon",
"wasmparser 0.248.0",
"wasmtime-environ",
"wasmtime-internal-cranelift",
"winch-codegen",
]
[[package]]
name = "wasmtime-internal-wit-bindgen"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae00896ad9bef1b3ca6401ae9a841daa6f357dd91541b6baf87082946d1bde1"
dependencies = [
"anyhow",
"bitflags 2.11.0",
"heck",
"indexmap",
"wit-parser 0.248.0",
]
[[package]]
name = "wast"
version = "252.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65"
dependencies = [
"bumpalo",
"leb128fmt",
"memchr",
"unicode-width",
"wasm-encoder 0.246.2",
"wasm-encoder 0.252.0",
]
[[package]]
name = "wat"
version = "1.246.2"
version = "1.252.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c"
checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb"
dependencies = [
"wast",
]
@ -4527,6 +5107,25 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winch-codegen"
version = "45.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89c09acfdfa281b3340e1e94ef3cf6618d69eab975280f881e154c29f49419c1"
dependencies = [
"cranelift-assembler-x64",
"cranelift-codegen",
"gimli",
"regalloc2",
"smallvec",
"target-lexicon",
"thiserror 2.0.18",
"wasmparser 0.248.0",
"wasmtime-environ",
"wasmtime-internal-core",
"wasmtime-internal-cranelift",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@ -4800,7 +5399,7 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
"wit-parser 0.244.0",
]
[[package]]
@ -4850,7 +5449,7 @@ dependencies = [
"wasm-encoder 0.244.0",
"wasm-metadata",
"wasmparser 0.244.0",
"wit-parser",
"wit-parser 0.244.0",
]
[[package]]
@ -4871,6 +5470,25 @@ dependencies = [
"wasmparser 0.244.0",
]
[[package]]
name = "wit-parser"
version = "0.248.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "247ad505da2915a082fe13204c5ba8788425aea1de54f43b284818cf82637856"
dependencies = [
"anyhow",
"hashbrown 0.17.1",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser 0.248.0",
]
[[package]]
name = "workflow"
version = "0.1.0"

View File

@ -74,11 +74,15 @@ pub struct PluginGrantConfig {
pub digest: Option<String>,
/// Explicit capabilities granted for the pinned package identity/version/digest.
pub permissions: Vec<PluginPermission>,
/// Bounded outbound HTTPS allowlist entries for `host_api.https`.
pub https: Vec<PluginHttpsGrant>,
/// Scoped filesystem allowlist entries for `host_api.fs`.
pub fs: Vec<PluginFsGrant>,
}
impl PluginGrantConfig {
pub fn is_empty(&self) -> bool {
self.permissions.is_empty()
self.permissions.is_empty() && self.https.is_empty() && self.fs.is_empty()
}
pub fn binding_error(
@ -87,7 +91,7 @@ impl PluginGrantConfig {
digest: &str,
version: &str,
) -> Option<&'static str> {
if self.permissions.is_empty() {
if self.is_empty() {
return None;
}
let Some(grant_id) = &self.id else {
@ -128,6 +132,75 @@ pub enum PluginPermission {
HostApi { api: PluginHostApi },
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct PluginHttpsGrant {
/// Exact HTTPS request host allowed by this grant. Wildcards are intentionally unsupported.
pub host: String,
/// Uppercase HTTP methods allowed for this host, for example `GET` or `POST`.
pub methods: Vec<String>,
/// Optional path prefixes allowed for this host. Empty means any absolute path on the host.
pub path_prefixes: Vec<String>,
}
impl PluginHttpsGrant {
pub fn label(&self) -> String {
let methods = if self.methods.is_empty() {
"<no-methods>".to_string()
} else {
self.methods.join(",")
};
let paths = if self.path_prefixes.is_empty() {
"*".to_string()
} else {
self.path_prefixes.join(",")
};
format!("{} {} {}", self.host, methods, paths)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct PluginFsGrant {
/// Absolute host path that bounds every relative `host_api.fs` request.
pub root: String,
/// Explicit operation kinds allowed below `root`; write does not imply read/list.
pub operations: Vec<PluginFsOperation>,
}
impl PluginFsGrant {
pub fn label(&self) -> String {
let operations = if self.operations.is_empty() {
"<no-operations>".to_string()
} else {
self.operations
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(",")
};
format!("{} {}", self.root, operations)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginFsOperation {
Read,
List,
Write,
}
impl fmt::Display for PluginFsOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read => f.write_str("read"),
Self::List => f.write_str("list"),
Self::Write => f.write_str("write"),
}
}
}
impl PluginPermission {
pub fn label(&self) -> String {
match self {
@ -305,12 +378,28 @@ impl PluginPackageManifest {
}
}
pub const PLUGIN_RUNTIME_WASM_KIND: &str = "wasm";
pub const PLUGIN_RUNTIME_WASM_ABI: &str = "yoi-plugin-wasm-1";
/// Manifest runtime kind for WebAssembly Component Model Tool packages.
///
/// Component runtime manifests must set `component` to the packaged component
/// artifact path and `world` to [`PLUGIN_COMPONENT_TOOL_WORLD`]. Raw core-Wasm
/// packages remain explicit `kind = "wasm"` plus `abi = "yoi-plugin-wasm-1"`.
pub const PLUGIN_RUNTIME_COMPONENT_KIND: &str = "wasm-component";
pub const PLUGIN_COMPONENT_TOOL_WORLD: &str = "yoi:plugin/tool@1.0.0";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PluginRuntimeManifest {
pub kind: String,
pub entry: String,
#[serde(default)]
pub entry: Option<String>,
#[serde(default)]
pub abi: Option<String>,
#[serde(default)]
pub component: Option<String>,
#[serde(default)]
pub world: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -436,7 +525,7 @@ pub struct ResolvedPluginRecord {
}
impl ResolvedPluginRecord {
fn from_resolved(resolved: &ResolvedPlugin) -> Self {
pub fn from_resolved(resolved: &ResolvedPlugin) -> Self {
Self {
identity: resolved.identity.clone(),
source: resolved.source,
@ -764,7 +853,7 @@ pub fn read_resolved_plugin_runtime_module(
.with_digest(&record.digest)
})?;
if runtime.kind != "wasm" {
if runtime.kind != PLUGIN_RUNTIME_WASM_KIND {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
@ -775,7 +864,7 @@ pub fn read_resolved_plugin_runtime_module(
.with_package(&record.package_label)
.with_digest(&record.digest));
}
if runtime.abi.as_deref() != Some("yoi-plugin-wasm-1") {
if runtime.abi.as_deref() != Some(PLUGIN_RUNTIME_WASM_ABI) {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
@ -787,6 +876,18 @@ pub fn read_resolved_plugin_runtime_module(
.with_digest(&record.digest));
}
let entry = runtime.entry.as_deref().ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"plugin WASM runtime entry is required",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
let metadata = fs::metadata(&record.package_path).map_err(|error| {
PluginDiagnostic::new(
PluginDiagnosticKind::Io,
@ -853,13 +954,13 @@ pub fn read_resolved_plugin_runtime_module(
}
validate_manifest_path(
&runtime.entry,
entry,
&archive,
&record.package_label,
record.source,
&record.manifest.id,
)?;
let normalized = normalize_archive_path(&runtime.entry).ok_or_else(|| {
let normalized = normalize_archive_path(entry).ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Traversal,
PluginDiagnosticPhase::Manifest,
@ -883,6 +984,154 @@ pub fn read_resolved_plugin_runtime_module(
})
}
/// Reads the WebAssembly Component Model artifact selected by a resolved plugin
/// package manifest while preserving package digest pinning.
pub fn read_resolved_plugin_runtime_component(
record: &ResolvedPluginRecord,
limits: &PluginDiscoveryLimits,
) -> Result<Vec<u8>, PluginDiagnostic> {
let runtime = record.manifest.runtime.as_ref().ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"resolved plugin package does not declare a component runtime",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
if runtime.kind != PLUGIN_RUNTIME_COMPONENT_KIND {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin runtime kind is unsupported",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest));
}
if runtime.world.as_deref() != Some(PLUGIN_COMPONENT_TOOL_WORLD) {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin component world is unsupported",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest));
}
let component = runtime.component.as_deref().ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"plugin component runtime artifact is required",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
let metadata = fs::metadata(&record.package_path).map_err(|error| {
PluginDiagnostic::new(
PluginDiagnosticKind::Io,
PluginDiagnosticPhase::Discovery,
format!(
"resolved plugin package metadata could not be read: {}",
safe_io_error(&error)
),
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
if !metadata.is_file() {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Malformed,
PluginDiagnosticPhase::Discovery,
"resolved plugin package is not a regular file",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest));
}
if metadata.len() > limits.max_package_size_bytes {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Bounds,
PluginDiagnosticPhase::Discovery,
"resolved plugin package exceeds the configured package size bound",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest));
}
let bytes = fs::read(&record.package_path).map_err(|error| {
PluginDiagnostic::new(
PluginDiagnosticKind::Io,
PluginDiagnosticPhase::Discovery,
format!(
"resolved plugin package content could not be read: {}",
safe_io_error(&error)
),
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
let archive = parse_stored_zip(&bytes, &record.package_label, record.source, limits)?;
let actual_digest = deterministic_digest(&archive.files);
if !digest_matches(&record.digest, &actual_digest) {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Digest,
PluginDiagnosticPhase::Resolution,
"resolved plugin package digest does not match current package content",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(actual_digest));
}
validate_manifest_path(
component,
&archive,
&record.package_label,
record.source,
&record.manifest.id,
)?;
let normalized = normalize_archive_path(component).ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Traversal,
PluginDiagnosticPhase::Manifest,
"plugin manifest references a path outside the package root",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})?;
archive.files.get(&normalized).cloned().ok_or_else(|| {
PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"plugin runtime component artifact is missing from the package",
)
.with_source(record.source)
.with_identity(&record.identity)
.with_package(&record.package_label)
.with_digest(&record.digest)
})
}
#[derive(Clone, Debug)]
struct PluginStore {
source: PluginSourceKind,
@ -1164,27 +1413,84 @@ fn validate_manifest(
.with_package(label));
}
if let Some(runtime) = &manifest.runtime {
if runtime.kind != "wasm" {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin runtime kind is unsupported",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
match runtime.kind.as_str() {
PLUGIN_RUNTIME_WASM_KIND => {
if runtime.abi.as_deref() != Some(PLUGIN_RUNTIME_WASM_ABI) {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin WASM ABI is unsupported",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
let Some(entry) = runtime.entry.as_deref() else {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"plugin WASM runtime entry is required",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
};
if runtime.component.is_some() || runtime.world.is_some() {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Malformed,
PluginDiagnosticPhase::Manifest,
"plugin WASM runtime must not declare component metadata",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
validate_manifest_path(entry, archive, label, source, &manifest.id)?;
}
PLUGIN_RUNTIME_COMPONENT_KIND => {
if runtime.abi.is_some() || runtime.entry.is_some() {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Malformed,
PluginDiagnosticPhase::Manifest,
"plugin component runtime must not declare raw WASM ABI metadata",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
if runtime.world.as_deref() != Some(PLUGIN_COMPONENT_TOOL_WORLD) {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin component world is unsupported",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
let Some(component) = runtime.component.as_deref() else {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Missing,
PluginDiagnosticPhase::Manifest,
"plugin component runtime artifact is required",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
};
validate_manifest_path(component, archive, label, source, &manifest.id)?;
}
_ => {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin runtime kind is unsupported",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
}
if runtime.abi.as_deref() != Some("yoi-plugin-wasm-1") {
return Err(PluginDiagnostic::new(
PluginDiagnosticKind::Api,
PluginDiagnosticPhase::Manifest,
"plugin WASM ABI is unsupported",
)
.with_source(source)
.with_identity(SourceQualifiedPluginId::new(source, manifest.id.clone()))
.with_package(label));
}
validate_manifest_path(&runtime.entry, archive, label, source, &manifest.id)?;
}
for hook in &manifest.hooks {
if !is_safe_id(&hook.id) {
@ -2052,6 +2358,8 @@ input_schema = { type = "object", properties = { query = { type = "string" } },
version: Some(PluginExactVersion("0.1.0".to_string())),
digest: Some(digest.clone()),
permissions: vec![PluginPermission::surface(PluginSurface::Hook)],
https: Vec::new(),
fs: Vec::new(),
};
let resolution = resolve_enabled_plugins(
&PluginConfig {
@ -2077,18 +2385,24 @@ input_schema = { type = "object", properties = { query = { type = "string" } },
version: Some(PluginExactVersion("0.1.0".to_string())),
digest: Some(digest.clone()),
permissions: vec![PluginPermission::surface(PluginSurface::Hook)],
https: Vec::new(),
fs: Vec::new(),
},
PluginGrantConfig {
id: Some("project:example".to_string()),
version: Some(PluginExactVersion("0.1.1".to_string())),
digest: Some(digest.clone()),
permissions: vec![PluginPermission::surface(PluginSurface::Hook)],
https: Vec::new(),
fs: Vec::new(),
},
PluginGrantConfig {
id: Some("project:example".to_string()),
version: Some(PluginExactVersion("0.1.0".to_string())),
digest: Some("sha256:unrelated".to_string()),
permissions: vec![PluginPermission::surface(PluginSurface::Hook)],
https: Vec::new(),
fs: Vec::new(),
},
] {
let resolution = resolve_enabled_plugins(

View File

@ -18,6 +18,7 @@ client = { workspace = true }
pod-registry = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
reqwest = { version = "0.13", default-features = false, features = ["blocking", "native-tls"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["fs", "io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
toml = { workspace = true }
@ -36,6 +37,7 @@ uuid = { workspace = true, features = ["v7"] }
session-metrics = { workspace = true }
arc-swap = "1.9.1"
wasmi = { version = "0.51.1", default-features = false, features = ["std", "extra-checks"] }
wasmtime = { version = "45.0.2", default-features = false, features = ["std", "runtime", "cranelift", "component-model"] }
[dev-dependencies]
dotenv = "0.15.0"

File diff suppressed because it is too large Load Diff

View File

@ -2503,10 +2503,10 @@ async fn load_multi_pod_snapshot(
#[cfg(feature = "e2e-test")]
let source_started = Instant::now();
let companion_presence = load_exact_companion_pod_presence(&companion_pod_name).await?;
let companion_presence = companion_pod_presence(&companion_pod_name, &list);
#[cfg(feature = "e2e-test")]
source_timings.push(PanelE2eSourceTiming {
source: "companion.presence",
source: "companion.presence.from_initial_list",
elapsed_ms: source_started.elapsed().as_millis(),
});
@ -2557,12 +2557,12 @@ async fn load_multi_pod_snapshot(
let orchestrator_presence = match &config {
TicketConfigAvailability::Absent | TicketConfigAvailability::Unusable(_) => None,
TicketConfigAvailability::Usable => {
Some(load_exact_pod_presence(&orchestrator_pod_name).await?)
Some(orchestrator_pod_presence(&orchestrator_pod_name, &list))
}
};
#[cfg(feature = "e2e-test")]
source_timings.push(PanelE2eSourceTiming {
source: "orchestrator.presence",
source: "orchestrator.presence.from_initial_list",
elapsed_ms: source_started.elapsed().as_millis(),
});
@ -3375,18 +3375,6 @@ fn existing_ticket_claim_notice(
}
}
async fn load_exact_companion_pod_presence(
pod_name: &str,
) -> Result<CompanionPodPresence, MultiPodError> {
let list = load_pod_list(Some(pod_name.to_string()), usize::MAX).await?;
Ok(companion_pod_presence(pod_name, &list))
}
async fn load_exact_pod_presence(pod_name: &str) -> Result<OrchestratorPodPresence, MultiPodError> {
let list = load_pod_list(Some(pod_name.to_string()), usize::MAX).await?;
Ok(orchestrator_pod_presence(pod_name, &list))
}
async fn load_pod_list(
selected_name: Option<String>,
max_entries: usize,

View File

@ -7,9 +7,7 @@ use client::PodClient;
use pod_registry::{LockFileGuard, default_registry_path};
use pod_store::{PodActiveSegmentRef, PodMetadata, PodMetadataStore};
use protocol::{Event, PodStatus};
use session_store::{
FsStore, LogEntry, LoggedContentPart, LoggedItem, SegmentId, SessionId, Store,
};
use session_store::{FsStore, SegmentId, SessionId};
#[derive(Debug, Clone)]
pub(crate) struct PodList {
@ -27,14 +25,6 @@ impl PodList {
) -> Self {
let mut entries_by_name: BTreeMap<String, PodListEntry> = BTreeMap::new();
for live_info in live {
let name = live_info.pod_name.clone();
entries_by_name
.entry(name.clone())
.or_insert_with(|| PodListEntry::new(name, source))
.merge_live(live_info);
}
for stored_info in stored {
let name = stored_info.pod_name.clone();
entries_by_name
@ -43,6 +33,14 @@ impl PodList {
.merge_stored(stored_info);
}
for live_info in live {
let name = live_info.pod_name.clone();
entries_by_name
.entry(name.clone())
.or_insert_with(|| PodListEntry::new(name, source))
.merge_live(live_info);
}
let mut entries: Vec<PodListEntry> = entries_by_name.into_values().collect();
for entry in &mut entries {
entry.finalize();
@ -358,7 +356,7 @@ pub(crate) async fn read_reachable_live_pod_infos(
}
async fn probe_reachable_live_pod_infos(
store: &FsStore,
_store: &FsStore,
records: Vec<LivePodInfo>,
) -> Result<Vec<LivePodInfo>, io::Error> {
let mut handles = Vec::with_capacity(records.len());
@ -371,10 +369,9 @@ async fn probe_reachable_live_pod_infos(
let result = handle
.await
.map_err(|e| io::Error::other(format!("live status probe task failed: {e}")))?;
let Ok(mut record) = result else {
let Ok(record) = result else {
continue;
};
record.summary = summarize_live_pod(store, &record);
reachable.push(record);
}
Ok(reachable)
@ -462,109 +459,22 @@ struct SegmentSummary {
preview: Option<String>,
}
fn summarize_live_pod(store: &FsStore, live: &LivePodInfo) -> PodEntrySummary {
let Some(segment_id) = live.segment_id else {
return PodEntrySummary::default();
};
let session_id = store.lookup_session_of(segment_id).ok().flatten();
let Some(session_id) = session_id else {
return PodEntrySummary {
active_session_id: None,
active_segment_id: Some(segment_id),
updated_at: 0,
preview: None,
};
};
let summary = summarize_segment(store, session_id, segment_id);
PodEntrySummary {
active_session_id: Some(session_id),
active_segment_id: Some(segment_id),
updated_at: summary.updated_at,
preview: summary.preview,
}
}
fn summarize_metadata(store: &FsStore, active: Option<&PodActiveSegmentRef>) -> SegmentSummary {
fn summarize_metadata(_store: &FsStore, active: Option<&PodActiveSegmentRef>) -> SegmentSummary {
let Some(active) = active else {
return SegmentSummary {
updated_at: 0,
preview: None,
};
};
let Some(segment_id) = active.segment_id else {
return SegmentSummary {
match active.segment_id {
Some(segment_id) => SegmentSummary {
updated_at: 0,
preview: Some(format!("active segment {segment_id}")),
},
None => SegmentSummary {
updated_at: 0,
preview: Some("[pending segment]".to_string()),
};
};
summarize_segment(store, active.session_id, segment_id)
}
fn summarize_segment(
store: &FsStore,
session_id: SessionId,
segment_id: SegmentId,
) -> SegmentSummary {
match store.read_all(session_id, segment_id) {
Ok(entries) => SegmentSummary {
updated_at: last_entry_ts(&entries).unwrap_or(0),
preview: last_message_preview(&entries).or_else(|| Some("[empty]".to_string())),
},
Err(_) => SegmentSummary {
updated_at: 0,
preview: Some("[corrupt segment]".to_string()),
},
}
}
fn last_entry_ts(entries: &[LogEntry]) -> Option<u64> {
entries.iter().map(log_entry_ts).max()
}
fn log_entry_ts(entry: &LogEntry) -> u64 {
match entry {
LogEntry::SegmentStart { ts, .. }
| LogEntry::Invoke { ts, .. }
| LogEntry::UserInput { ts, .. }
| LogEntry::AssistantItem { ts, .. }
| LogEntry::ToolResult { ts, .. }
| LogEntry::SystemItem { ts, .. }
| LogEntry::TurnEnd { ts, .. }
| LogEntry::RunCompleted { ts, .. }
| LogEntry::RunErrored { ts, .. }
| LogEntry::ConfigChanged { ts, .. }
| LogEntry::LlmUsage { ts, .. }
| LogEntry::Extension { ts, .. } => *ts,
}
}
fn last_message_preview(entries: &[LogEntry]) -> Option<String> {
for entry in entries.iter().rev() {
match entry {
LogEntry::UserInput { segments, .. } => {
let text = protocol::Segment::flatten_to_text(segments);
if !text.is_empty() {
return Some(format!("user: {}", trim_one_line(&text, 60)));
}
}
LogEntry::AssistantItem { item, .. } => {
if let Some(text) = first_text_logged(item) {
return Some(format!("assistant: {}", trim_one_line(&text, 60)));
}
}
_ => {}
}
}
None
}
fn first_text_logged(item: &LoggedItem) -> Option<String> {
match item {
LoggedItem::Message { content, .. } => content.iter().find_map(|p| match p {
LoggedContentPart::Text { text } => Some(text.clone()),
_ => None,
}),
_ => None,
}
}
@ -652,7 +562,7 @@ mod tests {
use pod_store::FsPodStore;
use pod_store::{PodActiveSegmentRef, PodMetadataStore};
use protocol::stream::JsonLineWriter;
use session_store::{new_segment_id, new_session_id};
use session_store::{LogEntry, Store, new_segment_id, new_session_id};
use tempfile::tempdir;
use tokio::net::UnixListener;
use tokio::sync::Barrier;
@ -660,44 +570,35 @@ mod tests {
const SOURCE: PodVisibilitySource = PodVisibilitySource::ResumePicker;
#[test]
fn pod_list_rows_are_sorted_by_active_segment_timestamp() {
fn stored_metadata_summary_uses_segment_marker_without_reading_session_log() {
let dir = tempdir().unwrap();
let store = FsStore::new(dir.path()).unwrap();
let earlier_session = new_session_id();
let later_session = new_session_id();
let earlier_segment = new_segment_id();
let later_segment = new_segment_id();
let session = new_session_id();
let segment = new_segment_id();
append_start(&store, earlier_session, earlier_segment, 10);
append_start(&store, session, segment, 10);
append_user(
&store,
earlier_session,
earlier_segment,
session,
segment,
100,
"old pod update",
"session log text should not be scanned",
);
append_start(&store, later_session, later_segment, 20);
append_user(&store, later_session, later_segment, 200, "new pod update");
let entries = PodList::from_sources(
let entry = single_entry(PodList::from_sources(
SOURCE,
vec![
metadata_info(&store, "older", earlier_session, earlier_segment),
metadata_info(&store, "newer", later_session, later_segment),
],
vec![metadata_info(&store, "stored", session, segment)],
vec![],
None,
10,
)
.entries;
));
assert_eq!(entries[0].name, "newer");
assert_eq!(entries[0].summary.updated_at, 200);
assert_eq!(entry.name, "stored");
assert_eq!(entry.summary.updated_at, 0);
assert_eq!(
entries[0].summary.preview.as_deref(),
Some("user: new pod update")
entry.summary.preview.as_deref(),
Some(format!("active segment {segment}").as_str())
);
assert_eq!(entries[1].name, "older");
}
#[test]

View File

@ -1,5 +1,6 @@
mod memory_lint;
mod objective_cli;
mod plugin_cli;
mod session_cli;
mod ticket_cli;
@ -17,6 +18,7 @@ enum Mode {
Help,
MemoryLintHelp,
MemoryLint(LintCliOptions),
Plugin(plugin_cli::PluginCliCommand),
Objective(objective_cli::ObjectiveCli),
Session(session_cli::SessionCli),
Ticket(ticket_cli::TicketCli),
@ -68,6 +70,13 @@ async fn main() -> ExitCode {
ExitCode::FAILURE
}
},
Mode::Plugin(command) => match plugin_cli::run(command) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("yoi plugin: {e}");
ExitCode::FAILURE
}
},
Mode::Objective(cli) => match objective_cli::run(cli) {
Ok(output) => {
print!("{}", output.stdout);
@ -173,6 +182,10 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
ticket_cli::parse_ticket_args(&args[1..]).map_err(|e| ParseError(e.to_string()))?;
return Ok(Mode::Ticket(ticket_cli));
}
"plugin" => {
let plugin_cli = parse_plugin_args(&args[1..])?;
return Ok(Mode::Plugin(plugin_cli));
}
"panel" => {
return Ok(Mode::Tui {
mode: LaunchMode::Panel,
@ -413,6 +426,97 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
})
}
fn parse_plugin_args(args: &[String]) -> Result<plugin_cli::PluginCliCommand, ParseError> {
let Some((subcommand, rest)) = args.split_first() else {
return Err(ParseError(
"yoi plugin requires `list` or `show <ref>`".to_string(),
));
};
match subcommand.as_str() {
"list" => {
let (plugin_args, positional) = parse_plugin_common_args(rest)?;
if !positional.is_empty() {
return Err(ParseError(
"yoi plugin list does not accept positional arguments".to_string(),
));
}
Ok(plugin_cli::PluginCliCommand::List(plugin_args))
}
"show" => {
let (plugin_args, positional) = parse_plugin_common_args(rest)?;
match positional.as_slice() {
[reference] => Ok(plugin_cli::PluginCliCommand::Show {
reference: reference.clone(),
args: plugin_args,
}),
[] => Err(ParseError(
"yoi plugin show requires a plugin ref".to_string(),
)),
_ => Err(ParseError(
"yoi plugin show accepts exactly one plugin ref".to_string(),
)),
}
}
"--help" | "-h" => Err(ParseError(plugin_usage().to_string())),
other => Err(ParseError(format!(
"unknown yoi plugin subcommand `{other}`"
))),
}
}
fn parse_plugin_common_args(
args: &[String],
) -> Result<(plugin_cli::PluginCliArgs, Vec<String>), ParseError> {
let mut parsed = plugin_cli::PluginCliArgs::default();
let mut positional = Vec::new();
let mut index = 0;
while index < args.len() {
let arg = &args[index];
match arg.as_str() {
"--json" => parsed.json = true,
"--workspace" => {
index += 1;
let Some(value) = args.get(index) else {
return Err(ParseError("--workspace requires a value".to_string()));
};
parsed.workspace = Some(PathBuf::from(value));
}
"--profile" => {
index += 1;
let Some(value) = args.get(index) else {
return Err(ParseError("--profile requires a value".to_string()));
};
parsed.profile = Some(value.clone());
}
"--help" | "-h" => return Err(ParseError(plugin_usage().to_string())),
_ if arg.starts_with("--workspace=") => {
let value = arg.trim_start_matches("--workspace=");
if value.is_empty() {
return Err(ParseError("--workspace requires a value".to_string()));
}
parsed.workspace = Some(PathBuf::from(value));
}
_ if arg.starts_with("--profile=") => {
let value = arg.trim_start_matches("--profile=");
if value.is_empty() {
return Err(ParseError("--profile requires a value".to_string()));
}
parsed.profile = Some(value.to_string());
}
_ if arg.starts_with('-') => {
return Err(ParseError(format!("unknown yoi plugin option `{arg}`")));
}
_ => positional.push(arg.clone()),
}
index += 1;
}
Ok((parsed, positional))
}
fn plugin_usage() -> &'static str {
"usage: yoi plugin list [--workspace PATH] [--profile REF] [--json]\n yoi plugin show <ref> [--workspace PATH] [--profile REF] [--json]"
}
fn parse_panel_workspace(args: &[String]) -> Result<PathBuf, ParseError> {
match args {
[] => std::env::current_dir()
@ -443,7 +547,7 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
fn print_help() {
println!(
"yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi ticket <COMMAND> [OPTIONS]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --workspace <PATH> Runtime workspace root (defaults to cwd)\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
"yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi pod [POD_OPTIONS]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi ticket <COMMAND> [OPTIONS]\n yoi plugin list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi plugin show <REF> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --workspace <PATH> Runtime workspace root (defaults to cwd)\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
);
}
@ -607,6 +711,33 @@ mod tests {
}
}
#[test]
fn parse_plugin_list_and_show() {
match parse_args_from(["plugin", "list", "--workspace=/tmp/ws", "--json"]).unwrap() {
Mode::Plugin(plugin_cli::PluginCliCommand::List(options)) => {
assert_eq!(options.workspace, Some(PathBuf::from("/tmp/ws")));
assert!(options.json);
}
_ => panic!("expected Plugin list mode"),
}
match parse_args_from([
"plugin",
"show",
"project:echo",
"--profile",
"project:inspect",
])
.unwrap()
{
Mode::Plugin(plugin_cli::PluginCliCommand::Show { reference, args }) => {
assert_eq!(reference, "project:echo");
assert_eq!(args.profile.as_deref(), Some("project:inspect"));
}
_ => panic!("expected Plugin show mode"),
}
}
#[test]
fn parse_memory_lint_rejects_usage_errors() {
let err = parse_args_from(["memory", "lint", "--workspace"]).unwrap_err();

1576
crates/yoi/src/plugin_cli.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
# Plugin Component Model migration
Yoi's current Plugin Tool runtime uses a narrow core-WebAssembly ABI. That was the right MVP shape because it made sandboxing, bounded input/output, and fail-closed host imports explicit. It should not become the long-term authoring interface.
The preferred direction is to adopt the WebAssembly Component Model for Plugin Tool authoring and host APIs. Component Model adoption means Plugin interfaces are described as typed WIT worlds and lowered through the canonical ABI, instead of every Plugin author or SDK wrapper hand-writing pointer/length memory plumbing.
## What Component Model changes
A core Wasm module exposes low-level functions and memory. Yoi's current Plugin Tool ABI is shaped like this:
```text
export memory
export yoi_tool_call() -> i32
import yoi:tool/tool_name_len() -> i32
import yoi:tool/tool_name_read(ptr, len) -> i32
import yoi:tool/input_len() -> i32
import yoi:tool/input_read(ptr, len) -> i32
import yoi:tool/output_write(ptr, len) -> i32
```
This is small and auditable, but it makes raw ABI details part of the authoring model. A Component Model world can instead describe a typed contract:
```wit
package yoi:plugin;
interface tool {
record request {
tool-name: string,
input-json: string,
}
record response {
output-json: string,
}
variant tool-error {
invalid-input(string),
denied(string),
failed(string),
}
run: func(req: request) -> result<response, tool-error>;
}
world tool-plugin {
export tool;
}
```
The exact WIT is still design work, but the important boundary is fixed: the Plugin author sees typed values and generated bindings; the host sees typed imports/exports; Yoi still enforces package enablement and Plugin grants outside the component.
## External patterns considered
Common Wasm extension systems normally ship more than a runtime:
- Extism-style systems provide host runtimes plus language PDKs. Plugin authors write normal typed functions while the PDK hides the raw ABI and host functions remain explicit.
- Spin-style systems combine a manifest, language SDK/templates, default-deny outbound/file capabilities, and Wasm components.
- wasmCloud-style systems separate components from capability providers and connect them through typed interfaces.
- The Component Model standardizes the interface layer with WIT and canonical ABI so host APIs can be versioned and bindings generated across languages.
The shared lesson is that a usable Wasm Plugin system needs a manifest, explicit capabilities, generated or hand-written SDK bindings, examples/templates, inspection tooling, and a versioned ABI. Yoi already has the manifest/discovery/enablement/grant/runtime foundation; the missing long-term piece is the typed component authoring interface.
## Yoi policy
Adopting the Component Model must not change Yoi's authority model:
- Package discovery is inventory only and does not register or execute a Plugin.
- Explicit enablement is required before any Tool surface is registered.
- Plugin grants are required before runtime execution and before `https` / `fs` / future host API calls.
- Component imports are not authority by themselves; host-side grant checks remain authoritative.
- Tool calls and Tool results continue through the ordinary ToolRegistry and Worker history path.
- No hidden context injection is introduced by component imports, resources, prompts, or SDK helpers.
- Plugin SDKs and templates are authoring aids, not trust boundaries.
## Migration shape
Yoi should support Component Model as an explicit runtime kind rather than silently changing existing raw-ABI packages.
Possible manifest direction:
```toml
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/tool@1.0.0"
```
The current raw core-Wasm runtime can remain explicit during migration:
```toml
[runtime]
kind = "wasm"
entry = "plugin.wasm"
abi = "yoi-plugin-wasm-1"
```
The migration should be phased:
1. Define WIT packages/worlds for Tool Plugin and initial host APIs.
2. Add manifest/schema support for `runtime.kind = "wasm-component"` without executing it during discovery.
3. Add a component runtime backend and typed host import/export binding.
4. Port `https` and `fs` host API designs to WIT-compatible interfaces.
5. Add a Rust authoring SDK/template around the component world.
6. Decide whether the raw ABI remains supported, becomes legacy-only, or is deprecated after examples and tests move.
## Runtime/backend caution
The current implementation uses `wasmi` for core Wasm. Component Model support will likely require a different backend or a significantly richer component adapter path, such as `wasmtime::component` plus generated bindings. That has consequences for binary size, Nix packaging, build time, runtime limits, and sandbox policy. The migration Ticket must measure and validate those effects explicitly.
If a component backend is added, keep it selected by package runtime metadata and Profile/feature policy. Do not make all Plugin packages depend on component execution during discovery or inspection.
## Relationship to pending host APIs
`https` and `fs` host API Tickets should avoid baking in raw pointer/length interfaces as the long-term authoring contract. If they land before the component runtime, implement them in a way that can be represented as WIT records/results later, and document raw ABI wrappers as transitional.
For example, `https` should be modeled as typed request/response data with explicit grant checks for host/method/path/body bounds. `fs` should be modeled as scoped read/list/write operations with path normalization and root-escape rejection. Those concepts translate well to WIT.
## Non-goals
- Component Model adoption does not imply WASI filesystem/network access.
- It does not replace Plugin grants with WIT imports.
- It does not introduce Service, Ingress, WebSocket, or inbound HTTP by itself.
- It does not merge Plugin and MCP. MCP remains a separate untrusted tool/resource/prompt bridge with its own policy.
## Implemented runtime boundary
Plugin Tool packages now select the runtime explicitly in `plugin.toml`:
```toml
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/tool@1.0.0"
```
The legacy core-Wasm ABI remains explicit and is not reinterpreted as a
component:
```toml
[runtime]
kind = "wasm"
entry = "plugin.wasm"
abi = "yoi-plugin-wasm-1"
```
The component runtime uses `wasmtime::component` and expects the exported world
`yoi:plugin/tool@1.0.0` with a `call(tool-name: string, input-json: string) ->
string` export. The returned string is the same ToolOutput JSON used by the raw
runtime, so registration and execution still flow through the existing
ToolRegistry and Worker Tool-result history path.
Host imports are stable names under `yoi:host/*@1.0.0`; the repository WIT files
live in `resources/plugin/wit/`. Importing `yoi:host/https@1.0.0` or
`yoi:host/fs@1.0.0` is not authority. The runtime checks package grants before
component instantiation and checks again on every host call. No WASI filesystem,
network, environment, or other ambient imports are linked.
Static discovery and `yoi plugin list/show` only parse package manifests and
reported runtime metadata. They do not instantiate or execute the component.
Wrong `world`, missing artifact metadata, missing `call` export, unsupported
imports, or core-Wasm bytes in a component package all fail closed with bounded
Plugin diagnostics or ordinary Tool errors.
See `docs/examples/plugin-component-tool/lib.rs` for a minimal
`wit-bindgen`/SDK-style authoring sketch. Package authors should generate
bindings from `resources/plugin/wit`, build a component artifact, and set the
component runtime metadata above.
### v1 request/response shape
The v1 component world intentionally keeps Tool input, Tool output, and host API
payloads as JSON strings. This is a migration bridge that preserves the existing
ToolOutput schema, Tool history behavior, grant checks, and raw-Wasm host API
semantics while moving package authors onto WIT/canonical ABI bindings.
Structured WIT records for Tool requests/responses/errors and host HTTPS/FS
payloads are deferred to a follow-up API-design step rather than accidentally
omitted.

View File

@ -52,6 +52,15 @@ entry = "plugin.wasm"
abi = "yoi-plugin-wasm-1"
```
The preferred future WASM authoring/runtime shape is the WebAssembly Component Model, recorded in [Plugin Component Model migration](plugin-component-model.md). Component packages should be explicit and source-compatible rather than silently changing the existing raw core-Wasm runtime:
```toml
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/tool@1.0.0"
```
First-pass fields accepted by the parser:
- `schema_version`: required integer; unsupported versions fail closed.
@ -195,3 +204,29 @@ Good follow-up Tickets are intentionally separable:
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"
```
This is separate from the legacy raw core-Wasm runtime:
```toml
[runtime]
kind = "wasm"
entry = "plugin.wasm"
abi = "yoi-plugin-wasm-1"
```
Component packages must not use `entry`/`abi`; raw packages must not use
`component`/`world`. Discovery reports the selected runtime kind/world without
executing the artifact. Component execution still requires explicit package
enablement, exact source/version/digest grants, and matching Tool/host API
permissions.

View File

@ -0,0 +1,23 @@
//! Minimal Component Model Tool plugin authoring sketch.
//!
//! Build this as a `wasm32-unknown-unknown` cdylib with `wit-bindgen`-generated
//! exports and package the adapted component as `plugin.component.wasm`.
wit_bindgen::generate!({
world: "tool",
path: "../../../resources/plugin/wit",
});
struct Plugin;
impl Guest for Plugin {
fn call(tool_name: String, input_json: String) -> String {
// Ordinary ToolOutput JSON. The runtime routes this through the normal
// Worker/Tool result path; no context is injected by the component.
format!(
r#"{{"summary":"component tool {tool_name}","content":"input was {input_json}"}}"#
)
}
}
export!(Plugin);

View File

@ -40,7 +40,7 @@ rustPlatform.buildRustPackage rec {
filter = sourceFilter;
};
cargoHash = "sha256-ud+3INcXnT5W26Bz0K4QXUqoqw3p/ER9c4F2Fhq3YuQ=";
cargoHash = "sha256-i4U7wXPoWIHA4EAJZva2HQXNN8P5+RhGVGNBAOZVGk0=";
depsExtraArgs = {
# Older fetchCargoVendor utilities used crates.io's API download endpoint,

View File

@ -0,0 +1,15 @@
package yoi:host@1.0.0;
/// Grant-bound HTTPS host API. Importing this interface does not grant
/// authority; package grants are checked before registration/execution and on
/// every host call.
interface https {
request: func(request-json: string) -> string;
}
/// Grant-bound filesystem host API. No ambient WASI filesystem is exposed.
interface fs {
read: func(request-json: string) -> string;
list: func(request-json: string) -> string;
write: func(request-json: string) -> string;
}

View File

@ -0,0 +1,11 @@
package yoi:plugin@1.0.0;
world tool {
import yoi:host/https@1.0.0;
import yoi:host/fs@1.0.0;
/// Execute a manifest-declared Tool. `input-json` is the normal Tool input
/// JSON and the returned string is the same ToolOutput JSON accepted by the
/// legacy raw-Wasm ABI.
export call: func(tool-name: string, input-json: string) -> string;
}

View File

@ -606,6 +606,7 @@ impl PanelHarness {
"xdg_config_home": config.xdg_config_home,
"xdg_runtime_dir": config.xdg_runtime_dir,
"fixture_root": config.fixture_root,
"launch_mode": "direct_exec",
"runtime_policy": {
"host_runtime_inherited": false,
"host_xdg_runtime_dir_present": std::env::var_os("XDG_RUNTIME_DIR").is_some(),
@ -686,6 +687,122 @@ impl PanelHarness {
})
}
pub fn spawn_via_shell_enter(config: PanelHarnessConfig) -> Result<(Self, Instant)> {
if !config.binary.exists() {
return Err(HarnessError::MissingBinary(config.binary));
}
fs::create_dir_all(&config.artifacts_dir)?;
let artifacts = PanelArtifacts {
dir: config.artifacts_dir.clone(),
events_jsonl: config.artifacts_dir.join("events.jsonl"),
input_log: config.artifacts_dir.join("input.log"),
output_log: config.artifacts_dir.join("pty-output.log"),
run_json: config.artifacts_dir.join("run.json"),
};
fs::write(&artifacts.events_jsonl, "")?;
fs::write(&artifacts.input_log, "")?;
fs::write(&artifacts.output_log, "")?;
let env_policy =
tui_env_policy(config.hold_background_task.is_some(), config.rewind_fixture);
fs::write(
&artifacts.run_json,
serde_json::to_vec_pretty(&serde_json::json!({
"binary": config.binary,
"args": &config.command_args,
"workspace": config.workspace,
"home": config.home,
"xdg_data_home": config.xdg_data_home,
"xdg_state_home": config.xdg_state_home,
"xdg_config_home": config.xdg_config_home,
"xdg_runtime_dir": config.xdg_runtime_dir,
"fixture_root": config.fixture_root,
"launch_mode": "shell_enter_exec",
"runtime_policy": {
"host_runtime_inherited": false,
"host_xdg_runtime_dir_present": std::env::var_os("XDG_RUNTIME_DIR").is_some(),
"tested_yoi_runtime_source": "fixture XDG_RUNTIME_DIR"
},
"terminal_size": {
"columns": config.terminal_size.0,
"rows": config.terminal_size.1,
},
"hold_background_task": config.hold_background_task,
"rewind_fixture": config.rewind_fixture,
"tested_yoi_env_policy": &env_policy,
}))?,
)?;
let (master, slave) = open_pty(config.terminal_size)?;
let slave_for_stdin = slave.try_clone()?;
let slave_for_stdout = slave.try_clone()?;
let mut command = Command::new("/bin/sh");
command
.current_dir(&config.workspace)
.env_clear()
.env("YOI_TUI_TEST_EVENTS", &artifacts.events_jsonl)
.env("YOI_POD_RUNTIME_COMMAND", &config.binary)
.env("HOME", &config.home)
.env("XDG_DATA_HOME", &config.xdg_data_home)
.env("XDG_STATE_HOME", &config.xdg_state_home)
.env("XDG_CONFIG_HOME", &config.xdg_config_home)
.env("XDG_RUNTIME_DIR", &config.xdg_runtime_dir)
.env("TERM", "xterm-256color")
.stdin(Stdio::from(slave_for_stdin))
.stdout(Stdio::from(slave_for_stdout))
.stderr(Stdio::from(slave));
if let Some(task) = &config.hold_background_task {
command.env("YOI_TUI_TEST_HOLD_BACKGROUND_TASK", task);
}
if config.rewind_fixture {
command.env("YOI_TUI_TEST_REWIND_FIXTURE", "1");
}
let child = command.spawn()?;
let output = Arc::new(Mutex::new(Vec::new()));
let output_for_thread = Arc::clone(&output);
let mut reader_file = master.try_clone()?;
let output_log = artifacts.output_log.clone();
let reader = thread::spawn(move || {
let mut sink = OpenOptions::new()
.append(true)
.create(true)
.open(output_log)
.ok();
let mut buf = [0_u8; 4096];
loop {
match reader_file.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
if let Some(sink) = sink.as_mut() {
let _ = sink.write_all(&buf[..n]);
}
if let Ok(mut output) = output_for_thread.lock() {
output.extend_from_slice(&buf[..n]);
}
}
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
}
}
});
let mut harness = Self {
child,
master,
reader: Some(reader),
output,
last_event_offset: 0,
artifacts,
};
let command_line = shell_exec_command(&config.binary, &config.command_args);
let started = Instant::now();
harness.write_input(
"shell Enter yoi panel",
format!("{command_line}\n").as_bytes(),
)?;
Ok((harness, started))
}
pub fn wait_for<F>(
&mut self,
what: impl Into<String>,
@ -1665,6 +1782,30 @@ fn command_display(program: &Path, args: &[String]) -> String {
.join(" ")
}
fn shell_exec_command(program: &Path, args: &[String]) -> String {
std::iter::once("exec".to_string())
.chain(std::iter::once(shell_quote(&program.to_string_lossy())))
.chain(args.iter().map(|arg| shell_quote(arg)))
.collect::<Vec<_>>()
.join(" ")
}
fn shell_quote(value: &str) -> String {
if value.is_empty() {
return "''".to_string();
}
let mut quoted = String::from("'");
for ch in value.chars() {
if ch == '\'' {
quoted.push_str("'\\''");
} else {
quoted.push(ch);
}
}
quoted.push('\'');
quoted
}
fn open_pty(size: (u16, u16)) -> Result<(File, File)> {
let mut master = 0;
let mut slave = 0;

View File

@ -372,6 +372,58 @@ fn panel_dashboard_content_ready_has_startup_budget() -> yoi_e2e::Result<()> {
Ok(())
}
#[test]
fn panel_dashboard_content_ready_from_shell_enter_path() -> yoi_e2e::Result<()> {
let binary = yoi_binary()?;
let fixture = FixtureWorkspace::new(&binary)?;
assert_fixture_paths_are_isolated(&fixture);
let expected_content = fixture.expected_dashboard_content();
let (mut panel, started) = PanelHarness::spawn_via_shell_enter(fixture.panel_config(binary))?;
let first_visible_remaining = FIRST_VISIBLE_RENDER_BUDGET
.checked_sub(started.elapsed())
.unwrap_or_else(|| Duration::from_millis(0));
panel.wait_for_first_visible_frame(first_visible_remaining)?;
let first_visible_elapsed = started.elapsed();
let content_ready_remaining = DASHBOARD_CONTENT_READY_BUDGET
.checked_sub(started.elapsed())
.unwrap_or_else(|| Duration::from_millis(0));
let content_ready =
panel.wait_for_dashboard_content_ready(&expected_content, content_ready_remaining)?;
assert_eq!(
content_ready.snapshot_for_expected(&expected_content),
expected_content.snapshot(),
"shell-enter dashboard content ready must match expected Ticket/action/overlay/header snapshot; artifacts at {}",
panel.artifacts().dir.display()
);
let content_ready_elapsed = started.elapsed();
eprintln!(
"panel shell-enter dashboard content ready: {content_ready_elapsed:?} (budget {DASHBOARD_CONTENT_READY_BUDGET:?}; first frame {first_visible_elapsed:?}); artifacts at {}",
panel.artifacts().dir.display()
);
assert!(
content_ready_elapsed <= DASHBOARD_CONTENT_READY_BUDGET,
"shell-enter dashboard content ready took {content_ready_elapsed:?}, budget {DASHBOARD_CONTENT_READY_BUDGET:?}; artifacts at {}",
panel.artifacts().dir.display()
);
let source_breakdown = panel.expect_dashboard_source_breakdown()?;
assert!(
source_breakdown.has_source("workspace_panel.build.total"),
"shell-enter dashboard source breakdown should include total panel build source; got {:?}; artifacts at {}",
source_breakdown,
panel.artifacts().dir.display()
);
panel.press(KeyPress::CtrlC)?;
let status = panel.expect_exit_within(PanelHarness::default_exit_wait())?;
assert!(status.success(), "panel should exit cleanly with Ctrl+C");
drop(panel);
assert_fixture_cleanup(fixture.cleanup()?);
Ok(())
}
#[test]
fn panel_mouse_click_selects_row_without_dispatching_action() -> yoi_e2e::Result<()> {
let binary = yoi_binary()?;