Files
wip-reference/contents/2.2-operation-and-interface.md

18 KiB
Raw Permalink Blame History

2.2. Operation and Interface

Interface

Objectは、Operation定義を直接持つ代わりに、0個以上のInterface参照をordered setとして持つ。各Interfaceは、Objectが公開する一つのcapability namespaceであり、利用できるOperationと各Operationのparameter、return type、documentationを定義する。

複数のObjectで同じInterfaceを共有できるため、ObjectごとにOperation定義を重複して送る必要はない。ObjectのInterface集合にはset semanticsを適用し、同じ参照を重複して含めない。列挙順はOperation解決には影響しないが、Hostの立項順としてUI表示に使用できる。

Operation callでは、この集合から一つのInterfaceを明示し、そのDescriptor内のOperationを実行する。

Interface実装者は、RustのtraitやTypeScriptのdefinition API等を使い、実装コード上でInterfaceを宣言する。Libraryはこの宣言から、typed handler、runtime validation、machine-readableなInterface Descriptorを生成する。

したがって、Interface Descriptorは人間が手書きするschema fileではない。Hostがfetch_interfaceで公開し、Clientが参照単位でcacheするprotocol dataである。

Interfaceの識別とscope

Interface参照は、scopeとnameを別fieldとして持つ構造化データである。scopeはWorldspace上の絶対path、nameはそのscope内のlocal nameを表す。同じClient-facing Worldspace内では、同じ有効な(scope, name)が同じInterfaceを表す。

{ scope: "/github", name: "issue.read" }
{ scope: "/gitlab", name: "issue.read" }

この二つの参照はlocal nameが同じでも、scopeが異なるため別のInterface参照となる。Local nameの.等には、階層やOperationとの区切りの意味を持たせない。同じscope内の異なるInterfaceに同じnameを割り当てない。複数Providerを同じscopeへ登録する場合にも、Hostが名前の衝突を検出・解消する。

Scopeの基準は、HostがClientへ公開するWorldspaceのrootである。Backendのpath、filesystem root、HTTP endpointのprefixを基準にはせず、相対pathも認めない。異なるWorldspaceにある同じ(scope, name)を、同一Interfaceとして扱わない。

Objectが利用できるscopeは、そのObjectの配置pathとの祖先関係に従う。ただし、祖先関係だけでInterfaceを自動継承するわけではない。使用するInterfaceはObjectで明示的に公開し、scopeだけを根拠にOperationを追加したり、実行を認可したりしない。

Protocolの参照fieldへは、Interface参照とOperation名を連結した表示文字列を格納しない。構造とvalidationは3. Protocolに従う。/github::issue.read#read等の人間・AI向け短縮表記は4.2. Text Viewで定義する。

HostがproxyやWorldspace合成を行う場合は、scopeを公開先の配置へ変換する。その際のpath、typed entry、identity、validatorとの一貫性はWorldspace compositionに従う。

Scope Objectと有効期間

Scopeは、そのpathにあるObject(scope Object)に紐づく。Object自身のpathをscopeにするself-scopeも許可し、root Objectはscope /を利用できる。Scope Object自身のinterfacesに、そのscopeの全Interfaceを公開する必要はない。

Scope Objectのrefがある場合は、観測したObjectとの紐付きを確認するために用いる。Interfaceの識別子はあくまで(scope, name)であり、refを第三成分にはしない。

このrefは、Objectの状態を確認するvalidatorとも、Descriptorの版を確認するInterface validatorとも役割が異なる。Scope Objectの通常の状態更新だけを理由に、scopeの紐付きを無効にしない。

一方、scope Objectがそのpathから削除・移動・差し替えられると、旧Objectに紐づくInterface参照の有効期間は終了する。同じpathへ新しいObjectとInterfaceを公開することは許可するが、旧Objectの登録を暗黙には引き継がない。移動先では新しいscopeによる別の参照となる。同じrefを観測しても、ClientにInterface参照の移動追跡やcacheの付け替えを要求しない。

