docs: draft scoped interface references and composition

This commit is contained in:
2026-10-07 03:28:34 +09:00
parent 456021b4ec
commit c4fdff5b6b
4 changed files with 167 additions and 2 deletions
+2
View File
@@ -12,6 +12,8 @@ Interface実装者は、RustのtraitやTypeScriptのdefinition API等を使っ
Interface Descriptorは人間が手書きするschema fileではない。Hostが`fetch_interface`で公開し、Clientが参照単位でcacheするprotocol dataである。 Interface Descriptorは人間が手書きするschema fileではない。Hostが`fetch_interface`で公開し、Clientが参照単位でcacheするprotocol dataである。
Interface参照へWorldspaceのpath scopeを導入する変更案は、[Scoped Interface References(草案)](draft/interface-scope.md)で扱う。現行Protocolのopaque参照を置き換えるための草案であり、本章の確定仕様へはまだ適用しない。
## Interface Descriptor ## Interface Descriptor
一つのInterface Descriptorは一つのInterfaceを定義し、少なくとも次を保持する。 一つのInterface Descriptorは一つのInterfaceを定義し、少なくとも次を保持する。
+1 -1
View File
@@ -262,7 +262,7 @@ Composition後も、同じ主体へ公開されたWorldspace全体で`ref`とInt
- 同じ有効なInterface参照とvalidatorの組を異なるDescriptorへ対応付けない。 - 同じ有効なInterface参照とvalidatorの組を異なるDescriptorへ対応付けない。
- Validator比較がProvider routingの変更によって別Objectへ適用されない。 - Validator比較がProvider routingの変更によって別Objectへ適用されない。
具体的なnamespaceおよび発行方式は決定しない。 Interface参照のnamespace案は[Scoped Interface References(草案)](interface-scope.md)で扱う。Worldspaceの絶対pathをscopeとし、Objectの祖先制約と、合成後の公開位置に合わせたHostの参照変換を検討する。Objectの`ref`の具体的な発行方式は引き続き未決定とする。
ただし、mount API、path routing、Provider間のparent / child関係、同名entry conflict、cross-provider Operation、transaction boundaryは未決定である。ProtocolはHost内部のcomposition方式を公開しないため、Host APIでこれらをどこまで標準化するかを別途検討する。 ただし、mount API、path routing、Provider間のparent / child関係、同名entry conflict、cross-provider Operation、transaction boundaryは未決定である。ProtocolはHost内部のcomposition方式を公開しないため、Host APIでこれらをどこまで標準化するかを別途検討する。
+163
View File
@@ -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を同時に規定しない。
+1 -1
View File
@@ -20,7 +20,7 @@
### Field semantics ### Field semantics
- `Object.interfaces`の要素、`fetch_interface`の`interface`、`InterfaceTarget.reference`で使用するopaque参照のscopeと有効期間。 - `Object.interfaces`、`fetch_interface`、`InterfaceTarget.reference`の参照scopeと有効期間。[Scoped Interface References(草案)](interface-scope.md)で、絶対pathによるscope、Objectの祖先制約、proxy / Worldspace合成時のHostによる座標変換を検討する。
### Type / Value ### Type / Value