@@ -0,0 +1,163 @@
# Scoped Interface References(草案)
## 位置付け
Interface参照にWorldspace上のpathをscopeとして持たせ、独立したProviderやWorldspaceが定義したInterfaceを、Hostによる合成後も衝突なく公開するためのProtocol変更案である。
この草案では、次を設計方針とする。
- Interface参照は、絶対pathのscopeと、そのscope内のlocal nameから構成する。
- Objectが公開できるInterfaceは、Objectの配置pathの祖先をscopeに持つものに制限する。
- 絶対pathは、Clientに対してHostが公開するWorldspaceのrootを基準とする。
- ProxyやWorldspace合成によって公開位置が変わる場合、Clientへ見せるpathとInterface参照を移動後の配置へ変換するのはHostの責務とする。
現行の[3. Protocol ](../3-protocol.md )ではInterface参照全体をopaque stringとして扱っている。本案はその一部に構造と意味を与えるものであり、単なる表示上の修飾ではない。Canonical文書へ採用するまでは草案として扱い、未確定の構文や境界条件を末尾にまとめる。
## Interface参照
表記案は次のとおりとする。
``` text
<scope-path>::<local-name>
/github::issue.read
/gitlab::issue.read
/github/my-rep::issue.admin
/::common.read
```
- `scope-path` はHostが公開するWorldspace内のcanonical absolute path。
- `local-name` はそのscope内でInterfaceを識別する名前。例の`issue.read` に含まれる`.` はObjectの階層やOperation名との区切りを意味しない。
- Interfaceはscopeとlocal nameの組で識別する。同じscope内の異なるInterfaceへ同じlocal nameを割り当てない。
- 同じlocal nameでもscopeが異なれば別の参照である。Descriptorの内容が似ていても同一性を推測しない。
- 同じscopeに複数のProviderを登録する場合、そのscope内の名前の衝突はHostが検出・解消する。Scopeを付けるだけで同一scope内の重複が自動的に解消されるわけではない。
Scopeはbackendのpath、OSのfilesystem root、URL origin、HTTP endpointのprefixではない。例えばHTTP endpointが`https://example.test/wip` でも、Worldspaceのscopeは`/github` であり、`/wip/github` へ自動的に変わるわけではない。
Scope pathの比較にはProtocolのpath規則を用いる。Unicode normalization、case変換、URL decode、文字列prefixだけによる祖先判定は行わない。`/github` は`/github-extra` の祖先ではない。
`::` による連結は表記案である。現行のpath segmentには`:` を含められるため、区切りの一意性とlocal nameの許可文字は構文確定時に決める。既存のpath規則へ暗黙に禁止文字を追加しない。
## Objectとscopeの関係
Objectをpath `P` で公開するとき、`Object.interfaces` の各参照のscope `S` は、`P` へ至る祖先chain上にある必要がある。「祖先」は直近のparentだけでなく、rootからparentまでの各pathを含む。兄弟や別subtreeのscopeは使用できない。
`/github/my-rep/issues` の場合は次のようになる。
| Interface参照 | このObjectでの利用 | 理由 |
| --- | --- | --- |
| `/::common.read` | 可 | `/` は祖先 |
| `/github::issue.read` | 可 | `/github` は祖先 |
| `/github/my-rep::issue.admin` | 可 | `/github/my-rep` はparent |
| `/gitlab::issue.read` | 不可 | 別subtree |
| `/github/other-rep::issue.read` | 不可 | 祖先ではない |
| `/github/my-rep/issues/123::issue.read` | 不可 | 子孫であって祖先ではない |
Object自身のpathをscopeとして認めるかは、root ObjectのInterfaceも含めて未確定とする。この草案の通常例はstrict ancestorを使用し、self-scopeを暗黙に導入しない。
この制約はInterfaceの自動継承ではない。祖先scopeで宣言されたInterfaceが、配下のすべてのObjectへ自動的に追加されるわけではない。Objectが明示的に公開した`interfaces` のmembershipと、scopeの祖先制約の両方を満たす必要がある。
Scopeが祖先であることもauthorizationの証明ではない。現在の主体への公開可視性、Operationの実行権限、validatorによるpreconditionは従来どおり検証する。
## Clientによる解決
ClientはObjectを観測したpathと、そこに公開された完全なInterface参照を使用する。
1. `observe` のrequest pathと各Objectの`name` から配置pathを得る。
2. `Object.interfaces` の各参照についてscopeがその配置pathの祖先であることを検証する。
3. 条件を満たす参照を、その完全な表記で`fetch_interface` へ渡す。
4. Operation callでは同じ参照を`InterfaceTarget.reference` へ設定し、Operation名は従来どおり別fieldへ設定する。
Hostは祖先制約に違反する参照をObjectへ公開してはならない。ClientはそのようなObject responseをProtocol違反として扱い、その参照を対象ObjectのInterfaceとして解決・使用しない。既存の分類に合わせ、Client-localな`InvalidResponse` として拒否する案とする。
Local nameだけによる検索、近い祖先から順に同名Interfaceを探すlookup、shadowing、scopeを省略した参照の補完は導入しない。`/::issue.read` と`/github::issue.read` の両方が公開されている場合も、完全な参照で明示的に区別する。
`fetch_interface` は完全な参照を受け取るため、祖先Objectを順に取得する必要はない。現行requestには対象Objectのpathがないので、「どのObjectから利用するか」の祖先検証はObject観測時とOperation call時に行う。Interface単独の取得可否はHostの公開可視性によって決まり、scope外のObjectから使えないこととDescriptor自体を取得できないことは同一視しない。
Hostも`call_operation` で、対象Objectに対するmembershipとscopeの適合を検証する。Clientの検証だけを信頼しない。Scope不適合を含むrequestのerror mappingと検証順は、既存の`InvalidRequest` / `InterfaceMismatch` 等との整合を含めて確定する。
Operation名とNamed typeのnamespaceは引き続きInterface Descriptor内に閉じる。本案はexternal type importやInterface間の型参照を追加しない。
## HostによるproxyとWorldspace合成
Clientが扱う絶対pathは、常に接続先HostのClient-facing Worldspaceのrootに基づく。Upstreamのrootや内部mount構成をClientに追跡・補完させない。
Upstream Worldspaceのrootを`/github` へ配置する例を示す。
| 項目 | Upstreamでの値 | Clientへ公開する値 |
| --- | --- | --- |
| Root | `/` | `/github` |
| Objectの配置path | `/my-rep/issues` | `/github/my-rep/issues` |
| Interface参照 | `/::issue.read` | `/github::issue.read` |
| Repository scopeの参照 | `/my-rep::issue.admin` | `/github/my-rep::issue.admin` |
| `entry` 型のpath | `/my-rep/issues/123` | `/github/my-rep/issues/123` |
別のUpstreamを`/gitlab` へ配置すれば、その`/::issue.read` は`/gitlab::issue.read` となり、GitHub側と衝突しない。
さらにこの合成Worldspace全体を`/services` へ配置するHostは、次のように再変換する。
``` text
/github/my-rep/issues -> /services/github/my-rep/issues
/github::issue.read -> /services/github::issue.read
/gitlab::issue.read -> /services/gitlab::issue.read
```
各Hostは自分がClientへ公開する座標系について責任を持つ。単にHTTP requestを中継してWorldspaceの配置を変えないproxyは、scopeを変更する必要がない。
### 変換する箇所
合成Hostは、表示だけでなくProtocol上の対応を一貫させる。
- `observe` のrequest pathをUpstream側へ変換し、返されるObjectの配置・`name` ・childrenが公開先のpath規則を満たすようにする。特にUpstream rootの空の`name` を非rootのmount先へそのまま公開しない。
- `Object.interfaces` のscopeを公開先へ変換する。
- Clientの`fetch_interface` 参照を適切なUpstreamとその参照へ対応付け、responseの`interface` はClientが要求した公開先の参照にする。
- `call_operation` のtarget pathとInterface参照をUpstream側へ対応付ける。転送先はlocal name単独ではなく完全な参照とtargetの対応で決める。
- Descriptorで`entry` と宣言されたargumentsをUpstream側へ、resultを公開先へ変換する。Record、list、union、Named typeを辿った内部の`entry` も対象にする。
通常の`string` 、opaqueな`json` 、documentation内のpathらしい文字列を推測して書き換えない。Application固有のdataに別の座標系がある場合は、そのApplication adapterの責務として扱う。
型として正しいUpstream pathであっても、公開先へ対応付けられないpathをそのまま返してはならない。公開subtreeの外への`entry` やscopeへの参照をどう扱うかは、明示的な対応付けまたは公開・実行の拒否を含め、Hostの合成方針として定める。Scopeを単に削除したりrootへ付け替えたりして制約を回避しない。
### 名前・同一性・validator
公開先の参照は、移動後のscopeとlocal nameの組に対して衝突なく登録する。配置変更後に同じlocal nameを維持しても、scopeが変わればProtocol上は別の参照である。古い参照と新しい参照の同一性をClientへ推測させない。
Scopeの変換だけでObjectの`ref` やvalidatorの安全性が保証されるわけではない。合成Hostは既存の同一性・観測整合性の規則も満たす必要がある。
- 異なるUpstreamのObjectへ同じ有効な公開`ref` を割り当てない。
- 同じ公開Interface参照とvalidatorの組を、異なるDescriptorへ対応付けない。
- Routingやmountの変更後に、古いObject validatorで別の対象へOperationを実行できないようにする。
- Upstream validatorをそのまま利用するかHost側で対応付け・wrapするかは、これらの保証を満たす範囲でHostが決める。
同じUpstream Objectを複数のscope配下へ配置すると、公開するInterface参照が配置ごとに変わり得る。現行Protocolは同じ有効な`ref` とvalidatorのObject representationが一致することを要求するため、異なるInterface集合を持つprojectionへ同じ公開identityを無条件に引き継げない。この場合の共通祖先scopeでの共有、projectionごとのidentity割当等は、採用前に整理する。
## Cacheと表示
Interface cacheはClient-facing Worldspaceの接続先・主体等のcontextで分離した上で、完全なscope付き参照とoptionalなvalidatorに基づいて扱う。異なるWorldspaceに同じ`/github::issue.read` があっても、同一Interfaceとはみなさない。ScopeはグローバルなURL namespaceではない。
[WIP Text View ](text-view.md )では公開先の参照をそのまま引用表示する。表示側でscopeを省略したり、Upstreamの参照へ戻したりしない。
``` text
object at "/github/my-rep/issues" {
name: "issues";
description: "RepositoryのIssue collection。";
interfaces: ["/github::issue.read"];
}
interface "/github::issue.read" {
operation list() -> { items: [entry] };
}
```
Text Viewがscopeを定義するのではなく、採用後のProtocol参照を表示する。現在のText View草案のopaque参照例は、Protocol側の採用時に更新する。
## 採用前に詰める点
- **Self-scopeとroot Object**: Object自身のpathをscopeとして許すか。Strict ancestorだけではroot Objectに利用可能なscopeがないため、rootの扱いを含めて決める。
- **参照構文**: path内の`::` と区切りの扱い、local nameの許可文字、canonicalな表記、既存opaque参照からの移行。現行のstring fieldを使う案を基本とし、具体的encodingは未確定とする。
- **Scopeの公開単位**: scope登録とそのpathのObjectの公開・削除の関係。祖先Object自身が同じInterfaceを公開することは要求しないが、registryのlifecycleを定める必要がある。
- **有効期間と移動**: scopeのrename、mountの付け替え、同じpathへの再登録、進行中callおよび古いcacheの扱い。
- **複数配置と部分合成**:同じObjectを複数scopeへ投影する場合の`ref` 、切り出したsubtreeの外にあるscope・`entry` への対応付け。
- **Failure semantics**:不正な参照構文、scope不適合、未公開scope / Interface、変換不能な`entry` に対するerrorと、既存の検証順・非公開情報の秘匿との整合。
本案はInterface参照のscopeとHostの公開座標変換を定義するためのものであり、Host内部のmount API、Provider登録API、合成設定の共通data structureを同時に規定しない。