Files

30 KiB

Rust API Model

Status

WIP HostをRustで実装する際、Interfaceの宣言とObjectの実装をどう組み合わせて公開するか。このdraftは、そのための概念的なAPI modelを提案する。

Trait名、attribute、derive、generated type名、method signatureは説明用であり、具体的なRust API syntaxは確定していない。Ownership、lifetime、async runtime、error type、macro crate構成も未決定である。

Protocol上の確定事項はCore Model、Object tree / Schema、Operation and Interface、Protocolに従う。動的なObject projectionの運用要件はDynamic Worldspace Projection、対応するTypeScript modelはTypeScript API Modelを参照する。

目的

Rust APIでは、Interfaceをtraitで宣言し、structで実装するmodelを自然に表現できるようにする。ただし、structが実装しているInterfaceと、ある時点で公開するInterfaceは分けて選べるようにする。

  1. Rust traitでWIP Interfaceを宣言する。
  2. Rust structが、Protocol Objectとして公開するmetadataを提供する。
  3. 同じstructが、必要に応じて公開する0個以上のInterface traitを実装する。
  4. Objectを返す時点で、公開するInterfaceのordered setを明示的に選択する。
  5. Trait declarationからInterface Descriptor、Value codec、validation、Operation dispatchに必要なcodeを生成する。
  6. Struct instanceをWorldspace全体について常駐させず、observeとcall_operationの解決時にdata-drivenに生成できる。

Protocol上でObjectとInterfaceを関連付けるのはObject.interfacesだけである。Operation implementationを呼び出すためのRust内部codeはSDK / Host Runtimeの実装詳細として扱い、新しいProtocol conceptは導入しない。

Conceptual model

Interface trait

WIP Interfaceは、Rust traitとして宣言する。

#[wip::interface]
trait FileInterface {
    /// Reads bytes from the file.
    async fn read(
        &self,
        /// Byte offset from the beginning of the file.
        offset: Option<i64>,
        /// Maximum number of bytes to return.
        length: Option<i64>,
    ) -> Bytes;

    /// Replaces the file contents.
    async fn write(
        &self,
        content: Bytes,
    ) -> WriteResult;
}

このcodeはconceptual syntaxであり、具体的なAPIとして確定したものではない。Option<i64>の対応範囲、Bytes wrapper、documentationの取得方法、async methodとerrorの具体形は別途決定する。

Trait declarationとProtocol上の宣言は、次のように対応付ける。

  • 一つのtrait declarationを、一つのInterface Descriptorを生成するRust側definitionとして扱う。
  • Trait名はgenerated Rust itemやlocal registrationを識別するために使用できるが、Protocol InterfaceDescriptorのfieldや構造化されたInterface参照としてそのまま公開しない。
  • Trait methodをOperation Declarationとして扱う。
  • ReceiverはProtocol parameterに含めない。
  • Method parameterを名前付きOperation parameterとして扱う。
  • Return typeをOperation return typeとして扱う。
  • Rust typeをDescriptorのTypeExprとProtocol Valueへ対応付ける。
  • Documentationをtrait、method、parameter、return、関連typeから取得できるようにする。

したがって、Descriptorのshapeを決めるのはtrait declarationである。各structがそのtraitをどう実装するかによって、Descriptorは変化しない。

Object implementation

その時点でWorldspaceへ投影するObjectは、Host内部ではRust structとして表現できる。

struct File {
    name: String,
    path: PathBuf,
    reference: Option<String>,
    validator: Option<Vec<u8>>,
}

公開metadataを提供するために、structが概念上のWipObject traitを実装する形を想定する。

impl WipObject for File {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> Option<&str> {
        None
    }

    fn reference(&self) -> Option<&str> {
        self.reference.as_deref()
    }

    fn validator(&self) -> Option<&[u8]> {
        self.validator.as_deref()
    }
}

WipObjectは、Protocol Objectの公開metadataのうち、publication時に選ぶInterface集合以外を提供する。返す値のownershipやborrow形式は未決定である。

Structが保持する情報のすべてを公開metadataにするわけではない。次の情報はWipObjectがProtocol metadataとして返す値に含めないが、implementing structのprivate fieldとして保持してよい。

  • Absolute path。PathはObjectのfieldではなく、Worldspace resolutionのcontextである。
  • Children。ChildrenはTree上の配置contextである。
  • 固定されたInterface集合。InterfaceはObjectを公開する時点で追加する。
  • Backend connection、file descriptor、credential等のHost-private state。

