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

237 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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を表す。
```text
{ 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との[祖先関係](2.1-object-tree-schema.md#interface-scope)に従う。ただし、祖先関係だけでInterfaceを自動継承するわけではない。使用するInterfaceはObjectで明示的に公開し、scopeだけを根拠にOperationを追加したり、実行を認可したりしない。
Protocolの参照fieldへは、Interface参照とOperation名を連結した表示文字列を格納しない。構造とvalidationは[3. Protocol](3-protocol.md#interface-reference)に従う。`/github::issue.read#read`等の人間・AI向け短縮表記は[4.2. Text View](4.2-text-view.md#参照表記)で定義する。
HostがproxyやWorldspace合成を行う場合は、scopeを公開先の配置へ変換する。その際のpath、typed `entry`、identity、validatorとの一貫性は[Worldspace composition](3-protocol.md#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](3-protocol.md#scope-objectの紐付き)に従う。
## 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](3-protocol.md)で定義する。実装コードの宣言から生成するcode-firstの形をとり、人間とAIへのInterface signatureは[4.2. Text View](4.2-text-view.md)に従う。
Clientは、取得したDescriptorの構造と意味を検証してからcacheする。Named typeの参照解決、Operation callのvalidation、表示生成に使うのは、検証済みのInterfaceだけである。
## Operation declaration
Operationには、名前付きparameter、return type、documentationを宣言する。
人間とAIへ提示するときは、Interface DescriptorからIDL風のInterface signatureを生成できる。Operation部分は、例えば次のように表示する。
```text
/// "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](4.2-text-view.md)で定義する。
Parameterは常に名前付きとし、位置引数は用いない。引数を持たないOperationは空のparameter listで表し、`call_operation`には空のrecordを渡す。
```wip
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の参照表記](4.2-text-view.md#参照表記)に従う。
## `fetch_interface`
`fetch_interface(interface)`は、Interface参照に対応する現在のInterface Descriptorを取得する。同時に、optionalなscope Objectの`ref`(`scope_ref`)とoptionalなInterface validatorを、一つの観測として取得する。
```text
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を実行する。
```text
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は定義しない。