feat: deliver committed Ticket events to plugin services

This commit is contained in:
2026-10-31 04:58:19 +09:00
parent 4f7dcfda61
commit b508fef698
94 changed files with 11852 additions and 247 deletions
+367
View File
@@ -0,0 +1,367 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "equivalent"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn 2.0.119",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "service-test-guest"
version = "0.1.0"
dependencies = [
"yoi-plugin-pdk",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"bitflags",
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn 2.0.119",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn 2.0.119",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "yoi-plugin-pdk"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"thiserror",
"wit-bindgen",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -0,0 +1,13 @@
[package]
name = "service-test-guest"
version = "0.1.0"
edition = "2024"
publish = false
[workspace]
[lib]
crate-type = ["cdylib"]
[dependencies]
yoi-plugin-pdk = { path = "../../../plugin-pdk" }
@@ -0,0 +1,47 @@
schema_version = 2
id = "service-test"
name = "Service test"
version = "1.0.0"
surfaces = ["service"]
config_schema = "{ mode = String }"
[runtime]
kind = "wasm-component"
component = "plugin.component.wasm"
world = "yoi:plugin/ticket-service-plugin@2.2.0"
required_imports = ["yoi:host/errors@2.0.0", "yoi:host/connections@2.0.0", "yoi:host/http@2.0.0", "yoi:host/logging@2.0.0", "yoi:host/objects@2.1.0", "yoi:host/ticket-read@2.1.0", "yoi:host/extension-data@2.1.0"]
[[services]]
name = "ticket_events"
subscriptions = ["ticket_changed", "ticket_deleted"]
[[connections]]
name = "api"
methods = ["POST"]
path_prefixes = ["/events"]
[[data_collections]]
collection = "notes"
schema_id = "note"
schema_version = "1"
schema = { type = "string", maxLength = 64 }
[[requested_grants]]
kind = "logging"
[[requested_grants]]
kind = "https"
slot = "api"
[[requested_grants]]
kind = "ticket_events"
[[requested_grants]]
kind = "ticket_read"
[[requested_grants]]
kind = "ticket_data_read"
collection = "notes"
schema_id = "note"
schema_version = "1"
[[requested_grants]]
kind = "ticket_data_write"
collection = "notes"
schema_id = "note"
schema_version = "1"
@@ -0,0 +1,135 @@
use std::sync::atomic::{AtomicU32, Ordering};
use yoi_plugin_pdk::{ToolError, wit_bindgen};
wit_bindgen::generate!({
path: "../../../../resources/plugin/wit-service",
world: "ticket-service-plugin",
generate_all,
runtime_path: "yoi_plugin_pdk::wit_bindgen::rt",
});
use exports::yoi::plugin::service::{Envelope, EventResult, TicketEventType};
use yoi::host2_0_0::{connections, errors, http, logging};
use yoi::host2_1_0::{extension_data as data, objects, ticket_read};
static CALLS: AtomicU32 = AtomicU32::new(0);
fn fresh() {
assert_eq!(CALLS.fetch_add(1, Ordering::SeqCst), 0);
}
fn failure(e: errors::HostError) -> ToolError {
ToolError::failed(format!("{:?}", e.code))
}
fn address() -> data::Address {
data::Address {
namespace: data::Collection {
collection: "notes".into(),
schema_id: "note".into(),
schema_version: "1".into(),
},
key: "main".into(),
}
}
fn request() -> http::Request {
http::Request {
method: http::Method::Post,
path: "/events".into(),
query: vec![],
headers: vec![],
body: b"event".to_vec(),
timeout_ms: 15_000,
effect_key: "event-hook".into(),
}
}
fn lifecycle(mode: String) -> yoi_plugin_pdk::Result<()> {
fresh();
match mode.as_str() {
"open" => {
connections::open("api").map_err(failure)?;
}
"resolve" => {
objects::resolve_ticket("T-796", objects::Access::Read).map_err(failure)?;
}
"read" => {
let t = unsafe { objects::Ticket::from_handle(0) };
ticket_read::read(&t).map_err(failure)?;
}
"data" => {
let t = unsafe { objects::Ticket::from_handle(0) };
data::get(&t, &address()).map_err(failure)?;
}
"http" => {
let c = unsafe { connections::Connection::from_handle(0) };
http::send(&c, &request()).map_err(failure)?;
}
"loop" => loop {
std::hint::spin_loop();
},
_ => {
logging::log(&logging::Entry {
level: logging::Level::Info,
event_code: "lifecycle".into(),
message: "configured".into(),
fields: vec![],
})
.map_err(failure)?;
}
}
Ok(())
}
fn start(config: String) -> yoi_plugin_pdk::Result<()> {
let config: std::collections::BTreeMap<String, String> =
yoi_plugin_pdk::parse_json_input(&config)?;
lifecycle(config.get("mode").cloned().unwrap_or_default())
}
fn stop(reason: String) -> yoi_plugin_pdk::Result<()> {
lifecycle(reason)
}
fn work(input: &Envelope) -> yoi_plugin_pdk::Result<()> {
if matches!(input.event_type, TicketEventType::TicketDeleted) {
return Ok(());
}
let ticket = objects::resolve_ticket(&input.ticket_ref, objects::Access::ExtensionWrite)
.map_err(failure)?;
let snapshot = ticket_read::read(&ticket).map_err(failure)?;
if input.config_json == r#"{"mode":"abi-probe"}"# {
data::list(&ticket, &address().namespace, None, 1).map_err(failure)?;
data::delete(&ticket, &address(), "observed", "delete").map_err(failure)?;
}
// A saved mutation is checked before reading the current cell or rebuilding
// its CAS request. Failed lookup is never interpreted as not-recorded.
match data::lookup_mutation(&ticket, &address(), "event-note").map_err(failure)? {
data::MutationLookup::Committed(_) => (),
data::MutationLookup::Expired => return Err(ToolError::failed("expired")),
data::MutationLookup::NotRecorded => {
let expected = match data::get(&ticket, &address()).map_err(failure)? {
data::Cell::NeverWritten => data::Expectation::NeverWritten,
data::Cell::Deleted(last) => data::Expectation::LastWrite(last),
data::Cell::Present(cell) => data::Expectation::LastWrite(cell.last_write),
};
let value = yoi_plugin_pdk::extension_data_json(&snapshot.title)?;
data::put(&ticket, &address(), &expected, "event-note", &value).map_err(failure)?;
}
}
let connection = connections::open("api").map_err(failure)?;
// Repeating this exact request uses the Host receipt, not a new effect key.
http::send(&connection, &request()).map_err(failure)?;
Ok(())
}
fn event(input: Envelope) -> EventResult {
fresh();
if input.config_json == r#"{"mode":"loop"}"# {
loop {
std::hint::spin_loop();
}
}
if input.config_json == r#"{"mode":"reject"}"# {
return EventResult::Reject("rejected".into());
}
if input.config_json == r#"{"mode":"oversize-retry"}"# {
return EventResult::RetryAfter(300_001);
}
if work(&input).is_err() && input.config_json != r#"{"mode":"swallow-unknown"}"# {
EventResult::RetryAfter(10)
} else {
EventResult::Ack
}
}
yoi_plugin_pdk::export_component_service!(ServiceGuest, start, event, stop);