たとえば、structのpathやbackend handleはHost内部のOperation implementationで使用できる。ただし、WipObjectのmetadataやProtocol Objectへ自動的にserializeする対象にはしない。

Interface implementation

Object implementationに使うstructは、公開したいInterface traitを実装する。

impl FileInterface for File {
    async fn read(
        &self,
        offset: Option<i64>,
        length: Option<i64>,
    ) -> Bytes {
        // self.pathが示す現在のfileを読む
    }

    async fn write(
        &self,
        content: Bytes,
    ) -> WriteResult {
        // self.pathが示す現在のfileへ書く
    }
}

Trait implementationが提供するのはOperationのbehaviorである。Interface Descriptorの生成元は、このimplementationではなくInterface trait declarationとする。

また、traitを実装しただけでInterfaceを公開することにはしない。Rustには、あるtypeが実装しているtraitを列挙する一般的なreflectionがなく、一つのstructが複数のInterface traitを実装することもあるためである。

Object publication

ObjectをWorldspaceへ返す時点で、struct instanceと、公開する0個以上のInterface Declarationを明示的に組み合わせる。Interfaceは追加順を保持するordered setとし、同じDefinitionの重複追加は認めない。

次のconceptual syntaxでは、解決したFileに公開するInterfaceを追加している。

let file = File::resolve(path).await?;
let object = PublishedObject::new(file)
    .expose(FileInterfaceDefinition);

各InterfaceDefinitionは、#[wip::interface]が生成するconceptualなcompanion definitionを表す。実際の名前も、associated item、module、valueのどの形で生成するかも未決定である。

Publication APIが各Definitionの追加に要求するboundは、概念的には次の形になる。

impl<T: WipObject> PublishedObject<T> {
    fn expose<I>(self, interface: I) -> Self
    where
        I: InterfaceDefinition<T>;
}

impl<T: FileInterface> InterfaceDefinition<T>
    for FileInterfaceDefinition
{
    // generated registration and dispatch metadata
}

このboundで、公開する値がObject metadataを提供し、追加する各InterfaceのOperation implementationも提供することをcompile timeに確認できる。Interfaceを一つも追加しないObjectも表現できる。

PublishedObjectはHost SDK内のconceptual typeであり、Protocol typeには追加しない。Host Runtimeはこの値からProtocol Objectを構築する。

Object {
    name: object.name(),
    description: object.description(),
    interfaces: selected_interface_references,
    r#ref: object.reference(),
    validator: object.validator(),
}

selected_interface_referencesには、Hostが各generated Interface Definitionの登録または公開時に割り当てる構造化参照{ scope, name }を、publication APIへの追加順で並べる。同じ(scope, name)の重複は拒否し、Objectの配置pathに対する祖先制約も検証する。Macroが参照をcompile timeに固定することは要求しない。

observeでObjectに含めるのは、Descriptor自体ではなく、このInterface参照のordered setだけである。Clientが各参照をfetch_interfaceすると、Host Runtimeはtrait declarationから生成したDescriptorと、存在する場合はInterface validatorを返す。

同じ参照とvalidatorの組に対応するDescriptorは、Host上で共有およびcacheできる。このため、Object instanceごとにDescriptorを再生成したり再送したりする必要はない。

Macroが生成する責務

#[wip::interface]には、宣言からDescriptorを生成するだけでなく、Valueの変換・検証とdispatchに必要なcodeを提供する役割を想定する。

  • Interface Descriptorを構築するdefinition。
  • Trait methodとOperation Declarationの対応情報。
  • ArgumentsのProtocol Value decoder。
  • ResultのProtocol Value encoder。
  • Argumentsとresultのruntime validation。
  • Operation名から対応するtrait methodを呼び出すdispatch code。
  • 任意のT: WipObject + InterfaceTraitについて、そのInterfaceをPublishedObject<T>へ追加するための型付きentry point。
  • HostがInterface参照、optional validator、Descriptorを登録、取得、cacheするためのidentity。

対象structがInterface traitを実装しているかは、publication時のgeneric boundで確認する。MacroがRust codebase内のtrait implementationを探索したり列挙したりすることは要求しない。

Generated dispatch codeはRust SDK内部の実装詳細である。Host内部のdispatch mechanismやRust type名等はProtocolへ公開しない。

Rust typeとProtocol type

