feat: add Astro reference site and organize book contents
Reference site / build (push) Canceled after 0s

This commit is contained in:
2026-10-09 23:40:37 +09:00
parent 6086f3c5ef
commit 01c9498e6d
39 changed files with 7772 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
# 1. コンセプト
## 背景
現在のLLMエージェントは、Filesystem、検索、Issue Tracker、データベース、外部API、他のAgentなどを、それぞれ別個のToolとして扱うことが多い。統合先が増えるほど、モデルに提示するTool SchemaとAction Spaceも増えていく。
Unix filesystemは、異なる資源を単一の名前空間へ配置し、少数の操作で探索できる強い抽象化を持つ。一方、file / directoryとread / write中心の操作だけでは、`Issue.close()`、`PullRequest.merge()`、`Agent.ask()`のような意味的操作を自然に表現しにくい。
## 中心アイデア
WIPは、任意のデジタル環境を一つの**Worldspace**として公開する。
WorldspaceはObjectのTreeとして構成される。本書では、Tree上に配置され、Pathで到達できるObjectを**エントリ**と呼ぶ。Objectとエントリは別スキーマではない。
Objectは、短いdescription、利用可能なOperation、子Object、必要に応じて`ref`を公開できる。本文やmetadataなどのドメインデータは常時露出せず、Operationを通して取得・操作することを基本とする。
## Tool DiscoveryではなくWorld Discovery
一般的なTool Callingでは、モデルは提示されたTool一覧から操作を選ぶ。
WIPでは、モデルは既知のWorldspaceを辿り、目的に関連する対象を見つけ、公開されたOperationを確認して実行する。Operation Resultの`entry`型から未知のpathを発見し、必要なものを取得してClientのKnown Spaceへ追加できる。
中心となる問題を**Tool SelectionからWorld Explorationへ移す**ことがWIPの狙いである。
## Worldspaceの構造
Pathはidentityではなく、現在のWorldspace上の配置位置である。同一Objectが複数のエントリとして現れてもよい。
親子関係は所有・内包を表す。所有関係にない意味的な関連は、それを表す名前付きOperationから関連エントリのpathを返し、Treeへ無理に押し込まない。
Worldspace全体を事前にmaterializeする必要はない。Hostはlazy / virtualなTreeとして、必要な範囲だけを動的に公開できる。
## 操作中心のモデル
WIPではObjectを受動的なJSON recordとして扱わない。
本文を読む、metadataを取得する、検索する、状態を変更するといった行為はいずれもOperationとして表現する。例えばIssueであれば、`read_body()`、`get_metadata()`、`close(reason)`などを公開できる。
Operationには、名前や型に加えて、用途を判断するための短いdescriptionを持たせる。長文documentationや大量のドメインデータをInterfaceへ埋め込むことは想定しない。
ObjectはOperation定義を直接埋め込まず、実装コード上のInterface Declarationから生成された0個以上のInterface Descriptorを参照する。各Interfaceに宣言された関数の集合が再利用可能なcapability unitとなり、複数のObjectが同じInterfaceを共有できる。Objectは現在公開するInterface集合を持ち、Operation callではそのうち一つを明示してOperationを実行する。
## 発見と既知世界
Clientは、これまでに発見したエントリを既知のWorldspaceとして保持できる。既知のエントリはClient側で自由に辿り、Operation結果から得たエントリを追加して、さらに探索できる。
機械的な広域探索の対象は、Hostがindexableとして公開した範囲に限定できる。これにより、大量の検索結果や動的Objectが自動的に探索indexへ流入することを防ぐ。
## Operation Resultと新規発見
OperationはHost背後の環境を変更し得るが、WIPはその状態遷移や変更差分をClientへ同期するprotocolではない。
Operation Resultは型付きの値であり、Interface Descriptorの`entry`型として、未観測だったエントリのpathを返せる。例えばRAG検索からResult群を発見したり、Agentへの問い合わせからConversationを発見したりできる。
Clientは必要に応じて返されたpathを取得し、変更後の状態を改めて観測する。
## Narrow Waist
WIPの目的は、Filesystem、GitHub、RAG、DBなどを一つの巨大APIへ統合することではない。
任意のデジタル環境を共通のWorldspaceとして投影し、発見・観測・Operation実行という小さなInteraction Modelへ収束させる。Host内部のAPI、DB、RPC、永続化方式は規定せず、TransportもProtocolのlogical data modelから分離する。
## 状態管理の非目標
WIPは、Host上の任意状態をClientへ同期するdata store protocolではない。Object propertyのreplication、change feed、reactive subscription、分散transaction、domain event deliveryはCore Modelの規定対象外とする。
Clientが保持するKnown SpaceとDiscoverable Spaceは、過去の観測から構成されたcacheであり、Host上の現在状態を同期したreplicaではない。HostがClientのcache状態を追跡または更新することを要求しない。Operationのauthorizationと実行条件はHostが実行時に評価する。
## Post-trainingとの仮説
Tool Useが事後学習の対象になるなら、個別Tool名ではなくWorldspace上のinteraction policyを学習できる可能性がある。
1. 既知の世界を探索する。
2. 対象の意味とOperationを理解する。
3. 必要ならOperationから未知のエントリを発見する。
4. Operationを実行する。
5. 必要ならOperation Resultやentry pathをもとに対象を再観測する。
最も強い仮説は、**未知のToolを使えるモデルではなく、未知のDigital Environmentを探索できるモデルを作れるのではないか**という点にある。
+137
View File
@@ -0,0 +1,137 @@
# 2. コアモデル
WIP Core Modelは、ClientがHostの提供するWorldspaceを観測し、Objectを発見し、Interfaceに従って操作するための意味論を定義する。
具体的なrequest / response structは[3. Protocol](3-protocol.md)、Transport Bindingの共通要件は[5. Transport Bindings](5-transports.md)、HTTPとJSONへの対応付けは[5.1. WIP over HTTP](5.1-wip-over-http.md)で定義する。
## 基本構造
Worldspaceは、Clientが探索できる**ObjectのTree**として公開される。
WIPではObjectとエントリを別の構造として定義しない。**ObjectをWorldspaceのTree上の位置として扱うとき、そのObjectをエントリと呼ぶ。**
したがって、ObjectはTree上で親子関係を持ち得るし、途中のNodeもそのまま操作対象になってよい。例えば`articles`は対象の集合を扱うObjectとして、そのInterfaceに`query`等を宣言し、配下に個別Articleを配置できる。
## ClientとHost
WIP上の主要な実装主体は**Client**と**Host**である。
### Client
Clientは、Hostが提供するWorldspaceをユーザーまたはAIへ提示する。
Hostから取得したObjectとInterface Descriptorをcacheし、`ref`によるObject同一性の追跡、観測時と操作時の対応関係の検証、GUI、AI用Tools、PTC / REPL向けAPI等への投影を行う。
### Host
Hostは、Clientに対して一つのWorldspaceを提供する。
どのObject、エントリ、Interface、可視性を公開するかを決定し、そのClientから見えるWorldspaceを構成する。
Host内部でGitHub API、Database、Filesystem、RAG、他Agent、別protocol等をどう接続、集約、永続化するかはWIPの規定対象外とする。
ClientとHostの境界は通信境界であると同時に、ある主体へどのWorldspaceを公開するかを決めるtrust boundaryである。
## Object / エントリ
**Object**は、Worldspace上で観測および操作できる対象のprotocol-level projectionである。
Objectは、探索に必要な公開情報とInterface参照の集合を持つ。Host内部のdomain entityやApplication dataをそのままObjectとして公開する必要はない。Objectの本文、状態、domain metadata等は、現在公開されているいずれかのInterfaceに宣言されたOperationを通して取得または変更する。
**エントリ**は、そのObjectをTree上の位置として見たときの呼び名である。
エントリがObjectへの参照を別途持つ、という二重構造はProtocolの交換形式として前提としない。Clientはcache実装上、Objectをflat storeへnormalizeし、Entry Treeから内部keyで参照してよい。
Tree上の位置はpathで表される。Pathはrootから各Objectの`name`を順に`/`で連結して構成する。Pathは恒久的なIdentityではなく、その時点でのnavigation coordinateである。
同じ親の直下ではObjectの`name`は一意であり、非root Objectの`name`は空であってはならない。同じObjectが複数の位置に現れる場合、そのObjectは同じ`name`を持ち、異なる親を経由する複数のpathで観測され得る。Objectの公開情報、name、path、Tree上の配置については[2.1. Object tree / Schema](2.1-object-tree-schema.md)で扱う。
## Interface
ObjectはOperation定義を直接持たず、0個以上のInterface参照をordered setとして持つ。
各Interfaceは、Objectで利用できるOperation、parameter、return type、documentationを定義する再利用可能なcapability unitである。Interface実装者はRustのtraitやTypeScriptのdefinition API等を使って実装コード上でInterfaceを宣言し、Libraryはその宣言からmachine-readableなInterface Descriptorを生成する。
複数のObjectが同じInterfaceを共有でき、一つのObjectも複数のInterfaceを組み合わせて公開できる。ObjectとInterfaceを分離することで、Operation定義を各Objectへ重複して持たせず、ClientがInterface Descriptorを参照単位でcacheできる。
Interface集合のmembershipはset semanticsを持ち、同じ参照を重複して含めない。列挙順はOperation解決には影響しないが、HostがInterfaceを公開した順序としてUI表示に使用できる。
InterfaceとOperationの意味論は[2.2. Operation and Interface](2.2-operation-and-interface.md)、Descriptorのlogical structは[3. Protocol](3-protocol.md)、AI向け操作は[4.1. AI Tools](4.1-ai-tools.md)、人間とAIへのsignature表記は[4.2. Text View](4.2-text-view.md)で扱う。
## Core interaction
Clientは、次の3操作によってWorldspaceを観測および操作する。
- `observe(path, depth)` — 指定pathのObjectと、指定した深さまでのindexableな子Objectを一つの観測として取得する。`depth = 0`では対象Objectだけを取得する。
- `fetch_interface(interface)` — Interface参照から現在のInterface Descriptorとoptionalなvalidatorを取得する。
- `call_operation(target, interface, operation, arguments)` — `path`とoptionalなObject validatorを持つtarget、およびObjectが現在公開する一つのInterface observationを指定してOperationを実行する。
この節は各操作の意味だけを定める。Parameterとreturn valueの具体的なstructはProtocolで定義する。
複数の`observe`や`fetch_interface`は、それぞれ独立したCore interactionである。Clientは必要なinteractionを個別に並行実行でき、Transport実装は複数のexchangeを一つのconnection上でmultiplexできる。並行実行やmultiplexingによって新しいCore interaction、request間のdata dependency、共通snapshot、または暗黙の先読みを導入しない。
## `ref`
Objectは、同じ主体に公開された一つのWorldspace内でObjectの同一性を示すために、optionalな`ref`を公開できる。
`ref`はopaque stringであり、Objectのprimary key、永続的なUID、navigation coordinateではない。Backendに適切なUIDがある場合、Hostはそれを直接または変換して使用できるが、WIPはbackend data形式から得られるUIDの公開を要求しない。
- 同じWorldspace内で同じ有効な`ref`を持つObjectは、同じObjectを表す。
- Hostは同じ有効な`ref`を異なるObjectへ付与してはならない。
- `ref`が異なる、または片方に存在しない場合、同じObjectかどうかについて保証しない。
- Hostが複数のエントリを同じObjectとしてClientへ認識させる場合、同じ`ref`を使用する。
- `ref`の比較は、一つの主体に公開された同じWorldspace内でのみ意味を持つ。
- 異なるWorldspace間で同じ文字列の`ref`が同じObjectを表すとはみなさない。
- Session、再接続、Host restart、Worldspace再構築をまたぐ永続性を要求しない。
- `ref`からObjectを取得するinteractionは提供しない。Objectの取得にはpathを使用する。
`ref`は完全なidentity systemではなく、Hostが明示した場合だけ複数の観測またはエントリを同一Objectとしてcorrelateするためのmarkerである。
## 観測整合性
エントリ自体をObjectとは別実体として扱う必要はない。ただし、あるpath上のObjectを観測した後、Operationを実行するまでの間に、そのpathが別のObjectを指すことはあり得る。
その場合、Client / Runtimeは、以前のObjectを対象としたOperationを新しいObjectへ黙って適用してはならない。実行前に観測時の対応関係を検証し、変化していればOperationを中止する。この性質を**observation consistency**と呼ぶ。
例えば、AIが`/issues/current`を観測した時点ではIssue Aだったが、`invoke`時にはIssue Bへ差し替わっていた場合、AIはIssue Aへ作用するつもりでIssue Bを操作してしまう可能性がある。
この観測整合性は利用者に追跡させるのではなく、Clientの責務とする。
処理の流れは次の通り。
1. Clientがpath上のObjectを観測する。
2. Objectがvalidatorを持つ場合、ClientはObjectとrequest pathを対応付けて保持する。
3. ユーザーまたはAIが観測したObjectについて判断する。
4. 同じObjectへ条件付きで操作する場合、Clientは`path`とObjectの`validator`をtargetとしてHostへ渡す。
5. Hostは現在のvalidatorとの一致確認とOperation実行をatomicに行う。
ValidatorはClientとHostの間で扱い、AIやユーザーに直接管理させない。Transport Bindingはvalidatorを対応するProtocol fieldとして保持し、HTTP ETag等のrepresentation metadataで置き換えない。
Validatorが一致しない場合、Hostは`validator_mismatch`を返してOperationを実行しない。同じOperationを再試行するなら、先に新しい観測を取得しなければならない。
## 同一性と観測整合性の分離
WIPでは次の二つを分けて扱う。
- **Object identity (`ref`)** — 一つのClient-facing Worldspace内で、複数の観測またはエントリが同じObjectを表すことを明示するoptionalなmarker。
- **Observation consistency** — `ref`の有無にかかわらず、観測したpath上のObjectと操作対象のObjectが一致していることを保証する仕組み。
したがって、`ref`を持たないObjectであっても安全な操作対象になり得る。`ref`は同一性を公開する仕組みであり、validatorはHostが定める現在状態との一致を条件付きOperationで検証する仕組みである。
## Application data
WIPは、Host上の任意状態をClientへ同期するdata store protocolではない。Object propertyのreplication、change feed、reactive subscription、分散transaction、domain event deliveryはCore Modelの規定対象外とする。
Operationが交換するApplication dataについて、WIPはInterfaceに宣言された型、wire encoding、boundary validationを規定する。値のdomain上の意味、保存方法、business validation、Client内部での利用方法はApplicationの責務である。
Clientが保持するKnown SpaceとDiscoverable Spaceは、過去の観測から構成されたcacheであり、Host上の現在状態を同期したreplicaではない。HostがClientのcache状態を追跡または更新することを要求しない。
この責務境界はObject、Interface、Operationの各節で一貫して適用する。
## Transportとの分離
Core ModelとProtocol structは、HTTP、TCP、WebSocket等の特定transportを要求しない。
通信方式、concrete encoding、endpointへの対応付けはTransport Bindingとして定義する。共通要件は[5. Transport Bindings](5-transports.md)、標準bindingは[5.1. WIP over HTTP](5.1-wip-over-http.md)で扱う。
この分離により、Worldspaceの意味論、Client / Host間のlogical contract、通信方式を独立して進化させる。
+202
View File
@@ -0,0 +1,202 @@
# 2.1. Object tree / Schema
## Objectの公開情報
Objectは、Worldspace上の対象を発見し、理解し、操作するためのprotocol-level projectionである。
Objectは少なくとも次の公開情報を持つ。
- **name** — 親Objectの下でのpath segment。
- **description** — Objectが何を表すかを説明する情報。
- **interfaces** — Objectで利用できるOperationを定義するInterface参照のordered set。
- **ref** — 同じClient-facing Worldspace内で複数の観測またはエントリが同じObjectを表すことを示すoptionalなmarker。
- **validator** — HostがObjectの現在状態へ付与する任意のopaque value。
`interfaces`のmembershipはset semanticsを持ち、同じInterface参照を重複して含めてはならない。集合は空でもよい。列挙順はOperation解決には影響せず、HostがInterfaceを立項した順序としてUI表示に使用できる。
具体的な型、必須性、resource limitは[3. Protocol](3-protocol.md)で定義する。
Objectの本文、状態、domain metadata等のApplication dataを、共通のproperty bagとしてObjectへ格納しない。それらはInterfaceに宣言されたOperationを通して取得または変更する。
`description`は探索時に対象を理解するための短い公開情報であり、長い本文やdomain entityの完全な表現を格納する場所ではない。
### 同じ`ref`を持つObject
同じClient-facing Worldspace内で同じ`ref`を持つObjectは、一つの共有された現在状態を持つ。同じObjectを異なるpathへ配置したことを理由に、path固有の`name`、`description`、Interface集合を返してはならない。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、Interface集合を含むObject representationが一致しなければならない。
- 同じ`ref`と異なるvalidatorを持つObjectは、同一Objectの異なる状態として扱う。
- Hostは異なるpathから同じ`ref`を観測した場合も、一つの現在状態を一貫して返す。
- Clientは最後に成功した観測を、その`ref`を持つObjectの現在状態として扱う。
同じObjectを異なる親の下へ配置する場合もObjectの`name`は共通である。Pathは親から各Objectの`name`を辿って構成するため、同じObjectは異なる親を経由して異なるpathに現れ得る。
## Interface参照
Objectは0個以上のInterfaceを参照する。各Interface Descriptorは、Objectに対して実行可能なOperationの一つのcapability namespaceを宣言する。
Operation名は一つのInterface Descriptor内で一意であればよく、異なるInterface間で同じOperation名を使用できる。Operation callはscopeとlocal nameからなるInterface参照を明示するため、Protocol上の解決は曖昧にならない。人間・AI向けのqualified表記は[Text Viewの参照表記](4.2-text-view.md#参照表記)に従う。
Interfaceは次を定義する。
- Operation名
- Operationの説明
- Parameter名と型
- Return type
- Named typeとdocumentation
Interface実装者は実装コード上でInterfaceを宣言し、Libraryはその宣言からInterface Descriptorを生成する。ClientはObjectの各Interface参照を使ってDescriptorを取得し、検証、cache、表示、Operation callのvalidationに利用する。
同じInterfaceを持つObjectは同じDescriptor参照を共有できる。
## Interface scope
Objectをpath `P`で公開するとき、そのInterface参照のscopeは`P`自身またはその祖先でなければならない。Self-scopeを許可し、rootから`P`までの各pathを含む。Path segmentの完全一致で判定し、単純な文字列prefixで判定しない。本仕様でInterface scopeの「祖先制約」と呼ぶ場合もself-scopeを含む。
例えば`/github/my-rep/issues`は、scope `/`、`/github`、`/github/my-rep`、`/github/my-rep/issues`のInterfaceを公開できる。`/gitlab`、`/github/other-rep`、`/github/my-rep/issues/123`のscopeは使用できない。`/github`は`/github-extra`の祖先ではない。Root Objectはscope `/`のInterfaceを公開できる。
この制約は配置pathに基づき、indexableかどうかには依存しない。非indexableなObjectを直接観測した場合も同じ規則を適用する。Scope Objectを先に観測することも、そのObject自身が同じInterfaceを公開することも要求しない。
Scopeの適合と`Object.interfaces`への明示的なmembershipは別の条件である。祖先scopeのInterfaceを自動継承せず、scopeをauthorizationの証明として扱わない。
ScopeはそのpathにあるObjectに紐づき、Objectとは独立して存続するnamespaceではない。Scope Objectがそのpathから消えれば、そのscopeのInterfaceの公開も終了する。消えたObjectの配下を、親Objectの存在しない孤立したsubtreeとして公開し続けない。別のObjectを同じpathへ公開し直すことはできる。
Scope Objectに`ref`があれば、ClientはそのObjectとの紐付きを確認できる。`ref`はoptionalであり、存在しない場合は同じpathでの差し替えをidentityによって追跡できない。これは許可された公開形態であり、Hostに`ref`の生成を強制したり、Clientが代替identityを推測したりしない。参照の有効期間、呼出時の確認、移動とcacheの扱いは[Operation and Interface](2.2-operation-and-interface.md#scope-objectと有効期間)および[Protocol](3-protocol.md#scope-objectの紐付き)に従う。
## Object / エントリ / path
WIPでは、Objectとエントリを別のdomain entityとして定義しない。ObjectをTree上の位置として扱うとき、そのObjectをエントリと呼ぶ。
### `name`
Objectの`name`は表示用labelではなく、親Objectの下でそのObjectを識別するpath segmentである。
- Root Objectの`name`は空文字列とする。
- 非root Objectの`name`は空であってはならない。
- `name`に`/`を含めてはならない。
- `name`全体が`.`または`..`であってはならない。
- `name`はUnicode stringとする。
- Unicode normalizationは行わない。
- Nameの比較はUnicode code point sequenceの完全一致で行う。
- 同じ親の直接のchildrenは、互いに異なる`name`を持たなければならない。
`name`を人間向けの自由なtitleとして扱わない。探索時の短い説明には`description`を使い、domain上のtitle等はOperationからApplication dataとして取得する。
### Pathの構成
Rootのpathは`/`とする。非root Objectのpathは、rootからそのObjectまでの各`name`を`/`で連結して構成する。
```text
root.name = ""
root.path = "/"
projects.name = "projects"
projects.path = "/projects"
example.name = "example"
example.path = "/projects/example"
```
Pathには空segment、`.`、`..`を含めない。Root以外のpathは末尾に`/`を持たない。Nameに`/`を許可しないため、Core Modelではsegment escapeを定義しない。Transport BindingがPathをURL componentへ格納する場合のURL encodingは、WIP Pathの構成とは別に扱う。
非root pathについては、最後のsegmentと取得したObjectの`name`が一致しなければならない。
```text
observe("/projects/example", depth = 0)
-> ObjectObservation.object.name == "example"
```
同じObjectが複数のpathへ配置される場合、同じ`ref`と`name`を持ち、異なる親を経由して異なるpathに現れる。Objectをrenameすると、そのObjectを参照するすべての配置とdescendantのpathが変化する。Pathは恒久的なObject identityとして使用しない。
`ref`、path、Interface参照は異なる役割を持つ。
| 値 | 役割 |
| --- | --- |
| path / `entry` | RootからObjectの`name`を辿って構成するnavigation coordinate |
| `ref` | 複数の観測にまたがる任意のObject identity |
| Interface参照 | Objectで利用可能なcapability definition |
通常のstringを内容だけからpathまたは`ref`と推測しない。Operation value内でWorldspace上の位置を返す場合は、Descriptorで`entry`型として宣言する。
## Tree
Objectは子Objectを持つことができる。
Parent / child関係はWorldspaceにおける**所有または内包**を表す。
- Filesystem: Directory / File
- GitHub: Repository / Issue
- Database: Database / Table
- RAG: Index / Document
関連、依存、参照等の非所有関係を、Treeの親子関係として濫用しない。そのような関係はOperationのresultとして`entry`を返し、Clientが必要に応じて移動できるようにする。
### `children`の意味
`children`はApplication dataではなく、Objectを指定pathで観測した時点におけるWorldspace上のprojectionである。
一つのObjectが複数pathへ配置される場合、そのObjectから見えるchildrenは配置ごとに異なり得る。したがって、childrenを`ref`が示すObject identityの一部とはみなさない。
Protocolでは`Object`自体にchildrenとpathを含めず、`observe` responseの`ObjectObservation`としてObjectとoptionalなchildren observationをまとめる。ValidatorはObjectに含まれる。各nodeのpathは、request pathを起点にObjectの`name`を辿って導出する。このwrapperはObjectとは別のdomain entityを導入するものではなく、指定depthまでの観測結果を表すresponse shapeである。
`children` fieldが省略されているnodeではchildrenを観測していない。`children` fieldが存在する場合、そのarrayはその主体へ公開された直接のindexable childrenをすべて含み、空arrayは観測時点でindexable childが存在しなかったことを表す。
## Objectの可視性
Hostは、主体に認識させるObjectだけでClient-facing Worldspaceを構成する。ObjectをWorldspaceへ公開することは、そのpathとObjectの公開情報を主体が認識できることを意味する。
主体に認識させないObjectは、そのWorldspaceに存在しないものとして扱う。
- `observe`では、そのObjectと、そのObjectだけを経由して到達するsubtreeをresponseから省略する。
- 省略されたObjectやchild数を示すredaction markerは返さない。
- 非公開pathを起点とする`observe`には、Host内部での存在理由にかかわらず`NotFound`を返す。Object観測に対する`PermissionDenied`は使用しない。
Objectを公開しながら、特定Interfaceを集合から除外すること、公開したInterfaceから一部のOperationを除外すること、公開されたOperationの実行を認可時に拒否することはできる。Operationの可視性と`PermissionDenied`の使い分けは[2.2. Operation and Interface](2.2-operation-and-interface.md)で定義する。
Objectの`description`には、その主体へ公開できる説明だけを含める。安全な説明を公開できない場合はoptionalな`description`を省略できるが、Protocolはfield-level redaction markerを定義しない。Objectの本文やdomain dataの部分的な開示は、各Operationのresult schemaとdomain semanticsで表現する。
## `observe`
`observe(path, depth)`は、指定pathのObjectと、そのObjectからindexable child edgeを指定depthまで辿った範囲を一つの観測として取得する。
```text
observe(path, depth) -> ObjectObservation
```
起点Objectをdepth `0`とする。`depth = 0`では起点Objectだけを返し、そのnodeの`children` fieldを省略する。成功responseはrequested depthまでの公開されたindexable childrenを完全に含み、requested depthより深いObjectを含めない。
- Nodeのdepthがrequestの`depth`より小さい場合、`children` fieldを必ず含める。
- 観測した直接のindexable childが存在しない場合、`children`は空arrayとする。
- Nodeのdepthがrequestの`depth`と等しい場合、`children` fieldを省略する。省略はemptyを意味しない。
- 同じparent nodeのchildrenは、互いに異なる`name`を持つ。
- Clientはrequest pathとchildrenの`name`から各nodeのpathを導出する。
各ObjectはHostが発行したvalidatorを持ち得るため、Clientは導出したpathとvalidatorから条件付きOperationのtargetを構築できる。Responseを部分的な成功として返す`limit`や`truncated`は定義しない。Requested depthまでの完全な観測をresource budget内で構築できない場合、Hostはsuccess responseの代わりに`ResourceLimitExceeded`を返す。
## Indexable children
Tree上のchildrenとして自然に列挙できるのは、**indexableなObject**だけである。
- 件数がboundedまたは現実的な範囲で列挙できる。
- 比較的安価に取得できる。
- 所有または内包関係として自然に理解できる。
全件列挙が高コストまたは不自然な集合は、Treeへ直接展開しない。対象の集合を扱うObjectのOperationを使って検索、filter、paginationを行い、resultとして関連する`entry`を返す。
例えば、大規模なIssue collectionは次のように公開できる。参照はscope `/`を含む短縮表記で示す。
```text
/issues
interfaces: ["/::IssueCollection"]
operation: /::IssueCollection#query(filter, cursor, limit) -> IssueQueryResult
```
```text
IssueQueryResult.items: [entry]
```
## 複数の取得
複数のObjectやInterface Descriptorが必要な場合も、各`observe`と`fetch_interface`は独立したCore interactionである。Clientは必要なrequestを個別に並行実行でき、Transport実装はconnection reuseやmultiplexingを利用できる。
WIPは複数のinteractionを包む共通request envelope、自動的な全量取得、暗黙の再帰展開、Clientが要求していない先読みを定義しない。
+230
View File
@@ -0,0 +1,230 @@
# 2.2. Operation and Interface
## Interface
ObjectはOperation定義を直接持たず、0個以上のInterface参照をordered setとして持つ。各InterfaceはObjectが公開する一つのcapability namespaceである。
Interfaceは、Objectで利用できるOperationと、各Operationのparameter、return type、documentationを定義する。複数のObjectが同じInterfaceを共有できるため、Operation定義をObjectごとに重複して送る必要はない。
ObjectのInterface集合はset semanticsを持ち、同じ参照を重複して含めない。列挙順はOperation解決には影響しないが、Hostの立項順としてUI表示に使用できる。Operation callは集合から一つのInterfaceを明示して、そのDescriptor内のOperationを実行する。
Interface実装者は、RustのtraitやTypeScriptのdefinition API等を使って実装コード上でInterfaceを宣言する。Libraryはその宣言から、typed handler、runtime validation、およびmachine-readableなInterface Descriptorを生成する。
Interface Descriptorは人間が手書きするschema fileではない。Hostが`fetch_interface`で公開し、Clientが参照単位でcacheするprotocol dataである。
## Interfaceの識別とscope
Interface参照は、Worldspace上の絶対pathである`scope`と、そのscope内のlocal nameである`name`を別fieldとして持つ構造化データである。同じClient-facing Worldspace内の同じ有効な`(scope, name)`は同じInterfaceを表す。
```text
{ scope: "/github", name: "issue.read" }
{ scope: "/gitlab", name: "issue.read" }
```
この二つは同じlocal nameを持つが、異なるInterface参照である。Local nameの`.`等に階層やOperationとの区切りの意味を持たせない。同じscope内の異なるInterfaceへ同じnameを割り当てない。複数Providerを同じscopeへ登録する場合も、Hostが名前の衝突を検出・解消する。
ScopeはClientに対してHostが公開するWorldspaceのrootを基準とする。Backendのpath、filesystem root、HTTP endpointのprefixではなく、相対pathは認めない。異なるWorldspace間で同じ`(scope, name)`を同一Interfaceとして扱わない。
Objectが利用できるscopeは、そのObjectの配置pathとの[祖先関係](2.1-object-tree-schema.md#interface-scope)に従う。この制約はInterfaceの自動継承ではない。Objectは使用するInterfaceを明示的に公開し、scopeだけを根拠にOperationを追加したり、実行を認可したりしない。
構造とvalidationは[3. Protocol](3-protocol.md#interface-reference)、`/github::issue.read#read`等の人間・AI向け短縮表記は[4.2. Text View](4.2-text-view.md#参照表記)で定義する。Interface参照とOperation名を連結した表示文字列をProtocolの参照fieldへ格納しない。
Hostによるproxy・Worldspace合成では、Hostがscopeを公開先の配置へ変換する。Path、typed `entry`、identity、validatorとの一貫性は[Worldspace composition](3-protocol.md#worldspace-composition)に従う。
## Scope Objectと有効期間
Scopeは、そのpathにあるObject(scope Object)に紐づく。Object自身のpathをscopeにするself-scopeを許可し、root Objectもscope `/`を利用できる。Scope Object自身がそのscopeの全Interfaceを`interfaces`へ公開する必要はない。
Interfaceの識別は`(scope, name)`のままであり、scope Objectの`ref`はその識別子の第三成分ではない。`ref`がある場合は観測したObjectとの紐付きを確認するために用いる。Objectの状態を確認するvalidatorや、Descriptorの版を確認するInterface validatorとは役割が異なる。Scope Objectの通常の状態更新だけを理由に、scopeの紐付きを無効にしない。
Scope Objectがそのpathから削除・移動・差し替えられると、旧Objectに紐づくInterface参照の有効期間は終了する。同じpathへ新しいObjectとInterfaceを公開することは許可するが、旧Objectの登録を暗黙に引き継がない。移動先では新しいscopeによる別の参照となり、同じ`ref`を観測してもClientにInterface参照の移動追跡やcacheの付け替えを要求しない。
Scope Objectの`ref`はoptionalである。`ref`のないObjectが同じpathで差し替えられても、そのidentityを追跡できないことは仕様上許容される。これを不正なHostとしたり、差し替えの検出を必須にしたりしない。ただし、現在のObjectへのInterface membership、scopeの適合、指定されたvalidator、authorizationは引き続き検証する。存続中の同じInterfaceについて、`ref`がないことを理由にDescriptorのimmutable / validator規則を弱めない。
Clientはscope Objectがそのpathから消えたことを確認した場合、そのscopeのInterface cacheを破棄できる。Client-localなcache evictionやindexable edgeの消失だけをObject削除の証拠にしない。実行中callは固定済みの観測を使用し、結果検証が終わるまで必要なDescriptorを保持する。削除通知やClientによる常時監視は要求しない。
具体的な紐付きの観測とcall preconditionは[Protocol](3-protocol.md#scope-objectの紐付き)に従う。
## Interface Descriptor
一つのInterface Descriptorは一つのInterfaceを定義し、少なくとも次を保持する。
- Format version
- Interface documentation
- Named type declaration
- Operation declaration
- Operationごとのparameterとreturn type
- Type、field、parameter、case等のdocumentation
Descriptorのlogical structは[3. Protocol](3-protocol.md)で定義する。Code-firstな生成は本章、人間とAIへのInterface signatureは[4.2. Text View](4.2-text-view.md)で扱う。
Clientは取得したDescriptorを構造検証および意味検証してからcacheする。Named typeの参照解決、Operation callのvalidation、表示生成には検証済みのInterfaceだけを使用する。
## Operation declaration
Operationは、名前付きparameter、return type、documentationを持つ。
人間とAIへ提示する場合は、Interface DescriptorからIDL風のInterface signatureを生成できる。以下はOperation部分の表示例である。
```text
/// "Returns items related to this Object."
operation get_related_items(
relation: Relation,
limit?: integer,
) -> {
items: [entry],
next_cursor?: string,
};
```
Interface signatureはInterfaceの交換形式や入力言語ではなく、検証済みDescriptorから生成する表示である。Object signatureとInterface signatureの表記は[4.2. Text View](4.2-text-view.md)で定義する。
Parameterは常に名前付きとし、位置引数は用いない。引数を持たないOperationは空のparameter listとして表し、`call_operation`には空のrecordを渡す。
```wip
operation refresh() -> unit;
```
Return typeにはDescriptorで定義される任意のvalue typeを使用できる。Evolution compatibilityが必要なOperationでは、fieldを追加できるrecordをreturn typeの基本とする。
Operationの用途は短いdocumentationで説明する。長文documentationや大量のApplication dataをInterface Descriptorへ埋め込まない。
Operation名は一つのInterface Descriptor内で一意とする。異なるInterface間では同じOperation名を使用でき、`call_operation`は構造化Interface参照とOperation名の組で解決する。表示用のqualified表記は[Text Viewの参照表記](4.2-text-view.md#参照表記)に従い、Protocolでは参照とOperation名を別fieldとして扱う。
## `fetch_interface`
`fetch_interface(interface)`は、Interface参照に対応する現在のInterface Descriptor、optionalなscope Objectの`ref`(`scope_ref`)、optionalなInterface validatorを一つの観測として取得する。
```text
fetch_interface(interface) -> Interface Descriptor, scope_ref?, validator?
```
ObjectはInterface参照のordered setだけを持ち、Descriptorを先行同梱しない。ClientはInterface cacheと各参照を比較し、未知のDescriptorを取得する。Interface validator mismatch等で更新が必要になった参照は再取得する。Hostは主体へ公開したObjectの各Interface参照について、現在のDescriptorをその主体から取得可能にしなければならない。
同じ有効期間内のInterface参照とvalidatorの組は同じDescriptorを表す。Validatorを返さないInterface参照は、その参照の有効期間中immutableでなければならない。`scope_ref`はこの観測の紐付きmetadataとして保持し、参照の識別子へ組み込まない。
HostはClientのcache状態を推測して、要求されていないDescriptorをObject responseへ追加しない。複数のDescriptorが必要なClientは、それぞれの`fetch_interface`を個別に並行実行できる。
## Operationの可視性
Hostは、主体の権限やrole / capabilityに基づいて、その主体へ公開するInterface参照の集合を決定してよい。同じObjectでも、主体ごとに異なるInterface集合を返してよい。
Operationを主体に認識させない場合、Hostは次のいずれかを選べる。
- そのOperationをcapabilityとしてまとめたInterface参照をObjectの集合から除外する。
- そのOperationを宣言しない別のInterface Descriptorを参照する。
ClientがObjectで公開されていないInterface参照を指定した場合は`InterfaceMismatch`、公開されているInterfaceに宣言されていないOperation名を指定した場合は`OperationNotFound`を返す。
Operationの存在を公開しつつ実行だけを認めない設計も可能である。この場合はOperationをいずれかのInterfaceに宣言し、`call_operation`時のauthorizationで拒否して`PermissionDenied`を返す。
したがって、Hostは主体に対して次を独立に選択できる。
- Object自体を公開せず、そのObjectへの`observe`とOperation callを`NotFound`にする。
- Objectを公開するが、特定Interfaceを集合から除外する。
- Objectを公開し、特定Operationを含まないInterfaceを使用する。
- Object、Interface、Operationを公開するが、そのOperationの実行を`PermissionDenied`にする。
Interface集合と各Interface内のOperation可視性は、頻繁に変化する実行可否やdomain stateを表すものではなく、**その主体に対してどのcapabilityを公開するか**というprojectionとして扱う。
WIPはOperationの`available` / `disabled`のような揮発的状態をInterface集合へ同期することを前提としない。現在状態を判断する必要がある場合は、必要に応じてdomain固有のOperationで観測する。公開されたOperationについても、最終的なauthorizationと実行条件の判定は`call_operation`時にHostが行う。
## `call_operation`
`call_operation`は、指定pathにあるObjectに対して、Interfaceに宣言されたOperationを実行する。
```text
call_operation(target, interface, operation, arguments) -> result, validator?
```
- `target.path`は対象Objectを観測したpath。
- `target.validator`はObjectから得たoptionalなopaque validator。
- `interface.reference`は対象Objectの`interfaces`から選んだInterface参照。
- `interface.scope_ref`は`fetch_interface`で観測したscope Objectのoptionalな`ref`。
- `interface.validator`は`fetch_interface`で観測したoptionalなInterface validator。
- `operation`は選択したInterfaceに宣言されたOperation名。
- `arguments`はOperationのparameterに従う名前付きrecord。
- `result`はOperationのreturn typeに従うvalue。
- Responseの`validator`はOperation実行後のObject状態に対してHostが発行したoptionalな値。
HostはOperationを実行する前に、次を検証する。
1. `target.path`が存在する。
2. `target.validator`が指定されている場合、現在のObject validatorと一致する。
3. `interface.reference`が`(scope, name)`の完全一致でObjectの現在の`interfaces`に含まれ、対象pathに対するscopeの祖先制約(self-scopeを含む)を満たす。`interface.scope_ref`が指定されている場合は現在のscope Objectの`ref`と一致する。
4. MutableなInterfaceでは`interface.validator`が指定され、現在のInterface validatorと一致する。
5. 選択したInterfaceにOperationが宣言されている。
6. Argumentsがparameter declarationに従っている。
7. ClientがOperationを実行できる。
対象Objectが現在のClient-facing Worldspaceへ公開されていない場合は`not_found`、指定Interfaceが対象Objectの現在の`interfaces`に含まれない場合や、指定された`scope_ref`の紐付きを確認できない場合は`interface_mismatch`、Operationが指定Interfaceに宣言されていない場合は`operation_not_found`、宣言されたOperationの実行が認められない場合は`permission_denied`を返す。`scope_ref`を持たない観測とcallも許可し、その省略だけでは拒否しない。
Object validator、Interface membership、scope Objectの紐付きと指定された`scope_ref`、Interface validator、Descriptor、Operation implementation、backend対象の確認と固定は、Operation実行に対して一貫したpreconditionとして扱う。Object validatorが一致しない場合は`validator_mismatch`、要求されるObject validatorがない場合は`validator_required`を返す。Mutable Interfaceのvalidatorがない場合は`interface_validator_required`、一致しない場合は`interface_validator_mismatch`を返す。いずれの場合もHostはOperationを実行しない。
Argumentsに未知のfieldがある場合、必須parameterがない場合、またはvalueが宣言型に一致しない場合、HostはOperationを実行せず`invalid_arguments`を返す。
ClientはHostから受け取ったsuccess responseのresultをreturn typeに従って検証する。Hostが宣言と異なるresultを返した場合はHostが返したProtocol failureではなく`InvalidResponse`として扱い、そのvalueをApplicationへ渡さない。Host Runtimeがinvalidなimplementation resultを送信前に検出した場合は、effectがcommitされていないと保証できるときだけ`Internal`を返し、保証できない場合は`OperationOutcomeUnknown`を返す。
## Application data
Operation argumentsとresultは、Applicationが定義し、利用するdataである。
WIPは次を規定する。
- Descriptor上の型表現
- Transport上のencoding
- ClientとHostのboundary validation
- `entry`等のWIP固有型の意味
WIPは次を規定しない。
- Dataのdomain上の意味
- 永続化方法
- Application内部のdomain model
- Business rule
- Clientが取得したdataを利用する方法
Wire schema上正しいことと、domain上有効であることは別である。例えばargumentsが型に従っていても、現在のdomain stateが操作を許可しない場合、Operation implementationはdomain failureを返し得る。
## Operation Resultとエントリの発見
Operation Resultには、Descriptorの`entry`型として新しいエントリのpathが含まれ得る。
`entry`は同じWorldspace内のcanonical absolute pathである。Clientはreturn typeに従って`entry`型の位置を認識する。通常の`string`やopaqueな`json`に含まれるpathらしい文字列を、エントリとして推測してはならない。
Clientは発見したpathを参照として利用でき、Objectの公開情報が必要になったときに、必要な深さで`observe`する。発見したすべてのpathの即時取得は要求しない。Operation ResultへObject公開情報やInterface Descriptorを先行同梱しない。
Operationの実行対象と、結果として発見されたエントリのTree上の位置は独立している。発見されたエントリは実行対象の子である必要はなく、親、兄弟、またはWorldspace内の任意の位置にあってよい。
Operationがpathを返したこと自体から、実行対象との間に親子関係や意味的関係を推論しない。親子関係はTree上の配置で表し、非内包の意味的関係はOperation名、result field名、またはApplication固有のrelation型で表現する。
## Protocol failureとdomain failure
Protocol failureは、WIPのrequest、対象、Interface、validation、validatorによる条件付き実行、Host executionに関する失敗である。
- `invalid_request`
- `not_found`
- `interface_not_found`
- `interface_mismatch`
- `interface_validator_required`
- `interface_validator_mismatch`
- `operation_not_found`
- `invalid_arguments`
- `validator_required`
- `validator_mismatch`
- `permission_denied`
- `resource_limit_exceeded`
- `internal`
- `operation_outcome_unknown`
`InvalidResponse`はClientが受信したsuccess responseまたはerror responseのProtocol違反を表すlocal classificationであり、Hostが返すProtocol failureではない。`UnsupportedDescriptorFormat`もClient-localであり、validだが未対応のDescriptorをProtocol違反として扱わない。
`call_operation`が`operation_outcome_unknown`以外のProtocol failureを返した場合、HostはOperationのeffectがcommitされていないことを保証する。`operation_outcome_unknown`またはresponse不達の場合、ClientはOperationを自動再実行せず、対象を再観測するかdomain固有の照会手段を利用する。
Domain failureは、型として正しいOperationをApplicationが実行した結果として生じる失敗である。
- `issue_has_open_dependencies`
- `payment_declined`
- `merge_conflict`
Domain failureをProtocol failureへ混在させない。現在のProtocolでは、Domain failureをOperation return typeのUnion caseとして宣言する。専用のfailure declarationは定義しない。
+741
View File
@@ -0,0 +1,741 @@
# 3. Protocol
WIP Protocolは、ClientとHostの間で交換するlogical data modelと、Core interactionのrequest / responseを定義する。
この章のstructはRust風の記法を使用するが、Rust固有のmemory layoutやserialization libraryを要求しない。TypeScriptを含む他言語でも同じ型、必須性、validation semanticsを実装できなければならない。
HTTP endpoint、JSON fieldへのencoding、status codeはTransport Bindingの責務とし、共通要件を[5. Transport Bindings](5-transports.md)、標準bindingを[5.1. WIP over HTTP](5.1-wip-over-http.md)で扱う。
## Protocol dataの分類
WIPがfirst-classなprotocol dataとして定義し、Clientが取得およびcacheするものは次の二つである。
- `Object`
- `InterfaceDescriptor`
Operationのargumentsとresultには、Interface Descriptorで宣言された`Value`を使用する。Application dataは独立したWIP resourceではなく、Operation callの入力または出力として交換される。
```text
Protocol data
|-- Object
`-- InterfaceDescriptor
Operation value
|-- arguments
`-- result
```
Path、Object参照、Interface参照、validator、error等の意味は、それらが現れるfieldまたはenvelopeによって決まる。Protocol上のnamed scalarとしては定義しない。
## 記法
この章では次の記法を使用する。
```rust
struct T { ... } // 名前付きfieldを持つrecord
enum T { ... } // 閉じたcaseの集合
Option<T> // 省略可能なfield
Vec<T> // 順序付きlist
BTreeMap<String, T> // string keyを持つlogical map
```
`Option<T>`はfieldの省略を表し、`null`を表さない。Transport Bindingは、省略されたfieldをwire valueへ出力してはならない。
`BTreeMap`はstring-keyed mapをRust風に示すための記法であり、keyのiteration orderをProtocol semanticsには含めない。
## Protocol primitives
Protocol structは、次のprimitiveおよびcompositeだけで表す。
```text
unit
boolean
integer
number
string
bytes
list
record
```
Path、Interface参照、Object identity、validator、Operation名等を独立したProtocol scalarにはしない。同じprimitiveを使用するfieldでも意味は交換可能ではなく、各fieldの定義とvalidation ruleに従って解釈する。
Rust SDKは利便性や安全性のために`Path`等のnewtypeを公開してよく、TypeScript SDKはbrand等を利用してよい。それらは言語別SDKのAPIであり、Protocol structの一部ではない。
## Path
Pathとして宣言されたfieldのvalueは、同じWorldspace内のObjectを取得するためのcanonical absolute pathでなければならない。
- Root pathは`/`とする。
- 非root pathは、rootから対象Objectまでの各`name`を`/`で連結して構成する。
- 空segmentを含めない。
- `.`または`..`であるsegmentを含めない。
- Root以外は末尾に`/`を持たない。
- Pathに含まれる各segmentはUnicode stringとして扱い、Unicode normalizationを行わない。
- Segmentの比較はUnicode code point sequenceの完全一致で行う。
- Navigationに使用できるが、恒久的なObject identityとして使用しない。
Nameに`/`を許可しないため、Protocolはpath segment escapeを定義しない。Transport BindingによるURL encodingはWIP Pathの構成とは別に扱う。
## Interface Reference
Interface参照は、scopeとlocal nameからなるrecordとして表す。新しいfirst-class resourceやnamed scalarではなく、Objectやinteractionが使用する参照のstructである。
```rust
struct InterfaceReference {
scope: String,
name: String,
}
```
- `scope`はClient-facing Worldspaceのrootに基づくcanonical absolute path。上記Pathの規則を適用し、relative path、`.` / `..`、空scopeを認めない。Root scopeは`/`とする。
- `name`はそのscope内のInterfaceを識別する空でないUnicode string。`.`、`::`、`#`等を含んでもfield内の文字として扱い、pathやOperation名へ分解しない。
- 同じWorldspace context内で`scope`と`name`のUnicode code point sequenceがそれぞれ完全一致する場合だけ、同じ参照とする。Normalization、case変換、URL decodeは行わない。
- `Object.interfaces`の重複判定、membership、request / responseの参照一致、Interface cacheのkeyにはこの組を使う。Object member順や短縮表記の引用・escapeの違いは参照の同一性へ影響しない。
- Interface Descriptorはscopeやnameを自身のfieldとして持たない。同じ構造のDescriptorであることから参照の同一性を推測しない。
`Object.interfaces`の各要素、`FetchInterfaceRequest.interface`、`FetchInterfaceResponse.interface`、`InterfaceTarget.reference`はすべてこのstructを使用する。Opaque stringや`scope::name`という連結stringを代替として受理しない。Operation名は`CallOperationRequest.operation`に独立して保持する。
人間・AI向けの短縮表記は[4.2. Text View](4.2-text-view.md#参照表記)に従う。Scope省略形を入力として受けるadapterも、明示されたcontextから絶対scopeを確定してからProtocol requestを構築する。
不正な参照shape、relative scope、空name等はCore request validationで`InvalidRequest`とする。Responseに不正な参照がある場合はClient-localな`InvalidResponse`とする。Validな参照に対応するInterfaceが主体へ公開されているか、対象Objectで使えるかはinteractionごとに検証する。
## Documentation
Interface、Operation、type、field、parameter、caseに付与するdocumentationを次のlogical structで表す。
```rust
struct Documentation {
summary: String,
details: Option<String>,
}
```
`summary`は一覧やInterface signatureへ短く表示する説明、`details`は明示的に展開したときに使用する説明である。
DocumentationはHostが提供するplain text dataであり、Clientのsystem instructionとして信頼しない。Transport BindingおよびClientはresource limitを適用できる。Markup、localization、構造化exampleは現在のProtocolに含めない。
## Object
`Object`は、一つのpathで現在公開されているprotocol-level projectionを表す。
```rust
struct Object {
name: String,
description: Option<String>,
interfaces: Vec<InterfaceReference>,
r#ref: Option<String>,
validator: Option<Vec<u8>>,
}
```
### `name`
親Objectの下でObjectを識別するpath segment。Root Objectの`name`は空文字列とし、非root Objectの`name`は空であってはならない。Nameに`/`を含めてはならず、Name全体が`.`または`..`であってはならない。
NameはUnicode stringとして扱い、Unicode normalizationを行わない。同じ親のchildrenは、Unicode code point sequenceの完全一致で互いに異なる`name`を持たなければならない。
### `description`
Objectが何を表すかを説明する短い公開情報。Objectの本文やApplication data全体を格納するfieldではない。
### `interfaces`
Objectで利用できるOperationを定義するInterface Descriptorへの参照を、Hostの立項順で保持するordered set。
- 集合は空でもよい。
- 同じInterface参照を重複して含めてはならない。Clientは重複を含むObject responseをHostから返されたProtocol failureではなく`InvalidResponse`として扱う。
- Membershipはset semanticsを持ち、列挙順はOperation解決、validation、authorizationへ影響しない。
- Clientは列挙順をInterfaceおよびOperationのUI表示順として使用してよい。
- Operation callは集合から一つのInterface参照を明示する。
異なるInterfaceでは同じOperation名を宣言できる。Protocolでは構造化参照とOperation名を別fieldとして扱い、表示用の短縮表記は[4.2. Text View](4.2-text-view.md#参照表記)に従う。
Hostはvalidatorを発行することで、同じInterface参照のDescriptorを時間とともに変更できる。Validatorを発行しない参照のDescriptorは、その参照が有効な間は変更しない。Clientは`fetch_interface`でDescriptorとvalidatorを一つの観測として取得する。
### `ref`
同じ主体に公開された一つのWorldspace内で、複数の観測またはエントリが同じObjectを表すことを示すoptionalなopaque string。
- 同じ有効な`ref`は同じObjectを表す。
- Hostは同じ有効な`ref`を異なるObjectへ付与してはならない。
- 異なるまたは欠落した`ref`からObjectが異なるとは判断しない。
- 異なるWorldspace間で`ref`を比較しない。
- Session、再接続、Host restart、Worldspace再構築をまたぐ永続性を要求しない。
- Navigationには使用せず、`ref`からObjectを取得するinteractionは提供しない。
Backendに適切なUIDがある場合、Hostはそれを直接または変換して使用できるが、Protocolはbackend UIDの存在または公開を要求しない。
### `validator`
HostがそのObjectの現在状態へ付与するoptionalなopaque bytes。Clientは内容を解釈せず、観測に基づくOperationを条件付きで実行するときに、pathとともに`target.validator`へコピーする。
Hostはvalidatorの生成方法を自由に選べる。Generation、content hash、database revision等を使用してよい。ValidatorはObject identityとHostが保護する現在状態の両方へ結び付け、target pathが別Objectを指す場合は、内容が同じでも一致させてはならない。Content hashを使用する場合はObject identityをhashへ含めるか、Hostがidentityを別途検証する。
Hostは、どの状態変化によってObject validatorを無効にするかについて、そのObjectに対して一貫した方針を適用する。Object validatorはpathに固有ではなく、同じ`ref`を持つObjectの現在状態に付与する。
Object validatorはAIやユーザーへ公開するApplication dataではない。Client runtimeがObject observationとともに保持するprotocol metadataである。
ここでいうObjectの「現在状態」は、Hostがvalidatorによってobservation consistencyを保護する対象を指す。WIP Objectはprotocol-level projectionであり、Application stateを格納する共通containerではない。WIPは状態遷移modelを定義せず、OperationをObject stateに対するread / writeとして分類しない。
### 同じ`ref`を持つObjectの整合性
同じWorldspace内で同じ`ref`を持つObjectは、一つの共有された現在状態を表す。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、`interfaces`を含む全fieldが一致しなければならない。
- 同じ`ref`と異なるvalidatorを持つObjectは、同一Objectの異なる状態として扱う。
- HostはObjectが配置されたpathを理由に、同じ`ref`へ異なる`name`、`description`、`interfaces`を同時に公開してはならない。
- Clientは最後に成功した観測を、その`ref`を持つObjectの現在状態として扱う。
同じObjectを複数のpathへ配置する場合、各配置は同じObjectの`name`を使用する。異なる親を経由することでpathが異なる。
`Object`はchildrenとpathを直接持たない。これらはObject identityに固有とは限らず、Objectを観測したentryのcontextに属するため、request pathと`ObjectObservation`で表す。
Objectの意味とApplication dataとの境界は[2. Core Model](2-core-model.md)と[2.1. Object tree / Schema](2.1-object-tree-schema.md)で扱う。
## Interface Descriptor
`InterfaceDescriptor`は、実装コード上のInterface Declarationから生成されるmachine-readable metadataである。
```rust
struct InterfaceDescriptor {
format: String,
documentation: Option<Documentation>,
types: Vec<TypeDeclaration>,
operations: Vec<OperationDeclaration>,
}
```
Interface実装者がこのstructのJSON encodingを手書きすることは想定しない。Rust macro、TypeScript definition API、code generator等が生成し、Hostが公開する。
`types`と`operations`のdeclaration順は表示時に保持してよいが、名前解決とvalidationの結果には影響しない。
### Type declaration
```rust
struct TypeDeclaration {
name: String,
documentation: Option<Documentation>,
definition: TypeExpr,
}
```
一つのDescriptor内でType名は一意でなければならない。
### Type expression
```rust
enum TypeExpr {
Unit,
Boolean,
Integer,
Number,
String,
Bytes,
Json,
Entry,
Named {
name: String,
},
Record {
fields: Vec<FieldDeclaration>,
},
List {
items: Box<TypeExpr>,
},
Enum {
cases: Vec<EnumCase>,
},
Union {
cases: Vec<UnionCase>,
},
}
```
`Named`は同じInterface Descriptor内の`TypeDeclaration`を参照する。External type importとrecursive type referenceは定義しない。Descriptorの意味検証では、存在しないTypeへの参照と参照cycleを拒否する。
### Record
```rust
struct FieldDeclaration {
name: String,
required: bool,
documentation: Option<Documentation>,
r#type: TypeExpr,
}
```
一つのRecord内でfield名は一意でなければならない。`required = false`はfieldの省略を許可するが、`null`を許可する意味ではない。
Record valueに未宣言fieldが存在する場合はvalidation failureとする。
### Enum
```rust
struct EnumCase {
name: String,
documentation: Option<Documentation>,
}
```
Enum caseはpayloadを持たない。一つのEnum内でcase名は一意でなければならない。
### Union
```rust
struct UnionCase {
name: String,
documentation: Option<Documentation>,
payload: Option<TypeExpr>,
}
```
Union caseは、caseごとに0個または1個のpayloadを持つ。一つのUnion内でcase名は一意でなければならない。
### Operation declaration
```rust
struct OperationDeclaration {
name: String,
documentation: Option<Documentation>,
parameters: Vec<ParameterDeclaration>,
returns: ReturnDeclaration,
}
struct ParameterDeclaration {
name: String,
required: bool,
documentation: Option<Documentation>,
r#type: TypeExpr,
}
struct ReturnDeclaration {
documentation: Option<Documentation>,
r#type: TypeExpr,
}
```
一つのDescriptor内でOperation名は一意でなければならない。一つのOperation内でparameter名は一意でなければならない。
Interface Descriptorの生成と意味論は[2.2. Operation and Interface](2.2-operation-and-interface.md)、人間とAIへのInterface signatureは[4.2. Text View](4.2-text-view.md)で扱う。
## Value
Operation argumentsとresultは、schema上の意味を持たない次のlogical valueで表す。
```rust
enum Value {
Unit,
Boolean(bool),
Integer(i64),
Number(f64),
String(String),
Bytes(Vec<u8>),
Record(BTreeMap<String, Value>),
List(Vec<Value>),
}
```
`Value`はHostとClientが交換するprimitiveおよびcompositeだけを表す。Path、enum case、union case等のsemantic variantを持たない。既知のInterfaceに対するgenerated bindingは、言語固有DTOと`Value`のcodecを提供できる。
### Primitive semantics
| Value | 意味 | 制約 |
| --- | --- | --- |
| `Unit` | 値を持たない | Optional fieldの省略とは異なる |
| `Boolean` | 真偽値 | — |
| `Integer` | 符号付き整数 | `-9007199254740991`から`9007199254740991`まで |
| `Number` | IEEE 754 binary64 | NaN、正負Infinityを禁止する |
| `String` | Unicode string | — |
| `Bytes` | 任意のoctet列 | Transport Bindingがencodingを定める |
| `Record` | String keyを持つvalueの集合 | Key順に意味を持たない |
| `List` | 順序付きvalue | — |
`integer`の範囲はRustとTypeScript間でlosslessに相互運用できる範囲へ制限する。
`number`を受信したruntimeは、有限値であることを検証する。
### Descriptorによる解釈
Interface Descriptorの`TypeExpr`は、rawな`Value`に意味と追加validationを与える。
| TypeExpr | 対応するValue | 追加の解釈 |
| --- | --- | --- |
| `Unit` | `Value::Unit` | 値を持たないresult等 |
| `Boolean` | `Value::Boolean` | — |
| `Integer` | `Value::Integer` | 相互運用範囲を検証する |
| `Number` | `Value::Number` | 有限値を検証する |
| `String` | `Value::String` | 通常の文字列として扱う |
| `Bytes` | `Value::Bytes` | Octet列として扱う |
| `Json` | JSON data modelで表現可能なValue | 内部構造をWIP schemaとして解釈しない |
| `Entry` | `Value::String` | Canonical absolute Worldspace pathとして検証する |
| `Record` | `Value::Record` | Field declarationに従って検証する |
| `List` | `Value::List` | 各要素をitems typeに従って検証する |
| `Enum` | `Value::String` | 宣言されたcase名として検証する |
| `Union` | `Value::Record` | Discriminatorとpayloadをcase declarationに従って検証する |
`Json`が受け付けるのはJSONのnull、boolean、number、string、array、objectに対応するValueだけであり、`Bytes`は含まない。JSON内の整数と数値にも`Value::Integer`と`Value::Number`と同じ範囲および有限性の制約を適用する。
`Entry`と`String`は同じprimitiveで交換する。ClientはDescriptor上で`Entry`と宣言された位置だけをWorldspace pathとして扱い、通常のStringを内容からEntryへ変換しない。
EnumもString、UnionもRecordとして交換する。SDKまたはruntimeは、Descriptorによるvalidation後の内部表現としてtyped enum、union、Path等を構築してよい。それらはProtocolの送受信形式ではない。
### Value validation
Valueは、Operation declarationのTypeExprと照合して検証する。
- `Named`を参照先Typeへ解決する。
- Required fieldまたはparameterの欠落を拒否する。
- 未宣言のRecord fieldを拒否する。
- Optional fieldの省略と`Unit`を区別する。
- EnumおよびUnionの未知caseを拒否する。
- Union discriminator、payloadの有無、payload typeをcase declarationに照合する。
- `Entry`として宣言されたStringがcanonical pathであることを検証する。
- Integerの相互運用範囲とNumberの有限性を検証する。
## Core request validation
Transport Bindingがframing、media type、authentication等を受理した後、Hostはinteraction固有のObject / Interface解決より前にlogical requestを検証する。
1. Request structとして解釈できない、required fieldがない、未知fieldがある、Path等のfield semanticsに違反する場合は`InvalidRequest`。
2. Field長、nesting、collection件数、`observe`のdepth等がHostのboundedな上限を超える場合は`ResourceLimitExceeded`。
3. 検証済みrequestについて、各interaction固有のtarget resolutionとpreconditionを評価する。
`InvalidArguments`は`call_operation` request自体がvalidであり、固定したOperation declarationに対して`arguments`が適合しない場合だけに使用する。Transport route、authentication、media type、framing等のfailureはCore interactionのProtocol failureではない。
## `observe`
一つのpathで現在公開されているObjectと、そのObjectから指定depthまでのindexable childrenを一つの観測として取得する。
```rust
struct ObserveRequest {
path: String,
depth: u32,
}
struct ObjectObservation {
object: Object,
children: Option<Vec<ObjectObservation>>,
}
type ObserveResponse = ObjectObservation;
```
起点Objectをdepth `0`とする。`depth = 0`は対象Objectだけを観測する通常の形であり、root nodeの`children`を省略する。
- Root nodeのObjectはrequest pathが指すObjectである。Root pathが`/`の場合はObjectの`name`が空文字列でなければならず、非root pathでは最後のsegmentとObjectの`name`が一致しなければならない。
- Request pathのObjectがその主体へ公開されていない場合は`NotFound`を返す。
- 各childのpathは、parent pathへchild Objectの`name`を追加して導出する。
- 同じparent nodeのchildrenは、互いに異なる`name`を持つ。
- Nodeのdepthがrequestの`depth`より小さい場合、`children`は必須であり、その主体へ公開された直接のindexable childrenをすべて含む。Childが存在しない場合は空listとする。
- Nodeのdepthがrequestの`depth`と等しい場合、`children`を省略する。省略された`children`からemptyを推測してはならない。
- Requested depthより深いnodeをresponseへ含めない。
- その主体へ公開されていないObjectはresponseへ含めず、存在や省略を示すmarkerも返さない。
Success responseはrequested depthまで完全である。Node数によるpartial success、`limit`、`truncated`は定義しない。Hostが完全なresponseをresource budget内で構築できない場合は`ResourceLimitExceeded`を返す。
各nodeのObjectがvalidatorを持つ場合、Clientはrequest pathとchildrenの`name`から導出したpathに対応付けてcacheし、条件付きOperationの`target`を構築できる。
HostとClientは各nodeの配置pathに対して[Interface scopeの祖先制約](2.1-object-tree-schema.md#interface-scope)を検証する。Self-scopeを含み、root Objectではscope `/`を使用できる。制約に違反する参照を含むObject responseは`InvalidResponse`であり、そのObjectを操作へ使用しない。
## `fetch_interface`
Interface参照に対応する現在のInterface Descriptorを取得する。
### Interface参照とvalidator
`Object.interfaces`の各要素、`FetchInterfaceRequest.interface`、`FetchInterfaceResponse.interface`、`InterfaceTarget.reference`は`InterfaceReference`である。同じWorldspace context内の同じ有効な`(scope, name)`は同じInterfaceを表すが、Hostはvalidatorを使用してDescriptorを時間とともに変更できる。
`FetchInterfaceResponse.validator`は、HostがそのInterface Descriptorの現在状態へ付与するoptionalなopaque bytesである。Clientは内容を解釈せず、観測したDescriptorに基づいてOperationを実行するときに、Interface参照とともに`InterfaceTarget`へコピーする。
同じ有効期間内のInterface参照とvalidatorには次の規則を適用する。有効期間とscope Objectの紐付きは次節に従う。
- 同じInterface参照と同じvalidator valueを持つ観測は、validatorが存在する場合、同じInterface Descriptorを表す。
- 同じInterface参照と異なるvalidatorを持つ観測は、同じInterfaceの異なる状態を表す。
- Validatorを返さないInterface参照は、その参照が有効である間、異なるDescriptorへ変更してはならない。
- 同じInterface参照のDescriptorを変更可能にするHostはvalidatorを返し、Descriptorの変更時に以前と異なるvalidatorを発行しなければならない。
- Validator valueはInterface参照と組み合わせて比較する。異なるInterface参照で同じopaque bytesを使用してもよい。
- Validatorから状態の順序、新旧、Descriptor内容を推測してはならない。
HostはInterface validatorの生成方法を自由に選べる。Generation、content hash、database revision等を使用してよい。Clientは生成方法を解釈せず、Interface参照とvalidator valueの組をOperation executionのpreconditionとして使用する。
Interface validatorはAIやユーザーへ公開するApplication dataではない。Client runtimeがInterface Descriptor observationとともに保持するprotocol metadataである。
### Scope Objectの紐付き
Interface scopeはそのpathのObjectに紐づく。Scope Objectがそのpathから消えれば、そのObjectに紐づくInterface参照の有効期間は終了し、未公開のInterfaceへの`fetch_interface`は`InterfaceNotFound`となる。削除されたObject自身と消えた配下への`observe` / `call_operation`は`NotFound`となる。同じpathへ別Objectが公開された場合は現在の公開状態で解決する。
`FetchInterfaceResponse.scope_ref`は、scope Objectが同じ主体へ公開する`Object.ref`である。Hostはその`ref`があれば同じstringを返し、なければfieldを省略する。別のscope専用identityやvalidatorを生成するfieldではない。Scope Objectとの紐付き、Descriptor、Interface validatorは一つの整合した観測として取得し、別々の時点の値を組み合わせない。Clientにscope Objectやその祖先の追加取得を必須にしない。
Clientは`scope_ref`をInterface observationとともに保持し、その観測を使うcallの`InterfaceTarget.scope_ref`へ存在する値をコピーする。Hostは指定された`scope_ref`を、現在のscope Objectの`ref`とUnicode code point sequenceの完全一致で比較する。一致しない場合、または現在のObjectが`ref`を持たない場合、その紐付きは確認できず`InterfaceMismatch`とする。異なる`ref`から別Objectだと断定するものではなく、指定された同一性条件を満たせないことを表す。Clientの事前確認だけで済ませず、Hostがcallの他のpreconditionと一貫して確認する。
`scope_ref`は参照の識別子に含めない。Interfaceの識別とmembershipは引き続き`(scope, name)`であり、通常の表示も変更しない。Scope Objectの状態validatorはこの確認に使わず、通常の状態更新でscopeの紐付きを無効にしない。Descriptorの変更検出には独立したInterface validatorを使う。
`scope_ref`が省略されたcallにはscope Objectのidentity preconditionを適用しない。Scope Objectの`ref`はoptionalであり、`ref`なしで同じpathのObjectが差し替わったことをClientが追跡できないのは仕様上許容される。Hostは`ref`がないことやidentityを追跡できないことだけを理由に拒否せず、現在の公開状態と残るpreconditionに従う。Hostにref生成、Clientに疑似refやscope validatorの生成を要求しない。
Scopeの削除・移動・差し替えによる有効期間終了を確認したClientは、旧Interface observationを新しいcallへ再利用せず、必要なら現在の参照を再取得する。同じpathへ再公開された参照の観測と、終了した有効期間のcacheは区別する。`scope_ref`の相違や欠落により以前の紐付きを確認できなくなった場合も、旧観測をそのまま付け替えない。異なる有効期間のDescriptorを、同じvalidatorであることだけから同一とみなさない。
`ref`も削除の観測もない場合、有効期間の切り替わりを検出できるとは限らない。Clientは得られた情報で通常のvalidationを行うが、検出不能な差し替えを防ぐ保証はない。この許容は、存続中の同じInterfaceのDescriptor整合性を弱めない。Scopeが別pathへ移動すれば別参照として扱い、Clientは移動を追跡しない。実行開始済みcallが固定した観測と結果検証用Descriptorの保持は、有効期間終了後も維持する。
### Requestとresponse
```rust
struct FetchInterfaceRequest {
interface: InterfaceReference,
}
struct FetchInterfaceResponse {
interface: InterfaceReference,
scope_ref: Option<String>,
descriptor: InterfaceDescriptor,
validator: Option<Vec<u8>>,
}
```
Clientは次を検証する。
- Responseの`interface`がrequestと一致する。
- Descriptorが構造検証および意味検証に成功する。
- Descriptorの`format`がClientの対応するversionである。
- 同じ有効期間のInterface参照と同じvalidator valueを持つ既存cache entryがある場合、validatorが存在する観測ではDescriptorがそのentryと一致する。Scopeの紐付きが確認できなくなった旧cacheとの比較は、前節の規則に従う。
ResponseまたはDescriptorがProtocol contractに違反する場合、ClientはHostが返したProtocol failureではなく`InvalidResponse`として扱う。Descriptorが構造的にはvalidだが、その`format`をClientが実装していない場合は`UnsupportedDescriptorFormat`として扱い、`InvalidResponse`と混在させない。
Clientはvalidatorが存在するInterface observationを、同じ有効期間内の`(interface, validator)`の組でcacheし、`scope_ref`も観測metadataとして保持する。Interface参照ごとに最後に成功した観測を現在のcache entryとしてよいが、実行中のOperationが参照する観測はresponseのvalidationが完了するまで保持する。次の規則は同じ有効期間の観測に適用し、終了・紐付きの不一致を確認した旧cacheは新しいcallに使わない。
- 同じInterface参照でvalidatorが異なる場合は、同じInterfaceの新しい状態としてlatest observationを更新する。
- Validatorが存在しないInterface参照はimmutableなDescriptorとして、Interface参照だけをcache keyに使用できる。
- 同じInterface参照と同じvalidator valueで異なるDescriptorを受け取った場合は`InvalidResponse`として扱い、そのDescriptorをOperationへ使用しない。
- 新しい観測を得ても、異なるvalidatorを使用して実行中のrequestに必要なDescriptorをresponse validation前に破棄しない。
`fetch_interface`は現在のDescriptorを観測するinteractionであり、requestにvalidatorを含めない。Responseにvalidatorが存在する場合、Clientは後続の`call_operation`で、そのDescriptorに基づくOperation実行の必須preconditionとして使用する。Responseにvalidatorが存在しない場合、そのInterface参照のDescriptorはimmutableであり、`call_operation`の`interface.validator`を省略する。
Hostは、主体へ公開したObjectの`interfaces`に含まれる各参照について、現在のDescriptorをその主体の`fetch_interface`で取得可能にしなければならない。現在公開されていないInterface参照には`InterfaceNotFound`を返し、Descriptor取得の認可失敗として`PermissionDenied`は使用しない。
`fetch_interface`は完全な`(scope, name)`を受け取るため、Clientに祖先Objectを順に取得するlookupを要求しない。Hostはscope Objectと、そのObjectに紐づくInterfaceの現在の公開状態を解決し、近い祖先の同名Interfaceへのfallbackは行わない。Requestに対象Objectのpathはなく、特定Objectで利用できるかの祖先制約(self-scopeを含む)は`observe`と`call_operation`で検証する。Scope外のObjectで使えないことと、Interface単独の取得可否は同一視しない。
## `call_operation`
指定pathにあるObjectへOperationを実行する。
```rust
struct Target {
path: String,
validator: Option<Vec<u8>>,
}
struct InterfaceTarget {
reference: InterfaceReference,
scope_ref: Option<String>,
validator: Option<Vec<u8>>,
}
struct CallOperationRequest {
target: Target,
interface: InterfaceTarget,
operation: String,
arguments: BTreeMap<String, Value>,
}
struct CallOperationResponse {
result: Value,
validator: Option<Vec<u8>>,
}
```
`target.path`はOperationを実行するObjectのpathである。`target.validator`は、Clientが観測したObjectのvalidatorを条件として指定する。Validatorを持たないObjectまたは無条件実行では省略する。
`interface.reference`は、ClientがObjectの`interfaces`から選択し、そのOperation schemaを取得するために使用したInterface参照である。`interface.validator`は、その参照への`fetch_interface`で観測したvalidatorである。`fetch_interface` responseにvalidatorが存在する場合は必ず指定し、validatorが存在しないimmutableなInterface参照では省略する。
`interface.scope_ref`は、そのDescriptor observationで得たscope Objectのoptionalな`ref`である。観測に存在する場合はコピーし、存在しない場合は省略する。Scope Objectを別途観測して得た新しい`ref`だけを古いDescriptorへ付け替えてはならない。紐付きが確認できなくなった場合はInterface observationを取得し直す。
`arguments`はOperation parameterに対応する名前付きrecordである。引数がない場合は空のmapを送る。
Hostは、現在その主体へ公開しているObjectとInterfaceに基づいてOperationを解決する。複数のfailure条件が同時に成立する場合は、次の順で最初のcodeだけを返す。
1. `target.path`のObjectが公開されていない場合は`NotFound`。
2. 指定された`target.validator`が現在のObject validatorと一致しない場合は`ValidatorMismatch`。
3. `interface.reference`が、現在そのObjectで公開されている`Object.interfaces`に`(scope, name)`の完全一致で含まれない、対象pathへのInterface scopeの祖先制約(self-scopeを含む)に違反する、または指定された`interface.scope_ref`が現在のscope Objectの`ref`と一致しない場合は`InterfaceMismatch`。指定があるのに現在の`ref`がない場合も一致しない。`scope_ref`の省略だけではこのfailureにしない。
4. 現在のInterfaceがvalidatorを持つにもかかわらず`interface.validator`が省略されている場合は`InterfaceValidatorRequired`。
5. 指定された`interface.validator`が現在のInterface validatorと一致しない場合は`InterfaceValidatorMismatch`。現在のInterfaceがvalidatorを持たない場合にvalidatorが指定されたときも一致しないものとする。
6. `operation`が現在のInterface Descriptorに宣言されていない場合は`OperationNotFound`。
7. HostがObject validatorを要求するOperationに`target.validator`が指定されていない場合は`ValidatorRequired`。
8. `arguments`が現在のOperation declarationに適合しない場合は`InvalidArguments`。
9. 宣言されたOperationの存在を公開しつつ、その主体による実行を認めない場合は`PermissionDenied`。
指定されたObject validatorまたはInterface validatorの不一致は、現在のDescriptorに基づくOperation lookupとarguments validationより先に判定する。これによりClientは、古いObjectまたはschemaに基づくrequestを`OperationNotFound`や`InvalidArguments`として扱わず、対応する観測を更新できる。
Hostは一つのlinearization pointで、現在のObject identityと状態、Object validator、Objectが公開するInterface参照、scope Objectとの紐付きと指定された`scope_ref`、Interface validator、Interface Descriptor、Operation implementation、実行対象を解決し、各preconditionを確認して、そのcallが使用する観測を固定する。ObjectまたはInterfaceが後から変化しても、callの途中で別のscope Object、Descriptor、Operation implementation、実行対象へ解決し直してはならない。
Argumentsとresultのvalidationは、固定したInterface Descriptorの同じOperation declarationを使用する。Authorizationも固定したObject、Interface、主体のcontextに対して評価する。
Hostが`target.validator`をbackendへのcommit条件として使用する場合、validatorの比較とcommitをatomicに行う。Responseの`validator`は、Operation完了後にHostがそのObjectについて発行する現在のvalidatorである。Operationが何を行ったか、またはApplication dataを変更したかをvalidatorの有無や値から推測しない。Object validatorを提供しない場合は省略する。
Validatorの比較はopaque bytesの完全一致で行う。Clientはvalidatorの生成方法または内容に基づいて分岐してはならない。
HostはOperationをInterfaceから除外して存在を非公開にすることも、Interfaceへ含めたうえで実行時に`PermissionDenied`として拒否することもできる。
Clientはresultを、requestの`interface`が指定し、Hostがpreconditionを確認して固定したOperation return typeに従って検証する。Validationに失敗したsuccess responseはHostから返されたProtocol failureではなく`InvalidResponse`として扱い、そのvalueをApplicationへ渡さない。
Host Runtimeがimplementation resultのencodeまたはvalidation failureを送信前に検出し、Operationのeffectがcommitされていないと保証できる場合は`Internal`を返す。Operationが未実行またはrollback済みであることを保証できず、validなsuccess responseも返せない場合は`OperationOutcomeUnknown`を返す。
## Worldspace composition
Clientが扱う絶対pathとInterface scopeは、常に接続先Hostが公開するClient-facing Worldspaceのrootに基づく。Proxyや合成によってUpstream Worldspaceの公開位置が変わる場合、Hostが公開先とUpstreamの座標を対応付ける。Upstreamのrootやmount構成をClientに追跡・補完させない。
Upstreamのrootを`/github`へ配置する例:
| 項目 | Upstream | 公開先 |
| --- | --- | --- |
| Root | `/` | `/github` |
| Object path | `/my-rep/issues` | `/github/my-rep/issues` |
| Interface参照 | `{ scope: "/", name: "issue.read" }` | `{ scope: "/github", name: "issue.read" }` |
| Repository scope | `/my-rep` | `/github/my-rep` |
| `entry` | `/my-rep/issues/123` | `/github/my-rep/issues/123` |
別のUpstreamを`/gitlab`へ配置した場合、その`{ scope: "/", name: "issue.read" }`は`{ scope: "/gitlab", name: "issue.read" }`となる。同じlocal nameでもscopeによって区別される。さらに全体を`/services`へ配置するHostは、`/github`を`/services/github`へ変換する。Worldspaceの配置を変えないHTTP proxyはscopeを変更する必要がない。HTTP endpointのpath prefixとWorldspace pathを混同しない。
### 座標の変換
合成Hostは次の対応を一貫させる。
- `observe`のrequest pathをUpstreamへ変換する。ResponseのObjectの`name`とchildrenは公開先pathの規則を満たすようにする。特にUpstream rootの空の`name`を、非rootのmount先へそのまま公開しない。
- `Object.interfaces`の各構造化参照のscopeを公開先へ変換し、公開先での祖先制約と名前の一意性を保つ。
- `fetch_interface`を適切なUpstreamと構造化参照へ対応付ける。Responseの`interface`はClientが要求した公開先の参照にする。
- `call_operation`のtarget pathとInterface参照をUpstreamへ対応付ける。Local name単独で転送先を選ばない。
- `scope_ref`は公開先scope Objectの`ref`と一致させる。Upstreamのscope Objectが持つ`ref`との対応をHostが保持し、公開先で指定されたpreconditionを検証してからUpstreamへ対応付ける。Opaqueな`ref`へpath prefixを機械的に加えたり、指定された条件を転送時に落としたりしない。公開先scope Objectが`ref`を持たない形態も許可する。
- Descriptorで`entry`と宣言されたargumentsをUpstreamへ、resultを公開先へ変換する。Named type、record、list、unionの内部にある`entry`も同じ規則で扱う。
通常の`string`、opaqueな`json`、documentation内のpathらしい文字列を推測して書き換えない。Application固有の別の座標系はApplication adapterの責務とする。
公開subtree外のscopeや`entry`に対応付けがない場合、Upstream pathをそのまま公開したり、scopeを削除・rootへ付け替えたりしてはならない。部分合成の対応付け方針と詳細な拒否条件は未確定だが、失敗時も既存のProtocol failure契約を守る。特にUpstreamでeffectがcommit済みのOperationのresultを変換できない場合、未実行を保証するerrorへ置き換えない。Validなsuccessを返せず、未実行またはrollbackを保証できない場合は`OperationOutcomeUnknown`として扱う。
### 同一性と観測整合性
- 公開先の同じ有効な`(scope, name)`とvalidatorを、異なるDescriptorへ対応付けない。Validatorのない参照のDescriptorは、その参照が有効な間immutableとする。
- 異なるUpstreamのObjectへ同じ有効な公開`ref`を割り当てない。
- Routingやmountの変更後に、古いObject validatorで別の対象へOperationを実行できないようにする。
- Upstream validatorをそのまま使用するか、Host側で対応付け・wrapするかは、これらの保証を満たす範囲でHostが決める。
- Scopeが変わればProtocol上は別のInterface参照である。Clientに移動前後の同一性を推測させない。
同じUpstream Objectを複数のscope配下へ配置すると、公開するInterface集合が配置ごとに変わり得る。異なる公開representationへ同じ`ref`とvalidatorを無条件に引き継げない。共通祖先scopeでの共有やprojectionごとのidentity割当等の選択は[未解決事項](draft/unresolved-issues.md)に残す。Renameやmount変更では新しいscopeを別参照として扱い、同じpathでのscope Objectの差し替えには上述のoptionalな`scope_ref`の確認を適用する。Clientによる移動追跡は要求しない。これらは既存のvalidator・実行中callの一貫性を弱めない。
## Protocol responseとfailure
Core interactionは、成功responseまたはProtocol failureを返す。
```rust
enum ProtocolResult<T> {
Success(T),
Failure(ProtocolError),
}
struct ProtocolError {
code: ProtocolErrorCode,
message: String,
}
enum ProtocolErrorCode {
InvalidRequest,
NotFound,
InterfaceNotFound,
InterfaceMismatch,
OperationNotFound,
InvalidArguments,
ValidatorRequired,
ValidatorMismatch,
InterfaceValidatorRequired,
InterfaceValidatorMismatch,
PermissionDenied,
ResourceLimitExceeded,
Internal,
OperationOutcomeUnknown,
}
```
`message`は人間向けのboundedな診断であり、Clientが分岐に使用する安定した値ではない。Clientは`code`を使用する。Hostは秘密情報、非公開Object、backend path、credential、内部stack traceをmessageへ含めない。
各interactionが返してよいcodeは次に限定する。
| Code | `observe` | `fetch_interface` | `call_operation` |
| --- | --- | --- | --- |
| `InvalidRequest` | ✓ | ✓ | ✓ |
| `ResourceLimitExceeded` | ✓ | ✓ | ✓ |
| `NotFound` | ✓ | — | ✓ |
| `InterfaceNotFound` | — | ✓ | — |
| `InterfaceMismatch` | — | — | ✓ |
| `InterfaceValidatorRequired` | — | — | ✓ |
| `InterfaceValidatorMismatch` | — | — | ✓ |
| `OperationNotFound` | — | — | ✓ |
| `ValidatorRequired` | — | — | ✓ |
| `ValidatorMismatch` | — | — | ✓ |
| `InvalidArguments` | — | — | ✓ |
| `PermissionDenied` | — | — | ✓ |
| `Internal` | ✓ | ✓ | ✓ |
| `OperationOutcomeUnknown` | — | — | ✓ |
Hostがinteractionに許可されていないcodeを返した場合、Clientはそのerror responseを`InvalidResponse`として扱う。
Validatorおよび観測関連errorの回復対象は次のとおりである。
- `NotFound`では、Clientは対象Entryをstaleとして扱い、必要に応じて親を再観測する。
- `InterfaceNotFound`では、ClientはそのInterface observationを使用せず、参照元Objectを再観測できる。
- `ValidatorRequired`または`ValidatorMismatch`では、Clientは対象Objectを再観測する。
- `InterfaceMismatch`では、Clientは対象Objectを再観測し、現在公開されているInterface参照の集合を取得する。Scopeの紐付きが変わった可能性もあるため、使用したInterface observationを新しいcallへ再利用せず、必要なら`fetch_interface`で再取得する。
- `InterfaceValidatorRequired`または`InterfaceValidatorMismatch`では、Clientは同じInterface参照を`fetch_interface`で再観測し、現在のDescriptorに基づいてOperationを再構築する。
同じOperationを再試行するなら、先に新しい観測を取得しなければならない。
- `InvalidRequest`または`InvalidArguments`では、Clientは同じrequestを変更せずに再送しない。
- `ResourceLimitExceeded`では、Clientはlogical request shapeやobservation depth等を削減できるが、`call_operation`を自動再実行しない。
`call_operation`が`OperationOutcomeUnknown`以外のProtocol failureを返した場合、HostはOperationのeffectがcommitされていないことを保証する。`OperationOutcomeUnknown`は、HostがOperationの未実行またはrollbackを保証できず、validなsuccess responseも返せないことを示す。Clientは対象Objectをstaleとして扱い、Operationを自動再実行せず、再観測またはdomain固有の照会手段を利用者へ提示する。
`Internal`はHost内部のfailureであり、`call_operation`ではeffectがcommitされていないと保証できる場合だけ返す。Clientは`Internal`を理由にOperationを自動再実行しない。Transport timeoutやconnection lossでresponse自体を受け取れない場合もOperation outcomeは不明になり得るが、それはProtocol failureではなくTransport failureとして扱う。
Objectを主体へ公開しない場合、HostはそのObjectをClient-facing Worldspaceに存在しないものとして扱い、`observe`と`call_operation`へ`NotFound`を返す。Objectの存在を秘匿する目的で`PermissionDenied`を返してはならない。
`PermissionDenied`は、現在公開されているInterfaceに宣言されたOperationについて、その存在を公開しつつ実行を認めない場合に使用する。Operation自体をInterfaceから除外している場合は`OperationNotFound`を返す。Operation argumentが参照するdomain resourceの不存在やbusiness rule failureを、対象Objectの`NotFound`へ変換しない。
`InvalidResponse`と`UnsupportedDescriptorFormat`はClient-localなclassificationであり、`ProtocolErrorCode`ではない。前者はsuccess responseまたはerror responseがProtocol contractに違反した場合、後者はvalidなDescriptor formatをClientが実装していない場合に使用する。
Protocol failureとdomain failureを混在させない。現在のProtocolでは、Domain failureをApplicationのOperation resultに含まれるUnion caseとして明示する。専用のfailure envelopeは定義しない。
Transport BindingはProtocol Resultをstatus code、response envelope、stream termination等へ対応付ける。Authentication、route、media type、framing、response不達等のTransport failureをProtocol errorへ変換しない。
## Resource limit
HostとClientは、untrusted inputによるresource exhaustionを避けるため、少なくとも次へ上限を設ける。
- Pathとして解釈するstring、validator、name、description、documentationの長さ。
- Interface Descriptor内のdeclaration、documentation、field等の総量。
- ObjectごとのInterface参照数。
- Type declaration、Operation、parameter、field、caseの数。
- TypeExprとValueのnesting depth。
- Record field数とList要素数。
- `observe`のdepthとresponse node数。
- Protocol error messageの長さ。
各実装はこれらにboundedな上限を設定する。Request fieldだけで判定できる上限違反は、interaction固有のtarget resolutionまたはOperation dispatchより前に`ResourceLimitExceeded`として拒否する。Hostが取得responseを構築する途中でresponse node数やlogical collection size等の上限超過を検出した場合も、success responseの代わりに`ResourceLimitExceeded`を返してよい。
`call_operation`では、dispatch後にresultのlogical size、validation、backend budget等の問題を検出しても、effectがcommitされていないと保証できる場合だけ`ResourceLimitExceeded`または`Internal`を返す。未実行またはrollbackを保証できない場合は`OperationOutcomeUnknown`とする。Transport implementationまたはdeploymentのmessage-size上限、response不達等はProtocol failureへ変換せず、Transport failureとして扱う。
## Transport Bindingの要件
Transport Bindingは、この章のlogical structについて次を定義する。
- Requestとresponseのframing。
- Protocol primitiveのconcrete encoding。
- Object、Descriptor、Valueのencoding。
- Optional fieldの省略。
- Protocol failureのmapping。
- Object validator、Interface validator、Operation preconditionのmapping。
Transport固有のmessage size、timeout、concurrency、security、connection policyはTransport実装とdeploymentの責務であり、BindingがWIP Protocolとして値またはnegotiationを定義する必要はない。複数のinteractionを並行実行またはmultiplexする場合も、それぞれは独立したrequestとresponseを持つ。
Transport Bindingは、logical typeの区別またはvalidation semanticsを失ってはならない。
+185
View File
@@ -0,0 +1,185 @@
# 4. クライアント
## 役割
Clientは、Hostが提供するWorldspaceを利用する実装主体である。WIP Protocolを利用しつつ、Objectが参照するInterface集合を解決し、用途に応じたView、Tool、API等の上位インターフェースへ投影する。
ClientはHostから得た情報をinteractionごとに破棄するだけでなく、rootからindexable edgeを辿って構成したエントリツリーと、必要に応じて選択したエントリを保持し、上位層にとっての**既知の空間**を構成する。
## Known Space
Known Spaceは、Clientが**現在保持しているエントリの集合**である。過去に発見したすべてのエントリを単調に蓄積する集合ではない。
Known SpaceとDiscoverable SpaceはClientが過去の観測から構築したcacheであり、Host上の現在状態を同期したreplicaではない。HostはClientのcache状態を追跡または更新しない。
Clientは、観測結果が同じeffective Worldspace viewに属する範囲でcacheを共有する。接続先、認証主体、tenant、その他のsecurity contextによって異なるWorldspaceが公開され得る場合、それらのKnown Space、Object observation、Interface cacheを混在させない。具体的なcache namespaceの構成はClientおよびTransport Bindingに委ねる。
次のような経路で得られたエントリをKnown Spaceへ追加できる。
- `observe`で取得したエントリ
- Operation結果の`entry`型pathを`depth = 0`で`observe`して取得したエントリ
Operationを通して発見されたpathは、必ずしもKnown Spaceへ追加する必要はない。Clientは現在の結果として一時的に扱うことも、取得後に必要なエントリだけを選択して保持することもできる。
既知エントリ間の移動や参照はClient側で処理できる。既知の空間を辿るたびにHostへ問い合わせる必要はない。
## 取得・保持・提示の分離
ユーザーやAIがpathを知っていること、ClientがObjectを観測・保持していること、Clientがindexable edgeも観測・保持していることは別の状態である。
ClientはKnown Spaceにない有効なpathも取得対象にできる。過去に観測していないことだけを理由に取得を拒否せず、現在のHostの公開状態に従って`observe`する。取得前に仮のObjectをKnown Spaceへ登録する必要はない。Pathを知っていること自体は公開可視性や実行権限を保証しない。
観測したObjectのどの情報を提示するかは、上位インターフェースの責務である。ClientがObject情報を保持していても、配置だけを提示する場面ですべての説明やInterfaceを展開する必要はない。提示済みの情報とcache内容を同一視しない。
Objectを観測・保持したことだけでは、そのpathを既知のtreeへ接続するindexable edgeを得たことにはならない。Treeの構成には観測済みのedgeを使い、pathのprefixや階層関係からedgeを推測しない。
## Entry TreeとObject observation
Clientは、観測したObjectをcanonical path上のEntryへ対応付け、観測済みのindexable child edgeとともに保持できる。具体的なcache表現は規定しない。Clientは`ObjectObservation`に近い入れ子構造またはforestを保持しても、path index、flatなObject store、Entry Treeへnormalizeしてもよい。
EntryのpathとchildrenはWorldspace上の配置contextであり、Object identityそのものには含まれない。同じObjectが複数のEntryから観測される場合、Clientはshared record、複製したsnapshot、その他の内部表現を使用できる。ただし、保存形式によって`ref`、validator、Object representationのconsistencyを変えてはならない。
- 同じ有効な`ref`を持つ観測は、同じWorldspace内の同じObjectを表す。Clientは一つのObject stateを共有しても、Entryごとにsnapshotを保持してもよい。
- `ref`がないこと、または異なることから、複数のEntryが同じObjectか異なるObjectかを判断しない。Clientはrepresentationが同じという理由だけでObject identityを推測しない。
- Objectの`interfaces`はmembershipをsetとして扱い、同じ参照の重複を`InvalidResponse`として拒否する。ClientはHostの立項順を保持し、InterfaceやOperationの提示順に使用してよい。
- 同じ`ref`と同じvalidator valueを持つObjectを再観測したとき、validatorが存在するにもかかわらずObject representationが異なる場合、Clientはresponseを`InvalidResponse`として扱う。同じ`ref`でvalidatorが異なる場合、またはvalidatorが存在しない場合は、同一Objectの新しい観測として扱い、最後に受理した成功観測をその`ref`の現在状態とする。
このcache表現はClient内部の実装であり、Objectと別のWIP resourceまたはProtocol上のEntry resourceを導入するものではない。
## Interface cache
Interface参照は`{ scope, name }`のrecordであり、同じeffective Worldspace view内の`(scope, name)`の完全一致で比較する。Local nameだけ、表示用の短縮表記、JSON serializationのbyte列をcache keyにしない。Scopeが異なれば別参照であり、Host内部のUpstream座標へ読み替えない。
Clientは各Objectの配置pathに対して[scopeの祖先制約](2.1-object-tree-schema.md#interface-scope)を検証する。参照の解決には完全なscopeとnameを使い、祖先Objectの順次取得、同名Interfaceの探索、shadowingを導入しない。Scopeの適合だけでInterface membershipやauthorizationを推測しない。
Objectの公開情報はOperation定義を直接含まず、Interface参照のordered setを持つ。Clientは必要な参照を`fetch_interface`で解決し、取得したInterface Descriptorとoptionalなvalidatorを観測としてcacheする。
```rust
struct CachedInterface {
scope_ref: Option<String>,
descriptor: InterfaceDescriptor,
validator: Option<Vec<u8>>,
}
```
Clientは同じ有効期間内でvalidatorが存在する観測を`(interface, validator)`の組で識別し、Interface参照ごとに最後に成功した観測をcurrent entryとして保持できる。Validatorが存在しないInterface参照は、その参照が有効な間Descriptorがimmutableなので、参照だけをcache keyに使用できる。
`scope_ref`は`fetch_interface`が返すscope Objectのoptionalな`ref`であり、参照の第三成分ではなく観測metadataとして保持する。観測に存在する場合はcallの`interface.scope_ref`へコピーし、なければ省略する。Clientはscope Objectや祖先を追加取得する必要がない。Scope Objectを別途観測して紐付きを確認できなくなった場合は、古いDescriptorの`scope_ref`だけを更新せず、Interface observationを取得し直す。
Scope Objectがそのpathから消えたことを確認したClientは、そのscopeのInterface cacheを破棄できる。Scope Objectの`ref`が以前の紐付きを確認できない値へ変わった場合や`ref`がなくなった場合も、旧観測を新しいcallへ使用しない。有効期間が終了した旧観測と、同じpathへ再公開された新しい観測を区別する。Scopeの移動先を追跡してcacheを付け替えない。
`ref`のないscopeが同じpathで差し替わる場合、Clientがそのidentityや有効期間の切り替わりを追跡できないことは仕様上許容される。欠落を不正なresponseとしたり、疑似identityを生成したりしない。得られた情報によるDescriptor・validatorの検証は引き続き行うが、検出不能な差し替えの防止は保証しない。
削除の確認とClient-localなcache evictionを混同しない。Indexable edgeが一覧から消えただけでは、直接観測可能な非indexable Objectとして存続している可能性があり、scope Objectの削除を断定しない。削除の即時検出、監視、全Interfaceの先行再取得は要求しない。破棄可能なcacheであっても、実行中callの結果検証に必要なDescriptorは完了まで保持する。
一つのInterface Descriptorに宣言されたOperationの集合が、そのInterfaceのOperation一覧である。Objectは複数のInterfaceを組み合わせられ、複数のObjectも同じInterfaceを共有できる。
ClientはDescriptorが必要なInterface参照とcacheを比較し、未知のInterfaceだけを取得する。Objectの`interfaces`からDescriptor interestを先行して導出しても、Operationを選択した時点で必要な参照だけを取得してもよい。Interface validator mismatch等で更新が必要になった参照は再取得する。複数の`fetch_interface`が必要な場合、それぞれを個別のrequestとして並行実行できる。
異なるInterfaceで同じOperation名が存在しても、Clientは構造化Interface参照とOperation名の組で識別する。UIやAI Toolのqualified表示は[Text Viewの参照表記](4.2-text-view.md#参照表記)に従う。Operation callではsignature全体をparseせず、Interface参照とOperation名を別fieldとして送る。
実行中のOperationが使用するDescriptor observationは、resultのvalidationが完了するまで保持する。新しいvalidatorのDescriptorを観測しても、古いvalidatorで開始済みのcallに必要なDescriptorを早期に破棄しない。
## Discoverable Space
Clientは、Hostから`observe`で取得したindexableなエントリツリーをDiscoverable Spaceとして保持する。Discoverable Spaceとして扱う間、Clientはその観測済みtreeの構造を保持する必要がある。
Discoverable SpaceはKnown Spaceの部分集合であり、AI向けの`tree`など、indexableな配置の探索に利用する。Worldspace rootまたはClientが選択した起点からindexable edgeで到達できる、materialize済みの範囲によって構成される。
Clientが`observe`結果をmergeし、`children`が存在するparent nodeから以前のindexable edgeが返されなくなった場合、その子エントリをDiscoverable Spaceから除外する。その結果、保持しているどの起点からも到達不能になったsubtreeは、その場で破棄できる。ただし、別の経路から到達できるエントリ、Clientがpinしたエントリ、実行中のOperationが参照しているエントリは引き続き保持してよい。`children`が省略されたnodeはedge削除の根拠にしない。
Operationを通して発見されたpathは、それだけではDiscoverable Spaceへ追加しない。そのエントリを起点として`depth > 0`で`observe`した場合に、そのindexableな範囲をDiscoverable Spaceへ追加できる。
## 一時的な発見とpin
Clientは、Operation Resultの`entry`型として発見したpathを、そのObjectを即時に観測せず、現在の結果として提示・利用できる。結果のpathを提示することと、Objectを取得することは別であり、発見したすべてのpathの即時取得は要求しない。
Objectの公開情報が必要になった時点で、Clientは必要なpathを観測できる。対象Objectだけが必要なら`depth = 0`を使う。Client policyとして先行取得してもよく、その場合はOperationのreturn typeからentry pathを抽出して重複を除去し、保持済みの観測と比較して必要な取得を行う。複数の`observe`が必要な場合、それぞれを個別のrequestとして並行実行できる。
一時的に取得したエントリは、結果の利用が終わった時点で破棄してよい。
後続のInteractionでも必要なエントリは、Client側でpinしてKnown Spaceへ保持できる。PinはClient内部の保持方針であり、Host上のObject lifetimeやWIP Protocol上の状態を変更しない。
単純なClientは発見したエントリをすべて保持してもよいが、WIPはそれを必須としない。具体的な容量制限やeviction algorithmはClient実装に委ねる。
## Objectとchildrenの観測
Clientは`observe(path, depth)`を使い、対象Objectだけが必要なら`depth = 0`、indexableな配下も必要なら正のdepthを指定する。一つのinteractionでrequested depthまでを取得するため、Clientが各nodeについてdata-dependentな取得requestを逐次発行する必要はない。
Response nodeの`children`が省略されている場合、そのnodeのchildrenは未観測であり、空であるとは扱わない。`children`が存在する場合、その一覧は完全であり、空arrayならindexable childが存在しない。Clientは起点pathと各Objectの`name`からchild pathを導出する。`children`が存在するnodeをcacheへmergeするときは、そのEntryで観測済みのdirect child edgeをresponseの完全な一覧で置き換える。`children`が省略されたnodeは構造について新しい情報を持たないため、既存edgeを削除したり、観測済みの一覧を未観測へ戻したりしない。
件数の非常に多い集合の個々の要素は、通常indexableな子エントリとして露出しない。個別要素は対象の集合を扱うObjectの`query`や`list`等のOperationを通して発見することを想定する。
## Interestと取得計画
Clientの上位層は、特定pathについて必要な観測depth、または特定Interface参照についてDescriptorが必要であることを、Client-localな**interest**として示せる。InterestはProtocol dataではなく、Hostはその状態を追跡しない。Interestの追加または削除は、保持済み観測のProtocol上の意味や有効性を変更しない。Interestがなくなった観測を保持するかevictするかはClient policyに委ねる。
Clientはinterestを、現在有効なObject observation、children coverage、Interface Descriptor observation、および進行中の取得requestと比較し、不足するlogical interactionだけを発行できる。要求されたdepthまでのcoverageがすでに存在する場合、または進行中のrequestによって満たされる場合、同じinterestの調整処理は重複requestを発行しない。Depth境界で`children`が省略されたnodeは、その配下に対する正のdepthのinterestを満たさない。
Clientは、interestを満たすためのidempotentな調整と、既存の観測を置き換える明示的な再観測を区別できる。明示的な再観測は、現在有効な観測または同じ対象の進行中requestがあっても新しいrequestを発行し、それ以前のrequestをsupersedeしてよい。
Clientは、保持する観測、進行中request、並列度、request rateへboundedな上限を設ける。上限によって新しい取得を開始できない場合、interest自体をProtocol failureとして扱わず、別のrequestの完了、容量の解放、またはinterestの変更後に再評価できる。具体的な優先順位、並行実行、prefetch範囲、fairness、cache容量、eviction algorithmはClient実装に委ねる。
Clientは、上位層から直接示されたinterestに加えて、観測したObjectのInterface参照やindexable child edgeから次のinterestを先行して導出してよい。WIPは全量取得、暗黙の再帰展開、または特定のprefetch範囲を要求しない。取得計画とevictionは、interestの変更や明示的な再観測がないまま同じ観測を繰り返し破棄して再取得する、自己維持的なloopを作ってはならない。
複数の取得requestを並行実行するClientは、各responseを元のrequestとそのeffective Worldspace viewへ対応付ける。Clientがある観測状態のownershipを後続requestへ移した場合、supersedeされたrequestのresponseでその状態を上書きしない。Overlapping observationを部分的にmergeする場合も、後続requestが所有するObjectまたはchild coverageを以前のresponseで置き換えない。Success、Protocol failure、Transport failure、またはlocal cancellationによって終了したrequestを、進行中として残してはならない。
`observe`または`fetch_interface`の失敗を自動的に再試行するかはClient policyに委ねるが、retry回数、間隔、並列度はboundedでなければならない。取得requestのretry policyを`call_operation`の再実行へ適用せず、OperationについてはProtocolのfailure semanticsに従う。
## Objectの同一性
`ref`は、同じ主体に公開された一つのWorldspace内で、複数の観測またはエントリが同じObjectを表すことを示すoptionalなopaque markerである。
同じ有効な`ref`は同じObjectを保証する。異なるまたは欠落した`ref`からObjectが異なるとは判断しない。Clientは異なるWorldspaceの`ref`を比較せず、`ref`を永続的なprimary keyまたはNavigationに使用しない。
Backend UIDが適している場合、Hostはそれを直接または変換して`ref`に利用できる。ただし、Clientは`ref`がbackend UIDであること、またはsessionやHost restartをまたいで安定することを仮定しない。
## 観測整合性
Clientは、Hostが提供した`ref`とvalidatorの保証範囲で、観測とOperation実行の整合性を管理する。Scopeの`ref`や対象Objectのvalidatorがない場合に、pathだけから観測時と同じObjectであると保証してはならない。Optional metadataの欠落自体は有効な公開形態であり、identity追跡不能を理由にProtocol違反としない。
Objectが`validator`を持つ場合、Clientは観測したpathとvalidatorを対応付ける。条件付きOperationでは両者をtargetとしてHostへ渡し、validatorの比較と実行をHost上でatomicに行わせる。ValidatorはAIやユーザーに直接管理させず、Transport BindingでもProtocol struct上のfieldとして保持する。HTTP ETag等のrepresentation metadataをWIP validatorの代用として解釈しない。
ClientはOperationに選択したInterface参照と、`fetch_interface`で観測したoptionalな`scope_ref`およびInterface validatorもrequestへ含める。MutableなInterfaceがvalidatorを返す場合は必須preconditionとして使用し、`InterfaceValidatorMismatch`ではInterfaceだけを再観測する。`InterfaceMismatch`ではObjectを再観測して現在のInterface集合を取得し、scopeの紐付きが変わった可能性もあるため、使用したInterface observationも無効化して必要なら再取得する。
Operation responseが新しいObject validatorを返した場合、Clientは対応するObject observationへ反映する。
## Failure handling
ClientはHostが返すProtocol errorの`code`で回復actionを決定し、人間向け`message`やTransport statusからcodeを推測しない。各interactionで許可されていないcode、schemaに違反するsuccess / error response、同じvalidatorに矛盾するrepresentationは`InvalidResponse`として拒否する。Validだが未対応のInterface Descriptor formatは`UnsupportedDescriptorFormat`として区別する。
- `NotFound`では対象Entryをstaleとして扱い、必要に応じて親を再観測する。
- `InterfaceNotFound`では参照元Objectを再観測できる。
- `InterfaceMismatch`では対象Objectを再観測し、使用したInterface observationも無効化して必要なら再取得する。
- `InterfaceValidatorRequired`または`InterfaceValidatorMismatch`では対象Interfaceを再観測する。
- `ValidatorRequired`または`ValidatorMismatch`では対象Objectを再観測する。
同じOperationを再試行するなら、先に新しい観測を取得しなければならない。
- `InvalidRequest`または`InvalidArguments`では同じrequestを変更せずに再送しない。
- `ResourceLimitExceeded`ではlogical request shapeやobservation depth等を削減できるが、Operationを自動再実行しない。
- `PermissionDenied`では公開済みOperationが実行拒否されたことを示し、Object自体が閲覧禁止で存在するとは推測しない。
`OperationOutcomeUnknown`では、Operationが未実行またはrollback済みであることが保証されない。ClientはOperationを自動再実行せず、対象Objectをstaleとして再観測するか、domain固有の照会手段を提示する。`Internal`、Transport timeout、connection lossの場合もOperationを自動再実行しない。Response不達はProtocol errorへ変換せず、Transport failureとOperation outcomeが不明な状態をClient内部で保持する。WIPはOperationをObject stateへのeffectで分類してretry可否を推測しない。
ClientがOperation結果の`entry`を付随的に取得する場合、その取得の成否は元のOperationの成否と分けて扱う。参照先の`observe`が失敗しても、成功済みOperationを失敗扱いしたり、そのOperationを再実行したりしてはならない。取得失敗は、その参照先への別のinteractionの失敗として提示・処理する。
HTTP authentication failureとWorldspace endpoint全体のaccess denialはTransport layerのfailureであり、WIPの`PermissionDenied`へ変換しない。
## 想定実装
Clientは用途に応じて複数の上位インターフェースを提供できる。
- GUI上のWorldspace View
- [AI Tools](4.1-ai-tools.md)
- PTC / REPL向けスクリプティングAPI
これらは同一のKnown Space、Discoverable Space、Object identity、観測整合性管理を共有してよい。
PTC / REPL bindingでは、Operationを通常のメソッド呼び出しへ投影してよい。
```python
issue.close(reason="duplicate")
```
この場合もClientがInterfaceとOperationを解決して`call_operation`を利用し、Object identityと観測整合性を管理する。AI Toolsの3操作は、そのAI向けインターフェースの契約であり、PTC / REPLの呼び出し構文を制限しない。
+107
View File
@@ -0,0 +1,107 @@
# 4.1. AI Tools
## 目的と適用範囲
AI Toolsは、WIP ClientがAIへ提供する、対象の配置を見て、できることを理解し、操作するための上位インターフェースである。本章で定義するAI向けの基礎ツールは **tree / inspect / invoke** の3操作に固定する。
Protocol interactionをそのままToolとして公開するものではない。Protocolの`observe` / `fetch_interface` / `call_operation`は[3. Protocol](3-protocol.md)、取得・cache・観測整合性の責務は[4. Client](4-client.md)に従う。GUIやPTC / REPLなど、他のClientインターフェースの構成は本章で制限しない。
## 基本的な利用モデル
| AI向け操作 | AIが行うこと | Clientが引き受けること |
| --- | --- | --- |
| `tree(path, depth)` | indexableな対象の配置を見る | 必要なdepthの観測とtree表示 |
| `inspect(path)` | 対象の説明とできることを知る | Object観測、Interface集合の解決、signature生成 |
| `invoke(target, interface, operation, arguments)` | 選択した操作を名前付き引数で呼ぶ | 引数検証、観測との対応、validator、Protocol requestと結果の処理 |
これらはProtocol interactionとの一対一対応ではない。特に`inspect`の対象はInterface参照ではなく、Objectを指すpathである。必要な`observe`と複数の`fetch_interface`はClient内部で行う。
Inspectで見つけたOperationも`invoke`から呼び出し、Operationごとに新しいToolを登録しない。
大量のObjectの発見は、各ドメインのObjectが公開する`list` / `search` / `query`等のOperationで行う。件数制限、検索条件、ページング等は、そのドメインに応じて各Operationが管理する。AIはこれらのOperationも`inspect`で確認し、`invoke`で呼び出す。発見方法が増えても基礎ツールは追加・変更しない。
## `tree`: indexableな配置を見る
`tree(path, depth)`は、起点をdepth 0として、指定depth内のindexableなツリーを全量取得・表示する。
- 必要なcoverageを持つ観測を再利用できる。不足する場合はClientが`observe`で取得する。
- 全量とは、指定した起点・depth・主体に公開されたindexableな範囲の全量であり、Worldspace全体ではない。
- 名前、path、親子構造を中心に提示する。すべてのObjectのDescriptorやドメインデータを展開しない。
- Protocolのdepth境界にある未観測のchildrenと、観測済みの空のindexable childrenを混同しない。
- 指定depth内の一部を黙って省略して完全なtreeとして返さない。既存のresource limit内で成立しない場合は、その失敗を伝える。
- Cacheに保持されたpathを単にprefixで集めてtreeにしない。Hostから観測したindexable edgeに従い、指定depth外やOperation経由で知っただけのentryを混ぜない。
HostがTreeへ公開する範囲は、[Indexable childrenの条件](2.1-object-tree-schema.md#indexable-children)に従う。
## `inspect`: 対象の説明とできることを知る
`inspect(path)`は、そのpathで公開されているObjectの説明と操作を返す。
AIがそのpathをtree、会話、Operation結果のいずれから知ったかは、事前条件にしない。ClientがまだObjectを観測していなくても、有効なpathを直接指定できる。Known Spaceにないことだけを理由に拒否せず、現在のHostの公開状態に従って観測する。
Clientは次の情報を保持済みの観測から解決し、不足するものを取得する。
1. 対象pathのObject。対象だけが必要なら`observe`の`depth = 0`を使用する。
2. Objectが公開するInterface参照のordered set。
3. 表示対象のInterfaceに対応する、取得・検証済みのDescriptor。
Clientはこれらの情報から、Objectの説明とInterfaceごとの操作を提示する。AIに「先にObjectをobserveして、Interface参照を抜き出し、一つずつfetchする」という手順を要求しない。一方で、Objectの本文やドメインmetadataまで暗黙に読み込むわけではない。それらの取得は対象のOperationとして扱う。
### 表示に必要な情報
- 対象のpathと、公開されているObjectの説明。
- InterfaceとOperationの対応。
- Operation名、短い用途説明、名前付き引数、必須性、引数・結果の型。
- 操作を理解するために必要な名前付き型・複合型の定義。
生のDescriptor JSONを返すだけではなく、検証済みの情報から読みやすいObject signatureとInterface signatureを生成する。各signatureの表記は[4.2. Text View](4.2-text-view.md)に従う。同名Operationが複数Interfaceに存在しても、一つへ平坦化したり、出所を失ったりしない。
Invokeへ渡すInterface参照とOperation名は区別して保持し、表示textを再parseして呼出先を決めない。Interfaceの未展開や取得失敗を「操作が存在しない」と誤表示しない。
Signatureの配置や、詳細表示・表示対象を選択する具体的なTool引数は本章で固定しない。個々のsignatureを生成するときの全宣言の表示規則はText Viewに従い、表示側でInterface内部を切り詰めない。
## `invoke`: 選んだ操作を実行する
`invoke(target, interface, operation, arguments)`は、対象ObjectのInterfaceとOperationを明示して呼び出す。`target`は対象Objectのpathを指定するものであり、AIにProtocolのvalidator付きTargetを組み立てさせるものではない。
`arguments`はOperationのparameter declarationに従う名前付きfieldのrecordであり、位置引数ではない。引数を持たないOperationには空のrecordを渡す。
Interfaceを明示するのは、同じObjectの複数Interfaceに同名Operationがあり得るためである。Interfaceの選択をAIへ示すことと、Descriptorの取得手順をAIに管理させることは別である。
選択するInterfaceは絶対scopeとlocal nameの組で識別する。Adapterが[Text Viewの短縮表記](4.2-text-view.md#参照表記)を入力として受ける場合も、入力境界で構造化参照へ変換し、Operation名は独立して保持する。Scope省略には明示contextが必要であり、対象pathからの推測や相対scopeの解決はしない。Protocolへ短縮表記のstringをそのまま送らない。
ClientはObjectとDescriptorの観測を保持し、引数を検証し、path、Object validator、Interface参照とvalidator、および観測に存在する`scope_ref`をProtocol requestへ設定する。AIにvalidatorやscope Objectの`ref`の取得・コピー・更新をさせない。同一性管理やvalidatorはsignatureに含めない。`ref`のないscopeではidentityによる差し替え検出ができないことを許容し、AIへ代替identityの指定を要求しない。
Pathを知っていることやInterfaceが表示されたことは、実行権限を意味しない。Hostは呼出時の公開状態、権限、capability、実行条件を引き続き検証する。ClientはresultをDescriptorに従って検証してから提示する。
再観測とOperationの再実行を同一視しない。Stale、取消、拒否、結果不明などの扱いはProtocolと[ClientのFailure handling](4-client.md#failure-handling)に従う。結果不明のOperationを自動再実行せず、Operation名からread / writeやretry可否を推測しない。
## Pathの発見と移動
AIはtree、会話で示されたpath、Operation結果の`entry`などから次の対象を知ることができる。Pathを知っていること、ClientがObjectを観測・保持していること、indexable edgeも観測していることは別の状態である。[Clientの取得・保持・提示の分離](4-client.md#取得保持提示の分離)に従い、未観測のpathは必要になった時点でinspectできる。
Clientが内部で保持する情報とAIに表示済みの情報は一致する必要がない。Treeの取得でObject情報を保持していても、AIには名前と構造だけを見せ、詳しい説明と操作はinspectで提示できる。
Operation結果の`entry`型pathは、そのまま次の対象への参照として提示できる。すべての結果を即時に観測したり、再帰展開したりすることを必須にしない。AIが必要なpathを選んでinspectするときに、Clientが観測できる。
Entryの抽出はDescriptorの型に従う。普通のstringを内容だけからpathと推測しない。結果のentryを観測・保持しても、それだけでindexable edgeを追加しない。
Clientが付随的なentry取得を行う場合、その取得失敗と元のOperationの成否を混同しない。成功済みOperationを、結果の参照先が取得できなかったために失敗扱いして再実行させてはならない。
## Tool bindingと表示
本章の小文字表記は操作の呼称であり、providerごとのTool登録名の大文字・小文字やProtocol名の接頭辞は規定しない。AI providerがTool schemaを要求する場合は、Client adapterがこの3操作のschemaを提供する。Hostにprovider固有のTool schemaを提供させたり、取得したDescriptorごとにToolを追加登録したりしない。DescriptorはClient内部のOperation validationとsignature生成に用いる。
Treeの具体的なtext layout、`depth`等の引数のdefault、inspectの表示選択引数、Tool response envelopeについて、本章は共通の具体schemaを定義しない。各bindingは、それらを提供するときにAIへ明示する契約として定め、本章の操作の意味を変えない。Text Viewもsignatureを包むresponse構造や配置を定義するものではない。
取得・表示・cache保持を分離するが、特定の内部class構成、eviction algorithm、prefetch schedulerは要求しない。
## 検証する利用シナリオ
- 観測が空のClientで既知pathを直接inspectし、説明と操作を取得できる。
- Treeが指定depthのindexable範囲を完全に提示し、cache内の他のpathを混ぜない。
- 複数Interfaceに同名Operationがあっても、正しいInterfaceとOperationを選んでinvokeできる。
- Search / list結果から必要なentryだけをinspectでき、全件先行取得を要求しない。
- 非indexableなentryを観測した後も、rootからのtreeにそのentryが混入しない。
- 参照先の取得失敗を、成功済みOperationの失敗や再実行の理由にしない。
- 表示やcache利用によって、権限・観測整合性・結果不明の扱いを弱めない。
+228
View File
@@ -0,0 +1,228 @@
# 4.2. Text View
## 位置付け
WIP Text Viewは、ObjectとInterfaceをAIおよび人間へ提示するためのプレーンテキスト表示形式である。ClientのTool結果、CLI、GUIのテキスト表示などで共通に利用できる。
この章では、一つのObjectの公開情報を表す **Object signature** と、一つのInterfaceの型・Operation・documentationを表す **Interface signature** の表記を定義する。ここでのsignatureは表示上のまとまりを指し、暗号学的な署名やidentityではない。
どのObjectやInterfaceを表示するか、各signatureをどこにどの順序で並べるか、同時に提示するかは表示側が決める。本章はsignatureを包む共通document構造、ObjectとInterfaceの組み合わせ、Tool response envelopeを定義しない。
データの正本は[3. Protocol](3-protocol.md)のObjectおよびInterface Descriptorであり、Text Viewは検証済みの情報から生成する表示である。
- Interfaceの交換形式、手書きのschema言語、Objectのserializationではない。
- Parser、round-trip、表示textからのDescriptor復元は要求しない。
- Operationの実行言語ではない。Clientは保持するInterface参照とOperation名を別々に扱い、表示textを再parseしてProtocol requestを構築しない。
- Textの一致をObject identity、Descriptorの同一性、validator、digestの代用にしない。
- HostへText Viewの生成・配信を要求せず、Transport BindingやAI providerのTool schemaとは独立させる。
## 表示の入力と責務
Object signatureは検証済みObjectと、分かる場合はそのObjectを観測したpathから生成する。PathはObjectのfieldではなく配置contextである。Object signatureの生成にInterface Descriptorは必要ない。
Interface signatureは、Interface参照と、それに対応する検証済みかつ対応formatのInterface Descriptorから生成する。Interface参照はDescriptor自身のfieldではない。Interface signatureの生成にObjectは必要ない。
観測、Descriptor取得、cache、表示対象の選択、rendererの呼び出しAPIや内部structは本章で規定しない。表示は観測時点の公開情報であり、最新状態、Operationの実行権限、実行成功を保証しない。
Object tree全体の描画、Operation arguments / resultのvalue表示、Protocol errorや診断metadataの表示は本章の対象に含めない。
## 参照表記
Interface参照のscopeとlocal nameの境界は`::`、InterfaceとOperationの境界は`#`で表す。
| 対象 | 短縮表記 |
| --- | --- |
| 完全なOperation指定 | `/github::issue.read#read` |
| 完全なInterface参照 | `/github::issue.read` |
| Scope省略のOperation指定 | `issue.read#read` |
| Root scopeのOperation指定 | `/::issue.read#read` |
`/github::issue.read#read`は`scope = "/github"`、`name = "issue.read"`、`operation = "read"`の組を示す。対象Objectのpath、arguments、validatorを含まず、request全体の代わりにはならない。Protocolでは構造化Interface参照とOperation名を別fieldに保持し、この連結stringをwireやcache keyに使用しない。
### 成分の引用
- Scopeはcanonical absolute pathであり、文字列全体が`[A-Za-z0-9_./-]+`に一致する場合は引用せず表示できる。
- Interface名とOperation名は、文字列全体が`[A-Za-z0-9_.-]+`に一致する場合は引用せず表示できる。
- それ以外は成分全体を二重引用符で囲み、JSON stringのescape規則を使用する。単純な成分も引用してよい。
- `::`と`#`は引用符の外側でだけ境界となる。区切りの前後に空白を出力しない。
```text
"/github::archive"::issue.read#read
/github::"issue#read"#read
/github::issue.read#"read#raw"
"/開発"::"課題.read"#read
"/github"::"issue.read"#"read"
```
最後の例は`/github::issue.read#read`と同じ成分を表す。引用やescapeは元の値を変えず、ProtocolへASCII制限や禁止文字を追加しない。Unicode normalization、case変換、URL encodingをしない。制御文字等には[名前と文字列](#名前と文字列)の安全なescape規則も適用する。境界を読む場合は引用とescapeを考慮し、単純な文字列splitを使用しない。
### Scope省略とcontext
Scopeの相対pathは、Protocol dataにも短縮表記にも認めない。`github::issue.read`や`../github::issue.read`を相対scopeとして解決しない。Scopeを明示するなら、引用の有無にかかわらずcanonical absolute pathを使う。
`issue.read#read`やInterfaceだけの`issue.read`は、scopeが別途明示されたcontextでのみ使える省略形である。Scopeの省略はroot scope `/`の指定とは異なる。Contextがなければ解決できず、rootへのdefault、対象Objectのpathからの推測、祖先の同名Interfaceの探索は行わない。Scopeが確定してもmembership、祖先制約、authorizationは検証する。
短縮表記を入力として受けるかはToolやUIが決める。受ける場合は専用の入力境界で成分を検証し、構造化参照とOperation名へ変換する。Signature全体を再parseして呼出先を決めることとは区別し、`invoke`を単一string引数の操作へ変更しない。
### Signature内の参照
Object signatureとInterface signatureでは、公開先の絶対scopeを含む完全な短縮表記を生成し、その全体をJSON stringとして引用表示する。外側の引用はsignatureの文字列表記であり、成分の引用とは別の層である。例えばscope `/github`、name `issue#read`の参照は`"/github::\"issue#read\""`と表示する。Scopeを省略したり、Upstreamの参照へ戻したりしない。
## Object signature
```text
object at "/issues/123" {
name: "123";
description: "Issue 123の公開操作。本文はOperationで取得する。";
interfaces: ["/::issue.read", "/::issue.lifecycle"];
}
```
Objectは`object at "<path>" { ... }`と表示する。Pathが分からない場合は`object { ... }`とし、`name`や`ref`からpathを推測しない。
Block内は次の順に表示する。
1. `name: "...";`にObjectの`name`をそのまま表示する。自由なtitleへ置き換えない。
2. `description`がある場合は`description: "...";`で表示する。Object全体の説明を通常のpropertyとして示す。
3. `interfaces: ["...", "..."];`に全Interface参照をHostの立項順で表示する。空集合は`interfaces: [];`と明示する。
Descriptionがない場合はそのpropertyを省略し、存在する空文字列は`description: "";`と表示する。説明の欠落からObjectの用途を補完しない。
Rootは`object at "/"`かつ`name: "";`となり、scope `/`のInterfaceを公開できる。Interface参照は[参照表記](#参照表記)に従い絶対scopeを含め、別の参照やlocal nameだけへ置き換えない。Scope Objectの`ref`やvalidatorを`@...`として参照へ付け足さず、`scope_ref`はClient runtimeの観測・precondition metadataとして扱う。
`ref`とvalidatorはObject signatureへ含めない。同一性管理と条件付き実行はClient runtimeの責務のままである。Objectの本文やdomain metadataを推測してpropertyとして追加しない。
ChildrenもObject signatureに含めない。表示にchildrenがないことは、未観測または観測済みの空集合を意味しない。
## Interface signature
```text
/// "Issueの内容と関連項目を読む。"
interface "/::issue.read" {
type Relation = enum { parent, sibling, related };
type RelatedItems = {
/// "同じWorldspace内の関連項目。"
items: [entry],
next_cursor?: string,
};
/// "本文を取得する。"
operation read_body() -> { body: string };
/// "関係を指定して関連項目を取得する。"
operation get_related_items(
relation: Relation,
limit?: integer,
) -> RelatedItems;
}
```
Interfaceは`interface "<reference>" { ... }`と表示する。`<reference>`は構造化参照から生成した完全な短縮表記であり、前述の規則で全体を引用表示する。
Block内では、Descriptorの全`types`を宣言順に、続いて全`operations`を宣言順に表示する。Record field、parameter、enum / union caseもすべて元の順序で表示する。この順序はsignature内部の表示規則であり、Protocol上の名前解決やvalidationを変更しない。
Interface signatureでは宣言の折りたたみや抜粋、budgetによる省略を行わない。型を`...`へ置き換えたり、field、parameter、caseを削ったりしない。DescriptorにtypeもOperationもない場合は空のblockとなる。
異なるInterfaceの宣言を一つのnamespaceへ平坦化しない。同名Operationも各Interfaceのblock内に表示する。Blockの参照が出所を示すため、Operation名を改名したり、参照をOperation名へ連結したりしない。
### 型の表記
| DescriptorのTypeExpr | 表示 |
| --- | --- |
| Unit / Boolean / Integer / Number | `unit` / `boolean` / `integer` / `number` |
| String / Bytes / Json / Entry | `string` / `bytes` / `json` / `entry` |
| Named | 参照先typeの名前 |
| Record | `{ field: Type, optional_field?: Type }` |
| List | `[Type]` |
| Enum | `enum { first, second }` |
| Union | `union { empty, found(Type) }` |
Named type declarationは`type Name = Type;`とする。Enum、unionを含むすべてのTypeExprを、Descriptorで現れる位置にそのまま表示する。Named typeは同じInterface Descriptor内の宣言を参照し、別Interfaceの同名typeとは混同しない。
`?`は`required = false`のfieldまたはparameterにだけ付ける。Nullable、default value、一般化したoptional型は意味しない。`unit`とoptionalの違いを維持する。
Payloadのないunion caseは`empty`、payloadがあるcaseは`found(Type)`とする。`empty`と`empty(unit)`を区別する。
```text
type CloseResult = union {
closed,
blocked({ dependencies: [entry] }),
};
```
空record、空enum、空unionが検証済みDescriptorに現れる場合は、それぞれ`{}`、`enum {}`、`union {}`と表示し、caseを補わない。
型の意味、numeric range、validationはProtocolに従い、表示仕様で再定義しない。特に`entry`はWorldspaceのpath型であり、`string`へ置き換えない。Unionのwire discriminatorやbytesのwire encodingはInterface signatureに混ぜない。
### Operationの表記
```text
operation get_related_items(
relation: Relation,
limit?: integer,
) -> {
items: [entry],
next_cursor?: string,
};
operation refresh() -> unit;
```
Operationは`operation Name(parameters) -> ReturnType;`とする。Parameterは名前付きであり、並び順から位置引数を導入しない。引数がない場合は`()`、return typeは`unit`を含め必ず表示する。
Operation名からread / write、idempotency、retry可否、domain failure、実行可能状態を推測してannotationを付けない。Domain failureがreturn typeのUnionとして宣言されていれば、そのUnionを通常どおり表示する。
## 名前と文字列
Path、Object name、Interface参照、説明文は、二重引用符で囲んだJSON stringのescape規則を使って表示する。これは文字列を安全に区切るための表記の借用であり、signature全体をJSONにするものではない。Unicode normalization、case変換、pathのURL encodingを行わない。
Type名、Operation名、parameter名、field名、case名は次のように表示する。
- `[A-Za-z_][A-Za-z0-9_]*`に一致し、下記の表示keywordでなければ、そのまま表示する。
- それ以外はbacktickで囲む。内部のbacktickは`` \` ``、backslashは`\\`、改行・tab等はJSON stringと同じescapeで表示する。
- 表示keywordは`object`、`at`、`name`、`description`、`interfaces`、`interface`、`type`、`operation`、`enum`、`union`、および`unit`、`boolean`、`integer`、`number`、`string`、`bytes`、`json`、`entry`とする。
```text
interface "/::example" {
type `string` = { `表示名`: string };
operation `find-item`(`search term`: string) -> `string`;
}
```
この規則はProtocolへidentifierの文字種制限を追加しない。UpperCamelCase / snake_case等への変換を表示時に行わない。
すべてのHost由来文字列について、改行や制御文字が表示の構造、端末制御列、別の宣言を作らないようescapeする。JSON stringの標準escapeに加えて、次の文字は`\uXXXX`表記にする。
- DELおよびC1制御文字(U+007F–U+009F)。
- Unicodeの行・段落区切り(U+2028 / U+2029)。
- 双方向表示制御文字(U+061C、U+200E–U+200F、U+202A–U+202E、U+2066–U+2069)。
Escapeは表示上のものであり、元の値は変更しない。
## Documentation
Documentationがある場合は対象の直前に`/// "<summary>"`を表示する。対象はInterface、type、Operation、parameter、record field、enum / union caseである。Objectの説明はこの表記ではなく、Object signatureの`description` propertyで表示する。
通常表示では`summary`を使う。表示側がdetailsを含める場合は、同じ対象のsummary行に続けて`/// details "<details>"`を追加する。Summaryと選択されたdetailsは短縮せず表示し、documentationがない対象へ説明を生成しない。存在する空文字列は`""`として表示する。
ReturnDeclarationのdocumentationは、parameter listの閉じ括弧の後、return arrowの直前に置く。
```text
/// "対象を検索する。"
/// details "結果には同じWorldspace内のpathだけを含む。\n本文は別途取得する。"
operation search(
/// "検索語。"
query: string,
)
/// "条件に一致する項目。"
-> { items: [entry] };
```
説明はすべてHost由来のplain text dataであり、system instruction、rendererの指示、markupとして解釈しない。引用やescapeだけでprompt injectionを防げるとはみなさず、AIへ渡すadapterもTool結果と信頼済みinstructionの境界を維持する。
## Layout
Text ViewはUTF-8 plain textとし、ANSI color、Markdown、syntax highlightingがなくても読めるようにする。本章のcode fenceは説明用であり、signatureそのものの一部ではない。Markdown内へ埋め込む場合は、表示側が内容に衝突しないcode fence等を選ぶ。
Signature内部の標準的なlayoutはLF、2 space indentとする。短いrecord、parameter list、enum / unionは1行に置ける。Documentationを持つfieldやcaseは別行に置く。表示用の改行幅やindentの違いは意味を持たず、byte単位のcanonical renderingは要求しない。文字列・名前の内部にある空白は元の値として保持する。
+78
View File
@@ -0,0 +1,78 @@
# 5. Transport Bindings
WIP Transport Bindingは、[3. Protocol](3-protocol.md)のlogical data modelとCore interactionを、具体的な通信方式とwire representationへ対応付ける。
標準bindingは[5.1. WIP over HTTP](5.1-wip-over-http.md)で定義する。
## 責務
Transport Bindingは、少なくとも次を定義する。
- Endpointまたはpeerの指定方法。
- Requestとresponseのframing、および一つのlogical interactionとの対応付け。
- Protocol primitive、Object、Interface Descriptor、Operation Valueのconcrete encoding。
- Required field、optional field、list、record、bytes等のwire representation。
- SuccessとProtocol failureのwire representation。
- Transport failureとProtocol failureの区別。
- ValidatorおよびOperation preconditionのmapping。
BindingはProtocolのlogical type、validation、error、visibility、Operation executionの意味を変更してはならない。Binding固有のstatus、header、stream event等を根拠として、新しいProtocol failureまたは回復actionを暗黙に追加してはならない。
## Logical interactionとphysical exchange
一つのCore interactionは、一つ以上のphysical messageを使用して実装できる。ただし、ClientとHostは各successまたはProtocol failureを、元のlogical requestおよびそのeffective Worldspace viewへ一意に対応付けられなければならない。
複数のCore interactionは、それぞれ独立したrequestとresponseを持つ。Clientはそれらを並行実行でき、Transport実装は複数のexchangeを一つのconnection上でmultiplexできる。WIPは複数のinteractionを包む共通request / response envelope、transaction、共通snapshot、request間のdata dependencyを定義しない。
## Effective Worldspace view
Endpoint、認証主体、tenant、capability、その他のdeployment contextによって、同じHostが異なるClient-facing Worldspaceを公開できる。
Clientは異なるeffective Worldspace viewのObject observation、Interface observation、validator、Known Spaceを混在させない。Transport connectionの再利用やmultiplexingによって、この分離を失ってはならない。
どのauthentication方式または通信securityを使ってviewを確立するかは、Transport実装とdeploymentの責務であり、WIPは標準化しない。
## Protocol failureとTransport failure
Hostがlogical interactionを受理して返す`ProtocolError`はProtocol failureである。Transport Bindingは、これを機械可読なwire representationへ対応付ける。
次はProtocol failureではない。
- Endpointまたはrouteへ到達できない。
- Transport implementationまたはdeploymentがrequestを拒否する。
- Unsupportedなmedia type、framing、content coding、Transport version。
- Connection loss、timeout、response不達。
- Intermediaryが生成した、validなProtocol error representationを持たないfailure。
- Clientがresponseを受信またはdecodeできない。
ClientはTransport failureから`ProtocolErrorCode`を推測しない。Transport authentication failureを`PermissionDenied`へ、未知routeを`NotFound`へ、timeoutを`Internal`または`OperationOutcomeUnknown`へ変換しない。
Protocol上の`OperationOutcomeUnknown`はHostが返すProtocol failureである。Response不達によってClient側でOperation outcomeを確定できない状態はTransport failureであり、Hostから同codeを受信したことにはしない。
## Dispatchとreplay
Transport Bindingは、physical exchangeの再送によって一つのlogical `call_operation`を複数回dispatchしてはならない。
RequestがHostへdispatchされた可能性のある状態でresponseを取得できなかった場合、Clientは`call_operation`を自動再実行しない。Connection close、timeout、local cancellationは、Host上の実行またはcommitを取り消した証拠ではない。
取得interactionを再送するか、どのtimeoutやretry policyを使用するかはClientとTransport実装の責務である。取得用policyを`call_operation`へ適用しない。
## Validator
Object validatorとInterface validatorはProtocol structのlogical fieldであり、Bindingはそれらをlosslessにencodeする。
Transport固有のcache validator、conditional request metadata、message identifier等は、WIP validatorと独立している。それらを理由にProtocol bodyのvalidatorを省略せず、ClientもTransport metadataをWIP validatorとして解釈しない。
## Transport運用との境界
通信scheme、TLS、peer authentication、proxy、gateway、HTTP cache、compression、connection管理、message size、timeout、concurrency、rate limit等はTransport実装またはdeploymentの関心であり、WIPは値、negotiation、運用profileを定義しない。
それらの設定によってrequestまたはresponseを交換できない場合はTransport failureである。HTTP status等がProtocol errorのcanonical mappingと偶然一致しても、validなProtocol error representationを受信しない限りProtocol failureへ変換しない。
WIPのlogical struct、observation completeness、TypeExpr / Value、Host execution等に対する`ResourceLimitExceeded`は[3. Protocol](3-protocol.md)で定義する。Transport implementation自身のmessage-size、timeout、queue、connection等の上限を同codeで包まない。
## Versioning
各Bindingは、wire互換性へ影響するmajor versionの選択方法を定義する。異なるmajor version間でroute、message、fieldを推測変換しない。
ProtocolまたはInterface Descriptorのversionと、Transport Bindingのversionは別である。Transport major versionが同じでも、Interface Descriptorの`format`は独立して検証する。
+715
View File
@@ -0,0 +1,715 @@
# 5.1. WIP over HTTP
WIP over HTTPは、[3. Protocol](3-protocol.md)のlogical data modelをHTTP request / responseとJSONへ対応付ける標準Transport Bindingである。[5. Transport Bindings](5-transports.md)の共通要件に従う。
## 目的と範囲
このbindingは、次のCore interactionをHTTPへ対応付ける。
- `observe`
- `fetch_interface`
- `call_operation`
HTTP version、communication scheme、TLS、authentication方式、connection reuse、multiplexing、proxy、gateway、cache、Host内部のframework、backend接続方式は規定しない。HTTP実装とdeploymentは、それらをWIPとは独立して選択する。複数のCore interactionが必要な場合も、それぞれを独立したHTTP request / responseとして交換する。
OperationがWebSocket等のendpointをApplication dataとして返すことは妨げない。そのendpoint上のprotocol、状態、lifecycleはWIPの規定対象外である。
## Endpoint
WIP endpointは、一つのWorldspace access surfaceを指すabsolute hierarchical base URLである。Endpoint URLのpath prefixはdeploymentが任意に選択できる。Clientはorigin直下、`/wip`等の既定prefix、またはWorldspace pathとの対応を仮定しない。
Endpoint URLは次を満たす。
- HTTP実装がrequest先として使用できるabsolute URLである。
- Authorityとhostを持つ。
- Queryとfragmentを持たない。
- Pathが`/`で終わるcanonical formである。
利用者から末尾`/`のないURLを受け付ける実装は、routeを解決する前に`/`を追加してcanonical formへ変換する。URL schemeと通信securityの選択はHTTP実装とdeploymentの責務であり、このbindingは制約しない。
各routeはendpoint URLをbase URLとして、次のrelative referenceをRFC 3986に従って解決する。すべてのinteractionは`POST`を使用する。
| Interaction | Method | Relative reference |
| --- | --- | --- |
| `observe` | `POST` | `v1/observe` |
| `fetch_interface` | `POST` | `v1/fetch_interface` |
| `call_operation` | `POST` | `v1/call_operation` |
`v1`はWIP over HTTP Bindingのmajor versionである。Interactionごとに独立したrouteを設定したり、固定suffixを別名へ置き換えたりしない。
例えば、次のendpointを指定した場合、
```text
http://gateway.example/tenants/acme/filesystem/wip/
```
`observe` routeは次になる。
```text
http://gateway.example/tenants/acme/filesystem/wip/v1/observe
```
Endpointのpath prefixはHTTP deployment上のroutingであり、Worldspace内のpathではない。Worldspace pathはrequest bodyのlogical fieldとして渡す。標準route URLはqueryを持たず、query付きURLまたは追加path segmentを同じrouteとして扱わない。
Clientは異なるcanonical endpoint間でKnown Space、Object observation、Interface observation、validatorを混在させない。同じendpointでも認証主体、tenant、capability、その他のsecurity contextによって異なるWorldspaceが見える場合、それらを異なるeffective Worldspace viewとして分離する。
## HTTP request metadata
標準routeへのrequestは次を満たす。
- Methodは`POST`。
- `Content-Type`は`application/json`または`application/json; charset=utf-8`。
- BodyはUTF-8のJSON text。
- 標準Clientは`Accept: application/json`を送る。
- Request URLは標準routeと完全に一致し、queryを持たない。
Media typeとparameter名の比較はHTTPの規則に従ってcase-insensitiveに行う。`charset`を指定する場合は`utf-8`だけを許可する。同じparameterの重複、UTF-8以外のcharset、その他のmedia type parameterを標準bindingでは使用しない。
`Accept`がないrequestをHostは受理してよい。`Accept`がある場合はHTTP content negotiationの規則を適用し、`application/json`に対するeffective quality valueが正である場合だけJSON responseを返す。
すべての実装はHTTP identity content codingを扱える。Requestまたはresponseのcontent codingはoptionalなHTTP機能であり、本bindingは特定の圧縮方式を必須にしない。Hostが対応しないrequest content codingはTransport-levelの`415 Unsupported Media Type`として拒否する。Clientが対応しないresponse content codingはTransport failureとして扱う。
## Routeとmetadataのfailure
Route、method、content negotiation、Transport authenticationはlogical WIP requestの外側にある。次のresponseはProtocol error envelopeを要求しない。
| 条件 | HTTP response |
| --- | --- |
| 未知route、query付きroute、追加path segment | `404 Not Found` |
| 既知routeへの`POST`以外のmethod | `405 Method Not Allowed`と`Allow: POST` |
| Unsupportedな`Content-Type`またはrequest content coding | `415 Unsupported Media Type` |
| JSON responseを拒否する`Accept` | `406 Not Acceptable` |
| Transport authenticationが必要 | `401 Unauthorized`、必要なら`WWW-Authenticate` |
| Endpoint access surface全体へのaccess拒否 | `403 Forbidden` |
Clientはこれらを`InvalidRequest`、`NotFound`、`PermissionDenied`等へ変換しない。
標準route、method、media type、authenticationが受理された後、bodyがmalformed JSONである、logical requestへdecodeできない、required fieldがない、未知fieldがある、またはfield semanticsに違反する場合、Hostは`400 Bad Request`と`invalid_request`のProtocol error envelopeを返す。
HTTP server、proxy、gateway、またはClientの運用設定によってmessage size、timeout、rate等の上限に達した場合はTransport failureである。HTTP statusが後述するProtocol error mappingと同じでも、validなProtocol error envelopeを持たないresponseをProtocol failureへ変換しない。
## HTTP response
各interactionが成功した場合、Hostは`200 OK`を返し、response bodyへ対応するProtocol responseを直接encodeする。Success bodyを`result`等の共通envelopeで包まない。
Protocol failureの場合、Hostは非2xx statusを返し、bodyへ本章のProtocol error envelopeをencodeする。
WIP Hostが生成するbody付きresponseは`Content-Type: application/json`または`application/json; charset=utf-8`を持つ。Protocol successまたはProtocol failureのbodyはUTF-8 JSON textである。
HTTP statusは大分類とHTTP infrastructureのための補助情報であり、Protocol failureの意味を決定しない。Clientはstatus、reason phrase、人間向けmessageから`ProtocolErrorCode`を推測しない。
非2xx responseが、正しいContent-TypeとvalidなProtocol error envelopeを持たない場合、ClientはTransport failureとして扱う。これにより、proxyやgatewayが生成したresponseをHost由来のProtocol failureと誤認しない。
次は`InvalidResponse`である。
- `200 OK`以外の2xx statusにvalidなsuccess bodyがある。
- 2xx statusにProtocol error envelopeがある。
- 非2xx statusにvalidなsuccess bodyがある。
- Success bodyまたはProtocol error envelopeがschema、request correlation、interactionごとのcode制約に違反する。
## JSON共通規則
JSON encodingには次の規則を適用する。
- JSON textはUTF-8でencodeする。
- RecordはJSON object、listはJSON arrayで表す。
- Field名と列挙stringはcase-sensitiveである。
- 同じJSON object内のduplicate memberを拒否する。
- 本章がschemaを定義するobjectでは、許可していないmemberを拒否する。Descriptorの未知formatとTypeExpr `Json`のobjectには、それぞれ後述する例外を適用する。
- Required fieldは必ず存在する。
- Protocol structのoptional fieldはmemberの省略で表し、`null`で置き換えない。
- JSON objectのmember順に意味を持たせない。
- Protocol listのarray順は保持する。
- StringへUnicode normalizationを適用しない。
- JSON syntaxが表現できないNaNと正負Infinityを使用しない。
本章のschema表で`optional`と記していないfieldはrequiredである。
`Vec<u8>`はRFC 4648 Section 4のstandard base64 alphabetを使用し、必要な`=` paddingを含むcanonical base64 stringとしてencodeする。Whitespace、URL-safe alphabet、padding省略、同じoctet列に対する別表現を受理しない。
Path、Interface参照、Object identity、名前、validatorの意味はfieldの位置とProtocolの型によって決まる。
| Logical field | JSON encoding |
| --- | --- |
| Path / `entry` | canonical absolute Worldspace pathのstring |
| Object、Operation、Type、Field、Parameter、Case名 | string |
| Interface参照 | `InterfaceReference` object |
| `ref` | string |
| `format` | string |
| Object / Interface validator | canonical base64 string |
JSON serializationのwhitespace、escapeの選択、object member順をcanonical化しない。同じlogical valueがbyte-for-byte同一のJSONになることを要求しない。
## Interface Reference
`InterfaceReference`は次のexactなJSON objectとしてencodeする。
| Field | JSON type | Presence |
| --- | --- | --- |
| `scope` | canonical absolute Worldspace pathのstring | required |
| `name` | 空でないUnicode string | required |
```json
{
"scope": "/github",
"name": "issue.read"
}
```
`Object.interfaces`の各要素、`FetchInterfaceRequest.interface`、`FetchInterfaceResponse.interface`、`InterfaceTarget.reference`のすべてでこの形を使用する。Scopeの省略、relative scope、`null`、空name、追加member、旧形式のopaque string、`"/github::issue.read"`等の短縮表記stringは認めない。Requestでの違反は`400 Bad Request`と`invalid_request`、responseでの違反はClient-localな`InvalidResponse`とする。
参照の比較はJSON decode後の`scope`と`name`の完全一致であり、object member順やJSON escapeの違いは同一性に影響しない。ScopeへURL encoding / decodingを追加せず、name内の`::`や`#`も通常の文字として保持する。ScopeはWorldspace上のpathであり、HTTP endpointのprefixを加えたりscope用のHTTP routeを生成したりしない。合成によるscope変換は[ProtocolのWorldspace composition](3-protocol.md#worldspace-composition)に従う。
## ObjectとObjectObservation
`Object`は次のJSON objectとしてencodeする。
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `description` | string | optional |
| `interfaces` | `InterfaceReference` objectのarray | required |
| `ref` | string | optional |
| `validator` | canonical base64 string | optional |
例:
```json
{
"name": "123",
"description": "First item",
"interfaces": [
{ "scope": "/", "name": "item-interface-v1" },
{ "scope": "/", "name": "item-marker-interface-v1" }
],
"ref": "item:123",
"validator": "aXRlbS12YWxpZGF0b3I="
}
```
`interfaces`のarray順はHostの立項順を保持し、`(scope, name)`の完全一致による重複を含まない。各参照のscopeは観測pathに対してProtocolの祖先制約を満たす。
`ObjectObservation`は次のJSON objectとしてencodeする。
| Field | JSON type | Presence |
| --- | --- | --- |
| `object` | `Object` | required |
| `children` | `ObjectObservation`のarray | optional |
`children`の省略は未観測を、空arrayは観測済みで直接のindexable childが存在しないことを表す。Requested depthより浅いnodeでは`children`を必ず含め、depth境界のnodeでは省略する。Success responseはrequested depthまで完全であり、truncated markerやpartial successを返さない。
## Interface Descriptor
### Format discriminator
標準v1のInterface Descriptor formatは`wip-interface/1`である。このformatでは以下のJSON grammarを使用する。
ClientはDescriptor objectのstring `format`を先に読み、対応しないformatを`UnsupportedDescriptorFormat`として扱える。未知formatでもDescriptorはJSON objectであり、string `format`を必ず持つ。それ以外のmemberはそのformat固有であるため、Clientは`wip-interface/1`のschemaで存在、欠落、型を判定せず、内容をOperationへ使用しない。標準v1 Hostは`wip-interface/1`を返す。
### Documentation
`Documentation`は次のJSON objectである。
| Field | JSON type | Presence |
| --- | --- | --- |
| `summary` | string | required |
| `details` | string | optional |
### Descriptor root
`InterfaceDescriptor`は次のJSON objectである。
| Field | JSON type | Presence |
| --- | --- | --- |
| `format` | string。標準v1では`wip-interface/1` | required |
| `documentation` | `Documentation` | optional |
| `types` | `TypeDeclaration`のarray | required |
| `operations` | `OperationDeclaration`のarray | required |
`types`と`operations`のarray順はdeclaration順を保持する。
### TypeDeclaration
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `documentation` | `Documentation` | optional |
| `definition` | `TypeExpr` | required |
### TypeExpr
すべての`TypeExpr`はstring `kind`を持つJSON objectとしてencodeする。`kind`ごとの正確なmemberは次のとおりである。
| TypeExpr | `kind` | その他のmember |
| --- | --- | --- |
| Unit | `unit` | なし |
| Boolean | `boolean` | なし |
| Integer | `integer` | なし |
| Number | `number` | なし |
| String | `string` | なし |
| Bytes | `bytes` | なし |
| Json | `json` | なし |
| Entry | `entry` | なし |
| Named | `named` | required string `name` |
| Record | `record` | required `FieldDeclaration` array `fields` |
| List | `list` | required `TypeExpr` `items` |
| Enum | `enum` | required `EnumCase` array `cases` |
| Union | `union` | required `UnionCase` array `cases` |
例:
```json
{
"kind": "list",
"items": {
"kind": "named",
"name": "ItemSummary"
}
}
```
Primitive kindのobjectへ別memberを追加してはならない。例えばUnitは`{"kind":"unit"}`だけで表す。
### FieldDeclaration
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `required` | boolean | required |
| `documentation` | `Documentation` | optional |
| `type` | `TypeExpr` | required |
### EnumCase
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `documentation` | `Documentation` | optional |
### UnionCase
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `documentation` | `Documentation` | optional |
| `payload` | `TypeExpr` | optional |
`payload`の省略はpayloadを持たないcaseを表す。
### OperationDeclaration
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `documentation` | `Documentation` | optional |
| `parameters` | `ParameterDeclaration`のarray | required |
| `returns` | `ReturnDeclaration` | required |
### ParameterDeclaration
| Field | JSON type | Presence |
| --- | --- | --- |
| `name` | string | required |
| `required` | boolean | required |
| `documentation` | `Documentation` | optional |
| `type` | `TypeExpr` | required |
### ReturnDeclaration
| Field | JSON type | Presence |
| --- | --- | --- |
| `documentation` | `Documentation` | optional |
| `type` | `TypeExpr` | required |
Descriptorの名前一意性、Named参照、cycle、型整合性等は[3. Protocol](3-protocol.md)に従って検証する。JSON shapeが正しいことだけではvalidなDescriptorにならない。
完全なDescriptorの例:
```json
{
"format": "wip-interface/1",
"documentation": {
"summary": "Reads items and their relationships."
},
"types": [
{
"name": "Relation",
"definition": {
"kind": "enum",
"cases": [
{ "name": "parent" },
{ "name": "sibling" }
]
}
},
{
"name": "RelatedItem",
"definition": {
"kind": "union",
"cases": [
{
"name": "found",
"payload": { "kind": "entry" }
},
{ "name": "not_found" }
]
}
}
],
"operations": [
{
"name": "get_related_item",
"parameters": [
{
"name": "relation",
"required": true,
"type": {
"kind": "named",
"name": "Relation"
}
}
],
"returns": {
"type": {
"kind": "named",
"name": "RelatedItem"
}
}
}
]
}
```
## Operation Value
Operationのargumentsとresultは、選択したInterface Descriptorの`TypeExpr`に従ってJSONへencodeする。Raw `Value` variantを示す共通tagged envelopeは使用しない。
| TypeExpr | JSON encoding |
| --- | --- |
| Unit | `null` |
| Boolean | boolean |
| Integer | 小数部と指数部を持たないJSON number。`-9007199254740991`から`9007199254740991`まで |
| Number | finiteなJSON number |
| String | string |
| Bytes | canonical base64 string |
| Json | JSONの`null`、boolean、number、string、array、object |
| Entry | canonical absolute Worldspace pathのstring |
| Named | 同じDescriptor内の参照先TypeExprと同じencoding |
| Record | declarationされたfieldをmemberとするobject |
| List | items TypeExprに従うarray |
| Enum | 宣言されたcase名のstring |
| Union | `$case`と、必要なら`value`を持つobject |
`Json`内部でもnumberは相互運用可能なsafe integerまたはfinite binary64として扱い、Bytesの特別なencodingは適用しない。小数部と指数部を持たずsafe integer範囲にあるJSON numberはlogical `Integer`、それ以外のfiniteなJSON numberはlogical `Number`としてdecodeする。`Json`内のbase64らしいstringをBytesと推測しない。
RecordとOperation argumentsでは、`required = false`のfieldまたはparameterを省略できる。省略と`null`は異なる。TypeExprがUnit、Json、またはそれらへ解決されるNamedである場合、存在するmemberの`null`はその型のvalueであり、省略を表さない。それ以外の型で`null`を使用してはならない。
Record valueはdeclarationされていないmemberを持たない。Operation argumentsはparameter名をmember名とするJSON objectであり、位置引数を使用しない。引数のないOperationは空objectを使用する。
Enumはcase名だけをstringで表し、object envelopeを使用しない。
Payloadを持つUnionは次の形である。
```json
{
"$case": "found",
"value": "/items/123"
}
```
Payloadを持たないUnionは`value`を省略する。
```json
{
"$case": "not_found"
}
```
Union objectは`$case`と必要な`value`以外のmemberを持たない。Payloadありcaseでは`value`がrequired、payloadなしcaseでは`value`は禁止される。
ClientはDescriptorに従ってvalueをdecodeし、`entry`と宣言された位置のstringだけをWorldspace entryとして認識する。通常のStringやopaqueなJsonに含まれるpathらしいstringをentryとして推測しない。
## `observe`
### Request
`POST v1/observe`のbodyは次のJSON objectである。
| Field | JSON type | Presence |
| --- | --- | --- |
| `path` | canonical absolute Worldspace pathのstring | required |
| `depth` | `0`以上`4294967295`以下のinteger | required |
```json
{
"path": "/items",
"depth": 1
}
```
### Success response
Success bodyはrootの`ObjectObservation`を直接encodeする。
```json
{
"object": {
"name": "items",
"description": "Item collection",
"interfaces": [{ "scope": "/", "name": "item-collection-interface-v1" }],
"validator": "cm9vdC12YWxpZGF0b3I="
},
"children": [
{
"object": {
"name": "123",
"description": "First item",
"interfaces": [{ "scope": "/", "name": "item-interface-v1" }],
"ref": "item:123",
"validator": "aXRlbS12YWxpZGF0b3I="
}
}
]
}
```
Responseのpath、name、depth、children completenessはrequestと[3. Protocol](3-protocol.md)に従って検証する。完全なresponseをresource budget内で構築できない場合、Hostはpartial successではなく`resource_limit_exceeded`を返す。
## `fetch_interface`
### Request
`POST v1/fetch_interface`のbodyは次のJSON objectである。
| Field | JSON type | Presence |
| --- | --- | --- |
| `interface` | `InterfaceReference` object | required |
```json
{
"interface": { "scope": "/", "name": "item-interface-v1" }
}
```
### Success response
| Field | JSON type | Presence |
| --- | --- | --- |
| `interface` | `InterfaceReference` object | required |
| `scope_ref` | scope Objectの`ref`を表すstring | optional |
| `descriptor` | Interface Descriptor object | required |
| `validator` | canonical base64 string | optional |
```json
{
"interface": { "scope": "/", "name": "item-interface-v1" },
"scope_ref": "world:root",
"descriptor": {
"format": "wip-interface/1",
"types": [],
"operations": []
},
"validator": "aW50ZXJmYWNlLXZhbGlkYXRvcg=="
}
```
Responseの`interface`はrequestと完全一致しなければならない。`scope_ref`、Descriptor、validatorは一つのInterface observationとして扱う。`scope_ref`はscope Objectの`ref`と同じopaque stringであり、base64化しない。Scope Objectが`ref`を持つ場合は含め、持たない場合は省略する。`null`は使用しない。
Scope Objectに`ref`がない場合のvalidなresponse例:
```json
{
"interface": { "scope": "/", "name": "item-interface-v1" },
"descriptor": {
"format": "wip-interface/1",
"types": [],
"operations": []
}
}
```
`scope_ref`もvalidatorもない観測は許可される。Scopeのidentity追跡はできず、Descriptorはその参照の有効期間中immutableとして扱う。
## `call_operation`
### Request
`POST v1/call_operation`のbodyは次のJSON objectである。
| Field | JSON type | Presence |
| --- | --- | --- |
| `target` | `Target` object | required |
| `interface` | `InterfaceTarget` object | required |
| `operation` | Operation名のstring | required |
| `arguments` | 選択したOperation declarationに従うobject | required |
`Target`は次のfieldを持つ。
| Field | JSON type | Presence |
| --- | --- | --- |
| `path` | canonical absolute Worldspace pathのstring | required |
| `validator` | canonical base64 string | optional |
`InterfaceTarget`は次のfieldを持つ。
| Field | JSON type | Presence |
| --- | --- | --- |
| `reference` | `InterfaceReference` object | required |
| `scope_ref` | 観測したscope Objectの`ref`を表すstring | optional |
| `validator` | canonical base64 string | optional |
```json
{
"target": {
"path": "/items/current",
"validator": "b2JqZWN0LXZhbGlkYXRvcg=="
},
"interface": {
"reference": { "scope": "/", "name": "item-interface-v1" },
"scope_ref": "world:root",
"validator": "aW50ZXJmYWNlLXZhbGlkYXRvcg=="
},
"operation": "get_related_item",
"arguments": {
"relation": "sibling"
}
}
```
`interface.reference`と`operation`は別fieldである。表示用の`/::item-interface-v1#get_related_item`を一つのfieldへencodeしない。`scope_ref`も`InterfaceReference`のmemberや短縮表記の一部にはせず、`InterfaceTarget`の独立したoptional fieldとして送る。
Clientは`fetch_interface`の観測に`scope_ref`があればコピーし、なければ省略する。`scope_ref`に`null`や非stringを指定したrequestは`invalid_request`である。Validなstringを指定したが現在のscope Objectの`ref`と一致しない場合(現在の`ref`の欠落を含む)は、Protocolの検証順に従って`interface_mismatch`とする。省略時はscope identity preconditionを適用せず、それだけを理由に拒否しない。
HostはDescriptorに依存しない`target`、`interface`、`operation`のshapeを検証し、Protocolのerror precedenceに従って現在のObjectとInterface observationを固定した後、そのDescriptorに従って`arguments`をdecodeする。
### Success response
| Field | JSON type | Presence |
| --- | --- | --- |
| `result` | Operation return TypeExprに従うJSON value | required |
| `validator` | 実行後のObject validatorを表すcanonical base64 string | optional |
```json
{
"result": {
"$case": "found",
"value": "/items/123"
},
"validator": "bmV3LW9iamVjdC12YWxpZGF0b3I="
}
```
`result`はrequestで指定し、Hostがprecondition確認時に固定したOperation declarationのreturn TypeExprに従ってencodeする。
## Validator
Object validatorとInterface validatorは対応するJSON body fieldへcanonical base64としてencodeする。
- `Object.validator`
- `Target.validator`
- `FetchInterfaceResponse.validator`
- `InterfaceTarget.validator`
- `CallOperationResponse.validator`
WIP validatorを`ETag`、`If-Match`、その他のHTTP headerだけへ移してbody fieldを省略してはならない。Host、gateway、HTTP cacheは通常のHTTP representation metadataとして`ETag`等を追加してよいが、その値はWIP validatorと独立している。ClientはHTTP metadataをObject identity、Interface observation、Operation preconditionとして解釈しない。
## Protocol error envelope
各interactionのProtocol failureは、次のexactなJSON objectとしてencodeする。
```json
{
"error": {
"code": "invalid_arguments",
"message": "Required argument `relation` is missing"
}
}
```
Top-level objectは`error`だけを持つ。`error` objectは次のfieldだけを持つ。
| Field | JSON type | Presence |
| --- | --- | --- |
| `code` | 下表のstableなsnake_case string | required |
| `message` | boundedな人間向け診断string | required |
Clientは`message`で分岐しない。Hostは秘密情報、credential、非公開Object、backend path、内部stack traceをmessageへ含めない。
`ProtocolErrorCode`のwire spellingとcanonical HTTP statusは次のとおりである。
| Protocol error code | Wire spelling | HTTP status |
| --- | --- | --- |
| `InvalidRequest` | `invalid_request` | `400 Bad Request` |
| `NotFound` | `not_found` | `404 Not Found` |
| `InterfaceNotFound` | `interface_not_found` | `404 Not Found` |
| `InterfaceMismatch` | `interface_mismatch` | `409 Conflict` |
| `OperationNotFound` | `operation_not_found` | `404 Not Found` |
| `InvalidArguments` | `invalid_arguments` | `422 Unprocessable Content` |
| `ValidatorRequired` | `validator_required` | `428 Precondition Required` |
| `ValidatorMismatch` | `validator_mismatch` | `412 Precondition Failed` |
| `InterfaceValidatorRequired` | `interface_validator_required` | `428 Precondition Required` |
| `InterfaceValidatorMismatch` | `interface_validator_mismatch` | `412 Precondition Failed` |
| `PermissionDenied` | `permission_denied` | `403 Forbidden` |
| `ResourceLimitExceeded` | `resource_limit_exceeded` | `422 Unprocessable Content` |
| `Internal` | `internal` | `500 Internal Server Error` |
| `OperationOutcomeUnknown` | `operation_outcome_unknown` | `500 Internal Server Error` |
`resource_limit_exceeded`は、observation completeness、logical nesting、collection件数等、WIP Protocolが扱うlogical requestまたはresponseの上限超過を表し、`422 Unprocessable Content`を使用する。HTTP implementationやdeploymentのmessage-size上限超過をこのcodeへ変換しない。
Rate limitまたはdeployment concurrency limitによる`429 Too Many Requests`はTransport / deployment concernである。ValidなProtocol error envelopeがない限り`ResourceLimitExceeded`へ変換しない。
Hostはinteractionごとに[3. Protocol](3-protocol.md)で許可されたcodeだけを返す。ClientがvalidなProtocol error envelopeを取得した場合、HTTP statusがcanonical mappingと一致しなくても回復actionは`code`に従い、statusから別codeを推測しない。Status mismatchはBinding違反として診断してよい。
`InvalidResponse`と`UnsupportedDescriptorFormat`はClient-local classificationであり、Hostがerror envelopeで返してはならない。Domain failureもProtocol errorへ変換せず、Operation return typeに従うApplication dataとしてencodeする。
## HTTP authentication boundary
Authentication方式、credential、mTLS、proxy、gateway等はHTTP implementationとdeploymentの関心であり、本bindingは規定しない。
HTTP layerがWIP interactionを開始せずに返す`401 Unauthorized`または`403 Forbidden`は、validなProtocol error envelopeを持たないTransport failureである。Clientはそれらを`PermissionDenied`へ変換しない。
WIP interactionを開始した後のObject、Interface、Operation visibilityとauthorizationはProtocol semanticsに従う。Protocolの`PermissionDenied`はvalidなWIP error envelopeを持つ`403 Forbidden`であり、HTTP layerのaccess rejectionと区別する。
## HTTP replay
`3xx` response自体はWIP successまたはProtocol failureではなく、Transport failureである。Endpoint discovery、redirect、proxy routingをどう扱うかはHTTP implementationとdeploymentの責務である。
ただし、それらの機構によって一つのlogical `call_operation` requestを複数回dispatchしてはならない。Dispatch後にredirect responseを受け取った場合も、methodとbodyを維持して別endpointへ自動再送しない。
## Dispatchとretry
RequestがHostへ送信されていないとTransport実装が証明できるfailureはnot dispatchedである。送信開始後にresponseを取得できない場合、ClientはHostがrequestを受理した可能性を否定しない。
`observe`と`fetch_interface`を再送するか、どのtimeout、retry、cancellation policyを使用するかはClientとHTTP実装の責務である。Response ownershipとProtocol上のcache semanticsは[4. Client](4-client.md)に従う。
`call_operation`には次を適用する。
- Redirect、connection retry、HTTP stackのautomatic replay、proxy retryによって再送しない。
- `Retry-After`またはHTTP statusだけを理由に再送しない。
- Response不達またはlocal cancellationを、Operationが未実行またはrollback済みである証拠として扱わない。
- `internal`または`operation_outcome_unknown`を受信しても自動再実行しない。
- 同じOperationを利用者が改めて実行する場合は、Protocolの回復規則に従って必要なObjectまたはInterfaceを再観測する。
HTTP implementationとdeploymentは、失敗した`POST v1/call_operation`を透過的にreplayしない。
## HTTP facilitiesとの境界
HTTP cache、proxy、gateway、compression、connection管理、TLS、message size、timeout、concurrency、rate limit等の設定と運用は、本bindingの規定対象外である。
HTTP cache metadataはWIP ClientのKnown Space、Interface cache、Object validator、Interface validatorとは別の機構である。`ETag`、`If-Match`、Age、Last-Modified等をWIP observationまたはvalidatorとして解釈しない。それ以外のHTTP cache policyはHTTP implementationとdeploymentが決定する。
HTTP implementationまたはdeploymentの都合でrequestまたはresponseを交換できない場合、ClientはTransport failureとして扱う。WIPはそれらの運用設定を規定せず、そのfailureを`resource_limit_exceeded`その他のProtocol errorへ包み直さない。
## 非目標
本bindingは次を定義しない。
- Host内部のAPI、database、filesystem、queueとの接続方法。
- Endpoint discovery service。
- 固定のauthentication scheme。
- HTTP cacheを使ったWorldspace同期。
- Operation idempotency keyまたはexactly-once execution。
- Operation cancellation protocol。
- Streaming、subscription、server push、WebSocket binding。
- 複数のCore interactionを一つのWIP requestへまとめる共通envelope。
- Domain failureの共通HTTP mapping。
@@ -0,0 +1,321 @@
# Dynamic Worldspace Projection
## Status
この文書は、Rust / TypeScript Host APIを設計する前に、動的なWorldspaceを運用するための責務、処理モデル、要件を整理するdraftである。
具体的なtrait、class、function、decorator、macro、builderのsyntaxは定義しない。この文書で使用する`Projection Provider`、`Projection Resolution`、`Projected Object`等の名称もworking terminologyであり、canonicalなProtocol typeではない。
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。言語別のconceptual API modelは[Rust API Model](rust-api-model.md)と[TypeScript API Model](typescript-api-model.md)で扱う。
## 目的
WIP Hostの中心的な実装対象は、事前に列挙された静的endpointだけではない。Filesystem、database row、repository内容、検索結果、外部service上のentityなど、実行時に存在と状態が決まる空間をWorldspaceへ投影できなければならない。
HostがWorldspace内のすべてのObjectを常駐instanceとして保持することは要求しない。Backend dataをrequest時に解決し、必要な範囲だけObjectとして投影できることを基本とする。
固定pathへ登録されたObjectやservice endpointは、同じ動的projection modelにおいてrouting / resolution structureが固定された特殊ケースとして扱えるべきである。現在の主体やbackend stateに応じて、そこで返すObject representationやvalidatorが変化することは妨げない。静的登録だけを基本形とし、動的空間を大量のObject登録によって模倣する設計にはしない。
## 基本的な分離
Host実装APIでは、少なくとも次の責務を区別する必要がある。この区別を一対一のpublic type、trait、objectへ分割することまでは決定しない。
### Interface Declaration
Operation名、parameter、return type、documentation等を宣言し、Interface Descriptor、Value codec、boundary validationを生成する。
Interface Declarationは、多数のObjectが共有できる比較的静的なcapability schemaである。Objectの列挙やbackend dataのlifecycleは担当しない。
### Worldspace Projection
Clientから受け取ったpathを、現在の主体とbackend stateに基づいて解決し、Protocol上のObject projectionを生成する。一つのProjected Objectは、Host固有のentityを持たないvirtual nodeでも、一つのbackend entityに対応するものでも、複数のbackend entityを集約したものでもよい。
Projectionは、少なくとも次を決定する。
- そのpathのObjectを現在の主体へ公開するか。
- 公開する`name`、optionalな`description`、`ref`、`validator`。
- Objectの`interfaces` fieldへ設定するInterface参照のordered set。
- `observe`から列挙可能なindexable children。
- Operation実行時に現在のProjected Objectへ作用するために、Host内部で何を再解決または確立する必要があるか。
### Host Runtime
Protocol requestを受け取り、ProjectionとInterface registryを協調させる。Operation callでは、現在のProjected Objectが公開するInterface集合からClientが指定した一つのDescriptorを固定し、そこに宣言されたOperationをObjectのHost内implementationへ適用する。
Host RuntimeはProtocol validation、Interface Descriptorの公開、Value validation、error mapping、resource limit等を担う。Backend固有のprojection resolutionやdomain operation implementationそのものはProvider等のHost componentへ委譲できる。Object implementationとOperation実装を接続する具体的なAPIは各言語のSDK側で決定し、Protocol conceptとして追加しない。
## Working terminology
このdraftでは、Host内部の議論のために次の語を使う。
- **Backend Entity** — File、database row、issue、repository、remote entity等、projectionの入力になり得るHost固有の実体。一つのProjected Objectと一対一に対応するとは限らない。
- **Projection Resolution** — 現在の主体とpathについて、公開するObject representationと、後続処理に必要なHost-private情報を得るrequest-timeの解決結果。
- **Projection Provider** — Pathと主体からProjection Resolutionを生成し、indexable childrenの発見を支援するHost component。
- **Projected Object** — その時点でClientへ返すProtocol `Object`。Backend Entityを持たないvirtual Object、一つのentityのprojection、複数entityのaggregateのいずれでもよく、Host内の常駐instanceを意味しない。
HostがOperationを実装するために使用するRust struct、TypeScript object、backend handle、transaction context等はHost-privateであり、共通するHost abstractionを必須にしない。これらをProtocolの`Target { path, validator }`、canonical path、Objectの`ref`として公開または流用しない。
## 運用モデル
### `observe`
`observe(path, depth)`では、Hostは概念的に次を行う。
1. Requestの主体とpathをProjection Providerへ渡す。
2. Providerが現在のbackend stateから、そのpathで公開するProjected ObjectのProjection Resolutionを生成する。
3. 非公開または存在しない場合は`NotFound`とする。
4. 公開する場合は、現在のObject metadata、Interface参照のordered set、optionalな`ref`と`validator`を生成する。
5. Nodeのdepthがrequested depthより小さい場合、その主体へ公開するdirect indexable childrenをclean exhaustionまで列挙し、各childについて同じ処理を再帰的に行う。
6. Nodeがrequested depthに達した場合はchildrenを観測せず、responseの`children` fieldを省略する。
7. Requested depthまで完全な`ObjectObservation`を返す。
ProviderはWorldspace全体をmaterializeせず、現在処理しているparentのdirect childrenを遅延して発見できる必要がある。Childrenをstream、iterator、cursor等のHost内部表現で生成してよいが、一つのparentについて成功するには公開されたindexable childrenをすべて調べ終えなければならない。
Backend failure、cancellation、またはresource budget超過をclean exhaustionとして扱わない。Requested depthまでの完全なresponseを構築できない場合、Host Runtimeは部分的なsuccess responseを返さず、対応するfailureを返す。
各childについて現在のObject projectionを構築し、そのcanonical pathを後の`observe`または`call_operation`で現在のProjected Objectとして再解決できなければならない。以前観測したbackend entityやHost内部instanceを再現することは要求しない。
この処理のために、Hostが以前の`observe`で生成したObject instanceを保持している必要はない。内部cacheを利用してもよいが、cacheの有無によってProtocol semanticsを変えてはならない。
### `fetch_interface`
ProjectionがObjectへ割り当てた各Interface参照は、同じ主体から取得可能なDescriptorへ解決されなければならない。
複数のObjectは同じInterface参照を共有でき、一つのObjectも複数のInterfaceを組み合わせられる。Validatorを返すInterface参照ではDescriptorを変更でき、同じ参照とvalidatorの組が一つのDescriptor observationを表す。Validatorを返さない参照のDescriptorは、その参照が有効な間immutableである。Interface参照のscopeは[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。登録API、生成時期、deduplication、cacheの具体実装は別途決定する。
### `call_operation`
`call_operation`では、HostはProviderまたはbackend固有のexecution boundaryへ入り、その同じboundary内で概念的に次を行う。
1. 現在の主体と`target.path`からProjection Resolutionを生成し、同じbackend対象を保持するstable handleまたはtransaction contextを取得する。
2. そのpathのProjected Objectを現在も公開するか確認する。
3. 指定されたObject validatorを現在のObject identityと状態へ照合する。
4. Clientが指定したInterface参照がObjectの現在の`interfaces`に含まれることを確認する。
5. `fetch_interface`で観測したInterface validatorを現在状態へ照合する。
6. 固定したInterface Descriptorで指定Operationが宣言されていることを確認する。
7. Argumentsを同じOperation Declarationに従って検証する。
8. 固定したObject implementation、Interface Descriptor、Operation implementationを、step 1で固定した同じbackend対象へ適用する。
9. Resultを同じOperation Declarationで検証し、nestedしたすべての`Entry`へ現在の主体のvisibility checkを適用してから、実行後Object validatorとともに返す。
Step 3から8までの間にObject、Interface、Descriptor、Operation implementation、backend対象の対応が変化し得る実装は、この手順を満たさない。Projectionは、Host RuntimeとProviderがこのatomicityを実現するために必要なstable handle、transaction、conditional operation、lock等を使用できるようにしなければならない。
過去の`observe`で作成したHost内部Object instanceの存在を前提にしてはならない。Clientから届くprotocol-level Targetはpathとoptionalなvalidatorであり、Hostはcall時の現在状態からObject implementationを解決する。Validatorを省略したcallを現在のOperationが許可する場合は、過去に観測したObjectではなく、実行時にpathが指す現在のObjectへ無条件に実行する。
Validatorを指定した場合は、Clientが観測したObject identityおよび保護対象状態と実行対象を照合する。Path、`ref`、以前のProjection Resolution、cacheされたHost内部instanceのいずれも、単独ではProtocol上の永続的なObject identityとして扱わない。
## Filesystem projectionの評価例
Filesystemを`/files`以下へ投影する場合、Hostが全file / directoryに対応するObject instanceを作成する必要はない。
- `observe("/files/a.txt", depth = 0)`では、pathをfilesystem entryへ解決し、stat等からObject projectionを構築する。
- `observe("/files", depth = 1)`では、directory listingから直接のindexable childrenをすべて列挙する。
- Regular file、directory、symlink等に応じてInterface参照の集合を選択できる。
- 主体の権限に応じてObjectを非公開にしたり、read-only、writable等のcapability Interfaceを集合へ追加または除外できる。
- File Operationでは、call時にpathを現在のfilesystem entryへ解決し、Rust struct、TypeScript object、file descriptor等を使ってOperationを実装できる。
この例で、Objectはfileそのものでも、file内容を複製したrecordでもない。FileをWorldspace上で発見および操作するためのprotocol-level projectionである。
## 要件
### Lazyかつdata-drivenであること
- Object集合を事前に列挙または常駐させなくてもよい。
- Backend dataからrequest時にObjectを生成できる。
- 大規模または無限に近いlogical spaceを、要求範囲だけ投影できる。
- Tree上でindexableでないObjectも、既知のpathを`depth = 0`で`observe`したときに直接解決できる。
- Operation resultの`Entry`等で新たに得たpathを、事前のObject登録なしに解決できる。
- 静的Objectも同じmodelで表現できる。
### Protocol上有効なprojectionを生成すること
- Rootと非rootに対する`name`規則を守る。
- 同じparentの公開childrenに重複する`name`を生成しない。
- Backend側の名前をそのままWIPのUnicode nameとして表現できない場合は、deterministicかつcanonicalで、同じparent内で衝突しないmappingを定義するか、そのentryを公開対象から除外する。
- Mappingしたnameは、親全体の無制限列挙を前提にせず、`depth = 0`の`observe`とOperation dispatchで同じchildへboundedに解決できなければならない。Encoding自体が可逆である必要はなく、boundedなbackend indexを利用してよい。
- Name mappingはchild enumeration、`observe`、`call_operation`で一貫させる。
- Objectの`interfaces`を重複のないordered setとして生成し、各参照を取得可能で有効なInterface Descriptorへ解決する。
- ProviderやOperation implementationの出力を信頼せず、Host RuntimeがProtocol boundaryで検証できる。
- Invalidなbackend dataを、Protocol上有効に見える別のObjectへ暗黙に衝突させない。
### Object representationとbackend dataを分離すること
- Protocol `Object`をbackend entity classやHost内のmutable instanceと同一視しない。
- Object projectionはrequestごとに再構築できるvalueとして扱える。
- Backend connection、database transaction、file descriptor、credential等をObject metadataへ混入させない。
- Host内部のObject implementation stateはwire representationへ露出させない。
### Interface DeclarationとObject publicationを分離すること
- Interface DeclarationはObjectの個数やpathに依存しない。
- 多数の動的Objectが同じInterface参照を共有でき、一つのObjectも複数のInterfaceをordered setとして組み合わせられる。
- Validatorを返すInterface参照は同じ参照でDescriptorを変更できるが、変更時にvalidatorを更新する。Validatorを返さない参照のDescriptorは、その参照が有効な間immutableとする。
- Projectionは主体、policy、resource kind、安定した公開capabilityに基づいて、Objectの`interfaces`へ含める参照と立項順を決定できる。
- 頻繁に変化する実行可否や通常のdomain stateを、Interface集合やOperation declarationへ同期しない。
- Operationを隠す場合はcapability Interfaceを集合から除外するか、そのOperationを宣言しないDescriptorを使用し、公開済みOperationの実行だけを拒否する場合は`PermissionDenied`を使用できる。
- Objectの`interfaces`に含まれる各Descriptorは、その主体から取得可能でなければならない。
- Interface参照からDescriptorを解決することと、そのInterfaceを実装するHost codeを区別する。
### `observe`と`call_operation`で一貫すること
- 同じ時点と主体について、異なるdepthの観測が矛盾したObject metadataを生成しない。
- 同じ`ref`を持つObjectを複数pathへ投影する場合も、一つの共有された現在状態として扱い、path固有の`name`、`description`、Interface集合を同時に返さない。
- 同じ`ref`と同じvalidator valueを持つObject projectionは、validatorが存在する場合、`name`、`description`、`interfaces`を含む全representationを一致させる。
- 同じ`ref`でvalidatorが異なる場合は同一Objectの異なる観測状態であり、別Objectとして扱わない。
- Validatorが存在しないObjectでは同validatorによるrepresentation equality checkを行えないため、Clientが最後に成功した観測を現在状態として扱えるようにする。
- 同じ`ref`を持つObjectの公開representationが変化する場合は、validatorが存在するなら以前と同じvalidatorのまま異なるrepresentationを返さない。
- 深い`observe`で得たpathを`depth = 0`で再観測した場合に、backend stateが変化していなければ同じObjectとして解釈できる。
- Operation call時には、その時点でpathが指す現在のProjected ObjectとInterface集合を再解決し、Clientが指定した参照のmembershipとvalidatorを確認する。
- 非公開Object、非公開Operation、公開済みだが実行拒否されたOperationをProtocolで定めたerrorへ一貫して対応付ける。
Backendが並行して変化する場合、複数request間で同じsnapshotを保証することまでは要求しない。観測したObject identityおよび状態と条件付き実行の対応はvalidatorで保護する。Validatorを省略したcallをOperationが許可する場合は実行時の現在Objectへ無条件に実行し、Operationがvalidatorを要求する場合は`ValidatorRequired`として実行しない。
### Operation解決と実行対象のatomicity
Operation callでは、visibility、Object identityとvalidator、Interface membershipとvalidator、Interface Descriptor、Operation implementation、backend対象を一つのlinearization pointで固定しなければならない。ValidatorなしのObject実行をOperationが許可する場合でも、解決後にpathの指すProjected Objectが変化し、以前のDescriptorに基づくOperation implementationが現在の別Objectへ適用されることを許してはならない。
少なくとも次を一つのatomicなexecution boundaryとして扱えなければならない。
- Pathが現在指すObject identityと保護対象状態の確認。
- Object validatorの必須判定と比較。
- 指定Interfaceの現在のmembership確認。
- Interface validatorの必須判定と比較。
- Interface DescriptorとOperation implementationの固定。
- Authorizationの確認。
- 同じbackend対象へのOperation実行。
単にpathを一度解決し、validatorを比較した後に、同じpathを別途開き直してOperationを実行する実装では、比較と実行の間に対象が差し替わる可能性がある。
Host APIは、backend transaction、lock、revision付きconditional update、file descriptor、identity再確認等、Providerがbackendに適した方法でatomicityを実現できる境界を提供する必要がある。すべてのbackendに共通する特定のlocking方式は要求しない。
### Host内部Object stateのlifecycleを限定できること
- Rust struct、TypeScript object、backend handle等をrequestまたはOperation execution scopeだけで構築できる。
- Hostは全Object分のimplementation instanceを保持しなくてよい。
- Providerは必要に応じてconnection poolやmetadata cache等を共有できる。
- Request終了後の内部state保持、再利用、破棄はbackend entity、connection、transaction等の安全性を損なわない。
- Clientが保持する`ref`やvalidatorからHost内部instanceを直接復元することを要求しない。
### 認可をrequest時に評価できること
- Projectionは主体に基づいてObject visibilityと、Objectの`interfaces`へ含める参照の集合を決定できる。
- 非公開ObjectとそのObjectだけを経由するsubtreeを`observe` responseから省略し、存在や省略数を示すmarkerを生成しない。
- Requested depthより浅いnodeでは、その主体へ公開されたindexable childrenを完全に返す。
- Operation implementationまたはその前段で、call時のauthorizationを再評価できる。
- 以前の`observe`時に許可されていたことだけを根拠にOperationを実行しない。
- 非公開Objectの存在や非公開Operationを内部error、log、timing等から不用意に漏らさない実装が可能である。
- Operation resultへ`Entry` pathを含める場合もObject visibilityを迂回せず、result生成時点でその主体に認識させてよいpathだけを返す。この規則はRecord、List、Union等へnestedしたすべての`Entry`位置へ再帰的に適用する。
- `Entry` argumentや既知pathの所持をauthorizationの証明として扱わず、参照先へ作用するOperationは現在の主体と状態に基づいて認可する。
- Result取得後のstateまたは権限変更により、その`Entry`への後続`observe`が`NotFound`になることは許容する。
### Backend固有のidentityを強制しないこと
- Providerは適切なbackend identityがある場合に`ref`へ利用できる。
- Backend EntityとProtocol Objectを常に一対一対応させず、例えば同じfilesystem identityが異なる名前で同時に現れ、Object representationの整合性規則を満たせない場合は、同じ`ref`を機械的に再利用しない。
- Backendに安定したUIDがなくてもObjectを投影できる。
- Host内部のexecution identityとClient-facingな`ref`を同一形式にする必要はない。
- `ref`をHost内部instanceのhandleやdereference keyとして使用することを要求しない。
- `ref`を持たないObjectでもvalidatorを用いた安全なOperationを実装できる。
- Validatorは状態だけでなく現在のObject identityにも結び付け、pathが別Objectを指す場合は内容が同じでも一致させない。
### Resource limitとcancellationを適用できること
- Child enumeration、backend lookup、descriptor生成、Value変換へboundedなresource limitを適用できる。
- `observe`のdepthをProvider側の無制限な再帰列挙へ変換せず、request全体へboundedなresource budgetを適用する。
- Client disconnect、timeout、Host shutdown等に応じてbackend処理へcancellationを伝播できる。
- `call_operation`をdispatchする前にresource limitへ達した場合は`ResourceLimitExceeded`として拒否し、Operationを実行しない。
- Dispatch後のcancellation、timeout、implementation failure、result encoding failureについて、Operationが未実行またはrollback済みであると保証できる場合だけ`Internal`または`ResourceLimitExceeded`を返す。保証できない場合は`OperationOutcomeUnknown`を返す。
- Side effectを持つOperationでは、cancellationやtimeoutを未実行またはrollback済みの証明として扱わず、Host RuntimeもClientも実行結果が不明なcallを自動retryしない。
- Partial backend failureをProtocol上の成功したObject observationへ黙って変換しない。
### RustとTypeScriptで同じsemanticsを表現できること
言語ごとのAPI syntaxは異なってよいが、少なくとも次を両方で実現できなければならない。
- Asyncなpath resolutionとchild enumeration。
- Data-drivenなObject projection。
- Interface Declarationの共有と、Objectへの0個以上のInterfaceのcomposition。
- Type-safeまたはruntime-validatedなOperation dispatch。
- Host-privateなObject implementation stateをrequest-timeに構築および利用できること。
- Object validator、Interface membershipとvalidator、Descriptor、Operation implementation、backend対象の一貫したlinearization point。
- Request context、主体、cancellation、resource limitの伝播。
- Protocol errorとbackend / implementation errorの分離。
Rust固有のtrait、generic、associated type、type erasureや、TypeScript固有のobject definition、generic inference、runtime schema libraryの選択は、この要件を満たすためのAPI設計段階で決める。
## Host内部cache
Hostは性能最適化として、path resolution、backend metadata、Object projection、Interface Descriptor、Host内部Object state等をcacheしてよい。ただし、cacheはProtocol contractではない。
- Cacheがなくても正しく動作しなければならない。
- Visibilityとauthorizationを異なる主体間で誤って共有してはならない。
- Stale cacheによってvalidator比較を迂回してはならない。
- Descriptor cacheは同じInterface参照とvalidatorの組に異なるDescriptorを対応付けず、validatorを返さない参照をimmutableとして扱う。
- Resource handleをcacheする場合は、そのlifecycleとidentityの再確認をbackendに適した方法で行う。
## Composition
一つのHostは、Filesystem、database、remote API等の複数Providerを一つのWorldspaceへ合成できることが望ましい。
Composition後も、同じ主体へ公開されたWorldspace全体で`ref`とInterface参照のProtocol semanticsを保たなければならない。Provider-local identityやInterface keyをそのまま公開すると衝突し得るため、Hostはnamespace、変換、中央発行等の方法で次を保証する。
- 異なるObjectへ同じ有効な`ref`を割り当てない。
- 同じ有効なInterface参照とvalidatorの組を異なるDescriptorへ対応付けない。
- Validator比較がProvider routingの変更によって別Objectへ適用されない。
Interface参照は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)の構造化参照を使用する。Objectの祖先制約と、合成後のscope・path・typed `entry`の変換は[ProtocolのWorldspace composition](../3-protocol.md#worldspace-composition)に従う。Objectの`ref`の具体的な発行方式は引き続き未決定とする。ScopeはそのpathのObjectに紐づき、self-scopeを許可する。RuntimeはDescriptorとscope Objectのoptionalな`ref`を一つのInterface observationとして公開し、callで指定された`scope_ref`をrouting・implementationの固定と一貫して確認する。`ref`のないscopeも許可し、そのidentityの追跡を要求しない。
ただし、mount API、path routing、Provider間のparent / child関係、同名entry conflict、cross-provider Operation、transaction boundaryは未決定である。ProtocolはHost内部のcomposition方式を公開しないため、Host APIでこれらをどこまで標準化するかを別途検討する。
## 非目標
このdraftは次を要求しない。
- Worldspace全体の事前materialization。
- Objectごとの常駐class instanceまたはactor。
- Backend entityとProtocol Objectの一対一対応。
- Host内部Object state、database key、file descriptor等のwire公開。
- Client cacheとHost stateの同期。
- Provider間にまたがる分散transaction。
- すべてのObjectに対する`ref`の発行。
- 特定のdependency injection framework、schema library、async runtimeの採用。
## API設計前に評価するシナリオ
具体API案は、少なくとも次のシナリオで不自然な大量登録や永続Object instanceを要求しないことを確認する。
1. 数百万entryを持つFilesystemを、directory listingに応じて遅延投影する。
2. Database tableのrowをObjectとして投影し、row keyからcall時に再解決する。
3. 検索Operationが、Treeではindexableでない結果Objectの`Entry` pathを返す。
4. 同じbackend dataを元に、主体ごとに異なるObject visibilityまたはInterface集合を公開する。
5. 観測後にpathの指すProjected Objectが変化し、validator付きOperationを拒否する。
6. 公開済みOperationをcall時のauthorizationで`PermissionDenied`にする。
7. 固定pathの静的service endpointを、動的modelの特殊ケースとして公開する。
8. 複数Providerを一つのWorldspaceへ合成し、Provider-localなidentityの衝突を防ぐ。
9. Backend entityを持たないvirtual Objectと、複数entityを集約するObjectへOperation implementationを適用する。
10. 深い`observe`がresource budgetを超える場合、部分的なresponseを返さず`ResourceLimitExceeded`にする。
11. Operation resultのnestedな`Entry`から、現在の主体へ非公開のObject pathが漏れないようにする。
## 未決定事項
- Projection ProviderをHostへ登録・合成する単位。
- Path routingおよびmountに相当する概念を標準Host APIへ含めるか。
- Path resolutionとchild discoveryの具体的な戻り値。
- Projected Objectがzero / one / multiple backend entityから導出される場合のHost内部Object implementation表現。
- Child discoveryがObject projectionを返すか、遅延解決用の内部keyを返すか。
- Clean exhaustion、failure、cancellationを区別し、requested depthまで完全な観測だけをsuccessとして返せる内部contract。
- Rust structやTypeScript objectを、Interface DeclarationおよびOperation implementationへ接続する言語別API。
- DynamicなProjected Object implementationをProviderから返すときのgeneric、type erasure、runtime representation。
- Interface DeclarationとOperation implementationを同じAPIにするか分離するか。
- Validator比較とOperation実行を囲むtransaction / callback / guardの抽象化。
- Backendがatomic conditional executionを提供できない場合に、そのOperationをどう制限するか。
- Subject-specificなInterface参照の選択、Descriptor生成、参照発行、cache方法。
- Descriptorを事前登録するか必要時に生成するか、および構造的に等しいDescriptorをinternするか。
- Operation result内のnestedな`Entry` visibilityを、Operation implementation、Projection、Host Runtimeのどこで検証するか。
- Backend nameをWIP nameへmappingする場合のencoding、index、collision処理。
- 複数Provider間で`ref`、Interface参照、validator identityをnamespace化する方法。
- Projection、Operation implementation、Host Runtimeが返すerrorとProtocolErrorの対応付け。
- Enumerationのstreaming、pagination、backpressureをHost内部APIでどう表すか。
- Provider composition時のconflict、ownership、lifecycle、shutdown順序。
- Observability、tracing、auditで非公開Objectやsecretを漏らさないための標準hook。
これらを決定する前に、Rust / TypeScript双方で複数のAPI sketchを作り、上記の評価シナリオと要件に照らして比較する。
+155
View File
@@ -0,0 +1,155 @@
# Filesystem Discovery: 入口と内容の分離(草案)
## 位置付け
この文書は、YoiのFS投影に対して決めた探索仕様を、WIP Hostの投影方針として記録するdraftである。**Treeで発見するのはfilesystemの入口までとし、その内容はdirectoryのlist / glob / grep Operationで発見する。**
Indexableな範囲の全量取得をClientで縮めるのではなく、Hostが何をindexableとして公開するかを修正する。個々のファイル・フォルダは非indexableなObjectとして要求時に直接解決できる。
[Object tree / SchemaのIndexable children](../2.1-object-tree-schema.md#indexable-children)、[Client](../4-client.md)、[4.1. AI Tools](../4.1-ai-tools.md)を前提とする。Yoi側の実装追跡はT-714、既存投影はT-699である。本稿は仕様決定の記録であり、実装済みであることを示さない。
## なぜFS全体をindexableにしないか
Indexable childrenには、件数がboundedまたは現実的な範囲で列挙でき、比較的安価で、自然な所有・内包関係であることが求められる。全件列挙が高コストまたは不自然な集合は、対象の集合を扱うObjectのOperationで検索・filter・paginationし、`entry` を返すのが既存の方針である。
任意のfilesystem directoryは、浅い位置に大量のfileやdirectoryを持ち得る。Depthが小さくてもAIが把握できない件数になるため、depth制限だけでは適切な探索空間を作れない。
次の対策だけでFS内容をindexableにしてよいとはしない。
- Clientが小さいdepthを選ぶこと。
- Hostが件数上限で `ResourceLimitExceeded` を返すこと。
- Directoryをlazyに列挙し、常駐Objectや事前indexを作らないこと。
- Clientが応答を切り捨てて表示すること。
前二者は必要な安全弁、lazy解決は実装上の性質であり、indexabilityの判断とは別である。部分的な応答を完全なtreeとして見せることもできない。本投影では、内容の件数が少ない場合だけchildrenへ展開する方式にはせず、入口と内容の境界を一貫させる。
## Indexableな公開境界
Yoiでは、rootからtreeで発見できる範囲を次とする。
```text
/
└── checkouts
├── main
└── docs
```
`/checkouts/<checkout-name>` は、現在利用できるfilesystemの入口であり、directoryとして操作できるObjectでもある。その直下から先のfile / directoryは、入口や内容directoryのindexable childrenには含めない。
例えば次のpathは有効な操作対象として存在し得るが、上のtreeには展開しない。
```text
/checkouts/main/README.md
/checkouts/main/src
/checkouts/main/src/lib.rs
```
`/checkouts` はYoi側の配置例であり、WIP Protocol全体へ固定namespaceを追加するものではない。単一FSをWorldspace `/` に直接投影する場合は `/` 自体が入口に相当し、その内容をindexable childrenへ展開しない。
入口の一覧自体についてもindexableの一般条件は維持する。この境界は、入口の数まで無制限に増やしてよいという意味ではない。
## 非indexableと非公開は別
File / directoryをindexableにしないことは、Objectとして非公開にすることでも、pathで解決不能にすることでもない。
- `observe("/checkouts/main/src/lib.rs", depth = 0)` は、現在の主体に公開できるfileなら、そのObjectを返せる。
- AI向け `inspect(path)` は、ClientがまだObjectを観測していないpathにも使える。
- Directoryの探索やfileの読み書きは、そのObjectが公開するInterfaceのOperationとして実行する。
- 直接解決にも現在の可視性・権限を適用する。Pathを知っていることは権限ではない。
非公開・存在しないpathの扱いは従来のProtocolに従う。非indexableという理由だけで `NotFound` を返してはならない。
## observeのchildren契約
FS入口およびその内容のdirectory/fileは、この投影では内容のindexable childrenを持たない。
| 観測 | 応答の意味 |
| --- | --- |
| `observe("/checkouts", 1)` | 接続済み入口をchildrenとして列挙。入口はdepth境界なのでchildren fieldを省略 |
| `observe("/checkouts", 2)` | 同じ入口までを列挙。各入口のchildrenは `[]`。内容は列挙しない |
| `observe("/checkouts/main", 0)` | 入口Objectのみ。children fieldを省略 |
| `observe("/checkouts/main", 1)` | 入口Objectと `children: []` |
| `observe("/checkouts/main/src", 1)` | 直接解決したdirectory Objectと `children: []` |
`children: []` は「indexable childがない」という意味であり、filesystem directoryが空であるとは限らない。内容が空かどうかを知るにはdirectoryの `list` を使う。
File数が増えても、入口の正のdepth観測でbackendの全内容を列挙する必要はない。Client側だけで非表示にするのではなく、Hostのchildren列挙境界で内容への走査を行わないようにする。Depth境界でのfield省略と、観測済みの空のchildrenは従来どおり区別する。
## 内容を発見するOperation
FS入口と配下directoryには、次の探索Operationを提供する。これらはInterface上のOperationであり、新しいProtocol interactionや `list` という子Objectではない。
| Operation | 役割 |
| --- | --- |
| `list` | 対象directory直下のfile / directoryを列挙する |
| `glob` | 指定した範囲とpath patternに一致する対象を探す |
| `grep` | 指定した範囲のfile内容を検索する |
### list
- 一つのdirectoryの直接の内容を対象とする。暗黙に全subtreeを再帰展開しない。
- Fileとdirectoryを区別でき、空directoryも対象として発見できる。
- 各項目から、その主体のWorldspace内の正しい `entry` pathへ辿れる。
- 大量件数はOperation結果として上限と継続を明示して扱う。部分的な一覧を全件であるかのように返さない。
- Cursor等の継続方式、並び順、parameter名、result recordの具体形は、このdraftでは未決とする。
Yoiの `/checkouts` 自体にある `list` は入口一覧であり、`/checkouts/<checkout-name>` や配下directoryの `list` は内容一覧である。Operation名が同じでも対象とInterfaceが異なる。
### glob / grep
対象directoryを基準にpatternや検索範囲を解釈する。Resultsからfile / directoryへ移動する参照は `entry` 型として宣言し、表示済みのfilenameや行番号付きtextを再parseして作らない。
Globは条件に一致する対象を探す操作であり、directory直下の全内容を表すとは限らない。特にYoiの通常ToolsのGlobはfileのみを返し、directoryは返さず、配下を再帰的に走査し得る。`glob("*")` を `list` と同じ契約として扱わない。
Grepは内容検索であり、treeの名前・構造の探索ではない。Ignore、検索範囲、結果上限、context、継続等は各Operationの明示された契約に従う。
## Clientとの接続
典型的な利用は次の流れになる。これは操作の順序の例であり、Tool responseやInterface参照の具体構文を定義するものではない。
1. `tree("/", depth)` で `/checkouts/main` の入口を知る。
2. `inspect("/checkouts/main")` でdirectoryの `list` / `glob` / `grep` を知る。
3. 選んだOperationを `invoke` し、結果から `/checkouts/main/src` やfileのpathを得る。
4. 必要なpathだけを `inspect` し、さらに探索するかfileのOperationを実行する。
結果のすべてのentryを即時にObjectへ展開する必要はない。AIはまずpathだけを受け取り、説明や操作が必要になったときにClientへinspectを依頼できる。
Clientがそれらを観測してKnown Spaceへ保持しても、rootからのtreeには混ぜない。Pathの階層関係だけからindexable edgeを生成しない。既知の内容directoryをtreeの起点として直接指定した場合も、そのObjectは表示できるが、内容をindexableな子として復活させない。
## 解決・権限・整合性
探索方式の変更で、既存の権限やファイル操作の意味を変えない。
- 現在の主体、root/attachment、scope、provider capabilityに基づき、一覧・検索・直接path解決・操作を実行する。
- Hostの一覧結果には、主体へ公開できる対象だけを含める。直接path指定でも同じ境界を守る。
- Detach、権限取消、入口名の再利用後に、古い観測から別の対象を操作しない。
- Object identityとvalidatorは既存Protocolとproviderの実行境界で管理する。
- Fileのread/edit/write等の具体的契約は、各投影のInterfaceに従う。本稿はbyte操作やtext操作の統一を要求しない。
Yoiの実装では、既存Workdir providerのList/Glob/Grep処理と権限・transport境界を共有する。Worker側でremote filesystemのホストパスを直接開く方法や、Bashの `ls` を必須にする方法へ置き換えない。
## 現行文書・実装との差
[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)のFilesystem評価例と、現行 `wip-fs-server` は、directoryの内容をchildrenとして列挙する経路を持つ。Yoiの既存 `/checkouts` 投影も、内部List処理をchildren列挙に使い、directoryの独立した `list` Operationは提供していなかった。
本draftは、それらの現状を承認する文書ではなく、FS内容の探索をOperationへ分離する修正方針である。Lazyなrequest-time解決と、既知pathからの直接解決は維持するが、directory listingをindexable childrenの列挙として使う部分は見直す。
既存ProtocolがあらゆるFS Objectのindexabilityを一律に禁止する明文を持つという意味ではない。既存のindexable条件を、任意件数の内容を持つFSへ適用する具体的方針として、このdraftでは入口で境界を固定する。
## 検証するシナリオ
- 入口直下に大量のfileを置いても、rootから深いdepthで取得するtreeの件数がfile数に比例せず、内容の全件走査も発生しない。
- 少数のfileしかないdirectoryでも、同じ非indexable境界が維持される。
- 深いdirectoryの正のdepth観測は `children: []` だが、そのdirectoryのlistは内容を返す。
- Directory listがfileと空directoryを返し、結果上限と継続を扱える。
- List/glob/grepで得たpathを直接inspect・操作できる。Bash/COMMAND権限を必要としない。
- 非indexableな結果をClientが保持しても、treeへ混入しない。
- Scope外、権限取消、detach、入口名再利用、stale validatorの拒否に回帰がない。
## 未決事項
- Directory listの具体的なschema、並び順、継続方式、変更中の一覧の保証。
- Symlinkや特殊entryのlist上の種別表現、各providerのignore方針との整合。
- 本稿を既存の動的投影例、FS実装、AI用Toolsの正本文書へどう反映するか。
通常Tools modeへ独立したList Toolを追加するかどうかは、directory Operationの公開とは別の適用事項であり、本稿で追加済みとは扱わない。
+498
View File
@@ -0,0 +1,498 @@
# Rust API Model
## Status
この文書は、WIP HostをRustで実装するための概念的なAPI modelを整理するdraftである。
このdraftで示すtrait名、attribute、derive、generated type名、method signatureは説明用であり、具体的なRust API syntaxとして確定しない。Ownership、lifetime、async runtime、error type、macro crate構成も未決定である。
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。動的なObject projectionの運用要件は[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)、対応するTypeScript modelは[TypeScript API Model](typescript-api-model.md)を参照する。
## 目的
Rust APIでは、次のmodelを自然に表現できるようにする。
1. Rust traitでWIP Interfaceを宣言する。
2. Rust structが、Protocol Objectとして公開するmetadataを提供する。
3. 同じstructが、必要に応じて公開する0個以上のInterface traitを実装する。
4. Objectを返す時点で、公開するInterfaceのordered setを明示的に選択する。
5. Trait declarationからInterface Descriptor、Value codec、validation、Operation dispatchに必要なcodeを生成する。
6. Struct instanceをWorldspace全体について常駐させず、`observe`と`call_operation`の解決時にdata-drivenに生成できる。
このmodelでは、Protocol上のObjectとInterfaceの関連付けは`Object.interfaces`だけで表す。Operation implementationへ到達するためのRust内部codeはSDK / Host Runtimeの実装詳細であり、新しいProtocol conceptを導入しない。
## Conceptual model
### Interface trait
WIP InterfaceはRust traitとして宣言する。
```rust
#[wip::interface]
trait FileInterface {
/// Reads bytes from the file.
async fn read(
&self,
/// Byte offset from the beginning of the file.
offset: Option<i64>,
/// Maximum number of bytes to return.
length: Option<i64>,
) -> Bytes;
/// Replaces the file contents.
async fn write(
&self,
content: Bytes,
) -> WriteResult;
}
```
このcodeはconceptual syntaxである。`Option<i64>`の対応範囲、`Bytes` wrapper、documentationの取得方法、async methodとerrorの具体形は別途決定する。
Trait declarationは、次のlogical declarationに対応する。
- 一つのtrait declarationを、一つのInterface Descriptorを生成するRust側definitionとして扱う。
- Trait名はgenerated Rust itemやlocal registrationを識別するために使用できるが、Protocol `InterfaceDescriptor`のfieldや構造化されたInterface参照としてそのまま公開しない。
- Trait methodをOperation Declarationとして扱う。
- ReceiverはProtocol parameterに含めない。
- Method parameterを名前付きOperation parameterとして扱う。
- Return typeをOperation return typeとして扱う。
- Rust typeをDescriptorの`TypeExpr`とProtocol `Value`へ対応付ける。
- Documentationをtrait、method、parameter、return、関連typeから取得できるようにする。
Descriptorのshapeはtrait declarationによって決まり、各structのtrait implementationによって変化しない。
### Object implementation
Rust structは、その時点でWorldspaceへ投影するObjectをHost内部で表現できる。
```rust
struct File {
name: String,
path: PathBuf,
reference: Option<String>,
validator: Option<Vec<u8>>,
}
```
Structは概念上の`WipObject` traitを実装する。
```rust
impl WipObject for File {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> Option<&str> {
None
}
fn reference(&self) -> Option<&str> {
self.reference.as_deref()
}
fn validator(&self) -> Option<&[u8]> {
self.validator.as_deref()
}
}
```
`WipObject`が返す値のownershipやborrow形式は未決定である。概念的にはProtocol Objectのうち、publication時に選択するInterface集合以外の公開metadataを提供する。
`WipObject`がProtocol metadataとして返す値へ次を含めない。これらをimplementing structのprivate fieldとして保持することは禁止しない。
- Absolute path。PathはObjectのfieldではなく、Worldspace resolutionのcontextである。
- Children。ChildrenはTree上の配置contextである。
- 固定されたInterface集合。InterfaceはObjectを公開する時点で追加する。
- Backend connection、file descriptor、credential等のHost-private state。
Structが保持する`path`やbackend handle等はHost内部のOperation implementationに使用できるが、`WipObject`のmetadataまたはProtocol Objectへ自動的にserializeしない。
### Interface implementation
Object implementationとして使用するstructは、公開したいInterface traitを実装する。
```rust
impl FileInterface for File {
async fn read(
&self,
offset: Option<i64>,
length: Option<i64>,
) -> Bytes {
// self.pathが示す現在のfileを読む
}
async fn write(
&self,
content: Bytes,
) -> WriteResult {
// self.pathが示す現在のfileへ書く
}
}
```
Trait implementationはOperationのbehaviorを提供する。Interface Descriptorはtrait implementationから生成せず、Interface trait declarationから生成する。
Rustには、あるtypeが実装しているtraitを列挙する一般的なreflectionがない。また、一つのstructが複数のInterface traitを実装し得る。そのため、trait implementationが存在するだけでInterfaceを暗黙に公開しない。
### Object publication
ObjectをWorldspaceへ返す時点で、struct instanceと公開する0個以上のInterface Declarationを明示的に組み合わせる。Interfaceは追加順を保持するordered setであり、同じDefinitionを重複して追加しない。
Conceptual syntaxは次のようになる。
```rust
let file = File::resolve(path).await?;
let object = PublishedObject::new(file)
.expose(FileInterfaceDefinition);
```
ここで各`InterfaceDefinition`は、`#[wip::interface]`が生成するconceptualなcompanion definitionである。実際の名前や、associated item、module、valueのどの形で生成するかは未決定である。
Publication APIは、追加する各Definitionについて概念的に次のboundを要求する。
```rust
impl<T: WipObject> PublishedObject<T> {
fn expose<I>(self, interface: I) -> Self
where
I: InterfaceDefinition<T>;
}
impl<T: FileInterface> InterfaceDefinition<T>
for FileInterfaceDefinition
{
// generated registration and dispatch metadata
}
```
このboundにより、Objectへ追加する各Interfaceについて、公開する値がObject metadataとOperation implementationの両方を提供することをcompile timeに確認できる。どのInterfaceも追加しないObjectも表現できる。
`PublishedObject`はHost SDK内のconceptual typeであり、Protocol typeとして追加しない。Host Runtimeはこれを使用して次を構築する。
```rust
Object {
name: object.name(),
description: object.description(),
interfaces: selected_interface_references,
r#ref: object.reference(),
validator: object.validator(),
}
```
`selected_interface_references`は、Hostが各generated Interface Definitionを登録または公開するときに割り当てる構造化参照`{ scope, name }`を、publication APIへの追加順で並べたものである。同じ`(scope, name)`の重複は拒否し、Objectの配置pathに対する祖先制約も検証する。Macroが参照をcompile timeに固定することは要求しない。
`observe`はDescriptor自体をObjectへ埋め込まず、このInterface参照のordered setだけを返す。Clientが各参照を`fetch_interface`したとき、Host Runtimeはtrait declarationから生成されたDescriptorと、存在する場合はInterface validatorを返す。同じ参照とvalidatorの組のDescriptorはHost上で共有およびcacheできるため、Object instanceごとにDescriptorを再生成または再送する必要はない。
## Macroが生成する責務
`#[wip::interface]`は、conceptualには次を生成または提供する。
- Interface Descriptorを構築するdefinition。
- Trait methodとOperation Declarationの対応情報。
- ArgumentsのProtocol `Value` decoder。
- ResultのProtocol `Value` encoder。
- Argumentsとresultのruntime validation。
- Operation名から対応するtrait methodを呼び出すdispatch code。
- 任意の`T: WipObject + InterfaceTrait`について、そのInterfaceを`PublishedObject<T>`へ追加するための型付きentry point。
- HostがInterface参照、optional validator、Descriptorを登録、取得、cacheするためのidentity。
MacroがRust codebase内のtrait implementationを探索または列挙することは要求しない。Publication時のgeneric boundによって、対象structがInterface traitを実装していることを確認する。
Generated dispatch codeはRust SDK内部の実装詳細である。Host内部のdispatch mechanismやRust type名等をProtocolへ公開しない。
## Rust typeとProtocol type
Interface traitで使用できるRust typeは、Descriptorの`TypeExpr`とProtocol `Value`へ対応付けられなければならない。
Conceptualにはderiveまたは明示的なcodec implementationを使用する。
```rust
#[derive(WipType)]
struct WriteResult {
bytes_written: i64,
}
```
```rust
#[derive(WipType)]
enum ReadResult {
Data { content: Bytes },
EndOfFile,
}
```
このsyntaxも未決定である。少なくとも次を表現できる必要がある。
- Unit、Boolean、Integer、Number、String、Bytes、Json、Entry。
- Record、List、Enum、Union。
- Descriptor内のNamed type。
- Requiredとoptionalなfieldおよびparameter。
- Documentation。
Rustの`Option<T>`をoptional field / parameter以外で許可するか、enumをProtocol EnumまたはUnionへどう対応付けるか、newtypeをどのように扱うかは未決定である。
Rust SDKは、integerのProtocol相互運用範囲、finite number、unknown field、Union shape、Entry path等をProtocol boundaryで検証しなければならない。
## Dynamic Worldspaceでのlifecycle
### `observe`
Projection implementationはrequest時にbackend dataを解決し、struct instanceを生成する。
```rust
async fn resolve_file(path: &WipPath) -> Result<PublishedObject> {
let stat = filesystem.stat(path).await?;
let file = File {
name: stat.name,
path: stat.path,
reference: make_reference(&stat),
validator: make_validator(&stat),
};
Ok(PublishedObject::new(file)
.expose(FileInterfaceDefinition))
}
```
Host Runtimeは`PublishedObject`からProtocol Objectを返す。Response生成後にこの`File` instanceを破棄してよく、Worldspace内の全fileに対応するinstanceを保持しない。
Requested depthが正の場合、Directory listingで発見した各entryについて、その時点のstruct instanceを生成して公開できる。Providerがdirectory全体を先に収集してから`Vec`として返すことは要求しないが、一つのparentのobservationを成功させるには公開されたindexable childrenをclean exhaustionまで列挙しなければならない。
```rust
let mut entries = filesystem.discover_children(path, budget).await?;
while let Some(entry) = entries.next().await? {
let Some(child) = project_current_object(
entry.path(),
context,
).await? else {
continue;
};
observation.push_child(child)?;
}
```
`discover_children`、`budget`、`observation`はconceptualであり、具体APIではない。必要なsemanticsは次である。
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
- 公開されたindexable childrenをすべて列挙し、clean exhaustionで終了する。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なsuccess responseを返さない。
- Requested depthに達したnodeではchild discoveryを行わず、`children`を未観測として省略する。
- 異なるconcrete published typeを、Providerから共通のreturn typeとして扱える。
具体的なreturn type、internal streaming、backpressure、type erasure、error propagationは未決定である。
### `call_operation`
Hostはcall時に、requestのTargetと`InterfaceTarget`をHost Runtimeへ渡す。Runtimeは一つのatomic execution boundaryへ入ってから`target.path`を現在のObject implementationとstableなbackend対象へ解決する。
```rust
runtime.call_current(
target,
interface,
operation,
arguments,
context,
|path, context| resolve_current_projection(path, context),
).await
```
`resolve_current_projection`と`runtime.call_current`というAPIを採用することは決定しない。ResolverはRuntimeが開始したexecution boundary内で呼ばれ、返す値はそのboundary中にObject identityとbackend対象を安定して保持するhandleまたはtransaction contextを含む必要がある。このexampleが示す要件は次である。
- 過去の`observe`で作成したstruct instanceを保持していなくてもOperationを実行できる。
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれ、self-scopeを含む祖先制約を満たすことを確認する。指定された`interface.scope_ref`と現在のscope Objectの`ref`も照合し、満たせない場合は`InterfaceMismatch`を返す。`scope_ref`の省略だけでは拒否しない。
- 現在のInterfaceにOperationが宣言されていなければ`OperationNotFound`、Operationを公開しつつ実行だけを拒否する場合は`PermissionDenied`を返す。
- `interface.validator`を現在のInterface validatorへ照合する。Mutable Interfaceにvalidatorがなければ`InterfaceValidatorRequired`、不一致なら`InterfaceValidatorMismatch`を返す。
- 現在のProjected Object、Object validator、選択されたInterfaceとそのvalidator、Descriptor、Operation、Rust側のObject implementation、実際のbackend対象を一つのexecution boundary内で一致させる。
- Object validatorが指定されている場合は、現在のObject identityと保護対象状態の両方へ照合し、同じexecution boundary内でOperationを実行する。
- Pathが別Objectを指す場合は、provider-local revisionや内容が同じでもObject validatorを一致させない。
- Providerのvalidatorがobject-localなrevisionだけの場合、HostはObject identityを含む形へnamespace化またはwrapできなければならない。
- Object validatorが省略され、Operationがvalidatorを要求する場合は、実行せず`ValidatorRequired`を返す。
- Object validatorなしの実行をOperationが許可する場合は、実行時にpathが指す現在Objectへ無条件に実行する。
- Macro-generated dispatchが、選択されたInterface traitのmethodを現在のstruct instanceに対して呼び出す。異なるInterfaceの同名Operationをmergeまたは曖昧に解決しない。
- Operation実行後の現在状態に対応するvalidatorを、必要に応じて`CallOperationResponse`へ返せる。
Object validatorをObject identityへ結び付けるためにClient-facingな`ref`を要求しない。ProviderまたはHostはHost-privateなidentityを使用できるが、そのidentity confirmation、Object validator比較、Interface membershipとvalidator比較、DescriptorとOperation implementationの固定、authorization、Operation実行を一つのatomic boundaryで行う。
Operation実行後validatorを、同じstruct instanceから再計算するか、Operation resultとともに返すか、backendを再観測するかは未決定である。どの方式でも、resultを生成した実行後状態とvalidatorを対応させなければならない。
Structが`PathBuf`だけを保持し、validator比較後にpathを無条件で開き直す実装は、対象差し替えに対するatomicityを保証できない場合がある。Rust APIは、file descriptor、transaction、conditional update、lock等を利用してbackend固有のatomicityを実現できる必要があるが、その抽象化は未決定である。
## 複数Interfaceの実装
一つのstructは複数のInterface traitを実装できる。
```rust
impl ReadOnlyFileInterface for File {
// ...
}
impl WritableFileInterface for File {
// ...
}
```
Publication時に、現在の主体へ公開する0個以上のInterfaceを選択し、表示したい順で追加する。
```rust
let mut object = PublishedObject::new(file)
.expose(ReadOnlyFileInterfaceDefinition);
if authorization.can_write() {
object = object.expose(WritableFileInterfaceDefinition);
}
object
```
Protocol Objectの`interfaces`は追加順を保持するordered setである。同じInterface参照の重複は拒否する。各Interface Descriptorは独立したOperation集合を宣言し、複数trait implementationを一つのDescriptorへ暗黙にmergeしない。
異なるInterfaceは同じOperation名を宣言できる。Dispatchはrequestの`InterfaceTarget.reference`とOperation名の組で一意に決まり、表示上のqualified表記は[Text Viewの参照表記](../4.2-text-view.md#参照表記)に従う。Signature全体をProtocol request fieldへparseし直さない。
Rust traitのsupertrait、trait alias、複数traitの組み合わせをDescriptor生成へどう対応付けるかは未決定であり、Protocol上のInterface inheritanceを暗黙に導入しない。
## 固定Interface向けのsugar
一つのRust typeを常に同じInterface集合で公開する場合、次のようなsugarを追加してよい。
```rust
#[wip::object(interfaces = [ReadOnlyFileInterface, WritableFileInterface])]
struct File {
// ...
}
```
または、conceptualには`WipObject`のassociated itemとしてdefault Interface集合を指定できる。
ただし、固定Interface集合をcore modelにはしない。主体、policy、resource kind等に基づいて同じstructへ異なるInterface subsetを追加できるexplicit publication modelを基礎とする。
Sugarを使用する場合も、対象typeが指定されたすべてのInterface traitを実装していることと、同じInterfaceを重複していないことを確認しなければならない。
## HostへのInterface登録
Generated Interface DefinitionをHostへ登録し、構造化されたInterface参照を割り当てる必要がある。
参照の絶対scopeとlocal name、および配置pathへの祖先制約は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。以下のconceptual API例はscope / nameを渡す具体構文を省略しており、暗黙のroot scopeを定めるものではない。登録時のcontextと引数の具体APIは引き続き未決定である。Self-scopeは許可され、scopeはそのpathのObjectに紐づく。Scope Objectのoptionalな`ref`はProtocolの`scope_ref`として観測・検証し、`ref`がない場合のidentity追跡不能も許容する。Scopeの移動追跡や独立したscope validatorを導入しない。
Conceptualには、eager registrationとlazy registrationの両方が考えられる。
```rust
let file_read_interface = host.register_interface(ReadOnlyFileInterfaceDefinition)?;
let file_write_interface = host.register_interface(WritableFileInterfaceDefinition)?;
let object = PublishedObject::new(file)
.expose(file_read_interface)
.expose(file_write_interface);
```
または、Hostが最初のpublication時に各definitionをinternしてよい。
どちらの場合も次を守る。
- MutableなInterface参照では`fetch_interface`がvalidatorを返し、同じ参照とvalidatorの組から異なるDescriptorを返さない。
- Validatorを返さないInterface参照は、その参照の有効期間中Descriptorをimmutableにする。
- Descriptorを更新する場合は同じ参照へ新しいvalidatorを発行するか、新しいInterface参照を使用する。
- Objectの`interfaces`へ設定した各Interface参照のDescriptorを、その主体から`fetch_interface`できる。
- 一つのObjectへ同じInterface参照を重複して追加しない。
- 構造的に同じDescriptorを同じ参照へdeduplicateすることは要求しない。
- Rust type名やprocess-local addressを、そのままportableなProtocol semanticsとして扱わない。
## Request contextとauthorization
Operation implementationは、Object instance以外にrequest contextを必要とし得る。
- 現在の主体。
- Cancellationとdeadline。
- Tracing / audit context。
- Host serviceやbackend connectionへのaccess。
- Resource limit。
これらをmethod parameter、receiver wrapper、generated context、task-local stateのどれで渡すかは未決定である。Protocol parameterとして宣言された値と、Hostが注入するcontextを明確に区別しなければならない。
Object publication時のInterface集合の選択と、Operation call時のmembership、Interface validator、authorizationの再評価を実装できる必要がある。以前の`observe`で公開されたInterface集合だけを根拠に、現在のOperation実行を許可してはならない。
Operation resultのRecord、List、Union等へnestedしたすべての`Entry`位置について、Hostは現在の主体にそのpathを認識させてよいことを再帰的に確認できなければならない。通常のstructural Value validationだけではこのvisibility checkを満たさない。Macro-generated code、Projection、Host Runtimeのどこで確認するかは未決定である。
Operation argumentとして`Entry` pathを受け取った場合も、pathの所持をauthorizationの証明として扱わず、参照先へ作用する時点で現在の主体について認可する。
## Error model
Rust methodのerror表現は、次の四種類を区別できなければならない。
- Protocol validationまたはdispatch failure。
- Effectがcommitされていないと保証できるHost / backend implementation failure。
- Operationの未実行またはrollbackを保証できない`OperationOutcomeUnknown`。
- Interfaceがreturn typeとして宣言したdomain failure。
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Rustの`Result<T, E>`をdomain Union、Host error、または両方のどれへ対応付けるかは未決定である。
MacroがRust panic、result encode failure、宣言型に違反するimplementation resultをdomain failureへ自動変換してはならない。Host Runtimeは、effectがcommitされていないと保証できる場合だけ`Internal`へmappingし、保証できない場合は`OperationOutcomeUnknown`へmappingする。Clientへ送信済みか不明なtransport切断も自動retryしない。
## 要件
Rust API modelは少なくとも次を満たす。
- Interface Descriptorをtrait declarationから生成し、trait implementationごとに変化させない。
- Structが`WipObject`と、追加する各Interface traitを実装していることをcompile timeに確認する。
- 0個以上のInterfaceをpublication時に明示し、実装済みtraitの自動列挙に依存しない。
- Objectのpath、children、Interface集合をstructへ恒久的に固定しない。
- Struct instanceをrequest scopeで生成および破棄できる。
- 全Object instanceの事前登録を要求しない。
- Direct childrenを現在の主体についてprojectし、非公開Objectとそのsubtreeを除外しつつlazyに発見し、success responseではclean exhaustionまで列挙する。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なobservationをsuccess responseとして返さない。
- 同じstructへ複数のInterfaceを同時に追加でき、追加順を保持し、重複を拒否できる。
- 異なるInterfaceの同名Operationを、Interface参照とOperation名の組でdispatchできる。
- Protocol boundaryでarguments、result、Object、Descriptorを検証する。
- Operation result内のnestedな`Entry`へ、現在の主体についてvisibility checkを適用できる。
- Host-private fieldをProtocol ObjectまたはValueへ暗黙にserializeしない。
- Call時の現在の主体、Object visibility、Object validator、Interface membershipとvalidator、Descriptor、Operation、Rust側implementation、backend対象を一貫したexecution boundaryで扱う。
- 非公開Object、Interface非membership、非公開Operation、公開済みOperationの実行拒否をそれぞれ`NotFound`、`InterfaceMismatch`、`OperationNotFound`、`PermissionDenied`へ対応付ける。
- Interface validatorの必須・不一致を`InterfaceValidatorRequired`、`InterfaceValidatorMismatch`へ対応付ける。
- Object validatorを現在のObject identityと保護対象状態へ結び付け、別Objectに同じprovider-local revisionがあっても一致させない。
- Object validator比較、identity confirmation、Interface membershipとvalidator比較、DescriptorとOperation実装の固定、authorization、Operation実行を同じatomic boundaryで行う。
- Object validatorを要求するOperationへの省略を`ValidatorRequired`として拒否できる。
- Provider、Host Runtime、macro-generated codeのerrorを、effect未commitを保証できる`Internal`と、保証できない`OperationOutcomeUnknown`へ安全に対応付けられる。
- InvalidなObject、Descriptor、Operation resultをsuccess responseとして送信せず、Host-side validation failureをdomain failureへ変換しない。
- Rust SDK固有の型やlifetimeをProtocol semanticsとして要求しない。
## 非目標
このdraftは次を決定しない。
- 最終的なcrate、module、trait、attribute、deriveの名前。
- Proc macroの内部実装。
- 特定のasync runtime。
- Object instanceの常駐registryまたはactor model。
- Rust trait objectをwireへ公開すること。
- Rust trait inheritanceをProtocol Interface inheritanceとして扱うこと。
- Backend entityとRust structまたはProtocol Objectの一対一対応。
- Client SDKのtyped proxy API。
## 未決定事項
- `WipObject`の具体的なmethodとmetadata ownership。
- Object metadata取得をsyncまたはasyncのどちらにするか。
- Publication APIがObject instanceをown、borrow、`Arc`化するか。
- Generated Interface Definitionの具体的なRust表現。
- Interface登録とscope / local name割当のlifecycle。
- Trait、Operation、parameter名のrename規則。
- Rust doc commentとProtocol Documentationの対応。
- Supported Rust typeと`TypeExpr`の対応。
- `Option<T>`を許可する位置。
- Generic method、associated type、lifetime parameterをInterface traitで許可するか。
- `&self`、`&mut self`、owned receiverのどれをOperationで許可するか。
- Async methodをnative syntax、generated boxed future、別macroのどれで扱うか。
- Request contextとProtocol parameterのAPI上の分離。
- Nestedな`Entry` resultのvisibility checkをMacro-generated code、Projection、Host Runtimeのどこで行うか。
- Domain failure、Host error、panicのRust上の表現。
- Macro-generated dispatchとHost Runtimeの責務境界。
- Published ObjectをProviderから返す際のtype erasure。
- Validator比較とOperation実行を囲むbackend-specificなatomicity abstraction。
- Provider-local validatorをObject identityへnamespace化またはwrapするHost API。
- Operation実行後validatorをObject instance、Operation implementation、Providerのどこから取得するか。
- 主体ごとに異なるInterface combinationを追加したPublished Objectのconcrete return typeをどう統一するか。
- Rust client APIを同じtrait declarationから生成するか。
これらを決定する際は、静的service objectだけでなく、Filesystem、database row、virtual aggregate、主体別Interface compositionを同じAPIで実装できるか評価する。
+701
View File
@@ -0,0 +1,701 @@
# TypeScript API Model
## Status
この文書は、WIP HostをTypeScriptで実装するための概念的なAPI modelを整理するdraftである。
このdraftで示すfunction、object shape、generic type、class、method signatureは説明用であり、具体的なTypeScript API syntaxとして確定しない。Package構成、schema builderの最終名称、ownership、async context、error type、bundler integrationも未決定である。
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。動的なObject projectionの運用要件は[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)、対応するRust modelは[Rust API Model](rust-api-model.md)を参照する。
## 目的
TypeScript APIでは、次のmodelを自然に表現できるようにする。
1. Runtimeに残るdefinition objectでWIP Interfaceを宣言する。
2. 同じdefinition objectからInterface Descriptor、Value codec、validationを構築する。
3. Definition objectからOperation implementationのTypeScript型を推論する。
4. Classまたはplain objectが、Protocol Objectとして公開するmetadataと、必要に応じて0個以上のInterfaceのOperation implementationを提供する。
5. Objectを返す時点で、公開するInterfaceのordered setを明示的に選択する。
6. Object instanceをWorldspace全体について常駐させず、`observe`と`call_operation`の解決時にdata-drivenに生成できる。
7. Custom transpiler、TypeScript compiler plugin、decorator metadataをcore APIの前提にしない。
このmodelでは、Protocol上のObjectとInterfaceの関連付けは`Object.interfaces`だけで表す。Operation implementationへ到達するためのTypeScript / JavaScript内部codeはSDK / Host Runtimeの実装詳細であり、新しいProtocol conceptを導入しない。
## TypeScriptの型消去
TypeScriptの`interface`、`type`、generic type argumentは、通常のJavaScript outputには残らない。
```ts
interface FileOperations {
read(args: ReadArguments): Promise<Uint8Array>;
}
```
このdeclarationだけから、runtimeにInterface Descriptorを取得することはできない。
```ts
host.registerInterface(FileOperations); // runtime valueではない
```
そのため、TypeScript APIではruntime valueをsource of truthにする。
```ts
const FileInterface = wip.interface({
// runtime schema
});
```
このobjectは通常のJavaScript valueとしてtranspile後も残る。TypeScript型は、このruntime definitionから推論する。
## Conceptual model
### Type definition
Record、Enum、Union等もruntime definition objectとして宣言する。
```ts
const WriteResult = wip.union({
name: "WriteResult",
cases: {
written: wip.record({
bytesWritten: wip.integer(),
}),
rejected: wip.record({
reason: wip.string(),
}),
},
});
```
TypeScript型はdefinitionから推論する。
```ts
type WriteResult = wip.Infer<typeof WriteResult>;
```
`wip.Infer`の具体的な型実装と、Union DTOのTypeScript representationは未決定である。Runtime definitionと推論された型が、同じProtocol `TypeExpr`および`Value` semanticsを表すことが要件である。
### Interface definition
WIP Interfaceはruntime definition objectとして宣言する。
```ts
const FileInterface = wip.interface({
documentation: {
summary: "Operations available on a file",
},
types: {
WriteResult,
},
operations: {
read: wip.operation({
name: "read",
documentation: {
summary: "Reads bytes from the file",
},
parameters: {
offset: wip.optional(wip.integer()),
length: wip.optional(wip.integer()),
},
returns: wip.bytes(),
}),
write: wip.operation({
name: "write",
documentation: {
summary: "Replaces the file contents",
},
parameters: {
content: wip.bytes(),
},
returns: WriteResult,
}),
},
});
```
このsyntaxはconceptualである。Definition objectのproperty名、documentationの指定方法、Named typeの参照方法、`optional`の表現は未決定である。
Interface definitionは、次のlogical declarationに対応する。
- 一つのruntime definition objectを、一つのInterface Descriptorを構築するTypeScript側definitionとして扱う。
- JavaScript variable名`FileInterface`はlocal code上の識別子であり、Protocol `InterfaceDescriptor`のfieldや構造化されたInterface参照としてそのまま公開しない。
- 各Operation definitionが保持するstableなstringをOperation Declarationの`name`として扱う。
- `parameters`で宣言されたstableなstringを名前付きOperation parameterとして扱う。
- `returns`をOperation return typeとして扱う。
- Runtime type definitionをDescriptorの`TypeExpr`とProtocol `Value`へ対応付ける。
- DocumentationをInterface、Operation、parameter、return、type、field、caseへ指定できるようにする。
Descriptorのshapeはruntime definition objectによって決まり、各classまたはplain objectのimplementationによって変化しない。
Operation、parameter、type、field、caseのProtocol名はfinalized definition内のstableなstring dataとして保持する。JavaScript variable名、constructor名、functionの`.name`、prototype上のmethod列挙から推測しない。Hostはincoming Operation名をfinalized dispatch table上のinternal tokenまたはaccessorへ解決し、Clientから受け取った名前をそのままobject property lookupへ使用しない。Object-literal keyをauthoring sugarとして使う場合にproperty manglingからどう保護するかは未決定である。
Definition objectはHostへの登録時に一つのimmutableなartifactとしてfinalizeする。MutableなProtocol Interface参照を提供する場合は、同じ参照へ新しいvalidatorと新しいfinalized artifactを対応付け、同じ参照とvalidatorの組から異なるDescriptorを返してはならない。
### Operation implementation type
Interface definitionから、必要なOperation implementationのTypeScript型を推論する。
```ts
type FileOperations =
wip.Implementation<typeof FileInterface>;
```
Conceptualには、次のようなobject typeへ展開される。
```ts
type FileOperations = {
readonly read: (args: {
offset?: number;
length?: number;
}) => Promise<Uint8Array>;
readonly write: (args: {
content: Uint8Array;
}) => Promise<WriteResult>;
};
```
Function-valued propertyを基本形とし、`strictFunctionTypes`を含むstrictなcompiler settingsの下でparameterをcontravariantに検査できるようにする。通常のclass method parameterはTypeScriptのassignabilityでbivariantに扱われる場合があるため、method-shapedな`implements`だけを「すべてのschema-valid argumentを安全に受け取れる」ことの完全な証明としては扱わない。
Definition-awareなbuilderでimplementationをcontextual typingする形も考えられる。
```ts
const operations = FileInterface.implement({
read: async (args) => {
// argsはruntime definitionから推論される
},
write: async (args) => {
// ...
},
});
```
`implement`の採用は未決定である。どの形でもruntime validationとcallable checkをauthorityとし、TypeScript型はdeveloper feedbackとして扱う。
このfunction property shapeは説明用である。Operation parameterを一つのarguments objectとして渡すか、request contextを別parameterにするか、sync returnを許可するかは未決定である。
### Object implementation
Classは、その時点でWorldspaceへ投影するObjectをHost内部で表現できる。
```ts
class File implements WipObject, FileOperations {
readonly #path: string;
readonly #objectMetadata: WipObjectMetadata;
constructor(
path: string,
objectMetadata: WipObjectMetadata,
) {
this.#path = path;
this.#objectMetadata = objectMetadata;
}
metadata(): WipObjectMetadata {
return this.#objectMetadata;
}
readonly read: FileOperations["read"] = async (args) => {
return filesystem.read(this.#path, args);
};
readonly write: FileOperations["write"] = async (args) => {
return filesystem.write(this.#path, args.content);
};
}
```
`WipObject`と`WipObjectMetadata`の具体的なshapeは未決定である。概念的にはProtocol Objectのうち、publication時に選択するInterface集合以外の公開metadataを提供する。
```ts
interface WipObject {
metadata(): WipObjectMetadata;
}
interface WipObjectMetadata {
name: string;
description?: string;
ref?: string;
validator?: Uint8Array;
}
```
`WipObjectMetadata`へ次を含めない。これらをimplementing classのprivate fieldとして保持することは禁止しない。
- Absolute path。PathはObjectのfieldではなく、Worldspace resolutionのcontextである。
- Children。ChildrenはTree上の配置contextである。
- 固定されたInterface集合。InterfaceはObjectを公開する時点で追加する。
- Backend connection、file descriptor、credential等のHost-private state。
Classが保持するpathやbackend handle等をOperation implementationに使用してよいが、Object metadataまたはProtocol Objectへ自動的にserializeしない。
Classの使用は必須ではない。Structural typingを利用してplain objectも使用できる。
```ts
const file = {
metadata() {
return objectMetadata;
},
read: async (args) => {
return filesystem.read(path, args);
},
write: async (args) => {
return filesystem.write(path, args.content);
},
} satisfies WipObject & FileOperations;
```
### Object publication
ObjectをWorldspaceへ返す時点で、object instanceと公開する0個以上のInterface definitionを明示的に組み合わせる。Interfaceは追加順を保持するordered setであり、同じdefinitionまたは同じ登録済み参照を重複して追加しない。
```ts
const file = new File(path, objectMetadata);
const object = wip.publish(file)
.expose(FileInterface, file);
```
Publication APIはconceptualに、追加する各definitionと、そのImplementation型を満たすbindingを受け取る。
```ts
interface WipInterfaceDefinition<Implementation extends object> {
// runtime descriptor, codec, and dispatch artifact
}
interface PublishedObject<T extends WipObject> {
expose<Implementation extends object>(
definition: WipInterfaceDefinition<Implementation>,
implementation: Implementation,
): PublishedObject<T>;
}
```
実際のTypeScript generic表現は未決定である。このconstraintは、strictなcompiler settingsとfunction-valued property shapeを使用する場合に、追加する各Interfaceへbindした値が必要なOperation functionを提供するか検査する。Object instance自身をimplementationとして渡しても、Interface専用のadapter objectを渡してもよく、どのInterfaceも追加しないObjectも表現できる。通常のclass methodやtype assertion等で検査を弱められるため、各Interfaceを追加する時点とdispatch時のruntime callable check、およびValue validationは常に必要である。
Host Runtimeは各bindingを登録済みInterface参照と対応付ける。異なるInterfaceが同名Operationを宣言しても、選択されたInterface参照に対応するbindingだけをdispatch対象とし、単一object上の同名propertyへmergeしない。
`PublishedObject`はHost SDK内のconceptual typeであり、Protocol typeとして追加しない。Host Runtimeはこれを使用して次を構築する。
```ts
const protocolObject = {
name: metadata.name,
description: metadata.description,
interfaces: selectedInterfaceReferences,
ref: metadata.ref,
validator: metadata.validator,
};
```
Optional propertyの`undefined`はwireへ出力せず、field省略として扱う。`null`へ自動変換しない。
`selectedInterfaceReferences`は、Hostが各runtime Interface definitionを登録または公開するときに割り当てる構造化参照`{ scope, name }`を、publication APIへの追加順で並べたものである。同じ`(scope, name)`の重複は拒否し、Objectの配置pathに対する祖先制約も検証する。Variable名やJavaScript object identityをportableなProtocol参照としてそのまま使用することは要求しない。
`observe`はDescriptor自体をObjectへ埋め込まず、このInterface参照のordered setだけを返す。Clientが各参照を`fetch_interface`したとき、Host Runtimeはruntime definition objectから構築したDescriptorと、存在する場合はInterface validatorを返す。同じ参照とvalidatorの組のDescriptorはHost上で共有およびcacheできるため、Object instanceごとにDescriptorを再生成または再送する必要はない。
## Runtime definitionの責務
`wip.interface(...)`が返すdefinition objectは、conceptualには次を生成または提供する。
- Interface Descriptorを構築するruntime definition。
- Operation propertyとOperation Declarationの対応情報。
- ArgumentsのProtocol `Value` decoder。
- ResultのProtocol `Value` encoder。
- Argumentsとresultのruntime validation。
- Operation名から、そのInterfaceへbindしたimplementationの対応functionを呼び出すdispatch code。
- 各Interfaceの推論されたImplementation型を満たす値を、そのInterface参照へbindするtyped entry point。
- HostがInterface参照、optional validator、Descriptorを登録、取得、cacheするためのlocal identity。
TypeScriptのstructural type checkはcompile-timeのdeveloper feedbackである。Runtimeでは少なくとも次を検証する。
- Object metadataがProtocol上有効である。
- 追加する各InterfaceへbindしたOperation implementationが呼び出し可能である。
- 同じInterface参照をObjectへ重複して追加していない。
- ArgumentsがDescriptorに従っている。
- ResultがDescriptorに従っている。
- Integer、Number、Entry、Record、Union等の追加制約を満たす。
Generatedまたはgenericなdispatch codeはTypeScript SDK内部の実装詳細である。Host内部のdispatch mechanism、class名、prototype、constructor等をProtocolへ公開しない。
## TypeScript typeとProtocol type
Runtime definitionは、Descriptorの`TypeExpr`、Protocol `Value`、TypeScript static typeの三つを対応付ける。
Conceptualな対応例は次のとおりである。
| WIP definition | TypeScript representation | Runtime validation |
| --- | --- | --- |
| `wip.unit()` | 専用の`WipUnit` sentinel | Optional省略と区別する |
| `wip.boolean()` | `boolean` | Booleanのみ |
| `wip.integer()` | `number` | Safe integer範囲 |
| `wip.number()` | `number` | Finite binary64 |
| `wip.string()` | `string` | Unicode string |
| `wip.bytes()` | `Uint8Array`等 | Octet列 |
| `wip.json()` | JSON-compatible type | Bytesを拒否する |
| `wip.entry()` | Branded string等 | Canonical Worldspace path |
| `wip.record(...)` | Object type | Required、optional、unknown field |
| `wip.list(...)` | Arrayまたはreadonly array | 各itemを検証 |
| `wip.enum(...)` | String literal union | 宣言caseのみ |
| `wip.union(...)` | Discriminated union | Case、payload、shape |
この表の具体的なpublic typeは未決定である。
`wip.json()`が受け付けるのは、null、boolean、finite number、string、Array、およびstring-keyedなplain objectから再帰的に構成されるJSON-compatible valueだけである。`undefined`、`bigint`、`symbol`、function、`Uint8Array`、`Date`、`Map`、`Set`、class instance、cyclic object graph、NaN、正負Infinity、sparse array等を暗黙にJsonへ変換せず、validation failureとする。Integerとして表現する値にもProtocolの相互運用範囲を適用する。
TypeScriptの`number`はIntegerとNumberの両方に使用できるため、runtime schemaの位置に基づいて検証する。`wip.integer()`はProtocolの相互運用範囲を超える値を拒否する。`bigint`を暗黙にIntegerへ変換しない。
Protocol `Unit`は、例えば`wip.unitValue`のような専用singleton / sentinelで表す。`undefined`をUnitとして使用しない。RequiredなUnit fieldはsentinelを明示し、optionalなUnit fieldではsentinelがpresent Unit、property欠落またはoptional positionの`undefined`が省略を表す。
`wip.optional(T)`はfieldまたはparameterの省略を表し、`null`を許可する意味ではない。Implementationへ渡すarguments objectではproperty欠落を省略とし、optional positionへ明示された`undefined`をSDK convenienceとして省略へnormalizeしてよい。`undefined`自体をProtocol Valueとしてencodeせず、required positionの`undefined`はvalidation failureとする。
## Dynamic Worldspaceでのlifecycle
### `observe`
Projection implementationはrequest時にbackend dataを解決し、classまたはplain object instanceを生成する。
```ts
async function resolveFile(
path: WipPath,
subject: Subject,
): Promise<PublishedObject | undefined> {
const entry = await filesystem.resolve(path);
if (!entry || !canSee(subject, entry)) {
return undefined;
}
const file = new File(
entry.path,
createObjectMetadata(entry),
);
let object = wip.publish(file)
.expose(ReadOnlyFileInterface, {
read: file.read,
});
if (canWrite(subject, entry)) {
object = object.expose(WritableFileInterface, {
write: file.write,
});
}
return object;
}
```
Host Runtimeは`PublishedObject`からProtocol Objectを返す。Response生成後にこの`File` instanceを破棄してよく、Worldspace内の全fileに対応するinstanceを保持しない。
Requested depthが正の場合、Providerはdirect childrenをlazyに発見できる。Directory全体を先に収集してArrayとして返すことは要求しないが、一つのparentのobservationを成功させるには公開されたindexable childrenをclean exhaustionまで列挙しなければならない。ConceptualにはAsync Iterable等を利用できる。
```ts
const entries = filesystem.discoverChildren(path, budget);
for await (const entry of entries) {
const child = await projectCurrentObject(
entry.path,
context,
);
if (!child) {
continue;
}
observation.pushChild(child);
}
```
`discoverChildren`、`budget`、`observation`はconceptualであり、具体APIではない。必要なsemanticsは次である。
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
- 公開されたindexable childrenをすべて列挙し、clean exhaustionで終了する。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なsuccess responseを返さない。
- Requested depthに達したnodeではchild discoveryを行わず、`children`を未観測として省略する。
- 異なるclassやplain objectを共通のpublished return typeとして扱える。
具体的なAsync Iterator contract、internal streaming、backpressure、error propagationは未決定である。
### `call_operation`
Hostはcall時に、requestのTargetと`InterfaceTarget`をHost Runtimeへ渡す。Runtimeは一つのatomic execution boundaryへ入ってから`target.path`を現在のObject implementationとstableなbackend対象へ解決する。
```ts
await runtime.callCurrent(
target,
interfaceTarget,
operation,
argumentsValue,
context,
(path, context) => resolveCurrentProjection(path, context),
);
```
`resolveCurrentProjection`と`runtime.callCurrent`というAPIを採用することは決定しない。ResolverはRuntimeが開始したexecution boundary内で呼ばれ、返す値はそのboundary中にObject identityとbackend対象を安定して保持するhandleまたはtransaction contextを含む必要がある。このexampleが示す要件は次である。
- 過去の`observe`で作成したobject instanceを保持していなくてもOperationを実行できる。
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれ、self-scopeを含む祖先制約を満たすことを確認する。指定された`interface.scope_ref`と現在のscope Objectの`ref`も照合し、満たせない場合は`InterfaceMismatch`を返す。`scope_ref`の省略だけでは拒否しない。
- 現在のInterfaceにOperationが宣言されていなければ`OperationNotFound`、Operationを公開しつつ実行だけを拒否する場合は`PermissionDenied`を返す。
- `interface.validator`を現在のInterface validatorへ照合する。Mutable Interfaceにvalidatorがなければ`InterfaceValidatorRequired`、不一致なら`InterfaceValidatorMismatch`を返す。
- 現在のProjected Object、Object validator、選択されたInterfaceとそのvalidator、Descriptor、Operation、そのInterfaceへbindしたTypeScript implementation、実際のbackend対象を一つのexecution boundary内で一致させる。
- Object validatorが指定されている場合は、現在のObject identityと保護対象状態の両方へ照合し、同じexecution boundary内でOperationを実行する。
- Pathが別Objectを指す場合は、provider-local revisionや内容が同じでもObject validatorを一致させない。
- Providerのvalidatorがobject-localなrevisionだけの場合、HostはObject identityを含む形へnamespace化またはwrapできなければならない。
- Object validatorが省略され、Operationがvalidatorを要求する場合は、実行せず`ValidatorRequired`を返す。
- Object validatorなしの実行をOperationが許可する場合は、実行時にpathが指す現在Objectへ無条件に実行する。
- Runtime dispatchが、選択されたInterface definitionへbindしたOperation implementationを現在のObject contextへ適用する。異なるInterfaceの同名Operationをmergeまたは曖昧に解決しない。
- Operation実行後の現在状態に対応するvalidatorを、必要に応じて`CallOperationResponse`へ返せる。
Object validatorをObject identityへ結び付けるためにClient-facingな`ref`を要求しない。ProviderまたはHostはHost-privateなidentityを使用できるが、そのidentity confirmation、Object validator比較、Interface membershipとvalidator比較、DescriptorとInterface-specific implementation bindingの固定、authorization、Operation実行を一つのatomic boundaryで行う。
Operation実行後validatorを、同じobject instanceから再計算するか、Operation resultとともに返すか、backendを再観測するかは未決定である。どの方式でも、resultを生成した実行後状態とvalidatorを対応させなければならない。
Object instanceがpath文字列だけを保持し、validator比較後にpathを無条件で開き直す実装は、対象差し替えに対するatomicityを保証できない場合がある。TypeScript APIは、transaction、conditional update、backend handle、lock等を利用してbackend固有のatomicityを実現できる必要があるが、その抽象化は未決定である。
## 複数Interfaceの実装
一つのclassまたはplain objectは複数のInterface implementation typeを満たせる。Property名が衝突する場合やInterfaceごとに異なるbehaviorを提供する場合は、Interfaceごとのadapter objectをbindできる。
```ts
type ReadOnlyFileOperations =
wip.Implementation<typeof ReadOnlyFileInterface>;
type WritableFileOperations =
wip.Implementation<typeof WritableFileInterface>;
class File
implements
WipObject,
ReadOnlyFileOperations,
WritableFileOperations
{
// ...
}
```
Publication時に、現在の主体へ公開する0個以上のInterfaceを選択し、表示したい順で追加する。
```ts
let object = wip.publish(file)
.expose(ReadOnlyFileInterface, {
read: file.read,
});
if (authorization.canWrite) {
object = object.expose(WritableFileInterface, {
write: file.write,
});
}
return object;
```
Protocol Objectの`interfaces`は追加順を保持するordered setである。同じInterface参照の重複は拒否する。複数のImplementation bindingを一つのDescriptorやdispatch tableへ暗黙にmergeせず、各Interface Descriptorとbindingは独立したOperation集合およびbehaviorを提供する。
異なるInterfaceは同じOperation名を宣言できる。Dispatchはrequestの`InterfaceTarget.reference`とOperation名の組で一意に決まり、そのInterfaceへbindしたimplementationを使用するため、同じObjectに意味の異なる同名Operationを実装できる。
```ts
const object = wip.publish(file)
.expose(UserFileInterface, {
inspect: () => inspectForUser(file),
})
.expose(AuditFileInterface, {
inspect: () => inspectForAudit(file),
});
```
表示上のqualified表記は[Text Viewの参照表記](../4.2-text-view.md#参照表記)に従う。Signature全体をProtocol request fieldへparseし直さない。
Intersection type、interface extension、mixin等をDescriptorのcompositionへ自動変換しない。Protocol上のInterface inheritanceを暗黙に導入しない。
## HostへのInterface登録
Runtime Interface definitionをHostへ登録し、構造化されたInterface参照を割り当てる必要がある。
参照の絶対scopeとlocal name、および配置pathへの祖先制約は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。以下のconceptual API例はscope / nameを渡す具体構文を省略しており、暗黙のroot scopeを定めるものではない。登録時のcontextと引数の具体APIは引き続き未決定である。Self-scopeは許可され、scopeはそのpathのObjectに紐づく。Scope Objectのoptionalな`ref`はProtocolの`scope_ref`として観測・検証し、`ref`がない場合のidentity追跡不能も許容する。Scopeの移動追跡や独立したscope validatorを導入しない。
Conceptualには、eager registrationとlazy registrationの両方が考えられる。
```ts
const readInterface = host.registerInterface(ReadOnlyFileInterface);
const writeInterface = host.registerInterface(WritableFileInterface);
const object = wip.publish(file)
.expose(readInterface, {
read: file.read,
})
.expose(writeInterface, {
write: file.write,
});
```
または、Hostが最初のpublication時に各definitionをinternしてよい。
どちらの場合も次を守る。
- MutableなInterface参照では`fetch_interface`がvalidatorを返し、同じ参照とvalidatorの組から異なるDescriptorを返さない。
- Validatorを返さないInterface参照は、その参照の有効期間中Descriptorをimmutableにする。
- Descriptorを更新する場合は同じ参照へ新しいvalidatorを発行するか、新しいInterface参照を使用する。
- Objectの`interfaces`へ設定した各Interface参照のDescriptorを、その主体から`fetch_interface`できる。
- 一つのObjectへ同じInterface参照を重複して追加しない。
- 構造的に同じDescriptorを同じ参照へdeduplicateすることは要求しない。
- Variable名、module path、JavaScript object address等を、そのままportableなProtocol semanticsとして扱わない。
- 一つのfinalized artifactを後からmutationしてDescriptorを変化させない。Hostはdefinitionをfreezeするか、検証済みのimmutable snapshotを保持できなければならない。
Registrationまたは最初のpublicationで、Descriptor、type codec、validator、Operation dispatch tableを一つのfinalized artifactとして再帰的に検証および固定する。`fetch_interface`、arguments / result validation、Operation dispatchはすべて同じartifactを使用する。Descriptorだけをsnapshotし、dispatch時にはmutation可能な元definitionを読み直す実装にはしない。Finalization後のmutationは拒否するか、既存artifactへ影響しないものとして扱う。
## Request contextとauthorization
Operation implementationは、Object instance以外にrequest contextを必要とし得る。
- 現在の主体。
- `AbortSignal`等のcancellation。
- Deadline。
- Tracing / audit context。
- Host serviceやbackend connectionへのaccess。
- Resource limit。
これらをmethod parameter、wrapper object、AsyncLocalStorage、closure等のどれで渡すかは未決定である。Protocol parameterとして宣言された値と、Hostが注入するcontextを明確に区別しなければならない。
Object publication時のInterface集合の選択と、Operation call時のmembership、Interface validator、authorizationの再評価を実装できる必要がある。以前の`observe`で公開されたInterface集合だけを根拠に、現在のOperation実行を許可してはならない。
Operation resultのRecord、List、Union等へnestedしたすべての`Entry`位置について、Hostは現在の主体にそのpathを認識させてよいことを再帰的に確認できなければならない。通常のstructural Value validationだけではこのvisibility checkを満たさない。Runtime definition、Projection、Host Runtimeのどこで確認するかは未決定である。
Operation argumentとして`Entry` pathを受け取った場合も、pathの所持をauthorizationの証明として扱わず、参照先へ作用する時点で現在の主体について認可する。
## Error model
TypeScript methodのerror表現は、次の四種類を区別できなければならない。
- Protocol validationまたはdispatch failure。
- Effectがcommitされていないと保証できるHost / backend implementation failure。
- Operationの未実行またはrollbackを保証できない`OperationOutcomeUnknown`。
- Interfaceがreturn typeとして宣言したdomain failure。
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Thrown value、rejected Promise、explicit result objectをdomain resultまたはHost errorへどう対応付けるかは、Interface authoring APIで明示しなければならない。
Arbitrary thrown value、Promise rejection、result encode failure、宣言型に違反するimplementation resultをdomain failureへ自動変換してはならない。Host Runtimeは、effectがcommitされていないと保証できる場合だけ`Internal`へmappingし、保証できない場合は`OperationOutcomeUnknown`へmappingする。Abortやtransport切断を未実行の証明として扱わず、自動retryしない。
## Custom transpilerを必須にしない
通常のTypeScript `interface`または`type`をsource of truthにしてInterface Descriptorを生成するには、TypeScript Compiler API、custom transformer、code generator等が必要になる。
Core APIはこれを要求しない。Runtime definition objectをsource of truthにすることで、通常の`tsc`、bundler、JavaScript runtimeで動作できるようにする。
Build-time toolingはoptionalなauthoring layerとして追加してよい。
```ts
/** @wip-interface */
interface FileOperations {
read(args: ReadArguments): Promise<Uint8Array>;
}
```
Toolingがこのdeclarationからruntime definition moduleを生成することはできるが、生成物はcore APIと同じruntime definition semanticsを満たさなければならない。
Build-time toolingには少なくとも次の課題がある。
- TypeScript compiler versionとCompiler APIへの依存。
- Bundler、incremental build、package boundaryとの統合。
- Generic、conditional type、mapped type、importされたtypeの解決。
- Documentation抽出。
- Sourceとgenerated runtime schemaの一致。
- Runtime validation codeの生成。
## Decoratorをcoreにしない
Decoratorだけでは、通常はUnion case、Record field、optional、Entry、Integer、nested type、generic、documentation等を完全なruntime schemaとして取得できない。
`emitDecoratorMetadata`等が提供するconstructor-level metadataだけでは、WIP Interface DescriptorとValue validationを再構築するには不足する。Decoratorをoptional sugarとして提供する場合も、明示的なruntime definitionまたはbuild-time generationへ接続する。
Class inheritanceをInterface declarationの基本形にすると、複数Interface implementation、plain object、data-drivenなpublicationが不必要に制限されるため、runtime definition objectをcore modelとする。
## 要件
TypeScript API modelは少なくとも次を満たす。
- Runtime definition objectをInterface Descriptorとruntime validationのsource of truthにする。
- 同じdefinitionからTypeScript implementation typeを推論できる。
- Strictなcompiler settingsとfunction-valued propertyを使用して、Object metadata providerが`WipObject`を満たし、各Interfaceへbindする値がそのImplementation型を満たすか検査できる。
- Compile-time型を完全なauthorityとせず、callable checkとProtocol boundaryのruntime validationを必須にする。
- 0個以上のInterfaceをpublication時に明示し、objectが持つpropertyやmethodの自動列挙に依存しない。
- Protocol上のOperation、parameter、type、field、case名をstableなstring dataとしてfinalizeし、constructor名やfunction `.name`から推測しない。
- Objectのpath、children、Interface集合をclassへ恒久的に固定しない。
- Classまたはplain object instanceをrequest scopeで生成および破棄できる。
- 全Object instanceの事前登録を要求しない。
- Direct childrenを現在の主体についてprojectし、非公開Objectとそのsubtreeを除外しつつlazyに発見し、success responseではclean exhaustionまで列挙する。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なobservationをsuccess responseとして返さない。
- 同じclassまたはplain objectへ複数のInterface-specific implementationを同時にbindでき、追加順を保持し、重複を拒否できる。
- 異なるInterfaceの同名Operationを、Interface参照とOperation名の組およびInterface-specific bindingでdispatchできる。
- Operation result内のnestedな`Entry`へ、現在の主体についてvisibility checkを適用できる。
- Host-private fieldをProtocol ObjectまたはValueへ暗黙にserializeしない。
- Call時の現在の主体、Object visibility、Object validator、Interface membershipとvalidator、Descriptor、Operation、Interface-specific implementation binding、backend対象を一貫したexecution boundaryで扱う。
- 非公開Object、Interface非membership、非公開Operation、公開済みOperationの実行拒否をそれぞれ`NotFound`、`InterfaceMismatch`、`OperationNotFound`、`PermissionDenied`へ対応付ける。
- Interface validatorの必須・不一致を`InterfaceValidatorRequired`、`InterfaceValidatorMismatch`へ対応付ける。
- Object validatorを現在のObject identityと保護対象状態へ結び付け、別Objectに同じprovider-local revisionがあっても一致させない。
- Object validator比較、identity confirmation、Interface membershipとvalidator比較、DescriptorとInterface-specific implementation bindingの固定、authorization、Operation実行を同じatomic boundaryで行う。
- Object validatorを要求するOperationへの省略を`ValidatorRequired`として拒否できる。
- Provider、Host Runtime、runtime definition codeのerrorを、effect未commitを保証できる`Internal`と、保証できない`OperationOutcomeUnknown`へ安全に対応付けられる。
- InvalidなObject、Descriptor、Operation resultをsuccess responseとして送信せず、Host-side validation failureをdomain failureへ変換しない。
- Custom transpiler、decorator、特定bundlerをHost実装の必須要件にしない。
- TypeScript SDK固有の型をProtocol semanticsとして要求しない。
## 非目標
このdraftは次を決定しない。
- 最終的なpackage、module、function、generic type、class、methodの名前。
- 特定のschema libraryまたはvalidation libraryの採用。
- 特定のJavaScript runtimeまたはbundler。
- Object instanceの常駐registryまたはactor model。
- JavaScript object、prototype、constructorをwireへ公開すること。
- TypeScript interface extensionをProtocol Interface inheritanceとして扱うこと。
- Backend entityとTypeScript objectまたはProtocol Objectの一対一対応。
- TypeScript Compiler APIまたはcustom transformerの必須化。
- Client SDKのtyped proxy API。
## 未決定事項
- `WipObject`と`WipObjectMetadata`の具体的なshape。
- Object metadata取得をsyncまたはasyncのどちらにするか。
- Publication APIがobjectを強参照、弱参照、closureのどれで保持するか。
- `wip.interface`、`wip.operation`、type builderの最終syntax。
- `wip.Infer`と`wip.Implementation`の具体的なgeneric type。
- `strictFunctionTypes`を含むminimum TypeScript compiler settings。
- Interface登録とscope / local name割当のlifecycle。
- 登録時にruntime definitionをdeep freezeするか、immutableなDescriptor / dispatch snapshotへ変換するか。
- Operation、parameter、type、field、caseのrename規則。
- Documentationのauthoring syntax。
- Runtime definitionと`TypeExpr`の対応。
- `readonly` property / arrayを許可するか。
- Class method、function property、closureのどれをOperation implementationとして許可するか。
- Runtime dispatchがmethod receiverの`this`を保持する方法。
- Operation property名とProtocol operation名をminifierやproperty manglingからどう保護するか。
- Sync returnを許可してPromiseへnormalizeするか。
- Request contextとProtocol parameterのAPI上の分離。
- Nestedな`Entry` resultのvisibility checkをRuntime definition、Projection、Host Runtimeのどこで行うか。
- Domain failure、thrown value、rejected Promise、Host errorの表現。
- Runtime dispatchとHost Runtimeの責務境界。
- Published ObjectをProviderから返す際のtype erasureまたは共通runtime representation。
- Validator比較とOperation実行を囲むbackend-specificなatomicity abstraction。
- Provider-local validatorをObject identityへnamespace化またはwrapするHost API。
- Operation実行後validatorをObject instance、Operation implementation、Providerのどこから取得するか。
- 主体ごとに異なるInterface combinationを追加したPublished Objectのstatic return typeをどう統一するか。
- TypeScript client APIを同じruntime definitionから生成するか。
- Build-time code generationをoptional packageとして提供するか。
これらを決定する際は、静的service objectだけでなく、Filesystem、database row、virtual aggregate、主体別Interface compositionを同じAPIで実装できるか評価する。
+41
View File
@@ -0,0 +1,41 @@
# 未解決事項
主要な未解決事項を、canonicalなCore文書へのindexとしてまとめる。
## 2.2. Operation and Interface
対象: [2.2. Operation and Interface](../2.2-operation-and-interface.md)
- RustとTypeScriptの動的Worldspace Projection Host API。[運用と要件のdraft](dynamic-worldspace-projection.md)
- RustとTypeScriptのcode-level Interface Declaration / Object publication API。[Rust API model](rust-api-model.md) / [TypeScript API model](typescript-api-model.md)のdraft
- Interface参照をcontent-addressedにし、Descriptor digestをProtocolへ導入するか。
- Descriptor version間の互換性規則。
- 現在plain textであるDocumentationへmarkup、localization、構造化exampleを追加するか。
- 現在Unionで表すDomain failureへ専用declarationを追加するか。
## 3. Protocol
対象: [3. Protocol](../3-protocol.md)
### Interface scopeとcomposition
構造化参照と絶対scopeは[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)、fieldとHost変換は[3. Protocol](../3-protocol.md#worldspace-composition)、短縮表記は[4.2. Text View](../4.2-text-view.md#参照表記)、JSON encodingは[5.1. WIP over HTTP](../5.1-wip-over-http.md#interface-reference)へ統合済み。
Self-scopeとroot scope `/`の利用は許可する。ScopeはそのpathのObjectに紐づき、optionalな`ref`で紐付きを確認する。`ref`なしの差し替えをidentityで追跡できないことは仕様上許容する。Scopeが消えれば旧参照の有効期間は終了し、Clientは不要なcacheを破棄できる。移動先は別参照であり、Clientの移動追跡は要求しない。詳細は[Scope Objectと有効期間](../2.2-operation-and-interface.md#scope-objectと有効期間)および[Protocolの紐付き確認](../3-protocol.md#scope-objectの紐付き)に従う。
残る論点:
- **複数配置とidentity**:同じUpstream Objectを複数scopeへ投影する場合の共通祖先scopeでの共有やprojectionごとの`ref`・validator。同じ`ref`とvalidatorで異なるrepresentationを返せるようにはしない。
- **部分合成と拒否条件**:公開subtree外のscope・typed `entry`への対応付け、対応不能時の公開・実行拒否とerror mapping。非公開情報の秘匿と既存のerror precedenceを維持し、effectがcommit済みか不明なcallを未実行保証のerrorへ置き換えない。
Host内部のmount API、Provider登録API、合成設定の共通data structureは引き続きHost API draftで検討する。
### Type / Value
- String length、numeric range、pattern等のconstraintを導入するか。
- 現在拒否するrecursive named typeを許可するか。
- `i64`、`u64`、`bigint`、`decimal`等を追加するか。
### Interaction / Error
- ProtocolErrorへstructured detailを追加するか。