feat: add authorized Plugin Ticket data capabilities
This commit is contained in:
@@ -257,6 +257,8 @@ loggingはlevel、event code、安全なmessageとscalar fieldsに限定する
|
||||
|
||||
## 9. 付随データの契約
|
||||
|
||||
Ticket拡張の機械的契約は[追加world](wit-ticket/yoi-plugin-v2.1.wit)と[resource imports](wit-ticket/deps/yoi-host-v2.1/yoi-host-v2.1.wit)、`server-api::plugin_data`に置く。旧`@2.0.0` worldへのimport追加や保存pinへのgrant補完はしない。package保存formatのschema versionと、Component world/APIのexact versionは別の境界である。
|
||||
|
||||
Server拡張はdomain objectに関連付けるPlugin所有のtyped dataとし、既存tableへの任意column追加では実現しない。物理保存schemaはHostが管理する。`feature-storage`のnative migration/SQLite handleは信頼済みHost用でありguestへ公開しない。
|
||||
|
||||
addressはHost contextのWorkspace/installationと、認可済みobject resource、宣言collection、schema id/version、keyから決まる。schema id/versionも物理・論理namespaceに含め、新旧artifactが同じcellを別schemaで読み書きしない。guestにWorkspace selectorやraw table名を与えない。最初のobject kindはTicketとし、Hostが存在・Workspace・callerのread/write権限を検証したTicket resourceだけを使う。一般objectへの拡張はadapterと権限を追加して行う。
|
||||
@@ -265,7 +267,7 @@ addressはHost contextのWorkspace/installationと、認可済みobject resource
|
||||
|
||||
`ticket-read.read(borrow<ticket>)`はread権限を再検査し、title、case state、content digest、bounded bodyとtruncated flagだけを返す専用read APIとする。thread、添付、認証・管理情報、将来増えるfieldを汎用serializeで公開しない。上限は64 KiB、完全本文が必要なら本APIではunsupportedとする。eventの削除通知自体は安全なmetadataとして配送できるが、削除済みTicket resourceをresolve/readできる意味ではない。
|
||||
|
||||
- packageはcollectionごとにschema id/versionとclosed typed schemaを宣言する。data valueのJSONはそのschemaで検証する。
|
||||
- packageはcollectionごとにschema id/versionとclosed typed schemaを宣言する。data valueのJSONはそのschemaで検証する。同installationの同collection/schema id/versionはrevoked artifactを含め同一schemaへ束縛し、artifact登録transactionで内容差を拒否する。exact artifactだけでは共有cellの意味を固定できないためである。value上限64 KiBはJSONのUTF-8表現(escapeを含む)のbyte数とし、listは最大100件、Remote envelopeの再escapeを含む有限response budgetを適用する。
|
||||
- APIは`get`、`put(expected_last_write | never_written, operation_key, value)`、`delete(expected_last_write, operation_key)`、bounded `list(cursor, limit)`。任意query、SQL、cross-object transactionは公開しない。
|
||||
- 同じcellへのput/deleteを確定した最終operation identityを、書込みtransaction内で観測値と比較する。CAS成功とoperation receiptを同じtransactionで保存し、自動読直しによるlast-write-winsへ変えない。同じ内容の再書込みや削除・再作成を時刻/内容hashだけで区別できないため、このoperationを比較する。
|
||||
- deleteは最終delete operationのtombstoneを保持する。再作成はそのoperationを期待値にし、`never_written`を通さない。これによりABAを防ぐ。`get`は対象objectの認可成功後に限り、present(value, last-write)、never-written、deleted(last-delete)を区別する。不可視objectと不存在は同じnot-foundにする。
|
||||
@@ -276,7 +278,7 @@ addressはHost contextのWorkspace/installationと、認可済みobject resource
|
||||
- 親object削除とPlugin data書込みはowner側の削除fenceで競合を解決する。削除後に新しい子dataを作らず、retention後にHostが回収する。
|
||||
- schemaの破壊的変更は新しいschema version/namespaceとする。既存dataをguest起動時にmigrationしない。移行は新旧schemaを明示した認可付き操作として後続で設計し、初期APIでは自動変換しない。
|
||||
- rollbackで旧artifactを使う場合も元schemaだけを読む。最新schemaへ読み替えない。
|
||||
- revoke/disableではdataを消さない。Workspace管理者の明示purgeをoperation keyと期待data-generationで行い、先に実行をrevoke/fenceする。purge receiptを残し、uninstallとの一操作への暗黙統合をしない。
|
||||
- revoke/disableではdataを消さない。Workspace管理者の明示purgeは先に全artifactをrevoke/fenceし、その後のtransactionで観測したinstallation最終catalog operationと、全cell(tombstone含む)のnamespace/key/最終write operation/deleted状態の順序付き集合digest・件数を期待値として比較する。新規writeだけでなく同一値の再書込みやdelete/recreateも区別し、汎用更新番号は導入しない。operation keyによるpurge receiptを残し、uninstallとの一操作への暗黙統合をしない。
|
||||
|
||||
## 10. commit済みeventとService
|
||||
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package yoi:plugin@2.1.0;
|
||||
|
||||
/// Additive exact world: never augment an old @2.0.0 pin's imports or grants.
|
||||
world ticket-tool-plugin {
|
||||
include yoi:plugin/tool-plugin@2.0.0;
|
||||
import yoi:host/errors@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;
|
||||
}
|
||||
Reference in New Issue
Block a user