Skip to content

Capabilities

В MF используются два списка с похожим названием, но разным назначением.

1. Возможности root runtime

Host объявляет, какие facades поддерживает его runtime:

ts
const runtime = createQueryRuntime({
    mode: "host",
    identity: {
        applicationId: "shell",
        version: "3.4.0",
    },
    capabilities: ["request", "fetch", "query", "mutation", "session"],
})

Допустимые значения фиксированы:

ЗначениеЧто означает
requestRuntime предоставляет scope.request.createStore.
fetchRuntime предоставляет scope.fetch.createStore.
queryRuntime предоставляет query/infinite factories, cache read и invalidation.
mutationRuntime предоставляет scope.mutation.createStore.
sessionВ host настроен общий session coordinator.

Это декларация совместимости. Она не разрешает конкретному remote все эти операции автоматически.

2. Требования remote к runtime

Remote contract или manifest описывает минимальные требования:

ts
const billingRuntimeRequirements = {
    protocolVersion: 2,
    minimumRuntimeVersion: "3.2.0",
    requiredRuntimeCapabilities: ["query", "mutation"],
} as const

Host применяет их при registration:

ts
runtime.createParticipantScope({
    participantId: "billing-remote",
    participantType: "remote",
    protocolVersion: billingRuntimeRequirements.protocolVersion,
    minimumRuntimeVersion: billingRuntimeRequirements.minimumRuntimeVersion,
    requiredRuntimeCapabilities: billingRuntimeRequirements.requiredRuntimeCapabilities,
    capabilities: {
        // permissions показаны ниже
    },
})

Если host слишком старый, protocol отличается или отсутствует mutation, scope не создаётся и UI remote не должен монтироваться.

Поля совместимости

Поле registrationTypeПроверка
protocolVersionnumberДолжен точно совпасть с host protocol.
minimumRuntimeVersionstringHost version должна быть не ниже.
requiredRuntimeCapabilitiesTQueryRuntimeCapability[]Все значения должны быть объявлены host.

3. Разрешения конкретного participant

Вложенное поле capabilities в registration отвечает не за поддержку API, а за доступ к данным:

ts
capabilities: {
  queryNamespaces: [
    ["billing", "invoices"],
    ["shared", "currency"],
  ],
  sharedQueryNamespaces: [
    ["shared", "currency"],
  ],
  mutationNamespaces: [
    "billing.pay",
    "billing.cancel",
  ],
  globalInvalidation: false,
}

Все поля participant capabilities

ПолеTypeDefault для remoteЧто разрешает
queryNamespacesreadonly TQueryKey[][]Создание, чтение и invalidation query с указанными префиксами.
sharedQueryNamespacesreadonly TQueryKey[][]Какие из разрешённых query могут разделяться между participants.
mutationNamespacesreadonly string[][]Mutation keys с указанными строковыми префиксами.
globalInvalidationbooleanfalseInvalidation без конкретного queryKey внутри data partition.

Для participantType: "remote" объект capabilities обязателен. Поля, которые не указаны, становятся закрытыми. Это deny-by-default.

Для application и host отсутствие namespace arrays означает unrestricted доступ, но явная политика всё равно проще для аудита.

Prefix matching query

При разрешении:

ts
queryNamespaces: [["billing"]]
Logical keyРезультат
["billing"]разрешён
["billing", "invoices"]разрешён
["billing", "invoice", "42"]разрешён
["support", "tickets"]запрещён
["billings"]запрещён

Чтобы ограничить remote сильнее:

ts
queryNamespaces: [
    ["billing", "invoices"],
    ["billing", "currency"],
]

Prefix matching mutation

При разрешении:

ts
mutationNamespaces: ["billing"]

разрешены billing, billing.pay и billing.invoice.cancel.

При более узком:

ts
mutationNamespaces: ["billing.pay"]

billing.cancel уже запрещён.

Всегда задавайте mutationKey у scoped MutationStore:

ts
scope.mutation.createStore({
    mutationKey: "billing.pay",
    request: (invoiceId: string, config) => api.pay(invoiceId, config),
})

Если ключ не указан, runtime создаёт default <participantId>.mutation. Он тоже должен соответствовать разрешённому namespace.

Shared namespace не расширяет доступ

sharedQueryNamespaces должен быть подмножеством фактически разрешённых queryNamespaces по смыслу:

ts
queryNamespaces: [["shared", "currency"]],
sharedQueryNamespaces: [["shared", "currency"]],

Shared prefix меняет owner partition cache, но не даёт доступа к чужому логическому namespace.

Каждый shared query требует definitionId.

Кто задаёт permissions

Remote может опубликовать декларацию необходимых namespaces как документацию, но host принимает окончательное решение:

ts
// Remote manifest просит billing namespace.
export const requestedCapabilities = {
    queryNamespaces: [["billing"]],
    mutationNamespaces: ["billing"],
} as const

Host не обязан доверять этому объекту напрямую. Он сравнивает запрос с allowlist и создаёт собственный registration. tenantId, userId и globalInvalidation remote определять не должен.

Почему запрет происходит до HTTP

Scope сначала проверяет logical key/mutation key, затем создаёт store или выполняет cache operation. Запрещённый namespace бросает ошибку до вызова API-функции. Это нужно проверять integration-тестом.

Следующая страница: все фасады scope.