Interface traitで使うRust typeには、DescriptorのTypeExprとProtocol Valueへの対応付けが必要となる。概念的には、deriveまたは明示的なcodec implementationを使うことを想定する。

#[derive(WipType)]
struct WriteResult {
    bytes_written: i64,
}
#[derive(WipType)]
enum ReadResult {
    Data { content: Bytes },
    EndOfFile,
}

このsyntaxも未決定である。Typeの対応付けでは、少なくとも次を表現できる必要がある。

  • Unit、Boolean、Integer、Number、String、Bytes、Json、Entry。
  • Record、List、Enum、Union。
  • Descriptor内のNamed type。
  • Requiredとoptionalなfieldおよびparameter。
  • Documentation。

RustのOption<T>をoptional field / parameter以外でも許可するかは、まだ決めていない。EnumをProtocol EnumまたはUnionへどう対応付けるか、newtypeをどう扱うかも未決定である。

型の対応付けとは別に、Protocol boundaryでの検証も必要となる。Rust SDKは、integerのProtocol相互運用範囲、finite number、unknown field、Union shape、Entry path等をそこで検証しなければならない。

Dynamic Worldspaceでのlifecycle

observe

Projection implementationはrequest時にbackend dataを解決し、その時点のstruct instanceを生成する。

async fn resolve_file(path: &WipPath) -> Result<PublishedObject> {
    let stat = filesystem.stat(path).await?;

    let file = File {
        name: stat.name,
        path: stat.path,
        reference: make_reference(&stat),
        validator: make_validator(&stat),
    };

    Ok(PublishedObject::new(file)
        .expose(FileInterfaceDefinition))
}

Host RuntimeはPublishedObjectからProtocol Objectを返す。Responseを生成した後は、このFile instanceを破棄してよい。Worldspace内の全fileに対応するinstanceを保持する必要はない。

Requested depthが正の場合は、Directory listingで発見した各entryについて、その時点のstruct instanceを生成して公開できる。Providerがdirectory全体を先に収集し、Vecとして返すことは要求しない。ただし、一つのparentのobservationを成功させるには、公開されたindexable childrenをclean exhaustionまで列挙する必要がある。

let mut entries = filesystem.discover_children(path, budget).await?;

while let Some(entry) = entries.next().await? {
    let Some(child) = project_current_object(
        entry.path(),
        context,
    ).await? else {
        continue;
    };

    observation.push_child(child)?;
}

discover_children、budget、observationは具体APIではなく、conceptualな表記である。Childの発見と公開には、次のsemanticsを要求する。

  • 各childを現在の主体についてprojectし、非公開Objectとそのsubtreeを返さない。
  • 公開されたindexable childrenをすべて列挙し、clean exhaustionで終了する。
  • Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なsuccess responseを返さない。
  • Requested depthに達したnodeではchild discoveryを行わず、childrenを未観測として省略する。
  • 異なるconcrete published typeを、Providerから共通のreturn typeとして扱える。

具体的なreturn type、internal streaming、backpressure、type erasure、error propagationは未決定である。

call_operation

Call時には現在のObjectを解決し直す。HostはrequestのTargetとInterfaceTargetをHost Runtimeへ渡し、Runtimeは一つのatomic execution boundaryに入ってから、target.pathを現在のObject implementationとstableなbackend対象へ解決する。

runtime.call_current(
    target,
    interface,
    operation,
    arguments,
    context,
    |path, context| resolve_current_projection(path, context),
).await

