Files

21 KiB

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の祖先制約を検証する。解決には完全なscopeとnameを使い、祖先Objectの順次取得、同名Interfaceの探索、shadowingを導入しない。Scopeが適合しても、それだけでInterface membershipやauthorizationがあるとは判断しない。

Objectの公開情報には、Operation定義そのものではなく、Interface参照のordered setが含まれる。Clientは必要な参照をfetch_interfaceで解決し、Descriptorとoptionalなvalidatorを観測としてcacheする。

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の参照表記に従う。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
  • PTC / REPL向けスクリプティングAPI

これらは同じKnown Space、Discoverable Space、Object identity、観測整合性管理を共有してよい。PTC / REPL bindingでは、Operationを通常のメソッド呼び出しとして提示することもできる。

issue.close(reason="duplicate")

この呼び方でも、ClientがInterfaceとOperationを解決してcall_operationを使い、Object identityと観測整合性を管理する。AI Toolsの3操作はAI向けインターフェースの契約であり、PTC / REPLの呼び出し構文を制限しない。