Scope Objectのrefはoptionalである。refのないObjectが同じpathで差し替えられた場合、そのidentityを追跡できないことは仕様上許容される。そのHostを不正とみなしたり、差し替えの検出を必須にしたりしない。

ただし、refの有無にかかわらず、現在のObjectへのInterface membership、scopeの適合、指定されたvalidator、authorizationは検証する。存続中の同じInterfaceでは、refがないことを理由にDescriptorのimmutable / validator規則を弱めない。

Clientは、scope Objectがそのpathから消えたと確認した場合、そのscopeのInterface cacheを破棄できる。Client-localなcache evictionやindexable edgeの消失だけを、Object削除の証拠にはしない。実行中callでは固定済みの観測を使い、結果検証が終わるまで必要なDescriptorを保持する。削除通知やClientによる常時監視は要求しない。

具体的な紐付きの観測とcall preconditionはProtocolに従う。

Interface Descriptor

一つのInterface Descriptorは一つのInterfaceを定義し、少なくとも次の情報を保持する。

  • Format version
  • Interface documentation
  • Named type declaration
  • Operation declaration
  • Operationごとのparameterとreturn type
  • Type、field、parameter、case等のdocumentation

Descriptorのlogical structは3. Protocolで定義する。実装コードの宣言から生成するcode-firstの形をとり、人間とAIへのInterface signatureは4.2. Text Viewに従う。

Clientは、取得したDescriptorの構造と意味を検証してからcacheする。Named typeの参照解決、Operation callのvalidation、表示生成に使うのは、検証済みのInterfaceだけである。

Operation declaration

Operationには、名前付きparameter、return type、documentationを宣言する。

人間とAIへ提示するときは、Interface DescriptorからIDL風のInterface signatureを生成できる。Operation部分は、例えば次のように表示する。

/// "Returns items related to this Object."
operation get_related_items(
  relation: Relation,
  limit?: integer,
) -> {
  items: [entry],
  next_cursor?: string,
};

このInterface signatureは、検証済みDescriptorから生成する表示であり、Interfaceの交換形式や入力言語ではない。Object signatureとInterface signatureの表記は4.2. Text Viewで定義する。

Parameterは常に名前付きとし、位置引数は用いない。引数を持たないOperationは空のparameter listで表し、call_operationには空のrecordを渡す。

operation refresh() -> unit;

Return typeには、Descriptorで定義される任意のvalue typeを使用できる。Evolution compatibilityが必要なOperationでは、fieldを追加できるrecordをreturn typeの基本とする。

Operationの用途は短いdocumentationで説明し、長文documentationや大量のApplication dataをInterface Descriptorへ埋め込まない。

Operation名は、一つのInterface Descriptor内で一意とする。異なるInterface間では同じOperation名を使用できる。call_operationは構造化Interface参照とOperation名の組で解決し、Protocolでは両者を別fieldとして扱う。表示用のqualified表記はText Viewの参照表記に従う。

fetch_interface

fetch_interface(interface)は、Interface参照に対応する現在のInterface Descriptorを取得する。同時に、optionalなscope Objectのref(scope_ref)とoptionalなInterface validatorを、一つの観測として取得する。

fetch_interface(interface) -> Interface Descriptor, scope_ref?, validator?

Objectが持つのはInterface参照のordered setだけであり、Descriptorは先行同梱しない。Clientは各参照をInterface cacheと比較して未知のDescriptorを取得し、Interface validator mismatch等で更新が必要になった参照を再取得する。Hostは、主体へ公開したObjectの各Interface参照について、現在のDescriptorをその主体から取得可能にしなければならない。

同じ有効期間内では、同じInterface参照とvalidatorの組は同じDescriptorを表す。Validatorを返さないInterface参照は、その有効期間中immutableでなければならない。scope_refは、観測の紐付きmetadataとして保持し、参照の識別子へは組み込まない。

HostはClientのcache状態を推測し、要求されていないDescriptorをObject responseへ追加しない。複数のDescriptorが必要なら、Clientはそれぞれのfetch_interfaceを個別に並行実行できる。

