Тема
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"],
})Допустимые значения фиксированы:
| Значение | Что означает |
|---|---|
request | Runtime предоставляет scope.request.createStore. |
fetch | Runtime предоставляет scope.fetch.createStore. |
query | Runtime предоставляет query/infinite factories, cache read и invalidation. |
mutation | Runtime предоставляет 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 constHost применяет их при 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 не должен монтироваться.
Поля совместимости
| Поле registration | Type | Проверка |
|---|---|---|
protocolVersion | number | Должен точно совпасть с host protocol. |
minimumRuntimeVersion | string | Host version должна быть не ниже. |
requiredRuntimeCapabilities | TQueryRuntimeCapability[] | Все значения должны быть объявлены 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
| Поле | Type | Default для remote | Что разрешает |
|---|---|---|---|
queryNamespaces | readonly TQueryKey[] | [] | Создание, чтение и invalidation query с указанными префиксами. |
sharedQueryNamespaces | readonly TQueryKey[] | [] | Какие из разрешённых query могут разделяться между participants. |
mutationNamespaces | readonly string[] | [] | Mutation keys с указанными строковыми префиксами. |
globalInvalidation | boolean | false | Invalidation без конкретного 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 constHost не обязан доверять этому объекту напрямую. Он сравнивает запрос с allowlist и создаёт собственный registration. tenantId, userId и globalInvalidation remote определять не должен.
Почему запрет происходит до HTTP
Scope сначала проверяет logical key/mutation key, затем создаёт store или выполняет cache operation. Запрещённый namespace бросает ошибку до вызова API-функции. Это нужно проверять integration-тестом.
Следующая страница: все фасады scope.