feat: provide v2 Plugin Component contracts and offline validation

Validate exact Component interfaces and static Tool/DCDL schemas without guest execution. Share canonical archive packing and independent package/artifact verification; build real pure and HTTPS PDK fixtures and retire v1/Service authoring stubs.
This commit is contained in:
2026-10-28 13:31:47 +09:00
parent 06883c51b7
commit 2d195f4058
40 changed files with 3056 additions and 1662 deletions
@@ -0,0 +1,21 @@
[package]
name = "yoi-rust-component-https-template"
version = "0.1.0"
edition = "2024"
license = "MIT"
publish = false
# Keep the embedded template checkable in-place without making it a member of
# Yoi's root workspace. A copied starter remains a normal standalone package.
[workspace]
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
yoi-plugin-pdk = { path = "../../../../crates/plugin-pdk" }
# Out-of-tree Plugin packages should replace the local path with a pinned
# Yoi source commit. Use rev, not branch, for reproducible builds:
# yoi-plugin-pdk = { git = "https://gitea.hareworks.net/Hare/yoi.git", package = "yoi-plugin-pdk", rev = "<pinned-yoi-commit-sha>" }
@@ -0,0 +1,5 @@
# Connection-bound HTTPS Tool雛形
接続先とsecretをguestが選べないよう、宣言slotからcall-local resourceを取得する。副作用の観測を失敗時にも保持し、自動再送やrollback成功を装わない。HTTP error statusはresponseとして扱う。
このソースをComponentへbuildできることは、HTTPS Hostの実装・認可・receipt保存を提供した証拠にはならない。packageの要求grantはWorkspace bindingや実行権限を自己発行しない。
@@ -0,0 +1,28 @@
schema_version = 2
id = "example.rust_component_https"
name = "Rust HTTPS Component Tool Template"
version = "0.1.0"
surfaces = ["tool"]
config_schema = "{}"
requested_grants = [{ kind = "https", slot = "api" }]
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/https-tool-plugin@2.0.0"
required_imports = ["yoi:host/errors@2.0.0", "yoi:host/connections@2.0.0", "yoi:host/http@2.0.0"]
[[tools]]
name = "example_status"
description = "Observe the status response through an explicitly bound HTTPS slot."
input_schema = { type = "object", properties = {}, additionalProperties = false }
output_schema = { type = "object", properties = { status = { type = "integer" }, body_bytes = { type = "integer" } }, required = ["status", "body_bytes"], additionalProperties = false }
# Even GET may have side effects. Host receipt/admission policy still applies.
error_codes = ["invalid_input", "invalid_output", "serialization", "unsupported_tool", "failed"]
external_write = true
[[connections]]
name = "api"
# Package requests HTTPS bounds; Workspace binding supplies origin and secrets.
methods = ["GET"]
path_prefixes = ["/status"]
@@ -0,0 +1,61 @@
use serde::{Deserialize, Serialize};
use yoi_plugin_pdk::wit_bindgen;
use yoi_plugin_pdk::{ToolContext, ToolError, ToolOutput};
wit_bindgen::generate!({
world: "https-tool-plugin",
path: "wit",
generate_all,
runtime_path: "yoi_plugin_pdk::wit_bindgen::rt",
});
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Input {}
#[derive(Serialize)]
struct Output {
status: u16,
body_bytes: usize,
}
fn handle_status(ctx: ToolContext, _input: Input) -> Result<ToolOutput, ToolError> {
use yoi::host::{connections, http};
if ctx.tool_name() != "example_status" {
return Err(ToolError::unsupported_tool(ctx.tool_name()));
}
// This resource is call-local; the slot has no URL or credential value.
let target = connections::open("api").map_err(host_failure)?;
let response = http::send(
&target,
&http::Request {
method: http::Method::Get,
path: "/status".into(),
query: Vec::new(),
headers: Vec::new(),
body: Vec::new(),
timeout_ms: ctx.remaining_ms().min(15_000),
// Stable within this logical invocation. Never automatically retry a failure.
effect_key: "status-v1".into(),
},
)
.map_err(host_failure)?;
// HTTP 4xx/5xx remain responses, not transport failures. Do not log raw bodies.
ToolOutput::json(
"HTTP response observed",
Output {
status: response.status,
body_bytes: response.body.len(),
},
)
}
fn host_failure(error: yoi::host::errors::HostError) -> ToolError {
// Preserve the effect observation for the caller without claiming rollback or retry safety.
ToolError::failed(format!(
"Host {:?}; effect {:?}; diagnostic {}",
error.code, error.effect, error.diagnostic_id
))
}
yoi_plugin_pdk::export_component_tool!(Plugin, handle_status);
@@ -0,0 +1,61 @@
package yoi:host@2.0.0;
interface errors {
enum code {
invalid-input, denied, not-found, conflict, unsupported,
quota-exceeded, deadline-exceeded, cancelled, unavailable,
indeterminate, receipt-expired,
}
enum effect-observation { not-dispatched, response-observed, indeterminate }
record host-error {
code: code,
message: string,
diagnostic-id: string,
effect: effect-observation,
}
}
interface connections {
use errors.{host-error};
/// Call-local resource. No serialization, persistence or cross-Worker transfer.
resource connection;
open: func(slot: string) -> result<connection, host-error>;
}
interface http {
use connections.{connection};
use errors.{host-error};
enum method { get, head, post, put, patch, delete, options }
record pair { name: string, value: string }
record request {
method: method,
path: string,
query: list<pair>,
headers: list<pair>,
body: list<u8>,
timeout-ms: u32,
effect-key: string,
}
record response {
status: u16,
headers: list<pair>,
body: list<u8>,
}
/// Host authorizes each operation and does not automatically retry or follow redirects.
send: func(target: borrow<connection>, input: request) -> result<response, host-error>;
}
interface logging {
use errors.{host-error};
enum level { trace, debug, info, warn, error }
variant scalar { text(string), boolean(bool), signed(s64), unsigned(u64), number(f64) }
record field { name: string, value: scalar }
record entry {
level: level,
event-code: string,
message: string,
fields: list<field>,
}
/// Bounded diagnostic data, not model context. Never include secrets or raw payloads.
log: func(input: entry) -> result<_, host-error>;
}
@@ -0,0 +1,49 @@
package yoi:plugin@2.0.0;
/// Only plugin-specific payloads use schema-validated JSON. Context is not authority.
interface tool {
record invocation {
invocation-id: string,
tool-name: string,
input-json: string,
config-json: string,
remaining-ms: u32,
}
record output {
summary: string,
content: option<string>,
structured-json: option<string>,
}
record tool-error {
code: string,
message: string,
}
invoke: func(input: invocation) -> result<output, tool-error>;
}
/// Pure computation: no ambient WASI or unused Host capabilities.
world tool-plugin {
export tool;
}
world https-tool-plugin {
import yoi:host/errors@2.0.0;
import yoi:host/connections@2.0.0;
import yoi:host/http@2.0.0;
export tool;
}
/// Logging is an explicit capability, never an ambient console.
world logged-tool-plugin {
import yoi:host/errors@2.0.0;
import yoi:host/logging@2.0.0;
export tool;
}
world https-logged-tool-plugin {
import yoi:host/errors@2.0.0;
import yoi:host/connections@2.0.0;
import yoi:host/http@2.0.0;
import yoi:host/logging@2.0.0;
export tool;
}