Files

188 lines
21 KiB
Markdown

# 4. クライアント
## 役割
Clientは、Hostが提供するWorldspaceを利用する実装主体である。WIP ProtocolでObjectを観測し、そのObjectが参照するInterface集合を解決して、用途に応じたView、Tool、API等へ投影する。
取得した情報は、interactionごとに捨てる必要はない。Rootからindexable edgeを辿って得たエントリツリーや、必要に応じて選んだエントリを保持すれば、上位層はそれを既知の空間として利用できる。
## Known Space
Known Spaceは、Clientが現在保持しているエントリの集合である。過去に発見したすべてのエントリを、増え続ける集合として蓄積するわけではない。
Known SpaceとDiscoverable Spaceは、過去の観測から構築したcacheである。Hostの現在状態を同期したreplicaではなく、HostはClientのcache状態を追跡・更新しない。
Cacheを共有できるのは、観測結果が同じeffective Worldspace viewに属する範囲に限られる。接続先、認証主体、tenant、その他のsecurity contextによって異なるWorldspaceが公開され得る場合、それらのKnown Space、Object observation、Interface cacheを混在させない。具体的なcache namespaceの構成は、ClientとTransport Bindingに委ねる。
Known Spaceへ追加できるのは、例えば次のエントリである。
- `observe`で取得したエントリ
- Operation結果の`entry`型pathを`depth = 0`で`observe`して取得したエントリ
Operationで発見したpathを、すべてKnown Spaceへ追加する必要はない。現在の結果として一時的に扱っても、取得後に必要なエントリだけを保持してもよい。保持したエントリ間の移動や参照はClient側で処理でき、辿るたびにHostへ問い合わせる必要はない。
## 取得・保持・提示の分離
Pathを知っていても、ClientがそのObjectを観測しているとは限らない。Objectを観測・保持していても、そこへ至るindexable edgeを観測・保持しているとは限らない。この三つは別の状態である。
Clientは、Known Spaceにない有効なpathも取得対象にできる。未観測であることだけを理由に拒否せず、現在のHostの公開状態に従って`observe`する。取得前に仮のObjectをKnown Spaceへ登録する必要はない。ただし、pathを知っていることは、公開可視性や実行権限の保証にはならない。
観測した情報のうち何を提示するかは、上位インターフェースが決める。配置だけを提示する場面では、保持している説明やInterfaceをすべて展開しなくてもよい。提示済みの情報とcache内容を同一視しない。
同様に、Objectを保持していることだけを根拠に、既知のtreeへ接続してはならない。Treeは観測済みのindexable edgeから構成し、pathのprefixや階層関係からedgeを推測しない。
## Entry TreeとObject observation
Clientは、観測したObjectをcanonical path上のEntryに対応付け、観測済みのindexable child edgeとともに保持できる。Cacheの表現は規定しない。`ObjectObservation`に近い入れ子構造やforestを使っても、path index、flatなObject store、Entry Treeへnormalizeしてもよい。
Entryのpathとchildrenは配置contextであり、Object identityには含まれない。同じObjectが複数のEntryから観測される場合、shared record、複製したsnapshot、その他の内部表現を使える。ただし、保存形式を変えても、`ref`、validator、Object representationのconsistencyは変えてはならない。
- 同じ有効な`ref`を持つ観測は、同じWorldspace内の同じObjectを表す。Clientは一つのObject stateを共有しても、Entryごとにsnapshotを保持してもよい。
- `ref`がないこと、または異なることから、複数のEntryが同じObjectか異なるObjectかを判断しない。Clientはrepresentationが同じという理由だけでObject identityを推測しない。
- Objectの`interfaces`はmembershipをsetとして扱い、同じ参照の重複を`InvalidResponse`として拒否する。ClientはHostの立項順を保持し、InterfaceやOperationの提示順に使用してよい。
- 同じ`ref`と同じvalidator valueを持つObjectを再観測したとき、validatorが存在するにもかかわらずObject representationが異なる場合、Clientはresponseを`InvalidResponse`として扱う。同じ`ref`でvalidatorが異なる場合、またはvalidatorが存在しない場合は、同一Objectの新しい観測として扱い、最後に受理した成功観測をその`ref`の現在状態とする。
Entry TreeはClient内部のcache表現であり、Objectとは別のWIP resourceやProtocol上のEntry resourceを追加するものではない。
## Interface cache
Interface参照は`{ scope, name }`というrecordである。比較には、同じeffective Worldspace view内の`(scope, name)`の完全一致を使う。Local nameだけ、表示用の短縮表記、JSON serializationのbyte列をcache keyにしない。Scopeが異なれば別参照であり、Host内部のUpstream座標へ読み替えない。
Clientは各Objectの配置pathに対して[scopeの祖先制約](2.1-object-tree-schema.md#interface-scope)を検証する。解決には完全なscopeとnameを使い、祖先Objectの順次取得、同名Interfaceの探索、shadowingを導入しない。Scopeが適合しても、それだけでInterface membershipやauthorizationがあるとは判断しない。
Objectの公開情報には、Operation定義そのものではなく、Interface参照のordered setが含まれる。Clientは必要な参照を`fetch_interface`で解決し、Descriptorとoptionalなvalidatorを観測としてcacheする。
```rust
struct CachedInterface {
scope_ref: Option<String>,
descriptor: InterfaceDescriptor,
validator: Option<Vec<u8>>,
}
```
Validatorがある観測は、同じ有効期間内で`(interface, validator)`の組により識別する。ClientはInterface参照ごとに、最後に成功した観測をcurrent entryとして保持できる。Validatorがない場合、その参照が有効な間はDescriptorがimmutableなので、参照だけをcache keyに使える。
`scope_ref`は、`fetch_interface`が返すscope Objectのoptionalな`ref`である。参照の第三成分ではなく、観測metadataとして保持する。存在する場合はcallの`interface.scope_ref`へコピーし、なければ省略する。Scope Objectや祖先を追加取得する必要はない。
Scope Objectを別途観測した結果、以前の紐付きを確認できなくなった場合は、Interface observationを取得し直す。古いDescriptorの`scope_ref`だけを更新してはならない。
Scope Objectがそのpathから消えたことを確認したClientは、そのscopeのInterface cacheを破棄できる。`ref`が以前の紐付きを確認できない値に変わった場合や、`ref`がなくなった場合も、旧観測を新しいcallに使わない。有効期間が終了した旧観測と、同じpathへ再公開された新しい観測を区別し、scopeの移動先を追跡してcacheを付け替えない。
一方、`ref`のないscopeが同じpathで差し替わる場合、Clientがidentityや有効期間の切り替わりを追跡できないことは許容される。`ref`の欠落を不正なresponseとしたり、疑似identityを生成したりしない。得られた情報によるDescriptorとvalidatorの検証は続けるが、検出不能な差し替えの防止までは保証しない。
Indexable edgeが一覧から消えても、scope Objectが削除されたとは限らない。直接観測可能な非indexable Objectとして存続している可能性があるためである。削除の確認と、Client-localなcache evictionを混同しない。削除の即時検出、監視、全Interfaceの先行再取得も要求しない。
Objectは複数のInterfaceを組み合わせられ、複数のObjectも同じInterfaceを共有できる。それぞれのOperation一覧は、そのInterface Descriptorに宣言されたOperationの集合である。
Clientは、必要なInterface参照とcacheを比較し、未知のInterfaceだけを取得する。Objectの`interfaces`からDescriptor interestを先行して導出しても、Operationを選択した時点で必要な参照だけを取得してもよい。Interface validator mismatch等で更新が必要になった参照は再取得する。複数の`fetch_interface`は、それぞれ個別のrequestとして並行実行できる。
同名Operationが異なるInterfaceに存在しても、Clientは構造化Interface参照とOperation名の組で識別する。UIやAI Toolのqualified表示は[Text Viewの参照表記](4.2-text-view.md#参照表記)に従う。Callではsignature全体をparseせず、Interface参照とOperation名を別fieldとして送る。
実行中のOperationが使うDescriptor observationは、resultのvalidationが終わるまで保持する。Cacheを破棄できる場合や、新しいvalidatorのDescriptorを観測した場合も、開始済みのcallが必要とするDescriptorを早期に捨てない。
## Discoverable Space
Discoverable Spaceは、Clientが`observe`で取得・保持したindexableなエントリツリーである。Worldspace rootまたはClientが選んだ起点から、観測済みのindexable edgeで到達できるmaterialize済みの範囲を指す。
Known Spaceの部分集合であり、AI向けの`tree`など、indexableな配置の探索に使う。Discoverable Spaceとして扱う間は、観測済みtreeの構造を保持する必要がある。
Clientが`observe`結果をmergeしたとき、`children`のあるparent nodeから以前のindexable edgeが返されなくなった場合は、その子エントリをDiscoverable Spaceから除外する。保持するどの起点からも到達不能になったsubtreeは、その場で破棄できる。ただし、別経路から到達できるエントリ、pinしたエントリ、実行中のOperationが参照するエントリは保持してよい。`children`が省略されたnodeは、edge削除の根拠にしない。
Operationからpathを発見しても、それだけではDiscoverable Spaceへ追加しない。そのエントリを起点に`depth > 0`で`observe`した場合に、取得したindexableな範囲を追加できる。
## 一時的な発見とpin
Operation Resultの`entry`型pathは、Objectを即時に観測しなくても、現在の結果として提示・利用できる。Pathの提示とObjectの取得は別であり、発見した全pathの即時取得は要求しない。
公開情報が必要になった時点で、必要なpathを観測できる。対象Objectだけなら`depth = 0`を使う。Client policyとして先行取得してもよい。その場合はOperationのreturn typeからentry pathを抽出し、重複を除去して、保持済みの観測と比較したうえで必要な取得を行う。複数の`observe`は、それぞれ個別のrequestとして並行実行できる。
一時的に取得したエントリは、結果を使い終えた時点で破棄してよい。後続のInteractionでも必要なら、Client側でpinしてKnown Spaceに保持できる。PinはClient内部の保持方針であり、Host上のObject lifetimeやWIP Protocol上の状態を変えない。
単純なClientは、発見したエントリをすべて保持してもよい。ただし必須ではなく、容量制限やeviction algorithmはClient実装に委ねる。
## Objectとchildrenの観測
`observe(path, depth)`では、対象Objectだけが必要なら`depth = 0`、indexableな配下も必要なら正のdepthを指定する。一つのinteractionでrequested depthまで取得するため、Clientが各nodeに対してdata-dependentなrequestを逐次発行する必要はない。
Response nodeの`children`が省略されていれば、そのchildrenは未観測である。存在する場合は完全な一覧であり、空arrayならindexable childが存在しない。未観測と空を同じ状態として扱わない。
Clientは起点pathと各Objectの`name`からchild pathを導出する。`children`があるnodeをcacheへmergeするときは、そのEntryの観測済みdirect child edgeを、responseの完全な一覧で置き換える。省略されたnodeは構造について新しい情報を持たないため、既存edgeを削除したり、観測済み一覧を未観測に戻したりしない。
件数の非常に多い集合では、通常、個々の要素をindexableな子エントリとして公開しない。集合を扱うObjectの`query`や`list`等のOperationを通して、個別要素を発見することを想定する。
## Interestと取得計画
Interestは、上位層がClientへ示す取得上の要求である。特定pathに必要な観測depthや、特定Interface参照のDescriptorが必要であることを示せる。Client-localな情報であり、Protocol dataではなく、Hostも追跡しない。
Interestを追加・削除しても、保持済み観測のProtocol上の意味や有効性は変わらない。Interestがなくなった観測を保持するかevictするかは、Client policyに委ねる。
Clientはinterestを、現在有効なObject observation、children coverage、Interface Descriptor observation、進行中の取得requestと比較し、不足するlogical interactionだけを発行できる。要求depthまでのcoverageがすでにある場合や、進行中requestで満たされる場合、同じinterestの調整処理は重複requestを発行しない。Depth境界で`children`が省略されたnodeは、その配下への正のdepthのinterestを満たさない。
このidempotentな調整と、既存観測を置き換える明示的な再観測は区別できる。明示的な再観測では、現在有効な観測や同じ対象の進行中requestがあっても、新たなrequestを発行して、それ以前のrequestをsupersedeしてよい。
Clientは、保持する観測、進行中request、並列度、request rateに上限を設ける。上限のために取得を開始できなくても、interest自体をProtocol failureにはしない。別requestの完了、容量の解放、interestの変更後に再評価できる。優先順位、並行実行、prefetch範囲、fairness、cache容量、eviction algorithmはClient実装に委ねる。
上位層から直接示されたinterestに加え、観測したObjectのInterface参照やindexable child edgeから、次のinterestを先行して導出してもよい。WIPは全量取得、暗黙の再帰展開、特定のprefetch範囲を要求しない。取得計画とevictionは、interestの変更や明示的な再観測がないまま、同じ観測を繰り返し破棄・再取得する自己維持的なloopを作ってはならない。
並行取得では、各responseを元のrequestとそのeffective Worldspace viewに対応付ける。ある観測状態のownershipを後続requestへ移した場合、supersedeされたrequestのresponseでその状態を上書きしない。Overlapping observationを部分的にmergeする場合も、後続requestが所有するObjectやchild coverageを以前のresponseで置き換えない。
Success、Protocol failure、Transport failure、local cancellationで終了したrequestは、進行中として残してはならない。`observe`や`fetch_interface`の失敗を自動retryするかはClient policyに委ねるが、回数、間隔、並列度には上限が必要である。取得requestのretry policyを`call_operation`の再実行へ流用せず、OperationにはProtocolのfailure semanticsを適用する。
## Objectの同一性
`ref`は、複数の観測やエントリが同じObjectを表すことを示すoptionalなopaque markerである。同じ主体に公開された一つのWorldspace内で、同じ有効な`ref`があれば、同じObjectであることを保証する。
逆に、`ref`が異なる、あるいは欠けていることから、Objectが異なるとは判断しない。異なるWorldspace間の`ref`も比較しない。Clientは`ref`を永続的なprimary keyやNavigationに使わない。
適したbackend UIDがあれば、Hostは直接または変換して`ref`に利用できる。ただしClientは、それがbackend UIDであることや、session・Host restartをまたいで安定することを仮定しない。
## 観測整合性
Clientが観測とOperation実行の整合性を管理できるのは、Hostから得た`ref`とvalidatorの保証範囲である。Scopeの`ref`や対象Objectのvalidatorがない場合、pathだけで観測時と同じObjectだと保証してはならない。Optional metadataの欠落は有効な公開形態であり、identityを追跡できないことをProtocol違反にはしない。
Objectに`validator`があれば、Clientは観測したpathと対応付ける。条件付きOperationでは両者をtargetとしてHostへ渡し、validatorの比較と実行をHost上でatomicに行わせる。AIやユーザーにvalidatorを直接管理させず、Transport BindingでもProtocol structのfieldとして保持する。HTTP ETag等のrepresentation metadataをWIP validatorの代用にしない。
Interfaceについても、選択した参照と、`fetch_interface`で観測したoptionalな`scope_ref`およびInterface validatorをrequestへ含める。MutableなInterfaceがvalidatorを返す場合は、必須preconditionとして使う。`InterfaceValidatorMismatch`ではInterfaceだけを再観測する。
`InterfaceMismatch`では、Objectを再観測して現在のInterface集合を取得する。Scopeの紐付きが変わった可能性もあるため、使ったInterface observationも無効化し、必要なら再取得する。Operation responseが新しいObject validatorを返した場合は、対応するObject observationへ反映する。
## Failure handling
回復actionは、Hostが返すProtocol errorの`code`で決める。人間向け`message`やTransport statusからcodeを推測しない。Interactionで許されないcode、schemaに違反するsuccess / error response、同じvalidatorに矛盾するrepresentationは、`InvalidResponse`として拒否する。有効だが未対応のInterface Descriptor formatは、`UnsupportedDescriptorFormat`として区別する。
- `NotFound`では対象Entryをstaleとして扱い、必要に応じて親を再観測する。
- `InterfaceNotFound`では参照元Objectを再観測できる。
- `InterfaceMismatch`では対象Objectを再観測し、使用したInterface observationも無効化して必要なら再取得する。
- `InterfaceValidatorRequired`または`InterfaceValidatorMismatch`では対象Interfaceを再観測する。
- `ValidatorRequired`または`ValidatorMismatch`では対象Objectを再観測する。
同じOperationを再試行するには、先に新しい観測を取得しなければならない。
- `InvalidRequest`または`InvalidArguments`では同じrequestを変更せずに再送しない。
- `ResourceLimitExceeded`ではlogical request shapeやobservation depth等を削減できるが、Operationを自動再実行しない。
- `PermissionDenied`では公開済みOperationが実行拒否されたことを示し、Object自体が閲覧禁止で存在するとは推測しない。
`OperationOutcomeUnknown`は、Operationの未実行やrollback済みを保証しない。自動再実行せず、対象Objectをstaleとして再観測するか、domain固有の照会手段を提示する。`Internal`、Transport timeout、connection lossでも、Operationを自動再実行しない。
Responseが届かなかった場合は、Protocol errorへ変換せず、Transport failureとOperation outcomeが不明な状態をClient内部に保持する。OperationをObject stateへのeffectで分類し、retry可否を推測することもしない。
Operation結果の`entry`を付随的に取得するときは、その取得と元のOperationの成否を分ける。参照先の`observe`が失敗しても、成功済みOperationを失敗扱いしたり、再実行したりしてはならない。その参照先に対する別interactionの失敗として提示・処理する。
HTTP authentication failureとWorldspace endpoint全体のaccess denialは、Transport layerのfailureである。WIPの`PermissionDenied`へ変換しない。
## 想定実装
Clientは、用途に応じて複数の上位インターフェースを提供できる。
- GUI上のWorldspace View
- [AI Tools](4.1-ai-tools.md)
- PTC / REPL向けスクリプティングAPI
これらは同じKnown Space、Discoverable Space、Object identity、観測整合性管理を共有してよい。PTC / REPL bindingでは、Operationを通常のメソッド呼び出しとして提示することもできる。
```python
issue.close(reason="duplicate")
```
この呼び方でも、ClientがInterfaceとOperationを解決して`call_operation`を使い、Object identityと観測整合性を管理する。AI Toolsの3操作はAI向けインターフェースの契約であり、PTC / REPLの呼び出し構文を制限しない。