feat: add Astro reference site and organize book contents
Reference site / build (push) Canceled after 0s
Reference site / build (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
# Dynamic Worldspace Projection
|
||||
|
||||
## Status
|
||||
|
||||
この文書は、Rust / TypeScript Host APIを設計する前に、動的なWorldspaceを運用するための責務、処理モデル、要件を整理するdraftである。
|
||||
|
||||
具体的なtrait、class、function、decorator、macro、builderのsyntaxは定義しない。この文書で使用する`Projection Provider`、`Projection Resolution`、`Projected Object`等の名称もworking terminologyであり、canonicalなProtocol typeではない。
|
||||
|
||||
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。言語別のconceptual API modelは[Rust API Model](rust-api-model.md)と[TypeScript API Model](typescript-api-model.md)で扱う。
|
||||
|
||||
## 目的
|
||||
|
||||
WIP Hostの中心的な実装対象は、事前に列挙された静的endpointだけではない。Filesystem、database row、repository内容、検索結果、外部service上のentityなど、実行時に存在と状態が決まる空間をWorldspaceへ投影できなければならない。
|
||||
|
||||
HostがWorldspace内のすべてのObjectを常駐instanceとして保持することは要求しない。Backend dataをrequest時に解決し、必要な範囲だけObjectとして投影できることを基本とする。
|
||||
|
||||
固定pathへ登録されたObjectやservice endpointは、同じ動的projection modelにおいてrouting / resolution structureが固定された特殊ケースとして扱えるべきである。現在の主体やbackend stateに応じて、そこで返すObject representationやvalidatorが変化することは妨げない。静的登録だけを基本形とし、動的空間を大量のObject登録によって模倣する設計にはしない。
|
||||
|
||||
## 基本的な分離
|
||||
|
||||
Host実装APIでは、少なくとも次の責務を区別する必要がある。この区別を一対一のpublic type、trait、objectへ分割することまでは決定しない。
|
||||
|
||||
### Interface Declaration
|
||||
|
||||
Operation名、parameter、return type、documentation等を宣言し、Interface Descriptor、Value codec、boundary validationを生成する。
|
||||
|
||||
Interface Declarationは、多数のObjectが共有できる比較的静的なcapability schemaである。Objectの列挙やbackend dataのlifecycleは担当しない。
|
||||
|
||||
### Worldspace Projection
|
||||
|
||||
Clientから受け取ったpathを、現在の主体とbackend stateに基づいて解決し、Protocol上のObject projectionを生成する。一つのProjected Objectは、Host固有のentityを持たないvirtual nodeでも、一つのbackend entityに対応するものでも、複数のbackend entityを集約したものでもよい。
|
||||
|
||||
Projectionは、少なくとも次を決定する。
|
||||
|
||||
- そのpathのObjectを現在の主体へ公開するか。
|
||||
- 公開する`name`、optionalな`description`、`ref`、`validator`。
|
||||
- Objectの`interfaces` fieldへ設定するInterface参照のordered set。
|
||||
- `observe`から列挙可能なindexable children。
|
||||
- Operation実行時に現在のProjected Objectへ作用するために、Host内部で何を再解決または確立する必要があるか。
|
||||
|
||||
### Host Runtime
|
||||
|
||||
Protocol requestを受け取り、ProjectionとInterface registryを協調させる。Operation callでは、現在のProjected Objectが公開するInterface集合からClientが指定した一つのDescriptorを固定し、そこに宣言されたOperationをObjectのHost内implementationへ適用する。
|
||||
|
||||
Host RuntimeはProtocol validation、Interface Descriptorの公開、Value validation、error mapping、resource limit等を担う。Backend固有のprojection resolutionやdomain operation implementationそのものはProvider等のHost componentへ委譲できる。Object implementationとOperation実装を接続する具体的なAPIは各言語のSDK側で決定し、Protocol conceptとして追加しない。
|
||||
|
||||
## Working terminology
|
||||
|
||||
このdraftでは、Host内部の議論のために次の語を使う。
|
||||
|
||||
- **Backend Entity** — File、database row、issue、repository、remote entity等、projectionの入力になり得るHost固有の実体。一つのProjected Objectと一対一に対応するとは限らない。
|
||||
- **Projection Resolution** — 現在の主体とpathについて、公開するObject representationと、後続処理に必要なHost-private情報を得るrequest-timeの解決結果。
|
||||
- **Projection Provider** — Pathと主体からProjection Resolutionを生成し、indexable childrenの発見を支援するHost component。
|
||||
- **Projected Object** — その時点でClientへ返すProtocol `Object`。Backend Entityを持たないvirtual Object、一つのentityのprojection、複数entityのaggregateのいずれでもよく、Host内の常駐instanceを意味しない。
|
||||
|
||||
HostがOperationを実装するために使用するRust struct、TypeScript object、backend handle、transaction context等はHost-privateであり、共通するHost abstractionを必須にしない。これらをProtocolの`Target { path, validator }`、canonical path、Objectの`ref`として公開または流用しない。
|
||||
|
||||
## 運用モデル
|
||||
|
||||
### `observe`
|
||||
|
||||
`observe(path, depth)`では、Hostは概念的に次を行う。
|
||||
|
||||
1. Requestの主体とpathをProjection Providerへ渡す。
|
||||
2. Providerが現在のbackend stateから、そのpathで公開するProjected ObjectのProjection Resolutionを生成する。
|
||||
3. 非公開または存在しない場合は`NotFound`とする。
|
||||
4. 公開する場合は、現在のObject metadata、Interface参照のordered set、optionalな`ref`と`validator`を生成する。
|
||||
5. Nodeのdepthがrequested depthより小さい場合、その主体へ公開するdirect indexable childrenをclean exhaustionまで列挙し、各childについて同じ処理を再帰的に行う。
|
||||
6. Nodeがrequested depthに達した場合はchildrenを観測せず、responseの`children` fieldを省略する。
|
||||
7. Requested depthまで完全な`ObjectObservation`を返す。
|
||||
|
||||
ProviderはWorldspace全体をmaterializeせず、現在処理しているparentのdirect childrenを遅延して発見できる必要がある。Childrenをstream、iterator、cursor等のHost内部表現で生成してよいが、一つのparentについて成功するには公開されたindexable childrenをすべて調べ終えなければならない。
|
||||
|
||||
Backend failure、cancellation、またはresource budget超過をclean exhaustionとして扱わない。Requested depthまでの完全なresponseを構築できない場合、Host Runtimeは部分的なsuccess responseを返さず、対応するfailureを返す。
|
||||
|
||||
各childについて現在のObject projectionを構築し、そのcanonical pathを後の`observe`または`call_operation`で現在のProjected Objectとして再解決できなければならない。以前観測したbackend entityやHost内部instanceを再現することは要求しない。
|
||||
|
||||
この処理のために、Hostが以前の`observe`で生成したObject instanceを保持している必要はない。内部cacheを利用してもよいが、cacheの有無によってProtocol semanticsを変えてはならない。
|
||||
|
||||
### `fetch_interface`
|
||||
|
||||
ProjectionがObjectへ割り当てた各Interface参照は、同じ主体から取得可能なDescriptorへ解決されなければならない。
|
||||
|
||||
複数のObjectは同じInterface参照を共有でき、一つのObjectも複数のInterfaceを組み合わせられる。Validatorを返すInterface参照ではDescriptorを変更でき、同じ参照とvalidatorの組が一つのDescriptor observationを表す。Validatorを返さない参照のDescriptorは、その参照が有効な間immutableである。Interface参照のscopeは[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。登録API、生成時期、deduplication、cacheの具体実装は別途決定する。
|
||||
|
||||
### `call_operation`
|
||||
|
||||
`call_operation`では、HostはProviderまたはbackend固有のexecution boundaryへ入り、その同じboundary内で概念的に次を行う。
|
||||
|
||||
1. 現在の主体と`target.path`からProjection Resolutionを生成し、同じbackend対象を保持するstable handleまたはtransaction contextを取得する。
|
||||
2. そのpathのProjected Objectを現在も公開するか確認する。
|
||||
3. 指定されたObject validatorを現在のObject identityと状態へ照合する。
|
||||
4. Clientが指定したInterface参照がObjectの現在の`interfaces`に含まれることを確認する。
|
||||
5. `fetch_interface`で観測したInterface validatorを現在状態へ照合する。
|
||||
6. 固定したInterface Descriptorで指定Operationが宣言されていることを確認する。
|
||||
7. Argumentsを同じOperation Declarationに従って検証する。
|
||||
8. 固定したObject implementation、Interface Descriptor、Operation implementationを、step 1で固定した同じbackend対象へ適用する。
|
||||
9. Resultを同じOperation Declarationで検証し、nestedしたすべての`Entry`へ現在の主体のvisibility checkを適用してから、実行後Object validatorとともに返す。
|
||||
|
||||
Step 3から8までの間にObject、Interface、Descriptor、Operation implementation、backend対象の対応が変化し得る実装は、この手順を満たさない。Projectionは、Host RuntimeとProviderがこのatomicityを実現するために必要なstable handle、transaction、conditional operation、lock等を使用できるようにしなければならない。
|
||||
|
||||
過去の`observe`で作成したHost内部Object instanceの存在を前提にしてはならない。Clientから届くprotocol-level Targetはpathとoptionalなvalidatorであり、Hostはcall時の現在状態からObject implementationを解決する。Validatorを省略したcallを現在のOperationが許可する場合は、過去に観測したObjectではなく、実行時にpathが指す現在のObjectへ無条件に実行する。
|
||||
|
||||
Validatorを指定した場合は、Clientが観測したObject identityおよび保護対象状態と実行対象を照合する。Path、`ref`、以前のProjection Resolution、cacheされたHost内部instanceのいずれも、単独ではProtocol上の永続的なObject identityとして扱わない。
|
||||
|
||||
## Filesystem projectionの評価例
|
||||
|
||||
Filesystemを`/files`以下へ投影する場合、Hostが全file / directoryに対応するObject instanceを作成する必要はない。
|
||||
|
||||
- `observe("/files/a.txt", depth = 0)`では、pathをfilesystem entryへ解決し、stat等からObject projectionを構築する。
|
||||
- `observe("/files", depth = 1)`では、directory listingから直接のindexable childrenをすべて列挙する。
|
||||
- Regular file、directory、symlink等に応じてInterface参照の集合を選択できる。
|
||||
- 主体の権限に応じてObjectを非公開にしたり、read-only、writable等のcapability Interfaceを集合へ追加または除外できる。
|
||||
- File Operationでは、call時にpathを現在のfilesystem entryへ解決し、Rust struct、TypeScript object、file descriptor等を使ってOperationを実装できる。
|
||||
|
||||
この例で、Objectはfileそのものでも、file内容を複製したrecordでもない。FileをWorldspace上で発見および操作するためのprotocol-level projectionである。
|
||||
|
||||
## 要件
|
||||
|
||||
### Lazyかつdata-drivenであること
|
||||
|
||||
- Object集合を事前に列挙または常駐させなくてもよい。
|
||||
- Backend dataからrequest時にObjectを生成できる。
|
||||
- 大規模または無限に近いlogical spaceを、要求範囲だけ投影できる。
|
||||
- Tree上でindexableでないObjectも、既知のpathを`depth = 0`で`observe`したときに直接解決できる。
|
||||
- Operation resultの`Entry`等で新たに得たpathを、事前のObject登録なしに解決できる。
|
||||
- 静的Objectも同じmodelで表現できる。
|
||||
|
||||
### Protocol上有効なprojectionを生成すること
|
||||
|
||||
- Rootと非rootに対する`name`規則を守る。
|
||||
- 同じparentの公開childrenに重複する`name`を生成しない。
|
||||
- Backend側の名前をそのままWIPのUnicode nameとして表現できない場合は、deterministicかつcanonicalで、同じparent内で衝突しないmappingを定義するか、そのentryを公開対象から除外する。
|
||||
- Mappingしたnameは、親全体の無制限列挙を前提にせず、`depth = 0`の`observe`とOperation dispatchで同じchildへboundedに解決できなければならない。Encoding自体が可逆である必要はなく、boundedなbackend indexを利用してよい。
|
||||
- Name mappingはchild enumeration、`observe`、`call_operation`で一貫させる。
|
||||
- Objectの`interfaces`を重複のないordered setとして生成し、各参照を取得可能で有効なInterface Descriptorへ解決する。
|
||||
- ProviderやOperation implementationの出力を信頼せず、Host RuntimeがProtocol boundaryで検証できる。
|
||||
- Invalidなbackend dataを、Protocol上有効に見える別のObjectへ暗黙に衝突させない。
|
||||
|
||||
### Object representationとbackend dataを分離すること
|
||||
|
||||
- Protocol `Object`をbackend entity classやHost内のmutable instanceと同一視しない。
|
||||
- Object projectionはrequestごとに再構築できるvalueとして扱える。
|
||||
- Backend connection、database transaction、file descriptor、credential等をObject metadataへ混入させない。
|
||||
- Host内部のObject implementation stateはwire representationへ露出させない。
|
||||
|
||||
### Interface DeclarationとObject publicationを分離すること
|
||||
|
||||
- Interface DeclarationはObjectの個数やpathに依存しない。
|
||||
- 多数の動的Objectが同じInterface参照を共有でき、一つのObjectも複数のInterfaceをordered setとして組み合わせられる。
|
||||
- Validatorを返すInterface参照は同じ参照でDescriptorを変更できるが、変更時にvalidatorを更新する。Validatorを返さない参照のDescriptorは、その参照が有効な間immutableとする。
|
||||
- Projectionは主体、policy、resource kind、安定した公開capabilityに基づいて、Objectの`interfaces`へ含める参照と立項順を決定できる。
|
||||
- 頻繁に変化する実行可否や通常のdomain stateを、Interface集合やOperation declarationへ同期しない。
|
||||
- Operationを隠す場合はcapability Interfaceを集合から除外するか、そのOperationを宣言しないDescriptorを使用し、公開済みOperationの実行だけを拒否する場合は`PermissionDenied`を使用できる。
|
||||
- Objectの`interfaces`に含まれる各Descriptorは、その主体から取得可能でなければならない。
|
||||
- Interface参照からDescriptorを解決することと、そのInterfaceを実装するHost codeを区別する。
|
||||
|
||||
### `observe`と`call_operation`で一貫すること
|
||||
|
||||
- 同じ時点と主体について、異なるdepthの観測が矛盾したObject metadataを生成しない。
|
||||
- 同じ`ref`を持つObjectを複数pathへ投影する場合も、一つの共有された現在状態として扱い、path固有の`name`、`description`、Interface集合を同時に返さない。
|
||||
- 同じ`ref`と同じvalidator valueを持つObject projectionは、validatorが存在する場合、`name`、`description`、`interfaces`を含む全representationを一致させる。
|
||||
- 同じ`ref`でvalidatorが異なる場合は同一Objectの異なる観測状態であり、別Objectとして扱わない。
|
||||
- Validatorが存在しないObjectでは同validatorによるrepresentation equality checkを行えないため、Clientが最後に成功した観測を現在状態として扱えるようにする。
|
||||
- 同じ`ref`を持つObjectの公開representationが変化する場合は、validatorが存在するなら以前と同じvalidatorのまま異なるrepresentationを返さない。
|
||||
- 深い`observe`で得たpathを`depth = 0`で再観測した場合に、backend stateが変化していなければ同じObjectとして解釈できる。
|
||||
- Operation call時には、その時点でpathが指す現在のProjected ObjectとInterface集合を再解決し、Clientが指定した参照のmembershipとvalidatorを確認する。
|
||||
- 非公開Object、非公開Operation、公開済みだが実行拒否されたOperationをProtocolで定めたerrorへ一貫して対応付ける。
|
||||
|
||||
Backendが並行して変化する場合、複数request間で同じsnapshotを保証することまでは要求しない。観測したObject identityおよび状態と条件付き実行の対応はvalidatorで保護する。Validatorを省略したcallをOperationが許可する場合は実行時の現在Objectへ無条件に実行し、Operationがvalidatorを要求する場合は`ValidatorRequired`として実行しない。
|
||||
|
||||
### Operation解決と実行対象のatomicity
|
||||
|
||||
Operation callでは、visibility、Object identityとvalidator、Interface membershipとvalidator、Interface Descriptor、Operation implementation、backend対象を一つのlinearization pointで固定しなければならない。ValidatorなしのObject実行をOperationが許可する場合でも、解決後にpathの指すProjected Objectが変化し、以前のDescriptorに基づくOperation implementationが現在の別Objectへ適用されることを許してはならない。
|
||||
|
||||
少なくとも次を一つのatomicなexecution boundaryとして扱えなければならない。
|
||||
|
||||
- Pathが現在指すObject identityと保護対象状態の確認。
|
||||
- Object validatorの必須判定と比較。
|
||||
- 指定Interfaceの現在のmembership確認。
|
||||
- Interface validatorの必須判定と比較。
|
||||
- Interface DescriptorとOperation implementationの固定。
|
||||
- Authorizationの確認。
|
||||
- 同じbackend対象へのOperation実行。
|
||||
|
||||
単にpathを一度解決し、validatorを比較した後に、同じpathを別途開き直してOperationを実行する実装では、比較と実行の間に対象が差し替わる可能性がある。
|
||||
|
||||
Host APIは、backend transaction、lock、revision付きconditional update、file descriptor、identity再確認等、Providerがbackendに適した方法でatomicityを実現できる境界を提供する必要がある。すべてのbackendに共通する特定のlocking方式は要求しない。
|
||||
|
||||
### Host内部Object stateのlifecycleを限定できること
|
||||
|
||||
- Rust struct、TypeScript object、backend handle等をrequestまたはOperation execution scopeだけで構築できる。
|
||||
- Hostは全Object分のimplementation instanceを保持しなくてよい。
|
||||
- Providerは必要に応じてconnection poolやmetadata cache等を共有できる。
|
||||
- Request終了後の内部state保持、再利用、破棄はbackend entity、connection、transaction等の安全性を損なわない。
|
||||
- Clientが保持する`ref`やvalidatorからHost内部instanceを直接復元することを要求しない。
|
||||
|
||||
### 認可をrequest時に評価できること
|
||||
|
||||
- Projectionは主体に基づいてObject visibilityと、Objectの`interfaces`へ含める参照の集合を決定できる。
|
||||
- 非公開ObjectとそのObjectだけを経由するsubtreeを`observe` responseから省略し、存在や省略数を示すmarkerを生成しない。
|
||||
- Requested depthより浅いnodeでは、その主体へ公開されたindexable childrenを完全に返す。
|
||||
- Operation implementationまたはその前段で、call時のauthorizationを再評価できる。
|
||||
- 以前の`observe`時に許可されていたことだけを根拠にOperationを実行しない。
|
||||
- 非公開Objectの存在や非公開Operationを内部error、log、timing等から不用意に漏らさない実装が可能である。
|
||||
- Operation resultへ`Entry` pathを含める場合もObject visibilityを迂回せず、result生成時点でその主体に認識させてよいpathだけを返す。この規則はRecord、List、Union等へnestedしたすべての`Entry`位置へ再帰的に適用する。
|
||||
- `Entry` argumentや既知pathの所持をauthorizationの証明として扱わず、参照先へ作用するOperationは現在の主体と状態に基づいて認可する。
|
||||
- Result取得後のstateまたは権限変更により、その`Entry`への後続`observe`が`NotFound`になることは許容する。
|
||||
|
||||
### Backend固有のidentityを強制しないこと
|
||||
|
||||
- Providerは適切なbackend identityがある場合に`ref`へ利用できる。
|
||||
- Backend EntityとProtocol Objectを常に一対一対応させず、例えば同じfilesystem identityが異なる名前で同時に現れ、Object representationの整合性規則を満たせない場合は、同じ`ref`を機械的に再利用しない。
|
||||
- Backendに安定したUIDがなくてもObjectを投影できる。
|
||||
- Host内部のexecution identityとClient-facingな`ref`を同一形式にする必要はない。
|
||||
- `ref`をHost内部instanceのhandleやdereference keyとして使用することを要求しない。
|
||||
- `ref`を持たないObjectでもvalidatorを用いた安全なOperationを実装できる。
|
||||
- Validatorは状態だけでなく現在のObject identityにも結び付け、pathが別Objectを指す場合は内容が同じでも一致させない。
|
||||
|
||||
### Resource limitとcancellationを適用できること
|
||||
|
||||
- Child enumeration、backend lookup、descriptor生成、Value変換へboundedなresource limitを適用できる。
|
||||
- `observe`のdepthをProvider側の無制限な再帰列挙へ変換せず、request全体へboundedなresource budgetを適用する。
|
||||
- Client disconnect、timeout、Host shutdown等に応じてbackend処理へcancellationを伝播できる。
|
||||
- `call_operation`をdispatchする前にresource limitへ達した場合は`ResourceLimitExceeded`として拒否し、Operationを実行しない。
|
||||
- Dispatch後のcancellation、timeout、implementation failure、result encoding failureについて、Operationが未実行またはrollback済みであると保証できる場合だけ`Internal`または`ResourceLimitExceeded`を返す。保証できない場合は`OperationOutcomeUnknown`を返す。
|
||||
- Side effectを持つOperationでは、cancellationやtimeoutを未実行またはrollback済みの証明として扱わず、Host RuntimeもClientも実行結果が不明なcallを自動retryしない。
|
||||
- Partial backend failureをProtocol上の成功したObject observationへ黙って変換しない。
|
||||
|
||||
### RustとTypeScriptで同じsemanticsを表現できること
|
||||
|
||||
言語ごとのAPI syntaxは異なってよいが、少なくとも次を両方で実現できなければならない。
|
||||
|
||||
- Asyncなpath resolutionとchild enumeration。
|
||||
- Data-drivenなObject projection。
|
||||
- Interface Declarationの共有と、Objectへの0個以上のInterfaceのcomposition。
|
||||
- Type-safeまたはruntime-validatedなOperation dispatch。
|
||||
- Host-privateなObject implementation stateをrequest-timeに構築および利用できること。
|
||||
- Object validator、Interface membershipとvalidator、Descriptor、Operation implementation、backend対象の一貫したlinearization point。
|
||||
- Request context、主体、cancellation、resource limitの伝播。
|
||||
- Protocol errorとbackend / implementation errorの分離。
|
||||
|
||||
Rust固有のtrait、generic、associated type、type erasureや、TypeScript固有のobject definition、generic inference、runtime schema libraryの選択は、この要件を満たすためのAPI設計段階で決める。
|
||||
|
||||
## Host内部cache
|
||||
|
||||
Hostは性能最適化として、path resolution、backend metadata、Object projection、Interface Descriptor、Host内部Object state等をcacheしてよい。ただし、cacheはProtocol contractではない。
|
||||
|
||||
- Cacheがなくても正しく動作しなければならない。
|
||||
- Visibilityとauthorizationを異なる主体間で誤って共有してはならない。
|
||||
- Stale cacheによってvalidator比較を迂回してはならない。
|
||||
- Descriptor cacheは同じInterface参照とvalidatorの組に異なるDescriptorを対応付けず、validatorを返さない参照をimmutableとして扱う。
|
||||
- Resource handleをcacheする場合は、そのlifecycleとidentityの再確認をbackendに適した方法で行う。
|
||||
|
||||
## Composition
|
||||
|
||||
一つのHostは、Filesystem、database、remote API等の複数Providerを一つのWorldspaceへ合成できることが望ましい。
|
||||
|
||||
Composition後も、同じ主体へ公開されたWorldspace全体で`ref`とInterface参照のProtocol semanticsを保たなければならない。Provider-local identityやInterface keyをそのまま公開すると衝突し得るため、Hostはnamespace、変換、中央発行等の方法で次を保証する。
|
||||
|
||||
- 異なるObjectへ同じ有効な`ref`を割り当てない。
|
||||
- 同じ有効なInterface参照とvalidatorの組を異なるDescriptorへ対応付けない。
|
||||
- Validator比較がProvider routingの変更によって別Objectへ適用されない。
|
||||
|
||||
Interface参照は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)の構造化参照を使用する。Objectの祖先制約と、合成後のscope・path・typed `entry`の変換は[ProtocolのWorldspace composition](../3-protocol.md#worldspace-composition)に従う。Objectの`ref`の具体的な発行方式は引き続き未決定とする。ScopeはそのpathのObjectに紐づき、self-scopeを許可する。RuntimeはDescriptorとscope Objectのoptionalな`ref`を一つのInterface observationとして公開し、callで指定された`scope_ref`をrouting・implementationの固定と一貫して確認する。`ref`のないscopeも許可し、そのidentityの追跡を要求しない。
|
||||
|
||||
ただし、mount API、path routing、Provider間のparent / child関係、同名entry conflict、cross-provider Operation、transaction boundaryは未決定である。ProtocolはHost内部のcomposition方式を公開しないため、Host APIでこれらをどこまで標準化するかを別途検討する。
|
||||
|
||||
## 非目標
|
||||
|
||||
このdraftは次を要求しない。
|
||||
|
||||
- Worldspace全体の事前materialization。
|
||||
- Objectごとの常駐class instanceまたはactor。
|
||||
- Backend entityとProtocol Objectの一対一対応。
|
||||
- Host内部Object state、database key、file descriptor等のwire公開。
|
||||
- Client cacheとHost stateの同期。
|
||||
- Provider間にまたがる分散transaction。
|
||||
- すべてのObjectに対する`ref`の発行。
|
||||
- 特定のdependency injection framework、schema library、async runtimeの採用。
|
||||
|
||||
## API設計前に評価するシナリオ
|
||||
|
||||
具体API案は、少なくとも次のシナリオで不自然な大量登録や永続Object instanceを要求しないことを確認する。
|
||||
|
||||
1. 数百万entryを持つFilesystemを、directory listingに応じて遅延投影する。
|
||||
2. Database tableのrowをObjectとして投影し、row keyからcall時に再解決する。
|
||||
3. 検索Operationが、Treeではindexableでない結果Objectの`Entry` pathを返す。
|
||||
4. 同じbackend dataを元に、主体ごとに異なるObject visibilityまたはInterface集合を公開する。
|
||||
5. 観測後にpathの指すProjected Objectが変化し、validator付きOperationを拒否する。
|
||||
6. 公開済みOperationをcall時のauthorizationで`PermissionDenied`にする。
|
||||
7. 固定pathの静的service endpointを、動的modelの特殊ケースとして公開する。
|
||||
8. 複数Providerを一つのWorldspaceへ合成し、Provider-localなidentityの衝突を防ぐ。
|
||||
9. Backend entityを持たないvirtual Objectと、複数entityを集約するObjectへOperation implementationを適用する。
|
||||
10. 深い`observe`がresource budgetを超える場合、部分的なresponseを返さず`ResourceLimitExceeded`にする。
|
||||
11. Operation resultのnestedな`Entry`から、現在の主体へ非公開のObject pathが漏れないようにする。
|
||||
|
||||
## 未決定事項
|
||||
|
||||
- Projection ProviderをHostへ登録・合成する単位。
|
||||
- Path routingおよびmountに相当する概念を標準Host APIへ含めるか。
|
||||
- Path resolutionとchild discoveryの具体的な戻り値。
|
||||
- Projected Objectがzero / one / multiple backend entityから導出される場合のHost内部Object implementation表現。
|
||||
- Child discoveryがObject projectionを返すか、遅延解決用の内部keyを返すか。
|
||||
- Clean exhaustion、failure、cancellationを区別し、requested depthまで完全な観測だけをsuccessとして返せる内部contract。
|
||||
- Rust structやTypeScript objectを、Interface DeclarationおよびOperation implementationへ接続する言語別API。
|
||||
- DynamicなProjected Object implementationをProviderから返すときのgeneric、type erasure、runtime representation。
|
||||
- Interface DeclarationとOperation implementationを同じAPIにするか分離するか。
|
||||
- Validator比較とOperation実行を囲むtransaction / callback / guardの抽象化。
|
||||
- Backendがatomic conditional executionを提供できない場合に、そのOperationをどう制限するか。
|
||||
- Subject-specificなInterface参照の選択、Descriptor生成、参照発行、cache方法。
|
||||
- Descriptorを事前登録するか必要時に生成するか、および構造的に等しいDescriptorをinternするか。
|
||||
- Operation result内のnestedな`Entry` visibilityを、Operation implementation、Projection、Host Runtimeのどこで検証するか。
|
||||
- Backend nameをWIP nameへmappingする場合のencoding、index、collision処理。
|
||||
- 複数Provider間で`ref`、Interface参照、validator identityをnamespace化する方法。
|
||||
- Projection、Operation implementation、Host Runtimeが返すerrorとProtocolErrorの対応付け。
|
||||
- Enumerationのstreaming、pagination、backpressureをHost内部APIでどう表すか。
|
||||
- Provider composition時のconflict、ownership、lifecycle、shutdown順序。
|
||||
- Observability、tracing、auditで非公開Objectやsecretを漏らさないための標準hook。
|
||||
|
||||
これらを決定する前に、Rust / TypeScript双方で複数のAPI sketchを作り、上記の評価シナリオと要件に照らして比較する。
|
||||
@@ -0,0 +1,155 @@
|
||||
# Filesystem Discovery: 入口と内容の分離(草案)
|
||||
|
||||
## 位置付け
|
||||
|
||||
この文書は、YoiのFS投影に対して決めた探索仕様を、WIP Hostの投影方針として記録するdraftである。**Treeで発見するのはfilesystemの入口までとし、その内容はdirectoryのlist / glob / grep Operationで発見する。**
|
||||
|
||||
Indexableな範囲の全量取得をClientで縮めるのではなく、Hostが何をindexableとして公開するかを修正する。個々のファイル・フォルダは非indexableなObjectとして要求時に直接解決できる。
|
||||
|
||||
[Object tree / SchemaのIndexable children](../2.1-object-tree-schema.md#indexable-children)、[Client](../4-client.md)、[4.1. AI Tools](../4.1-ai-tools.md)を前提とする。Yoi側の実装追跡はT-714、既存投影はT-699である。本稿は仕様決定の記録であり、実装済みであることを示さない。
|
||||
|
||||
## なぜFS全体をindexableにしないか
|
||||
|
||||
Indexable childrenには、件数がboundedまたは現実的な範囲で列挙でき、比較的安価で、自然な所有・内包関係であることが求められる。全件列挙が高コストまたは不自然な集合は、対象の集合を扱うObjectのOperationで検索・filter・paginationし、`entry` を返すのが既存の方針である。
|
||||
|
||||
任意のfilesystem directoryは、浅い位置に大量のfileやdirectoryを持ち得る。Depthが小さくてもAIが把握できない件数になるため、depth制限だけでは適切な探索空間を作れない。
|
||||
|
||||
次の対策だけでFS内容をindexableにしてよいとはしない。
|
||||
|
||||
- Clientが小さいdepthを選ぶこと。
|
||||
- Hostが件数上限で `ResourceLimitExceeded` を返すこと。
|
||||
- Directoryをlazyに列挙し、常駐Objectや事前indexを作らないこと。
|
||||
- Clientが応答を切り捨てて表示すること。
|
||||
|
||||
前二者は必要な安全弁、lazy解決は実装上の性質であり、indexabilityの判断とは別である。部分的な応答を完全なtreeとして見せることもできない。本投影では、内容の件数が少ない場合だけchildrenへ展開する方式にはせず、入口と内容の境界を一貫させる。
|
||||
|
||||
## Indexableな公開境界
|
||||
|
||||
Yoiでは、rootからtreeで発見できる範囲を次とする。
|
||||
|
||||
```text
|
||||
/
|
||||
└── checkouts
|
||||
├── main
|
||||
└── docs
|
||||
```
|
||||
|
||||
`/checkouts/<checkout-name>` は、現在利用できるfilesystemの入口であり、directoryとして操作できるObjectでもある。その直下から先のfile / directoryは、入口や内容directoryのindexable childrenには含めない。
|
||||
|
||||
例えば次のpathは有効な操作対象として存在し得るが、上のtreeには展開しない。
|
||||
|
||||
```text
|
||||
/checkouts/main/README.md
|
||||
/checkouts/main/src
|
||||
/checkouts/main/src/lib.rs
|
||||
```
|
||||
|
||||
`/checkouts` はYoi側の配置例であり、WIP Protocol全体へ固定namespaceを追加するものではない。単一FSをWorldspace `/` に直接投影する場合は `/` 自体が入口に相当し、その内容をindexable childrenへ展開しない。
|
||||
|
||||
入口の一覧自体についてもindexableの一般条件は維持する。この境界は、入口の数まで無制限に増やしてよいという意味ではない。
|
||||
|
||||
## 非indexableと非公開は別
|
||||
|
||||
File / directoryをindexableにしないことは、Objectとして非公開にすることでも、pathで解決不能にすることでもない。
|
||||
|
||||
- `observe("/checkouts/main/src/lib.rs", depth = 0)` は、現在の主体に公開できるfileなら、そのObjectを返せる。
|
||||
- AI向け `inspect(path)` は、ClientがまだObjectを観測していないpathにも使える。
|
||||
- Directoryの探索やfileの読み書きは、そのObjectが公開するInterfaceのOperationとして実行する。
|
||||
- 直接解決にも現在の可視性・権限を適用する。Pathを知っていることは権限ではない。
|
||||
|
||||
非公開・存在しないpathの扱いは従来のProtocolに従う。非indexableという理由だけで `NotFound` を返してはならない。
|
||||
|
||||
## observeのchildren契約
|
||||
|
||||
FS入口およびその内容のdirectory/fileは、この投影では内容のindexable childrenを持たない。
|
||||
|
||||
| 観測 | 応答の意味 |
|
||||
| --- | --- |
|
||||
| `observe("/checkouts", 1)` | 接続済み入口をchildrenとして列挙。入口はdepth境界なのでchildren fieldを省略 |
|
||||
| `observe("/checkouts", 2)` | 同じ入口までを列挙。各入口のchildrenは `[]`。内容は列挙しない |
|
||||
| `observe("/checkouts/main", 0)` | 入口Objectのみ。children fieldを省略 |
|
||||
| `observe("/checkouts/main", 1)` | 入口Objectと `children: []` |
|
||||
| `observe("/checkouts/main/src", 1)` | 直接解決したdirectory Objectと `children: []` |
|
||||
|
||||
`children: []` は「indexable childがない」という意味であり、filesystem directoryが空であるとは限らない。内容が空かどうかを知るにはdirectoryの `list` を使う。
|
||||
|
||||
File数が増えても、入口の正のdepth観測でbackendの全内容を列挙する必要はない。Client側だけで非表示にするのではなく、Hostのchildren列挙境界で内容への走査を行わないようにする。Depth境界でのfield省略と、観測済みの空のchildrenは従来どおり区別する。
|
||||
|
||||
## 内容を発見するOperation
|
||||
|
||||
FS入口と配下directoryには、次の探索Operationを提供する。これらはInterface上のOperationであり、新しいProtocol interactionや `list` という子Objectではない。
|
||||
|
||||
| Operation | 役割 |
|
||||
| --- | --- |
|
||||
| `list` | 対象directory直下のfile / directoryを列挙する |
|
||||
| `glob` | 指定した範囲とpath patternに一致する対象を探す |
|
||||
| `grep` | 指定した範囲のfile内容を検索する |
|
||||
|
||||
### list
|
||||
|
||||
- 一つのdirectoryの直接の内容を対象とする。暗黙に全subtreeを再帰展開しない。
|
||||
- Fileとdirectoryを区別でき、空directoryも対象として発見できる。
|
||||
- 各項目から、その主体のWorldspace内の正しい `entry` pathへ辿れる。
|
||||
- 大量件数はOperation結果として上限と継続を明示して扱う。部分的な一覧を全件であるかのように返さない。
|
||||
- Cursor等の継続方式、並び順、parameter名、result recordの具体形は、このdraftでは未決とする。
|
||||
|
||||
Yoiの `/checkouts` 自体にある `list` は入口一覧であり、`/checkouts/<checkout-name>` や配下directoryの `list` は内容一覧である。Operation名が同じでも対象とInterfaceが異なる。
|
||||
|
||||
### glob / grep
|
||||
|
||||
対象directoryを基準にpatternや検索範囲を解釈する。Resultsからfile / directoryへ移動する参照は `entry` 型として宣言し、表示済みのfilenameや行番号付きtextを再parseして作らない。
|
||||
|
||||
Globは条件に一致する対象を探す操作であり、directory直下の全内容を表すとは限らない。特にYoiの通常ToolsのGlobはfileのみを返し、directoryは返さず、配下を再帰的に走査し得る。`glob("*")` を `list` と同じ契約として扱わない。
|
||||
|
||||
Grepは内容検索であり、treeの名前・構造の探索ではない。Ignore、検索範囲、結果上限、context、継続等は各Operationの明示された契約に従う。
|
||||
|
||||
## Clientとの接続
|
||||
|
||||
典型的な利用は次の流れになる。これは操作の順序の例であり、Tool responseやInterface参照の具体構文を定義するものではない。
|
||||
|
||||
1. `tree("/", depth)` で `/checkouts/main` の入口を知る。
|
||||
2. `inspect("/checkouts/main")` でdirectoryの `list` / `glob` / `grep` を知る。
|
||||
3. 選んだOperationを `invoke` し、結果から `/checkouts/main/src` やfileのpathを得る。
|
||||
4. 必要なpathだけを `inspect` し、さらに探索するかfileのOperationを実行する。
|
||||
|
||||
結果のすべてのentryを即時にObjectへ展開する必要はない。AIはまずpathだけを受け取り、説明や操作が必要になったときにClientへinspectを依頼できる。
|
||||
|
||||
Clientがそれらを観測してKnown Spaceへ保持しても、rootからのtreeには混ぜない。Pathの階層関係だけからindexable edgeを生成しない。既知の内容directoryをtreeの起点として直接指定した場合も、そのObjectは表示できるが、内容をindexableな子として復活させない。
|
||||
|
||||
## 解決・権限・整合性
|
||||
|
||||
探索方式の変更で、既存の権限やファイル操作の意味を変えない。
|
||||
|
||||
- 現在の主体、root/attachment、scope、provider capabilityに基づき、一覧・検索・直接path解決・操作を実行する。
|
||||
- Hostの一覧結果には、主体へ公開できる対象だけを含める。直接path指定でも同じ境界を守る。
|
||||
- Detach、権限取消、入口名の再利用後に、古い観測から別の対象を操作しない。
|
||||
- Object identityとvalidatorは既存Protocolとproviderの実行境界で管理する。
|
||||
- Fileのread/edit/write等の具体的契約は、各投影のInterfaceに従う。本稿はbyte操作やtext操作の統一を要求しない。
|
||||
|
||||
Yoiの実装では、既存Workdir providerのList/Glob/Grep処理と権限・transport境界を共有する。Worker側でremote filesystemのホストパスを直接開く方法や、Bashの `ls` を必須にする方法へ置き換えない。
|
||||
|
||||
## 現行文書・実装との差
|
||||
|
||||
[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)のFilesystem評価例と、現行 `wip-fs-server` は、directoryの内容をchildrenとして列挙する経路を持つ。Yoiの既存 `/checkouts` 投影も、内部List処理をchildren列挙に使い、directoryの独立した `list` Operationは提供していなかった。
|
||||
|
||||
本draftは、それらの現状を承認する文書ではなく、FS内容の探索をOperationへ分離する修正方針である。Lazyなrequest-time解決と、既知pathからの直接解決は維持するが、directory listingをindexable childrenの列挙として使う部分は見直す。
|
||||
|
||||
既存ProtocolがあらゆるFS Objectのindexabilityを一律に禁止する明文を持つという意味ではない。既存のindexable条件を、任意件数の内容を持つFSへ適用する具体的方針として、このdraftでは入口で境界を固定する。
|
||||
|
||||
## 検証するシナリオ
|
||||
|
||||
- 入口直下に大量のfileを置いても、rootから深いdepthで取得するtreeの件数がfile数に比例せず、内容の全件走査も発生しない。
|
||||
- 少数のfileしかないdirectoryでも、同じ非indexable境界が維持される。
|
||||
- 深いdirectoryの正のdepth観測は `children: []` だが、そのdirectoryのlistは内容を返す。
|
||||
- Directory listがfileと空directoryを返し、結果上限と継続を扱える。
|
||||
- List/glob/grepで得たpathを直接inspect・操作できる。Bash/COMMAND権限を必要としない。
|
||||
- 非indexableな結果をClientが保持しても、treeへ混入しない。
|
||||
- Scope外、権限取消、detach、入口名再利用、stale validatorの拒否に回帰がない。
|
||||
|
||||
## 未決事項
|
||||
|
||||
- Directory listの具体的なschema、並び順、継続方式、変更中の一覧の保証。
|
||||
- Symlinkや特殊entryのlist上の種別表現、各providerのignore方針との整合。
|
||||
- 本稿を既存の動的投影例、FS実装、AI用Toolsの正本文書へどう反映するか。
|
||||
|
||||
通常Tools modeへ独立したList Toolを追加するかどうかは、directory Operationの公開とは別の適用事項であり、本稿で追加済みとは扱わない。
|
||||
@@ -0,0 +1,498 @@
|
||||
# Rust API Model
|
||||
|
||||
## Status
|
||||
|
||||
この文書は、WIP HostをRustで実装するための概念的なAPI modelを整理するdraftである。
|
||||
|
||||
このdraftで示すtrait名、attribute、derive、generated type名、method signatureは説明用であり、具体的なRust API syntaxとして確定しない。Ownership、lifetime、async runtime、error type、macro crate構成も未決定である。
|
||||
|
||||
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。動的なObject projectionの運用要件は[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)、対応するTypeScript modelは[TypeScript API Model](typescript-api-model.md)を参照する。
|
||||
|
||||
## 目的
|
||||
|
||||
Rust APIでは、次のmodelを自然に表現できるようにする。
|
||||
|
||||
1. Rust traitでWIP Interfaceを宣言する。
|
||||
2. Rust structが、Protocol Objectとして公開するmetadataを提供する。
|
||||
3. 同じstructが、必要に応じて公開する0個以上のInterface traitを実装する。
|
||||
4. Objectを返す時点で、公開するInterfaceのordered setを明示的に選択する。
|
||||
5. Trait declarationからInterface Descriptor、Value codec、validation、Operation dispatchに必要なcodeを生成する。
|
||||
6. Struct instanceをWorldspace全体について常駐させず、`observe`と`call_operation`の解決時にdata-drivenに生成できる。
|
||||
|
||||
このmodelでは、Protocol上のObjectとInterfaceの関連付けは`Object.interfaces`だけで表す。Operation implementationへ到達するためのRust内部codeはSDK / Host Runtimeの実装詳細であり、新しいProtocol conceptを導入しない。
|
||||
|
||||
## Conceptual model
|
||||
|
||||
### Interface trait
|
||||
|
||||
WIP InterfaceはRust traitとして宣言する。
|
||||
|
||||
```rust
|
||||
#[wip::interface]
|
||||
trait FileInterface {
|
||||
/// Reads bytes from the file.
|
||||
async fn read(
|
||||
&self,
|
||||
/// Byte offset from the beginning of the file.
|
||||
offset: Option<i64>,
|
||||
/// Maximum number of bytes to return.
|
||||
length: Option<i64>,
|
||||
) -> Bytes;
|
||||
|
||||
/// Replaces the file contents.
|
||||
async fn write(
|
||||
&self,
|
||||
content: Bytes,
|
||||
) -> WriteResult;
|
||||
}
|
||||
```
|
||||
|
||||
このcodeはconceptual syntaxである。`Option<i64>`の対応範囲、`Bytes` wrapper、documentationの取得方法、async methodとerrorの具体形は別途決定する。
|
||||
|
||||
Trait declarationは、次のlogical declarationに対応する。
|
||||
|
||||
- 一つのtrait declarationを、一つのInterface Descriptorを生成するRust側definitionとして扱う。
|
||||
- Trait名はgenerated Rust itemやlocal registrationを識別するために使用できるが、Protocol `InterfaceDescriptor`のfieldや構造化されたInterface参照としてそのまま公開しない。
|
||||
- Trait methodをOperation Declarationとして扱う。
|
||||
- ReceiverはProtocol parameterに含めない。
|
||||
- Method parameterを名前付きOperation parameterとして扱う。
|
||||
- Return typeをOperation return typeとして扱う。
|
||||
- Rust typeをDescriptorの`TypeExpr`とProtocol `Value`へ対応付ける。
|
||||
- Documentationをtrait、method、parameter、return、関連typeから取得できるようにする。
|
||||
|
||||
Descriptorのshapeはtrait declarationによって決まり、各structのtrait implementationによって変化しない。
|
||||
|
||||
### Object implementation
|
||||
|
||||
Rust structは、その時点でWorldspaceへ投影するObjectをHost内部で表現できる。
|
||||
|
||||
```rust
|
||||
struct File {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
reference: Option<String>,
|
||||
validator: Option<Vec<u8>>,
|
||||
}
|
||||
```
|
||||
|
||||
Structは概念上の`WipObject` traitを実装する。
|
||||
|
||||
```rust
|
||||
impl WipObject for File {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn reference(&self) -> Option<&str> {
|
||||
self.reference.as_deref()
|
||||
}
|
||||
|
||||
fn validator(&self) -> Option<&[u8]> {
|
||||
self.validator.as_deref()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`WipObject`が返す値のownershipやborrow形式は未決定である。概念的にはProtocol Objectのうち、publication時に選択するInterface集合以外の公開metadataを提供する。
|
||||
|
||||
`WipObject`がProtocol metadataとして返す値へ次を含めない。これらをimplementing structのprivate fieldとして保持することは禁止しない。
|
||||
|
||||
- Absolute path。PathはObjectのfieldではなく、Worldspace resolutionのcontextである。
|
||||
- Children。ChildrenはTree上の配置contextである。
|
||||
- 固定されたInterface集合。InterfaceはObjectを公開する時点で追加する。
|
||||
- Backend connection、file descriptor、credential等のHost-private state。
|
||||
|
||||
Structが保持する`path`やbackend handle等はHost内部のOperation implementationに使用できるが、`WipObject`のmetadataまたはProtocol Objectへ自動的にserializeしない。
|
||||
|
||||
### Interface implementation
|
||||
|
||||
Object implementationとして使用するstructは、公開したいInterface traitを実装する。
|
||||
|
||||
```rust
|
||||
impl FileInterface for File {
|
||||
async fn read(
|
||||
&self,
|
||||
offset: Option<i64>,
|
||||
length: Option<i64>,
|
||||
) -> Bytes {
|
||||
// self.pathが示す現在のfileを読む
|
||||
}
|
||||
|
||||
async fn write(
|
||||
&self,
|
||||
content: Bytes,
|
||||
) -> WriteResult {
|
||||
// self.pathが示す現在のfileへ書く
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Trait implementationはOperationのbehaviorを提供する。Interface Descriptorはtrait implementationから生成せず、Interface trait declarationから生成する。
|
||||
|
||||
Rustには、あるtypeが実装しているtraitを列挙する一般的なreflectionがない。また、一つのstructが複数のInterface traitを実装し得る。そのため、trait implementationが存在するだけでInterfaceを暗黙に公開しない。
|
||||
|
||||
### Object publication
|
||||
|
||||
ObjectをWorldspaceへ返す時点で、struct instanceと公開する0個以上のInterface Declarationを明示的に組み合わせる。Interfaceは追加順を保持するordered setであり、同じDefinitionを重複して追加しない。
|
||||
|
||||
Conceptual syntaxは次のようになる。
|
||||
|
||||
```rust
|
||||
let file = File::resolve(path).await?;
|
||||
let object = PublishedObject::new(file)
|
||||
.expose(FileInterfaceDefinition);
|
||||
```
|
||||
|
||||
ここで各`InterfaceDefinition`は、`#[wip::interface]`が生成するconceptualなcompanion definitionである。実際の名前や、associated item、module、valueのどの形で生成するかは未決定である。
|
||||
|
||||
Publication APIは、追加する各Definitionについて概念的に次のboundを要求する。
|
||||
|
||||
```rust
|
||||
impl<T: WipObject> PublishedObject<T> {
|
||||
fn expose<I>(self, interface: I) -> Self
|
||||
where
|
||||
I: InterfaceDefinition<T>;
|
||||
}
|
||||
|
||||
impl<T: FileInterface> InterfaceDefinition<T>
|
||||
for FileInterfaceDefinition
|
||||
{
|
||||
// generated registration and dispatch metadata
|
||||
}
|
||||
```
|
||||
|
||||
このboundにより、Objectへ追加する各Interfaceについて、公開する値がObject metadataとOperation implementationの両方を提供することをcompile timeに確認できる。どのInterfaceも追加しないObjectも表現できる。
|
||||
|
||||
`PublishedObject`はHost SDK内のconceptual typeであり、Protocol typeとして追加しない。Host Runtimeはこれを使用して次を構築する。
|
||||
|
||||
```rust
|
||||
Object {
|
||||
name: object.name(),
|
||||
description: object.description(),
|
||||
interfaces: selected_interface_references,
|
||||
r#ref: object.reference(),
|
||||
validator: object.validator(),
|
||||
}
|
||||
```
|
||||
|
||||
`selected_interface_references`は、Hostが各generated Interface Definitionを登録または公開するときに割り当てる構造化参照`{ scope, name }`を、publication APIへの追加順で並べたものである。同じ`(scope, name)`の重複は拒否し、Objectの配置pathに対する祖先制約も検証する。Macroが参照をcompile timeに固定することは要求しない。
|
||||
|
||||
`observe`はDescriptor自体をObjectへ埋め込まず、このInterface参照のordered setだけを返す。Clientが各参照を`fetch_interface`したとき、Host Runtimeはtrait declarationから生成されたDescriptorと、存在する場合はInterface validatorを返す。同じ参照とvalidatorの組のDescriptorはHost上で共有およびcacheできるため、Object instanceごとにDescriptorを再生成または再送する必要はない。
|
||||
|
||||
## Macroが生成する責務
|
||||
|
||||
`#[wip::interface]`は、conceptualには次を生成または提供する。
|
||||
|
||||
- Interface Descriptorを構築するdefinition。
|
||||
- Trait methodとOperation Declarationの対応情報。
|
||||
- ArgumentsのProtocol `Value` decoder。
|
||||
- ResultのProtocol `Value` encoder。
|
||||
- Argumentsとresultのruntime validation。
|
||||
- Operation名から対応するtrait methodを呼び出すdispatch code。
|
||||
- 任意の`T: WipObject + InterfaceTrait`について、そのInterfaceを`PublishedObject<T>`へ追加するための型付きentry point。
|
||||
- HostがInterface参照、optional validator、Descriptorを登録、取得、cacheするためのidentity。
|
||||
|
||||
MacroがRust codebase内のtrait implementationを探索または列挙することは要求しない。Publication時のgeneric boundによって、対象structがInterface traitを実装していることを確認する。
|
||||
|
||||
Generated dispatch codeはRust SDK内部の実装詳細である。Host内部のdispatch mechanismやRust type名等をProtocolへ公開しない。
|
||||
|
||||
## Rust typeとProtocol type
|
||||
|
||||
Interface traitで使用できるRust typeは、Descriptorの`TypeExpr`とProtocol `Value`へ対応付けられなければならない。
|
||||
|
||||
Conceptualにはderiveまたは明示的なcodec implementationを使用する。
|
||||
|
||||
```rust
|
||||
#[derive(WipType)]
|
||||
struct WriteResult {
|
||||
bytes_written: i64,
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
#[derive(WipType)]
|
||||
enum ReadResult {
|
||||
Data { content: Bytes },
|
||||
EndOfFile,
|
||||
}
|
||||
```
|
||||
|
||||
このsyntaxも未決定である。少なくとも次を表現できる必要がある。
|
||||
|
||||
- Unit、Boolean、Integer、Number、String、Bytes、Json、Entry。
|
||||
- Record、List、Enum、Union。
|
||||
- Descriptor内のNamed type。
|
||||
- Requiredとoptionalなfieldおよびparameter。
|
||||
- Documentation。
|
||||
|
||||
Rustの`Option<T>`をoptional field / parameter以外で許可するか、enumをProtocol EnumまたはUnionへどう対応付けるか、newtypeをどのように扱うかは未決定である。
|
||||
|
||||
Rust SDKは、integerのProtocol相互運用範囲、finite number、unknown field、Union shape、Entry path等をProtocol boundaryで検証しなければならない。
|
||||
|
||||
## Dynamic Worldspaceでのlifecycle
|
||||
|
||||
### `observe`
|
||||
|
||||
Projection implementationはrequest時にbackend dataを解決し、struct instanceを生成する。
|
||||
|
||||
```rust
|
||||
async fn resolve_file(path: &WipPath) -> Result<PublishedObject> {
|
||||
let stat = filesystem.stat(path).await?;
|
||||
|
||||
let file = File {
|
||||
name: stat.name,
|
||||
path: stat.path,
|
||||
reference: make_reference(&stat),
|
||||
validator: make_validator(&stat),
|
||||
};
|
||||
|
||||
Ok(PublishedObject::new(file)
|
||||
.expose(FileInterfaceDefinition))
|
||||
}
|
||||
```
|
||||
|
||||
Host Runtimeは`PublishedObject`からProtocol Objectを返す。Response生成後にこの`File` instanceを破棄してよく、Worldspace内の全fileに対応するinstanceを保持しない。
|
||||
|
||||
Requested depthが正の場合、Directory listingで発見した各entryについて、その時点のstruct instanceを生成して公開できる。Providerがdirectory全体を先に収集してから`Vec`として返すことは要求しないが、一つのparentのobservationを成功させるには公開されたindexable childrenをclean exhaustionまで列挙しなければならない。
|
||||
|
||||
```rust
|
||||
let mut entries = filesystem.discover_children(path, budget).await?;
|
||||
|
||||
while let Some(entry) = entries.next().await? {
|
||||
let Some(child) = project_current_object(
|
||||
entry.path(),
|
||||
context,
|
||||
).await? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
observation.push_child(child)?;
|
||||
}
|
||||
```
|
||||
|
||||
`discover_children`、`budget`、`observation`はconceptualであり、具体APIではない。必要なsemanticsは次である。
|
||||
|
||||
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
|
||||
- 公開されたindexable childrenをすべて列挙し、clean exhaustionで終了する。
|
||||
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なsuccess responseを返さない。
|
||||
- Requested depthに達したnodeではchild discoveryを行わず、`children`を未観測として省略する。
|
||||
- 異なるconcrete published typeを、Providerから共通のreturn typeとして扱える。
|
||||
|
||||
具体的なreturn type、internal streaming、backpressure、type erasure、error propagationは未決定である。
|
||||
|
||||
### `call_operation`
|
||||
|
||||
Hostはcall時に、requestのTargetと`InterfaceTarget`をHost Runtimeへ渡す。Runtimeは一つのatomic execution boundaryへ入ってから`target.path`を現在のObject implementationとstableなbackend対象へ解決する。
|
||||
|
||||
```rust
|
||||
runtime.call_current(
|
||||
target,
|
||||
interface,
|
||||
operation,
|
||||
arguments,
|
||||
context,
|
||||
|path, context| resolve_current_projection(path, context),
|
||||
).await
|
||||
```
|
||||
|
||||
`resolve_current_projection`と`runtime.call_current`というAPIを採用することは決定しない。ResolverはRuntimeが開始したexecution boundary内で呼ばれ、返す値はそのboundary中にObject identityとbackend対象を安定して保持するhandleまたはtransaction contextを含む必要がある。このexampleが示す要件は次である。
|
||||
|
||||
- 過去の`observe`で作成したstruct instanceを保持していなくてもOperationを実行できる。
|
||||
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
|
||||
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれ、self-scopeを含む祖先制約を満たすことを確認する。指定された`interface.scope_ref`と現在のscope Objectの`ref`も照合し、満たせない場合は`InterfaceMismatch`を返す。`scope_ref`の省略だけでは拒否しない。
|
||||
- 現在のInterfaceにOperationが宣言されていなければ`OperationNotFound`、Operationを公開しつつ実行だけを拒否する場合は`PermissionDenied`を返す。
|
||||
- `interface.validator`を現在のInterface validatorへ照合する。Mutable Interfaceにvalidatorがなければ`InterfaceValidatorRequired`、不一致なら`InterfaceValidatorMismatch`を返す。
|
||||
- 現在のProjected Object、Object validator、選択されたInterfaceとそのvalidator、Descriptor、Operation、Rust側のObject implementation、実際のbackend対象を一つのexecution boundary内で一致させる。
|
||||
- Object validatorが指定されている場合は、現在のObject identityと保護対象状態の両方へ照合し、同じexecution boundary内でOperationを実行する。
|
||||
- Pathが別Objectを指す場合は、provider-local revisionや内容が同じでもObject validatorを一致させない。
|
||||
- Providerのvalidatorがobject-localなrevisionだけの場合、HostはObject identityを含む形へnamespace化またはwrapできなければならない。
|
||||
- Object validatorが省略され、Operationがvalidatorを要求する場合は、実行せず`ValidatorRequired`を返す。
|
||||
- Object validatorなしの実行をOperationが許可する場合は、実行時にpathが指す現在Objectへ無条件に実行する。
|
||||
- Macro-generated dispatchが、選択されたInterface traitのmethodを現在のstruct instanceに対して呼び出す。異なるInterfaceの同名Operationをmergeまたは曖昧に解決しない。
|
||||
- Operation実行後の現在状態に対応するvalidatorを、必要に応じて`CallOperationResponse`へ返せる。
|
||||
|
||||
Object validatorをObject identityへ結び付けるためにClient-facingな`ref`を要求しない。ProviderまたはHostはHost-privateなidentityを使用できるが、そのidentity confirmation、Object validator比較、Interface membershipとvalidator比較、DescriptorとOperation implementationの固定、authorization、Operation実行を一つのatomic boundaryで行う。
|
||||
|
||||
Operation実行後validatorを、同じstruct instanceから再計算するか、Operation resultとともに返すか、backendを再観測するかは未決定である。どの方式でも、resultを生成した実行後状態とvalidatorを対応させなければならない。
|
||||
|
||||
Structが`PathBuf`だけを保持し、validator比較後にpathを無条件で開き直す実装は、対象差し替えに対するatomicityを保証できない場合がある。Rust APIは、file descriptor、transaction、conditional update、lock等を利用してbackend固有のatomicityを実現できる必要があるが、その抽象化は未決定である。
|
||||
|
||||
## 複数Interfaceの実装
|
||||
|
||||
一つのstructは複数のInterface traitを実装できる。
|
||||
|
||||
```rust
|
||||
impl ReadOnlyFileInterface for File {
|
||||
// ...
|
||||
}
|
||||
|
||||
impl WritableFileInterface for File {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Publication時に、現在の主体へ公開する0個以上のInterfaceを選択し、表示したい順で追加する。
|
||||
|
||||
```rust
|
||||
let mut object = PublishedObject::new(file)
|
||||
.expose(ReadOnlyFileInterfaceDefinition);
|
||||
|
||||
if authorization.can_write() {
|
||||
object = object.expose(WritableFileInterfaceDefinition);
|
||||
}
|
||||
|
||||
object
|
||||
```
|
||||
|
||||
Protocol Objectの`interfaces`は追加順を保持するordered setである。同じInterface参照の重複は拒否する。各Interface Descriptorは独立したOperation集合を宣言し、複数trait implementationを一つのDescriptorへ暗黙にmergeしない。
|
||||
|
||||
異なるInterfaceは同じOperation名を宣言できる。Dispatchはrequestの`InterfaceTarget.reference`とOperation名の組で一意に決まり、表示上のqualified表記は[Text Viewの参照表記](../4.2-text-view.md#参照表記)に従う。Signature全体をProtocol request fieldへparseし直さない。
|
||||
|
||||
Rust traitのsupertrait、trait alias、複数traitの組み合わせをDescriptor生成へどう対応付けるかは未決定であり、Protocol上のInterface inheritanceを暗黙に導入しない。
|
||||
|
||||
## 固定Interface向けのsugar
|
||||
|
||||
一つのRust typeを常に同じInterface集合で公開する場合、次のようなsugarを追加してよい。
|
||||
|
||||
```rust
|
||||
#[wip::object(interfaces = [ReadOnlyFileInterface, WritableFileInterface])]
|
||||
struct File {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
または、conceptualには`WipObject`のassociated itemとしてdefault Interface集合を指定できる。
|
||||
|
||||
ただし、固定Interface集合をcore modelにはしない。主体、policy、resource kind等に基づいて同じstructへ異なるInterface subsetを追加できるexplicit publication modelを基礎とする。
|
||||
|
||||
Sugarを使用する場合も、対象typeが指定されたすべてのInterface traitを実装していることと、同じInterfaceを重複していないことを確認しなければならない。
|
||||
|
||||
## HostへのInterface登録
|
||||
|
||||
Generated Interface DefinitionをHostへ登録し、構造化されたInterface参照を割り当てる必要がある。
|
||||
|
||||
参照の絶対scopeとlocal name、および配置pathへの祖先制約は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。以下のconceptual API例はscope / nameを渡す具体構文を省略しており、暗黙のroot scopeを定めるものではない。登録時のcontextと引数の具体APIは引き続き未決定である。Self-scopeは許可され、scopeはそのpathのObjectに紐づく。Scope Objectのoptionalな`ref`はProtocolの`scope_ref`として観測・検証し、`ref`がない場合のidentity追跡不能も許容する。Scopeの移動追跡や独立したscope validatorを導入しない。
|
||||
|
||||
Conceptualには、eager registrationとlazy registrationの両方が考えられる。
|
||||
|
||||
```rust
|
||||
let file_read_interface = host.register_interface(ReadOnlyFileInterfaceDefinition)?;
|
||||
let file_write_interface = host.register_interface(WritableFileInterfaceDefinition)?;
|
||||
let object = PublishedObject::new(file)
|
||||
.expose(file_read_interface)
|
||||
.expose(file_write_interface);
|
||||
```
|
||||
|
||||
または、Hostが最初のpublication時に各definitionをinternしてよい。
|
||||
|
||||
どちらの場合も次を守る。
|
||||
|
||||
- MutableなInterface参照では`fetch_interface`がvalidatorを返し、同じ参照とvalidatorの組から異なるDescriptorを返さない。
|
||||
- Validatorを返さないInterface参照は、その参照の有効期間中Descriptorをimmutableにする。
|
||||
- Descriptorを更新する場合は同じ参照へ新しいvalidatorを発行するか、新しいInterface参照を使用する。
|
||||
- Objectの`interfaces`へ設定した各Interface参照のDescriptorを、その主体から`fetch_interface`できる。
|
||||
- 一つのObjectへ同じInterface参照を重複して追加しない。
|
||||
- 構造的に同じDescriptorを同じ参照へdeduplicateすることは要求しない。
|
||||
- Rust type名やprocess-local addressを、そのままportableなProtocol semanticsとして扱わない。
|
||||
|
||||
## Request contextとauthorization
|
||||
|
||||
Operation implementationは、Object instance以外にrequest contextを必要とし得る。
|
||||
|
||||
- 現在の主体。
|
||||
- Cancellationとdeadline。
|
||||
- Tracing / audit context。
|
||||
- Host serviceやbackend connectionへのaccess。
|
||||
- Resource limit。
|
||||
|
||||
これらをmethod parameter、receiver wrapper、generated context、task-local stateのどれで渡すかは未決定である。Protocol parameterとして宣言された値と、Hostが注入するcontextを明確に区別しなければならない。
|
||||
|
||||
Object publication時のInterface集合の選択と、Operation call時のmembership、Interface validator、authorizationの再評価を実装できる必要がある。以前の`observe`で公開されたInterface集合だけを根拠に、現在のOperation実行を許可してはならない。
|
||||
|
||||
Operation resultのRecord、List、Union等へnestedしたすべての`Entry`位置について、Hostは現在の主体にそのpathを認識させてよいことを再帰的に確認できなければならない。通常のstructural Value validationだけではこのvisibility checkを満たさない。Macro-generated code、Projection、Host Runtimeのどこで確認するかは未決定である。
|
||||
|
||||
Operation argumentとして`Entry` pathを受け取った場合も、pathの所持をauthorizationの証明として扱わず、参照先へ作用する時点で現在の主体について認可する。
|
||||
|
||||
## Error model
|
||||
|
||||
Rust methodのerror表現は、次の四種類を区別できなければならない。
|
||||
|
||||
- Protocol validationまたはdispatch failure。
|
||||
- Effectがcommitされていないと保証できるHost / backend implementation failure。
|
||||
- Operationの未実行またはrollbackを保証できない`OperationOutcomeUnknown`。
|
||||
- Interfaceがreturn typeとして宣言したdomain failure。
|
||||
|
||||
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Rustの`Result<T, E>`をdomain Union、Host error、または両方のどれへ対応付けるかは未決定である。
|
||||
|
||||
MacroがRust panic、result encode failure、宣言型に違反するimplementation resultをdomain failureへ自動変換してはならない。Host Runtimeは、effectがcommitされていないと保証できる場合だけ`Internal`へmappingし、保証できない場合は`OperationOutcomeUnknown`へmappingする。Clientへ送信済みか不明なtransport切断も自動retryしない。
|
||||
|
||||
## 要件
|
||||
|
||||
Rust API modelは少なくとも次を満たす。
|
||||
|
||||
- Interface Descriptorをtrait declarationから生成し、trait implementationごとに変化させない。
|
||||
- Structが`WipObject`と、追加する各Interface traitを実装していることをcompile timeに確認する。
|
||||
- 0個以上のInterfaceをpublication時に明示し、実装済みtraitの自動列挙に依存しない。
|
||||
- Objectのpath、children、Interface集合をstructへ恒久的に固定しない。
|
||||
- Struct instanceをrequest scopeで生成および破棄できる。
|
||||
- 全Object instanceの事前登録を要求しない。
|
||||
- Direct childrenを現在の主体についてprojectし、非公開Objectとそのsubtreeを除外しつつlazyに発見し、success responseではclean exhaustionまで列挙する。
|
||||
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なobservationをsuccess responseとして返さない。
|
||||
- 同じstructへ複数のInterfaceを同時に追加でき、追加順を保持し、重複を拒否できる。
|
||||
- 異なるInterfaceの同名Operationを、Interface参照とOperation名の組でdispatchできる。
|
||||
- Protocol boundaryでarguments、result、Object、Descriptorを検証する。
|
||||
- Operation result内のnestedな`Entry`へ、現在の主体についてvisibility checkを適用できる。
|
||||
- Host-private fieldをProtocol ObjectまたはValueへ暗黙にserializeしない。
|
||||
- Call時の現在の主体、Object visibility、Object validator、Interface membershipとvalidator、Descriptor、Operation、Rust側implementation、backend対象を一貫したexecution boundaryで扱う。
|
||||
- 非公開Object、Interface非membership、非公開Operation、公開済みOperationの実行拒否をそれぞれ`NotFound`、`InterfaceMismatch`、`OperationNotFound`、`PermissionDenied`へ対応付ける。
|
||||
- Interface validatorの必須・不一致を`InterfaceValidatorRequired`、`InterfaceValidatorMismatch`へ対応付ける。
|
||||
- Object validatorを現在のObject identityと保護対象状態へ結び付け、別Objectに同じprovider-local revisionがあっても一致させない。
|
||||
- Object validator比較、identity confirmation、Interface membershipとvalidator比較、DescriptorとOperation実装の固定、authorization、Operation実行を同じatomic boundaryで行う。
|
||||
- Object validatorを要求するOperationへの省略を`ValidatorRequired`として拒否できる。
|
||||
- Provider、Host Runtime、macro-generated codeのerrorを、effect未commitを保証できる`Internal`と、保証できない`OperationOutcomeUnknown`へ安全に対応付けられる。
|
||||
- InvalidなObject、Descriptor、Operation resultをsuccess responseとして送信せず、Host-side validation failureをdomain failureへ変換しない。
|
||||
- Rust SDK固有の型やlifetimeをProtocol semanticsとして要求しない。
|
||||
|
||||
## 非目標
|
||||
|
||||
このdraftは次を決定しない。
|
||||
|
||||
- 最終的なcrate、module、trait、attribute、deriveの名前。
|
||||
- Proc macroの内部実装。
|
||||
- 特定のasync runtime。
|
||||
- Object instanceの常駐registryまたはactor model。
|
||||
- Rust trait objectをwireへ公開すること。
|
||||
- Rust trait inheritanceをProtocol Interface inheritanceとして扱うこと。
|
||||
- Backend entityとRust structまたはProtocol Objectの一対一対応。
|
||||
- Client SDKのtyped proxy API。
|
||||
|
||||
## 未決定事項
|
||||
|
||||
- `WipObject`の具体的なmethodとmetadata ownership。
|
||||
- Object metadata取得をsyncまたはasyncのどちらにするか。
|
||||
- Publication APIがObject instanceをown、borrow、`Arc`化するか。
|
||||
- Generated Interface Definitionの具体的なRust表現。
|
||||
- Interface登録とscope / local name割当のlifecycle。
|
||||
- Trait、Operation、parameter名のrename規則。
|
||||
- Rust doc commentとProtocol Documentationの対応。
|
||||
- Supported Rust typeと`TypeExpr`の対応。
|
||||
- `Option<T>`を許可する位置。
|
||||
- Generic method、associated type、lifetime parameterをInterface traitで許可するか。
|
||||
- `&self`、`&mut self`、owned receiverのどれをOperationで許可するか。
|
||||
- Async methodをnative syntax、generated boxed future、別macroのどれで扱うか。
|
||||
- Request contextとProtocol parameterのAPI上の分離。
|
||||
- Nestedな`Entry` resultのvisibility checkをMacro-generated code、Projection、Host Runtimeのどこで行うか。
|
||||
- Domain failure、Host error、panicのRust上の表現。
|
||||
- Macro-generated dispatchとHost Runtimeの責務境界。
|
||||
- Published ObjectをProviderから返す際のtype erasure。
|
||||
- Validator比較とOperation実行を囲むbackend-specificなatomicity abstraction。
|
||||
- Provider-local validatorをObject identityへnamespace化またはwrapするHost API。
|
||||
- Operation実行後validatorをObject instance、Operation implementation、Providerのどこから取得するか。
|
||||
- 主体ごとに異なるInterface combinationを追加したPublished Objectのconcrete return typeをどう統一するか。
|
||||
- Rust client APIを同じtrait declarationから生成するか。
|
||||
|
||||
これらを決定する際は、静的service objectだけでなく、Filesystem、database row、virtual aggregate、主体別Interface compositionを同じAPIで実装できるか評価する。
|
||||
@@ -0,0 +1,701 @@
|
||||
# TypeScript API Model
|
||||
|
||||
## Status
|
||||
|
||||
この文書は、WIP HostをTypeScriptで実装するための概念的なAPI modelを整理するdraftである。
|
||||
|
||||
このdraftで示すfunction、object shape、generic type、class、method signatureは説明用であり、具体的なTypeScript API syntaxとして確定しない。Package構成、schema builderの最終名称、ownership、async context、error type、bundler integrationも未決定である。
|
||||
|
||||
Protocol上の確定事項は[Core Model](../2-core-model.md)、[Object tree / Schema](../2.1-object-tree-schema.md)、[Operation and Interface](../2.2-operation-and-interface.md)、[Protocol](../3-protocol.md)を参照する。動的なObject projectionの運用要件は[Dynamic Worldspace Projection](dynamic-worldspace-projection.md)、対応するRust modelは[Rust API Model](rust-api-model.md)を参照する。
|
||||
|
||||
## 目的
|
||||
|
||||
TypeScript APIでは、次のmodelを自然に表現できるようにする。
|
||||
|
||||
1. Runtimeに残るdefinition objectでWIP Interfaceを宣言する。
|
||||
2. 同じdefinition objectからInterface Descriptor、Value codec、validationを構築する。
|
||||
3. Definition objectからOperation implementationのTypeScript型を推論する。
|
||||
4. Classまたはplain objectが、Protocol Objectとして公開するmetadataと、必要に応じて0個以上のInterfaceのOperation implementationを提供する。
|
||||
5. Objectを返す時点で、公開するInterfaceのordered setを明示的に選択する。
|
||||
6. Object instanceをWorldspace全体について常駐させず、`observe`と`call_operation`の解決時にdata-drivenに生成できる。
|
||||
7. Custom transpiler、TypeScript compiler plugin、decorator metadataをcore APIの前提にしない。
|
||||
|
||||
このmodelでは、Protocol上のObjectとInterfaceの関連付けは`Object.interfaces`だけで表す。Operation implementationへ到達するためのTypeScript / JavaScript内部codeはSDK / Host Runtimeの実装詳細であり、新しいProtocol conceptを導入しない。
|
||||
|
||||
## TypeScriptの型消去
|
||||
|
||||
TypeScriptの`interface`、`type`、generic type argumentは、通常のJavaScript outputには残らない。
|
||||
|
||||
```ts
|
||||
interface FileOperations {
|
||||
read(args: ReadArguments): Promise<Uint8Array>;
|
||||
}
|
||||
```
|
||||
|
||||
このdeclarationだけから、runtimeにInterface Descriptorを取得することはできない。
|
||||
|
||||
```ts
|
||||
host.registerInterface(FileOperations); // runtime valueではない
|
||||
```
|
||||
|
||||
そのため、TypeScript APIではruntime valueをsource of truthにする。
|
||||
|
||||
```ts
|
||||
const FileInterface = wip.interface({
|
||||
// runtime schema
|
||||
});
|
||||
```
|
||||
|
||||
このobjectは通常のJavaScript valueとしてtranspile後も残る。TypeScript型は、このruntime definitionから推論する。
|
||||
|
||||
## Conceptual model
|
||||
|
||||
### Type definition
|
||||
|
||||
Record、Enum、Union等もruntime definition objectとして宣言する。
|
||||
|
||||
```ts
|
||||
const WriteResult = wip.union({
|
||||
name: "WriteResult",
|
||||
|
||||
cases: {
|
||||
written: wip.record({
|
||||
bytesWritten: wip.integer(),
|
||||
}),
|
||||
|
||||
rejected: wip.record({
|
||||
reason: wip.string(),
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
TypeScript型はdefinitionから推論する。
|
||||
|
||||
```ts
|
||||
type WriteResult = wip.Infer<typeof WriteResult>;
|
||||
```
|
||||
|
||||
`wip.Infer`の具体的な型実装と、Union DTOのTypeScript representationは未決定である。Runtime definitionと推論された型が、同じProtocol `TypeExpr`および`Value` semanticsを表すことが要件である。
|
||||
|
||||
### Interface definition
|
||||
|
||||
WIP Interfaceはruntime definition objectとして宣言する。
|
||||
|
||||
```ts
|
||||
const FileInterface = wip.interface({
|
||||
documentation: {
|
||||
summary: "Operations available on a file",
|
||||
},
|
||||
|
||||
types: {
|
||||
WriteResult,
|
||||
},
|
||||
|
||||
operations: {
|
||||
read: wip.operation({
|
||||
name: "read",
|
||||
|
||||
documentation: {
|
||||
summary: "Reads bytes from the file",
|
||||
},
|
||||
|
||||
parameters: {
|
||||
offset: wip.optional(wip.integer()),
|
||||
length: wip.optional(wip.integer()),
|
||||
},
|
||||
|
||||
returns: wip.bytes(),
|
||||
}),
|
||||
|
||||
write: wip.operation({
|
||||
name: "write",
|
||||
|
||||
documentation: {
|
||||
summary: "Replaces the file contents",
|
||||
},
|
||||
|
||||
parameters: {
|
||||
content: wip.bytes(),
|
||||
},
|
||||
|
||||
returns: WriteResult,
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
このsyntaxはconceptualである。Definition objectのproperty名、documentationの指定方法、Named typeの参照方法、`optional`の表現は未決定である。
|
||||
|
||||
Interface definitionは、次のlogical declarationに対応する。
|
||||
|
||||
- 一つのruntime definition objectを、一つのInterface Descriptorを構築するTypeScript側definitionとして扱う。
|
||||
- JavaScript variable名`FileInterface`はlocal code上の識別子であり、Protocol `InterfaceDescriptor`のfieldや構造化されたInterface参照としてそのまま公開しない。
|
||||
- 各Operation definitionが保持するstableなstringをOperation Declarationの`name`として扱う。
|
||||
- `parameters`で宣言されたstableなstringを名前付きOperation parameterとして扱う。
|
||||
- `returns`をOperation return typeとして扱う。
|
||||
- Runtime type definitionをDescriptorの`TypeExpr`とProtocol `Value`へ対応付ける。
|
||||
- DocumentationをInterface、Operation、parameter、return、type、field、caseへ指定できるようにする。
|
||||
|
||||
Descriptorのshapeはruntime definition objectによって決まり、各classまたはplain objectのimplementationによって変化しない。
|
||||
|
||||
Operation、parameter、type、field、caseのProtocol名はfinalized definition内のstableなstring dataとして保持する。JavaScript variable名、constructor名、functionの`.name`、prototype上のmethod列挙から推測しない。Hostはincoming Operation名をfinalized dispatch table上のinternal tokenまたはaccessorへ解決し、Clientから受け取った名前をそのままobject property lookupへ使用しない。Object-literal keyをauthoring sugarとして使う場合にproperty manglingからどう保護するかは未決定である。
|
||||
|
||||
Definition objectはHostへの登録時に一つのimmutableなartifactとしてfinalizeする。MutableなProtocol Interface参照を提供する場合は、同じ参照へ新しいvalidatorと新しいfinalized artifactを対応付け、同じ参照とvalidatorの組から異なるDescriptorを返してはならない。
|
||||
|
||||
### Operation implementation type
|
||||
|
||||
Interface definitionから、必要なOperation implementationのTypeScript型を推論する。
|
||||
|
||||
```ts
|
||||
type FileOperations =
|
||||
wip.Implementation<typeof FileInterface>;
|
||||
```
|
||||
|
||||
Conceptualには、次のようなobject typeへ展開される。
|
||||
|
||||
```ts
|
||||
type FileOperations = {
|
||||
readonly read: (args: {
|
||||
offset?: number;
|
||||
length?: number;
|
||||
}) => Promise<Uint8Array>;
|
||||
|
||||
readonly write: (args: {
|
||||
content: Uint8Array;
|
||||
}) => Promise<WriteResult>;
|
||||
};
|
||||
```
|
||||
|
||||
Function-valued propertyを基本形とし、`strictFunctionTypes`を含むstrictなcompiler settingsの下でparameterをcontravariantに検査できるようにする。通常のclass method parameterはTypeScriptのassignabilityでbivariantに扱われる場合があるため、method-shapedな`implements`だけを「すべてのschema-valid argumentを安全に受け取れる」ことの完全な証明としては扱わない。
|
||||
|
||||
Definition-awareなbuilderでimplementationをcontextual typingする形も考えられる。
|
||||
|
||||
```ts
|
||||
const operations = FileInterface.implement({
|
||||
read: async (args) => {
|
||||
// argsはruntime definitionから推論される
|
||||
},
|
||||
|
||||
write: async (args) => {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`implement`の採用は未決定である。どの形でもruntime validationとcallable checkをauthorityとし、TypeScript型はdeveloper feedbackとして扱う。
|
||||
|
||||
このfunction property shapeは説明用である。Operation parameterを一つのarguments objectとして渡すか、request contextを別parameterにするか、sync returnを許可するかは未決定である。
|
||||
|
||||
### Object implementation
|
||||
|
||||
Classは、その時点でWorldspaceへ投影するObjectをHost内部で表現できる。
|
||||
|
||||
```ts
|
||||
class File implements WipObject, FileOperations {
|
||||
readonly #path: string;
|
||||
readonly #objectMetadata: WipObjectMetadata;
|
||||
|
||||
constructor(
|
||||
path: string,
|
||||
objectMetadata: WipObjectMetadata,
|
||||
) {
|
||||
this.#path = path;
|
||||
this.#objectMetadata = objectMetadata;
|
||||
}
|
||||
|
||||
metadata(): WipObjectMetadata {
|
||||
return this.#objectMetadata;
|
||||
}
|
||||
|
||||
readonly read: FileOperations["read"] = async (args) => {
|
||||
return filesystem.read(this.#path, args);
|
||||
};
|
||||
|
||||
readonly write: FileOperations["write"] = async (args) => {
|
||||
return filesystem.write(this.#path, args.content);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`WipObject`と`WipObjectMetadata`の具体的なshapeは未決定である。概念的にはProtocol Objectのうち、publication時に選択するInterface集合以外の公開metadataを提供する。
|
||||
|
||||
```ts
|
||||
interface WipObject {
|
||||
metadata(): WipObjectMetadata;
|
||||
}
|
||||
|
||||
interface WipObjectMetadata {
|
||||
name: string;
|
||||
description?: string;
|
||||
ref?: string;
|
||||
validator?: Uint8Array;
|
||||
}
|
||||
```
|
||||
|
||||
`WipObjectMetadata`へ次を含めない。これらをimplementing classのprivate fieldとして保持することは禁止しない。
|
||||
|
||||
- Absolute path。PathはObjectのfieldではなく、Worldspace resolutionのcontextである。
|
||||
- Children。ChildrenはTree上の配置contextである。
|
||||
- 固定されたInterface集合。InterfaceはObjectを公開する時点で追加する。
|
||||
- Backend connection、file descriptor、credential等のHost-private state。
|
||||
|
||||
Classが保持するpathやbackend handle等をOperation implementationに使用してよいが、Object metadataまたはProtocol Objectへ自動的にserializeしない。
|
||||
|
||||
Classの使用は必須ではない。Structural typingを利用してplain objectも使用できる。
|
||||
|
||||
```ts
|
||||
const file = {
|
||||
metadata() {
|
||||
return objectMetadata;
|
||||
},
|
||||
|
||||
read: async (args) => {
|
||||
return filesystem.read(path, args);
|
||||
},
|
||||
|
||||
write: async (args) => {
|
||||
return filesystem.write(path, args.content);
|
||||
},
|
||||
} satisfies WipObject & FileOperations;
|
||||
```
|
||||
|
||||
### Object publication
|
||||
|
||||
ObjectをWorldspaceへ返す時点で、object instanceと公開する0個以上のInterface definitionを明示的に組み合わせる。Interfaceは追加順を保持するordered setであり、同じdefinitionまたは同じ登録済み参照を重複して追加しない。
|
||||
|
||||
```ts
|
||||
const file = new File(path, objectMetadata);
|
||||
const object = wip.publish(file)
|
||||
.expose(FileInterface, file);
|
||||
```
|
||||
|
||||
Publication APIはconceptualに、追加する各definitionと、そのImplementation型を満たすbindingを受け取る。
|
||||
|
||||
```ts
|
||||
interface WipInterfaceDefinition<Implementation extends object> {
|
||||
// runtime descriptor, codec, and dispatch artifact
|
||||
}
|
||||
|
||||
interface PublishedObject<T extends WipObject> {
|
||||
expose<Implementation extends object>(
|
||||
definition: WipInterfaceDefinition<Implementation>,
|
||||
implementation: Implementation,
|
||||
): PublishedObject<T>;
|
||||
}
|
||||
```
|
||||
|
||||
実際のTypeScript generic表現は未決定である。このconstraintは、strictなcompiler settingsとfunction-valued property shapeを使用する場合に、追加する各Interfaceへbindした値が必要なOperation functionを提供するか検査する。Object instance自身をimplementationとして渡しても、Interface専用のadapter objectを渡してもよく、どのInterfaceも追加しないObjectも表現できる。通常のclass methodやtype assertion等で検査を弱められるため、各Interfaceを追加する時点とdispatch時のruntime callable check、およびValue validationは常に必要である。
|
||||
|
||||
Host Runtimeは各bindingを登録済みInterface参照と対応付ける。異なるInterfaceが同名Operationを宣言しても、選択されたInterface参照に対応するbindingだけをdispatch対象とし、単一object上の同名propertyへmergeしない。
|
||||
|
||||
`PublishedObject`はHost SDK内のconceptual typeであり、Protocol typeとして追加しない。Host Runtimeはこれを使用して次を構築する。
|
||||
|
||||
```ts
|
||||
const protocolObject = {
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
interfaces: selectedInterfaceReferences,
|
||||
ref: metadata.ref,
|
||||
validator: metadata.validator,
|
||||
};
|
||||
```
|
||||
|
||||
Optional propertyの`undefined`はwireへ出力せず、field省略として扱う。`null`へ自動変換しない。
|
||||
|
||||
`selectedInterfaceReferences`は、Hostが各runtime Interface definitionを登録または公開するときに割り当てる構造化参照`{ scope, name }`を、publication APIへの追加順で並べたものである。同じ`(scope, name)`の重複は拒否し、Objectの配置pathに対する祖先制約も検証する。Variable名やJavaScript object identityをportableなProtocol参照としてそのまま使用することは要求しない。
|
||||
|
||||
`observe`はDescriptor自体をObjectへ埋め込まず、このInterface参照のordered setだけを返す。Clientが各参照を`fetch_interface`したとき、Host Runtimeはruntime definition objectから構築したDescriptorと、存在する場合はInterface validatorを返す。同じ参照とvalidatorの組のDescriptorはHost上で共有およびcacheできるため、Object instanceごとにDescriptorを再生成または再送する必要はない。
|
||||
|
||||
## Runtime definitionの責務
|
||||
|
||||
`wip.interface(...)`が返すdefinition objectは、conceptualには次を生成または提供する。
|
||||
|
||||
- Interface Descriptorを構築するruntime definition。
|
||||
- Operation propertyとOperation Declarationの対応情報。
|
||||
- ArgumentsのProtocol `Value` decoder。
|
||||
- ResultのProtocol `Value` encoder。
|
||||
- Argumentsとresultのruntime validation。
|
||||
- Operation名から、そのInterfaceへbindしたimplementationの対応functionを呼び出すdispatch code。
|
||||
- 各Interfaceの推論されたImplementation型を満たす値を、そのInterface参照へbindするtyped entry point。
|
||||
- HostがInterface参照、optional validator、Descriptorを登録、取得、cacheするためのlocal identity。
|
||||
|
||||
TypeScriptのstructural type checkはcompile-timeのdeveloper feedbackである。Runtimeでは少なくとも次を検証する。
|
||||
|
||||
- Object metadataがProtocol上有効である。
|
||||
- 追加する各InterfaceへbindしたOperation implementationが呼び出し可能である。
|
||||
- 同じInterface参照をObjectへ重複して追加していない。
|
||||
- ArgumentsがDescriptorに従っている。
|
||||
- ResultがDescriptorに従っている。
|
||||
- Integer、Number、Entry、Record、Union等の追加制約を満たす。
|
||||
|
||||
Generatedまたはgenericなdispatch codeはTypeScript SDK内部の実装詳細である。Host内部のdispatch mechanism、class名、prototype、constructor等をProtocolへ公開しない。
|
||||
|
||||
## TypeScript typeとProtocol type
|
||||
|
||||
Runtime definitionは、Descriptorの`TypeExpr`、Protocol `Value`、TypeScript static typeの三つを対応付ける。
|
||||
|
||||
Conceptualな対応例は次のとおりである。
|
||||
|
||||
| WIP definition | TypeScript representation | Runtime validation |
|
||||
| --- | --- | --- |
|
||||
| `wip.unit()` | 専用の`WipUnit` sentinel | Optional省略と区別する |
|
||||
| `wip.boolean()` | `boolean` | Booleanのみ |
|
||||
| `wip.integer()` | `number` | Safe integer範囲 |
|
||||
| `wip.number()` | `number` | Finite binary64 |
|
||||
| `wip.string()` | `string` | Unicode string |
|
||||
| `wip.bytes()` | `Uint8Array`等 | Octet列 |
|
||||
| `wip.json()` | JSON-compatible type | Bytesを拒否する |
|
||||
| `wip.entry()` | Branded string等 | Canonical Worldspace path |
|
||||
| `wip.record(...)` | Object type | Required、optional、unknown field |
|
||||
| `wip.list(...)` | Arrayまたはreadonly array | 各itemを検証 |
|
||||
| `wip.enum(...)` | String literal union | 宣言caseのみ |
|
||||
| `wip.union(...)` | Discriminated union | Case、payload、shape |
|
||||
|
||||
この表の具体的なpublic typeは未決定である。
|
||||
|
||||
`wip.json()`が受け付けるのは、null、boolean、finite number、string、Array、およびstring-keyedなplain objectから再帰的に構成されるJSON-compatible valueだけである。`undefined`、`bigint`、`symbol`、function、`Uint8Array`、`Date`、`Map`、`Set`、class instance、cyclic object graph、NaN、正負Infinity、sparse array等を暗黙にJsonへ変換せず、validation failureとする。Integerとして表現する値にもProtocolの相互運用範囲を適用する。
|
||||
|
||||
TypeScriptの`number`はIntegerとNumberの両方に使用できるため、runtime schemaの位置に基づいて検証する。`wip.integer()`はProtocolの相互運用範囲を超える値を拒否する。`bigint`を暗黙にIntegerへ変換しない。
|
||||
|
||||
Protocol `Unit`は、例えば`wip.unitValue`のような専用singleton / sentinelで表す。`undefined`をUnitとして使用しない。RequiredなUnit fieldはsentinelを明示し、optionalなUnit fieldではsentinelがpresent Unit、property欠落またはoptional positionの`undefined`が省略を表す。
|
||||
|
||||
`wip.optional(T)`はfieldまたはparameterの省略を表し、`null`を許可する意味ではない。Implementationへ渡すarguments objectではproperty欠落を省略とし、optional positionへ明示された`undefined`をSDK convenienceとして省略へnormalizeしてよい。`undefined`自体をProtocol Valueとしてencodeせず、required positionの`undefined`はvalidation failureとする。
|
||||
|
||||
## Dynamic Worldspaceでのlifecycle
|
||||
|
||||
### `observe`
|
||||
|
||||
Projection implementationはrequest時にbackend dataを解決し、classまたはplain object instanceを生成する。
|
||||
|
||||
```ts
|
||||
async function resolveFile(
|
||||
path: WipPath,
|
||||
subject: Subject,
|
||||
): Promise<PublishedObject | undefined> {
|
||||
const entry = await filesystem.resolve(path);
|
||||
|
||||
if (!entry || !canSee(subject, entry)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const file = new File(
|
||||
entry.path,
|
||||
createObjectMetadata(entry),
|
||||
);
|
||||
|
||||
let object = wip.publish(file)
|
||||
.expose(ReadOnlyFileInterface, {
|
||||
read: file.read,
|
||||
});
|
||||
|
||||
if (canWrite(subject, entry)) {
|
||||
object = object.expose(WritableFileInterface, {
|
||||
write: file.write,
|
||||
});
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
```
|
||||
|
||||
Host Runtimeは`PublishedObject`からProtocol Objectを返す。Response生成後にこの`File` instanceを破棄してよく、Worldspace内の全fileに対応するinstanceを保持しない。
|
||||
|
||||
Requested depthが正の場合、Providerはdirect childrenをlazyに発見できる。Directory全体を先に収集してArrayとして返すことは要求しないが、一つのparentのobservationを成功させるには公開されたindexable childrenをclean exhaustionまで列挙しなければならない。ConceptualにはAsync Iterable等を利用できる。
|
||||
|
||||
```ts
|
||||
const entries = filesystem.discoverChildren(path, budget);
|
||||
|
||||
for await (const entry of entries) {
|
||||
const child = await projectCurrentObject(
|
||||
entry.path,
|
||||
context,
|
||||
);
|
||||
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
observation.pushChild(child);
|
||||
}
|
||||
```
|
||||
|
||||
`discoverChildren`、`budget`、`observation`はconceptualであり、具体APIではない。必要なsemanticsは次である。
|
||||
|
||||
- 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
|
||||
- 公開されたindexable childrenをすべて列挙し、clean exhaustionで終了する。
|
||||
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なsuccess responseを返さない。
|
||||
- Requested depthに達したnodeではchild discoveryを行わず、`children`を未観測として省略する。
|
||||
- 異なるclassやplain objectを共通のpublished return typeとして扱える。
|
||||
|
||||
具体的なAsync Iterator contract、internal streaming、backpressure、error propagationは未決定である。
|
||||
|
||||
### `call_operation`
|
||||
|
||||
Hostはcall時に、requestのTargetと`InterfaceTarget`をHost Runtimeへ渡す。Runtimeは一つのatomic execution boundaryへ入ってから`target.path`を現在のObject implementationとstableなbackend対象へ解決する。
|
||||
|
||||
```ts
|
||||
await runtime.callCurrent(
|
||||
target,
|
||||
interfaceTarget,
|
||||
operation,
|
||||
argumentsValue,
|
||||
context,
|
||||
(path, context) => resolveCurrentProjection(path, context),
|
||||
);
|
||||
```
|
||||
|
||||
`resolveCurrentProjection`と`runtime.callCurrent`というAPIを採用することは決定しない。ResolverはRuntimeが開始したexecution boundary内で呼ばれ、返す値はそのboundary中にObject identityとbackend対象を安定して保持するhandleまたはtransaction contextを含む必要がある。このexampleが示す要件は次である。
|
||||
|
||||
- 過去の`observe`で作成したobject instanceを保持していなくてもOperationを実行できる。
|
||||
- Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開なら`NotFound`を返す。
|
||||
- Requestの`interface.reference`が現在の`Object.interfaces`に含まれ、self-scopeを含む祖先制約を満たすことを確認する。指定された`interface.scope_ref`と現在のscope Objectの`ref`も照合し、満たせない場合は`InterfaceMismatch`を返す。`scope_ref`の省略だけでは拒否しない。
|
||||
- 現在のInterfaceにOperationが宣言されていなければ`OperationNotFound`、Operationを公開しつつ実行だけを拒否する場合は`PermissionDenied`を返す。
|
||||
- `interface.validator`を現在のInterface validatorへ照合する。Mutable Interfaceにvalidatorがなければ`InterfaceValidatorRequired`、不一致なら`InterfaceValidatorMismatch`を返す。
|
||||
- 現在のProjected Object、Object validator、選択されたInterfaceとそのvalidator、Descriptor、Operation、そのInterfaceへbindしたTypeScript implementation、実際のbackend対象を一つのexecution boundary内で一致させる。
|
||||
- Object validatorが指定されている場合は、現在のObject identityと保護対象状態の両方へ照合し、同じexecution boundary内でOperationを実行する。
|
||||
- Pathが別Objectを指す場合は、provider-local revisionや内容が同じでもObject validatorを一致させない。
|
||||
- Providerのvalidatorがobject-localなrevisionだけの場合、HostはObject identityを含む形へnamespace化またはwrapできなければならない。
|
||||
- Object validatorが省略され、Operationがvalidatorを要求する場合は、実行せず`ValidatorRequired`を返す。
|
||||
- Object validatorなしの実行をOperationが許可する場合は、実行時にpathが指す現在Objectへ無条件に実行する。
|
||||
- Runtime dispatchが、選択されたInterface definitionへbindしたOperation implementationを現在のObject contextへ適用する。異なるInterfaceの同名Operationをmergeまたは曖昧に解決しない。
|
||||
- Operation実行後の現在状態に対応するvalidatorを、必要に応じて`CallOperationResponse`へ返せる。
|
||||
|
||||
Object validatorをObject identityへ結び付けるためにClient-facingな`ref`を要求しない。ProviderまたはHostはHost-privateなidentityを使用できるが、そのidentity confirmation、Object validator比較、Interface membershipとvalidator比較、DescriptorとInterface-specific implementation bindingの固定、authorization、Operation実行を一つのatomic boundaryで行う。
|
||||
|
||||
Operation実行後validatorを、同じobject instanceから再計算するか、Operation resultとともに返すか、backendを再観測するかは未決定である。どの方式でも、resultを生成した実行後状態とvalidatorを対応させなければならない。
|
||||
|
||||
Object instanceがpath文字列だけを保持し、validator比較後にpathを無条件で開き直す実装は、対象差し替えに対するatomicityを保証できない場合がある。TypeScript APIは、transaction、conditional update、backend handle、lock等を利用してbackend固有のatomicityを実現できる必要があるが、その抽象化は未決定である。
|
||||
|
||||
## 複数Interfaceの実装
|
||||
|
||||
一つのclassまたはplain objectは複数のInterface implementation typeを満たせる。Property名が衝突する場合やInterfaceごとに異なるbehaviorを提供する場合は、Interfaceごとのadapter objectをbindできる。
|
||||
|
||||
```ts
|
||||
type ReadOnlyFileOperations =
|
||||
wip.Implementation<typeof ReadOnlyFileInterface>;
|
||||
|
||||
type WritableFileOperations =
|
||||
wip.Implementation<typeof WritableFileInterface>;
|
||||
|
||||
class File
|
||||
implements
|
||||
WipObject,
|
||||
ReadOnlyFileOperations,
|
||||
WritableFileOperations
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Publication時に、現在の主体へ公開する0個以上のInterfaceを選択し、表示したい順で追加する。
|
||||
|
||||
```ts
|
||||
let object = wip.publish(file)
|
||||
.expose(ReadOnlyFileInterface, {
|
||||
read: file.read,
|
||||
});
|
||||
|
||||
if (authorization.canWrite) {
|
||||
object = object.expose(WritableFileInterface, {
|
||||
write: file.write,
|
||||
});
|
||||
}
|
||||
|
||||
return object;
|
||||
```
|
||||
|
||||
Protocol Objectの`interfaces`は追加順を保持するordered setである。同じInterface参照の重複は拒否する。複数のImplementation bindingを一つのDescriptorやdispatch tableへ暗黙にmergeせず、各Interface Descriptorとbindingは独立したOperation集合およびbehaviorを提供する。
|
||||
|
||||
異なるInterfaceは同じOperation名を宣言できる。Dispatchはrequestの`InterfaceTarget.reference`とOperation名の組で一意に決まり、そのInterfaceへbindしたimplementationを使用するため、同じObjectに意味の異なる同名Operationを実装できる。
|
||||
|
||||
```ts
|
||||
const object = wip.publish(file)
|
||||
.expose(UserFileInterface, {
|
||||
inspect: () => inspectForUser(file),
|
||||
})
|
||||
.expose(AuditFileInterface, {
|
||||
inspect: () => inspectForAudit(file),
|
||||
});
|
||||
```
|
||||
|
||||
表示上のqualified表記は[Text Viewの参照表記](../4.2-text-view.md#参照表記)に従う。Signature全体をProtocol request fieldへparseし直さない。
|
||||
|
||||
Intersection type、interface extension、mixin等をDescriptorのcompositionへ自動変換しない。Protocol上のInterface inheritanceを暗黙に導入しない。
|
||||
|
||||
## HostへのInterface登録
|
||||
|
||||
Runtime Interface definitionをHostへ登録し、構造化されたInterface参照を割り当てる必要がある。
|
||||
|
||||
参照の絶対scopeとlocal name、および配置pathへの祖先制約は[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)に従う。以下のconceptual API例はscope / nameを渡す具体構文を省略しており、暗黙のroot scopeを定めるものではない。登録時のcontextと引数の具体APIは引き続き未決定である。Self-scopeは許可され、scopeはそのpathのObjectに紐づく。Scope Objectのoptionalな`ref`はProtocolの`scope_ref`として観測・検証し、`ref`がない場合のidentity追跡不能も許容する。Scopeの移動追跡や独立したscope validatorを導入しない。
|
||||
|
||||
Conceptualには、eager registrationとlazy registrationの両方が考えられる。
|
||||
|
||||
```ts
|
||||
const readInterface = host.registerInterface(ReadOnlyFileInterface);
|
||||
const writeInterface = host.registerInterface(WritableFileInterface);
|
||||
const object = wip.publish(file)
|
||||
.expose(readInterface, {
|
||||
read: file.read,
|
||||
})
|
||||
.expose(writeInterface, {
|
||||
write: file.write,
|
||||
});
|
||||
```
|
||||
|
||||
または、Hostが最初のpublication時に各definitionをinternしてよい。
|
||||
|
||||
どちらの場合も次を守る。
|
||||
|
||||
- MutableなInterface参照では`fetch_interface`がvalidatorを返し、同じ参照とvalidatorの組から異なるDescriptorを返さない。
|
||||
- Validatorを返さないInterface参照は、その参照の有効期間中Descriptorをimmutableにする。
|
||||
- Descriptorを更新する場合は同じ参照へ新しいvalidatorを発行するか、新しいInterface参照を使用する。
|
||||
- Objectの`interfaces`へ設定した各Interface参照のDescriptorを、その主体から`fetch_interface`できる。
|
||||
- 一つのObjectへ同じInterface参照を重複して追加しない。
|
||||
- 構造的に同じDescriptorを同じ参照へdeduplicateすることは要求しない。
|
||||
- Variable名、module path、JavaScript object address等を、そのままportableなProtocol semanticsとして扱わない。
|
||||
- 一つのfinalized artifactを後からmutationしてDescriptorを変化させない。Hostはdefinitionをfreezeするか、検証済みのimmutable snapshotを保持できなければならない。
|
||||
|
||||
Registrationまたは最初のpublicationで、Descriptor、type codec、validator、Operation dispatch tableを一つのfinalized artifactとして再帰的に検証および固定する。`fetch_interface`、arguments / result validation、Operation dispatchはすべて同じartifactを使用する。Descriptorだけをsnapshotし、dispatch時にはmutation可能な元definitionを読み直す実装にはしない。Finalization後のmutationは拒否するか、既存artifactへ影響しないものとして扱う。
|
||||
|
||||
## Request contextとauthorization
|
||||
|
||||
Operation implementationは、Object instance以外にrequest contextを必要とし得る。
|
||||
|
||||
- 現在の主体。
|
||||
- `AbortSignal`等のcancellation。
|
||||
- Deadline。
|
||||
- Tracing / audit context。
|
||||
- Host serviceやbackend connectionへのaccess。
|
||||
- Resource limit。
|
||||
|
||||
これらをmethod parameter、wrapper object、AsyncLocalStorage、closure等のどれで渡すかは未決定である。Protocol parameterとして宣言された値と、Hostが注入するcontextを明確に区別しなければならない。
|
||||
|
||||
Object publication時のInterface集合の選択と、Operation call時のmembership、Interface validator、authorizationの再評価を実装できる必要がある。以前の`observe`で公開されたInterface集合だけを根拠に、現在のOperation実行を許可してはならない。
|
||||
|
||||
Operation resultのRecord、List、Union等へnestedしたすべての`Entry`位置について、Hostは現在の主体にそのpathを認識させてよいことを再帰的に確認できなければならない。通常のstructural Value validationだけではこのvisibility checkを満たさない。Runtime definition、Projection、Host Runtimeのどこで確認するかは未決定である。
|
||||
|
||||
Operation argumentとして`Entry` pathを受け取った場合も、pathの所持をauthorizationの証明として扱わず、参照先へ作用する時点で現在の主体について認可する。
|
||||
|
||||
## Error model
|
||||
|
||||
TypeScript methodのerror表現は、次の四種類を区別できなければならない。
|
||||
|
||||
- Protocol validationまたはdispatch failure。
|
||||
- Effectがcommitされていないと保証できるHost / backend implementation failure。
|
||||
- Operationの未実行またはrollbackを保証できない`OperationOutcomeUnknown`。
|
||||
- Interfaceがreturn typeとして宣言したdomain failure。
|
||||
|
||||
現在のProtocolでは、domain failureはOperation resultのUnion caseとして表す。Thrown value、rejected Promise、explicit result objectをdomain resultまたはHost errorへどう対応付けるかは、Interface authoring APIで明示しなければならない。
|
||||
|
||||
Arbitrary thrown value、Promise rejection、result encode failure、宣言型に違反するimplementation resultをdomain failureへ自動変換してはならない。Host Runtimeは、effectがcommitされていないと保証できる場合だけ`Internal`へmappingし、保証できない場合は`OperationOutcomeUnknown`へmappingする。Abortやtransport切断を未実行の証明として扱わず、自動retryしない。
|
||||
|
||||
## Custom transpilerを必須にしない
|
||||
|
||||
通常のTypeScript `interface`または`type`をsource of truthにしてInterface Descriptorを生成するには、TypeScript Compiler API、custom transformer、code generator等が必要になる。
|
||||
|
||||
Core APIはこれを要求しない。Runtime definition objectをsource of truthにすることで、通常の`tsc`、bundler、JavaScript runtimeで動作できるようにする。
|
||||
|
||||
Build-time toolingはoptionalなauthoring layerとして追加してよい。
|
||||
|
||||
```ts
|
||||
/** @wip-interface */
|
||||
interface FileOperations {
|
||||
read(args: ReadArguments): Promise<Uint8Array>;
|
||||
}
|
||||
```
|
||||
|
||||
Toolingがこのdeclarationからruntime definition moduleを生成することはできるが、生成物はcore APIと同じruntime definition semanticsを満たさなければならない。
|
||||
|
||||
Build-time toolingには少なくとも次の課題がある。
|
||||
|
||||
- TypeScript compiler versionとCompiler APIへの依存。
|
||||
- Bundler、incremental build、package boundaryとの統合。
|
||||
- Generic、conditional type、mapped type、importされたtypeの解決。
|
||||
- Documentation抽出。
|
||||
- Sourceとgenerated runtime schemaの一致。
|
||||
- Runtime validation codeの生成。
|
||||
|
||||
## Decoratorをcoreにしない
|
||||
|
||||
Decoratorだけでは、通常はUnion case、Record field、optional、Entry、Integer、nested type、generic、documentation等を完全なruntime schemaとして取得できない。
|
||||
|
||||
`emitDecoratorMetadata`等が提供するconstructor-level metadataだけでは、WIP Interface DescriptorとValue validationを再構築するには不足する。Decoratorをoptional sugarとして提供する場合も、明示的なruntime definitionまたはbuild-time generationへ接続する。
|
||||
|
||||
Class inheritanceをInterface declarationの基本形にすると、複数Interface implementation、plain object、data-drivenなpublicationが不必要に制限されるため、runtime definition objectをcore modelとする。
|
||||
|
||||
## 要件
|
||||
|
||||
TypeScript API modelは少なくとも次を満たす。
|
||||
|
||||
- Runtime definition objectをInterface Descriptorとruntime validationのsource of truthにする。
|
||||
- 同じdefinitionからTypeScript implementation typeを推論できる。
|
||||
- Strictなcompiler settingsとfunction-valued propertyを使用して、Object metadata providerが`WipObject`を満たし、各Interfaceへbindする値がそのImplementation型を満たすか検査できる。
|
||||
- Compile-time型を完全なauthorityとせず、callable checkとProtocol boundaryのruntime validationを必須にする。
|
||||
- 0個以上のInterfaceをpublication時に明示し、objectが持つpropertyやmethodの自動列挙に依存しない。
|
||||
- Protocol上のOperation、parameter、type、field、case名をstableなstring dataとしてfinalizeし、constructor名やfunction `.name`から推測しない。
|
||||
- Objectのpath、children、Interface集合をclassへ恒久的に固定しない。
|
||||
- Classまたはplain object instanceをrequest scopeで生成および破棄できる。
|
||||
- 全Object instanceの事前登録を要求しない。
|
||||
- Direct childrenを現在の主体についてprojectし、非公開Objectとそのsubtreeを除外しつつlazyに発見し、success responseではclean exhaustionまで列挙する。
|
||||
- Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なobservationをsuccess responseとして返さない。
|
||||
- 同じclassまたはplain objectへ複数のInterface-specific implementationを同時にbindでき、追加順を保持し、重複を拒否できる。
|
||||
- 異なるInterfaceの同名Operationを、Interface参照とOperation名の組およびInterface-specific bindingでdispatchできる。
|
||||
- Operation result内のnestedな`Entry`へ、現在の主体についてvisibility checkを適用できる。
|
||||
- Host-private fieldをProtocol ObjectまたはValueへ暗黙にserializeしない。
|
||||
- Call時の現在の主体、Object visibility、Object validator、Interface membershipとvalidator、Descriptor、Operation、Interface-specific implementation binding、backend対象を一貫したexecution boundaryで扱う。
|
||||
- 非公開Object、Interface非membership、非公開Operation、公開済みOperationの実行拒否をそれぞれ`NotFound`、`InterfaceMismatch`、`OperationNotFound`、`PermissionDenied`へ対応付ける。
|
||||
- Interface validatorの必須・不一致を`InterfaceValidatorRequired`、`InterfaceValidatorMismatch`へ対応付ける。
|
||||
- Object validatorを現在のObject identityと保護対象状態へ結び付け、別Objectに同じprovider-local revisionがあっても一致させない。
|
||||
- Object validator比較、identity confirmation、Interface membershipとvalidator比較、DescriptorとInterface-specific implementation bindingの固定、authorization、Operation実行を同じatomic boundaryで行う。
|
||||
- Object validatorを要求するOperationへの省略を`ValidatorRequired`として拒否できる。
|
||||
- Provider、Host Runtime、runtime definition codeのerrorを、effect未commitを保証できる`Internal`と、保証できない`OperationOutcomeUnknown`へ安全に対応付けられる。
|
||||
- InvalidなObject、Descriptor、Operation resultをsuccess responseとして送信せず、Host-side validation failureをdomain failureへ変換しない。
|
||||
- Custom transpiler、decorator、特定bundlerをHost実装の必須要件にしない。
|
||||
- TypeScript SDK固有の型をProtocol semanticsとして要求しない。
|
||||
|
||||
## 非目標
|
||||
|
||||
このdraftは次を決定しない。
|
||||
|
||||
- 最終的なpackage、module、function、generic type、class、methodの名前。
|
||||
- 特定のschema libraryまたはvalidation libraryの採用。
|
||||
- 特定のJavaScript runtimeまたはbundler。
|
||||
- Object instanceの常駐registryまたはactor model。
|
||||
- JavaScript object、prototype、constructorをwireへ公開すること。
|
||||
- TypeScript interface extensionをProtocol Interface inheritanceとして扱うこと。
|
||||
- Backend entityとTypeScript objectまたはProtocol Objectの一対一対応。
|
||||
- TypeScript Compiler APIまたはcustom transformerの必須化。
|
||||
- Client SDKのtyped proxy API。
|
||||
|
||||
## 未決定事項
|
||||
|
||||
- `WipObject`と`WipObjectMetadata`の具体的なshape。
|
||||
- Object metadata取得をsyncまたはasyncのどちらにするか。
|
||||
- Publication APIがobjectを強参照、弱参照、closureのどれで保持するか。
|
||||
- `wip.interface`、`wip.operation`、type builderの最終syntax。
|
||||
- `wip.Infer`と`wip.Implementation`の具体的なgeneric type。
|
||||
- `strictFunctionTypes`を含むminimum TypeScript compiler settings。
|
||||
- Interface登録とscope / local name割当のlifecycle。
|
||||
- 登録時にruntime definitionをdeep freezeするか、immutableなDescriptor / dispatch snapshotへ変換するか。
|
||||
- Operation、parameter、type、field、caseのrename規則。
|
||||
- Documentationのauthoring syntax。
|
||||
- Runtime definitionと`TypeExpr`の対応。
|
||||
- `readonly` property / arrayを許可するか。
|
||||
- Class method、function property、closureのどれをOperation implementationとして許可するか。
|
||||
- Runtime dispatchがmethod receiverの`this`を保持する方法。
|
||||
- Operation property名とProtocol operation名をminifierやproperty manglingからどう保護するか。
|
||||
- Sync returnを許可してPromiseへnormalizeするか。
|
||||
- Request contextとProtocol parameterのAPI上の分離。
|
||||
- Nestedな`Entry` resultのvisibility checkをRuntime definition、Projection、Host Runtimeのどこで行うか。
|
||||
- Domain failure、thrown value、rejected Promise、Host errorの表現。
|
||||
- Runtime dispatchとHost Runtimeの責務境界。
|
||||
- Published ObjectをProviderから返す際のtype erasureまたは共通runtime representation。
|
||||
- Validator比較とOperation実行を囲むbackend-specificなatomicity abstraction。
|
||||
- Provider-local validatorをObject identityへnamespace化またはwrapするHost API。
|
||||
- Operation実行後validatorをObject instance、Operation implementation、Providerのどこから取得するか。
|
||||
- 主体ごとに異なるInterface combinationを追加したPublished Objectのstatic return typeをどう統一するか。
|
||||
- TypeScript client APIを同じruntime definitionから生成するか。
|
||||
- Build-time code generationをoptional packageとして提供するか。
|
||||
|
||||
これらを決定する際は、静的service objectだけでなく、Filesystem、database row、virtual aggregate、主体別Interface compositionを同じAPIで実装できるか評価する。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 未解決事項
|
||||
|
||||
主要な未解決事項を、canonicalなCore文書へのindexとしてまとめる。
|
||||
|
||||
## 2.2. Operation and Interface
|
||||
|
||||
対象: [2.2. Operation and Interface](../2.2-operation-and-interface.md)
|
||||
|
||||
- RustとTypeScriptの動的Worldspace Projection Host API。[運用と要件のdraft](dynamic-worldspace-projection.md)
|
||||
- RustとTypeScriptのcode-level Interface Declaration / Object publication API。[Rust API model](rust-api-model.md) / [TypeScript API model](typescript-api-model.md)のdraft
|
||||
- Interface参照をcontent-addressedにし、Descriptor digestをProtocolへ導入するか。
|
||||
- Descriptor version間の互換性規則。
|
||||
- 現在plain textであるDocumentationへmarkup、localization、構造化exampleを追加するか。
|
||||
- 現在Unionで表すDomain failureへ専用declarationを追加するか。
|
||||
|
||||
## 3. Protocol
|
||||
|
||||
対象: [3. Protocol](../3-protocol.md)
|
||||
|
||||
### Interface scopeとcomposition
|
||||
|
||||
構造化参照と絶対scopeは[2.2. Operation and Interface](../2.2-operation-and-interface.md#interfaceの識別とscope)、fieldとHost変換は[3. Protocol](../3-protocol.md#worldspace-composition)、短縮表記は[4.2. Text View](../4.2-text-view.md#参照表記)、JSON encodingは[5.1. WIP over HTTP](../5.1-wip-over-http.md#interface-reference)へ統合済み。
|
||||
|
||||
Self-scopeとroot scope `/`の利用は許可する。ScopeはそのpathのObjectに紐づき、optionalな`ref`で紐付きを確認する。`ref`なしの差し替えをidentityで追跡できないことは仕様上許容する。Scopeが消えれば旧参照の有効期間は終了し、Clientは不要なcacheを破棄できる。移動先は別参照であり、Clientの移動追跡は要求しない。詳細は[Scope Objectと有効期間](../2.2-operation-and-interface.md#scope-objectと有効期間)および[Protocolの紐付き確認](../3-protocol.md#scope-objectの紐付き)に従う。
|
||||
|
||||
残る論点:
|
||||
|
||||
- **複数配置とidentity**:同じUpstream Objectを複数scopeへ投影する場合の共通祖先scopeでの共有やprojectionごとの`ref`・validator。同じ`ref`とvalidatorで異なるrepresentationを返せるようにはしない。
|
||||
- **部分合成と拒否条件**:公開subtree外のscope・typed `entry`への対応付け、対応不能時の公開・実行拒否とerror mapping。非公開情報の秘匿と既存のerror precedenceを維持し、effectがcommit済みか不明なcallを未実行保証のerrorへ置き換えない。
|
||||
|
||||
Host内部のmount API、Provider登録API、合成設定の共通data structureは引き続きHost API draftで検討する。
|
||||
|
||||
### Type / Value
|
||||
|
||||
- String length、numeric range、pattern等のconstraintを導入するか。
|
||||
- 現在拒否するrecursive named typeを許可するか。
|
||||
- `i64`、`u64`、`bigint`、`decimal`等を追加するか。
|
||||
|
||||
### Interaction / Error
|
||||
|
||||
- ProtocolErrorへstructured detailを追加するか。
|
||||
Reference in New Issue
Block a user