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

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