resolve_current_projectionとruntime.call_currentを実際のAPIとして採用するかは決めていない。ResolverはRuntimeが開始したexecution boundary内で呼ばれ、そのboundary中にObject identityとbackend対象を安定して保持するhandleまたはtransaction contextを含む値を返す必要がある。上のexampleは、次の要件を表す。

  • 過去のobserveで作成したstruct instanceを保持していなくてもOperationを実行できる。
  • Call時の現在の主体、Object visibility、Object metadata、Interface集合を同じProjection pathで再解決し、Objectが非公開ならNotFoundを返す。
  • Requestのinterface.referenceが現在のObject.interfacesに含まれ、self-scopeを含む祖先制約を満たすことを確認する。指定されたinterface.scope_refと現在のscope Objectのrefも照合し、満たせない場合はInterfaceMismatchを返す。scope_refの省略だけでは拒否しない。
  • 現在のInterfaceにOperationが宣言されていなければOperationNotFound、Operationを公開しつつ実行だけを拒否する場合はPermissionDeniedを返す。
  • interface.validatorを現在のInterface validatorへ照合する。Mutable InterfaceにvalidatorがなければInterfaceValidatorRequired、不一致ならInterfaceValidatorMismatchを返す。
  • 現在のProjected Object、Object validator、選択されたInterfaceとそのvalidator、Descriptor、Operation、Rust側のObject implementation、実際のbackend対象を一つのexecution boundary内で一致させる。
  • Object validatorが指定されている場合は、現在のObject identityと保護対象状態の両方へ照合し、同じexecution boundary内でOperationを実行する。
  • Pathが別Objectを指す場合は、provider-local revisionや内容が同じでもObject validatorを一致させない。
  • Providerのvalidatorがobject-localなrevisionだけの場合、HostはObject identityを含む形へnamespace化またはwrapできなければならない。
  • Object validatorが省略され、Operationがvalidatorを要求する場合は、実行せずValidatorRequiredを返す。
  • Object validatorなしの実行をOperationが許可する場合は、実行時にpathが指す現在Objectへ無条件に実行する。
  • Macro-generated dispatchが、選択されたInterface traitのmethodを現在のstruct instanceに対して呼び出す。異なるInterfaceの同名Operationをmergeまたは曖昧に解決しない。
  • Operation実行後の現在状態に対応するvalidatorを、必要に応じてCallOperationResponseへ返せる。

Object validatorとObject identityの関連付けに、Client-facingなrefは必須ではない。ProviderまたはHostはHost-privateなidentityを使用できる。ただし、identity confirmation、Object validator比較、Interface membershipとvalidator比較、DescriptorとOperation implementationの固定、authorization、Operation実行は、一つのatomic boundaryで行う。

実行後のvalidatorをどこから取得するかも未決定である。同じstruct instanceから再計算する方式、Operation resultとともに返す方式、backendを再観測する方式が考えられるが、いずれもresultを生成した実行後状態とvalidatorを対応させる必要がある。

StructがPathBufだけを保持している場合、validator比較後にpathを無条件で開き直すと、対象差し替えに対するatomicityを保証できないことがある。Rust APIには、file descriptor、transaction、conditional update、lock等でbackend固有のatomicityを実現できることを求める。そのための抽象化は未決定である。

複数Interfaceの実装

一つのstructは、複数のInterface traitを実装できる。

impl ReadOnlyFileInterface for File {
    // ...
}

impl WritableFileInterface for File {
    // ...
}

そのうち、現在の主体へ公開する0個以上のInterfaceをpublication時に選び、表示したい順に追加する。

let mut object = PublishedObject::new(file)
    .expose(ReadOnlyFileInterfaceDefinition);

if authorization.can_write() {
    object = object.expose(WritableFileInterfaceDefinition);
}

object

Protocol Objectのinterfacesは追加順を保持するordered setであり、同じInterface参照の重複は拒否する。各Interface Descriptorは独立したOperation集合を宣言するため、複数のtrait implementationを一つのDescriptorへ暗黙にmergeしない。

異なるInterfaceが同じOperation名を宣言しても、dispatchはrequestのInterfaceTarget.referenceとOperation名の組で一意に決まる。表示上のqualified表記はText Viewの参照表記に従い、signature全体をProtocol request fieldへparseし直さない。

Rust traitのsupertrait、trait alias、複数traitの組み合わせをDescriptor生成にどう対応付けるかは未決定である。これらによって、Protocol上のInterface inheritanceを暗黙に導入することはしない。

固定Interface向けのsugar

一つのRust typeを常に同じInterface集合で公開する場合は、次のようなsugarを追加してよい。

#[wip::object(interfaces = [ReadOnlyFileInterface, WritableFileInterface])]
struct File {
    // ...
}

別のconceptualな形として、WipObjectのassociated itemにdefault Interface集合を指定することも考えられる。

ただし、core modelの基礎はexplicit publicationとする。主体、policy、resource kind等によって、同じstructへ異なるInterface subsetを追加できる必要があるため、固定Interface集合をcore modelにはしない。

Sugarを使う場合も、対象typeが指定したすべてのInterface traitを実装していることと、同じInterfaceを重複していないことを確認する必要がある。

HostへのInterface登録

Generated Interface Definitionには、Hostへの登録を通じて構造化されたInterface参照を割り当てる必要がある。参照の絶対scopeとlocal name、および配置pathへの祖先制約は2.2. Operation and Interfaceに従う。

