diff --git a/1-concept.md b/1-concept.md index 7d47769..032394c 100644 --- a/1-concept.md +++ b/1-concept.md @@ -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を実行する。 ## 発見と既知世界 diff --git a/2-core-model.md b/2-core-model.md index 7455033..3f47c57 100644 --- a/2-core-model.md +++ b/2-core-model.md @@ -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で定義する。 diff --git a/2.1-object-tree-schema.md b/2.1-object-tree-schema.md index 55c7786..b3befba 100644 --- a/2.1-object-tree-schema.md +++ b/2.1-object-tree-schema.md @@ -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表示では、必要に応じて`.`の形で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 diff --git a/2.2-operation.md b/2.2-operation.md index e5e9468..5b0e157 100644 --- a/2.2-operation.md +++ b/2.2-operation.md @@ -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では必要に応じて`.`と表示する。この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` diff --git a/3-protocol.md b/3-protocol.md index eaa3523..83386a4 100644 --- a/3-protocol.md +++ b/3-protocol.md @@ -43,7 +43,7 @@ BTreeMap // 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`として交換し、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` | HostがObjectへ付与するopaque validator | -| `CallOperationResponse.validator` | `Vec` | 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, - interface: String, + interfaces: Vec, r#ref: Option, validator: Option>, } @@ -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は必要に応じて`.`と表示してよい。この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>, } ``` @@ -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>, } +struct InterfaceTarget { + reference: String, + validator: Option>, +} + struct CallOperationRequest { target: Target, + interface: InterfaceTarget, operation: String, arguments: BTreeMap, } @@ -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。 diff --git a/4-client.md b/4-client.md index d2fb3c9..cab65d0 100644 --- a/4-client.md +++ b/4-client.md @@ -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, - interface: String, + interfaces: Vec, r#ref: Option, validator: Option>, 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>, +} +``` -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では必要に応じて`.`と表示する。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を自動再試行せず、対象が変化したことを上位インターフェースへ通知して再観測を要求する。 diff --git a/4.1-ai-tools-example.md b/4.1-ai-tools-example.md index fa8546e..f4060e6 100644 --- a/4.1-ai-tools-example.md +++ b/4.1-ai-tools-example.md @@ -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名が存在する場合や出所を明示したい場合は、`.`の形で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では、通常のメソッド呼び出しへ投影してよい。 diff --git a/draft/dynamic-worldspace-projection.md b/draft/dynamic-worldspace-projection.md new file mode 100644 index 0000000..326bc7c --- /dev/null +++ b/draft/dynamic-worldspace-projection.md @@ -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を作り、上記の評価シナリオと要件に照らして比較する。 diff --git a/draft/rust-api-model.md b/draft/rust-api-model.md new file mode 100644 index 0000000..07c000e --- /dev/null +++ b/draft/rust-api-model.md @@ -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, + /// Maximum number of bytes to return. + length: Option, + ) -> Bytes; + + /// Replaces the file contents. + async fn write( + &self, + content: Bytes, + ) -> WriteResult; +} +``` + +このcodeはconceptual syntaxである。`Option`の対応範囲、`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, + validator: Option>, +} +``` + +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, + length: Option, + ) -> 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 PublishedObject { + fn expose(self, interface: I) -> Self + where + I: InterfaceDefinition; +} + +impl InterfaceDefinition + 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`へ追加するための型付き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`を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 { + 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名の組で一意に決まり、表示上の曖昧さがある場合は`.`で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`を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`を許可する位置。 +- 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で実装できるか評価する。 diff --git a/draft/typescript-api-model.md b/draft/typescript-api-model.md new file mode 100644 index 0000000..88d651a --- /dev/null +++ b/draft/typescript-api-model.md @@ -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; +} +``` + +この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; +``` + +`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; +``` + +Conceptualには、次のようなobject typeへ展開される。 + +```ts +type FileOperations = { + readonly read: (args: { + offset?: number; + length?: number; + }) => Promise; + + readonly write: (args: { + content: Uint8Array; + }) => Promise; +}; +``` + +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 { + // runtime descriptor, codec, and dispatch artifact +} + +interface PublishedObject { + expose( + definition: WipInterfaceDefinition, + implementation: Implementation, + ): PublishedObject; +} +``` + +実際の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 { + 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; + +type WritableFileOperations = + wip.Implementation; + +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), + }); +``` + +表示上の曖昧さがある場合は`.`で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; +} +``` + +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で実装できるか評価する。 diff --git a/draft/unresolved-issues.md b/draft/unresolved-issues.md index 7cc385f..ed69040 100644 --- a/draft/unresolved-issues.md +++ b/draft/unresolved-issues.md @@ -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 diff --git a/draft/wip-over-https.md b/draft/wip-over-https.md index 326ba52..3efb67a 100644 --- a/draft/wip-over-https.md +++ b/draft/wip-over-https.md @@ -35,7 +35,8 @@ Protocol structの`String`はJSON string、`Vec`は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`の組で解決する。表示用の`.`を一つの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==" } } ]