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
+1 -1
View File
@@ -126,7 +126,7 @@ world tool-plugin {
Tool worldは呼出しごとにfresh Store/instanceとし、compiled Componentは共有できる。Toolのglobal/static memoryを永続化や他呼出しとの通信に使えない。v2初期Toolは応答一回のbounded callとし、guestがmodelへ直接streamを注入するAPIは設けない。
Service/event worldには`on-start(config) -> result`、`on-event(envelope) -> event-result`、`on-stop(reason) -> result`を設ける。いずれもfresh Storeで呼び、HostがWorker/installation/planに束縛したService resourceを保持する。ToolとServiceで共有する状態はHost経由の明示dataとし、guest memoryの残存を契約にしない。`status()`を呼んで診断のためにguestを実行せず、Hostが実行・接続・失敗の観測を返す。
Service/eventの機械的契約は[独立した`@2.2.0` world](wit-service/yoi-plugin-v2.2.wit)とする。既存`@2.0.0` HTTPS/loggingと`@2.1.0` Ticket/dataをexact importし、旧world/pinへの追加はしない。Service/event worldには`on-start(config) -> result`、`on-event(envelope) -> event-result`、`on-stop(reason) -> result`を設ける。いずれもfresh Storeで呼び、HostがWorker/installation/planに束縛したService resourceを保持する。ToolとServiceで共有する状態はHost経由の明示dataとし、guest memoryの残存を契約にしない。`status()`を呼んで診断のためにguestを実行せず、Hostが実行・接続・失敗の観測を返す。
`on-start`は設定検証とローカル初期化だけを行い、logging以外のHost作用を認めない。GETも副作用を持ち得るため、汎用HTTPや付随data書込みを初期化中に許可しない。外部event sourceの接続は全required Pluginの検証・導入成功後にHostが開始する。initialization失敗時に外部効果のrollbackを装わない。`on-stop`もloggingだけのbest effort通知で、resource解放の前提にしない。
@@ -0,0 +1,46 @@
package yoi:host@2.1.0;
/// Handles are invocation-local selectors, not durable authorization.
interface objects {
use yoi:host/errors@2.0.0.{host-error};
enum access { read, extension-write }
resource ticket;
resolve-ticket: func(ticket-ref: string, requested: access) -> result<ticket, host-error>;
}
interface ticket-read {
use yoi:host/errors@2.0.0.{host-error};
use objects.{ticket};
enum case-state { open, closed }
record snapshot {
title: string,
state: case-state,
content-digest: string,
body: string,
truncated: bool,
}
read: func(target: borrow<ticket>) -> result<snapshot, host-error>;
}
interface extension-data {
use yoi:host/errors@2.0.0.{host-error};
use objects.{ticket};
record collection {
collection: string,
schema-id: string,
schema-version: string,
}
record address { namespace: collection, key: string }
variant expectation { never-written, last-write(string) }
record present-cell { value-json: string, last-write: string }
variant cell { never-written, present(present-cell), deleted(string) }
record receipt { mutation-id: string, result-digest: string }
variant mutation-lookup { not-recorded, committed(receipt), expired }
record entry { key: string, value: cell }
record page { entries: list<entry>, next-cursor: option<string> }
get: func(target: borrow<ticket>, cell-address: address) -> result<cell, host-error>;
put: func(target: borrow<ticket>, cell-address: address, expected: expectation, operation-key: string, value-json: string) -> result<receipt, host-error>;
delete: func(target: borrow<ticket>, cell-address: address, expected-last-write: string, operation-key: string) -> result<receipt, host-error>;
%list: func(target: borrow<ticket>, namespace: collection, cursor: option<string>, limit: u32) -> result<page, host-error>;
lookup-mutation: func(target: borrow<ticket>, cell-address: address, operation-key: string) -> result<mutation-lookup, host-error>;
}
@@ -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,41 @@
package yoi:plugin@2.2.0;
interface service {
enum ticket-event-type { ticket-changed, ticket-deleted }
record envelope {
event-id: string,
event-type: ticket-event-type,
event-version: u32,
occurred-at: string,
ticket-ref: string,
domain-event-id: string,
content-digest: option<string>,
correlation-id: option<string>,
causation-id: option<string>,
invocation-id: string,
service-name: string,
config-json: string,
attempt: u32,
remaining-ms: u32,
}
variant event-result {
ack,
retry-after(u32),
reject(string),
}
record service-error { code: string, message: string }
on-start: func(config: string) -> result<_, service-error>;
on-event: func(input: envelope) -> event-result;
on-stop: func(reason: string) -> result<_, service-error>;
}
world ticket-service-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;
import yoi:host/objects@2.1.0;
import yoi:host/ticket-read@2.1.0;
import yoi:host/extension-data@2.1.0;
export service;
}