Self-scopeは許可され、scopeはそのpathのObjectに紐づく。Scope ObjectのoptionalなrefはProtocolのscope_refとして観測・検証する。refがなければidentityを追跡できないことも許容し、scopeの移動追跡や独立したscope validatorは導入しない。

登録方法としては、conceptualにはeager registrationとlazy registrationの両方が考えられる。次の例はscope / nameを渡す具体構文を省略しており、暗黙のroot scopeを定めてはいない。登録時のcontextと引数の具体APIも引き続き未決定である。

let file_read_interface = host.register_interface(ReadOnlyFileInterfaceDefinition)?;
let file_write_interface = host.register_interface(WritableFileInterfaceDefinition)?;
let object = PublishedObject::new(file)
    .expose(file_read_interface)
    .expose(file_write_interface);

また、Hostが最初のpublication時に各definitionをinternする方式でもよい。どちらの登録方法でも、次の条件を満たす必要がある。

  • MutableなInterface参照ではfetch_interfaceがvalidatorを返し、同じ参照とvalidatorの組から異なるDescriptorを返さない。
  • Validatorを返さないInterface参照は、その参照の有効期間中Descriptorをimmutableにする。
  • Descriptorを更新する場合は同じ参照へ新しいvalidatorを発行するか、新しいInterface参照を使用する。
  • Objectのinterfacesへ設定した各Interface参照のDescriptorを、その主体からfetch_interfaceできる。
  • 一つのObjectへ同じInterface参照を重複して追加しない。
  • 構造的に同じDescriptorを同じ参照へdeduplicateすることは要求しない。
  • Rust type名やprocess-local addressを、そのままportableなProtocol semanticsとして扱わない。

Request contextとauthorization

Operation implementationが必要とする情報は、Object instanceだけとは限らない。Request contextとして、次の情報も必要になり得る。

  • 現在の主体。
  • Cancellationとdeadline。
  • Tracing / audit context。
  • Host serviceやbackend connectionへのaccess。
  • Resource limit。

このcontextをmethod parameter、receiver wrapper、generated context、task-local stateのどれで渡すかは未決定である。どの形でも、Protocol parameterとして宣言された値とHostが注入するcontextは明確に区別する必要がある。

Interface集合はObject publication時に選択するだけでなく、Operation call時にもmembership、Interface validator、authorizationを再評価できなければならない。以前のobserveで公開したInterface集合だけを根拠に、現在のOperation実行を許可することはできない。

Resultのvisibility checkも、通常のstructural Value validationとは別に必要となる。Hostは、Operation resultのRecord、List、Union等にnestedしたすべてのEntryについて、現在の主体にそのpathを認識させてよいかを再帰的に確認できなければならない。この確認をMacro-generated code、Projection、Host Runtimeのどこで行うかは未決定である。

Argumentとして受け取ったEntry pathについても、pathの所持をauthorizationの証明にはしない。参照先へ作用する時点で、現在の主体について認可する。

Error model

Rust methodのerror表現には、次の四種類を区別できることを求める。

  • Protocol validationまたはdispatch failure。
  • Effectがcommitされていないと保証できるHost / backend implementation failure。
  • Operationの未実行またはrollbackを保証できないOperationOutcomeUnknown。
  • Interfaceがreturn typeとして宣言したdomain failure。

現在のProtocolはdomain failureをOperation resultのUnion caseとして表す。RustのResult<T, E>をdomain Union、Host error、またはその両方のどれに対応付けるかは未決定である。

Rust panic、result encode failure、宣言型に違反するimplementation resultを、Macroがdomain failureへ自動変換することは認めない。Host Runtimeは、effectがcommitされていないと保証できる場合だけInternalへmappingし、保証できなければOperationOutcomeUnknownへmappingする。Clientへ送信済みかどうか不明なtransport切断についても、自動retryはしない。

要件