Operationの可視性

Hostは、主体の権限やrole / capabilityに基づいて、その主体へ公開するInterface参照の集合を決定してよい。同じObjectに対しても、主体ごとに異なるInterface集合を返してよい。

Operationの存在を主体に認識させない場合には、次のいずれかを選べる。

  • そのOperationをcapabilityとしてまとめたInterface参照をObjectの集合から除外する。
  • そのOperationを宣言しない別のInterface Descriptorを参照する。

ClientがObjectで公開されていないInterface参照を指定すると、InterfaceMismatchを返す。公開されているInterfaceに宣言されていないOperation名を指定すると、OperationNotFoundを返す。

これとは別に、Operationの存在を公開し、実行だけを認めない設計も可能である。その場合は、OperationをいずれかのInterfaceに宣言したうえで、call_operation時のauthorizationで拒否し、PermissionDeniedを返す。

Hostは、主体に対する公開と実行の条件を次のように独立に選択できる。

  • Object自体を公開せず、そのObjectへのobserveとOperation callをNotFoundにする。
  • Objectを公開するが、特定Interfaceを集合から除外する。
  • Objectを公開し、特定Operationを含まないInterfaceを使用する。
  • Object、Interface、Operationを公開するが、そのOperationの実行をPermissionDeniedにする。

Interface集合と各Interface内のOperation可視性は、その主体に対してどのcapabilityを公開するかを表すprojectionである。頻繁に変化する実行可否やdomain stateを表すものとしては扱わない。

WIPは、Operationのavailable / disabledのような揮発的状態をInterface集合へ同期することを前提としない。現在状態の判断が必要なら、必要に応じてdomain固有のOperationで観測する。公開されたOperationでも、最終的なauthorizationと実行条件は、call_operation時にHostが判定する。

call_operation

call_operationは、指定pathにあるObjectに対し、Interfaceに宣言されたOperationを実行する。

call_operation(target, interface, operation, arguments) -> result, validator?
  • target.pathは対象Objectを観測したpath。
  • target.validatorはObjectから得たoptionalなopaque validator。
  • interface.referenceは対象Objectのinterfacesから選んだInterface参照。
  • interface.scope_refはfetch_interfaceで観測したscope Objectのoptionalなref。
  • interface.validatorはfetch_interfaceで観測したoptionalなInterface validator。
  • operationは選択したInterfaceに宣言されたOperation名。
  • argumentsはOperationのparameterに従う名前付きrecord。
  • resultはOperationのreturn typeに従うvalue。
  • ResponseのvalidatorはOperation実行後のObject状態に対してHostが発行したoptionalな値。

Hostは、Operationを実行する前に次の条件を検証する。

  1. target.pathが存在する。
  2. target.validatorが指定されている場合、現在のObject validatorと一致する。
  3. interface.referenceが(scope, name)の完全一致でObjectの現在のinterfacesに含まれ、対象pathに対するscopeの祖先制約(self-scopeを含む)を満たす。interface.scope_refが指定されている場合は現在のscope Objectのrefと一致する。
  4. MutableなInterfaceではinterface.validatorが指定され、現在のInterface validatorと一致する。
  5. 選択したInterfaceにOperationが宣言されている。
  6. Argumentsがparameter declarationに従っている。
  7. ClientがOperationを実行できる。

対象Objectが現在のClient-facing Worldspaceへ公開されていなければ、not_foundを返す。指定InterfaceがObjectの現在のinterfacesに含まれない場合、または指定されたscope_refの紐付きを確認できない場合は、interface_mismatchを返す。指定InterfaceにOperationが宣言されていなければoperation_not_found、宣言されたOperationの実行が認められなければpermission_deniedを返す。scope_refを持たない観測とcallも許可し、省略したことだけを理由には拒否しない。

Object validator、Interface membership、scope Objectの紐付きと指定されたscope_ref、Interface validator、Descriptor、Operation implementation、backend対象の確認と固定は、Operation実行に対して一貫したpreconditionとして扱う。

