docs: define composable object interfaces

This commit is contained in:
2026-09-18 05:01:14 +09:00
parent db1a69f73a
commit 30af56349f
12 changed files with 1793 additions and 127 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ WIPではObjectを受動的なJSON recordとして扱わない。
Operationには、名前や型に加えて、用途を判断するための短いdescriptionを持たせる。長文documentationや大量のドメインデータをInterfaceへ埋め込むことは想定しない。
ObjectはOperation定義を直接埋め込まず、実装コード上のInterface Declarationから生成されたInterface Descriptorを参照する。一つのInterfaceに宣言された関数の集合がOperation一覧となり、複数のObjectが同じInterfaceを共有できる。
ObjectはOperation定義を直接埋め込まず、実装コード上のInterface Declarationから生成された0個以上のInterface Descriptorを参照する。各Interfaceに宣言された関数の集合が再利用可能なcapability unitとなり、複数のObjectが同じInterfaceを共有できる。Objectは現在公開するInterface集合を持ち、Operation callではそのうち一つを明示してOperationを実行する。
## 発見と既知世界
+8 -6
View File
@@ -36,7 +36,7 @@ ClientとHostの境界は通信境界であると同時に、ある主体へど
**Object**は、Worldspace上で観測および操作できる対象のprotocol-level projectionである。
Objectは、探索に必要な公開情報とInterface参照を持つ。Host内部のdomain entityやApplication dataをそのままObjectとして公開する必要はない。Objectの本文、状態、domain metadata等は、Interfaceに宣言されたOperationを通して取得または変更する。
Objectは、探索に必要な公開情報とInterface参照の集合を持つ。Host内部のdomain entityやApplication dataをそのままObjectとして公開する必要はない。Objectの本文、状態、domain metadata等は、現在公開されているいずれかのInterfaceに宣言されたOperationを通して取得または変更する。
**エントリ**は、そのObjectをTree上の位置として見たときの呼び名である。
@@ -48,11 +48,13 @@ Tree上の位置はpathで表される。Pathはrootから各Objectの`name`を
## Interface
ObjectはOperation定義を直接持たず、一つのInterfaceを参照する。
ObjectはOperation定義を直接持たず、0個以上のInterface参照をordered setとして持つ。
Interfaceは、Objectで利用できるOperation、parameter、return type、documentationを定義する。Interface実装者はRustのtraitやTypeScriptのdefinition API等を使って実装コード上でInterfaceを宣言し、Libraryはその宣言からmachine-readableなInterface Descriptorを生成する。
各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を分離することで、Collection内の多数の同種Objectに同じOperation定義を重複して持たせず、ClientがInterface Descriptorを参照単位でcacheできる。
複数のObjectが同じInterfaceを共有でき、一つのObjectも複数のInterfaceを組み合わせて公開できる。ObjectとInterfaceを分離することで、Operation定義を各Objectへ重複して持たせず、ClientがInterface Descriptorを参照単位でcacheできる。
Interface集合のmembershipはset semanticsを持ち、同じ参照を重複して含めない。列挙順はOperation解決には影響しないが、HostがInterfaceを公開した順序としてUI表示に使用できる。
InterfaceとOperationの意味論は[2.2. Interface / Operation](2.2-operation.md)、Descriptorのlogical structは[3. Protocol](3-protocol.md)、人間とAIへの表示は[4.1. AI用Tools](4.1-ai-tools-example.md)で扱う。
@@ -62,8 +64,8 @@ Clientは、次の4操作によってWorldspaceを観測および操作する。
- `fetch(path)` — 一つのpathで現在公開されているObjectを取得する。
- `fetch_tree(path, depth, limit)` — 指定pathを起点にindexableな子ObjectをTreeとして取得する。
- `fetch_interface(interface)` — Interface参照からInterface Descriptorを取得する。
- `call_operation(target, operation, arguments)` — `path`とoptionalな`validator`を持つtargetに対してOperationを実行する。
- `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で定義する。
+13 -9
View File
@@ -8,10 +8,12 @@ Objectは少なくとも次の公開情報を持つ。
- **name** — 親Objectの下でのpath segment。
- **description** — Objectが何を表すかを説明する情報。
- **interface** — Objectで利用できるOperationを定義するInterfaceへの参照。
- **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を通して取得または変更する。
@@ -20,9 +22,9 @@ Objectの本文、状態、domain metadata等のApplication dataを、共通のp
### 同じ`ref`を持つObject
同じClient-facing Worldspace内で同じ`ref`を持つObjectは、一つの共有された現在状態を持つ。同じObjectを異なるpathへ配置したことを理由に、path固有の`name`、`description`、Interfaceを返してはならない。
同じClient-facing Worldspace内で同じ`ref`を持つObjectは、一つの共有された現在状態を持つ。同じObjectを異なるpathへ配置したことを理由に、path固有の`name`、`description`、Interface集合を返してはならない。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、Interfaceを含むObject representationが一致しなければならない。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、Interface集合を含むObject representationが一致しなければならない。
- 同じ`ref`と異なるvalidatorを持つObjectは、同一Objectの異なる状態として扱う。
- Hostは異なるpathから同じ`ref`を観測した場合も、一つの現在状態を一貫して返す。
- Clientは最後に成功した観測を、その`ref`を持つObjectの現在状態として扱う。
@@ -31,7 +33,9 @@ Objectの本文、状態、domain metadata等のApplication dataを、共通のp
## Interface参照
Objectは一つのInterfaceを参照する。
Objectは0個以上のInterfaceを参照する。各Interface Descriptorは、Objectに対して実行可能なOperationの一つのcapability namespaceを宣言する。
Operation名は一つのInterface Descriptor内で一意であればよく、異なるInterface間で同じOperation名を使用できる。Operation callはInterface参照を明示するため、Protocol上の解決は曖昧にならない。UIやAI Tool表示では、必要に応じて`<interface-reference>.<operation-name>`の形でqualifyする。この表示形式はProtocol fieldへ再parseする識別子ではなく、Interface参照とOperation名の組を人間へ示す表記である。
Interfaceは次を定義する。
@@ -41,7 +45,7 @@ Interfaceは次を定義する。
- Return type
- Named typeとdocumentation
Interface実装者は実装コード上でInterfaceを宣言し、Libraryはその宣言からInterface Descriptorを生成する。ClientはObjectのInterface参照を使ってDescriptorを取得し、検証、cache、表示、Operation callのvalidationに利用する。
Interface実装者は実装コード上でInterfaceを宣言し、Libraryはその宣言からInterface Descriptorを生成する。ClientはObjectの各Interface参照を使ってDescriptorを取得し、検証、cache、表示、Operation callのvalidationに利用する。
同じInterfaceを持つObjectは同じDescriptor参照を共有できる。
@@ -132,7 +136,7 @@ Hostは、主体に認識させるObjectだけでClient-facing Worldspaceを構
- そのpathを指定した`fetch`には、Host内部での存在理由にかかわらず`NotFound`を返す。Object取得に対する`PermissionDenied`は使用しない。
- `truncated`は、その主体へ公開されたindexable childrenについて判定する。非公開Objectを省略したことだけを理由に`true`にしない。
Objectを公開しながら、Objectが参照するInterfaceから一部のOperationを除外することや、公開されたOperationの実行を認可時に拒否することはできる。Operationの可視性と`PermissionDenied`の使い分けは[2.2. Interface / Operation](2.2-operation.md)で定義する。
Objectを公開しながら、特定Interfaceを集合から除外すること、公開したInterfaceから一部のOperationを除外すること、公開されたOperationの実行を認可時に拒否することはできる。Operationの可視性と`PermissionDenied`の使い分けは[2.2. Interface / Operation](2.2-operation.md)で定義する。
Objectの`description`には、その主体へ公開できる説明だけを含める。安全な説明を公開できない場合はoptionalな`description`を省略できるが、Protocolはfield-level redaction markerを定義しない。Objectの本文やdomain dataの部分的な開示は、各Operationのresult schemaとdomain semanticsで表現する。
@@ -144,7 +148,7 @@ Objectの`description`には、その主体へ公開できる説明だけを含
fetch(path) -> Object
```
返されたObjectは、Host内部のdomain entityの完全なcopyではない。そのClientへ現在公開されているname、description、Interface参照、任意の`ref`とvalidator等のprotocol projectionである。Clientは非rootのrequest pathについて、最後のsegmentとObjectの`name`が一致することを検証する。
返されたObjectは、Host内部のdomain entityの完全なcopyではない。そのClientへ現在公開されているname、description、Interface参照のordered set、任意の`ref`とvalidator等のprotocol projectionである。Clientは非rootのrequest pathについて、最後のsegmentとObjectの`name`が一致することを検証する。
ClientはObjectがvalidatorを持つ場合、そのObjectとrequest pathを対応付けて保持し、条件付きOperationのtargetを構築できる。
@@ -182,8 +186,8 @@ Tree上のchildrenとして自然に列挙できるのは、**indexableなObject
```text
/issues
interface: IssueCollection
operation: query(filter, cursor, limit) -> IssueQueryResult
interfaces: [IssueCollection]
operation: IssueCollection.query(filter, cursor, limit) -> IssueQueryResult
```
```text
+39 -20
View File
@@ -2,10 +2,12 @@
## Interface
ObjectはOperation定義を直接持たず、一つの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である。
@@ -54,62 +56,76 @@ Return typeにはDescriptorで定義される任意のvalue typeを使用でき
Operationの用途は短いdocumentationで説明する。長文documentationや大量のApplication dataをInterface Descriptorへ埋め込まない。
Operation名は一つのInterface Descriptor内で一意とする。異なるInterface間では同じOperation名を使用でき、`call_operation`はInterface参照とOperation名の組で解決する。UIやAI Toolでは必要に応じて`<interface-reference>.<operation-name>`と表示する。このqualified表記は表示用であり、一つのProtocol fieldへencodeまたは再parseしない。
## `fetch_interface`
`fetch_interface(interface)`は、Interface参照に対応するInterface Descriptorを取得する。
`fetch_interface(interface)`は、Interface参照に対応する現在のInterface Descriptorとoptionalなvalidatorを一つの観測として取得する。
```text
fetch_interface(interface) -> Interface Descriptor
fetch_interface(interface) -> Interface Descriptor, validator?
```
ObjectはInterface参照だけを持ち、Descriptorを先行同梱しない。ClientはInterface cacheと参照を比較し、未知のDescriptorだけを取得する。Hostは主体へ公開したObjectが参照するDescriptorを、その主体から取得可能にしなければならない。
ObjectはInterface参照のordered setだけを持ち、Descriptorを先行同梱しない。ClientはInterface cacheと各参照を比較し、未知のDescriptorを取得する。Interface validator mismatch等で更新が必要になった参照は再取得する。Hostは主体へ公開したObjectの各Interface参照について、現在のDescriptorをその主体から取得可能にしなければならない。
同じInterface参照とvalidatorの組は同じDescriptorを表す。Validatorを返さないInterface参照は、その参照の有効期間中immutableでなければならない。
HostはClientのcache状態を推測して、要求されていないDescriptorをObject responseへ追加しない。複数のDescriptor取得はTransport Bindingでbatchできる。
## Operationの可視性
Hostは、主体の権限やrole / capabilityに基づいて、その主体へ公開するInterfaceを決定してよい。同じObjectでも、主体ごとに異なるInterface参照を返してよい。
Hostは、主体の権限やrole / capabilityに基づいて、その主体へ公開するInterface参照の集合を決定してよい。同じObjectでも、主体ごとに異なるInterface集合を返してよい。
Operationを主体に認識させない場合、HostはそのOperationを含まないInterfaceをObjectから参照する。そのOperation名を推測して`call_operation`しても、現在公開されているInterfaceに宣言されていないため`OperationNotFound`を返す。
Operationを主体に認識させない場合、Hostは次のいずれかを選べる。
Operationの存在を公開しつつ実行だけを認めない設計も可能である。この場合はOperationをInterfaceに宣言し、`call_operation`時のauthorizationで拒否して`PermissionDenied`を返す。
- そのOperationをcapabilityとしてまとめたInterface参照をObjectの集合から除外する。
- そのOperationを宣言しない別のInterface Descriptorを参照する。
ClientがObjectで公開されていないInterface参照を指定した場合は`InterfaceMismatch`、公開されているInterfaceに宣言されていないOperation名を指定した場合は`OperationNotFound`を返す。
Operationの存在を公開しつつ実行だけを認めない設計も可能である。この場合はOperationをいずれかのInterfaceに宣言し、`call_operation`時のauthorizationで拒否して`PermissionDenied`を返す。
したがって、Hostは主体に対して次を独立に選択できる。
- Object自体を公開せず、Objectへの`fetch`とOperation callを`NotFound`にする。
- Objectを公開するが、特定OperationをInterfaceから除外する。
- ObjectとOperationを公開するが、そのOperationの実行を`PermissionDenied`にする。
- Objectを公開するが、特定Interfaceを集合から除外する。
- Objectを公開し、特定Operationを含まないInterfaceを使用する。
- Object、Interface、Operationを公開するが、そのOperationの実行を`PermissionDenied`にする。
Operationの可視性は、頻繁に変化する実行可否やdomain stateを表すものではなく、**その主体に対してどのInterfaceを公開するか**というprojectionとして扱う。
Interface集合と各Interface内のOperation可視性は、頻繁に変化する実行可否やdomain stateを表すものではなく、**その主体に対してどのcapabilityを公開するか**というprojectionとして扱う。
WIPはOperationの`available` / `disabled`のような揮発的状態をInterfaceへ同期することを前提としない。現在状態を判断する必要がある場合は、必要に応じてdomain固有のOperationで観測する。公開されたOperationについても、最終的なauthorizationと実行条件の判定は`call_operation`時にHostが行う。
WIPはOperationの`available` / `disabled`のような揮発的状態をInterface集合へ同期することを前提としない。現在状態を判断する必要がある場合は、必要に応じてdomain固有のOperationで観測する。公開されたOperationについても、最終的なauthorizationと実行条件の判定は`call_operation`時にHostが行う。
## `call_operation`
`call_operation`は、指定pathにあるObjectに対して、Interfaceに宣言されたOperationを実行する。
```text
call_operation(target, operation, arguments) -> result, validator?
call_operation(target, interface, operation, arguments) -> result, validator?
```
- `target.path`は対象Objectを観測したpath。
- `target.validator`はObjectから得たoptionalなopaque validator。
- `operation`は対象ObjectのInterfaceに宣言されたOperation名。
- `interface.reference`は対象Objectの`interfaces`から選んだInterface参照。
- `interface.validator`は`fetch_interface`で観測したoptionalなInterface validator。
- `operation`は選択したInterfaceに宣言されたOperation名。
- `arguments`はOperationのparameterに従う名前付きrecord。
- `result`はOperationのreturn typeに従うvalue。
- Responseの`validator`はOperation実行後の現在状態に対してHostが発行したoptionalな値。
- Responseの`validator`はOperation実行後のObject状態に対してHostが発行したoptionalな値。
HostはOperationを実行する前に、次を検証する。
1. `target.path`が存在する。
2. `target.validator`が指定されている場合、現在のvalidatorと一致する。
3. Objectが参照するInterfaceにOperationが宣言されている。
4. Argumentsがparameter declarationに従っている。
5. ClientがOperationを実行できる。
2. `target.validator`が指定されている場合、現在のObject validatorと一致する。
3. `interface.reference`がObjectの現在の`interfaces`に含まれる。
4. MutableなInterfaceでは`interface.validator`が指定され、現在のInterface validatorと一致する。
5. 選択したInterfaceにOperationが宣言されている。
6. Argumentsがparameter declarationに従っている。
7. ClientがOperationを実行できる。
対象Objectが現在のClient-facing Worldspaceへ公開されていない場合は`not_found`、Operationが現在公開されているInterfaceに宣言されていない場合は`operation_not_found`、宣言されたOperationの実行が認められない場合は`permission_denied`を返す。
対象Objectが現在のClient-facing Worldspaceへ公開されていない場合は`not_found`、指定Interfaceが対象Objectの現在の`interfaces`に含まれない場合は`interface_mismatch`、Operationが指定Interfaceに宣言されていない場合は`operation_not_found`、宣言されたOperationの実行が認められない場合は`permission_denied`を返す。
Validatorの比較とOperation実行はatomicに行う。一致しない場合、HostはOperationを実行せず`validator_mismatch`を返す。Hostがvalidatorを要求するOperationに指定がない場合は`validator_required`を返す。
Object validator、Interface membership、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`を返す。
@@ -155,6 +171,9 @@ Protocol failureは、WIPのrequest、対象、Interface、validation、validato
- `invalid_request`
- `not_found`
- `interface_not_found`
- `interface_mismatch`
- `interface_validator_required`
- `interface_validator_mismatch`
- `operation_not_found`
- `invalid_arguments`
- `invalid_result`
+107 -59
View File
@@ -43,7 +43,7 @@ BTreeMap<String, T> // string keyを持つlogical map
`BTreeMap`はstring-keyed mapをRust風に示すための記法であり、keyのiteration orderをProtocol semanticsには含めない。
## Primitiveとfield semantics
## Protocol primitives
Protocol structは、次のprimitiveおよびcompositeだけで表す。
@@ -58,23 +58,11 @@ list
record
```
Path、Interface参照、Object identity、Operation名等を独立したProtocol scalarにはしない。それらは`String`または`Vec<u8>`として交換し、fieldの位置とvalidation ruleによって意味を与える。
Path、Interface参照、Object identity、validator、Operation名等を独立したProtocol scalarにはしない。同じprimitiveを使用するfieldでも意味は交換可能ではなく、各fieldの定義とvalidation ruleに従って解釈する。
| Field | Primitive | 解釈 |
| --- | --- | --- |
| `FetchRequest.path` | `String` | rootからObjectの`name`を辿って構成したabsolute Worldspace path |
| `CallOperationRequest.target.path` | `String` | Operation対象のabsolute Worldspace path |
| `Object.interface` | `String` | Interface Descriptorへの参照 |
| `Object.ref` | `String` | 任意のopaque Object identity |
| `Object.validator` | `Vec<u8>` | HostがObjectへ付与するopaque validator |
| `CallOperationResponse.validator` | `Vec<u8>` | Operation実行後のopaque validator |
| `CallOperationRequest.operation` | `String` | Interface内のOperation名 |
Rust SDKは利便性や安全性のために`Path`等のnewtypeを公開してよく、TypeScript SDKはbrand等を利用してよい。それらは言語別SDKのAPIであり、Protocol structの一部ではない。
同じprimitiveを使用するfieldでも意味は交換可能ではない。例えば、`Object.ref`を`FetchRequest.path`へ渡してnavigationに使用してはならない。ただし、この区別をProtocol上のnewtypeとして表す必要はない。
Rust SDKは利便性や安全性のために`Path`等のnewtypeを公開してよく、TypeScript SDKはbrand等を利用してよい。それらは言語bindingのAPIであり、Protocol structの一部ではない。
### Pathとして解釈されるstring
## Path
Pathとして宣言されたfieldのvalueは、同じWorldspace内のObjectを取得するためのcanonical absolute pathでなければならない。
@@ -89,29 +77,6 @@ Pathとして宣言されたfieldのvalueは、同じWorldspace内のObjectを
Nameに`/`を許可しないため、Protocolはpath segment escapeを定義しない。Transport BindingによるURL encodingはWIP Pathの構成とは別に扱う。
### Opaqueな参照とvalidator
`Object.interface`と`FetchInterfaceRequest.interface`はInterface Descriptorへのopaqueな参照として解釈する。同じInterface参照が有効である間、Hostは異なるDescriptorを返してはならない。Descriptorを変更した場合は新しいInterface参照をObjectで公開する。
`Object.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の存在または公開を要求しない。
`Object.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は、どの状態変化によってvalidatorを無効にするかについて、そのObjectとInterfaceに対して一貫した方針を適用する。Validatorはpathに固有ではなく、同じ`ref`を持つObjectの現在状態に付与する。
ValidatorはAIやユーザーへ公開するApplication dataではなく、Client runtimeがObjectとともに保持するprotocol metadataである。
## Documentation
Interface、Operation、type、field、parameter、caseに付与するdocumentationを次のlogical structで表す。
@@ -135,7 +100,7 @@ DocumentationはHostが提供するplain text dataであり、Clientのsystem in
struct Object {
name: String,
description: Option<String>,
interface: String,
interfaces: Vec<String>,
r#ref: Option<String>,
validator: Option<Vec<u8>>,
}
@@ -151,25 +116,50 @@ NameはUnicode stringとして扱い、Unicode normalizationを行わない。
Objectが何を表すかを説明する短い公開情報。Objectの本文やApplication data全体を格納するfieldではない。
### `interface`
### `interfaces`
Objectで利用できるOperationを定義するInterface Descriptorへの参照。
Objectで利用できるOperationを定義するInterface Descriptorへの参照を、Hostの立項順で保持するordered set。
- 集合は空でもよい。
- 同じInterface参照を重複して含めてはならない。Clientは重複を含むObject responseを`InvalidResult`として扱う。
- Membershipはset semanticsを持ち、列挙順はOperation解決、validation、authorizationへ影響しない。
- Clientは列挙順をInterfaceおよびOperationのUI表示順として使用してよい。
- Operation callは集合から一つのInterface参照を明示する。
異なるInterfaceでは同じOperation名を宣言できる。UIやAI Toolは必要に応じて`<interface-reference>.<operation-name>`と表示してよい。このqualified表記は表示上の組であり、一つのProtocol stringへencodeまたはparseする規則を定義しない。
Hostはvalidatorを発行することで、同じInterface参照のDescriptorを時間とともに変更できる。Validatorを発行しない参照のDescriptorは、その参照が有効な間は変更しない。Clientは`fetch_interface`でDescriptorとvalidatorを一つの観測として取得する。
### `ref`
同一Objectを複数の観測にまたがって識別するための任意のopaque identity。
同じ主体に公開された一つの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`
Objectの現在状態にHostが付与する任意のopaque value。ClientはObjectとともに保持し、その観測に基づく条件付きOperationのtargetへ使用する。
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である。
### 同じ`ref`を持つObjectの整合性
同じWorldspace内で同じ`ref`を持つObjectは、一つの共有された現在状態を表す。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、`interface`を含む全fieldが一致しなければならない。
- 同じ`ref`と同じvalidator valueを持つObjectは、validatorが存在する場合、`name`、`description`、`interfaces`を含む全fieldが一致しなければならない。
- 同じ`ref`と異なるvalidatorを持つObjectは、同一Objectの異なる状態として扱う。
- HostはObjectが配置されたpathを理由に、同じ`ref`へ異なる`name`、`description`、`interface`を同時に公開してはならない。
- HostはObjectが配置されたpathを理由に、同じ`ref`へ異なる`name`、`description`、`interfaces`を同時に公開してはならない。
- Clientは最後に成功した観測を、その`ref`を持つObjectの現在状態として扱う。
同じObjectを複数のpathへ配置する場合、各配置は同じObjectの`name`を使用する。異なる親を経由することでpathが異なる。
@@ -441,7 +431,28 @@ struct FetchTreeResponse {
## `fetch_interface`
Interface参照に対応するInterface Descriptorを取得する。
Interface参照に対応する現在のInterface Descriptorを取得する。
### Interface参照とvalidator
`Object.interfaces`の各要素、`FetchInterfaceRequest.interface`、`FetchInterfaceResponse.interface`、`InterfaceTarget.reference`は、Hostが公開するInterfaceへのopaqueな参照である。同じ有効なInterface参照は同じInterfaceを表すが、Hostはvalidatorを使用してDescriptorを時間とともに変更できる。
`FetchInterfaceResponse.validator`は、HostがそのInterface Descriptorの現在状態へ付与するoptionalなopaque bytesである。Clientは内容を解釈せず、観測したDescriptorに基づいてOperationを実行するときに、Interface参照とともに`InterfaceTarget`へコピーする。
Interface参照とvalidatorには次の規則を適用する。
- 同じ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である。
### Requestとresponse
```rust
struct FetchInterfaceRequest {
@@ -451,6 +462,7 @@ struct FetchInterfaceRequest {
struct FetchInterfaceResponse {
interface: String,
descriptor: InterfaceDescriptor,
validator: Option<Vec<u8>>,
}
```
@@ -459,10 +471,18 @@ Clientは次を検証する。
- Responseの`interface`がrequestと一致する。
- Descriptorが構造検証および意味検証に成功する。
- Descriptorの`format`がClientの対応するversionである。
- 同じInterface参照と同じvalidator valueを持つ既存cache entryがある場合、validatorが存在する観測ではDescriptorがそのentryと一致する。
Interface参照はHostが発行するopaque stringである。Clientは参照をcache keyとして使用し、同じ有効な参照から異なるDescriptorを受け取った場合はProtocol failureとして扱う。
Clientはvalidatorが存在するInterface observationを、`(interface, validator)`の組でcacheする。Interface参照ごとに最後に成功した観測を現在のcache entryとしてよいが、実行中のOperationが参照する観測はresponseのvalidationが完了するまで保持する。
Hostは、主体へ公開したObjectの`interface`が参照するDescriptorを、その主体の`fetch_interface`で取得可能にしなければならない。現在公開されていないInterface参照には`InterfaceNotFound`を返し、Descriptor取得の認可失敗として`PermissionDenied`は使用しない。
- 同じInterface参照でvalidatorが異なる場合は、同じInterfaceの新しい状態としてlatest observationを更新する。
- Validatorが存在しないInterface参照はimmutableなDescriptorとして、Interface参照だけをcache keyに使用できる。
- 同じInterface参照と同じvalidator valueで異なるDescriptorを受け取った場合は`InvalidResult`として扱い、その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`は使用しない。
## `call_operation`
@@ -474,8 +494,14 @@ struct Target {
validator: Option<Vec<u8>>,
}
struct InterfaceTarget {
reference: String,
validator: Option<Vec<u8>>,
}
struct CallOperationRequest {
target: Target,
interface: InterfaceTarget,
operation: String,
arguments: BTreeMap<String, Value>,
}
@@ -488,23 +514,35 @@ struct CallOperationResponse {
`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参照では省略する。
`arguments`はOperation parameterに対応する名前付きrecordである。引数がない場合は空のmapを送る。
Hostは、現在その主体へ公開しているObjectとInterfaceに基づいてOperationを解決する。
Hostは、現在その主体へ公開しているObjectとInterfaceに基づいてOperationを解決する。複数のfailure条件が同時に成立する場合は、次の順で最初のcodeだけを返す。
- `target.path`のObjectが公開されていない場合は`NotFound`を返す。
- 現在公開されているInterfaceに`operation`が宣言されていない場合は`OperationNotFound`を返す。
- 宣言されたOperationの存在を公開しつつ、その主体による実行を認めない場合は`PermissionDenied`を返す。
1. `target.path`のObjectが公開されていない場合は`NotFound`。
2. 指定された`target.validator`が現在のObject validatorと一致しない場合は`ValidatorMismatch`。
3. `interface.reference`が、現在そのObjectで公開されている`Object.interfaces`に含まれない場合は`InterfaceMismatch`。
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`。
したがって、HostはOperationをInterfaceから除外して存在を非公開にすることも、Interfaceへ含めたうえで実行時に拒否することもできる。
指定されたObject validatorまたはInterface validatorの不一致は、現在のDescriptorに基づくOperation lookupとarguments validationより先に判定する。これによりClientは、古いObjectまたはschemaに基づくrequestを`OperationNotFound`や`InvalidArguments`として扱わず、対応する観測を更新できる。
`target.validator`が指定された場合、Hostは現在のvalidatorとの比較とOperation実行をatomicに行う。一致しない場合はOperationを実行せず`ValidatorMismatch`を返す。Hostがvalidatorを要求するOperationに指定がない場合は`ValidatorRequired`を返す。
Hostは一つのlinearization pointで、現在のObject identityと状態、Object validator、Objectが公開するInterface参照、Interface validator、Interface Descriptor、Operation implementation、実行対象を解決し、各preconditionを確認して、そのcallが使用する観測を固定する。ObjectまたはInterfaceが後から変化しても、callの途中で別のDescriptor、Operation implementation、実行対象へ解決し直してはならない。
Hostはvalidatorの生成方法と無効化する状態変化を決定する。Content hash、generation、database revision等を使用できる。比較はopaque bytesの完全一致で行う。
Argumentsとresultのvalidationは、固定したInterface Descriptorの同じOperation declarationを使用する。Authorizationも固定したObject、Interface、主体のcontextに対して評価する。
Responseの`validator`はOperation実行後の現在状態に対してHostが発行した値である。Read-only Operationでもresultを観測した時点のvalidatorを返してよい。Validatorを提供しない場合は省略する。
状態を変更するOperationでは、Object validatorの比較とbackendへのcommitをatomicに行う。Responseの`validator`は、そのcommit後の同じObject状態に対して発行する。Read-only Operationでもresultを観測した時点のObject validatorを返してよい。Object validatorを提供しない場合は省略する。
ClientはresultをOperationのreturn typeに従って検証する。Validationに失敗したvalueをApplicationへ渡さない。
Validatorの比較はopaque bytesの完全一致で行う。Clientはvalidatorの生成方法または内容に基づいて分岐してはならない。
HostはOperationをInterfaceから除外して存在を非公開にすることも、Interfaceへ含めたうえで実行時に`PermissionDenied`として拒否することもできる。
Clientはresultを、requestの`interface`が指定し、Hostがpreconditionを確認して固定したOperation return typeに従って検証する。Validationに失敗したvalueをApplicationへ渡さない。
## Protocol responseとfailure
@@ -525,11 +563,14 @@ enum ProtocolErrorCode {
InvalidRequest,
NotFound,
InterfaceNotFound,
InterfaceMismatch,
OperationNotFound,
InvalidArguments,
InvalidResult,
ValidatorRequired,
ValidatorMismatch,
InterfaceValidatorRequired,
InterfaceValidatorMismatch,
PermissionDenied,
ResourceLimitExceeded,
Internal,
@@ -538,6 +579,12 @@ enum ProtocolErrorCode {
`message`は人間向けのboundedな診断であり、Clientが分岐に使用する安定した値ではない。Clientは`code`を使用する。
Validator関連errorの回復対象は次のとおりである。
- `ValidatorRequired`または`ValidatorMismatch`では、Clientは対象Objectを再観測してから、ユーザーまたはApplicationの判断なしにOperationを自動再実行してはならない。
- `InterfaceMismatch`では、Clientは対象Objectを再観測し、現在公開されているInterface参照の集合を取得する。
- `InterfaceValidatorRequired`または`InterfaceValidatorMismatch`では、Clientは同じInterface参照を`fetch_interface`で再観測してから、現在のDescriptorに基づいてOperationを再構築する。ユーザーまたはApplicationの判断なしにOperationを自動再実行してはならない。
Objectを主体へ公開しない場合、HostはそのObjectをClient-facing Worldspaceに存在しないものとして扱い、`fetch`、`fetch_tree`、`call_operation`へ`NotFound`を返す。Objectの存在を秘匿する目的で`PermissionDenied`を返してはならない。
`PermissionDenied`は、現在公開されているInterfaceに宣言されたOperationについて、その存在を公開しつつ実行を認めない場合に使用する。Operation自体をInterfaceから除外している場合は`OperationNotFound`を返す。
@@ -552,6 +599,7 @@ HostとClientは、untrusted inputによるresource exhaustionを避けるため
- Pathとして解釈するstring、validator、name、description、documentationの長さ。
- Interface Descriptor全体のbyte数。
- ObjectごとのInterface参照数。
- Type declaration、Operation、parameter、field、caseの数。
- TypeExprとValueのnesting depth。
- Record field数とList要素数。
@@ -577,7 +625,7 @@ Transport Bindingは、この章のlogical structについて次を定義する
- Object、Descriptor、Valueのencoding。
- Optional fieldの省略。
- Protocol failureのmapping。
- Validatorのmapping。
- Object validator、Interface validator、Operation preconditionのmapping。
- Batch packagingを提供する場合はitemの対応付けと部分的failure。
- Message size、timeout、concurrency等のtransport固有limit。
+23 -6
View File
@@ -2,7 +2,7 @@
## 役割
Clientは、Hostが提供するWorldspaceをユーザーへ提示する実装主体である。WIP Protocolを利用しつつ、Objectが参照するInterfaceを解決し、用途に応じたViewやAPIへ投影する。
Clientは、Hostが提供するWorldspaceをユーザーへ提示する実装主体である。WIP Protocolを利用しつつ、Objectが参照するInterface集合を解決し、用途に応じたViewやAPIへ投影する。
ClientはHostから得た情報をそのまま一時表示するだけでなく、rootからindexable edgeを辿って構成したエントリツリーと、必要に応じて選択したエントリを保持し、ユーザーから見た**既知の空間**を構成する。
@@ -34,7 +34,7 @@ struct CachedWorldspace {
struct CachedObject {
name: String,
description: Option<String>,
interface: String,
interfaces: Vec<String>,
r#ref: Option<String>,
validator: Option<Vec<u8>>,
metadata_stale: bool,
@@ -53,17 +53,32 @@ struct CachedEntry {
- Entry Treeはpathと親子関係を保持し、Object storeはObjectの現在状態を保持する。
- 同じObjectを複数のEntryが参照しても、Object stateをEntryごとに複製しない。
Objectの`interfaces`はmembershipをsetとして扱い、同じ参照の重複を`invalid_result`として拒否する。ClientはHostの立項順をCachedObjectへ保持し、InterfaceやOperationの表示順に使用してよい。
同じ`ref`と同じvalidator valueを持つObjectを再観測したとき、validatorが存在するにもかかわらずObject representationが異なる場合、Clientはresponseを`invalid_result`として扱う。同じ`ref`でvalidatorが異なる場合、またはvalidatorが存在しない場合は、同一Objectの新しい観測として最後に成功した観測でCachedObjectを更新する。
このnormalizeはClientのcache実装であり、Objectと別のWIP resourceまたはProtocol上のEntry resourceを導入するものではない。
## Interface cache
Objectの公開情報はOperation定義を直接含まず、Interface参照を持つ。Clientは未知のInterface参照を`fetch_interface`で解決し、取得したInterface Descriptorを参照ごとにcacheする。
Objectの公開情報はOperation定義を直接含まず、Interface参照のordered setを持つ。Clientは各参照を`fetch_interface`で解決し、取得したInterface Descriptorとoptionalなvalidatorを観測としてcacheする。
一つのInterface Descriptorに宣言されたOperationの集合が、そのInterfaceのOperation一覧である。複数のObjectが同じInterfaceを参照する場合、ClientはDescriptorを一度だけ取得、検証すればよい。
```rust
struct CachedInterface {
descriptor: InterfaceDescriptor,
validator: Option<Vec<u8>>,
}
```
ClientはInterface cacheとObjectのInterface参照を比較し、未知のInterfaceだけを取得する。複数の`fetch_interface`は、Transport Bindingが提供するbatchを利用してまとめてよい。
Clientはvalidatorが存在する観測を`(interface, validator)`の組で識別し、Interface参照ごとに最後に成功した観測をcurrent entryとして保持できる。Validatorが存在しないInterface参照は、その参照が有効な間Descriptorがimmutableなので、参照だけをcache keyに使用できる。
一つのInterface Descriptorに宣言されたOperationの集合が、そのInterfaceのOperation一覧である。Objectは複数のInterfaceを組み合わせられ、複数のObjectも同じInterfaceを共有できる。
ClientはObjectの`interfaces`とcacheを比較し、未知のInterfaceだけを取得する。Interface validator mismatch等で更新が必要になった参照は再取得する。複数の`fetch_interface`は、Transport Bindingが提供するbatchを利用してまとめてよい。
異なるInterfaceで同じOperation名が存在しても、ClientはInterface参照とOperation名の組で識別する。UIやAI Toolでは必要に応じて`<interface-reference>.<operation-name>`と表示する。Operation callでは表示文字列をparseせず、Interface参照とOperation名を別fieldとして送る。
実行中のOperationが使用するDescriptor observationは、resultのvalidationが完了するまで保持する。新しいvalidatorのDescriptorを観測しても、古いvalidatorで開始済みのcallに必要なDescriptorを早期に破棄しない。
## Discoverable Space
@@ -109,7 +124,9 @@ Clientは、エントリを観測した後にOperationを実行するまでの
Objectが`validator`を持つ場合、Clientは観測したpathとvalidatorを対応付ける。条件付きOperationでは両者をtargetとしてHostへ渡し、validatorの比較と実行をHost上でatomicに行わせる。ValidatorはAIやユーザーに直接管理させない。HTTPS Bindingは、semanticsが適合する場合にHTTP ETag等へ対応付けてよい。
Operation responseが新しいvalidatorを返した場合、Clientは対象のCachedObjectへ反映する。Operationによって`name`、`description`、Interface等の公開情報が変化し得る場合、ClientはCachedObjectのmetadataをstaleとして扱い、利用前に`fetch`で再観測する。
ClientはOperationに選択したInterface参照と、`fetch_interface`で観測したoptionalなInterface validatorもrequestへ含める。MutableなInterfaceがvalidatorを返す場合は必須preconditionとして使用し、`InterfaceValidatorMismatch`ではInterfaceだけを再観測する。`InterfaceMismatch`ではObjectを再観測し、現在のInterface集合を取得する。いずれの場合もOperationを自動再試行しない。
Operation responseが新しいObject validatorを返した場合、Clientは対象のCachedObjectへ反映する。Operationによって`name`、`description`、Interface集合等の公開情報が変化し得る場合、ClientはCachedObjectのmetadataをstaleとして扱い、利用前に`fetch`で再観測する。
対象が変化していた場合、Clientは新しいObjectに対してOperationを自動再試行せず、対象が変化したことを上位インターフェースへ通知して再観測を要求する。
+5 -3
View File
@@ -16,7 +16,7 @@ AI用Toolsは、WIP ClientがAIへ提供する想定インターフェースで
## `inspect`
`inspect(target)` は、対象エントリ / Objectについて**何ができるか**を把握するための操作である。ClientはObjectのInterface参照を解決し、Interface Descriptorに宣言されたOperationを提示する。
`inspect(target)` は、対象エントリ / Objectについて**何ができるか**を把握するための操作である。ClientはObjectのInterface参照のordered setを解決し、各Interface Descriptorに宣言されたOperationを提示する。
Objectのドメインデータをpropertiesとして直接公開することは前提としない。データを読む行為もOperationとして表現する。
@@ -27,11 +27,13 @@ Objectのドメインデータをpropertiesとして直接公開することは
- `get_related_items()` — 関連Objectのentry pathを取得する。
- `close(reason: string)` — 理由を添えてIssueをクローズする。
Operationの関数宣言と短いdocumentationは、Objectが参照する[Interface Descriptor](2.2-operation.md)から取得する。Clientは検証済みDescriptorからIDL風のSignature Viewを生成し、人間とAIへ提示する。利用するAI providerがTool schemaを要求する場合、Client adapterがDescriptorからJSON Schema等へ変換する。Provider固有schemaをHostがWIP Interfaceとして提供する必要はない。
Operationの関数宣言と短いdocumentationは、Objectが参照する各[Interface Descriptor](2.2-operation.md)から取得する。ClientはObjectの立項順でInterfaceを表示し、異なるInterfaceで同じOperation名が存在する場合や出所を明示したい場合は、`<interface-reference>.<operation-name>`の形でqualifyできる。この表記は表示用であり、Protocol callではInterface参照とOperation名を別fieldとして送る。
Clientは検証済みDescriptorからIDL風のSignature Viewを生成し、人間とAIへ提示する。利用するAI providerがTool schemaを要求する場合、Client adapterがDescriptorからJSON Schema等へ変換する。Provider固有schemaをHostがWIP Interfaceとして提供する必要はない。
## `invoke`
`invoke(target, operation, arguments)` は、`inspect`によって発見したOperationを実行する。`arguments`はOperationのparameter declarationに従う名前付きfieldのrecordであり、位置引数は用いない。引数を持たないOperationには空のrecordを渡す。Clientはargumentsを検証し、対象pathと観測したObjectのoptionalなvalidatorから`call_operation`のtargetを構築する。
`invoke(target, interface, operation, arguments)` は、`inspect`によって発見したInterfaceとOperationの組を実行する。`arguments`はOperationのparameter declarationに従う名前付きfieldのrecordであり、位置引数は用いない。引数を持たないOperationには空のrecordを渡す。Clientはargumentsを検証し、対象path、観測したObjectのoptionalなvalidator、選択したInterface参照とvalidatorから`call_operation` requestを構築する。
PTC / REPL bindingでは、通常のメソッド呼び出しへ投影してよい。
+328
View File
@@ -0,0 +1,328 @@
# 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)、[Interface / Operation](../2.2-operation.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。
- `fetch_tree`から列挙可能な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`として公開または流用しない。
## 運用モデル
### `fetch`
`fetch(path)`では、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. Protocol `Object`を返す。
この処理のために、Hostが以前の`fetch`で生成したObject instanceを保持している必要はない。内部cacheを利用してもよいが、cacheの有無によってProtocol semanticsを変えてはならない。
### `fetch_tree`
`fetch_tree(path, depth, limit)`では、Host RuntimeがProjection Providerを通して起点Objectを解決し、indexable childrenをBreadth-First Searchで列挙する。
ProviderはWorldspace全体をmaterializeせず、要求されたparentのdirect childrenだけを遅延して発見できる必要がある。Host RuntimeはProviderと協調し、Protocolの`depth`、`limit`、visibility、resource limitを適用する。
Providerがchildrenとして返す内部表現は未決定である。少なくとも、各childについて現在のObject projectionを構築し、そのcanonical pathを後の`fetch`または`call_operation`で現在のProjected Objectとして再解決できなければならない。以前観測したbackend entityやHost内部instanceを再現することは要求しない。
Lazyなchild discoveryは、認可filter後の公開childrenについて、少なくとも次を区別できなければならない。
- 要求範囲のchildrenをすべて調べ終えたclean exhaustion。
- `depth`または`limit`によって一つ以上の公開されたindexable childを省略することになる状態。
- Backend failure。
- Cancellationまたはrequestのresource budget超過。
これによりHostは`truncated`を正確に設定する。`limit`件を返しただけでは追加childの存在を仮定せず、ちょうど全件を返した場合と省略が発生した場合を区別する。Depth境界でも、公開されたindexable childの有無をboundedに判定できる必要がある。Backend failure、cancellation、resource budget超過をclean exhaustionまたは`truncated`へ変換しない。Streaming、pagination、backpressureの具体形は決定しない。
### `fetch_interface`
ProjectionがObjectへ割り当てた各Interface参照は、同じ主体から取得可能なDescriptorへ解決されなければならない。
複数のObjectは同じInterface参照を共有でき、一つのObjectも複数のInterfaceを組み合わせられる。Validatorを返すInterface参照ではDescriptorを変更でき、同じ参照とvalidatorの組が一つのDescriptor observationを表す。Validatorを返さない参照のDescriptorは、その参照が有効な間immutableである。Interface参照のscope、発行、生成時期、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等を使用できるようにしなければならない。
過去の`fetch`で作成した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を作成する必要はない。
- `fetch("/files/a.txt")`では、pathをfilesystem entryへ解決し、stat等からObject projectionを構築する。
- `fetch_tree("/files", ...)`では、directory listingから要求範囲の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を、要求範囲だけ投影できる。
- `fetch_tree`でindexableでないObjectも、既知のpathを`fetch`したときに直接解決できる。
- 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は、親全体の無制限列挙を前提にせず、direct `fetch`とOperation dispatchで同じchildへboundedに解決できなければならない。Encoding自体が可逆である必要はなく、boundedなbackend indexを利用してよい。
- Name mappingはchild enumeration、direct `fetch`、`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を区別する。
### `fetch`、`fetch_tree`、`call_operation`で一貫すること
- 同じ時点と主体について、単体取得とTree列挙が矛盾した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を返さない。
- `fetch_tree`で得たpathを`fetch`した場合に、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を`fetch_tree`から省略し、存在や省略数を示すmarkerを生成しない。
- 非公開Objectの省略だけを理由に`fetch_tree.truncated`を`true`にせず、その主体へ公開されたindexable childrenについて判定する。
- Operation implementationまたはその前段で、call時のauthorizationを再評価できる。
- 以前の`fetch`時に許可されていたことだけを根拠に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`への後続`fetch`が`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を適用できる。
- `fetch_tree`の`depth`と`limit`をProvider側の無制限列挙へ変換しない。
- Client disconnect、timeout、Host shutdown等に応じてbackend処理へcancellationを伝播できる。
- Side effectを持つOperationでは、cancellationやtimeoutを未実行またはrollback済みの証明として扱わず、実行結果が不明なcallをHost Runtimeが自動retryしない。
- Partial backend failureをProtocol上の成功Treeへ黙って変換しない。
### 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へ適用されない。
具体的なnamespaceおよび発行方式は決定しない。
ただし、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. `fetch_tree`で結果がちょうど`limit`件の場合、追加childの有無とbackend failureを区別して`truncated`を決める。
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、additional child、failure、cancellationをHostへ伝え、`truncated`を正確に決める内部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を作り、上記の評価シナリオと要件に照らして比較する。
+505
View File
@@ -0,0 +1,505 @@
# 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)、[Interface / Operation](../2.2-operation.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全体について常駐させず、`fetch`、`fetch_tree`、`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やopaqueな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を登録または公開するときに割り当てるopaque参照を、publication APIへの追加順で並べたものである。同じ参照の重複は拒否する。Macroが特定のwire参照文字列をcompile timeに固定することは要求しない。
`fetch`と`fetch_tree`は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
### `fetch`
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を保持しない。
### `fetch_tree`
Directory listingで発見した各entryについて、その時点のstruct instanceを生成して公開できる。ただし、Providerがdirectory全体を先に収集してから`Vec`として返すことを要求しない。Host Runtimeの`depth`、`limit`、resource budgetに従って、direct childrenをlazyかつboundedに発見できなければならない。
```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;
};
traversal.accept(child)?;
if traversal.has_enough_evidence_to_finish() {
break;
}
}
let completion = entries.completion();
```
`discover_children`、`budget`、`traversal`、`completion`はconceptualであり、具体APIではない。必要なsemanticsは次である。
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
- 非公開childをtraversal count、`limit`、`truncated`判定へ含めない。
- Clean exhaustionと、追加の公開childが存在する状態を区別する。
- 結果がちょうど`limit`件の場合も、追加childの有無を根拠なく仮定しない。
- Depth境界で公開されたindexable childが省略されるか判定できる。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わない。
- Host RuntimeがProtocolの`truncated`を正確に設定できる情報を提供する。
- 異なるconcrete published typeを、Providerから共通のreturn typeとして扱える。
具体的なreturn type、streaming、pagination、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が示す要件は次である。
- 過去の`fetch`で作成したstruct instanceを保持していなくてもOperationを実行できる。
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれることを確認し、含まれなければ`InterfaceMismatch`を返す。
- 現在の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名の組で一意に決まり、表示上の曖昧さがある場合は`<interface-reference>.<operation-name>`でqualifyできる。この表示文字列を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へ登録し、opaqueなInterface参照を割り当てる必要がある。
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の再評価を実装できる必要がある。以前の`fetch`で公開された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。
- Host / backend implementation failure。
- Interfaceがreturn typeとして宣言したdomain failure。
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Rustの`Result<T, E>`をdomain Union、Host error、または両方のどれへ対応付けるかは未決定である。
MacroがRust panicをdomain failureへ自動変換してはならない。Panic isolationと`Internal`へのmappingはHost Runtimeの実装要件として別途決定する。
## 要件
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をcountせず、lazyかつboundedに発見する。
- Clean exhaustion、additional visible child、failure、cancellationを区別して`truncated`を正確に決定できる。
- 同じ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をProtocolErrorへ安全に対応付けられる。
- 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登録とopaque参照発行の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で実装できるか評価する。
+710
View File
@@ -0,0 +1,710 @@
# 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)、[Interface / Operation](../2.2-operation.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全体について常駐させず、`fetch`、`fetch_tree`、`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やopaqueな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を登録または公開するときに割り当てるopaque参照を、publication APIへの追加順で並べたものである。同じ参照の重複は拒否する。Variable名やJavaScript object identityをportableなProtocol参照としてそのまま使用することは要求しない。
`fetch`と`fetch_tree`は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
### `fetch`
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を保持しない。
### `fetch_tree`
Providerがdirectory全体を先に収集してArrayとして返すことを要求しない。Host Runtimeの`depth`、`limit`、resource budgetに従って、direct childrenをlazyかつboundedに発見できなければならない。
ConceptualにはAsync Iterable、pull-based cursor、paged source等を利用できる。
```ts
const entries = filesystem.discoverChildren(path, budget);
for await (const entry of entries) {
const child = await projectCurrentObject(
entry.path,
context,
);
if (!child) {
continue;
}
traversal.accept(child);
if (traversal.hasEnoughEvidenceToFinish()) {
break;
}
}
const completion = entries.completion();
```
`discoverChildren`、`budget`、`traversal`、`completion`はconceptualであり、具体APIではない。必要なsemanticsは次である。
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
- 非公開childをtraversal count、`limit`、`truncated`判定へ含めない。
- Clean exhaustionと、追加の公開childが存在する状態を区別する。
- 結果がちょうど`limit`件の場合も、追加childの有無を根拠なく仮定しない。
- Depth境界で公開されたindexable childが省略されるか判定できる。
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わない。
- Host RuntimeがProtocolの`truncated`を正確に設定できる情報を提供する。
- 異なるclassやplain objectを共通のpublished return typeとして扱える。
具体的なAsync Iterator contract、streaming、pagination、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が示す要件は次である。
- 過去の`fetch`で作成したobject instanceを保持していなくてもOperationを実行できる。
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれることを確認し、含まれなければ`InterfaceMismatch`を返す。
- 現在の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),
});
```
表示上の曖昧さがある場合は`<interface-reference>.<operation-name>`でqualifyできる。この表示文字列をProtocol request fieldへparseし直さない。
Intersection type、interface extension、mixin等をDescriptorのcompositionへ自動変換しない。Protocol上のInterface inheritanceを暗黙に導入しない。
## HostへのInterface登録
Runtime Interface definitionをHostへ登録し、opaqueなInterface参照を割り当てる必要がある。
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の再評価を実装できる必要がある。以前の`fetch`で公開された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。
- Host / backend implementation failure。
- Interfaceがreturn typeとして宣言したdomain failure。
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Thrown value、rejected Promise、explicit result objectをHost errorまたはdomain resultへどう対応付けるかは未決定である。
Arbitrary thrown valueをdomain failureへ自動変換してはならない。`Error`以外のthrow、Promise rejection、panicに相当するfatal conditionのisolationと`Internal`へのmappingはHost Runtimeの実装要件として別途決定する。
## 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をcountせず、lazyかつboundedに発見する。
- Clean exhaustion、additional visible child、failure、cancellationを区別して`truncated`を正確に決定できる。
- 同じ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をProtocolErrorへ安全に対応付けられる。
- 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登録とopaque参照発行の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で実装できるか評価する。
+3 -2
View File
@@ -6,7 +6,8 @@
対象: [2.2. Interface / Operation](../2.2-operation.md)
- RustとTypeScriptのcode-level Interface Declaration API。
- 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を追加するか。
@@ -20,7 +21,7 @@
### Field semantics
- `interface` fieldで使用するopaque参照のscopeと有効期間。
- `Object.interfaces`の要素、`fetch_interface`の`interface`、`InterfaceTarget.reference`で使用するopaque参照のscopeと有効期間。
### Type / Value
+51 -21
View File
@@ -35,7 +35,8 @@ Protocol structの`String`はJSON string、`Vec<u8>`はbindingが定めるopaque
| --- | --- | --- |
| `path` | string | canonical absolute Worldspace path |
| `name`、Operation / Type / Field / Parameter / Case名 | string | 各struct内の名前 |
| `interface` | string | Interface Descriptorへの参照 |
| `interfaces` | stringのarray | Objectが公開するInterface参照のordered set |
| `interface`、`InterfaceTarget.reference` | string | 一つのInterfaceへのopaqueな参照 |
| `format` | string | Descriptor format identifier |
| `ref` | string | opaqueなObject identity |
| `validator` | base64 stringまたはHTTP metadata | opaqueな条件付き操作用value |
@@ -62,12 +63,17 @@ Responseは次の形になる。
{
"name": "123",
"description": "First item",
"interface": "item-interface-v1",
"interfaces": [
"item-interface-v1",
"item-marker-interface-v1"
],
"ref": "item:123",
"validator": "b3BhcXVlLXZhbGlkYXRvcg=="
}
```
`interfaces`は0個以上のInterface参照を含み、membershipはsetとして扱う。同じ参照の重複はinvalidであり、array順序はUI等の表示順として保持してよいがOperation解決には影響しない。
`ref`、`description`、`validator`はoptional fieldであり、存在しない場合はfield自体を省略する。JSON `null`を代わりに使用しない。
Request pathはresponseへ重複して含めない。HTTP batchを使用する場合も、subrequestの`id`によってrequest pathとObject responseを対応付ける。
@@ -92,7 +98,9 @@ Responseは各nodeのObjectとchildrenを含む。各Objectのpathはrequest pat
"object": {
"name": "items",
"description": "Item collection",
"interface": "sha256:item-collection-interface",
"interfaces": [
"sha256:item-collection-interface"
],
"validator": "cm9vdC12YWxpZGF0b3I="
},
"children": [
@@ -100,7 +108,10 @@ Responseは各nodeのObjectとchildrenを含む。各Objectのpathはrequest pat
"object": {
"name": "123",
"description": "First item",
"interface": "item-interface-v1",
"interfaces": [
"item-interface-v1",
"item-marker-interface-v1"
],
"ref": "item:123",
"validator": "aXRlbS12YWxpZGF0b3I="
},
@@ -116,13 +127,13 @@ Responseは各nodeのObjectとchildrenを含む。各Objectのpathはrequest pat
## Interfaceの取得
ObjectはOperation定義を直接含まず、Interface Descriptorへの参照を持つ。`fetch_interface`は、その参照に対応するDescriptorを返す。
ObjectはOperation定義を直接含まず、Interface Descriptorへの参照のordered setを持つ。`fetch_interface`は、そのうち一つの参照に対応する現在のDescriptorとoptionalなvalidatorを返す。
Conceptual requestは次の形になる。
```json
{
"interface": "item-interface-v1"
"interface": "item-marker-interface-v1"
}
```
@@ -130,21 +141,24 @@ Responseは次の形になる。
```json
{
"interface": "item-interface-v1",
"interface": "item-marker-interface-v1",
"descriptor": {
"format": "wip-interface/1",
"documentation": {
"summary": "Operations provided by an item."
"summary": "Marker capability provided by an item."
},
"types": [],
"operations": []
}
},
"validator": "aW50ZXJmYWNlLXZhbGlkYXRvcg=="
}
```
Interface DescriptorはJSON-compatibleなlogical structとしてProtocolで定義される。JSONはHTTPS上のencodingであり、Interface実装者が手書きするauthoring形式ではない。
ClientはObjectのInterface参照をcacheと比較し、未知のDescriptorだけを`fetch_interface`する。HostはClientのcache状態を推測せず、`fetch`や`fetch_tree`のresponseへDescriptorを先行同梱しない。複数のInterface取得はHTTP batchingでまとめられる。
`validator`はInterface参照と組み合わせて解釈するoptionalなopaque bytesである。同じ参照とvalidatorの組は同じDescriptorを表す。Descriptorを変更可能にするHostはvalidatorを返して変更時に更新し、validatorを返さない参照は有効期間中immutableにする。
ClientはObjectの各Interface参照をcacheと比較し、未知の参照だけを`fetch_interface`する。Validatorが存在する観測は`(interface, validator)`の組でcacheし、Interface validator mismatch等で更新が必要になった参照は再取得する。Validatorを返さない参照は、その参照の有効期間中Descriptorをimmutableとしてcacheできる。HostはClientのcache状態を推測せず、`fetch`や`fetch_tree`のresponseへDescriptorを先行同梱しない。複数のInterface取得はHTTP batchingでまとめられる。
## Operation valueのJSON encoding
@@ -160,6 +174,10 @@ Conceptual request bodyは次の形になる。
"path": "/items/current",
"validator": "b3BhcXVlLXZhbGlkYXRvcg=="
},
"interface": {
"reference": "item-interface-v1",
"validator": "b3BlcmF0aW9uLWludGVyZmFjZQ=="
},
"operation": "get_related_item",
"arguments": {
"relation": "sibling",
@@ -168,7 +186,11 @@ Conceptual request bodyは次の形になる。
}
```
ResponseはOperation resultと、実行後の現在状態に対してHostが発行したoptionalなvalidatorを返す。
`interface.reference`はOperationを宣言する一つのInterfaceを明示し、現在のObjectの`interfaces`に含まれなければならない。異なるInterfaceに同名Operationがあっても、Hostは`interface.reference`と`operation`の組で解決する。表示用の`<interface-reference>.<operation-name>`を一つのrequest fieldとして送らない。
`fetch_interface`がvalidatorを返した場合、Clientは観測したDescriptorに対応する値を`interface.validator`へコピーする。これはDescriptor cache versionではなくOperation executionのpreconditionであり、HostはObject、Object validator、Interface membership、Interface validator、Descriptor、Operation implementation、backend対象を一つのlinearization pointで固定して検証・実行する。
ResponseはOperation resultと、実行後の現在状態に対してHostが発行したoptionalなObject validatorを返す。
```json
{
@@ -224,20 +246,24 @@ ClientはInterface Descriptorに従ってvalueをdecodeし、`entry`と宣言さ
## ValidatorとHTTP conditional request
WIP Protocolのlogical fieldは、ObjectおよびOperation responseの`validator`と、Operation requestの`target.validator`である。
WIP Protocolのlogical fieldには、ObjectおよびOperation responseのObject `validator`、Operation requestの`target.validator`、`FetchInterfaceResponse.validator`、Operation requestの`interface.validator`がある。Object validatorとInterface validatorは別のpreconditionであり、同じHTTP headerへ曖昧に畳み込まない。
単独のHTTP request / responseで、WIP validatorがHTTPのstrong validator semanticsを満たす場合、HTTPS Bindingは次へ対応付けてよい。
```text
Object.validatorまたはCallOperationResponse.validator -> ETag
CallOperationRequest.target.validator -> If-Match
FetchInterfaceResponse.validator -> ETag on fetch_interface response
CallOperationRequest.interface.validator -> request body or dedicated header
validator_mismatch -> 412 Precondition Failed
validator_required -> 428 Precondition Required
interface_validator_mismatch -> 412 Precondition Failed
interface_validator_required -> 428 Precondition Required
```
HTTP headerへmappingした場合も、Client adapterはProtocol上のObjectまたはresponseへvalidatorを復元する。ValidatorがHTTP response representationの変更を反映しない場合、strong ETagとして使用してはならず、body fieldで運ぶ。
HTTP headerへmappingした場合も、Client adapterは値を対応するProtocol上のObject、Operation response、またはInterface observationへ復元する。Object validatorとInterface validatorの両方がある`call_operation`では、標準`If-Match`だけで二つを表したと推測してはならない。Validatorが対象HTTP response representationの変更を反映しない場合、strong ETagとして使用してはならず、body fieldまたは意味を区別できる専用headerで運ぶ。
Batchではsubresponseごとにvalidatorが存在し得るため、各ObjectまたはOperation responseのbody fieldとしてencodeする。
Batchではsubresponseごとにvalidatorが存在し得るため、各Object、Operation response、または`fetch_interface` responseのbody fieldとしてencodeする。
## Protocol error
@@ -254,7 +280,7 @@ Protocol failureをJSON response bodyへ含める場合、概念的に次の形
`code`はProtocolが定義する安定した識別子、`message`は人間向けのboundedな診断である。Clientは`message`の内容で分岐しない。
Protocol error codeとHTTP statusのmapping、およびvalidation errorのstructured detailは未確定である。`not_found`、`operation_not_found`、`permission_denied`の使い分けはProtocolのObject / Operation visibility semanticsに従い、HTTP statusへのmappingによって変更しない。
Protocol error codeとHTTP statusのmapping、およびvalidation errorのstructured detailは未確定である。`not_found`、`interface_mismatch`、`operation_not_found`、`permission_denied`の使い分けと、Object / Interfaceそれぞれのvalidator failureの区別はProtocol semanticsに従い、HTTP statusへのmappingによって変更しない。
Domain failureをProtocol errorへ変換しない。Domain failureはOperation resultのApplication dataとしてencodeする。
@@ -277,10 +303,10 @@ Batch endpointは概念的に`POST {wip-endpoint}/batch`とし、bodyに一意
}
},
{
"id": "item-interface",
"id": "item-marker-interface",
"method": "fetch_interface",
"arguments": {
"interface": "item-interface-v1"
"interface": "item-marker-interface-v1"
}
}
]
@@ -297,20 +323,24 @@ Hostは各subrequestを、単独で受け取った場合と同じ規則で処理
"result": {
"name": "123",
"description": "First item",
"interface": "item-interface-v1",
"interfaces": [
"item-interface-v1",
"item-marker-interface-v1"
],
"ref": "item:123",
"validator": "b3BhcXVlLXZhbGlkYXRvcg=="
}
},
{
"id": "item-interface",
"id": "item-marker-interface",
"result": {
"interface": "item-interface-v1",
"interface": "item-marker-interface-v1",
"descriptor": {
"format": "wip-interface/1",
"types": [],
"operations": []
}
},
"validator": "aW50ZXJmYWNlLXZhbGlkYXRvcg=="
}
}
]