このdraftのRust API modelに求める要件は、少なくとも次のとおりである。

  • Interface Descriptorをtrait declarationから生成し、trait implementationごとに変化させない。
  • StructがWipObjectと、追加する各Interface traitを実装していることをcompile timeに確認する。
  • 0個以上のInterfaceをpublication時に明示し、実装済みtraitの自動列挙に依存しない。
  • Objectのpath、children、Interface集合をstructへ恒久的に固定しない。
  • Struct instanceをrequest scopeで生成および破棄できる。
  • 全Object instanceの事前登録を要求しない。
  • Direct childrenを現在の主体についてprojectし、非公開Objectとそのsubtreeを除外しつつlazyに発見し、success responseではclean exhaustionまで列挙する。
  • Backend failure、cancellation、resource budget超過をclean exhaustionとして扱わず、部分的なobservationをsuccess responseとして返さない。
  • 同じstructへ複数のInterfaceを同時に追加でき、追加順を保持し、重複を拒否できる。
  • 異なるInterfaceの同名Operationを、Interface参照とOperation名の組でdispatchできる。
  • Protocol boundaryでarguments、result、Object、Descriptorを検証する。
  • Operation result内のnestedなEntryへ、現在の主体についてvisibility checkを適用できる。
  • Host-private fieldをProtocol ObjectまたはValueへ暗黙にserializeしない。
  • Call時の現在の主体、Object visibility、Object validator、Interface membershipとvalidator、Descriptor、Operation、Rust側implementation、backend対象を一貫したexecution boundaryで扱う。
  • 非公開Object、Interface非membership、非公開Operation、公開済みOperationの実行拒否をそれぞれNotFound、InterfaceMismatch、OperationNotFound、PermissionDeniedへ対応付ける。
  • Interface validatorの必須・不一致をInterfaceValidatorRequired、InterfaceValidatorMismatchへ対応付ける。
  • Object validatorを現在のObject identityと保護対象状態へ結び付け、別Objectに同じprovider-local revisionがあっても一致させない。
  • Object validator比較、identity confirmation、Interface membershipとvalidator比較、DescriptorとOperation実装の固定、authorization、Operation実行を同じatomic boundaryで行う。
  • Object validatorを要求するOperationへの省略をValidatorRequiredとして拒否できる。
  • Provider、Host Runtime、macro-generated codeのerrorを、effect未commitを保証できるInternalと、保証できないOperationOutcomeUnknownへ安全に対応付けられる。
  • InvalidなObject、Descriptor、Operation resultをsuccess responseとして送信せず、Host-side validation failureをdomain failureへ変換しない。
  • Rust SDK固有の型やlifetimeをProtocol semanticsとして要求しない。

非目標

次の事項は、このdraftでは決定しない。

  • 最終的なcrate、module、trait、attribute、deriveの名前。
  • Proc macroの内部実装。
  • 特定のasync runtime。
  • Object instanceの常駐registryまたはactor model。
  • Rust trait objectをwireへ公開すること。
  • Rust trait inheritanceをProtocol Interface inheritanceとして扱うこと。
  • Backend entityとRust structまたはProtocol Objectの一対一対応。
  • Client SDKのtyped proxy API。

未決定事項

  • WipObjectの具体的なmethodとmetadata ownership。
  • Object metadata取得をsyncまたはasyncのどちらにするか。
  • Publication APIがObject instanceをown、borrow、Arc化するか。
  • Generated Interface Definitionの具体的なRust表現。
  • Interface登録とscope / local name割当のlifecycle。
  • Trait、Operation、parameter名のrename規則。
  • Rust doc commentとProtocol Documentationの対応。
  • Supported Rust typeとTypeExprの対応。
  • Option<T>を許可する位置。
  • Generic method、associated type、lifetime parameterをInterface traitで許可するか。
  • &self、&mut self、owned receiverのどれをOperationで許可するか。
  • Async methodをnative syntax、generated boxed future、別macroのどれで扱うか。
  • Request contextとProtocol parameterのAPI上の分離。
  • NestedなEntry resultのvisibility checkをMacro-generated code、Projection、Host Runtimeのどこで行うか。
  • Domain failure、Host error、panicのRust上の表現。
  • Macro-generated dispatchとHost Runtimeの責務境界。
  • Published ObjectをProviderから返す際のtype erasure。
  • Validator比較とOperation実行を囲むbackend-specificなatomicity abstraction。
  • Provider-local validatorをObject identityへnamespace化またはwrapするHost API。
  • Operation実行後validatorをObject instance、Operation implementation、Providerのどこから取得するか。
  • 主体ごとに異なるInterface combinationを追加したPublished Objectのconcrete return typeをどう統一するか。
  • Rust client APIを同じtrait declarationから生成するか。

これらの選択は、静的service objectに使えるだけでは足りない。Filesystem、database row、virtual aggregate、主体別Interface compositionも、同じAPIで実装できるかを評価して決定する。