Object validatorが一致しない場合はvalidator_mismatchを、要求されるObject validatorがない場合はvalidator_requiredを返す。Mutable Interfaceのvalidatorがない場合はinterface_validator_requiredを、一致しない場合はinterface_validator_mismatchを返す。いずれの場合も、HostはOperationを実行しない。

Argumentsに未知のfieldがある場合、必須parameterがない場合、valueが宣言型に一致しない場合にも、HostはOperationを実行せず、invalid_argumentsを返す。

Clientは、Hostから受け取ったsuccess responseのresultをreturn typeに従って検証する。宣言と異なるresultが返された場合は、Hostが返したProtocol failureではなくInvalidResponseとして扱い、そのvalueをApplicationへ渡さない。

Host Runtimeが送信前にinvalidなimplementation resultを検出した場合は、effectがcommitされていないと保証できるときだけInternalを返す。保証できない場合はOperationOutcomeUnknownを返す。

Application data

Operation argumentsとresultは、Applicationが定義して利用するdataである。WIPが規定する内容は次のとおりである。

  • Descriptor上の型表現
  • Transport上のencoding
  • ClientとHostのboundary validation
  • entry等のWIP固有型の意味

一方、次の内容はWIPでは規定しない。

  • Dataのdomain上の意味
  • 永続化方法
  • Application内部のdomain model
  • Business rule
  • Clientが取得したdataを利用する方法

例えば、argumentsが型に従っていても、現在のdomain stateが操作を許可しない場合には、Operation implementationはdomain failureを返し得る。Wire schema上の正しさと、domain上の有効性は別の条件である。

Operation Resultとエントリの発見

Operation Resultには、Descriptorのentry型として、新しいエントリのpathが含まれ得る。entryは、同じWorldspace内のcanonical absolute pathである。

Clientは、return typeに従ってentry型の位置を認識する。通常のstringやopaqueなjsonに含まれるpathらしい文字列を、エントリとして推測してはならない。

発見したpathは参照として利用できる。Clientは、Objectの公開情報が必要になったときに、必要な深さでobserveする。すべてのpathの即時取得は要求せず、Operation ResultへObject公開情報やInterface Descriptorを先行同梱しない。

発見されたエントリは、実行対象の子である必要はない。親、兄弟、Worldspace内の任意の位置にあってよい。Operationの実行対象と、結果として発見されたエントリのTree上の位置は独立している。

また、pathが返されたこと自体から、実行対象との親子関係や意味的関係を推論しない。親子関係はTree上の配置で表し、非内包の意味的関係はOperation名、result field名、Application固有のrelation型で表現する。

Protocol failureとdomain failure

Protocol failureは、WIPのrequest、対象、Interface、validation、validatorによる条件付き実行、Host executionに関する失敗である。

  • invalid_request
  • not_found
  • interface_not_found
  • interface_mismatch
  • interface_validator_required
  • interface_validator_mismatch
  • operation_not_found
  • invalid_arguments
  • validator_required
  • validator_mismatch
  • permission_denied
  • resource_limit_exceeded
  • internal
  • operation_outcome_unknown

これに対し、InvalidResponseは、Clientが受信したsuccess responseまたはerror responseのProtocol違反を表すlocal classificationである。Hostが返すProtocol failureには含めない。UnsupportedDescriptorFormatもClient-localであり、validだが未対応のDescriptorをProtocol違反としては扱わない。

call_operationがoperation_outcome_unknown以外のProtocol failureを返した場合、HostはOperationのeffectがcommitされていないことを保証する。operation_outcome_unknownまたはresponse不達の場合、ClientはOperationを自動再実行しない。対象を再観測するか、domain固有の照会手段を利用する。

Domain failureは、型として正しいOperationをApplicationが実行した結果として生じる失敗である。

  • issue_has_open_dependencies
  • payment_declined
  • merge_conflict

これらのDomain failureをProtocol failureへ混在させない。現在のProtocolでは、Operation return typeのUnion caseとして宣言し、専用のfailure declarationは定義しない。