docs: define composable object interfaces
This commit is contained in:
+1
-1
@@ -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
@@ -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で定義する。
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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を自動再試行せず、対象が変化したことを上位インターフェースへ通知して再観測を要求する。
|
||||
|
||||
|
||||
@@ -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では、通常のメソッド呼び出しへ投影してよい。
|
||||
|
||||
|
||||
@@ -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を作り、上記の評価シナリオと要件に照らして比較する。
|
||||
@@ -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で実装できるか評価する。
|
||||
@@ -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で実装できるか評価する。
|
||||
@@ -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
@@ -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=="
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user