From 009cdd91f0cc3ea77ce88a333ee08817d2d4bb57 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 02:01:08 +0000 Subject: [PATCH 01/10] docs: establish Ukrainian locale and entry pages --- docusaurus.config.js | 7 +- i18n/uk/code.json | 364 ++++++++++++++++++ .../options.json | 14 + .../current.json | 94 +++++ .../current/installation.md | 65 ++++ .../current/introduction.md | 157 ++++++++ .../version-1.x.json | 38 ++ i18n/uk/docusaurus-theme-classic/footer.json | 46 +++ i18n/uk/docusaurus-theme-classic/navbar.json | 22 ++ 9 files changed, 806 insertions(+), 1 deletion(-) create mode 100644 i18n/uk/code.json create mode 100644 i18n/uk/docusaurus-plugin-content-blog/options.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/installation.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/introduction.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/version-1.x.json create mode 100644 i18n/uk/docusaurus-theme-classic/footer.json create mode 100644 i18n/uk/docusaurus-theme-classic/navbar.json diff --git a/docusaurus.config.js b/docusaurus.config.js index 9d27994bcc..1683d67f75 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -53,7 +53,11 @@ const config = { i18n: { defaultLocale: 'en', - locales: ['en'], + locales: ['en', 'uk'], + localeConfigs: { + en: {label: 'English', htmlLang: 'en'}, + uk: {label: 'Українська', htmlLang: 'uk'}, + }, }, presets: [ @@ -202,6 +206,7 @@ const config = { dropdownActiveClassDisabled: true, }, {to: '/blog', label: 'Blog', position: 'left'}, + {type: 'localeDropdown', position: 'right'}, { type: 'custom-githubStar', href: 'https://github.com/durable-workflow/workflow', diff --git a/i18n/uk/code.json b/i18n/uk/code.json new file mode 100644 index 0000000000..c8956b8dee --- /dev/null +++ b/i18n/uk/code.json @@ -0,0 +1,364 @@ +{ + "theme.ErrorPageContent.title": { + "message": "На сторінці стався збій.", + "description": "The title of the fallback page when the page crashed" + }, + "theme.BackToTopButton.buttonAriaLabel": { + "message": "Прокрутити до початку", + "description": "The ARIA label for the back to top button" + }, + "theme.blog.archive.title": { + "message": "Архів", + "description": "The page & hero title of the blog archive page" + }, + "theme.blog.archive.description": { + "message": "Архів", + "description": "The page & hero description of the blog archive page" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "Навігація по сторінці списку блогів", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "Наступні записи", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "Попередні записи", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "Навігація по сторінці посту блогу", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "Наступний пост", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "Попередній пост", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.tags.tagsPageLink": { + "message": "Переглянути всі теги", + "description": "The label of the link targeting the tag list page" + }, + "theme.colorToggle.ariaLabel.mode.system": { + "message": "Автоматичний режим", + "description": "The name for the system color mode" + }, + "theme.colorToggle.ariaLabel.mode.light": { + "message": "Світлий режим", + "description": "The name for the light color mode" + }, + "theme.colorToggle.ariaLabel.mode.dark": { + "message": "Темний режим", + "description": "The name for the dark color mode" + }, + "theme.colorToggle.ariaLabel": { + "message": "Перемикання між темним та світлим режимом (зараз використовується {mode})", + "description": "The ARIA label for the color mode toggle" + }, + "theme.docs.breadcrumbs.navAriaLabel": { + "message": "Навігаційний ланцюжок поточної сторінки", + "description": "The ARIA label for the breadcrumbs" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "сторінка документації", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "Попередня сторінка", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "Наступна сторінка", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.versionBadge.label": { + "message": "Версія: {versionLabel}" + }, + "theme.docs.tagDocListPageTitle.nDocsTagged": { + "message": "Одна сторінка|{count} сторінки|{count} сторінок", + "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.tagDocListPageTitle": { + "message": "{nDocsTagged} з тегом \"{tagName}\"", + "description": "The title of the page for a docs tag" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "Це документація для майбутньої версії {siteTitle} {versionLabel}.", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "Це документація {siteTitle} для версії {versionLabel}, яка вже не підтримується.", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "Актуальна документація знаходиться на сторінці {latestVersionLink} ({versionLabel}).", + "description": "The label used to tell the user to check the latest version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "остання версія", + "description": "The label used for the latest version suggestion link label" + }, + "theme.common.editThisPage": { + "message": "Відредагувати цю сторінку", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "Пряме посилання на {heading}", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": " {date}", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": " від {user}", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "Останнє оновлення{atDate}{byUser}", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.navbar.mobileVersionsDropdown.label": { + "message": "Версії", + "description": "The label for the navbar versions dropdown on mobile view" + }, + "theme.NotFound.title": { + "message": "Сторінку не знайдено", + "description": "The title of the 404 page" + }, + "theme.tags.tagsListLabel": { + "message": "Теги:", + "description": "The label alongside a tag list" + }, + "theme.admonition.caution": { + "message": "обережно", + "description": "The default label used for the Caution admonition (:::caution)" + }, + "theme.admonition.danger": { + "message": "небезпека", + "description": "The default label used for the Danger admonition (:::danger)" + }, + "theme.admonition.info": { + "message": "інформація", + "description": "The default label used for the Info admonition (:::info)" + }, + "theme.admonition.note": { + "message": "примітка", + "description": "The default label used for the Note admonition (:::note)" + }, + "theme.admonition.tip": { + "message": "порада", + "description": "The default label used for the Tip admonition (:::tip)" + }, + "theme.admonition.warning": { + "message": "попередження", + "description": "The default label used for the Warning admonition (:::warning)" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "Закрити", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.sidebar.navAriaLabel": { + "message": "Навігація за останніми постами у блозі", + "description": "The ARIA label for recent posts in the blog sidebar" + }, + "theme.DocSidebarItem.expandCategoryAriaLabel": { + "message": "Розгорнути категорію в сайдбарі '{label}'", + "description": "The ARIA label to expand the sidebar category" + }, + "theme.DocSidebarItem.collapseCategoryAriaLabel": { + "message": "Згорнути категорію в сайдбарі '{label}'", + "description": "The ARIA label to collapse the sidebar category" + }, + "theme.IconExternalLink.ariaLabel": { + "message": "(відкривається в новій вкладці)", + "description": "The ARIA label for the external link icon" + }, + "theme.NavBar.navAriaLabel": { + "message": "Головна", + "description": "The ARIA label for the main navigation" + }, + "theme.navbar.mobileLanguageDropdown.label": { + "message": "Мови", + "description": "The label for the mobile language switcher dropdown" + }, + "theme.NotFound.p1": { + "message": "На жаль, ми не змогли знайти сторінку, яку ви запитували.", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "Будь ласка, зверніться до власника сайту, з якого ви перейшли на це посилання, щоб повідомити, що посилання не працює.", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.TOCCollapsible.toggleButtonLabel": { + "message": "Зміст цієї сторінки", + "description": "The label used by the button on the collapsible TOC component" + }, + "theme.blog.post.readMore": { + "message": "Читати далі", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.readMoreLabel": { + "message": "Докладніше про {title}", + "description": "The ARIA label for the link to full blog posts from excerpts" + }, + "theme.blog.post.readingTime.plurals": { + "message": "{readingTime} хв. читання|{readingTime} хв. читання|{readingTime} хв. читання", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.CodeBlock.copy": { + "message": "Копіювати", + "description": "The copy button label on code blocks" + }, + "theme.CodeBlock.copied": { + "message": "Скопійовано", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "Копіювати в буфер обміну", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.wordWrapToggle": { + "message": "Перемикати перенесення рядків", + "description": "The title attribute for toggle word wrapping button of code block lines" + }, + "theme.docs.breadcrumbs.home": { + "message": "Головна сторінка", + "description": "The ARIA label for the home page in the breadcrumbs" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "Згорнути сайдбар", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "Згорнути сайдбар", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.navAriaLabel": { + "message": "Сайдбар документації", + "description": "The ARIA label for the sidebar navigation" + }, + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { + "message": "← Перейти до головного меню", + "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + }, + "theme.docs.sidebar.closeSidebarButtonAriaLabel": { + "message": "Закрити сайдбар", + "description": "The ARIA label for close button of mobile sidebar" + }, + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { + "message": "Перемкнути сайдбар", + "description": "The ARIA label for hamburger menu button of mobile navigation" + }, + "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": { + "message": "Розгорнути випадаючий список", + "description": "The ARIA label of the button to expand the mobile dropdown navbar item" + }, + "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": { + "message": "Згорнути випадаючий список", + "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "Розгорнути сайдбар", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "Розгорнути сайдбар", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.SearchPage.existingResultsTitle": { + "message": "Search results for \"{query}\"", + "description": "The search page title for non-empty query" + }, + "theme.SearchPage.emptyResultsTitle": { + "message": "Search the documentation", + "description": "The search page title for empty query" + }, + "theme.SearchPage.searchContext.everywhere": { + "message": "Everywhere" + }, + "theme.SearchPage.documentsFound.plurals": { + "message": "1 document found|{count} documents found", + "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.SearchPage.noResultsText": { + "message": "No documents were found", + "description": "The paragraph for empty search result" + }, + "theme.SearchBar.noResultsText": { + "message": "No results" + }, + "theme.SearchBar.seeAllOutsideContext": { + "message": "See all results outside \"{context}\"" + }, + "theme.SearchBar.searchInContext": { + "message": "See all results within \"{context}\"" + }, + "theme.SearchBar.seeAll": { + "message": "See all results" + }, + "theme.SearchBar.label": { + "message": "Search", + "description": "The ARIA label and placeholder for search button" + }, + "theme.blog.post.plurals": { + "message": "{count} запис|{count} записи|{count} записів", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} з тегом \"{tagName}\"", + "description": "The title of the page for a blog tag" + }, + "theme.blog.author.pageTitle": { + "message": "{authorName} - {nPosts}", + "description": "The title of the page for a blog author" + }, + "theme.blog.authorsList.pageTitle": { + "message": "Автори", + "description": "The title of the authors page" + }, + "theme.blog.authorsList.viewAll": { + "message": "Переглянути всіх авторів", + "description": "The label of the link targeting the blog authors page" + }, + "theme.blog.author.noPosts": { + "message": "Цей автор ще не написав жодної публікації.", + "description": "The text for authors with 0 blog post" + }, + "theme.contentVisibility.unlistedBanner.title": { + "message": "Непублічна сторінка", + "description": "The unlisted content banner title" + }, + "theme.contentVisibility.unlistedBanner.message": { + "message": "Ця сторінка є непублічною. Пошукові системи її не індексуватимуть, і доступ до неї матимуть лише користувачі з прямим посиланням.", + "description": "The unlisted content banner message" + }, + "theme.contentVisibility.draftBanner.title": { + "message": "Чернетка", + "description": "The draft content banner title" + }, + "theme.contentVisibility.draftBanner.message": { + "message": "Це чернетка. Ця сторінка видима лише в режимі розробки й буде виключена з фінальної збірки.", + "description": "The draft content banner message" + }, + "theme.docs.DocCard.categoryDescription.plurals": { + "message": "{count} елемент|{count} елементи|{count} елементів", + "description": "The default description for a category card in the generated index about how many items this category includes" + }, + "theme.ErrorPageContent.tryAgain": { + "message": "Спробуйте ще раз", + "description": "The label of the button to try again rendering when the React error boundary captures an error" + }, + "theme.common.skipToMainContent": { + "message": "Перейти до основного вмісту", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + }, + "theme.tags.tagsPageTitle": { + "message": "Теги", + "description": "The title of the tag list page" + } +} diff --git a/i18n/uk/docusaurus-plugin-content-blog/options.json b/i18n/uk/docusaurus-plugin-content-blog/options.json new file mode 100644 index 0000000000..9239ff706c --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-blog/options.json @@ -0,0 +1,14 @@ +{ + "title": { + "message": "Blog", + "description": "The title for the blog used in SEO" + }, + "description": { + "message": "Blog", + "description": "The description for the blog used in SEO" + }, + "sidebar.title": { + "message": "Recent posts", + "description": "The label for the left sidebar" + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current.json b/i18n/uk/docusaurus-plugin-content-docs/current.json new file mode 100644 index 0000000000..81bb874d02 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,94 @@ +{ + "version.label": { + "message": "2.0", + "description": "The label for version current" + }, + "sidebar.tutorialSidebar.category.Service Mode": { + "message": "Режим сервісу", + "description": "The label for category 'Service Mode' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Service Mode.link.generated-index.description": { + "message": "Підключайте офіційні клієнти й worker на PHP, Python або Rust до Durable Workflow Cloud чи власного Server. Cloud містить Managed Waterline. Для самостійного розгортання оператори встановлюють Waterline окремо й підключають його до простору імен, яким керує Server.", + "description": "The generated-index page description for category 'Service Mode' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.CLI": { + "message": "CLI", + "description": "The label for category 'CLI' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.CLI.link.generated-index.description": { + "message": "Встановіть операторський клієнт dw та ознайомтеся з повним переліком його команд.", + "description": "The generated-index page description for category 'CLI' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.SDKs": { + "message": "SDKs", + "description": "The label for category 'SDKs' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.SDKs.link.generated-index.description": { + "message": "Створюйте клієнти й worker для режиму сервісу з офіційними SDK для PHP, Python та Rust.", + "description": "The generated-index page description for category 'SDKs' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Embedded": { + "message": "Вбудований режим", + "description": "The label for category 'Embedded' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Embedded.link.generated-index.description": { + "message": "Встановлюйте, створюйте, тестуйте й налаштовуйте workflow з середовищем виконання всередині застосунку Laravel.", + "description": "The generated-index page description for category 'Embedded' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Defining Workflows": { + "message": "Визначення workflow", + "description": "The label for category 'Defining Workflows' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Defining Workflows.link.generated-index.description": { + "message": "Створюйте класи workflow й activity, дані, ідентифікатори та обробку станів для середовища виконання всередині застосунку Laravel.", + "description": "The generated-index page description for category 'Defining Workflows' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Features": { + "message": "Можливості", + "description": "The label for category 'Features' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Features.link.generated-index.description": { + "message": "Обирайте потоки повідомлень, стійкі команди, очікування, повторні спроби, версіонування та шаблони обмеження історії для середовища виконання всередині застосунку Laravel.", + "description": "The generated-index page description for category 'Features' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Constraints": { + "message": "Обмеження", + "description": "The label for category 'Constraints' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Constraints.link.generated-index.description": { + "message": "Дізнайтеся про межі детермінованого виконання workflow та структурні обмеження середовища виконання всередині застосунку Laravel.", + "description": "The generated-index page description for category 'Constraints' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Configuration": { + "message": "Налаштування", + "description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Configuration.link.generated-index.description": { + "message": "Налаштовуйте сховище, з’єднання, межі сервісу та строки зберігання для середовища виконання всередині застосунку Laravel.", + "description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Run And Operate": { + "message": "Запуск та експлуатація", + "description": "The label for category 'Run And Operate' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Run And Operate.link.generated-index.description": { + "message": "Розгортайте, спостерігайте, діагностуйте, відновлюйте та підтримуйте запущені workflow.", + "description": "The generated-index page description for category 'Run And Operate' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.External Execution And Ingress": { + "message": "Зовнішнє виконання та вхідні запити", + "description": "The label for category 'External Execution And Ingress' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.External Execution And Ingress.link.generated-index.description": { + "message": "Налаштовуйте вхідні HTTP-запити, зовнішні worker, черги завдань та інтеграції на рівні протоколу.", + "description": "The generated-index page description for category 'External Execution And Ingress' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.AI And Automation": { + "message": "ШІ та автоматизація", + "description": "The label for category 'AI And Automation' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.AI And Automation.link.generated-index.description": { + "message": "Надавайте агентам програмування й засобам автоматизації стабільну документацію, маніфести, команди та інструменти workflow.", + "description": "The generated-index page description for category 'AI And Automation' in sidebar 'tutorialSidebar'" + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/installation.md b/i18n/uk/docusaurus-plugin-content-docs/current/installation.md new file mode 100644 index 0000000000..f0ba0de1e1 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/installation.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 3 +--- + +# Встановлення {#installation} + +Цей посібник описує встановлення PHP-пакета Durable Workflow для застосунків Laravel. + +Якщо ви обираєте між вбудованим пакетом та окремим Server, почніть із +[моделей розгортання](/docs/polyglot/deployment-modes). Ця сторінка описує +вбудований режим Laravel. + +> **Хочете почати з робочого застосунку?** +> [Sample App](/docs/sample-app) є готовим проєктом Laravel 13 на +> Durable Workflow 2.0 з окремим workflow для кожного шаблону, +> запуском через Codespaces або `docker compose`. Клонуйте його, +> виконайте `php artisan app:init`, і потрібне встановлення вже буде +> налаштовано. Поверніться сюди, коли будете готові додати Durable Workflow +> до власного застосунку Laravel. + +## Вимоги {#requirements} + +- PHP 8.1 або новіший +- Laravel 9 або новіший + +Durable Workflow підтримує всі драйвери черг Laravel, крім `sync`, зокрема: + +- Amazon SQS +- Beanstalkd +- Database +- Redis + +Кожен драйвер черги має власні +[передумови](https://laravel.com/docs/12.x/queues#driver-prerequisites). + +Durable Workflow також потребує драйвера кешу з підтримкою +[блокувань](https://laravel.com/docs/12.x/cache#atomic-locks). + +## Встановлення Durable Workflow {#installing-durable-workflow} + +Durable Workflow можна встановити через Composer: + +```bash +composer require %%artifact.workflowComposerPackage%% +``` + +Використовуйте `durable-workflow/workflow:^2.0`, щоб Composer автоматично +приймав сумісні оновлення 2.x. Правила сумісності середовища виконання та +пакетів описано в [сумісності версій](/docs/compatibility). + +Пакет автоматично завантажує свої міграції. Після встановлення достатньо +звичайного запуску міграцій: + +```bash +php artisan migrate +``` + +## Запуск worker {#running-workers} + +Durable Workflow використовує черги для фонового виконання workflow та +activity. Потрібно запустити +[команду](https://laravel.com/docs/12.x/queues#the-queue-work-command) +`queue:work` або скористатися [Horizon](https://laravel.com/docs/12.x/horizon) +для запуску worker черги. Без worker черги workflow та activity не +оброблятимуться. Для паралельного виконання потрібно кілька worker черги. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/introduction.md b/i18n/uk/docusaurus-plugin-content-docs/current/introduction.md new file mode 100644 index 0000000000..bf6ee78d7f --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/introduction.md @@ -0,0 +1,157 @@ +--- +sidebar_position: 1 +description: Оберіть Durable Workflow Cloud, власний Server 2.0 або вбудований режим Laravel і виконайте перший workflow за допомогою офіційного SDK. +tags: + - concepts + - getting-started + - workflows +keywords: + - durable workflow + - polyglot workflow engine + - AI agent workflow engine + - durable orchestration + - v2 workflow concepts +--- + +import PythonPackageReleaseLink from '@site/src/components/PythonPackageReleaseLink'; + +# Вступ {#introduction} + +Durable Workflow 2.0 зберігає стан та історію workflow поза короткочасними +процесами застосунку. Завдяки цьому worker на PHP, Python або Rust може безпечно +продовжити роботу після перезапуску. Якщо ви тут уперше, почніть із +[швидкого старту Durable Workflow 2.0](/docs/quickstart/). Він допоможе довести +один workflow до стану `completed`, перш ніж ви перейдете до докладного +[довідника можливостей](/docs/capabilities/). + +## Оберіть модель розгортання {#choose-a-deployment-model} + +### Сервісний режим {#service-mode} + +Застосунки звертаються до віддаленого середовища виконання через офіційні SDK. +Оберіть, хто його обслуговуватиме: + +- **[Durable Workflow Cloud](/docs/polyglot/cloud-control-plane/)** пропонує + керований сервіс. Durable Workflow відповідає за оркестрацію, збереження даних + та Managed Waterline. Ваша команда запускає клієнти SDK і worker, які + працюють із наданим namespace. **Користувачам Cloud не потрібно встановлювати + або запускати Durable Workflow Server чи окремий сервіс Waterline.** +- **[Власний Server](/docs/polyglot/server/)** надає вашій команді ту саму + сервісну межу. Ви самостійно розгортаєте, захищаєте, масштабуєте, оновлюєте + середовище виконання та створюєте його резервні копії. Waterline для + спостереження можна розгорнути як окремий сервіс, підключений до namespace, + яким керує Server. + +Обидва варіанти надають ту саму версіоновану площину керування HTTP+JSON, +протокол worker, модель namespace та мовно незалежний формат payload. +Межі відповідальності описано в +[моделях розгортання](/docs/polyglot/deployment-modes/). + +### Вбудований режим Laravel {#embedded-laravel} + +Вбудований режим є окремою моделлю розгортання для застосунку Laravel, який +хоче зберігати стан workflow, черги, конфігурацію та інструменти оператора у +власній інфраструктурі. Він встановлює `durable-workflow/workflow`, не +підключається до Cloud і не потребує окремого Server. Вбудований пакет +Waterline читає стан цього застосунку в тому самому процесі. + +Починайте зі [встановлення вбудованого пакета](/docs/installation/), якщо ви +свідомо обрали керування середовищем виконання всередині застосунку. + +Командам Laravel, які переходять зі стабільної v1 або переглядають наявне +вбудоване розгортання 2.0, варто скористатися +[посібником із впровадження в Laravel та переходу між середовищами виконання](/docs/laravel-adoption/). +Він допоможе порівняти робочі приклади вбудованого режиму та PHP SDK до +перемикання трафіку. + +## Оберіть SDK для сервісного режиму {#choose-a-service-mode-sdk} + +- **[PHP SDK](/docs/polyglot/php/):** встановіть `durable-workflow/sdk` у + застосунку PHP без прив'язки до фреймворку або у віддаленому worker. +- **[Python SDK](/docs/polyglot/python/):** створюйте детерміновані workflow + та activity й використовуйте асинхронний клієнт площини керування. + Стабільний випуск + Python зазначено в тому самому машинозчитуваному + маніфесті стабільних випусків, що й у швидкому старті для Server. +- **[Rust SDK](/docs/polyglot/rust/):** створюйте детерміновані workflow + та activity й запускайте нативні сервіси worker. + +Усі три SDK є офіційними реалізаціями тієї самої публічної сервісної межі. +Посібник [можливості клієнтів і worker](/docs/polyglot/cli-python-parity/) +чітко визначає підтримувані можливості та навмисні відмінності між їхніми +інтерфейсами. + +## Ваш перший завершений workflow {#your-first-completed-workflow} + +[Швидкий старт](/docs/quickstart/) одразу визначає мету, вибір середовища +виконання, передумови, потрібний час та очікуваний результат. PHP, Python і +Rust доступні на рівних умовах, а кожен приклад показує один робочий шлях +для обраної мови. Для вправи з опублікованими артефактами без вихідного коду +використовуйте локальний Server. Для керованого namespace використовуйте +параметри підключення Cloud, не запускаючи Server самостійно. + +## Як улаштований сервісний режим {#how-service-mode-fits-together} + +Розгортання в сервісному режимі складається з трьох частин: + +- **Середовище виконання** відповідає за збережений стан, запис команд та + історії, зіставлення завдань, timer, розклади, namespace та протоколи з + автентифікацією. Для керованих namespace його обслуговує Cloud, а для + власного розгортання ваша команда. +- **Worker застосунку** виконують код workflow та activity через PHP, + Python або Rust SDK. Їх можна розгортати разом із застосунком або як + незалежні сервіси та масштабувати окремо від середовища виконання. +- **Клієнти й інструменти оператора** запускають, перевіряють та керують + тим самим станом через клієнти SDK, CLI `dw`, HTTP API, + машинозчитувані схеми, Waterline та інтерфейси для агентів. + +## Єдиний публічний контракт виконання {#one-public-durable-execution-contract} + +Офіційні SDK використовують спільні зареєстровані рядкові назви типів workflow +та activity й публічний формат payload. Формат зазначає свій codec і містить +переносимі значення, замість серіалізації PHP, pickle Python або внутрішніх +типів реалізації Rust. + +Worker відновлюють рішення workflow із збережених команд та історії. +Вхідні дані й результати activity та дочірніх workflow можуть передаватися +між мовами, якщо worker оголошують той самий публічний codec і реєструють +відповідні назви типів. Перед використанням конкретної можливості SDK +перевірте [довідник можливостей](/docs/capabilities/) та discovery +середовища виконання. + +## Вивчайте відповідні приклади {#learn-from-the-matching-examples} + +- **Сервісний режим та кілька мов:** скористайтеся + [швидким стартом](/docs/quickstart/) і посібником PHP, Python або Rust SDK. +- **Вбудований режим Laravel:** галерея + [Sample App](/docs/sample-app/) показує типові workflow для Laravel та + результати їхнього спостереження у Waterline. + +Галерея вбудованих прикладів не є універсальною відправною точкою для +користувачів Cloud або власного Server у сервісному режимі. + +## Контракт для роботи агентів {#agent-operable-by-contract} + +Люди та автономні агенти використовують той самий машинозчитуваний контракт. +Цикл, який можна перевірити, має вигляд +**Виявити можливості -> Змінити -> Запустити -> Діагностувати -> Відновити**: +маніфести версій та можливостей, явні команди workflow, структуровані +результати, типізована історія й діагностика worker та черг, безпечні зміни +і перевірка після них. Дивіться +[робочий цикл агента](/docs/agent-operating-loop/) та +[оцінювання рушія для AI-агентів](/docs/ai-agent-workflow-engine/). + +## Чи потрібен вам workflow? {#do-you-need-a-workflow} + +Імовірно, вам потрібен workflow, якщо: + +- Процес триває хвилини, години або дні +- Потрібно чекати на схвалення людини +- Потрібно чекати на webhook або іншу зовнішню подію +- Потрібно призупинити роботу та продовжити пізніше, не тримаючи процес запущеним +- Потрібно відновитися після збою без помилок або повторного виконання роботи + +Якщо завдання полягає в тому, щоб запустити п'ять завдань черги по черзі та +зупинитися після першої помилки, зазвичай краще підходить ланцюжок завдань. +Durable Workflow потрібен там, де наступний крок залежить від зовнішньої +події, очікування або рішення, якого неможливо знати заздалегідь. diff --git a/i18n/uk/docusaurus-plugin-content-docs/version-1.x.json b/i18n/uk/docusaurus-plugin-content-docs/version-1.x.json new file mode 100644 index 0000000000..4c300d2a33 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/version-1.x.json @@ -0,0 +1,38 @@ +{ + "version.label": { + "message": "1.x", + "description": "The label for version 1.x" + }, + "sidebar.tutorialSidebar.category.Defining Workflows": { + "message": "Defining Workflows", + "description": "The label for category 'Defining Workflows' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Defining Workflows.link.generated-index.description": { + "message": "Workflows and activities are defined as classes that extend the base Workflow and Activity classes provided by the framework.", + "description": "The generated-index page description for category 'Defining Workflows' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Features": { + "message": "Features", + "description": "The label for category 'Features' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Features.link.generated-index.description": { + "message": "The Workflow package provides methods for executing activities, handling errors and retries, and communicating with the workflow.", + "description": "The generated-index page description for category 'Features' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Configuration": { + "message": "Configuration", + "description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Configuration.link.generated-index.description": { + "message": "The Workflow package allows you to specify various options when defining your workflows and activities.", + "description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Constraints": { + "message": "Constraints", + "description": "The label for category 'Constraints' in sidebar 'tutorialSidebar'" + }, + "sidebar.tutorialSidebar.category.Constraints.link.generated-index.description": { + "message": "The Workflow package's determinism and idempotency constraints for workflows and activities are important for ensuring the reliability and correctness of the overall system.", + "description": "The generated-index page description for category 'Constraints' in sidebar 'tutorialSidebar'" + } +} diff --git a/i18n/uk/docusaurus-theme-classic/footer.json b/i18n/uk/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000000..39011d2dda --- /dev/null +++ b/i18n/uk/docusaurus-theme-classic/footer.json @@ -0,0 +1,46 @@ +{ + "link.title.Docs": { + "message": "Документація", + "description": "The title of the footer links column with title=Docs in the footer" + }, + "link.title.Community": { + "message": "Спільнота", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "Інші ресурси", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Introduction": { + "message": "Вступ", + "description": "The label of footer link with label=Introduction linking to /docs/introduction" + }, + "link.item.label.Installation": { + "message": "Встановлення", + "description": "The label of footer link with label=Installation linking to /docs/installation" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.gg/xu5aDDpqVy" + }, + "link.item.label.X": { + "message": "X", + "description": "The label of footer link with label=X linking to https://x.com/DurableWorkflow" + }, + "link.item.label.Durable Workflow vs Temporal": { + "message": "Durable Workflow і Temporal", + "description": "The label of footer link with label=Durable Workflow vs Temporal linking to /compare/temporal/" + }, + "link.item.label.LLM Docs": { + "message": "Документація для LLM", + "description": "The label of footer link with label=LLM Docs linking to https://durable-workflow.com/llms-full.txt" + }, + "link.item.label.Packagist": { + "message": "Packagist", + "description": "The label of footer link with label=Packagist linking to https://packagist.org/packages/durable-workflow/workflow" + }, + "copyright": { + "message": "© 2026 Durable Workflow. Усі права захищено.", + "description": "The footer copyright" + } +} diff --git a/i18n/uk/docusaurus-theme-classic/navbar.json b/i18n/uk/docusaurus-theme-classic/navbar.json new file mode 100644 index 0000000000..4c472be619 --- /dev/null +++ b/i18n/uk/docusaurus-theme-classic/navbar.json @@ -0,0 +1,22 @@ +{ + "title": { + "message": "Durable Workflow", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "Логотип Workflow", + "description": "The alt text of navbar logo" + }, + "item.label.Docs": { + "message": "Документація", + "description": "Navbar item with label Docs" + }, + "item.label.Blog": { + "message": "Блог", + "description": "Navbar item with label Blog" + }, + "item.label.Star on GitHub": { + "message": "Підтримати зіркою на GitHub", + "description": "Navbar item with label Star on GitHub" + } +} From 0dd729226ac8be4b43ef6f631783b21b96f546b6 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 02:28:17 +0000 Subject: [PATCH 02/10] Translate Ukrainian site UI and core workflow guides --- README.md | 21 ++ docs/how-it-works.md | 6 +- docusaurus.config.js | 1 + i18n/uk/code.json | 272 +++++++++++++- .../current/configuration/_category_.json | 8 + .../configuration/customization-matrix.md | 137 +++++++ .../configuration/database-connection.md | 86 +++++ .../configuration/ensuring-same-server.md | 70 ++++ .../current/configuration/microservices.md | 140 +++++++ .../current/configuration/options.md | 213 +++++++++++ .../configuration/pruning-workflows.md | 98 +++++ .../configuration/publishing-config.md | 320 ++++++++++++++++ .../current/constraints/_category_.json | 8 + .../constraints/activity-constraints.md | 66 ++++ .../constraints/constraints-summary.md | 34 ++ .../constraints/execution-guarantees.md | 194 ++++++++++ .../idempotent-vs-deterministic.md | 123 ++++++ .../current/constraints/overview.md | 55 +++ .../current/constraints/structural-limits.md | 352 ++++++++++++++++++ .../constraints/workflow-constraints.md | 66 ++++ .../defining-workflows/_category_.json | 8 + .../current/defining-workflows/activities.md | 140 +++++++ .../defining-workflows/passing-data.md | 138 +++++++ .../defining-workflows/starting-workflows.md | 36 ++ .../defining-workflows/workflow-api.md | 277 ++++++++++++++ .../current/defining-workflows/workflow-id.md | 100 +++++ .../defining-workflows/workflow-status.md | 60 +++ .../current/defining-workflows/workflows.md | 41 ++ .../current/features/_category_.json | 8 + .../current/features/condition-waits.md | 41 ++ .../current/features/events.md | 200 ++++++++++ .../current/features/memo.md | 151 ++++++++ .../current/features/queries.md | 128 +++++++ .../current/features/search-attributes.md | 158 ++++++++ .../current/features/side-effects.md | 108 ++++++ .../current/features/signal+timer.md | 104 ++++++ .../current/features/signals.md | 89 +++++ .../current/features/timeouts.md | 203 ++++++++++ .../current/features/timers.md | 103 +++++ .../current/how-it-works.md | 103 +++++ .../current/sponsors.md | 18 + .../current/support.md | 124 ++++++ .../compare/temporal.mdx | 228 ++++++++++++ i18n/uk/docusaurus-theme-classic/footer.json | 2 +- src/components/CliInstall/index.js | 26 +- src/components/ConcurrencySimulator/index.js | 19 +- src/components/HeartbeatSimulator/index.js | 31 +- src/components/HomepageFeatures/index.js | 19 +- src/components/PassingDataSimulator/index.js | 19 +- src/components/ProductPromotion/index.js | 7 +- src/components/ProtocolCatalog/index.js | 11 +- .../PublicAuthorityIdentity/index.js | 17 +- src/components/QuerySimulator/index.js | 21 +- src/components/SignalSimulator/index.js | 21 +- src/components/SignalTimerSimulator/index.js | 25 +- src/components/TimerSimulator/index.js | 17 +- .../WorkerProtocolAuthorityRoles/index.js | 47 ++- src/pages/index.js | 22 +- src/theme/Footer/index.js | 2 +- src/theme/GitHubStarNavbarItem/index.js | 12 +- 60 files changed, 5002 insertions(+), 152 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/_category_.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/customization-matrix.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/database-connection.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/ensuring-same-server.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/microservices.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/options.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/pruning-workflows.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/_category_.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/activity-constraints.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/constraints-summary.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/execution-guarantees.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/idempotent-vs-deterministic.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/overview.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/structural-limits.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/constraints/workflow-constraints.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/_category_.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/activities.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/passing-data.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/starting-workflows.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-api.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-id.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-status.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflows.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/_category_.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/condition-waits.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/events.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/memo.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/queries.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/search-attributes.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/side-effects.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/signal+timer.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/signals.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/timers.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/how-it-works.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/sponsors.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/support.md create mode 100644 i18n/uk/docusaurus-plugin-content-pages/compare/temporal.mdx diff --git a/README.md b/README.md index 11472420ca..883700c281 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,27 @@ Build the same static site and validate the same links as CI: npm run build ``` +## Ukrainian translations + +English is the default locale. Current documentation and site pages are also +available in Ukrainian under `/uk/`. Preview that locale with: + +```bash +npm run start -- --locale uk +``` + +Translate current docs in `i18n/uk/docusaurus-plugin-content-docs/current/` +and standalone pages in `i18n/uk/docusaurus-plugin-content-pages/`. Keep the +English source file paths, examples, commands, API names and explicit heading +anchors intact. Keep workflow, activity, worker, signal, timer, query, update +and saga as English technical terms. + +Use Docusaurus translation markers for component text. Run +`npm run write-translations -- --locale uk` to extract new messages, then +translate them in `i18n/uk/code.json` and the plugin message files. Build both +locales with `npm run build` before submitting a change. Blog articles and the +versioned 1.x docs use the English fallback. + ## Repository layout | Path | Purpose | diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 65831d4f54..43042e5f6f 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -30,8 +30,10 @@ By calling multiple activities, a workflow can orchestrate the results between e If a workflow fails, the events leading up to the failure are replayed to rebuild the current state. This allows the workflow to pick up where it left off, with the same inputs and outputs as before, ensuring determinism. -Activities are always durable queued work in v2. There is no in-process local -activity fast path. Ordinary activities can run on any compatible worker, and +Ordinary activities are durable queued work in v2. Explicit +[local activities](./features/local-activities.md) run short activity work in +the workflow worker process while preserving durable history and retry semantics. +Ordinary activities can run on any compatible worker, and [worker sessions](./features/worker-sessions.md) add an explicit lease when a sequence of activity steps needs the same worker-local resource. If you need a replay-safe one-shot value without queueing an activity, use diff --git a/docusaurus.config.js b/docusaurus.config.js index 1683d67f75..637902627d 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -70,6 +70,7 @@ const config = { remarkPlugins: [artifactVersionRemarkPlugin], editUrl: 'https://github.com/durable-workflow/durable-workflow.github.io/edit/main/', + editLocalizedFiles: true, lastVersion: 'current', versions: { current: { diff --git a/i18n/uk/code.json b/i18n/uk/code.json index c8956b8dee..39136bb1be 100644 --- a/i18n/uk/code.json +++ b/i18n/uk/code.json @@ -271,38 +271,38 @@ "description": "The ARIA label and title attribute for expand button of doc sidebar" }, "theme.SearchPage.existingResultsTitle": { - "message": "Search results for \"{query}\"", + "message": "Результати пошуку за запитом «{query}»", "description": "The search page title for non-empty query" }, "theme.SearchPage.emptyResultsTitle": { - "message": "Search the documentation", + "message": "Пошук у документації", "description": "The search page title for empty query" }, "theme.SearchPage.searchContext.everywhere": { - "message": "Everywhere" + "message": "Усюди" }, "theme.SearchPage.documentsFound.plurals": { - "message": "1 document found|{count} documents found", + "message": "Знайдено {count} документ|Знайдено {count} документи|Знайдено {count} документів", "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" }, "theme.SearchPage.noResultsText": { - "message": "No documents were found", + "message": "Документів не знайдено", "description": "The paragraph for empty search result" }, "theme.SearchBar.noResultsText": { - "message": "No results" + "message": "Немає результатів" }, "theme.SearchBar.seeAllOutsideContext": { - "message": "See all results outside \"{context}\"" + "message": "Переглянути всі результати поза «{context}»" }, "theme.SearchBar.searchInContext": { - "message": "See all results within \"{context}\"" + "message": "Переглянути всі результати в «{context}»" }, "theme.SearchBar.seeAll": { - "message": "See all results" + "message": "Переглянути всі результати" }, "theme.SearchBar.label": { - "message": "Search", + "message": "Пошук", "description": "The ARIA label and placeholder for search button" }, "theme.blog.post.plurals": { @@ -360,5 +360,257 @@ "theme.tags.tagsPageTitle": { "message": "Теги", "description": "The title of the tag list page" + }, + "homepage.tagline": { + "message": "Стійке виконання для PHP, Python та Rust." + }, + "homepage.getStarted": { + "message": "Почати" + }, + "homepage.deploymentModes": { + "message": "Обрати режим розгортання" + }, + "homepage.description": { + "message": "Платформа стійкого виконання для PHP, Python та Rust, незалежна від мови клієнта, доступна як керований Cloud, власний Server або вбудоване середовище виконання Laravel." + }, + "homepage.cloudDescription": { + "message": "Запускайте worker на PHP, Python або Rust у керованому просторі імен, а Durable Workflow експлуатуватиме середовище оркестрації." + }, + "homepage.features.languages.title": { + "message": "Розробляйте на PHP, Python або Rust" + }, + "homepage.features.languages.description": { + "message": "Створюйте клієнти, workflow й activity з офіційними SDK, які мають спільний публічний протокол та переносиму модель даних Avro." + }, + "homepage.features.runtime.title": { + "message": "Обирайте, хто експлуатує середовище виконання" + }, + "homepage.features.runtime.description": { + "message": "Використовуйте Durable Workflow Cloud, запускайте окремий Server або залишайте вбудоване середовище виконання Laravel всередині свого застосунку." + }, + "homepage.features.recovery.title": { + "message": "Відновлюйте роботу після збоїв" + }, + "homepage.features.recovery.description": { + "message": "Стійка історія, детерміноване відтворення, timer, повторні спроби, signal, update, дочірні workflow та saga забезпечують поступ тривалої роботи." + }, + "promotion.eyebrow": { + "message": "Керований сервіс · обмежена група" + }, + "promotion.title": { + "message": "Група запуску Durable Workflow Cloud" + }, + "promotion.requestAccess": { + "message": "Запросити ранній доступ" + }, + "navbar.github.repository": { + "message": "Репозиторій GitHub" + }, + "navbar.github.repositoryStars": { + "message": "{repository} (зірок: {count})" + }, + "navbar.github.starCount": { + "message": "Зірок на GitHub: {count}" + }, + "navbar.github.stars": { + "message": "Зірки на GitHub" + }, + "cliInstall.windowsNote": { + "message": "Встановлює до %USERPROFILE%\\.durable-workflow\\bin та додає цей каталог до вашого PATH." + }, + "cliInstall.unixNote": { + "message": "Встановлює до ~/.local/bin. Задайте DURABLE_WORKFLOW_INSTALL_DIR для зміни каталогу." + }, + "cliInstall.detected": { + "message": "Виявлено {platform}." + }, + "cliInstall.switchBack": { + "message": "Повернутися" + }, + "cliInstall.verification": { + "message": "Інсталятор перевіряє маніфест SHA256SUMS випуску перед записом виконуваного файлу." + }, + "cliInstall.directDownload": { + "message": "Або завантажте виконуваний файл безпосередньо" + }, + "cliInstall.fromRelease": { + "message": "зі" + }, + "cliInstall.stableRelease": { + "message": "стабільного випуску" + }, + "simulator.passingData.title": { + "message": "Симулятор Hello World" + }, + "simulator.try": { + "message": "Спробуйте!" + }, + "simulator.runningArrow": { + "message": "▶️ Виконується..." + }, + "simulator.play": { + "message": "▶ Запустити" + }, + "simulator.reset": { + "message": "🔄 Скинути" + }, + "simulator.output": { + "message": "Результат:" + }, + "simulator.ready": { + "message": "⏸️ Готово до запуску" + }, + "simulator.running": { + "message": "▶️ Виконується" + }, + "simulator.completed": { + "message": "✅ Завершено" + }, + "simulator.executing": { + "message": "Виконується:" + }, + "protocolCatalog.specId": { + "message": "Ідентифікатор специфікації" + }, + "protocolCatalog.publicSpec": { + "message": "Публічна специфікація" + }, + "protocolCatalog.formatStatus": { + "message": "Формат і стан" + }, + "protocolCatalog.owner": { + "message": "Контракт власника" + }, + "protocolCatalog.objectFamilies": { + "message": "Сімейства об’єктів та контракти власників" + }, + "authority.identity": { + "message": "Машинозчитуване авторитетне джерело опубліковано за адресою {manifest} зі схемою {schema}, версією {version}." + }, + "simulator.query.title": { + "message": "Симулятор query та signal" + }, + "simulator.runningHourglass": { + "message": "⏳ Виконується..." + }, + "simulator.waiting": { + "message": "⏸️ Очікування..." + }, + "simulator.sendSignal": { + "message": "📤 Надіслати signal:" + }, + "simulator.waitingSignal": { + "message": "⏳ Очікування signal" + }, + "simulator.timer.title": { + "message": "Симулятор виконання timer" + }, + "navbar.github.starRepository": { + "message": "Підтримати Durable Workflow зіркою на GitHub" + }, + "simulator.pending": { + "message": "⏳ Очікує виконання" + }, + "simulator.series.title": { + "message": "Послідовне виконання" + }, + "simulator.mode": { + "message": "Режим:" + }, + "simulator.mode.series": { + "message": "Послідовний" + }, + "simulator.mode.mix": { + "message": "Змішаний" + }, + "simulator.mode.parallel": { + "message": "Паралельний" + }, + "simulator.heartbeat.title": { + "message": "Симулятор heartbeat" + }, + "simulator.stop": { + "message": "⏹ Зупинити" + }, + "simulator.withHeartbeat": { + "message": "З heartbeat" + }, + "simulator.withoutHeartbeat": { + "message": "Без heartbeat" + }, + "simulator.loopIterations": { + "message": "Ітерацій циклу:" + }, + "simulator.totalTime": { + "message": "Загальний час:" + }, + "simulator.heartbeatElapsed": { + "message": "Час від останнього heartbeat: {elapsed} / тайм-аут 5s" + }, + "simulator.noHeartbeat": { + "message": "❌ Тайм-аут — немає heartbeat!" + }, + "simulator.heartbeatActive": { + "message": "💓 Heartbeat активний" + }, + "simulator.signal.title": { + "message": "Симулятор виконання signal" + }, + "simulator.signalTimer.title": { + "message": "Симулятор signal + timer" + }, + "simulator.timeoutIn": { + "message": "До тайм-ауту:" + }, + "simulator.waitingSignalTimeout": { + "message": "⏳ Очікування signal або тайм-ауту" + }, + "simulator.signalReceived": { + "message": "signal отримано" + }, + "simulator.timeoutReached": { + "message": "тайм-аут настав" + }, + "workerAuthority.title": { + "message": "Ролі авторитетних джерел протоколу worker" + }, + "workerAuthority.description": { + "message": "Виявлення можливостей середовища виконання та перевірка відповідності використовують окремі авторитетні джерела протоколу. Обирайте рядок для свого завдання. Однакова мітка версії не робить версіоноване історичне джерело псевдонімом неверсіонованого авторитетного джерела Server." + }, + "workerAuthority.role": { + "message": "Роль" + }, + "workerAuthority.marker": { + "message": "Поточний маркер" + }, + "workerAuthority.resolver": { + "message": "Авторитетне джерело" + }, + "workerAuthority.currentServer": { + "message": "Поточний опублікований протокол Server" + }, + "workerAuthority.serverMirrors": { + "message": "Неверсіоновані дзеркала на основі Server:" + }, + "workerAuthority.currentConformance": { + "message": "Поточна ціль перевірки відповідності Workflow" + }, + "workerAuthority.suiteTarget": { + "message": "Набір {suite} перевіряє протокол {protocol}" + }, + "workerAuthority.fixtures": { + "message": "Версіоновані тестові дані, прив’язані до digest:" + }, + "workerAuthority.historicalConformance": { + "message": "Збережені історичні прив’язки відповідності" + }, + "workerAuthority.historicalBindings": { + "message": "Прив’язки з позначкою {marker} для протоколів {protocols}" + }, + "workerAuthority.historicalRecords": { + "message": "Незмінні записи джерел і digest у" + }, + "workerAuthority.manifest": { + "message": "маніфесті відповідності" } } diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/_category_.json b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/_category_.json new file mode 100644 index 0000000000..0faf7910cc --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Налаштування", + "position": 8, + "link": { + "type": "generated-index", + "description": "Пакет Workflow дозволяє задавати різні параметри під час визначення workflow й activity." + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/customization-matrix.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/customization-matrix.md new file mode 100644 index 0000000000..c860c88cdb --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/customization-matrix.md @@ -0,0 +1,137 @@ +--- +sidebar_position: 4.5 +title: Матриця налаштування +description: Зафіксована матриця підтримки Durable Workflow v2 для перевизначення моделей, налаштувань серіалізації, заміни репозиторію спостереження, стану, імпорту, міграцій та поведінки запису. +--- + +# Матриця налаштування {#customization-matrix} + +Ця сторінка фіксує підтримуваний контракт налаштування Durable Workflow v2. +Використовуйте її, якщо потрібно перенести стійкі моделі v2 на інше з’єднання, замінити підкласи моделей чи PHP-репозиторій операторського спостереження або визначити, чи старе налаштування серіалізації залишається чинним під час міграції. + +## Матриця підтримуваних перевизначень {#supported-override-matrix} + +| Можливість | Підтримуваний контракт | Примітки | +| --- | --- | --- | +| `workflows.v2.instance_model` із підкласом сумісної схеми, що зберігає базову назву `WorkflowInstance` | Підтримується | Використовуйте цей варіант, якщо потрібні лише інше з’єднання, перетворення типів або простір імен застосунку. Успадковані зв’язки зберігають контракт зовнішнього ключа `workflow_instance_id` пакета. | +| `workflows.v2.instance_model` із підкласом, що змінює таблицю чи базову назву | Підтримується з явним перевизначенням зв’язків | Перевизначте `runs()`, `commands()` та `updates()`, щоб вони зберігали `workflow_instance_id`. Пакет перевіряє це під час запуску та відхиляє успадковане визначення зв’язків, яке змінило б ці ключі. `currentRun()` уже явно задає `current_run_id` і не потребує перевизначення. | +| Інші перевизначення `workflows.v2.*_model` (`run_model`, `task_model`, `history_event_model`, проєкції, розклади, рядки activity, помилки, повідомлення, memo, атрибути пошуку, дочірні виклики) | Підтримується для підкласів із сумісною схемою | Зберігайте назви стовпців, первинні та зовнішні ключі пакета. Ці зв’язки вже явно задають свої ключі, тож підкласи зі зміненими таблицями можуть зберігати успадковані методи зв’язків, якщо схема сумісна. | +| Власні назви таблиць разом із власними назвами стовпців зовнішніх ключів | Не підтримується | Середовище виконання v2 та операторські засоби покладаються на назви стовпців і ключів пакета. Їх зміна виходить за межі підтримуваного контракту. | +| Заміна `OperatorObservabilityRepository` | Підтримується через прив’язку контейнера | Прив’яжіть свою реалізацію до `Workflow\V2\Contracts\OperatorObservabilityRepository`. Це контракт інтеграції PHP-середовища виконання для Waterline та `WorkflowStub::historyExport()`, а не стабільний міжмовний API. Репозиторій отримує об’єкти моделей пакета. | +| `serializer` | Підтримується лише для `avro` та застарілих кодеків завершення/імпорту | Нові запуски v2 завжди використовують семантику Avro. `workflow-serializer-y` та `workflow-serializer-base64` залишаються чинними лише для завершення чи імпорту історії v1, яка ще потребує нативного декодування PHP. Власні класи серіалізації v1 не підтримуються у v2. | +| Діагностика стану та doctor | Підтримується | `php artisan workflow:v2:doctor` та стан/статистика Waterline v2 відображають налаштовані класи моделей і позначають застарілі налаштування серіалізації як борг міграції. | +| Міграції пакета через нетипове з’єднання | Підтримується з опублікованими міграціями | Автоматично завантажені міграції пакета використовують типове з’єднання Laravel. Якщо стійкі моделі використовують інше з’єднання, опублікуйте міграції та явно задайте їх `$connection`. | +| Основний шлях запису середовища виконання (`WorkflowStub::make()`, `load()`, створення завдань, визначення поточного запуску) | Підтримується через налаштовані стійкі моделі | Середовище виконання враховує налаштовані `instance_model`, `run_model` та `task_model`. Зберігайте схеми таблиць запису сумісними зі стовпцями й ключами пакета. | + +## Точні ключі моделей v2 {#exact-v2-model-keys} + +Опубліковані налаштування `workflows.php` надають такі ключі стійких моделей у `workflows.v2`: + +- `instance_model` +- `run_model` +- `history_event_model` +- `task_model` +- `command_model` +- `link_model` +- `activity_execution_model` +- `activity_attempt_model` +- `timer_model` +- `failure_model` +- `run_summary_model` +- `run_wait_model` +- `run_timeline_entry_model` +- `run_timer_entry_model` +- `run_lineage_entry_model` +- `schedule_model` +- `schedule_history_event_model` + +Використовуйте підкласи моделей пакета для цих ключів. Зберігайте назви стовпців та зовнішні ключі пакета, якщо сторінка цієї документації не вказує інше. + +## Правило перевизначення моделі екземпляра {#instance-model-override-rule} + +Єдине поточне перевизначення v2, що потребує додаткової роботи зі зв’язками, — `workflows.v2.instance_model`, коли підклас змінює автоматично визначений зовнішній ключ із `workflow_instance_id` на інший. + +Типовий безпечний приклад: + +```php +namespace App\Models\V2; + +use Workflow\V2\Models\WorkflowInstance as BaseWorkflowInstance; + +final class WorkflowInstance extends BaseWorkflowInstance +{ + protected $connection = 'workflow'; +} +``` + +Оскільки базова назва підкласу залишається `WorkflowInstance`, Eloquent зберігає той самий автоматично визначений зовнішній ключ, а успадковані зв’язки залишаються узгодженими. + +Якщо використовуєте іншу базову назву чи модель сховища зі зміненою таблицею, явно перевизначте відповідні зв’язки: + +```php +namespace App\Workflow\Storage; + +use Illuminate\Database\Eloquent\Relations\HasMany; +use Workflow\V2\Models\WorkflowCommand; +use Workflow\V2\Models\WorkflowInstance as BaseWorkflowInstance; +use Workflow\V2\Models\WorkflowRun; +use Workflow\V2\Models\WorkflowUpdate; +use Workflow\V2\Support\ConfiguredV2Models; + +final class TenantWorkflowInstance extends BaseWorkflowInstance +{ + protected $table = 'tenant_workflow_instances'; + + public function runs(): HasMany + { + return $this->hasMany( + ConfiguredV2Models::resolve('run_model', WorkflowRun::class), + 'workflow_instance_id', + ); + } + + public function commands(): HasMany + { + return $this->hasMany( + ConfiguredV2Models::resolve('command_model', WorkflowCommand::class), + 'workflow_instance_id', + )->oldest('created_at'); + } + + public function updates(): HasMany + { + return $this->hasMany( + ConfiguredV2Models::resolve('update_model', WorkflowUpdate::class), + 'workflow_instance_id', + ) + ->orderBy('command_sequence') + ->oldest('accepted_at') + ->oldest('created_at') + ->oldest('id'); + } +} +``` + +Якщо цих перевизначень немає, запуск пакета одразу завершується з помилкою, замість переходу застосунку на автоматично визначені ключі на кшталт `tenant_workflow_instance_id` чи `custom_workflow_instance_id`. + +## Правила серіалізації, імпорту та стану {#serializer-import-and-health-rules} + +- Зберігайте `serializer = 'avro'` для нової роботи v2. +- Використовуйте застарілі кодеки лише під час завершення чи імпорту історії v1, яка ще потребує нативного декодування даних PHP. +- Не спрямовуйте v2 на видалені власні класи серіалізації v1. +- Запускайте `php artisan workflow:v2:doctor` після оновлень та змін налаштувань. Це засіб діагностики застарілих налаштувань серіалізації та інших проблем готовності до запуску. +- Стан і статистика Waterline v2 є операторським відображенням тих самих налаштувань стійких моделей. + +## Правила міграції та запису {#migration-and-write-side-rules} + +- Звичайний шлях — автоматично завантажені міграції пакета через типове з’єднання. +- Якщо стійкі моделі використовують інше з’єднання, опублікуйте міграції та задайте їх `$connection`, щоб `php artisan migrate` використовував ту саму базу даних, що й налаштовані підкласи моделей. +- Зберігайте назви таблиць, первинні та зовнішні ключі пакета для кожної моделі, яка бере участь у шляху запису середовища виконання. +- Налаштування шляху запису впливають не лише на читання: запуск, завантаження, визначення поточного запуску та виконання завдань використовують налаштовані стійкі моделі. + +## Пов’язані посібники {#related-guides} + +- [Публікація налаштувань](./publishing-config.md) +- [З’єднання з базою даних](./database-connection.md) +- [Посібник міграції](../migration.md) +- [Спостереження](../monitoring.md) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/database-connection.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/database-connection.md new file mode 100644 index 0000000000..2155f7a692 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/database-connection.md @@ -0,0 +1,86 @@ +--- +sidebar_position: 4 +--- + +# З’єднання з базою даних {#database-connection} + +Нижче наведено кроки для зміни з’єднання з базою даних, яке використовують стійкі +моделі workflow v2. Це потрібно *лише* тоді, коли ви хочете використовувати інше +з’єднання, ніж типове з’єднання вашого застосунку Laravel. + +Повний контракт перевизначення v2, зокрема підкласи зі зміненими таблицями, +правила серіалізації, заміну репозиторію, діагностику стану та вимоги до міграцій, +наведено в [матриці налаштування](./customization-matrix.md). + +1. Створіть класи в каталозі моделей застосунку, які успадковують базові моделі v2 +2. Задайте потрібний `$connection` у кожному класі +3. Опублікуйте файл налаштувань workflow +4. Оновіть прив’язки моделей `workflows.v2`, щоб вони вказували на ваші класи + +## Успадкування моделей workflow v2 {#extending-v2-workflow-models} + +Додайте цей код до `app\Models\V2\WorkflowInstance.php`. + +```php +namespace App\Models\V2; + +use Workflow\V2\Models\WorkflowInstance as BaseWorkflowInstance; + +class WorkflowInstance extends BaseWorkflowInstance +{ + protected $connection = 'mysql'; +} +``` + +Повторіть цей шаблон для кожної стійкої моделі v2, яку потрібно перенаправити, +щонайменше для таких: + +- `Workflow\V2\Models\WorkflowRun` +- `Workflow\V2\Models\WorkflowHistoryEvent` +- `Workflow\V2\Models\WorkflowTask` +- `Workflow\V2\Models\WorkflowCommand` +- `Workflow\V2\Models\WorkflowLink` +- `Workflow\V2\Models\ActivityExecution` +- `Workflow\V2\Models\ActivityAttempt` +- `Workflow\V2\Models\WorkflowTimer` +- `Workflow\V2\Models\WorkflowFailure` +- `Workflow\V2\Models\WorkflowRunSummary` +- `Workflow\V2\Models\WorkflowSchedule` +- `Workflow\V2\Models\WorkflowScheduleHistoryEvent` + +Кожен підклас має оголосити `protected $connection = 'mysql';` або іншу назву +з’єднання, визначену в `config/database.php`. + +## Реєстрація власних моделей {#registering-custom-models} + +Опублікуйте файл налаштувань workflow та оновіть прив’язки +`workflows.v2.*_model`, щоб вони вказували на ваші класи: + +```php +// config/workflows.php + +'v2' => [ + 'instance_model' => App\Models\V2\WorkflowInstance::class, + 'run_model' => App\Models\V2\WorkflowRun::class, + 'history_event_model' => App\Models\V2\WorkflowHistoryEvent::class, + 'task_model' => App\Models\V2\WorkflowTask::class, + 'command_model' => App\Models\V2\WorkflowCommand::class, + // ... +], +``` + +Пакет отримує моделі через `Workflow\V2\Support\ConfiguredV2Models`, тож кожен +зв’язок або запит до моделі v2 автоматично використовуватиме налаштоване з’єднання. + +Якщо ви виходите за межі підкласів із сумісною схемою та змінюєте автоматично +визначений зовнішній ключ власної `instance_model`, пакет під час запуску +перевіряє, що зв’язки `runs()`, `commands()` та `updates()` явно перевизначено. + +## Міграції {#migrations} + +Міграції Workflow автоматично завантажуються з пакета +(`WorkflowServiceProvider::loadMigrationsFrom`) і виконуються через типове +з’єднання з базою даних. Якщо таблиці v2 використовують інше з’єднання, +опублікуйте міграції командою `php artisan vendor:publish --tag=migrations` та +задайте `protected $connection = 'mysql';` у кожному опублікованому класі +міграції, щоб `php artisan migrate` виконав їх у правильній базі даних. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/ensuring-same-server.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/ensuring-same-server.md new file mode 100644 index 0000000000..e8d6816968 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/ensuring-same-server.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 3 +title: Розміщення та прив’язка worker +description: Обирайте маршрутизацію черг, локальні activity чи сесії worker, не сприймаючи назву черги як прив’язку до фізичного хоста. +tags: + - configuration + - workers + - affinity +keywords: + - activity worker affinity + - dedicated activity queue + - same process activity + - worker sessions +--- + +# Розміщення та прив’язка worker {#worker-placement-and-affinity} + +Назва черги визначає межу маршрутизації й не гарантує прив’язки до фізичного +хоста. Властивість `$queue` workflow або activity обмежує коло worker, які можуть +отримати завдання, але його може отримати будь-який сумісний worker, що опитує +цю чергу. Не передавайте дані між звичайними activity через локальну файлову +систему worker, якщо вони також не збережені в стійкому спільному сховищі. + +## Спрямування роботи до окремого пулу worker {#route-work-to-a-dedicated-worker-pool} + +Використовуйте окрему чергу, якщо певному класу роботи потрібен конкретний пул +worker, наприклад із підключеним спільним томом чи підтримкою GPU: + +```php +use Workflow\V2\Activity; + +final class RenderVideo extends Activity +{ + public ?string $queue = 'gpu-activities'; +} +``` + +Запускайте на цій черзі лише призначені для неї worker Laravel: + +```bash +php artisan queue:work --queue=gpu-activities +``` + +Laravel Horizon може наглядати за цією ж чергою. В обох випадках гарантується +виконання одним із worker, призначених цій черзі. Це не гарантія виконання в тому +самому процесі чи на тому самому хості, що виконував попередній крок. Дані для +передавання розміщуйте в стійких даних workflow, зовнішньому сховищі даних або +іншому спільному сховищі, доступному кожному відповідному worker. + +## Вибір потрібного контракту прив’язки {#choose-the-affinity-contract-you-need} + +- Використовуйте [звичайну activity в черзі](/docs/defining-workflows/activities), + якщо роботу може виконати будь-який сумісний worker її черги. +- Використовуйте [локальну activity](/docs/features/local-activities), якщо одна + коротка activity з повторними спробами має виконуватися в процесі worker + workflow, який зараз обробляє завдання workflow. +- Використовуйте [сесію worker](/docs/features/worker-sessions), якщо кілька + стійких кроків activity мають повторно використовувати одну оренду сесії worker + для стану процесу, пам’яті GPU чи файлової системи, підключеної локально до worker. + +Сесії worker спочатку також застосовують маршрутизацію черг. Після маршрутизації +вони додають явний контракт оренди та можливостей, включно зі строком дії й +необов’язковим повторним отриманням. Вони не роблять окрему машину безсмертною. +Якщо стан має пережити втрату worker чи повторне отримання сесії, зберігайте його +в стійкому спільному сховищі. + +Порівняння способів розміщення наведено в +[моделі виконання activity](/docs/features/activity-execution-model), а +налаштування черги на рівні класу й окремого виклику — у +[параметрах](/docs/configuration/options#queue). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/microservices.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/microservices.md new file mode 100644 index 0000000000..a68dfb979a --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/microservices.md @@ -0,0 +1,140 @@ +--- +sidebar_position: 5 +--- + +# Мікросервіси {#microservices} + +Workflow можуть охоплювати кілька застосунків Laravel. Один застосунок визначає +workflow, інший — activity. Обидва використовують спільні базу даних і чергу, +кожен запускає власний процес `queue:work`. + +## Спільні база даних і черга {#shared-database-and-queue} + +Налаштуйте обидва застосунки на ту саму базу даних і чергу Redis: + +```php +// config/database.php — add to both apps + +'connections' => [ + 'shared' => [ + 'driver' => 'mysql', + 'host' => env('SHARED_DB_HOST', '127.0.0.1'), + 'port' => env('SHARED_DB_PORT', '3306'), + 'database' => env('SHARED_DB_DATABASE', 'workflows'), + 'username' => env('SHARED_DB_USERNAME', 'root'), + 'password' => env('SHARED_DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + ], +], +``` + +```php +// config/queue.php — add to both apps + +'connections' => [ + 'shared' => [ + 'driver' => 'redis', + 'connection' => env('SHARED_REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('SHARED_REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + 'after_commit' => false, + ], +], +``` + +Запускайте міграції лише з одного застосунку: + +```bash +php artisan migrate +``` + +Якщо застосунки використовують інше з’єднання з базою даних, ніж `default`, +дивіться [з’єднання з базою даних](./database-connection.md), щоб спрямувати моделі +до спільного з’єднання. + +## Визначення workflow й activity {#defining-workflows-and-activities} + +Зареєструйте ключі типів, щоб рушій міг маршрутизувати завдання за назвою: + +```php +// App A (workflow service) — config/workflows.php + +'v2' => [ + 'types' => [ + 'workflows' => [ + 'order-processing' => App\Workflows\OrderWorkflow::class, + ], + 'activities' => [], + ], +], +``` + +```php +// App B (activity service) — config/workflows.php + +'v2' => [ + 'types' => [ + 'workflows' => [], + 'activities' => [ + 'charge-payment' => App\Activities\ChargePaymentActivity::class, + ], + ], +], +``` + +Workflow планує activity за ключем типу: + +```php +// App A — app/Workflows/OrderWorkflow.php + +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class OrderWorkflow extends Workflow +{ + public ?string $connection = 'shared'; + public ?string $queue = 'workflows'; + + public function handle(int $orderId): array + { + $charge = activity('charge-payment', $orderId); + return ['order' => $orderId, 'charge' => $charge]; + } +} +``` + +```php +// App B — app/Activities/ChargePaymentActivity.php + +use Workflow\V2\Activity; + +class ChargePaymentActivity extends Activity +{ + public ?string $connection = 'shared'; + public ?string $queue = 'activities'; + + public function handle(int $orderId): string + { + return "charged-{$orderId}"; + } +} +``` + +## Запуск worker {#running-workers} + +Кожен застосунок запускає worker черги на власній черзі: + +```bash +# App A +php artisan queue:work shared --queue=workflows + +# App B +php artisan queue:work shared --queue=activities +``` + +Worker застосунку A відтворює workflow та планує завдання activity. Worker +застосунку B отримує ці завдання, виконує їх і повертає результати. Рушій +координує передавання через спільну базу даних. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/options.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/options.md new file mode 100644 index 0000000000..1153eb1ec2 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/options.md @@ -0,0 +1,213 @@ +--- +sidebar_position: 2 +--- + +# Параметри {#options} + +Під час визначення workflow й activity доступні різні параметри: кількість спроб до невдачі, з’єднання й черга та максимальна дозволена тривалість виконання в секундах. + +```php +use Workflow\V2\Activity; + +class MyActivity extends Activity +{ + public ?string $connection = 'default'; + public ?string $queue = 'default'; + + public int $tries = 3; + + public function backoff(): array + { + return [1, 2, 5, 10, 15, 30, 60, 120]; + } +} +``` + +Властивості `$connection` та `$queue` у `Workflow\V2\Workflow` і `Workflow\V2\Activity` оголошені як `public ?string` і типово мають значення `null`. Перевизначення в підкласах мають зберігати тип із підтримкою null, щоб правила інваріантної типізації публічних властивостей PHP приймали повторне оголошення. Використовуйте `null` для успадкування типового з’єднання чи черги застосунку замість фіксованого значення. + +Тайм-аути activity не налаштовуються властивістю класу. Використовуйте [`ActivityOptions`](#activityoptions) для окремого виклику, наприклад `startToCloseTimeout`, або знімок політики повторних спроб activity на момент планування. Налаштування часу циклу worker наведено також у [політиці ремонту завдань](/docs/polyglot/server-config-reference#workflow-package-controls). + +## StartOptions {#startoptions} + +`Workflow\V2\StartOptions` містить налаштування видимості, усунення дублікатів та тайм-ауту виконання на момент запуску workflow. Він не обирає чергу. Маршрутизацію визначають властивості `$connection` і `$queue` класів workflow та activity разом із `ActivityOptions` окремого виклику. + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; +use Workflow\V2\Enums\DuplicateStartPolicy; + +$workflow = WorkflowStub::make(MyWorkflow::class); + +$workflow->start( + 'arg1', + new StartOptions( + duplicateStartPolicy: DuplicateStartPolicy::ReturnExistingActive, + businessKey: 'order-12345', + labels: ['tenant' => 'acme'], + executionTimeoutSeconds: 3600, + ), +); +``` + +`StartOptions` обробляє рушій workflow. Вони не передаються як аргументи до методу `handle()` workflow. Вони зберігаються разом із workflow та використовуються для подальшого надсилання workflow/activity, зокрема під час відтворення та continue-as-new. + +## ActivityOptions {#activityoptions} + +`Workflow\V2\Support\ActivityOptions` надає перевизначення маршрутизації, повторних спроб та тайм-аутів для окремого виклику activity без зміни самого класу activity: + +```php +use function Workflow\V2\activity; +use Workflow\V2\Support\ActivityOptions; + +$result = activity( + ChargeCard::class, + new ActivityOptions( + connection: 'redis', + queue: 'critical', + maxAttempts: 5, + startToCloseTimeout: 30, + ), + $orderId, +); +``` + +## З’єднання {#connection} + +Налаштування `$connection` задає з’єднання черги, до якого слід надіслати workflow чи activity. Типово `$connection` не задано, тому використовується типове з’єднання. Це можна перевизначити властивістю `$connection` класу workflow чи activity. + +## Черга {#queue} + +Налаштування `$queue` задає чергу, до якої слід додати workflow чи activity. Типово `$queue` не задано, тому використовується типова черга вказаного з’єднання. Це можна перевизначити властивістю `$queue` класу workflow чи activity. + +## Повторні спроби {#retries} + +Налаштування `$tries` керує загальною кількістю спроб activity до визнання її невдалою. Типово `$tries` дорівнює `1`: одна спроба без автоматичних повторень. Задайте `$tries` більше `1`, щоб дозволити повторні спроби, або `0` для нескінченних повторень. Для окремого виклику це можна перевизначити через `ActivityOptions::$maxAttempts`. + +## Тайм-аут {#timeout} + +Базовий клас `Activity` v2 не має властивості `$timeout`. Налаштовуйте тайм-аути activity для окремого виклику через [`ActivityOptions`](#activityoptions) за допомогою `startToCloseTimeout`, `scheduleToStartTimeout`, `scheduleToCloseTimeout` чи `heartbeatTimeout`. Під час планування середовище виконання зберігає знімок отриманої політики повторних спроб у виконанні activity. Тому тайм-аут уже запланованої спроби залишається стабільним, навіть якщо наступне розгортання змінить клас activity чи параметри. Час надсилання на рівні циклу worker контролює [політика ремонту завдань](/docs/polyglot/server-config-reference#workflow-package-controls). + +## Затримка між спробами {#backoff} + +Метод `backoff` повертає масив цілих чисел відповідно до поточної спроби. Типовий `backoff` задає експоненційну затримку до 2 хвилин. Це можна перевизначити реалізацією методу `backoff` у класі activity. + +## Простір імен {#namespace} + +Workflow можна обмежити простором імен для ізоляції кількох просторів. Налаштований простір імен застосовується як до API запуску площини керування, так і до `Workflow\V2\WorkflowStub::make()->start()`. Він зберігається в новому екземплярі, запуску, стійких завданнях та проєкції зведення запуску. Опитування завдань і фільтри видимості Waterline тоді можуть обмежити результати одним простором імен. Використовуйте Workflow 2.4.2 або новішу версію для узгодженого призначення простору імен через обидва PHP API запуску. + +Назви просторів імен мають містити лише малі латинські літери, цифри, крапки, підкреслення та дефіси: відповідність `[a-z0-9._-]+`, максимум 128 символів. + +Задайте типовий простір імен змінною середовища: + +```env +DW_V2_NAMESPACE=production +``` + +Або в `config/workflows.php`: + +```php +'v2' => [ + 'namespace' => env('DW_V2_NAMESPACE'), + // ... +], +``` + +Явний простір імен перевизначає налаштоване типове значення. Stub приймає його третім аргументом `make()`: + +```php +$workflow = \Workflow\V2\WorkflowStub::make( + OrderProcessingWorkflow::class, + 'order-12345', + 'staging', +); +$workflow->start(); +``` + +Площина керування приймає його в параметрах `start()`: + +```php +$controlPlane->start('order-processing', 'order-12345', [ + 'namespace' => 'staging', + // ... +]); +``` + +Коли простір імен не налаштований і не переданий явно, нові екземпляри зберігають простір імен `null`. Спостерігач, обмежений простором імен, не включає такі запуски. Зміна налаштувань не перепризначає наявні екземпляри. Запуск наявного екземпляра через інший простір імен відхиляється. + +### Обмеження Waterline простором імен {#waterline-namespace-scoping} + +Коли Waterline розгорнуто зі спільною базою даних для кількох просторів імен, задайте `WATERLINE_NAMESPACE`, щоб обмежити всі списки одним простором: + +```env +WATERLINE_NAMESPACE=production +``` + +Це додає фільтр простору імен до кожного запиту видимості, тож Waterline показує лише workflow налаштованого простору. Також усі команди (cancel, signal, terminate, update, repair, archive та query) обмежуються цим простором. Команда для екземпляра чи запуску іншого простору поверне 404 замість виконання. + +### Обмеження команд простором імен {#command-namespace-scoping} + +`WorkflowStub::load()`, `loadSelection()` та `loadRun()` приймають необов’язковий параметр `namespace`: + +```php +use Workflow\V2\WorkflowStub; + +// Load only if the instance belongs to the given namespace +$stub = WorkflowStub::load('order-12345', namespace: 'production'); + +// Load a specific run, scoped to namespace +$stub = WorkflowStub::loadRun($runId, namespace: 'production'); + +// Load a specific selection, scoped to namespace +$stub = WorkflowStub::loadSelection('order-12345', $runId, namespace: 'production'); +``` + +Коли `namespace` дорівнює `null` (типово), завантаження працює з усіма просторами імен без обмеження, зберігаючи зворотну сумісність. Коли простір імен задано, запит фільтрує його на рівні бази даних та викидає `ModelNotFoundException`, якщо workflow не існує в цьому просторі. + +Методи команд площини керування (`signal`, `cancel`, `terminate`, `update`, `repair`, `archive`) також приймають `namespace` у масиві параметрів: + +```php +$controlPlane->cancel('order-12345', [ + 'namespace' => 'production', +]); +``` + +### Фільтрування простору імен у мостах завдань {#task-bridge-namespace-filtering} + +Обидва мости завдань, workflow та activity, приймають необов’язковий параметр `namespace` у `poll()`: + +```php +$tasks = $bridge->poll('redis', 'default', limit: 10, namespace: 'production'); +``` + +Без цього параметра `poll()` повертає завдання з усіх просторів імен, зберігаючи сумісність з інсталяціями до появи просторів імен. + +## Стійкі псевдоніми типів {#durable-type-aliases} + +Стійкі ключі типів workflow й activity зберігаються під час реєстрації в `workflows.v2.types`. Дані помилок можуть використовувати той самий шаблон для класів винятків: + +```php +'v2' => [ + 'types' => [ + 'workflows' => [ + 'billing.invoice-sync' => App\Workflows\InvoiceSyncWorkflow::class, + ], + 'activities' => [ + 'payments.capture' => App\Activities\CapturePaymentActivity::class, + ], + 'exceptions' => [ + 'billing.invoice-declined' => App\Exceptions\InvoiceDeclined::class, + ], + 'exception_class_aliases' => [ + App\Exceptions\LegacyInvoiceDeclined::class => App\Exceptions\InvoiceDeclined::class, + ], + ], +], +``` + +Коли помилка activity, update, дочірнього workflow чи workflow записується з псевдонімом винятку, рушій зберігає його в типізованій історії як `exception_type` та в даних помилки як `type`. Відтворення визначає псевдонім перед використанням записаного PHP-класу. Тому подальше переміщення класу може зберегти семантику `catch` workflow, доки псевдонім указує на поточний клас винятку. + +Для імпортованих помилок v1, записаних до появи псевдонімів винятків, `workflows.v2.types.exception_class_aliases` може зіставити записану стару повну назву класу винятку з поточним класом. Стійкі псевдоніми типів `exceptions` усе одно мають пріоритет. Мапа псевдонімів класів — лише міст рефакторингу для вже записаних даних без стійкого `type`. Нові workflow мають використовувати стійкі псевдоніми типів винятків, щоб історія не залежала від назв PHP-класів. + +Фінальна v2 записує стійкі псевдоніми винятків під час запису помилки. Тому налаштуйте стабільні псевдоніми до запису помилок, класи яких можуть згодом переміститися. + +Якщо помилку під час відтворення не можна визначити через стійку мапу `exceptions`, мапу псевдонімів класів чи записаний клас, рушій не підміняє її загальним винятком виконання в коді workflow. Відтворення query викидає `UnresolvedWorkflowFailureException`, Waterline позначає помилку як `exception_replay_blocked = true`, а завдання worker з тією самою прогалиною залишається невдалим, поки запуск відкритий. Виправте зіставлення та відновіть запуск, замість покладання на загальні блоки `catch (RuntimeException)` для перейменованих історичних помилок. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/pruning-workflows.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/pruning-workflows.md new file mode 100644 index 0000000000..b922c8a5f2 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/pruning-workflows.md @@ -0,0 +1,98 @@ +--- +sidebar_position: 6 +--- + +# Очищення workflow {#pruning-workflows} + +Workflow v2 розділяє дві окремі операції життєвого циклу: + +1. **Архівування** позначає запуск у кінцевому стані як архівований, щоб + виключити його з оглядів активних запусків. Архівування **не** видаляє рядків. +2. **Очищення** видаляє застарілі рядки проєкцій чи стійкого стану з бази даних. + Очищення має виконуватися лише для архівованих запусків. + +## Архівування запусків у кінцевому стані {#archiving-terminal-runs} + +Архівуйте завершений, невдалий, скасований чи примусово завершений запуск через +площину керування: + +```php +use Workflow\V2\Contracts\WorkflowControlPlane; + +$controlPlane = app(WorkflowControlPlane::class); + +$result = $controlPlane->archive('order-12345', [ + 'reason' => 'Retention period expired', +]); + +if ($result['accepted']) { + // Run's archived_at is now set; it disappears from active fleet views. +} +``` + +Та сама операція доступна через HTTP-маршрут площини керування. Архівувати можна +лише закриті запуски. Архівування вже архівованого запуску повертає `accepted` +із результатом `archive_not_needed`. + +### Автоматизація архівування {#automating-archival} + +Архівуйте запуски в кінцевому стані за розкладом: знаходьте завершені запуски +поза строком зберігання в таблиці стійких запусків і викликайте `archive()` для +кожного. Для простого правила за часом додайте подібний код до `routes/console.php`: + +```php +use Illuminate\Support\Facades\Schedule; +use Workflow\V2\Models\WorkflowRun; +use Workflow\V2\Contracts\WorkflowControlPlane; + +Schedule::call(function (WorkflowControlPlane $controlPlane): void { + WorkflowRun::query() + ->whereIn('status', ['completed', 'failed', 'cancelled', 'terminated']) + ->whereNull('archived_at') + ->where('closed_at', '<=', now()->subMonth()) + ->with('instance:id') + ->chunkById(100, function ($runs) use ($controlPlane): void { + foreach ($runs as $run) { + $controlPlane->archive($run->instance->id, [ + 'reason' => 'retention_policy', + ]); + } + }); +})->daily(); +``` + +## Очищення рядків проєкцій {#pruning-projection-rows} + +Рядки проєкцій зведення запуску, очікування, часової шкали, timer та походження +можна відновити зі стійкої історії й команд. Використовуйте +`workflow:v2:rebuild-projections --prune-stale`, щоб видалити рядки проєкцій, +стійкий запуск яких більше не існує: + +```bash +# Preview what would be pruned +php artisan workflow:v2:rebuild-projections --prune-stale --dry-run + +# Actually prune stale projection rows +php artisan workflow:v2:rebuild-projections --prune-stale +``` + +## Очищення рядків стійкого стану {#pruning-durable-rows} + +Рядки стійкого стану (`workflow_instances`, `workflow_runs`, +`workflow_history_events`, `workflow_tasks`, `activity_executions`, +`activity_attempts`, `workflow_failures` тощо) слід видаляти лише після +архівування запуску та остаточного завершення строку зберігання. Типізована +історія й походження посилаються на ці рядки, тож очищення має дотримуватися +порядку залежностей. Пакет ще не містить повної команди очищення стійкого стану. +Якщо потрібно звільнити диск, використовуйте обмежене завдання очищення, яке: + +1. Обирає запуски з `archived_at IS NOT NULL AND archived_at <= now()->subMonths(3)`. +2. Спочатку видаляє залежні рядки: події історії, завдання, спроби activity, + виконання activity, помилки, timer, зв’язки та команди. +3. Видаляє рядок `workflow_runs`, а потім `workflow_instances`, коли всі запуски видалено. +4. Відновлює проєкції командою `workflow:v2:rebuild-projections --prune-stale`, + щоб очистити осиротілі рядки проєкцій. + +Відстежуйте строк архівування та строк зберігання стійкого стану окремо. +Архівовані, але не очищені запуски залишаються доступними для експорту історії +та дослідження інцидентів. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md new file mode 100644 index 0000000000..5b989511de --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md @@ -0,0 +1,320 @@ +--- +sidebar_position: 1 +--- + +# Публікація налаштувань {#publishing-config} + +Це створить файл налаштувань `workflows.php` у каталозі `config`. + +```bash +php artisan vendor:publish --provider="Workflow\Providers\WorkflowServiceProvider" --tag="config" +``` + +## Зміна каталогу workflow {#changing-workflows-folder} + +За замовчуванням команди `make` записують файли до каталогу `app/Workflows`. + +```php +php artisan make:workflow MyWorkflow +php artisan make:activity MyActivity +``` + +Це можна змінити налаштуванням `workflows_folder`. + +```php +'workflows_folder' => 'Workflows', +``` + +## Використання власних моделей у застарілій v1 {#using-custom-models-legacy-v1} + +:::note Застаріла версія + +Ключі `stored_workflow_*` налаштовують класи v1 `Workflow\Models\StoredWorkflow*`. +Workflow v2 використовує наведені нижче перевизначення стійких моделей: +`instance_model`, `run_model`, `task_model` тощо. Зберігайте ключі v1 лише тоді, +коли під час міграції ще виконуєте workflow v1. + +::: + +В опублікованому файлі `workflows.php` можна замінити класи моделей v1 власними +підкласами. + +```php +'stored_workflow_model' => App\Models\StoredWorkflow::class, + +'stored_workflow_exception_model' => App\Models\StoredWorkflowException::class, + +'stored_workflow_log_model' => App\Models\StoredWorkflowLog::class, + +'stored_workflow_signal_model' => App\Models\StoredWorkflowSignal::class, + +'stored_workflow_timer_model' => App\Models\StoredWorkflowTimer::class, +``` + +## Перевизначення моделей v2 {#using-model-overrides-v2} + +Середовище виконання також надає перевизначення моделей для таблиць стійких +екземплярів, запусків, завдань, історії та проєкцій: + +```php +'v2' => [ + 'instance_model' => App\Models\WorkflowInstance::class, + 'run_model' => App\Models\WorkflowRun::class, + 'task_model' => App\Models\WorkflowTask::class, + 'history_event_model' => App\Models\WorkflowHistoryEvent::class, + 'run_summary_model' => App\Models\WorkflowRunSummary::class, + 'run_wait_model' => App\Models\WorkflowRunWait::class, + 'run_timeline_entry_model' => App\Models\WorkflowTimelineEntry::class, + 'run_timer_entry_model' => App\Models\WorkflowRunTimerEntry::class, + 'run_lineage_entry_model' => App\Models\WorkflowRunLineageEntry::class, +], +``` + +Ці перевизначення впливають не лише на читання чи відновлення проєкцій. +`WorkflowStub::make()`, `load()`, `loadSelection()`, резервування екземпляра, +вибір запуску та виконання завдань workflow використовують налаштовані +`instance_model`, `run_model` і `task_model`. Деталі Waterline та експорт історії +читають через ті самі класи. Отже, одне перевизначення на рівні застосунку +керує як основним шляхом середовища виконання, так і операторським читанням. + +Зберігайте схему власних підкласів сумісною з вбудованими моделями. Якщо підклас +також змінює назви таблиць чи інші домовленості Eloquent, які моделі пакета +зазвичай визначають автоматично, перевизначте відповідні зв’язки, щоб +`currentRun()`, `runs()` та подібні запити залишалися узгодженими з вашою схемою. + +Дивіться [матрицю налаштування](./customization-matrix.md) для зафіксованого +контракту підтримки v2: які перевизначення безпечні як успадковані підкласи, +які потребують явного перевизначення зв’язків і як узгоджуються серіалізація, +репозиторій, стан, імпорт, міграція та поведінка запису. + +## Кодек даних {#payload-codec} + +v2 використовує `avro` для даних нових workflow: + +```php +'serializer' => 'avro', +``` + +- **`avro`** (типовий, обов’язковий для нових workflow v2) — бінарне кодування + Apache Avro. Компактне під час передавання й зберігання, із швидшим + кодуванням/декодуванням великих даних, ніж застарілий серіалізатор PHP. + +Якщо опубліковані налаштування v1 ще містять `serializer`, фінальна v2 читає це +значення для діагностики `workflow:v2:doctor`, але нові дані v2 все одно +використовують Avro. + +### Застарілі кодеки лише для міграції v1 {#legacy-codecs-v1-migration-only} + +Два кодеки лише для PHP залишаються доступними для читання історії v1 під час міграції: + +- `workflow-serializer-y` — PHP `SerializableClosure` з екрануванням байтів + (типовий у v1). +- `workflow-serializer-base64` — PHP `SerializableClosure` з кодуванням base64. + +Налаштування `serializer` на застарілий кодек буде позначено командою +`php artisan workflow:v2:doctor`. Нові workflow v2 усе одно використовують Avro. +Зберігайте застарілий кодек лише під час завершення чи імпорту запусків v1, +що потребують нативного декодування даних PHP. + +Внутрішній засіб читання для імпорту/завершення v1 розпізнає застарілі повні +назви класів, наприклад `Workflow\Serializers\Y::class`. Це не публічні псевдоніми +кодеків v2, і новий запуск v2 чи SDK не може їх обрати. + +## Маркери сумісності {#compatibility-markers} + +Середовище виконання може позначати кожен новий запуск маркером сумісності, +а worker — оголошувати маркери, які вони можуть безпечно виконувати. Цей захист +залишає тривалі запуски на сумісних збірках під час запланованих оновлень worker. + +Задайте маркер для нових запусків поточної збірки: + +```env +DW_V2_CURRENT_COMPATIBILITY=build-2026-04 +``` + +За потреби дозвольте worker приймати кілька маркерів під час оновлення: + +```env +DW_V2_SUPPORTED_COMPATIBILITIES=build-2026-04,build-2026-03 +``` + +Налаштуйте, як довго знімок heartbeat одного worker залишається видимим в огляді +worker на основі бази даних: + +```env +DW_V2_COMPATIBILITY_HEARTBEAT_TTL=30 +``` + +За потреби обмежте огляд одним застосунком чи простором імен розгортання, +якщо кілька застосунків використовують спільну базу даних workflow: + +```env +DW_V2_COMPATIBILITY_NAMESPACE=sample-app +``` + +Опубліковані налаштування `workflows.php` пов’язують ці значення так: + +```php +'v2' => [ + 'compatibility' => [ + 'current' => env('DW_V2_CURRENT_COMPATIBILITY'), + 'supported' => env('DW_V2_SUPPORTED_COMPATIBILITIES'), + 'namespace' => env('DW_V2_COMPATIBILITY_NAMESPACE'), + 'heartbeat_ttl_seconds' => (int) env('DW_V2_COMPATIBILITY_HEARTBEAT_TTL', 30), + ], +], +``` + +Якщо `supported` не задано, worker типово використовують єдиний маркер `current`. +Старі рядки завдань і зведень запусків, створені до появи маркерів сумісності +завдань, доповнюються маркером запуску під час міграції та, за потреби, знову +під час отримання чи відновлення в середовищі виконання. Завдання й запуски, +що справді ніде не мають маркера, залишаються доступними будь-якому worker. + +Резервна поведінка `getVersion()` віддає перевагу `workflow_definition_fingerprint` +на момент старту запуску, коли відтворення доходить до нової точки розгалуження, +що ще не має типізованої події `VersionMarkerRecorded`. Це дозволяє запуску з +тією самою сумісністю зберегти гілку `DEFAULT_VERSION`, коли він явно почався +на старішому визначенні workflow. Старі запуски, історія `WorkflowStarted` яких +передує знімку відбитка, використовують маркер сумісності на момент старту та +перевірки зайнятої послідовності. Тому продовжуйте змінювати +`DW_V2_CURRENT_COMPATIBILITY` для хвиль розгортання нового версіонованого коду +workflow та тимчасово вказуйте старий і новий маркери в +`DW_V2_SUPPORTED_COMPATIBILITIES`, поки старі worker завершують роботу. + +Кожен worker черги також записує до бази даних знімок heartbeat сумісності під +час `Looping` та обробки завдань. Waterline й засоби деталей показують огляд +локальної збірки (`compatibility_supported`, `compatibility_reason`) та всього +набору worker (`compatibility_namespace`, `compatibility_supported_in_fleet`, +`compatibility_fleet_reason`, `compatibility_fleet`). Коли задано +`DW_V2_COMPATIBILITY_NAMESPACE`, рядки heartbeat у базі даних мають відповідати +цьому простору імен. Кожен знімок повідомляє власний `namespace` разом із +`worker_id`, областю черг, підтримуваними маркерами та `source = database`. +Під час поступового оновлення цей огляд також читає старий формат heartbeat +з кешу для worker, які ще не перезапустилися на нову таблицю знімків. Такі +застарілі рядки кешу залишаються видимими як резервний варіант оновлення навіть +за налаштованого простору імен, але показують `namespace = null`, доки старі +worker не перезапустяться на шлях знімків із простором імен. Отже, сувора +ізоляція просторів імен стає повною лише після переходу всіх worker на heartbeat +у базі даних. Відновлення на рівні транспорту, наприклад повторне надсилання +простроченого завдання чи створення відсутнього, більше не залежить від здатності +worker перевірки виконати це завдання. Лише остаточне отримання залишається +обмеженим сумісністю. + +Коли відкрите завдання вже існує, але ані поточна збірка, ані активні знімки +heartbeat worker не оголошують його маркер, запуск показує очікування сумісного +worker замість хибного стану `repair_needed` на цій збірці. + +## Бюджети історії {#history-budgets} + +Waterline використовує проєкцію зведення запуску для повідомлення розміру +типізованої історії обраного запуску. Ці пороги визначають, коли проєкція +вмикає `continue_as_new_recommended`: + +```env +DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD=10000 +DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD=5242880 +``` + +The published `workflows.php` config maps those values here: + +```php +'v2' => [ + 'history_budget' => [ + 'continue_as_new_event_threshold' => (int) env('DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD', 10000), + 'continue_as_new_size_bytes_threshold' => (int) env('DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD', 5242880), + ], +], +``` + +Задайте будь-якому порогу `0`, щоб вимкнути відповідну частину рекомендації. +Прапорець дорадчий. Використовуйте його для планування меж `continueAsNew()` +до зростання вартості відтворення, не змінюючи поведінки обраного запуску. + +## Політика очікування update {#update-wait-policy} + +API update з очікуванням завершення, як-от `attemptUpdate()`, маршрути webhook +для update та елементи керування update у Waterline, очікують лише в межах +обмеженого бюджету, після чого повертають життєвий цикл усе ще прийнятого update. +Налаштуйте типовий бюджет тут: + +```env +DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS=10 +DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS=50 +``` + +The published `workflows.php` config maps those values here: + +```php +'v2' => [ + 'update_wait' => [ + 'completion_timeout_seconds' => (int) env('DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS', 10), + 'poll_interval_milliseconds' => (int) env('DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS', 50), + ], +], +``` + +`completion_timeout_seconds` визначає, як довго `attemptUpdate*` та HTTP-очікування +завершення намагаються отримати результат, застосований worker, до повернення +прийнятого життєвого циклу замість нескінченного блокування. +`poll_interval_milliseconds` лише налаштовує частоту перевірки викликачем +стійкого рядка update під час очікування. Він не змінює порядок виконання worker +чи поведінку відтворення. Операторські метрики Waterline показують активні +значення `update_wait` поруч із політикою ремонту, щоб оператори бачили +дієві типові налаштування без відкривання файлів. + +## Приховування даних експорту історії {#history-export-redaction} + +Експорт історії навмисно містить збережені дані workflow, команд, activity, +update, завдань та помилок, бо налагодження відтворення й передавання архіву +потребують стійких фактів. Якщо такі файли можуть залишити захищене середовище, +налаштуйте засіб приховування даних перед широким відкриттям точки експорту +чи виведення CLI. + +Створіть засіб приховування даних, який реалізує +`Workflow\V2\Contracts\HistoryExportRedactor`: + +```php +namespace App\Support; + +use Workflow\V2\Contracts\HistoryExportRedactor; + +final class WorkflowHistoryExportRedactor implements HistoryExportRedactor +{ + public function redact(mixed $value, array $context): mixed + { + return [ + 'redacted' => true, + 'path' => $context['path'], + ]; + } +} +``` + +Потім зареєструйте його в `config/workflows.php`: + +```php +'v2' => [ + 'history_export' => [ + 'redactor' => App\Support\WorkflowHistoryExportRedactor::class, + 'signing_key' => env('DW_V2_HISTORY_EXPORT_SIGNING_KEY'), + 'signing_key_id' => env('DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID'), + ], +], +``` + +Засіб отримує поточне значення та контекст, зокрема `path`, `category`, +`workflow_instance_id`, `workflow_run_id` та `workflow_type`. Експорт викликає +його для аргументів/результатів workflow, даних подій історії, даних/контексту +команд, даних update, завдань та activity, а також діагностики повідомлень, +файлів і стеків помилок. Результат містить `redaction.applied`, +`redaction.policy` та `redaction.paths`, щоб подальші інструменти могли +визначити політику, яка сформувала файл. + +Кожен експорт також містить блок `integrity`, обчислений після приховування +даних. Контрольна сума використовує `canonicalization = json-recursive-ksort-v1` +та `checksum_algorithm = sha256`. Коли налаштовано `signing_key`, ті самі +канонічні дані підписуються з `signature_algorithm = hmac-sha256`, а +необов’язковий `signing_key_id` повідомляється як `key_id`. Зберігайте ключ +підпису поза експортованим файлом і змінюйте ідентифікатор ключа, коли подальшим +засобам перевірки потрібно розрізняти ключі. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/_category_.json b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/_category_.json new file mode 100644 index 0000000000..c298a21151 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Обмеження", + "position": 6, + "link": { + "type": "generated-index", + "description": "Обмеження детермінованості й ідемпотентності пакета Workflow для workflow та activity важливі для надійності й коректності всієї системи." + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/activity-constraints.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/activity-constraints.md new file mode 100644 index 0000000000..50f95f749a --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/activity-constraints.md @@ -0,0 +1,66 @@ +--- +sidebar_position: 3 +--- + +# Обмеження activity {#activity-constraints} + +Activity — місце, де код workflow переходить через стійку межу до +введення-виведення та побічних ефектів. Вони не відтворюються як код workflow. +Вони **виконуються щонайменше один раз**, тобто повторні спроби, завершення оренди +та повторна доставка можуть спричинити повторне спостереження тієї самої +логічної роботи. + +Це передбачена поведінка. Якщо activity створює платіж, надсилає електронного +листа, записує до іншої системи або змінює зовнішній ресурс, операція має +допускати безпечне повторення. + +## Що це означає на практиці {#what-this-means-in-practice} + +- Типова ідентичність для ідемпотентності одного логічного виконання activity — + `activity_execution_id`. +- Кожна повторна спроба також отримує власний `activity_attempt_id`. +- Worker може завершити зовнішню роботу, втратити оренду й повідомити результат + із запізненням. Рушій може відхилити таке повідомлення, бо інший worker уже + виграв стійку гонку, але віддалений побічний ефект уже міг відбутися. +- Код activity може використовувати введення-виведення, реальний час та змінний + стан процесу. Код workflow не може. Зберігайте цю межу чіткою. + +## Рекомендовані шаблони ідемпотентності {#preferred-idempotency-patterns} + +Багато зовнішніх API підтримують передавання `Idempotency-Key`. Використовуйте +логічну ідентичність activity із середовища виконання workflow, якщо віддалений +сервіс це підтримує. + +- Віддавайте перевагу `activity_execution_id`, якщо віддалена система має + трактувати повторні спроби як той самий логічний запит. +- Використовуйте `activity_attempt_id` лише тоді, коли віддаленій системі потрібно + розрізняти окремі спроби тієї самої логічної роботи. + +Інші корисні шаблони: + +- Записуйте за детермінованою назвою зовнішнього ресурсу чи природним ключем. +- Використовуйте upsert або таблиці усунення дублікатів зі стійким + ідентифікатором як ключем. +- Робіть операцію природно ідемпотентною, щоб другий виклик нічого не змінював. + +Багато операцій природно ідемпотентні. Якщо закодувати відео двічі, результатом +усе одно буде те саме відео. Якщо двічі видалити той самий файл, друге видалення +нічого не зробить. + +Деякі операції не є ідемпотентними за своєю природою, але дублювання все одно може +бути безпечнішим наслідком збою. Якщо невідомо, чи провайдер справді надіслав +електронного листа, дубль листа може бути кращим за мовчазну втрату повідомлення. +Ухвалюйте таке рішення свідомо. + +## Чого не слід припускати {#what-not-to-assume} + +- Не припускайте, що одна спроба activity завжди виконується лише на одному worker. +- Не припускайте, що повторна спроба означає невдачу попереднього зовнішнього + побічного ефекту. +- Не припускайте, що запізніле завершення означає, що activity не виконувалася. +- Не переносіть побічні ефекти до коду workflow, щоб уникнути повторних спроб. + Це лише перетворить проблему ідемпотентності на помилку детермінованості. + +Повний контракт наведено в +[гарантіях виконання та ідемпотентності](./execution-guarantees.md), а операторську +модель відновлення — у [збоях та відновленні](../failures-and-recovery.md). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/constraints-summary.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/constraints-summary.md new file mode 100644 index 0000000000..fb64a174fc --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/constraints-summary.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 4 +description: Що може робити код workflow й activity. +--- + +# Підсумок обмежень {#constraints-summary} + +| Обмеження | Код workflow | Код activity | +| --- | --- | --- | +| Введення-виведення | Заборонено | Дозволено | +| Змінні глобальні змінні | Заборонено | Дозволено | +| Недетерміновані функції | Заборонено | Дозволено | +| `Carbon::now()` | Заборонено | Дозволено | +| `sleep()` | Заборонено | Дозволено | +| Зовнішні побічні ефекти | Перенесіть їх до activity | Дозволено лише за безпечного повторення | + +Код workflow має бути детермінованим, оскільки рушій відновлює стан workflow +шляхом відтворення історії стійких кроків. Він повторно викликає тіло workflow, +а не activity. Якщо тіло ухвалює інші рішення під час відтворення, рушій не може +коректно відновити стан і продовжити виконання. Тіло workflow не має бути +ідемпотентним. Для відтворення потрібна детермінованість. + +Activity мають бути ідемпотентними, бо їх виконання відбувається щонайменше один +раз. Повторні спроби, завершення оренди та повторна доставка можуть спричинити +повторне спостереження тієї самої спроби. Це передбачена поведінка. Безпечне +повторення тіла activity чи його зовнішнього цільового ресурсу, завдяки ключу +ідемпотентності, детермінованому ресурсу чи природно ідемпотентній операції, +запобігає дублюванню побічних ефектів під час повторного виконання. + +Дивіться [ідемпотентність та детермінованість workflow](/docs/constraints/idempotent-vs-deterministic/) +для прямого порівняння та прикладів, які розділяють ці властивості. + +Публічний контракт v2 щодо цих понять наведено в +[гарантіях виконання та ідемпотентності](./execution-guarantees.md). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/execution-guarantees.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/execution-guarantees.md new file mode 100644 index 0000000000..7e8081e52a --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/execution-guarantees.md @@ -0,0 +1,194 @@ +--- +sidebar_position: 3.5 +title: Гарантії виконання та ідемпотентність +description: Публічний контракт v2 щодо відтворення, повторних спроб, завершення оренди, повторної доставки та типових ідентифікаторів ідемпотентності. +tags: + - constraints + - idempotency + - retries + - replay +keywords: + - execution guarantees + - idempotency + - replay + - redelivery + - lease expiry + - at least once + - exactly once +--- + +# Гарантії виконання та ідемпотентність {#execution-guarantees-and-idempotency} + +Durable Workflow v2 чітко розмежовує **відтворення workflow** та +**виконання activity**: + +- Код workflow відтворюється із зафіксованої історії та має бути детермінованим. +- Код activity виконує побічні ефекти **щонайменше один раз**. +- Рівень стійкої історії записує зафіксовані результати workflow й activity + рівно один раз для заданого стійкого ідентифікатора, навіть якщо транспорт + доставив роботу кілька разів. + +Ці гарантії дозволяють рушію переживати перезапуски worker, завершення оренди, +повторну доставку з черги та поступові розгортання без втрати позиції запуску. + +## Відтворення та повторна спроба — різні операції {#replay-is-not-retry} + +Завдання workflow відновлюють стан шляхом відтворення зафіксованої історії, +а потім вирішують, що робити далі. Відтворення повторно викликає тіло workflow, +але **не** виконує знову activity, не надсилає повторно signal та не повторює +побічні ефекти, уже записані в історії. + +Тому код workflow має залишатися детермінованим. Якщо потрібно перейти через +стійку межу, використовуйте безпечні для workflow засоби, зокрема +[`Workflow::now()`](../defining-workflows/workflow-api.md), +[`sideEffect(...)`](../features/side-effects.md), query, update, результати +activity, memo та атрибути пошуку. + +## Activity виконується щонайменше один раз {#activity-execution-is-at-least-once} + +Activity — частина системи з побічними ефектами, тому її контракт інший: + +- Спробу activity можуть отримати кілька разів. +- Завершення оренди може спричинити повторну доставку іншому worker. +- Worker може завершити зовнішню роботу, втратити оренду та повідомити результат + із запізненням. +- Повторна спроба планує нову стійку спробу того самого логічного виконання activity. + +Тому повторне спостереження є **передбаченою поведінкою, а не умовою помилки**. +Автор застосунку має забезпечити безпечне повторення тіла activity чи викликів +віддаленої системи. + +Рекомендації щодо написання наведено в [обмеженнях activity](./activity-constraints.md), +а операторську поведінку відновлення — у [збоях та відновленні](../failures-and-recovery.md). + +## Що відбувається рівно один раз {#what-is-exactly-once} + +Durable Workflow **не** обіцяє, що процес worker бачить роботу з побічними +ефектами лише один раз. Він обіцяє, що зафіксовані стійкі факти є авторитетними +й не дублюються для того самого стійкого ідентифікатора. + +На практиці це означає: + +- Зафіксоване рішення workflow зберігається один раз у типізованій історії для + ідентифікатора стійкої команди чи кроку, який воно представляє. +- Зафіксований кінцевий результат однієї спроби activity зберігається один раз + для її `activity_attempt_id`. +- Відтворення читає ці зафіксовані факти та відновлює з них стан workflow, + замість повторного виконання зовнішньої роботи. + +Цей поділ — основа розуміння моделі: + +- **Транспорт і worker забезпечують виконання щонайменше один раз.** +- **Зафіксована стійка історія забезпечує запис рівно один раз для кожного + стійкого ідентифікатора.** + +## Завершення оренди та повторна доставка {#lease-expiry-and-redelivery} + +Завершення оренди — звичайний шлях відновлення в розподілених системах: + +- Отримане завдання містить власника оренди та час її завершення. +- Якщо оренда завершується до того, як worker повідомить поступ чи завершення, + завдання стає доступним для повторної доставки. +- Тоді інший worker може отримати ту саму логічну роботу. + +Повторна доставка не означає, що рушій забув уже зафіксовані факти. Вона означає, +що рушій відновлюється після невизначеності на рівні worker чи транспорту. + +Коли ви бачите ознаки дублювання виконання, окремо з’ясуйте два питання: + +1. Чи відбувся побічний ефект більше одного разу? +2. Чи записав стійкий стан більше одного зафіксованого результату для того + самого стійкого ідентифікатора? + +Перше питання вирішує ідемпотентне проєктування activity. Друге належить до +контракту рушія. + +## Типові ідентифікатори ідемпотентності {#default-idempotency-surfaces} + +Ці ідентифікатори — стабільні ключі для усунення дублікатів роботи: + +| Ідентифікатор | Що визначає | Типове використання | +| --- | --- | --- | +| `workflow_instance_id` | Один публічний екземпляр workflow | Обробка дублікатів запуску та бізнес-ідентичність виконання | +| `workflow_run_id` | Один конкретний стійкий запуск | Фіксація обраного запуску для query, експорту чи діагностики | +| `workflow_command_id` | Одна зовнішня команда зміни стану | Усунення дублікатів повторних запитів клієнта | +| `activity_execution_id` | Одне логічне виконання activity між повторними спробами | Типовий ключ віддаленої ідемпотентності для зовнішніх побічних ефектів | +| `activity_attempt_id` | Одна конкретна спроба activity | Кореляція, якщо віддаленій системі потрібно розрізняти окремі спроби | +| `schedule_id` | Одне визначення розкладу | Усунення дублікатів за власністю розкладу та ідентичністю тригера | +| `idempotencyKey` потоку повідомлень | Одне логічне надсилання повідомлення з повторними спробами | Запобігання дублюванню прийому повідомлень під час повторних спроб відправника | + +Якщо сумніваєтеся, використовуйте `activity_execution_id` як типовий ключ +ідемпотентності зовнішньої операції. Використовуйте `activity_attempt_id` лише +тоді, коли зовнішньому отримувачу справді потрібно розрізняти кожну спробу. + +```php +use Workflow\V2\Activity; + +final class ChargeCard extends Activity +{ + public function handle(array $payload): string + { + return app(PaymentGateway::class)->charge( + $payload, + idempotencyKey: $this->activityId(), + attemptCorrelation: $this->attemptId(), + ); + } +} +``` + +## Що розробники мають зробити ідемпотентним {#what-developers-must-make-idempotent} + +Вам **не** потрібно робити саме відтворення workflow ідемпотентним. Фреймворк +обробляє відтворення, відновлюючи стан із зафіксованої історії. + +Вам **потрібно** забезпечити безпечне повторення зовнішніх ефектів, зокрема: + +- викликів оплати чи білінгу +- електронних листів, текстових повідомлень та webhook +- записів до іншої бази даних чи сервісу +- створення чи завантаження файлів +- будь-яких команд, що створюють або змінюють стан поза історією workflow + +Поширені підходи: + +- Передавайте ключ ідемпотентності до віддаленого API. +- Записуйте до детермінованого цільового ресурсу, наприклад за відомим ключем об’єкта. +- Використовуйте upsert чи транзакцію з ключем за стійким ідентифікатором. +- Робіть дію природно повторюваною, щоб другий виклик нічого не змінював. + +## Рекомендації операторам {#operator-guidance} + +Діагностуючи запуск у Waterline, CLI чи журналах сервера: + +- Вважайте повторне спостереження activity після завершення оренди очікуваним, + доки стійкий результат спроби не покаже інше. +- Вважайте запізнілі повідомлення про завершення чи помилку гонкою, яку вирішує + рушій, а не доказом того, що віддалений побічний ефект не відбувся. +- Вважайте відтворення завдання workflow відновленням, а не повторною спробою + на рівні workflow. +- Відсутні сумісні worker, завислі оренди чи повторні ремонти — це операційні + ознаки, які потребують дослідження. Вони не є підставою припускати, що тіло + workflow має повторно виконувати побічні ефекти. + +Найважливіше операторське розмежування — між **невизначеністю транспорту** та +**стійким результатом**. Durable Workflow показує обидва, щоб ви могли їх +розрізнити. + +## Пов’язані посібники {#related-guides} + +- [Огляд](./overview.md) представляє поділ workflow та activity. +- [Обмеження workflow](./workflow-constraints.md) описують правила + детермінованого написання коду. +- [Обмеження activity](./activity-constraints.md) описують безпеку побічних + ефектів та методи ідемпотентності. +- [Збої та відновлення](../failures-and-recovery.md) описують повторні спроби, + застосування тайм-аутів та ремонт. +- [Модель виконання activity](../features/activity-execution-model.md) пояснює + взаємозв’язок activity в черзі, локальних activity, сесій worker та закріпленого + виконання. +- [Локальні activity](../features/local-activities.md) пояснюють спроби activity + в тому самому процесі, heartbeat завдання workflow, повторні спроби та холодне + відтворення. +- [Закріплене виконання](../features/sticky-execution.md) пояснює закріплені кеші + відтворення та роль холодного відтворення як резервного шляху коректності. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/idempotent-vs-deterministic.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/idempotent-vs-deterministic.md new file mode 100644 index 0000000000..1574ee5909 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/idempotent-vs-deterministic.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 2 +title: "Ідемпотентність і детермінованість" +sidebar_label: Ідемпотентність і детермінованість +description: У Durable Workflow 2.0 детермінованість робить оркестрацію безпечною для відтворення, а ідемпотентність робить безпечними ефекти повторних спроб activity. +slug: /constraints/idempotent-vs-deterministic +canonical_path: /uk/docs/constraints/idempotent-vs-deterministic/ +image: /img/idempotent-vs-deterministic.png +tags: + - constraints + - determinism + - idempotency + - replay +keywords: + - idempotent vs deterministic + - idempotency vs determinism + - deterministic vs idempotent + - deterministic workflow + - idempotent activity +--- + +# Ідемпотентні та детерміновані workflow: у чому різниця? {#idempotent-vs-deterministic-workflows-whats-the-difference} + +**Детермінована поведінка ухвалює ті самі рішення за тих самих вхідних даних та +історії. Ідемпотентна поведінка має той самий очікуваний ефект, незалежно від +того, чи операція виконується один раз або кілька.** Детермінованість робить одне +виконання передбачуваним. Ідемпотентність робить повторні виконання безпечними. +Жодна властивість не передбачає іншу. + +| Питання | Детермінованість | Ідемпотентність | +| --- | --- | --- | +| Що залишається незмінним? | Рішення за тих самих вхідних даних та історії | Очікуваний зовнішній ефект після одного чи багатьох викликів | +| Навіщо це Durable Workflow? | Відтворення має відновити той самий шлях workflow | Повторні спроби й повторна доставка не мають дублювати побічні ефекти | +| Де це потрібно? | У коді workflow та оркестрації | В activity та системах, які вони викликають | +| Чи забороняє це введення-виведення або випадковість? | Так, у коді workflow, що відтворюється, якщо результат не записаний | Ні, activity може використовувати введення-виведення, час чи випадковість і все одно забезпечувати безпечне повторення ефекту | + +## Властивості незалежні {#the-properties-are-independent} + +### Детермінованість без ідемпотентності {#deterministic-but-not-idempotent} + +Розгляньмо операцію, що збільшує лічильник: + +```text +increment(counter): + counter.value = counter.value + 1 +``` + +За того самого початкового значення лічильника ця операція завжди виконує те +саме обчислення, тому вона детермінована. Вона не ідемпотентна: два виклики +збільшують лічильник двічі, тоді як один виклик збільшує його один раз. + +### Ідемпотентність без детермінованості {#idempotent-but-not-deterministic} + +Тепер розгляньмо activity, яка встановлює замовленню відомий стан, але +використовує випадкову затримку між спробами під час звернення до сервісу: + +```text +archive(order_id): + wait(random_backoff()) + PUT /orders/{order_id}/status {"status": "archived"} +``` + +Час і кількість внутрішніх спроб можуть різнитися, тому ця реалізація не +детермінована. Але операція може бути ідемпотентною: після одного успішного +виклику чи кількох замовлення архівоване. Відповідь, затримка та внутрішній шлях +не мають бути однаковими. Однаковим має бути очікуваний зовнішній ефект. + +## Чому код workflow має бути детермінованим {#why-workflow-code-must-be-deterministic} + +Durable Workflow відновлює стан оркестрації шляхом відтворення зафіксованої +історії. За тих самих вхідних даних workflow та історії код workflow має планувати +ті самі activity, timer та очікування в тому самому порядку. Читання реального +часу, випадкове значення, запит до поточної бази даних чи мережева відповідь +усередині тіла workflow можуть обрати іншу гілку під час відтворення та створити +рішення, які не відповідають історії. + +Перенесіть ці операції до activity або використовуйте засіб workflow, який +записує значення в історію. Тоді відтворення повертає записаний результат +activity чи побічного ефекту, замість повторного виконання зовнішньої операції. +Детермінованість не вимагає, щоб кожен виклик activity давав універсальну відповідь +поза часом. Вона вимагає, щоб відтворена оркестрація ухвалювала рішення, +узгоджені з її записаною історією. + +Правила написання наведено в [обмеженнях workflow](./workflow-constraints.md), +а модель відтворення — у [принципі роботи](../how-it-works.md). + +## Чому activity та побічні ефекти мають бути ідемпотентними {#why-activities-and-side-effects-must-be-idempotent} + +Activity переходять через стійку межу до баз даних, платіжних API, поштових +провайдерів, об’єктних сховищ та інших систем. Вони можуть доставлятися повторно +після тайм-ауту, повторної спроби, збою worker чи завершення оренди. Worker може +навіть завершити зовнішню зміну й зазнати збою до того, як Durable Workflow +отримає його повідомлення. + +Спроєктуйте очікуваний ефект activity так, щоб наступна спроба була безпечною: + +- надсилайте стабільний ключ ідемпотентності до віддаленого API +- використовуйте upsert за стійким бізнес-ідентифікатором або ідентифікатором виконання +- записуйте за детермінованою назвою об’єкта чи ресурсу +- використовуйте природно ідемпотентну операцію, наприклад встановлення значення + чи видалення відомого ресурсу + +Ключ ідемпотентності не робить код activity детермінованим і не заважає початку +наступної спроби. Він дозволяє зовнішній системі розпізнати, що повторні спроби +представляють ту саму логічну операцію. + +Контракти повторних спроб, повторної доставки та стійкої історії наведено в +[обмеженнях activity](./activity-constraints.md), +[моделі виконання activity](../features/activity-execution-model.md) та +[гарантіях виконання та ідемпотентності](./execution-guarantees.md). + +## Одна межа для всіх SDK {#one-boundary-across-every-sdk} + +Той самий підхід діє незалежно від того, чи worker написано на PHP, Python або Rust: + +1. Зберігайте оркестрацію безпечною для відтворення: за тих самих вхідних даних та + історії видавайте ті самі стійкі команди. +2. Розміщуйте мережеві виклики, годинники, випадковість та змінний зовнішній стан + за межею activity. +3. Надавайте кожній activity із побічними ефектами стабільну ідентичність, за якою + цільова система може усувати дублікати. + +Синтаксис мови змінюється. Межа між детермінованістю та ідемпотентністю залишається. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/overview.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/overview.md new file mode 100644 index 0000000000..ece5d16e89 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/overview.md @@ -0,0 +1,55 @@ +--- +sidebar_position: 1 +--- + +# Огляд {#overview} + +Workflow й activity перебувають по різні боки стійкої межі та мають різні +обмеження. Код workflow **відтворюється**, а код activity **виконується повторно**. +Це різні операції, і правила випливають із цієї відмінності. + +Почніть із [ідемпотентності та детермінованості workflow](/docs/constraints/idempotent-vs-deterministic/), +щоб побачити пряме порівняння й конкретні приклади того, чому жодна з цих +властивостей не передбачає іншу. + +- **Код workflow має бути детермінованим.** Рушій відтворює історію, щоб + відновити стан workflow щоразу, коли він продовжується: на іншому worker, + після перезапуску, розгортання або під час тривалого виконання. Відтворення + повторно викликає тіло workflow, а не activity, тож workflow має ухвалювати + ті самі рішення в тому самому порядку, коли бачить ту саму історію. Читання + реального часу, поточного кешу, випадкові числа, мережеві виклики та інші + джерела змін заборонені в тілі workflow. Використовуйте + [`Workflow::now()`](../defining-workflows/workflow-api.md), + [`sideEffect(...)`](../features/side-effects.md), activity та подібні засоби + для переходу через стійку межу. + +- **Код activity має бути ідемпотентним.** Спроби activity виконуються + **щонайменше один раз**. Повторні спроби, завершення оренди й повторна доставка + можуть спричинити повторне спостереження тієї самої логічної роботи. Це + передбачена поведінка, а не умова помилки. Фреймворк записує щонайбільше один + кінцевий результат спроби на рівні стійкого стану, але тіло activity може + почати виконуватися кілька разів, перш ніж рушій побачить повідомлення + переможця. Вважайте повторне спостереження нормальним і використовуйте ключ + ідемпотентності, детермінований цільовий ресурс або природно ідемпотентну + операцію, якщо зовнішній побічний ефект не має дублюватися. + +- **Event sourcing зберігає історію й не повторює побічні ефекти.** Рушій + записує кожен стійкий крок як типізовану подію історії: завершення activity, + спрацювання timer, отримання signal чи запис побічного ефекту. Відтворення + читає цю історію та повертає збережені результати тілу workflow. Воно не + надсилає activity, timer чи signal повторно. Події стійкого стану для заданого + ідентифікатора записуються рівно один раз на рівні історії, навіть якщо + транспорт доставив роботу кілька разів. + +Разом детермінованість та ідемпотентність дозволяють рушію продовжувати workflow +через розгортання, перезапуски worker та розподілені повторні спроби без втрати +позиції та без дублювання зовнішніх побічних ефектів, які застосунок уже зробив +безпечними для повторення. + +Дивіться [гарантії виконання та ідемпотентність](./execution-guarantees.md), щоб +зрозуміти публічний контракт v2 щодо відтворення, повторної доставки, завершення +оренди та стійкої історії з записом рівно один раз. Потім використовуйте +[обмеження workflow](./workflow-constraints.md) для конкретних правил написання, +які зберігають детермінованість відтворення, та +[обмеження activity](./activity-constraints.md) для рекомендацій щодо +ідемпотентності, які роблять виконання щонайменше один раз безпечним. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/structural-limits.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/structural-limits.md new file mode 100644 index 0000000000..290481bb8d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/structural-limits.md @@ -0,0 +1,352 @@ +--- +sidebar_position: 5 +--- + +# Структурні обмеження {#structural-limits} + +Структурні обмеження встановлюють межі споживання ресурсів одним запуском +workflow. Якщо операція перевищила б налаштовану межу, рушій записує типізовану +помилку з машинозчитуваною категорією `structural_limit` та конкретним видом +обмеження, а потім завершує запуск із помилкою. Це захищає систему від +необмеженого розгалуження, завеликих даних та розростання метаданих. + +## Види обмежень {#limit-kinds} + +| Вид обмеження | Типове значення | Що обмежує | +|---|---|---| +| `pending_activity_count` | 2,000 | Одночасно відкриті виконання activity без кінцевого стану | +| `pending_child_count` | 1,000 | Одночасно відкриті дочірні workflow без кінцевого стану | +| `pending_timer_count` | 2,000 | Одночасно відкриті timer в очікуванні | +| `pending_signal_count` | 5,000 | Одночасно очікувані необроблені signal | +| `pending_update_count` | 500 | Одночасно очікувані нерозв’язані update | +| `command_batch_size` | 1,000 | Елементи одного паралельного розгалуження (`all()`) | +| `payload_size_bytes` | 2 MiB | Серіалізований розмір одного набору аргументів | +| `memo_size_bytes` | 256 KiB | Серіалізований розмір неіндексованих метаданих memo | +| `search_attribute_size_bytes` | 40 KiB | Серіалізований розмір індексованих метаданих атрибутів пошуку | +| `history_transaction_size` | 5,000 | Події історії за одне виконання завдання workflow | + +Усі обмеження застосовуються під час планування, запису чи прийому команди. +Значення `0` вимикає перевірку відповідного виду обмеження. + +## Попередження про наближення до межі {#soft-limit-warnings} + +Перш ніж жорстка межа завершить запуск чи відхилить команду, рушій може +попередити, що ресурс наближається до максимуму. Коли кількісний ресурс +(очікувані activity, дочірні workflow, timer, signal, update, розмір пакета команд +чи події транзакції історії) перевищує налаштований відсоток жорсткої межі, +рушій записує структуроване попередження. + +Типовий поріг попередження — **80%**. Наприклад, за типового обмеження +`pending_activity_count` у 2,000 попередження записується, коли запуск досягає +1,600 очікуваних activity. Запуск продовжується звичайним чином. Попередження +дає операторам час відреагувати: масштабувати worker, запустити continue-as-new +чи підвищити межу до того, як жорсткий захист завершить запуск із помилкою. + +Налаштовуйте поріг через `workflows.v2.structural_limits.warning_threshold_percent`: + +```env +DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT=80 +``` + +Задайте `0`, щоб повністю вимкнути попередження про наближення до межі. + +Записи попереджень містять структурований контекст: + +``` +[Durable Workflow] Run 42 approaching structural limit [pending_activity_count]: 1620 / 2000 (81% utilization, warning at 80%). +``` + +Структурований контекст журналу містить `workflow_run_id`, `workflow_type`, +`limit_kind`, `current`, `limit` та `utilization_percent` для інтеграції із засобами +збирання журналів і сповіщень. + +## Налаштування {#configuration} + +Перевизначайте будь-яку межу через `workflows.v2.structural_limits` у +налаштуваннях або через змінні середовища: + +```php +// config/workflows.php +'v2' => [ + 'structural_limits' => [ + 'pending_activity_count' => (int) env('DW_V2_LIMIT_PENDING_ACTIVITIES', 2000), + 'pending_child_count' => (int) env('DW_V2_LIMIT_PENDING_CHILDREN', 1000), + 'pending_timer_count' => (int) env('DW_V2_LIMIT_PENDING_TIMERS', 2000), + 'pending_signal_count' => (int) env('DW_V2_LIMIT_PENDING_SIGNALS', 5000), + 'pending_update_count' => (int) env('DW_V2_LIMIT_PENDING_UPDATES', 500), + 'command_batch_size' => (int) env('DW_V2_LIMIT_COMMAND_BATCH_SIZE', 1000), + 'payload_size_bytes' => (int) env('DW_V2_LIMIT_PAYLOAD_SIZE_BYTES', 2097152), + 'memo_size_bytes' => (int) env('DW_V2_LIMIT_MEMO_SIZE_BYTES', 262144), + 'search_attribute_size_bytes' => (int) env('DW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES', 40960), + 'history_transaction_size' => (int) env('DW_V2_LIMIT_HISTORY_TRANSACTION_SIZE', 5000), + 'warning_threshold_percent' => (int) env('DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT', 80), + ], +], +``` + +## Точки застосування {#enforcement-points} + +### Кількісні межі очікування на боці виконавця {#pending-count-limits-executor-side} + +Перш ніж виконавець планує activity, дочірній workflow чи timer, він рахує +поточні елементи цього типу без кінцевого стану в запуску. Якщо кількість уже +досягла чи перевищила налаштовану межу, запуск одразу завершується з +`StructuralLimitExceededException`. + +Це захищає від шаблонів на кшталт необмежених циклів паралельного розгалуження, +які накопичують тисячі очікуваних операцій: + +```php +// This will fail if $items exceeds the pending_activity_count limit +$calls = []; +foreach ($items as $item) { + $calls[] = fn () => activity(ProcessItemActivity::class, $item); +} +return all($calls); // Also checked against command_batch_size +``` + +Для обробки великих пакетів у межах обмежень обробляйте елементи частинами +обмеженого розміру: + +```php +foreach (array_chunk($items, 500) as $chunk) { + $calls = []; + foreach ($chunk as $item) { + $calls[] = fn () => activity(ProcessItemActivity::class, $item); + } + all($calls); +} +``` + +### Кількісні межі очікування на боці прийому {#pending-count-limits-intake-side} + +Коли команда signal чи update надходить через площину керування, webhook або +`WorkflowStub`, рушій до прийняття команди перевіряє кількість необроблених +signal (стан `received`) чи нерозв’язаних update (стан `accepted`) цільового запуску. + +Якщо кількість досягла чи перевищила налаштовану межу, команда **відхиляється** +з причиною `structural_limit_exceeded`. Відповідь містить машинозчитувані +метадані (`structural_limit_kind`, `structural_limit_value`, +`structural_limit_configured`), щоб викликачі могли визначити першопричину. + +На відміну від меж виконавця, які завершують запуск із помилкою, межі прийому +відхиляють окрему команду без завершення workflow. Запуск залишається активним, +і викликач може повторити спробу після обробки очікуваних елементів. + +```php +// If 5,000 signals are already pending, this will be rejected: +$result = $workflow->attemptSignal('process-item', $data); +if ($result->rejected()) { + // $result->rejectionReason() === 'structural_limit_exceeded' + // back off and retry later +} +``` + +### Розмір пакета команд {#command-batch-size} + +Функція `all()` перевіряє загальну кількість кінцевих операцій в одній групі +розгалуження щодо `command_batch_size`. Перевірка відбувається до планування +окремих activity чи дочірніх workflow, тож запуск завершується з помилкою +цілісно, без часткового планування пакета. + +### Розмір даних {#payload-size} + +Під час планування activity чи дочірнього workflow виконавець серіалізує +аргументи й перевіряє довжину в байтах щодо `payload_size_bytes`. Якщо +серіалізовані дані перевищують межу, запуск завершується з помилкою до створення +будь-яких рядків цієї операції в базі даних. + +Це застосовується до: + +- **Аргументів activity**: перевірка під час серіалізації аргументів + `ActivityCall` у `scheduleActivity`. +- **Аргументів дочірнього workflow**: перевірка під час серіалізації аргументів + запуску дочірнього workflow в `scheduleChildWorkflow`, до створення рядків + дочірнього екземпляра чи запуску. + +```php +// A 3 MiB payload will fail with the default 2 MiB limit +activity(ProcessDocumentActivity::class, $threeMegabyteBlob); +``` + +Щоб працювати в межах обмеження, увімкніть +[зовнішнє сховище даних](../features/external-payload-storage.md) для простору +імен, щоб середовище виконання автоматично виносило дані понад поріг до +налаштованого об’єктного сховища. Або зберігайте байти самостійно й передавайте +посилання на рівні застосунку: + +```php +$ref = Storage::put('docs/incoming.pdf', $blob); +activity(ProcessDocumentActivity::class, $ref); +``` + +Зовнішнє сховище даних зберігає цілісність відтворення, записуючи в історію +оболонку `durable-workflow.v2.external-payload-reference.v1` із хешем. Тому +обмеження бачить оболонку посилання як дані, а не самі байти. + +### Розмір memo {#memo-size} + +Коли workflow додає чи оновлює записи memo через `upsertMemo()`, виконавець +об’єднує нові записи з наявною мапою memo, кодує результат у JSON і перевіряє +довжину в байтах щодо `memo_size_bytes`. Якщо об’єднане memo перевищує межу, +запуск завершується з помилкою до збереження memo. + +### Розмір транзакції історії {#history-transaction-size} + +Кожне виконання завдання workflow (один крок відтворення та поступу вперед) +може створювати нові події історії: планування activity, створення timer, запис +побічного ефекту, оновлення атрибутів пошуку тощо. `history_transaction_size` +обмежує загальну кількість нових подій, які може створити одне завдання. + +Це виявляє неконтрольовані цикли, які створюють необмежену кількість подій +в одному завданні без передавання керування: + +```php +// If a workflow schedules thousands of operations in one task, +// the history transaction limit prevents the task from growing +// without bound. Process large batches in bounded chunks instead. +foreach (array_chunk($items, 500) as $chunk) { + $calls = []; + foreach ($chunk as $item) { + $calls[] = fn () => activity(ProcessItemActivity::class, $item); + } + all($calls); // Each chunk is a separate task execution +} +``` + +Перевірка виконується на початку кожної ітерації головного циклу виконавця. +Події під час відтворення (читання наявної історії) не зараховуються до межі. +Враховуються лише нові події, записані поточним завданням. + +### Розмір атрибутів пошуку {#search-attribute-size} + +Коли workflow додає чи оновлює атрибути пошуку через `upsertSearchAttributes()`, +виконавець об’єднує нові атрибути з наявним набором, кодує результат у JSON і +перевіряє довжину в байтах щодо `search_attribute_size_bytes`. Якщо об’єднані +атрибути перевищують межу, запуск завершується з помилкою до їх збереження. + +## Класифікація помилок {#failure-taxonomy} + +Коли структурне обмеження перевищено, рушій записує: + +- Рядок `WorkflowFailure` із `failure_category = structural_limit` +- Подію історії `WorkflowFailed` із такими полями: + - `failure_category = structural_limit` + - `structural_limit_kind` — конкретне перевищене обмеження (наприклад, + `pending_activity_count`, `command_batch_size`) + - `structural_limit_value` — поточна кількість чи розмір, що спричинили перевищення + - `structural_limit_configured` — налаштована верхня межа + +Ці метадані машинозчитувані, тож оператори, Waterline та зовнішні інструменти +можуть визначати першопричину без розбору повідомлень довільним текстом. + +## Перевірка стану {#health-check} + +Поточні налаштування структурних обмежень містяться у знімку перевірки стану v2 +під `structural_limits`, що показує операторам активні верхні межі: + +```json +{ + "structural_limits": { + "pending_activity_count": 2000, + "pending_child_count": 1000, + "pending_timer_count": 2000, + "pending_signal_count": 5000, + "pending_update_count": 500, + "command_batch_size": 1000, + "payload_size_bytes": 2097152, + "memo_size_bytes": 262144, + "search_attribute_size_bytes": 40960, + "history_transaction_size": 5000, + "warning_threshold_percent": 80 + } +} +``` + +## Обмеження залежно від бекенду {#backend-dependent-limits} + +Знімок можливостей бекенду публікує повний контракт структурних обмежень з +урахуванням поточної інфраструктури. Більшість меж є значеннями налаштувань, +незалежними від бекенду, але деякі бекенди додають обмеження: + +- **Черга SQS**: Amazon SQS обмежує відкладену доставку повідомлень 900 секундами, + тож знімок містить `max_single_timer_delay_seconds: 900`. Timer з більшою + тривалістю поділяються транспортним рівнем на частини. +- **База даних SQLite**: SQLite виконує записи послідовно, тож знімок містить + `concurrent_write_safety: limited`. Високі кількісні межі очікування можуть + спричинити конкуренцію за блокування під одночасним навантаженням worker. + +Повний контракт доступний у розділі `structural_limits` відповіді про можливості бекенду: + +```json +{ + "structural_limits": { + "configured": { "pending_activity_count": 2000, "..." : "..." }, + "backend_adjustments": { "max_single_timer_delay_seconds": 900 }, + "effective": { "pending_activity_count": 2000, "max_single_timer_delay_seconds": 900, "..." : "..." }, + "issues": [ + { + "component": "structural_limits", + "severity": "info", + "code": "queue_max_delay_constraint", + "message": "The [sqs] queue driver limits delayed dispatch to 900 seconds; timers exceeding this are chunked by the transport layer." + } + ] + } +} +``` + +## Waterline + +Waterline показує помилки структурних обмежень у таблиці винятків із категорією +`structural_limit`. Деталі помилки на часовій шкалі містять вид обмеження, +поточне значення та налаштовану верхню межу. + +## Обмеження на межі запиту Server {#server-request-boundary-limits} + +Під час використання окремого Server інший набір меж застосовується на межі +HTTP-запиту до того, як завдання workflow, signal, update чи query потрапляє +до площини керування. Ці межі одразу повертають `422 validation_failed` або +`413 payload_too_large` для перевірки всього тіла. Клієнти дізнаються причину +відхилення без запису сервером будь-якого рядка до бази даних. + +| Обмеження | Типове значення | Ключ налаштування | Що обмежує | +|---|---|---|---| +| Розмір тіла | 2 MiB | `server.limits.max_payload_bytes` | Загальна кількість байтів тіла HTTP-запиту | +| Розмір memo | 256 KiB | `server.limits.max_memo_bytes` | Серіалізоване memo в `POST /workflows` та `POST /schedules` | +| Кількість атрибутів пошуку | 100 | `server.limits.max_search_attributes` | Зареєстровані власні атрибути пошуку на простір імен | +| Довжина ключа атрибута пошуку | 128 байтів | `server.limits.max_search_attribute_key_length` | Довжина одного ключа атрибута пошуку під час запуску | +| Розмір значення атрибута пошуку | 2 KiB | `server.limits.max_search_attribute_value_bytes` | Кожне рядкове значення та кожен елемент масиву під час запуску | +| Назва signal / update / query | 256 байтів | `server.limits.max_operation_name_length` | Сегмент шляху URL для назв signal/update/query | +| Довжина `workflow_id` | 128 символів | валідатор контролера | ID workflow в `POST /workflows` | +| `workflow_type` / `task_queue` / `business_key` | 255 символів | валідатор контролера | Рядкові поля в `POST /workflows` | +| `request_id` | 255 символів | валідатор контролера | Токен усунення дублікатів для signal/update/cancel/terminate | +| `reason` | 1,000 символів | валідатор контролера | Текст причини для cancel/terminate/archive | + +Кожна межа окремо налаштовується через змінні середовища `DW_*`. Повний контракт +наведено в `config/dw-contract.php`. Значення `0` вимикає перевірку конкретної +межі, залишаючи решту чинними. + +Поточні значення публікуються під `limits` у відповіді `GET /api/cluster/info`, +тому клієнти можуть дізнатися їх під час виконання: + +```json +{ + "limits": { + "max_payload_bytes": 2097152, + "max_memo_bytes": 262144, + "max_search_attributes": 100, + "max_search_attribute_key_length": 128, + "max_search_attribute_value_bytes": 2048, + "max_operation_name_length": 256, + "max_pending_activities": 2000, + "max_pending_children": 2000 + } +} +``` + +Помилки валідації повертаються в стандартній оболонці помилок площини керування +з `reason: "validation_failed"` та мапою `validation_errors`, ключами якої є +проблемні поля (`signal_name`, `update_name`, `query_name`, `search_attributes`, +`memo` тощо). Відхилення через розмір даних використовують +`reason: "payload_too_large"` та код стану `413`. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/constraints/workflow-constraints.md b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/workflow-constraints.md new file mode 100644 index 0000000000..bbd454b1fd --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/constraints/workflow-constraints.md @@ -0,0 +1,66 @@ +--- +sidebar_position: 2 +--- + +# Обмеження workflow {#workflow-constraints} + +Вимога детермінованості класів workflow означає, що клас workflow не має +залежати від зовнішнього стану чи сервісів, які можуть змінюватися з часом. +Тому клас workflow не має виконувати операцій, що покладаються на поточну дату +й час, поточного користувача, зовнішні мережеві ресурси або інші джерела стану, +який може змінитися. + +Ось приклади того, чого не слід робити всередині класу workflow: + +- Не використовуйте `Carbon::now()` для поточної дати й часу, бо кожен виклик + може повертати інший результат. Використовуйте `Workflow\V2\Workflow::now()` + або допоміжну функцію `Workflow\V2\now()`, які повертають час workflow, + безпечний для відтворення. +- Не використовуйте `Auth::user()` для поточного користувача, бо результат + залежить від того, хто зараз увійшов у систему. Передавайте користувача як + вхідні дані workflow під час запуску. +- Не робіть мережевих запитів до зовнішніх ресурсів, бо вони можуть бути + повільними або недоступними в різні моменти. Передавайте потрібні дані до + workflow під час запуску або отримуйте їх через activity. +- Не використовуйте генератори випадкових чисел чи інші джерела випадковості + без побічного ефекту, бо кожен виклик дає інший результат. Передавайте потрібну + випадковість як вхідні дані workflow під час запуску. + +## Перевірки під час запуску застосунку {#boot-time-guardrails} + +Коли класи workflow зареєстровані в `workflows.v2.types.workflows`, пакет під час +запуску застосунку перевіряє їх на очевидні виклики, небезпечні для відтворення +(`Carbon::now()`, `Auth::user()`, `DB::`, `Http::`, `random_int()` та подібні), +і повідомляє результати відповідно до `workflows.v2.guardrails.boot`: + +| Режим | Поведінка | +|------|----------| +| `warn` (типовий) | Записує попередження для кожного виявленого випадку. Не блокує запуск застосунку. | +| `silent` | Повністю пропускає перевірку під час запуску. | +| `throw` | Викидає `LogicException` на першому виявленому випадку. Корисно для CI. | + +```php +// config/workflows.php +'v2' => [ + 'guardrails' => [ + 'boot' => env('DW_V2_GUARDRAILS_BOOT', 'warn'), + ], +], +``` + +Задайте `DW_V2_GUARDRAILS_BOOT=throw` у CI, щоб нові небезпечні для відтворення +виклики спричиняли невдачу збірки. У production залишайте `warn`, щоб прихований +випадок не блокував розгортання. + +У межах першого випуску перевірка під час запуску — єдиний блокувальний захист +режиму workflow. Середовище виконання не повторює діагностику детермінованості +під час отримання завдання workflow. Для 2.0 це свідоме відкладення: перевірка +під час запуску виявляє проблеми локально зареєстрованих PHP workflow до +розгортання, а Waterline показує зміну відбитка визначення для тривалих запусків, +не перетворюючи отримання завдань між збірками на нове джерело збоїв розгортання. + +Запуски без знімка відбитка також дотримуються обережної політики першого випуску. +Якщо запуск доходить до нової гілки `getVersion()`, а його історія +`WorkflowStarted` передує знімку відбитка, середовище залишає його на +`WorkflowStub::DEFAULT_VERSION` замість припущення, що поточне визначення безпечне. +Дивіться [версіонування](../features/versioning.md). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/_category_.json b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/_category_.json new file mode 100644 index 0000000000..399f7d8118 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Визначення workflow", + "position": 5, + "link": { + "type": "generated-index", + "description": "Workflow й activity визначаються як класи, що успадковують базові класи Workflow та Activity, надані фреймворком." + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/activities.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/activities.md new file mode 100644 index 0000000000..b095b05346 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/activities.md @@ -0,0 +1,140 @@ +--- +sidebar_position: 2 +title: Activity +description: Визначайте класи activity для недетермінованих побічних ефектів, повторних спроб, маршрутизації та параметрів окремих викликів. +tags: + - authoring + - activities + - retries +keywords: + - durable workflow activity class + - activity options + - non deterministic side effects +--- + +# Activity {#activities} + +Activity — це одиниця роботи, яка виконує конкретне завдання чи операцію +(наприклад, API-запит, обробку даних або надсилання електронного листа) та може +виконуватися з workflow. + +:::note Контракт стійкого виконання +Звичайні activity v2 виконуються як стійкі завдання в черзі. Локальні activity — +явний примітив для короткої роботи в тому самому процесі, який усе одно записує +стійку історію activity. Сесії worker доступні, коли кілька стійких кроків activity +потребують однієї оренди worker. Закріплене виконання є підтримуваною +оптимізацією відтворення, а не контрактом коректності. Точні контракти наведено в +[моделі виконання activity](/docs/features/activity-execution-model), +[локальних activity](/docs/features/local-activities), +[сесіях worker](/docs/features/worker-sessions) та +[закріпленому виконанні](/docs/features/sticky-execution). +::: + +Для створення нової activity можна використати команду Artisan `make:activity`: + +```php +php artisan make:activity MyActivity +``` + +Вона визначається успадкуванням класу `Activity` та реалізацією методу `handle()`. + +```php +use Workflow\V2\Activity; + +class MyActivity extends Activity +{ + public function handle() + { + // Perform some work... + return $result; + } +} +``` + +## Контракт виконання {#execution-contract} + +Кожен виклик `activity(...)` записує команду activity в історію workflow та +створює стійке завдання в черзі, яке може отримати worker. Звичайні activity +може отримати будь-який сумісний worker, а +[сесії worker](/docs/features/worker-sessions) дозволяють закріпити послідовність +спроб activity за однією орендою сесії worker, коли workflow явно обирає цей +контракт. + +Якщо вам потрібне значення, безпечне для відтворення, без планування роботи в +черзі, використовуйте [`sideEffect(...)`](/docs/features/side-effects). Якщо +потрібна коротка activity в тому самому процесі з повторними спробами, тайм-аутом, +heartbeat та історією activity, використовуйте +[локальні activity](/docs/features/local-activities). +Повну модель розміщення наведено в +[моделі виконання activity](/docs/features/activity-execution-model), а поведінку +закріпленого кешу відтворення — у +[закріпленому виконанні](/docs/features/sticky-execution). + +## Ідемпотентність та стійка ідентичність {#idempotency-and-durable-identity} + +Activity виконується щонайменше один раз. Повторні спроби, завершення оренди й +повторна доставка можуть призвести до повторного спостереження тієї самої +логічної activity, тому побічний ефект або віддалений отримувач має допускати +безпечне повторення. + +Усередині activity використовуйте стійкі ідентифікатори середовища виконання, +якщо потрібні кореляція чи віддалене усунення дублікатів: + +- `activityId()` визначає одне логічне виконання activity між повторними + спробами та є типовим ключем віддаленої ідемпотентності. +- `attemptId()` визначає одну конкретну спробу цього виконання. +- `attemptCount()` повідомляє, яка спроба зараз виконується. + +Віддавайте перевагу `activityId()`, якщо віддалена система має трактувати повторні +спроби як той самий логічний запит. Використовуйте `attemptId()` лише тоді, коли +віддаленій системі справді потрібно розрізняти окремі спроби. Якщо worker +завершить віддалену роботу, втратить оренду та повідомить результат із +запізненням, рушій може відхилити таке завершення, бо інший worker уже виграв +стійку гонку. Проте віддалений побічний ефект уже міг відбутися. + +Дивіться [гарантії виконання та ідемпотентність](/docs/constraints/execution-guarantees), +[heartbeat](/docs/features/heartbeats) та +[збої й відновлення](/docs/failures-and-recovery), щоб зрозуміти операційну модель +цих ідентифікаторів. + +## Перевизначення для окремого виклику {#per-call-overrides} + +Маршрутизація та повторні спроби за замовчуванням використовують властивості +`$connection`, `$queue`, `$tries` і `backoff()` класу activity. Якщо окремому виклику +потрібно їх перевизначити, наприклад спрямувати його до черги з вищим пріоритетом +або збільшити кількість спроб, передайте екземпляр `ActivityOptions`: + +```php +use function Workflow\V2\activity; +use Workflow\V2\Support\ActivityOptions; + +$result = activity( + MyActivity::class, + new ActivityOptions(queue: 'high-priority', maxAttempts: 5), + 'Taylor', +); +``` + +Повний перелік полів, зокрема тайм-аути та heartbeat, наведено в +[параметрах activity](/docs/configuration/options#activityoptions). + +## Виклики локальних activity {#local-activity-calls} + +Використовуйте `localActivity(...)`, якщо короткий ідемпотентний побічний ефект +має виконуватися всередині поточного процесу worker workflow, але потребує +семантики повторних спроб, тайм-ауту, heartbeat, скасування та історії activity: + +```php +use Workflow\V2\Support\LocalActivityOptions; +use function Workflow\V2\localActivity; + +$receipt = localActivity( + SendReceiptActivity::class, + new LocalActivityOptions(maxAttempts: 3, startToCloseTimeout: 10), + $orderId, +); +``` + +Локальні activity відхиляють `connection`, `queue`, маршрутизацію сесії worker +та параметри schedule-to-start, оскільки не створюють звичайних завдань activity. +Повний контракт наведено в [локальних activity](/docs/features/local-activities). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/passing-data.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/passing-data.md new file mode 100644 index 0000000000..8ebc48ac20 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/passing-data.md @@ -0,0 +1,138 @@ +--- +sidebar_position: 4 +--- + +# Передавання даних {#passing-data} + +Передавати дані до workflow можна через метод `start()`. + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(MyWorkflow::class); +$workflow->start('world'); +``` + +Аргументи передаються до методу `handle()` workflow. + +Так само можна передавати дані до activity через допоміжну функцію `activity()`. + +```php +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle($name) + { + return activity(MyActivity::class, $name); + } +} +``` + +Аргументи передаються до методу `handle()` activity. + +```php +use Workflow\V2\Activity; + +class MyActivity extends Activity +{ + public function handle($name) + { + return "Hello, {$name}!"; + } +} +``` + +import PassingDataSimulator from '@site/src/components/PassingDataSimulator'; + + + +Зазвичай так варто передавати лише невеликі обсяги даних. Замість передавання +великих обсягів записуйте дані до бази даних, кешу чи файлової системи. Потім +передавайте ключ або шлях до файлу у workflow й activity. Activity зможуть +прочитати дані за цим ключем або шляхом. + +Коли застосунку потрібно переносити великі обсяги байтів через історію, +наприклад документи, медіа чи серіалізовані експорти, увімкніть +[зовнішнє сховище даних](../features/external-payload-storage.md) для простору +імен. Середовище виконання виносить дані понад поріг до налаштованого об’єктного +сховища та записує в історію оболонку посилання, яку можна перевірити. Це +зберігає цілісність відтворення в межах +[`payload_size_bytes`](../constraints/structural-limits.md#payload-size). + +## Результат {#output} + +Після завершення workflow отримати результат можна методом `output()`. + +```php +$workflow->output(); +=> 'Hello, world!' +``` + +## Моделі {#models} + +Передавання моделей працює подібно до `SerializesModels`. + +```php +use App\Models\User; +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle(User $user) + { + return activity(MyActivity::class, $user->name); + } +} +``` + +Коли модель Eloquent передається до workflow або activity, серіалізується лише +її `ModelIdentifier`. Це зменшує розмір даних і допомагає workflow працювати +ефективно. + +``` +object(ModelIdentifier) { + id: 42, + class: "App\Models\User", + relations: [], + connection: "mysql" +} +``` + +Під час виконання workflow або activity отримує з бази даних повний екземпляр +моделі, включно із завантаженими зв’язками. Якщо потрібно уникнути додаткових +запитів до бази даних під час виконання, перетворіть модель на масив перед +передаванням. + +## Впровадження залежностей {#dependency-injection} + +Крім передавання даних, можна вказувати типи залежностей у методах `handle()` +workflow або activity. Сервісний контейнер Laravel автоматично впровадить ці +залежності. + +```php +use Illuminate\Contracts\Foundation\Application; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle(Application $app) + { + if ($app->runningInConsole()) { + // ... + } + } +} +``` + +Впровадження залежностей не послаблює вимогу детермінованості workflow. Workflow +може вказувати типи стабільних сервісів, наприклад контейнера застосунку Laravel +чи засобів читання налаштувань. Але залежності, які виконують роботу, усе одно +потребують правильної стійкої межі. Зовнішнє введення-виведення, доступ до бази +даних, виклики сервісів та іншу роботу, яка може зазнати збою чи потребує повторних +спроб, розміщуйте в `activity(...)` або `localActivity(...)`. Використовуйте +`sideEffect()` лише для одноразового знімка значення, безпечного для відтворення, +який має бути зафіксований в історії: читання реального часу, згенерованого +випадкового значення або налаштування, збереженого в момент рішення. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/starting-workflows.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/starting-workflows.md new file mode 100644 index 0000000000..d61f99726d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/starting-workflows.md @@ -0,0 +1,36 @@ +--- +sidebar_position: 3 +title: Запуск workflow +description: Запускайте екземпляри workflow асинхронно та додавайте аргументи, метадані видимості, бізнес-ключі й параметри запуску. +tags: + - authoring + - workflow-start + - visibility +keywords: + - start workflow + - workflow instance id + - durable workflow start options +--- + +# Запуск workflow {#starting-workflows} + +Щоб запустити workflow, створіть його екземпляр, а потім викличте метод `start()`. + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(MyWorkflow::class); +$workflow->start(); +``` + +Після запуску workflow виконуватиметься асинхронно worker черги. Метод `start()` +повертається одразу й не блокує поточний запит. + +Передавайте аргументи до методу `handle()` workflow через `start()`: + +```php +$workflow->start($orderId); +``` + +За допомогою `StartOptions` можна додати мітки видимості, бізнес-ключ, memo або +тайм-аути. За потреби дивіться [параметри запуску](/docs/configuration/options#startoptions). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-api.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-api.md new file mode 100644 index 0000000000..11b3983492 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-api.md @@ -0,0 +1,277 @@ +--- +sidebar_position: 6 +title: Workflow API +sidebar_label: Workflow API +description: Довідник Workflow API. +tags: + - reference + - workflows + - api +keywords: + - workflow API +--- + +# Workflow API {#workflow-api} + +Ця сторінка містить повний довідник. Для простішого викладу дивіться сторінку окремої можливості. + +- [Workflow](./workflows.md) описує структуру класу workflow та модель детермінованої оркестрації. +- [Activity](./activities.md) описує побічні ефекти, повторні спроби й маршрутизацію. +- [Локальні activity](../features/local-activities.md) описують коротку роботу activity в тому самому процесі з історією activity та семантикою повторних спроб. +- [Signal](../features/signals.md), [update](../features/updates.md) та [query](../features/queries.md) описують контракти взаємодії з workflow. +- [Timer](../features/timers.md), [очікування умов](../features/condition-waits.md) та [продовження як новий запуск](../features/continue-as-new.md) описують керування тривалим виконанням. +- [Потоки повідомлень](../features/message-streams.md) описують повторювані впорядковані повідомлення із семантикою курсора. + +## Додаткові відомості для ШІ {#more-info-for-ai} + +Більшість читачів може пропустити блок нижче. Відкрийте його, якщо потрібні точні сигнатури, контракти повернення, відомості для автоматизації або повний API в одному місці. + +
+Додаткові відомості для ШІ + +**Базовий об’єкт workflow** + +```php +use Workflow\V2\Workflow; + +abstract class Workflow +{ + public ?string $connection = null; + public ?string $queue = null; + + public function workflowId(): string; + public function runId(): string; + public function lastChild(): ?ChildWorkflowHandle; + public function children(): array; + public function historyLength(): int; + public function historySize(): int; + public function shouldContinueAsNew(): bool; +} +``` + +| Член | Коли використовувати | Контракт повернення | +| --- | --- | --- | +| `workflowId()` | Workflow потрібен стабільний публічний ідентифікатор екземпляра. | Рядок ідентифікатора екземпляра, незмінний між continue-as-new. | +| `runId()` | Workflow потрібен ідентифікатор поточного запуску. | Рядок ідентифікатора запуску обраного виконання. | +| `lastChild()` | Workflow має надіслати signal останньому створеному дочірньому workflow. | `ChildWorkflowHandle` або `null`. | +| `children()` | Workflow потрібні дескриптори дочірніх workflow, видимих поточній послідовності відтворення. | Список `ChildWorkflowHandle`. | +| `historyLength()` | Workflow потрібен кількісний показник бюджету історії. | Поточна кількість подій історії. | +| `historySize()` | Workflow потрібен показник бюджету історії в байтах. | Приблизний розмір збереженої історії в байтах. | +| `shouldContinueAsNew()` | Workflow має перейти до нового запуску до того, як історія стане дорогою. | `true`, якщо налаштовані бюджети історії рекомендують перехід. | + +## Стійкі команди {#durable-commands} + +Статичний фасад делегує виклики допоміжним функціям у просторі імен `Workflow\V2`. Обидві форми еквівалентні: + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\activity; + +$resultFromFacade = Workflow::activity(SendReceipt::class, $orderId); +$resultFromHelper = activity(SendReceipt::class, $orderId); +``` + + + +| Фасад | Допоміжна функція | Сигнатура | Стійкий ефект | +| --- | --- | --- | --- | +| `Workflow::activity()` | `activity()` | `activity(string $activity, mixed ...$arguments): mixed` | Планує activity та очікує її результат. | +| `Workflow::executeActivity()` | `activity()` | `executeActivity(string $activity, mixed ...$arguments): mixed` | Псевдонім `activity()`. | +| `Workflow::localActivity()` | `localActivity()` | `localActivity(string $activity, mixed ...$arguments): mixed` | Виконує коротку activity в поточному процесі worker workflow та записує історію activity з `execution_mode=local`. | +| `Workflow::executeLocalActivity()` | `localActivity()` | `executeLocalActivity(string $activity, mixed ...$arguments): mixed` | Псевдонім `localActivity()`. | +| `Workflow::child()` | `child()` | `child(string $workflow, ChildWorkflowOptions? $options = null, mixed ...$arguments): mixed` | Запускає дочірній workflow та очікує його результат. Передайте `ChildWorkflowOptions` першим аргументом для політики закриття батьківського workflow (типово `ParentClosePolicy::Abandon`) чи перевизначення маршрутизації дочірнього. | +| `Workflow::executeChildWorkflow()` | `child()` | `executeChildWorkflow(string $workflow, ChildWorkflowOptions? $options = null, mixed ...$arguments): mixed` | Псевдонім `child()`. | +| `Workflow::async()` | `async()` | `async(callable $callback): mixed` | Виконує callable як автоматично згенерований дочірній workflow. | +| `Workflow::all()` | `all()` | `all(iterable $calls): mixed` | Очікує паралельні виклики та повертає результати в порядку ітерації. | +| `Workflow::parallel()` | `all()` | `parallel(iterable $calls): mixed` | Псевдонім `all()`. | +| `Workflow::select()` | `select()` | `select(iterable $calls): SelectionResult` | Починає незалежні стійкі виклики та повертає першого зафіксованого переможця і стабільні дескриптори всіх учасників. | +| `Workflow::await()` | `await()` | `await(callable\|string $condition, int\|string\|CarbonInterval\|null $timeout = null, ?string $conditionKey = null): mixed` | Очікує іменований signal або умову, безпечну для відтворення. | +| `Workflow::awaitWithTimeout()` | `await()` | `awaitWithTimeout(int\|string\|CarbonInterval $timeout, callable\|string $condition, ?string $conditionKey = null): mixed` | Очікує signal чи умову з явним тайм-аутом. | +| `Workflow::awaitSignal()` | `await()` | `awaitSignal(string $name): mixed` | Очікує іменований signal. | +| `Workflow::timer()` | `timer()` | `timer(int\|string\|CarbonInterval $duration): mixed` | Призупиняє виконання до просування стійкого часу. | +| `Workflow::sideEffect()` | `sideEffect()` | `sideEffect(callable $callback): mixed` | Записує недетермінований результат в історію та відтворює його. | +| `Workflow::uuid4()` | `uuid4()` | `uuid4(): mixed` | Генерує стабільний під час відтворення UUIDv4. | +| `Workflow::uuid7()` | `uuid7()` | `uuid7(): mixed` | Генерує стабільний під час відтворення UUIDv7. | +| `Workflow::continueAsNew()` | `continueAsNew()` | `continueAsNew(mixed ...$arguments): mixed` | Завершує поточний запуск та починає новий для того самого екземпляра. | +| `Workflow::getVersion()` | `getVersion()` | `getVersion(string $changeId, int $minSupported = WorkflowStub::DEFAULT_VERSION, int $maxSupported = 1): mixed` | Узгоджує версію коду workflow, безпечну для відтворення. | +| `Workflow::patched()` | `patched()` | `patched(string $changeId): mixed` | Повертає, чи запуск перетнув іменований маркер зміни. | +| `Workflow::deprecatePatch()` | `deprecatePatch()` | `deprecatePatch(string $changeId): mixed` | Зберігає маркер зміни після видалення старого коду. | +| `Workflow::upsertMemo()` | `upsertMemo()` | `upsertMemo(array $entries): void` | Оновлює неіндексовані метадані запуску. | +| `Workflow::upsertSearchAttributes()` | `upsertSearchAttributes()` | `upsertSearchAttributes(array $attributes): void` | Оновлює індексовані метадані, видимі оператору. | +| `Workflow::now()` | `now()` | `now(): CarbonInterface` | Читає детермінований час workflow. | + +`activity()` та `executeActivity()` планують стійкі завдання activity в черзі. +`localActivity()` та `executeLocalActivity()` працюють у поточному процесі worker workflow й записують звичайну історію activity з локальним маркером. Використовуйте `sideEffect()` для знімків, безпечних для відтворення, яким не потрібна семантика повторних спроб, тайм-ауту, heartbeat чи скасування activity. Використовуйте `Workflow::workerSession()` або `Workflow\V2\workerSession()`, коли кільком звичайним крокам activity потрібен підтримуваний контракт прив’язки до сесії worker. Повний контракт виконання наведено в [моделі виконання activity](/docs/features/activity-execution-model), [локальних activity](/docs/features/local-activities) та [сесіях worker](/docs/features/worker-sessions). + +**Допоміжні функції timer** + +Допоміжні функції timer — скорочення для `timer()` та `Workflow::timer()`: + +```php +use Workflow\V2\Workflow; + +Workflow::seconds(30); +Workflow::minutes(5); +Workflow::hours(2); +Workflow::days(1); +Workflow::weeks(1); +Workflow::months(1); +Workflow::years(1); +``` + +| Допоміжна функція | Еквівалент | +| --- | --- | +| `seconds(int $seconds)` | `timer($seconds)` | +| `minutes(int $minutes)` | `timer($minutes * 60)` | +| `hours(int $hours)` | `timer($hours * 3600)` | +| `days(int $days)` | `timer($days * 86400)` | +| `weeks(int $weeks)` | `timer($weeks * 604800)` | +| `months(int $months)` | `timer("{$months} months")` | +| `years(int $years)` | `timer("{$years} years")` | + +Використовуйте явні виклики `timer()`, коли тривалість походить із налаштувань чи вхідних даних workflow. Використовуйте допоміжні функції timer, коли вихідний код має виражати фіксоване очікування бізнес-процесу. + +**Потоки повідомлень** + +Відкривайте стійкі потоки повідомлень з екземпляра workflow: + +```php +use Workflow\V2\MessageStream; +use Workflow\V2\Workflow; + +final class AssistantWorkflow extends Workflow +{ + public function handle(string $targetWorkflowId): array + { + $message = $this->inbox('ai.user')->receiveOne(); + + if ($message === null) { + return ['status' => 'waiting']; + } + + $reply = $this->outbox('ai.assistant')->sendReference( + targetInstanceId: $targetWorkflowId, + payloadReference: 'app://payloads/reply-123', + correlationId: $this->workflowId(), + idempotencyKey: 'reply-123', + metadata: ['kind' => 'assistant_reply'], + ); + + return [ + 'status' => 'sent', + 'stream' => $reply->stream_key, + 'sequence' => $reply->sequence, + ]; + } +} +``` + +| Метод | Сигнатура | Контракт | +| --- | --- | --- | +| `$this->messages()` | `messages(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Відкриває потік для читання чи надсилання. | +| `$this->inbox()` | `inbox(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Псевдонім для коду вхідних повідомлень. | +| `$this->outbox()` | `outbox(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Псевдонім для коду вихідних повідомлень. | +| `MessageStream::key()` | `key(): string` | Повертає ключ потоку. | +| `MessageStream::cursor()` | `cursor(): int` | Повертає стійку позицію курсора цього запуску. | +| `MessageStream::hasPending()` | `hasPending(): bool` | Повертає, чи потік містить неспожиті повідомлення. | +| `MessageStream::pendingCount()` | `pendingCount(): int` | Повертає кількість неспожитих повідомлень потоку. | +| `MessageStream::peek()` | `peek(int $limit = 100): Collection` | Читає очікувані повідомлення без споживання. | +| `MessageStream::receive()` | `receive(int $limit = 1, ?int $consumedBySequence = null): Collection` | Читає та споживає повідомлення, записуючи просування курсора. | +| `MessageStream::receiveOne()` | `receiveOne(?int $consumedBySequence = null): ?WorkflowMessage` | Читає та споживає одне повідомлення. | +| `MessageStream::sendReference()` | `sendReference(string $targetInstanceId, ?string $payloadReference = null, MessageChannel\|string $channel = MessageChannel::WorkflowMessage, ?string $correlationId = null, ?string $idempotencyKey = null, array $metadata = [], ?DateTimeInterface $expiresAt = null): WorkflowMessage` | Надсилає впорядковане повідомлення з посиланням на дані іншому екземпляру workflow. | + +Використовуйте потоки повідомлень для повторюваних упорядкованих повідомлень із семантикою курсора. Використовуйте [signal](../features/signals.md) для одноразових зовнішніх подій та [update](../features/updates.md) для змін за моделлю запит/повернення. + +**Атрибути та публічні контракти** + +```php +use Workflow\QueryMethod; +use Workflow\UpdateMethod; +use Workflow\V2\Attributes\Signal; +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +#[Type('order-approval')] +#[Signal('approved-by', [ + ['name' => 'approvedBy', 'type' => 'string', 'allows_null' => false], +])] +final class OrderApprovalWorkflow extends Workflow +{ + private string $stage = 'waiting'; + + public function handle(): void + { + $this->stage = Workflow::awaitSignal('approved-by'); + } + + #[QueryMethod('current-stage')] + public function currentStage(): string + { + return $this->stage; + } + + #[UpdateMethod('mark-ready')] + public function markReady(): string + { + return $this->stage = 'ready'; + } +} +``` + +| Атрибут | Ціль | Стабільний контракт | +| --- | --- | --- | +| `#[Type('type-key')]` | Клас workflow чи activity | Оголошує стійкий ключ типу, незалежний від мови. | +| `#[Signal('signal-name', [...])]` | Клас workflow, можна повторювати | Оголошує прийняті назви signal та необов’язкові впорядковані контракти параметрів. | +| `#[QueryMethod('query-name')]` | Метод workflow | Оголошує назву query, безпечну для відтворення. Без явної назви використовується назва PHP-методу. | +| `#[UpdateMethod('update-name')]` | Метод workflow | Оголошує назву update, безпечну для відтворення. Без явної назви використовується назва PHP-методу. | + +Signal, query та update — публічні контракти workflow. Віддавайте перевагу явним назвам, щоб перейменування PHP-методів не порушувало API. + +**Можливі помилки** + +Помилки API написання workflow є стійкими помилками workflow, якщо команду не відхилено до виконання: + +| API | Типова помилка | Значення для оператора | +| --- | --- | --- | +| `activity()` | Activity викидає виняток, перевищує тайм-аут чи вичерпує політику повторних спроб. | Запуск записує історію помилки activity та дотримується обробки помилок workflow. | +| `child()` | Дочірній workflow завершується з помилкою, скасовується, примусово завершується чи перевищує тайм-аут. | Батьківський workflow бачить помилку дочірнього на команді очікування. | +| `await()` | Тайм-аут спливає до виконання умови чи отримання signal. | Очікування повертається чи завершується з помилкою відповідно до обраної форми await. | +| `timer()` | Некоректна тривалість після нормалізації. | Код має передавати додатну тривалість або явне очікування нульової тривалості. | +| `continueAsNew()` | Новий запуск неможливо створити. | Поточний запуск залишається джерелом відомостей про невдалий перехід. | +| `upsertSearchAttributes()` | Ключ, кількість чи загальний розмір атрибутів перевищують межі. | Запуск завершується з помилкою до збереження некоректних індексованих метаданих. | +| `MessageStream::receive()` | Немає додатної послідовності історії workflow. | Отримання має відбуватися під час виконання workflow, а не безпосередньо з коду сервісу. | +| `MessageStream::sendReference()` | Посилання на дані, маршрут чи контракт сховища некоректні на стороні отримувача. | Упорядкування повідомлень залишається відокремленим від цілісності сховища даних. | + +Обмеження даних та історії наведено в [структурних обмеженнях](../constraints/structural-limits.md). Відповіді відхилення команд поза кодом PHP workflow наведено в [довіднику Server API](../polyglot/server-api-reference.md). + +**Правила детермінованості** + +Код workflow має бути безпечним для відтворення. Розміщуйте незворотну чи недетерміновану роботу за стійкими командами: + +```php +use Workflow\V2\Workflow; + +final class DeterministicWorkflow extends Workflow +{ + public function handle(): array + { + $workflowTime = Workflow::now(); + $stableId = Workflow::uuid7(); + $remoteQuote = Workflow::activity(FetchQuote::class); + + return [ + 'time' => $workflowTime->toIso8601String(), + 'id' => $stableId, + 'quote' => $remoteQuote, + ]; + } +} +``` + +- Використовуйте `Workflow::now()` замість реального часу в гілках workflow. +- Використовуйте `Workflow::uuid4()` або `Workflow::uuid7()` замість прямої випадковості. +- Розміщуйте мережеві виклики, записи до файлової системи, надсилання листів та зовнішні побічні ефекти в activity. +- Використовуйте `Workflow::sideEffect()` лише тоді, коли значення потрібно зафіксувати в історії, а сам побічний ефект не є бізнес-дією. +- Використовуйте `Workflow::getVersion()`, `Workflow::patched()` та `Workflow::deprecatePatch()` для розвитку коду workflow без порушення відтворення. + +
diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-id.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-id.md new file mode 100644 index 0000000000..870999b1e9 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-id.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 6 +--- + +# Ідентифікатор workflow {#workflow-id} + +## Ідентифікатори екземпляра та запуску {#instance-ids-and-run-ids} + +Стійкий workflow має два ідентифікатори з різними життєвими циклами: + +- **Ідентифікатор екземпляра** (`id()`, `workflowId()`) — стабільний публічний + ідентифікатор workflow. Він генерується під час першого запуску або надається + викликачем і не змінюється протягом життя workflow, зокрема в ланцюжках + continue-as-new. +- **Ідентифікатор запуску** (`runId()`) визначає одне конкретне покоління + виконання workflow. Він виділяється щоразу під час створення нового запуску: + при першому старті та коли workflow продовжується як новий. + +Використовуйте ідентифікатор екземпляра для публічних викликів. Ідентифікатор +запуску є вказівником на конкретне покоління, корисним для дослідження, +налагодження чи аудиту. Тривалі клієнти площини керування не мають його +відстежувати. + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(MyWorkflow::class); + +$id = $workflow->id(); + +$workflow->start(); + +$runId = $workflow->runId(); +``` + +Використовуйте id, коли потрібен стабільний публічний ідентифікатор workflow. +Ідентифікатор запуску використовуйте для дослідження одного конкретного виконання. + +## Життєвий цикл ідентифікатора запуску {#run-id-lifecycle} + +Ідентифікатор запуску визначає покоління виконання. Кожне покоління workflow v2 +має власний ідентифікатор запуску, а один ідентифікатор екземпляра може з часом +охоплювати багато поколінь: + +- Новий `start()` створює перший запуск і його перший ідентифікатор. +- `continueAsNew(...)` закриває поточний запуск як `completed` / `continued` + та починає новий із новим ідентифікатором запуску, зберігаючи ідентифікатор + екземпляра. Дивіться [продовження як новий запуск](/docs/features/continue-as-new). + +Оскільки ідентифікатор запуску належить одному поколінню, для стійкої поведінки +між такими переходами викликачі мають звертатися за ідентифікатором екземпляра: + +- Signal, query, update та операторські команди завжди знаходять найновіший + стійкий запуск екземпляра. Викликачам не потрібно відстежувати ідентифікатори + запусків через межі continue-as-new. +- Засоби детального дослідження конкретного покоління, наприклад сторінка деталей + запуску Waterline, експорт історії чи налагоджувач відтворення, приймають явний + ідентифікатор запуску для окремого дослідження одного виконання. + +Ідентифікатор запуску доступний як `runId()` у коді workflow й activity та як поле +`workflow_run_id` / `run_id` в історії, проєкціях та експортах. Це завжди +ідентифікатор покоління, якому належить подія чи рядок. Ідентифікатор запуску в +старих подіях історії може відрізнятися від поточного ідентифікатора запуску того +самого екземпляра. + +## Ідентифікатори екземпляра, задані користувачем {#user-defined-instance-ids} + +Під час запуску workflow можна задати власний id. Такі ідентифікатори мають бути +безпечними для URL рядками завдовжки до 191 символу, що містять лише літери, +цифри, `.`, `_`, `-` та `:`. + +```php +$workflow = WorkflowStub::make(MyWorkflow::class, 'order-123'); +``` + +Згодом workflow можна завантажити за власним id. + +```php +$workflow = WorkflowStub::load('order-123'); +``` + +## Доступ до ідентифікаторів усередині activity та workflow {#accessing-ids-inside-activities-and-workflows} + +Усередині activity: + +```php +use Workflow\V2\Activity; + +class MyActivity extends Activity +{ + public function handle(): void + { + $id = $this->workflowId(); + $runId = $this->runId(); + } +} +``` + +Усередині workflow `$this->workflowId()` повертає ідентифікатор екземпляра, +а `$this->runId()` — обраний ідентифікатор поточного запуску. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-status.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-status.md new file mode 100644 index 0000000000..aff62bf017 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflow-status.md @@ -0,0 +1,60 @@ +--- +sidebar_position: 5 +--- + +# Стан workflow {#workflow-status} + +Стан workflow можна відстежувати методом `running()`, який повертає `true`, +якщо workflow ще виконується, та `false`, якщо він завершився успішно чи з помилкою. + +```php +while ($workflow->running()); +``` + +## Значення станів {#status-values} + +Метод `status()` повертає рядкові значення стану: + +```text +reserved +pending +running +waiting +cancelled +terminated +completed +failed +``` + +- `reserved` означає, що ідентифікатор екземпляра створено, але першу команду + запуску ще не прийнято +- `pending` означає, що запуск існує та має роботу, готову для отримання +- `running` означає, що завдання workflow має активну оренду worker +- `waiting` означає, що запуск очікує на стійке джерело відновлення, наприклад + activity, timer або іменований signal +- `cancelled` означає, що прийнята команда скасування на рівні рушія закрила + поточний запуск +- `terminated` означає, що прийнята команда примусового завершення на рівні + рушія примусово закрила поточний запуск +- `completed`, `failed`, `cancelled` та `terminated` є кінцевими станами запуску + +Коли запуск використовує `continueAsNew()`, старий запуск завершується зі +`status = completed` та `closed_reason = continued`. Waterline залишає його в +групі завершених, але показує точне `closed_reason`, щоб оператори бачили, що +екземпляр перейшов до нового запуску. + +`running()` повертає `true` для `pending`, `running` та `waiting`. + +## Автомат станів {#state-machine} + +Це автомат станів workflow. + +import ThemedImage from '@site/src/components/ThemedImage'; + + diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflows.md b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflows.md new file mode 100644 index 0000000000..d0e4020690 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/defining-workflows/workflows.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 1 +title: Workflow +description: Визначайте класи workflow Durable Workflow v2 та зберігайте детермінованість коду оркестрації. +tags: + - authoring + - workflows + - determinism +keywords: + - durable workflow workflow class + - v2 workflow authoring + - deterministic workflow code +--- + +# Workflow {#workflows} + +Workflow й activity визначаються як класи, що успадковують базові класи +`Workflow` та `Activity`, надані фреймворком. Workflow — це клас, який визначає +послідовність activity, що виконуються паралельно, послідовно або поєднують +обидва підходи. + +Для створення нового workflow можна використати команду Artisan `make:workflow`: + +```php +php artisan make:workflow MyWorkflow +``` + +Він визначається успадкуванням класу `Workflow` та реалізацією методу `handle()`. + +```php +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle() + { + return activity(MyActivity::class); + } +} +``` diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/_category_.json b/i18n/uk/docusaurus-plugin-content-docs/current/features/_category_.json new file mode 100644 index 0000000000..8a280602ed --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Можливості", + "position": 7, + "link": { + "type": "generated-index", + "description": "Пакет Workflow надає методи для виконання activity, обробки помилок і повторних спроб та взаємодії з workflow." + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/condition-waits.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/condition-waits.md new file mode 100644 index 0000000000..bf914ea4fc --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/condition-waits.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 6 +--- + +# Очікування умови {#condition-waits} + +`await($condition, $conditionKey = null)` дає змогу очікувати на умову без порушення replay. Використовуйте його, коли предикат залежить лише від стану workflow, який уже отримано зі збережених вхідних даних, наприклад update, результатів activity або дочірніх workflow. Щоб безпосередньо отримати значення іменованого зовнішнього signal, викличте `await('name')` — див. [Signals](./signals.md). + +Стан для очікування умови змінює метод update замість методів, які змінюють стан через signal: + +```php +use Workflow\UpdateMethod; +use function Workflow\V2\await; +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +#[Type('approval-workflow')] +class MyWorkflow extends Workflow +{ + private bool $ready = false; + + public function handle(): void + { + await(fn () => $this->ready, 'approval.ready'); + } + + #[UpdateMethod] + public function setReady(bool $ready = true): array + { + $this->ready = $ready; + + return ['ready' => $this->ready]; + } +} +``` + +Необов’язковий ключ умови — це стабільна позначка очікування для оператора, придатна для URL. Під час replay перевіряється раніше записаний ключ, тому нове розгортання не зможе випадково використати той самий крок workflow для іншого предиката. Безпечну зміну workflow між розгортаннями описано в розділі [Versioning](./versioning.md). + +Очікування умови з тайм-аутом описано в розділі [Signal + Timer](./signal+timer.md). + +**Важливо:** `await()` слід використовувати лише у workflow, а не в activity. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/events.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/events.md new file mode 100644 index 0000000000..ceb2ee0ed6 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/events.md @@ -0,0 +1,200 @@ +--- +sidebar_position: 12 +--- + +# Події {#events} + +Події життєвого циклу надсилаються на ключових етапах виконання workflow й activity, щоб повідомляти застосунок про поступ, завершення чи збої. Це стандартні події Laravel. Реєструйте слухачі в `EventServiceProvider` або через `Event::listen()`. + +Усі події життєвого циклу V2 надсилаються **після фіксації стійкого стану** в базі даних. Слухачі бачать лише події, підтверджені зафіксованими фактами. Якщо транзакція відкочується, подія не надсилається. + +## Ідентичність подій {#event-identity} + +Кожна подія V2 містить поля стійкої ідентичності: + +| Поле | Опис | +|---|---| +| `instanceId` | ID екземпляра workflow, стабільний між continue-as-new. | +| `runId` | ID конкретного запуску виконання. | +| `workflowType` | Стійкий ключ типу, зареєстрований через `#[Type('...')]`. | +| `workflowClass` | Назва PHP-класу workflow. | +| `committedAt` | Часова позначка ISO 8601 фіксації стійкого запису: реальний час фіксації. | + +Події activity додатково містять: + +| Поле | Опис | +|---|---| +| `activityExecutionId` | Стійкий ID виконання activity. | +| `activityType` | Стійкий ключ типу activity. | +| `activityClass` | Назва PHP-класу activity. | +| `sequence` | Позиція activity у виконанні workflow. | +| `attemptNumber` | Номер спроби, починається з 1. | + +## Події workflow {#workflow-events} + +### WorkflowStarted {#workflowstarted} + +Надсилається після стійкої фіксації запуску workflow: перший запуск створено та подію історії `WorkflowStarted` записано. + +```php +use Workflow\V2\Events\WorkflowStarted; + +Event::listen(WorkflowStarted::class, function (WorkflowStarted $event) { + Log::info('Workflow started', [ + 'instance_id' => $event->instanceId, + 'run_id' => $event->runId, + 'type' => $event->workflowType, + ]); +}); +``` + +Ця подія також спрацьовує, коли новий запуск починається через continue-as-new. + +### WorkflowCompleted {#workflowcompleted} + +Надсилається після успішного завершення запуску workflow. + +```php +use Workflow\V2\Events\WorkflowCompleted; + +Event::listen(WorkflowCompleted::class, function (WorkflowCompleted $event) { + Log::info('Workflow completed', [ + 'instance_id' => $event->instanceId, + 'run_id' => $event->runId, + ]); +}); +``` + +### WorkflowFailed {#workflowfailed} + +Надсилається, коли запуск workflow остаточно завершується з помилкою. + +Додаткові поля: +- `exceptionClass`: назва PHP-класу винятку. +- `message`: повідомлення винятку. + +```php +use Workflow\V2\Events\WorkflowFailed; + +Event::listen(WorkflowFailed::class, function (WorkflowFailed $event) { + Log::error('Workflow failed', [ + 'instance_id' => $event->instanceId, + 'exception' => $event->exceptionClass, + 'message' => $event->message, + ]); +}); +``` + +## Події activity {#activity-events} + +### ActivityStarted {#activitystarted} + +Надсилається, коли завдання activity отримано та виконання починається. + +```php +use Workflow\V2\Events\ActivityStarted; + +Event::listen(ActivityStarted::class, function (ActivityStarted $event) { + Log::info('Activity started', [ + 'activity' => $event->activityType, + 'sequence' => $event->sequence, + 'attempt' => $event->attemptNumber, + ]); +}); +``` + +### ActivityCompleted {#activitycompleted} + +Надсилається після успішного завершення activity. + +```php +use Workflow\V2\Events\ActivityCompleted; + +Event::listen(ActivityCompleted::class, function (ActivityCompleted $event) { + Log::info('Activity completed', [ + 'activity' => $event->activityType, + 'execution_id' => $event->activityExecutionId, + ]); +}); +``` + +### ActivityFailed {#activityfailed} + +Надсилається після остаточної помилки activity: усі повторні спроби вичерпано або виняток не допускає повторення. Помилки, після яких буде повторна спроба, **не** спричиняють цю подію. + +Додаткові поля: +- `exceptionClass`: назва PHP-класу винятку. +- `message`: повідомлення винятку. + +```php +use Workflow\V2\Events\ActivityFailed; + +Event::listen(ActivityFailed::class, function (ActivityFailed $event) { + Log::error('Activity failed', [ + 'activity' => $event->activityType, + 'exception' => $event->exceptionClass, + 'message' => $event->message, + ]); +}); +``` + +## Події помилок {#failure-events} + +### FailureRecorded {#failurerecorded} + +Надсилається щоразу під час фіксації стійкого запису помилки, як для остаточних помилок workflow, так і activity. Це єдина точка інтеграції для повідомлень про помилки, наприклад Sentry чи Bugsnag. + +| Поле | Опис | +|---|---| +| `failureId` | ID стійкого запису помилки. | +| `sourceKind` | `"workflow_run"` або `"activity_execution"`. | +| `sourceId` | ID джерела: ID запуску чи виконання activity. | +| `exceptionClass` | Назва PHP-класу винятку. | +| `message` | Повідомлення винятку. | + +```php +use Workflow\V2\Events\FailureRecorded; + +Event::listen(FailureRecorded::class, function (FailureRecorded $event) { + // Report to Sentry, Bugsnag, etc. + report(new \RuntimeException( + "[{$event->sourceKind}] {$event->exceptionClass}: {$event->message}" + )); +}); +``` + +## Семантика часових позначок {#timestamp-semantics} + +Поле `committedAt` усіх подій означає **час фіксації**: реальний час запису стійкого запису (події історії чи запису помилки) до бази даних. Він відрізняється від: + +- **Віртуального часу workflow**: логічного часу всередині виконання workflow, який використовують timer. +- **Часу спроби**: коли конкретна спроба activity почалася чи завершилася. +- **Затримки відновлення**: скільки часу минуло після запланованого спрацювання timer до фактичного продовження workflow. + +Час фіксації найкорисніший для зовнішніх інтеграцій, бо відображає момент, коли стан став стійким і доступним для спостереження. + +## Життєвий цикл {#lifecycle} + +Типовий життєвий цикл успішного workflow: + +``` +Workflow\V2\Events\WorkflowStarted +Workflow\V2\Events\ActivityStarted +Workflow\V2\Events\ActivityCompleted +Workflow\V2\Events\WorkflowCompleted +``` + +Життєвий цикл workflow з остаточною помилкою activity: + +``` +Workflow\V2\Events\WorkflowStarted +Workflow\V2\Events\ActivityStarted +Workflow\V2\Events\ActivityFailed +Workflow\V2\Events\FailureRecorded (source: activity_execution) +Workflow\V2\Events\WorkflowFailed +Workflow\V2\Events\FailureRecorded (source: workflow_run) +``` + +## Простір імен подій {#event-namespace} + +Події V2 розміщено в просторі імен `Workflow\V2\Events`. Якщо оновлюєте наявний застосунок, дивіться [міграцію](../migration.md) для зіставлення сумісних подій V1. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/memo.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/memo.md new file mode 100644 index 0000000000..120ac4a31c --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/memo.md @@ -0,0 +1,151 @@ +--- +sidebar_position: 18 +--- + +# Memo {#memo} + +Memo — це неіндексовані метадані у вигляді пар ключ–значення, які workflow може читати й оновлювати в будь-який момент виконання. На відміну від [атрибутів пошуку](./search-attributes.md), які індексуються й підтримують фільтрування, memo призначені для складніших структурованих метаданих. Вони відображаються в детальному перегляді та експорті історії, але за контрактом не беруть участі у фільтруванні й сортуванні всіх запусків. + +## Додавання й оновлення memo {#upserting-memos} + +`upsertMemo()` — це стійкий допоміжний виклик для послідовного коду workflow. Кожен виклик записує типізовану подію історії `MemoUpserted` і об’єднує нові записи зі збереженим memo запуску. + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\{activity, upsertMemo}; + +final class OrderWorkflow extends Workflow +{ + public function handle(string $orderId, string $customer): array + { + upsertMemo([ + 'customer_name' => $customer, + 'order_id' => $orderId, + 'status' => 'processing', + 'line_items' => [ + ['sku' => 'WIDGET-1', 'qty' => 2], + ['sku' => 'GADGET-3', 'qty' => 1], + ], + ]); + + $result = activity(ProcessOrderActivity::class, $orderId); + + upsertMemo([ + 'status' => 'completed', + 'result_summary' => $result->outcome, + ]); + + return $result->toArray(); + } +} +``` + +Memo також можна задати під час запуску через `StartOptions`: + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(OrderWorkflow::class, 'order-123'); +$workflow->start( + 'ORD-456', + 'Taylor', + StartOptions::rejectDuplicate()->withMemo([ + 'source' => 'api', + 'priority' => 'high', + ]), +); +``` + +## Як це працює {#how-it-works} + +Функція `upsertMemo()` приймає асоціативний масив пар ключ–значення: + +- **Ключі** мають бути непорожніми рядками завдовжки до 64 символів +- **Значення** можуть містити будь-які дані, які серіалізуються в JSON: скалярні значення, null, масиви або вкладені об’єкти +- Значення `null` видаляє відповідний ключ із memo + +Кожен виклик: + +1. Призупиняє fiber workflow і передає команду `UpsertMemoCall` +2. Виконавець перевіряє й нормалізує записи, сортуючи ключі за абеткою +3. До історії додається подія `MemoUpserted` з оновленими `entries` та повним результатом `merged` +4. Стовпець `memo` запуску оновлюється об’єднаною мапою + +Під час replay використовуються записані події memo без повторного виконання оновлення. Це зберігає детермінізм. + +## Правила об’єднання {#merging-behavior} + +Кілька викликів оновлення в межах одного запуску об’єднують memo. Кожен виклик додає нові ключі та замінює значення наявних: + +```php +// First upsert +upsertMemo(['status' => 'processing', 'customer' => 'Taylor']); +// memo = { customer: Taylor, status: processing } + +// Second upsert +upsertMemo(['status' => 'completed', 'result' => 'success']); +// memo = { customer: Taylor, result: success, status: completed } +``` + +Щоб видалити ключ, задайте для нього `null`: + +```php +upsertMemo(['temporary_note' => null]); +``` + +## Видимість {#visibility} + +Memo відображаються в таких місцях: + +- **Детальний перегляд запуску** — повне об’єднане memo відображається на сторінці workflow у Waterline +- **Хронологія історії** — кожна подія `MemoUpserted` відображається як типізований запис у хронології запуску +- **Експорт історії** — memo входять до експорту історії у JSON +- **Describe** — відповідь `describe` площини керування містить поточне memo + +Memo **не** входять до проєкцій стислого представлення запуску й **не** доступні для фільтрування, сортування або збережених представлень. Для індексованих метаданих із фільтруванням використовуйте [атрибути пошуку](./search-attributes.md). + +## Continue-as-New {#continue-as-new} + +Під час continue-as-new поточне memo автоматично переноситься до нового запуску. Новий запуск починається з повного об’єднаного memo попереднього запуску й може продовжувати його оновлення. + +## Вкладені структури {#nested-structures} + +На відміну від атрибутів пошуку, які обмежені скалярними значеннями, memo підтримують вкладені структури JSON: + +```php +upsertMemo([ + 'order' => [ + 'id' => 'ORD-456', + 'items' => [ + ['sku' => 'WIDGET-1', 'qty' => 2], + ['sku' => 'GADGET-3', 'qty' => 1], + ], + ], + 'metadata' => [ + 'source' => 'api', + 'version' => 2, + ], +]); +``` + +Для вкладених об’єктів діють ті самі обмеження ключів: непорожні рядки завдовжки до 64 символів для кожного ключа на кожному рівні. + +## Обмеження {#constraints} + +- Назви ключів: непорожні рядки завдовжки до 64 символів на кожному рівні вкладеності +- Значення: будь-який тип, який серіалізується в JSON, зокрема скалярні значення, null, масиви та вкладені об’єкти +- Значення `null` видаляє відповідний ключ +- Memo — це метадані з узгодженістю в кінцевому підсумку, а не джерело істини для replay. Не обирайте гілки логіки workflow за значеннями memo + +## Memo, атрибути пошуку й позначки видимості {#memo-vs-search-attributes-vs-visibility-labels} + +| | Memo | Атрибути пошуку | Позначки видимості | +|---|---|---|---| +| **Коли задаються** | Під час запуску або виконання | У будь-який момент виконання | Лише під час запуску | +| **Змінюються** | Так, через `upsertMemo()` | Так, через `upsertSearchAttributes()` | Ні | +| **Типи значень** | Будь-які серіалізовані в JSON | Лише скалярні | Лише скалярні | +| **Індексуються** | Ні | Так | Так | +| **Фільтруються** | Ні | Так | Так | +| **Призначення** | Структуровані метадані, примітки, контекст | Динамічний стан, відстеження прогресу | Статична класифікація | +| **Події історії** | `MemoUpserted` на кожне оновлення | `SearchAttributesUpserted` на кожне оновлення | Немає, задаються під час запуску | diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/queries.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/queries.md new file mode 100644 index 0000000000..1a42b59e0a --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/queries.md @@ -0,0 +1,128 @@ +--- +sidebar_position: 2 +--- + +import QuerySimulator from '@site/src/components/QuerySimulator'; + +# Query {#queries} + +Query дозволяють отримувати інформацію про поточний стан workflow без впливу на його виконання. Це корисно для спостереження та налагодження. + +## Методи query, безпечні для відтворення {#replay-safe-query-methods} + +Query відтворюють зафіксовану історію поточного обраного запуску, а потім викликають анотований метод на відновленому об’єкті workflow. Вони не застосовують неявно прийняті, але ще не застосовані команди signal чи update. + +```php +use Workflow\QueryMethod; +use Workflow\V2\Workflow; +use function Workflow\V2\await; + +final class ApprovalWorkflow extends Workflow +{ + private string $stage = 'booting'; + + public function handle(): void + { + $this->stage = 'waiting-for-approval'; + + await('approved-by'); + + $this->stage = 'approved'; + } + + #[QueryMethod('current-stage')] + public function currentStage(): string + { + return $this->stage; + } + + #[QueryMethod('starts-with')] + public function startsWith(string $prefix): bool + { + return str_starts_with($this->stage, $prefix); + } +} +``` + +Звертайтеся до поточного запуску за публічним ідентифікатором екземпляра: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$workflow->currentStage(); // "waiting-for-approval" +$workflow->query('starts-with', 'waiting'); // true +$workflow->queryWithArguments('starts-with', ['prefix' => 'waiting']); // true +``` + +Щоб явно обрати один історичний чи визначений запуск, виконуйте query через `loadRun($runId)`: + +```php +$selectedRun = WorkflowStub::loadRun($runId); + +$selectedRun->currentStage(); +``` + +Поведінка query: + +- Query безпечні для відтворення: вони спостерігають лише зафіксовану історію та не змінюють стан workflow. +- Аргументи передаються анотованому методу. Оголосіть стабільну публічну назву через `#[QueryMethod('public-name')]`, щоб виклик зберігався після перейменування PHP-методу. +- `load($instanceId)` запитує найновіший стійкий запуск екземпляра. `loadRun($runId)` обирає один конкретний запуск, що корисно для query до continue-as-new. +- Прийняті, але ще не застосовані signal та update видимі в історії команд, але не враховуються як застосований стан, доки worker не запише `SignalApplied` / `UpdateApplied`. + +Для визначення методу query у workflow використовуйте анотацію `QueryMethod`. Необов’язковий рядковий аргумент фіксує публічну стійку назву query, яка зберігається після перейменування PHP-методів: + +```php +use Workflow\QueryMethod; +use Workflow\V2\Workflow; + +final class MyWorkflow extends Workflow +{ + private bool $ready = false; + + #[QueryMethod('is-ready')] + public function getReady(): bool + { + return $this->ready; + } +} +``` + +Щоб виконати query workflow, викличте метод на екземплярі workflow. Метод query поверне дані з workflow. + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$ready = $workflow->getReady(); +$sameReady = $workflow->query('is-ready'); +``` + +Використовуйте `queryWithArguments()`, якщо викликач уже має позиційний список чи мапу іменованих параметрів: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$workflow->queryWithArguments('starts-with', [ + 'prefix' => 'wait', +]); +``` + +Waterline використовує контракт query обраного запуску. Панель показує `declared_query_targets[*]` поряд із signal та update, але виконання query доступне лише за `can_query = true`. Оператор query обраного чи поточного запуску надсилає JSON `arguments` до `/waterline/api/instances/{instanceId}/queries/{query}` або `/waterline/api/instances/{instanceId}/runs/{runId}/queries/{query}`. Виконання query потребує доступного визначення workflow, оскільки обраний запуск має бути відтворений до виклику методу query. Коли стійкі цілі query існують, але визначення недоступне, деталі обраного запуску повідомляють `can_query = false` із `query_blocked_reason = workflow_definition_unavailable`, а POST query повертає HTTP `409 Conflict` із `blocked_reason = workflow_definition_unavailable`. Якщо запуск має лише неповний знімок, який поточна збірка більше не може доповнити, деталі повідомляють `declared_contract_source = unavailable`. Збережені цілі query залишаються видимими як діагностичні метадані, але іменовані аргументи query відхиляються з `422`, доки сумісна збірка не збереже відсутній контракт. + +Публічний міст webhook надає той самий безпечний для відтворення API query поза Waterline: + +```text +POST /webhooks/instances/{instanceId}/queries/{query} +POST /webhooks/instances/{instanceId}/runs/{runId}/queries/{query} +``` + +Ці маршрути webhook приймають те саме поле JSON `arguments`, що й Waterline, повертають типізований JSON `result` у разі успіху та використовують однакову форму помилки для некоректних аргументів (`422` із `validation_errors`) та блокування відтворення (`409` із `blocked_reason`). Якщо поточне визначення workflow доступне, викликачі можуть звертатися до query за стійкою ціллю `#[QueryMethod('public-name')]` чи назвою PHP-методу. Успішні HTTP-відповіді нормалізують `query_name` до стійкої публічної цілі, щоб зовнішні викликачі не залежали від деталей перейменування методів. + + + +**Важливо:** query workflow не просуває його виконання, на відміну від signal. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/search-attributes.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/search-attributes.md new file mode 100644 index 0000000000..a1d5488ad3 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/search-attributes.md @@ -0,0 +1,158 @@ +--- +sidebar_position: 17 +--- + +# Атрибути пошуку {#search-attributes} + +Атрибути пошуку — це типізовані індексовані пари ключ–значення, які workflow може додавати й оновлювати в будь-який момент виконання. На відміну від позначок видимості, які задаються один раз під час запуску, атрибути пошуку змінюються разом із виконанням workflow. Вони підходять для відстеження стану workflow, ідентифікаторів клієнтів та інших видимих оператору метаданих, які змінюються протягом запуску. + +## Додавання й оновлення атрибутів пошуку {#upserting-search-attributes} + +`upsertSearchAttributes()` — це стійкий допоміжний виклик для послідовного коду workflow. Кожен виклик записує типізовану подію історії `SearchAttributesUpserted` і об’єднує нові атрибути зі збереженими атрибутами пошуку запуску. + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\{activity, upsertSearchAttributes}; + +final class OrderWorkflow extends Workflow +{ + public function handle(string $orderId, string $customer): array + { + upsertSearchAttributes([ + 'status' => 'processing', + 'customer' => $customer, + 'order_id' => $orderId, + ]); + + $result = activity(ProcessOrderActivity::class, $orderId); + + upsertSearchAttributes([ + 'status' => 'completed', + 'result' => $result->outcome, + ]); + + return $result->toArray(); + } +} +``` + +## Атрибути пошуку під час запуску {#setting-search-attributes-at-start-time} + +Атрибути пошуку також можна задати під час запуску workflow через `StartOptions::withSearchAttributes()`. Вони одразу записуються в запуск і входять до подій історії `StartAccepted` та `WorkflowStarted`. Подальші виклики `upsertSearchAttributes()` об’єднують нові атрибути з цими початковими значеннями. + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(OrderWorkflow::class, 'order-42'); +$workflow->start( + $orderId, + $customer, + StartOptions::rejectDuplicate()->withSearchAttributes([ + 'env' => 'production', + 'region' => 'us-east', + 'priority' => 'high', + ]), +); +``` + +`withSearchAttributes()` можна поєднувати в ланцюжку з іншими методами побудови `StartOptions`: + +```php +$options = StartOptions::rejectDuplicate() + ->withBusinessKey('order-42') + ->withLabels(['tenant' => 'acme']) + ->withMemo(['note' => 'VIP order']) + ->withSearchAttributes(['priority' => 'high']); + +$workflow->start($orderId, $customer, $options); +``` + +Для атрибутів пошуку під час запуску діють ті самі правила перевірки, що й для `upsertSearchAttributes()`: ключі містять 1–64 символи, придатні для URL, значення є скалярними або null, а значення null відкидаються й не зберігаються. + +### Площина керування {#control-plane} + +Метод `start()` площини керування також приймає атрибути пошуку: + +```php +$controlPlane->start('orders.process-order', 'order-42', [ + 'arguments' => $serializedArgs, + 'search_attributes' => ['env' => 'production', 'priority' => 'high'], +]); +``` + +## Як це працює {#how-it-works} + +Функція `upsertSearchAttributes()` приймає асоціативний масив пар ключ–значення: + +- **Ключі** містять 1–64 символи, придатні для URL: літери, цифри, дефіси й підкреслення +- **Значення** мають бути скалярними (string, int, float, bool) або null. Рядки обмежені 191 символом +- Значення `null` видаляє відповідний ключ з атрибутів пошуку + +Кожен виклик: + +1. Призупиняє fiber workflow і передає команду `UpsertSearchAttributesCall` +2. Виконавець перевіряє й нормалізує атрибути, сортуючи ключі за абеткою +3. До історії додається подія `SearchAttributesUpserted` з оновленими `attributes` та повним результатом `merged` +4. Стовпець `search_attributes` запуску оновлюється об’єднаною мапою +5. Оновлюється проєкція стислого представлення запуску, щоб Waterline одразу відображав зміну + +Під час replay використовуються записані події атрибутів пошуку без повторного виконання оновлення. Це зберігає детермінізм. + +## Правила об’єднання {#merging-behavior} + +Кілька викликів оновлення в межах одного запуску об’єднують атрибути пошуку. Кожен виклик додає нові ключі та замінює значення наявних: + +```php +// First upsert +upsertSearchAttributes(['status' => 'processing', 'customer' => 'Taylor']); +// search_attributes = { customer: Taylor, status: processing } + +// Second upsert +upsertSearchAttributes(['status' => 'completed', 'result' => 'success']); +// search_attributes = { customer: Taylor, result: success, status: completed } +``` + +Щоб видалити ключ, задайте для нього `null`: + +```php +upsertSearchAttributes(['temporary_flag' => null]); +``` + +## Видимість і фільтрування {#visibility-and-filtering} + +Атрибути пошуку відображаються в таких місцях: + +- **Детальний перегляд запуску** — об’єднана мапа атрибутів пошуку відображається поруч із позначками й memo +- **Проєкція стислого представлення запуску** — атрибути пошуку входять до денормалізованого представлення для швидкого читання +- **Фільтри видимості** — оператори можуть фільтрувати запуски workflow за значеннями атрибутів пошуку в збережених представленнях Waterline +- **Хронологія історії** — кожна подія `SearchAttributesUpserted` відображається як типізований запис у хронології запуску +- **Експорт історії** — атрибути пошуку входять до експорту історії у JSON + +## Continue-as-New {#continue-as-new} + +Під час continue-as-new поточні атрибути пошуку автоматично переносяться до нового запуску. Новий запуск починається з повної об’єднаної мапи атрибутів попереднього запуску й може продовжувати її оновлення. + +## Приватність і обмеження {#privacy-and-limits} + +Атрибути пошуку — це **метадані оператора у відкритому тексті**. Їхні значення бачить кожен, хто має доступ до Waterline, і вони зберігаються без шифрування. Ніколи не зберігайте в атрибутах пошуку секрети, паролі, токени або персональні дані (PII). + +Обмеження: + +- Назви ключів: 1–64 символи, придатні для URL (`[a-zA-Z0-9_-]`) +- Значення: лише скалярні типи (string, int, float, bool), рядки обмежені 191 символом +- Значення `null` видаляє відповідний ключ + +## Атрибути пошуку, memo й позначки видимості {#search-attributes-vs-memo-vs-visibility-labels} + +| | Атрибути пошуку | Memo | Позначки видимості | +|---|---|---|---| +| **Коли задаються** | Під час запуску або виконання | Під час запуску або виконання | Лише під час запуску | +| **Змінюються** | Так, через `upsertSearchAttributes()` | Так, через `upsertMemo()` | Ні | +| **Типи значень** | Лише скалярні | Будь-які серіалізовані в JSON | Лише скалярні | +| **Індексуються** | Так | Ні | Так | +| **Фільтруються** | Так | Ні | Так | +| **Призначення** | Динамічний стан, відстеження прогресу | Структуровані метадані, примітки, контекст | Статична класифікація | +| **Події історії** | `SearchAttributesUpserted` на кожне оновлення | `MemoUpserted` на кожне оновлення | Немає, задаються під час запуску | + +Для складніших структурованих метаданих, які не потребують фільтрування або сортування, використовуйте [memo](./memo.md). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/side-effects.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/side-effects.md new file mode 100644 index 0000000000..0b45a54357 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/side-effects.md @@ -0,0 +1,108 @@ +--- +sidebar_position: 7 +--- + +# Побічні ефекти {#side-effects} + +Побічний ефект — замикання з недетермінованим кодом. Замикання виконується лише один раз, а результат зберігається. Якщо workflow виконується повторно, замикання не запускається знову, а повертає збережений результат. Це робить workflow детермінованим: відтворення завжди повертає те саме збережене значення без повторного виконання недетермінованого коду. + +```php +use function Workflow\V2\await; +use function Workflow\V2\sideEffect; +use Workflow\V2\Attributes\Signal; +use Workflow\V2\Workflow; + +#[Signal('finish')] +class MyWorkflow extends Workflow +{ + public function handle(): array + { + $token = sideEffect(fn () => random_int(1000, 9999)); + $finish = await('finish'); + + return compact('token', 'finish'); + } +} +``` + +Workflow викличе `random_int()` лише один раз і збереже результат, навіть якщо згодом зазнає збою та виконуватиметься повторно. + +## Коли використовувати побічні ефекти {#when-to-use-side-effects} + +Використовуйте `sideEffect()`, якщо потрібне недетерміноване значення, яке: + +- обчислюється локально без зовнішнього введення-виведення: випадкові числа, UUID чи часові позначки +- не має змінюватися після запису, навіть між відтвореннями +- не потребує семантики повторних спроб, бо замикання виконується рівно один раз + +```php +// Generate a correlation token for downstream systems. +$correlationId = sideEffect(fn () => (string) Str::uuid()); + +// Snapshot the current time for a business rule. +$decidedAt = sideEffect(fn () => now()->toIso8601String()); +``` + +## Коли натомість використовувати activity {#when-to-use-an-activity-instead} + +Якщо код може зазнати збою, звертається до зовнішнього сервісу чи потребує семантики повторних спроб/тайм-ауту, використовуйте [activity](../defining-workflows/activities.md) замість побічного ефекту: + +| Сценарій | Використовуйте | +|---|---| +| Генерування випадкового токена | `sideEffect()` | +| Читання налаштування в момент рішення | `sideEffect()` | +| Виклик зовнішнього API | `activity()` | +| Запис до бази даних | `activity()` | +| Надсилання листа чи сповіщення | `activity()` | +| Дороге обчислення, що може викинути виняток | `activity()` | + +Практичне правило: якщо замикання може викинути виняток, після якого ви хотіли б повторити спробу, воно має бути в activity. + +## Принцип роботи {#how-it-works} + +- кожен `sideEffect()` додає типізовану подію історії `SideEffectRecorded` з послідовністю кроку workflow +- відтворення workflow та query повторно використовують зафіксоване значення без повторного запуску замикання +- Waterline показує знімок побічного ефекту як типізований запис історії на часовій шкалі обраного запуску +- побічні ефекти призначені лише для знімків, безпечних для відтворення, а не для роботи, що може зазнати збою чи потребує повторних спроб + +## Антишаблони {#anti-patterns} + +**Не викликайте зовнішні сервіси всередині побічного ефекту.** Якщо виклик сервісу зазнає збою, побічний ефект не повторюватиметься, і workflow остаточно завершиться з помилкою: + +```php +// BAD: HTTP calls can fail and side effects do not retry. +$price = sideEffect(fn () => Http::get('/api/price')->json('amount')); + +// GOOD: Use an activity for external calls. +$price = activity(FetchPriceActivity::class); +``` + +**Не розміщуйте повільні чи блокувальні операції всередині побічного ефекту.** Замикання працює в потоці завдання workflow. Тривала робота затримує все завдання workflow: + +```php +// BAD: Expensive computation blocks the workflow task. +$hash = sideEffect(fn () => bcrypt($largePayload)); + +// GOOD: Offload heavy work to an activity. +$hash = activity(ComputeHashActivity::class, $largePayload); +``` + +**Не покладайтеся на змінний зовнішній стан.** Замикання виконується рівно один раз. Якщо ви читаєте значення, що змінюється з часом, знімок фіксується в момент першого виконання, а не відтворення: + +```php +// The cached value is whatever it was during the first execution. +// If the cache changes later, this workflow still sees the old value. +$setting = sideEffect(fn () => cache('feature.flag')); +``` + +Це навмисна поведінка: знімок фіксується для детермінованості. Якщо потрібне значення, яке оновлюється протягом життя workflow, використовуйте signal чи activity. + +## Запуск цього шаблону {#run-this-pattern} + +Workflow вимірювання тривалості в [Sample App](/docs/sample-app) — виконуваний приклад читання годинника через `sideEffect()`: + +```bash +php artisan app:elapsed +``` + +`App\Workflows\Elapsed\ElapsedTimeWorkflow` записує початкову й кінцеву часові позначки як цілі значення всередині callback `sideEffect()`, щоб значення зберігалося після декодування Avro під час відтворення. Деталі запуску Waterline показують дві події `MarkerRecorded` до й після спрацювання timer. Ця пара маркерів є збереженим на диску свідченням детермінованості читання годинника. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/signal+timer.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/signal+timer.md new file mode 100644 index 0000000000..64bdef8af2 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/signal+timer.md @@ -0,0 +1,104 @@ +--- +sidebar_position: 5 +--- + +# Signal + timer {#signal--timer} + +`Workflow\V2` підтримує `await($condition, timeout: $seconds, conditionKey: $key)` для очікування умов із тайм-аутом та `await('signal-name', timeout: $seconds)` для очікування іменованих signal із тайм-аутом. + +Використовуйте це, коли workflow має продовжитися одразу після того, як умова над стійким відтвореним станом стане істинною, але також має розблокуватися після крайнього строку, якщо стан не зміниться. + +```php +use Workflow\UpdateMethod; +use function Workflow\V2\await; +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +use function Workflow\V2\minutes; + +#[Type('approval-with-timeout')] +class MyWorkflow extends Workflow +{ + private bool $ready = false; + + public function handle(): string + { + $approved = await(fn () => $this->ready, timeout: minutes(5), conditionKey: 'approval.ready'); + + return $approved ? 'approved' : 'timed out'; + } + + #[UpdateMethod] + public function markReady(bool $ready = true): array + { + $this->ready = $ready; + + return ['ready' => $this->ready]; + } +} +``` + +import SignalTimerSimulator from '@site/src/components/SignalTimerSimulator'; + + + +`await()` із параметром `timeout:` працює так: + +- Предикат має залежати лише від відтворених стійких даних: результатів activity, змін update чи результатів дочірніх workflow. Нестійкий стан не пробудить очікування. +- Необов’язковий ключ умови — стабільна операторська мітка очікування. Відтворення перевіряє ключ щодо попередньої історії, щоб нове розгортання не могло непомітно використати той самий крок workflow для іншого предиката. +- Якщо предикат стає істинним до спрацювання timer, `await()` повертає `true`. +- Якщо першим настає крайній строк, `await()` повертає `false`. + +Для читабельних значень тайм-ауту доступні допоміжні функції timer: `seconds()`, `minutes()`, `hours()`, `days()`, `weeks()`, `months()`, `years()`. + +Для стійкої зміни предиката з коду застосунку викличте оголошений update: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('approval-with-timeout'); + +$workflow->markReady(); +``` + +Повертається `true`, коли умова стає істинною до спрацювання завдання тайм-ауту, та `false`, коли тайм-аут перемагає. + +Для іменованих signal `await('name', timeout: minutes(5))` повертає дані signal після надходження та `null`, якщо перемагає тайм-аут. `null` зарезервовано для тайм-ауту: signal без аргументів повертає `true`, з одним аргументом — це значення, з кількома — масив. `Workflow\V2` не використовує застарілі методи зміни стану `#[SignalMethod]`. + +## Допоміжна функція `awaitWithTimeout()` {#awaitwithtimeout-helper} + +`Workflow::awaitWithTimeout($timeout, $condition, $conditionKey = null)` — зручна оболонка для `await($condition, timeout: $timeout, conditionKey: $conditionKey)`. Крайній строк стоїть першим аргументом, щоб намір обмежити очікування був очевидним у місці виклику. + +```php +use function Workflow\V2\{await, minutes}; +use Workflow\V2\Workflow; + +#[Type('approval-with-timeout')] +class MyWorkflow extends Workflow +{ + private bool $ready = false; + + public function handle(): string + { + $approved = Workflow::awaitWithTimeout( + minutes(5), + fn () => $this->ready, + 'approval.ready', + ); + + return $approved ? 'approved' : 'timed out'; + } + + #[UpdateMethod] + public function markReady(bool $ready = true): array + { + $this->ready = $ready; + + return ['ready' => $this->ready]; + } +} +``` + +Семантика повернення така сама, як у `await()` із `timeout:`: виклик повертає `true`, якщо умова перемагає, `false`, якщо крайній строк перемагає для callable-предиката, дані signal, якщо іменований signal перемагає, та `null`, якщо крайній строк перемагає для іменованого signal. + +`awaitWithTimeout()` виражає «очікувати умову не довше цього часу», тоді як інша форма — «очікувати умову з параметром тайм-ауту». Обирайте форму, зрозумілішу в місці виклику. Середовище виконання трактує їх однаково, і вони записують ту саму історію workflow. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/signals.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/signals.md new file mode 100644 index 0000000000..33d4bea8ea --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/signals.md @@ -0,0 +1,89 @@ +--- +sidebar_position: 1 +title: Signal +description: Надсилайте типізовані зовнішні команди до активних workflow та дізнайтеся про валідацію, маршрутизацію й очікування signal. +tags: + - commands + - signals + - human-in-the-loop +keywords: + - workflow signals + - signal command + - human in the loop workflow +--- + +import SignalSimulator from '@site/src/components/SignalSimulator'; + +# Signal {#signals} + +Signal дозволяють спричиняти події у workflow ззовні. Це корисно для реакції на зовнішні події, втручання *людини в процес* чи повідомлення про завершення зовнішнього завдання. Для повторюваних упорядкованих вхідних даних зі стійким просуванням курсора використовуйте [потоки повідомлень](./message-streams.md). + +## Очікування іменованих signal {#named-signal-waits} + +Workflow безпосередньо викликає `await('signal-name')`. Наступна прийнята команда signal із цією назвою продовжує запуск та повертає детерміноване значення призупиненому workflow. + +```php +use Workflow\V2\Attributes\Signal; +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; +use function Workflow\V2\await; + +#[Type('order-approval')] +#[Signal('approved-by', [ + ['name' => 'approvedBy', 'type' => 'string'], +])] +final class OrderApprovalWorkflow extends Workflow +{ + public function handle(): array + { + $approvedBy = await('approved-by'); + + return [ + 'approved_by' => $approvedBy, + 'workflow_id' => $this->workflowId(), + 'run_id' => $this->runId(), + ]; + } +} +``` + +Надішліть signal із PHP за публічним ідентифікатором екземпляра: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$result = $workflow->attemptSignalWithArguments('approved-by', [ + 'approvedBy' => 'Taylor', +]); + +$result->accepted(); // true +$result->outcome(); // "signal_received" +$result->commandId(); // Durable signal-command id +$result->instanceId(); // "order-123" +``` + +Поведінка signal: + +- Заздалегідь оголосіть кожну назву зовнішнього signal через повторюваний атрибут класу `#[Signal('signal-name')]`. За потреби додайте впорядкований контракт параметрів. +- Команди signal спрямовані на публічний ідентифікатор екземпляра workflow, а не запуску, тому ланцюжки continue-as-new зберігають той самий публічний маршрут signal. +- За наявності контракту параметрів прийом відхиляє некоректні дані як `rejected_invalid_arguments` із машинозчитуваними `validation_errors`. +- Без контракту `await('name')` повертає `true`, якщо аргументів немає, єдиний аргумент, якщо передано один, або повний масив аргументів, якщо передано кілька. +- Невідомі назви signal відхиляються як `rejected_unknown_signal`. Signal для закритого чи незапущеного екземпляра відхиляються як `rejected_not_active` або `rejected_not_started`. + + + +**Важливо:** функцію `await()` слід використовувати лише у workflow, а не в activity. + +Для очікування умов, коли предикат над стійким станом має стати істинним, дивіться [очікування умов](./condition-waits.md). Для `await` із тайм-аутом дивіться [signal + timer](./signal+timer.md). Для повторюваних потоків inbox/outbox дивіться [потоки повідомлень](./message-streams.md). + +## Запуск цього шаблону {#run-this-pattern} + +Workflow, що запускається через webhook у [Sample App](/docs/sample-app), — виконуваний приклад шаблону очікування іменованого signal із цієї сторінки: + +```bash +php artisan app:webhook +``` + +`App\Workflows\Webhooks\WebhookWorkflow` запускається через вхідний HTTP webhook і зупиняється на `await('ready')` до надходження відповідного signal. Відкрийте Waterline під час очікування: подія `WorkflowExecutionSignaled` з’явиться в момент прийняття signal. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md new file mode 100644 index 0000000000..bca6d59d3d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/timeouts.md @@ -0,0 +1,203 @@ +--- +sidebar_position: 19 +--- + +# Тайм-аути {#timeouts} + +Тайм-аути рівня workflow обмежують допустиму тривалість його виконання. Є два рівні обмеження: + +- **Тайм-аут виконання** — обмежує загальний календарний час усіх запусків екземпляра workflow, включно з переходами continue-as-new. Кінцевий термін обчислюється один раз під час старту й переноситься без змін. +- **Тайм-аут запуску** — обмежує окремий запуск. Кінцевий термін перераховується на початку кожного нового запуску, зокрема після continue-as-new. + +Обидва тайм-аути необов’язкові й можуть застосовуватися разом. + +## Налаштування {#configuration} + +Тайм-аути налаштовуються через `StartOptions` під час запуску workflow: + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(MyWorkflow::class, 'order-123'); + +$workflow->start( + $orderId, + StartOptions::rejectDuplicate() + ->withExecutionTimeout(7200) // 2 hours across all runs + ->withRunTimeout(3600), // 1 hour per run +); +``` + +### Тайм-аут виконання {#execution-timeout} + +Тайм-аут виконання охоплює весь екземпляр workflow. Якщо workflow використовує continue-as-new, кінцевий термін залишається однаковим для всіх запусків у ланцюжку. + +```php +StartOptions::rejectDuplicate()->withExecutionTimeout(86400); // 24 hours +``` + +Значення тайм-ауту зберігається в моделі `WorkflowInstance`, а обчислений кінцевий термін фіксується в кожному `WorkflowRun`. + +### Тайм-аут запуску {#run-timeout} + +Тайм-аут запуску діє на один запуск. Після continue-as-new новий запуск отримує новий кінцевий термін: поточний час плюс налаштований тайм-аут запуску. + +```php +StartOptions::rejectDuplicate()->withRunTimeout(1800); // 30 minutes per run +``` + +### Перевірка значень {#validation} + +Значення тайм-ауту має становити щонайменше 1 секунду. Нульове або від’ємне значення спричиняє `LogicException`: + +```php +// Throws LogicException: "Workflow v2 execution timeout must be at least 1 second." +StartOptions::rejectDuplicate()->withExecutionTimeout(0); +``` + +### Площина керування {#control-plane} + +Тайм-аути також можна задати під час запуску workflow через площину керування: + +```php +$controlPlane->start('my-app.order-workflow', 'order-123', [ + 'execution_timeout_seconds' => 7200, + 'run_timeout_seconds' => 3600, +]); +``` + +Відповідь `describe` містить поля тайм-аутів і кінцевих термінів: + +```php +$description = $controlPlane->describe('order-123'); + +$description['execution_timeout_seconds']; // 7200 +$description['run']['run_timeout_seconds']; // 3600 +$description['run']['execution_deadline_at']; // ISO 8601 timestamp +$description['run']['run_deadline_at']; // ISO 8601 timestamp +``` + +### Waterline {#waterline} + +Якщо тайм-аути налаштовано, детальний перегляд запуску у Waterline показує їхню тривалість та обчислені кінцеві терміни. + +### Історія {#history} + +Дані події історії `WorkflowStarted` містять поля тайм-аутів і кінцевих термінів, якщо їх налаштовано: + +```json +{ + "execution_timeout_seconds": 7200, + "run_timeout_seconds": 3600, + "execution_deadline_at": "2026-04-12T14:00:00+00:00", + "run_deadline_at": "2026-04-12T13:00:00+00:00" +} +``` + +### Continue-as-new {#continue-as-new} + +Під час continue-as-new: + +- **Кінцевий термін виконання** переноситься з попереднього запуску без змін. +- Значення **тайм-ауту запуску** переноситься, але **кінцевий термін запуску** перераховується від поточного часу. + +Отже, тайм-аут виконання завжди відлічується від початкового старту, а кожен новий запуск отримує власне нове часове вікно. + +## Застосування тайм-аутів {#enforcement} + +Тайм-аути рівня workflow перевіряються у двох місцях: + +1. **На початку завдання workflow** — кожне завдання workflow перевіряє `deadlineExpired()` перед виконанням workflow. Якщо кінцевий термін минув, запуск одразу завершується через тайм-аут. +2. **Через TaskWatchdog** — watchdog шукає незавершені запуски, у яких минув кінцевий термін виконання або запуску, але немає відкритого завдання workflow. Для такого запуску він створює й відправляє завдання workflow про прострочений термін. Тайм-аут застосовується під час наступного виконання завдання. + +Коли спрацьовує тайм-аут, рушій: + +- Скасовує всі відкриті завдання (activity, timer, workflow), крім поточного +- Скасовує всі відкриті виконання activity, записуючи події історії `ActivityCancelled` +- Скасовує всі очікувані timer, записуючи події історії `TimerCancelled` +- Записує `WorkflowFailure` із `failure_category = timeout` та `WorkflowTimeoutException` +- Записує термінальну подію історії `WorkflowTimedOut`, де `timeout_kind` має значення `execution_timeout` або `run_timeout` +- Застосовує політику закриття батьківського workflow до всіх відкритих дочірніх workflow +- Сповіщає батьківські workflow, якщо це був дочірній запуск + +Запис помилки містить клас винятку `Workflow\V2\Exceptions\WorkflowTimeoutException`, а також `timeout_kind` і часову мітку кінцевого терміну для програмної перевірки. + +## Тайм-аути activity {#activity-timeouts} + +Тайм-аути activity обмежують допустиму тривалість окремих виконань activity. Є чотири типи тайм-аутів: + +- **Schedule-to-start** — обмежує час від планування до першого отримання завдання worker. Діє, поки activity має стан `Pending`. +- **Start-to-close** — обмежує час від отримання activity worker до її завершення. Починається заново для кожної повторної спроби. +- **Schedule-to-close** — обмежує загальний календарний час від планування до завершення для всіх повторних спроб. Цей тайм-аут завжди термінальний: повторна спроба не допоможе, оскільки загальний кінцевий термін уже минув. +- **Heartbeat** — обмежує час між heartbeat. Для тривалих activity, які викликають `$this->heartbeat()`, рушій вимагає heartbeat протягом налаштованого інтервалу. Інакше activity вважається такою, що не відповідає. + +Усі тайм-аути необов’язкові й можуть застосовуватися разом. Налаштовуйте їх через `ActivityOptions` під час виклику activity: + +```php +use function Workflow\V2\activity; +use Workflow\V2\Support\ActivityOptions; + +$result = activity( + LongRunningActivity::class, + new ActivityOptions( + scheduleToStartTimeout: 30, // must be claimed within 30s + startToCloseTimeout: 300, // each attempt has 5 minutes + scheduleToCloseTimeout: 600, // total 10 minutes across all retries + heartbeatTimeout: 15, // must heartbeat every 15 seconds + maxAttempts: 3, + ), + $input, +); +``` + +### Тайм-аут schedule-to-start {#schedule-to-start-timeout} + +Кінцевий термін обчислюється під час планування activity. Якщо до цього терміну жоден worker не отримає activity, `TaskWatchdog` застосовує тайм-аут. Якщо повторні спроби ще доступні, нове завдання activity планується із зафіксованою затримкою, а кінцевий термін schedule-to-start перераховується від моменту доступності повторної спроби. Кожна спроба отримує нове часове вікно. Якщо `scheduleToStartTimeout` не налаштовано, під час повторної спроби кінцевий термін очищається. Коли всі спроби вичерпано, записується термінальна подія історії `ActivityTimedOut` і workflow пробуджується. + +### Тайм-аут start-to-close {#start-to-close-timeout} + +Кінцевий термін обчислюється, коли worker отримує завдання activity. Кожна повторна спроба отримує новий кінцевий термін start-to-close. Якщо activity не завершиться вчасно, поточна спроба закривається. За наявності повторних спроб виконання повертається до стану `Pending` з новим кінцевим терміном schedule-to-start, якщо його налаштовано. Це запобігає негайному повторному тайм-ауту завдання. Якщо всі спроби вичерпано, записується термінальний тайм-аут. + +### Тайм-аут schedule-to-close {#schedule-to-close-timeout} + +Кінцевий термін обчислюється один раз під час планування й ніколи не починається заново. Коли він спливає, activity одразу завершується термінальною помилкою, навіть якщо повторні спроби ще доступні, адже загальний допустимий календарний час минув. Це обмежує загальну вартість нестабільної activity, яка інакше могла б повторюватися нескінченно в межах тайм-аутів окремих спроб. + +### Тайм-аут heartbeat {#heartbeat-timeout} + +Початковий кінцевий термін обчислюється, коли worker отримує завдання activity. Кожен успішний виклик `$this->heartbeat()` продовжує термін на налаштований інтервал. Якщо activity не викличе `heartbeat()` до спливу терміну, рушій вважає, що worker не відповідає. За наявності повторних спроб планується нова спроба з новим кінцевим терміном schedule-to-start, якщо його налаштовано. Інакше записується термінальний тайм-аут. + +```php +use Workflow\V2\Activity; + +class LongRunningActivity extends Activity +{ + public function handle($input) + { + foreach ($items as $item) { + $this->heartbeat(['processed' => $count]); + // ... process item ... + } + } +} +``` + +### Застосування тайм-аутів {#enforcement-1} + +`TaskWatchdog` застосовує тайм-аути activity під час кожного проходу циклу worker. Він шукає виконання з простроченими значеннями у стовпцях кінцевих термінів і передає їх `ActivityTimeoutEnforcer`. Кожне застосування записує: + +- Термінальну подію історії `ActivityTimedOut`, де `timeout_kind` має значення `schedule_to_start`, `start_to_close`, `schedule_to_close` або `heartbeat` +- Запис `WorkflowFailure` із `failure_category = timeout` та `propagation_kind = timeout` +- Завдання відновлення workflow, яке пробуджує батьківський workflow, щоб він міг отримати помилку + +У детальному перегляді activity Waterline показує політику повторних спроб із усіма налаштованими типами тайм-аутів. Хронологія показує тип тайм-ауту в повідомленні події про тайм-аут activity. + +### Що ще не реалізовано {#what-is-not-yet-covered} + +Наведені нижче можливості заплановано, але ще не реалізовано: + +- Політики повторних спроб на рівні workflow + +### Структурні обмеження {#structural-limits} + +Рушій застосовує типізовані помилки структурних обмежень для розміру даних, кількості очікуваних паралельних операцій і максимального розміру метаданих. Повний контракт обмежень, налаштування та класифікацію помилок наведено в розділі [Структурні обмеження](../constraints/structural-limits.md). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/timers.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/timers.md new file mode 100644 index 0000000000..ea9de67ddb --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/timers.md @@ -0,0 +1,103 @@ +--- +sidebar_position: 4 +title: Timer +description: Використовуйте стійкі timer та детермінований час workflow без залежності від реального часу процесу. +tags: + - timers + - deterministic-time + - waits +keywords: + - durable timers + - deterministic time + - workflow now +--- + +import TimerSimulator from '@site/src/components/TimerSimulator'; + +# Timer {#timers} + +Фреймворк дозволяє призупинити виконання workflow та продовжити його пізніше. +Це стійкі timer: вони переживають перезапуски й збої та залишаються узгодженими +із семантикою відтворення workflow. Це корисно для затримок, логіки повторних +спроб чи тайм-аутів. + +Для використання timer викличте `timer($duration)` усередині workflow: + +```php +use function Workflow\V2\timer; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle(): string + { + timer(30); + + return 'The workflow waited 30 seconds.'; + } +} +``` + + + +Поведінка timer: + +- кожен `timer()` створює стійкий рядок timer та типізовані події історії + `TimerScheduled`, `TimerFired` і, після заміщення, `TimerCancelled` +- відкладені timer виконуються через окреме завдання timer до відновлення + завдання workflow +- `timer(0)` спрацьовує безпосередньо під час завдання workflow та не створює + завдання timer +- шляхи відтворення й query вважають типізовану історію `TimerScheduled`, + `TimerFired` та `TimerCancelled` авторитетним джерелом життєвого циклу timer. + Тому звичайні timer залишаються заблокованими до надходження зафіксованої + події спрацювання, а деталі обраного запуску можуть відновити з історії + відкриті, спрацьовані чи скасовані очікування timer +- Waterline показує очікування timer у деталях запуску та даних панелі +- команди `cancel()` і `terminate()` на рівні рушія стійко заміщують відкриті + очікування timer. Запізнілі завдання timer нічого не змінюють і не відкривають + запуск повторно + +Timer будь-якої тривалості працюють із кожним драйвером черг Laravel, включно +з Amazon SQS. Рушій автоматично ділить довгі затримки на частини відповідно до +межі драйвера для одного повідомлення. + +`sideEffect()` доступний для знімків, безпечних для відтворення, наприклад +випадковості чи одноразових вхідних даних розгалуження. + +## Читання детермінованого часу {#reading-deterministic-time} + +У тілі workflow для поточного часу використовуйте `Workflow::now()` або +`Workflow\V2\now()` замість допоміжної функції Laravel `now()` чи `Carbon::now()`: + +```php +use function Workflow\V2\activity; +use function Workflow\V2\now; +use Workflow\V2\Workflow; + +class DurationAwareWorkflow extends Workflow +{ + public function handle(string $name): array + { + $startedAt = Workflow::now(); + $greeting = activity(GreetingActivity::class, $name); + $finishedAt = Workflow::now(); + + return [ + 'greeting' => $greeting, + 'took_ms' => $finishedAt->getTimestampMs() - $startedAt->getTimestampMs(), + ]; + } +} +``` + +`Workflow::now()` просувається, коли виконавець відтворює події історії. Він +повертає `recorded_at` останнього завершення activity, спрацювання timer, +отримання signal, розв’язання умови чи завершення дочірнього workflow, яке +спожило відтворення. До споживання будь-якої події він повертає `started_at` +запуску. Поза Fiber workflow, наприклад в activity чи методі query, що делегує +допоміжній функції поза workflow, він використовує реальний час `now()`. + +Використання `Workflow::now()` зберігає детермінованість workflow: два +відтворення тієї самої історії повертають ті самі значення часу, навіть якщо +між ними реальний час просунувся. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/how-it-works.md b/i18n/uk/docusaurus-plugin-content-docs/current/how-it-works.md new file mode 100644 index 0000000000..c168c24935 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/how-it-works.md @@ -0,0 +1,103 @@ +--- +sidebar_position: 10 +--- + +# Принцип роботи {#how-it-works} + +Durable Workflow використовує завдання черг Laravel та зберігання на основі подій для створення стійких корутин. Workflow призупиняються через допоміжні виклики на основі Fiber, забезпечуючи контракт стійкого відтворення. + +## Середовище виконання {#runtime} + +Workflow — клас, метод `handle()` якого послідовно викликає допоміжні функції, зокрема `activity()`, `await()`, `timer()`, `sideEffect()`, `child()` та `all([...])`. Кожен виклик призупиняє workflow до завершення відповідного стійкого кроку, а потім продовжує з того самого місця із записаним результатом. + +Кожен крок створює стійку подію історії. Рушій відтворює історію щоразу, коли workflow прокидається, відновлюючи стан із потоку подій перед виконанням наступного невиконаного кроку. Це відтворення дозволяє workflow переживати перезапуски worker, розгортання та збої машин без втрати позиції. + +`WorkflowStub::make()` резервує публічний ідентифікатор екземпляра workflow. Запуск workflow створює перший запуск і перше завдання workflow. Кожен запуск має власний ідентифікатор. Операції `signal()`, `cancel()` та `terminate()` спрямовані на поточний запуск екземпляра. + +## Event sourcing {#event-sourcing} + +Event sourcing відновлює поточний стан із послідовності збережених подій замість безпосереднього збереження стану. Це надає повну історію подій виконання та дозволяє продовжити workflow, якщо worker аварійно завершиться. + +## Корутини {#coroutines} + +Корутини — функції, виконання яких можна призупиняти та продовжувати. Стійкі точки призупинення виражаються послідовними допоміжними викликами на основі Fiber, зокрема `activity()`, `await()`, `timer()` та `sideEffect()`. + +Код користувацького workflow розміщується в `handle()`, звичайному методі, що безпосередньо викликає ці допоміжні функції. Середовище виконання спочатку перевіряє, чи крок уже завершився стійко. Якщо так, збережений результат відтворюється з історії без повторного виконання кроку. Інакше середовище ставить у чергу наступну activity, timer чи дочірню роботу та призупиняється до успіху чи помилки цього стійкого кроку. + +## Activity {#activities} + +Викликаючи кілька activity, workflow може координувати їхні результати. Виконання workflow чергується зі стійкими кроками, які він планує: workflow доходить до виклику activity, призупиняється до її завершення, а потім продовжує виконання з того самого місця. + +У разі збою workflow події до збою відтворюються для відновлення поточного стану. Це дозволяє workflow продовжити з того самого місця з тими самими вхідними даними й результатами, зберігаючи детермінованість. + +Звичайні activity у v2 — стійка робота в черзі. Явні +[локальні activity](./features/local-activities.md) виконують коротку роботу +activity в процесі worker workflow зі збереженням стійкої історії та семантики +повторних спроб. Звичайні activity можуть працювати на будь-якому сумісному +worker, а [сесії worker](./features/worker-sessions.md) додають явну оренду, +коли послідовності кроків activity потрібен той самий локальний ресурс worker. +Якщо потрібне одноразове значення, безпечне для відтворення, без постановки +activity в чергу, використовуйте [`sideEffect(...)`](./features/side-effects.md). +Повний контракт наведено в [моделі виконання activity](./features/activity-execution-model.md). + +## Гарантії виконання {#execution-guarantees} + +Код workflow та activity має різну семантику повторного виконання: + +- **Код workflow відтворюється.** Повторна доставка завдання workflow відновлює стан зі стійкої історії та повторно виконує детермінований код. Відтворення не повторює зовнішні побічні ефекти. +- **Activity — робота в черзі з виконанням щонайменше один раз.** Логічна activity може виконуватися повторно, доставлятися знову після завершення оренди чи спостерігатися повторно після втрати worker. Повторна доставка — звичайна умова розподіленої системи, а не помилка сама по собі. +- **Ідентичність activity стійка.** `activity_execution_id` визначає одне логічне виконання activity між повторними спробами й доставками, а `activity_attempt_id` — окрему спробу. Використовуйте `activity_execution_id` як типовий ключ віддаленої ідемпотентності. Використовуйте `activity_attempt_id` лише тоді, коли системі отримувача потрібна кореляція окремих спроб. + +Повний контракт v2 наведено в [гарантіях виконання та ідемпотентності](./constraints/execution-guarantees.md), [моделі виконання activity](./features/activity-execution-model.md) та [збоях і відновленні](./failures-and-recovery.md). + +## Черги {#queues} + +Завдання черг — фонові процеси, які виконуються пізніше. Laravel підтримує черги через Amazon SQS, Redis чи реляційну базу даних. Workflow й activity є завданнями черг, але поводяться дещо по-різному. Workflow під час звичайної роботи надсилається кілька разів: він виконується, надсилає одну чи кілька activity та виходить до їх завершення. Activity — завдання черги з виконанням щонайменше один раз. Зазвичай достатньо однієї успішної спроби, але повторна спроба, завершення оренди чи втрата worker можуть спричинити повторну доставку того самого логічного виконання activity. + +## Приклад {#example} + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\{activity, all}; + +class MyWorkflow extends Workflow +{ + public function handle(): array + { + return [ + activity(TestActivity::class), + activity(TestOtherActivity::class), + fn () => all([ + fn () => activity(TestParallelActivity::class), + fn () => activity(TestParallelOtherActivity::class), + ]), + ]; + } +} +``` + +## Діаграма послідовності {#sequence-diagram} + +Ця діаграма послідовності показує, як workflow проходить через послідовні та паралельні activity. + +import ThemedImage from '@site/src/components/ThemedImage'; + + + +1. Workflow починається з надсилання як завдання черги. +2. Перша activity, `TestActivity`, надсилається як завдання черги. Потім завдання workflow виходить. Після завершення `TestActivity` зберігає результат у базі даних та повертає керування workflow, знову надсилаючи його. +3. Workflow входить у цикл відтворення event sourcing. Він повертається до бази даних і читає потік подій для відновлення поточного стану. Це потрібно, бо workflow не є тривалим процесом. Він виходить, поки activity виконуються, та знову надсилається після їх завершення. +4. Після відтворення потоку подій workflow переходить до наступної activity, `TestOtherActivity`, запускаючи її як завдання черги. Після завершення вона також зберігає результат у базі даних і повертає керування workflow, надсилаючи його як завдання черги. +5. Workflow знову входить у цикл відтворення event sourcing та відновлює поточний стан із потоку подій. +6. Далі workflow запускає дві паралельні activity, `TestParallelActivity` та `TestOtherParallelActivity`. Обидві надсилаються. Після завершення вони зберігають результати в базі даних та повертають керування workflow. +7. Нарешті workflow востаннє входить у цикл відтворення event sourcing для відновлення поточного стану з потоку подій. Це завершує виконання workflow. + +## Детермінованість {#determinism} + +Оскільки історія відтворюється під час кожного пробудження, код workflow має створювати ті самі команди за тієї самої історії. Дивіться [обмеження](./constraints/overview.md) для правил написання та допоміжних засобів Durable Workflow (`Workflow\now()`, `sideEffect()`, `getVersion()` тощо) для ситуацій, де код інакше був би недетермінованим. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/sponsors.md b/i18n/uk/docusaurus-plugin-content-docs/current/sponsors.md new file mode 100644 index 0000000000..bb104ad76c --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/sponsors.md @@ -0,0 +1,18 @@ +--- +sidebar_position: 16 +--- + +# Спонсори {#sponsors} + +Спільнота підтримує Durable Workflow завдяки спонсорам і волонтерам. Якщо цей +пакет був вам корисний, розгляньте підтримку його подальшої розробки. + +## Поточні спонсори {#current-sponsors} + +- Freispace Resource Scheduling +- Translate a Book + +## Як стати спонсором {#becoming-a-sponsor} + +Якщо хочете підтримати подальшу розробку, зробіть це через +[GitHub Sponsors](https://github.com/sponsors/durable-workflow). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/support.md b/i18n/uk/docusaurus-plugin-content-docs/current/support.md new file mode 100644 index 0000000000..38705a6db0 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/support.md @@ -0,0 +1,124 @@ +--- +sidebar_position: 15 +--- + +# Підтримка {#support} + +Durable Workflow — проєкт із відкритим кодом, який можна безкоштовно використовувати за ліцензією MIT. Для запуску, поширення чи побудови production систем не потрібна комерційна угода. + +## Підтримка спільноти {#community-support} + +Основний спосіб отримати допомогу з Durable Workflow — спільнота. Підтримка спільноти найкраще підходить для публічних питань, обговорення проєктування, повідомлень про помилки та спільних операційних шаблонів. + +Це найкраще місце для: + +- питань щодо використання +- порад із моделювання workflow +- допомоги з розумінням детермінованості та відтворення +- обміну ідеями та шаблонами +- повідомлень про помилки чи неочікувану поведінку + +### GitHub Discussions {#github-discussions} + +GitHub Discussions — рекомендоване місце для розгорнутих питань, обговорення проєктування та тем, корисних іншим. + +https://github.com/durable-workflow/workflow/discussions + +### Discord {#discord} + +Discord найкраще підходить для: + +- коротких питань +- неформальних обговорень +- раннього зворотного зв’язку +- співпраці спільноти + +https://discord.gg/xu5aDDpqVy + +Підтримка спільноти надається в міру можливостей і часто достатня для розробки, експериментів та багатьох production сценаріїв. + +## Межі підтримки розгортання {#deployment-support-boundary} + +Durable Workflow спроєктовано для корисного використання без розмови з відділом продажів. Публічний дистрибутив цілеспрямовано оптимізований для варіантів розгортання, які потрібні більшості команд насамперед: + +- **Локальна розробка та внутрішні середовища поза production** + Запускайте пакет чи окремий Server локально, у локальній мережі або спільному staging-середовищі, щоб розробники могли швидко запускати workflow та ітерувати. + +- **Production на одному вузлі** + Запускайте окремий Server на одній VM, VPS чи внутрішньому хості зі стійкою базою даних, кешем, зворотним проксі, резервними копіями та обліковими даними з обмеженнями за ролями. + +- **Невеликі кластерні розгортання** + Запускайте 2–3 API-контейнери без стану за балансувальником зі спільною зовнішньою MySQL чи PostgreSQL, спільним Redis, незалежно масштабованими worker та рівно одним планувальником чи процесом обслуговування. + +- **Маніфести Kubernetes** + Використовуйте надані маніфести, якщо команда вже експлуатує Kubernetes та потребує початкового налаштування для нього. + +Durable Workflow Cloud — окремий [керований сервіс оркестрації](/docs/polyglot/cloud-control-plane). Cloud експлуатує своє середовище виконання, збереження, розміщення в одному регіоні та відновлення. Клієнти запускають SDK-клієнти й worker. Власний Server клієнта не підключається до Cloud. Міжрегіональна реплікація, перемикання регіону під час збою та повернення до попереднього регіону не входять до поточного контракту Cloud. + +Ми цілеспрямовано оптимізуємо публічний дистрибутив для локальної розробки, production на одному вузлі та вузького контракту малого кластера з посібника самостійного розгортання. Маніфести Kubernetes надано командам, які вже його експлуатують. Контракт малого кластера підтримує [поступові оновлення](/docs/rolling-upgrades), коли виконуються всі гарантії цієї сторінки. Міжрегіональна схема active/passive з перемиканням регіону оператором залишається рекомендацією для оцінювання за участі підтримки. Дивіться [active/passive між регіонами](/docs/deployment#activepassive-multi-region) в посібнику самостійного розгортання. Опублікований Helm chart є шляхом самостійного встановлення та оновлення. Власні зміни chart, інфраструктура провайдера, дубльовані планувальники, багатовузлова робота без Redis, перемикання провайдера, міжрегіональні active/passive чи active/active, самостійне автоматичне перемикання регіону та складні топології високої доступності потребують участі підтримки, бо вимагають специфічних для середовища рішень щодо розміру, бази даних, мережі, безпеки й оновлень. + +Дивіться [посібник самостійного розгортання](/docs/deployment) для конкретної матриці підтримки, рецептів Compose з опублікованими образами, меж сирих маніфестів Kubernetes, перевірок готовності та початку роботи за участі підтримки. + +Ця межа зберігає простоту самостійного шляху та дозволяє отримати глибшу допомогу, коли саме розгортання стає проєктом розподіленої системи. + +## Комерційна підтримка за бажанням {#commercial-support-optional} + +Для команд, яким потрібні додаткові гарантії чи експертиза команди підтримки проєкту, доступні комерційна підтримка та консультування. + +Використання Durable Workflow **не** потребує комерційної угоди. Усі можливості залишаються повністю відкритими незалежно від використання комерційної підтримки. + +Комерційна підтримка призначена для команд, які використовують Durable Workflow у production системах, де важливі коректність, безпека оновлень, топологія розгортання та семантика тривалого виконання. + +## Що охоплює комерційна підтримка {#what-commercial-support-covers} + +Комерційну підтримку очолює супровідник проєкту. Зазвичай вона включає: + +- **Планування production розгортання** + Допомога з вибором одного вузла, вузького малого кластера, Kubernetes чи моделі за участі підтримки, включно з базою даних, кешем, зворотним проксі, планувальником і топологією worker. + +- **Планування розміру, надійності та оновлень** + Допомога з припущеннями щодо місткості, стратегією резервного копіювання та відновлення, порядком початкового запуску й міграцій, безпекою розгортання та операційними інструкціями. + +- **Підтримка складних топологій** + Проєктування за участі підтримки для власних змін Helm chart, інфраструктури Kubernetes конкретного провайдера, власних overlay, дубльованих планувальників, багатовузлового режиму без Redis, високої доступності, міжрегіональних active/passive чи active/active, самостійного автоматичного перемикання регіону та синхронної міжрегіональної реплікації. Опублікований chart залишається шляхом самостійного встановлення. [Посібник самостійного розгортання](/docs/deployment#activepassive-multi-region) зберігає архітектуру та інструкції active/passive для оцінювання за участі підтримки. Окремий [контракт керованого середовища Cloud](/docs/polyglot/cloud-control-plane) документує розміщення сервісу в одному регіоні, відновлення та межі підключення. + +- **Перевірка безпеки** + Допомога з обліковими даними за ролями, відкритістю мережі, внутрішніми та публічними точками доступу, завершенням TLS та межами доступу операторів і worker. + +- **Перевірка архітектури та проєктування workflow** + Допомога з коректним моделюванням workflow: межі activity, signal, використання inbox/outbox та шаблони тривалого виконання. + +- **Детермінованість та безпечне відтворення** + Допомога з виявленням і уникненням недетермінованої поведінки, проблем відтворення та тонких проблем коректності, що можуть проявитися з часом. + +- **Стратегія оновлення та версіонування** + Допомога з безпечними оновленнями, версіонуванням workflow та визначенням, коли потрібні шаблони на кшталт `continue-as-new`. + +- **Налагодження складної поведінки workflow** + Діагностика завислих workflow, неочікуваних циклів, проблем обробки signal та іншої складної для розуміння поведінки середовища виконання. + +- **Виправлення та перенесення виправлень** + Доступ до виправлень для підтримуваних версій, щоб уникати супроводу приватних форків. + +- **Власні інтеграції за домовленістю** + Допомога з інтеграцією Durable Workflow з наявними системами: чат-платформами, системами погоджень, внутрішніми API чи інструментами спостереження. + +## Кому підходить комерційна підтримка {#who-commercial-support-is-for} + +Комерційна підтримка зазвичай підходить, якщо ви: + +- використовуєте Durable Workflow у **production** +- розгортаєте окремий Server для **кількох команд чи сервісів** +- створюєте **тривалі workflow чи workflow за участі людини** +- проєктуєте **кластерну топологію, Kubernetes/Helm для конкретного провайдера чи з власними змінами, міжрегіональні active/passive чи active/active або самостійне автоматичне перемикання** поза перевіреними операційними межами 2.0 в одному регіоні +- працюєте в середовищі, де **важливі коректність та безпека оновлень** +- шукаєте **експертизу супровідника проєкту**, а не загальне консультування +- хочете уникнути форків та довгострокових ризиків супроводу + +## Контакт {#contact} + +Для обговорення комерційної підтримки чи консультування звертайтеся: + +**support@durable-workflow.com** + +Ми можемо почати з короткої розмови, щоб зрозуміти ваш сценарій та визначити наступні кроки. diff --git a/i18n/uk/docusaurus-plugin-content-pages/compare/temporal.mdx b/i18n/uk/docusaurus-plugin-content-pages/compare/temporal.mdx new file mode 100644 index 0000000000..ba866bcb97 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-pages/compare/temporal.mdx @@ -0,0 +1,228 @@ +--- +title: Durable Workflow і Temporal +description: "Порівняння Durable Workflow і Temporal: workflow на PHP, Python та Rust, PHP на основі Fiber без RoadRunner, окрема архітектура, відтворення та самостійна експлуатація." +image: /img/durable-workflow-2.0.png +--- + +import CodeBlock from '@theme/CodeBlock'; +import handlers from '@site/static/examples/temporal-comparison/handlers.php'; + +# Durable Workflow і Temporal {#durable-workflow-vs-temporal} + +**Стійке виконання для PHP, Python та Rust на знайомому серверному стеку.** + +Durable Workflow — рушій workflow з відкритим кодом. Ваш застосунок запускає +worker для workflow й activity на PHP, Python або Rust. Server відповідає за +надійне зберігання історії, розподіл завдань, timer та відновлення. Вашому +застосунку Laravel не потрібен. + +Запускайте Server як окремий сервіс із опублікованого контейнера, а застосунок +і worker залишайте у власному мовному середовищі та середовищі розгортання. +Можна використовувати одну мову або поєднувати worker різними мовами. +Розробники PHP отримують workflow на основі Fiber та worker, який запускається +безпосередньо, без RoadRunner. + +Temporal використовує спеціалізований сервіс оркестрації та має ширший вибір SDK. +У PHP його API для workflow використовує генератори, а worker працюють через +RoadRunner. Вибір залежить від потреб застосунку та відповідальності за експлуатацію. + +Обіцянка Durable Workflow — **передбачувана вартість, передбачувана поведінка та +передбачувана експлуатація**: інфраструктура з планованим бюджетом, явні контракти +виконання та історія workflow, яку можна дослідити. + +*Від команди підтримки Durable Workflow. Оновлено 21 вересня 2026 року.* + +## Коротко про архітектуру {#architecture-at-a-glance} + +| Рішення | Durable Workflow | Temporal | +| --- | --- | --- | +| Окреме середовище виконання | Server, незалежний від мови клієнта, реалізований як застосунок Laravel. | Спеціалізований Temporal Service зі сховищами збереження й видимості. | +| Мови SDK | PHP, Python та Rust. | Go, Java, .NET, PHP, Python, Ruby, Rust та TypeScript. | +| Написання PHP-коду | Послідовні методи зі стійкими викликами на основі Fiber та звичайними типами повернення. | Методи workflow на основі генераторів зі стійкими операціями через yield. | +| PHP worker | Безпосередній процес PHP з HTTP-клієнтом. | Процеси PHP під керуванням RoadRunner. | +| Вбудовування в застосунок | Застосунки Laravel можуть запускати рушій у власній базі даних і чергах. | Worker застосунку підключаються до Temporal Service. | + +Джерела: [Durable Workflow Server](/docs/polyglot/server/), +[PHP SDK](https://php.durable-workflow.com/), +[Temporal Service](https://docs.temporal.io/temporal-service), +[workflow на PHP у Temporal](https://docs.temporal.io/develop/php/workflows/basics), +[worker на PHP у Temporal](https://docs.temporal.io/develop/php/workers/run-worker-process) +та [SDK Temporal](https://docs.temporal.io/develop). + +## Один Server, три офіційні SDK {#one-server-three-first-party-sdks} + +Ви можете писати workflow й activity на +[PHP](https://php.durable-workflow.com/), +[Python](https://python.durable-workflow.com/) та +[Rust](https://rust.durable-workflow.com/). Worker працюють у середовищі вашого +застосунку, підключаються до Server та масштабуються незалежно від нього. + +Команда Python може залишити весь застосунок на Python. Команда Rust може +залишити весь застосунок на Rust. Мова реалізації Server не стає залежністю +застосунку. + +Мови можна поєднувати й у межах одного workflow. +[Приклад із запуском кількох мов](https://github.com/durable-workflow/sample-app#service-mode) +використовує workflow на PHP, activity обчислення на Python та activity квитанції +на Rust в окремих чергах завдань. Рушій координує їх через іменовані типи +workflow й activity. + +Temporal пропонує [SDK для ширшого набору мов](https://docs.temporal.io/develop). +Для застосунку на Go, Java, .NET, Ruby або TypeScript така підтримка є конкретною +причиною обрати Temporal. + +## Послідовний PHP-код без RoadRunner {#straight-line-php-without-roadrunner} + +PHP SDK Durable Workflow використовує керований Fiber. Виклик +`$context->activity(...)` призупиняє виконання workflow, доки стійкий результат +не стане доступним. Потім метод продовжується з цим результатом. Ваш workflow +залишається послідовним PHP-методом зі звичайним типом повернення. У коді workflow +не потрібно використовувати `yield` чи типи повернення Generator. + +Worker запускається безпосередньо командою `php worker.php`. Його +[HTTP-транспорт PSR-18](https://github.com/durable-workflow/sdk-php/blob/main/src/Transport/Psr18Transport.php) +не потребує ані RoadRunner, ані розширення gRPC. + +[API workflow на PHP у Temporal](https://docs.temporal.io/develop/php/workflows/basics) +виражає призупинення через генератори й `yield`. Його +[налаштування PHP](https://docs.temporal.io/develop/php/set-up-your-local-php) +включає RoadRunner та розширення gRPC. + +Ось повний файл обробників Durable Workflow **для режиму сервісу**. Workflow +планує activity привітання та повертає її результат: + +{handlers} + +Завантажте [worker, клієнт і тест](https://github.com/durable-workflow/durable-workflow.github.io/tree/main/static/examples/temporal-comparison). +У цьому каталозі, маючи PHP 8.1+ та Composer, виконайте: + +```sh +composer install +php test.php +``` + +Тест перевіряє реєстрацію, планування та виконання activity, а також відтворення +із записаним результатом activity. Щоб запустити worker і клієнт із реальним +середовищем виконання, запустіть [локальний Server](/docs/polyglot/server/) за +адресою `http://localhost:8080` з документованим токеном розробки, а потім +виконайте `php worker.php` та `php client.php` в окремих терміналах: + +```json +{"greeting":"Hello, Ada!"} +``` + +Ми перевірили цей результат з опублікованими SDK та Server. +[Версії та результати тестів](https://github.com/durable-workflow/durable-workflow.github.io/pull/135) +записано разом із вихідним кодом. + +## Передбачувана поведінка між worker {#predictable-behavior-across-workers} + +Обидва рушії відновлюють стан workflow із записаної історії. Результати завершених +activity відтворюються в коді workflow без повторного виконання вже завершеної +роботи. Тому рішення workflow мають залишатися детермінованими: мережеві виклики +та зміни бази даних розміщуйте в activity, а для часу використовуйте стійкі API +timer і часу. Дивіться [модель виконання Durable Workflow](/docs/how-it-works/) +та [відтворення workflow у Temporal](https://docs.temporal.io/workflow-execution#replays). + +Доставка activity — окреме питання. Worker може виконати зовнішню дію та +завершитися з помилкою до повідомлення результату, тож повторна спроба може +повторити цю дію. Durable Workflow надає стабільну логічну ідентичність виконання +activity між спробами. Її можна використовувати як ключ ідемпотентності для +платежів та інших зовнішніх ефектів. +[Гарантії виконання](/docs/constraints/execution-guarantees/) пояснюють цей +контракт. Temporal також рекомендує +[ідемпотентні activity](https://docs.temporal.io/activities). + +### Спільний контракт даних {#a-shared-payload-contract} + +SDK Durable Workflow для PHP, Python та Rust використовують спільний фіксований +[контракт Avro Value](/docs/polyglot/avro-value-protocol/). Він зберігає між мовами +відмінності, зокрема між байтами й текстом та між цілими числами й числами з +рухомою комою. SDK містять схему, тож вам не потрібно експлуатувати реєстр схем. +Об’єкти застосунку адаптуються до цих переносимих значень. + +Temporal використовує [Data Converters](https://docs.temporal.io/dataconversion) +із типовою серіалізацією та підтримкою власних конвертерів і кодеків. Durable +Workflow обирає більш визначений підхід: один переносимий контракт значень +для всіх офіційних SDK. + +Для впевненості в розгортанні перевіряйте зміни workflow на записаній історії, +а також тестуйте бізнес-логіку activity. Durable Workflow надає +[тести відтворення PHP SDK](https://php.durable-workflow.com/build/testing/) та +[тестові підміни вбудованого режиму](/docs/testing/). Temporal документує свої +[засоби модульного та інтеграційного тестування PHP](https://docs.temporal.io/develop/php/best-practices/testing-suite). + +## Самостійне розгортання на знайомому стеку застосунків {#self-host-on-a-familiar-application-stack} + +**Запуск Durable Workflow Server означає запуск застосунку Laravel.** Він +використовує налаштування PHP-застосунку, базу даних, сервіси черг і кешу, а також +процеси worker та планувальника. Оператори, знайомі з Laravel, можуть застосувати +свої навички для розгортання, журналювання, нагляду за процесами та обслуговування +бази даних. + +[Вихідний код Server](https://github.com/durable-workflow/server) та +[посібник із розгортання](/docs/polyglot/server/) дозволяють дослідити це +налаштування. Опублікований Docker-образ уже містить застосунок. Його використання +не вимагає PHP чи Laravel у середовищі ваших worker на Python або Rust. + +| Розгортання | Що ви експлуатуєте | +| --- | --- | +| Durable Workflow Server | Застосунок Laravel із процесами API, worker і планувальника, сервіси бази даних, черг і кешу, а також SDK worker застосунку. | +| Вбудований Durable Workflow | Ваш застосунок Laravel, базу даних, споживачів черг та кеш із підтримкою блокувань. | +| Temporal Service | Temporal Server, сховища збереження й видимості, а також SDK worker застосунку. | + +[Опубліковане налаштування Compose](/docs/polyglot/server/#published-image--compose) +для Durable Workflow використовує MySQL та Redis. Локальний швидкий старт також +підтримує SQLite. +[Посібник із самостійного розгортання Temporal](https://docs.temporal.io/self-hosted-guide) +описує варіанти розгортання. Локальний сервіс розробки доступний через Temporal CLI. + +**Передбачувана вартість випливає з явної відповідальності.** +[Рушій Durable Workflow](https://github.com/durable-workflow/workflow/blob/main/LICENSE), +[Server](https://github.com/durable-workflow/server/blob/main/LICENSE) та +[Temporal Server](https://github.com/temporalio/temporal/blob/main/LICENSE) +мають ліцензію MIT. Самостійне розгортання будь-якого рушія не передбачає плати за +кожну дію в програмному забезпеченні. Ваші витрати — це інфраструктура та її +експлуатація. Durable Workflow дозволяє використовувати усталені інструменти +PHP-застосунків і незалежно масштабувати worker мовою вашого застосунку. + +### Дослідження історії workflow {#inspect-workflow-history} + +[Waterline](/docs/monitoring/) показує історію виконання Durable Workflow, +результати activity, timer та помилки. Він може працювати як окремий +операторський інтерфейс для Server або всередині застосунку Laravel для +вбудованого виконання. CLI та Server API також відкривають стан середовища +виконання. Temporal надає [вебінтерфейс](https://docs.temporal.io/web-ui) для +дослідження виконань та історії. + +### Інтеграція з Laravel, якщо застосунок використовує Laravel {#laravel-integration-when-your-application-uses-laravel} + +Користувачі Laravel мають два варіанти: вбудувати рушій або залишити окремий +Server і використовувати +[міст Laravel у PHP SDK](https://php.durable-workflow.com/frameworks/laravel/). +Міст надає обробники, які отримуються з контейнера, налаштування, журналювання, +команду Artisan для worker та тестову підміну. Відокремлення стану оркестрації +не вимагає відмовлятися від звичних правил фреймворку застосунку. + +Вбудовування йде далі: стан workflow зберігається в базі даних застосунку, а +worker черг Laravel виконують роботу. Ви розгортаєте рушій разом із застосунком, +замість експлуатації окремого сервісу. Почніть із +[посібника встановлення вбудованого режиму](/docs/installation/). + +## Який рушій підходить вашій команді? {#which-engine-fits-your-team} + +**Обирайте Durable Workflow** для застосунків на PHP, Python та Rust, якщо вам +потрібен рушій для самостійного розгортання зі спільним міжмовним контрактом +виконання та незалежно розгорнутими worker застосунку. Команди PHP також +отримують workflow на основі Fiber без RoadRunner і необов’язкову інтеграцію +з Laravel. + +**Обирайте Temporal**, якщо його ширша підтримка мов у SDK або ваші наявні +інструменти та досвід Temporal краще відповідають застосунку. + +**Почніть розробку:** [оберіть швидкий старт для PHP, Python чи Rust](/docs/quickstart/), +[запустіть Durable Workflow Server](/docs/polyglot/server/) або +[дослідіть SDK Temporal](https://docs.temporal.io/develop). + +Знайшли фактичну помилку? [Створіть issue документації](https://github.com/durable-workflow/durable-workflow.github.io/issues/new) +і вкажіть твердження та його джерело. diff --git a/i18n/uk/docusaurus-theme-classic/footer.json b/i18n/uk/docusaurus-theme-classic/footer.json index 39011d2dda..1b3b8b0cef 100644 --- a/i18n/uk/docusaurus-theme-classic/footer.json +++ b/i18n/uk/docusaurus-theme-classic/footer.json @@ -40,7 +40,7 @@ "description": "The label of footer link with label=Packagist linking to https://packagist.org/packages/durable-workflow/workflow" }, "copyright": { - "message": "© 2026 Durable Workflow. Усі права захищено.", + "message": "Авторські права © 2026 Durable Workflow.", "description": "The footer copyright" } } diff --git a/src/components/CliInstall/index.js b/src/components/CliInstall/index.js index 70e1f9f4d5..b51d974b9a 100644 --- a/src/components/CliInstall/index.js +++ b/src/components/CliInstall/index.js @@ -1,6 +1,7 @@ import React, {useEffect, useState} from 'react'; import BrowserOnly from '@docusaurus/BrowserOnly'; import CodeBlock from '@theme/CodeBlock'; +import Translate, {translate} from '@docusaurus/Translate'; const {ARTIFACT_PINS} = require('../../../scripts/public-artifact-versions'); @@ -10,7 +11,6 @@ const PLATFORMS = [ label: 'Linux (x86_64)', shell: 'bash', command: 'curl -fsSL https://durable-workflow.com/install.sh | sh', - note: 'Installs to ~/.local/bin. Set DURABLE_WORKFLOW_INSTALL_DIR to override.', asset: 'dw-linux-x86_64', }, { @@ -18,7 +18,6 @@ const PLATFORMS = [ label: 'Linux (arm64)', shell: 'bash', command: 'curl -fsSL https://durable-workflow.com/install.sh | sh', - note: 'Installs to ~/.local/bin. Set DURABLE_WORKFLOW_INSTALL_DIR to override.', asset: 'dw-linux-aarch64', }, { @@ -26,7 +25,6 @@ const PLATFORMS = [ label: 'macOS (Apple Silicon)', shell: 'bash', command: 'curl -fsSL https://durable-workflow.com/install.sh | sh', - note: 'Installs to ~/.local/bin. Set DURABLE_WORKFLOW_INSTALL_DIR to override.', asset: 'dw-macos-aarch64', }, { @@ -34,7 +32,6 @@ const PLATFORMS = [ label: 'Windows (x86_64)', shell: 'powershell', command: 'irm https://durable-workflow.com/install.ps1 | iex', - note: 'Installs to %USERPROFILE%\\.durable-workflow\\bin and adds it to your user PATH.', asset: 'dw-windows-x86_64.exe', }, ]; @@ -68,6 +65,9 @@ function Installer() { }, []); const platform = PLATFORMS.find((p) => p.id === selected) || PLATFORMS[0]; + const installationNote = platform.id === 'windows-x86_64' + ? translate({id: 'cliInstall.windowsNote', message: 'Installs to %USERPROFILE%\\.durable-workflow\\bin and adds it to your user PATH.'}) + : translate({id: 'cliInstall.unixNote', message: 'Installs to ~/.local/bin. Set DURABLE_WORKFLOW_INSTALL_DIR to override.'}); const assetUrl = `https://github.com/durable-workflow/cli/releases/download/` + `${ARTIFACT_PINS.cliVersion}/${platform.asset}`; @@ -114,7 +114,9 @@ function Installer() { {detected && detected !== selected && (

- Detected {PLATFORMS.find((p) => p.id === detected).label}.{' '} + {PLATFORMS.find((p) => p.id === detected).label}}}> + {'Detected {platform}.'} + {' '} { @@ -122,7 +124,7 @@ function Installer() { setSelected(detected); }} > - Switch back + Switch back .

@@ -131,19 +133,21 @@ function Installer() { {platform.command}

- {platform.note} The installer verifies the release SHA256SUMS manifest - before writing the binary. + {installationNote}{' '} + + The installer verifies the release SHA256SUMS manifest before writing the binary. +

- Or download the binary directly + Or download the binary directly

{platform.asset} {' '} - from the{' '} + from the{' '} - stable release + stable release .

diff --git a/src/components/ConcurrencySimulator/index.js b/src/components/ConcurrencySimulator/index.js index d09b91bb39..b08b231544 100644 --- a/src/components/ConcurrencySimulator/index.js +++ b/src/components/ConcurrencySimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ActivityStatus = { @@ -13,9 +14,9 @@ function ActivityBar({ name, status, progress, duration }) {
{name} - {status === ActivityStatus.PENDING && '⏳ Pending'} - {status === ActivityStatus.RUNNING && '▶️ Running'} - {status === ActivityStatus.COMPLETED && '✅ Completed'} + {status === ActivityStatus.PENDING && translate({id: 'simulator.pending', message: '⏳ Pending'})} + {status === ActivityStatus.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {status === ActivityStatus.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})}
@@ -38,7 +39,7 @@ export default function WorkflowSimulator({ { name: 'MyActivity3', duration: 1200 }, ], mode = 'series', - title = 'Series Execution', + title = translate({id: 'simulator.series.title', message: 'Series Execution'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [isRunning, setIsRunning] = useState(false); @@ -303,7 +304,7 @@ export default function WorkflowSimulator({ onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -316,14 +317,14 @@ export default function WorkflowSimulator({ onClick={handlePlay} disabled={isRunning} > - {isRunning ? '⏳ Running...' : '▶ Play'} + {isRunning ? translate({id: 'simulator.runningHourglass', message: '⏳ Running...'}) : translate({id: 'simulator.play', message: '▶ Play'})}
@@ -342,10 +343,10 @@ export default function WorkflowSimulator({
- {isRunning ? '▶️ Running' : allCompleted ? '✅ Completed' : '⏸️ Ready'} + {isRunning ? translate({id: 'simulator.running', message: '▶️ Running'}) : allCompleted ? translate({id: 'simulator.completed', message: '✅ Completed'}) : translate({id: 'simulator.ready', message: '⏸️ Ready'})} - Mode: {mode === 'series' ? 'Series' : mode === 'mix' ? 'Mix' : 'Parallel'} + Mode:{' '}{mode === 'series' ? translate({id: 'simulator.mode.series', message: 'Series'}) : mode === 'mix' ? translate({id: 'simulator.mode.mix', message: 'Mix'}) : translate({id: 'simulator.mode.parallel', message: 'Parallel'})}
diff --git a/src/components/HeartbeatSimulator/index.js b/src/components/HeartbeatSimulator/index.js index 8633fef879..ab39b293cb 100644 --- a/src/components/HeartbeatSimulator/index.js +++ b/src/components/HeartbeatSimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -41,7 +42,7 @@ class MyActivity extends Activity }`; export default function HeartbeatSimulator({ - title = "Heartbeat Simulator", + title = translate({id: 'simulator.heartbeat.title', message: 'Heartbeat Simulator'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [withHeartbeat, setWithHeartbeat] = useState(true); @@ -159,7 +160,7 @@ export default function HeartbeatSimulator({ onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -172,14 +173,14 @@ export default function HeartbeatSimulator({ className={styles.stopButton} onClick={stopSimulation} > - ⏹ Stop + ⏹ Stop ) : ( )} @@ -198,14 +199,14 @@ export default function HeartbeatSimulator({ onClick={() => setWithHeartbeat(true)} disabled={executionState === ExecutionState.RUNNING} > - With Heartbeat + With Heartbeat @@ -231,16 +232,16 @@ export default function HeartbeatSimulator({ {executionState === ExecutionState.RUNNING && (
- Loop iterations: + Loop iterations: {loopCount}
- Total time: + Total time: {elapsedTime.toFixed(1)}s
- Time since last heartbeat: {timeSinceHeartbeat.toFixed(1)}s / 5s timeout + {timeSinceHeartbeat.toFixed(1)}s}}>{'Time since last heartbeat: {elapsed} / 5s timeout'}
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} - {executionState === ExecutionState.FAILED && '❌ Timeout - No Heartbeat!'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})} + {executionState === ExecutionState.FAILED && translate({id: 'simulator.noHeartbeat', message: '❌ Timeout - No Heartbeat!'})} {executionState === ExecutionState.RUNNING && withHeartbeat && ( - 💓 Heartbeat active + 💓 Heartbeat active )}
diff --git a/src/components/HomepageFeatures/index.js b/src/components/HomepageFeatures/index.js index 5d74e37894..122c779dda 100644 --- a/src/components/HomepageFeatures/index.js +++ b/src/components/HomepageFeatures/index.js @@ -1,36 +1,37 @@ import React from 'react'; import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; import styles from './styles.module.css'; const FeatureList = [ { - title: 'Build in PHP, Python, or Rust', + title: Build in PHP, Python, or Rust, Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( - <> + Author clients, workflows, and activities with first-party SDKs that share one public protocol and portable Avro payload model. - + ), }, { - title: 'Choose who operates the runtime', + title: Choose who operates the runtime, Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( - <> + Use Durable Workflow Cloud, run the standalone Server, or keep the Laravel-native embedded runtime inside your application. - + ), }, { - title: 'Recover through failures', + title: Recover through failures, Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( - <> + Durable history, deterministic replay, timers, retries, signals, updates, child workflows, and sagas keep long-running work moving. - + ), }, ]; diff --git a/src/components/PassingDataSimulator/index.js b/src/components/PassingDataSimulator/index.js index 35a901daf5..9954be5952 100644 --- a/src/components/PassingDataSimulator/index.js +++ b/src/components/PassingDataSimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -31,7 +32,7 @@ class MyActivity extends Activity }`; export default function PassingDataSimulator({ - title = "Hello World Simulator", + title = translate({id: 'simulator.passingData.title', message: 'Hello World Simulator'}), inputValue = "world", }) { const [isExpanded, setIsExpanded] = useState(false); @@ -113,7 +114,7 @@ export default function PassingDataSimulator({ onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -126,14 +127,14 @@ export default function PassingDataSimulator({ onClick={runSimulation} disabled={executionState === ExecutionState.RUNNING} > - {executionState === ExecutionState.RUNNING ? '▶️ Running...' : '▶ Play'} + {executionState === ExecutionState.RUNNING ? translate({id: 'simulator.runningArrow', message: '▶️ Running...'}) : translate({id: 'simulator.play', message: '▶ Play'})}
@@ -181,20 +182,20 @@ export default function PassingDataSimulator({ {output !== null && (
- Output: + Output: '{output}'
)}
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})} {executionState === ExecutionState.RUNNING && ( - Executing: {currentFile === 'workflow' ? 'MyWorkflow' : 'MyActivity'} + Executing:{' '}{currentFile === 'workflow' ? 'MyWorkflow' : 'MyActivity'} )}
diff --git a/src/components/ProductPromotion/index.js b/src/components/ProductPromotion/index.js index 289a19a160..d2280e877e 100644 --- a/src/components/ProductPromotion/index.js +++ b/src/components/ProductPromotion/index.js @@ -1,5 +1,6 @@ import React, {useEffect, useId, useRef} from 'react'; import Link from '@docusaurus/Link'; +import Translate from '@docusaurus/Translate'; import styles from './styles.module.css'; @@ -61,8 +62,8 @@ export default function ProductPromotion({source, children}) { aria-labelledby={titleId} data-promotion-source={source}>
-

Managed service · limited cohort

-

Durable Workflow Cloud launch cohort

+

Managed service · limited cohort

+

Durable Workflow Cloud launch cohort

{children}
sendPromotionEvent(source, 'click')}> - Request early access + Request early access ); diff --git a/src/components/ProtocolCatalog/index.js b/src/components/ProtocolCatalog/index.js index 610b967f6a..738bcc39ac 100644 --- a/src/components/ProtocolCatalog/index.js +++ b/src/components/ProtocolCatalog/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import Translate from '@docusaurus/Translate'; import protocolCatalog from '@site/static/platform-protocol-specs.json'; const {availableProtocolEntries} = require('./catalog'); @@ -38,23 +39,23 @@ export default function ProtocolCatalog() {
-
Specification ID
+
Specification ID
{entry.spec_id}
-
Public specification
+
Public specification
{entry.spec_url}
-
Format and status
+
Format and status
{entry.format} · {entry.status}
-
Owning contract
+
Owning contract
{entry.owner_repo}
-

Object families and owning contracts

+

Object families and owning contracts

))} diff --git a/src/components/PublicAuthorityIdentity/index.js b/src/components/PublicAuthorityIdentity/index.js index e2bf3e45e2..ab72ead8c6 100644 --- a/src/components/PublicAuthorityIdentity/index.js +++ b/src/components/PublicAuthorityIdentity/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import Translate from '@docusaurus/Translate'; export default function PublicAuthorityIdentity({manifest, manifestUrl}) { const manifestPath = new URL(manifestUrl).pathname; @@ -9,13 +10,15 @@ export default function PublicAuthorityIdentity({manifest, manifestUrl}) { data-authority-manifest={manifestUrl} data-authority-schema={manifest.schema} data-authority-version={manifest.version}> - The machine-readable authority is published at{' '} - - {manifestPath} - {' '} - with schema{' '} - {manifest.schema}, version{' '} - {manifest.version}. + {manifestPath}, + schema: {manifest.schema}, + version: {manifest.version}, + }}> + {'The machine-readable authority is published at {manifest} with schema {schema}, version {version}.'} +

); } diff --git a/src/components/QuerySimulator/index.js b/src/components/QuerySimulator/index.js index 7b9a8e6d42..48b6252080 100644 --- a/src/components/QuerySimulator/index.js +++ b/src/components/QuerySimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -30,7 +31,7 @@ class MyWorkflow extends Workflow $this->ready = await('ready') === true; } }`, - title = "Query & Signal Simulator", + title = translate({id: 'simulator.query.title', message: 'Query & Signal Simulator'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [executionState, setExecutionState] = useState(ExecutionState.IDLE); @@ -129,7 +130,7 @@ class MyWorkflow extends Workflow onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -142,15 +143,15 @@ class MyWorkflow extends Workflow onClick={runSimulation} disabled={executionState === ExecutionState.RUNNING || executionState === ExecutionState.WAITING} > - {executionState === ExecutionState.RUNNING ? '⏳ Running...' : - executionState === ExecutionState.WAITING ? '⏸️ Waiting...' : '▶ Play'} + {executionState === ExecutionState.RUNNING ? translate({id: 'simulator.runningHourglass', message: '⏳ Running...'}) : + executionState === ExecutionState.WAITING ? translate({id: 'simulator.waiting', message: '⏸️ Waiting...'}) : translate({id: 'simulator.play', message: '▶ Play'})}
@@ -206,7 +207,7 @@ class MyWorkflow extends Workflow className={styles.signalButton} onClick={sendSignal} > - 📤 Send Signal: setReady(true) + 📤 Send Signal:{' '}setReady(true) )} @@ -214,10 +215,10 @@ class MyWorkflow extends Workflow
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.WAITING && '⏳ Waiting for Signal'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.WAITING && translate({id: 'simulator.waitingSignal', message: '⏳ Waiting for Signal'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})} $ready = {readyValue ? 'true' : 'false'} diff --git a/src/components/SignalSimulator/index.js b/src/components/SignalSimulator/index.js index ae738a6856..ad47a08d89 100644 --- a/src/components/SignalSimulator/index.js +++ b/src/components/SignalSimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -23,7 +24,7 @@ class MyWorkflow extends Workflow }`, steps = null, signalName = "ready", - title = "Signal Execution Simulator", + title = translate({id: 'simulator.signal.title', message: 'Signal Execution Simulator'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [executionState, setExecutionState] = useState(ExecutionState.IDLE); @@ -149,7 +150,7 @@ class MyWorkflow extends Workflow onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -162,15 +163,15 @@ class MyWorkflow extends Workflow onClick={runSimulation} disabled={executionState === ExecutionState.RUNNING || executionState === ExecutionState.WAITING} > - {executionState === ExecutionState.RUNNING ? '⏳ Running...' : - executionState === ExecutionState.WAITING ? '⏸️ Waiting...' : '▶ Play'} + {executionState === ExecutionState.RUNNING ? translate({id: 'simulator.runningHourglass', message: '⏳ Running...'}) : + executionState === ExecutionState.WAITING ? translate({id: 'simulator.waiting', message: '⏸️ Waiting...'}) : translate({id: 'simulator.play', message: '▶ Play'})}
@@ -205,17 +206,17 @@ class MyWorkflow extends Workflow className={styles.signalButton} onClick={sendSignal} > - 📤 Send Signal: {signalName} + 📤 Send Signal:{' '}{signalName} )}
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.WAITING && '⏳ Waiting for Signal'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.WAITING && translate({id: 'simulator.waitingSignal', message: '⏳ Waiting for Signal'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})}
diff --git a/src/components/SignalTimerSimulator/index.js b/src/components/SignalTimerSimulator/index.js index 56af22bebf..77f6d782cf 100644 --- a/src/components/SignalTimerSimulator/index.js +++ b/src/components/SignalTimerSimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -25,7 +26,7 @@ class MyWorkflow extends Workflow } }`, timeoutDuration = 300, // 5 minutes in seconds - title = "Signal + Timer Simulator", + title = translate({id: 'simulator.signalTimer.title', message: 'Signal + Timer Simulator'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [executionState, setExecutionState] = useState(ExecutionState.IDLE); @@ -137,7 +138,7 @@ class MyWorkflow extends Workflow onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -150,15 +151,15 @@ class MyWorkflow extends Workflow onClick={runSimulation} disabled={executionState === ExecutionState.RUNNING || executionState === ExecutionState.WAITING} > - {executionState === ExecutionState.RUNNING ? '⏳ Running...' : - executionState === ExecutionState.WAITING ? '⏸️ Waiting...' : '▶ Play'} + {executionState === ExecutionState.RUNNING ? translate({id: 'simulator.runningHourglass', message: '⏳ Running...'}) : + executionState === ExecutionState.WAITING ? translate({id: 'simulator.waiting', message: '⏸️ Waiting...'}) : translate({id: 'simulator.play', message: '▶ Play'})} @@ -190,7 +191,7 @@ class MyWorkflow extends Workflow
- Timeout in: {formatTime(timeRemaining)} + Timeout in:{' '}{formatTime(timeRemaining)}
- 📤 Send Signal: setReady(true) + 📤 Send Signal:{' '}setReady(true)
@@ -212,10 +213,10 @@ class MyWorkflow extends Workflow
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.WAITING && '⏳ Waiting for Signal or Timeout'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.WAITING && translate({id: 'simulator.waitingSignalTimeout', message: '⏳ Waiting for Signal or Timeout'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})} {result !== null && ( @@ -223,7 +224,7 @@ class MyWorkflow extends Workflow {result ? 'true' : 'false'} - ({result ? 'signal received' : 'timeout reached'}) + ({result ? translate({id: 'simulator.signalReceived', message: 'signal received'}) : translate({id: 'simulator.timeoutReached', message: 'timeout reached'})}) )} diff --git a/src/components/TimerSimulator/index.js b/src/components/TimerSimulator/index.js index 05678da917..0b773c7d05 100644 --- a/src/components/TimerSimulator/index.js +++ b/src/components/TimerSimulator/index.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; const ExecutionState = { @@ -24,7 +25,7 @@ class MyWorkflow extends Workflow { line: 7, duration: 5000, label: "timer('5 seconds')", showCountdown: true }, { line: 9, duration: 500, label: "return 'The workflow waited 5 seconds.'", showCountdown: false }, ], - title = "Timer Execution Simulator", + title = translate({id: 'simulator.timer.title', message: 'Timer Execution Simulator'}), }) { const [isExpanded, setIsExpanded] = useState(false); const [executionState, setExecutionState] = useState(ExecutionState.IDLE); @@ -117,7 +118,7 @@ class MyWorkflow extends Workflow onClick={() => setIsExpanded(!isExpanded)} > {isExpanded ? '▼' : '▶'} - Try it out! + Try it out! {isExpanded && ( @@ -130,14 +131,14 @@ class MyWorkflow extends Workflow onClick={runSimulation} disabled={executionState === ExecutionState.RUNNING} > - {executionState === ExecutionState.RUNNING ? '⏳ Running...' : '▶ Play'} + {executionState === ExecutionState.RUNNING ? translate({id: 'simulator.runningHourglass', message: '⏳ Running...'}) : translate({id: 'simulator.play', message: '▶ Play'})}
@@ -168,7 +169,7 @@ class MyWorkflow extends Workflow {executionState === ExecutionState.RUNNING && getCurrentStep() && getCurrentStep().showCountdown && (
- Executing: {getCurrentStep().label} + Executing:{' '}{getCurrentStep().label}
- {executionState === ExecutionState.IDLE && '⏸️ Ready'} - {executionState === ExecutionState.RUNNING && '▶️ Running'} - {executionState === ExecutionState.COMPLETED && '✅ Completed'} + {executionState === ExecutionState.IDLE && translate({id: 'simulator.ready', message: '⏸️ Ready'})} + {executionState === ExecutionState.RUNNING && translate({id: 'simulator.running', message: '▶️ Running'})} + {executionState === ExecutionState.COMPLETED && translate({id: 'simulator.completed', message: '✅ Completed'})}
diff --git a/src/components/WorkerProtocolAuthorityRoles/index.js b/src/components/WorkerProtocolAuthorityRoles/index.js index 7469724cb3..b39980d181 100644 --- a/src/components/WorkerProtocolAuthorityRoles/index.js +++ b/src/components/WorkerProtocolAuthorityRoles/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import Translate from '@docusaurus/Translate'; import compatibilityContract from '@site/static/compatibility-contract.json'; import conformanceContract from '@site/static/platform-conformance-contract.json'; import protocolCatalog from '@site/static/platform-protocol-specs.json'; @@ -33,19 +34,21 @@ export default function WorkerProtocolAuthorityRoles() { data-current-server-protocol-version={currentServer.protocolVersion} data-current-conformance-protocol-version={currentConformance.protocolVersion} data-current-conformance-suite-version={currentConformance.suiteVersion}> -

Worker protocol authority roles

+

Worker protocol authority roles

- Runtime discovery and conformance qualification use separate protocol - authorities. Choose the row for the job you are doing; a matching - version label does not make a versioned historical resolver an alias - for the unversioned Server authority. + + Runtime discovery and conformance qualification use separate protocol + authorities. Choose the row for the job you are doing; a matching + version label does not make a versioned historical resolver an alias + for the unversioned Server authority. +

- - - + + + @@ -55,13 +58,13 @@ export default function WorkerProtocolAuthorityRoles() { data-resolver-role={currentServer.resolverRole} data-api-url={currentServer.apiUrl} data-stream-url={currentServer.streamUrl}> - + + + diff --git a/src/pages/index.js b/src/pages/index.js index 48c19a5f95..9d4c0c7157 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -2,6 +2,7 @@ import React from 'react'; import clsx from 'clsx'; import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; +import Translate, {translate} from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import HomepageFeatures from '@site/src/components/HomepageFeatures'; @@ -10,9 +11,6 @@ import ProductPromotion from '@site/src/components/ProductPromotion'; import styles from './index.module.css'; const homepageTitle = 'Durable Workflow 2.0'; -const homepageDescription = - 'A language-neutral durable execution platform for PHP, Python, and Rust, available as managed Cloud, self-hosted Server, or an embedded Laravel runtime.'; - function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); return ( @@ -21,21 +19,23 @@ function HomepageHeader() { data-homepage-release="stable-2.0">

{siteConfig.title}

-

{siteConfig.tagline}

+

+ Durable execution for PHP, Python, and Rust. +

- Get started + Get started - Choose a deployment mode + Choose a deployment mode
@@ -44,6 +44,10 @@ function HomepageHeader() { } export default function Home() { + const homepageDescription = translate({ + id: 'homepage.description', + message: 'A language-neutral durable execution platform for PHP, Python, and Rust, available as managed Cloud, self-hosted Server, or an embedded Laravel runtime.', + }); return ( @@ -73,8 +77,10 @@ export default function Home() {
- Run PHP, Python, or Rust workers against a managed namespace while - Durable Workflow operates the orchestration runtime. + + Run PHP, Python, or Rust workers against a managed namespace while + Durable Workflow operates the orchestration runtime. +
diff --git a/src/theme/Footer/index.js b/src/theme/Footer/index.js index b165f00657..6a0b193cd8 100644 --- a/src/theme/Footer/index.js +++ b/src/theme/Footer/index.js @@ -13,7 +13,7 @@ export default function FooterWrapper(props) { // Canonical /llms-full.txt tracks the stable unversioned docs line. // Only the explicit prerelease docs path should switch to the 2.0 bundle. const pathname = window.location.pathname; - const isV2Docs = pathname.startsWith('/docs/'); + const isV2Docs = /^\/(?:uk\/)?docs\//.test(pathname); const llmDocsLink = footerRef.current.querySelector('a[href*="llms-full.txt"]'); if (llmDocsLink && isV2Docs) { diff --git a/src/theme/GitHubStarNavbarItem/index.js b/src/theme/GitHubStarNavbarItem/index.js index 25385f2125..5e2ee2bdb6 100644 --- a/src/theme/GitHubStarNavbarItem/index.js +++ b/src/theme/GitHubStarNavbarItem/index.js @@ -1,6 +1,7 @@ import React, {useEffect, useState} from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; +import {translate} from '@docusaurus/Translate'; const DEFAULT_STAR_COUNT = 1171; // latest count used to avoid showing a zero-star count before the first successful fetch const STAR_COUNT_CACHE_TTL = 1000 * 60 * 30; @@ -135,9 +136,12 @@ export default function GitHubStarNavbarItem({ const {starCount, resolved} = useGitHubStarCount(repo); const fullStarCount = resolved ? formatFullStarCount(starCount) : null; const compactStarCount = formatCompactStarCount(starCount); + const repositoryLabel = ariaLabel === 'Star Durable Workflow on GitHub' + ? translate({id: 'navbar.github.starRepository', message: 'Star Durable Workflow on GitHub'}) + : ariaLabel ?? translate({id: 'navbar.github.repository', message: 'GitHub repository'}); const resolvedAriaLabel = fullStarCount - ? `${ariaLabel ?? 'GitHub repository'} (${fullStarCount} stars)` - : ariaLabel ?? 'GitHub repository'; + ? translate({id: 'navbar.github.repositoryStars', message: '{repository} ({count} stars)'}, {repository: repositoryLabel, count: fullStarCount}) + : repositoryLabel; const sharedClassName = clsx( className, 'navbar-github-star-link', @@ -159,7 +163,9 @@ export default function GitHubStarNavbarItem({ {compactStarCount} From 0de054f8863ca440aeafee110197bd8b5e23d2c9 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 02:41:58 +0000 Subject: [PATCH 03/10] Translate activity lifecycle and scheduling documentation --- docs/features/cancel-and-terminate.md | 9 +- docs/features/child-workflows.md | 5 +- docs/features/continue-as-new.md | 6 +- docs/features/sticky-execution.md | 7 +- .../features/activity-execution-model.md | 153 +++++ .../current/features/cancel-and-terminate.md | 220 +++++++ .../current/features/child-workflows.md | 247 ++++++++ .../current/features/concurrency.md | 283 +++++++++ .../current/features/continue-as-new.md | 156 +++++ .../features/external-payload-storage.md | 246 ++++++++ .../current/features/heartbeats.md | 64 ++ .../current/features/local-activities.md | 195 ++++++ .../current/features/message-streams.md | 181 ++++++ .../current/features/sagas.md | 139 +++++ .../current/features/schedules.md | 555 ++++++++++++++++++ .../current/features/sticky-execution.md | 223 +++++++ .../current/features/updates.md | 120 ++++ .../current/features/versioning.md | 235 ++++++++ .../current/features/worker-sessions.md | 170 ++++++ 19 files changed, 3203 insertions(+), 11 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/cancel-and-terminate.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/child-workflows.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/concurrency.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/continue-as-new.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/external-payload-storage.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/heartbeats.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/message-streams.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/sagas.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/schedules.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/sticky-execution.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/updates.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md diff --git a/docs/features/cancel-and-terminate.md b/docs/features/cancel-and-terminate.md index 2737500864..771e0492f2 100644 --- a/docs/features/cancel-and-terminate.md +++ b/docs/features/cancel-and-terminate.md @@ -15,10 +15,11 @@ Cancel and terminate are first-class durable commands that close a running workf In the current embedded API, both commands close the run immediately. **Cancel** records a `cancelled` outcome; **terminate** records a `terminated` outcome. Neither command schedules cleanup inside the closed workflow. Embedded Laravel -also provides `requestCancellation()` for bounded cooperative cleanup; that -separate request is not yet exposed by Server or service-mode SDKs. If your -service-mode application needs compensation before closing, signal the workflow -to run it and wait for completion before issuing terminal cancel. +also provides `requestCancellation()` for bounded cooperative cleanup. +Server and the PHP, Python and Rust SDKs support the separate cooperative +request with workers that explicitly enable it. See +[Cooperative Cancellation](/docs/polyglot/cancellation) for the capability, +deadline, cleanup and recovery contract. ## Cancel diff --git a/docs/features/child-workflows.md b/docs/features/child-workflows.md index 2d14849326..c51f00328c 100644 --- a/docs/features/child-workflows.md +++ b/docs/features/child-workflows.md @@ -133,6 +133,7 @@ When a parent workflow closes — whether by completing, failing, timing out, be | Policy | Value | Behavior | |---|---|---| | Abandon | `abandon` | The child continues running independently. This is the default. | +| Request Cancellation | `request_cancellation` | The child receives a cooperative cleanup request with the parent's original cancellation lineage and deadline. | | Request Cancel | `request_cancel` | A cancel command is sent to the child when the parent closes. | | Terminate | `terminate` | A terminate command is sent to the child when the parent closes. | @@ -184,7 +185,7 @@ $results = all([ ### How it works - The policy is recorded on the `workflow_links` row (`parent_close_policy`) and in the `ChildWorkflowScheduled` history event payload. -- When the parent run closes for any reason, the engine queries open child links with a non-abandon policy and sends the appropriate command (cancel or terminate) to each open child. +- When the parent run closes for any reason, the engine queries open child links with a non-abandon policy and sends the appropriate command (request cancellation, cancel, or terminate) to each open child. - If the child has already closed by the time the policy is enforced, no action is taken — the command is silently skipped. - Policy enforcement is best-effort: if a child command is rejected (e.g. the child is already terminal), the parent's closure is not affected. When enforcement succeeds, a `ParentClosePolicyApplied` history event is recorded on the parent run. When enforcement fails, a `ParentClosePolicyFailed` history event is recorded instead, so operators can distinguish successful enforcement from silent failures. - Continue-as-new does **not** trigger parent-close policy, because the workflow instance remains active under a new run. @@ -207,6 +208,8 @@ Parent-close policy fires on every terminal parent disposition, and stays inert **Abandon** (default) is correct when children represent independent work that should complete regardless of the parent's fate — for example, a notification workflow or a cleanup task that must finish. +**Request Cancellation** uses `ParentClosePolicy::RequestCancellation` for cooperative cleanup. It preserves an existing parent cancellation request's root identity and immutable deadline. If the parent closes without such a request, the policy records one shared cleanup budget of 600 seconds from the parent's recorded closure time. Repeated enforcement does not grant more time. See [Cooperative Cancellation](/docs/polyglot/cancellation) for the service-mode worker capability and cleanup contract. + **Request Cancel** applies the child's terminal `cancel()` command when the parent closes. Despite the policy name, it does not deliver the separate cooperative `requestCancellation()` request. The child cannot run workflow-code cleanup or compensation after this policy closes its run. Complete any required compensation before closing the parent, or use **Abandon** for independent cleanup work. **Terminate** is correct when children must stop immediately. Use this for children that are purely auxiliary to the parent and have no independent value after the parent closes. diff --git a/docs/features/continue-as-new.md b/docs/features/continue-as-new.md index 6ada439fba..79f78baa8f 100644 --- a/docs/features/continue-as-new.md +++ b/docs/features/continue-as-new.md @@ -38,9 +38,9 @@ class CounterWorkflow extends Workflow if ($count >= $max) { return [ - ‘count’ => $result, - ‘workflow_id’ => $this->workflowId(), - ‘run_id’ => $this->runId(), + 'count' => $result, + 'workflow_id' => $this->workflowId(), + 'run_id' => $this->runId(), ]; } diff --git a/docs/features/sticky-execution.md b/docs/features/sticky-execution.md index 9e50f52414..f0f6913d0a 100644 --- a/docs/features/sticky-execution.md +++ b/docs/features/sticky-execution.md @@ -203,9 +203,10 @@ Use these diagnostics this way: ## Replay-Safe Code Workflow code must behave identically under sticky and cold-replay execution. -Only durable history is safe for workflow decisions: workflow inputs, activity -results, timers, signals, updates, side effects, version markers, memo, and -search attributes. +Workflow decisions must use durable history: workflow inputs, activity results, +timers, signals, updates, side effects and version markers. Memo and search +attribute projections are operator metadata; do not branch workflow logic on +their mutable values. Do not rely on mutable globals, local files, open sockets, object identity, random values, wall-clock reads, or any other process-local state for diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md new file mode 100644 index 0000000000..faa7132517 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md @@ -0,0 +1,153 @@ +--- +sidebar_position: 22 +title: Модель виконання activity +description: Як звичайні activity у черзі, локальні activity, сесії worker та sticky execution поєднуються в Durable Workflow v2. +tags: + - activities + - workers + - execution +keywords: + - модель виконання activity + - локальні activity + - сесії worker + - sticky execution + - стійка доставка завдань + - activity у черзі +--- + +# Модель виконання activity {#activity-execution-model} + +Підтримку worker сервісного режиму й узгодження можливостей із відмовою +за відсутності підтримки в PHP, Python та Rust наведено в матриці +[Portable Worker Affinity](/docs/polyglot/portable-worker-affinity). + +Durable Workflow v2 надає явні примітиви для типових способів розміщення activity: + +- звичайні activity у черзі для стійкої роботи з незалежною орендою; +- [локальні activity](/docs/features/local-activities) для короткої роботи + в тому самому процесі зі збереженням історії activity; +- [сесії worker](/docs/features/worker-sessions) для стійкої прив’язки activity + до worker на кількох кроках activity у черзі; +- [sticky execution](/docs/features/sticky-execution) для прив’язки до кешу + replay workflow зі звичайним replay як запасним шляхом коректності. + +## Звичайні activity у черзі {#ordinary-queued-activities} + +`activity(...)` та `Workflow::activity(...)` — це звичайні activity у черзі. +Вони залишаються типовим примітивом activity. + +1. Код workflow викликає `activity(MyActivity::class, ...)`. +2. Завдання workflow записує `ActivityScheduled` і створює стійке завдання + activity на налаштованому підключенні та в налаштованій черзі. +3. Сумісний worker отримує завдання activity за орендою. +4. Worker виконує клас activity й повідомляє про завершення, помилку, + скасування, heartbeat або тайм-аут. +5. Рушій записує результат activity в історію workflow та відновлює workflow + зі стійкого стану. + +Звичайні спроби activity можуть працювати на будь-якому сумісному worker. +Повторна спроба може потрапити до іншого процесу, хоста або збірки. Код activity +має бути ідемпотентним за повторної доставки, повторних спроб після спливу +оренди та гонок пізнього завершення. Використовуйте `activity_execution_id` +як типовий ключ ідемпотентності віддаленої роботи, а `activity_attempt_id` — +лише коли downstream-системі потрібна кореляція окремих спроб. + +```php +use function Workflow\V2\activity; + +$quote = activity(FetchQuoteActivity::class, $customerId); +$invoice = activity(CreateInvoiceActivity::class, $quote['id']); +``` + +## Локальні activity {#local-activities} + +`localActivity(...)`, `Workflow::localActivity(...)` та +`Workflow::executeLocalActivity(...)` виконують клас activity в процесі worker +workflow, який зараз виконує завдання workflow. Вони не створюють звичайного +завдання activity. + +Локальні activity також записують звичайну історію activity: + +- `ActivityScheduled` +- `ActivityStarted` +- `ActivityHeartbeatRecorded` +- `ActivityRetryScheduled` +- `ActivityCompleted` +- `ActivityFailed` +- `ActivityCancelled` +- `ActivityTimedOut` + +Кожна подія локальної activity містить `execution_mode=local` та +`local_activity=true`, а знімок виконання activity зберігає +`activity_options.execution_mode=local`. Heartbeat activity поновлюють оренду +завдання workflow, яке володіє локальною спробою, поки вона працює. + +Використовуйте локальні activity для коротких ідемпотентних побічних ефектів, +яким потрібна семантика повторних спроб, тайм-аутів, heartbeat, скасування +й видимості activity, але не потрібні маршрутизація черг або незалежна група +worker activity. Контракт API, тайм-аутів, повторних спроб, зупинки, холодного +replay, маршрутизації та метрик наведено в розділі +[Локальні activity](/docs/features/local-activities). + +```php +use Workflow\V2\Support\LocalActivityOptions; +use function Workflow\V2\localActivity; + +$receipt = localActivity( + SendReceiptActivity::class, + new LocalActivityOptions(maxAttempts: 3, startToCloseTimeout: 10), + $orderId, +); +``` + +## Сесії worker {#worker-sessions} + +Сесії worker прив’язують послідовність звичайних спроб activity у черзі до +однієї оренди сесії worker. Використовуйте їх, коли кілька стійких кроків мають +повторно використовувати локальні ресурси worker: пам’ять GPU, змонтовану +файлову систему або завантажену модель. + +Сесія worker не робить activity локальною. Кожна activity у сесії залишається +стійким завданням activity із власною орендою, heartbeat, тайм-аутом, +повторними спробами й термінальною історією. Сесія додає явну прив’язку та +правила допуску поверх звичайної маршрутизації черг. + +API сесій, життєвий цикл оренди, маршрутизацію, обробку помилок, поведінку +під час зупинки й діагностику оператора наведено в розділі +[Сесії worker](/docs/features/worker-sessions). + +## Sticky execution {#sticky-execution} + +Sticky execution — це оптимізація replay завдань workflow. Worker може +зберігати прогрітий локальний кеш replay після завершення завдання workflow, +а підбір завдань може надавати йому перевагу для наступного завдання workflow +того самого запуску. + +Sticky execution не робить прогрес workflow локальним для процесу. Коректність +завжди забезпечує звичайний холодний replay зі стійкої історії після промаху +кешу, перезапуску worker, drain, розгортання або витіснення. Код workflow не має +покладатися на стан у пам’яті поза історією. + +Життєвий цикл sticky-кешу, ідентичність маршрутизації, правила запасного +шляху, засоби керування розгортанням і метрики наведено в розділі +[Sticky execution](/docs/features/sticky-execution). + +## Вибір відповідного примітива {#choosing-the-right-primitive} + +Використовуйте код workflow безпосередньо для детермінованого розгалуження й обчислень. + +Використовуйте [`sideEffect(...)`](/docs/features/side-effects) для безпечного +щодо replay знімка недетермінованого значення, якому не потрібна семантика +повторних спроб, тайм-аутів, heartbeat або скасування activity. + +Використовуйте [локальні activity](/docs/features/local-activities) для коротких +побічних ефектів із повторними спробами в тому самому процесі, які мають +відображатися як спроби activity, але не проходити звичайний підбір завдань. + +Використовуйте звичайні [activity](/docs/defining-workflows/activities) для +віддалених викликів, повільного вводу-виводу, інтенсивних обчислень, контролю +навантаження черг, окремих груп worker або роботи, яка має продовжуватися +через окремо орендоване завдання activity. + +Використовуйте [сесії worker](/docs/features/worker-sessions), коли кільком +крокам звичайних activity потрібна явна прив’язка до локального worker. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/cancel-and-terminate.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/cancel-and-terminate.md new file mode 100644 index 0000000000..9a232b303c --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/cancel-and-terminate.md @@ -0,0 +1,220 @@ +--- +sidebar_position: 16 +--- + +# Cancel і Terminate {#cancel-and-terminate} + +Цей посібник описує вбудований Laravel API `Workflow\V2\WorkflowStub`. +Для клієнтів і worker у сервісному режимі використовуйте +[посібник PHP SDK](https://php.durable-workflow.com/), +[довідник скасування Python](https://python.durable-workflow.com/reference/errors/#durable_workflow.errors.ActivityCancelled) або +[API життєвого циклу Rust](https://rust.durable-workflow.com/durable_workflow/struct.Client.html#method.cancel_workflow). + +Cancel і terminate — це повноцінні стійкі команди, які закривають активний workflow. Обидві записуються в історію команд, створюють типізовані події історії та відображаються у Waterline. + +У поточному вбудованому API обидві команди закривають запуск негайно. +**Cancel** записує результат `cancelled`, а **terminate** — `terminated`. +Жодна не планує очищення всередині закритого workflow. Вбудований Laravel +також надає `requestCancellation()` для кооперативного очищення з обмеженим +часом. Server і SDK для PHP, Python та Rust підтримують окремий кооперативний +запит із worker, які явно його вмикають. Контракт можливостей, кінцевих +термінів, очищення й відновлення наведено в розділі +[Кооперативне скасування](/docs/polyglot/cancellation). + +## Cancel {#cancel} + +Cancel негайно переводить запуск у стан `cancelled` і записує стійку історію. + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$result = $workflow->cancel(); + +$result->accepted(); // true +$result->outcome(); // "cancelled" +$result->commandId(); // Durable command id +$result->reason(); // null (no reason provided) +``` + +### Cancel із причиною {#cancel-with-reason} + +Можна надати структуровану причину, щоб розрізняти скасування користувачем, політикою та оператором у журналі аудиту, історії команд і Waterline. + +```php +$result = $workflow->cancel('Customer requested cancellation'); + +$result->reason(); // "Customer requested cancellation" +``` + +Причина зберігається в стійкому записі команди та типізованих подіях історії `CancelRequested` і `WorkflowCancelled`, тому залишається доступною під час replay, експорту та автономного аналізу. + +### Дія cancel {#what-cancel-does} + +Коли команду cancel прийнято, рушій закриває всі відкриті виконання activity й очікувані timer, переводить запуск у стан `cancelled` та відновлює батьківський workflow, який очікує на скасований дочірній, щоб той міг отримати результат. + +### Коли cancel відхиляється {#cancel-is-rejected-when} + +- Екземпляр ще не розпочався: `rejection_reason = instance_not_started` +- Поточний запуск уже закрито: `rejection_reason = run_not_active` +- Cancel для вибраного запуску адресує історичний, а не поточний запуск: `rejection_reason = selected_run_not_current` + +## Terminate {#terminate} + +Terminate негайно закриває активний workflow без планування подальшого коду workflow. Як і cancel, він не надає часового вікна очищення. Використовуйте його, коли потрібен окремий результат `terminated`. + +```php +$result = $workflow->terminate(); + +$result->accepted(); // true +$result->outcome(); // "terminated" +``` + +### Terminate із причиною {#terminate-with-reason} + +```php +$result = $workflow->terminate('Operator emergency shutdown'); + +$result->reason(); // "Operator emergency shutdown" +``` + +### Дія terminate {#what-terminate-does} + +Terminate виконує ті самі транзакційні кроки, що й cancel, але: + +- Натомість записує події історії `TerminateRequested` та `WorkflowTerminated`. +- Створює запис `WorkflowFailure` із `failure_category = terminated` та `propagation_kind = terminated`. +- Задає `closed_reason = terminated` для запуску. +- Не планує подальше виконання коду workflow. + +### Коли terminate відхиляється {#terminate-is-rejected-when} + +Діють ті самі умови відхилення, що й для cancel. + +## Команди для вибраного запуску {#run-targeted-commands} + +Cancel і terminate можуть адресувати конкретний запуск замість поточного запуску екземпляра. + +```php +$selectedRun = WorkflowStub::loadRun($runId); +$result = $selectedRun->attemptCancel('Draining old run'); + +$result->targetScope(); // "run" +``` + +Команди для вибраного запуску відхиляються з `selected_run_not_current`, якщо адресований запуск уже не є поточним запуском екземпляра. Відповідь містить `requested_run_id` (адресований запуск) та `resolved_run_id` (поточний запуск, який слід використовувати далі). + +## API без винятків {#non-throwing-api} + +Використовуйте `attemptCancel()` та `attemptTerminate()` для обробки відхилення без винятків: + +```php +$result = $workflow->attemptCancel('Duplicate order'); + +if ($result->rejected()) { + $result->rejectionReason(); // e.g. "run_not_active" +} +``` + +`cancel()` та `terminate()` спричиняють `LogicException` у разі відхилення. + +## Webhook {#webhooks} + +Cancel і terminate доступні через маршрути webhook: + +```text +POST /webhooks/instances/{workflowId}/cancel +POST /webhooks/instances/{workflowId}/terminate +POST /webhooks/instances/{workflowId}/runs/{runId}/cancel +POST /webhooks/instances/{workflowId}/runs/{runId}/terminate +``` + +Передайте причину в тілі запиту: + +```json +{ + "reason": "Operator: duplicate order" +} +``` + +Відповідь містить результат команди, публічний ID екземпляра та причину: + +```json +{ + "outcome": "cancelled", + "workflow_id": "order-123", + "run_id": "01J10000000000000000000021", + "reason": "Operator: duplicate order", + "command_id": "01J40000000000000000000021", + "command_status": "accepted" +} +``` + +## Скасування не є помилкою для випадкового перехоплення {#cancellation-is-not-an-error-you-catch-by-accident} + +Скасування — це явний результат життєвого циклу, а не неочікувана помилка +застосунку. У вбудованому пакеті +`Workflow\V2\Exceptions\WorkflowCancelledException` успадковує `\Error`, +а не `\Exception`. Блок `catch (\Exception $e)` його не перехопить, +а `catch (\Throwable $t)` — перехопить. + +Під час читання результату скасованого workflow перехоплюйте виняток за +назвою, якщо потрібно відрізнити скасування від інших помилок. Перехоплення +винятку результату не відкриває скасований запуск знову й не планує очищення +всередині нього. + +## Waterline {#waterline} + +Waterline надає cancel і terminate як дії оператора в детальному перегляді вибраного запуску. Дані деталей містять прапорці `can_cancel` та `can_terminate`, визначені стійким станом. + +Перегляд історії команд показує кожну команду cancel або terminate з причиною, ідентичністю ініціатора та результатом. Поле `commands[*].reason` містить причину, надану оператором або кодом виклику, якщо її було задано. + +## Відповідність станів {#status-mapping} + +Запуски `cancelled` та `terminated` потрапляють до категорії стану `failed` для маршрутизації списків. Поля `status` та `closed_reason` розрізняють їх: + +| Стан | Категорія стану | Причина закриття | +| --- | --- | --- | +| `cancelled` | `failed` | `cancelled` | +| `terminated` | `failed` | `terminated` | + +## Типізована історія {#typed-history} + +Скасований запуск створює таку послідовність історії: + +``` +StartAccepted +WorkflowStarted +... (workflow progress) ... +CancelRequested <- reason field present when supplied +TimerCancelled <- for each open timer +ActivityCancelled <- for each open activity +WorkflowCancelled <- failure_id, failure_category, reason when supplied +``` + +Примусово завершений запуск створює: + +``` +StartAccepted +WorkflowStarted +... (workflow progress) ... +TerminateRequested <- reason field present when supplied +TimerCancelled <- for each open timer +ActivityCancelled <- for each open activity +WorkflowTerminated <- failure_id, failure_category, reason when supplied +``` + +## Порівняння cancel і terminate {#cancel-vs-terminate} + +| | Cancel | Terminate | +| --- | --- | --- | +| Планується подальший код workflow | Ні | Ні | +| Відкриті activity скасовуються | Так | Так | +| Відкриті timer скасовуються | Так | Так | +| Метадані причини | Так | Так | +| Стійка історія команд | Так | Так | +| Батьківський workflow отримує дочірній результат | Так | Так | +| Записується помилка | Так (`cancelled`) | Так (`terminated`) | +| Категорія стану | `failed` | `failed` | +| Причина закриття | `cancelled` | `terminated` | diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/child-workflows.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/child-workflows.md new file mode 100644 index 0000000000..d874cae655 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/child-workflows.md @@ -0,0 +1,247 @@ +--- +sidebar_position: 9 +--- + +# Дочірні workflow {#child-workflows} + +Поточний `Workflow\V2` підтримує стійкі дочірні workflow через послідовні виклики `child()` та бар’єри збирання результатів `all([...])`. Вони можуть містити лише дочірні workflow, поєднувати їх з activity або бути вкладеними в більші групи `all([...])`. Це дає батьківському workflow стійкий спосіб запланувати один чи кілька підпроцесів workflow та дочекатися їхніх результатів без необхідності тримати батьківський процес запущеним. + +Дочірній запуск залишається звичайним запуском workflow із власними ID екземпляра workflow та запуску. Дочірнім його роблять стійкий зв’язок із батьківським запуском та один стабільний `child_call_id`, виданий батьківським workflow для цього виклику. + +```php +use function Workflow\V2\child; +use Workflow\V2\Workflow; + +final class ParentWorkflow extends Workflow +{ + public function handle(string $name): array + { + $child = child(ChildWorkflow::class, $name); + + return [ + 'parent_workflow_id' => $this->workflowId(), + 'parent_run_id' => $this->runId(), + 'child' => $child, + ]; + } +} +``` + +## Поточна поведінка {#current-behavior} + +- Імпортуйте функцію через `use function Workflow\V2\child;` і викличте `child(ChildWorkflow::class, ...)`. Виклик призупиняє батьківський workflow, створює стійкий дочірній запуск і повертає його результат після закриття. +- Помилка дочірнього workflow спричиняє виняток у батьківському, а успішний дочірній workflow повертає результат. Скасування й примусове завершення представлені різними типами винятків. +- `$this->child()` повертає найновіший дескриптор дочірнього workflow, а `$this->children()` — усі дескриптори в порядку кроків workflow. Дескриптори надають `id()`, `runId()`, `callId()` та допоміжні методи signal, зокрема `signal()` і `signalWithArguments()`. +- Якщо дочірній workflow використовує `continueAsNew()`, батьківський автоматично слідує за найновішим запуском: `runId()` змінюється, а `id()` залишається сталим. +- `all()` може поєднувати замикання `fn () => child(...)` та `fn () => activity(...)` в одному бар’єрі. Батьківський workflow пробуджується за першої помилки дочірнього workflow, але перед продовженням успішного виконання очікує на закриття всіх успішних гілок. + +## Бар’єр паралельних дочірніх workflow {#parallel-child-barrier} + +Імпортуйте функції через `use function Workflow\V2\all;` та `use function Workflow\V2\child;`, щоб один батьківський крок очікував одразу на кілька дочірніх workflow. + +```php +use function Workflow\V2\all; +use function Workflow\V2\child; +use Workflow\V2\Workflow; + +final class ParentWorkflow extends Workflow +{ + public function handle(): array + { + $children = all([ + fn () => child(FirstChildWorkflow::class), + fn () => child(SecondChildWorkflow::class), + ]); + + return $children; + } +} +``` + +Така форма лише з дочірніми workflow корисна, коли всі паралельні учасники є дочірніми workflow: + +- очікує на всю дочірню групу як на один батьківський крок +- зберігає початковий порядок масиву в повернених результатах +- передає вибраний неуспішний результат дочірнього workflow до тіла батьківського, обираючи найраніший час закриття, а за однакового часу — найменший індекс + +Щоб поєднати дочірні workflow й activity в одному кроці збирання результатів, використовуйте змішаний бар’єр `all([...])`. Очікування дочірніх workflow та activity мають спільний `parallel_group_id`, а Waterline позначає цей бар’єр як `parallel_group_kind = mixed`. Якщо змішана або суто дочірня підгрупа вкладена в більшу `all([...])`, `parallel_group_id` визначає найвнутрішнішу групу, а `parallel_group_path` зберігає повний шлях від зовнішньої до внутрішньої. + +## Контракт ідентичності {#identity-contract} + +- `child()->id()` та `target_name` у Waterline позначають ID екземпляра дочірнього workflow, який залишається сталим у всьому ланцюжку його запусків. +- `child_call_id` позначає сам виклик, виданий батьківським workflow, і залишається сталим, навіть якщо дочірній workflow згодом використовує `continueAsNew()`. +- `resume_source_id`, `child_workflow_run_id` та маршрути вибраного запуску позначають один конкретний дочірній запуск у ланцюжку цього виклику. + +## Дескриптори дочірніх workflow {#child-handles} + +Коли батьківський workflow стійко досягнув дочірнього кроку, він може перевірити цей виклик через `ChildWorkflowHandle`. + +```php +use Workflow\UpdateMethod; +use function Workflow\V2\child; +use Workflow\V2\Workflow; + +final class ParentWorkflow extends Workflow +{ + public function handle() + { + return child(ApprovalWorkflow::class); + } + + #[UpdateMethod('approve-child')] + public function approveChild(string $approvedBy): void + { + $this->child()?->signal('approved-by', $approvedBy); + } +} +``` + +- `$this->child()` повертає найновіший видимий дескриптор дочірнього workflow або `null`. +- `$this->children()` повертає всі видимі дескриптори в порядку кроків workflow. +- У бар’єрах `all([fn () => child(...)])` дескриптори з’являються в тому самому порядку кроків, що й дочірні виклики всередині бар’єра, а `$this->child()` повертає останній видимий дескриптор. +- `runId()` дескриптора слідує за найновішою `ChildRunStarted`, записаною батьківським workflow у ланцюжку дочірнього виклику. Тому батьківський workflow, який очікує на дочірній після `continueAsNew()`, бачить найновіший ID дочірнього запуску, не втрачаючи початкового `callId()`. + +## Доступність дескрипторів {#handle-availability} + +Дескриптори дочірніх workflow спираються на історію, а не на припущення: + +- Поки батьківський workflow стійко не досягнув дочірнього кроку, `$this->child()` повертає `null`, а `$this->children()` — порожній список. +- Після запису батьківським workflow історії планування або початку дочірнього workflow для цього кроку дескриптор стає видимим для query, update та подальшого replay workflow. +- Replay query ніколи не відправляє signal дочірнім workflow із записаних update. Він лише відновлює стан у пам’яті, визначений зафіксованою історією. + +## Видимість у Waterline {#waterline-visibility} + +Коли батьківський workflow очікує на дочірній, Waterline показує це як дочірнє очікування, а не окремий тип завдання. Це означає: + +- стисле представлення батьківського запуску містить `wait_kind = child` та `liveness_state = waiting_for_child` +- `waits` містить `kind = child`, стабільний `child_call_id`, ID екземпляра дочірнього workflow у `target_name` та ID дочірнього запуску в `resume_source_id` +- якщо одночасно відкрито кілька дочірніх очікувань, деталі також містять `open_wait_count` +- дочірні очікування одного суто дочірнього або змішаного бар’єра `all([...])` мають спільний `parallel_group_id` та містять `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size` і `parallel_group_index` +- вкладені дочірні очікування також містять `parallel_group_path`, упорядкований від найзовнішнішого охоплюючого бар’єра до найвнутрішнішого +- `timeline` містить типізовані дочірні події, зокрема `ChildWorkflowScheduled`, `ChildRunStarted`, `ChildRunCompleted` та `ChildRunFailed`, із тим самим стабільним `child_call_id` +- відкриті дочірні очікування й походження запусків насамперед використовують типізовану історію батьківського workflow `ChildWorkflowScheduled` / `ChildRunStarted`, зберігають один логічний дочірній запис на стабільний `child_call_id` і слідують за найновішим дочірнім запуском, записаним батьківським workflow. Це зберігається, навіть якщо скопійовані записи `workflow_links` зникають або змінюваний `current_run_id` дочірнього екземпляра відхиляється після фіксації історії +- після запису типізованої події результату дочірнього workflow деталі вибраного запуску визначають завершене дочірнє очікування з батьківської історії, навіть якщо змінюваний запис дочірнього запуску згодом відхиляється. Термінальна історія дочірнього workflow або старі дані зв’язків можуть доповнювати походження й діагностику, але не замінюють відсутню типізовану історію дочірнього кроку в батьківському workflow для replay +- за наявності типізованої події результату дочірнього workflow походження вибраного запуску й експорт історії також беруть тип, клас, номер запуску, категорію стану та причину закриття дочірнього workflow з батьківської історії, не перечитуючи змінюваний запис дочірнього запуску для цих полів +- якщо є термінальний дочірній запис або зв’язок, але батьківський workflow не має типізованої історії `ChildWorkflowScheduled`, `ChildRunStarted`, `ChildRunCompleted`, `ChildRunFailed`, `ChildRunCancelled` чи `ChildRunTerminated` для цього кроку, replay worker і query блокується з `history_shape_mismatch` та записаними подіями `no typed history`. Waterline позначає вибране дочірнє очікування як `status = unsupported`, показує `history_authority = unsupported_terminal_without_history` та `history_unsupported_reason = terminal_child_link_without_typed_parent_history`, а за наявності даних визначає ідентичність дочірнього workflow із заблокованого завдання або запасних даних походження +- якщо після події результату дочірнього workflow втрачено запис завдання відновлення батьківського workflow, деталі вибраного запуску залишаються в стані `repair_needed` і показують синтетичне відсутнє завдання workflow з `workflow_wait_kind = child`, `child_call_id` та `child_workflow_run_id`. Ручний `repair()`, `workflow:v2:repair-pass` або відновлення в циклі worker створюють те саме завдання результату дочірнього workflow з типізованої батьківської історії +- масиви походження показують стійкі зв’язки батьківських і дочірніх workflow поруч зі зв’язками continue-as-new, а записи дочірніх workflow містять той самий `child_call_id` + +## Політика закриття батьківського workflow {#parent-close-policy} + +Коли батьківський workflow закривається через завершення, помилку, тайм-аут, скасування або примусове завершення, кожен відкритий дочірній workflow підпорядковується своїй **політиці закриття батьківського workflow**. Політика задається для кожного дочірнього виклику й визначає, що станеться з ним після завершення батьківського запуску. + +| Політика | Значення | Поведінка | +|---|---|---| +| Abandon | `abandon` | Дочірній workflow продовжує працювати незалежно. Це типова політика. | +| Request Cancellation | `request_cancellation` | Дочірній workflow отримує кооперативний запит очищення з початковим походженням скасування та кінцевим терміном батьківського workflow. | +| Request Cancel | `request_cancel` | Під час закриття батьківського workflow дочірньому надсилається команда cancel. | +| Terminate | `terminate` | Під час закриття батьківського workflow дочірньому надсилається команда terminate. | + +### Типова політика {#default-policy} + +Виклик `child()` без `ChildWorkflowOptions` завжди використовує `ParentClosePolicy::Abandon`. Сумісні на рівні вихідного коду функції `Workflow\V2\child()`, `Workflow\V2\Workflow::child()` та `Workflow\V2\Workflow::executeChildWorkflow()` створюють типові параметри, якщо перший аргумент не є `ChildWorkflowOptions`, і задають `parentClosePolicy` як `ParentClosePolicy::Abandon`. Те саме діє для кожного замикання `child()` у бар’єрі `all([...])`, де пропущено аргумент параметрів. Щоб змінити типову політику, передайте явний `ChildWorkflowOptions`, як показано нижче. + +### Задання політики {#setting-the-policy} + +Передайте `ChildWorkflowOptions` першим аргументом `child()`: + +```php +use function Workflow\V2\child; +use Workflow\V2\Enums\ParentClosePolicy; +use Workflow\V2\Support\ChildWorkflowOptions; +use Workflow\V2\Workflow; + +final class ParentWorkflow extends Workflow +{ + public function handle(): array + { + $options = new ChildWorkflowOptions( + parentClosePolicy: ParentClosePolicy::RequestCancel, + ); + + return child(ChildWorkflow::class, $options, 'argument1'); + } +} +``` + +Той самий підхід працює із замиканнями всередині `all()` для паралельних бар’єрів: + +```php +use function Workflow\V2\all; +use function Workflow\V2\child; +use Workflow\V2\Enums\ParentClosePolicy; +use Workflow\V2\Support\ChildWorkflowOptions; + +$cancelOptions = new ChildWorkflowOptions( + parentClosePolicy: ParentClosePolicy::RequestCancel, +); + +$results = all([ + fn () => child(FirstChild::class, $cancelOptions), + fn () => child(SecondChild::class, $cancelOptions, 'arg'), +]); +``` + +### Як це працює {#how-it-works} + +- Політика записується в запис `workflow_links` (`parent_close_policy`) та дані події історії `ChildWorkflowScheduled`. +- Коли батьківський запуск закривається з будь-якої причини, рушій знаходить відкриті дочірні зв’язки з політикою, відмінною від abandon, і надсилає відповідну команду (request cancellation, cancel або terminate) кожному відкритому дочірньому workflow. +- Якщо на момент застосування політики дочірній workflow уже закрито, команда пропускається без додаткової дії. +- Політика застосовується за принципом best-effort: відхилення дочірньої команди, наприклад через уже термінальний стан дочірнього workflow, не впливає на закриття батьківського. Успішне застосування записує подію історії `ParentClosePolicyApplied` у батьківський запуск. У разі помилки натомість записується `ParentClosePolicyFailed`, щоб оператори могли відрізнити успішне застосування від непомітного збою. +- Continue-as-new **не** запускає політику закриття батьківського workflow, оскільки його екземпляр залишається активним у новому запуску. + +### Матриця результатів батьківського workflow {#parent-disposition-matrix} + +Політика закриття батьківського workflow діє для кожного термінального результату батьківського запуску й не діє для активних запусків або переходів до продовженого запуску. Поведінка рушія однакова для всіх результатів, що роблять батьківський workflow термінальним: політика застосовується до кожного ще відкритого дочірнього зв’язку з політикою, відмінною від abandon. + +| Результат батьківського workflow | Політика застосовується? | Примітки | +|---|---|---| +| Completed | Так | Після запису `WorkflowCompleted`. Послідовний виклик `child()` завжди очікує на дочірній workflow, тому природне завершення зазвичай не має відкритих дочірніх workflow. Перевірка політики все одно виконується для надійності. | +| Failed | Так | Після запису `WorkflowFailed` для будь-якої термінальної помилки завдання workflow. | +| Timed out | Так | Після запису `WorkflowTimedOut` через тайм-аут запуску або виконання. | +| Cancelled | Так | Після того, як прийнята команда `CancelRequested` закриває батьківський запуск. | +| Terminated | Так | Після того, як прийнята команда `TerminateRequested` закриває батьківський запуск. | +| Continue-as-new | Ні | Екземпляр workflow залишається активним у новому запуску, тому наявні дочірні зв’язки спрямовуються на продовжений запуск зі збереженням початкової політики. | +| Reset | Наразі не застосовується | Runtime v2 не надає окремої команди reset або термінального результату reset. Майбутня операція reset чи repair, яка закриває або замінює батьківський запуск, має визначити, чи передається володіння дочірніми workflow. Якщо старий запуск стає термінальним, він має застосувати політику закриття перед припиненням володіння відкритими дочірніми workflow. | + +### Коли використовувати кожну політику {#when-to-use-each-policy} + +**Abandon** (типова політика) підходить, коли дочірні workflow представляють незалежну роботу, яка має завершитися незалежно від долі батьківського workflow, наприклад workflow сповіщень або обов’язкове завдання очищення. + +**Request Cancellation** використовує `ParentClosePolicy::RequestCancellation` для кооперативного очищення. Вона зберігає кореневу ідентичність та незмінний кінцевий термін наявного запиту скасування батьківського workflow. Якщо батьківський workflow закривається без такого запиту, політика записує один спільний бюджет очищення у 600 секунд від записаного часу його закриття. Повторне застосування не додає часу. Контракт очищення й вимоги до можливостей worker у сервісному режимі наведено в розділі [Кооперативне скасування](/docs/polyglot/cancellation). + +**Request Cancel** застосовує термінальну команду `cancel()` дочірнього workflow під час закриття батьківського. Попри назву політики, вона не доставляє окремий кооперативний запит `requestCancellation()`. Після закриття запуску цією політикою дочірній workflow не може виконати очищення або компенсацію в коді workflow. Завершіть потрібну компенсацію перед закриттям батьківського workflow або використовуйте **Abandon** для незалежного очищення. + +**Terminate** підходить, коли дочірні workflow мають зупинитися негайно. Використовуйте її для суто допоміжних дочірніх workflow, які не мають самостійної цінності після закриття батьківського. + +### Видимість у Waterline {#waterline-visibility-1} + +Waterline показує `parent_close_policy` у таких місцях: + +- запис дочірнього зв’язку в перегляді походження батьківського запуску +- дані запису хронології `ChildWorkflowScheduled` +- подія історії дочірнього workflow `CancelRequested` або `TerminateRequested` під час застосування політики з причиною, яка вказує на закриття батьківського + +## Поточні обмеження {#current-limitations} + +Поточний API не містить: + +- вбудованих засобів обмеження конкурентності, окрім поточних бар’єрів `all([fn () => child(...)])`, `all([fn () => activity(...)])` та змішаних `all([fn () => child(...), fn () => activity(...)])` + +Отже, стійкі дескриптори дочірніх workflow підтримуються для вже досягнутих дочірніх кроків. API вищого рівня для запуску з дескриптором або обмеженої конкурентності понад поточні `child(...)` та `all([...])` ще немає. + +## Запуск цього прикладу {#run-this-pattern} + +Workflow координації мікросервісів у +[Sample App](/docs/sample-app) — це виконуваний приклад оркестрації +батьківського й дочірніх workflow між застосунками: + +```bash +php artisan app:microservice +``` + +`App\Workflows\Microservice\MicroserviceWorkflow` виконує батьківський +workflow у головному Laravel-застосунку й відправляє дочірню роботу +до включеного worker мікросервісу. Відкрийте Waterline під час виконання, +щоб побачити дочірні зв’язки під батьківським запуском і політику закриття +в перегляді походження, як описано на цій сторінці. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/concurrency.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/concurrency.md new file mode 100644 index 0000000000..3b1ecaf0d5 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/concurrency.md @@ -0,0 +1,283 @@ +--- +sidebar_position: 10 +--- + +import ConcurrencySimulator from '@site/src/components/ConcurrencySimulator'; + +# Конкурентне виконання {#concurrency} + +Цей посібник описує конкурентне виконання у вбудованих Laravel workflow з +`Workflow\V2\Workflow`. Для worker у сервісному режимі використовуйте +[посібник PHP SDK](https://php.durable-workflow.com/build/workflows-activities/), +[посібник Python SDK](https://python.durable-workflow.com/sdk-reference/#deterministic-parallel-groups) або +[довідник Rust SDK](https://rust.durable-workflow.com/durable_workflow/struct.WorkflowContext.html#method.parallel). + +`all([...])` описує всю стійку групу перед призупиненням. Вбудований runtime +планує її команди activity й дочірніх workflow та повертає результати +у початковій вкладеній структурі вхідних даних. + +Використовуйте групу вибору, коли подальше виконання залежить від першого +завершеного учасника, а не від усього бар’єра. Вибір також стійкий: він запускає +кожного учасника, записує одного переможця й залишає решту працювати, доки +код workflow не дочекається їх або явно не скасує. + +## Послідовне виконання {#series} + +Цей приклад виконує 3 activity послідовно, очікуючи на завершення кожної перед переходом до наступної. + +```php +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle() + { + return [ + activity(MyActivity1::class), + activity(MyActivity2::class), + activity(MyActivity3::class), + ]; + } +} +``` + + + +## Паралельне виконання {#parallel} + +Цей приклад виконує 3 activity паралельно, очікує на завершення всіх і збирає результати. + +```php +use function Workflow\V2\{all, activity}; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle() + { + return all([ + fn () => activity(MyActivity1::class), + fn () => activity(MyActivity2::class), + fn () => activity(MyActivity3::class), + ]); + } +} +``` + + + +Основна відмінність між послідовним і паралельним прикладами — момент призупинення. У послідовному прикладі кожен виклик `activity()` безпосередньо призупиняє й відновлює workflow. У паралельному прикладі замикання спочатку описують увесь бар’єр, а `all()` призупиняє workflow один раз для всієї групи. Тому всі учасники можуть працювати паралельно до відновлення workflow. + +## Вибір першого завершення {#first-completion-selection} + +`select([...])` запускає незалежні activity, дочірні workflow, timer, очікування +signal, очікування умов або вкладені звичайні бар’єри й відновлює виконання, +коли один учасник фіксує придатний результат або типізовану помилку. Надавайте +учасникам стабільні ключі застосунку, якщо подальший код має розрізняти їх +або повторно звертатися до них. Ключ учасника має бути непорожнім рядком +або невід’ємним цілим числом. + +Наведений координатор починає відлік кінцевого терміну на тому самому +стійкому кроці, що й resolver. Обробка вхідних даних і прогрес resolver +не можуть скинути або відкласти цей термін: + +```php +use function Workflow\V2\{activity, await, select, timer}; +use Workflow\V2\Workflow; + +final class ResolveInRealTime extends Workflow +{ + public function handle(string $requestId): array + { + $selected = select([ + 'resolved' => fn () => activity(ResolveRequest::class, $requestId), + 'manual' => fn () => await('resolution.received'), + 'deadline' => fn () => timer('2 seconds'), + ]); + + if ($selected->key === 'deadline') { + $selected->handles['resolved']->cancel(); + + return ['status' => 'timed_out']; + } + + // The deadline is not cancelled just because another member won. + $selected->handles['deadline']->cancel(); + + return [ + 'status' => 'resolved', + 'source' => $selected->key, + 'value' => $selected->result(), + ]; + } +} +``` + +Результат містить стабільні ключ та індекс учасника, тип операції й стійку +ідентичність, результат або типізовану помилку, дескриптор переможця та +дескриптор кожного учасника. На іншого учасника можна пізніше очікувати +через `await()` або скасувати його через `cancel()`. Вибір ніколи не відкидає +й не скасовує інших учасників неявно. `cancel()` — це запит із результатом +void/unit, який не повідомляє, чи перемогло скасування. Джерелом істини щодо +результату є історія `SelectionOperationCancelled`. Якщо завершення зафіксовано +першим, runtime не записує позначку скасування. Replay проходить виклик cancel +лише після того, як зафіксована межа завдання workflow, наступна команда або +термінальна подія workflow доведе фіксацію цієї операції без ефекту. Replay +query зупиняється на незавершеному cancel, не показуючи спекулятивний стан +після нього. Очікування дескриптора після зафіксованої операції без ефекту +повертає попередній результат завершення. + +Runtime записує перший придатний результат, утримуючи блокування фіксації +батьківського запуску. Тому холодний перезапуск, повторне завантаження +збереженої історії, replay query і подальші завершення інших учасників +використовують записаного переможця, а не змагаються локальними futures мови +чи переосмислюють порядок історії. Точна повторна доставка ідемпотентна. +Конкурентні вхідні дані впорядковуються за їхньою зафіксованою стійкою історією. +Дані, що надійшли під час виконання activity, стають видимими на наступному +завданні workflow. Пізні або повторні дані не можуть замінити вже записаного +переможця. + +## Вкладені бар’єри {#nested-barriers} + +Вкладені групи `all([...])` дають одному кроку workflow змогу описати дерево стійкого розгалуження роботи й збирання результатів. Runtime планує кожну activity або дочірній workflow як стійку листову послідовність, записує повний `parallel_group_path` листка й очікує, доки кожен охоплюючий бар’єр зможе просунутися. Потім він відновлює початкову вкладену структуру результатів перед продовженням тіла workflow. Під час replay листок activity або дочірнього workflow з кроку `all([...])` має відповідати записаному шляху групи. Типізована історія листка без метаданих групи вважається несумісною історією старої попередньої версії, а не підставляється в поточний бар’єр за припущенням. + +```php +use function Workflow\V2\{all, activity}; +use Workflow\V2\Workflow; + +final class NestedWorkflow extends Workflow +{ + public function handle(): array + { + return all([ + fn () => activity(BuildSummary::class), + fn () => all([ + fn () => activity(BuildInvoice::class), + fn () => activity(BuildShipment::class), + ]), + ]); + } +} +``` + +У цьому прикладі Waterline показує три відкриті очікування листків, а не одне штучне «вкладене» очікування. Перший листок належить лише зовнішньому бар’єру. Другий і третій листки містять `parallel_group_path` із двома записами, тому оператори бачать як зовнішню групу, так і внутрішню підгрупу, яка досі відкрита. + +Розмір зовнішньої групи рахує стійкі листки, а не вкладені масиви. Кожен +вкладений листок має `parallel_group_path` від зовнішньої групи до внутрішньої. +Кожен запис шляху зберігає ту саму стійку позицію workflow. Група планує всі +листки до призупинення, а потім збирає успішні значення за позиціями вхідних +даних. Перезапуск worker і replay завершеної історії відновлюють ту саму +ідентичність групи. Точна повторна термінальна доставка ігнорується. Пізнє +завершення іншого учасника може доповнити часткову діагностику, але не змінює +учасника з помилкою, уже вибраного детермінованою політикою вбудованого runtime. + +Вбудований runtime передає типізовану помилку листка з `all()` і зберігає +стійкі метадані групи бар’єра в історії та представленнях для оператора. + +## Асинхронний callback {#async-callback} + +`async(...)` виконує серіалізований callback як стійкий дочірній workflow із системним типом `durable-workflow.async`. Асинхронні callback використовують той самий контракт допоміжних функцій лише для послідовного коду, що й іменовані workflow v2. Тому `activity()`, `await()`, `timer()`, `sideEffect()` та інші однокрокові функції призупиняють виконання безпосередньо в тілі callback без обов’язкового `yield`. + +```php +use function Workflow\V2\{activity, async}; +use Workflow\V2\Workflow; + +final class CustomerWorkflow extends Workflow +{ + public function handle(string $customerId): array + { + $profile = async(static function () use ($customerId): array { + $customer = activity(LoadCustomer::class, $customerId); + + return [ + 'customer' => $customer, + 'score' => activity(ScoreCustomer::class, $customer['id']), + ]; + }); + + return ['profile' => $profile]; + } +} +``` + +Батьківський запуск бачить callback як очікування дочірнього workflow. Тому історія команд, походження запусків і деталі у Waterline використовують ті самі `child_call_id`, ID дочірнього запуску та історію його результату, що й явний виклик `child(...)`. Callback серіалізується за допомогою підтримки серіалізованих замикань Laravel, тому обмежуйте його одним застосунком і розгортанням. Використовуйте іменований виклик `child(SomeWorkflow::class, ...)`, якщо потрібен стабільний публічний тип workflow для маршрутизації між сервісами або тривалої еволюції коду. У v2 callback `async(...)` підтримують лише послідовний код, тому викликайте `activity()`, `child()`, `await()`, `timer()` та `all([...])` безпосередньо без `yield`. + +## Змішані бар’єри activity та дочірніх workflow {#mixed-activity--child-barriers} + +Та сама функція `all()` також збирає результати змішаної групи activity й дочірніх workflow. Результати повертаються в початковому порядку масиву, успішні учасники очікують на решту групи, а перший учасник із помилкою одразу пробуджує батьківський workflow. + +Якщо на момент replay батьківського workflow кілька учасників бар’єра вже завершилися з помилками, батьківський workflow отримує помилку з найранішим записаним часом закриття. За однакового часу двох помилок перемагає менший індекс листка бар’єра. Тому відновлення workflow і replay query вибирають той самий виняток. Подальші помилки інших учасників не замінюють виняток, уже переданий батьківському кроку. + +```php +use function Workflow\V2\{all, activity, child}; +use Workflow\V2\Workflow; + +final class OrderWorkflow extends Workflow +{ + public function handle(): array + { + [$charge, $shipment] = all([ + fn () => activity(ChargeCustomer::class), + fn () => child(ShipOrderWorkflow::class), + ]); + + return compact('charge', 'shipment'); + } +} +``` + +## Поточні обмеження {#current-limits} + +Поточний API конкурентного виконання ще не містить: + +- вбудованих засобів обмеження конкурентності, окрім явних вкладених груп `all([...])` + +## Паралельні дочірні workflow {#child-workflows-in-parallel} + +Дочірні workflow також можуть виконуватися у власному бар’єрі `all([...])`. Він працює так само, як паралельне виконання activity, але для дочірніх workflow: батьківський workflow стійко запускає кілька дочірніх запусків і відновлюється лише тоді, коли весь дочірній бар’єр може просунутися. + +```php +use function Workflow\V2\{all, child}; +use Workflow\V2\Workflow; + +final class ParentWorkflow extends Workflow +{ + public function handle(): array + { + return all([ + fn () => child(MyChild1::class), + fn () => child(MyChild2::class), + fn () => child(MyChild3::class), + ]); + } +} +``` + + + +Так можна легко побудувати ієрархічний паралелізм у workflow, зокрема вкладені групи лише дочірніх workflow або змішані групи дочірніх workflow й activity, коли одному батьківському кроку потрібні кілька рівнів збирання результатів. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/continue-as-new.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/continue-as-new.md new file mode 100644 index 0000000000..103e17e077 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/continue-as-new.md @@ -0,0 +1,156 @@ +--- +sidebar_position: 14 +tags: + - continue-as-new + - history + - message-streams + - workflows +keywords: + - continue as new + - зростання історії workflow + - передача виконання новому запуску + - перенесення потоку повідомлень +--- + +# Continue As New {#continue-as-new} + +Шаблон **Continue As New** дає активному workflow змогу перезапустити себе з новими аргументами. +Він корисний, коли потрібно: + +* Запобігти необмеженому зростанню історії workflow. +* Моделювати цикли або рекурсивні workflow. +* Розділити тривалі workflow на менші керовані виконання зі збереженням безперервності. + +## Використання `continueAsNew` {#using-continueasnew} + +Щоб почати workflow як новий запуск, викличте допоміжну функцію `continueAsNew(...)` усередині методу `handle()` workflow. `continueAsNew()` є повноцінним переходом між запусками. + +```php +use function Workflow\V2\activity; +use function Workflow\V2\continueAsNew; +use Workflow\V2\Workflow; + +class CounterWorkflow extends Workflow +{ + public function handle(int $count = 0, int $max = 3) + { + $result = activity(CountActivity::class, $count); + + if ($count >= $max) { + return [ + 'count' => $result, + 'workflow_id' => $this->workflowId(), + 'run_id' => $this->runId(), + ]; + } + + return continueAsNew($count + 1, $max); + } +} +``` + +У цьому прикладі: + +* Workflow виконує activity на кожній ітерації. +* Якщо максимального лічильника ще не досягнуто, він продовжується новим запуском зі збільшеними аргументами. +* Остаточний результат повертається лише після завершення циклу. + +Під час continue-as-new: + +- ID екземпляра залишається сталим у всьому ланцюжку, а `WorkflowStub::runId()` переходить до найновішого запуску. +- Продовжений запуск закривається як `completed` / `continued`, а наступний одразу починається з новим ID запуску. +- Signal, query, update та команди оператора завжди знаходять найновіший стійкий запуск цього екземпляра. Код, який їх викликає, не має відстежувати ID запусків на межах continue-as-new. + +Тривалі цикли можуть звільнятися від накопиченої історії без створення нового публічного ID workflow за кожного переходу запуску. + +## Бюджет історії {#history-budget} + +Код workflow може перевіряти поточну довжину історії та прапорець рекомендації continue-as-new через явний контекст runtime. Це дає тривалим циклам змогу свідомо передавати виконання до досягнення граничного бюджету історії. + +```php +use function Workflow\V2\activity; +use function Workflow\V2\continueAsNew; +use Workflow\V2\Workflow; + +class PollingWorkflow extends Workflow +{ + public function handle(int $iteration = 0) + { + $result = activity(PollActivity::class); + + if ($this->shouldContinueAsNew()) { + return continueAsNew($iteration + 1); + } + + return ['result' => $result, 'iteration' => $iteration]; + } +} +``` + +### Доступні методи {#available-methods} + +| Метод | Тип результату | Опис | +| --- | --- | --- | +| `$this->historyLength()` | `int` | Кількість подій історії поточного запуску | +| `$this->historySize()` | `int` | Загальний розмір подій історії в байтах | +| `$this->shouldContinueAsNew()` | `bool` | `true`, коли запуск перевищує налаштований бюджет кількості подій або розміру в байтах | + +### Налаштування {#configuration} + +Пороги рекомендації continue-as-new можна налаштувати: + +```php +// config/workflows.php +'v2' => [ + 'history_budget' => [ + 'continue_as_new_event_threshold' => 10000, + 'continue_as_new_size_bytes_threshold' => 5242880, // 5 MB + ], +], +``` + +`shouldContinueAsNew()` повертає `true` після перетину будь-якого порога. Стислі представлення запусків також відстежують `history_event_count`, `history_size_bytes` та `continue_as_new_recommended`, щоб Waterline міг позначати тривалі запуски в загальних представленнях. + +## Перенесення метаданих {#metadata-carry-forward} + +Під час continue-as-new новий запуск автоматично успадковує метадані попереднього: + +| Метадані | Правила перенесення | +| --- | --- | +| Атрибути пошуку | Переноситься повна об’єднана мапа, включно з оновленнями під час запуску | +| Memo | Переноситься повна об’єднана мапа, включно з оновленнями під час запуску | +| Позначки видимості | Переносяться без змін, задаються один раз на початку | +| Бізнес-ключ | Переноситься без змін | +| Маркер сумісності | Переноситься, щоб новий запуск призначався тій самій збірці worker | +| Позиція курсора повідомлень | Переноситься, щоб новий запуск знав, які вхідні повідомлення вже спожито через [Потоки повідомлень](./message-streams.md) | + +Новий запуск починається з успадкованих метаданих і може продовжувати оновлення атрибутів пошуку й memo. Тому тривалий workflow опитування може накопичувати метадані між поколіннями, не втрачаючи стан під час звільнення від історії. + +```php +use function Workflow\V2\{activity, continueAsNew, upsertMemo, upsertSearchAttributes}; +use Workflow\V2\Workflow; + +class PollingWorkflow extends Workflow +{ + public function handle(int $iteration = 0) + { + $result = activity(PollActivity::class); + + upsertSearchAttributes(['iteration' => (string) $iteration, 'status' => 'polling']); + upsertMemo(['last_result' => $result, 'iteration' => $iteration]); + + if ($this->shouldContinueAsNew()) { + // The new run inherits all search attributes and memo + return continueAsNew($iteration + 1); + } + + return ['result' => $result, 'iteration' => $iteration]; + } +} +``` + +Для тривалих циклів введення користувачем або роботи асистента обирайте +повноцінний фасад `$this->inbox()` / `$this->outbox()` +[Потоків повідомлень](./message-streams.md). Очікувані повідомлення потоку +й позиція курсора переходять до продовженого запуску, а спожиті повідомлення +залишаються в початковому запуску як свідчення історії. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/external-payload-storage.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/external-payload-storage.md new file mode 100644 index 0000000000..bf5219bf44 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/external-payload-storage.md @@ -0,0 +1,246 @@ +--- +sidebar_position: 21 +tags: + - payloads + - storage + - history +keywords: + - зовнішнє сховище даних + - великі дані + - винесення даних у зовнішнє сховище + - S3 storage + - GCS storage + - Azure Blob storage +--- + +# Зовнішнє сховище даних {#external-payload-storage} + +Зовнішнє сховище даних переносить великі дані workflow до підключуваного +об’єктного сховища (S3, GCS, Azure Blob або локальної файлової системи) +й замінює вбудовані байти в історії workflow невеликим перевірюваним конвертом +посилання. Використовуйте його, коли аргументи activity чи дочірніх workflow, +результати, signal або дані update завеликі для безпосереднього зберігання +в записі бази даних, що містить історію workflow. + +Runtime зберігає дані вбудовано, доки їхній закодований розмір менший за поріг +namespace. Лише дані, що досягли порога, записуються через налаштований драйвер +і потрапляють до історії як конверт +`durable-workflow.v2.external-payload-reference.v1`. Replay та експорт історії +відмовляють, якщо об’єкт за посиланням відсутній, змінений або перебуває поза +налаштованим префіксом. Система ніколи непомітно не підставляє порожнє +значення замість відсутнього об’єкта. + +## Коли використовувати {#when-to-use-it} + +Обирайте зовнішнє сховище даних, коли застосунок справді має передавати +через workflow дані розміром понад кілька сотень кілобайт: + +- Обробка документів і медіа, яка передає PDF, зображення або аудіооб’єкти + від однієї activity до наступної. +- Звіти, експорти або архіви, кінцевий результат яких є великим + серіалізованим файлом. +- Дані потоків повідомлень із зовнішніх систем, які не надають стабільний + URL об’єкта для прямого посилання з workflow. +- Будь-які дані, які інакше перевищили б + [структурне обмеження `payload_size_bytes`](../constraints/structural-limits.md#payload-size). + +Малі дані, як-от поля площини керування, ID, прапорці стану й типовий JSON, +залишаються вбудованими без додаткових витрат. Політика діє лише після +досягнення порога, тому ввімкнення зовнішнього сховища для namespace +не переносить малі дані. + +## Як працює винесення даних {#how-offload-works} + +Кожен namespace має незалежну політику зовнішнього сховища даних. Коли +runtime кодує дані для стійкого зберігання, він порівнює кількість закодованих +байтів із налаштованим `threshold_bytes`: + +- Закодований розмір менший за поріг. Дані зберігаються вбудовано, як + і раніше. Історія не змінюється. +- Закодований розмір дорівнює порогу або перевищує його. Runtime передає + закодовані байти налаштованому драйверу, отримує URI, яким керує драйвер, + і записує посилання на зовнішні дані в історію. Посилання містить URI, + хеш SHA-256, точну довжину в байтах, кодек даних і необов’язкову підказку + `expires_at`. + +Під час replay worker отримують байти за посиланням через той самий драйвер, +перевіряють очікувані розмір і SHA-256 об’єкта й лише тоді передають дані +декодеру. Невідповідність розміру або хешу спричиняє +`ExternalPayloadIntegrityException` (PHP) або `ExternalPayloadIntegrityError` +(Python) і відображається як помилка replay, а не непомітні порожні дані. + +Конверт посилання є стабільним форматом передавання. Він однаковий незалежно +від того, хто створив дані: PHP workflow, worker Python SDK чи прямий клієнт +HTTP API. Повний контракт полів наведено в розділі +[Конверт посилання на зовнішні дані](../polyglot/server-api-reference.md#external-payload-reference-envelope). + +## Межа довіри декодування {#decode-trust-boundary} + +Зберігання й декодування даних мають окремі межі довіри. Об’єктне сховище +може містити закодовані байти або посилання, а сервер кодеків, власний декодер, +процес worker або засіб експорту історії, що декодує ці байти, може бачити +дані застосунку у відкритому тексті. + +Вважайте кожен сервер кодеків межею довіри, якою керує клієнт. Визначте, +де він працює, яка мережа має доступ, які ключі йому доступні, які журнали +аудиту він створює та як приховуються чутливі дані в декодованих переглядах +перед показом оператору. Durable Workflow записує назви кодеків, URI посилань, +хеші, розміри, відбитки схем і обмежені перегляди, але це не еквівалент +наскрізного шифрування. + +## Вибір драйвера {#driver-choices} + +| Драйвер | Схема URI | Типове використання | +| --- | --- | --- | +| `local` | `file://` | Локальна розробка, CI та однохостові розгортання, де сервер і worker використовують спільну файлову систему. Не підходить, якщо worker працюють на інших хостах. | +| `s3` | `s3://` | Amazon S3 та сумісні сховища, наприклад MinIO і Cloudflare R2, через диск файлової системи на сервері. | +| `gcs` | `gs://` | Google Cloud Storage через диск файлової системи на сервері. | +| `azure` | `azure://` | Azure Blob Storage через диск файлової системи на сервері. | + +Драйвери об’єктних сховищ налаштовують облікові дані bucket/container через +іменований диск файлової системи на сервері. Тому секрети перебувають +у конфігурації сервера, а не в записі політики namespace. + +## Налаштування namespace {#configuring-a-namespace} + +Налаштуйте політику через [CLI](../polyglot/cli-reference.md#namespace-and-search-attribute-commands) +або [HTTP API сервера](../polyglot/server-api-reference.md#namespace-and-storage). +Обидва записують той самий конверт `external_payload_storage` у запис namespace. + +### Через CLI {#with-the-cli} + +```bash +# Production namespace using Amazon S3 through the 'external-payload-objects' disk. +dw namespace:set-storage-driver billing s3 \ + --disk=external-payload-objects \ + --bucket=dw-payloads \ + --prefix=billing/ \ + --threshold-bytes=2097152 + +# Development namespace using the local filesystem. +dw namespace:set-storage-driver dev local \ + --uri=file:///var/lib/durable-workflow/payloads + +# Disable offload while keeping the policy record (all payloads stay inline). +dw namespace:set-storage-driver billing s3 \ + --disk=external-payload-objects \ + --bucket=dw-payloads \ + --disable +``` + +### Через API сервера {#with-the-server-api} + +```bash +curl -sS -X PUT "$DURABLE_WORKFLOW_SERVER_URL/api/namespaces/billing/external-storage" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "enabled": true, + "driver": "s3", + "threshold_bytes": 2097152, + "config": { + "disk": "external-payload-objects", + "bucket": "dw-payloads", + "prefix": "billing/" + } + }' +``` + +Опис namespace, повернений `GET /api/namespaces/{name}` або +`dw namespace:describe`, містить визначений конверт `external_payload_storage`, +щоб оператори й автоматизація могли перевірити активну політику без +повторного запису. + +## Перевірка політики {#verifying-the-policy} + +Перед допуском трафіку workflow використовуйте діагностику повного циклу, +щоб довести, що налаштована політика справді може записувати й читати байти +з обліковими даними namespace: + +```bash +dw storage:test --namespace=billing --large-bytes=2097152 --json +``` + +Діагностика записує малі вбудовані дані й одні дані, що досягли порога, +повторно отримує обидва набори, перевіряє розмір і SHA-256 та повертає +машиночитані об’єкти результатів `small_payload` і `large_payload`. +Успішний результат великих даних доводить, що драйвер може створити чинний +конверт `durable-workflow.v2.external-payload-reference.v1` у повному циклі. +Помилка діагностики означає проблему політики сховища. Не допускайте трафік +workflow через namespace, політика якого не проходить перевірку запису й читання. + +## Вибір порога {#picking-a-threshold} + +Типово дані залишаються вбудованими до досягнення `threshold_bytes`. +Орієнтири для початкового налаштування: + +- Обирайте поріг, за якого вбудовані дані починають створювати операційне + навантаження: зазвичай від 256 KiB до 2 MiB закодованих байтів. +- Залишайте достатній запас до структурного обмеження namespace + [`payload_size_bytes`](../constraints/structural-limits.md#payload-size), + щоб ліміт застосовувався до конверта посилання, а не до самих зовнішніх байтів. +- Задавайте один поріг для namespace. Обирайте його за activity або workflow, + що створює найбільше байтів на запуск, а не за медіанними даними. + +Надто низький поріг не дає переваги: малі дані швидше проходять цикл через +базу даних, ніж через зовнішнє сховище, а сам конверт посилання також займає +невелику частину історії. + +## Replay, зберігання й очищення {#replay-retention-and-cleanup} + +- **Цілісність replay.** Кожне отримання перевіряє збережений об’єкт за + `size_bytes` та `sha256` посилання. Змінений або відсутній об’єкт спричиняє + виняток цілісності замість непомітного підставлення іншого значення. +- **Кеш перевірених даних.** Worker кешують перевірені байти за + `(uri, sha256, size, codec)` з обмеженнями кількості записів і розміру. + Повторне читання історії того самого запуску не завантажує об’єкт знову, + зберігаючи перевірку цілісності під час першого завантаження. +- **Зберігання.** Коли серверний прохід retention видаляє запуск workflow, + він також видаляє зовнішні об’єкти даних, на які посилається його історія. + За працюючого retention об’єкти без посилань не накопичуються. +- **Експорт історії.** Експортована історія зберігає конверт посилання. + Downstream-споживачі, яким потрібні зовнішні байти, мають отримати їх через + той самий драйвер і перевірити за конвертом перед декодуванням. Формат + експорту не вбудовує зовнішні байти. + +## Використання з коду {#using-it-from-code} + +Більшість застосунків не викликають API сховища безпосередньо: runtime +прозоро виносить дані за політикою namespace, а SDK декодує посилання під час +replay. Застосунки, які мають створювати або споживати конверти поза runtime, +наприклад мовно-незалежний обробник мосту або тест із синтезованими великими +даними, використовують допоміжні функції SDK. + +- **PHP (пакет workflow).** Допоміжний клас + `Workflow\V2\Support\ExternalPayloadStorage` зберігає й отримує байти через + будь-який драйвер, що реалізує + `Workflow\V2\Contracts\ExternalPayloadStorageDriver`. + `LocalFilesystemExternalPayloadStorage` обробляє URI `file://`. Окремий сервер + постачається з драйвером диска файлової системи, який підтримує драйвери + політики `s3`, `gcs` та `azure` через іменований диск Laravel. +- **Python SDK.** Розділ + [Зовнішнє сховище даних](../polyglot/python.md#external-payload-storage) + описує `ExternalPayloadReference`, `ExternalPayloadCache`, + `store_external_payload()`, `fetch_external_payload()` та адаптери + `LocalFilesystemExternalStorage`, `S3ExternalStorage`, + `GCSExternalStorage` і `AzureBlobExternalStorage`. Клієнтами хмарних SDK + керує застосунок. SDK не додає boto3, google-cloud-storage або + azure-storage-blob як залежності runtime. +- **Прямий HTTP.** HTTP-клієнти, які кодують дані вручну, можуть зберегти + байти через драйвер, а потім передати конверт посилання в полі даних запиту. + Конверт даних протоколу worker (`{codec, blob}`) також містить посилання для + аргументів activity, результатів, даних signal та update. + +## Дивіться також {#see-also} + +- [Передавання даних](../defining-workflows/passing-data.md) описує типовий + контракт вбудованих даних. +- [Структурні обмеження: розмір даних](../constraints/structural-limits.md#payload-size) + описує ліміт рушія, у межах якого дає змогу працювати зовнішнє сховище. +- [Довідник API сервера: namespace і сховище](../polyglot/server-api-reference.md#namespace-and-storage) + містить повний контракт HTTP, зокрема поля конверта посилання. +- [Довідник CLI: команди namespace та атрибутів пошуку](../polyglot/cli-reference.md#namespace-and-search-attribute-commands) + описує використання `dw namespace:set-storage-driver` та `dw storage:test`. +- [Python SDK: зовнішнє сховище даних](../polyglot/python.md#external-payload-storage) + описує драйвери Python, допоміжні функції та кеш replay. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/heartbeats.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/heartbeats.md new file mode 100644 index 0000000000..8028548d61 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/heartbeats.md @@ -0,0 +1,64 @@ +--- +sidebar_position: 8 +--- + +# Heartbeat {#heartbeats} + +Heartbeat дають тривалій activity змогу повідомляти, що її поточна спроба досі працює. + +`heartbeat()` оновлює отриманий worker запис `activity_attempts`, переносить останній heartbeat у поточне виконання activity, поновлює оренду завдання activity й додає типізовану подію історії `ActivityHeartbeatRecorded` для поточної спроби. Детальний перегляд вибраного запуску у Waterline та експорт історії відновлюють стан спроби, ID завдання, worker, heartbeat, оренду, скасування й час закриття насамперед із типізованої історії `ActivityStarted`, `ActivityHeartbeatRecorded`, `ActivityRetryScheduled`, `ActivityCompleted`, `ActivityFailed` та `ActivityCancelled`. Змінювані записи спроб використовуються як запасне джерело або доповнення для старих даних. + +```php +use Workflow\V2\Activity; + +final class PollRemoteJob extends Activity +{ + public function handle(string $jobId): array + { + do { + sleep(1); + + $status = RemoteService::status($jobId); + + $this->heartbeat([ + 'message' => 'Polling remote job', + 'current' => $status['completed'] ?? null, + 'total' => $status['total'] ?? null, + 'unit' => 'steps', + 'details' => [ + 'remote_state' => $status['state'] ?? 'running', + ], + ]); + } while ($status['state'] === 'running'); + + return $status; + } +} +``` + +Усередині activity методи `activityId()`, `attemptId()` та `attemptCount()` надають ідентичність стійкого виконання й поточної спроби, якщо потрібні ключі кореляції для зовнішньої роботи. Типовим ключем ідемпотентності віддаленої роботи обирайте `activityId()`. Використовуйте `attemptId()` лише для систем, яким потрібно розрізняти окремі спроби одного стійкого виконання activity. + +Worker різних мов можуть надсилати heartbeat через HTTP-міст worker без створення екземпляра PHP-класу activity. Кінцеві точки наведено в [протоколі worker](/docs/polyglot/worker-protocol). + +## Знімки прогресу {#progress-snapshots} + +`Activity::heartbeat()` та `ActivityTaskBridge::heartbeat()` приймають необов’язковий знімок прогресу з обмеженим розміром: + +- `message` + - непорожній рядок завдовжки до 280 символів +- `current` + - невід’ємне ціле число або число з рухомою комою +- `total` + - невід’ємне ціле число або число з рухомою комою +- `unit` + - непорожній рядок завдовжки до 64 символів +- `details` + - пласка мапа з не більш ніж 20 записів `key => scalar|null`, де ключі відповідають `[A-Za-z0-9_.:-]{1,64}` + +Використовуйте ці дані для показу прогресу оператору, наприклад `"Downloading chunk"` або `2 / 5 chunks`, а не для великих журналів чи довільного вкладеного стану. Детальний перегляд вибраного запуску у Waterline, запис heartbeat у хронології та експорт історії повертають їх у полі `last_heartbeat_progress` activity й поточної спроби. Необроблена типізована подія історії `ActivityHeartbeatRecorded` зберігає ті самі нормалізовані дані в `progress`. + +## Стійке відстеження спроб {#durable-attempt-tracking} + +Runtime зберігає окремий стійкий запис для кожної спроби activity: номер, стан, час початку, останній heartbeat, сплив оренди й час закриття. Він також записує типізовану історію початку, heartbeat, повторних спроб, завершення, помилок і скасування activity. Тому історичні деталі спроб зберігаються, навіть якщо змінювані записи спроб або завдань згодом розходяться з історією. Кожен успішний heartbeat поточної отриманої спроби записує компактну подію хронології `ActivityHeartbeatRecorded` з ID виконання activity, ID спроби activity, часовою міткою heartbeat, спливом оренди та знімком activity на момент події. Якщо команда cancel або terminate закриває запуск, міст записує `ActivityCancelled`, коли worker виявляє зупинку, і відхиляє подальше завершення або помилку як застарілі, не перетворюючи скасування на результат. + +Heartbeat — це операційні ознаки активності. Кожен прийнятий heartbeat входить до стійкої історії, тому обирайте інтервал відповідно до потреб тайм-аутів і відновлення, а не використовуйте його як інтенсивний журнал прогресу. Необов’язковий знімок прогресу має залишатися компактним для діагностики оператором, а не перетворюватися на потік подій. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md new file mode 100644 index 0000000000..1d3788f2d5 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md @@ -0,0 +1,195 @@ +--- +sidebar_position: 23 +title: Локальні activity +description: Виконуйте короткі activity у поточному процесі worker workflow зі збереженням стійкої історії activity, повторних спроб, тайм-аутів і видимості. +tags: + - activities + - workers + - execution +keywords: + - локальні activity + - localActivity + - executeLocalActivity + - activity у тому самому процесі + - heartbeat завдання workflow +--- + +# Локальні activity {#local-activities} + +Локальні activity — це примітив v2 для короткої роботи activity, яку слід +виконувати в процесі worker workflow, що зараз виконує завдання workflow. +Вони зберігають семантику повторних спроб, тайм-аутів, heartbeat, скасування, +історії та видимості для оператора, але оминають звичайну чергу завдань activity. + +Використовуйте локальні activity для роботи з малою затримкою, яка є +ідемпотентною й підходить для самого worker workflow. Обирайте звичайні +activity у черзі, коли потрібні незалежне масштабування worker, маршрутизація +черг, тривале виконання або окрема оренда завдання activity. + +## Стислий контракт {#contract-summary} + +- `localActivity(...)` та `Workflow::localActivity(...)` виконують клас activity + у тому самому процесі, що й поточне завдання workflow. +- Runtime створює запис `activity_executions` і звичайні події історії activity + з позначками `execution_mode=local` та `local_activity=true`. +- Звичайне завдання `TaskType::Activity` не створюється, тому параметри + маршрутизації `connection`, `queue`, worker-session і schedule-to-start відхиляються. +- Локальна спроба володіє орендою завдання workflow. `heartbeat()` activity + поновлює цю оренду й записує `ActivityHeartbeatRecorded`. +- Повторні спроби — це стійкі завдання workflow із затримкою, а не приховані повторення в циклі. +- Холодний replay читає зафіксовану історію activity. Якщо після втрати worker + розпочата локальна спроба не має термінальної події, наступна спроба записує + `retry_reason=cold_replay`. +- Деталі запуску, експорт історії, хронологія та метрики оператора показують + локальні спроби окремо від звичайних спроб із черги. + +## API для написання коду {#authoring-api} + +Використовуйте допоміжну функцію з простору імен: + +```php +use Workflow\V2\Support\LocalActivityOptions; +use function Workflow\V2\localActivity; + +$receipt = localActivity( + SendReceiptActivity::class, + new LocalActivityOptions( + maxAttempts: 3, + startToCloseTimeout: 10, + scheduleToCloseTimeout: 30, + heartbeatTimeout: 5, + ), + $orderId, +); +``` + +Або використовуйте статичний фасад workflow: + +```php +use Workflow\V2\Workflow; + +$receipt = Workflow::localActivity(SendReceiptActivity::class, $orderId); +$receipt = Workflow::executeLocalActivity(SendReceiptActivity::class, $orderId); +``` + +`LocalActivityOptions` приймає поля повторних спроб і тайм-аутів: + +- `maxAttempts` +- `backoff` +- `startToCloseTimeout` +- `scheduleToCloseTimeout` +- `heartbeatTimeout` +- `nonRetryableErrorTypes` + +Він відхиляє `connection`, `queue`, маршрутизацію worker-session та +`scheduleToStartTimeout`, оскільки локальна activity не проходить звичайний +підбір завдань. + +## Виконання та історія {#execution-and-history} + +Коли replay workflow доходить до локальної activity, поточне завдання workflow: + +1. створює виконання activity з `activity_options.execution_mode=local`; +2. записує `ActivityScheduled` та `ActivityStarted` з локальною позначкою; +3. створює екземпляр класу activity й виконує його в процесі worker workflow; +4. записує `ActivityCompleted`, `ActivityFailed`, `ActivityTimedOut` або + `ActivityCancelled`; +5. відновлює код workflow із записаної події activity. + +Replay не виконує завершену локальну activity повторно. Replay query і холодний +replay читають ті самі події історії activity, що й звичайні activity. + +## Heartbeat {#heartbeats} + +Локальна activity не володіє орендою завдання activity. Вона володіє орендою +завдання workflow, яке зараз виконує workflow. На початку спроби runtime +поновлює цю оренду. Коли код activity викликає `$this->heartbeat()`, runtime +записує прогрес, оновлює спробу activity й поновлює оренду завдання workflow. + +Тривалі локальні activity мають надсилати heartbeat достатньо часто, щоб +не спливли ні тайм-аут heartbeat локальної activity, ні оренда завдання workflow: + +```php +use Workflow\V2\Activity; + +final class PollShortJobActivity extends Activity +{ + public function handle(string $jobId): array + { + $state = $this->fetch($jobId); + + $this->heartbeat([ + 'message' => 'Polling remote job', + 'job_id' => $jobId, + 'state' => $state['status'], + ]); + + return $state; + } +} +``` + +## Тайм-аути й повторні спроби {#timeouts-and-retries} + +`startToCloseTimeout` обмежує одну спробу. `scheduleToCloseTimeout` обмежує +все локальне виконання з повторними спробами. `heartbeatTimeout` обмежує +інтервал між записаними heartbeat локальної activity. + +У разі помилки, яка допускає повторну спробу, або тайм-ауту runtime записує +`ActivityRetryScheduled` і створює завдання workflow, доступне після затримки +повторної спроби. Це завдання відтворює історію workflow, доходить до тієї самої +позиції локальної activity й починає наступну локальну спробу. + +Кожна локальна спроба — це новий запис `activity_attempts`. Повторна спроба +записує в `retry_reason` значення `failure`, `timeout` або `cold_replay`. + +## Скасування та втрата worker {#cancellation-and-worker-loss} + +Скасування є кооперативним. Локальна activity виявляє скасування на межах +heartbeat, застосування тайм-ауту й завершення спроби. Скасована локальна +спроба записує `ActivityCancelled` із локальною позначкою. + +Якщо worker завершується до фіксації термінальної події локальної activity, +оренда завдання workflow спливає, а звичайне відновлення завдань повторно +отримує це завдання. Холодний replay читає зафіксовану історію. Якщо історія +містить розпочату локальну спробу без термінальної події, runtime планує +повторну спробу з `retry_reason=cold_replay`. + +## Видимість {#visibility} + +Оператори можуть розрізняти локальні activity скрізь, де показано стан activity: + +- дані історії містять `execution_mode=local` та `local_activity=true`; +- `activity_executions.activity_options.execution_mode` має значення `local`; +- деталі запуску й експорт історії містять `execution_mode` та `local_activity`; +- метрики оператора містять `activities.local`, `activities.local_open`, + `activities.local_attempts` і лічильники activity у черзі та локальних activity. + +Маніфест runtime публікується в +`worker_protocol.server_capabilities.local_activities` у відповіді +`GET /api/cluster/info`. Машиночитаний контракт — це +[`local-activity-runtime.schema.json`](/platform-protocol-specs/local-activity-runtime.schema.json), +включений до каталогу +[Platform Protocol Specs](/docs/platform-protocol-specs#local-activity-runtime-notes). + +Назви подій залишаються звичайними назвами подій activity, тому хронологія +та засоби replay зберігають порядок без окремої родини подій. + +## Вибір відповідного примітива {#choosing-the-right-primitive} + +Використовуйте локальну activity для коротких ідемпотентних побічних ефектів +із повторними спробами, які найкраще виконувати в процесі worker workflow +і які не потребують маршрутизації черг. + +Використовуйте звичайну [activity](/docs/defining-workflows/activities) для +віддалених викликів, повільного вводу-виводу, інтенсивних обчислень, окремих +груп worker, контролю навантаження або роботи, яка має продовжуватися через +окремо орендоване завдання activity після втрати worker workflow. + +Використовуйте [сесії worker](/docs/features/worker-sessions), коли кілька +кроків звичайних activity мають повторно використовувати локальні ресурси +worker, наприклад пам’ять GPU або змонтовану файлову систему. + +Використовуйте [`sideEffect(...)`](/docs/features/side-effects) лише для +безпечних щодо replay знімків, яким не потрібна семантика повторних спроб, +тайм-аутів, heartbeat або скасування activity. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/message-streams.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/message-streams.md new file mode 100644 index 0000000000..f00f3b6438 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/message-streams.md @@ -0,0 +1,181 @@ +--- +sidebar_position: 18 +tags: + - message-streams + - workflows + - continue-as-new + - AI +keywords: + - потоки повідомлень + - вхідний потік Workflow + - вихідний потік Workflow + - MessageStream + - повторювані повідомлення workflow +--- + +# Потоки повідомлень {#message-streams} + +Потоки повідомлень — це API v2 для повторюваних повідомлень workflow: +циклів введення користувачем, відповідей асистента, сповіщень між workflow +та інших упорядкованих повідомлень, які мають зберігатися після replay +і continue-as-new. + +Ця сторінка описує вбудований Laravel API inbox/outbox. Серверні +[Workflow Streams](/docs/polyglot/workflow-streams/) використовують ті самі +поняття потоку, зміщення, кількості очікуваних елементів, життєвого циклу +й помилок там, де їхня семантика збігається, але працюють лише на вихід. +Вони не додають вхідних повідомлень workflow або перенесення курсора +continue-as-new до сервісного режиму. + +Автори workflow мають використовувати повноцінний фасад: + +- `$this->inbox('stream-key')` +- `$this->outbox('stream-key')` +- `$this->messages('stream-key')` +- `Workflow\V2\MessageStream` + +Не записуйте рядки `workflow_messages` або курсорів безпосередньо з коду +workflow застосунку. Фасад є стабільним контрактом. Сервіси повідомлень нижчого +рівня призначені для інтеграції пакета та runtime. + +## Отримання повідомлень {#receiving-messages} + +Використовуйте `peek()`, коли workflow має перевірити очікувані вхідні +повідомлення без переміщення стійкого курсора. Використовуйте `receive()` або +`receiveOne()`, коли workflow готовий спожити повідомлення й записати +переміщення курсора в історію. + +```php +use Workflow\V2\Workflow; + +final class ApprovalInboxWorkflow extends Workflow +{ + public function handle(): array + { + $pending = $this->inbox('approval.requests')->peek(limit: 10); + + if ($pending->isEmpty()) { + return ['status' => 'waiting']; + } + + $message = $this->inbox('approval.requests')->receiveOne(); + + return [ + 'status' => 'received', + 'payload_reference' => $message?->payload_reference, + 'correlation_id' => $message?->correlation_id, + ]; + } +} +``` + +`peek()` лише читає дані. `receive()` та `receiveOne()` позначають повідомлення +спожитими й переміщують стійкий курсор повідомлень поточного запуску. +Переміщення курсора є фактом історії, тому replay, експорт Waterline і передача +continue-as-new можуть визначити, які вхідні повідомлення вже спожито. + +## Надсилання посилань {#sending-references} + +Використовуйте `outbox(...)->sendReference(...)`, коли workflow має +опублікувати впорядковане вихідне повідомлення. Таблиця повідомлень зберігає +метадані маршрутизації та посилання на дані. Великий вміст або вміст, який +належить застосунку, залишається в його сховищі даних. + +```php +use Workflow\V2\Workflow; + +final class AssistantReplyWorkflow extends Workflow +{ + public function handle(string $targetWorkflowId, string $replyReference): array + { + $message = $this->outbox('ai.assistant')->sendReference( + targetInstanceId: $targetWorkflowId, + payloadReference: $replyReference, + correlationId: $this->workflowId(), + metadata: ['kind' => 'assistant_reply'], + ); + + return [ + 'status' => 'sent', + 'stream' => $message->stream_key, + 'sequence' => $message->sequence, + ]; + } +} +``` + +Це зберігає малий розмір історії workflow й дає споживачам змогу перевіряти +сховище даних окремо від стійкого порядку повідомлень. + +## Continue-As-New {#continue-as-new} + +Потоки повідомлень насамперед належать екземпляру. Під час continue-as-new +очікувані вхідні повідомлення й позиція курсора переносяться до продовженого +запуску. Спожиті повідомлення залишаються прив’язаними до початкового запуску +як історичні свідчення. + +Тому тривалий workflow може звільнятися від історії, зберігаючи той самий +публічний маршрут повідомлень: + +```php +use function Workflow\V2\continueAsNew; +use Workflow\V2\Workflow; + +final class HumanInputLoopWorkflow extends Workflow +{ + public function handle(int $iteration = 0): array + { + $message = $this->inbox('human.replies')->receiveOne(); + + if ($message === null) { + return ['status' => 'waiting', 'iteration' => $iteration]; + } + + if ($this->shouldContinueAsNew()) { + return continueAsNew($iteration + 1); + } + + return [ + 'status' => 'processed', + 'iteration' => $iteration, + 'payload_reference' => $message->payload_reference, + ]; + } +} +``` + +Ініціатор продовжує адресувати ID екземпляра workflow. Йому не потрібно знати, +який запуск зараз володіє курсором потоку. + +## Правила написання коду {#authoring-rules} + +- Використовуйте змістовні ключі потоків, наприклад `ai.assistant`, + `human.replies` або `approval.requests`, як частину публічного контракту workflow. +- Використовуйте `peek()` для перевірки, а `receive()` / `receiveOne()` — + для стійкого споживання. +- Використовуйте `sendReference()` для вихідних повідомлень. Зберігайте великі + дані в сховищі застосунку й передавайте посилання. +- Код застосунку має використовувати `Workflow::inbox()`, `Workflow::outbox()` + та `MessageStream`, а не залежати від `MessageService`, `WorkflowMessage` + або прямого запису рядків курсора. +- Для одноразових зовнішніх подій використовуйте [Signal](./signals.md). + Для зміни стану з поверненням результату — [Update](./updates.md). Обирайте + потоки повідомлень, коли повторюваним упорядкованим повідомленням потрібна + семантика курсора. + +## Запуск цього прикладу {#run-this-pattern} + +Workflow туристичного агента у +[Sample App](/docs/sample-app) — це виконуваний приклад структури +inbox/outbox, описаної на цій сторінці: + +```bash +php artisan app:ai +``` + +`App\Workflows\Ai\AiWorkflow` зберігає великі дані асистента в таблиці +застосунку `ai_workflow_messages` і публікує лише стійке посилання у вихідному +потоці `ai.assistant`. Відповіді надходять через відповідний вхідний потік +за допомогою update `receive`. Перегляньте клас workflow разом із цією +сторінкою, щоб побачити наскрізне використання тих самих викликів `inbox()`, +`outbox()` та `sendReference()`, що й у наведених прикладах. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/sagas.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/sagas.md new file mode 100644 index 0000000000..f5dd18d262 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/sagas.md @@ -0,0 +1,139 @@ +--- +sidebar_position: 11 +--- + +# Saga {#sagas} + +Цей посібник описує компенсацію у вбудованих Laravel workflow з +`Workflow\V2\Workflow`. Для worker у сервісному режимі використовуйте +[посібник PHP SDK](https://php.durable-workflow.com/build/workflows-activities/), +[посібник Python SDK](https://python.durable-workflow.com/sdk-reference/#saga-compensation) або +[довідник Rust SDK](https://rust.durable-workflow.com/durable_workflow/struct.Saga.html). + +Saga — це усталений шаблон проєктування для керування складними тривалими операціями: + +- Saga керує розподіленими транзакціями через послідовність локальних транзакцій. +- Локальна транзакція — це одиниця роботи, яку виконує учасник saga, тобто activity. +- Кожну операцію saga можна скасувати компенсувальною activity. +- Шаблон saga передбачає, що всі операції або успішно завершуються, або відповідні компенсувальні activity виконуються для скасування вже завершеної роботи. + +```php +use function Workflow\V2\activity; +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +#[Type('booking-saga')] +class BookingSagaWorkflow extends Workflow +{ + public function handle(): array + { + try { + $flightId = activity(BookFlightActivity::class); + $this->addCompensation(fn () => activity(CancelFlightActivity::class, $flightId)); + + $hotelId = activity(BookHotelActivity::class); + $this->addCompensation(fn () => activity(CancelHotelActivity::class, $hotelId)); + + $carId = activity(BookRentalCarActivity::class); + $this->addCompensation(fn () => activity(CancelRentalCarActivity::class, $carId)); + + return compact('flightId', 'hotelId', 'carId'); + } catch (\Throwable $e) { + $this->compensate(); + + throw $e; + } + } +} +``` + +Коли workflow перехоплює виняток, `$this->compensate()` виконує всі зареєстровані компенсації у **зворотному порядку**. У наведеному прикладі, якщо `BookRentalCarActivity` завершується помилкою, рушій спочатку скасовує готель, а потім переліт, розгортаючи saga назад від найновішого кроку. + +Реєструйте кожну компенсацію після успіху її основної activity. Replay +відновлює цей порядок реєстрації й використовує записані результати activity, +зокрема завершених компенсацій. + +## Порядок компенсацій {#compensation-ordering} + +Типово компенсації виконуються **послідовно у зворотному порядку реєстрації**. Це найбезпечніший типовий порядок, оскільки пізніші кроки можуть залежати від попередніх. + +Типово `compensate()` зупиняється на першій помилці компенсації й передає її +коду, який викликав цей метод. + +## Паралельна компенсація {#parallel-compensation} + +Для паралельного виконання компенсацій використовуйте `setParallelCompensation(true)`. Коли паралельну компенсацію увімкнено, кожне замикання компенсації має повертати розпочатий виклик activity без очікування його результату, щоб рушій міг виконувати їх конкурентно: + +```php +use function Workflow\V2\activity; + +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +#[Type('parallel-saga')] +class ParallelSagaWorkflow extends Workflow +{ + public function handle(): void + { + $this->setParallelCompensation(true); + + try { + $flightId = activity(BookFlightActivity::class); + $this->addCompensation(fn () => activity(CancelFlightActivity::class, $flightId)); + + $hotelId = activity(BookHotelActivity::class); + $this->addCompensation(fn () => activity(CancelHotelActivity::class, $hotelId)); + + activity(ChargePaymentActivity::class); + } catch (\Throwable $e) { + $this->compensate(); + + throw $e; + } + } +} +``` + +Коли паралельну компенсацію увімкнено, замикання компенсацій повертають виклики activity, які рушій збирає й виконує через `all()`. + +## Продовження після помилки {#continue-with-error} + +Типово, якщо компенсувальна activity спричиняє виняток, решта компенсацій пропускаються, а помилка передається далі. Щоб виконати всі компенсації незалежно від окремих помилок, використовуйте `setContinueWithError(true)`: + +```php +$this->setContinueWithError(true); +``` + +Коли цей режим увімкнено, рушій перехоплює й відкидає винятки кожного замикання компенсації та переходить до наступного. Це корисно для незалежних компенсацій, коли потрібно виконати очищення за принципом best-effort навіть у разі помилок окремих кроків. + +### Поєднання обох прапорців {#combining-both-flags} + +`setParallelCompensation(true)` та `setContinueWithError(true)` можна використовувати разом. Тоді всі компенсації виконуються конкурентно через `all()`, а помилка будь-якої компенсації перехоплюється, щоб решта все одно завершилися. Без `setContinueWithError(true)` помилка паралельної компенсації одразу передається далі, і workflow завершується помилкою. + +```php +$this->setParallelCompensation(true); +$this->setContinueWithError(true); +``` + +## Як це працює {#how-it-works} + +- `addCompensation()` реєструє функцію, яка буде викликана під час `compensate()` +- `compensate()` перебирає зареєстровані компенсації у зворотному порядку +- кожне замикання компенсації є звичайним кроком workflow V2. Викликані ним activity створюють стійкі події історії, як і будь-яка інша activity +- компенсувальні activity видно у хронології Waterline та експорті історії +- якщо workflow завершується успішно, замикання компенсацій не викликаються й не створюють історії + +## Запуск цього прикладу {#run-this-pattern} + +Saga туристичного агента, керована signal, у +[Sample App](/docs/sample-app) — це виконуваний приклад для цієї +сторінки. Клонуйте sample app, задайте `OPENAI_API_KEY` і виконайте: + +```bash +php artisan app:ai +``` + +`App\Workflows\Ai\AiWorkflow` реєструє компенсації для кожної activity +бронювання. Тому помилка перельоту після успішного бронювання готелю +скасовує готель через список компенсацій. Це той самий шаблон, описаний +на цій сторінці, з подіями у хронології запуску Waterline. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/schedules.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/schedules.md new file mode 100644 index 0000000000..dfa14fe633 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/schedules.md @@ -0,0 +1,555 @@ +--- +sidebar_position: 20 +--- + +# Розклади {#schedules} + +Розклади дають змогу періодично запускати workflow за виразами cron. Кожен розклад — це іменована стійка сутність, яку рушій перевіряє на кожному tick, щоб визначити потребу нового запуску. + +## Створення розкладу {#creating-a-schedule} + +Використовуйте `ScheduleManager::create()` для визначення іменованого розкладу: + +```php +use Workflow\V2\Enums\ScheduleOverlapPolicy; +use Workflow\V2\Support\ScheduleManager; + +$schedule = ScheduleManager::create( + scheduleId: 'daily-invoice-sync', + workflowClass: InvoiceSyncWorkflow::class, + cronExpression: '0 2 * * *', + arguments: ['nightly'], + timezone: 'America/New_York', + overlapPolicy: ScheduleOverlapPolicy::Skip, + labels: ['team' => 'billing'], + memo: ['origin' => 'scheduled'], + searchAttributes: ['tenant_id' => '42'], + notes: 'Runs every night at 2 AM ET.', + executionTimeoutSeconds: 120, + runTimeoutSeconds: 60, +); +``` + +`scheduleId` — це унікальний ідентифікатор розкладу, обраний користувачем. Кожен запущений workflow отримує детермінований ID екземпляра, утворений з ID розкладу й часової мітки запуску. + +### Параметри {#parameters} + +| Параметр | Тип | Типове значення | Опис | +|---|---|---|---| +| `scheduleId` | `string` | обов’язковий | Унікальний ідентифікатор розкладу | +| `workflowClass` | `string` | обов’язковий | Клас workflow для запуску | +| `cronExpression` | `string` | обов’язковий | Стандартний вираз cron із 5 полів | +| `arguments` | `array` | `[]` | Аргументи для методу `handle()` workflow | +| `timezone` | `string` | `'UTC'` | Часовий пояс для обчислення виразу cron | +| `overlapPolicy` | `ScheduleOverlapPolicy` | `Skip` | Дія, якщо попередній запуск ще активний | +| `labels` | `array` | `[]` | Позначки видимості для кожного запуску | +| `memo` | `array` | `[]` | Поля memo для кожного запуску | +| `searchAttributes` | `array` | `[]` | Атрибути пошуку для кожного запуску | +| `jitterSeconds` | `int` | `0` | Максимальна випадкова затримка в секундах для кожного спрацювання, що згладжує масове одночасне навантаження | +| `maxRuns` | `int\|null` | `null` | Максимальна кількість запусків перед автоматичним видаленням розкладу | +| `connection` | `string\|null` | `null` | Підключення черги для запусків, що перевизначає типове значення класу workflow | +| `queue` | `string\|null` | `null` | Назва черги для запусків, що перевизначає типове значення класу workflow | +| `notes` | `string\|null` | `null` | Довільні примітки оператора | +| `namespace` | `string\|null` | `null` | Namespace розкладу, типово налаштований `workflows.v2.namespace` або `'default'` | +| `executionTimeoutSeconds` | `int\|null` | `null` | Тайм-аут кожного логічного виконання workflow з повторними спробами й continue-as-new | +| `runTimeoutSeconds` | `int\|null` | `null` | Тайм-аут окремого запуску, що починається заново після continue-as-new | + +### Тайм-аути workflow {#workflow-timeouts} + +Тайм-аут виконання охоплює одне логічне виконання з повторними спробами +й continue-as-new. Тайм-аут запуску обмежує окремий запуск і починається +заново після continue-as-new. Запуск не може перевищити залишок бюджету +виконання. Кожне спрацювання розкладу починає новий бюджет виконання, +відлік якого йде від фактичного старту workflow, а не запланованого часу. + +Обидва параметри приймають додатні цілі секунди або `null`. Пропущений +параметр або `null` залишає відповідний ліміт невстановленим. Ручні запуски, +автоматичні tick, буферизовані запуски й backfill застосовують збережені ліміти +через типовий PHP-стартер. + +Для `createFromSpec()` задайте ліміти в дії: + +```php +$schedule = ScheduleManager::createFromSpec( + scheduleId: 'hourly-invoice-sync', + spec: ['intervals' => [['every' => 'PT1H']]], + action: [ + 'workflow_class' => InvoiceSyncWorkflow::class, + 'input' => ['hourly'], + 'execution_timeout_seconds' => 120, + 'run_timeout_seconds' => 60, + ], +); +``` + +Створення та оновлення дії перевіряють тайм-аути PHP workflow за тими самими +правилами `StartOptions`, що й прямі запуски. Власний стартер не потрібен. + +Змініть ліміти майбутніх запусків, оновивши дію: + +```php +$schedule = ScheduleManager::update($schedule, action: [ + ...$schedule->action, + 'execution_timeout_seconds' => 240, + 'run_timeout_seconds' => 90, +]); +``` + +Дія замінюється повністю, тому зберігайте її клас workflow та вхідні дані, +як показано. Уже розпочаті запуски зберігають початкові кінцеві терміни. +Задайте полю тайм-ауту `null`, щоб прибрати ліміт із подальших запусків. + +## Розширене планування {#advanced-scheduling} + +Наступні розділи описують можливості, потрібні за межами простого cron: фіксовані інтервали, поєднання специфікацій cron та інтервалів і політики перекриття. Їх можна пропустити, якщо цих можливостей не потрібно. + +### Розклади за інтервалом {#interval-based-schedules} + +Окрім виразів cron, розклади підтримують спрацювання за інтервалом у синтаксисі тривалості ISO 8601. Використовуйте `ScheduleManager::createFromSpec()` для повного керування специфікацією: + +```php +use Workflow\V2\Support\ScheduleManager; + +$schedule = ScheduleManager::createFromSpec( + scheduleId: 'health-check-30m', + spec: [ + 'intervals' => [ + ['every' => 'PT30M'], + ], + ], + action: [ + 'workflow_type' => 'health-check', + 'workflow_class' => HealthCheckWorkflow::class, + 'input' => ['region' => 'us-east-1'], + ], +); +``` + +### Поля специфікації інтервалу {#interval-spec-fields} + +| Поле | Тип | Опис | +|---|---|---| +| `every` | `string` | Тривалість ISO 8601, наприклад `PT30M` для 30 хвилин, `PT1H` для 1 години, `P1D` для 1 дня | +| `offset` | `string\|null` | Фазове зміщення у форматі тривалості ISO 8601, що зсуває точку вирівнювання інтервалу | + +Параметр offset визначає момент спрацювання в циклі інтервалу. Наприклад, годинний інтервал зі зміщенням на 5 хвилин спрацьовує о `:05`, `:05+1h` тощо: + +```php +$schedule = ScheduleManager::createFromSpec( + scheduleId: 'offset-hourly', + spec: [ + 'intervals' => [ + ['every' => 'PT1H', 'offset' => 'PT5M'], + ], + ], + action: [ + 'workflow_type' => 'sync-workflow', + 'workflow_class' => SyncWorkflow::class, + 'input' => [], + ], +); +``` + +### Поєднані специфікації cron та інтервалів {#mixed-cron-and-interval-specs} + +Один розклад може поєднувати вирази cron та інтервали. Рушій обчислює всі специфікації й використовує найближчий час спрацювання: + +```php +$schedule = ScheduleManager::createFromSpec( + scheduleId: 'mixed-schedule', + spec: [ + 'cron_expressions' => ['0 12 * * *'], // noon daily + 'intervals' => [['every' => 'PT6H']], // every 6 hours + 'timezone' => 'America/Chicago', + ], + action: [ + 'workflow_type' => 'report-workflow', + 'workflow_class' => ReportWorkflow::class, + 'input' => [], + ], +); +``` + +Метод `createFromSpec` приймає ті самі параметри життєвого циклу, що й `create()` (`overlapPolicy`, `jitterSeconds`, `maxRuns`, `connection`, `queue`, `namespace` тощо), як окремі іменовані аргументи. + +## Політики перекриття {#overlap-policies} + +Якщо розклад спрацьовує, поки попередній запуск ще активний, поведінку визначає політика перекриття: + +| Політика | Поведінка | +|---|---| +| `Skip` | Не починати новий запуск, типова поведінка | +| `BufferOne` | Буферизувати одне очікуване спрацювання, пропускаючи подальші до спорожнення буфера. На наступному `tick()` після завершення активного запуску буферизоване спрацювання виконується автоматично. | +| `BufferAll` | Буферизувати всі очікувані спрацювання без обмеження. Вони виконуються послідовно після завершення попередніх запусків. | +| `AllowAll` | Почати новий запуск незалежно від стану попереднього | +| `CancelOther` | Скасувати попередній запуск, а потім почати новий | +| `TerminateOther` | Примусово завершити попередній запуск, а потім почати новий | + +## Керування розкладами {#managing-schedules} + +### Призупинення й відновлення {#pause-and-resume} + +```php +ScheduleManager::pause($schedule); + +// The schedule will not trigger while paused. + +ScheduleManager::resume($schedule); +// next_fire_at is recalculated from now. +``` + +### Оновлення {#update} + +```php +ScheduleManager::update( + $schedule, + cronExpression: '30 3 * * *', + timezone: 'America/Chicago', + overlapPolicy: ScheduleOverlapPolicy::AllowAll, + notes: 'Moved to 3:30 AM CT.', +); +``` + +Оновлення виразу cron або часового поясу перераховує `next_fire_at`. + +### Видалення {#delete} + +```php +ScheduleManager::delete($schedule); +``` + +Видалення є м’яким: запис залишається зі станом `deleted` і часовою міткою `deleted_at`. Видалений розклад не можна призупинити, відновити, оновити або запустити. + +### Опис {#describe} + +```php +$description = ScheduleManager::describe($schedule); + +$description->scheduleId; // 'daily-invoice-sync' +$description->namespace; // 'default' +$description->status; // ScheduleStatus::Active +$description->spec; // ['cron_expressions' => ['0 2 * * *'], 'timezone' => 'America/New_York'] +$description->overlapPolicy; // ScheduleOverlapPolicy::Skip +$description->firesCount; // 47 +$description->nextFireAt; // DateTimeInterface|null +$description->lastFiredAt; // DateTimeInterface|null +$description->latestInstanceId; // 'schedule:daily-invoice-sync:...' +$description->jitterSeconds; // 0 +$description->note; // 'Runs every night at 2 AM ET.' +$description->toArray(); // full array representation +``` + +### Пошук за ID розкладу {#find-by-schedule-id} + +```php +$schedule = ScheduleManager::findByScheduleId('daily-invoice-sync'); +``` + +## Запуск розкладів {#triggering-schedules} + +### Ручний запуск {#manual-trigger} + +```php +$instanceId = ScheduleManager::trigger($schedule); +``` + +Цей виклик одразу перевіряє політику перекриття й, якщо дозволено, починає новий запуск workflow. Він повертає ID екземпляра розпочатого workflow або `null`, якщо спрацювання пропущено. + +### Tick: перевірка всіх розкладів, час яких настав {#tick-evaluate-all-due-schedules} + +```php +$results = ScheduleManager::tick(); + +// Returns rows with schedule_id, instance_id, outcome, occurrence_time, +// last_fired_at, and next_fire_at when those fields apply. +``` + +`tick()` знаходить усі активні розклади, чий `next_fire_at` уже минув, і запускає їх за порядком. Поле `occurrence_time` містить час належного спрацювання, який побачив планувальник. Після кожного спрацювання `next_fire_at` переходить від поточного часу до наступного спрацювання cron або інтервалу. + +### Політика пропущених спрацювань {#missed-fire-policy} + +Поточна перевірка розкладів використовує семантику **одне спрацювання, потім звичайне продовження**. Якщо планувальник був недоступний протягом одного чи кількох запланованих моментів, перший tick після відновлення запускає один workflow для простроченого `next_fire_at`, записує цей час як `occurrence_time`, а потім визначає наступний `next_fire_at` від поточного часу. Інші моменти, що минули під час простою, пропускаються поточною перевіркою. + +Використовуйте `backfill()`, якщо потрібно виконати кожне пропущене спрацювання. Backfill явно перелічує спрацювання потрібного часового вікна й записує кожне окремо. + +### Команда Artisan {#artisan-command} + +Виконайте один tick із командного рядка: + +```bash +php artisan workflow:v2:schedule-tick +php artisan workflow:v2:schedule-tick --json +``` + +Окремий сервер надає той самий прохід перевірки через власну команду: + +```bash +php artisan schedule:evaluate --limit=100 +``` + +Для постійної перевірки розкладів викликайте цю команду з планувальника завдань Laravel: + +```php +// app/Console/Kernel.php +$schedule->command('workflow:v2:schedule-tick')->everyMinute(); +``` + +## Максимальна кількість запусків {#max-runs} + +Якщо задано `maxRuns`, розклад відстежує `remaining_actions`. Після останнього дозволеного спрацювання він автоматично м’яко видаляється. + +```php +$schedule = ScheduleManager::create( + scheduleId: 'one-shot-retry', + workflowClass: RetryWorkflow::class, + cronExpression: '*/5 * * * *', + maxRuns: 3, +); + +// After 3 triggers, the schedule status becomes 'deleted'. +``` + +## Backfill {#backfill} + +Backfill запускає workflow для пропущених минулих спрацювань cron, наприклад через призупинення розкладу, збій розгортання або пізнє створення: + +```php +$results = ScheduleManager::backfill( + $schedule, + from: new DateTimeImmutable('2026-04-10 00:00:00'), + to: new DateTimeImmutable('2026-04-14 00:00:00'), +); + +// Returns: [['schedule_id' => '...', 'instance_id' => '...|null', 'cron_time' => '...'], ...] +``` + +Кожне пропущене спрацювання cron запускається послідовно. Політика перекриття застосовується до кожного, з одним винятком: **політики буферизації (`BufferOne`, `BufferAll`) під час backfill трактуються як `AllowAll`**. Буферизація керує потоком у реальному часі й не має сенсу для надолуження минулих спрацювань. Backfill має запускати кожне пропущене спрацювання, а не залишати їх у буфері, який ніколи не спорожніє. Політику також можна явно перевизначити: + +```php +$results = ScheduleManager::backfill( + $schedule, + from: new DateTimeImmutable('2026-04-10 00:00:00'), + to: new DateTimeImmutable('2026-04-14 00:00:00'), + overlapPolicyOverride: ScheduleOverlapPolicy::AllowAll, +); +``` + +Backfill дотримується `maxRuns`: якщо доступні дії розкладу вичерпано під час backfill, операція зупиняється, а розклад автоматично видаляється. + +ID екземплярів backfill детерміновані: `schedule:{scheduleId}:backfill:{timestamp}`. + +## Маршрутизація черг {#queue-routing} + +Якщо в розкладі задано `connection` або `queue`, workflow відправляються до цього підключення й черги замість типових значень класу workflow: + +```php +$schedule = ScheduleManager::create( + scheduleId: 'priority-sync', + workflowClass: InvoiceSyncWorkflow::class, + cronExpression: '0 * * * *', + connection: 'redis', + queue: 'high-priority', +); + +// Every triggered run dispatches to redis/high-priority, +// regardless of InvoiceSyncWorkflow's default routing. +``` + +Пріоритет маршрутизації: поля розкладу → типові значення класу workflow → глобальна конфігурація черги. + +## Jitter {#jitter} + +Коли кілька розкладів мають однаковий вираз cron, вони спрацьовують одночасно й створюють різкий сплеск навантаження. Параметр `jitterSeconds` розподіляє спрацювання у випадковому часовому вікні, згладжуючи навантаження. + +```php +$schedule = ScheduleManager::create( + scheduleId: 'hourly-report', + workflowClass: ReportWorkflow::class, + cronExpression: '0 * * * *', + jitterSeconds: 300, // fire within 0–300 seconds after the top of the hour +); +``` + +Якщо задано `jitterSeconds`, кожен обчислений `next_fire_at` зсувається на випадкове значення від 0 до `jitterSeconds` включно. Jitter обирається заново під час кожного обчислення наступного спрацювання: після запуску, відновлення або оновлення. + +Jitter застосовується лише до часу спрацювання tick, збереженого в базі даних. Перелік backfill завжди використовує канонічні моменти cron без jitter, щоб спрацювання відповідали точним межам cron. + +Значення `jitterSeconds = 0`, яке є типовим, повністю вимикає jitter. Час спрацювання точно відповідає cron. + +## Типи подій історії {#history-event-types} + +Події життєвого циклу розкладу записуються у два окремі потоки: + +- **Походження запуску workflow.** Коли розклад запускає workflow, до історії + розпочатого запуску (`workflow_history_events`) додається `ScheduleTriggered`. + Це дає запуску перевірюваний зв’язок із розкладом, який його створив. +- **Потік аудиту розкладу.** Кожен перехід життєвого циклу розкладу + записується в його журнал аудиту (`workflow_schedule_history_events`) + із монотонно зростаючим `sequence`. Потік аудиту є джерелом істини щодо + того, що сталося з розкладом, а історія запуску — щодо причини його старту. + +Обидва потоки використовують той самий enum `HistoryEventType` і реєстр +ключів даних `HistoryEventPayloadContract`, тому назви подій та структури +даних залишаються узгодженими між потоками. + +### Подія походження в розпочатому запуску workflow {#run-lineage-event-on-the-started-workflow-run} + +`ScheduleTriggered` додається до історії запущеного workflow з такими +ключами даних: + +- `schedule_id` — ідентифікатор розкладу для користувача. +- `schedule_ulid` — внутрішній первинний ключ ULID розкладу. +- `cron_expression`, `timezone`, `overlap_policy` — основний вираз cron, + часовий пояс IANA та активна політика перекриття розкладу на момент + спрацювання. +- `trigger_number` — номер спрацювання, починаючи з 1. +- `occurrence_time` — час спрацювання розкладу, який перевірив планувальник. + Спрацювання tick записують належний `next_fire_at`, а backfill — + перелічений момент backfill. За ввімкненого jitter значення tick + включає час зі зсувом jitter, збережений у розкладі. + +### Потік аудиту самого розкладу {#schedule-audit-stream-on-the-schedule-itself} + +Кожен перехід життєвого циклу розкладу записується у власний потік аудиту. +Нумерація починається з `1` для `ScheduleCreated` і монотонно зростає +окремо для кожного розкладу. + +| Подія | Коли записується | Ключі даних | +| --- | --- | --- | +| `ScheduleCreated` | Розклад створено | `spec`, `action`, `overlap_policy`, `next_fire_at`, `command_context` | +| `SchedulePaused` | Розклад призупинено | `reason`, `paused_at`, `command_context` | +| `ScheduleResumed` | Розклад відновлено | `next_fire_at`, `command_context` | +| `ScheduleUpdated` | Змінено cron, часовий пояс, специфікацію, дію або політику розкладу | `changed_fields`, `spec`, `action`, `overlap_policy`, `next_fire_at`, `command_context` | +| `ScheduleTriggered` | Розклад почав запуск workflow | `workflow_instance_id`, `workflow_run_id`, `outcome`, `effective_overlap_policy`, `trigger_number`, `occurrence_time`, `command_context` | +| `ScheduleTriggerSkipped` | Спрацювання пропущено через політику перекриття, недопустимий стан або вичерпані дії | `reason`, `skipped_trigger_count`, `last_skipped_at`, `command_context` | +| `ScheduleDeleted` | Розклад м’яко видалено явним викликом або через вичерпання `max_runs` (`reason: max_runs_exhausted`) | `reason`, `deleted_at`, `command_context` | + +`command_context` містить суб’єкта, ID запиту, джерело й причину, які записує +`ScheduleManager`, коли ініціатор передає `CommandContext`. Він необов’язковий. +Події без контексту не містять цього ключа замість запису порожнього значення. + +### Стабільність контракту даних {#payload-contract-stability} + +Ключі даних обох наведених таблиць оголошено в +`Workflow\V2\Support\HistoryEventPayloadContract`. Вони перевіряються +під час кожного запису й зафіксовані тестами пакета workflow. Додавання +ключа до будь-якої події розкладу змінює формат передавання й дотримується +правил зміни подій історії з +[контракту сумісності версій](../compatibility.md). + +### Зберігання {#retention} + +Потік аудиту зберігається протягом життя запису розкладу. М’яке видалення +розкладу записує кінцеву подію `ScheduleDeleted`. Самі записи аудиту +не видаляються каскадно під час видалення запису розкладу. Оператори, які +остаточно очищають історичні розклади, мають обрати явну стратегію зберігання +для свого розгортання. Пакет не містить вбудованого TTL подій аудиту. + +### Видимість {#visibility} + +Потік аудиту доступний через усі інтерфейси оператора: + +- **У процесі (Eloquent).** `WorkflowSchedule::historyEvents()` + (отриманий через `ConfiguredV2Models::query( + 'schedule_history_event_model', ...)`) повертає потік розкладу + всередині основного застосунку. +- **Waterline HTTP.** + `GET /waterline/api/v2/schedules/{scheduleId}/history` повертає потік + із `limit` (1–500, типово 100) та курсорною пагінацією `after_sequence`. + Відповідь обмежена namespace Waterline, тому багатоклієнтські розгортання + бачать лише записи аудиту свого клієнта. +- **Waterline UI.** Дія **History** у кожному рядку реєстру розкладів + Waterline відкриває модальне вікно з потоком цього розкладу. Події + показують номер послідовності, час запису, тип, пов’язані ID екземпляра + workflow і запуску та відформатовані дані. Кнопка **Load more** + пересуває курсор порціями по 100. +- **HTTP окремого сервера.** + `GET /api/schedules/{scheduleId}/history` повертає той самий потік + із тим самим контрактом пагінації та правилами обмеження `X-Namespace`. + Історія залишається доступною після м’якого видалення розкладу, + оскільки оператори використовують аудит для відновлення відомостей + про видалений розклад. +- **CLI.** `dw schedule:history ` друкує потік таблицею + (`Seq`, `Event`, `Recorded At`, `Workflow Refs`) із підказкою + **More events available**, коли `has_more` має значення true. + `--limit` та `--after-sequence` передаються серверній кінцевій точці, + `--all` проходить усі події, що залишилися, сторінками, а + `--output=json` / `--output=jsonl` повертають структурований результат. + `jsonl` відкидає конверт курсора, тому кожен рядок є самостійною подією. +- **Python SDK.** `Client.get_schedule_history(schedule_id, *, limit=None, + after_sequence=None)` повертає одну `ScheduleHistoryPage`, а + `Client.iter_schedule_history(schedule_id, *, limit=None, after_sequence=None)` + є `AsyncIterator[ScheduleHistoryEvent]`, який проходить весь потік + сторінками з тими самими іменованими аргументами. `ScheduleHandle` + надає відповідні зручні методи `.history(...)` та `.iter_history(...)`. + Сервер обмежує `limit` діапазоном від 1 до 500 (типово 100). + `after_sequence` — невід’ємний курсор із `next_cursor` попередньої сторінки. + +Усі ці інтерфейси читають ту саму таблицю `workflow_schedule_history_events`. +Контракт ключів із розділу +[Стабільність контракту даних](#payload-contract-stability) діє незалежно +від обраного оператором інтерфейсу. + +### Міграція розкладів, створених до аудиту {#migration-behavior-for-pre-audit-schedules} + +Потік аудиту розкладів з’явився разом із таблицею +`workflow_schedule_history_events`. Розклади, створені до цієї міграції, +не отримують ретроспективної події `ScheduleCreated`. `ScheduleManager` +записує аудит лише під час переходів життєвого циклу, а міграція +не створює штучних подій для наявних розкладів. + +Що це означає для операторів: + +- Потік наявного розкладу порожній до наступного переходу його життєвого + циклу. Призупинення, відновлення, оновлення, запуск, пропуск спрацювання + або видалення розкладу відтоді додають події звичайним способом. +- Нумерація й далі починається з `1` і монотонно зростає для кожного + розкладу. Якщо перша записана подія наявного розкладу — `SchedulePaused`, + її запис матиме `sequence = 1`. Відсутність попередньої + `ScheduleCreated` є очікуваною. +- `ScheduleTriggered` та `ScheduleTriggerSkipped` записуються за кожної + перевірки розкладу tick. Тому розклад, який спрацьовує за cron після + міграції, накопичує аудит без втручання оператора. +- Оператору не потрібно окремо вмикати аудит розкладу. Потік завжди + ввімкнений, але не містить записів до першої події після міграції. + +## Відстеження пропусків {#skip-tracking} + +Коли спрацювання пропущено через політику перекриття, недопустимий стан або вичерпані дії, розклад записує пропуск: + +- `last_skip_reason` — причина останнього пропущеного спрацювання, наприклад `overlap_policy_skip`, `status_not_triggerable`, `remaining_actions_exhausted` +- `last_skipped_at` — час пропуску +- `skipped_trigger_count` — сумарна кількість пропущених спрацювань + +Ці поля входять до `ScheduleManager::describe()` та API деталей розкладу Waterline. + +## Обмеження namespace {#namespace-scoping} + +Розклад належить namespace. Якщо `namespace` передано до `create()` або `createFromSpec()`, розклад обмежений ним. Якщо параметр пропущено, розклад успадковує налаштований `workflows.v2.namespace`, типово `'default'`. + +ID розкладів унікальні в межах namespace. Той самий `scheduleId` може існувати в різних namespace без конфлікту. + +Стандартний PHP-стартер розкладів передає збережений namespace розкладу +до екземпляра workflow, запуску й стійких завдань. Подальша зміна типового +namespace застосунку не переносить виконання наявного розкладу. Для цього +поширення namespace використовуйте Workflow 2.4.2 або новіший. + +```php +$schedule = ScheduleManager::create( + scheduleId: 'daily-sync', + workflowClass: SyncWorkflow::class, + cronExpression: '0 2 * * *', + namespace: 'billing', +); + +// Find by schedule ID within a namespace: +$found = ScheduleManager::findByScheduleId('daily-sync', namespace: 'billing'); +``` + +Якщо для Waterline налаштовано namespace (`waterline.namespace`), кінцеві точки списку й деталей розкладів автоматично обмежуються ним. Тому багатоклієнтські розгортання показують лише розклади namespace оператора. + +## База даних {#database} + +Таблицю розкладів (`workflow_schedules`) створює міграція `2026_04_14_000157`. Клас моделі налаштовується через `workflows.v2.schedule_model`. + +Якщо розгортання виконує міграції пакета разом із міграціями застосунку, міграція 157 виявляє наявну таблицю `workflow_schedules` і обробляє її так: таблиця, яка вже відповідає схемі пакета, залишається без змін. Якщо її створила попередня допоміжна міграція з іншою схемою, таблиця замінюється. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/sticky-execution.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/sticky-execution.md new file mode 100644 index 0000000000..7dd324fa3a --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/sticky-execution.md @@ -0,0 +1,223 @@ +--- +sidebar_position: 25 +title: Sticky execution +description: Використовуйте sticky-маршрутизацію завдань workflow як підтримувану оптимізацію replay v2 із холодним replay для збереження коректності. +tags: + - sticky-execution + - replay + - operations +keywords: + - sticky execution + - кеш sticky replay + - запасний шлях холодного replay + - маршрутизація worker_id + - діагностика sticky-кешу +--- + +# Sticky execution {#sticky-execution} + +Sticky execution — це підтримувана оптимізація replay Durable Workflow v2. +Worker може зберігати прогрітий локальний кеш workflow після завершення +завдання workflow, а підбір завдань може надавати йому перевагу для наступного +завдання workflow того самого запуску. + +Sticky execution не є засобом забезпечення коректності. Прогрес workflow +фіксується лише через стійку історію, а звичайний холодний replay з історії +завжди залишається коректним. Код workflow не має покладатися на локальний +стан процесу для забезпечення правильності. + +## Стислий контракт {#contract-summary} + +Sticky execution надає чотири гарантії: + +- Sticky-кеші належать процесам worker, а не серверу чи базі даних. +- Sticky-маршрутизація використовує `worker_id` протоколу worker як ідентичність маршрутизації. +- Sticky-прив’язка є рекомендаційною та має термін дії. Холодний replay — + обов’язковий запасний шлях після промаху кешу, перезапуску worker, drain, + розгортання або витіснення. +- Оператори мають явні параметри й діагностику ввімкнення, TTL, місткості, + частки влучань і промахів, примусового холодного replay та тиску на місткість. + +Стійкі поля прив’язки — `sticky_worker_id` та `sticky_until` у запусках і +завданнях workflow. Поля діагностики завдань workflow — `sticky_replay_mode` +та `sticky_claimed_at`. + +## Життєвий цикл sticky-кешу {#sticky-cache-lifecycle} + +Sticky-кеш — це локальна структура даних worker із відтвореним станом +workflow одного чи кількох запусків. Коли worker із підтримкою sticky завершує +завдання workflow, сервер може записати його як sticky-власника запуску до +`sticky_until`. Подальші завдання workflow успадковують цю прив’язку під час +створення. + +Worker володіє вмістом кешу й політикою витіснення. Він може витісняти +кешовані запуски за досягнення місткості, початку drain, перезапуску, зміни +збірки, виявлення небезпечного кешованого стану або потреби звільнити пам’ять. +Сервер ніколи не вважає вміст кешу стійким станом. + +Якщо worker отримує завдання зі sticky-маршрутизацією, але вже не має чинного +запису кешу, він мусить виконати холодний replay зі стійкої історії. Оренда +завдання залишається єдиною підставою для фіксації прогресу workflow. + +## Ідентичність маршрутизації {#routing-identity} + +Ідентичність маршрутизації — це `worker_id` протоколу worker. Worker обирають +sticky-маршрутизацію, реєструючись із увімкненим sticky-кешем і продовжуючи +надсилати heartbeat як активні worker черги завдань. + +Підбір завдань дотримується таких правил: + +- Перевага надається завданню з активною прив’язкою до worker, який опитує чергу. +- Завдання без активної прив’язки може отримати будь-який сумісний worker. +- Завдання з активною прив’язкою до іншого живого sticky-worker утримується + для нього до спливу `sticky_until` або недоступності власника. +- Завдання зі спливлою прив’язкою, застарілим власником або вимкненим sticky + execution можна отримати звичайним способом і відтворити холодним replay. + +Sticky-маршрутизація не оминає перевірок сумісності, черги, namespace чи оренди. +Вона лише змінює пріоритет готових завдань, зберігаючи звичайний replay як +запасний шлях. + +## Семантика запасного шляху {#fallback-semantics} + +Холодний replay є обов’язковим запасним шляхом. Він застосовується, коли: + +- sticky execution вимкнено. +- worker не зареєстрував підтримку sticky-кешу. +- завдання не має активних `sticky_worker_id` та `sticky_until`. +- sticky-власник застарів, відсутній, перебуває у drain, перезапущений або замінений під час розгортання. +- sticky-власник витіснив запуск. +- worker, який опитує чергу, не є sticky-власником після спливу прив’язки. + +Діагностика режиму replay: + +- `sticky_hit_expected` — sticky-власник отримав завдання до спливу терміну. +- `cold_replay` — sticky-прив’язка не застосовувалася. +- `forced_cold_replay` — прив’язка існувала, але завдання потребує холодного replay. + +`forced_cold_replay` не означає порушення коректності. Він означає, що sticky +execution не забезпечив очікуваного прискорення replay для цього завдання. + +## Розгортання, drain і заміна worker {#deployment-drain-and-rollout} + +Sticky execution дотримується життєвого циклу worker. Під час drain worker +має припинити отримання нових завдань workflow, водночас завершуючи поточні, +надсилаючи heartbeat, повідомляючи про помилки або дозволяючи чинним орендам +спливти за звичайним контрактом. Коли worker стає застарілим або неактивним, +інші сумісні worker можуть отримати його sticky-завдання після спливу +прив’язки й виконати холодний replay. + +Нові worker не успадковують локальних кешів попередніх процесів. Тому +розгортання може збільшити `forced_cold_replay`, доки нові worker не прогріють +власні кеші. Використовуйте унікальні `worker_id` для кожного процесу worker +або перезапуску, щоб діагностика оператора розрізняла старого власника кешу +й новий процес. + +Сумісність build-id та відбиток визначення workflow і далі визначають, +чи може worker виконувати завдання workflow. Sticky execution не дозволяє +спрямувати несумісний код до запуску. + +## Засоби керування оператора {#operator-controls} + +Sticky execution керується налаштуваннями workflow/runtime та можливостями +worker, а не окремими змінними середовища серверного образу. Конфігурація +пакета workflow надає прапорець увімкнення й TTL прив’язки: + +```php +'workflows' => [ + 'v2' => [ + 'sticky_execution' => [ + 'enabled' => true, + 'ttl_seconds' => 300, + ], + ], +], +``` + +Кожен worker повідомляє місткість кешу під час реєстрації та heartbeat. +Sticky-маршрутизацію можна вимкнути в налаштуваннях runtime або запускати +worker без підтримки sticky-кешу, не змінюючи семантики workflow. Наявні +запуски продовжуються через звичайний холодний replay. + +## Поля протоколу worker {#worker-protocol-fields} + +Worker із підтримкою sticky повідомляють про неї під час реєстрації: + +```json +{ + "worker_id": "orders-worker-01", + "task_queue": "orders", + "runtime": "python", + "sticky_cache_enabled": true, + "sticky_cache_capacity": 100 +} +``` + +Worker повідомляють діагностику кешу через heartbeat: + +```json +{ + "worker_id": "orders-worker-01", + "sticky_cache": { + "enabled": true, + "capacity": 100, + "size": 72, + "hit_count": 940, + "miss_count": 31, + "forced_cold_replay_count": 8, + "eviction_count": 15 + } +} +``` + +Відповіді опитування завдань workflow містять `task.sticky_execution` з +`sticky_worker_id`, `sticky_until`, `replay_mode` та `cache_directive`. +Worker мають трактувати `resume_if_present` як дозвіл використати чинний +прогрітий кеш і виконувати холодний replay, якщо запис відсутній або нечинний. + +## Метрики й діагностика {#metrics-and-diagnostics} + +Метрики оператора містять: + +- `sticky_execution.active_sticky_runs` +- `sticky_execution.ready_sticky_tasks` +- `sticky_execution.leased_sticky_tasks` +- `sticky_execution.hit_expected_last_minute` +- `sticky_execution.miss_last_minute` +- `sticky_execution.forced_cold_replay_last_minute` +- `sticky_execution.cold_replay_last_minute` +- `sticky_execution.hit_rate_last_minute` +- `sticky_execution.miss_rate_last_minute` +- `sticky_execution.capacity_pressure_tasks` + +Окремий сервер також повідомляє в `sticky_execution_workers` місткість і +розмір кешу worker, кількість влучань, промахів, примусових холодних replay, +витіснень кешу та worker із тиском на місткість. + +Трактуйте діагностику так: + +- Низька частка влучань за здорових worker зазвичай означає надто короткий + TTL, часту заміну worker або замалу місткість кешу. +- Висока частка промахів або примусових холодних replay означає, що коректність + збережено, але sticky execution не зменшує вартість replay. +- Тиск на місткість означає, що worker наближаються до заявленої місткості + sticky-кешу або перевищують її й можуть витісняти прогріті запуски. + +## Код, безпечний щодо replay {#replay-safe-code} + +Код workflow має поводитися однаково за sticky-виконання й холодного replay. +Рішення workflow мають спиратися на стійку історію: вхідні дані workflow, +результати activity, timer, signal, update, побічні ефекти й маркери версій. +Проєкції memo та атрибутів пошуку є метаданими оператора. Не обирайте гілки +логіки workflow за їхніми змінюваними значеннями. + +Для коректності не покладайтеся на змінювані глобальні дані, локальні файли, +відкриті сокети, ідентичність об’єктів, випадкові значення, поточний календарний +час або інший локальний стан процесу. Sticky execution може випадково +зберегти їх для одного завдання, а потім втратити після промаху кешу, +перезапуску worker, розгортання або витіснення. + +Використовуйте [`sideEffect(...)`](/docs/features/side-effects) для +недетермінованих значень, які потрібно записати один раз, а звичайні activity — +для зовнішніх побічних ефектів. Контракт replay та стійкої історії наведено в +розділі [Гарантії виконання та ідемпотентність](/docs/constraints/execution-guarantees). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/updates.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/updates.md new file mode 100644 index 0000000000..b4d46e19de --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/updates.md @@ -0,0 +1,120 @@ +--- +sidebar_position: 3 +--- + +# Update {#updates} + +Update дають змогу одночасно отримати інформацію про поточний стан workflow і змінити його. По суті, це поєднання query та signal в одному виклику. + +## Явні команди update {#explicit-update-commands} + +Кожен прийнятий update: + +- Є викликом запит–відповідь до активного workflow. `attemptUpdate*()` очікує на завершення обробника, а `submitUpdate*()` повертається відразу після стійкого прийняття команди й надає `inspectUpdate($updateId)` для подальшого опитування. +- Використовує оголошену стійку назву update в історії команд і маршрутизації webhook. `#[UpdateMethod('mark-approved')]` зберігає публічну назву виклику за перейменування PHP-методу. +- Відхиляється для історичних або вже закритих запусків замість непомітної зміни поточного запуску. +- До виконання обробника відхиляє неоголошені назви методів як `rejected_unknown_update`, а аргументи, що порушують контракт, як `rejected_invalid_arguments` із машиночитаними `validation_errors`. + +Як і query, update спочатку відтворюють зафіксовану історію. На відміну від query, update можуть змінювати безпечний щодо replay стан workflow і повертати значення. + +Щоб визначити метод update у workflow, використовуйте анотацію `UpdateMethod`. Необов’язковий рядковий аргумент фіксує публічну стійку назву, яка зберігається за перейменування PHP-методу: + +```php +use Workflow\UpdateMethod; +use Workflow\V2\Workflow; + +final class MyWorkflow extends Workflow +{ + private bool $ready = false; + + #[UpdateMethod('mark-ready')] + public function updateReady(bool $ready): bool + { + $this->ready = $ready; + + return $this->ready; + } +} +``` + +Викликайте метод update безпосередньо, якщо потрібне лише повернене значення: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$ready = $workflow->updateReady(true); +``` + +Прямий PHP-виклик і далі використовує назву методу. Стійка ціль команди залишається `mark-ready`. + +Використовуйте `attemptUpdate()`, якщо також потрібен результат стійкої команди. Передайте йому стійку назву update: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123'); + +$result = $workflow->attemptUpdate('mark-ready', true); + +$result->accepted(); // true +$result->completed(); // true when the update body ran successfully +$result->updateStatus(); // "accepted", "completed", "failed", or "rejected" +$result->updateId(); // Durable update lifecycle id +$result->result(); // Raw update return value when completed +$result->failureMessage(); // Failure message when the update body threw +``` + +`attemptUpdate()` спочатку записує прийнятий update, а потім очікує, доки worker workflow застосує його й закриє життєвий цикл update. Час очікування обмежено `workflows.v2.update_wait.completion_timeout_seconds`. Якщо до спливу бюджету worker не закрив update, `attemptUpdate()` повертає прийнятий життєвий цикл із `waitTimedOut() === true` та `updateStatus() === 'accepted'`. + +Використовуйте `withUpdateWaitTimeout()`, якщо одному виклику потрібен інший бюджет очікування завершення: + +```php +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::load('order-123') + ->withUpdateWaitTimeout(5); + +$result = $workflow->attemptUpdate('mark-ready', true); +``` + +Використовуйте `attemptUpdateWithArguments()`, якщо код виклику вже має позиційний список або мапу іменованих параметрів: + +```php +$result = $workflow->attemptUpdateWithArguments('mark-ready', [ + 'ready' => true, +]); +``` + +Іменовані мапи перевіряються за стійко зафіксованим контрактом update й нормалізуються в порядку оголошення до прийняття. + +Використовуйте `submitUpdate()` або `submitUpdateWithArguments()`, якщо потрібне лише стійке прийняття, а worker workflow може застосувати update пізніше: + +```php +$accepted = $workflow->submitUpdate('mark-ready', true); + +$accepted->accepted(); // true +$accepted->completed(); // false +$accepted->updateStatus(); // "accepted" +$accepted->result(); // null until the worker records UpdateCompleted +``` + +Використовуйте `inspectUpdate()`, якщо вже маєте `update_id` і хочете пізніше прочитати збережений життєвий цикл без повторного очікування: + +```php +$latest = $workflow->inspectUpdate($accepted->updateId()); + +$latest->updateStatus(); // "accepted" +$latest->closedAt(); // null until the lifecycle closes +``` + +`inspectUpdate()` не очікує на виконання workflow. Він повторно завантажує збережений стійкий життєвий цикл і повертає поточний `UpdateResult`. + +Правила update: + +- `load($instanceId)` оновлює найновіший стійкий запуск екземпляра, зокрема після continue-as-new. +- `loadRun($runId)` відхиляється з `rejected_not_current`, щойно вибраний запуск стає історичним. +- Закриті запуски відхиляють update з `rejected_not_active`. +- Помилки тіла update не закривають запуск workflow. Вони записуються як помилки рівня update й залишають запуск відкритим для наступного завдання replay. +- Query відтворюють завершені update, а відхилені чи неуспішні update залишаються лише фактами команд та історії, які не відтворюються. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md new file mode 100644 index 0000000000..0b3c33df69 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/versioning.md @@ -0,0 +1,235 @@ +--- +sidebar_position: 15 +--- + +# Керування версіями {#versioning} + +Workflow можуть працювати довго, іноді місяцями або роками, тому часто потрібно змінювати визначення workflow, поки виконання ще тривають. Без керування версіями зміна коду workflow, яка впливає на шлях виконання, спричиняла б помилки недетермінізму під час replay. + +Функція `getVersion()` дає змогу безпечно змінювати активні workflow, створюючи точки розгалуження з версіями. + +## Використання `getVersion` {#using-getversion} + +`getVersion()` — це безпечна щодо replay функція для послідовного коду. Під час першого виконання кожна точка зміни записує стійкий маркер версії запуску. Кожен подальший replay використовує це зафіксоване значення замість повторного визначення гілки за поточним кодом. Старі запуски, що передують новій точці розгалуження, консервативно отримують `DEFAULT_VERSION`. + +```php +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; +use function Workflow\V2\{activity, getVersion}; + +final class MyWorkflow extends Workflow +{ + public function handle(): void + { + $version = getVersion( + 'my-change-id', + WorkflowStub::DEFAULT_VERSION, + 1 + ); + + if ($version === WorkflowStub::DEFAULT_VERSION) { + activity(OldActivity::class); + } else { + activity(NewActivity::class); + } + } +} +``` + +## Як це працює {#how-it-works} + +Метод `getVersion()` приймає три параметри: + +- **changeId** — унікальний ідентифікатор точки зміни +- **minSupported** — найменша версія, яку цей код ще підтримує +- **maxSupported** — найбільша, поточна версія для нових виконань + +Коли workflow доходить до `getVersion()`: + +- **Нові виконання** додають типізовану подію історії `VersionMarkerRecorded` із `change_id`, вибраною `version` і підтримуваним діапазоном, а потім повертають `maxSupported` +- **Виконання під час replay** повертають раніше записану версію з цього маркера історії +- **Запуски без маркера** переходять до `WorkflowStub::DEFAULT_VERSION`, коли runtime визначає, що гілка передує поточному визначенню workflow. Спочатку він перевіряє маркер сумісності запуску відносно поточного worker, а потім порівнює стійко зафіксований `workflow_definition_fingerprint` запуску з поточним доступним для завантаження класом workflow. Якщо відбитки збігаються, запуск використовує те саме визначення й записує новий маркер. Якщо ні, запуск передує зміні й застосовується запасний варіант. Запуски, чия історія `WorkflowStarted` передує фіксації відбитка та не містить його, консервативно отримують `DEFAULT_VERSION`, оскільки runtime не може довести незмінність визначення. Це зафіксована політика 2.0 для запусків без відбитка: runtime не блокує отримання завдання лише через відсутність записаного відбитка в старого запуску. Запасний варіант не додає штучного маркера й не споживає нового кроку workflow +- **Методи query** відтворюють той самий зафіксований маркер версії перед викликом анотованого методу, тому query бачать ту саму гілку, що й завдання workflow +- **Waterline** показує записані маркери в хронології вибраного запуску з `version_change_id`, `version`, `version_min_supported` та `version_max_supported`. Деталі вибраного запуску також містять `workflow_definition_fingerprint`, `workflow_definition_current_fingerprint` і `workflow_definition_matches_current`, щоб оператори бачили, коли тривалий запуск почався зі старішим визначенням, навіть до фіксації маркера версії + +Це дає новим workflow змогу використовувати найновіший шлях коду, поки наявні workflow продовжують використовувати початковий. + +## Додавання нової версії {#adding-a-new-version} + +Припустімо, наявний workflow викликає `prePatchActivity`: + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\activity; + +final class MyWorkflow extends Workflow +{ + public function handle() + { + $result = activity(PrePatchActivity::class); + + return $result; + } +} +``` + +Щоб замінити його на `postPatchActivity` без порушення активних workflow: + +```php +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; +use function Workflow\V2\{activity, getVersion}; + +final class MyWorkflow extends Workflow +{ + public function handle() + { + $version = getVersion( + 'activity-change', + WorkflowStub::DEFAULT_VERSION, + 1 + ); + + $result = $version === WorkflowStub::DEFAULT_VERSION + ? activity(PrePatchActivity::class) + : activity(PostPatchActivity::class); + + return $result; + } +} +``` + +Під час розгортання нової точки розгалуження `getVersion()` продовжуйте змінювати `DW_V2_CURRENT_COMPATIBILITY` для цієї хвилі збірок. Runtime насамперед використовує `workflow_definition_fingerprint`, зафіксований на початку запуску, щоб визначити, чи відсутній маркер версії належить новому виконанню або старішому запуску, який має залишитися на `DEFAULT_VERSION`. Маркер сумісності залишається важливим для маршрутизації до сумісних worker і перевіряється перед відбитком. Запуски без записаного відбитка консервативно залишаються на `DEFAULT_VERSION`, оскільки runtime не може перевірити незмінність визначення. + +## Додавання наступних версій {#adding-more-versions} + +Для подальших змін збільшуйте `maxSupported`: + +```php +$version = getVersion( + 'activity-change', + WorkflowStub::DEFAULT_VERSION, + 2 +); + +$result = match($version) { + WorkflowStub::DEFAULT_VERSION => activity(PrePatchActivity::class), + 1 => activity(PostPatchActivity::class), + 2 => activity(AnotherPatchActivity::class), +}; +``` + +## Припинення підтримки старих версій {#deprecating-old-versions} + +Після завершення всіх workflow, які використовують стару версію, можна припинити її підтримку, збільшивши `minSupported`. Тоді більше не потрібно підтримувати старі шляхи коду. + +```php +// After all DEFAULT_VERSION workflows have completed: +$version = getVersion( + 'activity-change', + 1, // No longer supporting DEFAULT_VERSION + 2 +); + +$result = match($version) { + 1 => activity(PostPatchActivity::class), + 2 => activity(AnotherPatchActivity::class), +}; +``` + +Якщо workflow із версією, старішою за `minSupported`, намагається виконати replay, виникає `VersionNotSupportedException`. Це також стосується запусків зі старішою сумісністю, чий безпечний запасний варіант досі є `WorkflowStub::DEFAULT_VERSION`. + +Якщо випадково повторно використати той самий `changeId` для іншої точки розгалуження, runtime вважатиме це помилкою детермінізму, а не непомітно прийме невідповідність. Зберігайте один стабільний `changeId` для кожної логічної зміни коду. + +## Кілька точок зміни {#multiple-change-points} + +Для незалежних змін можна використовувати кілька викликів `getVersion()` в одному workflow: + +```php +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; +use function Workflow\V2\getVersion; + +final class MyWorkflow extends Workflow +{ + public function handle(): void + { + $version1 = getVersion('change-1', WorkflowStub::DEFAULT_VERSION, 1); + $version2 = getVersion('change-2', WorkflowStub::DEFAULT_VERSION, 1); + + // Each change point is tracked independently + } +} +``` + +**Важливо:** кожен `changeId` має бути унікальним у межах workflow. Вибрана версія записується в типізовану історію workflow й детерміновано відтворюється в подальших завданнях workflow, query та деталях Waterline. Якщо Waterline не показує `VersionMarkerRecorded` для точки зміни в старішому запуску, replay залишився на старому шляху `DEFAULT_VERSION` без додавання нового маркера до наявної історії. Поля деталей вибраного запуску `workflow_definition_fingerprint`, `workflow_definition_current_fingerprint` та `workflow_definition_matches_current` показують, чи почався запуск з іншим визначенням workflow, ніж те, яке може завантажити поточна збірка. + +## Скорочений виклик `patched()` {#patched-shorthand} + +`patched($changeId)` — це скорочений виклик із двома станами для звичного питання: чи перейшов запуск через одноразову зміну коду? Він записує ту саму стійку подію історії `VersionMarkerRecorded`, що й `getVersion()`, але повертає булеве значення замість цілого числа. + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\{activity, patched}; + +final class MyWorkflow extends Workflow +{ + public function handle(): string + { + if (patched('use-new-payment-activity')) { + return activity(NewPaymentActivity::class); + } + + return activity(LegacyPaymentActivity::class); + } +} +``` + +Поведінка: + +- Нові запуски фіксують маркер, обирають нову гілку, а `patched()` повертає `true`. +- Запуски, які почалися до додавання цього `changeId`, залишаються на `DEFAULT_VERSION` за тією самою перевіркою відбитка, що й у `getVersion()`, а `patched()` повертає `false`. +- Replay обох типів читає раніше зафіксований маркер і повертає те саме значення, тому рішення про гілку є стійким. + +`patched()` точно еквівалентний `getVersion($changeId, DEFAULT_VERSION, 1) === 1`. Використовуйте його, коли є лише дві гілки й не потрібно зберігати стару гілку назавжди. Булевий виклик читається простіше, ніж `match` за `DEFAULT_VERSION` та `1`. + +Якщо потрібні понад дві гілки або згодом планується ще одна версія тієї самої логічної зміни, використовуйте `getVersion()` із явним `maxSupported`. Перехід від `patched()` до `getVersion()` для того самого `changeId` є помилкою детермінізму, оскільки наявний маркер записано з `maxSupported = 1`. + +## Видалення старої гілки через `deprecatePatch()` {#removing-the-legacy-branch-with-deprecatepatch} + +Коли всі старі запуски для точки зміни `patched()` завершилися, стару гілку можна видалити з коду workflow. Сумісний спосіб — замінити виклик `patched()` на `deprecatePatch()`, а не видаляти його повністю. + +```php +use Workflow\V2\Workflow; +use function Workflow\V2\{activity, deprecatePatch}; + +final class MyWorkflow extends Workflow +{ + public function handle(): string + { + deprecatePatch('use-new-payment-activity'); + + return activity(NewPaymentActivity::class); + } +} +``` + +`deprecatePatch()` зберігає точку зміни на хронології workflow, щоб уже зафіксовані події історії `VersionMarkerRecorded` і далі відповідали відомому виклику. Він повертає `null` і безумовно обирає новий шлях. Після випуску цієї версії: + +- Нові запуски фіксують маркер `deprecate_patch`, обирають нову гілку, а виклик `deprecatePatch()` повертає `null`. +- Наявні запуски, які вже зафіксували маркер `patched` для цього `changeId`, продовжують відтворювати його й проходять через гілку після припинення підтримки старого коду, яка тепер є єдиною, без помилки. +- Передбачається, що наявні запуски, які зафіксували `false` для `patched()` і використовували старий шлях, уже не активні. Якщо такий запуск досі працює й виконує replay, workflow прочитає старий маркер, але виконає нову гілку. Тому безпечний порядок — дочекатися завершення старих запусків перед розгортанням `deprecatePatch()`. + +Двоетапний життєвий цикл patch і правила розміщення: + +1. **Додайте `patched()`** у точці зміни. Обидві гілки залишаються в коді workflow. Нові запуски обирають нову гілку, а старі продовжують використовувати стару. +2. **Дочекайтеся завершення всіх старих запусків.** За допомогою пошуку запусків Waterline або `dw workflow:list` підтвердьте відсутність відкритих запусків, які можуть відтворювати стару гілку. +3. **Замініть `patched()` на `deprecatePatch()`** у тому самому місці з тим самим `changeId` і видаліть стару гілку з тіла функції. Залиште виклик, щоб стійка історія й далі відповідала відомій точці зміни. +4. **Необов’язково: повністю видаліть `deprecatePatch()`**, коли впевнені, що жоден історичний replay для query, експорту або аудиту більше не читатиме історію з цим маркером. Це незворотний крок. Більшість workflow можуть залишати `deprecatePatch()` необмежено довго без витрат під час виконання. + +Правила розміщення: + +- `patched()` та `deprecatePatch()` слід викликати з тіла функції workflow, а не з activity, обробників signal або методів query. Це кроки workflow, які мають відтворюватися детерміновано. +- Кожен `changeId` є однією логічною точкою зміни. Повторне використання того самого `changeId` для іншої гілки є помилкою детермінізму. +- Протягом життєвого циклу `changeId` може використовуватися у формі `patched()` або `deprecatePatch()`, але ніколи в обох одночасно. Розгортання, яке змінює виклик, має замінити його, а не залишати обидва. +- `getVersion()` та `patched()` не можуть співіснувати для того самого `changeId`. Оберіть одну модель під час додавання точки зміни. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md new file mode 100644 index 0000000000..e95b789799 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md @@ -0,0 +1,170 @@ +--- +sidebar_position: 24 +title: Сесії worker +description: Прив’язуйте послідовність стійких спроб activity до однієї оренди сесії worker для GPU, файлової системи або локального стану процесу. +tags: + - activities + - workers + - affinity + - operations +keywords: + - сесії worker + - прив’язка activity + - worker GPU + - worker з локальною файловою системою + - сесії протоколу worker +--- + +# Сесії worker {#worker-sessions} + +Сесії worker — це примітив прив’язки activity у v2. Використовуйте їх, коли +кілька стійких кроків activity мають повторно використовувати локальний стан +процесу, пам’ять GPU, змонтовану файлову систему або інший локальний ресурс worker. + +## Стислий контракт {#contract-summary} + +- У кожен момент сесія має одного власника оренди. +- Перша допущена activity у сесії створює або повторно отримує оренду. +- Heartbeat activity поновлюють оренду спроби activity та оренду сесії. +- `POST /api/worker/sessions`, `POST /api/worker/sessions/{sessionId}/heartbeat` + та `DELETE /api/worker/sessions/{sessionId}` надають явні операції життєвого + циклу для зовнішніх worker. +- Спочатку виконується маршрутизація черг. Допуск до сесії worker відбувається + після реєстрації worker у черзі завдань. +- `capabilities` реєстрації worker мають задовольняти всі вимоги сесії. +- Прострочені сесії та сесії без власника видимі операторам і можуть бути + отримані повторно, якщо сесія дозволяє відновлення після збою. + +## API для написання коду {#authoring-api} + +Автори PHP workflow створюють дескриптор сесії через `Workflow::workerSession()` +або `Workflow\V2\workerSession()` і планують activity через цей дескриптор: + +```php +use Workflow\V2\Support\WorkerSessionOptions; +use Workflow\V2\Workflow; + +$session = Workflow::workerSession( + 'gpu-render', + new WorkerSessionOptions( + queue: 'gpu-activities', + requirements: ['gpu:nvidia-l4'], + leaseSeconds: 120, + ttlSeconds: 1800, + maxConcurrentActivities: 1, + ), +); + +$frames = $session->activity(RenderFramesActivity::class, $videoId); +$manifest = $session->activity(AssembleManifestActivity::class, $frames['path']); +``` + +Знімок параметрів activity зберігає той самий контракт у +`activity_options.worker_session`. Зовнішні runtime використовують таку саму +структуру JSON у полі `worker_session` команди завдання workflow `schedule_activity`. + +```json +{ + "type": "schedule_activity", + "activity_type": "media.render-frames", + "worker_session": { + "session_id": "gpu-render", + "queue": "gpu-activities", + "requirements": ["gpu:nvidia-l4"], + "lease_seconds": 120, + "ttl_seconds": 1800, + "max_concurrent_activities": 1, + "create_if_missing": true, + "allow_reacquire_after_failure": true + } +} +``` + +## Життєвий цикл {#lifecycle} + +Сесія створюється ліниво. Шар підбору завдань створює або повторно отримує +сесію, коли перше завдання activity у сесії допускається до worker із потрібними +можливостями. Worker також може явно створити сесію перед опитуванням через +`POST /api/worker/sessions`. + +Активна сесія завершується після закриття власником, спливу оренди сесії, +спливу абсолютного TTL або виявлення збою worker-власника. Сплив TTL і явне +закриття є термінальними для цього ID сесії. Після спливу оренди або виявлення +відсутності власника сесію можна отримати повторно, якщо +`allow_reacquire_after_failure` має значення true. + +## Оренда й володіння {#lease-and-ownership} + +Одночасно орендою сесії володіє щонайбільше один worker. Сервер допускає +завдання activity у сесії лише за виконання однієї з умов: + +- сесія відсутня, а `create_if_missing` має значення true; +- активною сесією вже володіє worker, який опитує чергу; +- сесія прострочена, завершилася збоєм або втратила власника, а повторне отримання дозволено. + +Володіння сесією не замінює оренди окремої спроби activity. Кожна спроба +activity має власні `activity_attempt_id`, власника оренди, heartbeat, +шляхи завершення, помилки, тайм-ауту й скасування. + +## Допуск і маршрутизація {#admission-and-routing} + +Сесії worker беруть участь у звичайній маршрутизації черг. `WorkerSessionOptions` +може задавати `connection` та `queue`, а `ActivityOptions` окремого виклику +може їх перевизначити. Сервер перевіряє допуск до конкретної сесії після +звичайного допуску до черги завдань: + +- `capabilities` реєстрації worker мають покривати всі вимоги сесії; +- `max_concurrent_activities` обмежує кількість орендованих спроб activity + у сесії; +- `max_concurrent_worker_sessions` обмежує кількість активних оренд сесій, + які утримує одна реєстрація worker. + +Маршрутизація для конкретної групи worker використовує звичайні рядки +можливостей, наприклад `gpu:nvidia-l4`, `gpu:a100`, `fs:/mnt/models` або +`zone:us-east-1a`. + +## Збої, скасування й зупинка {#failure-cancellation-and-shutdown} + +Heartbeat activity поновлюють оренду спроби activity та оренду сесії. Worker +також можуть поновлювати сесію безпосередньо через протокол worker. Після +спливу оренди сесія стає `expired`. Якщо heartbeat реєстрації worker-власника +застарів або відсутній, сесія стає `orphaned`. + +Якщо worker-власник гине посеред послідовності, активні activity зберігають +звичайну семантику at-least-once: оренди спроб спливають, відновлення робить +їх знову доступними для отримання, а застаріле завершення може бути відхилене. +Worker із потрібними можливостями може повторно отримати сесію, якщо контракт +це дозволяє. Автори workflow мають передбачити відновлення локального стану +процесу після повторного отримання. + +Скасування workflow також передається у відповідях heartbeat activity. Оренда +сесії ніколи не дозволяє activity у сесії ігнорувати `cancel_requested`. +Перед запланованою зупинкою процесу worker має закрити свої сесії через +`DELETE /api/worker/sessions/{sessionId}`. Активні activity і далі завершуються, +повідомляють помилку, скасовуються або спливають за власними орендами спроб. + +## Видимість {#visibility} + +Можливості протоколу worker повідомляють `worker_session_verbs` і контракт +runtime `worker_sessions` через `GET /api/cluster/info` та кожну відповідь +площини worker. Машиночитаний контракт опубліковано в +[`worker-sessions-runtime.schema.json`](/platform-protocol-specs/worker-sessions-runtime.schema.json) +і включено до каталогу +[Platform Protocol Specs](/docs/platform-protocol-specs#worker-session-runtime-notes). + +Оператори можуть переглядати активні, закриті, прострочені, неуспішні сесії +та сесії без власника через `GET /api/worker-sessions`. Деталі містять ID сесії, +власника, чергу, вимоги, сплив оренди, сплив TTL, кількість активних activity +та причину збою. Системні метрики оператора містять кількість сесій кожного стану. + +## Коли обирати сесії worker {#choosing-worker-sessions} + +Обирайте звичайні activity у черзі, коли кожен крок незалежний. Обирайте одну +більшу activity, коли вся операція є одним атомарним побічним ефектом. +Використовуйте сесію worker лише тоді, коли кілька стійких кроків activity +мають повторно використовувати локальний ресурс worker, а workflow допускає +відновлення цього ресурсу після збою worker. + +Як сесії worker пов’язані зі звичайними activity у черзі, локальними activity +та sticky execution, описано в розділі +[Модель виконання activity](/docs/features/activity-execution-model). From 13cfaef7a4b1031de32edb3e1a86bdef23664b07 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 02:58:16 +0000 Subject: [PATCH 04/10] docs: translate Ukrainian capability guides and fix Unicode search --- docs/capabilities.md | 4 +- docs/contribute-a-sample.md | 4 +- .../current/ai-assisted-development.md | 125 ++ .../current/capabilities.md | 120 ++ .../current/contribute-a-sample.md | 150 ++ .../current/features/webhooks.md | 1207 +++++++++++++++++ .../current/polyglot/avro-value-protocol.md | 111 ++ .../current/polyglot/cancellation.md | 106 ++ .../current/polyglot/cli-python-parity.md | 103 ++ .../current/polyglot/deployment-modes.md | 162 +++ .../polyglot/portable-worker-affinity.md | 122 ++ .../current/sdk-neutrality.md | 210 +++ package.json | 2 +- patches/lunr+2.3.9.patch | 13 + scripts/check-local-search.test.js | 25 + 15 files changed, 2460 insertions(+), 4 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/ai-assisted-development.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/capabilities.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/contribute-a-sample.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/features/webhooks.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/avro-value-protocol.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cancellation.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-python-parity.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/deployment-modes.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/portable-worker-affinity.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/sdk-neutrality.md create mode 100644 patches/lunr+2.3.9.patch create mode 100644 scripts/check-local-search.test.js diff --git a/docs/capabilities.md b/docs/capabilities.md index 5dbab883e5..1aeb8a558c 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -79,7 +79,7 @@ implied to have parity. | Deterministic parallel composition | PHP `all()`/`parallel()`, Python nested list-yield, and Rust `WorkflowContext::parallel()`/`join()` schedule activity, child-workflow, timer, mixed, and nested groups through ordinary commands. All three publish the shared group identity/path metadata and restore results in nested input order. | [Concurrency](/docs/features/concurrency/), [Python SDK](/docs/polyglot/python/#fan-out), [Rust SDK](/docs/polyglot/rust/#deterministic-parallel-groups) | | Durable first-completion selection | Embedded PHP and the PHP, Python, and Rust service SDKs start independent activity, child-workflow, timer, external-input wait, and nested-group members. One winner is persisted with stable member identity; non-winners continue and retain await/cancel handles. | [Concurrency](/docs/features/concurrency/#first-completion-selection), [Python SDK](/docs/polyglot/python/#first-completion-selection), [Rust SDK](/docs/polyglot/rust/#durable-first-completion-selection) | | Saga compensation | PHP's Saga helpers, Python `WorkflowContext.saga()`, and Rust `WorkflowContext::saga()` register durable compensations after forward success and unwind sequentially in reverse order by default. Python and Rust stop at the first compensation failure and preserve the initiating plus compensation failures; PHP additionally exposes opt-in parallel and continue-on-error policies. | [Sagas](/docs/features/sagas/), [Python SDK](/docs/polyglot/python/#saga-compensation), [Rust SDK](/docs/polyglot/rust/#saga-compensation) | -| Cancellation | Service-mode PHP, Python, and Rust clients expose immediate terminal cancellation with selected-run safety and typed outcomes. Embedded Laravel separately supports a cooperative cancellation request with bounded cleanup; Server and service-mode SDKs do not yet expose that request. | [Cancel and terminate](/docs/features/cancel-and-terminate/), [Rust SDK](/docs/polyglot/rust/) | +| Cancellation | Embedded Laravel and service-mode PHP, Python, and Rust support cooperative cancellation requests with one root identity, an immutable deadline, and bounded cleanup on explicitly enabled workers. Immediate terminal cancellation remains available with selected-run safety and typed outcomes. | [Cancel and terminate](/docs/features/cancel-and-terminate/), [Service-mode cancellation](/docs/polyglot/cancellation/), [Rust SDK](/docs/polyglot/rust/) | | Termination | Termination closes a run immediately with a distinct terminal outcome; it is exposed through Cloud and self-hosted Server service runtimes, CLI, PHP, Python, and Rust control surfaces. | [Cancel and terminate](/docs/features/cancel-and-terminate/), [Server API](/docs/polyglot/server-api-reference/) | | Side effects | PHP, Python, and Rust record non-deterministic values exactly once and decode the recorded value on replay. | [Side effects](/docs/features/side-effects/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | | Workflow Streams | PHP, Python, and Rust expose typed list, describe, offset subscription/resume, append, close, and error operations for Server-owned run-scoped output streams. All three author replay-safe emits from a durable command identity. Python resolves external payloads with its configured storage driver; PHP and Rust preserve opaque references. | [Workflow Streams](/docs/polyglot/workflow-streams/), [exact SDK matrix](/workflow-stream-capabilities.json), [runtime scenarios](/platform-conformance/workflow-stream-runtime-scenarios.json) | @@ -90,7 +90,7 @@ implied to have parity. | Search attributes | Cloud and self-hosted Server service runtimes index typed search attributes. PHP and Python expose authoring/control surfaces; CLI and operator APIs expose structured discovery and filtering. | [Search attributes](/docs/features/search-attributes/), [Python SDK](/docs/polyglot/python/) | | Worker compatibility | SDKs register runtime, SDK version, build ID, supported types, protocol version, and capacity; the server publishes accepted versions and routing facts. | [Compatibility](/docs/compatibility/), [Worker compatibility and routing](/docs/polyglot/worker-compatibility-routing/) | | Local activities | Available in embedded Laravel, PHP and Python service SDKs, and Rust SDK 3.1.0 with explicit worker opt-in. | [Embedded activity execution](/docs/features/activity-execution-model/), [Service-mode support matrix](/docs/polyglot/portable-worker-affinity/) | -| Worker sessions | Available in embedded Laravel, PHP and Python service SDKs. Rust remains unsupported. | [Worker Sessions](/docs/features/worker-sessions/), [Service-mode support matrix](/docs/polyglot/portable-worker-affinity/) | +| Worker sessions | Available in embedded Laravel, PHP and Python service SDKs, and Rust SDK 3.2.0 with explicit worker opt-in. | [Worker Sessions](/docs/features/worker-sessions/), [Service-mode support matrix](/docs/polyglot/portable-worker-affinity/) | | Sticky execution | Available in embedded Laravel and the PHP service SDK. Python and Rust remain unsupported. | [Sticky Execution](/docs/features/sticky-execution/), [Service-mode support matrix](/docs/polyglot/portable-worker-affinity/) | | Codec interoperability | PHP, Python, and Rust use the public `codec` + `blob` envelope and one fixed recursive Avro Value schema. Named branches preserve integers versus doubles, text versus bytes, booleans versus integers, and lists versus maps. | [Avro Value protocol](/docs/polyglot/avro-value-protocol/), [Worker protocol](/docs/polyglot/worker-protocol/), [Rust SDK](/docs/polyglot/rust/) | | Diagnostics | Service runtimes and CLI publish version, protocol, worker, task-queue, replay, history, typed failure, and repair facts as JSON. Managed Waterline, a separately deployed self-hosted service, or the embedded package presents evidence from its owning runtime. | [Monitoring](/docs/monitoring/), [CLI reference](/docs/polyglot/cli-reference/), [Waterline operator API](/docs/waterline-operator-api/) | diff --git a/docs/contribute-a-sample.md b/docs/contribute-a-sample.md index 0f2326eba7..c0c266d32d 100644 --- a/docs/contribute-a-sample.md +++ b/docs/contribute-a-sample.md @@ -93,7 +93,9 @@ Three docs surfaces move when a sample lands: sample-app docs page is the canonical list; an entry that names a surface but does not link to a sample is treated as a gap. -These three changes ship in the same PR as the sample workflow itself. +Submit the sample and README changes in the sample-app PR, and the gallery +and pattern-page changes in a linked docs-site PR. Maintainers land the linked +changes together. A sample that lands without a docs-site mirror is in `gap` state for the upstream-coverage tracker until the docs PR catches up, so contributors are encouraged to ship them together. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/ai-assisted-development.md b/i18n/uk/docusaurus-plugin-content-docs/current/ai-assisted-development.md new file mode 100644 index 0000000000..e97befb659 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/ai-assisted-development.md @@ -0,0 +1,125 @@ +--- +sidebar_position: 9 +title: Розробка за допомогою ШІ +description: Використовуйте документацію v2, MCP, результат CLI й експорт історії Durable Workflow як стабільні інтерфейси для агентів програмування. +tags: + - ai + - agents + - llms +keywords: + - документація Durable Workflow для ШІ + - llms.txt + - асистенти програмування на основі ШІ +--- + +# Розробка за допомогою ШІ {#ai-assisted-development} + +Durable Workflow v2 дає людині змогу вивчити модель стійкого виконання, +а потім передати повторювані перевірки й операції інструментам. Важливий +інтерфейс продукту — це не підказка для чату, а набір стабільних точок доступу, +які агент може читати, викликати й цитувати без припущень. + +## Джерела документації {#retrieval-surfaces} + +Використовуйте ці файли для LLM, щоб надати ШІ-асистенту машиночитану +документацію Durable Workflow: + +| Джерело | URL | Призначення | +| --- | --- | --- | +| канонічний маніфест | `https://durable-workflow.com/llms.txt` | Індекс стабільної документації 2.0. Відповідає типовому шляху Docs сайту без версії. | +| канонічний повний набір | `https://durable-workflow.com/llms-full.txt` | Документація в одному файлі, що відповідає канонічному маніфесту. | +| маніфест v1 | `https://durable-workflow.com/llms-1.x.txt` | Архів із зафіксованою версією 1.x. | +| повний набір v1 | `https://durable-workflow.com/llms-full-1.x.txt` | Повний стабільний набір із зафіксованою версією 1.x. | +| маніфест v2 | `https://durable-workflow.com/llms-2.0.txt` | Індекс із зафіксованою версією 2.0, еквівалентний канонічному маніфесту. | +| повний набір v2 | `https://durable-workflow.com/llms-full-2.0.txt` | Повний набір із зафіксованою версією 2.0. | + +Типові підказки для агентів мають завантажувати `/llms.txt` або +`/llms-full.txt` для роботи зі стабільною 2.0. Для підтримки застосунку 1.x +фіксуйте URL із суфіксом `-1.x.txt`. + +## Локальний інтерфейс MCP {#local-mcp-surface} + +[Sample app](/docs/sample-app) надає Laravel MCP-сервер за +`/mcp/workflows`. Це еталонна інтеграція ШІ-клієнта для локальної розробки +workflow v2. Сторінка [Інтерфейс MCP Workflow](/docs/mcp-workflows) визначає +кінцеву точку, набір інструментів, безпечні базові перевірки workflow +та структуру звіту. + +MCP-сервер надає агенту структуровані операції workflow замість зчитування UI: + +| Інструмент | Стабільна операція | +| --- | --- | +| `list_workflows` | Знайти налаштовані ключі workflow, вимоги до облікових даних, значення станів і нещодавні запуски. | +| `start_workflow` | Запустити налаштований workflow v2 й отримати `workflow_id`, `run_id`, стан, бізнес-ключ і результат команди. | +| `get_workflow_result` | Опитати поточний або вибраний запуск щодо стану, результату, метаданих видимості й останньої помилки. | +| `get_workflow_history` | Отримати обмежений кінець типізованої історії v2 та нещодавні стійкі помилки. | + +Використовуйте `simple` або `elapsed` для базових перевірок без облікових +даних. Запускайте workflow із зовнішніми обліковими даними лише після +налаштування потрібних ключів у локальному середовищі. + +Коли агент редагує повторювані workflow ШІ або введення користувачем, +спрямуйте його до контракту v2 [Потоки повідомлень](/docs/features/message-streams). +Стабільний шаблон — `Workflow::inbox()` / `Workflow::outbox()` / +`MessageStream`. Прямі записи через `MessageService`, `WorkflowMessage` +або рядки курсорів є внутрішніми деталями runtime, а не шаблонами прикладів. + +## Контракти команд і діагностики {#command-and-diagnostic-contracts} + +Агентам слід віддавати перевагу машиночитаним інтерфейсам перед знімками екрана або текстовими описами: + +- [Робочий цикл агента](/docs/agent-operating-loop) поєднує документацію v2, + кінцеву точку MCP, JSON від `dw`, експорт історії Waterline й довідники SDK + в повторюваний цикл пошуку, зміни, запуску й діагностики. +- [Контракт інструментів агента](/docs/agent-tooling-contract) визначає, + як інструменти MCP, JSON CLI, діагностика сервера, експорт Waterline + та фікстури SDK утворюють спільний програмно керований інтерфейс. +- Команди `dw` надають стабільні коди виходу для автоматизації. Див. + [довідник CLI](/docs/polyglot/cli#exit-codes). +- [Можливості клієнтів і worker](/docs/polyglot/cli-python-parity/) + порівнюють життєвий цикл, повідомлення, розклади, видимість і виконання worker + у `dw` та всіх трьох офіційних SDK зі спільними доказами запитів там, + де вони доступні. +- [Інтерфейс зовнішнього виконання](/docs/polyglot/external-execution) + публікує межу завдання рівня activity, вимоги до носія виконання, результати + мосту й шляхи конвертів входу та результату через `/api/cluster/info`. +- Команди health, info, namespace, workflow, schedule, worker і task-queue + сервера є відповідними інструментами перевірок із shell. +- Експорт історії Waterline є джерелом для replay та діагностики помилок. + Він містить типізовані події історії, метадані джерел проєкцій, перевірки + цілісності, контекст вибраного запуску, очікування, timer, походження + й останні стійкі помилки. +- Типи й сигнатури методів Python SDK наведено в згенерованому + [довіднику Python API](https://python.durable-workflow.com/). + +Щоб агент міг пояснити завислий workflow, спочатку зберіть такі факти: + +1. `workflow_id` and `run_id` +2. стан поточного запуску й опис останньої помилки +3. нещодавні типізовані події історії +4. відкриті очікування, timer та очікувані завдання +5. стан worker і черги завдань +6. відповідну версію документації: `2.0` для поточної роботи з продуктом, + а `1.x` — лише для підтримки старої версії + +Цього набору достатньо, щоб відрізнити помилку стійкого workflow від збою +runtime worker, відсутніх зовнішніх облікових даних, недоступної черги +або дії оператора, яка очікує на схвалення. + +## Структура підказки {#prompt-shape} + +Надавайте агентам програмування явні обмеження й точки доступу: + +```text +Use Durable Workflow docs from https://durable-workflow.com/llms-full-2.0.txt. +Use the sample app MCP endpoint at /mcp/workflows for workflow discovery, +start, result, and history. Prefer dw JSON/exit-code contracts and Waterline +history exports over screenshots. For external handlers or bridge adapters, +read worker_protocol.external_execution_surface_contract from /api/cluster/info +and preserve the external task input/result envelopes. Use Durable Workflow +2.0 APIs unless I explicitly ask for legacy 1.x maintenance. +``` + +Мета проста: люди вивчають інваріант workflow/activity/replay, +а інструменти працюють через документовані контракти, не виводячи поведінку +продукту з припущень. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/capabilities.md b/i18n/uk/docusaurus-plugin-content-docs/current/capabilities.md new file mode 100644 index 0000000000..236ad0649d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/capabilities.md @@ -0,0 +1,120 @@ +--- +sidebar_position: 2 +title: Індекс можливостей 2.0 +description: Індекс SDK сервісного режиму, вбудованого runtime, протоколів і можливостей Durable Workflow 2.0. +tags: + - capabilities + - compatibility + - polyglot +keywords: + - можливості Durable Workflow 2.0 + - SDK PHP Python Rust + - матриця можливостей рушія workflow + - мінімальні версії можливостей +--- + +# Індекс можливостей Durable Workflow 2.0 {#durable-workflow-20-capability-index} + +Це індекс можливостей стабільної лінійки випусків Durable Workflow 2.0. + +## Підтримуваний канал встановлення {#supported-installable-channel} + +Точні ідентифікатори артефактів визначають реєстри пакетів і метадані +випусків, а не цей огляд можливостей. + +| Інтерфейс | Підтримуваний канал | Роль в індексі | +| --- | --- | --- | +| CLI | 2.0 stable | Машиночитаний клієнт для операторів і діагностики. | +| PHP SDK | 2.0 stable | Офіційний клієнт сервісного режиму `durable-workflow/sdk` і віддалений worker workflow/activity для runtime простору імен Cloud або самостійно розгорнутого Server. | +| Вбудований рушій Laravel | 2.0 stable | Окремий режим `durable-workflow/workflow` усередині застосунку з власним середовищем написання workflow, рушієм збереження, чергами та replay. | +| Python SDK | 2.0 stable | Офіційний SDK детермінованих workflow/activity сервісного режиму та клієнт операцій і площини керування для runtime простору імен Cloud або самостійно розгорнутого Server. Потрібен Python 3.10 або новіший. | +| Rust SDK | 2.0 stable | Офіційний SDK детермінованих workflow/activity сервісного режиму, служба worker і клієнт площини керування для runtime простору імен Cloud або самостійно розгорнутого Server. Потрібен Rust 1.86 або новіший. | +| Server | 2.0 stable | Незалежний від мови runtime, реалізований на PHP, для самостійного розгортання протоколів площини керування й worker v2. | +| Waterline | 2.0 stable | Cloud містить Managed Waterline. Оператори власного Server окремо розгортають образ служби для простору імен Server. Вбудований Laravel встановлює пакет Composer і читає стан застосунку в його процесі. | + +PHP, Python і Rust — три мови офіційних SDK сервісного режиму. +Кожен може працювати з підготовленим runtime простору імен Durable Workflow +Cloud або власним Server, якщо виявлення runtime повідомляє опубліковану +можливість. Вони використовують стабільні рядкові назви типів, одну модель +стійких команд та історії, версію площини керування `2`, основну версію +протоколу worker `1` і публічний конверт payload. Rust — повноцінний SDK +workflow: він виконує детермінований код workflow, activity та служби worker, +а не лише низькорівневі виклики протоколу. Python поєднує написання workflow +з клієнтськими операціями просторів імен, розкладів, worker, черг, історії, +відновлення та іншими операційними інтерфейсами. + +Вбудований Laravel залишається окремим режимом PHP усередині застосунку. +Він не потрібен клієнтам або worker сервісного режиму PHP. Незалежний від +фреймворку PHP SDK має власні версії, окремі від вбудованого рушія Laravel. + +Оберіть межу відповідальності за допомогою +[режимів розгортання](/docs/polyglot/deployment-modes/), а потім перейдіть до +[керованого runtime Cloud](/docs/polyglot/cloud-control-plane/) або +[самостійно розгорнутого Server](/docs/polyglot/server/). Наведені нижче +докази можливостей застосовуються лише там, де обраний runtime оголошує +потрібний контракт. + +Поточна мінімальна версія власного Server оголошує протокол worker `1.13`. +Python використовує заявлену базову версію `1.1`, а Rust — `1.2` і потребує +додаткового інтерфейсу завдань query `1.8` для query через replay. Можливість +підключення клієнта визначає виявлення runtime, а не припущення за номером +патча Server. Дивіться [сумісність версій](/docs/compatibility/). + +## Можливості й мінімальні версії SDK {#capability-and-sdk-floors} + +«Сервісний режим» означає, що віддалений runtime належить Cloud або власному +Server. «Вбудований» означає, що runtime працює в процесі Laravel-застосунку. +Стовпець SDK явно фіксує відмінності. Порожнє або обмежене покриття SDK +не означає паритету. + +| Можливість | Поточний контракт 2.0 і мінімальна офіційна підтримка | Явні докази 2.0 | +| --- | --- | --- | +| Workflow | Віддалені worker PHP, Python і Rust виконують детерміновані workflow. Вбудований Laravel використовує пакет Workflow. | [Написання workflow](/docs/defining-workflows/workflows/), [PHP SDK](/docs/polyglot/php/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Activity та служби | Вбудований рушій і всі три SDK сервісного режиму підтримують написання activity. Workflow та activity Rust виконуються в офіційних службах worker. Python і PHP також мають офіційні клієнти керування/служб. | [Activity](/docs/defining-workflows/activities/), [PHP SDK](/docs/polyglot/php/), [Виконання activity](/docs/features/activity-execution-model/), [Зовнішнє виконання](/docs/polyglot/external-execution/) | +| Signal | Стійка асинхронна зміна доступна для workflow PHP, Python і Rust за поточних мінімальних версій. | [Signal](/docs/features/signals/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Query | Підтримуються query лише для читання через replay. PHP, Python і Rust мають обробники/клієнти query. Завдання query Rust через replay потребують протоколу worker `1.8` або новішого. | [Query](/docs/features/queries/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Update | PHP, Python і Rust мають клієнтські інтерфейси update та обробники worker. Оголошені валідатори Python синхронно перевіряють update перед прийняттям, якщо виявлення Server оголошує саме цей контракт: прийняття йде після схвалення, а відхилення, втрата worker, тайм-аут і відсічене завершення явно завершуються помилкою. PHP і Rust не підтримують написання валідаторів і не оголошують їх. | [Update](/docs/features/updates/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/#workflow-updates) | +| Timer | Стійкі timer сервісного runtime відтворюються через replay у PHP, Python і Rust. | [Timer](/docs/features/timers/), [Rust SDK](/docs/polyglot/rust/) | +| Повторні спроби | Стійка політика повторних спроб activity записується разом із командою. PHP, Python і Rust мають параметри повторних спроб activity за поточних мінімальних версій. | [Помилки й відновлення](/docs/failures-and-recovery/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Тайм-аути | Сервісні runtime Cloud і власного Server забезпечують сімейства тайм-аутів activity та workflow. Rust містить параметри activity й дедлайни запуску для виконання/окремого run workflow. | [Тайм-аути](/docs/features/timeouts/), [Rust SDK](/docs/polyglot/rust/) | +| Дочірні workflow | PHP, Python і Rust запускають стійкі дочірні workflow та очікують їх. Ідентичність типу між мовами є стабільним рядком, а payload дочірнього workflow використовує спільний конверт. | [Дочірні workflow](/docs/features/child-workflows/), [Python SDK](/docs/polyglot/python/) | +| Детермінована паралельна композиція | PHP `all()`/`parallel()`, вкладений list-yield Python і Rust `WorkflowContext::parallel()`/`join()` планують activity, дочірні workflow, timer, змішані та вкладені групи через звичайні команди. Усі три публікують спільні метадані ідентичності/шляху групи й відновлюють результати у вкладеному порядку входу. | [Конкурентність](/docs/features/concurrency/), [Python SDK](/docs/polyglot/python/#fan-out), [Rust SDK](/docs/polyglot/rust/#deterministic-parallel-groups) | +| Стійкий вибір першого завершення | Вбудований PHP та сервісні SDK PHP, Python і Rust запускають незалежні activity, дочірні workflow, timer, очікування зовнішнього входу й учасників вкладених груп. Один переможець зберігається зі стабільною ідентичністю учасника. Інші продовжують роботу та зберігають дескриптори очікування/скасування. | [Конкурентність](/docs/features/concurrency/#first-completion-selection), [Python SDK](/docs/polyglot/python/#first-completion-selection), [Rust SDK](/docs/polyglot/rust/#durable-first-completion-selection) | +| Компенсація saga | Помічники Saga PHP, Python `WorkflowContext.saga()` і Rust `WorkflowContext::saga()` реєструють стійкі компенсації після успіху прямої дії та за замовчуванням виконують їх послідовно у зворотному порядку. Python і Rust зупиняються після першої помилки компенсації та зберігають початкову помилку разом із помилками компенсації. PHP додатково має політики паралельного виконання й продовження після помилки за явним увімкненням. | [Saga](/docs/features/sagas/), [Python SDK](/docs/polyglot/python/#saga-compensation), [Rust SDK](/docs/polyglot/rust/#saga-compensation) | +| Скасування | Вбудований Laravel та сервісні PHP, Python і Rust підтримують запити кооперативного скасування з однією кореневою ідентичністю, незмінним дедлайном і обмеженим часом очищення на явно увімкнених worker. Негайне термінальне скасування залишається доступним із безпечним вибором run і типізованими результатами. | [Скасування й припинення](/docs/features/cancel-and-terminate/), [Скасування в сервісному режимі](/docs/polyglot/cancellation/), [Rust SDK](/docs/polyglot/rust/) | +| Припинення | Припинення негайно закриває run з окремим термінальним результатом. Воно доступне в сервісних runtime Cloud і власного Server, CLI, інтерфейсах керування PHP, Python і Rust. | [Скасування й припинення](/docs/features/cancel-and-terminate/), [Server API](/docs/polyglot/server-api-reference/) | +| Side effect | PHP, Python і Rust записують недетерміновані значення рівно один раз і декодують записане значення під час replay. | [Side effect](/docs/features/side-effects/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Workflow Streams | PHP, Python і Rust мають типізовані операції списку, опису, підписки/відновлення зі зміщення, додавання, закриття й помилки для вихідних потоків окремого run, якими володіє Server. Усі три підтримують безпечні для replay emits зі стійкої ідентичності команди. Python розв’язує зовнішні payload через налаштований драйвер сховища. PHP і Rust зберігають непрозорі посилання. | [Workflow Streams](/docs/polyglot/workflow-streams/), [точна матриця SDK](/workflow-stream-capabilities.json), [сценарії runtime](/platform-conformance/workflow-stream-runtime-scenarios.json) | +| Маркери версій | PHP, Python і Rust мають стійкі маркери версій для сумісної еволюції коду. | [Версіонування](/docs/features/versioning/), [Python SDK](/docs/polyglot/python/), [Rust SDK](/docs/polyglot/rust/) | +| Детермінований replay | Worker workflow PHP, Python і Rust відновлюють рішення зі стійкої історії та повідомляють типізовану недетермінованість замість повторного виконання зовнішньої роботи. | [Гарантії виконання](/docs/constraints/execution-guarantees/), [Відповідність платформи](/docs/platform-conformance/), [Rust SDK](/docs/polyglot/rust/) | +| Розклади | Сервісні runtime Cloud і власного Server володіють стійкими розкладами. PHP, Python і CLI мають операції розкладів. Rust не заявляє API керування розкладами. | [Розклади](/docs/features/schedules/), [Довідник CLI](/docs/polyglot/cli-reference/), [Python SDK](/docs/polyglot/python/) | +| Простори імен | Сервісні runtime обмежені простором імен. Клієнти PHP/Python і CLI керують просторами імен там, де обраний runtime має цю операцію. Worker/клієнти Rust працюють із простором імен, але не заявляють його адміністрування за поточної мінімальної версії. | [Простори імен, автентифікація та worker](/docs/polyglot/namespace-auth-workers/), [Server API](/docs/polyglot/server-api-reference/) | +| Пошукові атрибути | Сервісні runtime Cloud і власного Server індексують типізовані пошукові атрибути. PHP і Python мають інтерфейси написання/керування. CLI й API оператора мають структуроване виявлення та фільтрування. | [Пошукові атрибути](/docs/features/search-attributes/), [Python SDK](/docs/polyglot/python/) | +| Сумісність worker | SDK реєструють runtime, версію SDK, build ID, підтримувані типи, версію протоколу й місткість. Server публікує прийняті версії та факти маршрутизації. | [Сумісність](/docs/compatibility/), [Сумісність і маршрутизація worker](/docs/polyglot/worker-compatibility-routing/) | +| Локальні activity | Доступні у вбудованому Laravel, сервісних SDK PHP і Python та Rust SDK 3.1.0 за явного увімкнення worker. | [Вбудоване виконання activity](/docs/features/activity-execution-model/), [Матриця підтримки сервісного режиму](/docs/polyglot/portable-worker-affinity/) | +| Сеанси worker | Доступні у вбудованому Laravel, сервісних SDK PHP та Python і Rust SDK 3.2.0 за явного увімкнення worker. | [Сеанси worker](/docs/features/worker-sessions/), [Матриця підтримки сервісного режиму](/docs/polyglot/portable-worker-affinity/) | +| Sticky execution | Доступне у вбудованому Laravel і сервісному PHP SDK. Python і Rust не підтримуються. | [Sticky execution](/docs/features/sticky-execution/), [Матриця підтримки сервісного режиму](/docs/polyglot/portable-worker-affinity/) | +| Сумісність кодеків | PHP, Python і Rust використовують публічний конверт `codec` + `blob` і одну фіксовану рекурсивну схему Avro Value. Іменовані гілки зберігають відмінності цілих чисел і double, тексту й bytes, boolean і цілих чисел, списків і мап. | [Протокол Avro Value](/docs/polyglot/avro-value-protocol/), [Протокол worker](/docs/polyglot/worker-protocol/), [Rust SDK](/docs/polyglot/rust/) | +| Діагностика | Сервісні runtime та CLI публікують факти версій, протоколів, worker, черг завдань, replay, історії, типізованих помилок і відновлення як JSON. Managed Waterline, окрема власна служба або вбудований пакет показують докази з runtime, що ними володіє. | [Моніторинг](/docs/monitoring/), [Довідник CLI](/docs/polyglot/cli-reference/), [API оператора Waterline](/docs/waterline-operator-api/) | +| Інструменти агентів | Виявити -> Змінити -> Виконати -> Діагностувати -> Відновити доступно через публічні маніфести, схеми, HTTP-операції, JSON CLI, клієнти SDK, типізовану історію/діагностику та безпечні зміни. MCP — один необов’язковий інтерфейс, а не визначення цього контракту. | [Контракт інструментів агентів](/docs/agent-tooling-contract/), [Операційний цикл агента](/docs/agent-operating-loop/) | + +## Межа сумісності payload {#payload-interoperability-boundary} + +Міжмовні виклики workflow та activity не передають серіалізовані об’єкти +PHP, pickle Python або типи реалізації Rust. Вони передають зареєстровані +рядкові назви типів і публічний конверт payload. З `avro` кожен офіційний +SDK використовує офіційну реалізацію своєї мови й ту саму фіксовану схему +Value. Декодовані мапи, списки, рядки, bytes, цілі числа, double, boolean +та null зберігають свої примітивні типи. Мовний клас відновлюється лише +в SDK, якому він належить. + +Цей контракт діє для входу й результату workflow, дочірніх workflow, +входу й результату activity, signal, query, update та зовнішнього виконання. +Перед допуском worker розгортання все одно має перевірити кодеки, +оголошені `GET /api/cluster/info`. + +## Межа випуску й зрілості {#release-and-maturity-boundary} + +Наведені тут контракти платформи є стабільним публічним інтерфейсом +продукту 2.0. Для стислого рішення про відповідність задачі скористайтеся +[оцінюванням для AI-агентів](/docs/ai-agent-workflow-engine/). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/contribute-a-sample.md b/i18n/uk/docusaurus-plugin-content-docs/current/contribute-a-sample.md new file mode 100644 index 0000000000..023118d190 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/contribute-a-sample.md @@ -0,0 +1,150 @@ +--- +sidebar_position: 9 +tags: + - sample-app + - contributing + - patterns +keywords: + - додати приклад + - внесок прикладу Durable Workflow + - процес sample-request + - зовнішній приклад workflow +--- + +# Додавання прикладу {#contribute-a-sample} + +[Sample App](/docs/sample-app) — це канонічне місце для демонстрації +шаблонів Durable Workflow у справжньому Laravel-проєкті. Якщо ваш шаблон +може допомогти іншим інженерам, наприклад нова структура saga, інше +використання потоків повідомлень або інтеграція з інструментом, якого +ще немає в прикладах, цей посібник допоможе включити його до проєкту. + +Цей контракт навмисно короткий. Приклад, який його дотримується, +потрапляє до проєкту в передбачуваний строк. Приклад із пропущеними +вимогами залишається на розгляді, доки прогалини не буде заповнено. + +## Перед написанням коду {#before-you-write-code} + +1. **Спочатку відкрийте issue `sample-request`.** + [Шаблон `sample-request`](https://github.com/durable-workflow/sample-app/issues/new/choose) + визначає можливість, публічну сторінку документації з її контрактом + і мінімальну потрібну версію пакета Durable Workflow. В issue + супроводжувачі й автор погоджують цінність прикладу *до* роботи над PR. +2. **Оберіть можливість, якої ще немає в індексі прикладів.** + [Індекс прикладів README](https://github.com/durable-workflow/sample-app#sample-index) + перелічує всі показані шаблони. Якщо ідея зводиться до іншого запису + простого workflow, це зміна документації + `App\Workflows\Simple\SimpleWorkflow`, а не новий приклад. +3. **Знайдіть найближчий наявний клас workflow.** Новий приклад має бути + схожим на сусідні: та сама структура каталогів + `app/Workflows//`, форма команди Artisan і підхід до тестів. + Приклади з новою довільною інфраструктурою відхиляються, навіть якщо + показаний шаблон корисний. + +## Яким має бути прийнятий приклад {#what-a-merged-sample-looks-like} + +Прийнятий приклад є *виконуваним*, *зрозумілим для навчання*, +*перевіреним тестами* й *доступним для пошуку*. Для цього в репозиторії +sample-app мають бути чотири конкретні складові: + +1. **Клас workflow** у `app/Workflows//`, який демонструє шаблон + від початку до кінця. Код workflow залишається детермінованим: + читання часу через `sideEffect()`, зовнішня робота в activity, + очікування через signal, update, timer або потоки повідомлень. + Клас компілюється без `OPENAI_API_KEY` та інших зовнішніх облікових + даних, якщо шаблон не вимагає їх явно. +2. **Команда Artisan**, яка запускає workflow з реалістичними вхідними + даними, щоб читач виконав одну команду й побачив запуск у Waterline. + Вона розміщується поруч із відповідними командами Artisan у + `routes/console.php` або аналогічному місці реєстрації та дотримується + тієї самої схеми назв `app:`. +3. **Запис `config/workflow_mcp.php`** із класом workflow, шаблоном, + командою, потрібними обліковими даними й аргументами. Він робить + приклад доступним через MCP-сервер. Перевірка upstream-coverage + не позначає рядок покриття як `covered`, доки запис не з’явиться. +4. **Тест у `tests/`**, який виконує workflow через worker v2 у пам’яті. + Не потрібно перевіряти кожну типізовану подію історії, але тест + має довести завершення workflow для вхідних даних команди Artisan. + +Якщо приклад демонструє *виправлення помилки*, а не можливість, +він розміщується в `app/Workflows/Bug//` із тими самими +чотирма складовими. + +## Зміни документації {#what-goes-in-the-docs} + +Після прийняття прикладу оновлюються три частини документації: + +- **Sample Index у README** репозиторію sample-app — один рядок для + кожного прикладу з метою, класом workflow, командою Artisan і ключем + MCP. Це джерело істини. +- **Галерея прикладів на сайті документації + ([`docs/sample-app.md`](/docs/sample-app))** — відображення того самого + рядка з очікуваним екраном Waterline. Галерея дає читачу змогу + обрати відповідний приклад перед клонуванням репозиторію. +- **Посилання зі сторінки шаблону** документації, що визначає можливість: + saga, signal, потоки повідомлень, дочірні workflow тощо. + Таблиця посилань унизу сторінки sample-app є канонічним списком. + Запис із назвою можливості без посилання на приклад вважається прогалиною. + +Надсилайте приклад і зміни README у PR sample-app, а галерею та зміни сторінки +шаблону — у пов’язаному PR сайту документації. Супроводжувачі включають +пов’язані зміни разом. Приклад без відображення на сайті документації має +стан `gap` у трекері upstream-coverage до прийняття PR документації, +тому авторам рекомендується готувати їх разом. + +## Назви, структура й стиль {#naming-layout-and-style} + +- **Структура класів.** `app/Workflows//Workflow.php` + із класами activity поруч у `app/Workflows//Activities/`. + Відтворення помилок розміщуються в `app/Workflows/Bug//` + за тією самою структурою. +- **Команда Artisan.** `app:` малими літерами без дефісів. + Довгі назви на кшталт `app:run-` не використовуються. +- **Ключ MCP.** Має відповідати короткій назві шаблону команди Artisan, + щоб MCP-клієнт бачив ту саму назву, яку людина вводить у терміналі. +- **Коментарі.** Пояснюйте *чому*, а не *що*. Інваріанти безпеки replay + варто коментувати: чому значення обгорнуте в `sideEffect()` або чому + очікування використовує timer замість `usleep()`. Однаковий коментар + біля кожного виклику activity не потрібен. +- **Знімки екрана Waterline.** Галерея sample-app на сайті документації + визначає очікуваний екран Waterline. Додайте або оновіть його + в межах роботи над прикладом. + +## Причини відхилення прикладу {#what-gets-a-sample-rejected} + +Приклад повертається на доопрацювання, якщо: + +- код workflow використовує `now()`, `random_*()` або інші + недетерміновані виклики поза `sideEffect()` або activity; +- activity виконують стійкий облік, яким уже керує рушій: прямо + записують `workflow_messages`, вручну переміщують курсори потоків + або зберігають стан Waterline з коду workflow; +- команда Artisan вимагає зовнішніх облікових даних без зазначення + цього в полі `requires` запису MCP; +- індекс прикладів README, рядок галереї документації та посилання + зі сторінки шаблону не входять до однієї узгодженої зміни; +- приклад повторює наявну можливість без нової структури, сценарію + помилки або інтеграції. Той самий шаблон з іншим описом є зміною + документації, а не новим прикладом. + +Ці самі вимоги перевіряють upstream-coverage та список перевірки sample-app. +Виправлення прогалин перед відкриттям PR пришвидшує його прийняття. + +## Короткий список перевірки {#quick-checklist} + +Використовуйте цей список під час відкриття PR: + +- [ ] Є issue `sample-request` із посиланням на публічну сторінку шаблону. +- [ ] Клас workflow у `app/Workflows//`. +- [ ] Команда Artisan зареєстрована з назвою `app:`. +- [ ] Запис `config/workflow_mcp.php` із класом, шаблоном, командою, + вимогами й аргументами. +- [ ] Тест, який виконує workflow від початку до кінця. +- [ ] Рядок Sample Index у README. +- [ ] Рядок галереї сайту документації у + [`docs/sample-app.md`](/docs/sample-app). +- [ ] Посилання з відповідної сторінки шаблону на сайті документації. +- [ ] Перевірка публічних меж пройшла (`scripts/check-public-boundary.sh`). + +Супроводжувачі перевіряють той самий список, тому PR, який виконує всі +вимоги, має потрапити до проєкту протягом одного циклу випуску. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/webhooks.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/webhooks.md new file mode 100644 index 0000000000..5573ea9e7d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/webhooks.md @@ -0,0 +1,1207 @@ +--- +sidebar_position: 13 +--- + +# Webhook {#webhooks} + +Фреймворк надає webhook, через які зовнішні системи можуть динамічно запускати workflow й надсилати signal. Це дає змогу інтегрувати зовнішні сервіси, API та засоби автоматизації. + +## Увімкнення webhook {#enabling-webhooks} +Щоб увімкнути webhook, зареєструйте їхні маршрути у файлі маршрутів застосунку (`routes/web.php` або `routes/api.php`): + +```php +use Workflow\V2\Webhooks; + +Webhooks::routes([ + App\Workflows\OrderWorkflow::class, + 'manual-invoice' => App\Workflows\InvoiceWorkflow::class, +]); +``` + +Передайте явну мапу класів workflow, які потрібно зробити доступними. Кожен псевдонім стає публічним сегментом маршруту, а кожен клас workflow має містити стабільний стійкий ключ типу через `#[Type(...)]` або реєстрацію в `workflows.v2.types.workflows`. Повну матрицю маршрутів наведено в розділі [Явні webhook команд і query](#explicit-command-and-query-webhooks). + +## Метадані видимості {#visibility-metadata} +Під час реєстрації маршрутів через `Workflow\V2\Webhooks::routes(...)` маршрут запуску також приймає зарезервований об’єкт `visibility`. Його поля зберігаються як метадані видимості workflow й не передаються до методу `handle()`. + +```bash +curl -X POST "https://example.com/webhooks/start/order-workflow" \ + -H "Content-Type: application/json" \ + -d '{ + "workflow_id": "order-123", + "orderId": 123, + "visibility": { + "business_key": "order-123", + "labels": { + "tenant": "acme", + "region": "us-east" + }, + "memo": { + "customer": { + "id": 42, + "name": "Taylor" + }, + "source": "checkout" + } + } + }' +``` + +`business_key` та `visibility.labels` копіюються в екземпляр, запуск, стисле представлення запуску, типізовану історію старту, деталі вибраного запуску й експорт історії. Waterline може фільтрувати списки за цими полями через параметри запиту з точним збігом. + +`visibility.memo` копіюється в екземпляр, запуск, типізовану історію старту, деталі вибраного запуску й експорт історії. Подальші запуски `continueAsNew()` типово успадковують це memo. На верхньому рівні воно має бути об’єктом JSON із вкладеними скалярними значеннями, `null`, масивами або об’єктами. `memo` — це лише повернені метадані, а не поле фільтрування списку чи пошуку в стислому представленні запуску. + +## Автентифікація webhook {#webhook-authentication} +Типово webhook не вимагають автентифікації, але одну з кількох стратегій можна налаштувати в `config/workflows.php`. + +**Важливо:** якщо URL webhook доступні зовнішнім сторонам або опубліковані, увімкніть автентифікацію токеном або підписом HMAC, щоб запобігти несанкціонованому доступу. + +### Способи автентифікації {#authentication-methods} +Підтримуються: +1. Без автентифікації (none) +2. Автентифікація токеном (token) +3. Перевірка підпису HMAC (signature) +4. Власна автентифікація (custom) + +### Автентифікація токеном {#token-authentication} +За автентифікації токеном webhook вимагають чинний API-токен у заголовках запиту. Типовий заголовок — `Authorization`, але його можна змінити в налаштуваннях. + +#### Приклад запиту {#example-request} +```bash +curl -X POST "https://example.com/webhooks/start/order-workflow" \ + -H "Content-Type: application/json" \ + -H "Authorization: your-api-token" \ + -d '{"orderId": 123}' +``` + +### Автентифікація підписом HMAC {#hmac-signature-authentication} +За автентифікації HMAC запити перевіряються секретним ключем. Типовий заголовок — `X-Signature`, але його також можна змінити. + +#### Приклад запиту {#example-request-1} +```bash +BODY='{"orderId": 123}' +SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "your-secret-key" | awk '{print $2}') + +curl -X POST "https://example.com/webhooks/start/order-workflow" \ + -H "Content-Type: application/json" \ + -H "X-Signature: $SIGNATURE" \ + -d "$BODY" +``` + +### Власна автентифікація {#custom-authentication} +Для власної автентифікації створіть клас, що реалізує інтерфейс `WebhookAuthenticator`: + +```php +use Illuminate\Http\Request; +use Workflow\Auth\WebhookAuthenticator; + +class CustomAuthenticator implements WebhookAuthenticator +{ + public function validate(Request $request): Request + { + $allow = true; + + if ($allow) { + return $request; + } else { + abort(401, 'Unauthorized'); + } + } +} +``` + +Потім налаштуйте його в `config/workflows.php`: + +```php +'webhook_auth' => [ + 'method' => 'custom', + 'custom' => [ + 'class' => App\Your\CustomAuthenticator::class, + ], +], +``` + +Метод `validate()` має повертати `Request` для чинного запиту або викликати `abort(401)` для неавторизованого. + +## Налаштування маршрутів webhook {#configuring-webhook-routes} +Типово webhook доступні за `/webhooks`. Шлях маршруту можна налаштувати в `config/workflows.php`: + +```php +'webhooks_route' => 'workflows', +``` + +Після зміни webhook будуть доступні за: +``` +POST /workflows/start/order-workflow +POST /workflows/signal/order-workflow/{workflowId}/mark-as-shipped +POST /workflows/instances/{workflowId}/queries/{query} +POST /workflows/instances/{workflowId}/runs/{runId}/queries/{query} +POST /workflows/instances/{workflowId}/signals/{signal} +POST /workflows/instances/{workflowId}/runs/{runId}/signals/{signal} +POST /workflows/instances/{workflowId}/repair +POST /workflows/instances/{workflowId}/cancel +POST /workflows/instances/{workflowId}/terminate +GET /workflows/instances/{workflowId}/describe +POST /workflows/instances/{workflowId}/runs/{runId}/repair +POST /workflows/instances/{workflowId}/runs/{runId}/cancel +POST /workflows/instances/{workflowId}/runs/{runId}/terminate +GET /workflows/instances/{workflowId}/runs/{runId}/describe +GET /workflows/workflow-tasks/poll +GET /workflows/activity-tasks/poll +POST /workflows/control-plane/start +``` + +## Явні webhook команд і query {#explicit-command-and-query-webhooks} + +Через HTTP доступні стійкі команди start, signal, update, repair, cancel і terminate, а також безпечні щодо replay маршрути query для читання поточного або вибраного запуску. + +Ви реєструєте явну мапу псевдонімів і надаєте маршрути лише потрібним workflow. + +```php +use Workflow\V2\Webhooks; + +Webhooks::routes([ + App\Workflows\OrderWorkflow::class, + 'manual-invoice' => App\Workflows\InvoiceWorkflow::class, +]); +``` + +За прямої реєстрації класу workflow він має визначати стабільний ключ типу через `#[Type(...)]` або бути зареєстрованим у `workflows.v2.types.workflows`: + +```php +use Workflow\V2\Attributes\Type; +use Workflow\V2\Workflow; + +#[Type('order-workflow')] +class OrderWorkflow extends Workflow +{ + public function handle(int $orderId) + { + // ... + } +} +``` + +Еквівалентна реєстрація в конфігурації: + +```php +// config/workflows.php +'v2' => [ + 'types' => [ + 'workflows' => [ + 'order-workflow' => App\Workflows\OrderWorkflow::class, + ], + ], +], +``` + +Отриманий маршрут запуску: + +``` +POST /webhooks/start/order-workflow +``` + +Приклад запиту: + +```bash +curl -X POST "https://example.com/webhooks/start/order-workflow" \ + -H "Content-Type: application/json" \ + -d '{"workflow_id":"order-123","orderId":123}' +``` + +`workflow_id` — це непрозорий публічний ID екземпляра workflow. Він не є ID запуску й має трактуватися як непрозорий рядок. + +Заданий ініціатором `workflow_id` має бути непорожнім рядком до 191 символу, придатним для URL, і містити лише літери, цифри, `.`, `_`, `-` та `:`. Порожні, задовгі ID або ID з непідтримуваними символами відхиляються під час перевірки webhook з HTTP `422`. + +Та сама мапа стійких типів є запасним шляхом worker, коли збережена назва класу workflow змінюється після рефакторингу. Якщо зберегти `order-workflow` сталим і спрямувати реєстрацію на новий клас, робота v2 у черзі зможе знайти стійкий ключ типу, навіть якщо початковий збережений PHP-клас більше не завантажується. + +Можна також явно задати поведінку повторного запуску: + +```bash +curl -X POST "https://example.com/webhooks/start/order-workflow" \ + -H "Content-Type: application/json" \ + -d '{"workflow_id":"order-123","orderId":123,"on_duplicate":"return_existing_active"}' +``` + +Підтримувані значення `on_duplicate`: + +- `reject_duplicate` +- `return_existing_active` + +Усі webhook команд повертають однаковий конверт JSON: + +```json +{ + "outcome": "started_new", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "rejection_reason": null +} +``` + +Прийнята відповідь після створення нового запуску: + +```json +{ + "outcome": "started_new", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "rejection_reason": null +} +``` + +Прийнята відповідь, коли ініціатор запитав повторне використання наявного активного запуску: + +```json +{ + "outcome": "returned_existing_active", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "rejection_reason": null +} +``` + +Відхилена відповідь повторного старту: + +```json +{ + "outcome": "rejected_duplicate", + "workflow_id": "order-123", + "run_id": "01J...", + "requested_run_id": null, + "resolved_run_id": "01J...", + "command_id": "01J...", + "workflow_type": "order-workflow", + "command_status": "rejected", + "command_source": "webhook", + "rejection_reason": "instance_already_started" +} +``` + +Поля відповіді: + +- `workflow_id` — публічний ID екземпляра workflow. +- `run_id` — перший активний запуск, створений прийнятою командою start. +- `requested_run_id` задається лише тоді, коли ініціатор явно адресував вибраний запуск. +- `resolved_run_id` — запуск, який рушій фактично визначив для команди. Для стартів, адресованих екземпляру, він збігається з `run_id`. +- `command_id` — стійкий ID команди start. +- `command_source` має значення `webhook` для маршрутів webhook. +- маршрути signal, update, repair, cancel і terminate, адресовані екземпляру, знаходять його найновіший стійкий запуск, не покладаючись лише на змінюваний покажчик поточного запуску. Тому ланцюжки continue-as-new залишаються доступними за тим самим публічним ID, навіть якщо цей стовпець відхилився. +- маршрути команд для вибраного запуску фіксують один запуск під тим самим публічним ID екземпляра й відхиляються з `target_scope = run`, `outcome = rejected_not_current` та `rejection_reason = selected_run_not_current`, якщо він уже не поточний. Тоді `run_id` та `requested_run_id` залишаються на історичному виборі, а `resolved_run_id` указує на поточний запуск для наступного звернення. +- ключі даних зіставляються з назвами параметрів `handle()` workflow у порядку оголошення. +- Workflow визначають аргументи старту в `handle()`. +- Класи workflow без `handle()` відхиляються з помилками перевірки HTTP `422` до створення запуску. +- відсутні обов’язкові ключі даних відхиляються з помилками перевірки HTTP `422`, а не непомітно пропускаються. +- порожні, задовгі або непридатні для маршруту `workflow_id` відхиляються з помилками перевірки HTTP `422` до обробки повторного старту. +- нечинні значення `on_duplicate` відхиляються з помилками перевірки HTTP `422`. +- `on_duplicate = return_existing_active` повертає HTTP `200` за повторного використання поточного активного запуску. +- повторні старти без повторного використання повертають HTTP `409` з `outcome = rejected_duplicate`, `command_status = rejected` та `rejection_reason = instance_already_started`. +- стійкий запис `workflow_commands` також зберігає контекст входу webhook окремо від бізнес-аргументів: позначку ініціатора, спосіб або результат автентифікації, назву й шлях маршруту та відбиток запиту з нормалізованих даних і вибраних заголовків, зокрема `X-Request-Id` та `X-Correlation-Id` + +Поточна матриця відповідей HTTP для webhook старту: + +- `202` з `outcome = started_new` після створення нового запуску +- `200` з `outcome = returned_existing_active` за повторного використання наявного активного запуску +- `409` з `outcome = rejected_duplicate` після відхилення повторного старту +- `401` за помилки автентифікації webhook +- `404` для невідомого псевдоніма +- `422` за помилки перевірки даних + + +### Webhook команд signal {#signal-command-webhooks} + +Команди signal доступні для екземпляра й вибраного запуску: + +```text +POST /webhooks/instances/{workflowId}/signals/{signal} +POST /webhooks/instances/{workflowId}/runs/{runId}/signals/{signal} +``` + +`workflowId` — публічний ID екземпляра workflow. Маршрут екземпляра визначає поточний активний запуск на момент застосування. Маршрут вибраного запуску також приймає `runId` і відхиляє історичний запуск. Назва signal береться з параметра маршруту. Тіло запиту зараз приймає одне необов’язкове поле верхнього рівня: + +```json +{ + "arguments": ["Taylor"] +} +``` + +Поле `arguments` має бути масивом. Якщо його пропущено, workflow, що очікує через `await('signal-name')`, продовжується зі значенням `true`. +Цільовий клас workflow має оголосити назву signal через `#[Workflow\V2\Attributes\Signal('...')]`. Неоголошені назви стійко відхиляються, а не сліпо буферизуються. + +Приклад signal: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/signals/approved-by" \ + -H "Content-Type: application/json" \ + -d '{"arguments":["Taylor"]}' +``` + +Прийнята відповідь signal: + +```json +{ + "outcome": "signal_received", + "workflow_id": "order-123", + "run_id": "01J...", + "requested_run_id": null, + "resolved_run_id": "01J...", + "command_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "rejection_reason": null +} +``` + +Прийняті й відхилені команди signal також отримують окремий стійкий запис життєвого циклу `workflow_signal_records`, пов’язаний із початковою командою. Відповіді signal повертають ID команди як ID кореляції HTTP. Деталі вибраного запуску Waterline та експорт історії показують запис життєвого циклу як `signals[*].id` разом із назвою signal, `signal_wait_id`, послідовністю команди, послідовністю workflow після застосування, станом, результатом, помилками перевірки й збереженими аргументами. + +Відхилена відповідь, коли екземпляр існує, але ще не має запуску: + +```json +{ + "outcome": "rejected_not_started", + "workflow_id": "order-123", + "run_id": null, + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "instance_not_started" +} +``` + +Відхилена відповідь, коли поточний запуск уже закрито: + +```json +{ + "outcome": "rejected_not_active", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "run_not_active" +} +``` + +Відхилена відповідь, коли маршрут адресує неоголошену назву signal: + +```json +{ + "outcome": "rejected_unknown_signal", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "unknown_signal" +} +``` + +Відхилена відповідь, коли маршрут signal для вибраного запуску адресує історичний запуск: + +```json +{ + "outcome": "rejected_not_current", + "workflow_id": "order-123", + "run_id": "01J...", + "requested_run_id": "01J...", + "resolved_run_id": "01J...", + "command_id": "01J...", + "target_scope": "run", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "selected_run_not_current" +} +``` + +Поточна матриця відповідей HTTP для webhook signal: + +- `202` з `outcome = signal_received` після прийняття команди signal +- `401` за помилки автентифікації webhook +- `404` для невідомого ID екземпляра workflow +- `404` з `outcome = rejected_unknown_signal`, якщо workflow не оголошує цю назву signal. Якщо запуск уже має типізований знімок контракту `WorkflowStarted` або старішу подію `WorkflowStarted` можна доповнити за першого сумісного прийняття, відхилення більше не залежить від попередньої рефлексії поточного класу workflow +- `409` з `outcome = rejected_not_started`, якщо екземпляр ще не має поточного запуску +- `409` з `outcome = rejected_not_active`, якщо цільовий поточний запуск уже закрито +- `409` з `outcome = rejected_not_current`, якщо маршрут вибраного запуску адресує історичний запуск +- `422`, якщо `arguments` наявне, але не є масивом + +### Webhook Signal-With-Start {#signal-with-start-webhooks} + +Один маршрут пов’язаного прийняття для екземпляра починає новий запуск або повторно використовує поточний активний перед записом signal: + +```text +POST /webhooks/start/{alias}/signals/{signal} +``` + +`alias` — псевдонім workflow, зареєстрований для webhook у `Workflow\V2\Webhooks::routes([...])`. `signal` — стійка назва signal, оголошена через `#[Signal(...)]`. + +Тіло запиту поєднує звичайні поля старту й зарезервоване поле `signal_arguments` для даних доданого signal: + +```json +{ + "workflow_id": "order-123", + "signal_arguments": ["Taylor"], + "visibility": { + "business_key": "order-123" + } +} +``` + +`signal_arguments` має бути масивом. Маршрут типово задає `on_duplicate` як `return_existing_active` й відхиляє будь-яку іншу політику повторного старту для цього пов’язаного прийняття. + +Прийнята відповідь, коли маршрут починає новий запуск: + +```json +{ + "outcome": "signal_received", + "workflow_id": "order-123", + "run_id": "01J...", + "requested_run_id": null, + "resolved_run_id": "01J...", + "command_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "start_command_id": "01J...", + "start_command_sequence": 1, + "start_outcome": "started_new", + "start_command_status": "accepted", + "intake_group_id": "01J...", + "rejection_reason": null +} +``` + +Якщо екземпляр workflow уже має активний поточний запуск, маршрут повертає `start_outcome = returned_existing_active` і залишає команду signal об’єктом відповіді верхнього рівня `command_*`. Пов’язані команди start і signal мають спільні `intake_group_id`, метадані маршруту й заголовки кореляції. Runtime записує обидві команди в одній транзакції до виконання worker першого користувацького кроку workflow. + +Деталі вибраного запуску й експорт історії фіксують те саме складене прийняття в `linked_intakes[*]`. Стійким джерелом істини є спільні `workflow_commands.context.intake.group_id` та `mode`, записані в кожній прийнятій команді. Для режиму `signal_with_start` згрупований запис містить `start_command_*`, нестартовий `primary_command_*`, `complete`, `missing_expected_command_types` та впорядковані вкладені знімки `commands[*]`. + +Якщо назва signal невідома або дані signal не проходять перевірку стійкого контракту, runtime відхиляє команду signal до створення нового запуску. Тоді відповідь залишає `run_id`, `start_command_id` та `start_outcome` зі значенням `null`. + +Поточна матриця відповідей HTTP для webhook signal-with-start: + +- `202` з `outcome = signal_received` та `start_outcome = started_new`, якщо запит створює запуск і записує signal +- `202` з `outcome = signal_received` та `start_outcome = returned_existing_active`, якщо запит повторно використовує поточний активний запуск і записує signal +- `401` за помилки автентифікації webhook +- `404` для невідомого псевдоніма workflow webhook +- `404` з `outcome = rejected_unknown_signal`, якщо workflow не оголошує цю назву signal +- `422` з `outcome = rejected_invalid_arguments`, якщо `signal_arguments` є масивом, але порушує стійкий контракт signal +- `422`, якщо `signal_arguments` наявне, але не є масивом, або `on_duplicate` має значення, відмінне від `return_existing_active` + +### Webhook query {#query-webhooks} + +Маршрути query доступні для екземпляра й вибраного запуску: + +```text +POST /webhooks/instances/{workflowId}/queries/{query} +POST /webhooks/instances/{workflowId}/runs/{runId}/queries/{query} +``` + +`workflowId` — публічний ID екземпляра workflow. Маршрут екземпляра після оновлення знаходить його найновіший стійкий запуск. Маршрут вибраного запуску фіксує один `runId`, зокрема історичний або вже закритий, оскільки query є операціями replay лише для читання. + +`query` — публічна ціль query, оголошена через `#[QueryMethod]`. Якщо визначення workflow ще можна завантажити, маршрут також приймає назву відповідного PHP-методу. Успішні відповіді нормалізують `query_name` до стійкої публічної цілі. + +Тіло запиту приймає позиційний список `arguments` або іменовану мапу `arguments` із ключами оголошених параметрів query: + +```json +{ + "arguments": ["start"] +} +``` + +```json +{ + "arguments": { + "prefix": "start" + } +} +``` + +На відміну від маршрутів webhook, які змінюють стан, webhook query не додають запис стійкої команди. Вони відтворюють вибраний запуск і відразу повертають серіалізований результат. + +Приклад query: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/queries/events-starting-with" \ + -H "Content-Type: application/json" \ + -d '{"arguments":{"prefix":"start"}}' +``` + +Прийнята відповідь query: + +```json +{ + "query_name": "events-starting-with", + "workflow_id": "order-123", + "run_id": "01J...", + "target_scope": "instance", + "result": "i:1;" +} +``` + +Відхилена відповідь, коли аргументи query не відповідають оголошеному контракту: + +```json +{ + "query_name": "events-starting-with", + "workflow_id": "order-123", + "run_id": "01J...", + "target_scope": "instance", + "message": "Workflow query [events-starting-with] received invalid arguments.", + "validation_errors": { + "prefix": [ + "The prefix argument is required." + ] + } +} +``` + +Відхилена відповідь, коли визначення workflow вибраного запуску більше не можна відтворити: + +```json +{ + "query_name": "events-starting-with", + "workflow_id": "order-123", + "run_id": "01J...", + "target_scope": "instance", + "blocked_reason": "workflow_definition_unavailable", + "message": "Workflow 01J... [order-123] cannot execute query [events-starting-with] because the workflow definition is unavailable for durable type [order-workflow]." +} +``` + +Поточна матриця відповідей HTTP для webhook query: + +- `200` із серіалізованим `result` після успішного replay query +- `401` за помилки автентифікації webhook +- `404` для невідомого ID екземпляра workflow або вибраного запуску +- `409`, якщо вибраний екземпляр ще не почався, потрібний query не оголошено у вибраному запуску або replay заблоковано з `blocked_reason = workflow_definition_unavailable` +- `422`, якщо оголошений контракт query відхиляє передані `arguments` + +### Webhook команд update {#update-command-webhooks} + +Доступні маршрути POST update для екземпляра й вибраного запуску та маршрути перевірки стійкого життєвого циклу: + +```text +POST /webhooks/instances/{workflowId}/updates/{update} +POST /webhooks/instances/{workflowId}/runs/{runId}/updates/{update} +GET /webhooks/instances/{workflowId}/updates/{updateId} +GET /webhooks/instances/{workflowId}/runs/{runId}/updates/{updateId} +``` + +`workflowId` — публічний ID екземпляра workflow. `update` — стійка назва update, оголошена через `#[UpdateMethod]`. `updateId` — стійкий ID життєвого циклу з відповіді update. POST екземпляра визначає поточний активний запуск на момент застосування. POST вибраного запуску також приймає `runId` і відхиляє історичний запуск. GET екземпляра читає збережений життєвий цикл цього екземпляра workflow, а GET вибраного запуску звужує пошук до нього. + +За використання `#[UpdateMethod('mark-approved')]` публічна ціль webhook — `mark-approved`, навіть якщо відповідний PHP-метод має іншу назву. + +Тіло запиту приймає позиційний список `arguments` або іменовану мапу `arguments` із ключами оголошених параметрів update: + +```json +{ + "arguments": [true, "api"] +} +``` + +```json +{ + "arguments": { + "approved": true + } +} +``` + +Задайте `wait_for` як `accepted`, якщо потрібне лише прийняття стійкої команди update, а worker workflow має застосувати її пізніше: + +```json +{ + "arguments": { + "approved": true + }, + "wait_for": "accepted" +} +``` + +Якщо `wait_for` пропущено або задано як `completed`, webhook спочатку записує прийнятий update, а потім очікує на його застосування worker workflow в межах налаштованого бюджету завершення. Перевизначте цей бюджет для окремого запиту через `wait_timeout_seconds`: + +```json +{ + "arguments": { + "approved": true + }, + "wait_timeout_seconds": 5 +} +``` + +Якщо worker завершує update в межах бюджету, webhook повертає звичайну відповідь завершеного або неуспішного update. Якщо бюджет очікування спливає раніше, webhook повертає HTTP `202` із ще відкритим прийнятим життєвим циклом замість необмеженого блокування. За `wait_for = accepted` webhook записує команду, окремий запис життєвого циклу update й типізовану історію `UpdateAccepted`, планує або повторно відправляє завдання workflow та повертає HTTP `202` без очікування застосування. + +Кожна відповідь POST і GET update містить звичайні поля команди та `update_id`, `update_name`, `update_status`, `workflow_sequence`, `accepted_at`, `applied_at`, `rejected_at` і `closed_at`. Прийняті, але відкриті життєві цикли повертають `workflow_sequence = null`, `result = null`, `applied_at = null`, `rejected_at = null` та `closed_at = null`, доки worker не закриє життєвий цикл. + +Коли іменовану мапу прийнято, рушій нормалізує її за порядком оголошення параметрів перед додаванням типізованої історії `UpdateAccepted` або `UpdateApplied`. Під час нормалізації необов’язкові параметри також заповнюються типовими значеннями PHP. + +Кожен прийнятий або відхилений update має один стійкий запис `workflow_updates` із ключем `update_id`. Тому ініціатор може один раз виконати POST і згодом прочитати збережений життєвий цикл через GET без визначення стану із загальних записів команд. + +Приклад update: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/updates/mark-ready" \ + -H "Content-Type: application/json" \ + -d '{"arguments":{"approved":true}}' +``` + +Відповідь завершеного update: + +```json +{ + "outcome": "update_completed", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "update_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "update_name": "mark-ready", + "update_status": "completed", + "workflow_sequence": 1, + "accepted_at": "2026-04-10T12:00:00.000000Z", + "applied_at": "2026-04-10T12:00:01.000000Z", + "rejected_at": null, + "closed_at": "2026-04-10T12:00:01.000000Z", + "wait_for": "completed", + "wait_timed_out": false, + "wait_timeout_seconds": 10, + "rejection_reason": null, + "validation_errors": [], + "result": { + "approved": true + }, + "failure_id": null, + "failure_message": null +} +``` + +Відповідь лише прийнятого update: + +```json +{ + "outcome": null, + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "update_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "update_name": "mark-ready", + "update_status": "accepted", + "workflow_sequence": null, + "accepted_at": "2026-04-10T12:00:00.000000Z", + "applied_at": null, + "rejected_at": null, + "closed_at": null, + "wait_for": "accepted", + "wait_timed_out": false, + "wait_timeout_seconds": null, + "rejection_reason": null, + "validation_errors": [], + "result": null, + "failure_id": null, + "failure_message": null +} +``` + +Приклад перевірки життєвого циклу: + +```bash +curl "https://example.com/webhooks/instances/order-123/updates/01J..." \ + -H "Content-Type: application/json" +``` + +Відповідь перевірки, поки update ще відкритий: + +```json +{ + "outcome": null, + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "update_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "update_name": "mark-ready", + "update_status": "accepted", + "workflow_sequence": null, + "accepted_at": "2026-04-10T12:00:00.000000Z", + "applied_at": null, + "rejected_at": null, + "closed_at": null, + "wait_for": "status", + "wait_timed_out": false, + "wait_timeout_seconds": null, + "rejection_reason": null, + "validation_errors": [], + "result": null, + "failure_id": null, + "failure_message": null +} +``` + +Відповідь перевірки після закриття життєвого циклу: + +```json +{ + "outcome": "update_completed", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "update_id": "01J...", + "command_sequence": 2, + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "command_source": "webhook", + "update_name": "mark-ready", + "update_status": "completed", + "workflow_sequence": 1, + "accepted_at": "2026-04-10T12:00:00.000000Z", + "applied_at": "2026-04-10T12:00:01.000000Z", + "rejected_at": null, + "closed_at": "2026-04-10T12:00:01.000000Z", + "wait_for": "status", + "wait_timed_out": false, + "wait_timeout_seconds": null, + "rejection_reason": null, + "validation_errors": [], + "result": { + "approved": true + }, + "failure_id": null, + "failure_message": null +} +``` + +Маршрути перевірки не очікують на виконання. Вони лише читають збережений стійкий життєвий цикл, тому завжди повертають `wait_for = status`, `wait_timed_out = false` та `wait_timeout_seconds = null`. + +Якщо вибраний запуск уже має раніше прийнятий signal, що очікує на застосування, подальший POST відхиляється як `rejected_pending_signal`, не виконуючи завдання workflow безпосередньо в шляху запиту ініціатора. Нечинні іменовані або позиційні дані відхиляються як `rejected_invalid_arguments` із `validation_errors`. POST до історичного вибраного запуску відхиляється як `rejected_not_current`. + +Поточна матриця відповідей HTTP для маршрутів POST update: + +- `200` з `outcome = update_completed`, якщо команду update прийнято й завершено +- `202` з `update_status = accepted`, якщо `wait_for = accepted` стійко приймає команду update й залишає її застосування worker workflow +- `202` з `update_status = accepted`, `wait_for = completed` та `wait_timed_out = true`, якщо webhook дочекався `wait_timeout_seconds` або типового налаштованого часу, але worker досі не закрив життєвий цикл update +- `401` за помилки автентифікації webhook +- `404` для невідомого ID екземпляра workflow +- `404` з `outcome = rejected_unknown_update` для невідомої стійкої назви update. Якщо запуск уже має типізований знімок контракту `WorkflowStarted` або старішу подію можна доповнити за першого сумісного прийняття, відхилення може бути визначене зі стійких метаданих запуску до звернення до поточного визначення +- `409` з `outcome = rejected_not_started`, якщо екземпляр ще не має поточного запуску +- `409` з `outcome = rejected_not_active`, якщо поточний запуск уже закрито +- `409` з `outcome = rejected_not_current`, якщо маршрут вибраного запуску адресує історичний запуск +- `409` з `outcome = rejected_pending_signal`, якщо раніше прийнятий signal ще має бути застосований до виконання update +- `422` з `outcome = rejected_invalid_arguments`, якщо дані update є масивом, але не відповідають оголошеному контракту через відсутні чи невідомі аргументи, невідповідність типів або порушення допустимості null +- `422` з `outcome = update_failed`, якщо тіло update спричиняє виняток +- `422`, якщо `arguments` наявне, але не є масивом. Така помилка структури запиту відхиляється до створення запису стійкої команди + +Поточна матриця відповідей HTTP для маршрутів GET перевірки update: + +- `200` після закриття життєвого циклу зі станом `update_status` як `completed`, `failed` або `rejected` +- `202`, поки `update_status = accepted` +- `401` за помилки автентифікації webhook +- `404` для невідомого ID екземпляра workflow, вибраного запуску або `update_id` у запитаній області + +### Webhook відновлення й термінальних команд {#repair-and-terminal-command-webhooks} + +Маршрути repair і термінальних команд доступні для екземпляра й вибраного запуску: + +```text +POST /webhooks/instances/{workflowId}/repair +POST /webhooks/instances/{workflowId}/cancel +POST /webhooks/instances/{workflowId}/terminate +POST /webhooks/instances/{workflowId}/runs/{runId}/repair +POST /webhooks/instances/{workflowId}/runs/{runId}/cancel +POST /webhooks/instances/{workflowId}/runs/{runId}/terminate +``` + +`workflowId` тут є публічним ID екземпляра workflow. Маршрути екземпляра завжди визначають поточний активний запуск. Маршрути запуску також приймають вибраний `runId` і відхиляють історичний вибір зі стійким результатом `rejected_not_current`. До термінальних команд застосовується та сама конфігурація `workflows.webhook_auth`, що й до маршруту старту. + +Приклад repair: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/repair" \ + -H "Content-Type: application/json" +``` + +Приклад cancel: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/cancel" \ + -H "Content-Type: application/json" +``` + +Приклад terminate: + +```bash +curl -X POST "https://example.com/webhooks/instances/order-123/terminate" \ + -H "Content-Type: application/json" +``` + +Прийнята відповідь repair, коли runtime відновлює стійкий прогрес поточного запуску: + +```json +{ + "outcome": "repair_dispatched", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "rejection_reason": null +} +``` + +Прийнята відповідь repair, коли поточний запуск уже має справний стійкий шлях відновлення: + +```json +{ + "outcome": "repair_not_needed", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "rejection_reason": null +} +``` + +Прийнята відповідь cancel: + +```json +{ + "outcome": "cancelled", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "rejection_reason": null +} +``` + +Прийнята відповідь terminate: + +```json +{ + "outcome": "terminated", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "accepted", + "rejection_reason": null +} +``` + +Відхилена відповідь, коли екземпляр існує, але ще не має запуску: + +```json +{ + "outcome": "rejected_not_started", + "workflow_id": "order-123", + "run_id": null, + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "instance_not_started" +} +``` + +Відхилена відповідь, коли поточний запуск уже закрито: + +```json +{ + "outcome": "rejected_not_active", + "workflow_id": "order-123", + "run_id": "01J...", + "command_id": "01J...", + "target_scope": "instance", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "run_not_active" +} +``` + +Відхилена відповідь, коли маршрут repair, cancel або terminate адресує історичний запуск: + +```json +{ + "outcome": "rejected_not_current", + "workflow_id": "order-123", + "run_id": "01J...", + "requested_run_id": "01J...", + "resolved_run_id": "01J...", + "command_id": "01J...", + "target_scope": "run", + "workflow_type": "order-workflow", + "command_status": "rejected", + "rejection_reason": "selected_run_not_current" +} +``` + +Поточна матриця відповідей HTTP для webhook repair, cancel і terminate: + +- `200` з `outcome = repair_dispatched`, якщо repair повторно відправляє прострочене готове завдання, повторно отримує прострочену оренду або відтворює відсутнє стійке завдання, яке ще не почало виконуватися. Це включає завдання застосування прийнятих update і signal, записи яких втрачено до застосування команди +- `200` з `outcome = repair_not_needed`, якщо запуск уже має справний стійкий шлях відновлення або вибраний запуск уже перебуває в активній activity без запису завдання +- `200` з `outcome = cancelled` або `outcome = terminated` після успішного закриття поточного запуску +- `401` за помилки автентифікації webhook +- `404` для невідомого ID екземпляра workflow +- `409` з `outcome = rejected_not_started`, якщо екземпляр ще не має поточного запуску +- `409` з `outcome = rejected_not_active`, якщо поточний запуск уже закрито +- `409` з `outcome = rejected_not_current`, якщо маршрут вибраного запуску адресує історичний запуск + +### Webhook describe {#describe-webhooks} + +Маршрути describe для перевірки стану workflow через HTTP доступні для екземпляра й вибраного запуску: + +```text +GET /webhooks/instances/{workflowId}/describe +GET /webhooks/instances/{workflowId}/runs/{runId}/describe +``` + +`workflowId` — публічний ID екземпляра workflow. Маршрут екземпляра повертає стан поточного запуску. Маршрут вибраного запуску описує конкретний запуск за ID, зокрема історичний або вже закритий. + +Describe — це операція перевірки лише для читання. Вона не створює запис стійкої команди й не відтворює workflow. Вона повертає метадані екземпляра, стан поточного або вибраного запуску, поля стислого представлення й доступність дій із зафіксованих даних проєкції. + +Приклад describe: + +```bash +curl "https://example.com/webhooks/instances/order-123/describe" +``` + +Відповідь для активного workflow: + +```json +{ + "found": true, + "workflow_instance_id": "order-123", + "workflow_type": "order-workflow", + "workflow_class": "App\\Workflows\\OrderWorkflow", + "business_key": "order-123", + "run": { + "workflow_run_id": "01J...", + "run_number": 1, + "is_current_run": true, + "status": "waiting", + "status_bucket": "running", + "closed_reason": null, + "compatibility": "build-a", + "connection": "redis", + "queue": "default", + "started_at": "2026-04-12T12:00:00+00:00", + "closed_at": null, + "last_progress_at": "2026-04-12T12:00:01+00:00", + "wait_kind": "signal", + "wait_reason": "Waiting for signal [approved-by]" + }, + "run_count": 1, + "actions": { + "can_signal": true, + "can_query": true, + "can_update": true, + "can_cancel": true, + "can_terminate": true + }, + "reason": null +} +``` + +Відповідь для примусово завершеного workflow: + +```json +{ + "found": true, + "workflow_instance_id": "order-123", + "workflow_type": "order-workflow", + "workflow_class": "App\\Workflows\\OrderWorkflow", + "business_key": "order-123", + "run": { + "workflow_run_id": "01J...", + "run_number": 1, + "is_current_run": true, + "status": "terminated", + "status_bucket": "failed", + "closed_reason": "terminated", + "compatibility": "build-a", + "connection": "redis", + "queue": "default", + "started_at": "2026-04-12T12:00:00+00:00", + "closed_at": "2026-04-12T12:01:00+00:00", + "last_progress_at": "2026-04-12T12:01:00+00:00", + "wait_kind": null, + "wait_reason": null + }, + "run_count": 1, + "actions": { + "can_signal": false, + "can_query": false, + "can_update": false, + "can_cancel": false, + "can_terminate": false + }, + "reason": null +} +``` + +Доступність дій показує, чи може операція виконатися зараз: закриті запуски не приймають команди, суто віддалені workflow не можуть обслуговувати query або update локально, а непоточні запуски не можуть отримувати signal, update, скасування або примусове завершення. + +Приклад describe для вибраного запуску: + +```bash +curl "https://example.com/webhooks/instances/order-123/runs/01J.../describe" +``` + +Відповідь вибраного запуску містить ті самі дані, але `is_current_run` матиме значення `false` для історичного запуску, а всі дії зміни стану також матимуть `false`. + +Відповідь, коли екземпляр не знайдено: + +```json +{ + "found": false, + "workflow_instance_id": "order-123", + "workflow_type": null, + "workflow_class": null, + "business_key": null, + "run": null, + "run_count": 0, + "actions": { + "can_signal": false, + "can_query": false, + "can_update": false, + "can_cancel": false, + "can_terminate": false + }, + "reason": "instance_not_found" +} +``` + +Відповідь, коли екземпляр існує, але вибраний запуск не знайдено: + +```json +{ + "found": true, + "workflow_instance_id": "order-123", + "workflow_type": "order-workflow", + "workflow_class": "App\\Workflows\\OrderWorkflow", + "business_key": "order-123", + "run": null, + "run_count": 1, + "actions": { + "can_signal": false, + "can_query": false, + "can_update": false, + "can_cancel": false, + "can_terminate": false + }, + "reason": "run_not_found" +} +``` + +Поточна матриця відповідей HTTP для webhook describe: + +- `200`, якщо екземпляр знайдено, незалежно від того, чи запуск активний, закритий або історичний +- `200`, якщо екземпляр знайдено, але вибраний запуск не існує (`reason = run_not_found`) +- `401` за помилки автентифікації webhook +- `404`, якщо ID екземпляра workflow не існує (`reason = instance_not_found`) + + +### Webhook старту площини керування {#control-plane-start-webhook} + +Маршрут старту площини керування безпосередньо приймає стійкий ключ типу workflow без вимоги локально знайти клас workflow. Це рекомендований шлях старту для зовнішнього споживача, який керує workflow через ключі типів, а не псевдоніми PHP-класів. + +```text +POST /webhooks/control-plane/start +``` + +Тіло запиту приймає: + +| Поле | Тип | Обов’язкове | Опис | +| --- | --- | --- | --- | +| `workflow_type` | string | так | Стійкий ключ типу workflow | +| `instance_id` | string | ні | Публічний ID екземпляра workflow, заданий ініціатором | +| `arguments` | string | ні | Серіалізовані аргументи з позначкою кодека | +| `connection` | string | ні | Перевизначення підключення черги | +| `queue` | string | ні | Перевизначення назви черги | +| `business_key` | string | ні | Бізнес-ключ, заданий ініціатором | +| `labels` | object | ні | Позначки видимості | +| `memo` | object | ні | Неіндексовані метадані | +| `duplicate_start_policy` | string | ні | `reject_duplicate` або `return_existing_active` | + +Приклад запиту: + +```bash +curl -X POST "https://example.com/webhooks/control-plane/start" \ + -H "Content-Type: application/json" \ + -d '{ + "workflow_type": "order-workflow", + "instance_id": "order-123", + "arguments": "{\"orderId\":123}", + "connection": "redis", + "queue": "default", + "business_key": "order-123", + "labels": {"tenant": "acme"} + }' +``` + +Відповідь для щойно розпочатого workflow: + +```json +{ + "started": true, + "workflow_instance_id": "order-123", + "workflow_run_id": "01J...", + "workflow_type": "order-workflow", + "outcome": "started_new", + "task_id": "01J...", + "reason": null +} +``` + +Якщо `instance_id` пропущено, рушій автоматично створює ID екземпляра на основі ULID. + +Якщо ключ типу workflow відповідає локально доступному класу через `workflows.v2.types.workflows` або атрибут `#[Type(...)]`, повний знімок контракту команд і маршрутизація застосовуються під час старту. Якщо клас локально недоступний, екземпляр створюється з ключем типу й явною маршрутизацією з параметрів. Фіксація контракту команд та відбитка визначення відкладається до worker, який отримає перше завдання. Тому зовнішній споживач може запускати workflow за ключами типів, які здатна знайти лише група worker. + +Поточна матриця відповідей HTTP для webhook старту площини керування: + +- `202` з `outcome = started_new` після створення нового запуску +- `200` з `outcome = returned_existing_active` за повторного використання наявного активного запуску +- `409` з `outcome = rejected_duplicate` після відхилення повторного старту +- `401` за помилки автентифікації webhook +- `422`, якщо `workflow_type` відсутнє або `instance_id` нечинне + +Контракт `WorkflowControlPlane` також доступний як singleton у контейнері для програмного використання. Отримайте `Workflow\V2\Contracts\WorkflowControlPlane` з контейнера Laravel, щоб викликати `start()`, `signal()`, `query()`, `update()`, `cancel()`, `terminate()` та `describe()` безпосередньо з PHP-коду, тестів або команд Artisan. + +Система webhook охоплює явне прийняття старту за псевдонімом, webhook команд signal, update, query, describe, repair, cancel і terminate для екземпляра й вибраного запуску, маршрути опитування завдань workflow та activity і маршрут старту площини керування для створення workflow за ключем типу. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/avro-value-protocol.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/avro-value-protocol.md new file mode 100644 index 0000000000..06663ba49e --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/avro-value-protocol.md @@ -0,0 +1,111 @@ +--- +title: Протокол Avro Value +sidebar_label: Протокол Avro Value +--- + +# Протокол Avro Value {#avro-value-protocol} + +Durable Workflow 2.0 використовує одну фіксовану рекурсивну схему +[`durable_workflow.protocol.Value`](/schemas/v2/durable_workflow.protocol.Value.v1.avsc) +для кожного payload Avro. Входи й результати workflow, значення та деталі +помилок activity, signal, query, update, історія replay та payload у зовнішньому +сховищі використовують ту саму схему. Застосунки не публікують власні +схеми Avro, а платформа не потребує мережевого реєстру схем. + +Об’єднання значень має окремі іменовані гілки для null, boolean, знакового +64-бітного цілого числа, скінченного double, bytes, рядка UTF-8, списку +та мапи з рядковими ключами. Це зберігає відмінності `7` і `7.0`, тексту +та bytes, списків і мап у PHP, Python і Rust. + +## Формат передавання {#wire-frame} + +Поле `blob` містить base64 стандартних байтів Avro single-object: + +```text +C3 01 || 8-byte little-endian CRC-64-AVRO fingerprint || Avro datum +``` + +Схема v1 має fingerprint `e2a33dff55802237`. SDK містять незмінну схему +для кожного підтримуваного fingerprint, обирають схему запису з кадру +й узгоджують її з поточною схемою читання. Невідомий fingerprint або +несумісна нова гілка завершуються `unsupported_payload_schema`. +Декодери не вгадують формат і не переходять до JSON. + +Майбутні види значень додаються в кінець об’єднання як нові гілки record +з унікальними назвами. Опубліковані гілки ніколи не змінюють порядок +і не використовуються повторно для іншого значення. + +## Правила значень {#value-policy} + +- Ключі мап мають бути рядками. SDK відхиляють інші ключі замість + перетворення їх на рядки. +- Цілі числа мають вкладатися в знаковий 64-бітний діапазон Avro `long`. +- Double мають бути скінченними. NaN і нескінченності відхиляються. +- Python `bytes` і Rust `AvroValue::Bytes` обирають Avro `bytes`. + Клієнти PHP використовують `AvroBinaryValue::fromBytes()`, оскільки + сам рядок PHP не визначає, чи є він текстом або двійковими даними. +- Decimal, цілі довільної точності, дата/час, UUID, enum, dataclass, + Pydantic та об’єкти предметної області потребують явних адаптерів + до канонічного виду значення. + +Avro — єдиний кодек payload Durable Workflow 2.0. Документи запитів і +відповідей HTTP залишаються транспортом JSON, але кожне стійке значення +в них використовує цю фіксовану схему й кадр single-object. Позначка кодека +`json`, невідомий кодек або сирий стійкий blob без позначки відхиляються +з `unsupported_payload_codec`. Runtime ніколи не перекодовують і не вгадують. + +## Проєкція для перегляду JSON {#json-inspection-projection} + +Описи run зберігають `input_envelope`, `output_envelope` і конверти +результатів як джерело payload без втрати даних. Інтерфейси перегляду JSON, +як CLI та Waterline, відображають значення, які JSON не може подати, +через типізовану проєкцію: + +```json +{"$type":"bytes","base64":"AP8="} +{"$type":"map","entries":[{"key":"0","value":"zero"}]} +``` + +Проєкція мапи застосовується для порожніх мап і рядкових ключів, схожих +на числа, які масиви PHP не зберігають без зміни типу. Звичайні скаляри, +списки та однозначні мапи з рядковими ключами залишаються звичайними +значеннями JSON. Споживачі, яким потрібне початкове типізоване значення, +декодують супровідний конверт, а не проєкцію відображення. + +## Відтворюваний benchmark {#repeatable-benchmark} + +Кожен SDK містить однаковий benchmark репрезентативних значень і +забезпечує бюджет обраного виробничого шляху: + +```bash +# PHP SDK checkout +composer benchmark-avro-value + +# Python SDK checkout +python benchmarks/avro_value.py --enforce + +# Rust SDK checkout +cargo run --release --example avro_value_benchmark -- --enforce +``` + +Вивід JSON порівнює компактний JSON, вилучену обгортку JSON-in-Avro та +фіксовану типізовану схему. Він повідомляє розміри сирого datum, payload +у кадрі та фактичного HTTP-конверта `{codec, blob}` разом із наскрізною +затримкою адаптера, кодування й декодування. `AVRO_VALUE_ENCODE_BUDGET_US` +і `AVRO_VALUE_DECODE_BUDGET_US` можуть посилити типові бюджети на runner +перевірки випуску. CI виконує ці команди з перевіркою бюджету. Регресію +виробничого шляху потрібно пояснити або виправити до випуску. Стара +реалізація обгортки існує лише в benchmark, без сумісного шляху runtime. + +## Перевірка перед розгортанням {#deployment-preflight} + +Під час запуску Server перелічує всі збережені `payload_codec` і перевіряє +magic single-object та fingerprint фіксованої схеми для вбудованих кадрів, +конвертів вкладеної історії та посилань на зовнішні payload. Розгортання +зупиняється до запуску нового runtime за наявності активного чи потрібного +для replay payload не Avro, payload без позначки, пошкодженого посилання +або застарілого кадру. Активні run можуть завершитися на поточній +попередній версії. Збережені термінальні та потрібні для replay дані мають +пройти [міграцію історії попередніх версій](/docs/polyglot/prerelease-history-migration) +із резервною копією перед змінами. Експорт run не змінює відхилений стан +бази даних. Ніколи не видаляйте історію для обходу попередньої перевірки. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cancellation.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cancellation.md new file mode 100644 index 0000000000..31b58779e4 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cancellation.md @@ -0,0 +1,106 @@ +--- +title: Кооперативне скасування +description: Запит обмеженого часом очищення workflow, його дочірніх workflow та activity. +--- + +# Кооперативне скасування {#cooperative-cancellation} + +Server 2.5.0 додає кооперативне скасування через протокол 1.20. Потрібні +worker, що явно вмикають цю можливість. Перед використанням перевірте +виявлення runtime та підтримку можливості SDK. + +## Запит очищення {#request-cleanup} + +Використовуйте `requestCancellation()` у PHP SDK, `request_cancellation()` +у Python чи Rust або маршрут Server `request-cancellation`. Run залишається +активним, поки код workflow обробляє запит і виконує очищення. Успішне +очищення завершує run зі статусом `Cancelled`. + +```http +POST /api/workflows/{workflowId}/runs/{runId}/request-cancellation +X-Durable-Workflow-Control-Plane-Version: 2 +Authorization: Bearer +X-Namespace: +Content-Type: application/json + +{"reason":"Order withdrawn","cleanup_timeout_seconds":30} +``` + +Обраний run має залишатися поточним. Перший прийнятий запит повертає +HTTP 202. Повторний повертає HTTP 200 з початковою ідентичністю запиту +й дедлайном. Зміна тайм-ауту в повторному запиті не продовжує бюджет очищення. + +Незмінний контекст скасування містить ID запиту й кореня, причину, +ініціатора, джерело, початковий час запиту, дедлайн і впорядковану лінію +походження. Код workflow отримує його на записаній межі доставки. +Раніше завершені виклики відтворюються звичайним replay. Детерміновані +помічники `deadline()` і `remaining()` дозволяють очищенню ухвалювати +ті самі рішення після перезапуску. + +Наявний `cancel()` негайно закриває run і не відновлює очищення workflow. +`terminate()` також закриває його негайно з результатом `Terminated`. + +## Оберіть політики операцій {#choose-operation-policies} + +| Політика | Дочірній workflow | Віддалена activity | Локальна activity | +| --- | --- | --- | --- | +| TryCancel | Запросити очищення дочірнього workflow і продовжити очищення батьківського | Запросити зупинку callback і продовжити очищення workflow | Зупинити callback під наглядом і продовжити очищення | +| WaitCancellationCompleted | Дочекатися записаного термінального результату дочірнього workflow | Дочекатися стійкого підтвердження зупинки початкового callback | Приєднатися до стійкого підтвердження зупинки початкового callback | +| Abandon | Залишити дочірній workflow незалежним | Залишити activity незалежною в межах її початкового скінченного загального тайм-ауту | Відхиляється до допуску | + +Закриття батьківського workflow має окрему політику. `RequestCancellation` +надсилає кооперативний запит із початковою лінією походження скасування +та обмеженим дедлайном. `RequestCancel` зберігає стару поведінку негайного +скасування. + +Кожен дочірній workflow із підтримкою кооперації успадковує кореневу +ідентичність запиту й бюджет очищення. Він може отримати коротший дедлайн, +але не збільшити бюджет. Покинута операція має незалежний час життя та +не входить до спільного очікування очищення. + +## Нагляд і відновлення {#supervision-and-recovery} + +Кооперативні worker спостерігають скасування незалежно від heartbeat +activity застосунку. PHP використовує процеси callback під наглядом. +Python здійснює нагляд за async і синхронними callback. Rust здійснює +нагляд за async futures callback. Від’єднана робота та зовнішні служби +потребують власної поведінки скасування й ідемпотентності. + +Очищення може використати захист від скасування для стійких activity, +timer і викликів дочірніх workflow. Захист зберігає початковий дедлайн. +Якщо worker гине, заміна відтворює ту саму межу доставки та завершені +виклики очищення перед продовженням. Спливання дедлайну чи припинення +відкликає решту повноважень виконання. + +Стара спроба activity не може опублікувати результат після відсікання. +Уже прийняті зовнішні побічні ефекти все одно потребують ідемпотентності +або звіряння на рівні застосунку. + +## Перегляд каскаду {#inspect-the-cascade} + +Діагностика run містить `cancellation_cascade` із кореневим запитом, +run-учасниками, спробами activity, політиками, підтвердженнями зупинки, +дедлайнами й результатами очищення. CLI та Waterline показують те саме. + +Життєвий цикл розрізняє `requested`, `delivered`, `cleaning_up`, `cancelled`, +`deadline_expired` і `terminated`. Неповне подання вказує, що не вдалося +перевірити, замість трактувати відсутнє підтвердження як доказ успіху. + +Запит для несумісного активного worker claim повертає явну відмову +можливості без запису часткового скасування. Оновлення реєстрації +не оновлює вже виданий claim. + +## Межі випуску {#release-boundary} + +Запити для всього run, політики дочірніх workflow та activity, обмежене +захищене очищення, відновлення й перегляд каскаду складають підтримуваний +випуск. Незалежно скасовувані області операцій залишаються вимкненим +попереднім варіантом у вихідному коді та позначені experimental у +довіднику протоколу. + +Точні форми запитів, підтверджень і можливостей наведені в +[специфікації протоколу worker](/platform-protocol-specs/worker-protocol-api.openapi.yaml). +Мовні посібники написання workflow розміщені на сайтах SDK +[PHP](https://php.durable-workflow.com/), +[Python](https://python.durable-workflow.com/) і +[Rust](https://rust.durable-workflow.com/). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-python-parity.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-python-parity.md new file mode 100644 index 0000000000..d421184b10 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-python-parity.md @@ -0,0 +1,103 @@ +--- +title: Можливості клієнтів і worker +description: Порівняння можливостей клієнтів і worker CLI, PHP SDK, Python SDK та Rust SDK. +--- + +# Можливості клієнтів і worker {#client-and-worker-capabilities} + +Оберіть інтерфейс за потребами процесу. CLI `dw` та офіційні SDK PHP, +Python і Rust використовують спільні контракти площини керування v2 +та worker, але їхні ролі оператора, клієнта й worker відрізняються. + +Цей посібник для всіх клієнтів залишається за початковим маршрутом +`/docs/polyglot/cli-python-parity/`, щоб наявні посилання працювали. + +## Порівняння можливостей {#capability-comparison} + +| Можливість | CLI `dw` | PHP SDK | Python SDK | Rust SDK | +| --- | --- | --- | --- | --- | +| Життєвий цикл workflow | **Підтримується:** команди оператора для запуску, списку, перегляду, очікування, скасування, припинення й архівування. [Команди](./cli-reference.md#workflow-commands) | **Підтримується:** запуск, опис, список, очікування результатів, скасування й припинення через `Client` і `WorkflowHandle`. [Докази життєвого циклу](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** запуск, список/опис, очікування результатів, скасування й припинення через async-клієнт. [Операції workflow](./python.md#workflow-operations) | **Підтримується:** запуск, опис, очікування результатів, скасування й припинення через `Client` і `WorkflowHandle`. [Термінальні операції](./rust.md#cancel-terminate-and-handle-terminal-outcomes) | +| Signal | **Підтримується:** надсилання за ID workflow або обраним run. [Команди](./cli-reference.md#workflow-commands) | **Підтримується:** методи надсилання клієнта й handle та історія signal worker. [PHP API](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** async-клієнт надсилає signal, а worker workflow обробляють їх. [Повідомлення](./python.md#signals-queries-and-updates) | **Підтримується:** надсилання клієнтом і handle та обробка signal worker. [Rust API](https://rust.durable-workflow.com/) | +| Query | **Підтримується:** виконання іменованого query лише для читання зі структурованим виводом. [Команди](./cli-reference.md#workflow-commands) | **Підтримується:** query клієнта й handle та зареєстровані обробники query worker. [PHP API](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** query async-клієнта та обробники query worker через replay. [Повідомлення](./python.md#signals-queries-and-updates) | **Підтримується:** query клієнта та обробники query worker через replay, коли виявлення runtime оголошує підтримку завдань query. [Rust API](https://rust.durable-workflow.com/) | +| Update | **Підтримується:** подання й очікування прийнятого або завершеного результату. [Команди](./cli-reference.md#workflow-commands) | **Підтримується:** `updateWorkflow` і `registerUpdate`. PHP SDK не має написання валідаторів і не оголошує їх. [PHP API](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** клієнти, обробники workflow та синхронні оголошені валідатори, коли виявлення оголошує контракт перевірки перед прийняттям. Worker із валідаторами відхиляють непідтримувані runtime. [Повідомлення](./python.md#signals-queries-and-updates) | **Підтримується:** клієнт, handle, payload Avro та зареєстровані інтерфейси update worker. Rust SDK не має написання валідаторів і не оголошує їх. [Update](./rust.md#workflow-updates) | +| Розклади | **Підтримується:** повний життєвий цикл розкладу, backfill та історія аудиту. [Команди](./cli-reference.md#schedule-commands) | **Підтримується:** повний життєвий цикл розкладу та список через `Client`. [PHP API](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** повний async-життєвий цикл розкладу та посторінкова історія аудиту. [Розклади](./python.md#schedules) | **Не підтримується:** поточний Rust SDK не заявляє API керування розкладами. Використовуйте CLI, PHP, Python або Server API. | +| Видимість | **Підтримується:** пошук workflow/run, worker, черги завдань, історія та JSON діагностики. [Команди](./cli-reference.md#workflow-commands) | **Підтримується:** фільтрування/посторінковий перегляд workflow, історія, простори імен, worker та черги завдань. [Докази видимості](./php.md#lifecycle-updates-schedules-and-visibility) | **Підтримується:** клієнтські інтерфейси workflow, розкладів, просторів імен, worker, черг, історії та пошукових атрибутів. [Client API](./python.md#client-api-reference) | **Відрізняється:** опис/результат обраного run підтримуються. Загальний список/пошук усіх worker/run та адміністрування просторів імен не заявлені. [Клієнт Rust](./rust.md#package-and-source) | +| Виконання worker | **Навмисно відрізняється:** низькорівневі команди протоколу worker підтримують діагностику й перевірку відповідності. `dw` не є runtime worker застосунку. [Команди worker](./cli-reference.md#worker-protocol-commands) | **Підтримується:** віддалені обробники workflow, activity, query та update через `durable-workflow/sdk`. [Worker PHP](./php.md#run-a-remote-php-worker) | **Підтримується:** детерміновані worker workflow та activity. [Worker Python](./python.md#worker) | **Підтримується:** власні обробники workflow, activity, query та update. [API worker Rust](https://rust.durable-workflow.com/) | + +**Підтримується** означає, що названий інтерфейс поточного випуску має +можливість. **Відрізняється** позначає навмисну межу ролі. +**Не підтримується** є явною поточною прогалиною без прихованої обіцянки. +Виявлення протоколу runtime залишається джерелом істини, коли можливість +залежить від узгодженого протоколу worker. + +## Докази за інтерфейсами продукту {#evidence-by-product-surface} + +### CLI {#cli} + +[Огляд CLI](./cli.mdx) визначає встановлення, профілі, структурований +вивід і поведінку кодів завершення. +[Довідник команд](./cli-reference.md) містить повний інтерфейс оператора: +життєвий цикл, повідомлення, розклади, видимість і низькорівневі команди +протоколу worker, які не є циклом worker SDK. + +### PHP SDK {#php-sdk} + +Зафіксований PHP SDK має API клієнта та віддаленого worker, незалежні +від фреймворку. Його поточний публічний інтерфейс містить життєвий цикл +workflow й handle результатів, signal, query, update, розклади, +фільтрування/історію workflow, видимість просторів імен, worker і черг +завдань, а також зареєстровані обробники workflow, activity, query та update. +Дивіться [посібник PHP SDK](./php.md) і згенерований +[довідник PHP API](https://php.durable-workflow.com/api/). + +### Python SDK {#python-sdk} + +Python SDK поєднує async-клієнт площини керування з детермінованими worker +workflow та activity. Його посібник описує +[операції клієнта](./python.md#client-api-reference), +[обробники повідомлень](./python.md#signals-queries-and-updates), +[виконання worker](./python.md#worker) і +[керування розкладами](./python.md#schedules). Згенерований +[довідник Python API](https://python.durable-workflow.com/) містить точні +сигнатури й типи результатів. + +### Rust SDK {#rust-sdk} + +Опублікований Rust SDK має площину керування й життєвий цикл обраного run, +signal, query через replay, update та власні worker workflow/activity. +Підтримка update містить `Client::update_workflow`, `WorkflowHandle::update`, +`Worker::register_update` та їхні варіанти зі значеннями Avro. +Керування розкладами й загальний список/пошук залишаються явними прогалинами. +Дивіться [посібник Rust SDK](./rust.md) і згенерований +[довідник Rust API](https://rust.durable-workflow.com/). + +## Докази спільного контракту {#shared-contract-evidence} + +Усі чотири продукти працюють з однаковою версійованою площиною керування +HTTP+JSON та публічним конвертом payload. Докази поділено за тим, що вони +підтверджують: + +- [Індекс можливостей](/docs/capabilities/) фіксує точні мінімальні + версії артефактів і поточний набір підтримки. +- [Набір перевірок відповідності платформи](/docs/platform-conformance/) + фіксує сценарії runtime між клієнтами та worker. +- Репозиторії CLI й Python зберігають спільні фікстури запитів операцій, + для яких семантичні тіла запитів зараз порівнюються байт у байт. +- Публічні довідники API PHP та Rust і тести випусків підтверджують + додаткові підтримувані методи, перелічені вище. + +Наявність спільної фікстури у двох репозиторіях не означає відсутності +операції в інших SDK. Так само спільний endpoint не означає, що кожен +продукт має однакову роль оператора чи worker. + +## Додавання або розширення клієнтського інтерфейсу {#adding-or-extending-a-client-surface} + +Під час додавання нової операції CLI або SDK: + +1. Зберігайте шляхи, методи, семантичні поля, конверти payload і результати + помилок незалежними від мови. +2. Додавайте докази запитів і runtime для кожного клієнта чи worker-учасника. +3. Документуйте навмисні відмінності синтаксису або межі ролей. +4. Явно позначайте непідтримувані продукти. +5. Вважайте мовну серіалізацію, шляхи файлів, назви класів або форми + помилок дефектами публічного контракту. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/deployment-modes.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/deployment-modes.md new file mode 100644 index 0000000000..4e4778b74f --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/deployment-modes.md @@ -0,0 +1,162 @@ +--- +sidebar_position: 2 +title: Режими розгортання +description: Оберіть сервісний режим через Durable Workflow Cloud або власний Server чи вбудуйте runtime у Laravel. +tags: + - deployment + - server + - laravel + - polyglot +keywords: + - режими розгортання Durable Workflow + - вбудований режим + - сервісний режим + - Durable Workflow Cloud + - окремий server + - Cloud або власне розгортання +--- + +import ProductPromotion from '@site/src/components/ProductPromotion'; + +# Режими розгортання {#deployment-modes} + + +Оберіть керований сервісний режим для worker PHP, Python або Rust, +щоб не обслуговувати runtime оркестрації самостійно. + + +Durable Workflow v2 має два режими розгортання: + +- **Сервісний режим:** застосунки й worker підключаються через SDK до + віддаленого runtime. Оберіть + [Durable Workflow Cloud](/docs/polyglot/cloud-control-plane/) або + [власний Server](/docs/polyglot/server/). +- **Вбудований режим:** Laravel-застосунок встановлює + `durable-workflow/workflow` і безпосередньо володіє runtime. + +Cloud і власний Server є варіантами runtime всередині сервісного режиму, +а не компонентами для спільного запуску. У Cloud Durable Workflow +обслуговує оркестрацію та збереження стану, а клієнти запускають клієнти +SDK і worker. Cloud містить Managed Waterline. **Клієнти Cloud не +встановлюють, не розгортають і не підключають власні служби Server чи +Waterline.** Власний Server не містить Waterline. Оператори можуть окремо +розгорнути Waterline для простору імен, яким володіє Server. + +Використовуйте цю сторінку для вибору розгортання, яке володітиме набором +workflow, планування переходу між варіантами або визначення частин +контракту продукту, що мають залишатися однаковими в обох режимах. + +Командам Laravel варто скористатися спеціалізованим +[посібником із впровадження Laravel і переходу runtime](/docs/laravel-adoption/) +для шляхів v1→v2 і вбудований→сервісний, зокрема мосту PHP SDK, що +постачається, та його тестової підміни Laravel. + +## Оберіть runtime сервісного режиму {#choose-a-service-mode-runtime} + +| Варіант runtime | Хто обслуговує стійкий стан | Що запускає ваша команда | З чого почати | +| --- | --- | --- | --- | +| Durable Workflow Cloud | Durable Workflow обслуговує керований runtime простору імен, збереження стану, оновлення, адресу служби та Managed Waterline. | Клієнти застосунків і worker PHP, Python або Rust із наданими обліковими даними. Не запускайте Server або окрему службу Waterline. | [Керований runtime Cloud](/docs/polyglot/cloud-control-plane/) | +| Власний Server | Ваша команда розгортає, захищає, масштабує, резервує й оновлює Server та його сховище. | Server, клієнти застосунків і worker PHP, Python або Rust. За потреби окремо розгорніть Waterline для простору імен Server. | [Власний Server](/docs/polyglot/server/) | + +Обидва варіанти використовують однакову модель клієнтів і worker. +Відрізняються обслуговування runtime та облікові дані. + +## Спільна стійка модель, інша межа {#same-durable-model-different-boundary} + +Вбудований і сервісний режими зберігають одне ядро v2. Змінюються межі +розміщення, автентифікації й транспорту навколо нього. У сервісному режимі +те саме ядро працює за інтерфейсами площини керування й worker HTTP+JSON. +Немає обов’язкового gRPC або другого рушія. + +| Інтерфейс | Вбудований режим | Сервісний режим | Незмінне в обох режимах | +| --- | --- | --- | --- | +| Стійка модель workflow | Laravel-застосунок безпосередньо містить пакет і записує стан workflow у runtime застосунку. | Cloud або власний Server володіє станом workflow за API служби. | ID workflow, ID run, типізована історія, результати команд, повторні спроби, семантика відновлення й експорт історії залишаються тим самим контрактом v2. | +| Площина керування | Запуски й команди надходять із коду застосунку, `WorkflowStub` або локальних інструментів оператора. | Запуски й команди проходять через API Server, CLI або SDK через HTTP+JSON з явними заголовками автентифікації та протоколу. Незалежні від фреймворку клієнти PHP використовують `DurableWorkflow\Client` із `durable-workflow/sdk`. | Політика повторного запуску, вибір run, ID команд та іменовані результати залишаються тими самими. Надсилайте подальші команди до runtime, який прийняв запуск. | +| Транспорт worker | Worker черги Laravel виконують завдання workflow та activity усередині розгортання застосунку. | Worker реєструються, використовують довге опитування, heartbeat і завершують роботу через протокол worker HTTP+JSON. Віддалені worker PHP використовують `DurableWorkflow\Worker` із `durable-workflow/sdk`. | Оренди завдань, маркери сумісності, семантика replay та виконання activity щонайменше один раз залишаються тими самими. | +| Типова диспетчеризація завдань | Завдання зазвичай надсилаються до черги Laravel у процесі застосунку. | Сервісний runtime використовує диспетчеризацію через опитування, щоб зовнішні worker знаходили роботу через HTTP. Оператори власного Server можуть явно змінити це типове значення. | Життєвий цикл ready/leased/repair і стійка модель завдання залишаються тими самими. | +| Ключі типів workflow та activity | Псевдоніми PHP можуть розв’язуватися в локальні класи застосунку. | Worker оголошують підтримувані ключі типів під час реєстрації. | Публічні ключі типів мають бути стабільними й незалежними від мови. Не робіть FQCN PHP або дзеркальні типи-заповнювачі PHP публічним контрактом. | +| Інтерфейс оператора | Вбудований пакет Waterline або локальні інструменти читають стійкий стан Laravel-застосунку в його процесі. | Cloud надає Managed Waterline для свого простору імен. Оператори власного Server можуть окремо розгорнути Waterline для його простору імен. API служб, CLI та SDK також читають стан runtime. | Пошукові атрибути, memo, статус run, діагностика черг та експорт історії є стійкими фактами runtime, що володіє run. Waterline не об’єднує runtime чи простори імен. | +| Межа автентифікації й орендарів | Автентифікацію застосунку визначає Laravel-хост для власних маршрутів і сесій. | Вибір простору імен та токени чи підписи автентифікації Server є обов’язковими межами API. | Назви просторів імен, черги завдань, маркери сумісності й фіксований контракт payload Avro мають залишатися стабільними під час переходу. | +| Виявлення runtime | Застосунок може знаходити служби в процесі або через локальну конфігурацію. | Worker і клієнти мають використовувати явну віддалену базову URL-адресу. | Не прив’язуйте жоден режим до спільних `APP_URL`, `APP_KEY`, припущень localhost або виявлення в тому самому контейнері. | +| Межа міграції | Наявні вбудовані run продовжують виконуватися там, де почалися. | Нові run сервісного режиму починаються в обраному Cloud або власному runtime й залишаються там. | Автоматичної живої міграції незавершених run між режимами немає. Експорт призначений для аудиту/налагодження, а не імпорту живого стану. | + +## Коли обрати вбудований режим {#choose-embedded-mode-when} + +- Ваш Laravel-застосунок поєднує написання workflow, виконання worker і + доступ оператора в одному розгортанні. +- Наявні черги й модель автентифікації застосунку є правильною межею + операцій workflow. +- Вам потрібен найменший самодостатній runtime без незалежного від мови + протоколу worker. +- Оператори можуть використовувати Waterline чи інструменти застосунку + як основний інтерфейс workflow. + +Почніть із [вбудованого встановлення](/docs/installation/) та +[вбудованої документації](/docs/category/embedded/), зокрема групи +налаштувань. + +## Коли обрати сервісний режим {#choose-service-mode-when} + +- Один runtime workflow мають спільно використовувати кілька застосунків + або команд. +- Не всі worker, клієнти площини керування чи оператори працюють із + Laravel/PHP. +- Потрібна явна віддалена межа автентифікації та простору імен між + клієнтами й рушієм workflow. +- Потрібно незалежно масштабувати вхід API, зіставлення/диспетчеризацію + та worker у підтримуваній + [топології ролей Server](/docs/polyglot/server-role-topology). +- Для власного Server ви хочете розгорнути Waterline як спостерігач + простору імен Server. Cloud натомість містить Managed Waterline. + +Для керованого runtime почніть із +[Durable Workflow Cloud](/docs/polyglot/cloud-control-plane/). Для власного +розгортання почніть із [Server](/docs/polyglot/server/) та +[самостійних розгортань](/docs/deployment/). Потім оберіть +[PHP SDK](/docs/polyglot/php/), [Python SDK](/docs/polyglot/python/) або +[Rust SDK](/docs/polyglot/rust/). Користувачі Cloud працюють через Managed +Waterline. Оператори власного Server можуть використовувати +[довідник Server API](/docs/polyglot/server-api-reference/) і +[моніторинг](/docs/monitoring#waterline-service) для розгортання окремої +служби Waterline. + +## Інструменти переходу до власного сервісного режиму {#migration-tooling-to-self-hosted-service-mode} + +Підтримуваний шлях від вбудованого до сервісного режиму — поетапне +впровадження: + +- Використовуйте [міграцію вбудованого режиму до Server](/docs/polyglot/embedded-to-server) + для покрокового переходу. +- Використовуйте `GET /api/cluster/info`, щоб підтвердити збірку цільового + Server, топологію та контракт можливостей перед перемиканням трафіку. +- Використовуйте `POST /api/worker/register` і протокол worker, щоб довести + здатність зовнішніх worker обслуговувати обрані стабільні ключі типів. +- Використовуйте `GET /api/system/operator-metrics`, `dw worker:list` або + подання оператора Waterline для перевірки реєстрації worker і покриття + сумісним набором worker перед перемиканням виробничого трафіку. +- Використовуйте + [можливості клієнтів і worker](/docs/polyglot/cli-python-parity/) + під час заміни локальних викликів площини керування автоматизацією + на основі Server. +- Використовуйте Managed Waterline Cloud, Waterline, підключений до + власного runtime, або вбудований експорт історії Server для доказів + аудиту/налагодження. Пакети експорту не є шляхом імпорту живих run, + якими керує Server. + +Три правила міграції обов’язкові: + +1. Наявні run залишаються в runtime, де вони почалися. +2. Нові run Server використовують стабільні ключі типів, назви просторів + імен, черги завдань і фіксований контракт payload Avro від першого переходу. +3. Signal, query, update, відновлення, скасування, припинення та архівування + мають надходити до runtime, який володіє цільовим run. + +## Пов’язані довідники {#related-references} + +- [Встановлення](/docs/installation) +- [Server](/docs/polyglot/server) +- [PHP SDK](/docs/polyglot/php) +- [Керований runtime Cloud](/docs/polyglot/cloud-control-plane) +- [Міграція вбудованого режиму до Server](/docs/polyglot/embedded-to-server) +- [Топологія ролей Server](/docs/polyglot/server-role-topology) +- [Довідник конфігурації Server](/docs/polyglot/server-config-reference) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/portable-worker-affinity.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/portable-worker-affinity.md new file mode 100644 index 0000000000..e048f174fa --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/portable-worker-affinity.md @@ -0,0 +1,122 @@ +--- +sidebar_position: 26 +title: Переносима спорідненість worker +description: Підтримка й обмеження спорідненості worker сервісного режиму в SDK PHP, Python і Rust. +tags: + - workers + - activities + - replay + - polyglot +--- + +# Переносима спорідненість worker {#portable-worker-affinity} + +Worker сервісного режиму реалізують наведені нижче можливості через +протокол worker Server. Доступність визначають версії SDK та налаштування +worker. + +Локальні activity, сеанси worker і sticky execution мають спільне правило +переносимості: сервісний worker має оголосити кожну можливість підтримуваною +або явно відхиленою. Для цих оголошень мінімальна версія протоколу — `1.18`. +Server відхиляє плоску можливість маршрутизації, якщо структурований +маніфест worker не позначає ту саму можливість як підтримувану. + +## Підтримка SDK {#sdk-support} + +| Worker SDK | Локальні activity | Сеанси worker | Sticky execution | +| --- | --- | --- | --- | +| PHP | Підтримується | Підтримується | Підтримується | +| Python | Підтримується з 2.2.0 | Підтримується з 2.3.0 | Не підтримується | +| Rust | Підтримується з 3.1.0 із `Worker::local_activities(true)` | Підтримується з 3.2.0 із `Worker::worker_sessions(true)` | Не підтримується | + +Worker оголошують лише реалізовані й увімкнені для свого профілю +можливості. Локальні activity та сеанси Rust потребують явного увімкнення. +Старі версії Rust відхиляють нереалізовані можливості. Python і Rust +продовжують відхиляти sticky execution. Звичайні workflow та activity +в черзі використовують повний replay стійкої історії без цих оптимізацій. + +## Запис локальної activity {#local-activity-recording} + +Worker PHP, Python і Rust із явно увімкненою підтримкою виконують локальну +activity усередині worker workflow. Завершення завдання workflow містить +аргументи, результати спроб, налаштування повторних спроб і тайм-аутів, +поступ heartbeat та термінальний результат або помилку. Server атомарно +записує цю послідовність як звичайну історію activity з позначкою +`execution_mode=local`. + +Replay читає записану термінальну подію activity й не викликає локальний +обробник повторно. Втрата worker до фіксації завершення Server може +призвести до повторного виконання обробника, тому зовнішні ефекти мають +бути ідемпотентними. + +Звичайні worker PHP виконують синхронний обробник безпосередньо. +Скасування та спливання тайм-аутів heartbeat, окремої спроби й загального +виконання спостерігаються перед спробою, у `ActivityContext::heartbeat()` +або після повернення обробника. Ці обробники мають залишатися короткими +й розділяти блокувальну роботу безпечними межами heartbeat. + +Async callback Python і Rust мають передавати керування runtime мови. +Rust поновлює точну оренду завдання workflow незалежно від heartbeat +застосунку, припиняє callback після тайм-ауту, втрати повноважень чи +зупинки worker та записує обмежені звіти спроб і heartbeat. Дивіться +[приклад локальної activity Rust](https://github.com/durable-workflow/sdk-rust/blob/3.1.0/examples/local_activities.rs) +і [довідник API](https://rust.durable-workflow.com/durable_workflow/struct.LocalActivityOptions.html). + +Worker PHP з увімкненим +[кооперативним скасуванням](/docs/polyglot/cancellation) використовують +протокол 1.20 та процеси callback під наглядом для підготовлених локальних +activity. Безпосереднє локальне виконання Rust використовує окремий +звичайний профіль worker і не поєднується з підготовленим кооперативним +наглядом локальних activity. Блокувальні callback потребують нагляду +процесів для гарантії фізичної зупинки. + +## Життєвий цикл сеансу worker {#worker-session-lifecycle} + +SDK PHP, Python і Rust мають типізовані параметри сеансів та операції +створення, використання, поновлення й закриття. Параметри містять вимоги, +чергу, тривалість оренди, загальний TTL, максимальну кількість одночасних +activity та політику повторного отримання. Під час коректної зупинки +worker закриває утримувані сеанси. + +Якщо утримувач зникає, його оренда й резервування конкурентності спливають. +Новий утримувач може отримати сеанс за збігу вимог, але має відновити +локальні ресурси worker до першого використання activity. Ідентичність +сеансу ніколи не робить пам’ять процесу стійкою. + +Повторне отримання зберігає початковий абсолютний TTL сеансу. Поновлення +продовжує повноваження утримувача без продовження цього TTL. Використовуйте +Server 2.5.1 або новіший для збереження початкового TTL та записаної +маршрутизації сеансу під час холодного replay. Дивіться +[приклад сеансу Rust](https://github.com/durable-workflow/sdk-rust/blob/3.2.0/examples/worker_sessions.rs) +і [WorkerSessionOptions](https://rust.durable-workflow.com/durable_workflow/struct.WorkerSessionOptions.html). + +## Sticky execution і холодний replay {#sticky-execution-and-cold-replay} + +Кеш PHP обмежений і має ключ із точних ID workflow, ID run та build ID +worker. Він повідомляє `hit`, `miss`, `eviction` і `forced_cold_replay`. +Спливання, витіснення, заміна worker, втрата утримувача або невідповідність +збірки відкидає оптимізацію й відтворює повну стійку історію. + +Sticky routing є оптимізацією спорідненості. Примусовий холодний replay +є діагностичним доказом недоступності оптимізації, а не помилкою +коректності workflow. Код workflow має залишатися детермінованим із +порожнім кешем. + +## Безпечні типові значення й поступові оновлення worker {#safe-defaults-and-rolling-fleets} + +Звичайні workflow не потребують налаштувань сеансу чи sticky execution. +Набори worker зі змішаними версіями відхиляють несумісні дані на межі +протоколу: Server перевіряє узгоджену версію, плоску можливість, +структурований маніфест і точну ідентичність sticky cache перед прийняттям +даних завершення конкретної можливості. + +Опублікований [маніфест між-SDK сценаріїв](/platform-conformance/portable-worker-affinity-runtime-scenarios.json) +охоплює правдивість маніфесту, replay локальних activity, втрату й повторне +отримання утримувача сеансу, sticky hits і витіснення, заміну worker, +примусовий холодний replay та workflow без додаткових налаштувань. + +Для вбудованих реалізацій Laravel дивіться +[локальні activity](/docs/features/local-activities), +[сеанси worker](/docs/features/worker-sessions) і +[sticky execution](/docs/features/sticky-execution). Ці API належать +пакету Workflow. Для сервісних worker використовуйте API відповідного SDK. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/sdk-neutrality.md b/i18n/uk/docusaurus-plugin-content-docs/current/sdk-neutrality.md new file mode 100644 index 0000000000..504055a1ac --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/sdk-neutrality.md @@ -0,0 +1,210 @@ +--- +sidebar_position: 19 +title: Незалежність від SDK +description: Постійний контракт незалежності від мови, який дозволяє реалізувати публічні інтерфейси Durable Workflow у майбутньому SDK TypeScript, Go, Java або .NET без зміни протоколу. +tags: + - compatibility + - protocols + - api-stability + - sdk + - language-agnosticism +keywords: + - незалежність від SDK + - незалежність від мови + - нейтральність протоколу + - нейтральність кодека + - typescript sdk + - go sdk + - java sdk + - dotnet sdk + - python sdk + - php workflow package +--- + +import PythonPackageReleaseLink from '@site/src/components/PythonPackageReleaseLink'; + +# Незалежність від SDK {#sdk-neutrality} + +Ця сторінка пояснює загальноплатформний контракт незалежності від SDK. +Вона перелічує мінімальні правила нейтральності для кожного публічного +інтерфейсу Durable Workflow та постійну перевірку незалежності від мови, +яку рецензенти випусків застосовують до нових інтерфейсів Server, +Workflow, CLI, Waterline й MCP. + +Архітектурний посібник upstream опублікований у +[`sdk-neutrality.md`](https://github.com/durable-workflow/workflow/blob/main/docs/architecture/sdk-neutrality.md). +Машиночитане джерело істини опубліковане на цьому сайті як +[`/sdk-neutrality-contract.json`](/sdk-neutrality-contract.json) зі schema id +`durable-workflow.v2.sdk-neutrality.contract`. Джерелом вимог до протоколу +та набору SDK є JSON у пакеті Workflow Composer за адресою +`resources/sdk-neutrality-contract.json`. Публічна копія додає поточні +метадані розповсюдження пакетів із централізованого набору артефактів +документації. Окремий Durable Workflow Server повторно публікує базовий +маніфест пакета через `GET /api/cluster/info` у `sdk_neutrality_contract`. +Якщо ця сторінка суперечить опублікованому контракту JSON, пріоритет має +контракт JSON, а суперечність є помилкою документації. + +Цей контракт спирається на +[сумісність версій](/docs/compatibility), що визначає, *які* інтерфейси +публічні та *як* вони можуть змінюватися, і каталог +[специфікацій протоколів платформи](/docs/platform-protocol-specs), що +визначає, *де* розміщена нормативна специфікація кожного інтерфейсу. +Він визначає дозволену *форму* цих специфікацій, щоб майбутній SDK поза +поточним набором PHP, Python і Rust міг реалізувати їх без зміни протоколу. + +## Навіщо це потрібно {#why-this-exists} + +Durable має три офіційні окремі SDK: пакет PHP `durable-workflow/sdk`, +пакет Python `durable_workflow` і crate Rust `durable-workflow`. +Окремий пакет PHP `durable-workflow/workflow` залишається вбудованим +рушієм Laravel, відповідальним за replay. Він не є PHP SDK, незалежним +від фреймворку. Створення чи підтримка широкого набору офіційних SDK +**не** є метою випуску. Попит на SDK для екосистем TypeScript, Go, Java +та .NET ще не доведено, а вартість підтримки широкого набору висока. + +Контракт захищає від поступового вбудовування мовних припущень у публічні +контракти під цими SDK. Якщо створення SDK TypeScript або Go стане +виправданим, робота має полягати в написанні нового клієнта за опублікованим +протоколом передавання, без потреби переробляти протокол заради іншої мови. + +## Область дії {#scope} + +| Ключ області | Значення | +| --- | --- | +| `goal` | Зберігати нейтральність протоколу й контракту, щоб майбутній SDK TypeScript, Go, Java або .NET міг існувати без зміни протоколу. | +| `non_goal` | Постачати широкий набір офіційних SDK. Набір навмисно вузький і розширюється лише за виправданого попиту. | +| `present_priority` | Python зараз є найціннішим шляхом поза PHP для наявних користувачів і має пріоритет у перевірках паритету. | +| `future_posture` | TypeScript, Go, Java, .NET та інші мови залежать від попиту. Вони не мають зарезервованого випуску, але кожен публічний контракт повинен дозволяти написати SDK цими мовами без порушення протоколу передавання. | + +## Політика набору офіційних SDK {#official-sdk-breadth-policy} + +Набір офіційних SDK навмисно вузький: + +| Мова | Статус | Опублікований пакет | Джерело вимог відповідності | +| --- | --- | --- | --- | +| PHP | `priority` | [`durable-workflow/sdk`](https://packagist.org/packages/durable-workflow/sdk) | [`signal_query_runtime_contract`](https://durable-workflow.github.io/platform-conformance/signal-query-runtime-scenarios.json), учасники `sdk_php`, `php_sdk_client` і `php_worker` | +| Python | `priority` | `durable_workflow` %%artifact.pythonPublishedSdkVersion%% | [`history_replay_bundles`](https://durable-workflow.github.io/platform-conformance/replay-runtime-scenarios.json), учасник `python_sdk_runtime` | +| Rust | `priority` | [`durable-workflow`](https://crates.io/crates/durable-workflow) | [`signal_query_runtime_contract`](https://durable-workflow.github.io/platform-conformance/signal-query-runtime-scenarios.json), учасники `rust_sdk`, `rust_worker` і `rust_sdk_client` | +| TypeScript | `demand_driven` | Немає | Публічні контракти мають залишатися придатними до реалізації в TypeScript без зміни протоколу. | +| Go | `demand_driven` | Немає | Публічні контракти мають залишатися придатними до реалізації в Go без зміни протоколу. | +| Java | `demand_driven` | Немає | Публічні контракти мають залишатися придатними до реалізації в Java без зміни протоколу. | +| .NET | `demand_driven` | Немає | Публічні контракти мають залишатися придатними до реалізації в .NET без зміни протоколу. | + +Контракт JSON перелічує точні `scenario_ids`, що складають покриття +`first_party.php_sdk`, `first_party.python_sdk` і `first_party.rust_sdk`. +Пакет Laravel відстежується окремо як `embedded_engines.php_workflow_engine` +із покриттям replay для учасника `workflow_php_runtime`. Споживачі можуть +знайти ці ідентифікатори безпосередньо в пов’язаних каталогах без клонування +репозиторію SDK або читання його тестів. + +Новий офіційний SDK додається лише за таких умов: + +1. Є задокументований попит користувачів, який наявні SDK не задовольняють. +2. Команда майбутніх супроводжувачів зобов’язується підтримувати SDK у + перевірках відповідності, каталозі специфікацій протоколів і маніфесті + джерел істини випуску. +3. Додавання SDK не потребує несумісних змін протоколу worker, площини + керування, форматів подій історії або фікстур replay. Якщо потребує, + виправлення потрібне протоколу. + +## Правила нейтральності {#neutrality-rules} + +Кожен публічний контракт Durable має відповідати всім семи правилам нижче. +Маніфест JSON визначає точні форми полів, а ці описи допомагають рецензентам. +Кожне правило маніфесту JSON містить `requirement`, `rationale`, рядок +`how_to_apply` і масив `authority`. Кожен запис джерела істини має стабільний +ID каталогу чи схеми й абсолютну URL-адресу опублікованого артефакту. + +| Правило | Вимога | +| --- | --- | +| `protocol_neutrality` | Публічні RPC та події використовують форми HTTP+JSON або AsyncAPI, які може створювати й читати будь-який runtime із підтримкою HTTP. | +| `codec_neutrality` | Кожен стійкий payload, що перетинає публічну межу, оголошує `avro`. Інші публічні або специфічні для рушія кодеки v2 не пропонуються. | +| `error_shape_neutrality` | Публічні об’єкти помилок мають структурований конверт із `code`, `message` і необов’язковим `details`. Назви класів винятків PHP та Python призначені лише для діагностики. | +| `type_identity_neutrality` | Типи workflow, activity, дочірніх workflow і винятків визначаються стабільними рядковими назвами. FQCN класів і шляхи модулів є зручністю вводу SDK, а не контрактом. | +| `replay_fixture_neutrality` | Фікстури replay й еталонні пакети історії є JSON за опублікованими схемами `history_event_payloads` і `replay_bundle`. | +| `discovery_neutrality` | Кожен публічний інтерфейс доступний через `GET /api/cluster/info` та каталог `platform_protocol_specs`. | +| `documentation_neutrality` | Документація публічних контрактів описує форми через схеми, маршрути й семантику полів. Поведінка класів PHP та Python подається як приклад SDK, а не нормативний контракт. | + +## Постійна перевірка незалежності від мови {#standing-language-agnosticism-audit} + +Кожен новий інтерфейс Server, Workflow, CLI, Waterline або MCP має пройти +перевірку нейтральності перед переходом до `stable`. Це постійна вимога +рецензування PR випуску для кожної зміни сімейства інтерфейсів у її області. +До цієї області належать: + +- `server_api` +- `worker_protocol` +- `cli_json` +- `waterline_api` +- `mcp_discovery_results` +- `cluster_info_manifests` + +Список перевірки має вісім кроків. Сім відповідають правилам нейтральності +вище. Восьмий, **уявна реалізація майбутнього SDK**, просить рецензента +двома реченнями пояснити, як SDK TypeScript або Go використовуватиме новий +інтерфейс лише за опублікованим каталогом специфікацій і стандартними +інструментами HTTP+JSON. Якщо відповідь потребує офіційного SDK, інтерфейс +не є нейтральним. Перед переходом до стабільного статусу його слід змінити +або записати цю прогалину як відоме обмеження. + +## На що спирається майбутній SDK {#what-a-future-sdk-relies-on} + +Контракт визначає інтерфейси, які майбутній SDK повинен читати без вивчення +вихідного коду жодного офіційного SDK: + +- **Протокол** — `durable-workflow.v2.control-plane-api`, + `durable-workflow.v2.worker-protocol-api` і + `durable-workflow.v2.worker-protocol-stream` в опублікованому + [каталозі протоколів](https://durable-workflow.github.io/platform-protocol-specs.json). +- **Кодеки** — універсальний набір кодеків, описаний + `durable-workflow.v2.worker-protocol-api` та оголошений схемою виявлення + `durable-workflow.v2.cluster-info-envelope`. +- **Форма помилки** — конверт помилки протоколу worker і схема + `durable-workflow.v2.repair-actionability-objects`. +- **Вхід replay** — JSON Schema `durable-workflow.v2.history-event-payloads` + та `durable-workflow.v2.replay-bundle`, а також ID сценаріїв публічного + каталогу [`history_replay_bundles`](https://durable-workflow.github.io/platform-conformance/replay-runtime-scenarios.json). +- **Виявлення** — `durable-workflow.v2.cluster-info-envelope` і сам каталог + `durable-workflow.v2.platform-protocol-specs.catalog`. + +Якщо кандидатний SDK певної мови не має доступу до одного з цих інтерфейсів, +його створення потребує змін протоколу й гарантія незалежності від мови +не виконується. + +## Вимоги до випуску {#release-gates} + +Випуск, що вводить нове сімейство публічних інтерфейсів або переводить +наявний інтерфейс із `prerelease` чи `experimental` до `stable`, має +зафіксувати результат перевірки в PR випуску. Розділ вимог маніфесту +перелічує конкретні перевірки: + +- `audit_recorded` — опис PR випуску вказує застосовані кроки перевірки та + містить посилання на запис каталогу специфікацій, фікстуру відповідності + й запис виявлення нового інтерфейсу. +- `no_php_or_python_only_required_fields` — жодне гарантоване поле + інтерфейсу `stable` не потребує кодека лише для PHP чи Python. +- `universal_codec_advertised` — узгодження протоколу worker оголошує + рівно один універсальний кодек, `avro`. +- `fixture_schema_validated` — нові фікстури replay або еталонні пакети + історії проходять перевірку за опублікованими JSON Schema. +- `discovery_entry_present` — нові публічні інтерфейси мають запис + каталогу `platform_protocol_specs` із непорожніми `surface_family`, + `owner_repo` та `format`. + +Перевірки поділено між автоматизацією й людьми. CI випуску розв’язує кожну +URL-адресу джерела істини, ID протоколу/схеми та ID сценарію відповідності +в публічному контракті, звіряє область перевірки із сімействами стабільності +інтерфейсів і відхиляє локальні шляхи репозиторію або символи реалізації +як джерела істини. Рецензенти підтверджують перевірку незалежності від SDK +в кожному PR випуску, який додає чи підвищує статус публічного інтерфейсу, +і залишаються відповідальними за уявну реалізацію майбутнього SDK. + +## Зміна контракту {#changing-this-contract} + +Додавання правила нейтральності, посилення наявного правила, додавання +обов’язкового кроку чи сімейства інтерфейсів до області перевірки, а також +зміна політики набору офіційних SDK є зміною контракту. Підвищіть версію +маніфесту, оновіть архітектурний посібник і цю сторінку в тій самій зміні +та узгодьте доступний для використання контракт +[`/sdk-neutrality-contract.json`](/sdk-neutrality-contract.json). +Вилучення правила нейтральності або кроку перевірки є великою зміною. diff --git a/package.json b/package.json index 77e06a38cd..caac5bdf54 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "postinstall": "patch-package", "prestart": "node scripts/check-dependency-security.js", "start": "docusaurus start", - "build": "npm run check:dependency-security && npm run check:node-toolchain && docusaurus build && node scripts/generate-llms.js && node scripts/generate-llms-full.js", + "build": "npm run check:dependency-security && npm run check:node-toolchain && node scripts/check-local-search.test.js && docusaurus build && node scripts/generate-llms.js && node scripts/generate-llms-full.js", "check:node-toolchain": "node scripts/check-node-toolchain.js && node scripts/check-node-toolchain.test.js", "check:dependency-security": "node scripts/check-dependency-security.js && node scripts/check-dependency-security.test.js", "check:platform-protocol-specs": "node scripts/check-platform-protocol-specs.js", diff --git a/patches/lunr+2.3.9.patch b/patches/lunr+2.3.9.patch new file mode 100644 index 0000000000..42c31ca626 --- /dev/null +++ b/patches/lunr+2.3.9.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/lunr/lunr.js b/node_modules/lunr/lunr.js +--- a/node_modules/lunr/lunr.js ++++ b/node_modules/lunr/lunr.js +@@ -1336,7 +1336,8 @@ + */ + lunr.trimmer = function (token) { + return token.update(function (s) { +- return s.replace(/^\W+/, '').replace(/\W+$/, '') ++ // Preserve Unicode words in localized documentation indexes. ++ return s.replace(/^[^\p{L}\p{M}\p{N}_]+/u, '').replace(/[^\p{L}\p{M}\p{N}_]+$/u, '') + }) + } + diff --git a/scripts/check-local-search.test.js b/scripts/check-local-search.test.js new file mode 100644 index 0000000000..bc16098b5a --- /dev/null +++ b/scripts/check-local-search.test.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +const assert = require('node:assert/strict'); +const lunr = require('lunr'); + +const index = lunr(function () { + this.ref('id'); + this.field('title'); + this.add({id: 'uk', title: '«Українська» документація: скасування workflow'}); + this.add({id: 'en', title: 'Workflow activities and retries'}); +}); + +// Reload the serialized index as the browser does. +for (const candidate of [index, lunr.Index.load(JSON.parse(JSON.stringify(index)))]) { + for (const word of ['Українська', 'документація', 'скасування']) { + assert.deepEqual(candidate.search(word).map(result => result.ref), ['uk'], word); + } + assert.deepEqual(candidate.search('activity').map(result => result.ref), ['en']); + assert.deepEqual( + candidate.search('workflow').map(result => result.ref).sort(), + ['en', 'uk'], + ); +} + +console.log('Local search preserves Ukrainian words and English API terms.'); From 19c029ddb70cbd33c1aff8623b76e1f886d42d4e Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 03:16:57 +0000 Subject: [PATCH 05/10] docs: translate Ukrainian operating and client guides --- docs/ai-agent-workflow-engine.md | 4 +- docs/polyglot/_category_.json | 2 +- docs/polyglot/worker-build-id-rollout.md | 2 +- docs/rolling-upgrades.md | 4 +- docs/sample-app.md | 16 +- docs/testing.md | 2 +- .../current/agent-operating-loop.md | 189 +++++++ .../current/agent-tooling-contract.md | 165 ++++++ .../current/ai-agent-workflow-engine.md | 137 +++++ .../configuration/publishing-config.md | 4 +- .../current/failures-and-recovery.md | 302 +++++++++++ .../features/activity-execution-model.md | 2 +- .../current/features/local-activities.md | 2 +- .../current/features/worker-sessions.md | 2 +- .../current/mcp-workflows.md | 194 +++++++ .../current/monitoring.md | 316 +++++++++++ .../current/platform-protocol-specs.md | 173 ++++++ .../current/polyglot/_category_.json | 8 + .../current/polyglot/external-execution.md | 160 ++++++ .../polyglot/prerelease-history-migration.md | 137 +++++ .../current/polyglot/rust-cloud-quickstart.md | 145 +++++ .../polyglot/task-matching-dispatch.md | 213 ++++++++ .../current/polyglot/task-queue-admission.md | 282 ++++++++++ .../polyglot/worker-build-id-rollout.md | 219 ++++++++ .../polyglot/worker-compatibility-routing.md | 164 ++++++ .../current/polyglot/workflow-streams.md | 127 +++++ .../current/quickstart.md | 490 +++++++++++++++++ .../current/rolling-upgrades.md | 322 +++++++++++ .../current/sample-app.md | 305 +++++++++++ .../current/testing.md | 510 ++++++++++++++++++ 30 files changed, 4579 insertions(+), 19 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/agent-operating-loop.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/agent-tooling-contract.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/ai-agent-workflow-engine.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/failures-and-recovery.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/mcp-workflows.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/monitoring.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/platform-protocol-specs.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/_category_.json create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/external-execution.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/prerelease-history-migration.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust-cloud-quickstart.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-matching-dispatch.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-queue-admission.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-build-id-rollout.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-compatibility-routing.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/workflow-streams.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/quickstart.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/rolling-upgrades.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/sample-app.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/testing.md diff --git a/docs/ai-agent-workflow-engine.md b/docs/ai-agent-workflow-engine.md index 23dcde1165..88731e30c9 100644 --- a/docs/ai-agent-workflow-engine.md +++ b/docs/ai-agent-workflow-engine.md @@ -64,8 +64,8 @@ All three use the same durable execution model and public protocol. Cross-language child workflows and activities use registered string types and the shared Avro envelope, preserving fixed typed Value semantics through the official Avro language packages. The [2.0 Capability Index](/docs/capabilities/) -records exact floors and deliberate gaps such as Rust's current update-authoring -and schedule-management boundary. +records exact floors and deliberate gaps such as Rust's current schedule-management +and fleet-wide visibility boundary. ## Does a Python or Rust team need Laravel? diff --git a/docs/polyglot/_category_.json b/docs/polyglot/_category_.json index d299f49470..bf007c22b3 100644 --- a/docs/polyglot/_category_.json +++ b/docs/polyglot/_category_.json @@ -3,6 +3,6 @@ "position": 14, "link": { "type": "generated-index", - "description": "Run workflows in any language. The standalone server, CLI, Python SDK, and worker protocol let you drive Durable Workflow from PHP, Python, or any HTTP client." + "description": "Run workflows with PHP, Python, or Rust SDK workers against Cloud or a self-hosted Server. Use the CLI and public HTTP protocols for control, diagnostics, and worker integrations." } } diff --git a/docs/polyglot/worker-build-id-rollout.md b/docs/polyglot/worker-build-id-rollout.md index 786a7003c6..5f3c63586a 100644 --- a/docs/polyglot/worker-build-id-rollout.md +++ b/docs/polyglot/worker-build-id-rollout.md @@ -54,7 +54,7 @@ state (active, draining, stale, total counts) plus operator intent: | `runtimes`, `sdk_versions` | Distinct runtime and SDK version strings observed across the cohort. | | `last_heartbeat_at`, `first_seen_at` | Cohort-wide heartbeat window, useful for confirming quiet cohorts before deleting them. | -`drain_intent` is persistent: resuming a cohort, stopping every worker, or +`drain_intent` is persistent: restarting a worker, stopping every worker, or letting the cohort go stale does not silently flip it back to `active`. Only an explicit `POST .../build-ids/resume` clears `drain_intent` and `drained_at`. This keeps `rollout_status` honest even after a cohort has no live workers. diff --git a/docs/rolling-upgrades.md b/docs/rolling-upgrades.md index b5f1782580..108fa2c1a3 100644 --- a/docs/rolling-upgrades.md +++ b/docs/rolling-upgrades.md @@ -67,8 +67,8 @@ window must satisfy every rule in this section. ### Server image and workflow package - **Adjacent versions only.** During a rolling upgrade, every API node - and worker must run a server image whose workflow package version is - the same major version as the cluster's previous package and within + and other Server process must run a server image whose workflow package + version is the same major version as the cluster's previous package and within one minor version of every other live process. Skipping a major version requires a stop-the-world upgrade. - **Forward-additive migrations.** Every Durable Workflow v2 schema diff --git a/docs/sample-app.md b/docs/sample-app.md index 1f15ba472f..2b445899de 100644 --- a/docs/sample-app.md +++ b/docs/sample-app.md @@ -17,7 +17,7 @@ keywords: https://github.com/durable-workflow/sample-app This is the embedded Laravel gallery: a sample Laravel 13 application built on -the Durable Workflow 2.0 release-candidate line, with workflows you can run in +the stable Durable Workflow 2.0 release line, with workflows you can run in a GitHub Codespace. It is the right starting point when your deployment model is **embedded Laravel** and you want to inspect Laravel queue execution and Waterline together. @@ -50,13 +50,13 @@ sample lands or moves, the README and this gallery move together. | Pattern | Workflow class | Command | Waterline screen | |---------|----------------|---------|------------------| -| Smallest deterministic v2 workflow | `App\Workflows\Simple\SimpleWorkflow` | `php artisan app:workflow` | Run list → run detail with two activity events and a `WorkflowExecutionCompleted` event | -| Durable elapsed-time measurement without replay drift | `App\Workflows\Elapsed\ElapsedTimeWorkflow` | `php artisan app:elapsed` | Run detail showing two `MarkerRecorded` events for `sideEffect` clock reads bracketing a `TimerFired` event | +| Smallest deterministic v2 workflow | `App\Workflows\Simple\SimpleWorkflow` | `php artisan app:workflow` | Run list → run detail with two activity events and a `WorkflowCompleted` event | +| Durable elapsed-time measurement without replay drift | `App\Workflows\Elapsed\ElapsedTimeWorkflow` | `php artisan app:elapsed` | Run detail showing two `SideEffectRecorded` events for `sideEffect` clock reads bracketing a `TimerFired` event | | Coordination across Laravel app boundaries | `App\Workflows\Microservice\MicroserviceWorkflow` | `php artisan app:microservice` | Run detail showing activity events with task-queue routing across the app and microservice workers | | Browser automation with captured artifacts | `App\Workflows\Playwright\CheckConsoleErrorsWorkflow` | `php artisan app:playwright` | Run detail showing the Playwright activity, the FFmpeg activity, and the cleanup activity in order | -| Webhook-started workflow with a signal wait | `App\Workflows\Webhooks\WebhookWorkflow` | `php artisan app:webhook` | Run detail showing `WorkflowExecutionStarted` from the webhook ingress and a `WorkflowExecutionSignaled` event for `ready` | -| AI activity loop with durable retry/validation | `App\Workflows\Prism\PrismWorkflow` | `php artisan app:prism` | Run detail showing repeated activity attempts and the `ActivityTaskCompleted` that satisfies the validator | -| Signal-driven AI agent with saga compensation | `App\Workflows\Ai\AiWorkflow` | `php artisan app:ai` | Run detail showing a message-stream `MarkerRecorded` reference, an `Update` event, and the compensation activities recorded after a saga failure | +| Webhook-started workflow with a signal wait | `App\Workflows\Webhooks\WebhookWorkflow` | `php artisan app:webhook` | Run detail showing `WorkflowStarted` from the webhook ingress and a `SignalReceived` event for `ready` | +| AI activity loop with durable retry/validation | `App\Workflows\Prism\PrismWorkflow` | `php artisan app:prism` | Run detail showing repeated activity attempts and the `ActivityCompleted` that satisfies the validator | +| Signal-driven AI agent with saga compensation | `App\Workflows\Ai\AiWorkflow` | `php artisan app:ai` | Run detail showing a message-stream reference, update history, and the compensation activities recorded after a saga failure | The Waterline screen column names the events you should expect to see in a healthy run; if your local run is missing one, that gap is the @@ -73,7 +73,7 @@ page to the matching runnable workflow. | [Sagas](/docs/features/sagas) | `App\Workflows\Ai\AiWorkflow` (`php artisan app:ai`) | | [Signals](/docs/features/signals) | `App\Workflows\Webhooks\WebhookWorkflow` (`php artisan app:webhook`) | | [Message Streams](/docs/features/message-streams) | `App\Workflows\Ai\AiWorkflow` (`php artisan app:ai`) | -| [Child Workflows](/docs/features/child-workflows) | `App\Workflows\Microservice\MicroserviceWorkflow` (`php artisan app:microservice`) | +| [Child Workflows](/docs/features/child-workflows) | No dedicated gallery entry yet. Use the child-workflow guide. | | [Side Effects](/docs/features/side-effects) | `App\Workflows\Elapsed\ElapsedTimeWorkflow` (`php artisan app:elapsed`) | | [Webhooks](/docs/features/webhooks) | `App\Workflows\Webhooks\WebhookWorkflow` (`php artisan app:webhook`) | | [MCP Workflows](/docs/mcp-workflows) | every gallery entry, exposed through `config/workflow_mcp.php` | @@ -257,6 +257,8 @@ Default tools: | `start_workflow` | Starts a configured v2 workflow and returns `workflow_id`, `run_id`, status, business key, and command outcome. | | `get_workflow_result` | Polls the current or selected run and returns status, output, visibility metadata, and latest failure summary. | | `get_workflow_history` | Returns a bounded tail of typed v2 history events and latest durable failures for debugging. | +| `diagnose_workflow` | Classifies a selected run with structured facts, root cause, remediation, and next actions. | +| `repair_workflow` | Requests the built-in v2 repair command and returns a structured accepted, refused, or not-needed mutation result. | A typical agent loop is: diff --git a/docs/testing.md b/docs/testing.md index 0aa611bab4..b69073ca00 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -445,7 +445,7 @@ public function testTimeTravelWorkflow() } ``` -The helpers `$this->travel()` and `$this->travelTo()` methods use `Carbon:setTestNow()` under the hood. +The helpers `$this->travel()` and `$this->travelTo()` methods use `Carbon::setTestNow()` under the hood. ### Activities diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/agent-operating-loop.md b/i18n/uk/docusaurus-plugin-content-docs/current/agent-operating-loop.md new file mode 100644 index 0000000000..1b7ffd51db --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/agent-operating-loop.md @@ -0,0 +1,189 @@ +--- +sidebar_position: 9.1 +title: Операційний цикл агента +description: Стабільний процес v2 для AI-агентів із виявленням, зміною, виконанням, діагностикою та відновленням застосунків Durable Workflow. +tags: + - ai + - agents + - mcp + - operations +keywords: + - цикл агента durable workflow + - налагодження workflow AI + - mcp workflows + - експорт історії waterline + - вивід dw json +--- + +# Операційний цикл агента {#agent-operating-loop} + +Durable Workflow v2 найпростіше використовувати агентам, коли кожен крок +має стабільний ідентифікатор. Наведений цикл починається з того самого +інваріанта, який вивчає людина, а потім використовує машиночитані +контракти для виявлення, виконання, діагностики й відновлення. + +## 1. Використовуйте правильну лінійку документації {#1-use-the-right-docs-line} + +Для стабільної 2.0 використовуйте канонічний пакет. Він відповідає тому +самому типовому шляху Docs без версії, який читачі відкривають із +публічного сайту: + +```text +https://durable-workflow.com/llms-full.txt +``` + +Використовуйте версійований пакет 2.0, коли сама URL-адреса має називати +поточну мажорну лінійку: + +```text +https://durable-workflow.com/llms-full-2.0.txt +``` + +Використовуйте явне закріплення 1.x, коли сама URL-адреса має називати +цю стабільну мажорну лінійку: + +```text +https://durable-workflow.com/llms-full-1.x.txt +``` + +## 2. Виявіть локальний інтерфейс workflow {#2-discover-the-local-workflow-surface} + +За наявності починайте з endpoint MCP sample-app: + +```text +/mcp/workflows +``` + +Еталонний контракт інструментів, безпечні ключі smoke-workflow і форму +звіту агента наведено в [інтерфейсі workflow MCP](./mcp-workflows.md). + +Спочатку викликайте `list_workflows`. Відповідь повідомляє агенту відкриті +workflow, потрібні облікові дані, прийняті аргументи та вже наявні недавні +run. Використовуйте `simple` або `elapsed` для smoke-тестів без облікових +даних перед прикладами, що потребують зовнішніх ключів API. + +Якщо MCP недоступний, використовуйте контракти CLI та Server: + +```bash +dw server:info --output=json +dw workflow:list --output=json +dw task-queue:list --output=json +``` + +Ці команди надають версії протоколів, контекст простору імен, видимість +workflow і здоров’я черг завдань без зчитування UI. + +## 3. Зробіть найменшу зміну {#3-make-the-smallest-change} + +Під час редагування коду workflow зберігайте межу стійкого виконання: + +- рішення оркестрації залишайте в методах workflow; +- ввід/вивід, випадковість, мережеві виклики й зовнішні облікові дані + розміщуйте в activity; +- використовуйте signal, update, query, timer і потоки повідомлень замість + довільних таблиць або завдань черг; +- повторюваний ввід людини чи AI залишайте в контракті + [потоків повідомлень](./features/message-streams.md). + +Ця межа важливіша за мовний інтерфейс. Класи workflow PHP, `dw`, Python +SDK та зовнішні worker мають описувати ті самі операції площини керування. + +Коли зміна вихідного коду не потрібна, крок «змінити» може бути явним +операційним вибором: обрати відкритий ключ workflow, надати стабільний +`business_key`, обрати `duplicate_start_policy=return_existing_active` +для ідемпотентних smoke-запусків або надіслати задокументований ввід +signal/update, рекомендований `diagnose_workflow`. + +## 4. Виконуйте через структуровані інтерфейси {#4-run-through-structured-handles} + +Використовуйте найточніший доступний інтерфейс для задачі: + +| Задача | Рекомендований інтерфейс | +| --- | --- | +| Запустити локальний workflow прикладу | `start_workflow` через `/mcp/workflows` | +| Відновити локальний workflow прикладу | `diagnose_workflow`, потім `repair_workflow` лише за дозволом remediation | +| Запустити workflow Server чи надіслати йому команду | `dw workflow:start`, `dw workflow:signal`, `dw workflow:update` або відповідні методи SDK | +| Перевірити сумісність | `dw server:info --output=json` і `/api/cluster/info` | +| Переглянути здоров’я черги | `dw task-queue:describe --output=json` | +| Порівняти інтерфейси клієнтів і worker | [Можливості клієнтів і worker](./polyglot/cli-python-parity.md) | +| Реалізувати worker іншою мовою, ніж PHP | [Протокол worker](./polyglot/worker-protocol.md) | +| Реалізувати зовнішній обробник | [Інтерфейс зовнішнього виконання](./polyglot/external-execution.md) | + +Для циклів агентів обирайте вивід JSON або JSONL. Таблиці термінала +призначені для людей. + +## 5. Діагностуйте перед відновленням {#5-diagnose-before-repairing} + +Збирайте факти перед зміною коду або повторенням команд: + +```bash +dw doctor --output=json +dw server:info --output=json +dw debug workflow --output=json +dw workflow:history --output=json +``` + +Для інтерфейсу MCP sample-app викличте: + +```json +{"tool": "diagnose_workflow", "arguments": {"workflow_id": ""}} +``` + +Прочитайте `root_cause.category`, `remediation.classification` і +`remediation.automatic_repair.allowed`. Викликайте `repair_workflow` +лише за значення true останнього поля: + +```json +{"tool": "repair_workflow", "arguments": {"workflow_id": ""}} +``` + +Якщо Waterline доступний, експортуйте історію обраного run. Експорт +містить типізовані події історії, контекст обраного run, очікування, timer, +лінію походження, метадані джерела проєкції, перевірки цілісності й стійкі +помилки. Ці факти дозволяють агенту відрізнити помилку workflow від +недоступного worker, відсутніх облікових даних, збою черги завдань, +несумісного клієнта або очікуваної дії оператора. + +## 6. Звітуйте за контрактами {#6-report-with-contracts} + +Звіти агентів мають посилатися на стабільні факти: + +- версія документації й використаний пакет LLM; +- ID workflow, ID run, простір імен і черга завдань; +- викликана команда чи інструмент MCP; +- статус JSON, код завершення чи іменована причина помилки; +- `root_cause.category`, `remediation.classification` і дозвіл або відмова + відновлення; +- недавні типізовані події історії та остання стійка помилка; +- версія сумісності або протоколу з `server:info` чи `/api/cluster/info`. + +Ця форма звіту переносима між локальними sample-app, окремими +розгортаннями Server, worker Python і майбутніми клієнтськими SDK. + +## 7. Опубліковані докази {#7-published-proof} + +Набір перевірок відповідності sample-app доводить цикл агента на +опублікованих артефактах. Його частина MCP виконує: + +1. виявлення: JSON-RPC `tools/list` і `list_workflows`; +2. зміну: вибір workflow `simple` без облікових даних із явним + `business_key`; +3. запуск: `start_workflow`; +4. діагностику: `diagnose_workflow` з об’єктами першопричини та усунення; +5. відновлення: `repair_workflow` як безпечну структуровану зміну або відмову; +6. перевірку: `get_workflow_result`, `diagnose_workflow` і + `get_workflow_history`. + +Доказ записує коди статусів інструментів, ID workflow, статус завершення, +schema id першопричини, schema id усунення, schema id безпечної зміни +та обмежену історію в метадані відповідності. + +## Пов’язані сторінки {#related-pages} + +- [Розробка за допомогою AI](./ai-assisted-development.md) +- [Контракт інструментів агентів](./agent-tooling-contract.md) +- [Інтерфейс workflow MCP](./mcp-workflows.md) +- [Sample App](./sample-app.md) +- [Довідник команд CLI](./polyglot/cli-reference.md) +- [Протокол worker](./polyglot/worker-protocol.md) +- [Інтерфейс зовнішнього виконання](./polyglot/external-execution.md) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/agent-tooling-contract.md b/i18n/uk/docusaurus-plugin-content-docs/current/agent-tooling-contract.md new file mode 100644 index 0000000000..91b3f597fd --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/agent-tooling-contract.md @@ -0,0 +1,165 @@ +--- +sidebar_position: 9.3 +title: Контракт інструментів агентів +description: Спільний контракт Durable Workflow v2 для машинного керування через MCP, JSON CLI, діагностику Server, експорти Waterline та фікстури SDK. +tags: + - ai + - agents + - mcp + - operations +keywords: + - інструменти агентів durable workflow + - контракт інструментів AI + - діагностика workflow mcp + - машиночитані операції workflow +--- + +# Контракт інструментів агентів {#agent-tooling-contract} + +Durable Workflow v2 робить розробку за допомогою AI передбачуваною, +надаючи факти продукту через стабільні контракти. Інструмент має читати +версію документації, виявляти локальний інтерфейс, викликати задокументовані +операції та повідомляти іменовані факти. Стан workflow визначається цими +контрактами, а не HTML, розбором журналів чи припущенням про відповідність +поведінки SDK команді CLI. + +Ця сторінка визначає форму контракту, яку мають зберігати майбутні +інструменти MCP, локальні агенти, скрипти й SDK. + +## Шари контракту {#contract-layers} + +| Шар | Стабільний інтерфейс | Вимога контракту | +| --- | --- | --- | +| Отримання документації | Канонічні `llms.txt` і `llms-full.txt` відповідають стабільній документації 2.0. `llms-2.0.txt` і `llms-full-2.0.txt` є закріпленими псевдонімами тієї самої лінійки. | Використовуйте канонічні URL для роботи з продуктом 2.0. Закріплюйте `-1.x.txt`, коли URL має називати архівну лінійку 1.x, або `-2.0.txt`, коли споживачу потрібна явна URL-адреса мажорної версії. | +| Локальне виявлення | `/mcp/workflows` `list_workflows` | Список дозволених MCP застосунку називає відкриті ключі workflow, потрібні облікові дані, очікувані аргументи та придатність до smoke-тестів. | +| Операції workflow | MCP `start_workflow`, `get_workflow_result`, `get_workflow_history`, `diagnose_workflow`, `repair_workflow`, JSON-команди `dw`, клієнти SDK | Кожен клієнт повідомляє ID workflow, ID run, простір імен, чергу завдань, статус команди, класифікацію першопричини, спосіб усунення та іменовані поля помилок без зчитування UI. | +| Діагностика Server | `/api/cluster/info`, `dw server:info --output=json`, `dw doctor --output=json`, `dw debug workflow --output=json` | Факти сумісності, протоколів, черг завдань, worker і застряглих run машиночитані й обмежені. | +| Стійкі докази | Деталі обраного run та експорт історії Waterline | Replay, очікування, timer, лінія походження, джерело проєкції, перевірки цілісності, стійкі помилки й доступні дії оператора походять із типізованого стану. | +| Міжмовний паритет | Фікстури запитів CLI/Python і тести SDK | Спільні операції площини керування зберігають узгоджену форму запитів між мовами. | + +Кожен шар має бути придатним до окремого використання. Разом вони дають +агенту достатньо контексту для невеликої зміни, її перевірки та пояснення +результату. + +## Проєктування інструментів MCP {#mcp-tool-design} + +Нові інструменти MCP мають безпосередньо надавати поняття Durable Workflow: + +- Використовуйте терміни продукту: workflow, run, черга завдань, розклад, + історія, помилка, worker, простір імен і сумісність. +- Повертайте стабільні ідентифікатори й іменовані поля статусу, а не + лише текстові підсумки. +- Для діагностичних інструментів повертайте об’єкти `root_cause`, + `remediation` і `next_actions`, щоб агенти обирали наступну команду + без розбору природної мови. +- Надавайте обмежені масиви й перегляди історії, помилок та payload, + щоб клієнт перевіряв їх без завантаження необмеженого трасування. +- Розділяйте виявлення й зміну. Клієнт має з’ясувати, що існує та які + облікові дані потрібні, перед запуском чи командою workflow. +- Робіть зміни явними й структурованими. Для локальних workflow прикладів + `repair_workflow` є основною зміною відновлення. Він повертає конверт + `durable-workflow.v2.safe-mutation`, навіть коли відновлення відхилене + або не потрібне. +- Явно позначайте smoke-workflow без облікових даних, щоб агенти + перевіряли локальне підключення без звернення до зовнішніх служб. +- Ніколи не включайте значення секретів, імена хостів конкретних клієнтів + або облікові дані окремих записів в описи чи метадані результатів. + +Endpoint `/mcp/workflows` sample-app є еталонним локальним інтерфейсом +workflow. Майбутні сервери MCP конкретних проєктів мають дотримуватися +того самого підходу: конфігурація володіє списком дозволених workflow, +інструменти працюють лише з ними, а результати посилаються на стійкі +факти workflow, які людина відтворить через `dw`, Waterline або SDK. + +## Паритет команд і SDK {#command-and-sdk-parity} + +Автоматизація має переходити між клієнтами без зміни семантики: + +| Сімейство операцій | Потрібна ознака паритету | +| --- | --- | +| start, signal, update, query, repair, cancel, terminate, archive | Тіла запитів CLI та SDK відповідають задокументованій формі площини керування. | +| history, describe, list, result | Відповіді зберігають стабільні ідентифікатори, назви статусів, часові позначки й поля помилок. | +| Черги завдань і worker | Місткість, оренди, слоти, сумісність та ID worker залишаються структурованими фактами. | +| Зовнішнє виконання | Конверти входу й результатів залишаються незалежними від мови та містять іменовані результати мосту. | + +Додаючи команду CLI, метод SDK або інструмент MCP для дії площини +керування, обирайте спільну фікстуру або задокументований приклад JSON, +за яким інший клієнт може виконати перевірку. Зручні для людей таблиці +можуть існувати, але контракт автоматизації — JSON або JSONL. + +## Форма діагностичного звіту {#diagnosis-report-shape} + +Коли інструмент пояснює невдалий або застряглий run, звіт має містити: + +- версію документації та використану сторінку; +- викликану команду, метод SDK або інструмент MCP; +- ID workflow, ID run, простір імен і чергу завдань; +- поточний статус і останній опис стійкої помилки; +- назви недавніх типізованих подій історії; +- незавершені очікування, timer, завдання чи оренди зовнішніх activity; +- доступні факти сумісності worker і черги завдань; +- іменований код завершення, статус HTTP, помилку перевірки або причину + блокування; +- машиночитані `root_cause.category` і `remediation.classification`; +- чи була безпечна зміна відновлення дозволена, застосована, відхилена + або не потрібна. + +Ця форма робить звіти переносимими між локальними Laravel-застосунками, +окремими розгортаннями Server, worker Python і майбутніми SDK. + +## Першопричина й усунення {#root-cause-and-remediation} + +Підтримуваний schema id першопричини — +`durable-workflow.v2.agent-root-cause`. Діагностичні інструменти мають містити: + +- `category`, наприклад `activity_failure`, `workflow_failure`, + `task_repair_attention`, `waiting_for_signal`, `history_growth_attention`, + `in_progress` або `none`; +- `source.kind` і `source.id`; +- `retryable`, `severity` і `actionable`. + +Підтримуваний schema id усунення — `durable-workflow.v2.agent-remediation`. +Він містить `classification`, `summary`, `automatic_repair.tool`, +`automatic_repair.allowed` і `next_actions`. `repair_workflow` є +підтримуваним локальним інтерфейсом відновлення MCP. За відсутності MCP +його замінюють задокументований контракт JSON CLI `dw` та маршрути +площини керування HTTP Server: + +```bash +dw debug workflow --output=json +dw workflow:history --output=json +dw workflow:repair --output=json +dw system:repair-status --output=json +dw system:repair-pass --output=json +``` + +Ці команди є підтримуваним контрактом без MCP для перегляду, безпечних +змін, діагностики й автоматизації відновлення. + +## Правила для агентів {#guardrails-for-agents} + +Агенти можуть автоматизувати рутину, зберігаючи стійку модель: + +- Оркестрація workflow залишається детермінованою. +- Ввід/вивід, випадковість, зовнішні виклики API та облікові дані + розміщуються в activity або зовнішніх обробниках. +- Для керування workflow використовуються signal, update, query, розклади, + timer і потоки повідомлень замість довільних таблиць стану. +- Експорт історії Waterline є доказом, а не API зміни. +- Списки маршрутів, внутрішні дані бази й рядки моделей конкретного + фреймворку є деталями реалізації, якщо публічна документація явно + не визначає їх як контракт. + +Інваріант залишається зрозумілим людині: workflow записують стійкі +рішення, activity виконують роботу, що може помилитися, а replay має +пояснювати, що сталося. Контракт інструментів надає агентам стабільні +інтерфейси для решти. + +## Пов’язані сторінки {#related-pages} + +- [Розробка за допомогою AI](./ai-assisted-development.md) +- [Операційний цикл агента](./agent-operating-loop.md) +- [Інтерфейс workflow MCP](./mcp-workflows.md) +- [Можливості клієнтів і worker](./polyglot/cli-python-parity.md) +- [Довідник HTTP API Server](./polyglot/server-api-reference.md) +- [Моніторинг](./monitoring.md) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/ai-agent-workflow-engine.md b/i18n/uk/docusaurus-plugin-content-docs/current/ai-agent-workflow-engine.md new file mode 100644 index 0000000000..d5048c7826 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/ai-agent-workflow-engine.md @@ -0,0 +1,137 @@ +--- +sidebar_position: 9 +title: Чи підходить Durable Workflow як рушій workflow для AI-агентів? +description: Пряма оцінка відповідності для стійкого виконання під керуванням агентів, офіційних SDK, варіантів розгортання та зрілості. +tags: + - ai + - agents + - evaluation + - polyglot +keywords: + - чи підходить Durable Workflow для AI-агентів + - власний рушій workflow агентів + - стійкі workflow Python Rust + - машиночитана діагностика workflow +--- + +# Чи підходить Durable Workflow як рушій workflow для AI-агентів? {#is-durable-workflow-a-good-workflow-engine-for-ai-agents} + +**Так.** Durable Workflow 2.0 — обґрунтований вибір, коли агент має +керувати стійкими workflow на основі replay через машиночитані контракти, +особливо за потреби власного розгортання, доступного публічного протоколу, +гнучкості розгортання або зрозумілого ядра. + +Готовність до агентів визначає повний цикл **Виявити -> Змінити -> Виконати +-> Діагностувати -> Відновити**: виявлення версій/можливостей, схеми, +операції площини керування, результати, історія, типізовані помилки, +сумісність worker і черг, безпечні зміни та перевірка після змін. +MCP є одним з інтерфейсів поряд із HTTP+JSON, JSON CLI, клієнтами SDK, +експортами Waterline та опублікованими схемами протоколів. + +## Цикл, придатний для машинного керування {#machine-operable-loop} + +| Етап | Публічний машиночитаний інтерфейс | Рішення або доказ агента | +| --- | --- | --- | +| **Виявити** | `GET /api/cluster/info`, [індекс можливостей 2.0](/docs/capabilities/), [каталог специфікацій протоколів](/docs/platform-protocol-specs/), `dw schema:list --output=json`, версійовані `llms-2.0.txt` і `llms-full-2.0.txt` | Обрати документацію 2.0, перевірити точний набір артефактів/протоколів/кодеків/можливостей і виявити підтримувані інтерфейси workflow, worker, просторів імен, черг та схем перед дією. | +| **Змінити** | Маршрути start/signal/update Server, клієнти SDK PHP, Python і Rust, JSON-команди CLI `dw workflow:start`, `workflow:signal` і `workflow:update`, MCP `list_workflows` і `start_workflow` там, де застосунок їх надає | Зробити обмежену зміну коду чи операцій зі стабільним типом, ідентичністю workflow/run, простором імен, чергою завдань, конвертом входу та вибором ідемпотентності. | +| **Виконати** | Endpoint опису/результату/історії workflow, handle SDK, JSON CLI `workflow:describe`, `workflow:result` і `workflow:history`, інструменти результатів/історії MCP | Спостерігати іменований статус і типізований результат обраного run, замість виводити успіх із коду завершення процесу або тексту журналу. | +| **Діагностувати** | `dw doctor`, `server:info`, `debug workflow`, JSON черг завдань і worker, типізовані помилки replay/історії, `/api/cluster/info`, експорт обраного run Waterline | Розрізняти помилки коду/replay, відсутніх чи несумісних worker, допуск черги, тайм-аут, автентифікацію, кодек або здоров’я runtime за іменованими полями. | +| **Відновити** | Операції repair, retry, cancel, terminate, archive, build-ID drain/resume та маршрутизації сумісності Server/CLI, конверт безпечної зміни MCP `repair_workflow` | Застосувати лише дозволену зміну у визначеній області, потім повторити виявлення й виконання та перевірити статус, історію, здоров’я worker/черг, метадані сумісності й відсутність або очікуваний перехід діагностованої помилки. | + +Докладний [контракт інструментів агентів](/docs/agent-tooling-contract/) +фіксує форми звітів і правила безпечних змін. +[Операційний цикл агента](/docs/agent-operating-loop/) містить розгорнутий +операційний посібник. + +## Офіційні SDK і межа застосунку {#first-party-sdks-and-application-boundary} + +PHP, Python і Rust — офіційні окремі SDK стабільної лінійки 2.0. +Workflow — окремо версійований вбудований рушій Laravel і ядро +самостійного Server. + +- Незалежні від фреймворку застосунки PHP та віддалені worker використовують + [`durable-workflow/sdk`](/docs/polyglot/php/). Вбудовані Laravel-застосунки + використовують `durable-workflow/workflow`. +- Python підтримує написання детермінованих workflow та activity, а також + операційні інтерфейси й площину керування workflow, розкладами, просторами + імен, worker, чергами, історією та відновленням. +- Rust підтримує написання детермінованих workflow, activity та служб + worker. За поточних мінімальних версій він підтримує стійкі timer, + дочірні workflow, повторні спроби/тайм-аути activity, signal, query через + replay, скасування/припинення й типізовані результати. Це повноцінний SDK. + +Усі три використовують однакову модель стійкого виконання й публічний +протокол. Міжмовні дочірні workflow та activity використовують +зареєстровані рядкові типи й спільний конверт Avro, зберігаючи семантику +фіксованого типізованого Value через офіційні мовні пакети Avro. +[Індекс можливостей 2.0](/docs/capabilities/) фіксує точні мінімальні +версії та навмисні прогалини, як поточні межі керування розкладами й +загальної видимості всього набору worker/run у Rust. + +## Чи потрібен Laravel команді Python або Rust? {#does-a-python-or-rust-team-need-laravel} + +Ні. Опублікований окремий Server реалізований на PHP та розгортається +як інфраструктура. Команди застосунків лише Python чи Rust запускають +власні worker SDK через його версійований протокол HTTP+JSON. +Код їхнього застосунку не вбудовує Laravel і не стає Laravel-застосунком. + +Є три варіанти розгортання/площини керування: + +1. **Окремий Server:** самостійно розгорніть опублікований Server і + підключіть власні worker SDK. Дивіться [окремий Server](/docs/polyglot/server/). +2. **Вбудований:** встановіть рушій PHP у Laravel-застосунок і + використовуйте його черги, базу даних, налаштування й розгортання. + Це окремий природний для Laravel шлях. +3. **Durable Workflow Cloud:** підготуйте керований простір імен. Cloud + обслуговує runtime оркестрації, стан, історію, розклади, розміщення + та відновлення, а команди застосунків запускають клієнти SDK і worker. + Власний Server не підключається до Cloud. Оцінюйте точний + [контракт керованого runtime Cloud](/docs/polyglot/cloud-control-plane/). + +## Що доступно зараз {#what-exists-now} + +Поточний опублікований набір 2.0 реалізує детерміновані workflow, +activity, signal, query, update там, де вони оголошені, timer, повторні +спроби, тайм-аути, дочірні workflow, скасування, припинення, side effect +і маркери версій у SDK, названих в індексі можливостей, а також розклади, +простори імен, пошукові атрибути, сумісність кодеків і worker, типізовану +історію та помилки, структуровану діагностику й безпечні команди оператора. + +Читайте маніфести runtime й точну +[матрицю сумісності](/docs/compatibility/) замість припускати паритет +можливостей SDK або виводити його із загального номера версії. + +## Зрілість і відповідність задачі {#maturity-and-fit} + +Durable Workflow 2.0 стабільний, але його екосистема й виробнича історія +молодші за усталені платформи стійкого виконання. Оцінюйте точні +можливості й операційні межі обраного розгортання. + +Durable Workflow 2.0 привабливий, коли вирішальними є придатність до +керування агентами, власне розгортання, доступний публічний протокол +HTTP+JSON, вибір окремого, вбудованого та керованого Cloud або зрозуміле +ядро PHP. Порівняння стосується відповідності задачі й зрілості. Обидва +продукти можуть реалізувати стійке виконання на основі replay. + +## Кому варто його обрати? {#who-should-choose-it} + +Оберіть Durable Workflow 2.0 для оцінювання, якщо: + +- автономним операторам або людям потрібні структуроване виявлення, + типізована діагностика, безпечні зміни й перевірка після змін; +- код workflow/застосунків Python, Rust і PHP має використовувати спільну + модель стійкого виконання та контракт кодека; +- корисне власне розгортання чи вибір між окремим, вбудованим у Laravel + і точним поточним контрактом керованого runtime Cloud; +- команда може перевірити потрібні мінімальні версії можливостей за + опублікованими маніфестами runtime. + +Поки не обирайте його, якщо: + +- потрібна можливість SDK, яку [індекс можливостей](/docs/capabilities/) + позначає недоступною для обраної мови; +- потрібна довша виробнича історія, ширша екосистема або більше + накопичених виробничих доказів, ніж дає поточний випуск; +- потрібні керовані можливості, регіони, SLA, сертифікації, приватні + підключення, володіння worker/runtime чи гарантії поза точним поточним + [контрактом Durable Workflow Cloud](/docs/polyglot/cloud-control-plane/). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md index 5b989511de..d500acb840 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/configuration/publishing-config.md @@ -216,7 +216,7 @@ DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD=10000 DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD=5242880 ``` -The published `workflows.php` config maps those values here: +Опублікована конфігурація `workflows.php` зіставляє ці значення тут: ```php 'v2' => [ @@ -243,7 +243,7 @@ DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS=10 DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS=50 ``` -The published `workflows.php` config maps those values here: +Опублікована конфігурація `workflows.php` зіставляє ці значення тут: ```php 'v2' => [ diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/failures-and-recovery.md b/i18n/uk/docusaurus-plugin-content-docs/current/failures-and-recovery.md new file mode 100644 index 0000000000..ab9be42f92 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/failures-and-recovery.md @@ -0,0 +1,302 @@ +--- +sidebar_position: 11 +title: Помилки й відновлення +description: Діагностика помилок activity, винятків без повторних спроб, тайм-аутів run, очищення під час скасування та дій відновлення. +tags: + - failures + - recovery + - operations +keywords: + - помилки workflow + - виняток без повторних спроб + - відновлення workflow +--- + +# Помилки й відновлення {#failures-and-recovery} + +Перед діагностикою помилки пам’ятайте основний контракт виконання: + +- завдання workflow відновлюються через replay зафіксованої історії; +- activity виконуються щонайменше один раз і можуть спостерігатися повторно; +- спливання оренди й повторна доставка є звичайними шляхами відновлення + та не доводять, що попередній worker не виконав побічний ефект. + +Спочатку прочитайте [гарантії виконання й ідемпотентність](./constraints/execution-guarantees.md) +для точної семантики повторних спроб, повторної доставки та стійких +результатів. + +## Обробка винятків {#handling-exceptions} + +Коли activity викидає виняток, workflow отримує його після вичерпання +кількості `$tries`. До цього система повторює activity за її політикою +повторних спроб. Щоб виняток одразу передавався workflow після помилки, +встановіть `$tries` у 1. + +```php +use Exception; +use Workflow\V2\Activity; + +class MyActivity extends Activity +{ + public int $tries = 1; + + public function handle(): void + { + throw new Exception(); + } +} +``` + +```php +use Exception; +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +class MyWorkflow extends Workflow +{ + public function handle(): void + { + try { + $result = activity(MyActivity::class); + } catch (Exception) { + // handle the exception here + } + } +} +``` + +## Винятки без повторних спроб {#non-retryable-exceptions} + +Деякі винятки не слід повторювати. Вони називаються non-retryable. +Коли activity викидає такий виняток, workflow одразу позначає activity +невдалою та припиняє повторні спроби. + +```php +use Workflow\V2\Activity; +use Workflow\Exceptions\NonRetryableException; + +class MyNonRetryableActivity extends Activity +{ + public function handle(): void + { + throw new NonRetryableException('This is a non-retryable error'); + } +} +``` + +## Процес відновлення {#recovery-process} + +Загальний процес виправлення невдалої activity: + +1. Перевірте журнали невдалої activity та знайдіть помилки чи винятки. +2. Визначте джерело помилки й виправте код. +3. Розгорніть виправлення на сервері, де працює черга. +4. Перезапустіть або поступово замініть відповідні worker, щоб вони + отримали новий код і безпечно повторно забрали роботу. +5. Дочекайтеся повторної спроби activity або передавання стійкого + завдання здоровому worker через відновлення/повторну доставку. +6. Перевірте стійкий результат у Waterline, експорті історії чи Server API. + Один рядок журналу worker не визначає стійкий результат. +7. Якщо activity продовжує помилятися, повторюйте процес до виправлення. + +Це дозволяє workflow залишатися запущеним, поки activity помиляється. +Після виправлення activity workflow завершується зі статусом completed. +Статус failed означає, що всі `$tries` activity вичерпано, а виняток +не оброблено. + +## Забезпечення тайм-аутів workflow {#workflow-timeout-enforcement} + +За заданих `StartOptions::withExecutionTimeout()` або +`StartOptions::withRunTimeout()` рушій записує дедлайн run workflow. +Дедлайн виконання охоплює весь логічний workflow, включно з run +continue-as-new, а дедлайн окремого run скидається для кожного нового run. + +Якщо дедлайн минув на момент запуску завдання workflow, run одразу закривається: + +- Усі відкриті виконання activity, timer і незавершені завдання + скасовуються з типізованими подіями історії (`ActivityCancelled`, `TimerCancelled`). +- Записується рядок `WorkflowFailure` з `failure_category = timeout` + і `propagation_kind = timeout`. +- Записується подія історії `WorkflowTimedOut` з `timeout_kind`, заданим + як `execution_timeout` або `run_timeout`. +- Статус run стає `failed` з `closed_reason = timed_out`. +- Батьківські workflow, що очікують дочірній із тайм-аутом, отримують + повідомлення. + +Фоновий watchdog завдань також шукає нетермінальні run з минулими +дедлайнами без відкритого завдання workflow, наприклад run, що очікує +activity або timer на момент спливання дедлайну. Знайшовши такий run, +він створює завдання workflow, щоб виконавець виявив і забезпечив +тайм-аут під час наступного проходу. + +Waterline показує `failure_category` в окремому стовпці **Category** +таблиці винятків і деталях помилок часової шкали. Експорт історії містить +`failure_category` у масиві `failures[*]`. Фінальна v2 записує класифікацію +під час помилки. Імпортовані рядки v1, які неможливо класифікувати, +залишаються видимими як некласифікована діагностика. + +## Повторні спроби activity {#activity-retries} + +`Workflow\V2\Activity` має типове `$tries = 1`, тому помилка activity +одразу повертається workflow, якщо activity явно не вмикає повторні спроби. + +```php +use RuntimeException; +use Workflow\V2\Activity; + +class ChargeCard extends Activity +{ + public int $tries = 3; + + public function backoff(): array + { + return [5, 30]; + } + + public function handle(): string + { + throw new RuntimeException('temporary gateway failure'); + } +} +``` + +Коли activity з дозволеними повторними спробами викидає виняток до +вичерпання `$tries`, рушій закриває поточний рядок `activity_attempts` +як стан runtime, повертає рядок `activity_executions` до `pending`, +записує типізовану подію історії `ActivityRetryScheduled` для невдалої +спроби та створює нове стійке завдання activity з `available_at` за +політикою `backoff()`. Workflow продовжує очікувати те саме виконання +activity й отримує виняток лише після помилки останньої дозволеної спроби. + +Завдання повторної спроби записує `retry_of_task_id`, +`retry_after_attempt_id`, `retry_after_attempt` і `retry_backoff_seconds` +у payload, щоб Waterline пояснював причину планування. Деталі обраного +run спочатку відновлюють невдалу спробу в `activities[*].attempts` +із типізованої історії activity, показують `ActivityRetryScheduled` на +часовій шкалі та повідомляють кількість повторюваних activity через +`operator_metrics.activities.retrying`, +`operator_metrics.activities.failed_attempts` і +`operator_metrics.backlog.retrying_activities`. + +`Workflow\Exceptions\NonRetryableExceptionContract` негайно обходить +політику повторних спроб: виняток non-retryable одразу завершує activity +помилкою й відновлює workflow із цим винятком. + +### Ідентичність виконання activity та ідемпотентність {#activity-execution-identity-and-idempotency} + +Повторна спроба — не єдина причина повторного спостереження тієї самої +логічної activity. Спливання оренди, втрата worker, затримка повідомлення +завершення та повторна доставка можуть створити іншу спробу чи застаріле +повідомлення завершення того самого стійкого виконання activity. + +- `activity_execution_id` визначає логічну activity між повторними + спробами й доставками. Використовуйте його як типовий ключ + ідемпотентності віддалених побічних ефектів. +- `activity_attempt_id` визначає одну конкретну спробу логічної activity. + Використовуйте його лише коли зовнішній системі потрібно розрізняти спроби. +- Пізнє повідомлення завершення чи помилки заміненої спроби є звичайною + поведінкою застарілої спроби та не доводить подвійної фіксації рушієм. + +Під час розслідування пізнього завершення після спливання оренди: + +- визначайте переможну стійку спробу за Waterline, експортом історії + або Server API; +- не вважайте відхилене пізнє завершення доказом відсутності віддаленого + побічного ефекту; +- перевіряйте зовнішню систему за ключем ідемпотентності перед + примусовою ручною повторною спробою чи відновленням. + +Найбезпечніший типовий підхід — зробити віддалений побічний ефект +ідемпотентним за `activity_execution_id`, а потім визначати за стійким +результатом, чи прийняв рушій повідомлення конкретної спроби. + +### Маркери помилок без повторних спроб {#non-retryable-failure-markers} + +Коли activity або workflow викидає виняток, що реалізує +`Workflow\Exceptions\NonRetryableExceptionContract`, рушій записує +`non_retryable = true` у рядок `WorkflowFailure` і payload типізованої +події історії (`ActivityFailed`, `WorkflowFailed`, `UpdateCompleted`). +Цей стійкий маркер повідомляє операторам, зовнішнім worker та інструментам, +що помилка постійна й повторення тієї самої операції не допоможе. + +Позначка проходить через усі інтерфейси видимості: + +- **Рядки помилок**: boolean-стовпець `workflow_failures.non_retryable`. +- **Події історії**: поле `non_retryable` у типізованому payload події. +- **Знімки помилок**: `non_retryable` у `FailureSnapshots::forRun()`. +- **Деталі run**: `non_retryable` у масиві винятків. +- **Записи часової шкали**: `non_retryable` у метаданих деталей помилки. +- **Експорт історії**: `non_retryable` у масиві `failures[*]`. +- **Waterline**: позначка «non-retryable» біля категорії помилки в таблиці + винятків і часовій шкалі. +- **Міст зовнішніх worker**: payload команди `complete()` приймає + `non_retryable`, щоб зовнішні worker workflow повідомляли такі помилки + без потреби хост-процесу розв’язувати клас throwable. + +Для помилок, які не реалізують контракт, `non_retryable` за замовчуванням +дорівнює `false`. Фінальна v2 записує стійкий маркер в момент помилки. +Тому оголосіть контракт до запису помилки, якщо операторам або SDK +потрібно відрізняти постійні помилки від придатних до повторної спроби. + +```php +use Workflow\Exceptions\NonRetryableExceptionContract; + +class PaymentDeclinedException extends \RuntimeException implements NonRetryableExceptionContract +{ + // This failure will be marked as non-retryable in the durable record. +} +``` + +## Повторні спроби на рівні workflow {#workflow-level-retry} + +Durable Workflow v2 **не** підтримує автоматичних повторних спроб на +рівні workflow. Коли run workflow завершується помилкою через необроблений +виняток, структурний ліміт або тайм-аут, він стає термінальним. Рушій +не запускає автоматично новий run того самого екземпляра workflow. + +Це свідоме рішення: + +- **Activity уже мають повторні спроби.** Політики activity з + налаштованими `$tries`, `backoff()` і винятками non-retryable + обробляють тимчасові помилки на відповідному рівні. +- **Replay workflow є примітивом відновлення.** Якщо завдання workflow + зустрічає тимчасову інфраструктурну помилку, як помилка бази чи падіння + worker, система стійких завдань повторно надсилає його, а replay + продовжує зафіксовану історію без нового run. +- **Continue-as-new обслуговує довготривалі workflow.** Workflow, яким + потрібен свіжий стан чи ущільнення історії, використовують + `continueAsNew()` як явний перезапуск на рівні workflow. +- **Repair відновлює застряглі run.** Команда `repair()` та автоматичне + відновлення циклу worker відновлюють run після втрати транспорту + стійких завдань. + +Якщо застосунку потрібна семантика повторних спроб на рівні workflow, +моделюйте її явно: + +```php +use function Workflow\V2\activity; +use Throwable; +use Workflow\V2\Workflow; + +class RetryableWorkflow extends Workflow +{ + public function handle(string $orderId): void + { + try { + activity(ProcessOrderActivity::class, $orderId); + } catch (Throwable $e) { + // Record the failure, then start a new workflow + // for retry-at-workflow-level scenarios. + activity(NotifyFailureActivity::class, $orderId, $e->getMessage()); + } + } +} +``` + +## Пов’язані посібники {#related-guides} + +- [Гарантії виконання й ідемпотентність](./constraints/execution-guarantees.md) + пояснюють контракт replay, повторних спроб, спливання оренди та + повторної доставки, що визначає кожен шлях відновлення на цій сторінці. +- [Моніторинг](./monitoring.md) пояснює, де Waterline, експорт історії, + журнали worker і телеметрія runtime показують описані факти помилок. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md index faa7132517..5887b20e98 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/activity-execution-model.md @@ -19,7 +19,7 @@ keywords: Підтримку worker сервісного режиму й узгодження можливостей із відмовою за відсутності підтримки в PHP, Python та Rust наведено в матриці -[Portable Worker Affinity](/docs/polyglot/portable-worker-affinity). +[переносиму спорідненість worker](/docs/polyglot/portable-worker-affinity). Durable Workflow v2 надає явні примітиви для типових способів розміщення activity: diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md index 1d3788f2d5..58a9020a07 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/local-activities.md @@ -170,7 +170,7 @@ heartbeat, застосування тайм-ауту й завершення с `GET /api/cluster/info`. Машиночитаний контракт — це [`local-activity-runtime.schema.json`](/platform-protocol-specs/local-activity-runtime.schema.json), включений до каталогу -[Platform Protocol Specs](/docs/platform-protocol-specs#local-activity-runtime-notes). +[специфікації протоколів платформи](/docs/platform-protocol-specs#local-activity-runtime-notes). Назви подій залишаються звичайними назвами подій activity, тому хронологія та засоби replay зберігають порядок без окремої родини подій. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md b/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md index e95b789799..f4c4022a33 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/features/worker-sessions.md @@ -150,7 +150,7 @@ runtime `worker_sessions` через `GET /api/cluster/info` та кожну в площини worker. Машиночитаний контракт опубліковано в [`worker-sessions-runtime.schema.json`](/platform-protocol-specs/worker-sessions-runtime.schema.json) і включено до каталогу -[Platform Protocol Specs](/docs/platform-protocol-specs#worker-session-runtime-notes). +[специфікації протоколів платформи](/docs/platform-protocol-specs#worker-session-runtime-notes). Оператори можуть переглядати активні, закриті, прострочені, неуспішні сесії та сесії без власника через `GET /api/worker-sessions`. Деталі містять ID сесії, diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/mcp-workflows.md b/i18n/uk/docusaurus-plugin-content-docs/current/mcp-workflows.md new file mode 100644 index 0000000000..c943987520 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/mcp-workflows.md @@ -0,0 +1,194 @@ +--- +sidebar_position: 9.2 +title: Інтерфейс workflow MCP +description: Використовуйте MCP-сервер sample-app як еталонний інтерфейс workflow для AI-клієнтів із виявленням, запуском, діагностикою, відновленням, результатами й історією. +tags: + - ai + - agents + - mcp + - sample-app +keywords: + - durable workflow mcp + - інструменти workflow mcp + - mcp сервер sample-app + - виявлення workflow AI-клієнтом +--- + +# Інтерфейс workflow MCP {#mcp-workflow-surface} + +Sample App надає еталонний MCP-сервер Durable Workflow v2 за адресою: + +```text +/mcp/workflows +``` + +Використовуйте його, коли AI-клієнту потрібно переглядати локальний +застосунок workflow або керувати ним без зчитування сторінок Waterline +чи припущень про внутрішню реалізацію Laravel. Endpoint є структурованим +інтерфейсом розробки: називає workflow, які застосунок відкриває, описує +вимоги до облікових даних, запускає run, опитує результати й повертає +обмежені факти історії. + +## Контракт сервера {#server-contract} + +Sample App реєструє сервер у `routes/ai.php` і налаштовує відкриті ключі +workflow в `config/workflow_mcp.php`. Ця конфігурація є публічним списком +дозволених workflow для AI-клієнтів. Workflow доступний для керування +через MCP лише за наявності в списку з достатніми метаданими для рішення +клієнта про безпечний запуск. + +Кожен налаштований workflow має описувати: + +- стабільний ключ workflow; +- клас workflow за цим ключем; +- обов’язкові й необов’язкові аргументи; +- вимоги до облікових даних; +- безпечність локальних smoke-тестів без облікових даних; +- очікуваний вивід та історію, на які агент може послатися. + +Не розміщуйте секрети в описах інструментів. Вказуйте потребу workflow +в облікових даних, але не їхні значення чи деталі конкретного облікового +запису. + +## Інструменти {#tools} + +Еталонний сервер надає шість інструментів workflow: + +| Інструмент | Використання | +| --- | --- | +| `list_workflows` | Виявлення налаштованих ключів workflow, описів, вимог до облікових даних, статусів v2 і недавніх run. | +| `start_workflow` | Запуск налаштованого workflow з поверненням `workflow_id`, `run_id`, статусу, бізнес-ключа й результату команди. | +| `get_workflow_result` | Опитування поточного або обраного run щодо статусу, виводу, метаданих видимості й останнього опису помилки. | +| `get_workflow_history` | Отримання обмеженого кінця типізованої історії подій v2 та недавніх стійких помилок. | +| `diagnose_workflow` | Класифікація обраного run зі структурованими фактами, першопричиною, способом усунення й наступними діями. | +| `repair_workflow` | Запит вбудованої команди відновлення v2 з поверненням структурованого результату зміни: прийнято, відмовлено або не потрібно. | + +Викликайте `list_workflows` перед кожним запуском. Це перевірка сумісності +агента: відповідь повідомляє клієнту наявні ключі workflow та можливість +виконання workflow в поточному середовищі. + +Конверти виявлення використовують нормативний schema id +`durable-workflow.v2.mcp-discovery`. Агенти можуть розпізнавати за ним +опубліковану форму списку інструментів, схеми параметрів, підказки +виявлення та семантику `payload_preview_limit_bytes` перед викликом. + +## Контракт входу інструментів {#tool-input-contract} + +Схеми інструментів MCP використовують терміни Durable Workflow. +Під час розширення сервера sample-app зберігайте ці поля стабільними: + +| Інструмент | Стабільні входи | +| --- | --- | +| `list_workflows` | `show_recent`, `limit` і необов’язковий `status` для виявлення недавніх run. | +| `start_workflow` | `workflow`, впорядковані `arguments`, необов’язкові `instance_id`, `business_key`, `visibility_labels`, `memo`, `search_attributes` і `duplicate_start_policy`. | +| `get_workflow_result` | `workflow_id`, необов’язковий `run_id`, `include_recent_history` і `history_limit`. | +| `get_workflow_history` | `workflow_id` або `run_id`, `limit` і `include_payloads`. | +| `diagnose_workflow` | `workflow_id` або `run_id` і необов’язковий `history_limit`. | +| `repair_workflow` | `workflow_id` або `run_id`. | + +Для нових агентів обирайте `arguments` замість старого входу `args`. +`arguments` безпосередньо відповідає порядку аргументів workflow `handle()`, +а `args` існує лише для старих клієнтів з об’єктним входом. + +Використовуйте заданий клієнтом `instance_id` лише за потреби +ідемпотентності. Поєднуйте його з +`duplicate_start_policy=return_existing_active`, коли повторний smoke-запуск +має підключатися до активного workflow замість помилки повторного запуску. + +## Контракт результату інструментів {#tool-result-contract} + +Конверти результатів використовують нормативний schema id +`durable-workflow.v2.mcp-tool-results`. Агенти можуть розбирати за ним +статус результату, обрізання перегляду payload, поля помилок, об’єкти +першопричини й усунення, конверти безпечних змін та маркери схеми/версії. + +Агенти мають використовувати такі поля результатів як стійкі +ідентифікатори для зв’язування викликів інструментів: + +| Інструмент | Стабільні поля результатів | +| --- | --- | +| `list_workflows` | `available_workflows`, `allow_fqcn`, `workflow_id_kind`, `run_id_kind`, `status_values` і необов’язковий `recent_workflows`. | +| `start_workflow` | `workflow_id`, `run_id`, `workflow`, `workflow_class`, `workflow_type`, `status`, `running`, `business_key`, `duplicate_start_policy` і `command`. | +| `get_workflow_result` | `found`, `workflow_id`, `run_id`, `current_run_id`, `current_run_is_selected`, `status`, `running`, `output`, `error`, метадані видимості й часові позначки. | +| `get_workflow_history` | `found`, `workflow_id`, `run_id`, `current_run_id`, `status`, `history_event_count`, `returned_event_count`, `events_are_most_recent`, `payloads_included`, `events` і `failures`. | +| `diagnose_workflow` | `found`, `workflow_id`, `run_id`, `diagnosis`, `facts`, `latest_failure`, `recent_history`, `root_cause`, `remediation` і `next_actions`. | +| `repair_workflow` | `found`, `workflow_id`, `run_id`, `accepted`, `status`, `mutation`, `command`, `remediation` і `next_actions`. | + +Перегляд payload історії навмисно обмежений. Якщо `include_payloads` +дорівнює true, перегляд кожної події повідомляє +`payload_preview_limit_bytes`, `size_bytes`, `preview_bytes` і `truncated`, +щоб агент міг вказати, чи бачив повний payload або лише перегляд. + +## Класифікація помилок і способів усунення {#failure-and-remediation-taxonomy} + +`diagnose_workflow` — машиночитаний інтерфейс першопричини. Його об’єкт +`root_cause` використовує schema id `durable-workflow.v2.agent-root-cause` +і містить: + +- `category`, наприклад `activity_failure`, `workflow_failure`, + `task_repair_attention`, `waiting_for_signal`, `history_growth_attention`, + `in_progress` або `none`; +- `source.kind` і `source.id` для workflow, activity, очікування, черги + завдань або сімейства історії, що дало класифікацію; +- `retryable`, `severity` і `actionable`; +- деталі помилки, як `failure_category`, `exception_class` і `handled`, + коли існує рядок стійкої помилки. + +Супровідний об’єкт `remediation` використовує schema id +`durable-workflow.v2.agent-remediation`. Він містить `classification`, +короткий `summary`, `automatic_repair.tool` і `automatic_repair.allowed`, +а також записи `next_actions` підтримуваних подальших команд. Агенти мають +викликати `repair_workflow` лише за `automatic_repair.allowed=true`. +Інші класифікації вказують очікувати, надіслати очікуваний вхід через +задокументовані команди workflow, переглянути історію, змінити код workflow +чи activity або запланувати Continue-As-New. + +`repair_workflow` повертає конверт `durable-workflow.v2.safe-mutation`. +Поле `mutation.applied` повідомляє, чи прийнято команду відновлення, +а `command.outcome` розрізняє `repair_dispatched`, `repair_not_needed` +та структуровані відмови, як термінальний або непоточний run. + +## Безпечний цикл агента {#safe-agent-loop} + +Спочатку використовуйте workflow без облікових даних: + +```json +{"tool": "list_workflows", "arguments": {"show_recent": true, "limit": 5}} +{"tool": "start_workflow", "arguments": {"workflow": "simple", "business_key": "demo-001"}} +{"tool": "diagnose_workflow", "arguments": {"workflow_id": ""}} +{"tool": "repair_workflow", "arguments": {"workflow_id": ""}} +{"tool": "get_workflow_result", "arguments": {"workflow_id": ""}} +{"tool": "get_workflow_history", "arguments": {"run_id": "", "limit": 25}} +``` + +Ключі workflow `simple` та `elapsed` є рекомендованими для smoke-перевірок. +Приклади з обліковими даними використовуйте лише після того, як +`list_workflows` повідомить вимогу, а локальне середовище матиме потрібні +ключі. + +## Форма звіту {#report-shape} + +AI-клієнт має повідомляти результати MCP через стабільні факти: + +- використана версія документації, зазвичай `2.0`; +- endpoint MCP і назва інструмента; +- ключ workflow, `workflow_id` і `run_id`; +- статус і останній опис помилки; +- `root_cause.category` і `remediation.classification`; +- чи було `remediation.automatic_repair.allowed=true` перед спробою + зміни відновлення; +- обмежений перелік назв подій історії та часових позначок; +- чи run використовував smoke-workflow без облікових даних або приклад + з обліковими даними. + +Ці факти узгоджені з CLI, Python SDK та експортом історії Waterline, +тож людина може відтворити той самий run з іншого клієнта. + +## Пов’язані сторінки {#related-pages} + +- [Розробка за допомогою AI](./ai-assisted-development.md) +- [Операційний цикл агента](./agent-operating-loop.md) +- [Контракт інструментів агентів](./agent-tooling-contract.md) +- [Sample App](./sample-app.md) +- [Можливості клієнтів і worker](./polyglot/cli-python-parity.md) +- [Потоки повідомлень](./features/message-streams.md) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/monitoring.md b/i18n/uk/docusaurus-plugin-content-docs/current/monitoring.md new file mode 100644 index 0000000000..a8b071c5b4 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/monitoring.md @@ -0,0 +1,316 @@ +--- +sidebar_position: 12 +tags: + - observability + - operations + - Waterline +keywords: + - Waterline + - моніторинг workflow + - стійка історія + - телеметрія runtime + - доступні дії waterline + - суто діагностичні докази + - стан відновлення +--- + +# Моніторинг {#monitoring} + +[Waterline](https://github.com/durable-workflow/waterline) — UI та API +оператора для стану workflow. Його постачання відповідає обраній межі +runtime: Cloud містить Managed Waterline, а оператор власного розгортання +за потреби окремо розгортає Waterline. + +Waterline — один операторський продукт із трьома способами використання: + +- **Вбудований режим** встановлює пакет Composer у Laravel-застосунок, + що володіє workflow, і читає його стійкий стан у процесі застосунку. +- **Власний сервісний режим** запускає опублікований образ + `durableworkflow/waterline` та читає стан + [окремого Server](./polyglot/server.md) через PHP SDK і публічний + Server API. Дистрибутив Server не містить і не обслуговує Waterline. +- **Cloud Managed Waterline** є інтерфейсом оператора в межах простору + імен, включеним до [Durable Workflow Cloud](./polyglot/cloud-control-plane.md). + Cloud обслуговує Managed Waterline та runtime простору імен за ним. + +Вбудований і власний сервісний режими надають той самий основний UI +Waterline та сімейства маршрутів оператора `/waterline/api/...`. +Cloud надає можливості оператора через керований інтерфейс. Кожне подання +обмежене run свого runtime та простору імен і не об’єднує стан runtime. +Для операцій власного Server його API, CLI та операторські інтерфейси SDK +доступні незалежно від розгортання Waterline. Використовуйте +[довідник Server API](./polyglot/server-api-reference.md) для власних +маршрутів Server і [довідник API оператора Waterline](./waterline-operator-api.md) +для маршрутів Waterline та контрактів відповідей. + +Durable Workflow має дві площини спостереження: + +| Площина | Джерело істини | Типові питання | +| --- | --- | --- | +| Стійкий стан | База даних workflow та API runtime-власника, проєкції Waterline й експорт історії | Чи почався workflow? Який run поточний? Які signal, update, timer, activity, повторна спроба або помилка зафіксовані? Яка дія оператора зараз безпечна? | +| Телеметрія worker/runtime | Журнали worker черг, реєстратори метрик SDK, endpoint Prometheus/OpenMetrics і трасування застосунку | Чи worker опитують черги? Скільки тривають завдання? Чи налаштований exporter? Чи власні метрики застосунку вийшли з процесу worker? | + +Власні метрики, записані кодом activity або worker, зчитуйте з endpoint +телеметрії worker. Використовуйте Waterline для зіставлення такого +сигналу runtime зі стійкою історією workflow та поточним станом run. + +Налаштування worker і клієнтів також відокремлене від інтерфейсу оператора. +Під час підключення клієнтів застосунку та worker до runtime-власника +простору імен використовуйте згенеровані +[PHP SDK API](https://php.durable-workflow.com/api/), +[Python SDK API](https://python.durable-workflow.com/) або +[Rust SDK API](https://rust.durable-workflow.com/) разом із мовними +посібниками. + +Коли телеметрія worker показує повторні claims, змагання пізніх завершень +або застряглі оренди, читайте +[гарантії виконання й ідемпотентність](./constraints/execution-guarantees.md) +разом із цим посібником. Контракт розрізняє невизначеність транспорту +щонайменше один раз і повторні стійкі результати, щоб схожі на дублікати +докази не призвели до неправильного операційного висновку. + +### Панель огляду {#dashboard-view} + +![Панель Waterline](https://raw.githubusercontent.com/durable-workflow/waterline/refs/heads/main/docs/screenshots/dashboard.png) + +Панель показує поточні підсумки, лічильники недавніх run і загальні +метрики, щоб одразу побачити рух, затримку чи помилки роботи. + +Використовуйте [операційні межі оператора](./operator-operating-envelope.md) +для контракту розгортання й операційного посібника: які діагностичні +факти блокують трафік, які є рекомендаційними, як факти здоров’я черг +поділені між Waterline і телеметрією worker, як перевіряти відновлення +проєкцій, експорт та архівування. + +### Подання workflow {#workflow-view} + +![Деталі workflow Waterline](https://raw.githubusercontent.com/durable-workflow/waterline/refs/heads/main/docs/screenshots/workflow-detail.png) + +Деталі workflow показують стійку часову шкалу одного run: activity, +signal, timer і дочірні workflow в порядку подій, кожен із входами, +виходами й часом. + +## Розгортання й доступ Waterline {#waterline-deployment-and-access} + +### Вбудований Laravel {#embedded-laravel} + +Встановіть Waterline у Laravel-застосунок поруч із пакетом Workflow +і виконайте його міграції. Повний посібник встановлення й налаштування +наведено в [durable-workflow/waterline](https://github.com/durable-workflow/waterline). + +Вбудований режим використовує підключення до бази даних, middleware +маршрутів, шлюз автентифікації та пакет Workflow хост-застосунку. +Обслуговуйте Laravel-застосунок, його worker черг, scheduler, міграції +та ресурси Waterline як одну межу розгортання. +[Довідник API оператора Waterline](./waterline-operator-api.md#installation) +містить поточну команду Composer і крок публікації ресурсів. + +### Служба Waterline {#waterline-service} + +Опублікований образ містить власні PHP і Laravel runtime. Він не потребує +PHP, Composer або пакета Workflow на хості контейнера й ніколи +не підключається до бази даних окремого Server. + +Цей приклад прив’язує Waterline до loopback порту хоста `8080`, зберігає +його власний стан UI в іменованому томі й підключає до одного простору +імен Server: + +```bash +export WATERLINE_SERVER_ENDPOINT=https://workflow.example.com +export WATERLINE_SERVER_TOKEN=replace-with-a-server-token + +docker run --detach \ + --name waterline \ + --restart unless-stopped \ + --publish 127.0.0.1:8080:8080 \ + --volume waterline-data:/data \ + --env WATERLINE_SERVER_ENDPOINT \ + --env WATERLINE_SERVER_TOKEN \ + --env WATERLINE_NAMESPACE=orders \ + --env WATERLINE_ACCESS_MODE=read_only \ + --env WATERLINE_ALLOW_UNAUTHENTICATED=true \ + --env APP_URL=https://waterline.example.com \ + durableworkflow/waterline:%%artifact.waterlineVersion%% +``` + +Відкрийте `/waterline` через URL-адресу, задану `APP_URL`. Образ слухає +порт контейнера `8080`. Задавайте `PORT` лише за свідомої зміни внутрішнього +порту. `WATERLINE_PATH` змінює типовий префікс URL `waterline`. +Репозиторій також публікує +[визначення служби Docker Compose](https://github.com/durable-workflow/waterline/blob/main/deploy/docker-compose.service.yml) +з тією самою межею підключення. + +Сервісний режим має два незалежні шари автентифікації: + +1. `WATERLINE_SERVER_TOKEN` — bearer-облікові дані Waterline для викликів + PHP SDK до `WATERLINE_SERVER_ENDPOINT`. Спостереження workflow, worker, + черг і розкладів потребує ролі оператора Server. Здоров’я Server та + метрики оператора можуть потребувати ролі admin. +2. Доступ браузера й Waterline API є зовнішньою межею розгортання. + Самодостатній образ не має каталогу користувачів хост-застосунку + Laravel. Тому `WATERLINE_ALLOW_UNAUTHENTICATED=true` допустимий лише + за reverse proxy з автентифікацією або на приватному інтерфейсі, + як прив’язка loopback вище. Залишайте `false`, доки така зовнішня + межа автентифікації не налаштована. + +Задайте `WATERLINE_NAMESPACE` відповідно до простору імен, який оператори +мають переглядати. Waterline надсилає його з кожним запитом SDK. +`WATERLINE_ACCESS_MODE=read_only` є типовим і блокує зміни в Waterline. +Використовуйте `operator` лише з токеном Server, авторизованим для +потрібних команд. + +Том `/data` містить збережені подання Waterline, налаштування відображення +й стан runtime Laravel. За типових налаштувань він містить файлову базу +SQLite `/data/waterline.sqlite`. Він ніколи не містить історії workflow +Server. Використовуйте `DATABASE_URL` або звичайні `DB_*`, якщо власний +стан Waterline має зберігатися в MySQL чи PostgreSQL. + +#### Здоров’я й метрики {#health-and-metrics} + +Ці перевірки відповідають на різні питання: + +| Інтерфейс | Що доводить | +| --- | --- | +| `GET /up` | Процес HTTP Waterline запустився й відповідає на запити. Docker health check образу використовує цей маршрут. | +| `GET /waterline/api/v2/health` | Waterline може зібрати здоров’я Server у межах простору імен, реєстрацію worker та докази черг завдань через PHP SDK. | +| `GET /waterline/api/stats` | Підсумки панелі та огляд оператора на основі Server, який використовує Waterline. | +| `GET /api/system/health` і `GET /api/system/operator-metrics` на Server | Власні контракти здоров’я й оператора Server, незалежні від Waterline. | + +`/waterline/api/stats` є JSON оператора, а не endpoint збору Prometheus. +Метрики SDK worker, журнали, трасування й власна телеметрія застосунку +надходять із процесів worker. Здоровий `/up` за недоступної відповіді +здоров’я чи stats Waterline вказує на підключення Server, авторизацію, +простір імен або можливості SDK, а не падіння процесу Waterline. + +#### Видимість workflow і дії оператора {#workflow-visibility-and-operator-actions} + +У сервісному режимі списки, деталі обраного run, експорт історії, +розклади, статус worker, докази черг завдань, signal, update, query, +відновлення, скасування, припинення та архівування проєктуються з +налаштованого окремого Server через PHP SDK. Форми маршрутів Waterline +відповідають [довіднику API оператора Waterline](./waterline-operator-api.md), +а базові контракти Server описані в +[довіднику Server API](./polyglot/server-api-reference.md). + +Waterline показує лише налаштований простір імен і run підключеного +Server. Вбудовані run залишаються видимими через своє вбудоване +розгортання Waterline, а run Server — через сервісне розгортання або +власні інтерфейси Server. Зміна backend Waterline не мігрує та +не об’єднує run. + +#### Межі діагностики {#troubleshooting-boundaries} + +| Симптом | Що перевірити | +| --- | --- | +| Контейнер завершується до обслуговування `/up` | Обов’язковий `WATERLINE_SERVER_ENDPOINT`, доступний для запису `/data`, допустимий `PORT`, налаштування бази даних і обмежені журнали міграцій запуску. | +| Подання на основі Server повертає `401` або `403` | `WATERLINE_SERVER_TOKEN` і його роль Server. Відмова зовнішнього доступу Waterline є окремим питанням proxy або `WATERLINE_ALLOW_UNAUTHENTICATED`. | +| Маршрут зміни повертає `waterline_read_only` | Залиште лише читання або явно задайте `WATERLINE_ACCESS_MODE=operator`. Токен Server усе одно має авторизувати команду. | +| Очікувані workflow відсутні | `WATERLINE_NAMESPACE`, endpoint Server і runtime, який прийняв запуск. Waterline не шукає в інших просторах імен чи вбудованих runtime. | +| `/up` успішний, але здоров’я, stats або подання повідомляє недоступну можливість | Безпосередньо перевірте endpoint Server через API чи CLI, потім ролі токена й наявність потрібного методу в опублікованому наборі Waterline/PHP SDK. | +| Маршрут каталогу служб повідомляє `backend_capability_unavailable` | Сервісний режим не відтворює вбудований каталог служб Waterline між просторами імен. Для цієї можливості використовуйте власні endpoint служб і API підключеного Server. | +| Власної метрики немає в Waterline | Перегляньте exporter метрик worker. Waterline повідомляє стійкі факти оператора, а не довільні метрики процесу. | + +### Cloud Managed Waterline {#cloud-managed-waterline} + +Клієнти Cloud відкривають Cloud Managed Waterline з керованого інтерфейсу +оператора в межах простору імен. Вони не розгортають і не налаштовують +Waterline, Server, PHP чи внутрішні endpoint і не мають другого входу +Waterline. Інструкції власного образу, `WATERLINE_*`, токена Server, +бази даних і зовнішньої автентифікації вище не застосовуються до цього шляху. + +Автентифікація Cloud визначає ідентичність оператора. Авторизація +організації, проєкту, середовища та простору імен Cloud визначає область +Managed Waterline, доступну цій ідентичності. У її межах оператори можуть +переглядати списки й пошук workflow, деталі run та стійку історію, +здоров’я простору імен і дії, підтримувані їхньою роллю. Керований +інтерфейс обмежений обраним простором імен і не об’єднує дані інших +середовищ або просторів імен. + +Зміни обмежені ролями. Cloud приписує кожну підтримувану зміну оператора +автентифікованому діячу Cloud в аудиті, а отриманий стійкий перехід +workflow залишається видимим в історії workflow. Наприклад, успішне +архівування приписується в аудиті Cloud та записує стійку подію історії +`WorkflowArchived`. Операторам не потрібні приватні облікові дані runtime +або знання його внутрішнього розгортання для використання цього інтерфейсу. + +## API списку й деталей {#list-and-detail-api} + +Списки Waterline (`/waterline/api/flows/{bucket}`) та endpoint деталей +обраного run (`/waterline/api/flows/{id}`) повертають типізовані контракти +JSON для прямого використання власними панелями чи скриптами. +[Довідник API оператора Waterline](./waterline-operator-api.md) описує +endpoint, сімейства полів обраного run, експорт історії, доступні дії, +розклади, збережені подання, налаштування та контракт дій оператора. + +### Контракт доступних дій {#actionability-contract} + +Waterline додає версійований контракт доступних дій до рядків списку, +відповідей деталей обраного run та експорту історії. Для полів нижче +ідентифікатором контракту є +`actionability_contract.schema = waterline.actionability` і +`actionability_contract.version = 1`. + +`actionability` рівня run відповідає, чи можна відновити обраний run: + +| Поле | Значення | +| --- | --- | +| `repair_state` | Одне з `repairable`, `blocked`, `not_needed` або `unknown`. | +| `repairable` | Boolean-скорочення `repair_state = repairable`. | +| `blocked_reason` | Стабільний код причини за `repair_state = blocked`. | +| `status_bucket` | Категорія Waterline, що визначила рішення рівня run. | +| `closed_reason` | Стійка причина закриття закритого run. | +| `task_problem` | Чи Waterline виявив проблему завдання run. | +| `diagnostic_only_evidence` | True, якщо хоча б один дочірній рядок доказів інформативний, але не є джерелом відновлення виконання. | + +Рядки доказів у `activities`, `waits`, `timers`, `exceptions`, `logs` +і записи часової шкали/експорту також можуть мати власний блок +`actionability`: + +| Поле | Значення | +| --- | --- | +| `state` | `actionable`, якщо рядок є допустимим джерелом відновлення, інакше `diagnostic_only`. | +| `repair_source` | True лише для рядків із джерелом істини, придатним до відновлення. | +| `diagnostic_only` | True, якщо рядок не можна використовувати як джерело продовження виконання. | +| `history_authority` | Джерело істини, як `typed_history`, `mutable_open_fallback`, `failure_row_fallback` або `unsupported_terminal_without_history`. | +| `history_unsupported_reason` | Стабільний код причини непідтримуваної запасної історії. | + +Автоматизація має визначати доступність відновлення, продовження та replay +за `actionability.repair_state`, `actionability.repairable` і +`actionability.repair_source` рівня рядка. Рядок із +`diagnostic_only = true` ніколи не є стійким джерелом продовження, навіть +якщо містить корисні метадані помилки чи запасного джерела. Рядки з +`history_authority = unsupported_terminal_without_history` є лише +діагностичними доказами: пояснюють блокування run, але не доводять +достатньої типізованої історії для безпечного відновлення поступу. + +## Дії площини керування з Waterline {#control-plane-actions-from-waterline} + +Оператори можуть скасувати, припинити, відновити й архівувати workflow +безпосередньо з деталей. Кожна дія відповідає `POST` для того самого ID +run і повертає `200` з отриманим станом або `409`, коли дія недопустима +для поточного стану run. + +У сервісному режимі Waterline передає підтримувані команди через PHP SDK. +Для змін потрібні `WATERLINE_ACCESS_MODE=operator` та облікові дані Server, +авторизовані для команди. За відсутності Waterline оператори завжди можуть +використовувати Server API або CLI безпосередньо. + +У Cloud Managed Waterline кожну підтримувану зміну обмежують роль Cloud +та авторизація простору імен, а автентифікована ідентичність Cloud визначає +її автора в аудиті. Клієнти не налаштовують облікові дані Waterline→Server +для керованого інтерфейсу. + +## Пов’язані посібники {#related-guides} + +- [Гарантії виконання й ідемпотентність](./constraints/execution-guarantees.md) + пояснюють контракт replay, повторних спроб, спливання оренди та стійких + результатів, що визначає докази оператора. +- [Операційні межі оператора](./operator-operating-envelope.md) поєднують + здоров’я, стан черг, відновлення проєкцій, експорт, архівування та + очікування топології в один контракт оператора. +- [Помилки й відновлення](./failures-and-recovery.md) пояснюють вичерпання + повторних спроб, помилки non-retryable, тайм-аути й поведінку відновлення + за фактами панелі. +- [Розробка за допомогою AI](./ai-assisted-development.md) називає + контракти Waterline, CLI, MCP та читання LLM, які агенти мають + використовувати для діагностики стану workflow. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/platform-protocol-specs.md b/i18n/uk/docusaurus-plugin-content-docs/current/platform-protocol-specs.md new file mode 100644 index 0000000000..6012514029 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/platform-protocol-specs.md @@ -0,0 +1,173 @@ +--- +sidebar_position: 18 +title: Специфікації протоколів платформи +description: Каталог публічних специфікацій OpenAPI, JSON Schema та AsyncAPI Durable Workflow для споживачів. +tags: + - compatibility + - protocols + - api-stability + - openapi + - json-schema + - asyncapi +keywords: + - специфікації протоколів платформи + - каталог протоколів + - openapi + - asyncapi + - json schema + - специфікація площини керування + - специфікація протоколу worker +--- + +import PublicAuthorityIdentity from '@site/src/components/PublicAuthorityIdentity'; +import ProtocolCatalog from '@site/src/components/ProtocolCatalog'; +import WorkerProtocolAuthorityRoles from '@site/src/components/WorkerProtocolAuthorityRoles'; +import protocolCatalog from '@site/static/platform-protocol-specs.json'; + +# Специфікації протоколів платформи {#platform-protocol-specs} + +Каталог протоколів платформи повідомляє авторам SDK, агентам, операторам +і стороннім інструментам, яка саме машиночитана специфікація визначає +кожен публічний інтерфейс Durable Workflow. + +Для автоматизації використовуйте +[машиночитаний каталог](https://durable-workflow.github.io/platform-protocol-specs.json). +Кожен доступний запис має стабільний ідентифікатор специфікації та +публічну URL-адресу HTTPS, що веде безпосередньо до артефакту OpenAPI, +AsyncAPI або JSON Schema. + +Шляхи репозиторію, символи реалізації та тестові фікстури виключені з +опублікованого каталогу й збережені лише для діагностики перевірок. +Джерелом істини для споживачів є опубліковані специфікації. + +## Ідентичність і виявлення каталогу {#catalog-identity-and-discovery} + + + +Каталог також доступний через: + +- публічний каталог JSON за адресою + https://durable-workflow.github.io/platform-protocol-specs.json; +- platform_protocol_specs у GET /api/cluster/info окремого Server; +- цей посібник 2.0 для пояснень читачам. + +URL-адреса JSON є джерелом істини каталогу для машинного використання. +Server повторно публікує той самий каталог, щоб клієнти виявляли активні +інтерфейси протоколів без припущень щодо структури репозиторію. + + + +## Поля для споживачів {#consumer-fields} + +Кожен запис надає такий контракт: + +| Поле | Значення | +|---|---| +| spec_id | Стабільний ідентифікатор протоколу Durable Workflow. Опубліковані документи містять відповідну ідентичність. | +| spec_url | Пряма публічна URL-адреса HTTPS машиночитаної специфікації. | +| format | openapi, json_schema або asyncapi. | +| status | Чи опублікований артефакт, розробляється або планується. | +| surface_family | Сімейство політики сумісності, що визначає інтерфейс. | +| authority_manifest | Маніфест виявлення, через який runtime оголошує інтерфейс. | +| owner_repo | Проєкт, відповідальний за специфікацію. | +| object_families | Назви публічних сімейств об’єктів та їхні проєкти-власники. | +| evolution_rule | Правило сумісності додаткових і несумісних змін. | +| breaking_change_release | Межа випуску, потрібна за цим правилом розвитку. | + +Споживачі мають відкривати spec_url і звіряти ідентичність документа +зі spec_id. Доступ до вихідного checkout не потрібен. + +## Доступні специфікації {#available-specifications} + +Цей перелік формується з публічного каталогу JSON. Ідентичність +специфікації, доступність, публічні посилання й власники сімейств об’єктів +залишаються даними каталогу. + + + +### Примітки runtime сеансів worker {#worker-session-runtime-notes} + +Ця схема охоплює виявлення можливостей сеансів worker, конверти життєвого +циклу, знімки спорідненості завдань і видимість оператора. Її стабільний +ідентифікатор — durable-workflow.v2.worker-sessions-runtime. + +### Примітки runtime локальних activity {#local-activity-runtime-notes} + +Ця схема охоплює виявлення можливостей локальних activity, знімки +параметрів, маркери історії, семантику повторних спроб і видимість +оператора. Її стабільний ідентифікатор — +durable-workflow.v2.local-activity-runtime. + +### Примітки конверта cluster-info {#cluster-info-envelope-notes} + +Ця схема охоплює GET /api/cluster/info та вкладені маніфести виявлення, +доступні через цей endpoint. Її стабільний ідентифікатор — +durable-workflow.v2.cluster-info-envelope. + +## Формати {#formats} + +| Формат | Використання | +|---|---| +| OpenAPI 3.1 | Інтерфейси запиту/відповіді HTTP та JSON, де маршрути, методи, коди статусу й конверти є частиною контракту. | +| JSON Schema 2020-12 | Збережені записи, payload подій, конверти результатів, параметри runtime й пов’язані сімейства об’єктів. | +| AsyncAPI 2.6 або новіший | Семантика опитування, потоків, поновлення оренди, порядку й доставки. | + +## Рівні статусу {#status-levels} + +| Статус | Значення | +|---|---| +| published | Публічна машиночитана специфікація доступна за spec_url і придатна до прямого використання. | +| in_progress | Публічна специфікація доступна, але її покриття часткове. Перелічені поля й маршрути є нормативними. | +| planned | Запис має стабільну ідентичність каталогу, але ще не має придатної до використання специфікації. Заплановані записи не оголошують spec_url. | + +## Правила розвитку {#evolution-rules} + +additive_minor_breaking_major дозволяє додаткові зміни в мінорних випусках. +Вилучення, перейменування, звуження типів і семантичні зміни потребують +мажорного випуску й за можливості паралельного маршруту чи поля. + +parallel_primitive_only визначає заморожені формати передавання. +Несумісну форму потрібно вводити під новим типом події, типом команди +або ідентифікатором схеми, зберігаючи декодування початкової форми. + +experimental_any_release застосовується лише до явно експериментальних +специфікацій і дозволяє зміну в будь-якому випуску. + +## Перевірка випуску {#release-validation} + +CI сайту документації забезпечує такі машинні перевірки: + +| Перевірка | Що перевіряє CI | +|---|---| +| catalog_aligned_with_surface_families | Кожен запис посилається на оголошене публічне сімейство сумісності. | +| owner_repo_known | Власники записів і сімейств об’єктів використовують словник каталогу. | +| format_known | Кожен доступний артефакт розбирається як оголошений формат. | +| public_spec_references_resolve | Кожна доступна spec_url є HTTPS-адресою в публічному просторі специфікацій протоколів і веде до артефакту випуску, ідентичність якого відповідає spec_id. | +| repository_local_authority_fields_rejected | Опубліковані записи не містять локальних шляхів репозиторію, символів реалізації, посилань на тести або старих полів джерела істини. | +| workflow_package_mirror_aligned | Публічний каталог відповідає каталогу пакета Workflow, коли цей вхід випуску доступний. | +| server_owned_spec_mirrors_aligned | Опубліковані артефакти Server відповідають копіям репозиторію-власника, коли ці входи доступні. | +| diagnostic_provenance_complete | Дані походження лише для перевірки охоплюють кожен запис каталогу й сімейство об’єктів. | +| object_family_metadata_declared | Записи каталогу й опубліковані документи погоджені щодо назв сімейств об’єктів та власників. | +| rendered_retrieval_surfaces_aligned | Зібрана сторінка 2.0 і повні пакети отримання даних моделлю 2.0 показують ідентичність каталогу, публічну URL-адресу й власника сімейства об’єктів кожного доступного запису. | +| breaking_change_release_consistent_with_evolution_rule | Кожне значення випуску несумісної зміни відповідає правилу розвитку. | +| deliverable_specs_published | Кожен потрібний інтерфейс платформи має опубліковану специфікацію, придатну до розбору. | + +Машинна перевірка завантажує публічний каталог JSON, перевіряє його +словник і безпечні для споживача посилання, розбирає специфікації випуску, +перевіряє метадані сімейств об’єктів та вбудованих схем і порівнює копії +пакетів, коли вони доступні. Після створення сайту й пакетів для моделей +семантична перевірка порівнює відображені значення каталогу з тим самим +джерелом JSON. Текст пояснень і заголовків не входить до порівняння. + +Коли контракт змінюється, оновлюйте разом каталог пакета Workflow, +цю публічну копію JSON і відповідні опубліковані артефакти специфікацій. +Людське рецензування підтверджує корисність пояснень, не роблячи їх +машинним джерелом істини. + +Перейдіть до [набору перевірок відповідності платформи](/docs/platform-conformance), +щоб знайти активні фікстури з прив’язкою до байтів, які перевіряють ці +специфікації протоколів. Історичні докази фікстур позначені окремо й +не замінюють поточного джерела істини каталогу. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/_category_.json b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/_category_.json new file mode 100644 index 0000000000..98fd5b9a47 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Міжмовна взаємодія", + "position": 14, + "link": { + "type": "generated-index", + "description": "Виконуйте workflow через worker SDK PHP, Python або Rust із Cloud чи власним Server. Використовуйте CLI та публічні протоколи HTTP для керування, діагностики й інтеграції worker." + } +} diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/external-execution.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/external-execution.md new file mode 100644 index 0000000000..5ad06c403d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/external-execution.md @@ -0,0 +1,160 @@ +--- +sidebar_position: 3 +title: Інтерфейс зовнішнього виконання +description: Незалежний від транспорту контракт зовнішнього виконання рівня activity для обмежених завдань, мостів і обробників, керованих AI. +tags: + - external-execution + - bridge-adapters + - worker-protocol + - AI +keywords: + - зовнішнє виконання + - адаптер мосту + - протокол worker activity + - конверт результату завдання +--- + +# Інтерфейс зовнішнього виконання {#external-execution-surface} + +Durable Workflow v2 визначає зовнішнє виконання публічним контрактом +продукту. Окремий Server публікує його машиночитаний загальний опис +через `GET /api/cluster/info` у +`worker_protocol.external_execution_surface_contract`. + +Контракт називається `activity_grade_external_execution`. Він призначений +для стійкої обмеженої роботи, яка може виконуватися поза повним runtime +workflow: + +- activity обслуговування оператора; +- автоматизація платформи; +- передавання між інтеграціями; +- адаптери мостів, що запускають, надсилають signal/update або передають + роботу; +- обробники на основі скриптів, daemon, HTTP, черг, serverless чи агентів. + +Основний напрям — автоматизація операцій, платформи та інтеграцій. +AI-агенти й скрипти є важливими споживачами стабільних інтерфейсів, +але не змінюють межі runtime. + +## Межа {#boundary} + +Зовнішні обробники можуть: + +- виконувати одне орендоване завдання workflow або activity; +- повідомляти поступ оренди через heartbeat протоколу worker; +- повертати структурований конверт успіху чи помилки; +- використовувати адаптер мосту для запуску, signal, update або + передавання обмеженої роботи. + +Зовнішні обробники не повинні: + +- інтерпретувати семантику replay workflow; +- володіти поведінкою `ContinueAsNew`; +- застосовувати правила порядку signal, update чи query поза контрактом + runtime; +- прямо змінювати історію подій; +- працювати як необмежений runtime workflow. + +Ці правила залишаються в Server і повноцінних runtime SDK. Транспорт +має лише переносити оголошені конверти входу й результатів через +транспортну межу. + +## Опубліковані точки контракту {#published-contract-seams} + +Прочитайте `GET /api/cluster/info` перед підключенням транспорту. +Відповідні шляхи v2: + +| Шлях | Призначення | +| --- | --- | +| `worker_protocol.external_execution_surface_contract` | Межі продукту й runtime, допустимі класи транспорту та статус точок контракту. | +| `worker_protocol.external_task_input_contract` | Незалежний від транспорту конверт входу одного орендованого завдання workflow або activity. | +| `worker_protocol.external_task_result_contract` | Незалежний від транспорту конверт успіху, помилки та неправильно сформованого виводу. | +| `worker_protocol.server_capabilities.external_execution_surface` | Компактний покажчик можливості для узгодження площини worker. | + +Інтерфейс зовнішнього виконання також називає заплановані точки +детермінованого поєднання автентифікації/профілю/TLS, конфігураційного +зіставлення обробників, обмежених адаптерів мостів, зовнішнього сховища +payload і безпеки допуску/розгортання. Це точки контракту, а не приватні +деталі реалізації. + +## Вимоги до транспорту {#carrier-requirements} + +Допустимим транспортом може бути CLI чи daemon з опитуванням, виклик +обробника HTTP, worker на основі черги або виклик serverless. Транспорт +може відрізнятися, але ці вимоги спільні: + +- створювати оголошену схему входу; +- приймати оголошену схему результату; +- зберігати `task.id`, `task.attempt` і `task.idempotency_key`; +- відображати транспортні помилки в структуровану помилку або результат + неправильно сформованого виводу; +- детерміновано розв’язувати входи автентифікації, TLS, профілю й середовища. + +Коди завершення, stderr, падіння процесів, помилки HTTP, тайм-аути видимості +черг та помилки платформи serverless є фактами транспорту. Вони стають +фактами workflow лише після відображення транспортом в оголошений +конверт результату або `malformed_output`. + +Перший конкретний транспорт за цим контрактом — +[викличний транспорт HTTP](./invocable-carrier.md), опублікований у +`worker_protocol.invocable_carrier_contract`. Він підтримує лише завдання +activity, потребує HTTPS, дозволяючи loopback HTTP лише для розробки, +та розв’язує автентифікацію через конфігурацію зовнішнього виконавця. +Його транспортна `retry_policy` відрізняється від стійкої політики повторних +спроб activity. Після повідомлення результату останню визначає Server/runtime. + +## Адаптери мостів {#bridge-adapters} + +Адаптери мостів є обмеженими інтерфейсами входу або передавання. Вони +можуть запускати, надсилати signal/update або передавати роботу, але +не є runtime workflow та не повинні приховувати поведінку replay чи +історії подій. + +Кожен адаптер мосту потребує явних машинних результатів для: + +- невідомої цілі; +- помилки автентифікації; +- неправильно сформованого payload; +- повторного запуску; +- непідтримуваної маршрутизації; +- прийнятого передавання; +- відхиленого передавання. + +Зберігайте ту саму зрозумілість для оператора, що й у решті площини +керування: стабільні коди статусу, іменовані значення `reason` і достатній +контекст для `dw`, Waterline, SDK та агентів, щоб пояснити подію без +розбору текстових пояснень. + +CLI безпосередньо надає інтерфейс мосту webhook: + +```bash +dw bridge:webhook stripe \ + --action=start_workflow \ + --idempotency-key=stripe-event-1001 \ + --target='{"workflow_type":"orders.fulfillment","task_queue":"external-workflows","business_key":"order-1001"}' \ + --input='{"order_id":"order-1001"}' \ + --json +``` + +Відповідь JSON є тим самим контрактом результатів адаптера мосту, який +публікує `/api/cluster/info`, включно з `outcome`, `reason`, +`control_plane_outcome`, `idempotency_key` і прихованим від чутливих даних +описом `target`. + +## Форма для керування агентами {#agent-operable-shape} + +Інтерфейс зовнішнього виконання є частиною підходу v2 до розробки за +допомогою AI. Люди вивчають інваріант workflow/activity/replay, а +інструменти працюють через стабільні контракти: + +1. виявити інтерфейс зовнішнього виконання через `/api/cluster/info`; +2. обрати транспорт за опублікованими вимогами; +3. створити конверт входу зовнішнього завдання; +4. зберегти ідентичність та ідемпотентність завдання; +5. повернути конверт успіху, помилки або неправильно сформованого виводу; +6. повідомити помилки мосту й транспорту іменованими результатами. + +Це робить генерацію коду за допомогою AI та автоматизацію оператора +передбачуваними: інструмент посилається на маніфест протоколу, конверт +завдання, конверт результату та результат мосту, замість виводити +поведінку з одного демонстраційного скрипта. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/prerelease-history-migration.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/prerelease-history-migration.md new file mode 100644 index 0000000000..b5c5e10b23 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/prerelease-history-migration.md @@ -0,0 +1,137 @@ +--- +title: Міграція збереженої історії попередніх версій +sidebar_label: Міграція історії попередніх версій +--- + +# Міграція збереженої історії попередніх версій {#migrate-retained-prerelease-history} + +Використовуйте цю офлайн-процедуру, коли розгортання Durable Workflow +2.0 лише з Avro повідомляє `unsupported_payload_codec` для стану, +збереженого з ранішої публічної попередньої версії 2.0. Вона перетворює +відомі представлення з позначкою JSON, без позначки та застарілі Avro +Value, не відкриваючи старий кодек запущеному застосунку v2. + +Експорт історії не замінює цю процедуру. Він є переносимою копією run +й не змінює рядків бази даних, які перевіряє запуск розгортання. +Ніколи не видаляйте історію для проходження попередньої перевірки. + +## Визначте, що потрібно перетворити {#decide-what-must-be-converted} + +Активні run можуть завершитися на поточній розгорнутій попередній версії. +Дочекайтеся їх завершення перед вікном обслуговування, якщо це +операційно безпечно. Термінальні run, закриті виконання, збережені для +replay, та стан, що не може завершитися, потрібно перетворити. +Міграція перелічує обидві групи й не видаляє жодної. + +Пробний запуск охоплює інтерфейси протоколу, які перевіряє запуск Server: + +- входи й результати workflow, payload activity, команди, signal, update, + виклики служб, завдання перевірки update та посилання стійких потоків; +- вбудовані кадри single-object та явні конверти payload у подіях історії; +- посилання зовнішнього сховища payload, включно з формою посилання, + кодеком, доступністю об’єкта, кількістю байтів і цілісністю SHA-256. + +Власні мапи memo, пошукових атрибутів, контексту й діагностики клієнта +не є оголошеннями кодека та залишаються незмінними. Невідомі кодеки, +невідомі fingerprint схем, пошкоджені посилання й значення, які не +представляються фіксованою схемою Avro Value, позначаються unsafe. +Небезпечна знахідка блокує застосування. + +## Межа відповідальності {#ownership-boundary} + +Команда належить пакету Workflow PHP, але має виконуватися в процесі, +що володіє підключенням до бази й налаштуваннями зовнішніх payload: + +- **Вбудований Laravel:** встановіть цільовий пакет Workflow у checkout + застосунку, потім виконайте його `php artisan` за зупинених процесів + web, черг, scheduler та обслуговування. +- **Окремий Server:** використовуйте одноразову оболонку або job із + цільового образу Server з тією самою базою, конфігурацією просторів + імен, обліковими даними та підключеними сховищами зовнішніх payload. + Тримайте звичайний bootstrap hook і всі процеси Server зупиненими + до успішного перетворення. + +Не підключайте сторонній checkout застосунку до бази. Політика сховища +простору імен є частиною перевірки цілісності. Процес, який не може +прочитати зовнішній об’єкт, повідомляє unsafe без припущень. + +## Порядок розгортання {#deployment-order} + +1. Залишайте стару попередню версію доступною до завершення активних + run, які ви вирішили дочекатися. Потім зупиніть усіх авторів записів + бази: вузли API, worker, scheduler, job bootstrap і job обслуговування. +2. Зробіть знімки бази та зовнішнього сховища об’єктів за звичайною + політикою відновлення. Збережіть старий артефакт застосунку або Server. +3. Встановіть або завантажте цільовий артефакт із + `workflow:v2:migrate-prerelease-history`, але не запускайте його. + Виконайте пробну інвентаризацію з цього артефакту: + + ```bash + php artisan workflow:v2:migrate-prerelease-history --dry-run --json + ``` + + Нульовий код завершення означає безпечну конвертованість кожного + зачепленого значення. Перегляньте шляхи й ID зачеплених run. + Ненульовий код із `unsafe_fields` понад нуль блокує оновлення. + Виправте доступ до сховища або залишайтеся на старій попередній версії. +4. Оберіть новий приватний шлях резервної копії та новий приватний + каталог доказів у постійному сховищі. Жоден із них не має існувати. + Застосуйте один раз: + + ```bash + php artisan workflow:v2:migrate-prerelease-history \ + --backup=/secure/dw-prerelease-history-backup.json \ + --evidence-dir=/secure/dw-prerelease-history-evidence + ``` + + Команда записує резервну копію до початку оновлень і відмовляється + перезаписувати докази. Вона блокує та оновлює рядки бази в одній + транзакції, зберігає ідентичності workflow та run, перевіряє хеші й + розміри зовнішніх об’єктів і залишає кожен початковий зовнішній + об’єкт. Заміни є копіями Avro з адресацією за вмістом. Історія + не видаляється. +5. Дочекайтеся завершення команди. Вона експортує й виконує строгий + replay кожного зачепленого збереженого run, перебудовує його проєкції + огляду, очікувань, часової шкали, timer і лінії походження, перевіряє + їхню відповідність та повторює інвентаризацію payload. Успіх + потребує всіх трьох перевірок. Зберігайте звіти replay і проєкцій + разом із резервною копією. +6. Виконайте звичайний цільовий bootstrap лише після успішної міграції: + + ```bash + php artisan server:bootstrap --force + ``` + + Для вбудованого Laravel виконайте звичайну процедуру міграції/bootstrap + застосунку. Попередня перевірка payload лише Avro має бути чистою + перед запуском будь-якого нового worker, вузла API, scheduler або + процесу обслуговування. +7. Запустіть цільове розгортання та перевірте готовність і replay чи + query збереженого run через звичайний інтерфейс оператора. Зберігайте + пакет міграції протягом вікна відкату. + +## Відкат {#rollback} + +Якщо застосування, replay, перевірка проєкцій або фінальна інвентаризація +завершуються помилкою, оновлення заблоковане. Залишайте всі процеси +запису зупиненими й виконайте команду відкату, надруковану міграцією. +Для наведених шляхів вона така: + +```bash +php artisan workflow:v2:migrate-prerelease-history \ + --rollback-from=/secure/dw-prerelease-history-backup.json \ + --evidence-dir=/secure/dw-prerelease-history-evidence +``` + +Відкат перевіряє digest резервної копії та стан міграції, відмовляється +перезаписувати докази відкату й відновлювати рядок, змінений після +перетворення. Він атомарно відновлює початкові значення бази та зберігає +замінні зовнішні об’єкти як докази. Потім відновіть попередній артефакт +застосунку або Server. Якщо відкат відмовляє через зміни іншого процесу +запису, залиште оновлення заблокованим і відновіть узгоджені знімки бази +та сховища об’єктів із кроку 2. + +Після відкату розгортання лише Avro очікувано знову не проходить +попередню перевірку. Усуньте небезпечний стан, створіть нові шляхи +резервної копії та доказів і повторіть повну процедуру перед новою +спробою оновлення. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust-cloud-quickstart.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust-cloud-quickstart.md new file mode 100644 index 0000000000..62abca69d3 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust-cloud-quickstart.md @@ -0,0 +1,145 @@ +--- +sidebar_position: 4 +title: Швидкий старт Rust у Cloud +description: Запуск спільного середовища Rust Sample App із підготовленим простором імен Durable Workflow Cloud. +tags: + - Rust + - Cloud + - quickstart + - workers +keywords: + - швидкий старт Rust Cloud + - URL runtime простору імен + - worker Rust + - playground Sample App +--- + +# Швидкий старт Rust у Cloud {#rust-cloud-quickstart} + +:::caution Контрольований ранній доступ + +Durable Workflow Cloud доступний через контрольований ранній доступ. +Використовуйте цей посібник лише після підготовки Cloud простору імен +і двох облікових даних runtime з окремими ролями. Загальнодоступний +шлях Rust не потребує Cloud. Почніть із [посібника Rust SDK](./rust.md) +або виконайте `scripts/playground rust` із типовим локальним runtime +середовища прикладів. + +::: + +Sample App має одне симетричне середовище прикладів для PHP, Python +і Rust. Ця сторінка обирає Rust і змінює лише ціль runtime. +Відповідні шляхи SDK [PHP](./php.md) і [Python](./python.md) +використовують ту саму команду з `php` або `python`. +[Контракт середовища прикладів Sample App](https://github.com/durable-workflow/sample-app/blob/main/README.md#symmetric-sdk-playground) +описує всі три варіанти. + +Середовище визначає поточні стабільні версії артефактів із машинних +метаданих Sample App. Використовуйте згенеровані версії, не копіюючи +номери до команд і не додаючи окремого кроку встановлення SDK. + +## 1. Відкрийте підготовлений Sample App {#1-open-the-prepared-sample-app} + +[Створіть Codespace з гілки `main` Sample App](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main), +дочекайтеся завершення налаштування й відкрийте термінал у корені +репозиторію. Підготовлений образ містить інструменти SDK і `dw`, +потрібні спільному середовищу прикладів. + +Cloud надає URL runtime, простір імен runtime та два облікові дані +ролей. Оберіть чергу завдань застосунку й експортуйте ці заповнювачі +лише після заміни відповідними значеннями: + +```bash +export DURABLE_WORKFLOW_RUNTIME_URL='' +export DURABLE_WORKFLOW_RUNTIME_NAMESPACE='' +export DURABLE_WORKFLOW_CLIENT_TOKEN='' +export DURABLE_WORKFLOW_WORKER_TOKEN='' +export DURABLE_WORKFLOW_TASK_QUEUE='' +``` + +URL runtime є коренем runtime простору імен, поверненим Cloud. +Потрібна абсолютна URL-адреса HTTPS без query, fragment або кінцевого +`/api`. SDK та CLI додають власні маршрути API. Простір імен runtime +є окремим поверненим значенням. Не визначайте його назву зі шляху URL. + +Облікові дані клієнта запускають, описують і читають workflow. +Облікові дані worker реєструють, опитують, надсилають heartbeat +і завершують завдання. Це мають бути різні секрети з окремими ролями, +хоча одна команда запускає обидва дочірні процеси. Ключ адміністрування +Cloud керує просторами імен та обліковими даними. Не експортуйте +й не передавайте його як облікові дані runtime. + +## 2. Виконайте шлях Rust {#2-run-the-rust-journey} + +Виконайте спільний контракт зовнішнього runtime: + +```bash +scripts/playground rust --runtime managed \ + --runtime-url "$DURABLE_WORKFLOW_RUNTIME_URL" \ + --namespace "$DURABLE_WORKFLOW_RUNTIME_NAMESPACE" \ + --task-queue "$DURABLE_WORKFLOW_TASK_QUEUE" +``` + +Команда створює відсутні файли в `.playground/rust`, не замінюючи +код клієнта. Вона друкує фактичні типи workflow й activity, чергу, +команди worker та клієнта, вхід і очікуваний результат до запуску. +Worker отримує лише свої облікові дані. Після появи його точної +реєстрації клієнт отримує лише облікові дані клієнта. + +Очікування реєстрації обмежене 60 секундами. Сам запуск процесу +не є готовністю. Продовжуйте лише після повідомлення контрольної +точки такої форми, з ілюстративними ідентичностями: + +```text +Worker ready: target=managed runtime_url= namespace= id= queue= workflow_type=sample-app.playground.rust.authored-workflow activity_type=sample-app.playground.rust.authored-activity +``` + +Та сама команда запускає один workflow, очікує результат клієнта SDK +до 120 секунд, підтверджує `status=completed` через `dw` і перевіряє +потрібну історію workflow та activity. Приклад успішного результату: + +```text +Completed rust workflow : {"greeting":"Hello, Durable Workflow, from the Sample App Rust playground","input":{"name":"Durable Workflow"},"activity_runtime":"rust","workflow_runtime":"rust"} +``` + +Останній рядок `Playground success` повторює ціль runtime, простір +імен, чергу, зареєстровані типи й форму очікуваного результату без +значень облікових даних. Команда також записує +`storage/app/playground-rust-evidence.json` з обраними версіями +артефактів, точною реєстрацією, ідентичністю workflow/run, статусом +`completed`, результатом і типами подій історії. Managed Waterline +залишається інтерфейсом оператора підготовленого простору імен. +Керований шлях не запускає локальні Server чи Waterline. + +## Обмежена діагностика {#bounded-diagnosis} + +Якщо контрольна точка `Worker ready` не з’явилася за 60 секунд, +почніть із фактичного контракту, надрукованого над помилкою: + +- **Невідповідність черги:** runner перевіряє точне значення + `--task-queue`. Підтвердьте допуск цієї черги Cloud і те саме + значення в надрукованій реєстрації, потім повторіть команду. +- **Невідповідність типів:** помилка називає типи workflow та activity, + які worker має оголосити. Звірте їх із фактичним контрактом. + Якщо власні файли містять старі жорстко задані реєстрації, оновіть + їх або перевірте поточний згенерований приклад у новому каталозі + через `--source "$HOME/durable-rust-worker"`. +- **Невідповідність ролей облікових даних:** помилка авторизації перед + реєстрацією вказує на облікові дані worker, а під час опису чи запуску + run — на облікові дані клієнта. Не міняйте їх місцями й не замінюйте + ключем адміністрування Cloud. +- **Невідповідність runtime:** обидві ролі мають використовувати точну + надану URL-адресу runtime та простір імен. Приберіть кінцевий `/api`. + Не підставляйте URL адміністрування Cloud чи власного Server. + +Якщо реєстрація успішна, але завершення ні, збережіть надруковану +ідентичність workflow/run. Перегляньте обраний run у Managed Waterline +і звірте тип незавершеного workflow або activity та чергу завдань +із фактичним контрактом. Виправте невідповідність до нового запуску. +Збережений шлях доказів і обмежений вивід worker визначають невдалу +перевірку: результат клієнта, стійкий статус, очікуваний результат +або потрібна історія. + +Поверніться до повного [посібника Rust SDK](./rust.md) або використовуйте +згенерований [довідник Rust API](https://rust.durable-workflow.com/durable_workflow/) +для окремих типів і методів. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-matching-dispatch.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-matching-dispatch.md new file mode 100644 index 0000000000..f8258dff51 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-matching-dispatch.md @@ -0,0 +1,213 @@ +--- +sidebar_position: 6 +title: Зіставлення й диспетчеризація завдань +description: Як Durable Workflow перетворює готові стійкі завдання на призначення worker, від пробудження long-poll до окремої ролі matching. +tags: + - worker-protocol + - task-queues + - operations + - dispatch +keywords: + - зіставлення завдань + - диспетчеризація завдань + - роль matching + - виявлення готових завдань + - пробудження черги +--- + +# Зіставлення й диспетчеризація завдань {#task-matching-and-dispatch} + +Використовуйте цей посібник, щоб зрозуміти, як Durable Workflow +знаходить готову роботу, призначає сумісним worker і масштабує цей +шлях поза опитуванням усього кожним вузлом. Це контракт опитування +завдань workflow та activity, пробудження черг і окремого розгортання +ролі matching. + +Durable Workflow розділяє дві відповідальності: + +- стійка історія й стан завдань залишаються в базі даних; +- matching визначає живого worker для наступного допустимого завдання. + +Це важливо ще до введення окремої служби matching. Оператори можуть +розглядати виявлення готових завдань, володіння чергами, зміну оренд +і зворотний тиск як одну явну роль, а не випадкові наслідки кожного +процесу worker. + +## Що виконує роль matching {#what-the-matching-role-does} + +Роль matching відповідає за: + +- виявлення готових завдань workflow та activity; +- звуження допустимого набору за простором імен, підключенням, + чергою та сімейством сумісності; +- надання завдання одному worker за раз; +- перетворення успішного claim на оренду з часом спливання; +- збереження того самого завдання після невдалого claim, спливання + оренди або зникнення worker; +- видимість стану пробуджень і backlog для оператора. + +Matching визначає можливість наступного виконання. Стійка історія, +replay workflow і виконання завдань залишаються окремими відповідальностями. + +## Форми розгортання {#deployment-shapes} + +Durable Workflow зараз підтримує три практичні форми: + +### Matching усередині worker {#in-worker-matching} + +Це типова форма. Процеси worker використовують long-poll готової роботи +та відсікання під час claim, щоб орендою завдання володів лише один +worker за раз. + +Використовуйте її, якщо: + +- працюєте на одному вузлі або невеликому наборі; +- тиск черг помірний; +- окремий daemon matching ще не потрібен. + +### Matching HTTP усередині Server {#in-server-http-matching} + +У розгортанні окремого Server зовнішні worker звертаються до того +самого контракту matching через протокол worker: + +- `POST /api/worker/workflow-tasks/poll` +- `POST /api/worker/activity-tasks/poll` + +Server стає мережевою точкою входу того самого процесу виявлення +готових завдань, claim, heartbeat і завершення. + +### Окреме розгортання ролі matching {#dedicated-matching-role-deployment} + +Більші набори можуть зосередити загальне виявлення готових завдань +в окремому процесі matching. Поточна задокументована форма оператора: + +```bash +php artisan workflow:v2:repair-pass --loop +``` + +Запустіть цей daemon окремим процесом і задайте +`DW_V2_MATCHING_ROLE_QUEUE_WAKE=0` на вузлах лише виконання, щоб вони +припинили загальне пробудження worker черг на кожному tick циклу. + +Використовуйте форму, якщо: + +- вузли виконання мають зосередитися на replay та activity; +- незалежне загальне опитування створює зайву конкуренцію ресурсів; +- потрібне явне місце відповідальності за виявлення готових завдань + і частоту проходів. + +Окрема роль змінює місце matching. Контракт протоколу worker, +семантика poll, claim, оренд і повторної доставки залишаються тими самими. + +## Виявлення готових завдань {#ready-task-discovery} + +Matching шукає стійкі завдання, які справді готові виконуватися зараз: + +- вид завдання відповідає poller: `workflow` або `activity`; +- завдання має стан `ready`; +- настав його `available_at`; +- фільтри простору імен, черги, підключення й сумісності ще відповідають; +- poll activity також потребує відповідного оголошеного типу activity. + +Пробудження long-poll прискорюють виявлення. Якщо сигнал затриманий +або відсутній, завдання все одно видиме через стійке опитування. +Збій пробудження може підвищити затримку, але не залишити роботу +без шляху виконання. + +## Claim, оренда й зворотний тиск {#claim-lease-and-backpressure} + +Matching лише пропонує можливість. Володіння починається після +успішного claim одного worker й отримання оренди. + +Оренда визначає зворотний тиск Durable Workflow: + +- один worker володіє завданням до поновлення, завершення, звільнення + чи спливання оренди; +- невдалий або застарілий worker не утримує завдання назавжди; +- повторна доставка звичайна й має оброблятися як частина виконання + щонайменше один раз; +- несумісні worker отримують явну відмову й не забирають оренду. + +Завдяки орендам тиск черг проявляється як backlog, застарілі оренди +чи повторна доставка, а не прихована втрата в пам’яті. + +## Розділення черг {#queue-partitioning} + +Основні примітиви розділення: + +- **простір імен** для меж орендаря чи середовища; +- **підключення** та **черга** для меж маршрутизації роботи; +- **сімейство сумісності** для безпечної маршрутизації маркерами; +- **фільтри типів activity** для worker лише вибраних activity. + +Використовуйте окремі черги для сильнішої ізоляції, незалежних пулів +worker або інших бюджетів залежностей. Сімейства сумісності потрібні, +коли та сама черга має працювати під час оновлення чи відкату без +переходу незавершеної роботи до несумісних виконавців. + +## Сигнали пробудження й окремі проходи {#wake-signals-and-dedicated-sweeps} + +Сигнали пробудження скорочують час між готовністю завдання й +виявленням poller. Стійке джерело істини залишається в базі даних. + +У типовій формі worker черг можуть надсилати пробудження зі звичайного +циклу. За окремої ролі matching вузли лише виконання вимикають загальний +шлях пробудження, а daemon matching визначає частоту проходів. + +Засоби налаштування: + +- `DW_V2_MATCHING_ROLE_QUEUE_WAKE` визначає пробудження в worker + на вузлах виконання; +- `DW_WAKE_SIGNAL_TTL_SECONDS` визначає час видимості маркерів пробудження; +- тайм-аути long-poll визначають тривалість очікування роботи poller. + +## Що мають спостерігати оператори {#what-operators-should-watch} + +Використовуйте ці інтерфейси разом: + +- `GET /api/cluster/info` для топології ролей вузла: `current_shape`, + `current_process_class`, поточні ролі й повний контракт `matching_role`: + `queue_wake_enabled`, форма розгортання `shape`, `wake_owner`, + `task_dispatch_mode`, фіксовані `partition_primitives` і поточний + `backpressure_model`; +- видимість черг завдань для глибини готової роботи, активних слотів + і throttling; +- видимість набору worker для активних і застарілих poller кожної черги; +- `dw system:operator-metrics --json` або `/api/system/operator-metrics` + для того самого локального контракту `matching_role` поруч із живими + лічильниками backlog, відновлення, worker і здоров’я процесу відповіді; +- здоров’я та діагностику Waterline для нездорових завдань, застарілих + оренд і прогалин сумісних worker; +- перевірки поступового оновлення під час вікна співіснування, + коли matching має блокувати небезпечні claims. + +Якщо вік найстарішого готового завдання зростає за доступних сумісних +worker, шлях matching не просуває роботу достатньо швидко. Якщо готові +завдання зберігаються, але сумісний worker не може їх забрати, +потрібна діагностика сумісності. + +## Коли вводити окрему роль matching {#when-to-introduce-a-dedicated-matching-role} + +Переходьте від типового matching усередині worker до окремої ролі, якщо: + +- загальне опитування створює видиму конкуренцію бази даних чи кешу; +- вузли виконання мають перестати витрачати ресурси на проходи + готових завдань; +- справедливість черг і відповідальність диспетчеризації потрібно + розглядати як одну явну підсистему; +- потрібен окремий процес для масштабування, нагляду й діагностики + виявлення готових завдань. + +Залишайте типову форму для малого набору, коли основна потреба — +зрозуміла поведінка оновлення й сумісності без нової топології. + +## Пов’язані довідники {#related-references} + +- [Протокол worker](/docs/polyglot/worker-protocol) для poll, heartbeat, + complete і fail. +- [Допуск черги завдань](/docs/polyglot/task-queue-admission) для бюджетів + слотів, оренд і диспетчеризації поверх matching. +- [Поступові оновлення](/docs/rolling-upgrades) для процесу оновлення, + де одночасно діють matching і сумісність. +- [Довідник конфігурації Server](/docs/polyglot/server-config-reference) + для `DW_V2_MATCHING_ROLE_QUEUE_WAKE`, часу poll і сигналів пробудження. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-queue-admission.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-queue-admission.md new file mode 100644 index 0000000000..3873810ed2 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/task-queue-admission.md @@ -0,0 +1,282 @@ +--- +sidebar_position: 6 +title: Допуск черги завдань +description: Налаштування слотів worker, серверних бюджетів черг і зворотного тиску завдань query для окремого Server. +tags: + - admission + - operations + - task-queues + - bounded-growth +keywords: + - допуск черги завдань + - бюджет диспетчеризації + - ліміти черги + - обмежені черги worker +--- + +# Допуск черги завдань {#task-queue-admission} + +Допуск черг не дозволяє одній черзі, орендарю або зовнішній залежності +вичерпати весь набір worker. Durable Workflow надає контроль трьох рівнів: + +- реєстрації worker оголошують локальні слоти workflow та activity; +- Server може обмежувати активні оренди workflow та activity для + простору імен і черги; +- Server може обмежувати диспетчеризацію workflow та activity за хвилину + для простору імен і черги; +- Server може обмежувати диспетчеризацію за хвилину для іменованих + бюджетних груп зовнішніх залежностей, спільних для кількох черг; +- завдання query мають обмежену чергу очікування, щоб синхронне читання + швидко відмовляло замість необмеженого зростання. + +Використовуйте ці засоби, коли черга залежить від служби з обмеженням +частоти, орендарі мають спільний Server або оператору потрібно +довести причину очікування workflow. + +Допуск працює поверх контракту matching. +[Зіставлення й диспетчеризація завдань](/docs/polyglot/task-matching-dispatch) +описує виявлення й оренду готової роботи до рішення цих бюджетів +про допуск наступного завдання. + +## Як застосовується бюджет {#how-the-budget-is-applied} + +Опитування workflow та activity починається з worker, зареєстрованих +для простору імен і черги завдань. Кожен оголошує +`max_concurrent_workflow_tasks` і `max_concurrent_activity_tasks`. +Server підсумовує активні незастарілі worker для обчислення +зареєстрованої кількості слотів черги. + +Серверні ліміти активних оренд і частоти диспетчеризації необов’язкові. +За їх налаштування Server перевіряє короткочасне блокування кешу перед +орендою наступного завдання workflow або activity. За вичерпаного +ліміту активних оренд опитування не повертає завдання замість +перевищення бюджету незавершеної роботи. За вичерпаного хвилинного +ліміту опитування не повертає завдання до появи місткості в наступній +хвилинній групі. Бюджетні групи залежностей застосовують ту саму +хвилинну поведінку до кожної черги простору імен зі спільною назвою +`dispatch_budget_group`. + +Для query площина керування додає тимчасове завдання й очікує відповідь +worker. `DW_QUERY_TASK_MAX_PENDING_PER_QUEUE` обмежує кількість +незавершених query для кожного простору імен і черги. За повної черги +нові query повертають `query_task_queue_full` з HTTP `429`. Якщо сховище +кешу не надає блокування для зміни черги query, повертається +`query_task_queue_unavailable` з HTTP `503`. + +## Налаштування Server {#server-configuration} + +Задайте глобальні ліміти для однакової стелі кожної черги: + +```bash +DW_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_QUEUE=25 +DW_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_QUEUE=100 +DW_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE=500 +DW_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE=2000 +DW_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE=600 +DW_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE=1200 +DW_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE=12000 +DW_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE=24000 +DW_QUERY_TASK_MAX_PENDING_PER_QUEUE=1024 +``` + +Ліміти черги захищають одну чергу завдань. Ліміти простору імен +захищають загальний обсяг усіх його черг. Це корисно, коли орендар +може розділити роботу на багато черг, але використовує одну зовнішню +квоту. Ліміти бюджетної групи захищають іменовану залежність у вибраних +чергах без сповільнення всіх черг простору імен. + +Використовуйте `DW_TASK_QUEUE_ADMISSION_OVERRIDES` для інших бюджетів +окремих черг чи просторів імен. Ключі перевіряються в порядку +`namespace:task_queue`, `namespace:*`, `task_queue`, потім `*`. + +```bash +DW_TASK_QUEUE_ADMISSION_OVERRIDES='{ + "production:payments": { + "workflow_tasks": { + "max_active_leases_per_queue": 8, + "max_dispatches_per_minute": 120, + "dispatch_budget_group": "downstream-openai", + "max_dispatches_per_minute_per_budget_group": 600 + }, + "activity_tasks": { + "max_active_leases_per_queue": 12, + "max_dispatches_per_minute": 240 + } + }, + "production:*": { + "workflow_tasks": { + "max_active_leases_per_namespace": 300, + "max_dispatches_per_minute_per_namespace": 6000 + }, + "activity_tasks": { + "max_active_leases_per_namespace": 1200, + "max_dispatches_per_minute_per_namespace": 12000 + } + }, + "email": { + "activity_tasks": { + "max_active_leases_per_queue": 4, + "max_dispatches_per_minute": 60, + "dispatch_budget_group": "downstream-sendgrid", + "max_dispatches_per_minute_per_budget_group": 300 + } + }, + "*": { + "workflow_tasks": { "max_active_leases_per_queue": 50 } + } +}' +``` + +Значення перевизначення також приймає `max_active_leases` як псевдонім +`max_active_leases_per_queue` і `budget_group` як псевдонім +`dispatch_budget_group`. + +Кеш має підтримувати атомарні блокування для серверних лімітів +активних оренд, частоти диспетчеризації та допуску query. Лічильники +частоти є короткочасними хвилинними групами лише для обмежених черг, +які фактично орендують завдання. Для багатовузлового розгортання +рекомендований Redis. + +## Реєстрація слотів worker {#worker-slot-registration} + +Worker Python надають локальні семафори через `Worker(...)` і +надсилають ті самі значення під час реєстрації: + +```python +worker = Worker( + client, + task_queue="payments", + workflows=[PaymentWorkflow], + activities=[charge_card, send_receipt], + max_concurrent_workflow_tasks=8, + max_concurrent_activity_tasks=12, +) +``` + +Власні worker HTTP надсилають поля слотів до `POST /api/worker/register`: + +```json +{ + "worker_id": "payments-python-1", + "task_queue": "payments", + "runtime": "python", + "supported_workflow_types": ["payments.PaymentWorkflow"], + "supported_activity_types": ["payments.charge_card", "payments.send_receipt"], + "max_concurrent_workflow_tasks": 8, + "max_concurrent_activity_tasks": 12 +} +``` + +Слоти worker самі не є жорстким бюджетом орендаря. Вони описують +поточну здатність активних worker обробляти роботу. Додайте серверні +ліміти для стелі всієї черги, що діє після розгортання додаткових worker. + +## Перегляд допуску {#inspect-admission} + +Під час діагностики використовуйте CLI: + +```bash +dw task-queue:list +dw task-queue:describe payments +dw task-queue:describe payments --json | jq '.admission' +``` + +Server надає ті самі дані через: + +- `GET /api/task-queues` +- `GET /api/task-queues/{name}` + +Payload допуску має три розділи: + +```json +{ + "workflow_tasks": { + "status": "throttled", + "active_worker_count": 3, + "configured_slot_count": 24, + "leased_count": 8, + "ready_count": 5, + "available_slot_count": 16, + "server_max_active_leases_per_queue": 8, + "server_active_lease_count": 8, + "server_remaining_active_lease_capacity": 0, + "server_max_active_leases_per_namespace": 300, + "server_namespace_active_lease_count": 149, + "server_remaining_namespace_active_lease_capacity": 151, + "server_max_dispatches_per_minute": 120, + "server_dispatch_count_this_minute": 120, + "server_remaining_dispatch_capacity": 0, + "server_max_dispatches_per_minute_per_namespace": 6000, + "server_namespace_dispatch_count_this_minute": 3520, + "server_remaining_namespace_dispatch_capacity": 2480, + "server_dispatch_budget_group": "downstream-openai", + "server_max_dispatches_per_minute_per_budget_group": 600, + "server_budget_group_dispatch_count_this_minute": 600, + "server_remaining_budget_group_dispatch_capacity": 0, + "server_lock_required": true, + "server_lock_supported": true, + "budget_source": "worker_registration.max_concurrent_workflow_tasks", + "server_budget_source": "server.admission.queue_overrides" + }, + "activity_tasks": { + "status": "accepting", + "configured_slot_count": 36, + "server_max_active_leases_per_queue": 12, + "server_remaining_active_lease_capacity": 4, + "server_max_dispatches_per_minute": 240, + "server_remaining_dispatch_capacity": 197 + }, + "query_tasks": { + "status": "accepting", + "max_pending_per_queue": 1024, + "approximate_pending_count": 7, + "remaining_pending_capacity": 1017, + "lock_supported": true, + "budget_source": "server.query_tasks.max_pending_per_queue" + } +} +``` + +## Довідник статусів {#status-reference} + +| Розділ | Статус | Значення | +|---------|--------|---------| +| Workflow/activity | `accepting` | Активні worker мають вільні слоти, жоден серверний ліміт не вичерпаний. | +| Workflow/activity | `throttled` | Необов’язковий серверний ліміт активних оренд або диспетчеризації за хвилину вичерпаний. | +| Workflow/activity | `saturated` | Усі зареєстровані слоти worker орендовані, навіть без серверного ліміту. | +| Workflow/activity | `no_slots` | Активні worker зареєстрували нуль слотів цього виду завдання. | +| Workflow/activity | `no_active_workers` | Жоден активний незастарілий worker не опитує чергу. | +| Workflow/activity | `unavailable` | Налаштований серверний ліміт потребує недоступного блокування кешу. | +| Query | `accepting` | Черга незавершених завдань query має вільну місткість. | +| Query | `full` | Завдання query досягли `DW_QUERY_TASK_MAX_PENDING_PER_QUEUE`. Нові query повертають HTTP `429`. | +| Query | `unavailable` | Черга query не може отримати блокування кешу. Нові query повертають HTTP `503`. | + +## Підхід до налаштування {#tuning-pattern} + +1. Почніть зі слотів worker відповідно до процесу: кількість завдань + workflow з навантаженням CPU зазвичай нижча за activity з інтенсивним I/O. +2. Додайте ліміти активних оренд для черг, яким потрібна спільна + стеля незавершеної роботи всіх worker. +3. Додайте ліміти активних оренд простору імен, якщо орендар може + створювати багато черг, але потребує загальної стелі. +4. Додайте хвилинні ліміти диспетчеризації черг, що захищають API + з обмеженням частоти, пул бази даних, орендаря або стару службу + від сплесків навіть за вільних слотів worker. +5. Додайте ліміти бюджетних груп для кількох черг однієї залежності, + залишаючи потік непов’язаних черг простору імен. +6. Додайте ліміти диспетчеризації простору імен, коли зовнішня квота + діє для всього орендаря. +7. Переглядайте `dw task-queue:describe ` під навантаженням. + `saturated` означає потребу додаткової місткості worker або меншого + fan-out workflow. `throttled` означає дію ліміту активних оренд чи + частоти. `no_active_workers` означає відсутність здорового poller черги. +8. Залишайте місткість query достатньою для звичайного читання оператора, + але обмеженою для швидкої відмови під час інцидентів. Переповнення + query є зворотним тиском, а не втратою даних. + +## Пов’язані посібники {#related-guides} + +- [Server](/docs/polyglot/server) +- [CLI](/docs/polyglot/cli) +- [Python SDK](/docs/polyglot/python) +- [Протокол worker](/docs/polyglot/worker-protocol) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-build-id-rollout.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-build-id-rollout.md new file mode 100644 index 0000000000..d688a83944 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-build-id-rollout.md @@ -0,0 +1,219 @@ +--- +sidebar_position: 7 +title: Оновлення build ID worker +description: Оновлення, canary, drain і відкат збірок worker без покинутих виконань через стан build ID окремого Server. +tags: + - worker-protocol + - operations + - rollouts + - build-ids + - task-queues +keywords: + - оновлення build id worker + - drain збірки worker + - відкат збірки worker + - canary worker + - перехід до версійованих worker +--- + +# Оновлення build ID worker {#worker-build-id-rollout} + +Використовуйте цей довідник для переходу від worker без версії до +worker із позначкою збірки, canary нової збірки в черзі завдань, +drain старої збірки перед виведенням або відкату невдалої. Server +записує намір оператора поруч із живими рядками worker, тому наступне +опитування, опис CLI чи `list_task_queue_build_ids` правдиво показує +стан оновлення, навіть якщо старі worker зникли до завершення backlog. + +Цей посібник описує керування групами. Повний контракт маршрутизації +наведено в [сумісності й маршрутизації worker](/docs/polyglot/worker-compatibility-routing): +незавершена робота має залишатися закріпленою за сумісними виконавцями, +а відсутність доступного сумісного worker є явним станом оператора. + +Durable Workflow Server представляє оновлення однієї черги як набір +**груп build ID**. Група об’єднує всі реєстрації worker з однаковим +`build_id` у `POST /api/worker/register`. Worker без `build_id` утворюють +**групу без версії**, типову до оновлення, від якої відбувається +перший перехід. + +## Стан оновлення, який записує Server {#rollout-state-the-server-records} + +Кожна група `(namespace, task_queue, build_id)` має сукупний стан +worker — кількості active, draining, stale та загальну — і намір оператора: + +| Поле | Призначення | +| --- | --- | +| `build_id` | Зареєстрована ідентичність збірки. `null` визначає групу без версії. | +| `rollout_status` | Сукупний стан прийняття нових завдань: `active`, `active_with_draining`, `draining`, `stale_only` або `no_workers`. | +| `drain_intent` | Намір оператора для групи: `active` або `draining`. | +| `drained_at` | Час першої позначки draining. Відсутній для активної групи. Повторні виклики drain не змінюють час. | +| `active_worker_count` | Живі worker, що зараз приймають нові завдання. | +| `draining_worker_count` | Живі worker, які ще мають незавершені завдання, але більше не забирають нову роботу. | +| `stale_worker_count` | Worker з останнім heartbeat раніше за межу застарілості. | +| `total_worker_count` | Сума трьох кількостей worker групи. | +| `runtimes`, `sdk_versions` | Унікальні рядки runtime та версій SDK, спостережені в групі. | +| `last_heartbeat_at`, `first_seen_at` | Вікно heartbeat групи для підтвердження неактивності перед видаленням. | + +`drain_intent` стійкий: перезапуск worker, зупинка всіх worker або +застарівання групи не повертає його приховано до `active`. Лише явний +`POST .../build-ids/resume` очищає `drain_intent` і `drained_at`. +Це зберігає правдивий `rollout_status` навіть без живих worker групи. + +## Перегляд оновлення {#inspect-the-rollout} + +Перед drain чи видаленням збірки підтвердьте ще доступні групи черги: + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/task-queues/orders-critical/build-ids" \ + -H "Authorization: Bearer $DW_OPERATOR_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" +``` + +Той самий знімок доступний у CLI оператора та Python SDK: + +```bash +dw task-queue:build-ids orders-critical --json +``` + +```python +from durable_workflow import Client + +async with Client("https://durable-workflow.example", token=operator_token) as client: + rollout = await client.list_task_queue_build_ids("orders-critical") + for cohort in rollout.build_ids: + print(cohort.build_id, cohort.rollout_status, cohort.total_worker_count) +``` + +## Перший перехід: від worker без версії до версійованих {#first-cutover-unversioned-to-versioned} + +Черга, яку завжди обслуговували worker без версії, повідомляє одну +групу `build_id: null` із `rollout_status: "active"`. Перший перехід +додає нову групу з позначкою збірки поруч із нею. + +1. Розгорніть новий набір worker зі стабільним `build_id`, наприклад + `orders-worker-2026-04-22`, через `POST /api/worker/register`. +2. Підтвердьте активність обох груп: + + ```bash + dw task-queue:build-ids orders-critical --json + ``` + + Ви маєте побачити `null` і новий `build_id` із + `rollout_status: "active"` та ненульовим `active_worker_count` кожного. +3. Почніть drain групи без версії після початку обробки роботи новими worker: + + ```bash + dw task-queue:drain orders-critical --unversioned + ``` + + `drain_intent` групи без версії стає `draining`. Запущені worker + обробляють поточні завдання, але припиняють забирати нові. + Майбутні реєстрації чи heartbeat без `build_id` також потрапляють + до draining. +4. Дочекайтеся нуля `active_worker_count` і `draining_worker_count` + групи без версії. Вона залишається в списку з + `drain_intent: "draining"` для підтвердження постійності переходу. + +## Canary нової збірки {#canary-a-new-build} + +Canary — друга збірка, що отримує малу частку трафіку, поки основна +продовжує обслуговування. Використовуйте окремий `build_id` для +окремого перегляду стану кожної групи. + +1. Розгорніть worker canary із `build_id: orders-worker-2026-04-22-canary`. +2. Перегляньте `list_task_queue_build_ids` і підтвердьте + `rollout_status: "active"` обох груп із потрібними кількостями worker. +3. Підвищте нову збірку, додаючи worker нового `build_id` і зменшуючи + основну групу, або виведіть canary через drain: + + ```bash + dw task-queue:drain orders-critical --build-id orders-worker-2026-04-22-canary + ``` + +Server не керує розподілом завдань між групами. Оператори визначають +розміри груп і покладаються на розподіл опитування для ваги трафіку. +Стан оновлення build ID дозволяє підтвердити групи, здатні забирати +роботу, і почати чисте передавання, коли група готова зупинитися. + +## Drain старої збірки {#drain-an-older-build} + +Drain залишає вже орендовані завдання на старій збірці, спрямовуючи +нові до інших активних груп черги: + +```bash +dw task-queue:drain orders-critical --build-id orders-worker-2026-04-21-z9 +``` + +Server записує `drain_intent: "draining"` групи й позначає кожен +worker цього `build_id` як draining на наступному heartbeat. +Виклик ідемпотентний: повторення не скидає `drained_at`, тому +автоматизація може безпечно його повторювати. + +Після позначки рядка worker `draining` маршрути опитування завдань +workflow, activity та query припиняють надавати нові оренди. +Опитування відмовляють з HTTP `409`, `poll_status: "draining"` +і `reason: "worker_draining"` до відновлення групи. + +`draining` є частиною загального контракту відповіді опитування. +Той самий `poll_status` показує звичайні `leased` і `empty`, +результат допуску `throttled` і типізовані помилки координації +`unavailable` інших шляхів опитування. + +Спостерігайте drain через `list_task_queue_build_ids` і зменшення +`active_worker_count` та `draining_worker_count` до нуля. Тоді група +показує `rollout_status: "draining"` із нульовими кількостями worker: +живих worker немає, а намір оператора все ще записує drain. Це +безпечний момент зупинки процесів worker і видалення артефакту збірки. + +## Відкат невдалої збірки {#roll-back-a-bad-build} + +Відкат виконує зворотний шлях: відновлює раніше спорожнену групу, +повертає до неї новий трафік і переводить невдалу збірку в drain. + +1. Відновіть відому робочу групу: + + ```bash + dw task-queue:resume orders-critical --build-id orders-worker-2026-04-21-z9 + ``` + + Server очищає `drain_intent`, видаляє `drained_at` і негайно + повертає до `active` рядки worker цього `build_id`, які ще + надсилають heartbeat, щоб endpoint читання припинив показувати draining. +2. Переведіть невдалу групу в drain: + + ```bash + dw task-queue:drain orders-critical --build-id orders-worker-2026-04-22 + ``` + +3. Збільште відому робочу збірку або розгорніть її знову, якщо worker + уже зупинені. Worker, що реєструються з її `build_id`, отримують + очищений намір drain і стають `active`. + +Resume також ідемпотентний. Повторення для вже активної групи +нічого не змінює, тому автоматизований відкат може безпечно його викликати. + +## Довідник endpoint і команд {#endpoints-and-commands-reference} + +| Намір | Endpoint HTTP | CLI | Метод Python SDK | +| --- | --- | --- | --- | +| Перегляд стану груп | `GET /api/task-queues/{taskQueue}/build-ids` | `dw task-queue:build-ids` | `Client.list_task_queue_build_ids` | +| Позначити групу draining | `POST /api/task-queues/{taskQueue}/build-ids/drain` | `dw task-queue:drain` | `Client.drain_task_queue_build_id` | +| Відновити раніше спорожнену групу | `POST /api/task-queues/{taskQueue}/build-ids/resume` | `dw task-queue:resume` | `Client.resume_task_queue_build_id` | + +Drain і resume приймають тіло JSON `{"build_id": "..."}` або +`{"build_id": null}` для групи без версії. CLI позначає її через +`--unversioned`, а іншу збірку через `--build-id `. +Поєднання обох одразу відхиляється. + +## Пов’язані довідники {#related-references} + +- [Простори імен, автентифікація та реєстрація worker](/docs/polyglot/namespace-auth-workers) + для `POST /api/worker/register`, що записує `build_id` кожного worker. +- [Допуск черги завдань](/docs/polyglot/task-queue-admission) для + бюджетів слотів worker і диспетчеризації, що діють разом зі станом оновлення. +- [Довідник Server API](/docs/polyglot/server-api-reference) для + повного списку маршрутів площини керування, потрібних ролей і + заголовків протоколів. +- [Довідник команд CLI](/docs/polyglot/cli-reference) для форми + аргументів і параметрів кожної підкоманди `dw task-queue:*`. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-compatibility-routing.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-compatibility-routing.md new file mode 100644 index 0000000000..e7e2bd97ef --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-compatibility-routing.md @@ -0,0 +1,164 @@ +--- +sidebar_position: 5 +title: Сумісність і маршрутизація worker +description: Безпечне оновлення збірок worker із закріпленням незавершеної роботи за сумісними виконавцями та явними прогалинами оновлення. +tags: + - worker-protocol + - compatibility + - operations + - rollouts +keywords: + - сумісність worker + - оновлення збірок worker + - маркери сумісності + - маршрутизація build id + - відкат worker +--- + +# Сумісність і маршрутизація worker {#worker-compatibility-and-routing} + +Використовуйте цей посібник для розгортання нової збірки worker, +пробного оновлення однієї черги завдань, відкату невдалої збірки або +збереження довготривалих workflow під час оновлення. Durable Workflow +v2 визначає сумісність worker контрактом маршрутизації. + +Основне правило просте: run, запущений в одному сімействі сумісності, +має надалі потрапляти на worker, здатні безпечно відтворювати й виконувати +його. Система має явно показувати стан «немає доступного сумісного worker» +замість прихованого передавання завдання іншій збірці. + +## Дві пов’язані ідентичності {#two-related-identities} + +Durable Workflow має два пов’язані, але різні інтерфейси оновлення: + +- **Build id** — ідентичність групи для оператора окремого Server. + Worker реєструє її як `build_id`. API оновлення черги завдань + використовують її для drain або resume. +- **Маркер сумісності** — ідентичність маршрутизації незавершеної + роботи. Для вбудованих і розміщених на Server worker PHP він походить + із `DW_V2_CURRENT_COMPATIBILITY` і `DW_V2_SUPPORTED_COMPATIBILITIES`. + На окремому Server групи build ID є способом оператора переглядати + ці сумісні групи worker та керувати ними. + +Обидва значення є непрозорими рядками, як `orders-2026-04-28` або +`api-v3`. Durable Workflow не інтерпретує semver, не порівнює дати +й не вгадує, який рядок новіший. + +## Що закріплюється {#what-gets-pinned} + +Сумісність приєднується до стійкої роботи workflow, а не лише живих +процесів worker. + +- **Запуск workflow** записує поточне сімейство сумісності в новий run. +- **Завдання workflow** успадковують сімейство й зберігають його через + повторні спроби, спливання оренди та повторну диспетчеризацію. +- **Завдання activity** успадковують сімейство сумісності батьківського run. +- **Повторні run** зберігають сімейство вихідного run. +- **Continue-as-new** зберігає сімейство поточного run. +- **Дочірні workflow** успадковують сімейство батьківського run. + +Отже, довготривалий workflow не переходить до іншого сімейства +виконавців лише через зміну розгортання. Нові збірки впливають на нові +запуски, якщо ви навмисно не переводите старі групи в drain і не +переміщуєте трафік. + +## Як працює маршрутизація {#how-routing-works} + +Сумісність забезпечується на кількох рівнях. + +### Звуження під час опитування {#poll-time-narrowing} + +Worker звужують запити за чергою завдань і групою сумісності/збірки, +щоб Server або вбудований рушій спочатку не пропонували явно несумісних +завдань. + +Це підказка ефективності. Остаточна межа безпеки діє під час claim. + +### Перевірка під час claim {#claim-time-enforcement} + +Перевірка під час claim є межею коректності. Якщо worker не може +безпечно виконати завдання, Durable Workflow відхиляє claim з явною +причиною сумісності замість прихованого перепризначення власника. + +Саме на цей контракт слід спиратися під час змін, що впливають на сумісність: + +- завдання ніколи приховано не допускаються до несумісного worker; +- спливання оренди й повторна доставка зберігають початкове сімейство; +- відсутність сумісного worker є спостережуваним станом. + +## Що мають налаштувати оператори {#what-operators-should-configure} + +Використовуйте стабільні зрозумілі назви сімейств сумісності для збірок, +що безпечно відтворюють ті самі незавершені workflow. + +Для вбудованих чи розміщених на Server worker PHP задайте: + +```bash +DW_V2_CURRENT_COMPATIBILITY=orders-2026-04-28 +DW_V2_SUPPORTED_COMPATIBILITIES=orders-2026-04-28,orders-2026-04-21 +``` + +`DW_V2_CURRENT_COMPATIBILITY` називає сімейство закріплення нових run. +`DW_V2_SUPPORTED_COMPATIBILITIES` називає сімейства, завдання яких worker +ще може забирати під час оновлення або відкату. Використовуйте `*` +лише для наборів однієї збірки чи вузьких тестових середовищ. Не +приховуйте ним невизначену політику оновлення. + +Для worker окремого Server реєструйте стабільний `build_id` і навмисно +переводьте групи в drain або resume через API оновлення build ID. + +## Безпечний шаблон оновлення {#safe-rollout-pattern} + +Для змін worker, що впливають на сумісність: + +1. Запустіть нову групу worker із новим build ID або маркером сумісності. +2. Залишайте стару групу живою до підтвердження отримання роботи новою. +3. Переведіть стару групу в drain: вона припиняє брати нові завдання, + але може завершити чи звільнити вже орендовані. +4. Спостерігайте сумісність і черги завдань, доки стара група + більше не потрібна. +5. Відновлюйте стару групу лише за потреби відкату. + +Зберігайте сімейство сумісності для збірок, сумісних із replay. +Змінюйте його лише коли нові worker більше не повинні забирати +завдання старого сімейства. + +## Явний стан відсутнього worker {#explicit-missing-worker-state} + +Коли жоден worker не задовольняє потрібного сімейства, Durable Workflow +має чітко це показати: + +- огляди набору worker показують відсутність покриття потрібної сумісності; +- інтерфейси черг і оновлення показують активні, draining, застарілі + або зниклі групи збірок; +- діагностика workflow показує очікування сумісного worker; +- здоров’я й метрики оператора показують заблоковану сумісністю роботу + як іменований стан. + +Це очікуваний сигнал часткового оновлення або неповного відкату. +Ставтеся до нього як до операційної проблеми, яку потрібно виправити. + +## Зв’язок з оновленнями build ID {#how-this-relates-to-build-id-rollouts} + +Стан оновлення build ID та маршрутизація сумісності мають різні завдання: + +- **Оновлення build ID** показує оператору живі групи worker і дозволяє + навмисно переводити їх у drain або resume. +- **Маршрутизація сумісності** визначає допустимість виконання + конкретного незавершеного завдання конкретним worker. + +Зазвичай обидва використовуються разом. Групи build ID показують +доступних виконавців, а маршрутизація сумісності спрямовує довготривалу +роботу лише до сумісної групи. + +## Пов’язані довідники {#related-references} + +- [Простори імен, автентифікація та реєстрація worker](/docs/polyglot/namespace-auth-workers) + для payload реєстрації, включно з `build_id`. +- [Оновлення build ID worker](/docs/polyglot/worker-build-id-rollout) + для життєвого циклу drain/resume однієї черги. +- [Довідник конфігурації Server](/docs/polyglot/server-config-reference) + для `DW_V2_CURRENT_COMPATIBILITY`, `DW_V2_SUPPORTED_COMPATIBILITIES` + та пов’язаних налаштувань. +- [Операційні межі оператора](/docs/operator-operating-envelope) + для здоров’я, метрик і діагностики run, що показують прогалини сумісності. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/workflow-streams.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/workflow-streams.md new file mode 100644 index 0000000000..cea08e1fb6 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/workflow-streams.md @@ -0,0 +1,127 @@ +--- +title: Workflow Streams +description: Створення й читання стійких вихідних потоків окремого run через SDK PHP, Python і Rust. +tags: + - service-mode + - workflow-streams + - php + - python + - rust +--- + +# Workflow Streams {#workflow-streams} + +Workflow Streams — іменовані журнали виводу окремого run, якими володіє +Durable Workflow Server. PHP, Python і Rust мають типізовані операції +списку, опису, підписки, додавання, закриття та помилки, тому застосунки +не мають складати запити HTTP самостійно. Runtime оголошує +`durable-workflow.v2.workflow-streams.contract@1`. +[Машиночитана матриця SDK](/workflow-stream-capabilities.json) фіксує +точну підтримку кожного SDK. + +Кожен потік призначає монотонно зростаючі зміщення від 0. `next_offset` +сторінки є наступним включним зміщенням для запиту. Доставка споживачам +відбувається щонайменше один раз: завершіть ідемпотентні ефекти сторінки, +потім стійко збережіть `next_offset` як контрольну точку. Падіння до +контрольної точки може повторно доставити елементи. + +## Emit із коду workflow {#emit-from-workflow-code} + +Три SDK створюють елементи через ту саму детерміновану межу команди: +`record_side_effect.workflow_stream`. Кожен SDK утворює ключ ідемпотентності +елемента зі стійкої ідентичності команди workflow завдання, порядкового +номера команди потоку та індексу пакета. Server разом фіксує зміну потоку +й записаний side effect. Replay читає цей side effect і не може створити +другий стійкий елемент для того самого логічного додавання. + +PHP: + +```php +use DurableWorkflow\Model\WorkflowStreamAppendItem; + +$context->appendWorkflowStream('progress', [ + new WorkflowStreamAppendItem(['percent' => 50]), +]); +$context->closeWorkflowStream('progress'); +``` + +Python: + +```python +from durable_workflow import WorkflowStreamAppendItem + +yield ctx.append_workflow_stream("progress", [ + WorkflowStreamAppendItem(payload={"percent": 50}), +]) +yield ctx.close_workflow_stream("progress") +``` + +Rust: + +```rust +let item = WorkflowStreamAppendItem::new(serde_json::json!({"percent": 50}))?; +ctx.append_workflow_stream("progress", &[item], None)?; +ctx.close_workflow_stream("progress", None)?; +``` + +Помічники workflow призначені для детермінованого написання коду. +Код поза workflow може використовувати типізовану операцію `append` +кожного клієнта й надавати власний стабільний ключ ідемпотентності. + +## Підписка й відновлення {#subscribe-and-resume} + +Підписка читає обмежену сторінку, за потреби очікуючи нові елементи +до 60 секунд. PHP приймає callback скасування між обмеженими опитуваннями. +Python приймає звичайне скасування завдання або `asyncio.Event`, що +скасовує активне опитування. Відкидання future підписки Rust скасовує запит. + +| SDK | Операція сторінки | Значення відновлення | Типізована ітерація | +| --- | --- | --- | --- | +| PHP | `subscribeWorkflowStream(...)` | `WorkflowStreamPage::$nextOffset` | `iterateWorkflowStream(...)` | +| Python | `await subscribe_workflow_stream(...)` | `WorkflowStreamPage.next_offset` | `iter_workflow_stream(...)` | +| Rust | `subscribe_workflow_stream(...).await` | `WorkflowStreamPage::next_offset` | Повторювати future обмеженої сторінки до `terminal`. | + +`open`, `closed` і `errored` — стани життєвого циклу. Типізований опис +також надає `last_offset`, `total_items`, `pending_items` і `error_reason`. +Додавання до термінального потоку відхиляється. Коли досягнуто налаштованої +межі незавершених елементів, додавання повертає `stream_full`. Виробники +мають уповільнитися або дочекатися читання потоку споживачами. + +## Посилання на зовнішні payload {#external-payload-references} + +Вбудовані значення використовують спільний конверт Avro Value. +Зовнішні посилання дотримуються наявного контракту сховища кожного SDK: + +| SDK | Поведінка зовнішніх посилань | +| --- | --- | +| PHP | Додає й повертає непрозорий `payload_reference`. Завантаження та отримання зі сховища залишаються відповідальністю застосунку. | +| Python | Використовує налаштований драйвер зовнішнього сховища для завантаження, перевірки цілісності, кешування, отримання й декодування зовнішнього конверта Avro. Без драйвера клієнти можуть додавати або переглядати непрозорий `payload_reference`. | +| Rust | Додає й повертає непрозоре посилання та його метадані. Поточний Rust SDK не заявляє драйвер зовнішнього сховища. | + +## Workflow Streams і вбудований MessageStream {#workflow-streams-and-embedded-messagestream} + +Назви узгоджені там, де узгоджена поведінка, але моделі не взаємозамінні. + +| Поняття | Workflow Stream сервісного режиму | MessageStream вбудованого Laravel | +| --- | --- | --- | +| Адреса | Run workflow + назва потоку | Екземпляр/run workflow + ключ потоку | +| Напрям | Лише вивід workflow | Вхідна й вихідна скриньки workflow | +| Перше зміщення | 0 | 1 | +| Доставка | Щонайменше один раз, контрольна точка споживача | Щонайменше один раз, курсор повідомлень рушія | +| Continue as new | Курсор потоку не передається | Курсор вхідної скриньки передається продовженому run | +| Вхідні повідомлення workflow | Не надаються, використовуйте signal або update | Надаються через `inbox()->receive()` | + +Waterline використовує одну нормалізовану таблицю для обох режимів +і показує режим, життєвий цикл, зміщення голови/курсора, кількість +незавершених елементів, напрям і помилку. Вихідні потоки сервісу +не показуються як вхідна скринька workflow. + +## Перевірка відповідності {#qualification} + +Публічний [маніфест сценаріїв Workflow Stream](/platform-conformance/workflow-stream-runtime-scenarios.json) +потребує запусків опублікованих артефактів для виробника PHP зі +споживачами Python і Rust, перезапуску worker виробника, повторного +підключення споживача, життєвого циклу/зворотного тиску/скасування +та зовнішніх посилань payload. Результат випуску має записати кожен +обов’язковий сценарій. Самі модульні тести вихідного коду не є доказом +відповідності. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/quickstart.md b/i18n/uk/docusaurus-plugin-content-docs/current/quickstart.md new file mode 100644 index 0000000000..60c177f637 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/quickstart.md @@ -0,0 +1,490 @@ +--- +sidebar_position: 2 +title: Швидкий старт Durable Workflow 2.0 +description: Оберіть runtime сервісного режиму та завершіть перший workflow із PHP, Python або Rust. +tags: + - quickstart + - getting-started + - PHP + - Python + - Rust +keywords: + - швидкий старт Durable Workflow + - швидкий старт Durable Workflow 2.0 + - швидкий старт окремого PHP SDK + - швидкий старт Python SDK + - швидкий старт Rust SDK + - швидкий старт окремого Server Docker +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import PythonPackageReleaseLink from '@site/src/components/PythonPackageReleaseLink'; + +# Швидкий старт Durable Workflow 2.0 {#durable-workflow-20-quickstart} + +## Перед початком {#before-you-begin} + +**Мета:** виконати один workflow сервісного режиму та прочитати його +завершений стійкий результат із PHP, Python або Rust. + +**Очікуваний час:** приблизно 15 хвилин після підготовки runtime. + +**Результат завершення:** обраний SDK запускає worker і workflow, потім +друкує ID workflow, `status=completed` і `Hello, !`. + +**Передумови:** + +- `curl` і термінал; +- Docker для локального власного розгортання або підготовлений простір + імен Durable Workflow Cloud; +- інструменти однієї мови: PHP 8.1+ із Composer, Python 3.10+ або Rust 1.86+. + +Сервісний режим не потребує Laravel. Окремий шлях вбудованого Laravel +наведено наприкінці посібника. + +## 1. Оберіть runtime сервісного режиму {#1-choose-your-service-mode-runtime} + +| Runtime | Коли обрати | Наступна дія | +| --- | --- | --- | +| Durable Workflow Cloud | Ви хочете, щоб Durable Workflow обслуговував runtime, збереження стану та Managed Waterline. | Виконайте [перший workflow Cloud](/docs/polyglot/cloud-control-plane/#cloud-first-workflow), що зіставляє повні приклади PHP, Python і Rust з наданими обліковими даними та результатом `completed`. **Не запускайте Server або окрему службу Waterline.** | +| Власний Server | Ви хочете обслуговувати runtime самостійно або виконати цей точний локальний приклад на опублікованих артефактах. | Продовжіть нижче з Docker і `curl`. Окремо розгортайте Waterline лише за потреби його UI оператора. | + +Виконуваний код нижче використовує локальний власний Server, щоб приклад +працював без облікового запису чи checkout вихідного коду. Cloud використовує +ту саму модель SDK та worker. Замініть локальне підключення розробки на +надані значення з [керованого runtime Cloud](/docs/polyglot/cloud-control-plane/). + +## 2. Запустіть локальний Server {#2-start-the-local-server} + +Пропустіть цю дію, якщо обрали Cloud. Для власного розгортання розгорніть +і виконайте точне закріплене налаштування. Воно запускає Server без +вихідного checkout із SQLite та токеном розробки. + +
+Запустіть закріплений образ Server + + +```bash +export DW_SERVER_IMAGE=%%artifact.serverDockerHubImage%% +export DW_AUTH_TOKEN=dev-token + +docker volume create durable-workflow-quickstart + +docker run --rm \ + -v durable-workflow-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" server-bootstrap + +docker rm -f durable-workflow-server >/dev/null 2>&1 || true +docker run -d --name durable-workflow-server \ + -p 8080:8080 \ + -v durable-workflow-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" + +until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done +curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \ + http://localhost:8080/api/cluster/info +``` + +
+ +**Очікуваний результат:** запит готовності успішний, а cluster info визначає +локальний окремий Server. Залиште його запущеним під час виконання +шляху обраної мови. + +## 3. Оберіть одну мову {#choose-one-language} + +Усі три офіційні SDK доступні на одному рівні. Показується лише обрана +вкладка, щоб ви виконали один шлях без прокручування двох інших програм. + + + + +Вимоги: PHP 8.1 або новіший і Composer. Використовується незалежний від +фреймворку пакет `durable-workflow/sdk`. + +1. **Встановіть SDK.** + + +```bash +mkdir durable-workflow-php-quickstart +cd durable-workflow-php-quickstart +composer require %%artifact.phpSdkComposerPackage%% +``` + +2. **Додайте worker і клієнт.** Розгорніть повний код і скопіюйте обидва + файли в новий проєкт. + +
+Повний виконуваний код PHP + +Worker реєструє один тип workflow та один тип activity у власній черзі +завдань. + + +```bash +cat > worker.php <<'PHP' +registerActivity( + 'quickstart.greet', + static fn (ActivityContext $context, string $name): string => "Hello, {$name}!", +); + +$worker->registerWorkflow( + 'quickstart.greeter', + static function (WorkflowContext $context, string $name): array { + $greeting = $context->activity('quickstart.greet', [$name]); + + return ['greeting' => $greeting, 'language' => 'php']; + }, +); + +$worker->run(); +PHP +``` + +#### Клієнт і читання результату {#client-and-result-reader} + +Цей клієнт запускає workflow з унікальною назвою, очікує його обраний run +і потім описує стійкий термінальний стан, збережений Server. + + +```bash +cat > start.php <<'PHP' +startWorkflow( + workflowType: 'quickstart.greeter', + workflowId: $workflowId, + taskQueue: 'quickstart-php', + input: ['PHP'], +); + +$result = $handle->result(timeoutSeconds: 30); +$execution = $handle->describeSelectedRun(); + +echo "workflow_id={$execution->workflowId}\n"; +echo "status={$execution->status}\n"; +echo 'result='.json_encode($result, JSON_THROW_ON_ERROR)."\n"; +PHP +``` + +
+ +3. **Запустіть worker і клієнт.** + + +```bash +php worker.php > quickstart-worker.log 2>&1 & +export QUICKSTART_WORKER_PID=$! +trap 'kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true' EXIT + +php start.php + +kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true +trap - EXIT +``` + +**Очікуваний результат:** `status=completed` і результат із +`"greeting":"Hello, PHP!"`. Ви запустили окремий worker PHP та +переглянули його стійкий результат без Laravel. + +Продовжіть із [посібником PHP SDK](/docs/polyglot/php/). + +
+ + +Вимоги: Python 3.10 або новіший. Програма містить worker і клієнт в одному +процесі, але вони спілкуються із Server через публічні API worker та +площини керування. + +1. **Встановіть SDK.** + + Використовуйте стабільний + випуск Python SDK із маніфесту вище. + Згенерована точна вимога зберігає цей виконуваний шлях на задокументованій + стабільній лінійці випуску. + + +```bash +mkdir durable-workflow-python-quickstart +cd durable-workflow-python-quickstart + +python3 -m venv .venv +. .venv/bin/activate +pip install %%artifact.pythonPackagePin%% +``` + +2. **Створіть і запустіть worker та клієнт.** Розгорніть повну програму. + Її остання команда запускає виконання. + +
+Повний виконуваний код Python + + +```bash +cat > greeter.py <<'PY' +import asyncio +import time + +from durable_workflow import Client, Worker, activity, workflow + + +@activity.defn(name="quickstart.greet") +async def greet(name: str) -> dict: + return {"greeting": f"Hello, {name}!", "language": "python"} + + +@workflow.defn(name="quickstart.greeter") +class GreeterWorkflow: + def run(self, ctx, name): + return (yield ctx.schedule_activity("quickstart.greet", [name])) + + +async def main(): + workflow_id = f"quickstart-python-greeter-{int(time.time())}" + + async with Client( + "http://localhost:8080", + token="dev-token", + namespace="default", + ) as client: + handle = await client.start_workflow( + workflow_type="quickstart.greeter", + task_queue="quickstart-python", + workflow_id=workflow_id, + input=["Python"], + ) + + worker = Worker( + client, + task_queue="quickstart-python", + workflows=[GreeterWorkflow], + activities=[greet], + ) + await worker.run_until(workflow_id=workflow_id, timeout=30.0) + + result = await handle.result(timeout=10.0) + execution = await handle.describe_run() + + print(f"workflow_id={execution.workflow_id}") + print(f"status={execution.status}") + print(f"result={result}") + + +asyncio.run(main()) +PY + +python greeter.py +``` + +
+ +**Очікуваний результат:** `status=completed` і результат із +`Hello, Python!`. Останні два виклики SDK читають результат обраного run +та стійкий термінальний стан із Server. + +Продовжіть із [посібником Python SDK](/docs/polyglot/python/). + +
+ + +Вимоги: Rust 1.86 або новіший. Приклад запускає власний worker і клієнт +в одному процесі Tokio. + +1. **Встановіть SDK.** + + +```bash +cargo new durable-workflow-rust-quickstart +cd durable-workflow-rust-quickstart +%%artifact.rustCargoAddCommand%% +cargo add tokio --features macros,rt-multi-thread,time +``` + +2. **Створіть і запустіть worker та клієнт.** Розгорніть повну програму. + Її остання команда компілює та запускає виконання. + +
+Повний виконуваний код Rust + + +```bash +cat > src/main.rs <<'RS' +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use durable_workflow::{json, Client, Result, Worker, WorkflowResultOptions}; + +#[tokio::main] +async fn main() -> Result<()> { + let client = Client::builder("http://localhost:8080") + .token(Some("dev-token".to_string())) + .namespace("default") + .build()?; + let task_queue = "quickstart-rust"; + let mut worker = Worker::new(client.clone(), task_queue); + + worker.register_activity("quickstart.greet", |_context, arguments| async move { + let name = arguments + .get(0) + .and_then(|value| value.as_str()) + .unwrap_or("Rust"); + Ok(json!({"greeting": format!("Hello, {name}!"), "language": "rust"})) + }); + + worker.register_workflow("quickstart.greeter", |context, input| async move { + let name = input.get(0).and_then(|value| value.as_str()).unwrap_or("Rust"); + context.activity("quickstart.greet", json!([name])).await + }); + + worker.register().await?; + let workflow_id = format!("quickstart-rust-greeter-{}", unique_suffix()); + let handle = client + .start_workflow( + "quickstart.greeter", + task_queue, + &workflow_id, + json!(["Rust"]), + ) + .await?; + + let watcher = handle.clone(); + worker + .run_until(async move { + loop { + if watcher.describe().await.is_ok_and(|run| run.is_terminal()) { + break; + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + }) + .await?; + + let result = handle.result(WorkflowResultOptions::default()).await?; + let execution = handle.describe_selected_run().await?; + + println!("workflow_id={workflow_id}"); + println!("status={}", execution.status.as_deref().unwrap_or("unknown")); + println!("result={result}"); + Ok(()) +} + +fn unique_suffix() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() +} +RS + +cargo run +``` + +
+ +**Очікуваний результат:** `status=completed` і результат JSON із +`"greeting":"Hello, Rust!"`. Приклад очікує завершення run своїм worker, +потім читає стійкий статус і декодований результат обраного run. + +Продовжіть із [посібником Rust SDK](/docs/polyglot/rust/). + +
+
+ +## 4. Приберіть локальний Server {#4-clean-up-the-local-server} + +Користувачам Cloud не потрібно видаляти локальний Server. Для прикладу +власного розгортання: + +```bash +docker rm -f durable-workflow-server +docker volume rm durable-workflow-quickstart +``` + +## Окремий шлях: вбудований Laravel {#separate-path-embedded-laravel} + +Вбудований Laravel — окремий офіційний режим розгортання PHP для +застосунків, яким потрібні стан workflow, виконання черг, налаштування й +інструменти оператора всередині наявної інфраструктури Laravel. Він +встановлює `durable-workflow/workflow` без окремого Server або +`durable-workflow/sdk`. + +Створіть новий вбудований застосунок з опублікованим пакетом: + +```bash +composer create-project laravel/laravel durable-workflow-laravel-quickstart +cd durable-workflow-laravel-quickstart +composer require %%artifact.workflowComposerPackage%% +php artisan migrate +php artisan queue:work +``` + +Для UI оператора в тому самому Laravel-застосунку додайте перевірений +вбудований пакет Waterline Composer: + +```bash +composer require %%artifact.waterlineComposerPackage%% +php artisan waterline:install +``` + +Цей пакет Composer відрізняється від окремо розгорнутої служби Waterline +для власного Server. Вбудований Laravel не запускає Server і не встановлює +SDK сервісного режиму. + +Продовжіть із [вбудованим встановленням](/docs/installation/) для +налаштування черги Laravel, відмінної від `sync`, потім +[визначте](/docs/defining-workflows/workflows/) і +[запустіть](/docs/defining-workflows/starting-workflows/) вбудований workflow. +[Режими розгортання](/docs/polyglot/deployment-modes/) порівнюють цей +спеціалізований шлях із платформою сервісного режиму. + +## Наступні кроки {#next-steps} + +- Використовуйте [індекс можливостей](/docs/capabilities/) для перевірки + підтримуваного інтерфейсу обраного runtime та SDK. +- Продовжіть із посібником сервісного режиму + [PHP SDK](/docs/polyglot/php/), [Python SDK](/docs/polyglot/python/) + або [Rust SDK](/docs/polyglot/rust/). +- Порівняйте життєвий цикл, повідомлення, розклади, видимість і виконання + worker у [можливостях клієнтів і worker](/docs/polyglot/cli-python-parity/). +- Обслуговуйте відповідний runtime через + [керований runtime Cloud](/docs/polyglot/cloud-control-plane/) або + [власний Server](/docs/polyglot/server/), потім додайте + [CLI](/docs/polyglot/cli/) за потреби автоматизації shell. +- Плануйте безпечне розгортання сервісних worker через + [сумісність і маршрутизацію worker](/docs/polyglot/worker-compatibility-routing/) + та [оновлення build ID](/docs/polyglot/worker-build-id-rollout/). + +Для написання вбудованих timer, signal, query, activity та дочірніх +workflow Laravel використовуйте окрему +[вбудовану документацію](/docs/category/embedded/). + +[Набір перевірок відповідності платформи](/docs/platform-conformance/) +містить вимоги до перевірки випуску: точну матрицю артефактів, перевірки +публічних джерел, повні журнали виконання, критерії реального часу, +прибирання та машиночитаний контракт quickstart для сертифікації. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/rolling-upgrades.md b/i18n/uk/docusaurus-plugin-content-docs/current/rolling-upgrades.md new file mode 100644 index 0000000000..4d582c7874 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/rolling-upgrades.md @@ -0,0 +1,322 @@ +--- +sidebar_position: 15 +title: Поступові оновлення +description: Самостійне поступове оновлення вузлів API Server, worker і scheduler Durable Workflow без зупинки живого трафіку. +tags: + - operations + - upgrades + - rollouts + - rollback +keywords: + - поступове оновлення + - безпека поступового оновлення + - drain worker + - відсікання схемою + - контракт готовності + - процес відкату +--- + +# Поступові оновлення {#rolling-upgrades} + +Виконуйте поступове оновлення для заміни вузлів API, worker чи scheduler +без вимкнення розгортання. Контракт охоплює невелику кластерну форму з +[посібника самостійного розгортання](/docs/deployment): два чи три вузли +API за балансувальником, спільні зовнішні MySQL або PostgreSQL, спільний +Redis, незалежно масштабовані worker та рівно один scheduler або runner +обслуговування. + +Поступове оновлення підтримується за виконання всіх гарантій цієї +сторінки. Поза цими межами використовуйте задокументований процес +оновлення із зупинкою всього розгортання. + +Терміни ролей і маніфест форм цього контракту наведені в +[топології ролей Server](/docs/polyglot/server-role-topology). Цей посібник +описує поступове оновлення поточних класів процесів `standalone_server`. + +## Що означає поступове оновлення {#what-rolling-upgrade-means-here} + +**Поступове оновлення** замінює процеси по одному, завершуючи роботу +кожного перед зупинкою, поки решта набору обслуговує трафік. Результат — +відсутність простою розгортання загалом і обмежене вікно співіснування +старих і нових процесів. + +Контракт розрізняє чотири класи процесів поточної форми +`standalone_server`, кожен із власним підходом до оновлення: + +- **Вузли HTTP/API**: процеси Server без стану, що обслуговують HTTP + і зараз містять ролі `api_ingress`, `control_plane`, `matching` і + `history_projection`. +- **Worker**: процеси SDK, що опитують площину worker та виконують + завдання activity й workflow як `execution_plane`. +- **Scheduler / runner обслуговування**: єдиний процес, що запускає + розклади та забезпечує тайм-аути activity й зберігання історії як + роль `scheduler`. +- **Bootstrap**: одноразовий процес міграцій бази даних і створення + типового простору імен. + +Вузли API та worker оновлюються незалежно. Scheduler має один екземпляр: +зупиніть старий перед запуском нового. Решта розгортання обслуговує +трафік протягом цього проміжку. + +## Правила сумісного змішування версій {#compatible-version-skew-rules} + +Вікно співіснування — час, коли одночасно працюють кілька образів Server, +версій пакета Workflow або версій SDK worker. У ньому мають виконуватися +всі правила цього розділу. + +### Образ Server і пакет Workflow {#server-image-and-workflow-package} + +- **Лише сусідні версії.** Під час поступового оновлення кожен вузол + API та інший процес Server має використовувати образ Server, версія + пакета Workflow якого має ту саму мажорну версію, що й попередній + пакет кластера, і відрізняється від кожного іншого живого процесу + щонайбільше на одну мінорну версію. Перехід через мажорну версію + потребує оновлення із загальною зупинкою. +- **Міграції лише з додаванням.** Кожна зміна схеми Durable Workflow + v2 є додатковою в межах мажорної версії. Нові вузли не повинні + потребувати стовпця чи таблиці, яких ще немає після міграцій. + Старі вузли мають працювати з новим стовпцем, якого не читають. + Це забезпечують [правила порядку міграцій](#schemabootstrap-ordering). +- **Сусідні версії площини керування й протоколу worker.** Кожен + вузол публікує підтримувані діапазони `control_plane.version` і + `worker_protocol.version` через `GET /api/cluster/info`. Під час + оновлення підтримуваний діапазон нового образу має перетинатися + з діапазоном кожного живого старого вузла. Виявіть діапазон перед + оновленням і припиніть його за відсутності перетину. + +### SDK worker та ідентичність збірки {#worker-sdk-and-build-identity} + +- **Worker позначають кожну збірку.** Кожен worker поступового + оновлення має реєструватися через `POST /api/worker/register` зі + стабільним `build_id`. Група без версії (`build_id: null`) є типовою + до оновлення. [Посібник оновлення build ID worker](/docs/polyglot/worker-build-id-rollout) + пояснює перший перехід. +- **Fingerprint визначення workflow залишаються закріпленими.** + Образи Server із `DW_V2_PIN_TO_RECORDED_FINGERPRINT=true`, типовим + значенням, закріплюють незавершені run за fingerprint визначення + workflow, записаним у `WorkflowStarted`. Новий worker з іншим + fingerprint того самого workflow відмовляється забирати ці run + до їх завершення. +- **Підхід до допуску збірок, що співіснують.** Оберіть + [`DW_V2_FLEET_VALIDATION_MODE`](/docs/polyglot/server-config-reference) + перед початком. `warn` дозволяє оновлення навіть без живого worker + потрібного маркера сумісності. `fail` блокує диспетчеризацію та + закриває допуск контракту готовності в цьому вікні. Виробничі + оновлення, що потребують чистого переходу, мають використовувати `fail`. + +## Порядок схеми й bootstrap {#schemabootstrap-ordering} + +Зміни схеми виконуються одним проходом bootstrap. Порядок важливий. + +1. **Спочатку виконайте bootstrap рівно один раз.** Виконайте + `php artisan server:bootstrap --force` або еквівалент опублікованого + образу з одного контейнера перед запуском будь-якого нового вузла + API, worker чи scheduler. Bootstrap виконує `migrate` і створення + типового простору імен. Він приймає міграції пакета Workflow, + таблиці яких уже існують на підключенні. +2. **Нова схема має бути сумісною зі старим кодом.** Кожна міграція + v2 цього шляху лише додає таблиці, стовпці чи індекси. Старі + вузли API й worker продовжують працювати з новою схемою. +3. **Не запускайте новий код до завершення bootstrap.** Оновлюйте + образ Server лише після успішного завершення bootstrap. Нові + вузли API й worker можуть залежати від щойно створених таблиць. + Запуск до завершення bootstrap є найпоширенішою причиною сплеску + 5xx під час переходу. +4. **Bootstrap ідемпотентний.** Його безпечно повторювати. Якщо він + частково завершився помилкою, виправте причину й повторіть. + Журнал міграцій продовжує з місця зупинки, а створення простору + імен нічого не змінює за наявності рядка. + +Міграція, що з’явилася на одному сервері раніше за інший, НЕ ПОВИННА +порушувати інтерфейс готовності. Якщо під час планування знайдено зміну, +яка не є додатковою, використовуйте вікно загальної зупинки для цього +випуску й поверніться до поступових оновлень у наступному. + +## Drain і допуск у вікні співіснування {#drain-and-admission-during-the-overlap-window} + +Три інтерфейси допуску автоматично забезпечують безпеку співіснування: + +- **Допуск запуску.** Кожен процес Server завантажує + `BackendCapabilities`, `LongPollCacheValidator`, `WorkflowModeGuard` + і контракт готовності під час запуску. Процес із backend чи кешем, + що не відповідає контракту v2, відмовляється позначати себе готовим. +- **Сумісність worker.** За `DW_V2_FLEET_VALIDATION_MODE=fail` і + відсутності живого worker потрібного маркера сумісності в області + підключення та черги завдання роль matching блокує диспетчеризацію, + а перевірка здоров’я `worker_compatibility` переходить із `warning` + до `error`. Завдання залишаються готовими й видимими та не втрачаються. + Контракт готовності повертає 503 на цьому вузлі, щоб балансувальник + виключив його з обслуговування. +- **Безпека маршрутизації.** Готове завдання без живого worker потрібної + сумісності зберігається й рахується в метриці backlog + `compatibility_blocked_runs`. Drain маршрутизації не призводить + до прихованої втрати завдань. Гарантія виконання щонайменше один + раз залишається чинною. + +Щоб скоротити вікно співіснування worker, переведіть старі групи в drain +після появи нових. Використовуйте процес +[оновлення build ID worker](/docs/polyglot/worker-build-id-rollout): + +```bash +dw task-queue:drain orders-critical --build-id orders-worker-2026-04-21-z9 +``` + +`drain_intent` групи стає `draining`. Worker цієї збірки завершують +поточну роботу, але припиняють забирати нові завдання. Дочекайтеся нуля +`active_worker_count` і `draining_worker_count` перед зупинкою старих +процесів worker. + +Scheduler не має довготривалої черги завдань і не потребує drain worker. +Зупиніть старий контейнер scheduler, виконайте bootstrap, якщо його ще +не виконано, і запустіть новий. Вікно між ними обмежене часом запуску +нового контейнера. Розклади відновлюються зі збереженого стану на +наступному tick. + +Якщо оновлення також вводить окреме розгортання ролі matching, явно +задайте зміну топології. Не припускайте, що кожен вузол виконання +продовжить загальний пошук готових завдань. У +[зіставленні й диспетчеризації завдань](/docs/polyglot/task-matching-dispatch) +наведена форма `workflow:v2:repair-pass --loop` разом із +`DW_V2_MATCHING_ROLE_QUEUE_WAKE=0`. Перевірте живий контракт вузла +через `GET /api/cluster/info`: `topology.current_shape` має відповідати +розгортанню переходу, `topology.current_roles` — задокументованому набору +ролей вузла, а `topology.matching_role.queue_wake_enabled`, +`topology.matching_role.shape` і `topology.matching_role.wake_owner` +мають показувати очікуваного власника загального пошуку готових завдань. +Типова форма повідомляє `queue_wake_enabled: true`, `shape: "in_worker"` +і `wake_owner: "worker_loop"`. Окремі розгортання matching змінюють +вузли виконання на `queue_wake_enabled: false`, `shape: "dedicated"` +і `wake_owner: "dedicated_repair_pass"`. + +## Готовність і перемикання {#readiness-and-cutover} + +Визначайте допуск трафіку на вузол за контрактом готовності: + +- `GET /api/health` доводить обслуговування HTTP процесом. +- `GET /api/ready` доводить доступ до налаштованих залежностей runtime, + включно з міграціями, типовим простором імен та перевіркою допуску + сумісності worker за `DW_V2_FLEET_VALIDATION_MODE=fail`. +- `GET /api/cluster/info` доводить можливість автентифікованого клієнта + виявити ідентичність збірки, протоколи площини керування й worker, + кодеки payload та можливості Server. +- `POST /api/worker/register` доводить можливість автентифікації worker + в очікуваному просторі імен і черзі завдань. + +Послідовність перемикання одного вузла API: + +1. Виключіть вузол із ротації балансувальника. Найпростіший шлях — + спричинити невдачу перевірки готовності балансувальника, зупинивши + pre-start hook нового образу перед запуском нового контейнера. +2. Дочекайтеся завершення поточних запитів HTTP. Більшість клієнтів + повторює запит після скидання підключення. Довгі підключення, як + long-poll worker, підключаються до решти набору вузлів. +3. Зупиніть старий контейнер і запустіть новий. +4. Дочекайтеся 200 від `GET /api/ready` та нової ідентичності збірки + в `GET /api/cluster/info`. За зміни топології matching також + підтвердьте відповідність `topology.matching_role.task_dispatch_mode`, + `topology.matching_role.queue_wake_enabled`, + `topology.matching_role.shape`, `topology.matching_role.wake_owner`, + `topology.matching_role.partition_primitives` і + `topology.matching_role.backpressure_model` потрібному розгортанню + перед поверненням трафіку. Використовуйте `/api/system/operator-metrics` + для того самого локального контракту ролі matching разом із живими + лічильниками backlog, відновлення й worker процесу, що відповідає. +5. Поверніть вузол до ротації. + +Повторюйте по одному вузлу. Не оновлюйте наступний, доки попередній +не повернувся до ротації й не обслуговує трафік без помилок. + +Worker перемикаються за групами: + +1. Запустіть нову групу worker із новим `build_id`. +2. Підтвердьте `rollout_status: "active"` і ненульовий + `active_worker_count` обох груп у `dw task-queue:build-ids --json`. +3. Переведіть стару групу в drain через `dw task-queue:drain`. +4. Дочекайтеся нуля `active_worker_count` і `draining_worker_count` + старої групи. +5. Зупиніть старі процеси worker. + +## Відкат {#rollback} + +Кожен крок оборотний. Сплануйте відкат перед початком. + +- **Відкат bootstrap.** Міграції v2 оборотні через стандартний шлях + Laravel `down()`. Відкат міграції, потрібної новому образу, вимагає + спочатку зупинити всі нові вузли. Інакше новий код бачить відсутній + стовпець, а контракт готовності повертає 503. Більшість відкатів + не потребує скасування міграцій, оскільки зміни схеми додаткові. +- **Відкат вузла API.** Зупиніть новий контейнер і запустіть старий + на тому самому вузлі. Виключіть вузол із ротації на час запуску + та поверніть після успішного `GET /api/ready`. Повторіть для інших + оновлених вузлів API. Старий код читає нову схему, бо зміна додаткова. +- **Відкат worker.** Відновіть раніше спорожнену групу, переведіть + невдалу групу в drain і знову масштабуйте відому робочу збірку: + + ```bash + dw task-queue:resume orders-critical --build-id orders-worker-2026-04-21-z9 + dw task-queue:drain orders-critical --build-id orders-worker-2026-04-22 + ``` + + Resume очищає `drain_intent` і `drained_at`. Worker, що надсилає + heartbeat із відновленим `build_id`, повертається до `active` на + наступному опитуванні. Обидва виклики ідемпотентні. + +Якщо оновлення виявило недодаткову проблему схеми, використовуйте +вікно загальної зупинки для відкату, виконайте коригувальні міграції +та сплануйте перехід заново. + +## Перевірка оператором {#operator-verification} + +Перевіряйте кожен етап оновлення за інтерфейсами оператора. + +| Питання | Інтерфейс | +| --- | --- | +| Чи bootstrap завершений? | Код завершення 0 `php artisan server:bootstrap --force`. `migrate:status` показує виконання всіх міграцій. | +| Чи новий вузол готовий? | `GET /api/ready` повертає 200, `GET /api/cluster/info` повідомляє нову збірку. | +| Чи здоровий допуск сумісності? | `workers.fleet`, `workers.active_workers` і `workers.active_workers_supporting_required` у `GET /api/system/operator-metrics` погоджені щодо ненульової кількості worker кожного потрібного маркера сумісності. | +| Чи просувається drain worker? | `dw task-queue:build-ids --json` показує зменшення `active_worker_count` і `draining_worker_count` групи drain. | +| Чи маршрутизація безпечна? | `backlog.compatibility_blocked_runs` і `backlog.max_compatibility_blocked_age_ms` у `GET /api/system/operator-metrics` близькі до нуля, перевірка `worker_compatibility` не має `error`. | +| Чи scheduler наздогнав розклад? | `schedules.missed` у `GET /api/system/operator-metrics` дорівнює нулю, `schedules.oldest_overdue_at` — null. | +| Чи накопичуються застряглі run? | `runs.repair_needed` і `runs.max_repair_needed_age_ms` у `GET /api/system/operator-metrics` залишаються близькими до базових значень перед оновленням. | + +`dw system:operator-metrics --json` надає той самий знімок метрик +оператора в консолі набору окремого Server, тож оператори можуть +обрати зручний для свого процесу інтерфейс. Окрема служба Waterline +може читати той самий стан оновлення Server через PHP SDK у +`/waterline/api/v2/health` і `/waterline/api/stats`. Узгодьте її endpoint, +простір імен та токен Server з оновлюваним набором. Вбудовані +Laravel-розгортання надають ці маршрути Waterline зі свого пакета +в процесі. Server API та CLI залишаються доступними в обох випадках, +як описано в [операційних межах оператора](/docs/operator-operating-envelope). + +## Сценарії помилок і дії {#failure-modes-and-what-to-do} + +| Симптом | Імовірна причина | Дія | +| --- | --- | --- | +| Новий вузол API не проходить `GET /api/ready` після запуску | Bootstrap не завершений або задано `DW_V2_FLEET_VALIDATION_MODE=fail` без живого сумісного worker | Повторіть bootstrap. Запустіть сумісну групу worker перед поверненням вузла API до ротації. | +| `worker_compatibility` переходить до `error` під час оновлення | Потрібний маркер сумісності не має живого worker із підтримкою | Запустіть більше worker підтримуваного `build_id`. Відновіть раніше спорожнену групу, якщо потрібен відкат. | +| `backlog.compatibility_blocked_runs` і `max_compatibility_blocked_age_ms` зростають | Завдання очікують маркер, якого не підтримує живий worker | Та сама дія. Завдання зберігаються й автоматично надсилаються після heartbeat сумісного worker. | +| `dw task-queue:drain` успішний, але worker продовжують забирати завдання | Процес worker не надсилав heartbeat після drain | Дочекайтеся одного циклу heartbeat. Якщо група залишається активною, перезапустіть процес worker, щоб отримати намір drain. | +| Запуски розкладів припинилися після перезапуску scheduler | Старий і новий scheduler зупинені | Запустіть новий контейнер scheduler. Перевірте повернення `schedules.missed` до нуля під час наступного зчитування метрик оператора. | + +Якщо симптому немає в списку, вважайте оновлення невдалим: припиніть +додавати нові процеси, переведіть запущені нові групи в drain та +відновіть попередню збірку перед подальшою діагностикою. + +## Пов’язані довідники {#related-references} + +- [Самостійні розгортання](/docs/deployment) для форм розгортання, + які передбачає цей контракт. +- [Оновлення build ID worker](/docs/polyglot/worker-build-id-rollout) + для викликів drain і resume груп. +- [Операційні межі оператора](/docs/operator-operating-envelope) + для контракту діагностики, черг і відновлення проєкцій, який + оператори читають разом із сигналами оновлення. +- [Довідник конфігурації Server](/docs/polyglot/server-config-reference) + для змінних середовища безпеки оновлення (`DW_V2_FLEET_VALIDATION_MODE`, + `DW_V2_PIN_TO_RECORDED_FINGERPRINT`, `DW_V2_GUARDRAILS_BOOT`, + `DW_V2_CACHE_VALIDATION_MODE`, `DW_V2_MULTI_NODE`, + `DW_V2_VALIDATE_CACHE_BACKEND`, `DW_V2_TASK_REPAIR_*`). +- [Довідник Server API](/docs/polyglot/server-api-reference) для + endpoint готовності, cluster info й метрик оператора, що + перевіряють кожен етап оновлення. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/sample-app.md b/i18n/uk/docusaurus-plugin-content-docs/current/sample-app.md new file mode 100644 index 0000000000..19155571ee --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/sample-app.md @@ -0,0 +1,305 @@ +--- +sidebar_position: 8 +tags: + - sample-app + - Laravel + - message-streams + - Waterline +keywords: + - sample app + - застосунок Laravel + - приклад workflow AI + - панель Waterline +--- + +# Sample App {#sample-app} + +https://github.com/durable-workflow/sample-app + +Це галерея вбудованого Laravel: приклад застосунку Laravel 13 на +стабільній лінійці Durable Workflow 2.0 із workflow для запуску в +GitHub Codespace. Починайте тут, якщо ваша модель розгортання — +**вбудований Laravel** і ви хочете разом переглянути виконання черг +Laravel та Waterline. + +Оберіть приклади відповідно до runtime: + +| Модель розгортання | Шлях прикладу | +| --- | --- | +| Durable Workflow Cloud | Почніть із [керованого runtime Cloud](/docs/polyglot/cloud-control-plane/), потім оберіть посібник SDK PHP, Python або Rust із наданими обліковими даними простору імен. Користувачі Cloud не запускають Server. | +| Власний сервісний режим | Використовуйте [швидкий старт Durable Workflow 2.0](/docs/quickstart/) для повних прикладів PHP, Python і Rust на опублікованих артефактах із локальним Server. | +| Вбудований Laravel | Продовжуйте з цією галереєю та [вбудованим встановленням](/docs/installation/). | + +Галерея дає наскрізні докази роботи рушія в Laravel. Це перше місце +реалістичного покриття вбудованих можливостей Laravel і джерело +прикладів природних для Laravel шаблонів на цьому сайті. Для сервісних +і міжмовних розгортань використовуйте відповідні приклади вище. +Якщо хочете поділитися шаблоном durable-workflow, +[посібник додавання прикладу](/docs/contribute-a-sample) пояснює процес. + +> Шукаєте версію Laravel 12 / Durable Workflow 1.x? Її збережено на [гілці `Laravel-12`](https://github.com/durable-workflow/sample-app/tree/Laravel-12). Старі дописи й посібники з шаблонами v1, як `Workflow\Workflow`, `yield activity(...)`, `Workflow\Activity`, стосуються цієї гілки. + +## Галерея прикладів {#sample-gallery} + +Кожен запис називає шаблон, який викладає приклад, клас workflow, +що його виконує, команду Artisan для запуску й екран Waterline, +що доводить фіксацію run. Галерея відтворює «Sample Index» README +репозиторію sample-app. Після додавання чи переміщення прикладу +README та галерея змінюються разом. + +| Шаблон | Клас workflow | Команда | Екран Waterline | +|---------|----------------|---------|------------------| +| Найменший детермінований workflow v2 | `App\Workflows\Simple\SimpleWorkflow` | `php artisan app:workflow` | Список run → деталі run із двома подіями activity та подією `WorkflowCompleted` | +| Стійке вимірювання тривалості без розбіжностей replay | `App\Workflows\Elapsed\ElapsedTimeWorkflow` | `php artisan app:elapsed` | Деталі run із двома `SideEffectRecorded` для читання часу `sideEffect` навколо події `TimerFired` | +| Координація між Laravel-застосунками | `App\Workflows\Microservice\MicroserviceWorkflow` | `php artisan app:microservice` | Деталі run із подіями activity та маршрутизацією черг між worker застосунку й мікросервісу | +| Автоматизація браузера зі збереженими результатами | `App\Workflows\Playwright\CheckConsoleErrorsWorkflow` | `php artisan app:playwright` | Деталі run з activity Playwright, activity FFmpeg і activity очищення в порядку виконання | +| Запуск workflow webhook з очікуванням signal | `App\Workflows\Webhooks\WebhookWorkflow` | `php artisan app:webhook` | Деталі run із `WorkflowStarted` від входу webhook і подією `SignalReceived` для `ready` | +| Цикл activity AI зі стійкою повторною спробою/перевіркою | `App\Workflows\Prism\PrismWorkflow` | `php artisan app:prism` | Деталі run із повторними спробами activity та `ActivityCompleted`, що задовольняє валідатор | +| AI-агент із signal та компенсацією saga | `App\Workflows\Ai\AiWorkflow` | `php artisan app:ai` | Деталі run із посиланням потоку повідомлень, історією update та activity компенсації після помилки saga | + +Стовпець екрана Waterline називає очікувані події здорового run. +Якщо однієї немає локально, ця прогалина допомагає швидко знайти +проблему worker або середовища. + +## Посилання зі сторінок шаблонів {#pattern-page-cross-links} + +Сторінки шаблонів цього сайту ведуть прямо до workflow прикладу, +який виконує відповідну можливість. Використовуйте таблицю для переходу +від сторінки шаблону до виконуваного workflow. + +| Сторінка шаблону | Workflow прикладу | +|--------------|-----------------| +| [Saga](/docs/features/sagas) | `App\Workflows\Ai\AiWorkflow` (`php artisan app:ai`) | +| [Signal](/docs/features/signals) | `App\Workflows\Webhooks\WebhookWorkflow` (`php artisan app:webhook`) | +| [Потоки повідомлень](/docs/features/message-streams) | `App\Workflows\Ai\AiWorkflow` (`php artisan app:ai`) | +| [Дочірні workflow](/docs/features/child-workflows) | Окремого запису галереї ще немає. Використовуйте посібник дочірніх workflow. | +| [Side effect](/docs/features/side-effects) | `App\Workflows\Elapsed\ElapsedTimeWorkflow` (`php artisan app:elapsed`) | +| [Webhook](/docs/features/webhooks) | `App\Workflows\Webhooks\WebhookWorkflow` (`php artisan app:webhook`) | +| [Workflow MCP](/docs/mcp-workflows) | Усі записи галереї відкриті через `config/workflow_mcp.php` | + +**Крок 1** + +Створіть codespace з гілки main цього репозиторію. + +![Створення codespace](https://user-images.githubusercontent.com/1130888/233664377-f300ad50-5436-4bb8-b172-c52e12047264.png) + +**Крок 2** + +Після створення codespace дочекайтеся його збірки. Зазвичай це +триває від 5 до 10 хвилин. + +**Крок 3** + +Після завершення ви побачите редактор і термінал унизу. + +![Редактор і термінал](https://user-images.githubusercontent.com/1130888/233665550-1a4f2098-2919-4108-ac9f-bef1a9f2f47c.png) + +**Крок 4** + +Виконайте composer install. + +```bash +composer install +``` + +**Крок 5** + +Виконайте init для налаштування застосунку, встановлення додаткових +залежностей і міграцій. + +```bash +php artisan app:init +``` + +Шлях `php artisan migrate` sample-app безпосередньо підхоплює міграції +пакетів Workflow і Waterline, тому всі таблиці та збережені подання +Waterline готові після звичайного встановлення. + +**Крок 6** + +Запустіть worker черги. Це дозволить обробляти workflow та activity. + +```bash +php artisan queue:work +``` + +Для перевірки самого циклу відновлення без очікування наступного +циклу `Looping` зайнятого worker виконайте один явний прохід відновлення +з другого термінала: + +```bash +php artisan workflow:v2:repair-pass +``` + +Команда використовує ту саму політику пошуку й backoff, що й цикл +worker. Вона корисна після виправлення локальної проблеми черги/backend, +для перевірки сценарію втраченого завдання або codespace з малим +трафіком, що довго очікує наступний прохід. Додайте `--run-id=...` +для обмеження одним або кількома обраними run під час експерименту +чи `--instance-id=...` для всього екземпляра. + +**Крок 7** + +Створіть нове вікно термінала. + +![Новий термінал](https://user-images.githubusercontent.com/1130888/233666917-029247c7-9e6c-46de-b304-27473fd34517.png) + +**Крок 8** + +Запустіть workflow прикладу в новому терміналі. + +```bash +php artisan app:workflow +``` + +**Крок 9** + +Панель Waterline доступна за адресою https://[your-codespace-name]-80.preview.app.github.dev/waterline/dashboard. + +![Панель Waterline](https://user-images.githubusercontent.com/1130888/233669600-3340ada6-5f73-4602-8d82-a81a9d43f883.png) + +Waterline показує стійкий стан: чи почався workflow, який run поточний, +які типізовані події історії зафіксовані, які очікування відкриті та +які дії оператора доступні. Телеметрія worker окрема: затримка +опитування, тривалість завдань, налаштування exporter, власні метрики +застосунку та помилки процесу походять із журналів worker PHP або +endpoint метрик SDK зовнішнього worker. + +| Інтерфейс | На що відповідає | Перевірка прикладу | +| --- | --- | --- | +| Waterline та експорт історії | Стійкий статус workflow, історія, повторні спроби, очікування, signal, update, помилки й дії оператора | Відкрийте `/waterline/dashboard` і експортуйте історію обраного run | +| Журнали worker | Помилки процесу worker черги PHP та рядки журналу застосунку | Переглядайте `storage/logs/laravel.log` під час роботи `php artisan queue:work` | +| Метрики SDK | Кількість запитів зовнішнього worker/клієнта, затримка опитування й тривалість завдань | Зчитайте endpoint Prometheus/OpenMetrics worker SDK | + +Для worker Python встановіть додаткову підтримку Prometheus і +відкрийте метрики з його процесу: + +```bash +pip install 'durable-workflow[prometheus]' +``` + +```python +from prometheus_client import start_http_server + +from durable_workflow import Client, PrometheusMetrics, Worker + +metrics = PrometheusMetrics() +start_http_server(9102) + +async with Client("http://localhost:8080", token="secret", metrics=metrics) as client: + worker = Worker( + client, + task_queue="default", + workflows=[GreeterWorkflow], + activities=[greet], + metrics=metrics, + ) + await worker.run() +``` + +Замініть `GreeterWorkflow` і `greet` на обробники workflow та activity, +зареєстровані цим worker. + +Зчитуйте `:9102/metrics` для рядів `durable_workflow_worker_*` і +`durable_workflow_client_*`. Вони пояснюють продуктивність runtime +worker, а Waterline визначає зафіксовану історію workflow. + +Екран деталей Waterline має дію «Export History» для обраного run. +Коли показаний поточний run екземпляра, кнопка використовує маршрут +експорту поточного run екземпляра. Деталі історичного run використовують +явний `/runs/{runId}/history-export`. Той самий пакет replay/налагодження +можна експортувати з термінала sample-app: + +```bash +php artisan workflow:v2:history-export {workflow-instance-id} --run-id={workflow-run-id} --output=storage/app/workflow-history/example.json --pretty +``` + +Експорт містить контрольну суму цілісності SHA-256. Задайте +`DW_V2_HISTORY_EXPORT_SIGNING_KEY` і `DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID` +у середовищі застосунку, якщо іншій системі потрібно перевіряти пакет +підписом HMAC. + +Експортований блок `selected_run` містить `waits_projection_source`, +`timeline_projection_source`, `timers_projection_source` і +`lineage_projection_source`. Блок `links` також містить `projection_source`. +Розділи `links.parents` / `links.children` спочатку походять із типізованої +історії лінії походження обраного run. Зв’язки дочірніх workflow та +continue-as-new залишаються видимими в пакеті, навіть якщо змінювані +рядки зв’язків розійшлися під час локального експерименту. Коли рядок +лінії походження зберігається лише через старі змінювані дані сумісності, +пакет позначає його `history_authority = mutable_open_fallback` і +`diagnostic_only = true` без прихованого відновлення додаткових +метаданих зв’язків під час експорту. + +## Потоки повідомлень AI workflow {#ai-workflow-message-streams} + +Workflow з повторюваним вводом AI або людини мають використовувати +основний фасад потоків повідомлень v2 як шаблон написання: + +```php +$reply = $this->inbox('ai.assistant')->receiveOne(); + +$this->outbox('ai.assistant')->sendReference( + targetInstanceId: $this->workflowId(), + payloadReference: $storedReplyReference, + correlationId: $requestId, +); +``` + +Великі тіла запитів/відповідей залишайте у сховищі payload застосунку, +передаючи збережене посилання через потік. Нові приклади workflow +не повинні прямо записувати `workflow_messages`, `MessageStreamCursor` +або викликати `MessageService`. Стабільний контракт вхідної/вихідної +скриньки v2 наведено в [потоках повідомлень](/docs/features/message-streams). + +**Крок 10** + +Виконайте тести workflow та activity. + +```bash +vendor/bin/phpunit +``` + +Тепер ви можете створювати й тестувати workflow. + +## MCP-сервер AI-клієнта {#ai-client-mcp-server} + +Sample App також надає сервер Laravel MCP за `/mcp/workflows`. +Це еталонний інтерфейс AI-клієнта Durable Workflow v2: він надає агентам +структуроване виявлення workflow, запуск, статус, вивід, недавню +типізовану історію та факти помилок без зчитування Waterline. + +Докладний контракт endpoint та інструментів наведено в +[інтерфейсі workflow MCP](/docs/mcp-workflows). Ширший контракт +розробки за допомогою AI, зокрема маніфести LLM v2, коди завершення +CLI, експорти Waterline та довідники SDK, наведено в +[розробці за допомогою AI](/docs/ai-assisted-development). + +Пакет Laravel MCP реєструє сервер у `routes/ai.php`. Відкриті ключі +workflow задані в `config/workflow_mcp.php`. Кожен запис може містити +клас workflow та метадані виявлення: опис, вимоги до облікових даних +і очікувані аргументи. + +Типові інструменти: + +| Інструмент | Призначення | +| --- | --- | +| `list_workflows` | Перелічує налаштовані ключі workflow, вимоги до облікових даних, значення статусу v2 і за потреби недавні run. | +| `start_workflow` | Запускає налаштований workflow v2 і повертає `workflow_id`, `run_id`, статус, бізнес-ключ і результат команди. | +| `get_workflow_result` | Опитує поточний або обраний run і повертає статус, вивід, метадані видимості й останній опис помилки. | +| `get_workflow_history` | Повертає обмежений кінець типізованої історії v2 та останні стійкі помилки для діагностики. | +| `diagnose_workflow` | Класифікує обраний run зі структурованими фактами, першопричиною, способом усунення й наступними діями. | +| `repair_workflow` | Запитує вбудовану команду відновлення v2 та повертає структурований результат зміни: прийнято, відмовлено або не потрібно. | + +Типовий цикл агента: + +```json +{"tool": "list_workflows", "arguments": {"show_recent": true, "limit": 5}} +{"tool": "start_workflow", "arguments": {"workflow": "simple", "business_key": "demo-001"}} +{"tool": "get_workflow_result", "arguments": {"workflow_id": ""}} +{"tool": "get_workflow_history", "arguments": {"run_id": "", "limit": 25}} +``` + +Використовуйте `simple` або `elapsed` для smoke-тестів без облікових +даних. Workflow `prism` відкритий як приклад AI, але потребує +`OPENAI_API_KEY` для завершення worker. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/testing.md b/i18n/uk/docusaurus-plugin-content-docs/current/testing.md new file mode 100644 index 0000000000..22c09abc60 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/testing.md @@ -0,0 +1,510 @@ +--- +sidebar_position: 9 +--- + +# Тестування {#testing} + +## `Workflow\V2` {#workflowv2} + +`Workflow\V2\WorkflowStub::fake()` надає детермінований шлях тестування +безпосередньо в процесі для підтримуваного інтерфейсу fake `V2`. + +- готові завдання workflow та activity виконуються в процесі без очікування worker черги; +- вкладені дочірні workflow виконуються як справжні вкладені run `V2` у тому самому режимі fake; +- підміни activity все одно записують стійкі рядки `activity_executions`, завдань та історії; +- перевірки можливостей backend пропускаються для fake, тому `sync` придатний для тестів; +- `WorkflowStub::assertDispatched()`, `assertDispatchedTimes()`, `assertNotDispatched()` і `assertNothingDispatched()` перевіряють диспетчеризацію activity `V2`; +- `WorkflowStub::assertSignalSent()`, `assertSignalSentTimes()` і `assertSignalNotSent()` перевіряють signal `V2`, надіслані через `WorkflowStub::signal()` або `signalWithStart()`; +- `WorkflowStub::assertUpdateSent()`, `assertUpdateSentTimes()` і `assertUpdateNotSent()` перевіряють update `V2`, надіслані через `WorkflowStub::update()`, `attemptUpdate()` або `submitUpdate()`; +- відкладені завдання timer залишаються стійкою роботою черги до свого часу. Після переміщення часу `WorkflowStub::runReadyTasks()` виконує вже належні завдання в процесі. + +### Область тестування першого випуску {#first-release-testing-scope} + +Наведене нижче **не** входить до інтерфейсу fake `V2` першого випуску +й може додаватися майбутніми додатковими контрактами: + +- немає еквівалента старого мосту `resume()` для `V2`; +- немає окремої підміни дочірнього workflow або помічника перевірки диспетчеризації. Дочірні workflow виконуються як справжні вкладені run `V2` у fake, тому перевіряйте їх через спостережуваний результат; +- немає hook відкладеного callback для введення signal чи update зі зміщенням віртуального часу під час одного виконання fake. Натомість переміщуйте час і викликайте `WorkflowStub::runReadyTasks()` між явними викликами signal/update. + +`WorkflowStub::mock()` забезпечує цю область під час виконання: +передавання підкласу `Workflow` викидає `LogicException` зі зрозумілою +вказівкою тестувати дочірні workflow через спостережуваний результат. +Цілями підміни можуть бути лише класи `Activity` та нерозв’язані рядкові ключі. + +```php +use function Workflow\V2\activity; +use Workflow\V2\Workflow; + +final class MyWorkflow extends Workflow +{ + public function handle(): array + { + return [ + 'result' => activity(MyActivity::class, 'Taylor'), + 'workflow_id' => $this->workflowId(), + 'run_id' => $this->runId(), + ]; + } +} +``` + +```php +use Workflow\V2\Testing\ActivityFakeContext; +use Workflow\V2\WorkflowStub; + +public function testWorkflow(): void +{ + WorkflowStub::fake(); + + WorkflowStub::mock(MyActivity::class, function (ActivityFakeContext $context, string $name): string { + $this->assertSame('Taylor', $name); + $this->assertSame('my-workflow-id', $context->workflowId()); + + return "Hello, {$name}!"; + }); + + $workflow = WorkflowStub::make(MyWorkflow::class, 'my-workflow-id'); + $workflow->start(); + + $this->assertTrue($workflow->refresh()->completed()); + $this->assertSame('Hello, Taylor!', $workflow->output()['result']); + + WorkflowStub::assertDispatched(MyActivity::class, function (string $name): bool { + return $name === 'Taylor'; + }); +} +``` + +Використовуйте `WorkflowStub::assertDispatched()`, `assertDispatchedTimes()`, +`assertNotDispatched()` і `assertNothingDispatched()` для перевірки +записаної диспетчеризації activity `V2`. + +Для workflow з timer перемістіть час помічниками travel Laravel, +а потім явно виконайте чергу завдань, час яких настав: + +```php +use function Workflow\V2\timer; +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; + +final class MyTimerWorkflow extends Workflow +{ + public function handle(): array + { + timer(60); + + return ['done' => true]; + } +} + +public function testTimerWorkflow(): void +{ + WorkflowStub::fake(); + + $workflow = WorkflowStub::make(MyTimerWorkflow::class, 'timer-workflow'); + $workflow->start(); + + $this->travel(60)->seconds(); + + WorkflowStub::runReadyTasks(); + + $this->assertTrue($workflow->refresh()->completed()); + $this->assertSame(['done' => true], $workflow->output()); +} +``` + +`WorkflowStub::runReadyTasks()` виконує лише завдання, час яких уже настав. +Він не переміщує реальний час і не змушує майбутні timer спрацьовувати раніше. + +### Надсилання signal у режимі fake {#sending-signals-in-fake-mode} + +Workflow, що очікують signal, можуть отримувати їх у fake. Виклик +`$workflow->signal(...)` записує стійку команду signal і створює завдання +workflow. У fake воно виконується в процесі, тому workflow продовжується +синхронно: + +```php +use function Workflow\V2\activity; +use Workflow\V2\Attributes\Signal; +use function Workflow\V2\await; +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; + +#[Signal('name-provided')] +final class ApprovalWorkflow extends Workflow +{ + public function handle(): array + { + $name = await('name-provided'); + $greeting = activity(GreetingActivity::class, $name); + + return ['name' => $name, 'greeting' => $greeting]; + } +} + +public function testSignalWorkflow(): void +{ + WorkflowStub::fake(); + WorkflowStub::mock(GreetingActivity::class, 'Hello, Taylor!'); + + $workflow = WorkflowStub::make(ApprovalWorkflow::class, 'approval-1'); + $workflow->start(); + + // Workflow suspends at await('name-provided') + $this->assertSame('waiting', $workflow->refresh()->status()); + + // Send the signal — resumes the workflow inline + $workflow->signal('name-provided', 'Taylor'); + + $this->assertTrue($workflow->refresh()->completed()); + $this->assertSame('Taylor', $workflow->output()['name']); + + WorkflowStub::assertSignalSent('name-provided'); + WorkflowStub::assertSignalSentTimes('name-provided', 1); + WorkflowStub::assertSignalNotSent('other-signal'); +} +``` + +Використовуйте `WorkflowStub::assertSignalSent()`, `assertSignalSentTimes()` +і `assertSignalNotSent()` для перевірки надісланих signal. Форма callback +отримує ID екземпляра й аргументи signal: + +```php +WorkflowStub::assertSignalSent( + 'name-provided', + fn (string $instanceId, string $name): bool => + $instanceId === 'approval-1' && $name === 'Taylor' +); +``` + +### Надсилання update у режимі fake {#sending-updates-in-fake-mode} + +Update також застосовуються в процесі у fake. Виклик +`$workflow->attemptUpdate(...)` або `$workflow->update(...)` записує стійку +команду update, створює завдання workflow для його застосування й виконує +завдання в процесі: + +```php +use Workflow\UpdateMethod; +use Workflow\V2\Attributes\Signal; +use function Workflow\V2\await; +use Workflow\V2\Workflow; +use Workflow\V2\WorkflowStub; + +#[Signal('done')] +final class SettingsWorkflow extends Workflow +{ + private bool $enabled = false; + + public function handle(): array + { + await('done'); + + return ['enabled' => $this->enabled]; + } + + #[UpdateMethod] + public function toggle(bool $enabled): array + { + $this->enabled = $enabled; + + return ['enabled' => $this->enabled]; + } +} + +public function testUpdateWorkflow(): void +{ + WorkflowStub::fake(); + + $workflow = WorkflowStub::make(SettingsWorkflow::class, 'settings-1'); + $workflow->start(); + + $this->assertSame('waiting', $workflow->refresh()->status()); + + $result = $workflow->attemptUpdate('toggle', true); + + $this->assertTrue($result->accepted()); + + WorkflowStub::assertUpdateSent('toggle'); + WorkflowStub::assertUpdateSentTimes('toggle', 1); + WorkflowStub::assertUpdateNotSent('other-update'); + + // Complete the workflow + $workflow->signal('done'); + $this->assertTrue($workflow->refresh()->completed()); + $this->assertTrue($workflow->output()['enabled']); +} +``` + +Використовуйте `WorkflowStub::assertUpdateSent()`, `assertUpdateSentTimes()` +і `assertUpdateNotSent()` для перевірки надісланих update. Форма callback +отримує ID екземпляра й аргументи update: + +```php +WorkflowStub::assertUpdateSent( + 'toggle', + fn (string $instanceId, bool $enabled): bool => + $instanceId === 'settings-1' && $enabled === true +); +``` + +### Тестування результатів запуску й політики повторного запуску {#testing-start-outcomes-and-duplicate-start-policy} + +Використовуйте `attemptStart()` для перевірки поведінки повторного запуску +без викидання винятків. `StartResult` має типізовані помічники результатів: + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; + +public function testRejectDuplicateStart(): void +{ + WorkflowStub::fake(); + WorkflowStub::mock(MyActivity::class, 'result'); + + $workflow = WorkflowStub::make(MyWorkflow::class, 'order-123'); + + $first = $workflow->start('Taylor'); + $this->assertTrue($first->startedNew()); + + $second = WorkflowStub::load('order-123'); + $duplicate = $second->attemptStart('Taylor'); + + $this->assertTrue($duplicate->rejected()); + $this->assertTrue($duplicate->rejectedDuplicate()); + $this->assertSame('instance_already_started', $duplicate->rejectionReason()); +} +``` + +Для тестування повернення наявного активного run передайте +`StartOptions::returnExistingActive()`: + +```php +public function testReturnExistingActiveStart(): void +{ + WorkflowStub::fake(); + + $workflow = WorkflowStub::make(MySignalWorkflow::class, 'order-123'); + $workflow->start(); + + $this->assertSame('waiting', $workflow->refresh()->status()); + + $second = WorkflowStub::load('order-123'); + $result = $second->attemptStart(StartOptions::returnExistingActive()); + + $this->assertTrue($result->accepted()); + $this->assertTrue($result->returnedExistingActive()); + $this->assertSame($workflow->runId(), $result->runId()); +} +``` + +Обидві політики записують стійкі команди та події історії. Для глибшої +перевірки можна також перевіряти рядки `WorkflowCommand` і +`WorkflowHistoryEvent`. + +### Тестування бюджету історії {#testing-history-budget} + +Поля `HistoryBudget` (`history_event_count`, `history_size_bytes`, +`continue_as_new_recommended`) надаються проєкцією огляду run й доступні +через `RunDetailView`. У коді workflow базовий клас `Workflow` надає їх +як `$this->historyLength()`, `$this->historySize()` і +`$this->shouldContinueAsNew()`: + +```php +use Workflow\V2\Workflow; + +final class LongRunningWorkflow extends Workflow +{ + public function handle(): void + { + while (true) { + // ... process work ... + + if ($this->shouldContinueAsNew()) { + Workflow::continueAsNew($this->carryForwardState()); + } + } + } +} +``` + +Для тестування порогів бюджету історії задайте низькі значення +й перевірте рекомендацію: + +```php +public function testHistoryBudgetRecommendsContinueAsNew(): void +{ + config()->set('workflows.v2.history_budget.continue_as_new_event_threshold', 5); + + WorkflowStub::fake(); + + // Run a workflow that produces enough history events to trip the threshold + $workflow = WorkflowStub::make(ManyActivitiesWorkflow::class, 'budget-test'); + $workflow->start(); + + $summary = $workflow->summary(); + $this->assertTrue($summary->continue_as_new_recommended); +} +``` + +## Старий `Workflow\WorkflowStub` {#legacy-workflowworkflowstub} + +:::warning Стара v1 + +Приклади цього розділу використовують runtime v1 (`Workflow\Workflow`, +`use function Workflow\activity`, генераторний `execute()` із `yield`, +`Workflow\Models\StoredWorkflow`). Зберігайте ці шаблони для тестів workflow +v1 під час міграції. Нові тести мають використовувати +`Workflow\V2\WorkflowStub::fake()` із `Workflow\V2\Workflow` і послідовними +методами `handle()`, як показано вище. + +::: + +### Workflow {#workflows} + +У тестовому середовищі можна синхронно виконувати workflow та підміняти +activity й дочірні workflow, визначаючи очікувану поведінку й результати +без запуску справжніх реалізацій. + +``` +use function Workflow\activity; +use Workflow\Workflow; + +class MyWorkflow extends Workflow +{ + public function execute() + { + $result = yield activity(MyActivity::class); + + return $result; + } +} +``` + +Наведений workflow можна протестувати викликом `WorkflowStub::fake()` +із подальшою підміною activity. + +``` +public function testWorkflow() +{ + WorkflowStub::fake(); + + WorkflowStub::mock(MyActivity::class, 'result'); + + $workflow = WorkflowStub::make(MyWorkflow::class); + $workflow->start(); + + $this->assertSame($workflow->output(), 'result'); +} +``` + +До ` WorkflowStub::mock()` також можна передати callback замість +значення результату. + +Callback також отримує `$context` workflow та аргументи поточної activity. + +``` +public function testWorkflow() +{ + WorkflowStub::fake(); + + WorkflowStub::mock(MyActivity::class, function ($context) { + return 'result'; + }); + + $workflow = WorkflowStub::make(MyWorkflow::class); + $workflow->start(); + + $this->assertSame($workflow->output(), 'result'); +} +``` + +Перевіряйте, які activity чи дочірні workflow були надіслані, методами +`assertDispatched`, `assertNotDispatched` і `assertNothingDispatched`: + +``` +WorkflowStub::assertDispatched(MyActivity::class); + +// Assert the activity was dispatched twice... +WorkflowStub::assertDispatched(MyActivity::class, 2); + +WorkflowStub::assertNotDispatched(MyActivity::class); + +WorkflowStub::assertNothingDispatched(); +``` + +До `assertDispatched` або `assertNotDispatched` можна передати замикання +для перевірки, що надіслана activity чи дочірній workflow задовольняє +умову. Callback отримує аргументи activity або дочірнього workflow. + +``` +WorkflowStub::assertDispatched(TestOtherActivity::class, function ($string) { + return $string === 'other'; +}); +``` + +### Пропуск часу {#skipping-time} + +Змінюючи системний час через `$this->travel()` або `$this->travelTo()`, +можна моделювати workflow, залежні від часу, та тестувати тайм-аути, +затримки й іншу часову логіку. + +``` +use function Workflow\{activity, timer}; +use Workflow\Workflow; + +class MyTimerWorkflow extends Workflow +{ + public function execute() + { + yield timer(60); + + $result = yield activity(MyActivity::class); + + return $result; + } +} +``` + +Наведений workflow очікує 60 секунд перед виконанням activity. +`$this->travel()` і `$workflow->resume()` дозволяють пропустити цей +період очікування в старому runtime. + +``` +public function testTimeTravelWorkflow() +{ + WorkflowStub::fake(); + + WorkflowStub::mock(MyActivity::class, 'result'); + + $workflow = WorkflowStub::make(MyTimerWorkflow::class); + $workflow->start(); + + $this->travel(120)->seconds(); + + $workflow->resume(); + + $this->assertSame($workflow->output(), 'result'); +} +``` + +Помічники `$this->travel()` і `$this->travelTo()` використовують +`Carbon::setTestNow()` усередині. + +### Activity {#activities} + +Тестування activity схоже на тестування завдань Laravel. Вручну +створіть activity й викличте метод `handle()`. + +``` +$workflow = WorkflowStub::make(MyWorkflow::class); + +$activity = new MyActivity(0, now()->toDateTimeString(), StoredWorkflow::findOrFail($workflow->id())); + +$result = $activity->handle(); +``` + +Старі activity все ще виконуються через метод завдання runtime `handle()`. +У `Workflow\V2` код workflow та activity користувача також міститься +в `handle()`. From 3643aaca44cafe1a2947d508730dc3cfa2da9fa9 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 03:27:44 +0000 Subject: [PATCH 06/10] docs: translate Ukrainian runtime and operator contracts --- docs/polyglot/namespace-auth-workers.md | 2 +- docs/polyglot/php.md | 7 +- .../current/compatibility.md | 333 +++++++++++++++ .../current/polyglot/invocable-carrier.md | 255 +++++++++++ .../current/polyglot/invocable-php-handler.md | 280 +++++++++++++ .../polyglot/namespace-auth-workers.md | 256 +++++++++++ .../current/polyglot/php.md | 238 +++++++++++ .../polyglot/server-config-reference.md | 318 ++++++++++++++ .../current/polyglot/server-role-topology.md | 348 +++++++++++++++ .../current/waterline-operator-api.md | 396 ++++++++++++++++++ 10 files changed, 2429 insertions(+), 4 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/compatibility.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-carrier.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-php-handler.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/namespace-auth-workers.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/php.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-config-reference.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-role-topology.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/waterline-operator-api.md diff --git a/docs/polyglot/namespace-auth-workers.md b/docs/polyglot/namespace-auth-workers.md index 87145b67e3..445cf26a0f 100644 --- a/docs/polyglot/namespace-auth-workers.md +++ b/docs/polyglot/namespace-auth-workers.md @@ -157,7 +157,7 @@ curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/worker/register" \ | --- | --- | --- | | `worker_id` | no | Stable process identity. The server generates one when omitted, but long-running runtimes should set it for logs and task queue diagnostics. | | `task_queue` | yes | Queue this worker polls. Poll requests for a different queue fail with `reason: "task_queue_mismatch"`. | -| `runtime` | yes | One of `php`, `python`, `typescript`, `go`, or `java`. | +| `runtime` | yes | One of `php`, `python`, `rust`, `typescript`, `go`, `java`, or `external`. An accepted runtime identifier does not imply a first-party SDK is available. | | `sdk_version` | no | Runtime SDK version shown in worker visibility and diagnostics. | | `build_id` | no | Deploy/build identity used by task queue build-id visibility and rollout cohorts. It should stay stable for one replay-compatible worker family. | | `supported_workflow_types` | no | Workflow type keys this worker can replay. Empty means no workflow-type filter. | diff --git a/docs/polyglot/php.md b/docs/polyglot/php.md index df106b9bee..84101367ac 100644 --- a/docs/polyglot/php.md +++ b/docs/polyglot/php.md @@ -217,9 +217,10 @@ publishes its accepted protocol and codec set from `GET /api/cluster/info`. Check runtime discovery during deployment instead of inferring compatibility from a server patch version. -The PHP SDK is versioned independently from the 2.0 Laravel package. Keep the -exact published pin in runnable prerelease examples and evaluate release notes -when moving between pre-1.0 SDK releases; no cross-release shim is implied. +The PHP SDK is versioned independently from the Laravel package. Keep the +exact published pin in runnable examples and evaluate release notes when +upgrading SDK releases. Compatibility follows the SDK's declared contract, +not matching package version numbers. ## Related references diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/compatibility.md b/i18n/uk/docusaurus-plugin-content-docs/current/compatibility.md new file mode 100644 index 0000000000..1d6ff9a31e --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/compatibility.md @@ -0,0 +1,333 @@ +--- +sidebar_position: 17 +title: Сумісність версій +description: Канонічний контракт сумісності та повноважень випуску публічних поверхонь Durable Workflow. +tags: + - compatibility + - operations + - protocols + - api-stability +keywords: + - сумісність версій + - джерело контракту сумісності + - повноваження випуску + - рівні стабільності + - protocol manifests + - версія площини керування + - версія worker-протоколу + - контракт стабільності поверхонь +--- + +import PublicAuthorityIdentity from '@site/src/components/PublicAuthorityIdentity'; +import WorkerProtocolAuthorityRoles from '@site/src/components/WorkerProtocolAuthorityRoles'; +import protocolCatalog from '@site/static/platform-protocol-specs.json'; + +# Сумісність версій {#version-compatibility} + +Ця сторінка є **канонічним контрактом сумісності та повноважень випуску** +публічної платформи Durable Workflow. Вона є єдиним джерелом істини щодо: + +- публічних поверхонь, +- рівня стабільності кожної публічної поверхні, +- змін, дозволених у patch, minor чи major release, +- належності поля до контракту або лише до діагностики, +- протоколу узгодження версій runtime, за яким клієнти відхиляють роботу, + якщо Server оголошує поверхню, яку вони не підтримують. + +Документи стабільності окремих пакетів, наприклад `docs/api-stability.md` +репозиторію `durable-workflow/workflow`, довідник CLI `dw` та сторінка +операторського API Waterline, **підпорядковані** цій сторінці. +Вони додають деталі пакетів за цими правилами. У разі розбіжності +діє ця сторінка, а розбіжність є помилкою документа пакета. + +Той самий контракт публікується в машиночитаній формі, щоб SDK, +Server manifests та перевірки CI могли звірятися з одним джерелом істини: + +- `surface_stability_contract` у тілі відповіді `GET /api/cluster/info` + окремого Durable Workflow Server, schema + `durable-workflow.v2.surface-stability.contract`, версія `5`. +- Закріплене дзеркало того самого manifest у цьому репозиторії: + `static/compatibility-contract.json`. +- PHP-клас `Workflow\V2\Support\SurfaceStabilityContract`, який є + внутрішньопроцесним джерелом, повторно експортованим Server. + +Допуск каналів артефактів окремо визначає +[`/public-artifact-release-policy.json`](pathname:///public-artifact-release-policy.json). +Ця незалежно перевірена політика визначає, які канали 2.0 можуть стати +канонічним публічним набором версій. + +Випуск, який змінює рівень стабільності, набір полів або правила +несумісних змін будь-якої наведеної нижче поверхні, має одночасно +оновити цю сторінку, JSON-дзеркало, PHP manifest та відповідні документи +стабільності пакетів. Docs CI перевіряє машиночитаний контракт, +політику фази випуску артефактів, metadata опублікованого Rust за наявності +та worker-protocol specs. Узгодження тексту цієї сторінки з manifest +залишається явним обов'язком перевірки випуску. + +## Додатково: каталог специфікацій протоколів платформи {#companion-platform-protocol-spec-catalog} + +Ця сторінка визначає, *які* поверхні публічні та *як* вони можуть змінюватися. +Каталог [специфікацій протоколів платформи](/docs/platform-protocol-specs) +визначає, *де* лежить нормативна машиночитана специфікація кожної поверхні, +*який формат* вона використовує: OpenAPI для HTTP API, JSON Schema для +сімейств об'єктів, AsyncAPI для семантики event streams. Він також визначає +*репозиторій-власник*, керовані сімейства об'єктів та публічний URL артефакту. +Автори SDK, агенти й оператори мають перевіряти відповідність каталогу specs, +а не залежати від повторного читання тексту чи деталей реалізації репозиторію. + +Каталог оголошується як `platform_protocol_specs` +у `GET /api/cluster/info`. + + + +`surface_family` кожного запису каталогу має існувати в контракті вище. +CI документації перевіряє каталог, отримує кожен публічний spec URL +і відхиляє поля повноважень, локальні для репозиторію. + + + +Кожен обов'язковий запис каталогу протоколів платформи має позначку +`published`. Запис invocable carrier залишається `in_progress`. +Кожен доступний запис безпосередньо посилається на публічний документ +OpenAPI, AsyncAPI чи JSON Schema. Schema +[`cluster_info_envelope`](/docs/platform-protocol-specs#cluster-info-envelope-notes) +закріплює discovery-поверхню, через яку доступні всі інші записи каталогу. + +## Рівні стабільності {#stability-levels} + +Кожна публічна поверхня Durable Workflow має рівно один із цих рівнів +стабільності. Рівні задаються явно. Некласифікована поверхня не є публічною. + +| Рівень | Значення | Коли дозволені несумісні зміни | +|-------|---------|-----------------------------------| +| `frozen` | Формат передачі чи збережена форма, які мають декодуватися однаково протягом життя workflow. Перейменування, видалення чи зміна призначення поля порушує протокол і не є minor-зміною. | Лише через **паралельний primitive** з новою назвою типу. Початкова форма назавжди залишається придатною до декодування. | +| `stable` | Публічна поверхня під гарантією semver платформи. Додаткові можливості випускаються в minor releases. | Лише major release. | +| `prerelease` | Функціонально завершена публічна поверхня, яка ще може змінюватися до відповідного випуску `1.0.0` / `2.0.0`. | У явно позначених prerelease-версіях із повідомленням у release notes. | +| `experimental` | Нестабільна публічна поверхня. Може змінюватися в будь-якому випуску, включно з patch. Користувач має явно погодитися, прочитавши experimental-прапорець поверхні. | Будь-який випуск із повідомленням у release notes. | + +## Сімейства публічних поверхонь {#public-surface-families} + +Це повний список сімейств публічних поверхонь. Додавання, видалення +або зміна класифікації сімейства вимагає зміни контракту: +підвищення версії `SurfaceStabilityContract`, оновлення цієї сторінки +та JSON-дзеркала в одному commit. + +| Сімейство | Стабільність | Джерело manifest у `/api/cluster/info` | Область | +|--------|-----------|-------------------------------------------|----------------| +| `server_api` | `stable` | `control_plane` | HTTP API окремого Server: маршрути керування, namespaces, schedules, системи та `/api/health`, `/api/ready`, `/api/cluster/info`. Версію маршруту визначають `control_plane.request_contract` і `control_plane.response.contract`. Верхньорівнева `version` Server визначає збірку, а не сумісність клієнта. | +| `worker_protocol` | `stable` | `worker_protocol` | HTTP API worker plane для реєстрації зовнішніх SDK workers, polling, heartbeat, leases worker sessions, успішного завершення та помилок workflow, activity і query tasks. Включає runtime-контракти `worker_sessions` і `local_activities`, `external_execution_surface_contract`, `external_executor_config_contract`, `invocable_carrier_contract`, `external_task_input_contract` та `external_task_result_contract`. | +| `cli_json` | `stable` | не застосовується (дивіться довідник CLI) | Форми `--output=json` та `--output=jsonl`, які виводить `dw`. JSON exit codes і назви JSON-полів є durable-поверхнею. Людиночитана форма `--output=table` є документацією, а не контрактом. | +| `waterline_api` | `stable` | не застосовується (дивіться операторський API Waterline) | HTTP API спостереження Waterline під `/waterline/api/v2/*`, контракт engine source та JSON-форми dashboard. Waterline має відповідати major-версії пакета workflow. | +| `mcp_discovery_results` | `stable` | не застосовується (дивіться MCP workflows) | Поверхні Model Context Protocol `/mcp/*` та discovery-файли `llms.txt` / `llms-2.0.txt`. Назви MCP-інструментів, schemas параметрів і семантика `payload_preview_limit_bytes` є контрактом. Описи інструментів та discovery hints є діагностичними. | +| `official_sdks` | `stable` | `client_compatibility` | Офіційні SDK: PHP `durable-workflow/sdk`, Python `durable_workflow` та Rust `durable-workflow`. CLI `dw` є офіційним клієнтом команд. Публічну поверхню кожного SDK визначає його документ стабільності, підпорядкований цій сторінці. | +| `history_event_wire_formats` | `frozen` | не застосовується (закріплені форми, дивіться workflow `docs/api-stability.md`) | Збережена форма кожного рядка `workflow_history_events` та `workflow_schedule_history_events`. Після запису події workflow кожен майбутній SDK, який replay її, має декодувати той самий набір полів. | +| `cluster_info_manifests` | `stable` | `surface_stability_contract`, `client_compatibility`, `control_plane`, `worker_protocol`, `auth_composition_contract`, `coordination_health` | Самі protocol manifests, опубліковані `GET /api/cluster/info`. Кожен вкладений manifest має власні `schema` і `version` та змінюється за власними правилами контракту. Ключі оболонки сталі. | + +### Документи стабільності пакетів {#per-package-stability-documents} + +Ці документи додають деталі пакетів за правилами цієї сторінки: + +- `durable-workflow/workflow` (PHP) — [`docs/api-stability.md`](https://github.com/durable-workflow/workflow/blob/main/docs/api-stability.md). Джерело контракту PHP API написання workflow, серверних класів `Support\*` та таблиць закріплених форматів передачі подій історії. +- `durable-workflow/sdk` (PHP) — [`README.md`](https://github.com/durable-workflow/sdk-php/blob/main/README.md). Джерело контракту незалежного від фреймворку API віддаленого клієнта та worker з Packagist. +- `durable-workflow/server` — [`README.md`](https://github.com/durable-workflow/server/blob/main/README.md) та `docs/contracts/*`. Джерело контрактів запитів і відповідей окремого Server. +- CLI `dw` — [`/docs/polyglot/cli-reference`](/docs/polyglot/cli-reference). Джерело контракту форм JSON-виводу та exit codes. +- Python SDK — `README.md` у `durable-workflow/sdk-python`. Джерело контракту публічного API пакета `durable_workflow`. +- Rust SDK — `README.md` і `[package.metadata.durable-workflow]` у `durable-workflow/sdk-rust`. Джерело контракту публічного API crate `durable-workflow` та його оголошення сумісності. + +## Правила випусків {#release-rules} + +Ці правила діють для кожного сімейства публічних поверхонь вище. +Вони відтворені в JSON-дзеркалі під `release_rules`. + +### Patch releases {#patch-releases} + +Дозволено: +- виправлення помилок зі збереженням документованого контракту +- виправлення документації +- оновлення залежностей без зміни публічної поверхні +- зміни поверхонь з позначкою `experimental` + +Заборонено: +- видалення або перейменування будь-якого поля, маршруту, команди чи класу `stable` або `frozen` +- звуження прийнятих вхідних даних маршруту чи команди `stable` +- зміна значення наявного поля `stable` + +### Minor releases {#minor-releases} + +Дозволено: +- додавання нових полів, маршрутів, команд чи класів до поверхні `stable` +- додавання необов'язкових параметрів із безпечними стандартними значеннями +- додавання capability flags до discovery-відповідей +- переведення поверхні `prerelease` чи `experimental` до `stable` + +Заборонено: +- видалення або перейменування будь-якого поля, маршруту, команди чи класу `stable` або `frozen` +- зміна значення наявного поля `stable` або `frozen` + +### Major releases {#major-releases} + +Дозволено: +- видалення, перейменування або звуження поверхні `stable` +- підвищення потрібної `control_plane.version` чи `worker_protocol.version` +- припинення підтримки раніше підтримуваного діапазону версій SDK або CLI + +Обов'язково: +- повідомити в release notes щонайменше за один minor release до major +- за можливості випустити нову поверхню поряд зі старою в попередньому + minor release, щоб користувачі могли мігрувати до major +- до публікації описати шлях переходу в + [настанові міграції](/docs/migration) + +## Діагностичні та гарантовані поля {#diagnostic-only-versus-guaranteed-fields} + +Кожне поле кожної поверхні `stable` чи `frozen` є **гарантованим** +або **лише діагностичним**. Для них діють різні правила зміни: + +- **Гарантовані поля** є частиною документованого контракту. + Виробники мають продовжувати надавати їх у документованій формі. + Споживачі можуть покладатися на їхню присутність і значення. + Видалення чи перейменування гарантованого поля поверхні `stable` + є major-зміною. +- **Лише діагностичні поля** надаються для розбору проблем людиною, + налагодження та спостереження. Їх можна додавати, перейменовувати + або прибирати в будь-якому minor release. Усюди в документації + вони мають бути позначені `diagnostic_only: true` або відповідником + на сторінці. **Споживачі не можуть розбирати, зберігати чи використовувати + діагностичні поля для production-логіки прийняття рішень.** + +Старіші споживачі мають ігнорувати невідомі додаткові поля форм +`stable` чи `frozen` для forward compatibility. Невідомі обов'язкові +поля мають спричиняти відхилення. SDK та CLI публікують власну поведінку +forward compatibility в документах стабільності пакетів. + +## Матриця сумісності {#compatibility-matrix} + +Операційна матриця сумісності записує, які версії клієнтів перевірено +з якими protocol manifests Server. Компоненти перевіряють матрицю +під час роботи через `GET /api/cluster/info` та відхиляють роботу, +коли manifests не узгоджуються. + +### Останній перевірений відтворюваний набір {#last-qualified-reproducible-tuple} + +Таблиця є незмінним підтвердженням сумісності останнього спільно +перевіреного набору 2.0. Це запис для відтворення, а не окремі +твердження про найновіший пакет кожного реєстру: + +| Компонент | Підтримувана версія | Ідентичність установлення | +|-----------|-------------------|------------------| +| Server | `%%artifact.serverVersion%%` | `%%artifact.serverDockerHubImage%%` | +| CLI | `%%artifact.cliVersion%%` | `%%artifact.cliInstallerEnv%%` | +| Рушій Workflow | `%%artifact.workflowVersion%%` | `%%artifact.workflowComposerPackage%%` | +| Операторський Waterline | `%%artifact.waterlineVersion%%` | `%%artifact.waterlineComposerPackage%%` | +| PHP SDK | `%%artifact.phpSdkVersion%%` | `%%artifact.phpSdkComposerPackage%%` | +| Python SDK | `%%artifact.pythonSdkVersion%%` | `%%artifact.pythonPackagePin%%` | +| Rust SDK | `%%artifact.rustSdkVersion%%` | `%%artifact.rustCargoRequirement%%` | + +PyPI показує версію Python distribution як +`%%artifact.pythonRegistryVersion%%`. Документоване позначення встановлення +PEP 440 `%%artifact.productTrainVersion%%` вибирає той самий випуск. +Ця нормалізація не створює другої підтримуваної версії. + +Перевірка координується для всього набору. Набір можна опублікувати +лише за спільного ідентифікатора джерела всіх семи записів, збігу обох +реєстрів Server та використання цих точних артефактів згенерованим +quickstart-контрактом. Оновлювач реєстрів відхиляє неузгоджений набір +замість поєднання незалежно найновіших пакетів. Його поточна дозволена +фаза випуску — `%%artifact.releasePhase%%`. Tags пізніших каналів реєстрів +залишаються недопустимими до перевірки та зміни політики випуску. + +Попередні alpha- та beta-артефакти належать до історії випусків. +Вони не є альтернативами для початку роботи. Історія випусків зберігається. + +Можливості цієї серії є базовими для 2.0, тому не мають матриці версій +першої появи функцій. Нові можливості проходять через звичайні сумісні +випуски: додаткова робота підвищує сумісну версію, а несумісна зміна +публічної поверхні чекає наступної major-версії. +Кожен стабільний компонент дотримується semantic versioning від цієї бази 2.0. + +### Сумісність протоколів runtime {#runtime-protocol-compatibility} + +Верхньорівневі версії пакетів вибирають підтримувану серію. +Protocol manifests runtime надають другу перевірку з відхиленням невідповідності: + +| Клієнт | Серія продукту | Площина керування | Запит worker-протоколу | +|--------|---------------|---------------|-------------------------| +| CLI | `%%artifact.cliVersion%%` | `2` | не застосовується | +| PHP SDK | `%%artifact.phpSdkVersion%%` | `2` | `1.13` | +| Python SDK | `%%artifact.pythonSdkVersion%%` | `2` | `1.1` | +| Rust SDK | `%%artifact.rustSdkVersion%%` | `2` | `1.2` | + +Поточний опублікований Server оголошує версію worker-протоколу, +наведену в таблиці повноважень вище. Він приймає заголовки запиту +з тією самою major-версією та minor не вище оголошеної, після чого +повертає оголошену версію. Відсутні або некоректні заголовки, інші major +та worker minor вище Server відхиляються. +CLI перевіряє `control_plane.version: "2"`. + +Верхньорівнева `version` Server ідентифікує збірку. +Для узгодження протоколу клієнти мають використовувати manifests +`control_plane`, `worker_protocol`, `client_compatibility` та +`surface_stability_contract`, повернуті `GET /api/cluster/info`. + +Workflow та Waterline мають використовувати сумісні випуски 2.x. +Після стабільного випуску межу сумісності визначають runtime discovery +та обмеження пакетів. + +### Приклади перевірки runtime {#runtime-validation-examples} + +SDK перевіряють discovery перед реєстрацією worker. +Несумісний Server дає явну помилку сумісності замість спроби використати +старий prerelease-шлях. Усі запити worker надсилають +`X-Durable-Workflow-Protocol-Version`, а запити площини керування — +`X-Durable-Workflow-Control-Plane-Version: 2`. + +Перед просуванням серії продукту перевірка випуску має починатися +з чистого середовища, установлювати лише наведені вище опубліковані +артефакти та проходити conformance для PHP, Python і Rust. +Source checkouts і неопубліковані заміни не є підтвердженням +опублікованих артефактів. + +Metadata стабільних пакетів та runtime discovery визначають підтримувану +межу сумісності 2.x. Перевірка випуску й далі спільно встановлює +публічні артефакти перед публікацією, а patch і minor releases +дотримуються наведених нижче правил semantic versioning. + +### Розвиток випусків {#release-progression} + +Patch releases зберігають документовані стабільні контракти. +Minor releases можуть додавати поля, маршрути, команди, класи +чи необов'язкові параметри з безпечними стандартними значеннями. +Несумісні стабільні зміни потребують major release та документованого +шляху міграції. Закріплені форми подій історії назавжди залишаються +придатними до декодування. Нова форма використовує паралельний primitive +замість зміни наявної події. + +Кожен випуск має узгоджувати metadata пакетів, release notes, +команди встановлення та міжмовні приклади. + +### Перевірка випуску {#release-review-checklist} + +- Перевірте відповідність машиночитаного контракту сумісності + manifest стабільності поверхонь Workflow. +- Перевірте кожну вибрану версію SDK з вибраним Server. +- Перевірте використання стабільного каналу в прикладах установлення. +- Перевірте точні версії SDK та протоколів у metadata пакета. + Запишіть пізнішу перевірку Server у даних сумісності. +- Перевірте проходження conformance опублікованих артефактів + у чистому середовищі для PHP, Python і Rust. +- Перевірте, що release notes описують доповнення після базового випуску + та не подають старі prereleases як підтримувані варіанти. + +## Дивіться також {#see-also} + +- [Налаштування Server](/docs/polyglot/server) — розгортання окремого Server +- [Довідник Server API](/docs/polyglot/server-api-reference) — `GET /api/cluster/info` та protocol manifests +- [PHP SDK](/docs/polyglot/php) — PHP-клієнт і worker +- [Python SDK](/docs/polyglot/python) — Python-клієнт і worker +- [Rust SDK](/docs/polyglot/rust) — Rust-клієнт і worker +- [CLI](/docs/polyglot/cli) — інтерфейс командного рядка +- [Настанова міграції](/docs/migration) — перехід із v1 до v2 +- [PHP workflow `docs/api-stability.md`](https://github.com/durable-workflow/workflow/blob/main/docs/api-stability.md) — стабільність пакета PHP workflow diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-carrier.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-carrier.md new file mode 100644 index 0000000000..aca54407fc --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-carrier.md @@ -0,0 +1,255 @@ +--- +sidebar_position: 4 +title: Invocable HTTP carrier +description: Налаштування invocable_http carrier лише для activities, щоб Server міг POST leased activity task до HTTPS-обробника й узгодити структурований результат. +tags: + - external-execution + - invocable-carrier + - worker-protocol +keywords: + - invocable carrier + - invocable_http + - конфігурація зовнішнього виконавця + - HTTP-обробник activity + - оболонка вхідних даних external task + - оболонка результату external task +--- + +# Invocable HTTP carrier {#invocable-http-carrier} + +Invocable HTTP carrier є першим конкретним carrier, визначеним у межах +[зовнішнього виконання](./external-execution.md). Він публікується +в `GET /api/cluster/info` за шляхом +`worker_protocol.invocable_carrier_contract` +зі значеннями `schema: durable-workflow.v2.invocable-carrier.contract`, +`version: 1` та `carrier_type: invocable_http`. + +Коли налаштоване зіставлення обробника визначає carrier `invocable_http`, +Server викликає його endpoint з незалежною від carrier +[оболонкою вхідних даних external task](./external-execution.md#published-contract-seams) +і узгоджує відповідь з +[оболонкою результату external task](./external-execution.md#published-contract-seams). +Workflow tasks, порядок signals/updates, replay і зміни історії +залишаються всередині Server. Carrier лише передає визначені оболонки +вхідних даних і результатів через межу HTTPS. + +## Призначення {#what-it-is} + +Invocable HTTP carrier призначений для роботи рівня activity, яку +операторська команда воліє розмістити як HTTPS-обробник замість long-poll worker: + +- activities обслуговування та автоматизація платформи у внутрішньому HTTP-сервісі +- обмежені передачі роботи інтеграціям, які вже мають HTTPS endpoint +- serverless або контейнерні обробники activity з одним POST-маршрутом + на тип activity + +Виконання activity передається налаштованому endpoint. +Durable-стан workflow, історія та контракт завершення activity +залишаються відповідальністю Server. + +## Область лише activities {#activity-only-scope} + +Опублікований manifest закріплює область carrier як +`task_kinds: [activity_task]` і явно визначає те, чого він не робить, +щоб конфігурація не змінювала цю межу: + +- `workflow_task_execution` — workflow tasks залишаються на справжніх workflow runtimes +- `workflow_replay` — carrier ніколи не replay історію workflow +- `history_mutation` — обробники не можуть безпосередньо змінювати історію подій +- `generic_webhook_ingress` — загальні вхідні webhooks проходять через bridge adapter + +Зіставлення обробника з `kind`, відмінним від activity, або зіставлення carrier +з capability, відмінною від `activity_task`, не проходить перевірку +конфігурації з `invalid_invocable_carrier_scope` ще до появи +в activity poll-відповіді. + +## HTTPS-ціль та метод {#https-target-and-method} + +Server перевіряє `target_fields` carrier перед публікацією зіставлення: + +| Поле | Обов'язкове | Дозволено | Примітки | +| --- | --- | --- | --- | +| `url` | так | абсолютний HTTPS URL або HTTP для локальної loopback-розробки (`localhost`, `127.0.0.0/8`, `::1`) | Облікові дані в URL (`scheme://user:pass@host`) заборонені. | +| `method` | ні | `POST` | Стандартно `POST`. Інші HTTP-методи не приймаються. | +| `timeout_seconds` | ні | ціле число 1–900 | Транспортний deadline однієї спроби обробника. Deadline task окремо контролює Server/runtime. | +| `retry_policy` | ні | об'єкт (дивіться нижче) | Транспортний retry budget carrier. | + +Некоректні цілі відхиляються з `invalid_carrier_target` +і ніколи не потрапляють у discovery. + +## Модель автентифікації {#auth-model} + +Зіставлення `invocable_http` поза loopback мають отримати `auth_ref` +з конфігурації зовнішнього виконавця. Invocable HTTP без автентифікації +дозволений лише для loopback HTTP-цілей під час локальної розробки. + +- `auth_refs` оголошуються один раз на верхньому рівні конфігурації + та використовуються зі стандартних налаштувань або окремих зіставлень. + Підтримувані типи: `profile`, `env`, `token_file`, `mtls` і `signed_headers`. +- Зіставлення з ціллю поза loopback без дієвого `auth_ref` не проходить + перевірку конфігурації з `missing_invocable_auth_ref`. +- Токени, секрети, підписи та заголовки авторизації ніколи не виводяться + в діагностиці кластера, `dw server:info` чи activity poll-відповіді. + Діагностика зіставлення показує вибраний `auth_ref` і приховане + зведення, а не значення облікових даних. + +Оператор може перевірити за діагностикою runtime, який секрет автентифікації +буде використано, без розкриття самого секрету Server. + +## Транспортні retries та durable retries activity {#transport-retry-versus-durable-activity-retry} + +Необов'язкова `retry_policy` invocable carrier діє **лише на транспорт**. +Вона керує HTTP-доставкою carrier до повідомлення результату обробником: + +| Поле `retry_policy` | Дозволено | Стандартне значення | Значення | +| --- | --- | --- | --- | +| `max_attempts` | ціле число 1–5 | `1` | Максимальна кількість спроб HTTP-доставки carrier для одного task lease. | +| `backoff_seconds` | масив цілих чисел 0–300, до 5 елементів | `[]` | Затримка перед кожною наступною HTTP-спробою. | +| `retryable_status_codes` | підмножина `[408, 425, 429, "5xx"]` | `[408, 429, "5xx"]` | Коди відповіді, які допускають транспортний retry. | + +Транспортні retries не стають подіями історії. Server отримує лише +остаточний результат carrier: структуровану оболонку успіху чи помилки +від обробника, транспортний timeout, зіставлений із +`failure.kind=timeout, classification=deadline_exceeded`, +або один із варіантів некоректного результату. + +Після повідомлення результату обробником **durable retry policy activity +залишається повноваженням Server/runtime**. Retry task, його планування +та backoff продовжують виконувати політику activity, оголошену у workflow. +`retry_policy` carrier не подовжує, не перевизначає й не замінює її. + +## Оболонки запиту та відповіді {#request-and-response-envelope} + +Server надсилає незалежну від carrier оболонку вхідних даних +і очікує таку саму незалежну оболонку результату: + +| Напрямок | Content type | Schema | +| --- | --- | --- | +| Запит | `application/vnd.durable-workflow.external-task-input+json` | `external_task_input_contract` | +| Відповідь | `application/vnd.durable-workflow.external-task-result+json` | `external_task_result_contract` | + +Обробник має зберегти `task.id`, `task.attempt` і `task.idempotency_key` +із вхідної оболонки та повернути визначену оболонку успіху чи помилки +або отримати відхилення через зіставлення некоректного результату. + +Carrier детерміновано зіставляє транспортні факти з результатами: + +- транспортний timeout → `failure.kind=timeout`, + `classification=deadline_exceeded` +- відповідь не 2xx без коректної оболонки результату → `malformed_output` +- некоректний JSON або невідповідність schema у відповіді → `malformed_output` +- оболонка результату з непідтримуваним посиланням на payload → `unsupported_payload` + +Обробники мають бути ідемпотентними. Ті самі `task.id` і `task.idempotency_key` +можуть надійти кілька разів, якщо carrier повторює транспортну доставку +або runtime повторно доставляє незавершений lease. + +## Приклад конфігурації {#configuration-example} + +Server читає зіставлення обробників з `DW_EXTERNAL_EXECUTOR_CONFIG_PATH` +і може вибрати overlay через `DW_EXTERNAL_EXECUTOR_CONFIG_OVERLAY`. +Повний список змінних середовища наведено в +[довіднику конфігурації Server](./server-config-reference.md). + +Мінімальний документ `durable-workflow.external-executor.config` нижче +реєструє один carrier `invocable_http` та одне зіставлення activity: + +```json +{ + "schema": "durable-workflow.external-executor.config", + "version": 1, + "defaults": { + "profile": "prod", + "namespace": "operations", + "task_queue": "operator-tasks", + "auth_ref": "handler-token" + }, + "auth_refs": { + "handler-token": { + "type": "env", + "env": "DURABLE_WORKFLOW_HANDLER_TOKEN" + } + }, + "carriers": { + "ops-invocable": { + "type": "invocable_http", + "url": "https://handlers.example.com/durable/activity", + "method": "POST", + "timeout_seconds": 60, + "capabilities": ["activity_task"], + "retry_policy": { + "max_attempts": 3, + "backoff_seconds": [2, 5], + "retryable_status_codes": [408, 429, "5xx"] + } + } + }, + "mappings": [ + { + "name": "billing.reconcile-ledger", + "kind": "activity", + "task_queue": "operator-tasks", + "activity_type": "billing.reconcile-ledger", + "carrier": "ops-invocable", + "handler": "billing.reconcile-ledger", + "timeout_seconds": 60 + } + ] +} +``` + +Локальний loopback-варіант використовує +`"url": "http://127.0.0.1:8080/durable/activity"` +і може не містити `auth_ref`, бо loopback HTTP є єдиним дозволеним +шляхом без автентифікації. + +## Огляд та діагностика {#inspection-and-diagnostics} + +Дві CLI-команди показують опублікований контракт invocable carrier +з активного Server: + +- `dw server:info` відображає назву schema, версію контракту, тип carrier, + види tasks, дозволені content types запитів і відповідей та діагностику + зіставлень зовнішнього виконавця з прихованими секретами. + Перевірте нею, що Server завантажив очікувані зіставлення. +- `dw doctor` виконує діагностику cluster info, виводить блок + `invocable_carrier_contract` і показує помилки + `invalid_carrier_target`, `missing_invocable_auth_ref`, + `invalid_invocable_carrier_scope`, `unknown_carrier`, + `unknown_auth_ref` або `unknown_handler`, які завадили публікації зіставлення. + +Activity poll-відповідь сама повідомляє вибране зіставлення, ціль carrier +з прихованими даними автентифікації та дієву `retry_policy`. +Workers і операторські інструменти використовують ці поля як джерело +істини замість власної кешованої копії конфігурації. + +## Співіснування та rollout {#coexistence-and-rollout} + +Межа rollout invocable carrier публікується в тому самому manifest: + +- Poll-based carrier та `invocable_http` можуть спільно використовувати + чергу лише за зіставлень для конкретних типів activity. + Два зіставлення не можуть володіти тією самою парою `(task_queue, activity_type)`. +- Перед видаленням облікових даних оператор має прибрати або вимкнути + через overlay зіставлення invocable, що їх використовують. + Видалення облікових даних до drain зіставлення переводить кожну + нову спробу в `failure.kind=auth`, а не в удаваний успіх. +- `retry_policy` carrier стосується лише транспорту. + Durable retry policy activity залишається єдиним джерелом рішень + щодо кількості спроб task, які бачить workflow. + +## Пов'язані поверхні {#related-surfaces} + +- [Зовнішнє виконання](./external-execution.md) — незалежна від carrier + продуктова межа, яку реалізує цей carrier. +- [PHP-обробник invocable activity](./invocable-php-handler.md) — PHP-помічник + для розбору вхідних оболонок зовнішнім процесом, виклику activity callable + та формування структурованих оболонок результату, яких очікує carrier. +- [Довідник конфігурації Server](./server-config-reference.md) — змінні + середовища `DW_EXTERNAL_EXECUTOR_CONFIG_PATH` + та `DW_EXTERNAL_EXECUTOR_CONFIG_OVERLAY`. +- [Зовнішнє сховище payload](../features/external-payload-storage.md) — + перенесення завеликих payload запиту чи результату в налаштований driver + та подання їх перевірюваними посиланнями в тих самих schemas. +- [Worker-протокол](./worker-protocol.md) — ширший контракт worker plane, + який публікує цей carrier поруч із poll-based формами обробників. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-php-handler.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-php-handler.md new file mode 100644 index 0000000000..d7108526e5 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/invocable-php-handler.md @@ -0,0 +1,280 @@ +--- +sidebar_position: 5 +title: PHP-обробник invocable activity +description: Використання PHP-помічника InvocableActivityHandler для HTTPS-обробників activities, які отримують оболонки вхідних даних external task та повертають оболонки результату. +tags: + - external-execution + - invocable-carrier + - worker-protocol +keywords: + - обробник invocable activity + - InvocableActivityHandler + - вхідні дані external task + - результат external task + - PHP activity adapter + - Lambda activity handler +--- + +# PHP-обробник invocable activity {#php-invocable-activity-handler} + +[Invocable HTTP carrier](./invocable-carrier.md) дає змогу Server надсилати +leased activity task через POST до HTTPS endpoint замість очікування +long-poll worker. PHP-помічник `Workflow\V2\Support\InvocableActivityHandler` +є еталонною реалізацією, за допомогою якої зовнішній PHP-процес перетворює +запит на оболонку результату, яку Server може узгодити. + +Використовуйте його для інтеграції обробника activity з: + +- AWS Lambda або Google Cloud Function, яку викликають через HTTPS +- Laravel controller за невеликим HTTP-сервісом +- контейнером, який надає один POST endpoint на чергу activities + +Помічник розбирає незалежну від carrier оболонку вхідних даних external task, +знаходить зареєстрований callable, контролює deadline lease і формує +незалежну від carrier оболонку результату external task, +включно з формами помилок, яких очікує Server. + +## Швидкий початок {#quick-start} + +Установіть пакет Durable Workflow у зовнішньому процесі, зареєструйте +один callable для кожної назви обробника activity +та передайте тіло запиту до `handle()`: + +```php +use Workflow\V2\Support\InvocableActivityHandler; + +$handler = new InvocableActivityHandler([ + 'billing.charge-card' => static function (int $amount, string $currency): array { + // Real charge-card logic. Must be idempotent per task id. + return [ + 'approved' => true, + 'amount' => $amount, + 'currency' => $currency, + ]; + }, +]); + +$envelope = json_decode(file_get_contents('php://input'), associative: true); +$result = $handler->handle($envelope); + +header('Content-Type: application/vnd.durable-workflow.external-task-result+json'); +echo json_encode($result, JSON_UNESCAPED_SLASHES); +``` + +Обробник отримує вхідну оболонку й повертає оболонку результату. +Invocable HTTP carrier відповідає за контракт carrier: HTTPS, POST, +автентифікацію, timeouts та retry budget. PHP-помічник відповідає +за декодування аргументів, dispatch обробника, контроль deadline, +кодування результату й класифікацію помилок. + +## Реєстрація обробників {#registering-handlers} + +Перший аргумент конструктора є map із ключами, що відповідають +`task.handler` у вхідній оболонці від Server. Це значення поля `handler` +відповідного запису конфігурації зовнішнього виконавця: + +```php +new InvocableActivityHandler( + handlers: [ + 'billing.charge-card' => [$billingService, 'chargeCard'], + 'billing.refund' => [$billingService, 'refund'], + 'ops.rotate-key' => static fn (string $keyId): array => $keys->rotate($keyId), + ], + carrier: 'billing-lambda', + resultCodec: 'avro', +); +``` + +Вхідні дані з незареєстрованим `task.handler` дають результат +`failed` із `failure.kind = application`, +`classification = application_error` та `type = UnknownActivityHandler`. +Налаштована retry policy activity продовжує діяти на Server. + +Необов'язкове ім'я `carrier` повертається в `metadata.carrier` кожної +оболонки результату, щоб оператор міг визначити зовнішній runtime відповіді. +Використовуйте сталий ідентифікатор без секретів, наприклад +`billing-lambda`, `ops-cloud-run` або `laravel-admin-api`. + +Необов'язковий `resultCodec` визначає серіалізацію поверненого значення +в `result.payload.blob`. Стандартно це `avro`, що відповідає codec +durable payload PHP workers. Codec результату має бути відомим +`CodecRegistry` Server. `protobuf` не приймається +й негайно відхиляється в конструкторі. + +## Оболонка результату {#result-envelope} + +За успіху `handle()` повертає незалежну від carrier оболонку успіху: + +```json +{ + "schema": "durable-workflow.v2.external-task-result", + "version": 1, + "outcome": { + "status": "succeeded", + "recorded": true + }, + "task": { + "id": "acttask_01HV7D3G3G61TAH2YB5RK45XJS", + "kind": "activity_task", + "attempt": 1, + "idempotency_key": "attempt_01HV7D3KJ1C8WQNNY8MVM8J40X" + }, + "result": { + "payload": { + "codec": "avro", + "blob": "" + }, + "metadata": { + "content_type": "application/vnd.durable-workflow.result+json" + } + }, + "metadata": { + "handler": "billing.charge-card", + "carrier": "billing-lambda", + "duration_ms": 42 + } +} +``` + +Поля `task.id`, `task.attempt` і `task.idempotency_key` копіюються +з вхідної оболонки, щоб Server міг узгодити результат +із початковим lease. + +За помилки `handle()` повертає оболонку помилки. Блок `failure` +містить вид, classification, повідомлення, початковий PHP-тип, stack trace +і можливість retry. Помилка deadline також містить назву `deadline` +та значення `expires_at`: + +```json +{ + "schema": "durable-workflow.v2.external-task-result", + "version": 1, + "outcome": { + "status": "failed", + "retryable": true, + "recorded": true + }, + "task": {"id": "...", "kind": "activity_task", "attempt": 1, "idempotency_key": "..."}, + "failure": { + "kind": "timeout", + "classification": "deadline_exceeded", + "message": "Invocable activity task received after lease.expires_at.", + "type": "ExternalTaskDeadlineExceeded", + "stack_trace": null, + "timeout_type": "deadline_exceeded", + "cancelled": false, + "details": { + "deadline": "lease.expires_at", + "expires_at": "2026-04-22T15:14:02.000000Z" + } + }, + "metadata": {"handler": "billing.charge-card", "carrier": "billing-lambda", "duration_ms": 3} +} +``` + +## Класифікація помилок {#failure-taxonomy} + +| `failure.kind` | `classification` | Retry можливий | Коли виникає | +| --- | --- | --- | --- | +| `timeout` | `deadline_exceeded` | так | Deadline lease чи вхідних даних уже сплив на момент отримання оболонки або сплив під час виконання до повернення обробника. | +| `decode_failure` | `decode_failure` | ні | Аргументи не вдалося декодувати оголошеним codec, рядок deadline не вдалося розібрати, обробник підняв `TypeError` / `ValueError` через параметри або payload успіху не вдалося закодувати налаштованим codec результату. | +| `application` | `application_error` | залежить від винятку | Обробник підняв виняток. `retryable` дорівнює `false`, якщо виняток реалізує `Workflow\Exceptions\NonRetryableExceptionContract`, інакше `true`. Повідомлення береться з винятку, а `type` є його класом. | +| `application` | `application_error` | ні | `task.kind` не дорівнює `activity_task` або `task.handler` не зареєстрований у map. | + +Помічник ніколи не повертає голий виняток. Кожен шлях виконання формує +структуровану оболонку результату, щоб invocable carrier міг +детерміновано узгодити відповідь. + +## Deadlines та ідемпотентність {#deadlines-and-idempotency} + +Вхідна оболонка містить активний `lease.expires_at` та оголошені +deadlines activity: `schedule_to_start`, `start_to_close`, +`schedule_to_close` і `heartbeat`. Помічник перевіряє всі: + +- Перед dispatch обробника будь-який спливлий deadline одразу дає + помилку `timeout`. `details.deadline` указує відповідне поле. + Зареєстрований callable не викликається. +- Після повернення обробника помічник повторно перевіряє deadlines. + Обробник, що працював довше за свій lease, дає ту саму помилку + `timeout`, а повернене значення не потрапляє в оболонку. + +Оскільки carrier повторює транспортну доставку, а runtime повторно доставляє +leases, про які не отримав результату, ті самі `task.id` і +`task.idempotency_key` можуть надійти кілька разів. +Код обробника має бути ідемпотентним. Ключ ідемпотентності кожної оболонки +залишається сталим для retries тієї самої спроби. + +## Codecs payload та зовнішнє сховище {#payload-codecs-and-external-storage} + +Payload аргументів надходять у `payloads.arguments` із полем `codec`. +Помічник декодує їх через `CodecRegistry` Server. +Єдиний публічний codec v2 — `avro`. `json`, невідомі codecs, +некоректні blobs та blobs без позначення відхиляються. +JSON залишається документом HTTP carrier, а не codec durable payload. + +Коли вхідні дані workflow перевищують налаштований поріг +[зовнішнього сховища payload](../features/external-payload-storage.md), +Server зберігає bytes у налаштованому driver і надсилає оболонку +посилання замість inline blob. Передайте `ExternalPayloadStorageDriver` +до конструктора, щоб помічник міг отримати дані за посиланнями +перед викликом обробника: + +```php +use Workflow\V2\Contracts\ExternalPayloadStorageDriver; +use Workflow\V2\Support\InvocableActivityHandler; + +$handler = new InvocableActivityHandler( + handlers: $handlers, + carrier: 'billing-lambda', + resultCodec: 'avro', + externalStorage: $driver, +); +``` + +Driver має виконувати той самий контракт, який Server використовує +для запису payload, щоб посилання, hash і codec збігалися. +Посилання, яке зовнішній процес не може отримати, дає +`decode_failure` замість прихованого порожнього payload. + +## Production-інтеграція {#production-wiring} + +Laravel controller, який розміщує обробник за одним POST-маршрутом, +має такий вигляд: + +```php +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Workflow\V2\Support\InvocableActivityHandler; + +final class BillingActivityController +{ + public function __construct(private readonly InvocableActivityHandler $handler) {} + + public function __invoke(Request $request): JsonResponse + { + $result = $this->handler->handle($request->all()); + + return new JsonResponse( + data: $result, + headers: ['Content-Type' => 'application/vnd.durable-workflow.external-task-result+json'], + ); + } +} +``` + +У конфігурації зовнішнього виконавця вкажіть HTTPS URL цього controller +і оголосіть `auth_ref` для автентифікації маршруту. +Виняток loopback HTTP діє лише для локальної розробки. + +## Пов'язані поверхні {#related-surfaces} + +- [Invocable HTTP carrier](./invocable-carrier.md) — серверний контракт + carrier для доставки вхідних оболонок та узгодження оболонок результату. +- [Зовнішнє виконання](./external-execution.md) — незалежна від carrier + продуктова межа, включно зі schemas вхідних даних і результату, + які реалізує помічник. +- [Зовнішнє сховище payload](../features/external-payload-storage.md) — + перенесення завеликих аргументів чи результатів у налаштований driver + та подання їх перевірюваними посиланнями. +- [Worker-протокол](./worker-protocol.md) — ширший контракт worker plane, + що публікує invocable carrier поруч із poll-based формами обробників. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/namespace-auth-workers.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/namespace-auth-workers.md new file mode 100644 index 0000000000..7ba49252fa --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/namespace-auth-workers.md @@ -0,0 +1,256 @@ +--- +sidebar_position: 4 +title: Namespace, автентифікація та реєстрація worker +description: Довідник контрактів вибору namespace, автентифікації за ролями та реєстрації worker у Server. +tags: + - server + - namespaces + - authentication + - worker-protocol +keywords: + - Durable Workflow namespace auth + - токени Server для окремих ролей + - контракт реєстрації worker + - X-Namespace +--- + +# Namespace, автентифікація та реєстрація worker {#namespace-auth-and-worker-registration} + +Використовуйте цей довідник під час підготовки окремого Server, видачі +облікових даних автоматизації або реалізації worker runtime. +Для виконання роботи потрібно узгодити три окремі контракти Server: + +- запит визначає namespace через `X-Namespace`, `?namespace=` або + стандартний namespace Server +- облікові дані мають роль, потрібну для цієї групи маршрутів +- workers реєструють ті самі namespace, task queue, runtime, ключі типів + та місткість, які використовують запуск workflow і polling tasks + +## Повноваження запиту {#request-authority} + +Durable Workflow визначає повноваження запиту через namespace, роль +автентифікації та версію протоколу. Не виводьте їх з ідентифікаторів +workflow, tasks чи назв для відображення. + +| Група запитів | Потрібна роль облікових даних | Потрібний заголовок версії | Джерело namespace | +| --- | --- | --- | --- | +| Discovery `GET /api/cluster/info` | `worker`, `operator` або `admin` | немає | необов'язковий `X-Namespace` для контексту | +| Список та опис namespaces | `operator` або `admin` | `X-Durable-Workflow-Control-Plane-Version: 2` | ціль маршруту або контекст запиту | +| Створення, зміна та політика зберігання namespace | `admin` | `X-Durable-Workflow-Control-Plane-Version: 2` | ціль маршруту або тіло запиту | +| Огляд workflows, schedules, task queues, bridge adapters та workers | `operator` або `admin` | `X-Durable-Workflow-Control-Plane-Version: 2` | `X-Namespace`, `?namespace=`, потім стандартний namespace | +| Стан системи, метрики, проходи обслуговування та тести сховища | `admin` | `X-Durable-Workflow-Control-Plane-Version: 2` | `X-Namespace`, `?namespace=`, потім стандартний namespace | +| Реєстрація worker, polling, heartbeats і завершення tasks | `worker` | `X-Durable-Workflow-Protocol-Version: 1.0` | `X-Namespace`, `?namespace=`, потім стандартний namespace | + +На маршрутах із перевіркою ролі Server перевіряє роль перед існуванням +namespace. Токен із неправильною роллю отримує помилку автентифікації, +а не інформацію про існування namespace. Після успішної перевірки ролі +та версії маршрути в межах namespace відхиляють невідомі namespaces +з `reason: "namespace_not_found"`. + +## Ролі автентифікації {#auth-roles} + +Автентифікація токенами є стандартним production-варіантом: + +```bash +DW_AUTH_DRIVER=token +DW_WORKER_TOKEN=worker-secret +DW_OPERATOR_TOKEN=operator-secret +DW_ADMIN_TOKEN=admin-secret +``` + +Якщо розгортання використовує один спільний `DW_AUTH_TOKEN`, цей токен +фактично має дозволи на всі маршрути. Для production віддавайте перевагу +токенам з окремими ролями, щоб процес worker не міг змінювати namespaces +чи запускати системне обслуговування. + +Такий самий поділ ролей діє для автентифікації підписами: + +```bash +DW_AUTH_DRIVER=signature +DW_WORKER_SIGNATURE_KEY=worker-signature-secret +DW_OPERATOR_SIGNATURE_KEY=operator-signature-secret +DW_ADMIN_SIGNATURE_KEY=admin-signature-secret +``` + +`DW_AUTH_DRIVER=none` призначений лише для локальної розробки. +Він прибирає межу автентифікації з усіх маршрутів. Такий Server +ніколи не слід відкривати за межами довіреної локальної мережі. + +## Контракт namespace {#namespace-contract} + +Bootstrap створює стандартний namespace. Установіть `DW_DEFAULT_NAMESPACE`, +якщо запити без заголовка namespace мають використовувати інший +namespace замість `default`: + +```bash +DW_DEFAULT_NAMESPACE=default +``` + +Створюйте namespace кожного tenant або середовища перед підключенням +до нього клієнтів чи workers: + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/namespaces" \ + -H "Authorization: Bearer $DW_ADMIN_TOKEN" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "orders-prod", + "description": "production order workflows", + "retention_days": 90 + }' +``` + +Назви namespace переводяться в нижній регістр і можуть містити літери, +цифри, крапку, підкреслення та дефіс. Після нормалізації вони мають бути +унікальними. Наприклад, створення `Production`, а потім `production` +спричиняє конфлікт із `reason: "namespace_already_exists"`. + +Звичайний запуск workflow визначає namespace через `X-Namespace`: + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/workflows" \ + -H "Authorization: Bearer $DW_OPERATOR_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "workflow_type": "orders.fulfillment", + "workflow_id": "order-1001", + "task_queue": "orders", + "input": ["order-1001"] + }' +``` + +Читання площини керування, команди workflow, операції schedule, +операції search attributes, огляд task queues і workers використовують +той самий контекст namespace. Винятком є самі маршрути адміністрування +namespace: вони визначають namespace у маршруті або тілі запиту +й не вимагають, щоб він уже існував перед create чи describe. + +## Контракт реєстрації worker {#worker-registration-contract} + +Кожен процес worker має зареєструватися перед polling. Реєстрація прив'язана +до namespace, тому пара `(namespace, worker_id)` ідентифікує запис worker. +Зареєстрована `task_queue` має відповідати подальшим poll-запитам цього worker. + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/worker/register" \ + -H "Authorization: Bearer $DW_WORKER_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Protocol-Version: 1.0" \ + -H "Content-Type: application/json" \ + -d '{ + "worker_id": "py-orders-1", + "task_queue": "orders", + "runtime": "python", + "sdk_version": "", + "build_id": "orders-worker-2026-04-22", + "supported_workflow_types": ["orders.fulfillment"], + "workflow_definition_fingerprints": { + "orders.fulfillment": "sha256:definition-fingerprint" + }, + "supported_activity_types": ["payments.capture"], + "max_concurrent_workflow_tasks": 10, + "max_concurrent_activity_tasks": 50 + }' +``` + +| Поле | Обов'язкове | Контракт | +| --- | --- | --- | +| `worker_id` | ні | Стала ідентичність процесу. Server генерує її, якщо поле відсутнє, але довготривалим runtimes варто задавати його для логів і діагностики task queue. | +| `task_queue` | так | Черга, яку опитує worker. Poll-запит до іншої черги завершується з `reason: "task_queue_mismatch"`. | +| `runtime` | так | Одне зі значень `php`, `python`, `rust`, `typescript`, `go`, `java` або `external`. Прийняття ідентифікатора runtime не означає наявності офіційного SDK. | +| `sdk_version` | ні | Версія SDK runtime, видима в огляді workers і діагностиці. | +| `build_id` | ні | Ідентифікатор розгортання чи збірки для огляду build ID task queue та rollout-груп. Він має залишатися сталим для однієї групи workers із сумісним replay. | +| `supported_workflow_types` | ні | Ключі типів workflow, які worker може replay. Порожній список означає відсутність фільтра типів workflow. | +| `workflow_definition_fingerprints` | ні | Детерміновані fingerprints визначень кожного workflow. Повторну реєстрацію активного worker зі зміненим fingerprint відхиляють. | +| `supported_activity_types` | ні | Ключі типів activity, які worker може виконувати. Порожній список означає відсутність фільтра типів activity. | +| `max_concurrent_workflow_tasks` | ні | Оголошена кількість локальних слотів workflow tasks. Стандартне значення `100`, мінімальне `1`. | +| `max_concurrent_activity_tasks` | ні | Оголошена кількість локальних слотів activity tasks. Стандартне значення `100`, мінімальне `1`. | + +Повторна реєстрація активного worker з тим самим ідентифікатором дозволена, +якщо оголошені fingerprints визначень не змінилися. Якщо активний worker +змінює код уже оголошеного типу workflow, він має перезапуститися з новим +`worker_id`. Інакше реєстрація завершується +з `reason: "workflow_definition_changed"`. + +`build_id` є ідентифікатором групи, який оператор використовує +в rollout API task queue. Зберігайте один `build_id` для workers, +які можуть безпечно replay ту саму незавершену роботу, і змінюйте його, +коли rollout створює нову групу сумісності. +Контракт закріплення та rollback описано в +[сумісності workers і маршрутизації](/docs/polyglot/worker-compatibility-routing), +а процедуру drain/resume — у +[rollout за build ID worker](/docs/polyglot/worker-build-id-rollout). + +## Polling та огляд стану {#polling-and-visibility} + +Poll-запити worker мають використовувати ті самі namespace, +worker ID і task queue, що й реєстрація: + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/worker/workflow-tasks/poll" \ + -H "Authorization: Bearer $DW_WORKER_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Protocol-Version: 1.0" \ + -H "Content-Type: application/json" \ + -d '{ + "worker_id": "py-orders-1", + "task_queue": "orders", + "timeout_seconds": 30 + }' +``` + +Оператори можуть оглянути той самий стан реєстрації через +площину керування: + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/task-queues/orders" \ + -H "Authorization: Bearer $DW_OPERATOR_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" | jq '.pollers, .admission' +``` + +Коли workers не отримують tasks, спочатку перевірте огляд task queue. +Він розрізняє відсутність workers, невідповідність черги, непідтримувані +фільтри типів workflow/activity, зайняті слоти worker, серверні обмеження +активних leases, обмеження швидкості dispatch і backpressure query tasks. + +Самі poll-відповіді надають той самий машиночитаний результат через +`poll_status`, коли Server оголошує +`worker_protocol.server_capabilities.poll_status = true` +у `GET /api/cluster/info`. Маршрути polling workflow tasks, +activity tasks і query tasks зберігають це поле навіть за `task: null`, +тому runtimes workers можуть використовувати одну сталу поверхню: + +- `leased`: task успішно отримано в lease. +- `empty`: до повернення poll-відповіді не було готових tasks. +- `throttled`: обмеження приймання черги не дозволили видати новий task. +- `unavailable`: Server не міг безпечно координувати чергу й повернув + типізований результат unavailable замість удаваної порожньої черги. +- `draining`: група build ID worker перебуває в drain, тому poll + завершується з HTTP `409` та `reason: "worker_draining"` + до відновлення групи. + +## Помилки {#error-surface} + +Автоматизація має розгалужуватися за названими reasons, а не за текстом повідомлень. + +| Reason | Де | Значення | Дія оператора | +| --- | --- | --- | --- | +| `missing_control_plane_version` | маршрут площини керування | Запит не містить `X-Durable-Workflow-Control-Plane-Version: 2`. | Додайте заголовок версії або оновіть профіль клієнта. | +| `missing_protocol_version` | маршрут worker | Запит не містить `X-Durable-Workflow-Protocol-Version: 1.0`. | Виправте клієнт worker або узгодження версії SDK. | +| `namespace_not_found` | маршрут у межах namespace | Namespace із `X-Namespace`, query string чи стандартної конфігурації Server не існує. | Створіть namespace або виправте namespace клієнта. | +| `namespace_already_exists` | `POST /api/namespaces` | Нормалізована назва namespace вже існує. | Використайте її або виберіть іншу назву. | +| `task_queue_mismatch` | poll-маршрут worker | Worker, зареєстрований для однієї черги, намагався опитувати іншу. | Перезапустіть його з новим worker ID або опитуйте зареєстровану чергу. | +| `worker_draining` | poll-маршрут worker | Група build ID worker перебуває в drain. Вона може завершити поточну роботу, але не отримувати нові tasks. | Відновіть групу для rollback або зупиніть worker після завершення його поточних leases. | +| `workflow_definition_changed` | `POST /api/worker/register` | Активний worker ID намагався оголосити змінені fingerprints workflow. | Перезапустіть змінений процес із новим worker ID. | +| `validation_failed` | будь-який JSON-маршрут | Поле відсутнє, некоректне, завелике або поза дозволеними межами. | Прочитайте `errors` чи `validation_errors` та виправте payload. | + +## Дивіться також {#see-also} + +- [Довідник Server API](/docs/polyglot/server-api-reference) +- [Worker-протокол](/docs/polyglot/worker-protocol) +- [Приймання роботи task queue](/docs/polyglot/task-queue-admission) +- [Довідник команд CLI](/docs/polyglot/cli-reference) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/php.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/php.md new file mode 100644 index 0000000000..083192fe1e --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/php.md @@ -0,0 +1,238 @@ +--- +sidebar_position: 9 +title: PHP SDK +description: Підключення незалежних від фреймворку PHP-застосунків і віддалених workers до самостійно розгорнутого Server або керованого Cloud runtime. +tags: + - php + - sdk + - workers + - polyglot +keywords: + - sample-app-playground + - Durable Workflow PHP SDK + - віддалений PHP worker + - PHP клієнт окремого Server +--- + +import ProductPromotion from '@site/src/components/ProductPromotion'; + +# PHP SDK {#php-sdk} + +Використовуйте `durable-workflow/sdk`, коли PHP-застосунок або віддалений worker +підключається до окремого Durable Workflow Server чи runtime URL namespace +у Durable Workflow Cloud. Цей офіційний SDK не залежить від фреймворку: +він надає клієнт площини керування, автентифікацію, транспорт, публічний codec +payload, обробник replay та керований життєвий цикл віддаленого worker, +не потребуючи Laravel або пакета вбудованого рушія. + +## Спробуйте локальний playground Sample App {#try-the-local-sample-app-playground} + +Для найкоротшого шляху до написання workflow без Cloud відкрийте +[`main` у GitHub Codespaces](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main) +для поточного Sample App і виконайте: + + +```bash +scripts/playground php +``` + +Локальний playground генерує код workflow та activity, яким володіє користувач, +вибирає поточні стабільні артефакти й запускає опубліковані Server та Waterline. +Перед запуском workflow він очікує реєстрації worker, у якого ідентифікатор, +тип workflow, тип activity і task queue відповідають згенерованому контракту. +Успішний запуск має повернути очікуваний результат та історію. Після цього +термінал виводить точне локальне посилання на run у Waterline та шлях +до структурованих JSON-даних перевірки. + +Наведені нижче quickstart і довідник API самого пакета залишаються прямим шляхом +для тих, хто додає SDK до наявного проєкту без Sample App. + + +Запускайте PHP-клієнти та workers у керованому Cloud namespace з окремими +обліковими даними для кожної ролі. + + +Покрокові настанови щодо початку роботи, інтеграції з фреймворками, тестування, +розгортання й діагностики наведено на +[порталі PHP-розробника](https://php.durable-workflow.com/). Точні сигнатури +конструкторів, типи результатів та класи винятків дивіться в окремому +[згенерованому довіднику API](https://php.durable-workflow.com/api/). +[Машиночитаний контракт](https://php.durable-workflow.com/quickstart-contract.json) +порталу визначає пакет, форми runtime, облікові дані ролей, вихідні файли, +очікуваний результат і smoke-перевірку опублікованого артефакту як єдиний +перевірений шлях. + +Клієнти Cloud використовують runtime URL та namespace, повернуті під час +підготовки середовища, з окремими обліковими даними клієнта і worker. +Цю межу підключення описано в +[керованому Cloud runtime](/docs/polyglot/cloud-control-plane). +Приклади нижче показують той самий SDK із локальними значеннями +для самостійно розгорнутого Server. + +Для окремого вбудованого режиму Laravel використовуйте +`durable-workflow/workflow`. У ньому застосунок зберігає стан workflow +у власній базі даних і виконує роботу через черги Laravel. +Повне порівняння відповідальності наведено в +[режимах розгортання](/docs/polyglot/deployment-modes/). + +## Вимоги {#requirements} + +- PHP 8.1 або новіший +- Доступний [самостійно розгорнутий Server](/docs/polyglot/server/) або підготовлений + [Cloud namespace runtime](/docs/polyglot/cloud-control-plane) + +## Встановлення {#install} + +Установіть поточний опублікований PHP SDK. Точна вимога нижче генерується +з джерела опублікованих артефактів, яке оновлюється з реєстрів. +Composer записує вибраний пакет у `composer.lock`: + + +```bash +composer require %%artifact.publishedPhpSdkComposerPackage%% +``` + +SDK використовує офіційний Composer-пакет `apache/avro` для публічної оболонки +payload. Його production-залежності не включають Laravel, Illuminate, +`durable-workflow/workflow` чи `durable-workflow/server`. + +## Запуск та огляд workflow {#start-and-inspect-a-workflow} + +Quickstart SDK створює чистий Composer-проєкт, визначає workflow та activity +з атрибутами, запускає worker, починає workflow з унікальним ідентифікатором +і чекає результату. Ті самі файли `bootstrap.php`, `worker.php` і +`client.php` установлюються з пакета та виконуються захищеною smoke-перевіркою +опублікованого артефакту. Тому ця сторінка не підтримує ще одну копію коду. + +Виберіть лише значення підключення до runtime: + +| Runtime | Значення для `Client` | Формування шляху запиту SDK | +| --- | --- | --- | +| Самостійно розгорнутий Server | Адреса origin без шляху, наприклад `http://localhost:8080` | Додає один сегмент `/api`. Користувач не додає `/api` самостійно. | +| Durable Workflow Cloud | Повний підготовлений URI, наприклад `https://cloud.example/api/runtime/v1/namespaces/` | Зберігає шлях namespace runtime і додає після нього endpoint `/api`. | + +Точні команди й відкритий вихідний код наведено в +[перевіреному PHP-сценарії](https://php.durable-workflow.com/). +Клієнтські операції читають `DURABLE_WORKFLOW_CLIENT_TOKEN`, +а polling worker читає `DURABLE_WORKFLOW_WORKER_TOKEN`. +Настанова зберігає ці облікові дані в окремих процесах, не виводячи +й не додаючи жодне значення до репозиторію. + +`Worker::register()` знаходить обробники `#[Workflow]` та `#[Activity]` +в одному вихідному файлі. Настанова також описує пряму альтернативу +`registerWorkflow()`/`registerActivity()` для коду на основі callable. +Bootstrap звільняє користувача від вибору шляху autoloader, коли ці файли +запускаються в окремому проєкті, checkout SDK, установленому пакеті +або playground/контейнері, де вони лежать поруч із Composer-каталогом `vendor/`. + +`WorkflowHandle` стежить за поточним run після переходу continue-as-new. +Коли операцію потрібно обмежити конкретним run, використовуйте його +методи для вибраного run. + +## Життєвий цикл, updates, schedules та огляд стану {#lifecycle-updates-schedules-and-visibility} + +Поточний публічний клієнт підтримує більше, ніж отримання результату вибраного run: + +- `WorkflowHandle` надає `describe`, `result`, `signal`, `query`, + `cancel` та `terminate`, з варіантами для безпечної роботи з конкретним run. +- `Client` надає `listWorkflows` із серверними фільтрами та пагінацією, + `workflowHistory`, `updateWorkflow`, `cancelWorkflow` та `terminateWorkflow`. +- `requestCancellation()` запитує обмежене в часі cleanup у workers, + які підтримують кооперативне скасування. Наявні `cancel()` та + `cancelWorkflow()` закривають run негайно. Контракт написання та відновлення + описано в [кооперативному скасуванні](/docs/polyglot/cancellation). +- Методи schedules охоплюють створення, опис, список, зміну, призупинення, + відновлення, ручний запуск, backfill і видалення. +- Операційний огляд включає `listNamespaces`, `listWorkers` та + `listTaskQueues` з відповідними методами опису. + +Віддалені workers реєструють обробники workflow, activity, query та update +через `registerWorkflow`, `registerActivity`, `registerQuery` +та `registerUpdate`. Повні параметри й типи результатів наведено +у [згенерованому довіднику PHP SDK API](https://php.durable-workflow.com/api/). + +## Запуск віддаленого PHP worker {#run-a-remote-php-worker} + +Обробники workflow є звичайними callable, які виконуються послідовно +в керованому Fiber. Викликайте такі операції, як +`WorkflowContext::activity()`, безпосередньо. SDK призупиняє Fiber на durable-рішеннях +і під час replay повертає записані результати, не повторюючи зовнішню activity. +Не оголошуйте workflow як Generator і не повертайте команди `WorkflowContext` +через yield: результати Generator відхиляються. Керований worker реєструє +назви типів workflow та activity, опитує публічний worker-протокол, +надсилає heartbeats, завершує tasks успішно або з помилкою та підтримує +коректне завершення процесу, коли доступний `pcntl`. + +### Вибір першого завершення {#first-completion-selection} + +Використовуйте `WorkflowContext::select()`, щоб почати незалежні відкладені +activities, дочірні workflows, timers, очікування умов або вкладені звичайні +бар'єри та продовжити з першим переможцем, durable-результат якого вже зафіксовано. +Повернений `SelectionResult` містить сталі ключі й ідентифікатори учасників +та по одному handle для кожного: + +```php +$selected = $ctx->select([ + 'resolver' => fn () => $ctx->activity('resolve-request', [$requestId]), + 'input' => fn () => $ctx->waitCondition( + fn (): bool => $this->resolution !== null, + key: 'resolution-ready', + ), + 'deadline' => fn () => $ctx->sleep(2), +]); + +if ($selected->key === 'deadline') { + $selected->handles['resolver']->cancel(); +} +``` + +Вибір не скасовує інших учасників. Викличте `await()` на handle, +щоб отримати його майбутній результат, або `cancel()`, щоб записати явне +скасування. Новий worker або replay завершеної історії використовує записаного +переможця навіть за іншого порядку доставки повторних чи пізніших вхідних +і термінальних подій. +`cancel()` повертає void і не повідомляє термінальний результат. +Лише історія `SelectionOperationCancelled` доводить, що скасування перемогло. +Replay проходить запит cancel без такого запису. Якщо операція завершилася +раніше, `await()` усе одно повертає її результат. + +## Service mode фреймворків та вбудований Laravel {#framework-service-mode-and-embedded-laravel} + +Той самий пакет містить офіційні інтеграції +[Laravel service mode](https://github.com/durable-workflow/sdk-php#laravel-service-mode) +та [Symfony service mode](https://github.com/durable-workflow/sdk-php#symfony-service-mode). +Вони зберігають dependency injection фреймворку, конфігурацію, логування, +консольні workers і тестові fakes, підключаючись до Cloud або Server. + +Ці інтеграції відрізняються від +[вбудованих Laravel workflows](/docs/installation/), де +`durable-workflow/workflow` передає самому Laravel-застосунку зберігання +durable-стану та виконання через черги Laravel. +[Інтеграція Laravel та перехід між runtime](/docs/laravel-adoption/) +показує той самий типовий Laravel-сценарій у v1, вбудованому v2 та цьому +service mode, включно з drain і rollback. +Ширше порівняння меж runtime наведено в +[режимах розгортання](/docs/polyglot/deployment-modes/). + +## Межі протоколу та випусків {#protocol-and-release-boundary} + +SDK оголошує підтримуваний діапазон Server, версію worker-протоколу, +версію площини керування й codecs payload у Composer metadata. +Server також публікує прийняті протоколи та codecs через +`GET /api/cluster/info`. Під час розгортання перевіряйте discovery runtime, +а не виводьте сумісність із patch-версії Server. + +PHP SDK має незалежну від Laravel-пакета версію. Зберігайте точну опубліковану +версію у виконуваних прикладах і читайте примітки до випуску під час оновлення +SDK. Сумісність визначає оголошений контракт SDK, а не збіг номерів версій пакетів. + +## Пов'язані довідники {#related-references} + +- [Окремий Server](/docs/polyglot/server/) +- [Режими розгортання](/docs/polyglot/deployment-modes/) +- [Worker-протокол](/docs/polyglot/worker-protocol/) +- [Каталог можливостей](/docs/capabilities/) +- [Портал PHP-розробника](https://php.durable-workflow.com/) +- [Довідник PHP API](https://php.durable-workflow.com/api/) +- [Виконуваний PHP quickstart-контракт](https://php.durable-workflow.com/quickstart-contract.json) +- [Вихідний код PHP SDK](https://github.com/durable-workflow/sdk-php) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-config-reference.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-config-reference.md new file mode 100644 index 0000000000..056b173be3 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-config-reference.md @@ -0,0 +1,318 @@ +--- +sidebar_position: 3 +title: Довідник конфігурації Server +description: Довідник змінних середовища Durable Workflow Server, стандартних значень, застарілих aliases і налаштувань інфраструктури runtime. +tags: + - server + - config + - reference + - operations +keywords: + - конфігурація Durable Workflow Server + - змінні середовища DW + - перевірка середовища Server + - середовище workflow Server +--- + +# Довідник конфігурації Server {#server-config-reference} + +Ця сторінка описує операторський контракт змінних середовища `DW_*` +для образу Durable Workflow Server. Використовуйте її для шаблонів +розгортання, перевірки production-конфігурації або переходу від старих +назв `WORKFLOW_*` / `ACTIVITY_*` до контракту Server v2. + +Server також використовує звичайні налаштування Laravel runtime, +зокрема `DB_*`, `REDIS_*`, `QUEUE_CONNECTION` та `CACHE_STORE`. +Ці інфраструктурні змінні перелічено окремо як налаштування розгортання, +що не є керуванням Durable Workflow API. + +## Застосування контракту {#how-the-contract-is-enforced} + +Канонічний контракт зберігається в `config/dw-contract.php` +репозиторію Server. Під час старту контейнера `php artisan env:audit` +попереджає про невідомі змінні `DW_*` та застарілі назви. +Установіть `DW_ENV_AUDIT_STRICT=1`, щоб entrypoint завершувався +з помилкою замість старту з попередженнями. + +```bash +DW_ENV_AUDIT_STRICT=1 +DW_AUTH_DRIVER=token +DW_ADMIN_TOKEN="${DW_ADMIN_TOKEN}" +DW_OPERATOR_TOKEN="${DW_OPERATOR_TOKEN}" +DW_WORKER_TOKEN="${DW_WORKER_TOKEN}" +``` + +Застарілі назви працюють лише як fallback. У кожному новому розгортанні +віддавайте перевагу `DW_*`. Попередження про стару назву означає +незавершену міграцію, навіть якщо Server ще використовує її значення. + +## Ідентичність та режим Server {#server-identity-and-mode} + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_MODE` | `service` | Режим Server: `service` передає polling зовнішнім workers, `embedded` виконує dispatch локально через чергу Laravel. | `WORKFLOW_SERVER_MODE` | +| `DW_SERVER_ID` | `gethostname()` | Унікальний ідентифікатор екземпляра Server для володіння leases та реєстрації workers. | `WORKFLOW_SERVER_ID` | +| `DW_SERVER_TOPOLOGY_SHAPE` | `standalone_server` | Форма топології для discovery кластера, наприклад `embedded`, `standalone_server` чи `split_control_execution`. | `WORKFLOW_SERVER_TOPOLOGY_SHAPE` | +| `DW_SERVER_PROCESS_CLASS` | `server_http_node` | Клас процесу цього вузла у вибраній формі топології, наприклад `server_http_node`, `worker_node` чи `scheduler_node`. | `WORKFLOW_SERVER_PROCESS_CLASS` | +| `DW_SERVER_KEY` | генерується під час старту контейнера | Необов'язковий внутрішній runtime key Server. Docker-образи автоматично генерують його, якщо він відсутній. | - | +| `DW_DEFAULT_NAMESPACE` | `default` | Namespace для запитів без заголовка namespace. | `WORKFLOW_SERVER_DEFAULT_NAMESPACE` | +| `DW_TASK_DISPATCH_MODE` | не задано | Перевизначає `workflows.v2.task_dispatch_mode`. У service mode Server стандартно використовує `poll`, якщо не вказано інше. | `WORKFLOW_V2_TASK_DISPATCH_MODE` | +| `DW_WORKFLOW_MEMO_MIGRATION_RECOVERY` | не задано | Одноразове підтвердження відновлення MySQL для незаписаного переписування memo: `raw-json` або `envelope-prefix:`. | `WORKFLOW_SERVER_WORKFLOW_MEMO_MIGRATION_RECOVERY` | +| `DW_EXTERNAL_EXECUTOR_CONFIG_PATH` | не задано | Шлях до JSON-файлу `durable-workflow.external-executor.config` зі зіставленнями обробників зовнішнього виконавця. | `WORKFLOW_SERVER_EXTERNAL_EXECUTOR_CONFIG_PATH` | +| `DW_EXTERNAL_EXECUTOR_CONFIG_OVERLAY` | не задано | Назва overlay конфігурації зовнішнього виконавця для застосування перед перевіркою та discovery Server. | `WORKFLOW_SERVER_EXTERNAL_EXECUTOR_CONFIG_OVERLAY` | + +## Автентифікація {#authentication} + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_AUTH_PROVIDER` | не задано | Клас, який може створити Laravel і який реалізує `App\Contracts\AuthProvider`. | `WORKFLOW_SERVER_AUTH_PROVIDER` | +| `DW_AUTH_DRIVER` | `token` | Driver автентифікації: `none`, `token` або `signature`. | `WORKFLOW_SERVER_AUTH_DRIVER` | +| `DW_AUTH_TOKEN` | не задано | Один спільний bearer token, якщо токени окремих ролей не налаштовані. | `WORKFLOW_SERVER_AUTH_TOKEN` | +| `DW_SIGNATURE_KEY` | не задано | Спільний ключ HMAC-підпису, якщо ключі окремих ролей не налаштовані. | `WORKFLOW_SERVER_SIGNATURE_KEY` | +| `DW_WORKER_TOKEN` | не задано | Bearer token для реєстрації worker, polling, heartbeats і завершення tasks. | `WORKFLOW_SERVER_WORKER_TOKEN` | +| `DW_OPERATOR_TOKEN` | не задано | Bearer token для читання та операторських дій площини керування. | `WORKFLOW_SERVER_OPERATOR_TOKEN` | +| `DW_ADMIN_TOKEN` | не задано | Bearer token для змін namespaces, retention та інших адміністративних дій. | `WORKFLOW_SERVER_ADMIN_TOKEN` | +| `DW_PRINCIPAL_TOKENS` | не задано | JSON map токенів іменованих principals. Кожен запис надає `token`, `subject`, `roles`, необов'язкові `tenant`, `label` і несекретні `claims`. | `WORKFLOW_SERVER_PRINCIPAL_TOKENS` | +| `DW_WORKER_SIGNATURE_KEY` | не задано | Ключ HMAC для запитів ролі worker за автентифікації підписами. | `WORKFLOW_SERVER_WORKER_SIGNATURE_KEY` | +| `DW_OPERATOR_SIGNATURE_KEY` | не задано | Ключ HMAC для запитів ролі operator за автентифікації підписами. | `WORKFLOW_SERVER_OPERATOR_SIGNATURE_KEY` | +| `DW_ADMIN_SIGNATURE_KEY` | не задано | Ключ HMAC для запитів ролі admin за автентифікації підписами. | `WORKFLOW_SERVER_ADMIN_SIGNATURE_KEY` | +| `DW_AUTH_BACKWARD_COMPATIBLE` | `true` | Використання спільних `DW_AUTH_TOKEN` / `DW_SIGNATURE_KEY` як fallback, якщо немає облікових даних окремої ролі. | `WORKFLOW_SERVER_AUTH_BACKWARD_COMPATIBLE` | + +Для production використовуйте облікові дані з окремими ролями. +`DW_AUTH_DRIVER=none` призначений лише для локальних smoke-перевірок: +з ним кожен endpoint доступний без bearer token чи підпису. + +Використовуйте `DW_PRINCIPAL_TOKENS`, коли журнал аудиту потребує сталих +ідентифікаторів виконавців замість назв ролей. Server визначає записаного +principal за відповідним записом токена. Клієнти не можуть змінити +його через payload чи заголовки запиту. + +## Атрибуція команд {#command-attribution} + +Ці налаштування дають довіреному gateway змогу зберегти metadata +ініціатора в durable-історії команд. Залишайте +`DW_TRUST_FORWARDED_ATTRIBUTION_HEADERS=false`, якщо Server не стоїть +за gateway, що прибирає недовірені заголовки від клієнтів. + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_TRUST_FORWARDED_ATTRIBUTION_HEADERS` | `false` | Запис forwarded-заголовків ініціатора та автентифікації до історії команд workflow. | `WORKFLOW_SERVER_TRUST_FORWARDED_ATTRIBUTION_HEADERS` | +| `DW_CALLER_TYPE_HEADER` | `X-Workflow-Caller-Type` | Заголовок з переданим типом ініціатора. | `WORKFLOW_SERVER_CALLER_TYPE_HEADER` | +| `DW_CALLER_LABEL_HEADER` | `X-Workflow-Caller-Label` | Заголовок з переданою міткою ініціатора. | `WORKFLOW_SERVER_CALLER_LABEL_HEADER` | +| `DW_AUTH_STATUS_HEADER` | `X-Workflow-Auth-Status` | Заголовок з переданим станом автентифікації. | `WORKFLOW_SERVER_AUTH_STATUS_HEADER` | +| `DW_AUTH_METHOD_HEADER` | `X-Workflow-Auth-Method` | Заголовок з переданим методом автентифікації. | `WORKFLOW_SERVER_AUTH_METHOD_HEADER` | + +## Polling worker та приймання роботи {#worker-polling-and-admission} + +Ці значення керують часом long poll, координацією wake, +серверними обмеженнями приймання роботи та обмеженими budgets dispatch tasks. + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_WORKER_POLL_TIMEOUT` | `30` | Кількість секунд, протягом яких Server тримає poll відкритим в очікуванні task. | `WORKFLOW_SERVER_WORKER_POLL_TIMEOUT` | +| `DW_WORKER_POLL_INTERVAL_MS` | `1000` | Мілісекунди між внутрішніми скануваннями під час відкритого poll. | `WORKFLOW_SERVER_WORKER_POLL_INTERVAL_MS` | +| `DW_WORKER_POLL_SIGNAL_CHECK_INTERVAL_MS` | `100` | Мілісекунди між перевірками wake signals під час відкритого poll. | `WORKFLOW_SERVER_WORKER_POLL_SIGNAL_CHECK_INTERVAL_MS` | +| `DW_POLLING_CACHE_PATH` | `storage/framework/cache/server-polling/` | Каталог стану координації polling worker за файлового polling cache. | `WORKFLOW_SERVER_POLLING_CACHE_PATH` | +| `DW_WAKE_SIGNAL_TTL_SECONDS` | `max(DW_WORKER_POLL_TIMEOUT + 5, 60)` | TTL wake signals кожної черги, які достроково завершують очікування poll. | `WORKFLOW_SERVER_WAKE_SIGNAL_TTL_SECONDS` | +| `DW_WORKER_LONG_POLL_MAX_CONCURRENT` | не задано; визначається для `PHP_CLI_SERVER_WORKERS` | Необов'язкове обмеження одночасних відкритих long-poll очікувань workflow/activity workers на цьому вузлі Server. Polls query tasks мають окремий budget. | `WORKFLOW_SERVER_WORKER_LONG_POLL_MAX_CONCURRENT` | +| `DW_WORKER_LONG_POLL_RESERVED_HTTP_WORKERS` | `2` | PHP CLI server workers, зарезервовані для health і площини керування під час визначення обмеження long-poll очікування workflow/activity. | `WORKFLOW_SERVER_WORKER_LONG_POLL_RESERVED_HTTP_WORKERS` | +| `DW_MAX_TASKS_PER_POLL` | `1` | Максимальна кількість tasks у відповіді на poll worker. | `WORKFLOW_SERVER_MAX_TASKS_PER_POLL` | +| `DW_SQLITE_CLAIM_LOCK_TTL_SECONDS` | `10` | Секунди утримання кешованої claim-блокади polling worker у SQLite quickstart backend до спливу lock. | `WORKFLOW_SERVER_SQLITE_CLAIM_LOCK_TTL_SECONDS` | +| `DW_SQLITE_CLAIM_LOCK_WAIT_SECONDS` | `5` | Секунди очікування SQLite claims polling worker на кешовану claim-блокаду до повернення тиску блокувань backend. | `WORKFLOW_SERVER_SQLITE_CLAIM_LOCK_WAIT_SECONDS` | +| `DW_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_QUEUE` | не задано | Обмеження активних leases workflow tasks на namespace/task queue. | `WORKFLOW_SERVER_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_QUEUE` | +| `DW_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE` | не задано | Обмеження активних leases workflow tasks у всіх чергах namespace. | `WORKFLOW_SERVER_WORKFLOW_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE` | +| `DW_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE` | не задано | Обмеження dispatch workflow tasks за хвилину на namespace/task queue. | `WORKFLOW_SERVER_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE` | +| `DW_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE` | не задано | Обмеження dispatch workflow tasks за хвилину в namespace. | `WORKFLOW_SERVER_WORKFLOW_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE` | +| `DW_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_QUEUE` | не задано | Обмеження активних leases activity tasks на namespace/task queue. | `WORKFLOW_SERVER_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_QUEUE` | +| `DW_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE` | не задано | Обмеження активних leases activity tasks у всіх чергах namespace. | `WORKFLOW_SERVER_ACTIVITY_TASK_MAX_ACTIVE_LEASES_PER_NAMESPACE` | +| `DW_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE` | не задано | Обмеження dispatch activity tasks за хвилину на namespace/task queue. | `WORKFLOW_SERVER_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE` | +| `DW_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE` | не задано | Обмеження dispatch activity tasks за хвилину в namespace. | `WORKFLOW_SERVER_ACTIVITY_TASK_MAX_DISPATCHES_PER_MINUTE_PER_NAMESPACE` | +| `DW_TASK_QUEUE_ADMISSION_OVERRIDES` | `{}` | JSON-перевизначення за ключами `namespace:task_queue`, `namespace:*`, `task_queue` чи `*` для активних leases, швидкості dispatch, обмежень namespace і budget-груп downstream. | `WORKFLOW_SERVER_TASK_QUEUE_ADMISSION_OVERRIDES` | +| `DW_DUE_TIMER_RECOVERY_SCAN_LIMIT` | `5` | Максимальна кількість належних до виконання timer tasks service mode, відновлених за прохід polling worker. | `WORKFLOW_SERVER_DUE_TIMER_RECOVERY_SCAN_LIMIT` | +| `DW_EXPIRED_WORKFLOW_TASK_RECOVERY_SCAN_LIMIT` | `5` | Максимальна кількість спливлих workflow tasks, відновлених за прохід. | `WORKFLOW_SERVER_EXPIRED_WORKFLOW_TASK_RECOVERY_SCAN_LIMIT` | +| `DW_EXPIRED_WORKFLOW_TASK_RECOVERY_TTL_SECONDS` | `5` | Мінімальні секунди між проходами відновлення спливлих tasks на чергу. | `WORKFLOW_SERVER_EXPIRED_WORKFLOW_TASK_RECOVERY_TTL_SECONDS` | + +Sticky execution не має окремих змінних середовища образу Server. +Життєвий цикл sticky cache, поля worker-протоколу, режими replay +та діагностику описано в +[sticky execution](/docs/features/sticky-execution). + +Приклад перевизначення обмежень приймання роботи: + +```json +{ + "production:billing": { + "workflow_tasks": { + "max_active_leases": 50, + "max_dispatches_per_minute": 600 + }, + "activity_tasks": { + "max_active_leases": 200 + } + }, + "production:*": { + "workflow_tasks": { + "max_active_leases_per_namespace": 500 + } + } +} +``` + +## Worker-протокол та транспорт query {#worker-protocol-and-query-transport} + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_WORKER_PROTOCOL_VERSION` | `WorkerProtocolVersion::VERSION` | Версія worker-протоколу, оголошена у відповідях worker plane. | `WORKFLOW_SERVER_WORKER_PROTOCOL_VERSION` | +| `DW_HISTORY_PAGE_SIZE_DEFAULT` | `WorkerProtocolVersion::DEFAULT_HISTORY_PAGE_SIZE` | Стандартний розмір сторінки читання історії worker. | `WORKFLOW_SERVER_HISTORY_PAGE_SIZE_DEFAULT` | +| `DW_HISTORY_PAGE_SIZE_MAX` | `WorkerProtocolVersion::MAX_HISTORY_PAGE_SIZE` | Максимальний розмір сторінки читання історії worker. | `WORKFLOW_SERVER_HISTORY_PAGE_SIZE_MAX` | +| `DW_UPDATE_VALIDATION_TIMEOUT` | `10` | Секунди очікування площиною керування синхронного результату validator update до acceptance. | `WORKFLOW_SERVER_UPDATE_VALIDATION_TIMEOUT` | +| `DW_UPDATE_VALIDATION_LEASE_TIMEOUT` | `5` | Секунди володіння lease task перевірки update, після яких інший worker з підтримкою validator може повторити спробу. | `WORKFLOW_SERVER_UPDATE_VALIDATION_LEASE_TIMEOUT` | +| `DW_QUERY_TASK_TIMEOUT` | `DW_WORKER_POLL_TIMEOUT` | Секунди очікування площиною керування відповіді worker на query task. | `WORKFLOW_SERVER_QUERY_TASK_TIMEOUT` | +| `DW_QUERY_TASK_LEASE_TIMEOUT` | `DW_WORKFLOW_TASK_TIMEOUT` | Timeout lease тимчасових query tasks, переданих workers. | `WORKFLOW_SERVER_QUERY_TASK_LEASE_TIMEOUT` | +| `DW_QUERY_TASK_TTL_SECONDS` | `180` | Час зберігання Server рядків результатів query tasks перед видаленням. | `WORKFLOW_SERVER_QUERY_TASK_TTL_SECONDS` | +| `DW_QUERY_TASK_MAX_PENDING_PER_QUEUE` | `1024` | Максимальна кількість незавершених query tasks у cache на namespace/task queue перед відхиленням нових queries. | `WORKFLOW_SERVER_QUERY_TASK_MAX_PENDING_PER_QUEUE` | +| `DW_QUERY_TASK_POLL_TIMEOUT` | `5` | Максимальні секунди очікування кожного idle poll query task worker перед повторною перевіркою тиску workflow tasks та доступності площини керування. | `WORKFLOW_SERVER_QUERY_TASK_POLL_TIMEOUT` | +| `DW_QUERY_TASK_POLL_MAX_CONCURRENT` | не задано; визначається для `PHP_CLI_SERVER_WORKERS` | Необов'язкове обмеження одночасних idle long-poll очікувань query task workers на цьому вузлі Server. Незавершені query tasks можна отримати негайно перед idle-очікуванням poll. | `WORKFLOW_SERVER_QUERY_TASK_POLL_MAX_CONCURRENT` | +| `DW_WORKFLOW_TASK_TIMEOUT` | `60` | Стандартний timeout lease workflow task у секундах. | `WORKFLOW_TASK_TIMEOUT` | +| `DW_ACTIVITY_TASK_TIMEOUT` | `300` | Стандартний timeout lease activity task у секундах. | `ACTIVITY_TASK_TIMEOUT` | +| `DW_WORKER_STALE_AFTER_SECONDS` | `max(DW_WORKER_POLL_TIMEOUT * 2, 60)` | Секунди після heartbeat worker, коли реєстрація worker стає застарілою. | `WORKFLOW_SERVER_WORKER_STALE_AFTER_SECONDS` | +| `DW_WORKER_HEARTBEAT_INTERVAL_SECONDS` | `60` | Інтервал у секундах, оголошений SDK у підтвердженнях реєстрації та heartbeat worker. | `WORKFLOW_SERVER_WORKER_HEARTBEAT_INTERVAL_SECONDS` | + +## Обмеження, retention та метрики {#limits-retention-and-metrics} + +Ці налаштування обмежують запити та зростання даних. Якщо застосунок +регулярно наближається до цих значень, віддавайте перевагу меншим payload, +зовнішньому сховищу payload або continue-as-new замість звичного +підвищення обмежень. + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_METRICS_WORKFLOW_TASK_FAILURE_TYPE_LIMIT` | `20` | Максимальна кількість series `workflow_type` у `dw_workflow_task_consecutive_failures`. Решта типів підсумовується. | `WORKFLOW_SERVER_METRICS_WORKFLOW_TASK_FAILURE_TYPE_LIMIT` | +| `DW_METRICS_PROMETHEUS_WORKFLOW_SERIES_LIMIT` | `100` | Максимальна кількість workflow series у `/api/system/prometheus-metrics` перед підсумовуванням решти. | `WORKFLOW_SERVER_METRICS_PROMETHEUS_WORKFLOW_SERIES_LIMIT` | +| `DW_METRICS_PROMETHEUS_ACTIVITY_SERIES_LIMIT` | `100` | Максимальна кількість activity series у `/api/system/prometheus-metrics` перед підсумовуванням решти. | `WORKFLOW_SERVER_METRICS_PROMETHEUS_ACTIVITY_SERIES_LIMIT` | +| `DW_METRICS_PROMETHEUS_TASK_QUEUE_SERIES_LIMIT` | `100` | Максимальна кількість runtime series task queue у `/api/system/prometheus-metrics` перед підсумовуванням решти. | `WORKFLOW_SERVER_METRICS_PROMETHEUS_TASK_QUEUE_SERIES_LIMIT` | +| `DW_MAX_HISTORY_EVENTS` | `50000` | Максимальна кількість подій історії на run workflow перед примусовим continue-as-new. | `WORKFLOW_MAX_HISTORY_EVENTS` | +| `DW_HISTORY_RETENTION_DAYS` | `30` | Стандартна кількість днів зберігання історії закритих runs, якщо namespace не перевизначає її. | `WORKFLOW_HISTORY_RETENTION_DAYS` | +| `DW_MAX_PAYLOAD_BYTES` | `2097152` | Максимальна кількість серіалізованих bytes одного payload. | `WORKFLOW_MAX_PAYLOAD_BYTES` | +| `DW_EXTERNAL_PAYLOAD_MAX_BYTES` | `67108864` | Максимальний закодований розмір у bytes (64 MiB), який приймає автентифікований транспорт завантаження та отримання зовнішнього payload. | `WORKFLOW_SERVER_EXTERNAL_PAYLOAD_MAX_BYTES` | +| `DW_EXTERNAL_PAYLOAD_REQUEST_TIMEOUT` | `30` | Оголошений Server budget timeout запитів завантаження та отримання для клієнтів у секундах. | `WORKFLOW_SERVER_EXTERNAL_PAYLOAD_REQUEST_TIMEOUT` | +| `DW_EXTERNAL_PAYLOAD_UPLOAD_EXPIRY` | `3600` | Секунди, протягом яких завантажений зовнішній payload може залишатися незатребуваним до спливу його непрозорого посилання. | `WORKFLOW_SERVER_EXTERNAL_PAYLOAD_UPLOAD_EXPIRY` | +| `DW_MAX_MEMO_BYTES` | `262144` | Максимальна кількість серіалізованих bytes memo workflow. | `WORKFLOW_MAX_MEMO_BYTES` | +| `DW_MAX_SEARCH_ATTRIBUTES` | `100` | Максимальна кількість search attributes одного workflow. | `WORKFLOW_MAX_SEARCH_ATTRIBUTES` | +| `DW_MAX_SEARCH_ATTRIBUTE_KEY_LENGTH` | `128` | Максимальна довжина ключа search attribute у bytes. | `WORKFLOW_MAX_SEARCH_ATTRIBUTE_KEY_LENGTH` | +| `DW_MAX_SEARCH_ATTRIBUTE_VALUE_BYTES` | `2048` | Максимальний розмір одного рядкового значення search attribute у bytes. | `WORKFLOW_MAX_SEARCH_ATTRIBUTE_VALUE_BYTES` | +| `DW_MAX_OPERATION_NAME_LENGTH` | `256` | Максимальна довжина назви signal, update чи query у bytes. | `WORKFLOW_MAX_OPERATION_NAME_LENGTH` | +| `DW_MAX_PENDING_ACTIVITIES` | `2000` | Максимальна кількість незавершених activities на run workflow перед відхиленням пакета команд. | `WORKFLOW_MAX_PENDING_ACTIVITIES` | +| `DW_MAX_PENDING_CHILDREN` | `2000` | Максимальна кількість незавершених дочірніх workflows на run перед відхиленням пакета команд. | `WORKFLOW_MAX_PENDING_CHILDREN` | +| `DW_MAX_NEXUS_OPERATIONS_PER_CALLER` | `200` | Максимальна кількість Nexus operations на ініціатора в історії операцій перед обов'язковою пагінацією клієнта. | `WORKFLOW_MAX_NEXUS_OPERATIONS_PER_CALLER` | +| `DW_COMPRESSION_ENABLED` | `true` | Увімкнення стиснення JSON-відповіді після перевищення мінімального порогу розміру. | `WORKFLOW_SERVER_COMPRESSION_ENABLED` | + +## Docker bootstrap та походження пакета {#docker-bootstrap-and-provenance} + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_EXPOSE_PACKAGE_PROVENANCE` | `false` | Додавання `package_provenance` до `/api/cluster/info` для запитів admin. | `WORKFLOW_SERVER_EXPOSE_PACKAGE_PROVENANCE` | +| `DW_PACKAGE_PROVENANCE_PATH` | `/.package-provenance` | Абсолютний шлях до файлу походження Docker build. | `WORKFLOW_SERVER_PACKAGE_PROVENANCE_PATH` | +| `DW_SERVICE_BOUNDARY_CROSS_NAMESPACE_DEFAULT` | `allow` | Стандартна дія межі service call для викликів між namespaces, якщо немає точнішого правила. | `WORKFLOW_SERVER_SERVICE_BOUNDARY_CROSS_NAMESPACE_DEFAULT` | +| `DW_SERVICE_BOUNDARY_RATE_LIMIT_PER_MINUTE` | не задано | Необов'язкове обмеження швидкості service calls за хвилину. | `WORKFLOW_SERVER_SERVICE_BOUNDARY_RATE_LIMIT_PER_MINUTE` | +| `DW_SERVICE_BOUNDARY_MAX_IN_FLIGHT` | не задано | Необов'язкове обмеження одночасних service calls. | `WORKFLOW_SERVER_SERVICE_BOUNDARY_MAX_IN_FLIGHT` | + +## Docker bootstrap {#docker-bootstrap} + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_ENV_AUDIT_STRICT` | `0` | Помилка старту контейнера, якщо `env:audit` виявляє невідомі або застарілі змінні `DW_*`. | - | +| `DW_BOOTSTRAP_RETRIES` | `30` | Кількість спроб bootstrap перед відмовою entrypoint від migrations і створення стандартного namespace. | `WORKFLOW_SERVER_BOOTSTRAP_RETRIES` | +| `DW_BOOTSTRAP_DELAY_SECONDS` | `2` | Секунди між спробами bootstrap. | `WORKFLOW_SERVER_BOOTSTRAP_DELAY_SECONDS` | + +## Налаштування пакета workflow {#workflow-package-controls} + +Образ Server містить `durable-workflow/workflow`, тому також надає +налаштування пакета `DW_V2_*`. Вони мають відповідати розгортанням +workers, які виконують ті самі workflows. + +| Змінна | Стандартне значення | Призначення | Застарілий alias | +| --- | --- | --- | --- | +| `DW_V2_NAMESPACE` | не задано | Обмеження екземплярів workflow певним namespace. | `WORKFLOW_V2_NAMESPACE` | +| `DW_V2_TENANCY_ORGANIZATION` | не задано | Необов'язковий сегмент organization ієрархії tenancy пакета workflow, видимий у readiness, discovery та операторських поверхнях. | `WORKFLOW_V2_TENANCY_ORGANIZATION` | +| `DW_V2_TENANCY_PROJECT` | не задано | Необов'язковий сегмент project ієрархії tenancy пакета workflow, видимий у readiness, discovery та операторських поверхнях. | `WORKFLOW_V2_TENANCY_PROJECT` | +| `DW_V2_TENANCY_ENVIRONMENT` | не задано | Необов'язковий сегмент environment ієрархії tenancy пакета workflow, видимий у readiness, discovery та операторських поверхнях. | `WORKFLOW_V2_TENANCY_ENVIRONMENT` | +| `DW_V2_CURRENT_COMPATIBILITY` | не задано | Маркер сумісності worker, який оголошує цей worker. | `WORKFLOW_V2_CURRENT_COMPATIBILITY` | +| `DW_V2_SUPPORTED_COMPATIBILITIES` | не задано | Маркери сумісності, які приймає worker, розділені комами, або `*`. | `WORKFLOW_V2_SUPPORTED_COMPATIBILITIES` | +| `DW_V2_COMPATIBILITY_NAMESPACE` | не задано | Namespace сумісності для спільних баз workflow з незалежними fleets. | `WORKFLOW_V2_COMPATIBILITY_NAMESPACE` | +| `DW_V2_COMPATIBILITY_HEARTBEAT_TTL` | `30` | Секунди чинності heartbeat сумісності worker. | `WORKFLOW_V2_COMPATIBILITY_HEARTBEAT_TTL` | +| `DW_V2_PIN_TO_RECORDED_FINGERPRINT` | `true` | Вибір незавершених runs за fingerprint workflow, записаним у `WorkflowStarted`. | `WORKFLOW_V2_PIN_TO_RECORDED_FINGERPRINT` | +| `DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD` | `10000` | Кількість подій історії, за якої пакет рекомендує автору workflow continue-as-new. | `WORKFLOW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD` | +| `DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD` | `5242880` | Розмір серіалізованої історії у bytes, за якого пакет рекомендує continue-as-new. | `WORKFLOW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD` | +| `DW_V2_HISTORY_EXPORT_SIGNING_KEY` | не задано | Необов'язковий ключ HMAC для автентифікації архівів експорту історії. | `WORKFLOW_V2_HISTORY_EXPORT_SIGNING_KEY` | +| `DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID` | не задано | Ідентифікатор ключа, записаний поруч із підписаними exports історії. | `WORKFLOW_V2_HISTORY_EXPORT_SIGNING_KEY_ID` | +| `DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS` | `10` | Секунди очікування Server переходу update до термінальної стадії. | `WORKFLOW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS` | +| `DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS` | `50` | Мілісекунди між polls стадії update. | `WORKFLOW_V2_UPDATE_WAIT_POLL_INTERVAL_MS` | +| `DW_V2_GUARDRAILS_BOOT` | `warn` | Режим структурних обмежень під час старту: `warn`, `fail` або `silent`. | `WORKFLOW_V2_GUARDRAILS_BOOT` | +| `DW_V2_LIMIT_PENDING_ACTIVITIES` | `2000` | Обмеження незавершених activities на рівні пакета. | `WORKFLOW_V2_LIMIT_PENDING_ACTIVITIES` | +| `DW_V2_LIMIT_PENDING_CHILDREN` | `1000` | Обмеження незавершених дочірніх workflows на рівні пакета. | `WORKFLOW_V2_LIMIT_PENDING_CHILDREN` | +| `DW_V2_LIMIT_PENDING_TIMERS` | `2000` | Обмеження незавершених timers на рівні пакета. | `WORKFLOW_V2_LIMIT_PENDING_TIMERS` | +| `DW_V2_LIMIT_PENDING_SIGNALS` | `5000` | Обмеження незавершених signals на рівні пакета. | `WORKFLOW_V2_LIMIT_PENDING_SIGNALS` | +| `DW_V2_LIMIT_PENDING_UPDATES` | `500` | Обмеження незавершених updates на рівні пакета. | `WORKFLOW_V2_LIMIT_PENDING_UPDATES` | +| `DW_V2_LIMIT_COMMAND_BATCH_SIZE` | `1000` | Максимальна кількість команд в одному завершенні workflow task. | `WORKFLOW_V2_LIMIT_COMMAND_BATCH_SIZE` | +| `DW_V2_LIMIT_PAYLOAD_SIZE_BYTES` | `2097152` | Обмеження bytes одного payload на рівні пакета. | `WORKFLOW_V2_LIMIT_PAYLOAD_SIZE_BYTES` | +| `DW_V2_LIMIT_MEMO_SIZE_BYTES` | `262144` | Обмеження bytes memo workflow на рівні пакета. | `WORKFLOW_V2_LIMIT_MEMO_SIZE_BYTES` | +| `DW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES` | `40960` | Обмеження bytes search attributes на рівні пакета. | `WORKFLOW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES` | +| `DW_V2_LIMIT_HISTORY_TRANSACTION_SIZE` | `5000` | Обмеження кількості подій транзакції історії на рівні пакета. | `WORKFLOW_V2_LIMIT_HISTORY_TRANSACTION_SIZE` | +| `DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT` | `80` | Відсоток структурного обмеження, за якого пакет виводить попередження. | `WORKFLOW_V2_LIMIT_WARNING_THRESHOLD_PERCENT` | +| `DW_V2_TASK_DISPATCH_MODE` | `queue` | Режим dispatch tasks на рівні пакета, зазвичай перевизначений `DW_TASK_DISPATCH_MODE` у режимі Server. | - | +| `DW_V2_MATCHING_ROLE_QUEUE_WAKE` | `true` | Чи виконують queue workers внутрішній wake ролі matching на кожній події Looping. Установіть `false`, щоб execution-only вузли не виконували broad-poll wake, коли прохід належить окремому daemon `php artisan workflow:v2:repair-pass --loop`. | `WORKFLOW_V2_MATCHING_ROLE_QUEUE_WAKE` | +| `DW_V2_TASK_REPAIR_REDISPATCH_AFTER_SECONDS` | `3` | Секунди до повторного dispatch осиротілого workflow task через repair. | `WORKFLOW_V2_TASK_REPAIR_REDISPATCH_AFTER_SECONDS` | +| `DW_V2_TASK_REPAIR_LOOP_THROTTLE_SECONDS` | `5` | Мінімальні секунди між проходами repair tasks на чергу. | `WORKFLOW_V2_TASK_REPAIR_LOOP_THROTTLE_SECONDS` | +| `DW_V2_TASK_REPAIR_SCAN_LIMIT` | `25` | Максимальна кількість tasks, розглянутих за прохід repair. | `WORKFLOW_V2_TASK_REPAIR_SCAN_LIMIT` | +| `DW_V2_TASK_REPAIR_FAILURE_BACKOFF_MAX_SECONDS` | `60` | Максимальний backoff помилки repair tasks у секундах. | `WORKFLOW_V2_TASK_REPAIR_FAILURE_BACKOFF_MAX_SECONDS` | +| `DW_V2_MULTI_NODE` | `false` | Оголошення багатовузлового розгортання для перевірки cache backends на координацію між вузлами. | `WORKFLOW_V2_MULTI_NODE` | +| `DW_V2_VALIDATE_CACHE_BACKEND` | `true` | Перевірка cache backend long poll під час старту. | `WORKFLOW_V2_VALIDATE_CACHE_BACKEND` | +| `DW_V2_CACHE_VALIDATION_MODE` | `warn` | Режим помилки перевірки cache backend: `fail`, `warn` або `silent`. | `WORKFLOW_V2_CACHE_VALIDATION_MODE` | +| `DW_V2_FLEET_VALIDATION_MODE` | `warn` | Режим перевірки сумісності fleet: `warn` записує лог, `fail` блокує dispatch і відхиляє роботу за відсутності сумісного worker. | `WORKFLOW_V2_FLEET_VALIDATION_MODE` | +| `DW_SERIALIZER` | `avro` | Діагностичний параметр codec payload. Остаточний v2 використовує Avro для payload нових runs. | `WORKFLOW_SERIALIZER` | + +Використовуйте `DW_V2_GUARDRAILS_BOOT` у CI та deployment manifests. +Стару назву `WORKFLOW_V2_GUARDRAILS_BOOT` залишено лише для того, +щоб `env:audit` повідомляв операторам alpha-версій про перейменування. +Пакет workflow більше не читає її як runtime fallback. + +Використовуйте [matching та dispatch tasks](/docs/polyglot/task-matching-dispatch) +під час налаштування `DW_V2_MATCHING_ROLE_QUEUE_WAKE` або окремого daemon +`workflow:v2:repair-pass --loop`. Ці налаштування змінюють місце пошуку +готових tasks, зберігаючи той самий контракт worker-протоколу. + +## Змінні інфраструктури runtime {#runtime-infrastructure-variables} + +Інфраструктурні змінні runtime керують фреймворком та залежностями. +Аудит Server розпізнає їх і не попереджає, але вони не є сталими +налаштуваннями Durable Workflow API. + +| Група | Змінні | Використання | +| --- | --- | --- | +| Застосунок | `APP_NAME`, `APP_ENV`, `APP_KEY`, `APP_DEBUG`, `APP_URL`, `APP_VERSION`, locale, timezone, налаштування maintenance і cipher | Ідентичність runtime Laravel-застосунку та поведінка старту. | +| Логування | `LOG_CHANNEL`, `LOG_LEVEL`, налаштування stack, daily, Slack та Papertrail | Напрямки логування Laravel та retention. | +| База даних | `DB_CONNECTION`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `DB_SOCKET`, `DB_URL`, налаштування charset, collation та foreign keys | SQL-сховище стану таблиць workflow, namespace, worker і projections. | +| Redis/cache/queue | `REDIS_*`, `QUEUE_CONNECTION`, `QUEUE_FAILED_DRIVER`, `CACHE_STORE`, `CACHE_PREFIX`, `SESSION_*` | Queue workers, cache locks, long-poll signals і підтримка web/session runtime. | +| Файлові системи/пошта/broadcasting | `FILESYSTEM_DISK`, `MAIL_*`, `BROADCAST_*`, `PUSHER_*`, `AWS_*` | Інтеграції фреймворку для особливостей конкретного розгортання. | +| Build/runtime | `MYSQL_VERSION`, `REDIS_VERSION`, `PHP_CLI_SERVER_WORKERS`, `VITE_APP_NAME`, `BCRYPT_ROUNDS` | Docker Compose images та поведінка runtime фреймворку. | + +## Примітки до міграції {#migration-notes} + +Під час міграції старого розгортання спочатку змініть публічну назву +й залиште застарілу назву неналаштованою. Якщо присутні обидві, +оператор має орієнтуватися на значення `DW_*` та прибрати стару назву. + +```bash +# Before +WORKFLOW_SERVER_AUTH_DRIVER=token +WORKFLOW_SERVER_OPERATOR_TOKEN=operator-secret + +# After +DW_AUTH_DRIVER=token +DW_OPERATOR_TOKEN=operator-secret +``` + +Після міграції запускайте образ Server з `DW_ENV_AUDIT_STRICT=1`, +щоб виявити помилки в назвах змінних, старі aliases та налаштування, +скопійовані з попередніх runbooks. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-role-topology.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-role-topology.md new file mode 100644 index 0000000000..1ea747b2bd --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-role-topology.md @@ -0,0 +1,348 @@ +--- +title: Топологія ролей Server +description: Читання topology manifest /api/cluster/info для ролей Durable Workflow Server, меж масштабування, областей відмов і кроків міграції. +tags: + - server + - topology + - deployment + - control-plane + - workers +keywords: + - топологія ролей Durable Workflow + - cluster info topology + - межі масштабування Server + - split control execution +--- + +# Топологія ролей Server {#server-role-topology} + +## Призначення manifest {#why-this-manifest-exists} + +`GET /api/cluster/info` публікує manifest `topology` Server зі schema +`durable-workflow.v2.role-topology`. Це публічний контракт назв ролей, +підтримуваних форм розгортання, повноважень durable-запису, очікувань +щодо областей відмов, меж масштабування та впорядкованого шляху міграції +від поточного окремого Server до топології з розділеними керуванням і виконанням. + +Ця сторінка допомагає працювати з формою Server у scripts, dashboards, +runbooks та автоматизації rollout. HTTP-поверхня наведена в +[довіднику Server API](/docs/polyglot/server-api-reference), +а налаштування розгортання — у +[настанові Server](/docs/polyglot/server). + +## Читання topology manifest {#reading-the-topology-manifest} + +Об'єкт `topology` відповідає на такі питання контракту: + +| Група полів | Питання | +| --- | --- | +| `schema`, `version` | Яку редакцію контракту топології ви читаєте? | +| `supported_shapes` | Які форми розгортання продукту дозволені? | +| `role_vocabulary` | Які назви ролей дійсні для цього контракту? | +| `current_shape`, `current_process_class`, `current_roles`, `execution_mode` | Що зараз робить вузол, який відповів? | +| `matching_role.*` | Хто відповідає за широке wake готових tasks, які осі маршрутизації закріплено та який режим dispatch/backpressure активний? | +| `role_catalog`, `authority_surfaces` | Які інтерфейси та поверхні durable-змін належать кожній ролі? | +| `shape_assignments` | Які класи процесів дозволені для кожної підтримуваної форми? | +| `authority_boundaries`, `failure_domains`, `scaling_boundaries` | Що кожна роль може записувати, як відмовляє та за яким навантаженням масштабується? | +| `supported_topologies`, `migration_path` | Які сімейства розгортань підтримує продукт та який порядок переходу від окремого Server до ізольованіших ролей? | +| `kernel_invariants` | Які гарантії durable-ядра має зберігати поділ ролей незалежно від активної форми? | + +`current_shape`, `current_process_class` і `current_roles` описують вузол, +який відповів на HTTP-запит, а не весь fleet. Використовуйте +`current_process_class` як оголошену ідентичність вузла, а для перевірки +зіставляйте `current_roles` з наборами ролей класів процесів у `shape_assignments`. + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/cluster/info" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" \ + | jq '{ + current_shape: .topology.current_shape, + current_process_class: .topology.current_process_class, + current_roles: .topology.current_roles, + execution_mode: .topology.execution_mode, + matching_role: .topology.matching_role, + scaling_boundaries: .topology.scaling_boundaries, + migration_path: .topology.migration_path + }' +``` + +## Назви ролей {#role-vocabulary} + +Публічні назви ролей закріплені в `topology.role_vocabulary`: + +| Роль | Відповідальність | +| --- | --- | +| `api_ingress` | Приймання зовнішнього HTTP-трафіку, включно з discovery та входом до площини керування. | +| `control_plane` | Запуск, signal, update, repair, cancel, terminate, archive та інші зміни життєвого циклу workflow. | +| `matching` | Пошук готової роботи, володіння task leases та координація тиску dispatch. | +| `history_projection` | Зберігання durable-історії та підтримка похідних зведень runs і exports. | +| `scheduler` | Запуск schedules та зберігання стану schedule runs. | +| `execution_plane` | Виконання workflow tasks та activity tasks. | + +Автоматизація має використовувати саме ці ідентифікатори як сталі назви. + +## Підтримувані форми розгортання {#supported-deployment-shapes} + +`topology.supported_shapes` визначає дозволені форми розгортання продукту: + +| Форма | Класи процесів у `shape_assignments` | Значення контракту | +| --- | --- | --- | +| `embedded` | `application_process` | Один процес застосунку відповідає за керування, matching, проєкцію історії, scheduler і виконання. | +| `standalone_server` | `server_http_node`, `scheduler_node`, `worker_node` | Поточний окремий Server: HTTP ingress і керування на вузлі Server, scheduler в окремому класі процесу, виконання на вузлах workers. | +| `split_control_execution` | `ingress_node`, `control_plane_node`, `scheduler_node`, `matching_node`, `execution_node` | Той самий контракт продукту з вужчими класами процесів за ролями, щоб переміщувати межі масштабування та відмов за підсистемами. | + +`split_control_execution` є підтримуваною топологією. Вона використовує +той самий рушій та API. Одна discovery-поверхня описує і окремий Server, +і форму з розділеними ролями. + +## Ідентичність поточного вузла {#current-node-identity} + +Використовуйте разом `current_shape`, `current_process_class`, +`current_roles` та `execution_mode`: + +- `current_shape` визначає контракт форми вузла, який відповів. +- `current_process_class` визначає оголошений клас процесу цього вузла. +- `current_roles` визначає активний набір ролей вузла. +- `execution_mode` розрізняє `remote_worker_protocol` та `local_queue_worker`. + +Для окремого Server `current_shape` залишається `standalone_server`, +навіть коли `DW_MODE=embedded` перемикає виконання на локальні queue workers. +У цьому випадку `execution_mode` змінюється на `local_queue_worker`, +а HTTP-вузол зберігає контракт ролей окремого Server. + +### Перевірка розміщених маршрутів {#hosted-route-gating} + +Той самий контракт топології визначає, коли вузол має відхиляти трафік. +Автентифіковані розміщені маршрути відхиляються, якщо вузол не оголошує +потрібний їм поточний HTTP-набір керування: +`api_ingress` разом із `control_plane`. + +Відповідь неправильного вузла містить `503`, +`reason: "topology_role_unavailable"` та дані топології +для вибору іншого маршруту: + +- `current_shape` +- `current_process_class` +- `current_roles` +- `required_roles` +- `missing_roles` + +Ця перевірка передує визначенню namespace на розміщених маршрутах. +Тому запит до `scheduler_node`, `matching_node` чи `execution_node` +не дізнається, чи існує названий namespace, перед переходом до правильного +класу вузла. `GET /api/health`, `GET /api/ready` та автентифікований +`GET /api/cluster/info` залишаються доступними, щоб автоматизація могла +визначити форму вузла перед повторенням запиту в іншому місці. + +### Перевірка bootstrap workflow {#workflow-bootstrap-gate} + +Друга перевірка маршрутів відхиляє запити, коли вузол має нерозв'язані +blockers bootstrap workflow v2. Поки `checks.workflow_v2.status` +дорівнює `blocked`, автентифіковані маршрути запуску та зміни workflow, +зміни schedule, bridge adapters і worker-протоколу повертають +`503` із `reason: "workflow_v2_blocked"` та: + +- `blocked_by`: упорядкований список попередніх blockers готовності, + наприклад `migrations`. +- `remediation`: інструкція оператору для усунення перелічених blockers, + скопійована з `checks.workflow_v2.remediation` у `/api/ready`. + +Перевірка bootstrap виконується на тому самому етапі, що й перевірка +топології розміщених маршрутів: після ролі та версії протоколу, +перед визначенням namespace. Тому заблокований запит ніколи +не отримує інформації про існування namespace. + +Перевіряються маршрути workflow, наприклад `/api/workflows`, +зміни schedules, зокрема `POST /api/schedules`, +`PUT /api/schedules/{scheduleId}`, `DELETE /api/schedules/{scheduleId}`, +`/pause`, `/resume`, `/trigger` та `/backfill`, маршрути bridge adapters, +наприклад `/api/bridge-adapters/webhook/{adapter}`, +і worker-протокол під `/api/worker`. + +**Читання** schedules (`GET /api/schedules`, +`GET /api/schedules/{scheduleId}`, `GET /api/schedules/{scheduleId}/history`) +навмисно виключено з перевірки, щоб оператор міг оглянути стан schedule +під час відновлення. Маршрути worker-протоколу повертають той самий payload +перевірки bootstrap в оболонці worker-протоколу. Тому SDK workers використовують +той самий машиночитаний `reason: "workflow_v2_blocked"`, що й площина керування. + +## Контракт ролі matching {#matching-role-contract} + +`topology.matching_role` закріплює поточні matching і wake +для вузла, який відповів: + +| Поле | Значення | +| --- | --- | +| `queue_wake_enabled` | Чи увімкнено зараз короткотривалі wake signals черги. | +| `shape` | Яку форму розгортання matching оголошує вузол: `in_worker` чи `dedicated`. | +| `wake_owner` | Хто зараз відповідає за широкий wake-прохід: `worker_loop` чи `dedicated_repair_pass`. | +| `task_dispatch_mode` | Чи dispatch виконується через віддалені workers із `poll`, чи через локальне виконання з `queue`. | +| `partition_primitives` | Закріплені осі маршрутизації matching у порядку: `connection`, `queue`, `compatibility`, `namespace`. | +| `backpressure_model` | Durable-межа приймання роботи, яку контролює matching. Поточний v2 повідомляє `lease_ownership`. | +| `discovery_limits` | Закріплений числовий контракт matching, скомпільований у пакеті workflow: `poll_batch_cap`, `availability_ceiling_seconds`, `wake_signal_ttl_seconds`, `workflow_task_lease_seconds` та `activity_task_lease_seconds`. | + +Це дає операторам та автоматизації змогу розрізняти погіршення wake +за наявного matching і навмисний інший режим dispatch вузла. +Воно також надає ті самі назви маршрутизації та backpressure, +які Server публікує в операторських метриках. + +`discovery_limits` є числовим контрактом matching: `poll_batch_cap` +закріплює максимальну кількість рядків готових tasks на poll, +`availability_ceiling_seconds` — допуск різних backends до `available_at`, +щоб щойно доступні tasks витримували дрейф timestamp менше секунди, +`wake_signal_ttl_seconds` — стандартний TTL signal `CacheLongPollWakeStore`, +а `workflow_task_lease_seconds` / `activity_task_lease_seconds` — стандартні +тривалості leases workflow та activity tasks. Оператори перевіряють за цими +значеннями відповідність розгортання контракту matching без пошуку в коді пакета. +Посилення будь-якого обмеження є зміною протоколу, оскільки від нього +залежать строки dispatch, workers та прискорення в інших частинах контракту. +Перейменування поля також порушує сумісність протоколу. + +## Межі повноважень {#authority-boundaries} + +`topology.authority_boundaries` визначає поверхні durable-запису, +які кожна роль має змінювати: + +| Роль | Опубліковані записи | +| --- | --- | +| `api_ingress` | `worker_registrations` | +| `control_plane` | `workflow_instances`, `workflow_runs.status`, `workflow_tasks.lifecycle` | +| `matching` | `workflow_tasks.leases`, `activity_tasks.leases` | +| `history_projection` | `history_events`, `workflow_run_summaries`, `workflow_history_exports` | +| `scheduler` | `workflow_schedules.fire_state`, `workflow_starts.scheduled` | +| `execution_plane` | `workflow_tasks.outcomes`, `activity_attempts`, `worker_compatibility_heartbeats` | + +За цим контрактом виявляйте вихід за межі ролей перед поділом процесів +або додаванням автоматизації для окремої топології. + +## Межі відмов і масштабування {#failure-and-scaling-boundaries} + +### Області відмов {#failure-domains} + +`topology.failure_domains` визначає перше погіршення поведінки +та перший видимий оператору signal для відмови кожної ролі: + +| Область відмови | `effect` | `operator_signal` | +| --- | --- | --- | +| `control_plane_down` | `workers_continue_claimed_tasks_only_until_lease_expiry` | `operator_commands_fail_fast` | +| `execution_plane_down` | `ready_tasks_accumulate_without_loss` | `operators_see_ready_depth_growth` | +| `matching_down` | `claim_falls_back_to_direct_ready_task_discovery` | `ready_depth_rises_while_claim_rate_falls` | +| `history_projection_down` | `projection_reads_may_stale_while_durable_writes_continue` | `projection_lag_seconds_may_increase` | +| `scheduler_down` | `scheduled_workflows_stop_firing_and_record_missed_runs` | `operators_see_missed_schedule_state` | +| `api_ingress_down` | `external_http_traffic_stops_at_the_edge` | `embedded_in_process_calls_may_continue` | + +Це очікувана поведінка продукту. Використовуйте її для визначення +того, що має відбутися за погіршення ролі, перед читанням логів. + +### Межі масштабування {#scaling-boundaries} + +`topology.scaling_boundaries` визначає основну вісь навантаження, +за якою масштабується кожна роль у моделі поділу ролей: + +| Роль | Межа масштабування | +| --- | --- | +| `api_ingress` | `incoming_http_request_rate` | +| `control_plane` | `operator_commands_and_run_lifecycle_transitions` | +| `matching` | `ready_task_rate_and_poller_count` | +| `history_projection` | `durable_event_rate` | +| `scheduler` | `active_schedule_count` | +| `execution_plane` | `workflow_and_activity_task_rate` | + +Це явна відповідь на питання, що масштабується незалежно +в топології з поділеними ролями. + +## Шлях міграції {#migration-path} + +`topology.migration_path` упорядкований. Кожен крок зберігає одне +durable-ядро та чіткіше ізолює відповідальність: + +1. `audit_role_boundaries` + Результат: інструменти виявляють записи за межами ролей перед зміною форми runtime. +2. `expose_role_bindings` + Результат: точки інтеграції контейнера дозволяють позапроцесні adapters без змін пакета. +3. `introduce_dedicated_matching_shape` + Результат: matching працює у власному класі процесу без зміни контракту claim. +4. `split_history_projection` + Результат: історія та projections можуть перейти за межі процесу без другого writer. +5. `split_scheduler` + Результат: запуск schedule може використовувати leader election, зберігаючи підтримку однієї репліки. +6. `optional_execution_partitioning` + Результат: workers можуть поділятися за namespace, connection, queue та compatibility. + +Цей список визначає підтримуваний порядок переходу топології +для поточного рушія. + +Кожен запис `topology.migration_path[]` має явний прапорець +`reversible: true`. Шлях міграції працює в обидва боки: +розгортання, яке досягло `split_history_projection`, МОЖЕ повернути +роль історії до процесу площини керування та залишатися дозволеною +формою топології. Rollback є частиною контракту. + +## Інваріанти durable-ядра {#durable-kernel-invariants} + +`topology.kernel_invariants` перелічує гарантії, які поділ ролей +зберігає за кожної підтримуваної форми. Використовуйте цей список +для перевірки, що запропонована зміна топології підтримується продуктом +і зберігає контракт рушія: + +| Інваріант | Гарантія | +| --- | --- | +| `single_persistence_engine` | Одна база workflow підтримує всі форми топології. Поділ ролей не вводить другого рушія зберігання. | +| `single_worker_protocol` | Один HTTP worker-протокол передає claim, complete, fail і heartbeat у кожній топології. Поділ ролей не створює окремого контракту worker. | +| `single_history_writer` | `history_events` має рівно одного durable writer на логічну подію незалежно від місця виконання ролі історії/projection. | +| `single_control_authority_per_run` | Кожна зміна run workflow проходить через один центр повноважень площини керування. Row locks run серіалізують переходи між репліками. | +| `embedded_topology_remains_supported` | Вбудована форма з усіма ролями в одному процесі МАЄ залишатися дозволеною. Наявні embedded hosts не змушують мігрувати. | +| `role_split_is_topology_only` | Поділ ролей змінює топологію, зберігаючи той самий продукт. Повернення всіх ролей до одного процесу завжди є дозволеною топологією. | + +Поле `applies_to` кожного запису перелічує форми, які охоплює інваріант. +Для підтримуваного сімейства топологій кожен інваріант зараз діє для +`embedded`, `standalone_server` та `split_control_execution`. +Якщо оновлення додає нову форму, інваріанти, чиє `applies_to` її +не містить, МАЮТЬ бути перевірені до визнання підтримки цієї форми продуктом. + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/cluster/info" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" \ + | jq '.topology.kernel_invariants[] | {id, applies_to}' +``` + +## Стан координації {#coordination-health} + +`/api/cluster/info` також публікує `coordination_health` поруч +із `topology`. Вони описують різне: + +- `topology` визначає дозволені дії вузла та очікувану поведінку форми продукту. +- `coordination_health` визначає поточний стан перевірок безпеки rollout + та координації між namespaces. +- `coordination_health.blocked_by`, `coordination_health.message` та + `coordination_health.remediation` з'являються, коли Server не може перевірити + безпеку rollout через відсутність передумов готовності, наприклад migrations + чи підключення до бази даних. +- `coordination_health.routing_drains` підсумовує групи build ID у drain + між чергами та namespaces. `queues_with_drains` показує, чи автоматизація + rollout зараз навмисно утримує трафік від певної групи. +- `coordination_health.warning_checks`, `coordination_health.error_checks` та + `coordination_health.checks` залишаються нормалізованим переліком перевірок + після запуску оцінювання безпеки rollout. +- `coordination_health.checks[]` завжди містить закріплену перевірку + `activity_path` поруч із `worker_compatibility`, `task_transport`, + `routing_health`, `durable_resume_paths` та перевірками projection/scheduler. + `activity_path` є аналогом `task_transport` для activities: вона показує + виконання activities зі спливлими, але не застосованими deadlines + schedule-to-start, start-to-close, schedule-to-close або heartbeat, + а також сталий backlog retries activities. Перейменування перевірки + є зміною протоколу. + +Використовуйте обидві поверхні разом, щоб визначити, +чи зміна топології підтримується та чи безпечна вона зараз. + +## Пов'язані довідники {#related-references} + +- [Довідник Server API](/docs/polyglot/server-api-reference) — + автентифікований HTTP-контракт `/api/cluster/info`. +- [Настанова Server](/docs/polyglot/server) — налаштування розгортання + та ширша модель експлуатації окремого Server. +- [Режими розгортання](/docs/polyglot/deployment-modes) — вибір вбудованого + режиму, окремого Server чи ширших топологій за участі підтримки. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/waterline-operator-api.md b/i18n/uk/docusaurus-plugin-content-docs/current/waterline-operator-api.md new file mode 100644 index 0000000000..11d9e1163c --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/waterline-operator-api.md @@ -0,0 +1,396 @@ +--- +sidebar_position: 13 +title: Довідник операторського API Waterline +description: Типізовані контракти API Waterline для списків dashboard, деталей вибраного run, експорту історії, schedules, збережених подань, налаштувань і дій оператора. +tags: + - Waterline + - observability + - API + - reference +keywords: + - довідник API Waterline + - деталі вибраного run + - експорт історії + - контракт actionability + - операторські дії workflow +--- + +# Довідник операторського API Waterline {#waterline-operator-api-reference} + +Waterline надає оператору durable-стан і для вбудованого Laravel, +і для окремого Server. UI використовує той самий HTTP+JSON API, +який описано тут. Тому scripts, dashboards та агенти можуть читати +факти durable workflow без розбору HTML. + +Використовуйте цей довідник для типізованих операторських даних: +деталей вибраного run, експорту історії, actionability, доступності команд, +збережених подань, налаштувань та огляду schedules. +[Моніторинг](./monitoring.md) пояснює поділ між durable-станом Waterline +та телеметрією workers/runtime. + +## Встановлення {#installation} + +Установіть Waterline у тому самому вбудованому Laravel-застосунку, +де встановлено пакет workflow: + +```bash +composer require \ + %%artifact.waterlineComposerPackage%% \ + %%artifact.workflowComposerPackage%% \ + %%artifact.phpSdkComposerPackage%% +php artisan waterline:install +``` + +Згенеровані pins вибирають стабільні випуски 2.x Waterline, Workflow +та PHP SDK. Після оновлення Waterline опублікуйте поточні assets: + +```bash +composer update durable-workflow/waterline +php artisan waterline:publish +``` + +Самодостатній service image та його параметри підключення, +автентифікації й зберігання описано в +[моніторингу](./monitoring.md#waterline-service). + +## Межа розгортання {#deployment-boundary} + +Waterline має два backend adapters: + +- Embedded mode працює в Laravel host і читає durable-стан пакета + workflow всередині процесу. +- Service mode запускає опублікований образ Waterline та читає один + namespace окремого Server через `durable-workflow/sdk` і публічний API Server. + +Обидва adapters надають маршрути Waterline цієї сторінки. +Service mode перетворює їх на відповідні контракти Server: + +| Група маршрутів Waterline | Контракт окремого Server у service mode | +| --- | --- | +| `GET /waterline/api/v2/health` | Стан Server, реєстрація workers та огляд task queues. | +| `GET /waterline/api/stats` | Операторський dashboard і snapshot метрик у межах namespace. | +| Списки flows, деталі вибраного run та експорт історії | Список workflows, деталі, runs, історія та діагностика з [довідника Server API](./polyglot/server-api-reference.md). | +| Дії signal, update, query, cancel, terminate, repair та archive | Відповідні контракти команд workflow та repair Server. | +| Маршрути під `/waterline/api/v2/schedules` | Контракти списку, деталей, історії та зміни schedule Server. | + +Після розгортання Waterline API Server, CLI та SDK залишаються доступними +безпосередньо. Не припускайте видимості між режимами чи namespaces: +runs залишаються доступними для читання та дій у власних runtime і namespace. +[Режими розгортання](./polyglot/deployment-modes.md) визначають +цю спільну продуктову межу. + +## Базовий шлях та область {#base-path-and-scope} + +Waterline використовує налаштований базовий шлях Waterline +Laravel-застосунку. Приклади нижче використовують `/waterline`. +Якщо ваш застосунок публікує Waterline з іншим префіксом, +зберігайте ті самі суфікси `/api/...`. + +Коли налаштовано `WATERLINE_NAMESPACE`, кожен маршрут списку, деталей, +schedule та операторської дії обмежений namespace. +Читання та команди інших namespaces повертають not-found, +не розкриваючи стан workflow іншого namespace. + +```bash +curl -sS "$APP_URL/waterline/api/instances/order-1001" \ + -H "Accept: application/json" | jq '.status, .run_id, .actionability' +``` + +## Автентифікація {#authentication} + +Кожен controller UI та JSON API Waterline проходить перевірку доступу +Waterline. У embedded mode Laravel host визначає цю перевірку та вирішує, +які guards, route middleware, gates, policies, SSO/OIDC/SAML sessions, +групи каталогу, service tokens і rate limits діють. + +Поточна група JSON operator API виключає CSRF middleware Laravel, +щоб host міг послідовно застосувати межу session або service token +і до читання, і до команд. Host відповідає за автентифікацію цих запитів +та документування додаткового CSRF-захисту browser sessions навколо API. + +Самодостатній service image не має користувачів host-застосунку. +Перед установленням `WATERLINE_ALLOW_UNAUTHENTICATED=true` розмістіть його +за reverse proxy з автентифікацією або на приватному інтерфейсі. +Це налаштування передає вхідну перевірку Waterline навколишньому розгортанню. +Окремо `WATERLINE_SERVER_TOKEN` автентифікує виклики PHP SDK Waterline +до окремого Server. Ніколи не передавайте ці облікові дані Server браузеру. + +`WATERLINE_ACCESS_MODE=read_only` локально блокує маршрути зміни Waterline. +`operator` дозволяє їх, але підключений Server усе одно застосовує +власну роль токена та авторизацію namespace. Приклад розгортання +й обидві межі автентифікації наведено в +[моніторингу](./monitoring.md#waterline-service). + +## Dashboard та стан {#dashboard-and-health} + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/stats` | Зведення dashboard, лічильники backlog, repair policy, операторські метрики та тренди fleet. | +| `GET` | `/waterline/api/v2/health` | Стан пакета/runtime Waterline та готовність джерела даних v2. | + +`/waterline/api/stats` є зведенням dashboard. Використовуйте +`operator_metrics` як JSON-діагностику Waterline, а не як поверхню +Prometheus scrape. Для latency runtime та власної телеметрії застосунку +використовуйте метрики SDK worker. + +[Операторські межі експлуатації](./operator-operating-envelope.md) +визначають читання цієї діагностики під час rollout і реагування +на інциденти. Зокрема: + +| Група полів | Значення | +| --- | --- | +| `operator_metrics.backlog.*` | Кількість durable-роботи, готової до виконання, відкладеної, leased, нездорової, з потребою repair, невдалим claim чи блокуванням сумісності, та загальні для fleet факти потоку черги `tasks_added_last_minute` і `tasks_dispatched_last_minute`. | +| `operator_metrics.matching_role.*` | Локальний контракт matching/dispatch вузла для процесу, що обслуговує запит: `queue_wake_enabled`, форма розгортання `shape`, `task_dispatch_mode`, закріплені `partition_primitives` і поточний `backpressure_model`. | +| `operator_metrics.repair.*` | Обсяг проходу repair loop, включно з вибраними candidates, їхнім віком і тиском scan. | +| `operator_metrics.projections.*` | Кількість drift projections для зведень runs, waits, timelines, timers та lineage. | +| `operator_metrics.command_contracts.*` | Старі snapshots контрактів WorkflowStarted, які ще потребують backfill. | +| `operator_metrics.history.*` | Тиск розміру та кількості подій історії, а також рекомендації continue-as-new. | +| `queue_visibility.available`, `queue_visibility.reason` | Доступність огляду черг у межах namespace на цьому host-застосунку та причина його відсутності. | +| `queue_visibility.task_queues[].stats.*` | Backlog окремої черги, його вік, кількість pollers, готові та leased workflow/activity tasks і факти потоку `tasks_added_last_minute` / `tasks_dispatched_last_minute` кожної черги. | +| `queue_visibility.task_queues[].repair.*` | Тиск repair окремої черги: candidates, помилки dispatch, спливлі leases, прострочені dispatch та найстарший вік кожної умови. | +| `coordination_alerts[]` | Зведені warnings і errors з health checks та ризиків огляду черг, зокрема backlog без pollers, застарілі pollers чи старі repair candidates. | +| `checks[]`, `categories.*` | Блокувальні та рекомендаційні health checks v2 з `category = correctness | acceleration` кожної перевірки та зведеннями категорій Waterline для поверхні workers. | +| `engine_source`, `readiness_contract` | Чи Waterline активно використовує операторський bridge v2 та який контракт readiness визначає цей стан. | + +Поля потоку черги показують, чи durable-робота надходить швидше, +ніж система виконує dispatch. `tasks_added_last_minute` рахує окремі +рядки durable tasks, створені за останні 60 секунд, +а `tasks_dispatched_last_minute` — окремі рядки durable tasks, +чий останній успішний dispatch відбувся в тому самому вікні. + +Використовуйте загальні значення потоку черги разом із маршрутами +огляду task queue Server. Waterline показує, чи durable inflow випереджає +dispatch, а `/api/task-queues` та `/api/task-queues/{taskQueue}` — +чи навантажена черга є `saturated`, навмисно `throttled`, +`no_active_workers` або має іншу нестачу здорових pollers чи slots. + +`GET /waterline/api/v2/health` надає ті самі дані окремих черг +у Waterline. Scripts і dashboards для поточного стану черг одного +namespace мають читати `queue_visibility.task_queues[]` для статистики +кожної черги та `coordination_alerts[]` для зведення умов черги +або health checks, які зараз потребують уваги оператора. + +Поля `matching_role` показують, яку форму matching зараз надає цей вузол. +`shape` розрізняє `in_worker` і `dedicated`, `partition_primitives` +закріплює осі маршрутизації `connection`/`queue`/`compatibility`/`namespace`, +а `backpressure_model` зараз повідомляє `lease_ownership`. +Цей блок описує локальний процес. Під час rollout зі змішаними формами +порівнюйте його між вузлами перед висновком, що різниця backlog чи polls +означає проблему worker. + +`GET /waterline/api/v2/health` використовує той самий поділ: +`error` блокує, `warning` є рекомендаційним, +а `ok` означає готовність поточного операторського bridge v2. + +## Списки {#list-views} + +Списки групуються за durable-статусом: + +| Метод | Шлях | Група | +| --- | --- | --- | +| `GET` | `/waterline/api/flows/running` | Відкриті або активно заблоковані runs. | +| `GET` | `/waterline/api/flows/completed` | Успішно закриті runs, включно з продовженими runs із `closed_reason = continued`. | +| `GET` | `/waterline/api/flows/failed` | Runs із помилкою. | +| `GET` | `/waterline/api/flows/cancelled` | Скасовані runs. | +| `GET` | `/waterline/api/flows/terminated` | Runs після terminate. | + +Рядки списків є стислими операторськими зведеннями. Сталі поля включають +ідентичність workflow (`id`, `instance_id`, `run_id`, `workflow_type`), +стан (`status`, `closed_reason`, `archived_at`), час, +`history_event_count`, `history_size_bytes`, `continue_as_new_recommended` +та позначки repair/actionability, зокрема `repair_blocked`. + +## Деталі вибраного run {#selected-run-detail} + +Waterline має два режими адресації: + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/instances/{instanceId}` | Поточний вибраний run екземпляра workflow. | +| `GET` | `/waterline/api/instances/{instanceId}/runs/{runId}` | Явно вибраний run. | +| `GET` | `/waterline/api/flows/{id}` | Legacy-пошук деталей за ID рядка Waterline або run. | + +Деталі вибраного run є авторитетним JSON-контрактом операторських екранів. +Вони містять durable-стан та похідну діагностику для: + +| Група полів | Значення | +| --- | --- | +| `activities`, `timers`, `waits`, `children` | Поточний та історичний стан очікування, відновлений передусім із типізованої історії. | +| `signals`, `updates`, `declared_signals`, `declared_updates`, `declared_queries` | Рядки життєвого циклу команд та цілі контракту workflow, які можна завантажити. | +| `timeline` | Упорядковані durable-події та діагностичні записи. | +| `exceptions`, `logs` | Факти помилок і контекст replay/налагодження. | +| `can_signal`, `can_update`, `can_query`, `can_cancel`, `can_terminate`, `can_archive` | Доступні дії UI та автоматизації для вибраного run. | +| `actionability` | Версійований контракт repair та даних, описаний нижче. | + +Автоматизація має віддавати перевагу деталям вибраного run перед screenshots. +Screenshot показує те, що бачив оператор. Деталі вибраного run пояснюють, +чому workflow можна чи не можна repair, query, cancel, archive або replay. + +## Експорт історії {#history-export} + +Exports історії є наборами для replay/налагодження. Вони навмисно містять +збережені payload workflow, команд, activities, updates, tasks, помилок +та історії, щоб розробник міг відтворити або архівувати вибраний run. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/instances/{instanceId}/history-export` | Експорт поточного вибраного run екземпляра. | +| `GET` | `/waterline/api/instances/{instanceId}/runs/{runId}/history-export` | Експорт явно вибраного run. | +| `GET` | `/waterline/api/flows/{id}/history-export` | Legacy-експорт деталей за ID рядка Waterline або run. | + +Якщо export може вийти за межі захищеного середовища, спочатку налаштуйте +redactor експорту історії workflow. Подальші інструменти мають зберігати +metadata `redaction` export, щоб перевіряльник міг визначити +політику обробки артефакту. + +## Контракт actionability {#actionability-contract} + +Waterline додає версійований контракт actionability до рядків списків, +відповідей деталей вибраного run, записів timeline та exports історії: + +```json +{ + "actionability_contract": { + "schema": "waterline.actionability", + "version": 1 + } +} +``` + +Ідентифікатор контракту — +`actionability_contract.schema = waterline.actionability` +із `actionability_contract.version = 1`. + +`actionability` рівня run визначає, чи можна repair вибраний run: + +| Поле | Значення | +| --- | --- | +| `repair_state` | Одне зі значень `repairable`, `blocked`, `not_needed` або `unknown`. | +| `repairable` | Булеве скорочення для `repair_state = repairable`. | +| `blocked_reason` | Сталий код reason, коли `repair_state = blocked`. | +| `status_bucket` | Група Waterline, яка визначила рішення рівня run. | +| `closed_reason` | Durable-причина закриття закритого run. | +| `task_problem` | Чи Waterline виявив проблему task на run. | +| `diagnostic_only_evidence` | True, коли принаймні один рядок є інформаційним і не є джерелом resume. | + +Рядки даних у `activities`, `waits`, `timers`, `exceptions`, `logs` +та записах timeline/export можуть мати власний блок `actionability`: + +| Поле | Значення | +| --- | --- | +| `state` | `actionable`, коли рядок є дійсним джерелом repair, інакше `diagnostic_only`. | +| `repair_source` | True лише для рядків із джерелом, яке дозволяє repair. | +| `diagnostic_only` | True, коли рядок не можна використовувати як джерело resume. | +| `history_authority` | Повноваження джерела, наприклад `typed_history`, `mutable_open_fallback`, `failure_row_fallback` чи `unsupported_terminal_without_history`. | +| `history_unsupported_reason` | Сталий код reason для непідтримуваної fallback-історії. | + +Агенти й scripts мають приймати рішення repair/resume за +`actionability.repair_state`, `actionability.repairable` +та `actionability.repair_source` кожного рядка. +Рядок із `diagnostic_only = true` надає інформацію, +але не дозволяє replay чи resume. + +## Дії оператора {#operator-actions} + +Дії Waterline є durable-командами через контракт вибраного run. +Маршрути instance спрямовані на поточний run. Маршрути run явно +відхиляють застарілі цілі чи цілі іншого run. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `POST` | `/waterline/api/instances/{instanceId}/queries/{query}` | Виконання оголошеного query для поточного run. | +| `POST` | `/waterline/api/instances/{instanceId}/signals/{signal}` | Надсилання signal поточному run. | +| `POST` | `/waterline/api/instances/{instanceId}/updates/{update}` | Подання update поточному run. | +| `POST` | `/waterline/api/instances/{instanceId}/repair` | Dispatch проходу repair поточного run, коли це дозволяє actionability. | +| `POST` | `/waterline/api/instances/{instanceId}/cancel` | Запит скасування поточного run. | +| `POST` | `/waterline/api/instances/{instanceId}/terminate` | Terminate поточного run. | +| `POST` | `/waterline/api/instances/{instanceId}/archive` | Архівування вибраного закритого run. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/queries/{query}` | Виконання query для явно вибраного run. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/signals/{signal}` | Надсилання signal лише за умови, що вибраний run є поточним. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/updates/{update}` | Подання update лише за умови, що вибраний run є поточним. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/repair` | Repair явно вибраного run. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/cancel` | Скасування лише за умови, що вибраний run є поточним. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/terminate` | Terminate лише за умови, що вибраний run є поточним. | +| `POST` | `/waterline/api/instances/{instanceId}/runs/{runId}/archive` | Архівування вибраного закритого run. | + +Signals, updates та queries приймають JSON `arguments`. +Некоректні аргументи повертають помилки перевірки окремих полів. +Блокування replay повертає `409 Conflict` зі сталим reason, +наприклад `workflow_definition_unavailable`, замість спроби +виконання без потрібних гарантій. + +## Огляд update {#update-inspection} + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/instances/{instanceId}/updates/{updateId}` | Огляд рядка життєвого циклу update поточного run. | +| `GET` | `/waterline/api/instances/{instanceId}/runs/{runId}/updates/{updateId}` | Огляд рядка життєвого циклу update явного run. | +| `GET` | `/waterline/api/flows/{id}/updates/{updateId}` | Legacy-пошук деталей update. | + +Використовуйте огляд update, коли UI або агент має пояснити, +чи update прийнято, застосовано, завершено, відхилено, +чи сплив час його очікування або його заблокувала сумісність replay. + +## Schedules {#schedules} + +Маршрути schedules Waterline надають огляд v2 та дії оператора: + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/v2/schedules` | Список schedules поточного namespace. | +| `GET` | `/waterline/api/v2/schedules/{scheduleId}` | Опис одного schedule. | +| `POST` | `/waterline/api/v2/schedules/{scheduleId}/pause` | Призупинення майбутніх запусків. | +| `POST` | `/waterline/api/v2/schedules/{scheduleId}/resume` | Відновлення призупиненого schedule. | +| `POST` | `/waterline/api/v2/schedules/{scheduleId}/trigger` | Негайний запуск. | +| `POST` | `/waterline/api/v2/schedules/{scheduleId}/backfill` | Backfill часового вікна. | +| `DELETE` | `/waterline/api/v2/schedules/{scheduleId}` | Видалення schedule. | + +Відповіді schedule показують action, статус, наступний запуск, +недавню історію запусків, overlap policy, note, memo та search attributes +для перевірки оператором. + +## Збережені подання та налаштування {#saved-views-and-preferences} + +Збережені подання та налаштування є операторською конфігурацією UI. +Вони не є історією workflow, але корисні для сталих runbooks: +команди можуть ділитися точними фільтрами й розташуванням таблиці, +які використовували під час інциденту. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/waterline/api/saved-views` | Список збережених операторських подань. | +| `POST` | `/waterline/api/saved-views` | Створення збереженого подання. | +| `GET` | `/waterline/api/saved-views/{view}` | Читання одного збереженого подання. | +| `PUT` | `/waterline/api/saved-views/{view}` | Зміна збереженого подання. | +| `DELETE` | `/waterline/api/saved-views/{view}` | Видалення збереженого подання. | +| `GET` | `/waterline/api/preferences/{surface}` | Читання налаштувань UI однієї поверхні. | +| `PUT` | `/waterline/api/preferences/{surface}` | Зміна налаштувань UI однієї поверхні. | + +Не використовуйте назви збережених подань чи payload налаштувань +як факти durable workflow. Для підтвердження посилайтеся +на деталі вибраного run або export історії. + +## Контракт помилок {#error-contract} + +Waterline використовує звичайні HTTP status codes і JSON-деталі помилок: + +| Статус | Значення | +| --- | --- | +| `400` | Некоректний запит або непідтримуваний payload дії. | +| `404` | Екземпляр workflow, run, update, schedule, збережене подання чи поверхню налаштувань не знайдено в поточному namespace. | +| `409` | Вибраний run не може виконати дію, часто через історичний чи закритий стан, блокування replay або недоступність визначення. | +| `422` | Перевірка не пройдена. Відповідь містить помилки окремих полів. | +| `500` | Неочікувана помилка застосунку. | + +Автоматизація має розгалужуватися за статусом та сталими полями, +зокрема `blocked_reason`, `query_blocked_reason`, `outcome`, +`reason` і значеннями `actionability`. Не розбирайте текст +сповіщень чи назви кнопок як контракт. + +## Дивіться також {#see-also} + +- [Моніторинг](./monitoring.md) +- [Помилки та відновлення](./failures-and-recovery.md) +- [Queries](./features/queries.md) +- [Скасування та terminate](./features/cancel-and-terminate.md) +- [Контракт інструментів агента](./agent-tooling-contract.md) From 2759d9368420c4b19344d249adb2c556dac2c48f Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 03:34:14 +0000 Subject: [PATCH 07/10] docs: translate Ukrainian adoption and deployment guides --- docs/laravel-adoption.md | 4 +- docs/polyglot/embedded-to-server.md | 9 +- .../current/deployment.md | 659 ++++++++++++++++++ .../current/laravel-adoption.md | 581 +++++++++++++++ .../current/polyglot/embedded-to-server.md | 462 ++++++++++++ .../current/polyglot/rust.md | 565 +++++++++++++++ 6 files changed, 2274 insertions(+), 6 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/deployment.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/laravel-adoption.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/embedded-to-server.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust.md diff --git a/docs/laravel-adoption.md b/docs/laravel-adoption.md index 31b41523da..f11982b3b5 100644 --- a/docs/laravel-adoption.md +++ b/docs/laravel-adoption.md @@ -88,7 +88,7 @@ database, network, clock, and other side effects belong in activities. ### Stable v1 Laravel -Keep the [stable installation](/docs/installation/) and its real queue-driver +Keep the [stable v1 installation](/docs/1.x/installation/) and its real queue-driver requirements. This representative implementation uses a constructor-injected application starter and method-injected activity services: @@ -565,7 +565,7 @@ last step, never the cutover mechanism. ## Continue with the chosen path -- [Stable v1 installation](/docs/installation/) +- [Stable v1 installation](/docs/1.x/installation/) - [Stable v1 migration planning](/docs/migration/) - [2.0 embedded installation](/docs/installation/) - [Detailed v1-to-v2 package migration](/docs/migration/) diff --git a/docs/polyglot/embedded-to-server.md b/docs/polyglot/embedded-to-server.md index 7711555b31..aba89d0fe5 100644 --- a/docs/polyglot/embedded-to-server.md +++ b/docs/polyglot/embedded-to-server.md @@ -42,7 +42,7 @@ Workflow Cloud. For the separate managed-service choice, see Supported today: - Start new workflows through the server control-plane API or CLI. -- Register PHP, Python, or custom HTTP workers against the server. +- Register PHP, Python, Rust, or custom HTTP workers against the server. - Poll and complete workflow and activity tasks through the worker protocol. - Import eligible embedded v2 history-export bundles into the server as server-managed workflow state. @@ -67,7 +67,7 @@ either drain in place or move through the import workflow below. For the frozen embedded-vs-service comparison, see [Deployment Modes](/docs/polyglot/deployment-modes). This migration guide -adds three cutover-specific rules on top of that shared contract: +adds four cutover-specific rules on top of that shared contract: - Existing embedded runs keep executing where they started. - New server-managed runs use stable type keys, namespace names, task queues, @@ -409,12 +409,13 @@ curl -X POST "$SERVER/api/workflows/order-123/archive" \ ## Phase F: Add Polyglot Workers -Once a workflow family runs through the server, add Python or custom workers by +Once a workflow family runs through the server, add Python, Rust, or custom workers by registering the same namespace, task queue, and type keys. Every public v2 payload uses `avro`; keep activity inputs and outputs language-neutral: arrays, objects, strings, numbers, booleans, and nulls. -For Python, use [the Python SDK guide](/docs/polyglot/python). For direct +Use [the Python SDK guide](/docs/polyglot/python) or +[the Rust SDK guide](/docs/polyglot/rust). For direct HTTP implementations, use [the worker protocol reference](/docs/polyglot/worker-protocol). ## Cutover Checklist diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/deployment.md b/i18n/uk/docusaurus-plugin-content-docs/current/deployment.md new file mode 100644 index 0000000000..e01ab42565 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/deployment.md @@ -0,0 +1,659 @@ +--- +sidebar_position: 13 +title: Самостійне розгортання +description: Вибір підтримуваного шляху самостійного розгортання Durable Workflow v2 та початкового артефакту. +--- + +# Самостійне розгортання {#self-hosting-deployments} + +Durable Workflow v2 підтримує кілька форм самостійного розгортання. +Виберіть найменший шлях, який відповідає вашому середовищу, +і явно визначте образ, базу даних, cache, автентифікацію, +readiness та контракт оновлення. + +Ця настанова описує окремий Server. Для Laravel-пакета, +вбудованого у власний застосунок, використовуйте сторінки встановлення +та конфігурації пакета. Durable Workflow Cloud є окремим керованим +сервісом, де Cloud оперує runtime, зберіганням, розміщенням і відновленням. +Власний Server ніколи не підключається до Cloud. +Дивіться [керований Cloud runtime](/docs/polyglot/cloud-control-plane). +Поточний контракт Cloud визначає керований runtime та відновлення +в одному регіоні. Він не обіцяє multi-region replication, +автоматичного regional failover чи failback. + +Власний Server не містить і не оперує Waterline. +Розгортайте Waterline окремо, коли потрібний його операторський UI, +і підключайте до namespace Server за +[настановою розгортання та моніторингу Waterline](/docs/monitoring/#waterline-service). +Cloud включає Managed Waterline для свого керованого namespace. + +## Матриця підтримки розгортань {#deployment-support-matrix} + +| Шлях | Початковий артефакт | Підтримується для | Що цей шлях не обіцяє | Коли потрібна комерційна підтримка | +| --- | --- | --- | --- | --- | +| Локальна розробка та внутрішнє non-production | [`docker-compose.published.yml`](https://github.com/durable-workflow/server/blob/main/docker-compose.published.yml) із `%%artifact.serverTagEnv%%` або `%%artifact.serverImageEnv%%` | Один комп'ютер розробника, LAN demos, спільний staging, інтеграційні тести SDK та workers | Відкритий в інтернет production, гарантії durable backup, сувора ротація секретів, багатовузловий failover | Потрібна допомога з перетворення робочого dev stack на production runbook | +| Одновузловий production | [`docker-compose.published.yml`](https://github.com/durable-workflow/server/blob/main/docker-compose.published.yml) із production env file, volumes MySQL і Redis, токенами ролей, backups, TLS через reverse proxy та закріпленими image tags чи digests | Одна VM, VPS чи внутрішній Docker host із постійним станом workflow і простою операційною моделлю | HA host, автоматичний failover бази, multi-region recovery, великі зміни топології без перерви | Розгортання обслуговує production-трафік і потребує перевірки backup, restore, auth, TLS, upgrade чи rollback | +| Невеликий кластер | Опубліковані образи `durableworkflow/server` чи `ghcr.io/durable-workflow/server`, [Compose recipe](https://github.com/durable-workflow/server/blob/main/docker-compose.published.yml) як шаблон контейнера/процесу, 2–3 API nodes, спільна зовнішня MySQL/PostgreSQL, спільний Redis, незалежно масштабовані workers та рівно один scheduler/maintenance runner | Горизонтальна місткість API та workers, коли одного вузла недостатньо. Rolling upgrades за виконання всіх гарантій [контракту rolling upgrades](/docs/rolling-upgrades) | SQLite clustering, багатовузловий режим без Redis, дублікати schedulers як стала топологія, active/active multi-writer бази, власний regional failover без втручання, широкі SLA «five-nines» чи «zero-downtime» та самостійний HA failover одного регіону до проходження [перевірки випуску](#release-evidence-status) | Потрібне планування місткості, областей відмов, rollout чи recovery між hosts або твердження про умовну поведінку single-region HA | +| Helm chart для Kubernetes | [Helm chart Server](https://github.com/durable-workflow/server/tree/main/k8s/helm/durable-workflow) з `oci://ghcr.io/durable-workflow/charts/durable-workflow` із вашими зовнішніми базою, Redis, ingress та керуванням секретами | Відтворюване production-встановлення й оновлення ресурсів chart: server, worker, singleton scheduler, bootstrap, service, probes та policies | Вбудоване сховище, provider-managed інфраструктура, active/active multi-region, custom operators та самостійний single-region HA failover до проходження [перевірки випуску](#release-evidence-status) | Потрібні архітектура конкретного provider, capacity, recovery чи зміни поза опублікованим контрактом values chart | +| Raw Kubernetes manifests | Manifests Server [`k8s/`](https://github.com/durable-workflow/server/tree/main/k8s) з опублікованими образами Server та вашими базою, Redis, ingress і керуванням секретами | Команди з досвідом Kubernetes, яким потрібні відкриті для перевірки manifests API, worker, scheduler, bootstrap, service, probes, config та secrets | Окремий життєвий цикл Helm, перевірка managed-Kubernetes provider, active/active multi-region, custom operators, рішення сховища/мережі/безпеки середовища та самостійний single-region HA failover до проходження [перевірки випуску](#release-evidence-status) | Потрібні overlays, перевірка managed cluster, production-планування provider чи твердження про умовну поведінку single-region HA | +| Оцінювання active/passive multi-region | Перевірене одновузлове чи невелике кластерне розгортання на регіон, асинхронна реплікація бази з active до standby та перевірений failover/failback runbook | Архітектурне оцінювання з підтримкою та репетиція disaster recovery регіону для конкретного середовища | Доведений самостійний контракт 2.0, active/active multi-region, автоматичний regional failover без втручання, синхронна реплікація між регіонами (RPO=0), активна міжрегіональна видимість чи federated search, прив'язані до регіону task queues як вісь маршрутизації рушія | Перед production-залежністю від міжрегіональної реплікації, failover, failback, RPO чи RTO | +| Топології з підтримкою | Перевірений дизайн вашого середовища | Власні active/passive чи active/active multi-region, regional failover без втручання, RPO=0 cross-writer replication, дублікати scheduler runners як стала топологія, спеціальна безпека/мережа, приватні SLOs, custom overlays, планування міграції | Самостійний запуск копіюванням прикладу | Коли сама топологія є частиною ризику продукту | + +Публічний дистрибутив оптимізований для локальної розробки, +одновузлового production та невеликих кластерів. Kubernetes manifests +надано командам, які вже оперують Kubernetes. +Матеріал active/passive multi-region доступний як оцінювальна настанова +з підтримкою, а не доведений самостійний контракт 2.0. +Дивіться [active/passive multi-region](#activepassive-multi-region) нижче. +Single-region HA failover: failover керованої бази, керованого Redis, +втрата API node чи worker та restart scheduler runner у межах регіону, +зараз потребує підтримки, поки перевірка точного випуску не пройдена. +Дивіться [single-region HA та failover](#single-region-high-availability-and-failover). +Для власного Server active/active multi-region, автоматичний regional failover, +дублікати scheduler runners як стала топологія та перевірка managed-Kubernetes +provider залишаються шляхами з підтримкою через залежність від бази, cache, +мережі, безпеки, runner та upgrade. Опублікований Helm chart надає +самостійне пакування в тих самих межах відповідальності оператора. +Cloud зараз надає керовану роботу та recovery в одному регіоні, +без multi-region replication чи regional failover. +Дивіться [керований Cloud runtime](/docs/polyglot/cloud-control-plane) +та [межу підтримки](/docs/support) для комерційної моделі. + +## Матеріали production-відновлення {#production-recovery-deliverables} + +«Готовність до production» означає публікацію набору відновлення +для топології разом із запуском контейнерів. Зберігайте актуальні +підтвердження в тому самому runbook, що й команди розгортання: + +| Шлях | Мінімальний опублікований набір відновлення | +| --- | --- | +| Одновузловий production | Розклад backup, закріплений image чи digest, місце snapshot env/config, максимальна прийнятна затримка restore та timestamp останньої успішної репетиції restore з даними перевірки. | +| Невеликий кластер | Одновузловий набір та очікуваний вплив втрати одного API node, worker node, scheduler/maintenance runner, Redis або спільної бази. Кроки повторної реєстрації worker після restore та документований rolling-upgrade чи stop-the-world режим поточного випуску. | +| Helm chart для Kubernetes | Кластерний набір, версія chart, редакція values, image digest, власники ingress і secrets кластера та остання репетиція upgrade/rollback chart. | +| Raw Kubernetes manifests | Кластерний набір та власники storage, secrets, ingress і rollout кластера, які потрібно відновити або повторно застосувати перед визнанням трафіку здоровим. | +| Оцінювання single-region HA з підтримкою до проходження перевірки випуску | Кластерний чи raw-manifest набір та репетиції failover керованої бази, керованого Redis, втрати API node, worker і restart scheduler runner. Кожна має завершитися в межах цілі [single-region HA контракту](#single-region-high-availability-and-failover) без втрати підтверджених записів. | +| Оцінювання active/passive multi-region з підтримкою | Набір кожного регіону, дизайн реплікації бази та fencing, виміряна затримка реплікації, RPO/RTO оператора, runbooks promotion і failback та репетиції конкретного середовища. | + +Якщо цей набір неможливо надати на вимогу, вважайте середовище staging, +доки контракт відновлення не записаний і не відрепетируваний. +[Операторські межі експлуатації](/docs/operator-operating-envelope) +визначають порядок restore, перевірку та періодичність репетицій цих наборів. + +## Безпека, дані та аудит {#security-data-and-audit-posture} + +Власні розгортання Durable Workflow успадковують більшість механізмів +безпеки з вашого середовища. Публікуйте ці факти в тому самому наборі +випуску чи runbook, що й image tag, план міграції та підтвердження recovery: + +| Область | Точне твердження випуску | +| --- | --- | +| Обробка даних | Аргументи workflow, результати, історія, memos, search attributes, мітки видимості, контекст команд, рядки аудиту, повідомлення винятків та операторські notes можуть містити дані застосунку клієнта. Search attributes і мітки є видимими оператору metadata, а не сховищем секретів. | +| Шифрування | Використовуйте TLS на кожній production HTTP-поверхні. Шифрування at rest надають база, object storage, файлова система, черга, cache та secret manager. Пакет workflow і Server не шифрують автоматично кожне поле payload. | +| Compliance | Open-source пакет та власний Server надають механізми й дані аудиту, а не саму compliance-сертифікацію. Твердження SOC 2, HIPAA, PCI, ISO чи FedRAMP належать вашій програмі, якщо hosted offering не документує інше. | +| Логи аудиту | Команди workflow, audit events schedules, metadata exports історії та записи service calls надають durable-операційні дані. Повний SIEM, DLP, незмінний зовнішній ledger чи legal-hold system потребує додаткових компонентів. | +| Підтримка | Тут документовано credentials ролей, TLS termination, backups, репетиції restore та вузькі самостійні топології. Advanced identity, mTLS rollout, приватна мережа, custom policy engines, compliance provider та окрема перевірка топології потребують підтримки, якщо публічні docs не визначають інше. | + +Мережева модель має бути явною: + +- **Webhook ingress:** документуйте публічний endpoint, метод auth, + replay або стратегію ідемпотентності, timeout, обмеження payload, + конфігурацію довірених proxy headers і план ротації секретів. +- **Worker-to-backend трафік:** використовуйте перевірку TLS, credentials ролі + worker, заголовки namespace, приватну мережу чи mTLS за недовіреної мережі + та ротацію, що не надає токенам worker повноважень operator. +- **Операторські поверхні:** захищайте Waterline, операторські API Server, + endpoints автоматизації CLI та власні admin panels автентифікованими + sessions чи service credentials ролей, із CSRF-захистом browser sessions + та документованими межами proxy/TLS. + +## Опубліковані образи {#published-images} + +Для власних розгортань Server використовуйте опубліковані образи: + +- Docker Hub: `%%artifact.serverDockerHubImage%%` +- GitHub Container Registry: `%%artifact.serverGhcrImage%%` +- Закріплення digest: `durableworkflow/server@sha256:...` або + `ghcr.io/durable-workflow/server@sha256:...` + +Використовуйте mutable tags лише для локальних експериментів. +Production env files мають закріплювати конкретну version tag чи digest, +щоб кроки upgrade і rollback можна було перевірити. + +## Локальна розробка та внутрішнє non-production {#local-development-and-internal-non-production} + +Використовуйте Compose recipe опублікованого образу для stack +без source code з MySQL та Redis: + +```bash +curl -fsSLO https://raw.githubusercontent.com/durable-workflow/server/main/docker-compose.published.yml + +export %%artifact.serverTagEnv%% +export DW_AUTH_TOKEN=dev-token + +docker compose -f docker-compose.published.yml up -d --wait +``` + +Перевірте API, readiness, discovery кластера та реєстрацію worker: + +```bash +curl http://localhost:8080/api/health +curl http://localhost:8080/api/ready +curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \ + http://localhost:8080/api/cluster/info + +curl -X POST http://localhost:8080/api/worker/register \ + -H "Authorization: Bearer $DW_AUTH_TOKEN" \ + -H "Content-Type: application/json" \ + -H "X-Namespace: default" \ + -H "X-Durable-Workflow-Protocol-Version: 1.0" \ + -d '{"worker_id":"compose-worker","task_queue":"compose","runtime":"python"}' +``` + +Цей шлях придатний для розробки та внутрішнього staging. +Він не є межею production-безпеки: приклад використовує один токен +сумісності, стандартні passwords сервісів, локальні named volumes і не має TLS. + +## Одновузловий production {#single-node-production} + +Використовуйте той самий Compose-артефакт із production-конфігурацією +поза source control: + +```env +%%artifact.serverImageEnv%% +SERVER_PORT=8080 +APP_ENV=production +APP_DEBUG=false + +DB_DATABASE=durable_workflow +DB_USERNAME=workflow +DB_PASSWORD=replace-with-random-password +DB_ROOT_PASSWORD=replace-with-random-root-password + +DW_AUTH_DRIVER=token +DW_AUTH_BACKWARD_COMPATIBLE=false +DW_WORKER_TOKEN=replace-with-worker-token +DW_OPERATOR_TOKEN=replace-with-operator-token +DW_ADMIN_TOKEN=replace-with-admin-token +``` + +Запустіть із цим env file: + +```bash +docker compose --env-file durable-workflow.prod.env \ + -f docker-compose.published.yml up -d --wait +``` + +Оперуйте host як production-сервісом: + +- Розмістіть TLS, public routing, логування запитів та IP allow lists + у reverse proxy перед контейнером API. +- Не відкривайте MySQL чи Redis публічно. +- Використовуйте `DW_WORKER_TOKEN` для workers, `DW_OPERATOR_TOKEN` + для застосунку та операторського трафіку, `DW_ADMIN_TOKEN` + для namespaces та адміністрування. +- Резервуйте MySQL volume перед кожним image upgrade та регулярно. + Redis слід зберігати для graceful restarts, але MySQL залишається + durable-джерелом істини історії workflow. +- Зберігайте точний env file, image tag чи digest та backup бази разом для restore. + +Порядок upgrade: + +1. Створіть backup MySQL і запишіть поточний image reference. +2. Змініть лише `DW_SERVER_IMAGE` або `DW_SERVER_TAG`. +3. Завантажте новий image. +4. Виконайте `docker compose --env-file durable-workflow.prod.env -f docker-compose.published.yml up -d --wait`. +5. Перевірте `/api/ready`, `/api/cluster/info` та реєстрацію worker + перед переведенням зовнішнього трафіку. + +README Server підтримує актуальні Compose-команди в розділі +[Official Image + Compose](https://github.com/durable-workflow/server#official-image--compose). + +## Невеликі кластерні розгортання {#small-clustered-deployments} + +Невеликий кластер трохи розширює одновузлову модель. +Перевірений самостійний контракт навмисно вузький: + +- Запускайте 2–3 stateless API-контейнери за load balancer. + Health, readiness, discovery кластера, реєстрація worker, polling workflow tasks + та completion workflow tasks мають працювати без sticky sessions. +- Використовуйте одну спільну зовнішню MySQL чи PostgreSQL для durable-історії. + SQLite підтримує лише один вузол і не є backend кластерного зберігання. +- Використовуйте спільний Redis для cache, long-poll wake signals, locks + query-task queues, locks приймання task queue та стану черг. + Багатовузловий режим без Redis не входить до підтримуваного контракту. +- Перевіряйте `GET /api/cluster/info` кожного API node під час rollout. + `topology.current_shape` має залишатися `standalone_server`, + `topology.current_roles` — містити `api_ingress`, `control_plane`, + `matching` і `history_projection`, `topology.execution_mode` — + залишатися `remote_worker_protocol` для вузлів окремого Server. + Перевіряйте фактичний matching через `topology.matching_role`. + Стандартні вузли повідомляють `queue_wake_enabled: true`, `shape: "in_worker"` + та `wake_owner: "worker_loop"`. Rollout dedicated matching перемикає вузли + з `DW_V2_MATCHING_ROLE_QUEUE_WAKE=0` на `queue_wake_enabled: false`, + `shape: "dedicated"` і `wake_owner: "dedicated_repair_pass"`. + Той самий блок має зберігати очікуваний `task_dispatch_mode`, закріплені + осі маршрутизації `partition_primitives` та поточний `backpressure_model`. + Значення кожного поля описано в + [топології ролей Server](/docs/polyglot/server-role-topology). +- Масштабуйте зовнішні SDK workers незалежно від API nodes. + Workers можуть працювати на окремих hosts чи процесах, але мають + підключатися до load-balanced API endpoint, а не одного sticky node. +- Налаштуйте [приймання task queue](/docs/polyglot/task-queue-admission) + для черг, які захищають tenant, зовнішній API, database pool + або іншу спільну downstream-залежність. +- Запускайте рівно один scheduler чи maintenance process для schedules, + застосування activity timeouts та pruning історії. +- Виконуйте bootstrap/migrations один раз на rollout перед прийманням + трафіку новими API та worker containers. +- Виберіть режим rollout випуску: stop-the-world (drain workers, + зупинка scheduler/maintenance, заміна API nodes, bootstrap/migrations, + restart workers і scheduler) або [rolling upgrades](/docs/rolling-upgrades), + коли діє кожна гарантія цього контракту. +- Розглядайте базу та Redis як основні області відмов. + Контейнери Server можна замінити. Persistence і coordination — ні. + +Опубліковані самостійні recipes починаються з форми `standalone_server`, +хоча discovery також називає `split_control_execution` підтримуваною +топологією продукту. Це один контракт із різними призначеннями ролей. +Під час подальшої проби явнішого поділу ролей продовжуйте читати +`topology.current_shape`, `topology.current_roles` і `topology.matching_role` +з `/api/cluster/info`, а не виводити обов'язки з hostname чи назви контейнера. +[Топологія ролей Server](/docs/polyglot/server-role-topology) +пояснює призначення ролей та шлях міграції. + +Кожен API node має використовувати однакові auth tokens чи signature keys, +версію застосунку, версію пакета workflow, конфігурацію codec payload, +підключення бази та Redis. Задайте кожному API node унікальний +`DW_SERVER_ID`, щоб discovery і логи могли розрізняти вузли. + +Непідтримувані межі визначено явно: SQLite clustering, багатовузловий режим +без Redis, дублікати schedulers як стала топологія, active/active multi-writer +бази, власний regional failover без втручання та широкі SLA «five-nines» +чи «zero-downtime» потребують окремої перевірки або дизайну з підтримкою +перед використанням. Active/passive multi-region також є оцінювальним +матеріалом із підтримкою. Дивіться +[active/passive multi-region](#activepassive-multi-region). +Single-region HA failover: failover керованої бази, керованого Redis, +втрата API node, worker та restart scheduler runner в одному регіоні, +залишається оцінювальним контрактом із підтримкою до проходження перевірки +випуску. Цілі recovery рушія, readiness під час failover та потрібні +підтвердження наведено в +[single-region HA та failover](#single-region-high-availability-and-failover). + +Цей шлях самостійний, коли команда вже має чітку модель VM, мережі, +бази, cache, backup і load balancer. Підтримка потрібна, коли слід +визначити ці межі, capacity, порядок rollout чи процедури recovery. + +## Helm chart для Kubernetes {#helm-chart-for-kubernetes} + +Репозиторій Server володіє +[кодом chart та настановами випуску](https://github.com/durable-workflow/server/tree/main/k8s/helm/durable-workflow). +Установіть опублікований chart з OCI registry: + +```bash +helm install durable-workflow \ + oci://ghcr.io/durable-workflow/charts/durable-workflow \ + --namespace durable-workflow --create-namespace \ + -f my-values.yaml +``` + +Values file має вказувати зовнішні MySQL/PostgreSQL і Redis, +надавати production credentials через наявні Kubernetes Secrets +та налаштовувати ingress/TLS вашого середовища. +Chart не створює сховище чи provider-managed інфраструктуру. +Для відтворюваного production додайте `--version ` +і закріпіть образ Server за digest. Репозиторій Server публікує +версії chart та документує upgrades поруч із кодом chart. + +## Kubernetes manifests {#kubernetes-manifests} + +Репозиторій Server містить raw manifests під +[`k8s/`](https://github.com/durable-workflow/server/tree/main/k8s) +для команд, які вже оперують Kubernetes: + +- Namespace та спільні labels +- Поділ ConfigMap і Secret +- Bootstrap/migration Job +- API Deployment і Service +- Worker Deployment +- Scheduler CronJob +- PodDisruptionBudget +- Liveness probe `/api/health` та readiness probe `/api/ready` +- Консервативні resource requests і limits + +Перед застосуванням manifests замініть image tag на конкретну +опубліковану версію чи digest, надайте справжні credentials бази та Redis +і підключіть ConfigMap до сервісів вашого кластера. +Manifests навмисно відкриті для перевірки та не є Helm chart. +Вони не обіцяють універсальної поведінки managed Kubernetes. + +Для production rollout Kubernetes перевірте щонайменше: + +```bash +kubectl -n durable-workflow wait --for=condition=complete job/durable-workflow-migrate --timeout=180s +kubectl -n durable-workflow rollout status deploy/durable-workflow-server +kubectl -n durable-workflow rollout status deploy/durable-workflow-worker +kubectl -n durable-workflow port-forward svc/durable-workflow-server 8080:8080 +curl http://localhost:8080/api/ready +curl -H "Authorization: Bearer $DW_ADMIN_TOKEN" http://localhost:8080/api/cluster/info +``` + +Single-region HA failover: failover керованої бази, керованого Redis, +втрата API node, worker та restart scheduler runner в одному регіоні, +на raw-manifest шляху потребує підтримки, як і на малому кластері, +поки перевірка випуску не пройдена. Передбачений контракт потребує +правил readiness, singleton scheduler та спільного substrate у +[single-region HA та failover](#single-region-high-availability-and-failover). +Load balancers provider, storage classes, network policies, +active/active multi-region та власний regional failover без втручання +потребують підтримки або окремо відстежуються поза raw-manifest контрактом. +Для встановлення та upgrades через Helm використовуйте окремо +версійований Helm-шлях вище. + +## Single-region HA та failover {#single-region-high-availability-and-failover} + +Single-region HA failover зараз є **неперевіреним кандидатом контракту +з підтримкою** поверх невеликого кластера та raw Kubernetes. +Він охоплює відмови, які рушій розрахований витримати **в одному регіоні**: +failover керованої бази (RDS Multi-AZ, Aurora cluster failover, Cloud SQL HA, +Patroni promotion тощо), керованого Redis (Sentinel, Elasticache +replication-group failover, Memorystore HA тощо), втрату API node, +worker та restart scheduler/maintenance runner. +Active/passive recovery між регіонами є окремим оцінюванням із підтримкою. +Дивіться [active/passive multi-region](#activepassive-multi-region). + +Передбачений контракт: поведінка рушія, readiness, цілі recovery +кожної події, запобігання split brain та приймальна репетиція, +зберігається в бібліотеці workflow: +[`docs/deployment/ha-failover.md`](https://github.com/durable-workflow/workflow/blob/main/docs/deployment/ha-failover.md) +та в окремому Server: +[`docs/ha-failover-validation.md`](https://github.com/durable-workflow/server/blob/main/docs/ha-failover-validation.md). +Цей розділ є публічною поверхнею цих документів. + +### Стан підтвердження випуску {#release-evidence-status} + +Для документованого публічного випуску Server немає посилання +на успішний результат точного випуску. Записана спроба опублікованого +образу зупинилася під час `topology_start`, очікуючи readiness API, +до підтвердження матриці відмов чи меж recovery. +Наявність runner, його scenario manifest чи репетиція окремого +розгортання не доводять проходження повної матриці опублікованим образом. +Тому single-region HA залишається шляхом із підтримкою. + +Самостійний контракт дозволений лише після запуску без source code +з точним документованим випуском Server для image, runner та Compose-топології +і публікації `single-region-failover-result.json` із `outcome: "pass"`, +`runner_blocked: false`, `status: "pass"` кожного запису `phase_outcomes`, +успішними вердиктами всіх меж recovery та даними кожного обов'язкового +сценарію публічного +[scenario manifest single-region failover](/platform-conformance/single-region-failover-scenarios.json). +Посилання на цей публічний успішний результат має бути в цьому розділі +з тим самим образом Server, що й `%%artifact.serverDockerHubImage%%`. +До виконання всіх умов межі recovery нижче є цілями оцінювання, +а не твердженнями перевірки опублікованого образу. + +### Кандидат топології {#candidate-topology} + +Передбачений HA-контракт діє, коли розгортання відповідає формі +невеликого кластера чи raw manifests, а оператор зберігає три правила: + +- **Завжди один writable endpoint бази workflow.** Керований failover + (RDS Multi-AZ, Aurora cluster failover, Cloud SQL HA, Patroni тощо) + дозволений, якщо попередній primary відгороджений через fencing + до повторного підключення: відкликання write user, переведення + в `read_only=on`, розрив replication чи restore відомого справного snapshot. + Connection proxy (RDS Proxy, ProxySQL, PgBouncer) між API/scheduler + та базою дозволений. Він не змінює гарантій, бо контракт рушія + визначається підключенням, яке він бачить. +- **Один Redis endpoint одночасно з документованим promotion.** + Керований failover Redis (Sentinel, Elasticache replication-group failover, + Memorystore HA тощо) дозволений. Redis є шаром прискорення, + тому failover Redis впливає на latency та не змінює correctness. +- **Завжди один scheduler/maintenance runner.** Orchestrator + (Compose service з `deploy.replicas: 1`, systemd unit під host lease + чи Kubernetes `Deployment` із `replicas: 1` та `RollingUpdate.maxSurge: 0`) + відповідає за збереження singleton під час restart. + Дублікати scheduler runners як стала топологія не входять до контракту. + +### Поведінка та цілі recovery кожної події {#per-event-behavior-and-recovery-targets} + +Передбачений контракт рушія визначає обмежені цілі recovery +для кожного класу подій. Вони не є гарантіями опублікованого образу +до проходження перевірки випуску. Спостережений оператором wall-clock recovery +дорівнює цілі рушія плюс latency promotion самого керованого сервісу. + +| Подія | Передбачена поведінка рушія | Ціль recovery після повернення substrate / runner | +| --- | --- | --- | +| Failover керованої бази | Writes і reads повертають помилки. Writes не буферизуються приховано. `/api/ready` не проходить на всіх API nodes та scheduler. Підтверджена робота не втрачається. | Одне перепідключення connection pool, один інтервал `task_repair` (стандартно 3 с) та один timeout long poll (стандартно 30 с, максимум 60 с) для активних pollers. | +| Failover керованого Redis | Втрачені wake signals переводять discovery на timeout long poll. Health checks прискорення стають **warning**, а не error. `/api/ready` зазвичай залишається успішним. | Інтервал reconnect Redis client. | +| Втрата API node (1 з N) | Load balancer прибирає несправний вузол у межах readiness interval. Активні запити до нього не проходять у LB і повторюються клієнтом. | Readiness interval load balancer, контрольований оператором, зазвичай 5–10 с. | +| Втрата worker | Tasks несправного worker чекають до спливу lease. Інші workers продовжують claim своїх tasks. | Сплив lease (5 хв для activity tasks) плюс один інтервал `task_repair`. | +| Restart scheduler/maintenance | Запуски schedules, застосування activity timeouts та pruning історії призупиняються. Усі три відновлюються на наступному tick після restart. Дублікатів запуску немає, бо runner singleton. | Restart latency orchestrator плюс один tick scheduler. | + +### Правила load balancer, readiness та переведення трафіку {#load-balancer-readiness-and-traffic-shift-rules} + +Load balancer перед API nodes є єдиною точкою рішення +про приймання трафіку під час failover: + +- Підключіть load balancer до **`GET /api/ready`**, а не лише `/api/health`. + `/api/health` доводить обслуговування HTTP процесом. `/api/ready` + доводить доступ Server до durable-бази та повідомляє здоровий + чи погіршений стан прискорення wake Redis. За відмови бази `/api/ready` + правильно не проходить на всіх вузлах. Load balancer МАЄ приймати + стан all-down без повернення до застарілого «останнього справного» списку. +- Задайте інтервал перевірки 5–10 секунд та поріг видалення + 2–3 послідовних невдалих перевірок. +- Не вимагайте sticky sessions. Small-cluster smoke доводить, + що зовнішній worker може poll `server-a` і complete на `server-b`. +- За failover лише Redis readiness залишається успішним на кожному вузлі, + поки database-backed durable-шляхи доступні. Погіршення прискорення + проявляється як `checks.cache.status=warning` з + `checks.cache.degraded_capability=long_poll_wake_acceleration`. + Не прибирайте вузли load balancer через цей warning. + +Після recovery substrate рекомендовано дочекатися HTTP 200 від +`/api/ready` хоча б одного вузла, запитати `/api/cluster/info` +через load balancer з admin token для перевірки topology manifest, +виконати `POST /api/worker/register` probe worker через load-balanced +endpoint, перевірити рівно один активний scheduler runner +у його orchestrator та відновити зовнішній трафік. + +### Репетиція точного артефакту {#run-the-exact-artifact-rehearsal} + +Випуск Server містить багаторазову базову репетицію повної матриці відмов +без build продукту з checkout. Runner і Compose-топологія мають походити +з того самого release tag, що й image Server. Не поєднуйте мінливий checkout +стандартної гілки з довільним image. На чистому host з Docker Engine, +Docker Compose v2, Python 3.11 або новішим та доступом до публічних +реєстрів виконайте: + + +```bash +export DW_SERVER_RELEASE=%%artifact.serverVersion%% +git clone --depth 1 --single-branch --branch "$DW_SERVER_RELEASE" \ + https://github.com/durable-workflow/server.git "server-$DW_SERVER_RELEASE" +cd "server-$DW_SERVER_RELEASE" +export DW_SERVER_IMAGE="durableworkflow/server:$DW_SERVER_RELEASE" +scripts/conformance/single-region-failover-published-artifacts.sh \ + --result-dir ./failover-result +``` + +Цей шлях навмисно визначає checkout та `DW_SERVER_IMAGE` за однією +release variable. Не перевизначайте image незалежно. +Вибирайте інший випуск зміною `DW_SERVER_RELEASE` перед clone. +Runner потребує конкретного публічного tag чи digest Server, +завантажує всі допоміжні images, визначає repository digests усіх runtime images +і відхиляє Compose build sections, product-source bind mounts та локальні +або rolling references Server. Він запускає рівно два API nodes +за одним nginx endpoint, одну MySQL, один Redis +та один scheduler/maintenance runner. + +Отриманий `single-region-failover-result.json` використовує schema +`durable-workflow.v2.single-region-failover.result`. +Він записує точні версії артефактів та інструментів, нормалізовану +топологію, readiness transitions, виміряні recovery times і вердикти меж, +ідентичності workflow/run/task/schedule та перевірки дублікатів/втрат +для cross-node completion, втрати API node, interruption бази, +Redis, втрати lease worker та restart singleton scheduler. +Зовнішні runners знаходять виклик і публічний scenario manifest +у `single_region_failover_contract` відповіді `GET /api/cluster/info`. + +Результат підтверджує видимі рушію interruption та recovery +опублікованого образу лише після проходження вимог випуску вище. +Невдалий, частковий чи runner-blocked результат є діагностикою. +Навіть успішна база не перетворює restart локального контейнера +MySQL чи Redis на підтвердження promotion cloud provider. +Зберігайте provider-native promotion, fencing, RPO та elapsed-time дані +поруч із базовим результатом перед твердженнями про managed-service HA. + +### Доповнення набору відновлення {#recovery-packet-additions} + +Невеликий кластер чи raw-manifest розгортання, яке заявляє цей контракт, +МАЄ доповнити набір відновлення за +[операторськими межами експлуатації](/docs/operator-operating-envelope) +репетиціями кожного класу подій: + +- failover керованої бази без втрати підтверджених записів + та в межах recovery time вище; +- failover керованого Redis без коливань ротації load balancer, + без втрати підтвердженої роботи та з `checks.cache.status=warning` + і degraded capability `long_poll_wake_acceleration`; +- втрату API node, яку load balancer поглинає в налаштованому + readiness interval без втрати підтверджених записів; +- втрату worker зі збереженням durable run до спливу lease, + повторним claim у налаштованій межі repair та рівно одним completion; +- restart scheduler runner на іншому host без повторних schedules + і без schedule, що залишився неоціненим після `next_fire_at` плюс один tick. + +Поки перевірка випуску не пройдена, кожне розгортання потребує підтримки, +навіть якщо власна репетиція успішна. Після публічного успішного результату +всіх фаз розгортання стає самостійним за цим контрактом лише після +запису репетиції конкретного середовища в набір відновлення оператора +та її оновлення за періодичністю операторських меж експлуатації. + +### Межа непідтримуваних тверджень HA {#boundary-against-unsupported-ha-claims} + +Single-region HA контракт навмисно вузький. +Таке залишається **поза** ним і потребує дизайну з підтримкою, +бо сама топологія є частиною ризику продукту: + +- active/active multi-writer топології баз; +- active/passive multi-region та автоматичний regional failover + без втручання для власних топологій. + [Наступний розділ](#activepassive-multi-region) зберігає оцінювальну настанову; +- синхронна міжрегіональна реплікація бази (RPO=0); +- дублікати scheduler/maintenance runners як стала топологія; +- прив'язані до регіону task queues як вісь маршрутизації рушія; +- перевірка managed-Kubernetes provider поза контрактом пакування chart; +- широкі SLA «five-nines» чи «zero-downtime» поза обмеженими recovery times вище. + +Контракт визначає *обмежене recovery під час названих подій*. +Uptime залежить від бази, мережі та orchestrator оператора. +Маркетингові чи SLA-твердження власних розгортань НЕ МОЖУТЬ +переходити цю межу без окремої перевірки. + +## Active/passive multi-region {#activepassive-multi-region} + +Active/passive multi-region є **оцінювальною настановою з підтримкою**, +а не доведеним самостійним контрактом підтримуваних меж експлуатації 2.0. +Використовуйте цей розділ для перевірки кандидата архітектури та створення +репетиції конкретного середовища з підтримкою перед production. +Кожен регіон починається з документованого одновузлового, невеликого +кластерного чи raw Kubernetes шляху, але їхні single-region контракти +не встановлюють міжрегіональну replication, failover, failback, RPO, RTO +чи split-brain поведінку. Глибший дизайн: повноваження даних, +припущення replication, поведінка namespace/task queue/worker, +runbook failover/failback, fencing та компроміси consistency/latency, +міститься в бібліотеці workflow: +[`docs/deployment/multi-region.md`](https://github.com/durable-workflow/workflow/blob/main/docs/deployment/multi-region.md) +та в окремому Server: +[`docs/multi-region-validation.md`](https://github.com/durable-workflow/server/blob/main/docs/multi-region-validation.md). + +Кандидат форми для оцінювання: + +- Один **active region** із перевіреним одновузловим чи невеликим + кластерним контрактом: API containers за load balancer, спільна + зовнішня MySQL чи PostgreSQL як writable durable-база, спільний Redis, + рівно один scheduler/maintenance runner та зовнішні workers. +- Один **standby region** з асинхронно реплікованою standby-базою workflow, + необов'язковим standby Redis, без scheduler/maintenance process + і з нулем чи більше заздалегідь підготовлених API/worker containers, + які простоюють до promotion. +- **Regional failover** є явною роботою оператора: зупинка write traffic + несправного регіону, перевірка replication за опублікованим RPO, + promotion standby-бази, потрібні випуску migrations нового primary, + старт singleton scheduler/maintenance runner нового active region, + зміна endpoints workers, переведення зовнішнього трафіку та відновлення + похідних projections. Автоматичного міжрегіонального cutover немає. +- **Failback** виконує ту саму послідовність у зворотному порядку після + відновлення початкового регіону. Відновлений primary відгороджується + до підключення як standby: відкликання write user, `read_only=on`, + розрив replication чи restore відомого справного snapshot. + +Повноваження даних та припущення replication: + +- База workflow є єдиним durable-джерелом істини, прив'язаним до регіону. + У будь-який момент записує рівно один регіон. База standby region + залишається read replica до promotion. Recovery point objective (RPO) + дорівнює затримці асинхронної replication, а recovery time objective (RTO) — + часу виконання runbook оператором. +- Redis є локальним прискоренням регіону. Wake signals, locks query-task queues + та admission locks не переносяться між регіонами. Кожен регіон запускає + власний Redis. Cache standby може бути cold або warm за рішенням + оператора. Correctness не залежить від його збереження під час failover. +- Огляд стану читає базу active region. Спочатку promotion, потім читання. + +Поведінка namespace, task queue та реєстрації worker: + +- Namespaces і task queues зберігаються в базі workflow та переживають + promotion точно в стані останнього реплікованого commit. + Рушій не поділяє їх за регіонами. +- Workers нового active region реєструються на локальному API endpoint + після promotion. Попередні реєстрації несправного регіону залишаються + в базі й спливають через звичайний шлях expiry worker. +- Rollouts build ID та deployment-lifecycle state переживають failover, + оскільки зберігаються в базі workflow. + +Компроміси consistency та latency у сталому стані: + +- Запуски workflow, signals і updates фіксуються в базі active region. + Їхня latency відповідає commit latency цього регіону. + Вони відхиляються під час відкликання повноважень у failover. +- Доставка workflow tasks та activity tasks дотримується single-region + контракту прискорення active region: менше секунди за здорового Redis, + інакше durable poll cadence. +- Schedules запускає singleton scheduler active region. + Вони призупиняються без scheduler і відновлюються з durable-рядків + schedule після promotion. +- Огляд стану має read-after-write лише в active region. + Рушій не надає міжрегіонального read-your-writes чи RPO=0. + +Цей кандидат disaster recovery не замінює backups та підтвердження. +Набір відновлення за +[операторськими межами експлуатації](/docs/operator-operating-envelope) +залишається обов'язковим. Доповніть його replication-lag SLO, +latency promotion runbook, датою останньої успішної failover-репетиції +та fencing-процедурою відновленого primary. + +Для власних розгортань active/passive та active/active multi-region, +автоматичний regional failover, синхронна міжрегіональна replication (RPO=0), +активна міжрегіональна видимість та region-pinned task queues +як вісь маршрутизації рушія залишаються +[шляхами з підтримкою](/docs/support), бо топологія є частиною ризику продукту. +Поточний [контракт керованого Cloud runtime](/docs/polyglot/cloud-control-plane) +є single-region і не надає цих multi-region гарантій. + +## Контракт readiness {#readiness-contract} + +Використовуйте перевірки і health, і readiness: + +- `GET /api/health` доводить обслуговування HTTP процесом. +- `GET /api/ready` доводить доступ Server до налаштованих runtime-залежностей, + включно з migrations і готовністю стандартного namespace. +- `GET /api/cluster/info` доводить, що автентифікований клієнт може знайти + ідентичність build, протокол керування, worker-протокол, codecs payload + та capabilities Server. +- `POST /api/worker/register` доводить можливість автентифікації workers + в очікувані namespace та task queue. + +Не переводьте трафік лише за `/api/health`. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/laravel-adoption.md b/i18n/uk/docusaurus-plugin-content-docs/current/laravel-adoption.md new file mode 100644 index 0000000000..0c0ffd0bbe --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/laravel-adoption.md @@ -0,0 +1,581 @@ +--- +sidebar_position: 4 +title: Інтеграція Laravel та перехід між runtime +description: Перехід Laravel workflow fleet із v1 до embedded або service mode Durable Workflow 2.0 зі збереженням інтеграції фреймворку та чіткої відповідальності runtime. +tags: + - laravel + - migration + - service-mode + - embedded +keywords: + - міграція Laravel Durable Workflow + - Laravel service mode + - міграція з v1 до v2 + - embedded to service mode +--- + +# Інтеграція Laravel та перехід між runtime {#laravel-adoption-and-runtime-transition} + +Laravel-застосунки мають два офіційні режими Durable Workflow 2.0. +Embedded mode зберігає durable-виконання всередині застосунку. +Service mode зберігає Laravel як фреймворк застосунку та workers, +а Cloud або самостійно розгорнутий Server відповідає за durable-стан. + +Ця сторінка допомагає вибрати та виконати один із трьох переходів: + +1. Стабільний Laravel v1 до embedded 2.0. +2. Стабільний Laravel v1 до PHP SDK 2.0 у service mode. +3. Laravel embedded 2.0 до PHP SDK 2.0 у service mode. + +Якщо безпосередня мета — оновлення пакета v1, тримайте поруч докладну +[настанову міграції 2.0](/docs/migration/). +Якщо ви вже вибрали власний service runtime, після вибору політики +переходу тут продовжіть із +[процедурою embedded-to-Server](/docs/polyglot/embedded-to-server/). + +## Вибір кінцевого режиму {#pick-the-destination} + +| Початковий режим | Кінцевий режим | Коли вибрати | Перша межа переходу | +| --- | --- | --- | --- | +| Стабільний Laravel v1 | Embedded 2.0 | Laravel-застосунок має й далі відповідати за зберігання workflow, черги, виконання та embedded Waterline. | Оновіть підтримуваний пакет `durable-workflow/workflow` до лінії 2.0. Наявні runs v1 завершуються через шлях сумісності v1 пакета, нові запуски використовують v2. | +| Стабільний Laravel v1 | PHP SDK service mode | Laravel має зберігати dependency injection, конфігурацію, Artisan, логування, events і test fakes, а Cloud чи Server — відповідати за durable-стан. | Установіть `durable-workflow/sdk` як окрему межу service client/worker. Історія v1 та durable Laravel queue jobs залишаються на runtime v1 до термінального стану. | +| Laravel embedded 2.0 | PHP SDK service mode | Застосунок має передати orchestration runtime або потребує спільної/polyglot межі runtime. | Спрямовуйте нові запуски до service runtime після готовності його workers. Завершіть embedded runs там, де вони почалися, або використайте явний import допустимих embedded-v2 runs для власного Server. | + +PHP SDK bridge та embedded-пакет підтримують Laravel 9–13 +на сумісних версіях PHP. Тому підтримуваний застосунок v1 може +вибрати будь-який кінцевий режим без окремого оновлення Laravel. + +## Порівняння відповідальності {#compare-the-ownership-boundary} + +| Питання | Стабільний Laravel v1 | Laravel embedded 2.0 | PHP SDK 2.0 service mode | +| --- | --- | --- | --- | +| Власник runtime | Laravel-застосунок відповідає за orchestration та replay. | Laravel-застосунок відповідає за рушій v2, історію, matching, timers та replay. | Cloud оперує namespace runtime або ваша команда оперує власним Server. Laravel запускає клієнтів і віддалені workers, а не runtime. | +| Composer-пакет | `durable-workflow/workflow` стабільної лінії 1.x. | `durable-workflow/workflow` з джерела артефактів 2.0. | `durable-workflow/sdk` з незалежного джерела опублікованого PHP SDK. Він не залежить від embedded-рушія. | +| Потрібні процеси | Laravel web/CLI та Laravel queue workers або Horizon. | Laravel web/CLI та queue workers або Horizon. Ролі scheduler і repair визначає конфігурація embedded-розгортання. | Процеси Laravel-застосунку та один чи більше `php artisan durable-workflow:worker`. Cloud надає runtime. Власне розгортання також потребує Server та його сховища. | +| Черги та сховище | Рядки workflow та PHP-орієнтована історія зберігаються в налаштованих базах Laravel. Jobs workflow, activity, retry та timer — у backends черг Laravel. | Історія подій та projections зберігаються в налаштованій базі застосунку. Embedded tasks зазвичай використовують черги Laravel. | Durable-історія та стан tasks зберігаються у Cloud чи Server. `DURABLE_WORKFLOW_TASK_QUEUE` ідентифікує віддалену task queue, яку опитує SDK worker, а не підключення черги Laravel. | +| Ідентичність workflow/activity | PHP-класи workflow та activity є durable-ідентифікаторами. | Налаштуйте сталі aliases у `workflows.v2.types`. Класи залишаються локальними реалізаціями. | `#[Workflow('orders.fulfill')]` і `#[Activity('orders.reserve')]` публікують сталі рядкові ключі типів під час реєстрації worker. Не використовуйте PHP FQCNs як контракт між runtimes. | +| Облікові дані | Облікові дані Laravel-застосунку, бази, cache та черги. Початковий `APP_KEY` залишається частиною відновлення v1. | Облікові дані Laravel-застосунку, бази, cache, черги та embedded Waterline, чию автентифікацію визначає host-застосунок. | Процеси застосунку Cloud отримують лише control credential, а workers — лише worker credential. Власне розгортання може використовувати спільний токен або відповідні scoped credentials. Облікові дані передаються процесам і навмисно відсутні в кешованій конфігурації Laravel. | +| Операційні інструменти | Логи/events Laravel, інструменти черг, Horizon, Waterline v1 та `workflow:v1:list` за наявності. | Логи/events Laravel, інструменти черг, `workflow:v2:doctor`, команди історії/repair та embedded Waterline. | Діагностика SDK проходить через PSR logger Laravel та `WorkerDiagnosticEvent`. Cloud включає Managed Waterline. Власні оператори використовують API/CLI Server і можуть окремо розгорнути Waterline для namespace Server. | +| Відповідальність за cutover і rollback | Ваша команда зберігає базу v1, стан черг, конфігурацію та секрети як єдиний набір відновлення. | Ваша команда розділяє запуски v1 і v2, зберігає wake-шляхи v1 та вирішує, коли можна прибрати старі таблиці/черги. | Ваша команда спрямовує кожну команду до runtime її run. Cloud відповідає за runtime rollback керованого сервісу, власні оператори — за зберігання та rollback Server. Rollback застосунку має зберігати старі й нові маршрути runtime до термінального стану їхніх runs. | + +Cloud та власний Server є альтернативами service mode. +Користувачі Cloud не встановлюють Server чи окремий Waterline service. +Власні оператори розгортають, захищають, резервують, масштабують +і оновлюють Server. Waterline є окремим необов'язковим розгортанням +для namespace, яким володіє Server. + +## Один Laravel-сценарій у трьох режимах {#one-laravel-use-case-in-all-three-modes} + +Приклади резервують inventory для `orders.fulfill`. +Явно зіставте стабільний клас v1 з ключами типів v2, потім зберігайте +ці ключі та налаштовану task queue сталими. Резервування inventory +та споживач бізнес-подій мають бути ідемпотентними, бо activity може +виконуватися повторно після retry чи втрати підтвердження completion. + +Дія застосунку однакова в production і тестах: + +| Режим | Точка входу production | Точка входу після ввімкнення fake | +| --- | --- | --- | +| Стабільний Laravel v1 | `app(StartOrderFulfillment::class)->start('1001')` | `app(StartOrderFulfillment::class)->start('1001')` | +| Laravel embedded 2.0 | `app(StartOrderFulfillment::class)->start('1001')` | `app(StartOrderFulfillment::class)->start('1001')` | +| PHP SDK service mode | `app(StartOrderFulfillment::class)->start('1001')` | `app(StartOrderFulfillment::class)->start('1001')` | + +Constructor injection Laravel доступний на межі застосунку в кожному режимі. +Стабільний v1 передає залежності activity в `execute()`. +Embedded v2 і service mode використовують контейнер Laravel для створення +об'єктів workflow та activity, тому їхні залежності мають звичайні конструктори. +Усе, що читає workflow під час replay, має бути детермінованим +і визначеним конфігурацією. База даних, мережа, годинник та інші +side effects належать activities. + +### Стабільний Laravel v1 {#stable-v1-laravel} + +Зберігайте вимоги [стабільного встановлення v1](/docs/1.x/installation/) +та справжнього queue driver. Ця типова реалізація використовує application starter +з constructor injection та services activity з method injection: + + +```php +assertFulfillable($orderId); + $reservation = $inventory->reserve($orderId); + $logger->info('Inventory reserved', ['order_id' => $orderId]); + $events->dispatch(new InventoryReserved($orderId)); + + return $reservation; + } +} + +final class StartOrderFulfillment +{ + public function __construct(private readonly LoggerInterface $logger) + { + } + + public function start(string $orderId): void + { + $workflow = WorkflowStub::make(FulfillOrderWorkflow::class); + $workflow->start($orderId, new WorkflowOptions('redis', 'orders')); + $this->logger->info('Order workflow started', ['order_id' => $orderId]); + } +} +``` + +`config/queue.php` та середовище розгортання й далі вибирають справжнє +підключення черги Laravel. Запустіть worker налаштованої черги: + + +```bash +php artisan queue:work redis --queue=orders +``` + +Стабільний fake виконує ту саму дію застосунку з injection, +яку використовує production: + + +```php + 'reserved']); + +app(StartOrderFulfillment::class)->start('1001'); + +WorkflowStub::assertDispatched( + ReserveInventoryActivity::class, + fn (string $orderId): bool => $orderId === '1001', +); +``` + +Laravel event assertions залишаються доступними під час тестування справжньої +activity. Не замінюйте workers v1 лише через додавання SDK v2 +в іншій частині застосунку. + +### Laravel embedded 2.0 {#20-embedded-laravel} + +Стабільне встановлення використовує той самий пакет Workflow, +що й embedded quickstart: + + +```bash +composer require %%artifact.workflowComposerPackage%% +``` + +Опублікуйте або оновіть `config/workflows.php`, потім надайте +тому самому бізнес-контракту сталі aliases. Опублікований файл +містить додаткові обов'язкові налаштування v1 і v2. +Об'єднайте ці значення з наявними масивами замість заміни +всього файлу цим окремим фрагментом: + + +```php + [ + 'namespace' => env('DW_V2_NAMESPACE', 'production'), + 'types' => [ + 'workflows' => [ + 'orders.fulfill' => App\Workflows\FulfillOrderWorkflow::class, + ], + 'activities' => [ + 'orders.reserve' => App\Activities\ReserveInventoryActivity::class, + ], + ], + ], +]; +``` + +API написання workflow переходить на `handle()` та більше не yield +результати activity. Laravel створює обидва об'єкти. +Activity залишається належним місцем для логування та бізнес-подій, +які представляють side effects: + + +```php +orders->assertFulfillable($orderId); + + return activity(ReserveInventoryActivity::class, $orderId); + } +} + +final class ReserveInventoryActivity extends Activity +{ + public ?string $connection = 'redis'; + public ?string $queue = 'orders'; + + public function __construct( + private readonly OrderPolicy $orders, + private readonly InventoryGateway $inventory, + private readonly LoggerInterface $logger, + private readonly Dispatcher $events, + ) { + } + + public function handle(string $orderId): array + { + $this->orders->assertFulfillable($orderId); + $reservation = $this->inventory->reserve($orderId); + $this->logger->info('Inventory reserved', ['order_id' => $orderId]); + $this->events->dispatch(new InventoryReserved($orderId)); + + return $reservation; + } +} + +final class StartOrderFulfillment +{ + public function __construct(private readonly LoggerInterface $logger) + { + } + + public function start(string $orderId): void + { + WorkflowStub::make(FulfillOrderWorkflow::class, "order-{$orderId}") + ->start($orderId); + $this->logger->info('Order workflow started', ['order_id' => $orderId]); + } +} +``` + +Запустіть Laravel queue worker так, як потребує embedded-розгортання: + + +```bash +php artisan queue:work redis --queue=orders +``` + +Використовуйте embedded fake через ту саму дію застосунку. +Він inline виконує готові workflow tasks і записує dispatch activities: + + +```php + 'reserved']); + +app(StartOrderFulfillment::class)->start('1001'); +while (WorkflowStub::runReadyTasks() > 0) { +} + +WorkflowStub::assertDispatched( + ReserveInventoryActivity::class, + fn (string $orderId): bool => $orderId === '1001', +); +``` + +Цей fake перевіряє embedded durable-рядки та історію. +Він не є fake SDK-клієнта та не замінює service runtime. + +### PHP SDK service mode у Laravel {#php-sdk-service-mode-in-laravel} + +Установіть незалежно опублікований SDK-пакет. +Команда генерується з джерела артефактів опублікованого PHP SDK, +а не копіюється з окремого випуску: + + +```bash +composer require %%artifact.publishedPhpSdkComposerPackage%% +``` + +Laravel 9–13 автоматично знаходять provider. +Опублікуйте конфігурацію пакета та перелічіть класи обробників з атрибутами: + + +```php + env('DURABLE_WORKFLOW_RUNTIME_URL', 'http://localhost:8080'), + 'namespace' => env('DURABLE_WORKFLOW_NAMESPACE', 'production'), + 'task_queue' => env('DURABLE_WORKFLOW_TASK_QUEUE', 'orders'), + 'handlers' => [ + App\Workflows\FulfillOrderWorkflow::class, + App\Activities\ReserveInventoryActivity::class, + ], + 'poll_timeout_seconds' => 5, +]; +``` + +Service workflow використовує ті самі публічні рядки, що й embedded v2. +Контейнер Laravel створює обидва service-класи з атрибутами: + + +```php +orders->assertFulfillable($orderId); + + return $context->activity('orders.reserve', [$orderId]); + } +} + +final class ReserveInventoryActivity +{ + public function __construct( + private readonly OrderPolicy $orders, + private readonly InventoryGateway $inventory, + private readonly LoggerInterface $logger, + private readonly Dispatcher $events, + ) { + } + + #[Activity('orders.reserve')] + public function run(ActivityContext $context, string $orderId): array + { + $this->orders->assertFulfillable($orderId); + $reservation = $this->inventory->reserve($orderId); + $this->logger->info('Inventory reserved', ['order_id' => $orderId]); + $this->events->dispatch(new InventoryReserved($orderId)); + + return $reservation; + } +} +``` + +Код застосунку отримує Laravel-native інтерфейс SDK через injection +замість доступу через facade або побудови транспортного клієнта. +Клас з атрибутом визначає тип workflow, а опублікована конфігурація — +стандартну task queue: + + +```php +workflows->start( + FulfillOrderWorkflow::class, + [$orderId], + workflowId: "order-{$orderId}", + ); + } + + public function handle(string $orderId): WorkflowHandleInterface + { + return $this->workflows->handle( + FulfillOrderWorkflow::class, + "order-{$orderId}", + ); + } +} +``` + +Після публікації конфігурації кешуйте лише несекретні налаштування. +Задайте `RUNTIME_URL` як повний підготовлений URI Cloud namespace runtime +або origin власного Server. Не додавайте `/api`. + +Запустіть worker лише з обліковими даними ролі worker: + + +```bash +php artisan vendor:publish --tag=durable-workflow-config +env -u DURABLE_WORKFLOW_TOKEN \ + -u DURABLE_WORKFLOW_CLIENT_TOKEN \ + -u DURABLE_WORKFLOW_WORKER_TOKEN \ + DURABLE_WORKFLOW_RUNTIME_URL="$RUNTIME_URL" \ + DURABLE_WORKFLOW_NAMESPACE=production \ + DURABLE_WORKFLOW_TASK_QUEUE=orders \ + php artisan config:cache +env -u DURABLE_WORKFLOW_CLIENT_TOKEN \ + DURABLE_WORKFLOW_WORKER_TOKEN="$WORKER_SECRET" \ + php artisan durable-workflow:worker +``` + +Без перевизначення `--queue` Artisan worker опитує налаштовану task queue +`orders`. Перед звичайним polling діагностика registered-and-polling +називає runtime, namespace, чергу, типи workflow/activity з атрибутами +та роль облікових даних worker. + +Процеси застосунку/web/queue отримують `DURABLE_WORKFLOW_CLIENT_TOKEN`, +а не токен worker. Власні оператори можуть використовувати +`DURABLE_WORKFLOW_TOKEN`, коли один credential навмисно дозволений +для обох ролей. + +SDK worker записує діагностику життєвого циклу, retry, помилок handler +та shutdown через PSR logger Laravel і надсилає +`DurableWorkflow\Bridge\Event\WorkerDiagnosticEvent` через events Laravel. +Бізнес-activities можуть продовжувати використовувати logger +та event dispatcher через injection, як показано вище. + +Facade fake пакета замінює той самий інтерфейс з injection, +який використовує код застосунку: + + +```php +setWorkflowResult('order-1001', ['status' => 'fulfilled']); + +app(StartOrderFulfillment::class)->start('1001'); +$result = app(StartOrderFulfillment::class)->handle('1001')->result(); + +$this->assertSame(['status' => 'fulfilled'], $result); +$workflows->assertWorkflowStarted( + FulfillOrderWorkflow::class, + ['1001'], + workflowId: 'order-1001', +); +$workflows->assertResultRequested('order-1001'); +``` + +Цей fake перевіряє взаємодію Laravel-застосунку без Cloud чи Server. +Для тестів команд обробників workflow/activity використовуйте worker test harness +SDK. Fake клієнта не виконує код віддаленого worker. + +## Drain, cutover та rollback {#drain-cut-over-and-roll-back} + +Зміна Composer-вимоги не переміщує активний workflow, його історію, +відкладений timer, retry activity чи готовий/зарезервований queue job. +Розглядайте старий і новий runtimes як окремі durable-системи +протягом переходу. + +### 1. Визначення точки відновлення {#1-establish-the-recovery-cut} + +Перед зміною трафіку: + +- Зберіть дані про підключення бази v1/embedded, кожну налаштовану чергу + workflow та activity, account/region/prefix queue backend, cache locks + і посилання secret manager на початковий `APP_KEY`. +- Заблокуйте нові запуски та вхідні повідомлення на час приведення джерела + до спокійного стану. Зупиніть кожен worker після поточного job. + Сам `queue:restart` не забезпечує спокійного стану, коли supervisor + негайно замінює процес. +- Завершіть усі початкові runs або створіть application-consistent точку + відновлення, що містить SQL та кожен готовий, відкладений і зарезервований + queue job. Backup лише бази не відновлює queue-backed timers, retries чи activities. +- Запишіть runtime-власник кожного нетермінального workflow ID. + Signals, queries, updates, cancellation, termination, repair і читання + результатів мають продовжувати використовувати цей запис відповідальності. + +### 2. Застосування правила вибраного шляху {#2-apply-the-path-specific-rule} + +| Перехід | Наявні нетермінальні runs | Нові runs після cutover | Межа rollback | +| --- | --- | --- | --- | +| v1 до embedded 2.0 | Завершуються через шлях сумісності v1. Зберігайте таблиці v1, вимоги PHP-декодування, споживачів Laravel queues і стан черг до термінального стану. | Починаються через `Workflow\V2\WorkflowStub` лише після успішного canary v2. | Відновіть узгоджену точку SQL/queue v1 і точну конфігурацію застосунку або виправляйте вперед. Не відновлюйте SQL поверх новішого стану черг. | +| v1 до Cloud чи власного service mode | Залишаються на v1. Історію v1 не можна імпортувати до service runtime або replay віддаленими workers. | Починаються через `LaravelWorkflowClientInterface` після реєстрації SDK worker з очікуваними ключами типів. | Повертайте нові запуски до v1 лише якщо runtime v1 здоровий. Runs, уже прийняті service mode, залишаються там. | +| Embedded v2 до Cloud | Завершуються на місці. Cloud onboarding не перетворює зміну пакета на import історії. | Починаються в підготовленому Cloud namespace після перевірок client/worker credentials і task queue. | За потреби поверніть нові запуски до embedded. Команди runs, прийнятих Cloud, залишайте спрямованими до Cloud. | +| Embedded v2 до власного Server | Завершуються на місці або окремо проходять підтримувані export, dry run та atomic import допустимого спокійного embedded-v2 run. Не визначайте допустимість за встановленням пакета. | Починаються через SDK після перевірок discovery Server, namespace, реєстрації worker і черги. | Невдалий import не записує часткового run. Після зафіксованого import Server володіє run. Не відновлюйте embedded-копію. | + +Для власного Server використовуйте повну +[процедуру міграції embedded-to-Server](/docs/polyglot/embedded-to-server/#phase-e-import-eligible-embedded-v2-runs). +Вона відхиляє v1, історію з redaction, leased tasks, активні спроби activity +та інші небезпечні snapshots. Сам export історії надає дані аудиту/налагодження. +Лише явна перевірена операція import створює стан, яким володіє Server. + +### 3. Перевірка кінцевого режиму перед зміною трафіку {#3-prove-the-destination-before-switching-traffic} + +1. Налаштуйте один namespace, task queue та сталий набір типів workflow/activity. +2. Запустіть новий runtime та його SDK workers поруч зі старим runtime. +3. Перевірте discovery Server чи підготовку Cloud, credentials ролей, + реєстрацію worker, оголошені ключі типів, огляд task queue та діагностику Laravel. +4. Надішліть shadow/canary order з унікальним ID та дочекайтеся термінального + результату в операторській поверхні нового режиму. +5. Переводьте нові запуски по одному сімейству workflow. + Не допускайте запуску того самого бізнес-ключа в обох runtimes. +6. Зберігайте початкові workers, стан черг, credentials, логи/events + та стару операторську поверхню до термінального стану всіх початкових runs + або завершення підтримуваного import власного Server. + +Прибирання старого пакета, черги, таблиці, секрету чи розгортання Waterline +є останнім кроком, а не механізмом cutover. + +## Продовжуйте вибраним шляхом {#continue-with-the-chosen-path} + +- [Стабільне встановлення v1](/docs/1.x/installation/) +- [Планування міграції стабільного v1](/docs/migration/) +- [Встановлення embedded 2.0](/docs/installation/) +- [Докладна міграція пакета з v1 до v2](/docs/migration/) +- [PHP SDK service mode](/docs/polyglot/php/) +- [Керований Cloud runtime](/docs/polyglot/cloud-control-plane/) +- [Власний Server](/docs/polyglot/server/) +- [Embedded v2 до власного Server](/docs/polyglot/embedded-to-server/) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/embedded-to-server.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/embedded-to-server.md new file mode 100644 index 0000000000..b1406b8609 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/embedded-to-server.md @@ -0,0 +1,462 @@ +--- +sidebar_position: 1 +title: Міграція з embedded до Server +description: Перехід Laravel embedded v2 на окремий Durable Workflow Server. +--- + +# Міграція з embedded до Server {#embedded-to-server-migration} + +Ця настанова призначена для команд, які вже запускають Durable Workflow v2 +всередині Laravel-застосунку й переводять нові workflows на окремий Server. +Якщо застосунок ще використовує v1, почніть із +[міграції на 2.0](/docs/migration) та залиште наявні runs v1 +на рушії v1 до завершення. + +Спочатку прочитайте [інтеграцію Laravel та перехід між runtime](/docs/laravel-adoption/), +якщо кінцевий режим ще не вибрано або інтеграція з Laravel є частиною рішення. +Ця сторінка описує самостійно розгорнутий Server після цього вибору, +а не міграцію до Cloud. + +Міграція є шляхом інтеграції, а не переміщенням бази даних на місці. +Окремий Server відповідає за нові запуски площини керування, durable-історію, +schedules, реєстрацію workers і доставку tasks через HTTP. +Workers вашого застосунку й далі відповідають за код workflow та activity. + +
+ Durable Workflow 2.0 має один публічний codec payload: avro. + JSON є транспортом HTTP-документа, а не codec durable payload. PHP-only readers v1 + залишаються внутрішньою частиною import/drain і не можуть використовуватися + для нових runs v2. Дивіться протокол Avro Value. +
+ +Ця настанова описує самостійно розгорнуту міграцію. Вона не перетворює +embedded-розгортання на Cloud namespace, а отриманий Server не підключений +до Durable Workflow Cloud. Окремий вибір керованого сервісу описано в +[керованому Cloud runtime](/docs/polyglot/cloud-control-plane). + +## Поточні межі {#current-boundary} + +Зараз підтримується: + +- Запуск нових workflows через API площини керування Server або CLI. +- Реєстрація PHP, Python, Rust чи власних HTTP workers на Server. +- Polling та завершення workflow й activity tasks через worker-протокол. +- Імпорт допустимих exports історії embedded v2 у Server як стан workflow, + керований Server. +- Експорт історії закритих runs embedded v2 або Server v2 для аудиту, + налагодження та передачі до архіву. +- Спостереження за керованими Server workflows через API Server, CLI + та SDK, які читають стан Server, або окремий + [Waterline service mode](/docs/monitoring#waterline-service) + з підключенням через PHP SDK. Пакет Waterline в початковому + Laravel-застосунку продовжує читати лише стан того embedded runtime. + +Зараз не підтримується як автоматична операція: + +- Переміщення runs v1 до Server. +- Replay історії v1 workers, що не використовують PHP. + +Плануйте cutover так, щоб runs v1 завершилися там, де почалися. +Runs embedded v2 можна завершити на місці або перемістити +через процедуру import нижче. + +## Контракт режимів розгортання {#deployment-mode-contract} + +Закріплене порівняння embedded і service наведено в +[режимах розгортання](/docs/polyglot/deployment-modes). +Ця настанова додає до спільного контракту чотири правила cutover: + +- Наявні embedded runs продовжують виконання там, де почалися. +- Нові керовані Server runs використовують сталі ключі типів, назви + namespaces, task queues і контракт payload Avro від першого cutover. +- Signals, queries, updates, repair, cancel, terminate та archive + мають спрямовуватися до runtime, який володіє цільовим run. +- Кожне розгортання Waterline має залишатися підключеним до runtime + та namespace, які спостерігає. Воно не об'єднує embedded і Server runs. + +## Інваріанти cutover {#cutover-invariants} + +Зберігайте ці правила протягом усієї міграції: + +- Налаштуйте Server як явну віддалену залежність: задайте base URL, + namespace, task queue та облікові дані прямо замість виведення + їх із локальних налаштувань Laravel-застосунку. +- Зберігайте сталі workflow IDs, правила вибору run, ключі типів workflow/activity, + тег codec payload (`avro`) та маркери сумісності в обох runtimes. +- Спрямовуйте signals, queries, updates, repair, cancel, terminate та archive + до runtime, який володіє цільовим run. +- Перед імпортом embedded run призупиніть embedded workers на час експорту + спокійного стану без leased workflow/activity tasks і активної спроби activity. +- Мовна незалежність є частиною контракту міграції: керовані Server workflows + використовують сталі aliases і закріплену schema Avro Value замість + PHP-only назв класів чи форматів payload. + +## Етап A: підготовка embedded v2 {#phase-a-prepare-embedded-v2} + +Перед розгортанням Server переведіть embedded-застосунок на мовно-незалежні +контракти. Це зменшує обсяг змін коду під час cutover. + +1. Визначте сталі ключі типів workflow та activity. + + ```php + // config/workflows.php + 'v2' => [ + 'types' => [ + 'workflows' => [ + 'orders.process' => App\Workflows\ProcessOrderWorkflow::class, + ], + 'activities' => [ + 'orders.reserve-inventory' => App\Activities\ReserveInventory::class, + 'orders.capture-payment' => App\Activities\CapturePayment::class, + ], + ], + ], + ``` + +2. Використовуйте ці ключі на кожній зовнішній межі. + + Клієнти Server надсилають `workflow_type: "orders.process"`, а workers + реєструють `supported_workflow_types` / `supported_activity_types` + із тими самими рядками. Не робіть PHP FQCNs durable-публічним контрактом. + +3. Використовуйте контракт payload Avro v2. + + ```php + // config/workflows.php + 'serializer' => 'avro', + ``` + + Avro обов'язковий для нових embedded і керованих Server workflows v2. + Старий PHP reader можна залишити лише всередині import/drain v1, + поки завершуються старі runs v1. + +4. Виберіть назви namespace і task queue. + + Виберіть назви, які залишатимуться сталими протягом переходу з embedded до Server: + + ```bash + export DURABLE_WORKFLOW_NAMESPACE=production + export DURABLE_WORKFLOW_TASK_QUEUE=orders + ``` + +5. Вирішіть, чи потрібні маркери сумісності. + + Розгортання з одним fleet може не задавати сумісність. + Стандартні механізми rollout v2: canary, drain, rollback і replay-debug, + діють у межах одного сталого контракту v2. Окремого шляху інтеграції + «mixed-fleet v2» немає. Якщо під час переходу embedded до Server + з'являється вікно різних builds, установіть `DW_V2_CURRENT_COMPATIBILITY` + і `DW_V2_SUPPORTED_COMPATIBILITIES` до cutover, щоб новий build + не отримував runs несумісного build протягом цього вікна. + +## Етап B: розгортання Server поруч із embedded {#phase-b-deploy-the-server-beside-embedded} + +Запустіть окремий Server поруч із Laravel-застосунком, +поки застосунок продовжує обробляти наявні embedded runs. + +```bash +git clone https://github.com/durable-workflow/server.git +cd server +cp .env.example .env +docker compose up -d +``` + +Для локальної розробки можна задати `DW_AUTH_DRIVER=none`. +У спільних середовищах використовуйте облікові дані з окремими ролями: + +```bash +DW_AUTH_DRIVER=token +DW_WORKER_TOKEN=worker-secret +DW_OPERATOR_TOKEN=operator-secret +DW_ADMIN_TOKEN=admin-secret +``` + +Перевірте discovery і створіть namespace: + +```bash +export SERVER=http://localhost:8080 +export ADMIN_TOKEN=admin-secret +export OPERATOR_TOKEN=operator-secret + +curl "$SERVER/api/health" + +curl "$SERVER/api/cluster/info" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" + +curl -X POST "$SERVER/api/namespaces" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{"name":"production","description":"Production workflows","retention_days":30}' +``` + +Server відхиляє запити площини керування без +`X-Durable-Workflow-Control-Plane-Version: 2`. +Workers використовують окремий заголовок +`X-Durable-Workflow-Protocol-Version: 1.0`. + +## Етап C: підключення workers {#phase-c-connect-workers} + +Workers мають зареєструватися перед polling. +Реєстрація оголошує runtime, task queue та ключі типів, +які worker може виконувати. + +```bash +export WORKER_TOKEN=worker-secret + +curl -X POST "$SERVER/api/worker/register" \ + -H "Authorization: Bearer $WORKER_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Protocol-Version: 1.0" \ + -H "Content-Type: application/json" \ + -d '{ + "worker_id": "orders-php-1", + "task_queue": "orders", + "runtime": "php", + "sdk_version": "", + "supported_workflow_types": ["orders.process"], + "supported_activity_types": [ + "orders.reserve-inventory", + "orders.capture-payment" + ], + "max_concurrent_workflow_tasks": 10, + "max_concurrent_activity_tasks": 50 + }' +``` + +Потім запустіть workers у Server mode для тих самих namespace і task queue. +Python workers дотримуються того самого контракту реєстрації та poll +через Python SDK. Власні workers можуть безпосередньо використовувати +[worker-протокол](/docs/polyglot/worker-protocol). + +Перевірте огляд стану перед cutover: + +```bash +curl "$SERVER/api/workers" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" + +curl "$SERVER/api/task-queues/orders" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" +``` + +## Етап D: спрямування нових запусків до Server {#phase-d-route-new-starts-to-the-server} + +Переводьте по одному сімейству workflow. Залишайте embedded-застосунок +і queue workers активними для старих runs до їхнього завершення. + +Спочатку запустіть shadow workflow: + +```bash +curl -X POST "$SERVER/api/workflows" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "workflow_id": "orders-shadow-1001", + "workflow_type": "orders.process", + "task_queue": "orders", + "input": [{"order_id":"1001","mode":"shadow"}] + }' +``` + +Спостерігайте за run: + +```bash +curl "$SERVER/api/workflows/orders-shadow-1001" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" +``` + +Після успішної перевірки shadow-сценарію переведіть production-ініціатора +з `WorkflowStub::start()` на клієнт Server, CLI або прямий HTTP API. +Залиште signals, queries, updates, cancel і terminate спрямованими +до runtime, який почав workflow. Embedded workflow має отримувати +embedded-команди, а workflow Server — команди площини керування Server. + +## Етап E: імпорт допустимих runs embedded v2 {#phase-e-import-eligible-embedded-v2-runs} + +Використовуйте export історії як формат імпорту. +Embedded runtime залишається джерелом істини для набору. +Server перевіряє його, записує durable-рядки в одній транзакції +та відновлює з них свої projections. + +Допустимість: + +- Schema набору має бути `durable-workflow.v2.history-export` + із `schema_version: 1`. +- Початковий run має належати embedded v2, а export має містити + `workflow.source_runtime: "embedded"`. Історія v1 не входить до області. +- Нетермінальний run має бути поточним embedded run. +- Термінальний run має містити `history_complete: true`. +- Набори з redaction відхиляються. +- Leased workflow tasks, leased activity tasks та активні спроби activity + відхиляються. Призупиніть workers або дочекайтеся спливу leases + й експортуйте знову. + +Правила джерела істини import: + +- `history_events` копіюються як авторитетний журнал replay та аудиту. +- Ідентичність workflow, payload, команди, signals, updates, tasks, + виконання activities, timers, помилки й зв'язки lineage відновлюються з набору. +- Projections зведень, waits, timers, timeline та lineage Server відновлюються + після import. Рядки projection не є джерелом повноважень import. +- Незавершені workflow tasks залишаються доступними для claim сумісним workers Server. +- Незавершені виконання activity та готові activity tasks залишаються + доступними для claim сумісним activity workers Server. +- Незавершені timers зберігають timestamp `fire_at` і доступні + для repair/recovery timers Server. + +Помилки та rollback: + +- Import повністю виконується або не виконується в одній транзакції бази. +- Якщо процес завершується або перевірка не проходить до commit, + на Server не залишається часткового стану run. +- Повторний import того самого набору ідемпотентний за `run_id` та `dedupe_key`. +- Якщо інший run Server уже володіє тим самим `run_id`, import відхиляється. + +Огляд та аудит: + +- Імпортовані runs надають `engine_source: embedded_v2_import` + у списках і деталях Server. +- Durable-рядок run записує `import_source=embedded_v2`, `import_id`, + `import_dedupe_key`, `import_contract_version` та `imported_at`. + +Процедура оператора: + +Для runs embedded v2: + +```bash +php artisan workflow:v2:history-export order-123 \ + --output=storage/workflow-history/order-123.json \ + --pretty +``` + +Виконайте dry run import на Server: + +```bash +php artisan workflow:v2:history-import storage/workflow-history/order-123.json \ + --namespace=production \ + --dry-run \ + --json +``` + +Імпортуйте набір: + +```bash +php artisan workflow:v2:history-import storage/workflow-history/order-123.json \ + --namespace=production \ + --import-id=orders-cutover-2026-05-05 +``` + +Або викличте HTTP endpoint оператора: + +```bash +curl -X POST "$SERVER/api/workflows/import/embedded-v2" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + --data-binary @order-123-import-request.json +``` + +де `order-123-import-request.json` містить: + +```json +{ + "import_id": "orders-cutover-2026-05-05", + "bundle": { + "schema": "durable-workflow.v2.history-export", + "schema_version": 1, + "workflow": { + "source_runtime": "embedded" + } + } +} +``` + +Об'єкт `bundle` вище скорочено. Передавайте повний JSON-документ +`workflow:v2:history-export`. + +Для керованих Server runs, які потрібно лише зберегти або перевірити: + +```bash +curl "$SERVER/api/workflows/order-123/runs/$RUN_ID/history/export" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + > order-123-history.json +``` + +Архівуйте термінальні runs Server після export, щоб +виключити їх з retention pruning: + +```bash +curl -X POST "$SERVER/api/workflows/order-123/archive" \ + -H "Authorization: Bearer $OPERATOR_TOKEN" \ + -H "X-Namespace: production" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{"reason":"history exported to archive storage"}' +``` + +## Етап F: додавання polyglot workers {#phase-f-add-polyglot-workers} + +Коли сімейство workflow працює через Server, додайте Python, Rust +чи власні workers, зареєструвавши ті самі namespace, task queue та ключі типів. +Кожен публічний payload v2 використовує `avro`. +Зберігайте вхідні дані й результати activity мовно-незалежними: +масиви, об'єкти, рядки, числа, булеві значення та null. + +Використовуйте [настанову Python SDK](/docs/polyglot/python) +або [настанову Rust SDK](/docs/polyglot/rust). +Для прямих HTTP-реалізацій використовуйте +[довідник worker-протоколу](/docs/polyglot/worker-protocol). + +## Перевірка cutover {#cutover-checklist} + +- [ ] Runs v1 завершені або навмисно залишені на рушії v1. +- [ ] Embedded v2 використовує сталі ключі типів, а не PHP FQCNs, на зовнішніх межах. +- [ ] Кожен новий workflow v2 використовує єдиний публічний codec `avro`. +- [ ] `/api/health` та `/api/cluster/info` Server доступні й успішні з мережі розгортання. +- [ ] Цільовий namespace існує на Server. +- [ ] Workers реєструють очікувану task queue та підтримувані ключі типів. +- [ ] Shadow workflow запускається, отримується worker і досягає очікуваного стану. +- [ ] Оператори можуть перелічити workflows, оглянути task queues і реєстрації workers. +- [ ] Старі embedded-ініціатори призупинені чи переведені, щоб однакові бізнес-ключі + не починалися в обох runtimes. +- [ ] Вибрані для import runs embedded v2 експортовані зі спокійного embedded runtime + та проходять `workflow:v2:history-import --dry-run`. +- [ ] Імпортовані runs показують `engine_source: embedded_v2_import` + та очікуваний `import_id` у деталях і списках Server. +- [ ] Закриті старі runs, яким потрібне лише зберігання, експортовані до durable-сховища. + +## Діагностика {#troubleshooting} + +| Симптом | Імовірна причина | Виправлення | +| --- | --- | --- | +| `missing_control_plane_version` | Клієнт викликав маршрут площини керування без заголовка v2 | Надсилайте `X-Durable-Workflow-Control-Plane-Version: 2` | +| `missing_protocol_version` | Worker викликав маршрут worker без заголовка worker-протоколу | Надсилайте `X-Durable-Workflow-Protocol-Version: 1.0` | +| `namespace_not_found` | Namespace не створений на Server | Створіть його через `POST /api/namespaces` | +| Polls worker не повертають task | Task queue, ключ типу, namespace чи маркер сумісності не збігається | Порівняйте payload запуску workflow, реєстрацію worker та огляд task queue | +| Worker не на PHP не може декодувати payload v1 | Run містить PHP-native історію v1 | Залиште run на внутрішньому PHP import/drain v1. Не спрямовуйте його до worker v2 | +| Старий workflow не відповідає на signal/query/update Server | Run почався в embedded mode | Надсилайте команди через embedded-застосунок до завершення run | +| Import відхиляє `tasks.leased_task_present` | Embedded export містить leased task | Призупиніть embedded workers, дочекайтеся звільнення чи завершення leases та експортуйте знову | +| Import повертає `already_imported` | Ті самі `run_id` і `dedupe_key` уже присутні на Server | Вважайте повторення успішним і перевірте run Server | +| Імпортований run видимий, але не отримує claim | Ключ типу worker, task queue, namespace чи маркер сумісності не відповідає імпортованому рядку | Порівняйте звіт import, деталі run і реєстрацію worker | + +## Пов'язані настанови {#related-guides} + +- [Налаштування Server](/docs/polyglot/server) +- [Worker-протокол](/docs/polyglot/worker-protocol) +- [Python SDK](/docs/polyglot/python) +- [Міграція на 2.0](/docs/migration) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust.md new file mode 100644 index 0000000000..154f4b53cf --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/rust.md @@ -0,0 +1,565 @@ +--- +sidebar_position: 5 +title: Rust SDK +description: Встановлення офіційного Rust SDK, його crate, вихідний код і згенерована документація API. +tags: + - Rust + - SDK + - workers + - polyglot +keywords: + - sample-app-playground + - Rust SDK + - durable-workflow crate + - Rust worker + - crates.io +--- + +import ProductPromotion from '@site/src/components/ProductPromotion'; + +# Rust SDK {#rust-sdk} + +Ця настанова описує загальнодоступну поверхню SDK. Для окремих типів +і методів переходьте до згенерованого довідника API. Обліковий запис Cloud +не потрібний: основний шлях використовує опублікований Rust crate +із самостійно розгорнутим Server. Розробники, які вже беруть участь +у контрольованому ранньому доступі Durable Workflow Cloud, можуть вибрати +окремий [quickstart керованого runtime](./rust-cloud-quickstart.md). + +Офіційний Rust SDK надає написання workflow разом із протокольною сумісністю. +Rust дає змогу створювати детерміновані workflows, activities та довготривалі +worker services з тією самою моделлю durable-виконання, що й PHP та Python. +Асинхронний клієнт площини керування запускає executions, надсилає signals, +виконує queries та updates, скасовує, terminate й очікує результати. +Worker runtime replay історію workflow, виконує обробники workflow/activity/update, +повідомляє heartbeats worker та activity й обмінюється мовно-незалежними +payload із самостійно розгорнутим Server або namespace runtime Durable Workflow Cloud. + +[Початкова сторінка документації Rust](https://rust.durable-workflow.com/) +надає індекс SDK та загальні точки входу. Modules, structs, traits і methods +crate наведено у згенерованому +[довіднику Rust SDK API](https://rust.durable-workflow.com/durable_workflow/). + +Стабільний Rust SDK підтримує durable timers, дочірні workflows, retries +і timeouts activity, local activities, worker sessions, signals, +query handlers з replay, cancellation і termination, серверні deadlines +workflow, типізовані side effects, маркери версій, updates та типізовані +термінальні помилки й помилки replay. Керування schedules поки не заявлено. +Використовуйте [каталог можливостей 2.0](/docs/capabilities/), +а не припускайте однаковий набір можливостей у кожному SDK. + +## Спробуйте локальний playground Sample App {#try-the-local-sample-app-playground} + +Для найкоротшого шляху до написання workflow без Cloud відкрийте +[`main` поточного Sample App у GitHub Codespaces](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main) +і виконайте: + + +```bash +scripts/playground rust +``` + +Локальний playground генерує код workflow та activity, яким володіє користувач, +вибирає поточні стабільні артефакти й запускає опубліковані Server та Waterline. +Перед запуском workflow він очікує реєстрації worker, чия ідентичність, +тип workflow, тип activity та task queue відповідають згенерованому контракту. +Успіх потребує очікуваного завершеного результату та історії. +Після цього термінал виводить точне локальне посилання на run у Waterline +та шлях до структурованих JSON-даних перевірки. + +Цей навмисно невеликий транспортний scaffold використовує `serde_json::Value`. +Він не є єдиним рекомендованим контрактом Rust-застосунку. +Продовжіть із наявним +[прикладом типізованих вхідних даних і результатів](https://github.com/durable-workflow/sdk-rust/blob/main/examples/hello_world.rs), +[прикладом політики activity з retry, timeout, heartbeat і термінальними помилками](https://github.com/durable-workflow/sdk-rust/blob/main/examples/activity_options.rs) +та [прикладом local activity](https://github.com/durable-workflow/sdk-rust/blob/3.1.0/examples/local_activities.rs). +Пакет, приклад репозиторію та згенерований довідник API нижче залишаються +прямими шляхами для тих, хто не використовує Sample App. + +## Пакет та вихідний код {#package-and-source} + +- [Crate на crates.io](https://crates.io/crates/durable-workflow) +- [Репозиторій вихідного коду](https://github.com/durable-workflow/sdk-rust) + +Установіть Rust SDK з останнього успішно перевіреного набору. +Точна вимога генерується з того самого машиночитаного джерела, +що й quickstart Server: + + +```bash +%%artifact.rustCargoAddCommand%% +``` + +Або оголосіть ту саму перевірену вимогу безпосередньо в `Cargo.toml`: + +```toml +[dependencies] +%%artifact.rustCargoRequirement%% +``` + +Crate потребує Rust 1.86 або новішого. Metadata пакета оголошує точний +перевірений діапазон Durable Workflow Server, worker-протокол 1.19 +та площину керування 2. Під час розгортання визначальними залишаються +protocol manifests, оголошені `GET /api/cluster/info`. + +Server узгоджує заголовки worker-протоколу в межах major `1`: +Server з `1.N` приймає заголовок worker `1.M` лише за `M <= N`. +Звичайні workers Rust SDK надсилають +`X-Durable-Workflow-Protocol-Version: 1.19` і потребують Server, +який оголошує `1.19` або новіший. Перевірений Server оголошує `1.20`. +Явно ввімкнений профіль кооперативного скасування потребує `1.20`. + +Узгодження відхиляє невідповідність. Відсутній чи некоректний заголовок, +інший major або worker minor вище оголошеного Server відхиляються. +Діапазон версій Server вибирає сімейство випусків і не перевизначає +protocol manifest runtime. + +## Local activities {#local-activities} + +Rust SDK 3.1.0 додає inline local execution на workers із налаштуванням +`Worker::local_activities(true)`. Реєструйте callbacks звичайними +методами реєстрації activity, а потім викликайте local activity methods +контексту workflow. Конфігурацію retry та timeout наведено у +[виконуваному прикладі](https://github.com/durable-workflow/sdk-rust/blob/3.1.0/examples/local_activities.rs) +та [довіднику LocalActivityOptions](https://rust.durable-workflow.com/durable_workflow/struct.LocalActivityOptions.html). + +Server записує термінальний результат для cold replay. +Незафіксовані локальні ефекти можуть повторитися після втрати worker +і мають бути ідемпотентними. Асинхронні callbacks мають передавати +керування Tokio. Блокувальна робота потребує окремого нагляду процесу. +Inline local execution та підготовлений кооперативний локальний нагляд +використовують окремі профілі worker. Sticky execution у Rust не підтримується. + +## Worker sessions {#worker-sessions} + +Увімкніть `Worker::worker_sessions(true)` та оголосіть вимоги, +які задовольняє worker, через `capabilities(...)`. +Спрямовуйте віддалені activities через +`in_worker_session(WorkerSessionOptions::new(session_id))`. +Контекст activity надає спільний handle через `worker_session()`. +Явні handles підтримують `create()`, `renew()` та `close(reason)` +після реєстрації worker. + +Обмежте кількість worker sessions через `max_concurrent_worker_sessions(...)`, +а одночасні activities session — через `max_concurrent_activities(...)`. +Явно поновлюйте idle handles. Heartbeats activity також поновлюють lease +власника, зберігаючи початковий абсолютний TTL. Асинхронний callback, +який передає керування Tokio, відкидається після локально визначеного +спливу lease чи TTL без потреби в application heartbeats. + +Ресурси session живуть у процесі власника. Заміна має відбудувати їх. +Перервані зовнішні ефекти потребують ідемпотентності та attempt fencing. +Зафіксовані результати replay без повторного виконання callback. +Graceful shutdown закриває утримувані sessions перед deregistration. +Local activities не можуть використовувати session routing. + +Підтримка sessions починається з Rust SDK 3.2.0. +Використовуйте Server 2.5.1 або новіший. +[Виконуваний приклад](https://github.com/durable-workflow/sdk-rust/blob/3.2.0/examples/worker_sessions.rs) +повторно використовує process-local cache у двох типізованих activities +та виводить його generation. Конфігурацію і методи життєвого циклу наведено +в [WorkerSessionOptions](https://rust.durable-workflow.com/durable_workflow/struct.WorkerSessionOptions.html) +та [WorkerSession](https://rust.durable-workflow.com/durable_workflow/struct.WorkerSession.html). + +## Підготовка прикладу опублікованого репозиторію {#prepare-the-released-repository-example} + +[Приклад `hello_world`](https://github.com/durable-workflow/sdk-rust/blob/main/examples/hello_world.rs) +репозиторію реєструє Rust worker, починає workflow, надсилає signal, +виконує activity, повідомляє heartbeat activity й очікує завершеного результату. +Оскільки приклад запускає клієнт застосунку та worker в одному процесі, +шлях без облікового запису нижче підключає його до самостійно розгорнутого Server. +Підготовлений Cloud namespace є необов'язковим другим runtime +з окремими обліковими даними для ролей. + +Для відтворюваної вправи отримайте точний код crate, записаний +у перевіреному наборі. Значення нижче генерується з цього машиночитаного +джерела. Каталог прикладу абсолютний, щоб обидва шляхи підключення +могли перейти до нього безпосередньо: + + +```bash +export DURABLE_WORKFLOW_RUST_VERSION=%%artifact.rustSdkVersion%% +export DURABLE_WORKFLOW_RUST_EXAMPLE_DIR="$PWD/durable-workflow-rust-${DURABLE_WORKFLOW_RUST_VERSION}" +git clone --depth 1 --single-branch --branch "$DURABLE_WORKFLOW_RUST_VERSION" \ + https://github.com/durable-workflow/sdk-rust.git "$DURABLE_WORKFLOW_RUST_EXAMPLE_DIR" +``` + +## Запуск спільного прикладу з власним Server {#run-the-combined-example-with-self-hosted-server} + +Незмінений `hello_world` приймає один токен для самостійно розгорнутого +Durable Workflow Server, чия політика автентифікації дозволяє +тим самим обліковим даним виконувати команди workflow і polling роботи: + + +```bash +cd "$DURABLE_WORKFLOW_RUST_EXAMPLE_DIR" +DURABLE_WORKFLOW_SERVER_URL=http://localhost:8080 \ +DURABLE_WORKFLOW_TOKEN=dev-token \ +cargo run --example hello_world +``` + +Приклад використовує namespace `default`, підготовлений локальним quickstart +Server. Задайте `TASK_QUEUE`, щоб перевизначити стандартну чергу `rust-workers`. +Цей шлях не потребує участі в Cloud або облікових даних Cloud. + +## Необов'язковий другий шлях: Durable Workflow Cloud {#optional-secondary-path-connect-to-durable-workflow-cloud} + +Для найкоротшої перевірки керованого runtime використовуйте +[Rust Cloud quickstart](./rust-cloud-quickstart.md). +Він запускає той самий playground Sample App, що доступний PHP та Python, +динамічно вибирає поточні стабільні артефакти, перевіряє точну готовність +worker і очікує завершеного результату. + + +Уже берете участь у Cloud? Запустіть спільний Rust playground +у підготовленому namespace з окремими обліковими даними ролей runtime. + + +Нижчий рівень спільного прикладу SDK нижче корисний для адаптації +наявного Rust-процесу. Експортуйте значення, повернуті Cloud +під час підготовки namespace та створення двох його runtime credentials: + +Межу керованого підключення описано в +[керованому Cloud runtime](/docs/polyglot/cloud-control-plane). + + +```bash +export DURABLE_WORKFLOW_RUNTIME_URL='https://your-runtime-url' +export DURABLE_WORKFLOW_RUNTIME_NAMESPACE='orders' +export DURABLE_WORKFLOW_CLIENT_TOKEN='dwr_client_credential' +export DURABLE_WORKFLOW_WORKER_TOKEN='dwr_worker_credential' +``` + +У `$DURABLE_WORKFLOW_RUST_EXAMPLE_DIR/examples/hello_world.rs` +замініть наявні налаштування `server_url`, `token` та `Client::builder(...)` +на цю конфігурацію builder з окремими токенами: + + +```rust +let runtime_url = std::env::var("DURABLE_WORKFLOW_RUNTIME_URL") + .expect("DURABLE_WORKFLOW_RUNTIME_URL must be set"); +let runtime_namespace = std::env::var("DURABLE_WORKFLOW_RUNTIME_NAMESPACE") + .expect("DURABLE_WORKFLOW_RUNTIME_NAMESPACE must be set"); +let client_token = std::env::var("DURABLE_WORKFLOW_CLIENT_TOKEN") + .expect("DURABLE_WORKFLOW_CLIENT_TOKEN must be set"); +let worker_token = std::env::var("DURABLE_WORKFLOW_WORKER_TOKEN") + .expect("DURABLE_WORKFLOW_WORKER_TOKEN must be set"); + +let client = Client::builder(runtime_url) + .namespace(runtime_namespace) + .control_token(Some(client_token)) + .worker_token(Some(worker_token)) + .build()?; +``` + +Потім запустіть приклад як звичайно: + + +```bash +cd "$DURABLE_WORKFLOW_RUST_EXAMPLE_DIR" +cargo run --example hello_world +``` + +Виклики start, signal, describe і result workflow прикладу використовують +`control_token`. Реєстрація, polling, heartbeat і completion `Worker` +використовують `worker_token`. Обидві ролі використовують однакові runtime URL, +runtime namespace та task queue від Cloud, але різні облікові дані. +Не замінюйте жодні облікові дані Cloud на `.token(...)`. +Цей метод є загальним single-token fallback власного Server вище. + +## Запуск із серверними timeouts workflow {#start-with-server-enforced-workflow-timeouts} + +Rust SDK надає `WorkflowStartOptions` та `Client::start_workflow_with_options` +для deadlines workflow, які Server контролює навіть після завершення +процесу-ініціатора. Execution timeout охоплює весь екземпляр workflow, +включно з runs continue-as-new. Run timeout охоплює один run +і обчислюється заново на початку нового run. + + +```rust +use durable_workflow::{json, Client, Result, WorkflowStartOptions}; + +async fn start(client: &Client) -> Result<()> { + let handle = client.start_workflow_with_options( + "orders.await-payment", + "orders", + "order-42", + WorkflowStartOptions::new() + .execution_timeout_seconds(300) + .run_timeout_seconds(30), + json!([{"order_id": "order-42"}]), + ).await?; + + println!("workflow={} run={:?}", handle.workflow_id, handle.run_id); + Ok(()) +} +``` + +Обидва значення задаються в секундах і мають бути додатними. +Run timeout не може перевищувати execution timeout. +Наявний зручний метод `Client::start_workflow` використовує +`WorkflowStartOptions::default()`: execution timeout 3600 секунд +і run timeout 600 секунд. Серверні deadlines та семантику continue-as-new +описано в [timeouts](/docs/features/timeouts). + +Це політика workflow, а не HTTP timeout чи timeout polling результату. +Зокрема, `WorkflowResultOptions::timeout` лише припиняє локальне очікування +виклику `result()`. Він не закриває, не скасовує й не змінює run workflow. +Ініціатор може перевірити повернену ідентичність та чекати знову. +Execution чи run deadline із `WorkflowStartOptions` є durable-станом Server. +Після його спливу Server закриває run із термінальним результатом `timed_out`. + +## Детерміновані паралельні групи {#deterministic-parallel-groups} + +`WorkflowContext::parallel` та його alias `join` поєднують activities, +дочірні workflows, timers, змішані та вкладені групи без нової команди Server. +Конструктори `ParallelOperation` відкладають кожен leaf +до визначення всього дерева: + +```rust +use durable_workflow::{json, ChildWorkflowOptions, ParallelOperation}; +use std::time::Duration; + +let results = ctx.parallel(vec![ + ParallelOperation::activity("load-profile", json!(["customer-42"])), + ParallelOperation::group(vec![ + ParallelOperation::child_workflow( + "quote-shipping", + ChildWorkflowOptions::new("shipping-workers"), + json!(["customer-42"]), + ), + ParallelOperation::timer(Duration::from_secs(1)), + ]), +]).await?; +``` + +Кожен leaf має спільну сталу ідентичність групи та повний шлях +від зовнішнього до внутрішнього рівня. Успішні результати зберігають +вкладену форму та порядок вхідних даних. `Error::ParallelFailed` +зберігає типізовану причину leaf, детермінований шлях учасника, +шлях групи та завершених сусідів. Незавершені й завершені історії +replay без повторного планування. Точні дублікати та пізні completions +не змінюють вибраного позиційного результату. + +## Durable-вибір першого завершення {#durable-first-completion-selection} + +`WorkflowContext::select` та `select_keyed` одночасно починають activities, +дочірні workflows, timers, signals, очікування умов і вкладені звичайні +групи, а потім продовжують із переможцем, записаним runtime: + +```rust +use durable_workflow::{json, ParallelOperation, SelectionKey}; +use std::time::Duration; + +let selected = ctx.select_keyed(vec![ + ("resolver", ParallelOperation::activity("resolve-request", json!([request_id]))), + ("input", ParallelOperation::signal("resolution.received")), + ("deadline", ParallelOperation::timer(Duration::from_secs(2))), +]).await?; + +if selected.key == SelectionKey::Name("deadline".to_string()) { + if let Some(resolver) = selected.handle(&SelectionKey::Name("resolver".to_string())) { + resolver.cancel().await?; + } +} +``` + +`SelectionResult` зберігає сталий ключ/index, вид та ідентичність операції, +типізоване значення чи помилку й `DurableOperationHandle` кожного учасника. +Інші учасники продовжують виконання, доки workflow пізніше не очікує +`handle.await_result()` або `handle.cancel()`. +Future скасування повертає unit. Лише зафіксована історія +`SelectionOperationCancelled` доводить, що скасування перемогло. +Replay проходить запит без такого запису, а `await_result()` +повертає completion, який зафіксувався раніше. +Restart і replay використовують збереженого переможця. +Пізні чи повторні термінальні події не можуть його змінити. + +## Компенсація saga {#saga-compensation} + +Реєструйте compensation лише після завершення відповідної forward activity, +потім передайте forward-результат до `Saga::finish`: + +```rust +let mut saga = ctx.saga(); +let outcome = async { + let flight = ctx.activity("trip.reserve-flight", json!([])).await?; + saga.add_compensation("trip.cancel-flight", json!([flight]))?; + + let hotel = ctx.activity("trip.reserve-hotel", json!([])).await?; + saga.add_compensation("trip.cancel-hotel", json!([hotel]))?; + + ctx.activity("trip.charge", json!([])).await?; + Ok(json!({"status": "booked"})) +}.await; + +saga.finish(outcome).await +``` + +Оброблена помилка виконує наявну команду activity у зворотному порядку +реєстрації, по одній compensation. Компенсація зупиняється на першій помилці. +`Error::SagaCompensationFailed` зберігає обидві типізовані помилки, +тип compensation activity та порядок її реєстрації через restart і replay. +Термінальний `cancel_workflow` не відновлює код workflow для compensations. + +## Детерміновані side effects та маркери версій {#deterministic-side-effects-and-version-markers} + +Rust SDK записує невеликі недетерміновані значення через +`WorkflowContext::side_effect` та виводить детерміновані UUIDv4 через +`WorkflowContext::uuid_v4`. Cold replay декодує записане значення +замість повторного виклику callback. Змінений порядок, відсутні, +повторні чи несумісні з codec маркери повертають типізований +`Error::NonDeterministicReplay`. + +Використовуйте `WorkflowContext::get_version(change_id, min_supported, max_supported)`, +щоб зберігати replay-сумісність старих і нових гілок workflow під час rollout. +Нові runs записують `max_supported`, а наявні використовують durable-маркер. +`patched(change_id)` надає булеву форму rollout, +а `deprecate_patch(change_id)` зберігає маркер після завершення старої гілки. +Спільну durable-семантику описано в +[side effects](/docs/features/side-effects/) та [версіюванні](/docs/features/versioning/). + +## Cancel, terminate та термінальні результати {#cancel-terminate-and-handle-terminal-outcomes} + +У service mode `cancel_workflow` та `terminate_workflow` закривають run +негайно. Вони записують різні термінальні результати, але не просять код +workflow виконати cleanup. Для обмеженого cleanup workflow використовуйте +`request_cancellation()` із workers, які підтримують кооперацію. +Політики операцій, supervision та recovery описано в +[настанові кооперативного скасування](/docs/polyglot/cancellation). + +```rust +use durable_workflow::{Client, WorkflowCommandOptions}; + +# async fn cancel(client: &Client) -> durable_workflow::Result<()> { +client.cancel_workflow( + "order-42", + WorkflowCommandOptions::new() + .reason("customer withdrew the order") + .request_id("cancel-order-42"), +).await?; +# Ok(()) +# } +``` + +`cancel_workflow` і `terminate_workflow` рівня instance вибирають +поточний run на Server. Для безпеки вибраного run викликайте +`cancel_workflow_run` або `terminate_workflow_run` чи методи handle +`cancel_selected_run` і `terminate_selected_run`. +Якщо вибраний run застарів, `Error::WorkflowCommandRejected` надає +сталий reason `historical_run_command_rejected` разом із workflow ID, +run ID, областю цілі, HTTP status і тілом відповіді. + +Успішний `WorkflowHandle::result` продовжує повертати декодоване +JSON-значення. Для решти результатів обробляйте типізовані термінальні +варіанти. Розгалужуйтеся за сталими полями reason та category, +а не за текстом відображення: + + +```rust +use durable_workflow::{Error, WorkflowHandle, WorkflowResultOptions}; + +# async fn wait(handle: WorkflowHandle) -> durable_workflow::Result<()> { +match handle.result(WorkflowResultOptions::default()).await { + Ok(value) => println!("completed: {value}"), + Err(Error::WorkflowCancelled(outcome)) => { + println!("cancelled {:?}: {}", outcome.run_id, outcome.reason); + } + Err(Error::WorkflowTerminated(outcome)) => { + println!("terminated: {}", outcome.reason); + } + Err(Error::WorkflowFailed(outcome)) => { + println!("failure {:?}: {:?}", outcome.failure_id, outcome.exception_class); + } + Err(Error::WorkflowTimedOut(outcome)) => match ( + outcome.reason.as_str(), + outcome.failure_category.as_deref(), + ) { + ("result_wait_timeout", Some("client_timeout")) => { + println!( + "caller deadline for {} / {:?}; the run may still be open", + outcome.workflow_id, outcome.run_id, + ); + } + ("execution_timeout" | "run_timeout", category) => { + println!( + "server timeout for {} / {:?}: reason={} category={:?}", + outcome.workflow_id, outcome.run_id, outcome.reason, category, + ); + } + (reason, category) => { + println!("other typed timeout: reason={reason} category={category:?}"); + } + } + Err(error) => return Err(error), +} +# Ok(()) +# } +``` + +Кожен термінальний результат містить ідентичність workflow та run. +Він також зберігає публічний reason, категорію та ідентичність помилки, +тип і клас винятку, non-retryable стан, повідомлення та payload винятку, +якщо Server їх надає. Локальний deadline очікування має +reason `result_wait_timeout` і category `client_timeout`. +Серверний timeout є термінальним run `timed_out` зі сталим reason +`execution_timeout` або `run_timeout`. + +Handles обох методів start зберігають вибраний `run_id`. +`WorkflowHandle::result` описує маршрут конкретного run, тому повторне +використання workflow ID для нового run не може непомітно повернути +новіший результат під час очікування. Зберігайте `outcome.workflow_id` +та `outcome.run_id` у логах, метриках і записах retry. +Використовуйте пошук рівня instance лише коли навмисно стежите за поточним run. + +## Workflow updates {#workflow-updates} + +Rust підтримує durable updates у ролях клієнта застосунку, +вибраного handle та написання worker. Використовуйте +`Client::update_workflow` чи `WorkflowHandle::update` для JSON-сумісних +значень, а відповідні `update_workflow_avro_value` чи `update_avro_value` +для явно типізованих Avro-значень. + +Workers реєструють іменовані обробники через `Worker::register_update`. +Використовуйте `register_update_avro_value`, коли обробник безпосередньо +приймає та повертає Avro-значення. Update є durable-зміною workflow +з результатом. Він відрізняється від signals без очікування відповіді +та read-only queries із replay. + +## Оболонка payload {#payload-envelope} + +Вхідні дані workflow, signals, activities, queries та результати +використовують опублікований контракт `PayloadEnvelope`: +`codec` та закодований `blob`. Стандартний шлях SDK `avro` +використовує оголошену залежність `apache-avro` та закріплену версійовану +Value schema платформи. Не створюйте blob самостійно й не замінюйте +оболонку записом, специфічним для реалізації. + + +```rust +use durable_workflow::{decode_payload, json, PayloadEnvelope, Result, Value}; + +fn round_trip() -> Result<()> { + let envelope = PayloadEnvelope::avro(&json!({"order_id": "order-42"}))?; + assert_eq!(envelope.codec, "avro"); + + let decoded: Value = decode_payload(&envelope)?; + assert_eq!(decoded["order_id"], "order-42"); + Ok(()) +} +``` + +`start_workflow` та `start_workflow_with_options` автоматично +застосовують цю оболонку до серіалізовуваних вхідних даних. +Використовуйте публічні helpers лише коли програма має +безпосередньо обмінюватися оболонкою. + +Довготривалі activities мають надсилати heartbeats та перевіряти `should_stop()`. +За скасування звільніть тимчасові файли, підключення та інші +process-local ресурси й швидко поверніть керування. +Пізній completion відхиляється durable-станом і не може перетворити +скасований чи terminated run на успіх. Керовані workers продовжують +polling після такого остаточного відхилення та після restart. + +Образи Server, автентифікацію та production-топологію описано в +[настанові налаштування Server](/docs/polyglot/server). From 50f8000a31ee1f9e8050b1819c5641b728d1de29 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 03:39:28 +0000 Subject: [PATCH 08/10] docs: translate Ukrainian Cloud and CLI references --- docs/polyglot/cloud-control-plane.md | 30 +- docs/polyglot/worker-protocol.md | 18 +- .../current/polyglot/cli.mdx | 417 ++++++++++++ .../current/polyglot/cloud-control-plane.md | 614 ++++++++++++++++++ .../current/polyglot/worker-protocol.md | 605 +++++++++++++++++ 5 files changed, 1666 insertions(+), 18 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli.mdx create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cloud-control-plane.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-protocol.md diff --git a/docs/polyglot/cloud-control-plane.md b/docs/polyglot/cloud-control-plane.md index ffb3cc46b8..00ba4e6d33 100644 --- a/docs/polyglot/cloud-control-plane.md +++ b/docs/polyglot/cloud-control-plane.md @@ -34,21 +34,25 @@ an isolated managed runtime; customer PHP, Python, and Rust workers run in your own environment. Check [Cloud pricing](https://cloud.durable-workflow.com/pricing) and the dashboard for current provisioning availability. +Cloud Dev is available now. The four HA plans are in qualification and cannot +yet be purchased or provisioned. Their prices, capacities, availability and SLA +terms below are planned terms, not current purchase offers. + | Plan | Runtime capacity | Included durable storage | Availability | Price (USD) | | --- | --- | --- | --- | --- | | Cloud Dev | 1 vCPU, 1 GB RAM | 5 GB | Single host, no SLA | $0.075/hour, $2 active-month minimum, $50/month cap | -| Cloud Standard | 1 vCPU, 2 GB RAM | 25 GB | Single-region HA, 99.99% SLA | $0.45/hour, $10 active-month minimum, $300/month cap | -| Cloud Multi-Region | 1 vCPU, 2 GB RAM | 25 GB | Multi-region HA, 99.99% SLA | $0.60/hour, $15 active-month minimum, $400/month cap | -| Cloud Business | 4 vCPU, 8 GB RAM | 100 GB | Single-region HA, 99.99% SLA | $2.25/hour, $50 active-month minimum, $1,500/month cap | -| Cloud Business Multi-Region | 4 vCPU, 8 GB RAM | 100 GB | Multi-region HA, 99.99% SLA | $3.00/hour, $75 active-month minimum, $2,000/month cap | +| Cloud Standard | 1 vCPU, 2 GB RAM | 25 GB | Planned single-region HA, 99.99% SLA | $0.45/hour, $10 active-month minimum, $300/month cap | +| Cloud Multi-Region | 1 vCPU, 2 GB RAM | 25 GB | Planned multi-region HA, 99.99% SLA | $0.60/hour, $15 active-month minimum, $400/month cap | +| Cloud Business | 4 vCPU, 8 GB RAM | 100 GB | Planned single-region HA, 99.99% SLA | $2.25/hour, $50 active-month minimum, $1,500/month cap | +| Cloud Business Multi-Region | 4 vCPU, 8 GB RAM | 100 GB | Planned multi-region HA, 99.99% SLA | $3.00/hour, $75 active-month minimum, $2,000/month cap | Capacity covers the managed runtime components, not customer worker compute. -HA replication and standby capacity are included in the plan price; the table +For planned HA plans, replication and standby capacity are included in the price; the table does not add replicas together as extra workflow-execution capacity. Every plan includes Managed Waterline, basic encrypted backups, upgrades, and a stable runtime URL. -Every plan is billed for provisioned runtime time, metered by the minute from +Provisioned runtimes are billed for runtime time, metered by the minute from activation to deprovisioning. Minimums and caps apply per runtime purchase in a UTC calendar month. Idle provisioned time is billable; a month with no provisioned time has no runtime charge. A fresh purchase has its own minimum @@ -256,19 +260,21 @@ and must allow the worker protocol's long-lived poll requests. ## Region Placement And Recovery Boundary -Choose the recovery boundary with the plan: +Cloud Dev is available with the recovery boundary below. The HA boundaries +describe the planned plans that remain unavailable pending qualification: - **Cloud Dev:** one isolated host with persistent state and backups. Maintenance and recovery may interrupt service. There is no uptime SLA or automatic regional failover. -- **Single-region HA:** three replicated hosts in one region, with automatic +- **Planned single-region HA:** three replicated hosts in one region, with automatic primary failover. One host may fail without losing the remaining quorum. A whole-region outage is outside this plan's SLA coverage. -- **Multi-region HA:** three replicated hosts across three regions, with +- **Planned multi-region HA:** three replicated hosts across three regions, with automatic primary failover. The SLA includes loss or isolation of one configured region, provided the remaining members can form a quorum. -The stable runtime URL follows the elected primary; you do not change SDK +For the planned HA profiles, the stable runtime URL is intended to follow the +elected primary; you do not change SDK configuration during a supported failover. An isolated former primary is prevented from continuing to serve authoritative work. If a safe primary cannot be established, the runtime stops serving rather than accepting @@ -282,7 +288,7 @@ recovery-time guarantee or a month's achieved availability. ### SLA Measurement And Credits -The four SLA plans provide a 99.99% uptime SLA over a UTC calendar month, +The four planned SLA plans specify a 99.99% uptime SLA over a UTC calendar month, measured at the customer runtime endpoint in one-minute windows. Missing measurements count as unavailable, and planned maintenance is not excluded. Customer-hosted worker availability is separate from managed runtime availability. @@ -535,7 +541,7 @@ disk; request a larger runtime plan before reaching the included allocation. Idle runtimes still incur capacity charges. Deprovisioning stops runtime capacity billing and removes active runtime data and credentials; it is not a -pause/resume operation. SLA plans use the same provisioned-time model at their +pause/resume operation. Planned SLA terms use the same provisioned-time model at their own rates, minimums and caps. Use the plan's returned `billing_terms` when interpreting usage; historical invoices keep their accepted terms. diff --git a/docs/polyglot/worker-protocol.md b/docs/polyglot/worker-protocol.md index 68fbf04daf..89fd1a3fb5 100644 --- a/docs/polyglot/worker-protocol.md +++ b/docs/polyglot/worker-protocol.md @@ -18,7 +18,7 @@ Durable Workflow exposes a versioned worker protocol through two bridge contract ## Protocol Version -The current server-advertised protocol version is **1.13**. The protocol +The current server-advertised protocol version is **1.20**. The protocol follows semver-style numbering: - **Major** bumps when a change is backwards-incompatible (new required fields, removed verbs, changed pagination semantics). @@ -26,9 +26,10 @@ follows semver-style numbering: Workers may use an older minor within the same major. A server advertising `1.N` accepts `X-Durable-Workflow-Protocol-Version: 1.M` when `M <= N` and -returns its advertised `1.N` on the response. The default `1.13` server -therefore accepts request versions `1.0` through `1.13`, including Rust SDK -the Rust SDK on `1.2`. Missing or malformed headers, different majors, and worker +returns its advertised `1.N` on the response. The default `1.20` server +therefore accepts request versions `1.0` through `1.20`. Discover each SDK's +required minor and optional capability profile before deployment. +Missing or malformed headers, different majors, and worker minors newer than the server fail closed. You can retrieve the full protocol description programmatically: @@ -290,7 +291,11 @@ When completing a workflow task, the external worker submits a list of typed com `start_child_workflow` accepts optional `parent_close_policy`, `retry_policy`, `execution_timeout_seconds`, and `run_timeout_seconds` fields. -`parent_close_policy` is one of `abandon`, `request_cancel`, or `terminate`. +`parent_close_policy` is one of `abandon`, `request_cancel`, +`request_cancellation`, or `terminate`. `request_cancel` closes the child +immediately; `request_cancellation` requests cooperative cleanup with the +original cancellation identity and deadline. See +[cooperative cancellation](/docs/polyglot/cancellation). Child retry policy uses the same `max_attempts`, `backoff_seconds`, and `non_retryable_error_types` object shape as activities. Retry backoff applies after a child run fails; invalid child start commands are protocol errors and @@ -355,7 +360,8 @@ Condition-timeout resumes set `workflow_wait_kind: "condition"`, When a control-plane query targets a workflow whose code is owned by an external runtime, the standalone server cannot replay that workflow in the PHP process. Instead, it creates an ephemeral query task and waits for an active -non-PHP worker on the workflow's task queue to execute it. +query-capable worker on the workflow's task queue to execute it. This includes +remote PHP SDK workers whose workflow code is outside the Server process. Query tasks are read-only. Workers replay the supplied history, invoke the registered query handler, and then complete or fail the query task. They do not diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli.mdx b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli.mdx new file mode 100644 index 0000000000..3f842a2407 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli.mdx @@ -0,0 +1,417 @@ +--- +sidebar_position: 3 +title: CLI +description: Встановлення та використання Durable Workflow CLI для роботи з окремим Server. +tags: + - CLI + - operations + - automation +keywords: + - Durable Workflow CLI + - команда dw + - профіль CLI + - встановлення CLI +--- + +import CliInstall from '@site/src/components/CliInstall'; + +# CLI {#cli} + +Durable Workflow CLI (`dw`) є shell-інтерфейсом +[окремого Server](/docs/polyglot/server). +Він дає операторам змогу запускати й перелічувати workflows, +виконувати signal, query, update, repair, cancel, terminate та archive, +керувати schedules, оглядати task queues і перевіряти стан Server +із командного рядка. + +Той самий CLI працює з будь-яким Durable Workflow Server незалежно +від мови workflows. Підтримувані та навмисно різні поверхні CLI, PHP, +Python і Rust наведено в +[можливостях clients і workers](/docs/polyglot/cli-python-parity/). + +## Встановлення {#install} + + + +### Перевірка {#verify} + +```bash +dw --version +``` + +### Встановлення підтримуваного каналу {#supported-channel-install} + +Стандартний installer вибирає випуск CLI з останнього успішно перевіреного +набору артефактів без потреби в номері RC. Новіший опублікований prerelease CLI +не вибирається до qualification публічним джерелом сумісності. +Для відтворюваного CI запишіть отримане `dw --version` +і передавайте цей точний tag через `VERSION` у наступних запусках. + +Обидва installer scripts завантажують `SHA256SUMS` випуску +й перевіряють checksum артефакту перед заміною `dw`. +Підмінене mirror не проходить встановлення. + +
+Linux та macOS (shell installer) + +```bash +%%artifact.cliChannelInstallerCommand%% +dw --version +``` + +`VERSION` приймає будь-який опублікований release tag, `supported`, +`prerelease` або `stable`. Без значення використовується перевірений +канал `supported`. Явний канал `prerelease` вимагає, щоб перевірений +випуск був prerelease. Додаткові змінні середовища: + +- `DURABLE_WORKFLOW_INSTALL_DIR` — каталог установлення, стандартно `~/.local/bin`. +- `DURABLE_WORKFLOW_BIN_NAME` — назва встановленого executable, стандартно `dw`. + +Приклад GitHub Actions: + +```yaml +- name: Install Durable Workflow CLI + run: | + %%artifact.cliChannelInstallerCommand%% + echo "$HOME/.local/bin" >> "$GITHUB_PATH" +- name: Verify + run: dw --version +``` + +
+ +
+Windows (PowerShell installer) + +```powershell +%%artifact.cliChannelPowerShellCommand%% +dw --version +``` + +Installer записує `dw.exe` до `%USERPROFILE%\.durable-workflow\bin` +і додає цей каталог до `PATH` користувача. + +
+ +
+PHAR (переносний, потребує PHP 8.2+) + +```bash +VERSION= +curl -fsSL -o dw.phar \ + "https://github.com/durable-workflow/cli/releases/download/${VERSION}/dw.phar" +curl -fsSL -o SHA256SUMS \ + "https://github.com/durable-workflow/cli/releases/download/${VERSION}/SHA256SUMS" +sha256sum --check --ignore-missing SHA256SUMS +chmod +x dw.phar +./dw.phar --version +``` + +PHAR працює всюди, де вже доступний PHP 8.2 або новіший. +Це рекомендований артефакт спільних CI runners, які вже містять PHP. + +
+ +### Оновлення встановленого binary {#update-an-installed-binary} + +Для standalone-встановлень випуску через shell чи PowerShell installer +вище `dw upgrade` замінює активний binary найновішим опублікованим +випуском після перевірки артефакту за `SHA256SUMS` випуску. + +```bash +dw upgrade # upgrade through the stable release channel +dw upgrade --tag= # pin to a specific release tag +dw upgrade --dry-run # resolve the target release without downloading +``` + +`dw upgrade` відмовляється переписувати Composer vendor, Homebrew cellar +та PHAR installs, бо ці шляхи належать іншому інструменту. +Перевстановіть закріплений публічний випуск installer, оновіть +відповідний package manager або використайте +`brew upgrade durable-workflow/tap/dw` для Homebrew tap. +Повний сталий контракт options і status fields наведено в +[довіднику CLI](./cli-reference.md#self-upgrade). + +## Налаштування {#configure} + +Укажіть ваш Server для CLI: + +```bash +export DURABLE_WORKFLOW_SERVER_URL=http://localhost:8080 +export DURABLE_WORKFLOW_AUTH_TOKEN=your-token +export DURABLE_WORKFLOW_NAMESPACE=default +``` + +Або передавайте ці значення кожній команді: + +```bash +dw --server=http://localhost:8080 --token=your-token workflow:list +``` + +## П'ятихвилинний quickstart оператора {#five-minute-operator-quickstart} + +Цей шлях перевіряє CLI зі справжнім окремим Server без написання коду +застосунку. Він запускає опублікований локальний Server stack, +установлює закріплений `dw`, створює reusable profile, починає +один workflow та стежить за потраплянням run до черги worker. + +```bash +export DW_SERVER_IMAGE=%%artifact.serverDockerHubImage%% +export DW_AUTH_TOKEN=dev-token + +docker volume create durable-workflow-cli-quickstart + +docker run --rm \ + -v durable-workflow-cli-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" server-bootstrap + +docker rm -f durable-workflow-server >/dev/null 2>&1 || true +docker run -d --name durable-workflow-server \ + -p 8080:8080 \ + -v durable-workflow-cli-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" + +until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done +``` + +Установіть CLI в іншому терміналі: + +```bash +%%artifact.cliChannelInstallerCommand%% +export PATH="$HOME/.local/bin:$PATH" +dw --version +``` + +Один раз збережіть підключення Server: + +```bash +dw env:set local \ + --server=http://localhost:8080 \ + --token=dev-token \ + --namespace=default \ + --make-default + +dw doctor +dw server:health +``` + +Почніть workflow та огляньте run: + +```bash +dw workflow:start \ + --type=quickstart.order \ + --workflow-id=quickstart-order-001 \ + --task-queue=quickstart \ + --input='{"order_id":"order-001","total":42.50}' \ + --json + +dw workflow:describe quickstart-order-001 --output=json +dw workflow:history quickstart-order-001 +dw task-queue:describe quickstart +``` + +Run тепер є durable-станом Server. Поки worker типу `quickstart.order` +не опитує чергу `quickstart`, `dw task-queue:describe quickstart` +найшвидше пояснить очікування. Після підключення worker виконайте +`dw watch workflow quickstart-order-001`, щоб стежити до термінального стану. +Повний сценарій із підключенням опублікованого Python SDK worker +та досягненням `status=completed` наведено в +[quickstart Durable Workflow 2.0](/docs/quickstart). + +## Команди {#commands} + +### Server {#server} + +```bash +dw server:health # Check server health +dw server:info # Show server version, role topology, protocols, and worker fleet +``` + +`dw server:info` відображає `GET /api/cluster/info`. +Поряд із build, protocol та worker-fleet фактами він виводить manifest +топології ролей Server: підтримувані форми, поточні shape/process class/roles, +подробиці wake і partition matching, поточні межі запису, +масштабування та області відмов. Використовуйте `--output=json`, +коли автоматизації потрібні raw-поля `topology.*`, або читайте +[топологію ролей Server](./server-role-topology.md) +для контракту кожного поля цього виводу. + +### Workflows {#workflows} + +```bash +dw workflow:list # List workflows +dw workflow:start --type=MyWorkflow --input='["arg1"]' # Start a workflow +dw workflow:start --type=MyWorkflow --input-file=input.json +dw workflow:describe # Describe a workflow +dw workflow:signal --input='["ok"]' +dw workflow:query # Run a query +dw workflow:cancel # Close as cancelled immediately +dw workflow:terminate # Force terminate +dw workflow:history # Show run history +``` + +Кожна команда з payload ініціатора використовує однакову форму: +`--input` для inline-значень, `--input-file` для шляху файлу +чи `-` для stdin, `--input-encoding=json|raw|base64` для кодування. +Стандартно JSON. Raw та base64 input передаються як один +позиційний аргумент workflow. + +### Bridge adapters {#bridge-adapters} + +```bash +dw bridge:webhook stripe \ + --action=start_workflow \ + --idempotency-key=stripe-event-1001 \ + --target='{"workflow_type":"orders.fulfillment","task_queue":"external-workflows"}' \ + --input='{"order_id":"order-1001"}' + +dw bridge:webhook pagerduty \ + --action=signal_workflow \ + --idempotency-key=pd-event-3003 \ + --target='{"workflow_id":"wf-remediation-42","signal_name":"incident_escalated"}' \ + --input='{"severity":"critical"}' \ + --json +``` + +Bridge adapters є обмеженими інструментами ingress інтеграційних подій. +Вони повертають іменовані bridge outcomes прийнятих, повторних +і відхилених deliveries та не стають workflow runtimes. + +### Schedules {#schedules} + +```bash +dw schedule:list # List schedules +dw schedule:create --workflow-type=MyWorkflow \ + --cron='0 * * * *' # Create hourly schedule +dw schedule:update --input-file=input.json +dw schedule:describe # Describe a schedule +dw schedule:pause # Pause a schedule +dw schedule:resume # Resume a schedule +dw schedule:trigger # Trigger immediately +dw schedule:delete # Delete a schedule +``` + +### Activities {#activities} + +```bash +dw activity:complete --input='{"ok":true}' +dw activity:complete --input-file=result.json +dw activity:fail --message='upstream failed' +``` + +### Workers та task queues {#workers-and-task-queues} + +```bash +dw worker:list # List registered workers +dw worker:describe # Describe a worker +dw task-queue:list # List active task queues +dw task-queue:describe # Describe a task queue +``` + +`dw task-queue:list` є стислим оглядом fleet. +Він показує admission status columns для workflow, activity та query tasks. +`dw task-queue:describe` розгортає той самий payload Server з pollers, +поточними leases, dispatch capacity черги, namespace та downstream budget groups, +залишковою capacity, джерелом budget і pending capacity query tasks. + +Для scripts `dw task-queue:describe --json` надає локальні backlog, +lease, poller та admission state черги. Використовуйте його, +щоб розрізняти відсутність workers, admission throttling +і одну чергу з наростанням backlog: + +```bash +dw task-queue:describe orders --json | jq '.stats | { + approximate_backlog_count, + approximate_backlog_age_seconds, + workflow_tasks, + activity_tasks +}' +``` + +Загальні durable inflow та dispatch rates fleet не належать +JSON-контракту task queue. Читайте їх через +`dw system:operator-metrics --json`: + +```bash +dw system:operator-metrics --json | jq '.operator_metrics.backlog | { + tasks_added_last_minute, + tasks_dispatched_last_minute +}' +``` + +Операторський контракт налаштування цих полів описано в +[прийманні task queue](/docs/polyglot/task-queue-admission). + +Для контракту маршрутизації matching вузла, який ви запитали, +прочитайте блок `matching_role` того самого payload: + +```bash +dw system:operator-metrics --json | jq '.operator_metrics.matching_role | { + queue_wake_enabled, + shape, + task_dispatch_mode, + partition_primitives, + backpressure_model +}' +``` + +`partition_primitives` закріплює осі маршрутизації +(`connection`, `queue`, `compatibility`, `namespace`), а +`backpressure_model` визначає використання рушієм зайнятих leases +або іншої межі приймання роботи. Поточний v2 повідомляє `lease_ownership`. + +### Namespaces {#namespaces} + +```bash +dw namespace:list # List namespaces +dw namespace:create --name=production # Create a namespace +dw namespace:describe # Describe a namespace +``` + +### Search attributes {#search-attributes} + +```bash +dw search-attribute:list # List search attributes +dw search-attribute:create --name=env --type=keyword # Register an attribute +``` + +## Exit codes {#exit-codes} + +CLI використовує сталу політику exit codes, щоб scripts і CI pipelines +могли реагувати на конкретні відмови без розбору stderr. +Значення починаються з канонічних `0`/`1`/`2` Symfony Console +для success / failure / usage та розширюють їх. + +| Код | Назва | Значення | +|------|------|---------| +| `0` | `SUCCESS` | Операція завершилася успішно. | +| `1` | `FAILURE` | Загальна помилка: команда виконалася без успіху. | +| `2` | `INVALID` | Некоректне використання: аргументи, невідомі options чи локальна validation. Також для HTTP 4xx, не описаних нижче, наприклад 400, 422. | +| `3` | `NETWORK` | Server недоступний: connection refused, DNS failure, TLS handshake failure чи transport error. | +| `4` | `AUTH` | Помилка authentication чи authorization. Для HTTP `401` та `403`. | +| `5` | `NOT_FOUND` | Ресурс не знайдено. Для HTTP `404`. | +| `6` | `SERVER` | Помилка Server. Для HTTP `5xx`. | +| `7` | `TIMEOUT` | Timeout запиту до відповіді Server. Також для HTTP `408`. | + +Приклад: + +```bash +dw workflow:describe chk-does-not-exist +echo $? # 5 (NOT_FOUND) + +dw server:health --server=http://unreachable:9999 +echo $? # 3 (NETWORK) +``` + +Канонічне джерело — +[`DurableWorkflow\Cli\Support\ExitCode`](https://github.com/durable-workflow/cli/blob/main/src/Support/ExitCode.php) +у репозиторії CLI. + +## Довідник {#reference} + +Форми команд, options, output modes та поведінку помилок автоматизації +наведено в [довіднику команд CLI](/docs/polyglot/cli-reference). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cloud-control-plane.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cloud-control-plane.md new file mode 100644 index 0000000000..165502b368 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cloud-control-plane.md @@ -0,0 +1,614 @@ +--- +sidebar_position: 3 +title: Керований Cloud runtime +description: Порівняння планів Cloud, виміряної місткості workflow, billing і availability та підключення PHP, Python чи Rust workers до керованого namespace. +tags: + - cloud + - managed-runtime + - polyglot + - operations +keywords: + - Durable Workflow Cloud + - namespace runtime URL + - runtime credentials + - workers клієнта + - приватна мережа + - single-region managed runtime + - multi-region managed runtime +--- + +# Керований Cloud runtime {#cloud-managed-runtime} + +Durable Workflow Cloud є керованим сервісом orchestration. +Cloud оперує hosted-площиною керування та orchestration runtime, +включно зі станом workflow, історією, schedules, task queues, leases +та durable-оглядом стану. Клієнти запускають SDK clients і workers +для підготовленого Cloud namespace. + +Це окремий шлях від власного розгортання. +Власний Durable Workflow Server працює незалежно й ніколи не підключається до Cloud. + +## Плани та ціни {#plans-and-pricing} + +Оплачуйте підготовлену місткість, а не семантику workflow. +Кожен namespace отримує ізольований керований runtime. +PHP, Python та Rust workers клієнта працюють у вашому середовищі. +Перевіряйте [ціни Cloud](https://cloud.durable-workflow.com/pricing) +і dashboard щодо поточної доступності provisioning. + +Cloud Dev доступний зараз. Чотири HA-плани перебувають у qualification +та ще не доступні для придбання чи provisioning. +Їхні ціни, місткість, availability та SLA нижче є запланованими умовами, +а не поточними пропозиціями придбання. + +| План | Місткість runtime | Включене durable-сховище | Доступність | Ціна (USD) | +| --- | --- | --- | --- | --- | +| Cloud Dev | 1 vCPU, 1 GB RAM | 5 GB | Один host, без SLA | $0.075/год, мінімум $2 за активний місяць, максимум $50/місяць | +| Cloud Standard | 1 vCPU, 2 GB RAM | 25 GB | Запланований single-region HA, SLA 99.99% | $0.45/год, мінімум $10 за активний місяць, максимум $300/місяць | +| Cloud Multi-Region | 1 vCPU, 2 GB RAM | 25 GB | Запланований multi-region HA, SLA 99.99% | $0.60/год, мінімум $15 за активний місяць, максимум $400/місяць | +| Cloud Business | 4 vCPU, 8 GB RAM | 100 GB | Запланований single-region HA, SLA 99.99% | $2.25/год, мінімум $50 за активний місяць, максимум $1,500/місяць | +| Cloud Business Multi-Region | 4 vCPU, 8 GB RAM | 100 GB | Запланований multi-region HA, SLA 99.99% | $3.00/год, мінімум $75 за активний місяць, максимум $2,000/місяць | + +Місткість охоплює компоненти керованого runtime, а не обчислення workers клієнта. +Для запланованих HA-планів replication і standby capacity включено в ціну. +Таблиця не підсумовує replicas як додаткову місткість виконання workflows. +Кожен план включає Managed Waterline, базові зашифровані backups, +upgrades та сталий runtime URL. + +Підготовлені runtimes оплачуються за runtime time, виміряний похвилинно +від activation до deprovisioning. Мінімум та максимум діють на кожне +придбання runtime в календарному місяці UTC. +Idle provisioned time оплачується. За місяць без provisioned time плати +за runtime немає. Нове придбання має власні мінімум і максимум. +Керовані сервісом repair чи recovery не скидають їх. +Stripe виставляє рахунок за накопичене використання щомісяця. +Ціни не включають застосовні податки. + +Storage фіксований у межах включеного обсягу. Окремих storage add-ons, +автоматичного збільшення чи плати за перевищення storage немає. +Для більшого місця [запросіть більший runtime plan](https://cloud.durable-workflow.com/contact) +до досягнення межі. Сам перехід із single-region до multi-region +не збільшує storage. Capacity upgrade потребує справжньої зміни runtime, +а не лише billing, і зараз узгоджується з підтримкою. +Використання мережі вимірюється, зараз не оплачується та не є необмеженим. + +Виберіть план за [цінами Cloud](https://cloud.durable-workflow.com/pricing). +Для більшої місткості, іншого connectivity, SSO, enterprise support +чи власної вимоги availability +[зверніться до нас](https://cloud.durable-workflow.com/contact). + +## Межа керованого сервісу {#managed-service-boundary} + +Видима клієнту межа — один Cloud namespace: + +```text +Cloud organization + project + environment + namespace + stable runtime URL + client runtime credential ---> workflow starts and commands + worker runtime credential ---> registration, polling, and completion + +Cloud-operated runtime + workflow state and history + schedules and task queues + leases, matching, and visibility + Managed Waterline +``` + +Cloud відповідає за provisioning namespace, роботу runtime, зберігання, +розміщення, стан runtime, recovery та Managed Waterline namespace. +Клієнти відповідають за код застосунку, реалізації workflow/activity +та процеси своїх workers. Клієнти Cloud не розгортають окремий Waterline service. + +HTTPS endpoint namespace підключається безпосередньо до його керованого runtime. +Трафік SDK не проходить через website/control-plane застосунок Cloud. +Використовуйте повернений runtime URL без змін і без додавання `/api`. +SDK та CLI будують свої API paths. Адміністративні API calls використовують +`https://cloud.durable-workflow.com/api/v1`. + +Адміністрування Cloud та runtime traffic використовують різні credentials: + +- Cloud API key (`dwc_...`) керує projects, environments, namespaces, + billing і життєвим циклом runtime credentials. +- Client runtime credential (`dwr_...`) починає та керує workflows + в одному managed namespace. +- Worker runtime credential (`dwr_...`) реєструє workers, виконує long poll + tasks, надсилає heartbeats та завершує роботу цього namespace. + +Cloud API key не приймається runtime URL namespace. +Runtime credentials обмежені одним namespace і роллю, повертаються +лише під час створення та відсутні в подальших list і audit responses. + +Для Cloud onboarding через CLI встановіть його за +[настановою CLI](./cli.mdx) та явно оновіть наявне standalone-встановлення +через `dw upgrade`. CLI ніколи не оновлюється у background. +Після встановлення чи upgrade виконайте `command -v dw` та `dw --version`. +Виконайте PATH-інструкції installer до використання credentials Cloud, +щоб активним binary був вибраний випуск. + +## Provisioning та підключення namespace {#provision-and-connect-a-namespace} + +### 1. Створення та provisioning namespace {#1-create-and-provision-the-namespace} + +У [Cloud dashboard](https://cloud.durable-workflow.com/) створіть +organization, project, environment і namespace та виберіть capacity plan. +Створення account чи визначення namespace не готує платну місткість. +Завершіть payment setup через Stripe, потім виберіть **Provision** +на сторінці namespace. Саме додавання картки не починає provisioning. + +Provisioning асинхронний. Сторінка namespace показує progress, +поки Cloud готує runtime. Перед роботою SDK дочекайтеся активного стану. +Сторінка також надає створення runtime credentials та доступ +до Managed Waterline для огляду workflows і їхньої історії. + +Для API-driven setup спочатку завершіть payment setup. +Приклад використовує стандартний план organization. +`capacity_plan_version` може вибрати інший доступний план. +Не передавайте власний Server URL, deployment identifier чи placement record: + +```bash +curl -X POST \ + https://cloud.durable-workflow.com/api/v1/projects/PROJECT/environments/ENVIRONMENT/namespaces \ + -H "Authorization: Bearer dwc_..." \ + -H "Content-Type: application/json" \ + -d '{"name":"orders","retention_days":30}' + +curl -X POST \ + https://cloud.durable-workflow.com/api/v1/projects/PROJECT/environments/ENVIRONMENT/namespaces/orders/provision \ + -H "Authorization: Bearer dwc_..." +``` + +Після provisioning відповідь namespace надає сталий `runtime_url`, +`runtime_namespace`, managed status та видиму клієнту інформацію про region. +Повернені URL і namespace є конфігурацією Cloud. +Не виводьте endpoint самостійно й не замінюйте його адресою власного Server. + +### 2. Окремі client і worker credentials {#2-issue-separate-client-and-worker-credentials} + +Видайте обидві runtime roles незалежно: + +```bash +curl -X POST \ + https://cloud.durable-workflow.com/api/v1/projects/PROJECT/environments/ENVIRONMENT/namespaces/orders/runtime-credentials \ + -H "Authorization: Bearer dwc_..." \ + -H "Content-Type: application/json" \ + -d '{"name":"orders-client","role":"client"}' + +curl -X POST \ + https://cloud.durable-workflow.com/api/v1/projects/PROJECT/environments/ENVIRONMENT/namespaces/orders/runtime-credentials \ + -H "Authorization: Bearer dwc_..." \ + -H "Content-Type: application/json" \ + -d '{"name":"orders-worker","role":"worker"}' +``` + +Кожен токен показується один раз у create response. +Зберігайте його в secret store, доступному лише відповідній ролі. +Навмисно спільний client/worker process отримує обидва значення +як два окремі secrets. Ротуйте й відкликайте ролі незалежно. +Ніколи не замінюйте runtime credential на Cloud API key. + +### 3. Перший workflow у Cloud {#cloud-first-workflow} + +Спільний external-runtime playground Sample App виконує той самий +написаний workflow/activity сценарій із PHP, Python чи Rust. +Відкрийте [Sample App Codespace](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main), +експортуйте підготовлені значення namespace і два runtime credentials +та виберіть task queue застосунку: + +```bash +export DURABLE_WORKFLOW_RUNTIME_URL='' +export DURABLE_WORKFLOW_NAMESPACE='' +export DURABLE_WORKFLOW_CLIENT_TOKEN='' +export DURABLE_WORKFLOW_WORKER_TOKEN='' +export DURABLE_WORKFLOW_TASK_QUEUE='' +``` + +Потім виберіть офіційний SDK і запустіть той самий managed-runtime контракт: + +```bash +language=php # Choose php, python, or rust. +scripts/playground "$language" --runtime managed \ + --runtime-url "$DURABLE_WORKFLOW_RUNTIME_URL" \ + --namespace "$DURABLE_WORKFLOW_NAMESPACE" \ + --task-queue "$DURABLE_WORKFLOW_TASK_QUEUE" +``` + +Runner визначає поточні стабільні версії артефактів, передає кожен credential +лише відповідному дочірньому процесу, чекає до 60 секунд реєстрації worker +з точною чергою та згенерованими типами workflow/activity, +після чого починає один client request. +Успіх потребує очікуваного результату SDK, статусу `completed` від `dw` +та потрібної історії workflow/activity. У managed mode команда +не запускає локальний Server чи Waterline. + +Для написання workflow конкретного SDK продовжуйте мовною настановою: + +- [PHP SDK](/docs/polyglot/php/): виконайте `scripts/playground php`. +- [Python SDK](/docs/polyglot/python/): виконайте `scripts/playground python`. +- [Rust managed-runtime quickstart](/docs/polyglot/rust-cloud-quickstart/): + виконайте `scripts/playground rust` і використовуйте його діагностику + готовності worker, завершеного результату та невідповідностей. + +Спільний client/worker сценарій зберігає обидва runtime credentials +як окремі secrets. Cloud administration key не є runtime credential +і не може експортуватися в жодній змінній runtime token. + +### 4. Подальша керована робота {#4-continue-with-managed-operation} + +SDK client починає workflows і надсилає наступні команди через runtime URL +namespace. Workers клієнта реєструються та виконують long poll через той самий +URL із роллю worker. Cloud автентифікує та обмежує кожен запит, +виконує orchestration protocol у managed runtime +та зберігає стан workflow й історію. + +Застосунок клієнта не вибирає runtime deployment для операції. +Workflow IDs, run IDs, task queues, compatibility markers і кодування Avro +залишаються durable-контрактами застосунку всередині Cloud namespace. + +## Підключення workers клієнта {#customer-run-worker-connectivity} + +Workers можуть працювати у вашій мережі, VM fleet, контейнерній платформі +чи середовищі застосунку. Їм потрібний вихідний HTTPS-доступ до runtime URL +namespace та дозвіл довготривалих poll requests worker-протоколу. + +- Вхідне підключення від Cloud до worker не потрібне. +- Proxies та egress gateways не мають скорочувати long polls до busy retry loop. +- Workers мають повторювати тимчасові connection failures та service-unavailable + responses з обмеженим backoff. +- Переміщення worker process не переміщує стан workflow. + Cloud зберігає durable-стан namespace та історію. +- Ротація credentials не потребує зміни runtime URL, namespace чи task queue. + +## Розміщення в регіоні та межа recovery {#region-placement-and-recovery-boundary} + +Cloud Dev доступний із межею recovery нижче. +HA-межі описують заплановані плани, які залишаються +недоступними до завершення qualification: + +- **Cloud Dev:** один ізольований host із постійним станом і backups. + Maintenance і recovery можуть переривати сервіс. + Uptime SLA та автоматичного regional failover немає. +- **Запланований single-region HA:** три репліковані hosts в одному регіоні + з автоматичним primary failover. Один host може відмовити + без втрати решти quorum. Відмова цілого регіону не покривається SLA цього плану. +- **Запланований multi-region HA:** три репліковані hosts у трьох регіонах + з автоматичним primary failover. SLA включає втрату чи ізоляцію одного + налаштованого регіону за можливості решти members сформувати quorum. + +Для запланованих HA-профілів сталий runtime URL має стежити за elected primary. +SDK-конфігурацію не потрібно змінювати під час підтримуваного failover. +Ізольований колишній primary не може продовжувати authoritative-роботу. +Якщо безпечний primary неможливо визначити, runtime припиняє обслуговування +замість приймання суперечливих записів. HA не обіцяє безперервних запитів: +клієнти та workers усе одно потребують bounded retries тимчасових помилок. + +Сторінка namespace показує топологію, інформацію про region та service status. +Backups підтримують recovery, але не замінюють live replication. +Короткі failover-тести демонструють виконані випадки відмов, +а не універсальну гарантію recovery time чи місяць досягнутої availability. + +### Вимірювання SLA та credits {#sla-measurement-and-credits} + +Чотири заплановані SLA-плани визначають uptime SLA 99.99% +за календарний місяць UTC, виміряний на runtime endpoint клієнта +в однохвилинних вікнах. Відсутні вимірювання рахуються як недоступність. +Planned maintenance не виключається. Availability workers клієнта +відокремлена від availability managed runtime. + +| Місячна availability | Автоматичний account credit | +| --- | ---: | +| Щонайменше 99.99% | Немає | +| Щонайменше 99.9%, нижче 99.99% | 10% | +| Щонайменше 99.0%, нижче 99.9% | 25% | +| Нижче 99.0% | 100% | + +Account credits застосовуються автоматично. +Cash refunds потребують перевірки та схвалення. +Cloud Dev не має SLA credits. +Область availability та billing terms плану наведено в +[цінах Cloud](https://cloud.durable-workflow.com/pricing). + +## Приватне підключення та межа підтримки {#private-connectivity-and-support-boundary} + +Самостійний контракт Cloud 2.0 передбачає вихідний доступ clients +і workers до публічного runtime URL namespace. Private-only ingress, +окремі VPN чи peering та private routing конкретного provider +є connectivity-дизайном із підтримкою, а не прихованими стандартними +налаштуваннями. Клієнтам не надають внутрішні runtime addresses +і не пропонують обхід namespace URL. + +## Cloud або власний Server {#cloud-or-self-hosted-server} + +Виберіть Cloud, коли Durable Workflow має оперувати orchestration runtime, +зберіганням, availability вибраного плану, recovery та Managed Waterline, +а ваша команда — SDK clients і workers. + +Виберіть [власний Server](/docs/polyglot/server), коли ваша команда +має незалежно оперувати image Server, базою, cache, мережею, +автентифікацією, backups і failover. Власний Server не можна +зареєструвати, підключити чи використати як backing runtime +Cloud namespace. Embedded Laravel, власний Server та Cloud +є окремими виборами розгортання. + +## Benchmarks стандартного workflow {#standard-workflow-benchmarks} + +[DW Standard Workflow v1](https://github.com/durable-workflow/server/tree/main/benchmarks/capacity) +визначає невелике відтворюване навантаження: один запуск workflow, +одну external activity та один completion workflow з визначеними +Avro inputs і results по 1 KiB. Worker клієнта працює поза +виділеною місткістю managed runtime. + +Cloud Dev виміряно 21 вересня 2026 року на тому самому плані, +який готують клієнти. Інші чотири baselines є історичними: +вони включають replicated HA topology, але передують поточному runtime release. +Їх не екстрапольовано з Dev, і вони не підтверджують +місткість поточних SLA-планів. + +| План | Стандартних workflows/секунду | Workflow actions за 30 днів | Вимірювання | +| --- | ---: | ---: | --- | +| Cloud Dev | 0.25 | 1,296,000 | 21 вересня 2026 року, деталі нижче | +| Cloud Standard | 0.25 | 1,296,000 | Історичне, очікується повторне вимірювання | +| Cloud Multi-Region | 0.10 | 518,400 | Історичне, очікується повторне вимірювання | +| Cloud Business | 0.50 | 2,592,000 | Історичне, очікується повторне вимірювання | +| Cloud Business Multi-Region | 0.20 | 1,036,800 | Історичне, очікується повторне вимірювання | + +Оцінка за 30 днів дорівнює +`workflows/second x 2,592,000 seconds x 2 workflow actions`: +один start та одна activity у цьому порівнянні. +Вона припускає безперервну роботу з цією швидкістю протягом 30 днів. +Це не включена квота actions, billing unit чи точне зіставлення +з визначеннями actions іншого рушія. + +Це виміряні baselines навантаження, а не максимальний throughput +чи гарантована місткість кожного застосунку. Більші payload, більше activities, +timers, signals, queries, історія з великим replay, міжрегіональна +комунікація та latency workers клієнта змінюють результат. +Таблиця не заявляє окремих benchmarks timer, signal, replay чи saturation. +Плануйте фактичну суміш workflow замість множення цих чисел +на довільний розмір workflow. + +### Вимірювання Cloud Dev {#cloud-dev-capacity} + +Cloud Dev є ізольованим single-host managed runtime для розробки +та оцінювання. Вимірювання використовувало звичайно підготовлений +Dev runtime без змін CPU, memory, storage, ingress чи backup: + +| Ресурс | Cloud Dev | +| --- | --- | +| Обчислення runtime | 1 shared vCPU, 1 GB RAM | +| Включене durable-сховище | 5 GB | +| Сервіси runtime | Server, queue worker, scheduler, MySQL і Redis | +| Managed Waterline | Включений через автентифікований Cloud-застосунок, не є процесом на Dev host | +| Мережевий шлях | Прямий HTTPS ingress конкретного space | +| Workers клієнта | Працюють у середовищі клієнта | +| Доступність | Без SLA, перерви на обслуговування дозволені | +| Поточна ціна runtime | Дивіться [плани та ціни](#plans-and-pricing) | +| Більше storage | Запросіть більший runtime plan, storage add-ons немає | + +Запуск використав публічне +[PHP-визначення DW Standard Workflow v1](https://github.com/durable-workflow/server/blob/68168f34b637a19c054211f4db2df9c616799a86/benchmarks/capacity/v1/bindings/php/capacity_adapter.php): +один workflow start, одну зовнішню echo activity та один completion +із рядком 1,024 bytes в Avro на кожній межі workflow/activity input і result. +Server 2.3.12 містив Workflow 2.0.14. Зовнішній worker та clients +використовували PHP SDK 2.0.11 на PHP 8.4.25. +Runtime використовував PHP 8.3.33 і MySQL 8.0.46. + +Один PHP worker використовував task-poll timeout в одну секунду. +Два незалежні client slots подавали один workflow кожні чотири секунди +після окремого 30-секундного warmup. Фіксоване вікно вимірювання — +**18:50:30–18:55:30 UTC 21 вересня 2026 року**. +Throughput рахує лише completions, спостережені в ці п'ять хвилин. +Пізніший drain не збільшує повідомлену швидкість. +Кожен result, completed status та впорядкована історія workflow/activity перевірені. + +| Виміряний результат | Значення | +| --- | ---: | +| Подана та завершена швидкість | 0.25 стандартних workflows/секунду | +| Завершені workflows | 75 із 75 | +| Помилки / пропущені start slots | 0 / 0 | +| Start-to-result latency, p50 | 2.76 секунди | +| Start-to-result latency, p95 | 4.18 секунди | +| Start-to-result latency, p99 | 4.33 секунди | +| Незавершені workflows виміряної групи | 0 | +| Workflow actions за 30 днів | 1,296,000 | + +Latency включає мережу клієнта та polling результату кожні 100 ms. +Це не внутрішня latency планування task, і її не слід порівнювати +з внутрішньою scheduling-метрикою рушія. + +Host CPU у середньому був **63.5% busy**, із піком **89%** у п'ятисекундних +samples. **OOM kills чи restarts контейнерів не було**. +Спостережені піки memory containers: 95.21 MiB Server, 197.1 MiB MySQL, +45.84 MiB queue worker, 43.75 MiB scheduler, 9.49 MiB Redis +та 34.59 MiB ingress connector. Ці окремі піки не є одночасним RSS +усього host. OS та інші host processes також споживають memory. +Host мав звичайну конфігурацію swap 1 GiB, із зайнятими приблизно +до 90 MiB під час вимірювання. Початковий зашифрований backup +було перевірено до вікна. Жоден backup capture з ним не перетинався. + +Це виміряна development baseline, а не універсальний перерахунок +кожного workflow, maximum-throughput тест, довготривалий soak чи SLA. +Більші payload, додаткові activities, timers, signals, queries, +історія з великим replay та latency workers клієнта змінюють capacity. +Cloud billing базується на provisioned runtime time з фіксованим +включеним storage, а не на workflow operations. + +## API billing usage {#billing-usage-api} + +Cloud Dev використовує одну ізольовану single-host runtime cell +для кожного підготовленого Dev space. Billing terms: + +| Умова оплати | Cloud Dev | +| --- | ---: | +| Provisioned runtime | $0.075 за годину, вимірювання похвилинно | +| Максимум календарного місяця | $50 на придбання runtime | +| Мінімум активного місяця | $2 на придбання runtime в активному місяці UTC | +| Керована місткість | 1 vCPU, 1 GB memory, 5 GB durable storage | +| Додаткове durable-сховище | Не продається окремо, запросіть більший runtime plan | +| Базові зашифровані backups | Включено | +| Доступність | Один host, без SLA | + +Виділена місткість охоплює Server, MySQL, Redis, scheduling, доступ Waterline, +backups, upgrades та інфраструктуру Cloud. PHP, Python і Rust workers +клієнта працюють поза нею. Workflow starts, activity attempts, retries, +timers, signals, queries, updates та child workflows є операційною +телеметрією, а не окремими billing units. +Ціни не включають застосовні податки. Включений storage не створює окремої плати. + +Cloud надає billing usage у межах organization для finance, operations +та chargeback automation. Endpoint автентифікується Cloud API key +і не приймає customer чи organization ID у запиті. +Organization ініціатора визначається bearer token `dwc_`, +щоб один клієнт не міг читати usage іншого. + +```http +GET /api/v1/billing/usage?period_start=2026-05-01&period_end=2026-05-31 +Authorization: Bearer dwc_... +Accept: application/json +``` + +`period_start` і `period_end` є необов'язковими ISO-8601 dates. +Без них Cloud повертає поточний календарний місяць. +Читання та exports billing usage залишаються доступними, навіть якщо +billing restrictions призупинили provisioning namespace чи workflow operations, +щоб finance teams могли відновити стан account. + +Schema відповіді — `durable_workflow.cloud.namespace_capacity_usage.v1`. +Вона розділяє allocated capacity time та semantic event counters. +Storage-related поля залишаються для історичного billing +і не дозволяють придбання add-on. Скорочена відповідь Cloud Dev +нижче показує поточні plan terms. + +```json +{ + "schema": "durable_workflow.cloud.namespace_capacity_usage.v1", + "access_control": { + "scope": "organization_billing_usage", + "read_allowed": true, + "export_allowed": true + }, + "current_period": { + "starts_at": "2026-05-01T00:00:00+00:00", + "ends_at": "2026-05-31T23:59:59+00:00" + }, + "metering_policy": { + "invoice_drivers": [ + "namespace_plan_capacity", + "additional_durable_storage_gb_month" + ], + "semantic_events_are_invoice_units": false, + "network": "measured_not_billable", + "basic_backups": "included", + "customer_worker_compute": "excluded" + }, + "by_namespace": [ + { + "namespace": "development", + "project": "sample-app", + "environment": "development", + "plan": { + "version": "cloud-dev.single-host-v2", + "name": "Cloud Dev", + "availability_class": "development_single_host", + "sla_status": "none", + "billing_terms": { + "currency": "usd", + "unit": "provisioned_runtime_hour", + "hourly_rate_cents": "7.5", + "monthly_cap_cents": 5000, + "active_month_minimum_cents": 200, + "scope": "runtime_purchase", + "billing_period": "calendar_month_utc", + "metering_resolution_seconds": 60, + "additional_storage_unit": "gb_month", + "additional_storage_rate_cents": 0, + "additional_storage_available": false, + "additional_storage_metering_unit": "gb_hour", + "additional_storage_in_monthly_cap": false + } + }, + "allocation": { + "managed_cpu_vcpu": 1, + "managed_memory_gb": 1, + "included_durable_storage_gb": 5 + }, + "operational_telemetry": { + "billing_status": "not_billable", + "counters": { + "workflow_execution_count": 20, + "activity_execution_count": 40, + "timer_fire_count": 5, + "signal_delivery_count": 3, + "update_delivery_count": 0, + "query_task_count": 2 + } + } + } + ] +} +``` + +Time meter Cloud Dev починається під час activation ізольованого runtime +та зупиняється під час deprovisioning. Місячні максимум і мінімум +діють на придбання runtime в календарних місяцях UTC. +Storage не зростає та не починає оплачуватися автоматично. +Cloud зберігає operating та recovery reserve на runtime disk. +Запросіть більший runtime plan до досягнення включеного обсягу. + +Idle runtimes усе одно оплачують capacity. Deprovisioning припиняє +capacity billing і видаляє активні runtime data та credentials. +Це не pause/resume операція. Заплановані SLA terms використовують +ту саму provisioned-time модель із власними rates, мінімумами й максимумами. +Для читання usage використовуйте повернені `billing_terms` плану. +Історичні invoices зберігають прийняті ними terms. + +Експортуйте ті самі дані у CSV чи JSON report для передачі файлу +до downstream finance system: + +```bash +curl -OJ "https://cloud.durable-workflow.com/api/v1/billing/usage/export?period_start=2026-05-01&period_end=2026-05-31" \ + -H "Authorization: Bearer dwc_..." + +curl -OJ "https://cloud.durable-workflow.com/api/v1/billing/usage/report?period_start=2026-05-01&period_end=2026-05-31" \ + -H "Authorization: Bearer dwc_..." +``` + +Для JSON-backed dashboard panel запитайте той самий API +з часовим діапазоном panel: + +```text +GET https://cloud.durable-workflow.com/api/v1/billing/usage?period_start=${__from:date:YYYY-MM-DD}&period_end=${__to:date:YYYY-MM-DD} +Authorization: Bearer dwc_... +``` + +Потім перетворіть namespace capacity rows на плоску структуру: + +```jq +.by_namespace[] +| { + project, + environment, + namespace, + plan: .plan.name, + cpu_vcpu: .allocation.managed_cpu_vcpu, + memory_gb: .allocation.managed_memory_gb, + included_storage_gb: .allocation.included_durable_storage_gb, + capacity_status + } +``` + +Використовуйте `invoice_units` для звірки capacity та storage. +`operational_telemetry` пояснює структуру навантаження і поведінку benchmarks. +Не перетворюйте ці event counters на оплату. + +## Пов'язані довідники {#related-references} + +- [Режими розгортання](/docs/polyglot/deployment-modes) +- [PHP SDK](/docs/polyglot/php) +- [Python SDK](/docs/polyglot/python) +- [Rust SDK](/docs/polyglot/rust) +- [Server](/docs/polyglot/server) +- [Самостійне розгортання](/docs/deployment) +- [Підтримка](/docs/support) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-protocol.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-protocol.md new file mode 100644 index 0000000000..e0a62953a8 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/worker-protocol.md @@ -0,0 +1,605 @@ +--- +sidebar_position: 5 +title: Worker-протокол +description: Реалізація версійованого протоколу worker plane для polling, leases, replay історії, heartbeats, completion і результатів external tasks. +tags: + - worker-protocol + - external-workers + - polyglot +keywords: + - worker-протокол + - polling зовнішнього worker + - workflow task bridge +--- + +# Worker-протокол {#worker-protocol} + +Durable Workflow надає версійований worker-протокол через два bridge contracts. +Вони визначають повний набір операцій, якими зовнішні workers, +включно з окремим Durable Workflow Server, виконують poll, claim, +execute та complete workflow й activity tasks. + +## Версія протоколу {#protocol-version} + +Поточна версія протоколу, оголошена Server, — **1.20**. +Протокол використовує нумерацію за принципами semver: + +- **Major** підвищується за несумісної зміни: нові обов'язкові поля, + видалені операції, змінена семантика пагінації. +- **Minor** підвищується за додаткових змін: нові необов'язкові поля, + нові нетермінальні типи команд. + +Workers можуть використовувати старіший minor того самого major. +Server з `1.N` приймає `X-Durable-Workflow-Protocol-Version: 1.M` +за `M <= N` і повертає оголошений `1.N` у відповіді. +Стандартний Server `1.20` приймає версії запиту від `1.0` до `1.20`. +Перед розгортанням визначте потрібний minor та необов'язковий профіль +capabilities кожного SDK. Відсутні чи некоректні заголовки, +інші majors та worker minors вище Server відхиляються. + +Повний опис протоколу можна отримати програмно: + +```php +use Workflow\V2\Support\WorkerProtocolVersion; + +$summary = WorkerProtocolVersion::describe(); +// Returns version, verb lists, command types, and pagination defaults. +``` + +## Discovery capabilities {#capability-discovery} + +Окремий Server публікує capabilities worker-протоколу під +`worker_protocol.server_capabilities` у `GET /api/cluster/info`. +Той самий об'єкт повертається як `server_capabilities` у відповідях +worker plane, включно з poll, heartbeat, complete та fail. + +Прочитайте ці поля перед надсиланням необов'язкових полів команд: + +- `supported_workflow_task_commands`: типи команд, прийняті completion workflow task. +- `poll_status`: poll-відповіді містять машиночитаний статус навіть без lease task, + розрізняючи `leased`, `empty`, `throttled` та `unavailable` + без виведення стану черги з `task: null`. +- `activity_retry_policy` і `activity_timeouts`: retry та timeout options activity. +- `worker_session_verbs` і `worker_sessions`: операції життєвого циклу + worker session, поля команд activity, поновлення, виявлення відмов + і термінальні статуси. Runtime-форму worker sessions визначає + [`worker-sessions-runtime.schema.json`](/platform-protocol-specs/worker-sessions-runtime.schema.json). +- `child_workflow_retry_policy` і `child_workflow_timeouts`: retry та timeout options child workflow. +- `parent_close_policy`: підтримка політики закриття parent для child workflow. +- `query_tasks`: спрямовані Server query tasks workflow зовнішніх runtimes. +- `non_retryable_failures`: підтримка metadata помилок workflow та activity. + +Перед polling кожен worker має зареєструвати namespace, task queue, +runtime, підтримувані ключі типів та локальну місткість через +`POST /api/worker/register`. +[Namespace, автентифікація та реєстрація worker](/docs/polyglot/namespace-auth-workers) +закріплює цей payload і контракт auth ролей. +Ширший контракт пошуку готових tasks та призначення leases описано в +[matching та dispatch tasks](/docs/polyglot/task-matching-dispatch). +Affinity activity між кількома durable-кроками описано в +[worker sessions](/docs/features/worker-sessions). + +## Семантика виконання {#execution-semantics} + +Транспорт worker є розподіленою координацією at-least-once, +а не доставкою exactly-once: + +- replay workflow task відновлює стан із зафіксованої історії + та не є retry цілого workflow +- сплив lease activity task може спричинити повторну доставку іншому worker +- пізні reports completion чи failure можуть відхилятися як застарілі, + бо інша спроба вже перемогла durable race + +Використовуйте `activity_execution_id` як стандартний ключ віддаленої +ідемпотентності під час звернення worker чи carrier до іншої системи. +Використовуйте `activity_attempt_id` лише якщо downstream-система +має розрізняти окремі спроби того самого логічного виконання activity. + +Авторитетний контракт replay, retry, lease expiry, повторної доставки +та exactly-once durable-історії описано в +[гарантіях виконання та ідемпотентності](/docs/constraints/execution-guarantees). + +## Workflow Task Bridge {#workflow-task-bridge} + +Контракт `WorkflowTaskBridge` визначає взаємодію зовнішнього worker +з durable workflow tasks: + +| Операція | Опис | +|------|-------------| +| `poll` | Пошук готових workflow tasks за чергою та критеріями сумісності | +| `claim` / `claimStatus` | Claim конкретного task з отриманням lease на 5 хвилин | +| `historyPayload` | Отримання повної історії replay leased task | +| `historyPayloadPaginated` | Отримання історії сторінками для великих workflows | +| `execute` | Claim та виконання task у процесі через executor пакета | +| `complete` | Подання команд зовнішнього worker для завершення task | +| `fail` | Запис помилки task зовнішнім worker | +| `heartbeat` | Подовження lease отриманого task | + +### Історія зі сторінками {#paginated-history} + +Для workflows з великою історією отримуйте події сторінками +через `historyPayloadPaginated`: + +```php +use Workflow\V2\Contracts\WorkflowTaskBridge; + +$bridge = app(WorkflowTaskBridge::class); + +$afterSequence = 0; +$allEvents = []; + +do { + $page = $bridge->historyPayloadPaginated($taskId, $afterSequence, 500); + $allEvents = array_merge($allEvents, $page['history_events']); + $afterSequence = $page['next_after_sequence'] ?? $afterSequence; +} while ($page['has_more']); +``` + +Стандартний розмір сторінки — 500 подій, що відповідає +`WorkerProtocolVersion::DEFAULT_HISTORY_PAGE_SIZE` і +`default_history_page_size` capabilities worker-протоколу Server. +Максимум — 1000. Server може оголосити інше дієве стандартне значення +в worker-protocol manifest, тому читайте capability замість жорсткого +задання числа. Відповідь містить `has_more` та `next_after_sequence` +для cursor pagination. + +### Стиснення історії {#history-compression} + +Для workflows з дуже великою історією bridge чи Server може стиснути +payload подій для зменшення передачі. Стиснення явно вмикається +ініціатором через параметр на зразок `Accept-Encoding`. + +Коли кількість подій відповіді перевищує поріг стиснення 50, +bridge може повернути: + +- `history_events`: `[]`, порожній масив, що вказує на події у стисненому полі +- `history_events_compressed`: стиснений payload у base64 +- `history_events_encoding`: алгоритм `gzip` або `deflate` + +Ініціатор декодує base64, розпаковує вказаним алгоритмом і декодує JSON +для відновлення початкового масиву `history_events`. + +```php +use Workflow\V2\Support\HistoryPayloadCompression; + +// Compress a history payload for transfer (bridge/server side). +$compressed = HistoryPayloadCompression::compress($payload, 'gzip'); + +// Decompress on the worker side. +$original = HistoryPayloadCompression::decompress($compressed); +``` + +Якщо ініціатор не запитує стиснення або кількість подій нижча за поріг, +відповідь містить стандартний нестиснений масив `history_events`. + +### Семантика long poll {#long-poll-semantics} + +Обидві операції `poll` підтримують необов'язковий long-poll mode. +За параметра `timeout_seconds` bridge чи Server утримує підключення +до вказаного часу в очікуванні готового відповідного task +замість негайного порожнього результату. + +| Параметр | Стандартне значення | Мінімум | Максимум | +|-----------|---------|-----|-----| +| `timeout_seconds` | 30 | 1 | 60 | + +Поведінка: + +- Якщо task стає готовим під час очікування, він повертається негайно. +- Якщо timeout спливає без task, відповідь зберігає звичайну poll-оболонку + та повертає `task: null` із `poll_status: "empty"`. +- Після порожньої long-poll відповіді клієнт має повторити запит негайно, + якщо не завершує роботу. +- HTTP timeouts транспорту мають перевищувати 60 секунд + для запобігання передчасному disconnect. + +Кожна poll-відповідь worker містить `poll_status`: + +| Значення | Значення статусу | +|------|---------| +| `leased` | Server надав task у lease цьому worker. | +| `empty` | Відповідний task не став готовим до завершення short poll чи timeout long poll. | +| `throttled` | Обмеження приймання не дозволили Server видати lease, хоча черга може мати готові tasks. | +| `unavailable` | Черга чи matching тимчасово недоступні. Worker має трактувати poll як тимчасову інфраструктурну невдачу. | + +Перевіряйте `poll_status` перед висновком, що порожній poll означає +вільну місткість. `task: null` із `poll_status: "throttled"` +означає backpressure, а не відсутність роботи. + +```php +use Workflow\V2\Support\WorkerProtocolVersion; + +$semantics = WorkerProtocolVersion::longPollSemantics(); +// ['default_timeout_seconds' => 30, 'min_timeout_seconds' => 1, 'max_timeout_seconds' => 60] + +// Clamp a caller-supplied timeout to the valid range. +$clamped = WorkerProtocolVersion::clampLongPollTimeout($userTimeout); +``` + +### Статус poll-відповіді {#poll-response-status} + +Server зберігає один контракт poll-відповіді для polling +workflow tasks, activity tasks і query tasks: + +- `task`: payload leased task або `null`, якщо lease не видано. +- `poll_status`: машиночитаний результат спроби poll. +- `protocol_version` та `server_capabilities`: повернуті поля + worker-protocol manifest. + +Workers мають перевіряти `poll_status` перед припущеннями +щодо `task` для конкретного маршруту: + +| `poll_status` | Типовий HTTP status | Значення | +| --- | --- | --- | +| `leased` | `200` | Server видав lease роботи, `task` містить payload. | +| `empty` | `200` | Відповідний task не став готовим до повернення poll. | +| `throttled` | `200` | Обмеження приймання черги не дозволили новий lease цієї спроби poll. | +| `unavailable` | `503` або `200` | Server не міг безпечно координувати чергу й повернув типізований unavailable. | +| `draining` | `409` | Група build ID worker у drain. Server відмовляє в новому lease і повертає `reason: "worker_draining"`. | + +### Запити completion, heartbeat і fail {#completion-heartbeat-and-fail-requests} + +Endpoints workflow-task `complete`, `heartbeat` та `fail` вимагають +повторення worker двох полів ідентичності lease з відповіді poll чи claim: + +| Поле | Тип | Опис | +|------|------|-------------| +| `lease_owner` | string | Ідентичність worker-власника lease task. Має збігатися з `lease_owner` із `poll` чи `claim`. | +| `workflow_task_attempt` | integer ≥ 1 | Номер спроби leased task. Має збігатися з `workflow_task_attempt` із `poll` чи `claim`. | + +Застарілі спроби чи неправильні власники lease відхиляються перед +застосуванням команд. Worker зі спливлим lease не може зафіксувати +команди replay для task, який уже отримав інший claim. + +Endpoints і тіла запитів: + +- `POST /api/worker/workflow-tasks/{task_id}/complete` — потребує + `lease_owner`, `workflow_task_attempt` та непорожнього масиву `commands`. + Форми команд наведено в [типах команд](#command-types). +- `POST /api/worker/workflow-tasks/{task_id}/heartbeat` — потребує + `lease_owner` та `workflow_task_attempt`. Повертає поновлений expiry lease + та поточний статус run. +- `POST /api/worker/workflow-tasks/{task_id}/fail` — потребує + `lease_owner`, `workflow_task_attempt` та об'єкта `failure` + з обов'язковим `message` і необов'язковими `type` та `stack_trace`. + +Приклад запиту complete: + +```json +{ + "lease_owner": "py-worker-1", + "workflow_task_attempt": 1, + "commands": [ + { "type": "complete_workflow" } + ] +} +``` + +Приклад запиту heartbeat: + +```json +{ + "lease_owner": "py-worker-1", + "workflow_task_attempt": 1 +} +``` + +Приклад запиту fail: + +```json +{ + "lease_owner": "py-worker-1", + "workflow_task_attempt": 1, + "failure": { + "message": "Replay mismatch at event 7", + "type": "DeterminismFailed" + } +} +``` + +### Типи команд {#command-types} + +Під час completion workflow task зовнішній worker подає список +типізованих команд. Один completion може мати не більше однієї +термінальної команди. + +**Нетермінальні команди**, нуль чи більше, обробляються за порядком: + +| Тип | Обов'язкові поля | Опис | +|------|----------------|-------------| +| `schedule_activity` | `activity_type` | Планування activity task для виконання | +| `start_timer` | `delay_seconds` | Планування durable timer | +| `start_child_workflow` | `workflow_type` | Запуск екземпляра child workflow | +| `complete_update` | `update_id` | Позначення прийнятого update застосованим і завершеним | +| `fail_update` | `update_id`, `message` | Позначення прийнятого update невдалим | +| `record_side_effect` | `result` | Запис детермінованого результату side effect | +| `record_version_marker` | `change_id`, `version`, `min_supported`, `max_supported` | Запис рішення версіювання | +| `upsert_search_attributes` | `attributes` | Upsert індексованих metadata run workflow | + +`schedule_activity` приймає необов'язкові `retry_policy`, +`start_to_close_timeout`, `schedule_to_start_timeout`, +`schedule_to_close_timeout` та `heartbeat_timeout`. +`retry_policy` використовує `max_attempts`, `backoff_seconds` +і `non_retryable_error_types`. + +`start_child_workflow` приймає необов'язкові `parent_close_policy`, +`retry_policy`, `execution_timeout_seconds` та `run_timeout_seconds`. +`parent_close_policy` є `abandon`, `request_cancel`, +`request_cancellation` або `terminate`. `request_cancel` закриває child +негайно. `request_cancellation` запитує кооперативне cleanup з початковими +ідентичністю cancellation та deadline. Дивіться +[кооперативне скасування](/docs/polyglot/cancellation). +Child retry policy використовує той самий об'єкт `max_attempts`, +`backoff_seconds` та `non_retryable_error_types`, що й activities. +Retry backoff діє після невдалого child run. Некоректні child start commands +є помилками протоколу та не витрачають child retry attempts. + +`complete_update` закриває прийнятий update з `update_id` після застосування +update handler worker. Він приймає необов'язковий payload `result` +у тій самій оболонці `{codec, blob}`, що й результати completion workflow. +`fail_update` закриває прийнятий update як невдалий і приймає +необов'язкові `exception_class`, `exception_type` та `non_retryable` +разом з обов'язковим `message`. + +**Термінальні команди**, не більше однієї: + +| Тип | Обов'язкові поля | Опис | +|------|----------------|-------------| +| `complete_workflow` | — | Позначення run завершеним, необов'язковий `result` | +| `fail_workflow` | `message` | Позначення run невдалим | +| `continue_as_new` | — | Закриття run та початок нового, необов'язкові `arguments`, `workflow_type` | + +Якщо cancel чи terminate закриває run під час lease workflow task, +виклики task `history`, `heartbeat`, `complete` і `fail` зберігають +оболонку worker-протоколу, але відхиляються з `reason: "run_closed"`. +Відповідь також містить `can_continue: false`, `cancel_requested: true` +та конкретний `stop_reason`, наприклад `run_cancelled` чи `run_terminated`. +Workers можуть відрізнити спостереження cancellation від загальної lease error. +`run_closed_reason` і `run_closed_at` із durable-запису run +дають workers змогу логувати точний стан закриття, який зупинив leased task. + +Poll-відповіді workflow task містять сталий контекст resume, +скопійований із durable payload task: + +| Поле | Значення | +|------|---------| +| `workflow_wait_kind` | Очікування цього task: `update`, `signal`, `child`, `condition`, `timer` чи `null` для звичайного replay/start | +| `open_wait_id` | Стала ідентичність очікування, наприклад `update:{id}` чи `signal-application:{id}` | +| `resume_source_kind` / `resume_source_id` | Durable-джерело wake task, наприклад `workflow_update`, `workflow_signal`, `timer` чи `child_workflow_run` | +| `workflow_update_id` | ID прийнятого update, який застосовує task | +| `workflow_signal_id` | ID прийнятого signal, який застосовує task | +| `signal_name` / `signal_wait_id` | Ціль signal та стала ідентичність очікування, коли task застосовує signal чи timer-backed очікування signal | +| `workflow_command_id` | ID команди площини керування, яка створила task, за наявності | +| `activity_execution_id` / `activity_attempt_id` / `activity_type` | Ідентифікатори activity після успішного чи невдалого completion activity | +| `child_call_id` / `child_workflow_run_id` | Ідентифікатори очікування child під час визначення результату child workflow | +| `timer_id` / `condition_wait_id` | Ідентифікатори чистого timer та timer-backed condition під час resume після timer | +| `condition_key` / `condition_definition_fingerprint` | Стала мітка condition та fingerprint predicate, записані timer-backed очікуванням condition | +| `workflow_sequence` / `workflow_event_type` | Sequence історії та тип події event-backed tasks результату activity, child чи timer | + +Незастосовні поля дорівнюють `null`. SDK workers мають використовувати +ці поля замість сканування історії для зв'язку leased task із прийнятим +update, signal, результатом activity, результатом child чи timer-backed wait. +Pure timer resume задає `workflow_wait_kind: "timer"`, +`open_wait_id: "timer:{timer_id}"`, `resume_source_kind: "timer"` та `timer_id`. +Signal-backed resume задає `workflow_wait_kind: "signal"` і `signal_name`. +Tasks застосування прийнятого signal також задають `workflow_signal_id`, +а timer-backed signal waits — `signal_wait_id` та `timer_id`, що спрацював. +Condition-timeout resume задає `workflow_wait_kind: "condition"`, +`condition_wait_id` і для keyed waits — `condition_key` +та `condition_definition_fingerprint`. + +## Query tasks {#query-tasks} + +Коли query площини керування спрямований до workflow, чий код належить +зовнішньому runtime, окремий Server не може replay його в PHP-процесі. +Натомість він створює тимчасовий query task і чекає активного worker +з підтримкою query на task queue workflow. Це включає віддалені +PHP SDK workers, чий код workflow перебуває поза процесом Server. + +Query tasks є read-only. Workers replay надану історію, +викликають зареєстрований query handler і complete чи fail query task. +Вони не записують durable-події історії та не повторюються +після timeout query площини керування в ініціатора. + +| Endpoint | Опис | +|----------|-------------| +| `POST /api/worker/query-tasks/poll` | Long poll query task зареєстрованої task queue worker | +| `POST /api/worker/query-tasks/{query_task_id}/complete` | Подання результату query | +| `POST /api/worker/query-tasks/{query_task_id}/fail` | Відхилення чи помилка query | + +Poll-запит: + +```json +{ + "worker_id": "py-worker-1", + "task_queue": "orders" +} +``` + +Poll-відповідь: + +```json +{ + "poll_status": "leased", + "task": { + "query_task_id": "01J...", + "query_task_attempt": 1, + "workflow_id": "order-123", + "run_id": "01J...", + "workflow_type": "order-processing", + "query_name": "status", + "payload_codec": "avro", + "workflow_arguments": { "codec": "avro", "blob": "" }, + "query_arguments": { "codec": "avro", "blob": "" }, + "history_events": [], + "task_queue": "orders", + "lease_owner": "py-worker-1", + "lease_expires_at": "2026-04-18T12:00:00.000000Z" + }, + "protocol_version": "1.13", + "server_capabilities": { "query_tasks": true } +} +``` + +`task` дорівнює `null`, якщо poll не видав lease. Використовуйте +`poll_status`, щоб розрізняти звичайне порожнє очікування, throttling +та тимчасову недоступність черги. Worker має повторити `lease_owner` +і `query_task_attempt` під час completion чи failure. +Застарілі спроби та неправильні власники leases відхиляються. + +Запит complete: + +```json +{ + "lease_owner": "py-worker-1", + "query_task_attempt": 1, + "result": { "status": "ready" }, + "result_envelope": { "codec": "avro", "blob": "" } +} +``` + +Запит fail: + +```json +{ + "lease_owner": "py-worker-1", + "query_task_attempt": 1, + "failure": { + "reason": "rejected_unknown_query", + "message": "unknown query 'status'", + "type": "QueryFailed" + } +} +``` + +Використовуйте `reason: "rejected_unknown_query"`, якщо тип workflow +не має відповідного query handler. Ініціатор площини керування отримує `404`. +Інші query failures worker мають використовувати `reason: "query_rejected"` +і повертати `409`. За відсутності активного worker, який може прийняти query, +площина керування повертає `query_worker_unavailable`. +Без результату до налаштованого timeout вона повертає `query_worker_timeout`. + +## Activity Task Bridge {#activity-task-bridge} + +Контракт `ActivityTaskBridge` визначає взаємодію зовнішнього worker +з activity tasks: + +| Операція | Опис | +|------|-------------| +| `poll` | Пошук готових activity tasks за чергою та критеріями сумісності | +| `claim` / `claimStatus` | Claim конкретного activity task з lease | +| `complete` | Запис completion activity з результатом | +| `fail` | Запис failure activity з необов'язковими `failure.details` із codec | +| `status` | Перевірка liveness та стану cancellation без поновлення lease | +| `heartbeat` | Подовження lease та повідомлення необов'язкового прогресу | + +Відповіді heartbeat activity містять `can_continue` та `cancel_requested`, +щоб довготривалі activities могли реагувати на запити скасування. +Коли cancel чи terminate рівня run зупиняє leased activity, +відповіді heartbeat, complete та fail також містять +`run_closed_reason` і `run_closed_at`. + +## Codecs payload {#payload-codecs} + +Кожен рядок bytes payload через межу worker-протоколу позначений +**`payload_codec`**, що визначає формат blob. V2 використовує один +мовно-незалежний codec **`avro`**, щоб будь-який SDK (PHP, Python, Go, +TypeScript, Rust) міг кодувати та декодувати payload без спільного +runtime чи app key. Активний Server оголошує підтримку codecs у +**`capabilities.payload_codecs`** відповіді `GET /api/cluster/info`. + +### Codec `avro` {#the-avro-codec} + +`avro` є codec типізованих значень v2. Кожен payload використовує +закріплену рекурсивну schema `durable_workflow.protocol.Value` +та стандартне Avro single-object framing. Іменовані branches зберігають +booleans, signed 64-bit integers, скінченні doubles, bytes, UTF-8 strings, +lists та string-keyed maps без schemas окремих workflows чи registry. +Дивіться [протокол Avro Value](/docs/polyglot/avro-value-protocol/). + +### Формат передачі: оболонка payload {#wire-format-payload-envelope} + +Поля з bytes payload (`arguments`, `result`, `payload` тощо) +надають codec поруч із непрозорим рядком. Poll-відповіді мають вигляд: + +```json +{ + "task_id": "...", + "payload_codec": "avro", + "arguments": { + "codec": "avro", + "blob": "" + }, + "history_events": [ ... ] +} +``` + +Worker читає `payload_codec` і підтверджує `avro` перед декодуванням. +Невідомий codec є помилкою. Worker не має визначати формат за вмістом чи вгадувати. + +Completions activity надсилають `result` у тій самій оболонці `{codec, blob}`. +Failures activity можуть надсилати структуровані діагностичні payload +під `failure.details`. За наявності це також оболонка `{codec, blob}`. +Server зберігає details blob без змін і записує `details_payload_codec` +з durable payload помилки, щоб workers не на PHP могли передавати +діагностичні дані в обидва боки без PHP serialization. + +Сталий міжмовний контракт помилки включає `activity_type`, +`failure_category`, `exception_type`, `message`, `code`, `non_retryable` +та `details` із codec. Runtime-поля, зокрема назви класів винятків, +шляхи source files, номери рядків і stack traces, є лише діагностикою. +SDK не мають надавати їх у стандартному `exception_payload`. +Вони можуть показувати їх лише якщо worker чи Server явно записав +оболонку `diagnostics` або `runtime_diagnostics`. + +### Запуск workflow {#starting-a-workflow} + +`POST /api/workflows` приймає `input` у двох формах: + +1. **Звичайний JSON-масив** — Server перетворює кожне JSON-значення + на закріплену Avro Value schema. JSON input не може виразити bytes branch. + Клієнти, яким потрібні bytes, надсилають явну Avro envelope. + + ```json + { "workflow_type": "MyWorkflow", "input": ["hello", 42] } + ``` + +2. **Явна оболонка** — для клієнтів із уже закодованими bytes: + + ```json + { + "workflow_type": "MyWorkflow", + "input": { "codec": "avro", "blob": "" } + } + ``` + +Server зберігає blob без змін і позначає run codec `avro`. + +Codec зберігається на `WorkflowRun` і **діє протягом життя run**: +аргументи activities, результати, аргументи signals/updates +та вхідні дані child workflows кодуються Avro. + +Embedded/package starts, запущені з PHP через +`WorkflowStub::make(...)->start(...)` замість HTTP API, +також визначають стандартний codec нового run через остаточний +контракт Avro-only v2. + +## Отримання bridges {#resolving-the-bridges} + +Обидва bridges зареєстровані в контейнері Laravel +та доступні безпосередньо: + +```php +use Workflow\V2\Contracts\WorkflowTaskBridge; +use Workflow\V2\Contracts\ActivityTaskBridge; + +$workflowBridge = app(WorkflowTaskBridge::class); +$activityBridge = app(ActivityTaskBridge::class); +``` + +## Пов'язані настанови {#related-guides} + +- [Server](./server.md) описує endpoints площини керування + та форму розгортання, які надають цей протокол. +- [Зовнішнє виконання](./external-execution.md) пояснює контракти + worker, bridge та handler рівня activity поверх worker-протоколу. From 6aeaac2858b36d8427663a2dbe31cb3cef145bee Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 03:59:02 +0000 Subject: [PATCH 09/10] Complete Ukrainian migration, operations and service guides --- docs/operator-operating-envelope.md | 10 +- docs/polyglot/python.md | 2 +- docs/polyglot/server-api-reference.md | 2 +- docs/polyglot/server.md | 11 +- .../current/migration.md | 838 +++++++++ .../current/operator-operating-envelope.md | 656 +++++++ .../current/platform-conformance.md | 539 ++++++ .../current/polyglot/cli-reference.md | 486 +++++ .../current/polyglot/python.md | 1669 +++++++++++++++++ .../current/polyglot/server-api-reference.md | 724 +++++++ .../current/polyglot/server.md | 1168 ++++++++++++ 11 files changed, 6094 insertions(+), 11 deletions(-) create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/migration.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/operator-operating-envelope.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/platform-conformance.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-reference.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-api-reference.md create mode 100644 i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server.md diff --git a/docs/operator-operating-envelope.md b/docs/operator-operating-envelope.md index 98917ba30c..228e2c56b7 100644 --- a/docs/operator-operating-envelope.md +++ b/docs/operator-operating-envelope.md @@ -351,11 +351,11 @@ These facts describe durable workflow-task and activity-task traffic only. When you need queue-local drill-down instead of fleet totals, use the server task-queue visibility routes for backlog age, poller state, current leases, -and admission budgets. Those routes do not currently expose per-queue -`stats.tasks_added_last_minute` or `stats.tasks_dispatched_last_minute`; use -the fleet-level `operator_metrics.backlog.*` pair above to compare durable -inflow with dispatch, then use queue-local routes to see which queue is -building backlog or has no available worker capacity. +admission budgets, `stats.tasks_added_last_minute`, and +`stats.tasks_dispatched_last_minute`. Compare queue-local inflow with dispatch +to identify the queue building backlog or lacking available worker capacity. +Use the fleet-level `operator_metrics.backlog.*` pair above for the aggregate +comparison. Waterline's `GET /waterline/api/v2/health` surface publishes the same queue drill-down under `queue_visibility.*` for the configured namespace. Treat these diff --git a/docs/polyglot/python.md b/docs/polyglot/python.md index 361109051f..2ae7694e44 100644 --- a/docs/polyglot/python.md +++ b/docs/polyglot/python.md @@ -344,7 +344,7 @@ code. | Method | Returns | Notes | | --- | --- | --- | -| `await client.health()` | `dict[str, Any]` | Calls the health endpoint for readiness checks. | +| `await client.health()` | `dict[str, Any]` | Calls the health endpoint for liveness checks. | | `await client.get_cluster_info()` | `dict[str, Any]` | Reads server version, protocol, capability, and compatibility metadata. | | `await client.list_task_queues()` | `TaskQueueList` | Lists task queues visible in the namespace. | | `await client.describe_task_queue(name)` | `TaskQueueDescription` | Returns worker capacity, current leases, query admission, and dispatch-budget facts. | diff --git a/docs/polyglot/server-api-reference.md b/docs/polyglot/server-api-reference.md index defeceb05e..244947b011 100644 --- a/docs/polyglot/server-api-reference.md +++ b/docs/polyglot/server-api-reference.md @@ -454,7 +454,7 @@ curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/workflows" \ ### Workflow Commands -Protocol 1.20's release candidate also provides cooperative +The server also provides cooperative `request-cancellation` routes. See [Cooperative cancellation](/docs/polyglot/cancellation/) for the bounded cleanup contract and worker requirements. diff --git a/docs/polyglot/server.md b/docs/polyglot/server.md index dde155d6ff..9f9c5ed806 100644 --- a/docs/polyglot/server.md +++ b/docs/polyglot/server.md @@ -1075,8 +1075,10 @@ maintenance runner. Choose stop-the-world upgrades or [rolling upgrades](/docs/rolling-upgrades) per release; the rolling-upgrade contract names the version-skew, schema, drain, readiness, and rollback guarantees that must hold. SQLite clustering, Redis-less multi-node mode, -duplicate schedulers, active/active multi-region, Helm, and provider-specific -failover are outside that contract until separately validated. Active/passive +duplicate schedulers, active/active multi-region, and provider-specific +failover are outside that contract until separately validated. The separately +versioned Helm path is documented in the +[deployment guide](/docs/deployment#helm-chart-for-kubernetes). Active/passive multi-region material in the [self-hosting guide](/docs/deployment#activepassive-multi-region) is support-led evaluation guidance, not a proven self-serve 2.0 contract; each @@ -1153,8 +1155,9 @@ and lease counters. The detailed route also includes the `admission` object so automation can separate worker-capacity pressure from server-side queue or query-task throttling. Fleet-level durable inflow versus dispatch rates live on the operator-metrics surfaces (`operator_metrics.backlog.tasks_added_last_minute` -and `operator_metrics.backlog.tasks_dispatched_last_minute`), not on the -per-queue task-queue routes. +and `operator_metrics.backlog.tasks_dispatched_last_minute`). The task-queue +routes also expose `stats.tasks_added_last_minute` and +`stats.tasks_dispatched_last_minute` for the same comparison within one queue. ### Worker Protocol diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/migration.md b/i18n/uk/docusaurus-plugin-content-docs/current/migration.md new file mode 100644 index 0000000000..9f1cbbd8b9 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/migration.md @@ -0,0 +1,838 @@ +--- +sidebar_position: 13 +title: Перехід на 2.0 +description: Оновлення наявного застосунку Laravel Workflow v1 до Durable Workflow v2. +tags: + - migration + - upgrade + - v2 +keywords: + - перехід на Durable Workflow v2 + - оновлення Laravel Workflow + - міграція схеми v2 + - workflow v1 до v2 +--- + +# Перехід на 2.0 {#migrating-to-20} + +Цей посібник описує основні зміни під час оновлення наявного застосунку Laravel з v1 до v2. +Спочатку виберіть, чи Laravel і надалі керуватиме runtime, чи підключатиметься до Cloud або +самостійно розгорнутого Server через PHP SDK, за допомогою +[посібника з упровадження в Laravel і переходу між runtime](/docs/laravel-adoption/). +Далі ця сторінка описує докладну процедуру переходу вбудованого пакета з v1 до v2. + +## Процедура оновлення {#upgrade-procedure} + +### Перед оновленням {#before-upgrading} + +**1. Складіть перелік усіх сховищ виконання v1** + +Стан виконання v1 може зберігатися поза базою даних workflow. Окрім рядків workflow та історії, +завдання черги Laravel можуть бути готовими, відкладеними або зарезервованими в Redis, +базі даних черги, SQS чи іншому бекенді черги. До зміни коду зафіксуйте: + +- з'єднання зі сховищем workflow та фактичні відповідності моделей і таблиць v1 +- усі з'єднання та назви черг, які використовують workflow й activity v1, + включно з перевизначеннями для окремих workflow чи activity +- базу даних, префікс ключів, регіон, обліковий запис та інші параметри маршрутизації + бекенду черги, потрібні для відновлення тих самих черг +- посилання на секрет у менеджері секретів і його незмінну версію для отримання точного + `APP_KEY`, а також сховище кешу для блокувань унікальних завдань v1 + +Не копіюйте значення `APP_KEY`, облікові дані відновлення менеджера секретів, +бази даних чи провайдера черги до цього переліку. Для зашифрованих завдань і серіалізованих +аргументів workflow потрібен початковий ключ, але маніфест відновлення має містити лише +посилання на нього в менеджері секретів і версію. Облікові дані для отримання ключа +зберігайте окремо від резервних копій SQL і черг, з окремим контролем доступу. + +Типові таблиці v1: `workflows`, `workflow_logs`, `workflow_signals`, +`workflow_timers`, `workflow_exceptions` і `workflow_relationships`. +Опублікований файл `config/workflows.php` може перевизначати +`stored_workflow_model`, `stored_workflow_log_model`, +`stored_workflow_signal_model`, `stored_workflow_timer_model` або +`stored_workflow_exception_model`. Власна модель може також перевизначати +Eloquent `$table`, а `workflow_relationships_table` окремо задає таблицю зв'язків. +Створіть резервні копії таблиць і з'єднання зі сховищем, визначених цими +налаштованими моделями, а не лише таблиць із типовими назвами. + +**2. Виберіть межу відкату й призупиніть роботу v1** + +Заблокуйте запуск нових workflow і надсилання signal на час створення узгодженого +набору відновлення. Призупиніть планувальники й зупиніть усі worker, що споживають +завдання з перелічених черг, після завершення поточного завдання. Наприклад: + +```bash +# Horizon +php artisan horizon:pause + +# Supervisor or systemd (use the names from your deployment) +sudo supervisorctl stop :* +sudo systemctl stop laravel-worker +``` + +Сам по собі `php artisan queue:restart` не призупиняє роботу, якщо Supervisor, +systemd, Kubernetes чи інший менеджер процесів одразу запускає нового worker. +Перш ніж фіксувати стан, переконайтеся, що жоден споживач не може зарезервувати +наступне завдання. + +Виберіть і зафіксуйте одну з цих політик: + +- **Завершити роботу v1:** заблокуйте нову роботу v1 і залиште worker v1 працювати, + доки `php artisan workflow:v1:list` (якщо команда доступна) або еквівалентний + запит до налаштованої таблиці збережених workflow не покаже відсутність незавершених workflow. + Потім зупиніть worker і переконайтеся, що відповідні черги не містять готових, + відкладених чи зарезервованих завдань v1. Для таких завершених workflow v1 достатньо відкату SQL. +- **Зберегти активну роботу v1:** зупиніть worker між завданнями та зробіть узгоджений + знімок SQL і кожного надійного бекенду черги. Знімок черги має містити готову, + відкладену й зарезервовану роботу та відповідати тому самому моменту відновлення, + що й SQL. Дочекайтеся завершення зарезервованого завдання, якщо бекенд не описує, + як безпечно відновити його повідомлення та lease. +- **Прийняти втрату активної роботи:** якщо бекенд не дає створити відновлюваний + узгоджений знімок черги, зафіксуйте відповідні ID workflow і явно прийміть, що + незавершені виконання, залежні від черги, неможливо відновити цим відкатом. + Виключайте придатний рядок `pending` з цього переліку лише після перевірки + шляху Watchdog нижче, а справжнє очікування лише signal лише після перевірки + збереженого входу signal. Не вважайте інші рядки SQL відновлюваною роботою. + +Очікування лише signal може правомірно не мати завдання в черзі, якщо зовнішній +вхід signal залишається доступним після відкату. Повторні спроби activity й timer +залежать від черги: рядок `workflow_timers` або рядок `workflows` зі станом очікування +не відтворює втрачене відкладене завдання. + +Підтримувана v1.0.77 також запускає типово ввімкнений `Workflow\Watchdog` із циклу +worker черги. Watchdog може знайти workflow `pending`, чий `updated_at` старший за +п'ять хвилин і чиї серіалізовані `arguments` наявні, та повторно направити його +до записаних з'єднання й черги. Це обмежений шлях пробудження лише для `pending`, +а не заміна резервної копії черги. Покладайтеся на нього лише після перевірки +в тестовому середовищі: відновлений цикл worker v1 запускає Watchdog, Watchdog +повторно направляє придатний застарілий рядок `pending`, а workflow просувається +на очікуваній черзі. Цей шлях не відтворює повторні спроби activity чи завдання timer +та не відновлює рядки `waiting` або `running`. Для них потрібне збережене завдання +черги або справний зовнішній шлях signal, якщо workflow справді очікує signal. + +**3. Створіть резервну копію набору відновлення для відкату** + +Перед оновленням створіть повну резервну копію бази даних: + +```bash +# MySQL/MariaDB +mysqldump -u root -p your_database > backup-v1-$(date +%Y%m%d-%H%M%S).sql + +# PostgreSQL +pg_dump -U postgres your_database > backup-v1-$(date +%Y%m%d-%H%M%S).sql + +# Laravel backup package (if installed) +php artisan backup:run --only-db +``` + +Потім збережіть стан черги відповідно до її бекенду: + +- **Черга в базі даних:** включіть таблиці черги та їхнє з'єднання до того самого + узгодженого набору відновлення. Вони можуть бути поза базою даних workflow. +- **Черга Redis:** використайте відновлюваний знімок чи резервну копію Redis із + точною базою даних і префіксом черги та її готовими, відкладеними й зарезервованими + ключами. Якщо ця база Redis містить потрібні блокування унікальних завдань, + збережіть і їх. Бажано мати окрему базу чи екземпляр для черг, адже відновлення + спільного знімка Redis може відкотити сторонні дані застосунку. +- **SQS чи інша керована черга:** використовуйте відновлення повідомлень на момент часу, + підтримуване провайдером, лише якщо воно узгоджено зберігає доступні, відкладені + й активні повідомлення. SQS не надає довільного знімка черги для цієї процедури, + тому завершіть роботу v1 або визнайте решту залежної від черги роботи v1 невідновлюваною. + Єдині винятки для відновлення лише SQL: перевірений рядок `pending`, придатний + для Watchdog, і перевірене очікування лише signal. + +Зберігайте резервні копії SQL і черг, перелік без секретів і час відновлення разом. +Маніфест відновлення може містити посилання на `APP_KEY` у менеджері секретів і +його версію, але не сам ключ чи облікові дані для його отримання. Доступ до облікових +даних менеджера секретів, бази даних, провайдера черги та відновлення резервних копій +контролюйте окремо. Відновлення SQL і стану черги з різних моментів часу не є +підтримуваним способом відкату активної роботи. + +**4. Спочатку перевірте в тестовому середовищі** + +**Не оновлюйте production без перевірки в тестовому середовищі.** Оновлення включає: + +- Зміни схеми бази даних: надійне ядро v2 додає нові таблиці, точну кількість + визначає `php artisan migrate`, а майбутня об'єднана міграція може згрупувати файли +- Зміни просторів імен, що потребують оновлення коду +- Перезапуск worker черги з короткою перервою +- Перевірку можливостей бекенду + +**Контрольний список перевірки:** + +- [ ] Розгорніть код v2 у тестовому середовищі +- [ ] Виконайте міграції тестової бази даних +- [ ] Перезапустіть worker черги +- [ ] Виконайте `php artisan workflow:v2:doctor --strict` +- [ ] Запустіть новий workflow v2 і перевірте його завершення +- [ ] Перевірте, що workflow v1, якщо вони є, також завершуються +- [ ] Перевірте відображення workflow v1 і v2 у Waterline +- [ ] Запустіть тести свого застосунку +- [ ] Перевірте відсутність помилок у журналах + +Переходьте до production лише після успішної перевірки в тестовому середовищі. + +### Кроки оновлення {#upgrade-steps} + +**1. Оновіть залежність Composer** + +```bash +composer require %%artifact.workflowComposerPackage%% +``` + +Підтримуваний пакет Composer для v1 і v2 має назву `durable-workflow/workflow`. +Ця команда змінює обмеження версії на поточну зафіксовану публічну версію v2. +Стара назва `laravel-workflow/laravel-workflow` є лише псевдонімом сумісності для +графів залежностей, створених до перейменування пакета. Для нових вимог і відкату +використовуйте підтримувану назву. Поточна публічна зафіксована версія належить +стабільному пакету 2.0. Використовуйте `durable-workflow/workflow:^2.0`, якщо +хочете, щоб Composer автоматично приймав сумісні оновлення 2.x. + +**2. Виконайте міграції бази даних** + +```bash +php artisan migrate +``` + +v2 додає таблиці надійного ядра, які забезпечують контракт функцій v2. +Поточні міграції окремих функцій створюють: + +- Ядро: `workflow_instances`, `workflow_runs`, `workflow_history_events`, `workflow_tasks`, `workflow_commands` +- Activity: `activity_executions`, `activity_attempts` +- Функції: `workflow_updates`, `workflow_signal_records`, `workflow_run_waits`, `workflow_run_timeline_entries`, `workflow_run_lineage_entries`, `workflow_schedules`, `workflow_schedule_history_events` +- Спостережуваність: `workflow_run_summaries`, `workflow_failures`, `workflow_links`, `worker_compatibility_heartbeats` +- Timer: `workflow_run_timers`, `workflow_run_timer_entries` +- Пошук / memo / повідомлення / дочірні workflow: `workflow_search_attributes`, `workflow_memos`, `workflow_messages`, `workflow_child_calls` +- Каталог сервісів: `workflow_service_endpoints`, `workflow_services`, `workflow_service_operations`, `workflow_service_calls` + +Майбутня об'єднана міграція може згрупувати їх у меншу кількість файлів без зміни +надійного контракту. Підтримуваний спосіб визначити фактичний стан бази даних: +виконати `php artisan migrate:status` після оновлення. + +Типові таблиці v1 (`workflows`, `workflow_logs`, `workflow_signals`, +`workflow_timers`, `workflow_exceptions`, `workflow_relationships`) +зберігаються для завершення виконання на v1. Якщо налаштовані моделі v1 +перевизначають таблиці чи з'єднання зі сховищем, саме ці таблиці є джерелом істини v1. + +**3. За потреби оновіть конфігурацію** + +Конфігурація v2 зворотно сумісна. Якщо ви опублікували `config/workflow.php` +у v1, він продовжить працювати. Нові параметри v2: + +- `durable_types`: псевдоніми типів для незалежних від мови посилань на workflow +- `task_repair_policy`: обробка застряглих завдань +- `backend_capability_check`: сувора чи дозвільна перевірка +- `projection_rebuild`: стратегії відновлення з історії +- `history_budget`: обмеження кількості подій для continue-as-new + +Вони мають практичні типові значення. Змінюйте їх лише за потреби іншої поведінки. +Докладніше в розділі [Конфігурація](/docs/configuration/options/). + +**Типовий кодек payload змінено на `avro`.** v1 типово використовувала +PHP-специфічний `Workflow\Serializers\Y::class`. v2 типово використовує незалежний +від мови кодек `avro`, щоб worker інших мов могли декодувати payload без спільного +PHP runtime. `avro` є єдиним підтримуваним кодеком для нових workflow v2. +Нові workflow v2 отримують `payload_codec = "avro"`. + +Якщо у вас є опублікований `config/workflows.php` із v1 зі значенням +`'serializer' => Workflow\Serializers\Y::class`, v2 продовжує читати його для +діагностики міграції, але payload нових workflow v2 використовує Avro. +Після оновлення виконайте `php artisan workflow:v2:doctor`: команда позначить +застарілий кодек як питання завершення чи імпорту роботи v1. + +Щоб явно прийняти типове значення v2, не задавайте `serializer` або зафіксуйте його: + +```php +// config/workflows.php — v2 default (language-neutral, compact binary) +'serializer' => 'avro', +``` + +Зберігайте застарілий кодек (`'workflow-serializer-y'` чи +`'workflow-serializer-base64'`) лише для завершення workflow v1, що обмінюються +власними значеннями PHP між сервером і worker лише на PHP. Застарілі назви класів +(`Workflow\Serializers\Y::class` тощо) залишаються псевдонімами для декодування v1. + +**Власні класи серіалізаторів v1 не підтримуються у v2.** Публічний реєстр v2 +розпізнає лише `avro`. Застарілі декодери `workflow-serializer-y` і +`workflow-serializer-base64` обмежені внутрішнім шляхом імпорту та завершення v1. +Їх не можна вибрати для нового виконання v2 чи payload SDK. Якщо у вас був власний +серіалізатор, завершіть виконання v1 до оновлення або перекодуйте історичний payload +в `avro`: власний клас не викликається. `php artisan workflow:v2:doctor` позначає +інші значення `workflows.serializer` як борг міграції. Відсутність кодека нового +виконання означає `avro`. + +**Власні підкласи моделей підтримуються лише зі збереженням контракту колонок і ключів пакета.** +Зафіксована [матриця налаштувань](/docs/configuration/customization-matrix/) +є авторитетним джерелом: підкласи моделей екземпляра, виконання, завдання, події історії, +проєкції, schedule, activity, помилки, зв'язку, повідомлення, memo, атрибута пошуку +та дочірнього виклику v2 підтримуються зі збереженням назв таблиць, первинних і зовнішніх +ключів пакета. Власні назви таблиць із власними назвами колонок зовнішніх ключів +виходять за межі контракту. Waterline читає проєкції v2 через контракт +`Workflow\V2\Contracts\OperatorObservabilityRepository`, тому сумісний зі схемою +підклас не потребує зміни Waterline. + +**Змінні середовища:** + +v2 не додає нових обов'язкових змінних середовища. Наявні `QUEUE_CONNECTION`, +`CACHE_DRIVER` і `DB_CONNECTION` продовжують працювати. + +**4. Перезапустіть worker черги** + +Worker черги потрібно перезапустити для завантаження коду v2: + +```bash +# If using Laravel queue workers +php artisan queue:restart + +# If using Supervisor +sudo supervisorctl restart :* + +# If using systemd +sudo systemctl restart laravel-worker + +# If using Horizon +php artisan horizon:terminate +``` + +Worker: +1. Завершать поточне завдання +2. Коректно завершать процес +3. Перезапустяться із завантаженим кодом v2 + +**Worker мають перезапуститися до обробки workflow v2.** Workflow v1 можуть +завершуватися зі старими чи новими worker завдяки сумісності finish-on-v1. + +### Після оновлення {#after-upgrading} + +**1. Перевірте можливості бекенду** + +```bash +php artisan workflow:v2:doctor --strict +``` + +Очікуваний результат: + +``` +✓ Database driver supports required features +✓ Queue driver supports required features +✓ Cache driver supports locks +✓ All backend capabilities present +``` + +Якщо перевірка не пройшла, дивіться передумови драйверів у розділі +[Вимоги до бекенду](/docs/installation/#requirements). + +**2. Перевірте успішний запуск workflow v2** + +Запустіть тестовий workflow через API v2: + +```php +use Workflow\V2\WorkflowStub; +use Workflow\V2\StartOptions; + +$workflow = WorkflowStub::make(TestWorkflow::class, 'test-upgrade'); +$result = $workflow->start(['test' => true], new StartOptions()); +$runId = $result->runId(); +``` + +`WorkflowStub::start()` повертає об'єкт `StartResult`. Отримайте ID виконання +через `runId()` перед порівнянням із рядками бази даних. Спроба порівняти +повернений об'єкт із колонкою як звичайний рядок непомітно дає неправильне порівняння. + +Перевірте: + +- Workflow відображається у Waterline +- У таблиці `workflow_instances` є рядок із відповідним `instance_id` (`$workflow->id()`) +- У таблиці `workflow_runs` є рядок із відповідним `run_id` (`$result->runId()`) +- Workflow завершується чи просувається як очікується + +**3. Перевірте workflow v1, якщо вони є** + +Якщо у вас є активні workflow v1: + +```bash +php artisan workflow:v1:list +``` + +Перевірте їх подальше просування. Workflow v1 мають завершитися на рушії v1 без помилок. + +**4. Перевірте журнали на помилки** + +Стежте за помилками workflow в журналах застосунку: + +```bash +tail -f storage/logs/laravel.log | grep -i workflow +``` + +Типові проблеми: + +- Помилки просторів імен: код досі використовує `Workflow\Workflow` замість `Workflow\V2\Workflow` +- Помилки методів: класам workflow чи activity v2 ще потрібно перейменувати початковий метод на `handle()` +- Помилки драйвера черги: драйвер `sync` у режимі черги не підтримується. У режимі poll + (`workflows.v2.task_dispatch_mode=poll`) черга не використовується для доставлення завдань, тому `sync` прийнятний + +**5. Перевірте спостережуваність Waterline** + +Відкрийте Waterline (типово `/waterline`) і перевірте: + +- Workflow v1, якщо вони є, відображаються з початковими даними +- Workflow v2 відображаються з повними відомостями про виконання, історію й activity +- Waterline відображається без помилок + +### Процедура відкату {#rollback-procedure} + +Відкат потребує узгодженого набору відновлення. Саме лише відновлення бази даних +SQL workflow не відновлює активне виконання v1, якщо стан черги Laravel +зберігається в Redis, SQS, іншій базі даних чи зовнішньому бекенді. + +Якщо оновлення production не вдалося, повертайтеся лише до політики й моменту +відновлення, вибраних до оновлення: + +**1. Призупиніть вхідні запити застосунку, планувальники й worker черги** + +Використайте ту саму процедуру зупинки, що й перед оновленням. Переконайтеся, +що жоден worker не може зарезервувати завдання, а жоден запит не може запустити +workflow чи надіслати signal під час відновлення стану. + +**2. Перевірте набір відновлення** + +- Набір відновлення лише SQL підтримується, якщо на вибраний момент не було + незавершеного виконання v1, що залежить від збереженої роботи в черзі. + Придатний застарілий рядок `pending` може використовувати перевірений шлях + Watchdog v1.0.77, описаний вище, а справжнє очікування лише signal може + використовувати перевірений зовнішній вхід signal. Не поширюйте ці винятки на інші стани. +- Набір відновлення активної роботи має містити SQL і відновлюваний стан готових, + відкладених та зарезервованих завдань черги з того самого моменту для повторних + спроб, timer, activity та роботи `waiting` або `running`. Для очікування лише + signal потрібен справний вхід signal, для timer потрібні їхні відкладені завдання. +- Якщо жодна умова не виконується, зупиніться. Виправте поточну версію, вручну + узгодьте зафіксовані workflow або дійте відповідно до заздалегідь задокументованого + прийняття невідновлюваності цих виконань. + +**3. Відновіть резервну копію бази даних** + +```bash +# MySQL/MariaDB +mysql -u root -p your_database < backup-v1-YYYYMMDD-HHMMSS.sql + +# PostgreSQL +psql -U postgres -d your_database < backup-v1-YYYYMMDD-HHMMSS.sql +``` + +Відновіть усі налаштовані власні таблиці моделей v1 та +`workflow_relationships_table`, навіть якщо вони використовують інше з'єднання. + +**4. За потреби відновіть стан черги на вибраний момент** + +Не запускайте споживачів. Дотримуйтеся процедури відновлення провайдера черги +та відновіть точні з'єднання, черги, префікси, готові, відкладені й зарезервовані +завдання, збережені разом із SQL. Відновіть потрібні блокування унікальних завдань, +якщо вони входили до набору відновлення. Порожня черга з тією самою назвою +не може пробудити відновлені повторні спроби, timer чи роботу `waiting`/`running`. +Порожньої доступної для запису черги достатньо лише для придатного рядка `pending` +після перевірки запуску Watchdog v1.0.77 і повторного направлення ним цього рядка. + +**5. Поверніть попередню залежність Composer** + +```bash +composer require durable-workflow/workflow:^1.0 --with-all-dependencies +``` + +`durable-workflow/workflow` є підтримуваною назвою пакета для підтримуваних +версій v1 і v2. `laravel-workflow/laravel-workflow` є лише застарілим псевдонімом, +оголошеним підтримуваним пакетом для сумісності зі старими графами залежностей. +Не використовуйте його у вимогах відкату. + +Отримайте точний `APP_KEY` за зафіксованими посиланням і версією в менеджері +секретів, використовуючи окремо контрольовані облікові дані відновлення. +До запуску споживачів відновіть його разом зі з'єднанням зі сховищем workflow, +з'єднаннями й назвами черг, префіксами Redis та налаштуваннями власних моделей. +Зашифровані завдання й серіалізовані посилання на моделі залежать від відповідності +цієї конфігурації збереженому моменту відновлення. + +**6. Перезапустіть worker черги й відновіть вхідні запити** + +```bash +# Use the start command for your worker system, for example: +sudo supervisorctl start :* +sudo systemctl start laravel-worker +php artisan horizon:continue +``` + +**7. Перевірте можливість продовжити виконання v1** + +Видимість у Waterline підтверджує відновлення рядка, але не здатність worker +продовжити виконання. Перед оголошенням успішного відкату виконайте цей список: + +- [ ] `composer show durable-workflow/workflow` показує потрібну версію v1, + а worker завантажили цей код. +- [ ] Фактичні налаштовані таблиці v1 і `workflow_relationships_table` наявні + на очікуваних з'єднаннях зі сховищем. +- [ ] Кожен незавершений рядок налаштованої таблиці збережених workflow + (типово `workflows`) класифікований за наступним шляхом пробудження: + готове/відкладене/зарезервоване завдання черги, збережене завдання timer або + перевірений зовнішній вхід signal. Придатний рядок `pending` може натомість + посилатися на перевірені докази повторного направлення Watchdog v1.0.77 нижче. +- [ ] Перевірка провайдера черги підтверджує наявність кожного залежного від черги + пробудження повторної спроби, timer, activity та workflow `waiting` або `running` + на зафіксованих з'єднанні й черзі. Сам рядок timer у SQL не задовольняє цю + перевірку, а Watchdog не заміняє ці завдання. +- [ ] Якщо відновлений рядок `pending` не має збереженого завдання workflow, + спостерігається постановка Watchdog у чергу циклом worker v1.0.77. Коли рядок + досягає п'ятихвилинної межі застарілості, Watchdog ставить workflow на його + записані з'єднання й чергу, а рядок просувається. Без цієї наскрізної перевірки + вважайте рядок застряглим, навіть якщо Waterline його показує. +- [ ] Жоден відновлений рядок повторної спроби, timer, `waiting` чи `running` + не позбавлений потрібного виконуваного завдання черги або доступного зовнішнього + шляху signal для справжнього очікування signal. Такий рядок вважайте застряглим. + Докази Watchdog лише для `pending` не роблять його відновлюваним. +- [ ] Відновлена повторна спроба чи timer просувається далі позначки до знімка, + а контрольоване очікування signal може поставити й обробити пробудження signal. +- [ ] Новий контрольний workflow v1 завершується, а журнали не містять помилок + відсутніх завдань, дешифрування, таблиць моделей, з'єднань чи маршрутизації черг. + +За типових таблиць цей запит дає початковий перелік. Для власної моделі +збережених workflow замініть таблицю на визначену цією моделлю: + +```sql +SELECT id, class, status +FROM workflows +WHERE status NOT IN ('completed', 'failed', 'cancelled'); +``` + +**Важливі зауваження щодо відкату:** + +- Відкат відкидає всі workflow v2, запущені після оновлення: вони існують лише в таблицях v2 +- Відкат лише SQL відновлює записи workflow v1, але не повідомлення зовнішніх черг +- Активне виконання v1 підтримується лише за відновлення SQL і надійного стану черги + з одного узгодженого моменту або за збереження доступного входу для задокументованого + очікування лише signal. Додатковий виняток для SQL: придатний застарілий рядок + `pending` із перевіреним повторним направленням Watchdog v1.0.77. Він не охоплює + повторні спроби, timer чи роботу `waiting`/`running` +- Відтворення стану до оновлення може повторити зовнішні побічні ефекти. + Перевірте ідемпотентність застосунку перед відновленням activity з черги +- Якщо потрібно зберегти workflow v2, запущені під час оновлення, не відновлюйте + базу даних, а виправте проблему в поточній версії + +## Зміни коду {#code-changes} + +Розділи нижче описують зміни коду, потрібні для переходу від API v1 до API v2. + +### Зміна простору імен {#namespace-change} + +Усі класи v2 належать до `Workflow\V2`. Оновіть імпорти: + +```php +// v1 +use Workflow\Workflow; +use Workflow\Activity; +use Workflow\WorkflowStub; + +// v2 +use Workflow\V2\Workflow; +use Workflow\V2\Activity; +use Workflow\V2\WorkflowStub; +``` + +### Початковий метод {#entry-method} + +Workflow й activity v2 використовують `handle()` як початковий метод. +Під час переходу перейменуйте методи `execute()` із v1 на `handle()`: + +```php +// v1 +class MyWorkflow extends Workflow +{ + public function execute($input) + { + $result = yield ActivityStub::make(MyActivity::class, $input); + return $result; + } +} + +// v2 +use function Workflow\V2\activity; + +class MyWorkflow extends Workflow +{ + public function handle($input) + { + return activity(MyActivity::class, $input); + } +} +``` + +Не залишайте `execute()` початковим методом workflow чи activity v2: runtime відхиляє його. + +### Виклики activity {#activity-calls} + +v2 замінює `ActivityStub::make()` і `yield` прямими функціями-помічниками: + +```php +// v1 +$result = yield ActivityStub::make(MyActivity::class, $arg1, $arg2); + +// v2 +use function Workflow\V2\activity; + +$result = activity(MyActivity::class, $arg1, $arg2); +``` + +Activity тепер мають надійну ідентичність. Кожна запланована activity отримує +рядок `activity_executions` зі сталим ID виконання, а кожна конкретна спроба +отримує рядок `activity_attempts` із типізованою історією. + +### Ідентичність workflow {#workflow-identity} + +v2 розділяє ідентичність на ID екземпляра й ID виконання: + +- `id()`: публічний ID екземпляра workflow, спільний для переходів continue-as-new +- `runId()`: ID поточного виконання + +У v1 це було одним поняттям. + +### Signal {#signals} + +v2 використовує іменовані очікування signal замість методів зміни стану з атрибутом `#[SignalMethod]`: + +```php +// v1 +#[SignalMethod] +public function approve() +{ + $this->approved = true; +} + +// v2 +use function Workflow\V2\await; + +$approved = await('approve'); +``` + +Іменовані signal підтримують `await('name')` для блокувального очікування в коді +workflow та `signal()` / `attemptSignal()` для зовнішніх даних. Скасування й +примусове завершення залишаються явними командами runtime й не моделюються як signal. + +### Query {#queries} + +v2 використовує безпечні для replay методи query замість прямого читання властивостей workflow: + +```php +// v1 +#[QueryMethod] +public function getStatus(): string +{ + return $this->status; +} + +// v2 +use function Workflow\V2\query; + +// Queries are defined as named, replay-safe accessors +``` + +### Timer і побічні ефекти {#timers-and-side-effects} + +Функції-помічники заміняють статичні методи v1: + +```php +// v1 +yield Timer::make(60); +$value = yield SideEffect::make(fn() => random_int(1, 100)); + +// v2 +use function Workflow\V2\timer; +use function Workflow\V2\sideEffect; + +timer(60); +$value = sideEffect(fn() => random_int(1, 100)); +``` + +### Тайм-аути {#timeouts} + +v2 додає тайм-аути рівня workflow через `StartOptions`: + +```php +use Workflow\V2\StartOptions; +use Workflow\V2\WorkflowStub; + +$workflow = WorkflowStub::make(MyWorkflow::class, 'order-123'); + +$workflow->start( + $orderId, + StartOptions::rejectDuplicate() + ->withExecutionTimeout(7200) // 2 hours across all runs + ->withRunTimeout(3600), // 1 hour per run +); +``` + +- **Тайм-аут виконання** охоплює весь екземпляр, включно з переходами continue-as-new. +- **Тайм-аут run** діє для одного виконання та починається заново після continue-as-new. + +### Міграції бази даних {#database-migrations} + +v2 додає нові таблиці й колонки. Пакет автоматично завантажує свої міграції, +тому після оновлення виконайте: + +```bash +composer update durable-workflow/workflow +php artisan migrate +``` + +Версія 2.0.0 включає базові міграції таблиць для чистого встановлення. +Звичайний шлях: дозволити Laravel автоматично завантажити міграції пакета +та виконати `php artisan migrate`. + +Якщо ви раніше опублікували міграції Durable Workflow у застосунку, +виберіть одне джерело міграцій і підтримуйте його актуальним: + +- **Автоматично завантажувані міграції пакета:** видаліть старі опубліковані + файли міграцій Durable Workflow із `database/migrations` і виконайте `php artisan migrate`. +- **Опубліковані міграції:** опублікуйте поточний набір перед застосуванням: + + ```bash + php artisan vendor:publish \ + --provider="Workflow\Providers\WorkflowServiceProvider" \ + --tag=migrations \ + --force + + php artisan migrate + ``` + +Не залишайте застарілі опубліковані файли одночасно з використанням нових +автоматично завантажуваних файлів пакета. Інакше застосунок може не отримати +новіші таблиці v2 чи виправні міграції. + +Якщо ви змінили файли міграцій, під час кожного оновлення порівнюйте свої копії +з каталогом `src/migrations` пакета. Зберігайте назви таблиць, колонки, індекси +та правила nullable/default сумісними зі схемою моделей пакета. Якщо власна +інсталяція направляє таблиці workflow на нетипове з'єднання, опублікуйте міграції, +задайте їхній `$connection` і далі переносіть кожну нову міграцію пакета +в порядку часових позначок. + +Для користувачів попередніх версій v2 колонка `workflow_run_summaries.memo` +відновлюється ідемпотентно, якщо старіша опублікована міграція таблиці зведень +створила `workflow_run_summaries` без цієї колонки. Чисті інсталяції вже +створюють її в базовій міграції таблиці зведень. + +### Перевірка можливостей бекенду {#backend-capability-check} + +v2 перевіряє відповідність драйверів черги, бази даних і кешу своїм вимогам. +Після оновлення виконайте команду doctor: + +```bash +php artisan workflow:v2:doctor --strict +``` + +### Конфігурація {#configuration} + +v2 додає кілька параметрів конфігурації. Розділ +[Конфігурація](/docs/configuration/options/) описує: + +- Псевдоніми надійних типів +- Політику відновлення завдань +- Перевірки можливостей бекенду +- Відновлення проєкцій +- Бюджети історії та приховування даних в експорті + +### Waterline {#waterline} + +Waterline, інтерфейс моніторингу, оновлено для v2: + +- Деталі виконання показують тривалості тайм-аутів і дедлайни +- Відстеження спроб activity зі сталими ID +- Оновлене відображення статусів workflow + +### Continue-as-new {#continue-as-new} + +v2 додає бюджети історії, що можуть автоматично запускати continue-as-new +після перевищення межі кількості подій. Метадані (memo, атрибути пошуку, +тайм-аути) переносяться між виконаннями. + +## Наявні workflow {#existing-workflows} + +### Стратегія finish-on-v1 {#finish-on-v1-strategy} + +**Workflow, запущені на v1, продовжують виконання через шляхи сумісності v1.** +Нові workflow після оновлення використовують семантику v2. Мігрувати активні +екземпляри workflow не потрібно. + +Після оновлення до 2.0: + +1. **Дані v1 зберігаються:** типові таблиці `workflows`, `workflow_logs`, + `workflow_signals`, `workflow_timers`, `workflow_exceptions` і + `workflow_relationships` залишаються цілими, а налаштовані перевизначення + моделей і таблиць v1 залишаються авторитетними +2. **Workflow v1 завершуються на рушії v1:** активні workflow v1 продовжують + виконання на рушії replay v1 до термінального стану +3. **Workflow v2 використовують рушій v2:** усі workflow після оновлення + використовують схему v2 (`workflow_instances`, `workflow_runs`, `workflow_history_events` тощо) +4. **Waterline показує обидві версії:** інтерфейс моніторингу відображає workflow v1 і v2 поруч + +Finish-on-v1 також залежить від бекенду черги Laravel. Зберігайте кожне +з'єднання та чергу v1 до завершення їхніх workflow. Рядки бази даних +зберігають історію й статус, але не відтворюють втрачені готові, відкладені +чи зарезервовані завдання зовнішнього бекенду. Watchdog v1.0.77 може повторно +направити придатний застарілий workflow `pending` із SQL, але цей обмежений +шлях не відтворює повторні спроби, timer чи роботу `waiting`/`running`. + +### Відстеження завершення workflow v1 {#tracking-v1-workflow-completion} + +Щоб побачити workflow v1, які залишаються активними після оновлення: + +```bash +php artisan workflow:v1:list +``` + +Ця команда показує всі workflow v1, що ще не досягли термінального стану +(completed, failed, cancelled). Відстежуйте нею завершення workflow v1. + +Приклад результату: + +``` ++--------------------------------------+---------------------+-----------+------------+ +| ID | Class | Status | Created | ++--------------------------------------+---------------------+-----------+------------+ +| 01J1234567890ABCDEFGHIJK | App\OrderWorkflow | running | 2 days ago | +| 01J9876543210ZYXWVUTSRQP | App\InvoiceWorkflow | pending | 1 day ago | ++--------------------------------------+---------------------+-----------+------------+ +``` + +#### Видимість у Waterline {#waterline-visibility} + +Після оновлення до 2.0 Waterline автоматично показує workflow з обох рушіїв: + +- **Workflow v1** відображають початкові дані `StoredWorkflow`: клас, статус, журнали, signal, винятки +- **Workflow v2** відображають повні деталі v2: виконання, події історії, timer, activity, атрибути пошуку + +Налаштування не потрібне. Waterline читає обидва набори таблиць і надає спільний перегляд. + +### Коли видаляти таблиці v1 {#when-to-clean-up-v1-tables} + +Коли всі workflow v1 завершилися, що підтверджує нуль активних workflow +у `workflow:v1:list`, можна за бажанням видалити таблиці v1: + +```sql +DROP TABLE IF EXISTS workflow_relationships; +DROP TABLE IF EXISTS workflow_exceptions; +DROP TABLE IF EXISTS workflow_timers; +DROP TABLE IF EXISTS workflow_signals; +DROP TABLE IF EXISTS workflow_logs; +DROP TABLE IF EXISTS workflows; +``` + +**Важливо:** не видаляйте ці таблиці, доки хоч один workflow v1 залишається активним. +Це спричинить збій replay v1 і залишить workflow застряглими. + +### Навіщо finish-on-v1? {#why-finish-on-v1} + +Стратегія finish-on-v1 дозволяє оновлення без примусової міграції даних. +v1 і v2 мають принципово різні моделі зберігання: + +- **v1** зберігає стан workflow у денормалізованому рядку `workflows` із пов'язаними журналами, signal і timer +- **v2** зберігає стан workflow як історію подій із проєкціями (`workflow_instances`, `workflow_runs`, `workflow_history_events`) + +Перетворення активних workflow v1 на історію v2 потребувало б відновлення +послідовностей подій із журналів v1, що створює ризик втрати даних і +неузгодженості replay. Finish-on-v1 дозволяє workflow v1 безпечно завершитися +на початковому рушії, а новій роботі одразу перейти до v2. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/operator-operating-envelope.md b/i18n/uk/docusaurus-plugin-content-docs/current/operator-operating-envelope.md new file mode 100644 index 0000000000..4f40251f63 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/operator-operating-envelope.md @@ -0,0 +1,656 @@ +--- +sidebar_position: 14 +title: Межі експлуатації для операторів +description: Операторський контракт Durable Workflow v2 для здоров'я, стану черг, відновлення проєкцій, експорту, архівів і топології розгортання. +tags: + - operations + - observability + - Waterline +keywords: + - межі експлуатації + - здоров'я workflow v2 + - відновлення проєкцій + - експорт історії + - перевірка архіву + - здоров'я черг + - висока доступність +--- + +# Межі експлуатації для операторів {#operator-operating-envelope} + +Цей посібник визначає операторський контракт Durable Workflow v2. +Він допомагає вирішити, яка діагностика блокує розгортання, а яка є рекомендаційною, +які відомості про черги належать до Waterline чи телеметрії worker, як перевіряти +відновлення проєкцій та експорт і які форми розгортання охоплюють задокументовані +межі експлуатації. + +Описані процедури розгортання й відновлення стосуються вбудованого Laravel і +самостійно розгорнутого Server. Durable Workflow Cloud є окремим керованим сервісом: +Cloud відповідає за зберігання стану runtime, розміщення в одному регіоні, +резервні копії та відновлення, а клієнти керують клієнтами SDK і worker через URL +runtime namespace. Не використовуйте цей посібник для підключення Server до Cloud. +Цю межу клієнта описує [керований runtime Cloud](/docs/polyglot/cloud-control-plane). + +## Джерела істини {#source-of-truth-surfaces} + +Використовуйте ці поверхні разом: + +| Поверхня | Призначення | Клас контракту | +| --- | --- | --- | +| `php artisan workflow:v2:doctor --strict` | Перевірка можливостей бекенду до трафіку v2 чи оновлень | Блокувальний | +| `GET /waterline/api/v2/health` | Поточна готовність джерела рушія та блокувальні й рекомендаційні перевірки v2 | Блокувальний за `status = error`, рекомендаційний за `status = warning` | +| `GET /waterline/api/stats` | Надійні підсумки парку, backlog, факти циклу repair, відхилення проєкцій, сумісність worker | Рекомендаційний і для benchmark | +| `php artisan workflow:v2:rebuild-projections ...` | Перегляд і виправлення відхилень проєкцій | Обслуговування | +| `php artisan workflow:v2:backfill-command-contracts ...` | Перегляд і backfill старих знімків контрактів команд | Обслуговування | +| `php artisan workflow:v2:history-export ...` і маршрути експорту історії Waterline | Replay, передавання архіву та матеріали інциденту | Перевірка | +| Дії архівування Waterline й `archive()` площини керування | Переходи життєвого циклу закритих виконань | Життєвий цикл | +| Метрики, трасування й журнали SDK worker | Затримка schedule-to-start, успішність poll, sticky-cache та власна телеметрія застосунку | Телеметрія runtime | + +Контракт оператора щодо надійного стану належить runtime, який володіє виконанням. +Waterline відображає цей контракт із вбудованого пакета workflow або самостійного +Server через PHP SDK. Телеметрія worker залишається джерелом істини для +затримки й поведінки процесів усередині ваших worker. + +### Відповідність поверхонь формам розгортання {#surface-mapping-by-deployment-shape} + +Маршрути Waterline з таблиці вище доступні в обох формах його розгортання. +Вбудований режим читає надійний стан хоста Laravel у процесі. Сервісний режим +запускає опублікований образ Waterline та читає один namespace самостійного +Server через PHP SDK. Автентифікований API Server і CLI `dw` доступні +безпосередньо як із Waterline, так і без нього: + +| Питання оператора | Waterline (вбудований чи сервісний режим) | Власна поверхня самостійного Server | +| --- | --- | --- | +| Готовність джерела рушія та блокувальні й рекомендаційні перевірки | `GET /waterline/api/v2/health` | `GET /api/system/health` (автентифікація адміністратора, control-plane v2); `dw server:health` для життєздатності й `dw server:info` для топології, протоколу й безпечності розгортання | +| Надійні підсумки парку, backlog, repair, сумісність worker, відхилення проєкцій | `GET /waterline/api/stats` | `GET /api/system/operator-metrics` and `dw system:operator-metrics` | +| Деталі вибраного виконання й експорт історії | `GET /waterline/api/instances/...` і `/waterline/api/.../history-export` | `GET /api/workflows/{workflowId}`, `/runs/{runId}` і `/runs/{runId}/history/export` (див. [довідник API Server](./polyglot/server-api-reference.md)) | +| Операторські команди (cancel, terminate, repair, archive, signal/update/query) | `POST /waterline/api/instances/.../{cancel\|terminate\|repair\|archive}` і маршрути signal/update/query | `POST /api/workflows/{workflowId}/{cancel\|terminate\|repair\|archive}` and `POST /api/system/repair/pass` | +| Виявлення топології та ідентичності вузла | Вбудований режим: `php artisan workflow:v2:doctor --json` (об'єкт `topology`); сервісний: власна поверхня підключеного Server | `GET /api/cluster/info`, `GET /api/health`, `GET /api/ready` (або `dw server:info`) | + +Сімейства полів і назви контрактів нижче однакові незалежно від поверхні читання. +Інсталяція Waterline обмежена налаштованими runtime і namespace. +Вона не об'єднує вбудовані виконання з виконаннями під керуванням Server. + +## Датований знімок доказів 2.0 {#dated-20-evidence-snapshot} + +Цей знімок розділяє поведінку, виміряну на випущених артефактах, і процедури, +які продукт підтримує, але оператор має відпрацювати у власному середовищі. +Успішний рядок стосується задокументованих сценарію 2.0 й топології, а не +довільного середовища чи більшого розгортання. + +Поточний перелік доказів: + +| Доказ і дата (UTC) | Безпосередньо виміряний результат | Межа | +| --- | --- | --- | +| Тренування failover одного регіону, 2026-07-28 | Термінальне завершення між вузлами, втрата одного API вузла, переривання й відновлення MySQL, переривання Redis із переходом на опитування бази, втрата й повторне отримання lease worker, перезапуск єдиного планувальника. Перевірки втрати й дублікатів пройшли на кожному етапі. | Два API вузли за спільним endpoint, один MySQL, один шар прискорення Redis, один worker черги й один процес планувальника/обслуговування в одному регіоні. Переривання бази й Redis стосувалися контейнерів, а не promotion керованого провайдера. | +| Матриця перезапуску timer, 2026-07-28 | Сплячі workflow завершилися після перезапуску worker і Server з одним плануванням та спрацюванням timer на виконання й без дубліката команди replay. | Відновлення надійних timer і replay на тестовій топології опублікованого Server, без довільного стану процесу чи зовнішніх побічних ефектів. | +| Матриця runtime activity, 2026-07-29 | Відновлення результату після перезапуску worker, повторні спроби й тайм-аути, спостереження heartbeat і скасування, надійний запис термінального результату й ідемпотентна обробка завершення через потрібні поверхні продукту. | Поведінка рушія на рівні activity. Ідемпотентність зовнішніх побічних ефектів залишається відповідальністю застосунку. | +| Матриця життєвого циклу workflow, 2026-07-29 | Термінальні успіх і помилка, політики повторного запуску й повторного використання ID workflow, тайм-аути й повторні спроби, неперервність історії та запобігання повторним побічним ефектам через PHP, Python, Rust, CLI, API, історію й Waterline. | Коректність життєвого циклу перевірених сценаріїв, без benchmark доступності чи пропускної здатності. | + +Підтримувані лінії релізів описані в [контракті сумісності](./compatibility.md). +Відпрацюйте задокументовану процедуру відновлення на своєму розгортанні, +перш ніж заявляти про перемикання після збою для конкретної топології. + +### Виміряна гарантія й підтримувана процедура {#measured-guarantee-vs-supported-procedure} + +| Збій чи результат | Безпосередньо доведено датованими доказами | Підтримувана процедура оператора | Не встановлено цими доказами | +| --- | --- | --- | --- | +| Втрата одного API вузла | Тренування зупинило один із двох API вузлів, звернулося до решти через спільний endpoint, зберегло підтверджений стан і завершило виконання. | Вилучіть несправні вузли через `/api/ready`, повторіть перервані запити клієнта, перевірте топологію й реєстрацію worker перед поверненням трафіку. | Втрата всього парку, ізоляція зони доступності чи довільне розділення мережі. | +| Переривання бази даних | Обидва API вузли стали неготовими, надійний стан зберігся, новий worker отримав завдання після завершення lease, виконання завершилося, повторне завершення було відхилено. | Спочатку відновіть базу для запису, дочекайтеся готовності, відгородіть старого власника завдання, перевірте репрезентативне виконання й відновіть трафік. Promotion керованої бази потребує fencing провайдера та доказів RPO і часу в наборі відновлення. | Promotion керованої бази, підтверджені записи під час split-brain чи RPO/RTO поза виміряним локальним перериванням. | +| Переривання Redis | Готовність повідомила про погіршення прискорення, опитування бази зберегло надійний стан, повторення запиту poll не створило дубліката lease, готовність відновилася після повернення Redis. | Використовуйте Redis як прискорення, допускайте готовність із warning, відновіть Redis після надійного сховища та перевірте повернення затримки poll до базового рівня. | Два primary Redis, поведінка кешу під час розділення мережі чи час promotion провайдера. | +| Перезапуск чи втрата worker | Доказ failover повторно отримав і завершив орендований workflow після втрати worker. Новіша матриця activity довела надійне відновлення результату після перезапуску й ідемпотентне завершення. | Дочекайтеся завершення lease чи виконайте drain worker, запустіть сумісну заміну, перевірте реєстрацію й отримання черги та зберігайте ідемпотентність зовнішніх ефектів. | Відновлення недовговічного локального стану процесу чи зовнішні ефекти exactly-once. | +| Перезапуск Server чи планувальника | Матриця timer завершила сплячі workflow після перезапуску Server без дублікатів команд timer. Доказ failover зберіг прогрес після втрати одного API вузла й перезапуску єдиного планувальника. | Відновіть сховище, поверніть ролі Server до готовності, підтвердьте рівно одного планувальника/процес обслуговування, перевірте реєстрацію worker і репрезентативне завершення. | Одночасна втрата всіх вузлів Server і сховища, багаторегіональний failover чи постійна робота дублікатів планувальника. | +| Повторне доставлення | Етапи відновлення бази й втрати worker записали одне логічне завершення та відхилили дублікат HTTP `409`. Погіршення Redis повернуло початковий lease без нового дубліката. Матриця activity окремо пройшла перевірку ідемпотентного завершення. | Використовуйте сталі ідентичності запитів і завдань, дотримуйтеся відмов старим спробам і робіть побічні ефекти activity ідемпотентними. | Універсальна гарантія exactly-once доставлення чи побічних ефектів поза надійним рушієм. | +| Термінальне завершення | Workflow між вузлами, після втрати API, відновлення бази й втрати worker досягли `completed`. Новіша матриця життєвого циклу пройшла завершення, помилку, скасування, terminate, тайм-аут і retry. | Перевірте вибране виконання та докази його історії/експорту перед архівуванням чи оголошенням відновлення завершеним. | Завершення форм навантаження, інтеграцій чи комбінацій збоїв, які не перевірялися. | + +### Найбільший поточний тест навантаження {#largest-current-load-cell} + +Найбільший налаштований тест навантаження Server запускає **1 000 workflow** +із паралельністю запуску **8** та **8** одночасними worker опитування протягом +120 секунд. Він вимагає щонайменше 98% успішних запусків workflow й перевіряє +доступність endpoint, очищення ключів кешу, покриття вибірки та обмежене +споживання ресурсів. + +Це коротка **перевірка коректності**, а не тривалий production benchmark. +Вона не визначає універсальної пропускної здатності, затримки, пам'яті чи +максимальної паралельності. Визначте їх benchmark і тривалим тестом для +точної топології, набору worker, бази даних, кешу й релізу, які експлуатуватимете. + +### Явно поза охопленням {#explicitly-out-of-scope} + +Докази 2.0 не охоплюють: + +- розділення мережі +- навмисне зміщення годинника +- роботу в кількох регіонах +- поведінку split-brain + +Ці експерименти не є передумовами релізу 2.0. Вони потрібні лише для відповідних +заяв про розділення мережі, зміщення часу, кілька регіонів чи split-brain. +Підтримувані межі релізу 2.0 охоплюють опубліковану топологію, її набір відновлення +та поведінку, виміряну задокументованими сценаріями. Непідтримувані сценарії chaos +не слід подавати як докази непридатності задокументованих процедур одного регіону. + +## Підтримувані топології {#supported-topologies} + +Durable Workflow v2 підтримує ці операторські форми. Назви першої колонки +відповідають `topology.current_shape`, опублікованим `/api/cluster/info` та +маніфестом [топології ролей Server](./polyglot/server-role-topology.md). +Так операторський контракт узгоджується з контрактом виявлення вашої автоматизації. + +| Форма оператора (`topology.current_shape`) | Підтримуваний операторський контракт | Основні області відмов | Очікування відновлення й failover | +| --- | --- | --- | --- | +| `embedded`, один вузол | Waterline, маршрути площини керування, здоров'я, rebuild, експорт і архів працюють з одного процесу застосунку з однією надійною базою й одним кешем. | Процес Laravel, надійна база й кеш одного хоста. | Втрата хоста чи бази є повним перериванням сервісу. Спочатку відновіть надійний стан, поверніть вузол застосунку до готовності й перевірте реєстрацію worker перед трафіком. | +| `embedded`, невеликий кластер одного регіону | Спільна база й кеш для координації пробудження, однакова сумісність і конфігурація workflow на вузлах. Активні вузли мають бути в одному датацентрі чи регіоні для обмеженої затримки пробудження черг і timer. | Спільна база, кеш/координація пробудження, маршрутизація балансувальника та єдина роль планувальника чи обслуговування. | Втрата вузла зменшує місткість, а не коректність. Втрата бази блокує надійний трафік. Втрата лише Redis погіршує прискорення й дає warning готовності, а опитування бази зберігає коректність. Failover планувальника й оновлення залишаються явними процедурами оператора. | +| Розгортання `standalone_server` | Використовуйте матрицю [самостійного розгортання](./deployment.md), потім ті самі відмінності здоров'я, статистики, експорту, архіву й черг через `/api/system/...` і `/api/workflows/...` чи окремий сервіс Waterline (див. відповідність поверхонь вище). | Спільні база й Redis, контейнери API, незалежно масштабовані worker, необов'язковий спостерігач Waterline та єдиний планувальник/процес обслуговування. | API контейнери є замінними вузлами процесів, Waterline є замінним спостерігачем стану Server. База, Redis і єдиний планувальник визначають порядок відновлення. Спочатку відновіть сховище, перевірте `/api/ready`, `/api/cluster/info` і реєстрацію worker, потім повертайте трафік. | +| `split_control_execution` | Контракт як у `standalone_server`, але кожна роль ізольована у власному класі процесу (`ingress_node`, `control_plane_node`, `scheduler_node`, `matching_node`, `execution_node`). Метрики, здоров'я й команди застосовуються на вузлах. Направляйте адміністративні читання на вузол потрібної ролі. | Кожна роль має свій клас процесу, тому [топологія ролей Server](./polyglot/server-role-topology.md) визначає перший збій підсистеми. Спільні база, Redis і вибори єдиного планувальника залишаються областями відмов усього парку. | Порядок відновлення як у `standalone_server`, але перевіряйте `topology.current_shape`, `topology.current_process_class` і `topology.current_roles` кожного вузла перед готовністю. Маршрути повертають `503 topology_role_unavailable` на неправильному класі вузла. | + +`split_control_execution` є тим самим операторським контрактом, що й +`standalone_server`, із класами процесів окремих ролей у `topology.shape_assignments`. +Це не окремий рушій чи продукт. Решта посібника застосовується до обох форм, +якщо розділ не називає конкретну роль. [Топологія ролей Server](./polyglot/server-role-topology.md) +визначає словник ролей, межі повноважень і шлях міграції. + +Сервісний режим Waterline є розгортанням спостерігача, а не новою топологією +Server. Додавання чи видалення Waterline не змінює `topology.current_shape`, +доступність власних API чи CLI Server або runtime, що володіє виконанням. + +Опублікуйте порядок відновлення, частоту резервних копій, очікувану затримку +перемикання та прив'язку поведінки до регіону в інструкції своєї топології. +Контракт продукту визначає факти для вимірювання, а контракт розгортання +фіксує прийнятні час відновлення, ручні дії та області відмов. + +### Перелік областей відмов за формою {#failure-domain-checklist-by-supported-shape} + +Таблиця топологій вище дає коротке зведення. Складіть інструкцію відповідно +до цих докладніших моделей втрат: + +- **Вбудований Laravel, один вузол:** один процес застосунку одночасно володіє + ролями площини керування, matching, проєкцій, планувальника й виконання. + Втрата процесу повністю перериває надійні команди, просування workflow, + запуск schedule та читання оператором до відновлення готовності того самого + застосунку зі збереженим надійним сховищем. +- **Вбудований Laravel, невеликий кластер одного регіону:** втрата звичайного + вузла має зменшити лише частину HTTP місткості та місткості worker, тоді як + решта вузлів отримують роботу зі спільного надійного сховища. Основні межі + коректності парку: спільна база даних, спільний шлях пробудження через кеш і + вузол, що зараз володіє єдиною роллю планувальника чи обслуговування. +- **Самостійний Server (`standalone_server`):** втрата `server_http_node` зупиняє + приймання запитів і команди площини керування лише на цьому вузлі. Здорові + worker можуть завершувати орендовану роботу, а інші API вузли обслуговують + запити. Втрата `worker_node` має збільшити backlog, вік черги чи попередження + сумісності лише для відповідних областей `(connection, queue, compatibility)`. + Втрата `scheduler_node` призупиняє нові запуски schedule й проходи обслуговування + без скасування активних workflow. Втрата бази даних є збоєм усього парку. + Втрата лише Redis зберігає надійне опитування бази даних, погіршує + `long_poll_wake_acceleration` і збільшує затримку виявлення до підключення Redis. +- **Server із розділеними ролями (`split_control_execution`):** кожна роль працює + у власному класі процесу: `ingress_node`, `control_plane_node`, `scheduler_node`, + `matching_node`, `execution_node`. Втрата класу погіршує лише його роль: + ingress зупиняє зовнішній HTTP на межі, control-plane швидко відхиляє + операторські команди, поки орендована робота триває, matching переходить на + прямий пошук готових завдань, scheduler призупиняє schedule й записує + пропущені запуски, execution накопичує готові завдання без втрати надійного стану. + Втрата бази даних залишається збоєм усього парку. Втрата лише Redis погіршує + прискорення з тим самим переходом на опитування бази й попередженням готовності, + що й у `standalone_server`. + +Якщо ваше розгортання спирається на інші припущення, опишіть цю топологію +окремою інструкцією з перевіреним контрактом, замість незмінного застосування +посібника для самостійної експлуатації. + +### Опублікований набір відновлення за топологією {#published-recovery-packet-by-topology} + +Топології вище готові до production лише після публікації відповідного набору +відновлення в інструкції розгортання: + +| Топологія | Факти, які має опублікувати оператор | +| --- | --- | +| `embedded`, один вузол | Графік резервних копій бази, очікування збереження кешу, точна ревізія застосунку й знімок env/config для відновлення, максимально прийнятне відставання даних і докази останнього успішного тренування відновлення. | +| `embedded`, невеликий кластер одного регіону | Усе з набору одного вузла, а також поточний власник планувальника/обслуговування, наслідки втрати звичайного вузла порівняно зі спільною базою чи кешем та кроки failover для координації пробудження. | +| Розгортання `standalone_server` | Частота копій бази й Redis, зафіксований образ/digest Server, розташування матеріалів автентифікації, очікуваний failover для `server_http_node`, `worker_node`, `scheduler_node`, останні докази перевірки `/api/ready` і `/api/cluster/info` та повторної реєстрації worker після відновлення. | +| Розгортання `split_control_execution` | Усе з набору `standalone_server`, очікування масштабування й відмов кожного класу `ingress_node`, `control_plane_node`, `scheduler_node`, `matching_node`, `execution_node` та правила маршрутизації клієнтів за `503 topology_role_unavailable` від неправильного класу вузла. | + +Якщо набір відсутній, застарілий чи не перевірений, вважайте топологію +придатною лише для розробки незалежно від кількості вузлів. + +### Перевірте фактичну ідентичність топології до використання базових показників {#verify-live-topology-identity-before-trusting-the-baseline} + +Для самостійного Server і розгортання з розділеними ролями підтвердьте +ідентичність вузла, яку повідомляє сам продукт, перш ніж тлумачити сигнали +черги, планувальника чи збоїв ролей. `GET /api/cluster/info` є джерелом істини: + +| Поле | Призначення | +| --- | --- | +| `topology.current_shape` | Підтверджує, чи вузол оголошує `embedded`, `standalone_server` або `split_control_execution`. | +| `topology.current_roles` | Підтверджує логічні ролі цього вузла. | +| `topology.supported_shapes` | Підтверджує форми розгортання, публічно підтримувані збіркою Server. | +| `topology.shape_assignments` | Зіставляє підтримувані форми із задокументованими наборами ролей класів процесів для порівняння поточного набору з топологією. | + +Використовуйте ці поля як першу перевірку зміни топології під час розгортання: + +- У формі самостійного Server вузли API мають надалі повідомляти набір ролей + `api_ingress`, `control_plane`, `matching`, `history_projection`, вузли + планувальника мають повідомляти `scheduler`, а worker мають повідомляти `execution_plane`. +- У формі з розділеними ролями перевірте відповідність `current_roles` кожного + вузла задокументованому набору в `shape_assignments` до тлумачення backlog + чи затримки планувальника як проблеми worker. +- Якщо `current_roles` відхиляються від плану розгортання, базові показники + черг і відновлення після збоїв залишаються сумнівними до виправлення ідентичності вузла. + +Вбудовані інсталяції не публікують `/api/cluster/info`. Для локального огляду +топології пакета виконайте `php artisan workflow:v2:doctor --json` і перевірте +об'єкт `topology`. Він публікує ту саму схему топології ролей та +`current_shape`, `current_process_class`, `current_roles`, `execution_mode` +і вкладене зведення `matching_role` вбудованого застосунку. + +## Блокувальна й рекомендаційна діагностика {#blocking-and-advisory-diagnostics} + +Durable Workflow v2 розрізняє блокувальну й рекомендаційну діагностику. + +| Рівень | Значення | Типова дія оператора | +| --- | --- | --- | +| Блокувальний | Поточні конфігурація чи готовність небезпечні для трафіку v2 | Зупиніть розгортання, виправте передумову й повторіть перевірку | +| Рекомендаційний | Поверхня доступна, але похідні факти потребують rebuild, backfill чи ручної перевірки | За можливості продовжуйте трафік, потім виправте названу поверхню | +| Здоровий | Поточних проблем поверхні не знайдено | Продовжуйте звичайну роботу | + +Застосовуйте це правило до випущених поверхонь: + +- `workflow:v2:doctor --strict` блокує роботу, коли проблеми можливостей бекенду + мають рівень `error`. Наприклад, непідтримуваний драйвер черги в режимі queue + або кеш без блокувань. Інформаційна діагностика черги в режимі poll є рекомендаційною. +- `GET /waterline/api/v2/health` повертає: + - `status = ok`, якщо операторська поверхня v2 готова й поточні перевірки узгоджені. + - `status = warning`, якщо поверхня доступна для читання, але певні факти + потребують відновлення, backfill чи виправлення перед повною довірою. + - `status = error` з HTTP `503`, якщо міст джерела рушія не готовий або + блокувальна проблема можливостей робить поверхню v2 недоступною. +- `GET /waterline/api/stats` публікує надійні операторські факти. Використовуйте + поля JSON для діагностики в панелях і скриптах, а не як endpoint збору метрик. + +## Перевірки коректності й прискорення {#correctness-vs-acceleration-checks} + +Кожна перевірка здоров'я v2 має `category` зі значенням `correctness` чи +`acceleration`. Знімок публікує зведення за категоріями, щоб оператор міг +відповісти на два окремі питання без повторного агрегування перевірок. + +- **Перевірки коректності** описують справність надійного пошуку готових завдань, + актуальності проєкцій, backfill контрактів команд, зберігання історії, + сумісності worker та можливостей бекенду. `status = error` у цій категорії + означає ризик для безпечного отримання завдань чи стану, якому довіряє оператор. + Розгортання слід зупинити до усунення проблеми. +- **Перевірки прискорення** описують своєчасність необов'язкового поширення + сигналів пробудження. Надійне опитування забезпечує коректність, тому + `status = warning` прискорення означає можливе збільшення затримки + пробудження між вузлами без втрати доступності завдань. + +Кожен запис `checks` містить `category`, а знімок додає зведення `categories` +для швидкого огляду обох питань у панелях: + +```json +{ + "status": "warning", + "categories": { + "correctness": {"status": "ok", "check_count": 8}, + "acceleration": {"status": "warning", "check_count": 1} + } +} +``` + +Погіршення `acceleration` стосується лише прискорення: перевірте здоров'я +кешу чи бекенду пробудження, але не блокуйте трафік, що залежить лише від +надійного пошуку готових завдань. Погіршення `correctness` є блокувальним +сигналом. `long_poll_wake_acceleration` є канонічною перевіркою прискорення +й ніколи не перевищує `warning`. Усі інші перевірки належать до коректності. + +## Семантика здоров'я черг {#queue-health-semantics} + +Здоров'я черг розділене між надійним станом черги й телеметрією worker/runtime. + +### Надійні факти черги {#durable-queue-facts} + +Для надійного стану завдань використовуйте статистику панелі й огляди черг Waterline: + +| Факт | Значення | +| --- | --- | +| `operator_metrics.backlog.runnable_tasks` | Надійні завдання, готові до отримання зараз. | +| `operator_metrics.backlog.delayed_tasks` | Надійні завдання, що ще очікують `available_at`. | +| `operator_metrics.backlog.leased_tasks` | Надійні завдання, отримані worker. | +| `operator_metrics.backlog.tasks_added_last_minute` | Окремі рядки надійних завдань, створені за останні 60 секунд. Це надходження до надійної черги, а не лічильник спроб транспорту. | +| `operator_metrics.backlog.tasks_dispatched_last_minute` | Окремі рядки надійних завдань, чий останній успішний `last_dispatched_at` припав на останні 60 секунд. Порівнюйте з `tasks_added_last_minute` для виявлення перевищення надходження над доставленням. | +| `operator_metrics.starts.pending_runs`, `operator_metrics.starts.pending_commands`, `operator_metrics.starts.ready_tasks`, `operator_metrics.starts.oldest_pending_start_at`, `operator_metrics.starts.max_pending_ms` | Надійний backlog запусків workflow. Відокремлює прийняті запуски, які ще не стали активною роботою завдань workflow, від звичайної затримки черги worker. | +| `operator_metrics.tasks.oldest_ready_due_at`, `operator_metrics.tasks.max_ready_due_age_ms` | Найстаріше придатне завдання та його вік готовності до доставлення. Машиночитана пара затримки backlog для найстарішого готового завдання. | +| `operator_metrics.tasks.dispatch_overdue`, `operator_metrics.tasks.oldest_dispatch_overdue_since`, `operator_metrics.tasks.max_dispatch_overdue_age_ms` | Готові надійні завдання без успішного пробудження dispatch і вік найстарішого. Виявляє погіршення прискорення notifier окремо від звичайного росту черги. | +| `operator_metrics.backlog.unhealthy_tasks` | Надійні завдання з помилками dispatch/claim, простроченим dispatch чи lease. | +| `operator_metrics.backlog.repair_needed_runs` | Відкриті виконання без надійного шляху продовження. | +| `operator_metrics.tasks.oldest_lease_expired_at`, `operator_metrics.tasks.max_lease_expired_age_ms` | Найстаріший прострочений lease і його вік. Основний індикатор віку застряглих lease та ризику дублікатів. | +| `operator_metrics.backlog.oldest_compatibility_blocked_started_at`, `operator_metrics.backlog.max_compatibility_blocked_age_ms` | Найстаріше блокування маршрутизації сумісності й його вік: робота збережена, але жоден сумісний worker не має права її отримати. | +| Активні й застарілі poller | Чи зареєстровані worker продовжують heartbeat черги. | +| Поточні lease | Які завдання workflow чи activity зараз орендовані та чи lease прострочений. | + +Ці факти описують лише надійний потік завдань workflow та activity. + +Для докладного огляду окремої черги замість загальних підсумків парку використовуйте +маршрути видимості черг Server. Вони показують вік backlog, стан poller, +поточні lease, бюджети допуску та `stats.tasks_added_last_minute` і +`stats.tasks_dispatched_last_minute`. Порівнюйте надходження й доставлення +окремої черги, щоб знайти чергу з ростом backlog або без доступної місткості +worker. Для загального порівняння використовуйте пару +`operator_metrics.backlog.*` вище. + +`GET /waterline/api/v2/health` надає той самий огляд черг у +`queue_visibility.*` для налаштованого namespace. Ці сімейства полів є +типізованим контрактом здоров'я черг: + +| Сімейство полів | Значення | +| --- | --- | +| `queue_visibility.available`, `queue_visibility.reason` | Чи Waterline може показати локальну видимість черг налаштованого namespace і причина недоступності. | +| `queue_visibility.task_queues[].stats.approximate_backlog_count`, `queue_visibility.task_queues[].stats.approximate_backlog_age` | Кількість backlog черги й вік найстарішої надійної роботи. | +| `queue_visibility.task_queues[].stats.tasks_added_last_minute`, `queue_visibility.task_queues[].stats.tasks_dispatched_last_minute` | Надходження й доставлення надійної роботи черги за останні 60 секунд. Виявляє гарячу чергу серед здорових підсумків парку. | +| `queue_visibility.task_queues[].stats.pollers.active_count`, `queue_visibility.task_queues[].stats.pollers.stale_count`, `queue_visibility.task_queues[].stats.pollers.stale_after_seconds` | Здорові й застарілі poller черги та використаний поріг застарілого heartbeat. | +| `queue_visibility.task_queues[].stats.workflow_tasks.*`, `queue_visibility.task_queues[].stats.activity_tasks.*` | Локальні кількості готових, орендованих завдань і прострочених lease окремо для workflow й activity. | +| `queue_visibility.task_queues[].repair.candidates`, `dispatch_failed`, `expired_leases`, `dispatch_overdue` | Тиск repair черги: завдання, які вже потребують виправлення, мають помилку dispatch, прострочені lease чи потребують повторного dispatch. | +| `queue_visibility.task_queues[].repair.oldest_dispatch_failed_at`, `max_dispatch_failed_age_ms`, `oldest_lease_expired_at`, `max_lease_expired_age_ms`, `oldest_dispatch_overdue_since`, `max_dispatch_overdue_age_ms` | Вік найстаріших помилки dispatch, простроченого lease й простроченого dispatch надійного завдання. | + +`coordination_alerts[]` того самого payload `GET /waterline/api/v2/health` +є операторським зведенням локальних фактів черг і списку перевірок здоров'я. +Використовуйте його як готове до сповіщення зведення попереджень і помилок, +а докази шукайте у відповідних записах `queue_visibility` чи `checks`. + +Локальний стан допуску черги є основним сигналом слотів і poller цієї черги. +`saturated` означає, що живі worker є, але всі зареєстровані слоти орендовані. +`throttled` означає, що ліміт lease чи доставлення на сервері навмисно стримує +нову роботу. `no_slots` означає, що worker зареєстровані, але оголошують нульову +місткість для цього виду завдань. `no_active_workers` означає відсутність +здорових poller черги, а `unavailable` означає, що налаштований захист +допуску з блокуванням зараз не може довести безпечність. + +Використовуйте `operator_metrics.starts.*`, якщо нові запуски workflow +застрягають, хоча звичайна затримка черги нормальна. Ці факти відокремлюють +борг допуску запусків і створення першого завдання від отримання роботи worker. + +### Навантаження poller і бюджети допуску {#poller-pressure-and-admission-budgets} + +Якщо потік черги погіршується, використовуйте маршрути деталей черги чи +`dw task-queue:describe`, щоб розрізнити нестачу місткості worker, +навмисне обмеження сервера й повну відсутність живих poller: + +| Стан черги | Значення | Тлумачення | +| --- | --- | --- | +| `accepting` | Worker мають вільні слоти, ліміти сервера не вичерпані. | Здоровий базовий стан. | +| `saturated` | Усі зареєстровані слоти worker орендовані. | Тиск на місткість worker. | +| `throttled` | Ліміт активних lease чи швидкості доставлення сервера навмисно стримує чергу. | Рекомендаційний, якщо ліміт очікуваний і backlog не перевищує опублікованого базового рівня. | +| `no_slots` | Активні worker зареєстровані, але не оголошують слотів цього виду завдань. | Блокувальний для черги. | +| `no_active_workers` | Немає здорового poller черги. | Блокувальний для черги. | +| `unavailable` | Черга не може отримати блокування для налаштованого шляху допуску. | Блокувальний до відновлення залежності допуску. | + +Використовуйте ці стани разом із фактами потоку черги: + +- `tasks_added_last_minute > tasks_dispatched_last_minute` разом із + `saturated` означає, що надійне надходження перевищує місткість worker. +- Такий самий дисбаланс разом із `throttled` означає явне обмеження сервера. + Оцінюйте його за призначеним контрактом ліміту, а не необмеженою пропускною здатністю. +- Зростання віку найстарішого готового завдання разом із `no_active_workers` + чи застарілими poller означає втрату здорових отримувачів завдань. + Це слід вважати збоєм маршрутизації відповідної області. + +### Форма розгортання ролі matching {#matching-role-deployment-shape} + +Використовуйте `operator_metrics.matching_role.*`, щоб підтвердити фактичний +контракт matching/dispatch поточного вузла: + +| Факт | Значення | +| --- | --- | +| `operator_metrics.matching_role.queue_wake_enabled` | Чи вузол ще запускає широкий шлях пробудження poll усередині worker за подіями циклу worker черги. | +| `operator_metrics.matching_role.shape` | `in_worker`, якщо вузол володіє цим шляхом, `dedicated`, якщо проходи wake/repair мають працювати окремим процесом `workflow:v2:repair-pass --loop`. | +| `operator_metrics.matching_role.task_dispatch_mode` | Режим доставлення готових завдань вузла: `queue` або `poll`. | +| `operator_metrics.matching_role.partition_primitives` | Зафіксовані осі маршрутизації в порядку: `connection`, `queue`, `compatibility`, `namespace`. | +| `operator_metrics.matching_role.backpressure_model` | Межа надійного допуску, яку застосовує рушій. Поточна v2 повідомляє `lease_ownership`. | + +Ці поля належать вузлу, а не всьому парку. Під час розгортання змішаних форм +читайте знімок кожного вузла чи pod, який переводите, щоб підтвердити +перенесення matching до тлумачення змін backlog чи poller як стану worker. + +### Телеметрія worker і SDK {#worker-and-sdk-telemetry} + +Використовуйте метрики, трасування й журнали worker для: + +- Затримки `schedule_to_start` workflow й activity +- Успішності poll та поведінки sync/eager-dispatch +- Розміру sticky-cache та витіснення записів +- Навантаження CPU, пам'яті, потоків і циклу подій worker +- Власних метрик застосунку з activity чи коду worker + +Синхронні query, інструменти живого налагодження та інші виклики +площини керування без надійних завдань позначайте окремо в панелях. Вони не входять до надійного +backlog завдань і не змінюють лічильники відновлення Waterline. + +## Сумісність worker і здоров'я розгортання {#worker-compatibility-and-rollout-health} + +`operator_metrics.workers` публікує факти сумісності, що визначають здатність +активного парку worker безпечно виконувати потрібний контракт workflow: + +| Факт | Значення | +| --- | --- | +| `operator_metrics.workers.required_compatibility` | Маркери сумісності, які worker має оголосити для отримання роботи namespace. | +| `operator_metrics.workers.active_workers` | Кількість окремих живих worker за heartbeat сумісності. | +| `operator_metrics.workers.active_worker_scopes` | Кількість областей `(connection, queue)`, які покривають ці worker. | +| `operator_metrics.workers.active_workers_supporting_required` | Worker, чия оголошена сумісність покриває потрібні маркери. | +| `operator_metrics.workers.fleet` | Список активних worker за областю з `worker_id`, `connection`, `queue`, оголошеними `supported`, прапорцем `supports_required`, джерелом heartbeat `source` (`database` чи `cache`) та `recorded_at`. | + +Використовуйте зведені кількості для виявлення стану розгортання, у якому +частина worker не може безпечно отримувати потрібну роботу. Переглядайте +`fleet` для визначення конкретних `(connection, queue)` без покриття. +Операторська панель Waterline показує той самий парк у панелі сумісності +worker, тому не потрібно вручну запитувати метрики. + +Коли `active_workers_supporting_required` дорівнює нулю для namespace, +Waterline показує діагностику `no_compatible_worker_for_task` на відповідних +виконаннях. Супровідна перевірка `worker_compatibility` повертає `warning` +у `correctness` за тієї самої умови. Це переводить зведення `correctness` +у `warning`, щоб прогалина парку була видимою одразу, а не лише в списку перевірок. + +Потік drain/resume, узгоджений із цими фактами під час розгортання build-id, +описано в [розгортанні збірок worker через build ID](./polyglot/worker-build-id-rollout.md). +Контракт прив'язки для цієї діагностики описано в +[сумісності й маршрутизації worker](./polyglot/worker-compatibility-routing.md). + +## Семантика сповіщень {#alert-semantics} + +Пороги сповіщень залежать від розгортання. Опублікуйте власні числові базові +показники віку черг, затримки відновлення, покриття worker та часу відновлення. +Сповіщайте, якщо наведений контракт порушений довше за одне нормальне вікно +repair чи watchdog вашої топології. + +| Сімейство сповіщень | Джерело | Тлумачення | Умова ескалації | Відповідь оператора | +| --- | --- | --- | --- | --- | +| Блокувальна готовність | `workflow:v2:doctor --strict`, `GET /waterline/api/v2/health` | Блокувальне | `doctor --strict` повертає помилку або endpoint здоров'я повертає `status = error` / HTTP `503` | Зупиніть розгортання чи перенесення трафіку, виправте передумову й повторіть перевірки готовності й сумісності. | +| Покриття сумісними worker | `operator_metrics.workers.*`, `worker_compatibility` health check, run diagnostic `no_compatible_worker_for_task` | Блокувальне | `active_workers_supporting_required = 0` для namespace чи потрібної області `(connection, queue)` | Виконайте drain несумісних worker, зареєструйте сумісні й перевірте очищення `correctness` перед новим отриманням завдань. | +| Затримка надійної черги | Огляди черг Waterline, `operator_metrics.backlog.*`, телеметрія `schedule_to_start` worker | Блокувальне за тривалого стану, рекомендаційне за короткого | Вік найстарішого готового завдання чи schedule-to-start тривало перевищує базовий рівень топології за наявності сумісних worker | Додайте місткість worker, перевірте ліміти допуску черги та просування scheduler чи matching. | +| Тиск poller і насичення допуску | Маршрути деталей черг, `dw task-queue:describe`, `status` черги, застарілі poller й швидкості додавання/доставлення | Блокувальне для `no_active_workers`, `no_slots`, `unavailable`, рекомендаційне для навмисного `throttled` | Черга тривало `saturated` зі зростанням віку найстарішої роботи й розриву додавання/доставлення або переходить у `no_active_workers`, `no_slots`, `unavailable` поза плановим обслуговуванням | Додайте слоти worker, відновіть відсутню групу poller чи підтвердьте правильну роботу ліміту сервера й залежності блокування перед масштабуванням. | +| Backlog запусків workflow | `operator_metrics.starts.*`, телеметрія запуску площини керування, `schedule_to_start` перших завдань worker | Блокувальне за тривалого стану, рекомендаційне за короткого | `pending_commands`, `ready_tasks` чи `max_pending_ms` тривало перевищують базовий рівень за наявності сумісних worker і місткості черг | Перевірте шлях запуску наскрізно: перетворення команд у надійні завдання, своєчасне створення першого завдання matching/dispatch, відокремлення боргу запуску від загальної затримки worker перед масштабуванням. | +| Відхилення проєкцій і борг repair | `run_summary_projection` / `selected_run_projections` health checks, `operator_metrics.repair.*` | Рекомендаційне | Попередження відхилень тривають довше планового вікна rebuild або найбільший вік кандидата зростає | Перегляньте rebuild/repair, виконайте виправлення, підтвердьте зникнення попередження й повернення віку до базового рівня. | +| Шторм повторних спроб чи помилок | `operator_metrics.backlog.unhealthy_tasks`, надійна діагностика виконань, телеметрія помилок worker | Рекомендаційне, з переходом у блокувальне за перешкоджання надійному прогресу | Помилки dispatch/claim, прострочені lease чи вичерпання retry тривало перевищують базовий рівень топології | Перевірте сімейство помилкових завдань, порівняйте телеметрію worker з надійними фактами помилок і вирішіть щодо drain трафіку чи ізоляції черги. | +| Погіршення прискорення пробудження | `long_poll_wake_acceleration` і зведення категорії `acceleration` | Рекомендаційне | Попередження прискорення триває після вікна обслуговування кешу чи notifier | Перевірте кеш і поширення пробудження. Вважайте це збоєм коректності лише за одночасного погіршення `correctness`. | + +Мета: сповіщати про ризик надійного контракту, а не про кожен тимчасовий сигнал. +Сповіщення черг і worker стають блокувальними лише за загрози операторському +контракту фактичної топології. + +## Очікування щодо rebuild, repair і restore {#rebuild-repair-and-restore-expectations} + +Якщо операторська поверхня повідомляє про відхилення, виконуйте перевірки в такому порядку: + +1. Перевірте `GET /waterline/api/v2/health`. + - Попередження `run_summary_projection` і `selected_run_projections` + означають доступність Waterline, але потребу відновлення фактів списку чи деталей. + - `command_contract_snapshots` означає, що старі виконання потребують backfill + контракту WorkflowStarted для довіри до оголошених форм signal, update чи query. + - `durable_resume_paths` означає, що відкриті виконання потребують відновлення + до використання їхнього спроєктованого наступного джерела продовження. +2. Перегляньте роботу проєкцій: + + ```bash + php artisan workflow:v2:rebuild-projections --needs-rebuild --dry-run + ``` + +3. Відновіть відповідні проєкції: + + ```bash + php artisan workflow:v2:rebuild-projections --needs-rebuild + ``` + +4. Перегляньте роботу backfill контрактів команд: + + ```bash + php artisan workflow:v2:backfill-command-contracts --dry-run + ``` + +5. Виконайте backfill контрактів, якщо поточний клас workflow досі доступний: + + ```bash + php artisan workflow:v2:backfill-command-contracts + ``` + +6. Використовуйте `--prune-stale` лише після навмисного видалення надійних рядків + процедурою retention для видалення проєкцій, чиї виконання або рядки історії + більше не існують. + +`operator_metrics.repair.*` публікує охоплення проходів циклу відновлення. +За кількістю кандидатів, вибраних записів, найбільшим віком кандидата та +навантаженням на межу сканування оцінюйте відповідність роботи базовому рівню +чи потребу дослідження місткості. + +## Перевірка експорту й архіву {#export-and-archive-verification} + +Експорт історії й архівування мають різні призначення: + +- **Експорт історії** створює артефакт replay, налагодження чи архіву. +- **Архівування** позначає закрите виконання як архівне для вилучення з активних оглядів парку. +- **Prune** видаляє проєкції чи надійні рядки після завершення строку retention. + +Використовуйте цю послідовність перевірки: + +1. Експортуйте вибране виконання: + + ```bash + php artisan workflow:v2:history-export --run-id= --output=storage/app/workflow-history/run.json --pretty + ``` + +2. Перевірте наявність очікуваного ID виконання, версії схеми й налаштованих метаданих приховування даних. +3. Архівуйте закрите виконання лише після збереження експорту в місці, передбаченому інструкцією. +4. Зберігайте архівні, але ще не видалені виконання для розслідування інцидентів. +5. Видаляйте надійні рядки завданням retention, потім відновіть/очистіть проєкції + через `workflow:v2:rebuild-projections --prune-stale`. + +Відповідні маршрути експорту історії й архівування Waterline наведені в +[довіднику операторського API Waterline](./waterline-operator-api.md). + +## Контракт резервних копій і аварійного відновлення {#backup-restore-and-disaster-recovery-contract} + +Резервні копії й аварійне відновлення входять до меж експлуатації. +Для кожної підтримуваної топології опублікуйте й відпрацюйте: + +1. Надійний набір резервних копій: база даних, образ Server чи застосунку, + файл середовища runtime чи конфігурація, розташування матеріалів автентифікації + та точні примітки топології чи відновлення для повторного підключення worker. +2. Цілі відновлення: максимально прийнятне відставання відновлених даних, + очікувана затримка перемикання та право оголосити трафік знову безпечним. +3. Порядок відновлення: надійне сховище, кеш, bootstrap чи міграції, + єдина роль планувальника чи обслуговування, готовність API, реєстрація worker. +4. Перевірку: `/api/ready` чи `/waterline/api/v2/health`, `/api/cluster/info` + за наявності, одну репрезентативну реєстрацію worker та один експорт + історії з відновленого стану. +5. Прохід виправлення: відновлення проєкцій, backfill контрактів за потреби та + повернення метрик черг, сумісності й відновлення до базового рівня + перед оголошенням середовища здоровим. + +Кілька регіонів і split-brain залишаються поза підтримуваними межами експлуатації +2.0. [Посібник самостійного розгортання](/docs/deployment#activepassive-multi-region) +зберігає архітектуру active/passive та матеріали інструкцій лише для оцінювання +із підтримкою. Він не встановлює гарантій самостійної реплікації, failover, +failback, RPO чи RTO. Поточний +[контракт керованого runtime Cloud](/docs/polyglot/cloud-control-plane) +розміщує кожен namespace в одному керованому регіоні й також не обіцяє +багаторегіональної реплікації чи регіональних failover і failback. + +Частота тренувань відновлення також належить до публічного операторського +контракту. Щонайменше відпрацьовуйте задокументовану послідовність: + +- до першого production розгортання топології +- після зміни механізму резервних копій, схеми/bootstrap, моделі автентифікації чи топології +- регулярно за опублікованим графіком у тій самій інструкції, що й частота резервних копій + +Якщо немає дати останнього успішного тренування, тривалості відновлення та +доказів перевірки, резервні копії й DR для цієї топології залишаються непідтвердженою заявою. + +## Межі benchmark {#benchmark-envelope} + +Durable Workflow v2 публікує виміри для benchmark вашого середовища. +Зафіксуйте базові показники в тестовому чи canary середовищі, перш ніж +production трафік почне залежати від них: + +| Вимір | Базові показники | Джерело | +| --- | --- | --- | +| Здоров'я проєкцій | Сталий `needs_rebuild = 0`, тривалість rebuild після навмисного відхилення й час очищення застарілих/осиротілих записів | `/waterline/api/v2/health`, `/waterline/api/stats`, `workflow:v2:rebuild-projections` | +| Навантаження черг | Вік backlog і найстарішого готового завдання, готові й відкладені завдання, швидкість додавання й доставлення, вік простроченого dispatch, застарілі poller, стан допуску (`accepting`, `saturated`, `throttled`, `no_slots`, `no_active_workers`) | Статистика й черги Waterline, `operator_metrics.backlog.*` / `operator_metrics.tasks.*` | +| Затримка запуску workflow | Прийняті команди запуску, що очікують першого завдання, найстаріший очікуваний запуск і отримання першого завдання після допуску | `operator_metrics.starts.*` і телеметрія `schedule_to_start` worker | +| Затримка schedule-to-start | Очікування workflow й activity від постановки до початку | Метрики SDK worker | +| Пробудження масових timer | Час поширення пробудження й затримка видимості готового завдання від запланованого спрацювання за сплеску timer | Телеметрія worker і перевірки координації пробудження в одному регіоні | +| Вартість проходу repair | Кандидати, вибрані записи, найбільший вік кандидата й відсутнього виконання, навантаження сканування | `operator_metrics.repair.*` | +| Навантаження історії | Кількість подій, розмір історії й рекомендовані пороги continue-as-new | `operator_metrics.history.*` | + +Це виміри benchmark, а не універсальні обіцянки затримки. +Опублікуйте прийнятні діапазони для своєї топології. + +## Докази тривалого тесту {#long-soak-evidence} + +Самих знімків benchmark недостатньо. Перш ніж вважати топологію надійною +для тривалого трафіку, зберігайте докази тривалого тесту, що показують +дотримання заявлених меж у часі. + +Щонайменше включіть: + +- форму навантаження: топологія, образ Server чи ревізія застосунку, build ID + worker, розташування черг, бекенди кешу й бази даних, репрезентативна суміш + запусків workflow, timer, activity, query та експортів +- вікно тесту: початок і кінець та тривалість, що охоплює хоча б одне звичайне + вікно repair, один прохід retention чи архівування за потреби та характерну + зміну трафіку бізнес-циклу цього середовища +- стабільність надійних черг: вік backlog, готових завдань і backlog запусків, + співвідношення додавання й доставлення завдань і кількості застарілих poller + в межах опублікованого базового рівня топології +- стабільність коректності: без тривалого `status = error` від + `GET /waterline/api/v2/health`, непоясненого росту `operator_metrics.repair.*` + і постійних прогалин сумісності в `operator_metrics.workers.*` +- стабільність процесів і кешу: обмежені пам'ять, CPU, навантаження циклу подій + чи потоків worker та розмір/кількість записів кешу без монотонного росту за сталого навантаження +- докази відновлення: час останньої успішної резервної копії та тренування + відновлення, тривалість відновлення й команди перевірки готовності + +Зберігайте набір там, де оператори можуть отримати інструкцію розгортання. +Заявлені числа benchmark, семантику сповіщень чи час відновлення без +відповідного тривалого тесту вважайте попередніми, а не перевіреними доказами +меж експлуатації. + +## Наскрізний контрольний список оператора {#end-to-end-operator-checklist} + +Використовуйте цей список після оновлень і перед довірою до нового середовища: + +1. Виконайте `php artisan workflow:v2:doctor --strict`. +2. Перевірте `GET /waterline/api/v2/health` і визначте стан `ok`, `warning` чи `error`. +3. Прочитайте `GET /waterline/api/stats`: backlog, repair, історія, контракти команд, + сумісність worker та відхилення проєкцій. +4. За повідомлення про відхилення перегляньте відновлення проєкцій чи backfill контрактів. +5. Експортуйте репрезентативне виконання та перевірте шлях артефакту архіву/replay. +6. Підтвердьте зникнення архівних виконань з активних оглядів парку зі збереженням + надійних рядків до очищення retention. +7. Відпрацюйте послідовність restore чи failover з інструкції розгортання й + перевірте відповідність виміряної затримки опублікованому очікуванню топології. + +## Пов'язані посібники {#related-guides} + +- [Моніторинг](./monitoring.md) +- [Довідник операторського API Waterline](./waterline-operator-api.md) +- [Очищення workflow](./configuration/pruning-workflows.md) +- [Самостійне розгортання](./deployment.md) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/platform-conformance.md b/i18n/uk/docusaurus-plugin-content-docs/current/platform-conformance.md new file mode 100644 index 0000000000..cab2dc58f6 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/platform-conformance.md @@ -0,0 +1,539 @@ +--- +sidebar_position: 19 +title: Набір перевірок відповідності платформи +description: Публічне авторитетне джерело маніфесту перевірок відповідності Durable Workflow v2, каталогу fixture, правил harness і вимог релізів. +tags: + - compatibility + - conformance + - protocols + - api-stability +keywords: + - набір перевірок відповідності платформи + - harness відповідності + - fixture відповідності + - сумісність durable workflow + - platform-conformance-contract +--- + +import PublicAuthorityIdentity from '@site/src/components/PublicAuthorityIdentity'; +import WorkerProtocolAuthorityRoles from '@site/src/components/WorkerProtocolAuthorityRoles'; +import platformConformanceContract from '@site/static/platform-conformance-contract.json'; + +# Набір перевірок відповідності платформи {#platform-conformance-suite} + +Ця сторінка є публічним авторитетним джерелом **набору перевірок відповідності +платформи** Durable Workflow. Вона визначає матрицю цілей, каталог повторно +використовуваних fixture, контракт harness, правила успіху й помилок та вимоги +релізів для реалізацій, що заявляють сумісність із Durable Workflow v2. + + + +Самостійний Server оголошує той самий маніфест у `GET /api/cluster/info` +під `platform_conformance_suite`. Каталог +[специфікацій протоколів платформи](/docs/platform-protocol-specs) називає +вкладений маніфест сімейством об'єктів `platform_conformance_suite_manifest` +у специфікації `cluster_info_envelope`. + +Набір підпорядковується авторитетному джерелу +[сумісності версій](/docs/compatibility). Назви сімейств поверхонь і правила +стабільності мають відповідати контракту стабільності поверхонь. +Джерело сумісності визначає контракт, а ця сторінка визначає, як реалізація +доводить його виконання. + + + +## Публічні джерела відповідності {#public-conformance-authorities} + +Набір реєструє стабільні машиночитані джерела за ідентичністю схеми й публічним +URL. Незалежний від фреймворку контракт PHP SDK опубліковано як +[`durable-workflow.v2.php-sdk-conformance-contract`](pathname:///platform-conformance/php-sdk-conformance.json). +Він визначає топологію випущених пакетів, вимоги сценаріїв і доказів та +публічні ідентифікатори runner і схеми результату для оцінювання +`durable-workflow/sdk` проти випущеного самостійного Server. + +## Матриця цілей {#target-matrix} + +**Ціль** відповідності є видом реалізації, що може заявляти сумісність Durable. +Реалізація може заявляти кілька цілей. Наприклад, самостійний Server заявляє +`standalone_server`, `worker_protocol_implementation` і `repair_actionability_surface`. + +| Ціль | Обов'язкові сімейства поверхонь | Обов'язкові категорії fixture | +| --- | --- | --- | +| `standalone_server` | `server_api`, `worker_protocol`, `cluster_info_manifests` | `control_plane_request_response`, `signal_query_runtime_contract`, `workflow_update_runtime_contract`, `search_attribute_runtime_contract`, `schedules_runtime_contract`, `namespace_runtime_contract`, `child_workflow_runtime_contract`, `saga_runtime_contract`, `worker_versioning_runtime_contract`, `migration_runtime_contract`, `skew_refusal_matrix_contract`, `principal_attribution_contract`, `worker_task_lifecycle`, `failure_repair_actionability` | +| `embedded_engine` | `history_event_wire_formats` | `history_replay_bundles` | +| `official_sdk` | `official_sdks`, `worker_protocol`, `history_event_wire_formats` | `control_plane_request_response`, `signal_query_runtime_contract`, `workflow_update_runtime_contract`, `search_attribute_runtime_contract`, `schedules_runtime_contract`, `namespace_runtime_contract`, `child_workflow_runtime_contract`, `saga_runtime_contract`, `worker_versioning_runtime_contract`, `migration_runtime_contract`, `skew_refusal_matrix_contract`, `principal_attribution_contract`, `worker_task_lifecycle`, `history_replay_bundles` | +| `worker_protocol_implementation` | `worker_protocol`, `history_event_wire_formats` | `worker_task_lifecycle`, `signal_query_runtime_contract`, `workflow_update_runtime_contract`, `search_attribute_runtime_contract`, `schedules_runtime_contract`, `namespace_runtime_contract`, `child_workflow_runtime_contract`, `saga_runtime_contract`, `worker_versioning_runtime_contract`, `migration_runtime_contract`, `skew_refusal_matrix_contract`, `history_replay_bundles` | +| `cli_json_client` | `cli_json` | `control_plane_request_response`, `signal_query_runtime_contract`, `workflow_update_runtime_contract`, `search_attribute_runtime_contract`, `schedules_runtime_contract`, `namespace_runtime_contract`, `child_workflow_runtime_contract`, `saga_runtime_contract`, `worker_versioning_runtime_contract`, `migration_runtime_contract`, `skew_refusal_matrix_contract`, `principal_attribution_contract`, `cli_json_envelopes` | +| `waterline_contract_surface` | `waterline_api` | `signal_query_runtime_contract`, `workflow_update_runtime_contract`, `search_attribute_runtime_contract`, `namespace_runtime_contract`, `saga_runtime_contract`, `worker_versioning_runtime_contract`, `migration_runtime_contract`, `skew_refusal_matrix_contract`, `principal_attribution_contract`, `waterline_observer_envelopes` | +| `repair_actionability_surface` | `worker_protocol`, `server_api` | `failure_repair_actionability` | +| `mcp_discovery_surface` | `mcp_discovery_results` | `mcp_discovery_envelopes` | +| `prerelease_release_candidate` | `server_api`, `official_sdks`, `cli_json`, `waterline_api`, `cluster_info_manifests` | `skew_refusal_matrix_contract`, `workflow_update_runtime_contract`, `principal_attribution_contract`, `prerelease_readiness_contract` | + +Цілі стабільні. Додавання цілі, потрібної поверхні до наявної цілі чи категорії +fixture, переведення provisional категорії в обов'язкову, зміна `operations` +чи `pass_criteria` стабільного runtime сценарію, зміна його публічної вимоги +(`artifact_policy`, `common_result_evidence`, `required_matrix`, +`scenario_requirements`, `host_runner_contract`) або правил успіху/помилок +є зміною контракту набору й потребує збільшення версії маніфесту. + +## Каталог fixture {#fixture-catalog} + +Кожне стабільне джерело є публічним артефактом із трьома машиночитаними +прив'язками в каталозі: `artifact_id`, `resolver_url`, `sha256`. +ID артефактів runtime закінчуються версією набору, ID протоколів версією +каталогу протоколів платформи. Споживач отримує артефакт за `resolver_url` +і приймає байти лише за збігу SHA-256 із прив'язкою каталогу. + +| Категорія | Статус | Джерело, доступне споживачу | +| --- | --- | --- | +| `control_plane_request_response` | `stable` | [`durable-workflow.v2.control-plane-api@catalog-16`](https://raw.githubusercontent.com/durable-workflow/durable-workflow.github.io/f781ced1ae33c8697835bd527a125bdf3eaf4321/static/platform-protocol-specs/control-plane-api.openapi.yaml) | +| `worker_task_lifecycle` | `stable` | [`durable-workflow.v2.worker-protocol-api@catalog-16`](https://durable-workflow.github.io/platform-protocol-specs/v1.19/worker-protocol-api.openapi.yaml) | +| `worker_task_lifecycle` | `stable` | [`durable-workflow.v2.worker-protocol-stream@catalog-16`](https://durable-workflow.github.io/platform-protocol-specs/v1.19/worker-protocol-stream.asyncapi.yaml) | +| `signal_query_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/signal_query_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/signal-query-runtime-scenarios.json) | +| `workflow_update_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/workflow_update_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/workflow-update-runtime-scenarios.json) | +| `search_attribute_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/search_attribute_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/search-attribute-runtime-scenarios.json) | +| `schedules_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/schedules_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/schedules-runtime-scenarios.json) | +| `history_replay_bundles` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/history_replay_bundles@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/replay-runtime-scenarios.json) | +| `history_replay_bundles` | `stable` | [`durable-workflow.v2.history-event-payloads@catalog-16`](https://raw.githubusercontent.com/durable-workflow/durable-workflow.github.io/f781ced1ae33c8697835bd527a125bdf3eaf4321/static/platform-protocol-specs/history-event-payloads.schema.json) | +| `history_replay_bundles` | `stable` | [`durable-workflow.v2.replay-bundle@catalog-16`](https://raw.githubusercontent.com/durable-workflow/durable-workflow.github.io/f781ced1ae33c8697835bd527a125bdf3eaf4321/static/platform-protocol-specs/replay-bundle.schema.json) | +| `namespace_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/namespace_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/namespace-runtime-scenarios.json) | +| `child_workflow_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/child_workflow_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/child-workflow-runtime-scenarios.json) | +| `worker_versioning_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/worker_versioning_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/worker-versioning-runtime-scenarios.json) | +| `saga_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/saga_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/saga-runtime-scenarios.json) | +| `migration_runtime_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/migration_runtime_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/migration-runtime-scenarios.json) | +| `skew_refusal_matrix_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/skew_refusal_matrix_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/skew-refusal-matrix-scenarios.json) | +| `prerelease_readiness_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/prerelease_readiness_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/prerelease-readiness-scenarios.json) | +| `failure_repair_actionability` | `stable` | [`durable-workflow.v2.repair-actionability-objects@catalog-16`](https://raw.githubusercontent.com/durable-workflow/durable-workflow.github.io/f781ced1ae33c8697835bd527a125bdf3eaf4321/static/platform-protocol-specs/repair-actionability-objects.schema.json) | +| `cli_json_envelopes` | `stable` | [`durable-workflow.cli.output-schema-manifest@3`](https://durable-workflow.github.io/cli-json-envelopes/v3/manifest.json) | +| `principal_attribution_contract` | `stable` | [`durable-workflow.v2.platform-conformance.runtime-scenarios/principal_attribution_contract@38`](https://raw.githubusercontent.com/durable-workflow/workflow/75dfd5c869823409ef3d6c4b009a7882159ae9a2/resources/conformance/suite-v38/platform-conformance/principal-attribution-scenarios.json) | + +Рядок набору зберігає ревізію схеми CLI v3 як незмінний історичний доказ. +Поточний контракт CLI є повним набором +[`durable-workflow.cli.output-schema-manifest@4`](https://durable-workflow.github.io/cli-json-envelopes/v4/manifest.json), +який публікує поля payload лише Avro без зміни байтів v2 чи v3. + +Поточна ціль протоколу worker у наборі використовує незалежні від життєвого +циклу байти протоколу 1.19, записані як +`durable-workflow.v2.worker-protocol-api@catalog-16` і +`durable-workflow.v2.worker-protocol-stream@catalog-16`. Ревізії 1.15–1.18 +залишаються доступними за версіонованими URL із історії набору. +Ці fixture не заміняють неверсіонованого поточного джерела протоколу +опублікованого Server. Колишні байти з формулюванням beta доступні лише як +явно історична прив'язка +[`durable-workflow.v2.worker-protocol-api@catalog-16-beta-history`](https://raw.githubusercontent.com/durable-workflow/durable-workflow.github.io/e990bc36731463cc5b2cb2a9175dbccfdea61704/static/platform-protocol-specs/worker-protocol-api.openapi.yaml) +у маніфесті набору. + +Заплановані категорії `waterline_observer_envelopes` і `mcp_discovery_envelopes` +залишаються `provisional`. Їхні шляхи вихідного коду є ненормативними +заповнювачами: harness не має отримувати їх як fixture чи використовувати +для заяви стабільної відповідності. + +## Джерело релізу життєвого циклу workflow {#workflow-lifecycle-release-authority} + +Точне джерело сценаріїв випущеного життєвого циклу workflow +[опубліковано як JSON](https://durable-workflow.github.io/platform-conformance/workflow-lifecycle-scenarios.json). +Воно фіксує вимоги життєвого циклу, перевірені на поточній межі релізів +опублікованих Server, PHP SDK і Rust SDK. + +Шард PHP встановлює точний пакет `durable-workflow/sdk` із поточного кортежу +артефактів з Packagist у тимчасовий проєкт Composer. Він запускає окремі +процеси клієнта й worker PHP проти відповідного публічного образу Server, +фіксує походження дистрибутива Packagist і офіційного `apache/avro` та +має повідомити `local_product_source_checkouts_used=false`. + +Шард Rust встановлює точний crate поточного кортежу артефактів із crates.io +та фіксує джерело реєстру й контрольну суму цього crate та офіційного +`apache-avro`. Доказ payload використовує опубліковану Avro обгортку SDK +на основі `apache-avro`. Власний кодек чи локальні вихідні коди продукту +не є прийнятним походженням. + +Шард Rust має виконати й повідомити всі ці перевірки: + +- `instance_cancel` і `instance_terminate` через публічні команди SDK. +- `selected_run_guard` і `stale_run_rejection`, щоб вибране виконання не + плуталося з поточним виконанням екземпляра. +- `typed_failed`, `typed_cancelled`, `typed_terminated`, `typed_timed_out` + як типізовані термінальні результати з ідентичністю workflow й виконання. +- `cancellation_heartbeat` і `late_activity_completion_refused`, щоб activity + спостерігала скасування, а пізнє завершення не перезаписувало термінальний результат. +- `worker_restart_during_cancellation`, поки врегулювання скасування ще триває, + щоб довести відсутність повторного отримання закритої activity новим worker. + +Маніфест також вимагає точні версії артефактів і Server, ідентичність кластера, +походження інсталяції, ідентичності workflow, результати кожної перевірки, +сталі причини, контракт payload, топологію виконання, версію контракту Rust +шарда, ідентичність runner і статус завершення шарда. Відсутні, непідтримувані, +заблоковані runner чи неуспішні докази Rust не задовольняють категорію. + +Категорія fixture обов'язкова для цілі лише якщо ціль її перелічує та статус +категорії не `provisional`. Provisional категорії дають рекомендаційні +попередження й стають обов'язковими лише після переведення в `stable` +у пізнішій версії набору. + +Категорія `signal_query_runtime_contract` стабільна. Результат має фіксувати +конкретні опубліковані версії артефактів і називати кожен потрібний сценарій +як `pass`, `fail`, `unsupported`, `not_covered` чи `runner_blocked` із +посиланнями на знахідки. Заповнювачі чи невизначені версії на кшталт +`latest`, `current`, `head`, ``, `${VERSION}` чи `{{ version }}` +не проходять перевірку результату. Лише `pass` зараховується до успішної категорії: +`published_artifact_install_only`, +`python_worker_cli_and_sdk_baseline`, `php_worker_cli_and_sdk_baseline`, +`python_worker_php_facing_and_cli_clients`, +`php_worker_python_and_cli_clients`, +`rust_worker_rust_php_python_clients`, `python_worker_rust_client`, +`php_worker_rust_client`, `rust_query_error_and_immutability`, +`ordered_signal_delivery`, +`dedup_contract_observation`, `signal_during_replay`, +`query_during_replay`, +`rust_replayed_instance_state_query_after_cold_restart`, +`completed_run_signal_and_query`, +`unknown_signal_and_query_errors`, +`malformed_signal_and_query_payloads`, and +`waterline_operator_visibility`. + +ID цих сценаріїв та критерії успіху опубліковані як машиночитаний маніфест +runtime сценаріїв у +[`static/platform-conformance/signal-query-runtime-scenarios.json`](pathname:///platform-conformance/signal-query-runtime-scenarios.json). +Тести реалізації можуть перевіряти сценарії, але не є стабільним джерелом +fixture для зовнішніх harness. + +Перевірки Rust встановлюють точну версію crate `durable-workflow`, оголошену +маніфестом runtime сценаріїв, із crates.io та фіксують джерело Cargo і +контрольні суми SDK та визначеної залежності `apache-avro`. +Транспорт query, отриманий зі знімка, оцінюють +`rust_worker_rust_php_python_clients` і `rust_query_error_and_immutability`. +Це не стан екземпляра workflow, відновлений replay. Окрема перевірка +`rust_replayed_instance_state_query_after_cold_restart` використовує +`register_replayed_workflow` і `register_replayed_query`, запускає новий +процес worker Rust після холодної зупинки, відновлює надійну історію й +порівнює активний, відновлений і завершений стан через виклики Rust, PHP і Python. +Успішні й помилкові послідовності query фіксують кількості історії й команд +workflow до першого успішного вимірюваного query та залишають їх незмінними. + +Для `completed_run_signal_and_query` чисто завершене виконання з оголошеним +обробником query, придатним для replay, має повертати остаточний стан query +через кожну заявлену публічну поверхню. Сталі помилки термінального стану +допустимі лише для явно непідтримуваних станів чи недоступних обробників. +Загальна помилка completed-run не є успіхом для завершеного виконання з replay. + +`workflow_update_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат має використовувати опубліковані артефакти, фіксувати версії Server, +CLI, Python SDK, PHP SDK і Waterline, зазначати використання локальних +вихідних кодів продукту та називати кожен сценарій update як `pass`, `fail`, +`unsupported`, `not_covered` чи `runner_blocked` із посиланнями на знахідки. +Успішні докази update мають перевіряти публічні поверхні площини керування, +історії, CLI/SDK й оператора. Виконання локальних вихідних кодів не зараховується. + +Обов'язкові сценарії update охоплюють інсталяцію опублікованих артефактів, +видимість оголошеного контракту update, докази прийнятого update в площині +керування й історії, видимість активного чи очікувального стану оператором, +отримання завершеного результату, помилки й відмови, дублікати та ідемпотентність, +відмову невідомому update чи неправильному вводу, обгортку payload, +термінальний workflow, автентифікованого суб'єкта, паритет клієнта/worker PHP +і Python та операторську діагностику. Якщо SDK не підтримує update як основну +функцію, результат має містити типізований unsupported і конкретну знахідку +SDK замість мовчазного пропуску мови. + +ID цих сценаріїв і критерії успіху опубліковані в +[`static/platform-conformance/workflow-update-runtime-scenarios.json`](pathname:///platform-conformance/workflow-update-runtime-scenarios.json). +Поточне передавання host-runner навмисно має `runner_blocked`, доки +зареєстрований host runner не встановить зафіксовані опубліковані артефакти +й не виконає всю матрицю update workflow. + +`search_attribute_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти та охоплює start/upsert PHP і Python, +query і помилки CLI, видимість у Waterline, обмін через кодек між мовами, +query рівності/діапазону/bool, граматику OR/NOT, належність keyword-list, +безпеку типів, розподіл затримки індексування, затримку під навантаженням, +ізоляцію namespace, відмову зарезервованим назвам і захист від ін'єкції query. +Підмножина Python/Server не відповідає контракту, доки кожна потрібна перевірка +не записана як `pass`, `fail`, `unsupported`, `not_covered` чи `runner_blocked` +із посиланнями на знахідки. Лише `pass` зараховується до успішної категорії. + +ID сценаріїв атрибутів пошуку та критерії успіху опубліковані в +[`static/platform-conformance/search-attribute-runtime-scenarios.json`](pathname:///platform-conformance/search-attribute-runtime-scenarios.json). + +`schedules_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює періодичність cron +і fixed-rate, list/describe, pause/resume без спрацювань, припинення запусків +після delete, політику пропущених запусків, відновлення після restart, +операції CLI, Python SDK та PHP, запуски PHP workflow з Python schedule і навпаки, +відмову неправильному cron і невідомому типу workflow. Часткова перевірка +життєвого циклу schedule не відповідає контракту, доки кожна потрібна перевірка +не записана як `pass`, `fail`, `unsupported`, `not_covered` чи `runner_blocked` +із посиланнями на знахідки. Лише `pass` зараховується до успішної категорії. + +ID сценаріїв schedule та критерії успіху опубліковані в +[`static/platform-conformance/schedules-runtime-scenarios.json`](pathname:///platform-conformance/schedules-runtime-scenarios.json). + +`history_replay_bundles` також є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює replay PHP і Python +завершеної історії, restart worker, activity, signal/update, wait-condition, +version-marker, saga-компенсацію, явну відмову розбіжності коду, зміні історії +на сервері й неправильній історії та час restart під час signal. +Часткова перевірка еталонної історії не відповідає контракту, доки всі потрібні +перевірки не записані як `pass`, `fail`, `unsupported`, `not_covered` чи +`runner_blocked` із посиланнями на знахідки. Лише `pass` зараховується до успіху. + +ID сценаріїв replay та критерії успіху опубліковані в +[`static/platform-conformance/replay-runtime-scenarios.json`](pathname:///platform-conformance/replay-runtime-scenarios.json). + +`namespace_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює create/update/describe/list +namespace, очищення й повторне створення, ізоляцію видимості й змін workflow, +черг worker PHP, контекст namespace CLI й типової області, паритет вибору +namespace SDK, ізоляцію схем і query значень атрибутів пошуку, schedule, +видимість Waterline/оператора, явні виклики Nexus між namespace, відмову +зарезервованим назвам і маршрутизацію записів результатів до знахідок продукту. +Часткова перевірка namespace не відповідає контракту, доки всі перевірки не +записані як `pass`, `fail`, `unsupported`, `not_covered` чи `runner_blocked` +із посиланнями на знахідки. Лише `pass` зараховується до успіху. + +Ненормативні примітки реалізації видимості Waterline/оператора можуть походити +з опублікованого артефакту Waterline чи окремого його шарда. Корисні знімки +для перевірки: обмежені списки й деталі workflow, schedule за наявності, +значення атрибутів пошуку, область панелі, статистика API оператора та +задокументований висновок для типового чи необмеженого огляду продукту. +Нормативні критерії залишаються в сценарії `waterline_operator_namespace_visibility`. + +ID сценаріїв namespace та критерії успіху опубліковані в +[`static/platform-conformance/namespace-runtime-scenarios.json`](pathname:///platform-conformance/namespace-runtime-scenarios.json). + +`child_workflow_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює батьківські/дочірні +виконання PHP і Python однією мовою та між мовами, типізоване повернення помилки +дочірнього workflow, поширення скасування батька на дитину, спостереження +батьком прямого скасування дитини, replay після restart батьківського worker +під час очікування дитини, паралельний запуск п'яти дітей і поведінку namespace. +Одна перевірка parent/child не відповідає контракту, доки всі перевірки не +записані як `pass`, `fail`, `unsupported`, `not_covered` чи `runner_blocked` +із посиланнями на знахідки. Лише `pass` зараховується до успіху. + +ID сценаріїв дочірніх workflow та критерії успіху опубліковані в +[`static/platform-conformance/child-workflow-runtime-scenarios.json`](pathname:///platform-conformance/child-workflow-runtime-scenarios.json). + +`saga_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює успіх прямого виконання, +помилку пізнього кроку з компенсацією у зворотному порядку, помилку раннього кроку +без зайвої компенсації, ідемпотентність повторної компенсації, термінальну +видимість помилки компенсації, restart worker під час неї, PHP workflow з +Python компенсацією й навпаки, типізовані помилки компенсації та її видимий +оператору поточний стан. Перевірка лише успішного шляху чи одного SDK не +відповідає контракту, доки всі перевірки не записані як `pass`, `fail`, +`unsupported`, `not_covered` чи `runner_blocked` із посиланнями на знахідки. +Лише `pass` зараховується до успіху. Матриця runtime називає пакет PHP +`workflow-php`, а порівняння набору артефактів релізу також може записувати `workflow`. + +ID сценаріїв saga та критерії успіху опубліковані в +[`static/platform-conformance/saga-runtime-scenarios.json`](pathname:///platform-conformance/saga-runtime-scenarios.json). + +`worker_versioning_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює реєстрацію build-ID, +видимість розгортання оператором, drain/resume, прив'язки сумісності виконань, +replay лише сумісними worker, маршрутизацію нових запусків до promoted версії, +replay після витіснення кешу, діагностику відсутності сумісного worker, +поверхні CLI й Waterline, прив'язки PHP/Python між мовами, навмисну відсутність +збільшення версії та прив'язки версій API історії. Часткова перевірка не +відповідає контракту, доки всі перевірки не записані як `pass`, `fail`, +`unsupported`, `not_covered` чи `runner_blocked` із посиланнями на знахідки. +Лише `pass` зараховується до успіху. + +Докази прив'язки PHP/Python між мовами мають фіксувати ідентичність runtime +worker, ID workflow й виконання, стан розгортання черги, результати публічних +poll і rollout для перевірки прив'язки, джерело й версію встановленого +артефакту worker та відсутність локальних вихідних кодів продукту. + +ID сценаріїв версіонування worker та критерії успіху опубліковані в +[`static/platform-conformance/worker-versioning-runtime-scenarios.json`](pathname:///platform-conformance/worker-versioning-runtime-scenarios.json). + +`migration_runtime_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює стан останньої +підтримуваної v1, задокументовані кроки міграції, збереження й replay завершеної +історії, прогрес активного workflow, стан повторної activity, очікування signal +чи timer, періодичність schedule, проєкцію реєстрації worker, видимість Waterline, +доступ CLI до стану перед оновленням, нові запуски v2, відкат з урахуванням черги +та явну відмову непідтримуваній різниці версій. Докази відкату мають перелічувати +готову, відкладену й зарезервовану роботу v1, зазначати завершення роботи, +узгоджений знімок SQL із чергою чи прийняту невідновлюваність та доводити +наявність виконуваного пробудження через чергу чи signal для кожного +відновленого незавершеного рядка v1. Придатний застарілий `pending` може +використовувати Watchdog v1.0.77 лише за спостереження запуску типово +ввімкненого Watchdog циклом worker, повторного направлення workflow після +п'ятихвилинної межі та просування рядка на записаній черзі. Цей шлях лише +для pending не заміняє збереження черги повторних спроб, timer чи роботи +`waiting`/`running`. Відновлення лише SQL є успішним доказом відкату лише +за відсутності інших незавершених залежних від черги виконань v1 на момент знімка. +Маніфест відновлення має містити лише посилання на `APP_KEY` у менеджері +секретів і версію, тоді як ключ і облікові дані відновлення зберігаються +окремо від копій SQL і черг під окремим контролем доступу. Чиста інсталяція +чи міграція без реалістичного стану v1 не відповідає контракту, доки всі +перевірки не записані як `pass`, `fail`, `unsupported`, `not_covered` чи +`runner_blocked` із посиланнями на знахідки. Лише `pass` зараховується до успіху. + +Перевірка задокументованих кроків має записати актуальний список команд +посібника, фактично виконані команди в його порядку, коди завершення й час +кожної команди. Знімки до й після мають містити завершену історію, активний +workflow, повторну activity, очікування signal чи timer, schedule й реєстрацію worker. + +ID сценаріїв міграції та критерії успіху опубліковані в +[`static/platform-conformance/migration-runtime-scenarios.json`](pathname:///platform-conformance/migration-runtime-scenarios.json). + +`skew_refusal_matrix_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й охоплює сумісні пари, старіші, +новіші та поза вікном сумісності версії CLI, Python SDK, самостійного worker +PHP SDK і Waterline. Workflow присутній лише як рушій вбудованого Laravel і +Waterline. Перевірка також тестує межі майбутніх версій, фіксує запити й +відповіді кожної операції з різницею версій, класифікує worker як +`register_refused`, `register_and_serve` чи `register_and_drop`, Waterline як +`banner`, `render_refused` чи `stale_render`. Часткова перевірка маніфесту +протоколу не відповідає контракту, доки всі сценарії не записані як `pass`, +`fail`, `unsupported`, `not_covered` чи `runner_blocked` із посиланнями на знахідки. +Лише `pass` зараховується до успіху. `register_and_drop` і `stale_render` +без явного попередження є блокувальними дефектами продукту. + +ID сценаріїв відмови несумісним версіям та критерії успіху опубліковані в +[`static/platform-conformance/skew-refusal-matrix-scenarios.json`](pathname:///platform-conformance/skew-refusal-matrix-scenarios.json). + +`principal_attribution_contract` є стабільною категорією runtime сценаріїв. +Результат використовує опубліковані артефакти й доводить запис визначених +сервером суб'єктів в історію для запуску, signal, query, скасування, завершення, +помилки, анонімних і серверних подій. Він перевіряє підробку payload/заголовків, +іменованих alice/bob, ротацію облікових даних, видимість CLI і Waterline та +автентифіковані start/signal через Python SDK і PHP `DurableWorkflow\Client`. +Перевірки SDK записують версію пакета, результати операцій, приклади суб'єктів +історії/API й еталонних HTTP викликів для порівняння форми та очікуваних ID. +PHP має отримати `durable-workflow/sdk` як точний дистрибутив Packagist +і записати тип, URL і reference. `durable-workflow/workflow` встановлюється +окремо лише для вбудованого Laravel і Waterline. Часткова перевірка role-token +не відповідає контракту, доки всі сценарії не записані як `pass`, `fail`, +`unsupported`, `not_covered` чи `runner_blocked` із посиланнями на знахідки. + +ID сценаріїв визначення суб'єкта та критерії успіху опубліковані в +[`static/platform-conformance/principal-attribution-scenarios.json`](pathname:///platform-conformance/principal-attribution-scenarios.json). + +`prerelease_readiness_contract` є стабільною категорією runtime сценаріїв +узгодженого кандидата релізу 2.0. Результат використовує лише опубліковані +артефакти й публічну документацію, фіксує окремі GO / NO-GO для Workflow і +Waterline, охоплює повноту основних функцій, готовність міграції, стабільність +API, точність документації, зрозумілість конфігурації та сумісність компонентів. +Server, CLI, SDK PHP/Python/Rust, Workflow, Waterline, sample app і публічні +документи оцінюються як один кортеж екосистеми. Кожен версіонований +самостійний quickstart 2.0 та окремий вбудований шлях Laravel має пройти +з актуальної публічної документації до видимого завершення workflow за +10 хвилин із точними командами, виводом, версіями, походженням пакетів і часом. +Перевірка лише виявлення quickstart не відповідає контракту до запису сценаріїв +PHP, Python, Rust і Laravel як `pass`, `fail`, `unsupported`, `not_covered` +чи `runner_blocked`. Відсутній артефакт, застаріла сторінка, незадокументований +крок міграції, проблема інсталяції, нестабільність API, ризик несумісності +компонентів чи невідповідність каналу релізу мають бути non-pass із знахідкою. +Докази лише runner не можуть зробити готовність prerelease успішною. + +ID сценаріїв готовності prerelease та критерії успіху опубліковані в +[`static/platform-conformance/prerelease-readiness-scenarios.json`](pathname:///platform-conformance/prerelease-readiness-scenarios.json). +Стислий [quickstart 2.0](/docs/quickstart/) є публічним шляхом початку роботи. + +## Правила успіху й помилок {#pass--fail-rules} + +1. **`guaranteed_field_equality`.** Кожне гарантоване поле схеми fixture + має бути присутнім із правильним типом і тим самим значенням у відповіді + реалізації. Суто діагностичні поля ігноруються. +2. **`unknown_additive_fields_tolerated`.** Зайві поля допустимі лише як + задокументовані суто діагностичні або за рівня стабільності fixture, + що дозволяє додавання полів. +3. **`frozen_shape_exact_match`.** Fixture поверхні `frozen` мають + збігатися точно. Розбіжність замороженої форми завжди є помилкою. +4. **`required_fixtures_must_pass`.** Реліз, що заявляє ціль, має пройти + кожну обов'язкову категорію fixture цієї цілі. Один провал означає + невідповідність релізу цій цілі. +5. **`stable_runtime_scenario_coverage`.** Стабільна категорія runtime + має повідомити кожен потрібний сценарій зі статусом свого маніфесту: + `pass`, `fail`, `unsupported`, `not_covered`, `runner_blocked`. + Повна відповідність потребує успіху всіх сценаріїв. Часткова перевірка, + пропуск, непідтримувана поверхня, непокрита чи runner-blocked перевірка + не відповідають контракту й мають посилатися на знахідку. Цей набір + статусів і правило лише pass належать семантиці версії набору 5+. +6. **`provisional_categories_warn_only`.** Провал fixture provisional + категорії дає попередження й не блокує реліз. +7. **`diagnostic_only_mismatches_pass`.** За відмінностей лише діагностичних + полів harness записує їх у `diagnostic_diff`, а fixture проходить. + +Документ результату harness оголошує один із чотирьох рівнів відповідності: + +| Рівень | Значення | +| --- | --- | +| `full` | Усі обов'язкові fixture успішні для всіх заявлених цілей. | +| `partial` | Усі обов'язкові fixture успішні хоча б для однієї цілі, але інша ціль неуспішна. | +| `provisional` | Провалилися лише provisional категорії, усі обов'язкові успішні. | +| `nonconforming` | Для кожної заявленої цілі провалився хоча б один обов'язковий fixture. | + +## Контракт harness {#harness-contract} + +Harness, що відповідає контракту: + +- завантажує маніфест із `platform_conformance_suite` у `GET /api/cluster/info` + або зі статичного дзеркала для офлайн перевірок; +- отримує кожен оголошений fixture з авторитетного джерела; +- викликає реалізацію задокументованою операцією fixture; +- порівнює відповідь за правилами вище; +- створює один документ результату перевірки зі схемою + `durable-workflow.v2.platform-conformance.result`, версією набору, + ідентичністю реалізації, результатами fixture, діагностичними відмінностями + та загальним рівнем відповідності; +- завершується ненульовим кодом тоді й лише тоді, коли рівень `nonconforming`. + +Документ результату є артефактом. Заява сумісності дійсна лише за отримання +результату для конкретної збірки реалізації та версії набору, названої цією збіркою. + +## Вимоги релізів {#release-gates} + +| Реліз | Обов'язкові заявлені цілі | Обов'язковий артефакт | +| --- | --- | --- | +| `durable-workflow/server` | `standalone_server`, `worker_protocol_implementation`, `repair_actionability_surface` | Документ результату harness, прикріплений до релізу. | +| `durable-workflow/workflow` | `embedded_engine` | Документ результату harness, прикріплений до релізу. | +| `durable-workflow/sdk` | `official_sdk`, `worker_protocol_implementation` | Документ результату harness, прикріплений до релізу. | +| `durable_workflow` | `official_sdk`, `worker_protocol_implementation` | Документ результату harness, прикріплений до релізу. | +| `dw` | `cli_json_client` | Документ результату harness, прикріплений до релізу. | +| `waterline` | `waterline_contract_surface` | Документ результату harness, прикріплений до релізу. | +| `durable-workflow/2.0-release-candidate` | `prerelease_release_candidate` | Запис відповідності зберігає результат готовності prerelease на опублікованих артефактах. | + +Під час перевірки релізу підтверджують наявність результату harness, рівень +`full` чи `provisional` і збіг версії набору в результаті з версією тестованої +збірки. `nonconforming` блокує реліз. + +## Перевірка релізу {#release-check} + +Перевірка релізу сайту `scripts/check-platform-conformance-authority.js` +завершує збірку помилкою, якщо статичний маніфест посилається на відсутнє, +локальне для репозиторію, суто версійний псевдонім чи позасайтове джерело. +Вона перевіряє `authority_doc` кожного стабільного fixture як канонічний URL +поточної документації й відхиляє відносні дерева коду, тестові fixture, +документацію, каталоги схем, неверсіоновані ID артефактів, непублічні resolver +і неправильні digest байтів стабільних категорій. Категорії runtime додатково +мають надавати публічний маніфест сценаріїв, прив'язаний до набору. +Перевірка також вимагає перелік схеми маніфесту, цілей, категорій fixture, +правил успіху/помилок і вимог релізу на цій сторінці за машиночитаним дзеркалом. + +Після зміни набору оновіть цю сторінку та `static/platform-conformance-contract.json` +разом. Якщо зміна додає ціль чи потрібну категорію fixture, переводить +provisional у stable, змінює `operations` чи `pass_criteria` стабільного сценарію, +його публічну вимогу (`artifact_policy`, `common_result_evidence`, +`required_matrix`, `scenario_requirements`, `host_runner_contract`) або правило +успіху/помилки, збільште версію набору в тій самій зміні релізу. +Перевірка фіксує критерії стабільних сценаріїв і знімки публічних вимог через +версіоновані digest, щоб зовнішні harness не отримували нові критерії чи +політики доказів під старою версією набору. + +Опубліковані записи digest критеріїв і публічних вимог runtime сценаріїв +дозволяють лише додавання. Для зміни `operations` чи `pass_criteria` залиште +наявні `VERSIONED_RUNTIME_SCENARIO_CRITERIA_DIGESTS` незмінними, збільште +версію набору, оновіть маніфест сценаріїв і додайте digest нової версії. +Для зміни вимог доказів, політики артефактів, матриці, полів вимог сценарію +чи контракту host-runner залиште наявні +`VERSIONED_RUNTIME_SCENARIO_PUBLIC_REQUIREMENT_DIGESTS` незмінними, +збільште версію набору, оновіть маніфест і додайте відповідний digest. +Перевірка порівнює опубліковані записи цільової гілки з поточною зміною, +тому редагування чи видалення старого запису спричиняє помилку навіть +за додавання digest нової поточної версії. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-reference.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-reference.md new file mode 100644 index 0000000000..ebb3b28555 --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/cli-reference.md @@ -0,0 +1,486 @@ +--- +sidebar_position: 4 +title: Довідник команд CLI +description: Команди Durable Workflow CLI, стабільні формати виводу, спільні параметри й поведінка при помилках. +tags: + - CLI + - reference + - control-plane + - automation +keywords: + - довідник команд dw + - вивід CLI + - bridge webhook + - команда workflow +--- + +# Довідник команд CLI {#cli-command-reference} + +Ця сторінка описує команди `dw` v2 як контракт для операторів та автоматизації. +Встановлення й налаштування профілів описані в [посібнику CLI](/docs/polyglot/cli). +Цей довідник допоможе підключити скрипти, завдання CI, інструкції операцій чи AI +агентів до точних форм команд. + +Усі команди, що працюють із сервером, використовують протокол площини керування +самостійного Server версії `2`. CLI перевіряє опубліковані сервером маніфести +протоколів, перш ніж довіряти канонічним полям запитів і відповідей. + +## Глобальні параметри {#global-options} + +Команди для сервера приймають ці параметри, якщо не зазначено іншого. + +| Параметр | Значення | +| --- | --- | +| `--server`, `-s` | Базовий URL Server. Перевизначає профілі й змінні середовища для цього виклику. | +| `--namespace` | Цільовий namespace. Перевизначає namespace профілю. | +| `--token` | Bearer token для цього виклику. Для збереженої автоматизації краще використовувати профілі з `--token-env`. | +| `--env` | Іменований профіль середовища CLI. Невідоме ім'я спричиняє помилку без резервного вибору. | +| `--output=table|json|jsonl` | Контракт виводу: `table` для людини, `json` як один JSON документ, `jsonl` як один JSON об'єкт на рядок для списків. | +| `--json` | Локальний псевдонім JSON виводу в командах, що його надають. | + +Команди з даними від користувача використовують спільний контракт вводу: + +| Параметр | Значення | +| --- | --- | +| `--input`, `-i` | Документ вводу безпосередньо в параметрі. | +| `--input-file` | Читати ввід із файлу або зі stdin через `-`. Несумісний із `--input`. | +| `--input-encoding=json|raw|base64` | Декодувати ввід як JSON, передати сирий текст одним аргументом або декодувати base64 одним аргументом. Типово `json`. | + +Ввід JSON для запуску workflow, signal, query, update, schedule та завершення +activity є позиційним масивом аргументів v2. Ввід raw і base64 стає одним +позиційним аргументом, тому сервер завжди отримує канонічний масив `input`. + +## З'єднання та діагностика {#connection-and-diagnostics} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw --version` | Показати ідентичність збірки CLI. Якщо `DW_ENV` чи `DURABLE_WORKFLOW_SERVER_URL` вибирає ціль, також виконати коротку перевірку сумісності. | | `-V`, `--version` | +| `dw server:health` | Перевірити стан Server і доступність автентифікації. | | глобальні параметри, `--json` | +| `dw server:info` | Показати версію Server, маніфест топології ролей, маніфести протоколів, контракт запиту, протокол worker, факти про парк worker і метадані сумісності. | | глобальні параметри, `--json` | +| `dw doctor` | Пояснити визначений стан профілю, Server, token і TLS, попередження віддаленої сумісності та наступні дії. | | глобальні параметри, `--json` | +| `dw debug workflow ` | Зібрати діагностику застряглого виконання: стан, очікувані завдання, відомості про чергу, помилки й метадані сумісності. | | `--run-id`, глобальні параметри, `--json` | +| `dw server:start-dev` | Запустити локальний сервер розробки для коротких перевірок. | | `--port`, `--db=sqlite|mysql|pgsql` | +| `dw watch workflow ` | Опитувати workflow до термінального стану чи заданої межі опитувань. | | `--run-id`, `--interval`, `--max-polls`, глобальні параметри | +| `dw upgrade` | Замінити поточний самостійний бінарний файл `dw` новішим чи зафіксованим релізом. Відмовляється змінювати Composer vendor, Homebrew cellar і PHAR. | | `--tag`, `--dry-run`, `--force`, `--output=table|json` | + +Використовуйте `server:info` для перевірки форми контракту, `doctor` для пояснення +проблем зв'язку CLI із сервером, а `debug workflow` для отримання +машиночитаного знімка виконання для підтримки. + +### Інформація про Server і топологію ролей {#server-info-and-role-topology} + +`dw server:info` надає CLI доступ до `GET /api/cluster/info`. Табличний режим +містить розділ `Topology:`, який узагальнює контракт топології ролей, +опублікований сервером для вузла, що відповідає: + +- `Supported Shapes` і `Current Shape` визначають дозволені форми розгортання + та форму, яку зараз обслуговує цей вузол. +- `Current Process Class` і `Current Roles` називають клас процесу та набір ролей вузла. +- `Matching Role`, `Matching Partitions` і `Matching Backpressure` показують + форму ролі matching, володіння пробудженням, режим доставлення завдань та + примітиви партицій, що визначають отримання готової роботи. +- `Matching Discovery Limits` узагальнює зафіксований числовий контракт ролі + matching: `poll_batch_cap`, `availability_ceiling_seconds`, + `wake_signal_ttl_seconds`, `workflow_task_lease_seconds` і + `activity_task_lease_seconds`. Оператори можуть перевірити відповідність + розгортання контракту без пошуку у вихідному коді пакета. +- `Current Write Boundaries` перелічує поверхні надійного запису, якими володіють ролі вузла. +- `Scaling Boundaries` і `Failure Domains` пояснюють джерела навантаження + та перші ознаки збоїв для кожної ролі. + +Використовуйте `--output=json`, якщо скрипту потрібен сам маніфест. +`topology.schema` і `topology.version` фіксують його ревізію для виявлення +зміни форми, а `topology.execution_mode` показує режим доставлення вузла. +Стабільні машинні поля містяться в `topology`: `supported_shapes`, +`current_shape`, `current_process_class`, `current_roles`, `matching_role`, +`role_catalog`, `authority_boundaries`, `authority_surfaces`, +`supported_topologies`, `scaling_boundaries` і `failure_domains`. +Блок `topology.matching_role` також публікує `partition_primitives` і +`backpressure_model`, щоб скрипти перевіряли осі маршрутизації пошуку готових +завдань без розбору тексту. `topology.matching_role.discovery_limits` +надає зафіксований числовий контракт: `poll_batch_cap`, +`availability_ceiling_seconds`, `wake_signal_ttl_seconds`, +`workflow_task_lease_seconds` і `activity_task_lease_seconds`. Скрипти можуть +фіксувати ці числа пакета workflow без читання розділу для людини. + +```bash +dw server:info --output=json \ + | jq '.topology | {current_shape, current_process_class, current_roles, matching_role, scaling_boundaries, failure_domains}' +``` + +Значення цих полів описані в розділі +[Топологія ролей Server](/docs/polyglot/server-role-topology). + +### Інформація про Server і стан координації {#server-info-and-coordination-health} + +`dw server:info` також публікує висновок про безпечність розгортання для всіх +namespace сервера, щоб оператори могли читати стан координації без окремої +поверхні перевірки здоров'я. Табличний режим показує `Coordination Health:` +під `Topology:`. У `--output=json` ті самі дані містяться в +`coordination_health` зі стабільними машинними полями: + +- `coordination_health.schema` і `coordination_health.version` фіксують ревізію контракту маніфесту. +- `coordination_health.namespace_scope` показує, чи висновок охоплює один namespace або весь парк. +- `coordination_health.status` і `coordination_health.http_status` показують + загальний висновок та HTTP перевірку, яку сервер застосовує до готовності. +- `coordination_health.generated_at` фіксує час знімка. +- `coordination_health.categories` узагальнює кількість перевірок за категоріями, + наприклад `correctness`, `safety`, `routing`. +- `coordination_health.warning_checks` і `coordination_health.error_checks` + перелічують перевірки, що перевели висновок у попередження чи помилку. +- `coordination_health.checks[]` містить деталі кожної перевірки з `name`, + `status`, `category` і `message` для автоматизованого пояснення погіршення. + Зафіксований перелік завжди містить `worker_compatibility`, `task_transport`, + `routing_health`, `durable_resume_paths`, перевірки проєкцій і планувальника та + `activity_path`. `activity_path` є відповідником `task_transport` для activity: + він показує виконання activity, чиї дедлайни schedule-to-start, start-to-close, + schedule-to-close або heartbeat минули без примусового застосування, а також + тривалий backlog повторних спроб activity. Перейменування `activity_path` + є зміною рівня протоколу. + +```bash +dw server:info --output=json \ + | jq '.coordination_health | {status, http_status, namespace_scope, warning_checks, error_checks}' +``` + +Базовий контракт готовності цих полів описаний у +[довіднику API Server](/docs/polyglot/server-api-reference). + +### Самооновлення {#self-upgrade} + +`dw upgrade` завантажує відповідний платформі файл із релізу GitHub +`durable-workflow/cli`, перевіряє його за `SHA256SUMS` релізу та замінює +поточний бінарний файл лише після збігу контрольної суми. `--tag` фіксує +конкретний тег релізу, `--dry-run` визначає цільовий реліз і показує URL файлів +без завантаження, а `--force` примусово завантажує й замінює файл навіть +за однакових поточної та цільової версій. + +Команда відмовляється змінювати інсталяції, якими керує інший інструмент. +JSON містить стабільне поле `status` для автоматизації без розбору тексту: + +| `status` | Значення | +| --- | --- | +| `upgraded` | Бінарний файл замінено на `target_version`. | +| `noop` | `current_version` уже відповідає `target_version`. `--force` обходить цю перевірку. | +| `dry-run` | `--dry-run` визначив `target_version`, `asset_url` і `checksum_url` без завантаження. | +| `refused` | Інсталяція не є самостійним бінарним файлом релізу (Composer vendor, Homebrew cellar чи PHAR) або для платформи немає опублікованого файлу. | +| `permission-denied` | Каталог інсталяції недоступний для запису. Payload містить `hint` із рекомендованою дією. | +| `error` | Помилка отримання каталогу релізів, невідповідність контрольної суми чи помилка файлової системи. | + +У разі відмови `installation.kind` визначає інструмент керування +(`composer-vendor`, `homebrew`, `phar` чи `binary`), а `reason` називає +належний спосіб оновлення. Для публічної автоматизації без вихідного коду +перевстановіть зафіксований реліз через `https://durable-workflow.com/install.sh`. +Для інсталяцій Homebrew використовуйте `brew upgrade durable-workflow/tap/dw`. + +## Профілі середовищ {#environment-profiles} + +Профілі містяться в `~/.config/dw/config.json` або +`$XDG_CONFIG_HOME/dw/config.json`, якщо змінна задана. Для ізоляції профілів +тестів чи CI задайте `DW_CONFIG_HOME`. + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw env:set ` | Створити чи оновити іменований профіль. | | `--server`, `--namespace`, `--token-env`, `--token`, `--tls-verify=true|false`, `--profile-output=table|json|jsonl`, `--make-default`, `--json` | +| `dw env:list` | Показати профілі з типовим приховуванням буквальних token. | | `--show-token`, `--json`, `--output=jsonl` | +| `dw env:show [name]` | Показати один профіль, типово поточний. | | `--show-token`, `--json` | +| `dw env:use ` | Задати типовий профіль. Невідомі імена спричиняють помилку. | | `--json` | +| `dw env:delete ` | Видалити профіль. | | `--json` | + +Для production профілів надавайте перевагу `--token-env=NAME`, щоб секрети +залишалися в середовищі виконання й не потрапляли до файлу профілю. + +## Команди workflow {#workflow-commands} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw workflow:start` | Запустити workflow через площину керування. | | `--type`, `--workflow-id`, `--business-key`, `--task-queue`, `--duplicate-policy`, `--memo`, `--search-attr key=value`, `--execution-timeout`, `--run-timeout`, `--wait`, параметри вводу, `--json` | +| `dw workflow:list` | Показати екземпляри workflow. | | `--type`, `--status`, `--query`, `--limit`, глобальні параметри виводу | +| `dw workflow:describe ` | Описати поточне чи вибране виконання. | | `--run-id`, `--follow`, `--json` | +| `dw workflow:list-runs ` | Показати виконання екземпляра workflow. | | `--json` | +| `dw workflow:show-run ` | Показати одне виконання. | | `--follow`, `--json` | +| `dw workflow:history ` | Прочитати події історії виконання. | | `--follow`, `--page-size`, `--json` | +| `dw workflow:history-export ` | Експортувати архівний payload історії виконання. | | `--output-file`, глобальні параметри | +| `dw workflow:signal ` | Надіслати signal. | | `--run-id`, параметри вводу, `--json` | +| `dw workflow:query ` | Виконати query workflow лише для читання. | | `--run-id`, параметри вводу, `--json` | +| `dw workflow:update ` | Подати чи виконати update workflow. | | `--wait=accepted|completed`, `--run-id`, параметри вводу, `--json` | +| `dw workflow:cancel [workflow-id]` | Закрити один workflow чи вибрану групу як скасовані без очищення в коді workflow. | | `--reason`, `--run-id`, `--all-matching`, `--type`, `--status`, `--limit`, `--yes`, `--json` | +| `dw workflow:terminate ` | Примусово завершити workflow. | | `--reason`, `--run-id`, `--json` | +| `dw workflow:repair ` | Попросити сервер відновити застрягле виконання чи виконання, що допускає повторну спробу. | | `--json` | +| `dw workflow:archive ` | Архівувати закрите виконання. | | `--reason`, `--json` | + +Приклади: + +```bash +dw workflow:start \ + --type=App\\Workflows\\ProcessOrder \ + --workflow-id=order-123 \ + --task-queue=payments \ + --input='["order-123"]' \ + --json + +dw workflow:update order-123 approve --wait=completed --input='["manager"]' +dw workflow:cancel --all-matching='WorkflowType = "ImportJob"' --limit=25 --yes +``` + +`workflow:start`, `workflow:signal`, `workflow:query` і `workflow:update` +перевіряють канонічні поля запиту за контрактом, опублікованим сервером. +Неканонічні застарілі псевдоніми відхиляються до надсилання запиту. + +## Команди адаптерів bridge {#bridge-adapter-commands} + +Команди bridge є обмеженими інструментами приймання та передавання подій +інтеграції. Вони викликають серверні адаптери bridge і повертають форму +`durable-workflow.v2.bridge-adapter-outcome.contract` у режимі JSON. +Вони не виконують код workflow і не керують переходами стану workflow. + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw bridge:webhook ` | Надіслати одну подію webhook bridge, що запускає workflow, надсилає signal чи update через площину керування. | | `--action=start_workflow|signal_workflow|update_workflow`, `--idempotency-key`, `--target`, параметри вводу, `--correlation`, `--json` | + +Приклади: + +```bash +dw bridge:webhook stripe \ + --action=start_workflow \ + --idempotency-key=stripe-event-1001 \ + --target='{"workflow_type":"orders.fulfillment","task_queue":"external-workflows","business_key":"order-1001"}' \ + --input='{"order_id":"order-1001"}' \ + --json + +dw bridge:webhook pagerduty \ + --action=signal_workflow \ + --idempotency-key=pd-event-3003 \ + --target='{"workflow_id":"wf-remediation-42","signal_name":"incident_escalated"}' \ + --input='{"severity":"critical"}' +``` + +Використовуйте поля результату bridge замість висновків лише за HTTP статусом. +`outcome`, `reason`, `control_plane_outcome`, `idempotency_key` та зведення +`target` із прихованими даними є контрактом автоматизації для дублікатів, +відсутніх маршрутів, неправильних payload і прийнятих передавань. + +## Команди schedule {#schedule-commands} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw schedule:create` | Створити schedule. | | `--schedule-id`, `--workflow-type`, `--cron`, `--interval`, `--task-queue`, `--timezone`, `--execution-timeout`, `--run-timeout`, `--overlap-policy`, `--jitter`, `--max-runs`, `--paused`, `--note`, параметри вводу, `--json` | +| `dw schedule:list` | Показати schedule. | | глобальні параметри виводу | +| `dw schedule:describe ` | Описати один schedule. | | `--json` | +| `dw schedule:update ` | Оновити специфікацію schedule чи ввід workflow. | | параметри створення schedule, параметри вводу, `--json` | +| `dw schedule:pause ` | Призупинити schedule. | | `--note`, `--json` | +| `dw schedule:resume ` | Відновити schedule. | | `--note`, `--json` | +| `dw schedule:trigger ` | Негайно запустити schedule. | | `--overlap-policy`, `--json` | +| `dw schedule:backfill ` | Виконати backfill часового вікна. | | `--start-time`, `--end-time`, `--overlap-policy`, `--json` | +| `dw schedule:delete ` | Видалити schedule. | | `--json` | + +Під час створення schedule використовуйте `--cron` або `--interval`. +`--paused` дозволяє зареєструвати schedule під час розгортання без запуску роботи. + +## Команди worker і черг завдань {#worker-and-task-queue-commands} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw worker:register [worker-id]` | Зареєструвати worker із місткістю й метаданими сумісності. | | `--task-queue`, `--runtime`, `--sdk-version`, `--build-id`, `--workflow-type`, `--activity-type`, `--max-workflow-tasks`, `--max-activity-tasks`, `--json` | +| `dw worker:list` | Показати worker. | | `--task-queue`, `--status`, глобальні параметри виводу | +| `dw worker:describe ` | Описати одного worker. | | `--json` | +| `dw worker:deregister ` | Скасувати реєстрацію worker. | | `--json` | +| `dw task-queue:list` | Показати активні черги завдань і стан допуску. | | глобальні параметри виводу | +| `dw task-queue:describe ` | Описати місткість worker, lease, бюджети доставлення й місткість очікуваних завдань query. | | `--json` | +| `dw task-queue:build-ids ` | Перевірити стан груп за build-id і розгортання однієї черги. | | `--json` | +| `dw task-queue:drain ` | Позначити групу build-id як draining, щоб вона перестала отримувати нові завдання. | | `--build-id `, `--unversioned`, `--json` | +| `dw task-queue:resume ` | Зняти попередній drain, щоб група знову отримувала нові завдання. | | `--build-id `, `--unversioned`, `--json` | + +Команди черг завдань є рекомендованим операторським оглядом обмеження потоку, +місткості та відсутності worker. Серверну політику цих полів описано в +[допуску черг завдань](/docs/polyglot/task-queue-admission), а повний перехід +від неверсіонованих до версіонованих worker, canary, drain і відкат описано в +[розгортанні build-id worker](/docs/polyglot/worker-build-id-rollout). + +`dw task-queue:drain` і `dw task-queue:resume` потребують або +`--build-id ` для певної групи збірки, або `--unversioned` для worker, +зареєстрованих без `build_id`. Поєднання цих параметрів одразу спричиняє +помилку параметрів. Обидві команди ідемпотентні: повторний drain не зсуває +записаний час `drained_at`, а resume вже активної групи не змінює стану. + +## Команди протоколу worker {#worker-protocol-commands} + +Ці команди є низькорівневими інструментами протоколу для діагностики, +коротких перевірок і експериментів із worker без SDK. Звичайні worker PHP +і Python мають використовувати цикли своїх SDK. + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw workflow-task:poll ` | Опитати одне завдання workflow. | | `--task-queue`, `--build-id`, `--poll-request-id`, `--history-page-size`, `--accept-history-encoding`, `--json` | +| `dw workflow-task:history ` | Отримати наступну сторінку історії орендованого завдання workflow. | | `--lease-owner`, `--attempt`, `--json` | +| `dw workflow-task:complete ` | Завершити одне завдання workflow з payload команд. | | `--lease-owner`, `--complete-result`, `--command`, `--json` | +| `dw workflow-task:fail ` | Повідомити про помилку виконання завдання workflow для повторної спроби чи діагностики. Відрізняється від завершення завдання командою `fail_workflow`. | | `--lease-owner`, `--message`, `--type`, `--stack-trace`, `--json` | +| `dw query-task:poll ` | Опитати й орендувати одне маршрутизоване завдання query workflow. | | `--task-queue`, `--json` | +| `dw query-task:complete ` | Завершити орендоване завдання query результатом JSON і відповідною обгорткою. | | `--lease-owner`, `--result`, `--json` | +| `dw query-task:fail ` | Повідомити про помилку орендованого завдання query з машиночитаною причиною. | | `--lease-owner`, `--message`, `--reason`, `--type`, `--stack-trace`, `--json` | +| `dw activity:complete ` | Завершити одну орендовану спробу activity. | | `--lease-owner`, параметри вводу, `--json` | +| `dw activity:fail ` | Завершити одну орендовану спробу activity помилкою. | | `--lease-owner`, `--message`, `--type`, `--non-retryable`, `--json` | + +Використовуйте `workflow-task:fail` для помилок виконання на боці worker, +наприклад розбіжностей replay чи помилок десеріалізації. Команда звертається до +`POST /worker/workflow-tasks/{taskId}/fail`. Завершення завдання командою, +що завершує workflow помилкою, є окремою дією через `workflow-task:complete` +з відповідним payload команди. + +`query-task:poll`, `query-task:complete` і `query-task:fail` працюють із +маршрутизованими query worker через `/worker/query-tasks/…`. Звичайні worker +отримують завдання query через обробник query SDK. CLI призначений для +діагностики, перевірок паритету CLI/SDK та експериментів без SDK. +Типова причина помилки query: `--reason=query_rejected`. Використовуйте сталу +причину, наприклад `unknown_query`, `decode_failure` чи ідентифікатор runtime, +щоб викликаючий код розрізняв очікувані відхилення та помилки runtime. + +### Сторінки історії завдання workflow {#workflow-task-history-pages} + +`workflow-task:history` є діагностичною обгорткою CLI для endpoint сторінок +історії worker. Використовуйте її лише після отримання орендованого завдання +workflow з `next_history_page_token` через `workflow-task:poll`. +Звичайні worker мають дозволяти SDK завантажувати додаткові сторінки історії. + + +```bash +dw workflow-task:history workflow-task-01 history-page-2 \ + --lease-owner=python-worker-1 \ + --attempt=2 \ + --json +``` + +JSON повертає відповідь сервера без перейменування полів: + + +```json +{ + "history_events": [ + {"event_id": 2, "event_type": "ActivityScheduled", "payload": {}} + ], + "total_history_events": 4, + "next_history_page_token": "history-page-3" +} +``` + +Автоматизація має читати `history_events`, `total_history_events` і +`next_history_page_token`. Endpoint історії worker не використовує поля +`events` чи `next_page_token` історії виконання площини керування. + +## Команди namespace й атрибутів пошуку {#namespace-and-search-attribute-commands} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw namespace:list` | Показати namespace. | | глобальні параметри виводу | +| `dw namespace:create ` | Створити namespace. | | `--description`, `--retention`, `--json` | +| `dw namespace:describe ` | Описати один namespace. | | `--json` | +| `dw namespace:update ` | Оновити метадані namespace. | | `--description`, `--retention`, `--json` | +| `dw namespace:set-storage-driver ` | Налаштувати політику зовнішнього сховища payload namespace для закодованих payload, що перевищують поріг винесення. | | `--threshold-bytes`, `--uri`, `--disk`, `--bucket`, `--prefix`, `--region`, `--endpoint`, `--auth-profile`, `--disable`, `--json` | +| `dw storage:test` | Перевірити запис і читання малого вбудованого й великого винесеного payload через вибрану політику сховища namespace чи перевизначений драйвер. | | `--driver=local|s3|gcs|azure`, `--small-bytes`, `--large-bytes`, глобальні параметри, `--json` | +| `dw search-attribute:list` | Показати атрибути пошуку. | | глобальні параметри виводу | +| `dw search-attribute:create ` | Зареєструвати атрибут пошуку. | | `--json` | +| `dw search-attribute:delete ` | Видалити атрибут пошуку. | | `--json` | + +Типи атрибутів пошуку є сумісними із сервером значеннями: `keyword`, `text`, +`int`, `double`, `bool`, `datetime` і `keyword_list`. + +Команди зовнішнього сховища payload викликають API сховища namespace сервера. +Драйвер має бути `local`, `s3`, `gcs` чи `azure`. Драйвери об'єктних сховищ +використовують серверну конфігурацію файлової системи, тому параметри CLI +описують політику namespace й не містять облікових даних провайдера. +`--disk` прив'язує драйвер `s3`, `gcs` чи `azure` до іменованого диска серверної +файлової системи з фактичними обліковими даними. `--disable` зберігає запис +політики, але забороняє нове винесення payload. + +Приклади: + +```bash +dw namespace:set-storage-driver billing s3 \ + --disk=external-payload-objects \ + --bucket=dw-payloads \ + --prefix=billing/ \ + --threshold-bytes=2097152 \ + --json + +dw namespace:set-storage-driver dev local \ + --uri=file:///var/lib/durable-workflow/payloads + +dw storage:test --namespace=billing --large-bytes=2097152 --json +dw storage:test --driver=s3 --small-bytes=128 --large-bytes=3145728 --json +``` + +У режимі JSON `namespace:set-storage-driver` повертає payload namespace із +політикою `external_payload_storage`. `storage:test` повертає діагностичний +статус і об'єкти результатів `small_payload` та `large_payload`. +Автоматизація має використовувати ці поля замість розбору таблиці для людини. + +Ця діагностика також є найшвидшою операторською перевіркою контракту посилань +на payload. Успішна перевірка великого payload доводить, що вибрана політика +може записати закодовані байти, повернути посилання +`durable-workflow.v2.external-payload-reference.v1`, прочитати об'єкт і +перевірити `size_bytes` та `sha256`. Діагностичну помилку розглядайте як +проблему політики сховища до дозволу workflow виносити payload через цей namespace. + +## Системні команди {#system-commands} + +Системні команди надають явні операції обслуговування сервера для скриптів. + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw system:repair-status` | Показати backlog і стан відновлення workflow. | | `--json` | +| `dw system:repair-pass` | Виконати один прохід відновлення. | | `--run-id`, `--limit`, `--json` | +| `dw system:activity-timeout-status` | Показати backlog і стан тайм-аутів activity. | | `--json` | +| `dw system:activity-timeout-pass` | Виконати один прохід тайм-аутів activity. | | `--task-id`, `--limit`, `--json` | +| `dw system:retention-status` | Показати backlog і стан retention. | | `--json` | +| `dw system:retention-pass` | Виконати один прохід очищення retention. | | `--run-id`, `--limit`, `--json` | + +В інструкціях операцій спочатку використовуйте команди status, а потім pass, +щоб оператори бачили обсяг очікуваної роботи до зміни стану сервера. + +## Команди схем {#schema-commands} + +| Команда | Призначення | Важливі параметри | +| --- | --- | --- | +| `dw schema:list` | Показати опубліковані машиночитані схеми. | | з'єднання із сервером не потрібне | +| `dw schema:show ` | Показати вбудовану JSON Schema виводу однієї команди. | | `--output=json|jsonl`; з'єднання із сервером не потрібне | +| `dw schema:manifest` | Показати маніфест схем. | | з'єднання із сервером не потрібне | + +Команди схем корисні, якщо AI клієнту чи завданню CI потрібен поточний контракт +площини керування, відповіді чи виводу без розбору документації. +[Поточний маніфест v4](https://durable-workflow.github.io/cli-json-envelopes/v4/manifest.json) +прив'язує кожну JSON обгортку та схему записів JSONL через публічний resolver +і digest. Поля payload запуску/виконання workflow та query/update приймають +лише кодек `avro`. Збережені +[маніфест v3](https://durable-workflow.github.io/cli-json-envelopes/v3/manifest.json) +і [маніфест v2](https://durable-workflow.github.io/cli-json-envelopes/v2/manifest.json) +залишаються доступними з початковими байтами для користувачів, що фіксують ревізію. + +## Контракт виводу й завершення {#output-and-exit-contract} + +Використовуйте `--output=json` для одного об'єкта та `--output=jsonl`, якщо +список передається потоковому процесору. Таблиці для людини можуть +удосконалюватися, а назви полів JSON є контрактом автоматизації. + +Усі команди використовують стабільну політику кодів завершення з +[посібника CLI](/docs/polyglot/cli#exit-codes): + +| Код | Значення | +| --- | --- | +| `0` | Успіх. | +| `1` | Команда виконалася, але завершилася загальною помилкою. | +| `2` | Неправильне локальне використання чи помилка перевірки. | +| `3` | Помилка мережі чи транспорту. | +| `4` | Помилка автентифікації чи авторизації. | +| `5` | Ресурс не знайдено. | +| `6` | Серверна помилка `5xx`. | +| `7` | Тайм-аут. | + +Для підтримки зберіть `dw doctor --output=json`, `dw server:info +--output=json` і `dw debug workflow --output=json`. + +## Пов'язані посібники {#related-guides} + +- [CLI](./cli.mdx): встановлення, профілі та коди завершення. +- [Server](./server.md): площина керування HTTP для серверних команд. +- [Можливості клієнтів і worker](./cli-python-parity.md): порівняння + клієнтів і worker CLI, PHP, Python та Rust. diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md new file mode 100644 index 0000000000..a40da4e58d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -0,0 +1,1669 @@ +--- +sidebar_position: 4 +tags: + - Python + - SDK + - workers + - polyglot +keywords: + - sample-app-playground + - Python SDK + - durable_workflow Client + - worker Python + - асинхронний клієнт workflow +--- + +import ProductPromotion from '@site/src/components/ProductPromotion'; +import PythonPackageReleaseLink from '@site/src/components/PythonPackageReleaseLink'; + +# Python SDK {#python-sdk} + +Python SDK є легким клієнтом із пріоритетом асинхронної роботи для власного +Durable Workflow Server або runtime namespace Durable Workflow Cloud. +Він дозволяє процесам Python запускати, спостерігати, надсилати signal і +скасовувати workflow через control-plane API та реєструватися як worker +для виконання завдань workflow й activity. + +## Спробуйте локальний майданчик Sample App {#try-the-local-sample-app-playground} + +Для найкоротшого шляху розробки без Cloud відкрийте поточну +[гілку `main` Sample App у GitHub Codespaces](https://codespaces.new/durable-workflow/sample-app?quickstart=1&ref=main) +і виконайте: + + +```bash +scripts/playground python +``` + +Локальний майданчик генерує вихідний код workflow й activity, який належить +користувачу, вибирає поточні стабільні артефакти та запускає опубліковані +Server і Waterline. Перед запуском workflow чекає реєстрації worker з +ідентичністю, типами workflow/activity й чергою, відповідними згенерованому +контракту. Успіх потребує очікуваних завершеного результату й історії. +Термінал показує точне локальне посилання виконання Waterline та шлях +до структурованих доказів JSON. + +Встановлення пакета, quickstart, довідник API й приклад репозиторію нижче +залишаються прямими шляхами для користувачів без Sample App. + + +Запускайте клієнти й worker Python проти керованого Cloud namespace +з окремими обліковими даними ролей. + + +SDK використовує ту саму надійну модель, що й пакет PHP: спільні ID +екземплярів і виконань, події історії, черги й ключі типів. +Worker Python може виконувати activity для workflow PHP і навпаки. + +Порівняння Python SDK, PHP SDK, Rust SDK і `dw` наведено в +[можливостях клієнтів і worker](/docs/polyglot/cli-python-parity/). + +Сигнатури конструкторів, типи результатів, класи винятків і назви метрик є +в згенерованому [довіднику API Python SDK](https://python.durable-workflow.com/). + +Клієнти Cloud використовують надані URL runtime і namespace з окремими +обліковими даними клієнта й worker. Цю межу підключення описано в +[керованому runtime Cloud](/docs/polyglot/cloud-control-plane). +Quickstart нижче використовує локальні значення самостійного Server. + +## Вимоги {#requirements} + +- Python 3.10 чи новіший +- Docker для локального Server цього quickstart, наявний + [самостійний Server](/docs/polyglot/server) або наданий + [runtime namespace Cloud](/docs/polyglot/cloud-control-plane) + +## Встановлення {#installation} + +Встановіть стабільний реліз +Python SDK. Точна вимога генерується з того +самого кортежу, що й quickstart Server. Файл lock чи constraints +може зберегти її для відтворюваності. + + +```bash +pip install %%artifact.pythonPackagePin%% +``` + +SDK залежить від [httpx](https://www.python-httpx.org/) для HTTP та пакета +`fastavro` для кодування Apache Avro єдиного публічного кодека payload v2. +Підтримка метрик Prometheus необов'язкова. + +## Quickstart {#quickstart} + +Ця повна програма Python визначає workflow з однією activity, +запускає його на локальному Durable Workflow Server і чекає результату: + + +```python +import asyncio +import uuid + +from durable_workflow import Client, Worker, workflow, activity + +@activity.defn(name="greet") +async def greet(name: str) -> dict: + return {"greeting": f"Hello, {name}!", "length": len(name)} + +@workflow.defn(name="greeter") +class GreeterWorkflow: + def run(self, ctx, *args): + result = yield ctx.schedule_activity("greet", list(args)) + return result + +async def main(): + workflow_id = f"greeting-{uuid.uuid4().hex}" + + async with Client("http://localhost:8080", token="dev-token", namespace="default") as client: + handle = await client.start_workflow( + workflow_type="greeter", + task_queue="default", + workflow_id=workflow_id, + input=["world"], + ) + + worker = Worker( + client, + task_queue="default", + workflows=[GreeterWorkflow], + activities=[greet], + ) + + await worker.run_until(workflow_id=workflow_id, timeout=30.0) + result = await handle.result(timeout=10.0) + + print(result) # {"greeting": "Hello, world!", "length": 5} + +asyncio.run(main()) +``` + +### Запуск із локальним Server {#running-against-a-local-server} + +Програма вище очікує Server на `http://localhost:8080` з локальним +`dev-token`. Найшвидший запуск без вихідного коду: опублікований образ Server: + +```bash +export DW_SERVER_IMAGE=%%artifact.serverDockerHubImage%% +export DW_AUTH_TOKEN=dev-token + +docker volume create durable-workflow-python-quickstart + +docker run --rm \ + -v durable-workflow-python-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" server-bootstrap + +docker rm -f durable-workflow-server >/dev/null 2>&1 || true +docker run -d --name durable-workflow-server \ + -p 8080:8080 \ + -v durable-workflow-python-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$DW_SERVER_IMAGE" + +until curl -sf http://localhost:8080/api/ready > /dev/null; do sleep 1; done +``` + +Production розгортання, драйвери автентифікації, базу й TLS описано в +[посібнику налаштування Server](/docs/polyglot/server). + +Більший приклад: [`examples/order_processing`](https://github.com/durable-workflow/sdk-python/tree/main/examples/order_processing) +у репозиторії SDK. Стек Docker Compose запускає worker Python і виконує +workflow замовлення наскрізно. + +## Довідник API клієнта {#client-api-reference} + +Асинхронний `durable_workflow.Client` є публічною точкою входу до HTTP +control-plane і worker. Використовуйте його як async context manager, +щоб пул з'єднань `httpx.AsyncClient` коректно закривався. + +```python +from durable_workflow import Client + +async with Client( + "https://workflow.example.com", + token="shared-token", + namespace="default", + timeout=60.0, +) as client: + info = await client.get_cluster_info() +``` + +### Конструктор {#constructor} + +| Аргумент | Тип | Типове значення | Призначення | +| --- | --- | --- | --- | +| `base_url` | `str` | обов'язковий | Origin Server без `/api`. | +| `token` | `str | None` | `None` | Один bearer token для control-plane і worker. | +| `control_token` | `str | None` | `None` | Окремий bearer token control-plane від poll worker. | +| `worker_token` | `str | None` | `None` | Окремий bearer token worker від операторських викликів. | +| `namespace` | `str` | `"default"` | Цільовий namespace Server через `X-Namespace`. | +| `timeout` | `float` | `60.0` | Перевизначення типового HTTP тайм-ауту. | +| `retry_policy` | `TransportRetryPolicy | None` | типова політика | Налаштування retry тимчасових помилок HTTP. | +| `metrics` | `MetricsRecorder | None` | no-op | Метрики клієнта й worker через власний recorder. | +| `payload_size_limit_bytes` | `int` | типове SDK | Відповідність максимальному розміру payload Server. | +| `payload_size_warning_threshold_percent` | `int` | типове SDK | Попередження до досягнення межі payload. | +| `payload_size_warnings` | `bool` | `True` | Вимкнення локальних попереджень розміру в тестах чи контрольованих скриптах. | + +`token` є найпростішим варіантом. Якщо задані `control_token` і +`worker_token`, методи control-plane використовують перший, а worker другий. + +### Зовнішнє сховище payload {#external-payload-storage} + +SDK експортує той самий контракт посилання на зовнішній payload, що й +Server та API сховища CLI. Використовуйте його, якщо обробники activity +Python чи носії invocable мають декодувати великі посилання з історії, +або процес Python створює незалежну від мови обгортку без великих +вбудованих байтів. + +| API | Роль | Помилки | +| --- | --- | --- | +| `ExternalStorageDriver` | Протокол `put(data, sha256=..., codec=...)`, `get(uri)`, `delete(uri)`. | Помилки сховища драйвера. | +| `LocalFilesystemExternalStorage` | Драйвер `file://` без залежностей для локальної розробки й тестів. | `ValueError` за URI поза налаштованим коренем. | +| `S3ExternalStorage` | Адаптер клієнта, сумісного з boto3. | `ValueError` за сторонній bucket/prefix чи небайтову відповідь. | +| `GCSExternalStorage` | Адаптер клієнта стилю google-cloud-storage. | `ValueError` за сторонній bucket/prefix чи небайтову відповідь. | +| `AzureBlobExternalStorage` | Адаптер клієнта контейнера Azure. | `ValueError` за сторонній container/prefix чи небайтову відповідь. | +| `ExternalPayloadReference` | Незмінне посилання з `uri`, `sha256`, `size_bytes`, `codec` і схемою. | `ValueError`, якщо `from_dict()` отримує непідтримувану схему чи неправильні поля. | +| `ExternalPayloadCache` | Обмежений кеш replay вже перевірених зовнішніх байтів. | Конструктор відхиляє недодатні межі кількості записів чи байтів. | +| `store_external_payload()` | Зберігає закодовані байти драйвером і повертає `ExternalPayloadReference`. | Помилки драйвера. | +| `fetch_external_payload()` | Отримує байти й перевіряє розмір та SHA-256 до декодування. | `ExternalPayloadIntegrityError` за невідповідності розміру/hash. | +| `delete_external_payload()` | Видаляє об'єкт і запис кешу. | Помилки сховища драйвера. | +| `external_storage_envelope()` | Кодує значення всередині до перевищення порогу, потім пише драйвером. | `ValueError` за неправильного порогу чи відсутнього драйвера посилання. | +| `external_storage_driver_from_policy()` | Створює драйвер політики Server чи Cloud `external_payload_storage` із клієнтами провайдерів застосунку. | `ValueError` за вимкненої/непідтримуваної політики чи відсутніх client/bucket/container. | + +```python +from durable_workflow import ( + ExternalPayloadCache, + LocalFilesystemExternalStorage, + external_storage_envelope, + to_avro_payload_value, +) +from durable_workflow.serializer import encode +from durable_workflow.external_storage import fetch_external_payload, store_external_payload + +storage = LocalFilesystemExternalStorage("/var/lib/durable-workflow/payloads") +cache = ExternalPayloadCache(max_entries=256, max_bytes=32 * 1024 * 1024) + +payload = to_avro_payload_value({"invoice_pdf": "x" * 1_000_000}) +envelope = external_storage_envelope( + payload, + external_storage=storage, + threshold_bytes=64 * 1024, +) + +reference = store_external_payload( + storage, + encode({"archived": True}).encode("utf-8"), + codec="avro", +) +payload_bytes = fetch_external_payload(storage, reference, cache=cache) +``` + +Схема посилання: `EXTERNAL_PAYLOAD_REFERENCE_SCHEMA` +(`durable-workflow.v2.external-payload-reference.v1`). Адаптери об'єктних +сховищ не додають залежностей cloud SDK до `durable-workflow`: +застосунок передає вже налаштовані клієнти S3, GCS чи Azure. + +Поля мережевої обгортки навмисно невеликі й стабільні: + +```json +{ + "schema": "durable-workflow.v2.external-payload-reference.v1", + "uri": "s3://dw-payloads/billing/run-001/input.avro", + "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "size_bytes": 1048576, + "codec": "avro", + "expires_at": "2026-05-22T00:00:00Z" +} +``` + +`ExternalPayloadReference.from_dict()` відхиляє невідомі схеми й неправильні +поля. `fetch_external_payload()` перевіряє `size_bytes` і `sha256` до +повернення байтів та піднімає `ExternalPayloadIntegrityError` за відсутнього, +обрізаного чи зміненого об'єкта. Replay worker має використовувати +`ExternalPayloadCache` лише після перевірки, щоб уникати повторного +завантаження blob без послаблення цілісності. + +Після читання політики `external_payload_storage` namespace з control-plane +Server чи Cloud функція `external_storage_driver_from_policy()` повертає +відповідний драйвер. Клієнти провайдерів належать застосунку, тому SDK +не додає boto3, google-cloud-storage чи azure-storage-blob як залежності runtime: + +```python +from durable_workflow.external_storage import external_storage_driver_from_policy + +namespace = await client.describe_namespace("billing") +driver = external_storage_driver_from_policy( + namespace.external_payload_storage, + s3_client=application_owned_s3_client, +) +``` + +Фабрика піднімає `ValueError` за вимкненої політики, непідтримуваного +драйвера чи відсутнього клієнта провайдера. Вимкнена політика не повертає +no-op драйвера. Перевіряйте `policy.enabled` перед запитом драйвера. + +### Namespace {#namespaces} + +Namespace є межею орендарів workflow, schedule, атрибутів пошуку й +зовнішнього сховища payload. Client вибирає один namespace через аргумент +конструктора `namespace=`, але операторська поверхня нижче діє для +кожного namespace, дозволеного bearer token. + +| Метод | Результат | Помилки | +| --- | --- | --- | +| `await client.list_namespaces()` | `NamespaceList` | Помилки автентифікації/Server. | +| `await client.describe_namespace(name)` | `NamespaceDescription` | `NamespaceNotFound`, помилки автентифікації/Server. | +| `await client.create_namespace(name, description=None, retention_days=30)` | `NamespaceDescription` | `InvalidArgument` за дубліката назви чи неправильного retention, помилки автентифікації/Server. | +| `await client.update_namespace(name, description=None, retention_days=None)` | `NamespaceDescription` | `NamespaceNotFound`, `InvalidArgument`, помилки автентифікації/Server. Надсилаються лише задані поля. | +| `await client.set_namespace_external_storage(name, driver=..., enabled=True, threshold_bytes=None, config=None)` | `NamespaceDescription` | `InvalidArgument` за відхилення політики перевіркою Server, помилки автентифікації/Server. | +| `await client.test_external_storage(driver=None, small_payload_bytes=None, large_payload_bytes=None)` | `StorageTestResult` | `InvalidArgument` за відсутніх потрібних полів політики, помилки автентифікації/Server. | + +`set_namespace_external_storage` відповідає `dw namespace:set-storage-driver`. +Перший позиційний аргумент є `name` namespace, як у `describe_namespace`, +`create_namespace`, `update_namespace`. Застарілий псевдонім `namespace=` +дає `DeprecationWarning`. Новий код має використовувати позиційний `name`. +Словник `config` містить ключі драйвера, включно з необов'язковим `disk` +для `s3`, `gcs`, `azure`, щоб облікові дані залишалися на сервері. +`NamespaceDescription` відображає фактично збережену політику, включно +з типовим порогом сервера, якщо викликач не задав значення. + +`test_external_storage` відповідає `dw storage:test`. Server перевіряє +запис і читання малого й великого payload через прив'язаний драйвер +та повертає `StorageTestResult` із записами `StoragePayloadTestResult` +і полями `wrote`, `read`, `verified`, `latency_ms`, `bytes`. + +`NamespaceDescription` містить політику `external_payload_storage`, +описану в [зовнішньому сховищі payload](#external-payload-storage). +Поєднуйте його з `external_storage_driver_from_policy()` для створення +драйвера Python без повторного читання облікових даних кодом застосунку. + +### Кластер і черги завдань {#cluster-and-task-queues} + +| Метод | Результат | Примітки | +| --- | --- | --- | +| `await client.health()` | `dict[str, Any]` | Викликає endpoint здоров'я для перевірки життя. | +| `await client.get_cluster_info()` | `dict[str, Any]` | Читає версію Server, протокол, можливості й метадані сумісності. | +| `await client.list_task_queues()` | `TaskQueueList` | Показує видимі черги namespace. | +| `await client.describe_task_queue(name)` | `TaskQueueDescription` | Повертає місткість worker, lease, допуск query та бюджет dispatch. | +| `await client.list_task_queue_build_ids(task_queue)` | `TaskQueueBuildIdRollout` | Знімок груп build-id черги, включно з неверсіонованими worker групи `build_id=None`. | +| `await client.drain_task_queue_build_id(task_queue, build_id)` | `TaskQueueBuildIdRolloutState` | Позначає групу build-id як draining без нових завдань. `build_id=None` виконує drain неверсіонованих worker. Ідемпотентний. | +| `await client.resume_task_queue_build_id(task_queue, build_id)` | `TaskQueueBuildIdRolloutState` | Знімає drain для відновлення роботи групи. `build_id=None` відновлює неверсіонованих worker. Ідемпотентний. | + +Результати черг надають вкладені dataclass `TaskQueueAdmission`, +`TaskQueueTaskAdmission`, `TaskQueueQueryAdmission`, `TaskQueueBuildIdCohort`, +`TaskQueueBuildIdRollout`, `TaskQueueBuildIdRolloutState` для перевірки +місткості Server і розгортання build-id без розбору тексту. +[Розгортання build-id worker](/docs/polyglot/worker-build-id-rollout) +описує наскрізний процес. Відповідні CLI команди: +`dw task-queue:build-ids`, `dw task-queue:drain`, `dw task-queue:resume`. + +### Операції workflow {#workflow-operations} + +| Метод | Результат | Помилки | +| --- | --- | --- | +| `await client.start_workflow(...)` | `WorkflowHandle` | `WorkflowAlreadyStarted`, `InvalidArgument`, `Unauthorized`, `ServerError` | +| `await client.describe_workflow(workflow_id)` | `WorkflowExecution` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.list_workflows(...)` | `WorkflowList` | Помилки автентифікації/Server | +| `await client.list_workflow_runs(workflow_id)` | `WorkflowRunList` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.describe_workflow_run(workflow_id, run_id)` | `WorkflowRun` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.get_history(workflow_id, run_id)` | декодований payload історії | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.export_history(workflow_id, run_id)` | декодований архівний payload історії | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.signal_workflow(workflow_id, signal_name, args=None)` | `None` | `WorkflowNotFound`, `InvalidArgument`, помилки автентифікації/Server | +| `await client.query_workflow(workflow_id, query_name, args=None)` | декодований результат query | `QueryFailed`, `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.update_workflow(workflow_id, update_name, args=None, ...)` | декодований результат update | `UpdateRejected`, `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.cancel_workflow(workflow_id, reason=None)` | `None` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.terminate_workflow(workflow_id, reason=None)` | `None` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.repair_workflow(workflow_id)` | `WorkflowCommandResult` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.archive_workflow(workflow_id, reason=None)` | `WorkflowCommandResult` | `WorkflowNotFound`, помилки автентифікації/Server | +| `await client.get_result(handle, poll_interval=0.5, timeout=30.0)` | декодований результат workflow | `WorkflowFailed`, `WorkflowCancelled`, `WorkflowTerminated`, `TimeoutError` | + +`start_workflow` приймає `workflow_type`, `task_queue`, необов'язкові +`workflow_id`, `input`, `duplicate_policy`, `memo`, `search_attributes`, +`business_key`, `execution_timeout_seconds`, `run_timeout_seconds`. +Усі дані викликача отримують Avro обгортку до перетину HTTP межі. + +Використовуйте `client.get_workflow_handle(workflow_id, run_id=None, workflow_type="")`, +якщо скрипт уже знає ID workflow та потребує методів handle. + +| Метод `WorkflowHandle` | Еквівалентний метод клієнта | +| --- | --- | +| `await handle.result(...)` | `client.get_result(handle, ...)` | +| `await handle.describe()` | `client.describe_workflow(handle.workflow_id)` | +| `await handle.signal(name, args=None)` | `client.signal_workflow(...)` | +| `await handle.query(name, args=None)` | `client.query_workflow(...)` | +| `await handle.update(name, args=None, ...)` | `client.update_workflow(...)` | +| `await handle.cancel(reason=None)` | `client.cancel_workflow(...)` | +| `await handle.terminate(reason=None)` | `client.terminate_workflow(...)` | + +Обидві операції негайно закривають виконання Server з різними термінальними +результатами. `cancel_workflow` не доставляє кооперативного запиту до коду +workflow й не запускає очищення saga/finally. Для обмеженого очищення +використовуйте `request_cancellation()` із кооперативними worker. +[Посібник кооперативного скасування](/docs/polyglot/cancellation) описує +політики операцій, нагляд і відновлення. + +### Schedule {#schedules} + +Schedule використовують `ScheduleSpec` для календаря/інтервалів і +`ScheduleAction` для запиту запуску workflow за спрацювання. + +| Метод | Результат | Примітки | +| --- | --- | --- | +| `await client.create_schedule(...)` | `ScheduleHandle` | Створює schedule і повертає handle. | +| `await client.list_schedules()` | `ScheduleList` | Показує видимі schedule. | +| `await client.describe_schedule(schedule_id)` | `ScheduleDescription` | Читає стан, дію, наступне спрацювання й лічильники. | +| `await client.update_schedule(schedule_id, ...)` | `None` | Оновлює специфікацію, дію, overlap, jitter, memo, атрибути пошуку чи примітку. | +| `await client.pause_schedule(schedule_id, note=None)` | `None` | Призупиняє майбутні запуски. | +| `await client.resume_schedule(schedule_id, note=None)` | `None` | Відновлює призупинений schedule. | +| `await client.trigger_schedule(schedule_id, overlap_policy=None)` | `ScheduleTriggerResult` | Просить негайного запуску. | +| `await client.backfill_schedule(schedule_id, start_time=..., end_time=..., overlap_policy=None)` | `ScheduleBackfillResult` | Повторює пропущені вікна запуску. | +| `await client.get_schedule_history(schedule_id, *, limit=None, after_sequence=None)` | `ScheduleHistoryPage` | Повертає сторінку історії аудиту schedule за зростанням `sequence`. Server обмежує `limit` між 1 і 500 (типово 100). `after_sequence` є невід'ємним курсором попереднього `next_cursor`. Піднімає `ScheduleNotFound` за невідомого ID. Історія зберігається після `delete_schedule` для аудиту причини вилучення. | +| `client.iter_schedule_history(schedule_id, *, limit=None, after_sequence=None)` | `AsyncIterator[ScheduleHistoryEvent]` | Видає всі події аудиту, читаючи сторінки до `has_more=False`. Помилки як у `get_schedule_history`. | +| `await client.delete_schedule(schedule_id)` | `None` | Видаляє schedule. | + +`client.get_schedule_handle(schedule_id)` повертає `ScheduleHandle` із +методами `describe`, `update`, `pause`, `resume`, `trigger`, `backfill`, +`history`, `iter_history`, `delete`, що викликають відповідні методи клієнта. +`ScheduleHandle.history(...)` повертає `ScheduleHistoryPage`, а +`ScheduleHandle.iter_history(...)` повертає `AsyncIterator[ScheduleHistoryEvent]`. + +`ScheduleHistoryPage` містить упорядковані `events`, прапорець `has_more`, +цілий `next_cursor` (чи `None` на останній сторінці), `schedule_id` та +власника `namespace`. `ScheduleHistoryEvent` містить `sequence`, +`event_type` (`ScheduleCreated`, `SchedulePaused`, `ScheduleResumed`, +`ScheduleUpdated`, `ScheduleTriggered`, `ScheduleTriggerSkipped`, `ScheduleDeleted`), +`recorded_at`, необов'язкові `workflow_instance_id`, `workflow_run_id` +запущених workflow та сирий словник `payload` переходу control-plane. + +### Події bridge й методи площини worker {#bridge-events-and-worker-plane-methods} + +`await client.send_webhook_bridge_event(adapter, action=..., target=..., input=..., idempotency_key=..., correlation=None)` +повертає `BridgeAdapterOutcome` за тим самим машиночитаним контрактом, +що й `dw bridge:webhook`. Це точка входу Python для обмеженого +приймання даних із систем формату webhook. + +Низькорівневі методи worker публічні для власних worker і тестів протоколу, +але звичайні застосунки мають використовувати `Worker`: + +| Група методів | Методи | +| --- | --- | +| Реєстрація worker | `register_worker` | +| Завдання workflow | `poll_workflow_task`, `complete_workflow_task`, `fail_workflow_task`, `workflow_task_history` | +| Завдання query | `poll_query_task`, `complete_query_task`, `fail_query_task` | +| Завдання activity | `poll_activity_task`, `complete_activity_task`, `fail_activity_task`, `heartbeat_activity_task` | + +Ці методи надсилають `X-Durable-Workflow-Protocol-Version` і використовують +`worker_token`, якщо він заданий. Надавайте перевагу `Worker`, якщо +не пишете адаптер SDK чи тест відповідності протоколу. + +`workflow_task_history(...)` читає історію replay сторінками для вже +орендованого завдання. Викликайте лише після повернення +`next_history_page_token` методом `poll_workflow_task(...)`: + +```python +page = await client.workflow_task_history( + task_id="workflow-task-01", + next_history_page_token="history-page-2", + lease_owner="python-worker-1", + workflow_task_attempt=2, +) +``` + +Тіло запиту містить `next_history_page_token`, `lease_owner`, +`workflow_task_attempt`. Декодована відповідь містить поля протоколу worker +`history_events`, `total_history_events`, `next_history_page_token`, +а не поля історії control-plane `events` чи `next_page_token`. + +### Worker {#workers} + +Реєстр worker показує нещодавно видимих Server worker та їхні можливості. +Використовуйте його для розгортання build-id, вилучення застарілих worker +і узгодження місткості парку скриптами оператора. + +| Метод | Результат | Помилки | +| --- | --- | --- | +| `await client.list_workers(task_queue=None, status=None)` | `WorkerList` | Помилки автентифікації/Server. | +| `await client.describe_worker(worker_id)` | `WorkerDescription` | `WorkerNotFound`, помилки автентифікації/Server. | +| `await client.deregister_worker(worker_id)` | `dict[str, Any]` | `WorkerNotFound`, помилки автентифікації/Server. | + +`list_workers` фільтрує на сервері: `task_queue` обмежує чергу, `status` +вибирає один підтримуваний статус. Типово повертає всіх зареєстрованих +worker namespace. + +`WorkerDescription` містить runtime, версію SDK, build ID, оголошені типи +workflow/activity, останній heartbeat і поточний допуск завдань для +вибору групи drain чи worker для вилучення. + +`deregister_worker` ідемпотентний на Server. Він вилучає worker без heartbeat +для чистого обліку місткості й `list_workers`, але не перериває активні lease. + +### Атрибути пошуку {#search-attributes} + +Атрибути пошуку є типізованими метаданими namespace на виконаннях workflow +для фільтрації й індексування. Python клієнт відображає ту саму +поверхню control-plane, що й CLI. + +| Метод | Результат | Помилки | +| --- | --- | --- | +| `await client.list_search_attributes()` | `SearchAttributeList` | Помилки автентифікації/Server. | +| `await client.create_search_attribute(name, attribute_type)` | `dict[str, Any]` | `InvalidArgument` за дублікатів чи непідтримуваного типу, помилки автентифікації/Server. | +| `await client.delete_search_attribute(name)` | `dict[str, Any]` | `SearchAttributeNotFound`, `InvalidArgument` за системного атрибута, помилки автентифікації/Server. | + +`SearchAttributeList` розділяє визначення `system` і `custom` для перевірки +наявності ключів рушія (ID workflow, статус, тип, початок тощо) до відмови +створення конфліктного власного ключа. `attribute_type` підтримує +`keyword`, `text`, `int`, `double`, `bool`, `datetime`, `keyword_list`. + +### Системне обслуговування {#system-maintenance} + +Операторські скрипти й чергова автоматизація керують через Client тими самими +циклами обслуговування, що й `dw system:*`. Кожен метод вимагає admin scope +bearer token, інакше Server повертає `Unauthorized`. + +| Метод | Результат | Відповідник CLI | +| --- | --- | --- | +| `await client.repair_status()` | `dict[str, Any]` | `dw system:repair-status` | +| `await client.repair_pass(run_ids=None, instance_id=None)` | `dict[str, Any]` | `dw system:repair-pass` | +| `await client.retention_status()` | `dict[str, Any]` | `dw system:retention-status` | +| `await client.retention_pass(run_ids=None, limit=None)` | `dict[str, Any]` | `dw system:retention-pass` | +| `await client.activity_timeout_status()` | `dict[str, Any]` | `dw system:activity-timeout-status` | +| `await client.activity_timeout_pass(execution_ids=None, limit=None)` | `dict[str, Any]` | `dw system:activity-timeout-pass` | + +`repair_pass` виконує один прохід виправлення завдань. Без фільтрів охоплює +namespace. `run_ids` обмежує список виконань, `instance_id` обмежує +один активний екземпляр. + +`retention_pass` застосовує вікно retention namespace до термінальних виконань. +Без фільтрів видаляє прострочені виконання до межі сканування. `run_ids` +обмежує прохід, `limit` кількість виконань. `retention_status()` показує +вікно retention, межу часу й придатні до видалення ID до межі сканування. + +`activity_timeout_pass` застосовує start-to-close і schedule-to-close +до activity з минулим дедлайном. Без фільтрів обробляє прострочені виконання +до межі сканування. `execution_ids` вибирає список, `limit` обмежує прохід. + +Ці методи не піднімають винятків за порожньої роботи. Повторні виклики +у тихий період повертають ті самі `passes` / `repaired` / `pruned` +з нульовими змінами, тому придатні для скриптів cron. + +## Визначення workflow {#defining-workflows} + +Workflow є класами Python з `@workflow.defn`. Метод `run` є генератором, +який передає команди Server через yield. + +```python +from durable_workflow import workflow + +@workflow.defn(name="order-processing") +class OrderWorkflow: + def run(self, ctx, *args): + order = args[0] if args else {} + + # Schedule an activity and wait for the result + validated = yield ctx.schedule_activity( + "validate_order", [order] + ) + + # Start a timer (durable sleep) + yield ctx.start_timer(seconds=60) + + # Schedule another activity + receipt = yield ctx.schedule_activity( + "process_payment", [validated] + ) + + return receipt +``` + +`name` у `@workflow.defn(name="...")` є ключем типу для всіх мов. +Це має бути звичайний рядок, а не шлях модуля Python чи посилання на клас. + +### Signal, query й update {#signals-queries-and-updates} + +Signal записуються в надійну історію й передаються обробникам Python під час replay: + +```python +@workflow.defn(name="approval") +class ApprovalWorkflow: + def __init__(self) -> None: + self.approved = False + self.approved_by = None + + @workflow.signal("approve") + def approve(self, by: str) -> None: + self.approved = True + self.approved_by = by + + def run(self, ctx, *args): + yield ctx.schedule_activity("wait_for_approval", []) + return {"approved": self.approved, "approved_by": self.approved_by} +``` + +Декоратори query й update дозволяють класам публікувати сталі назви обробників. +Worker Python виконують маршрутизовані Server query через replay зафіксованої +історії й виклик зареєстрованого обробника зі станом replay. Вони також +застосовують прийняті update, доставлені через завдання workflow: + +```python +@workflow.defn(name="approval") +class ApprovalWorkflow: + def __init__(self) -> None: + self.approved = False + + @workflow.query("status") + def status(self) -> dict: + return {"approved": self.approved} + + @workflow.update("set_approval") + def set_approval(self, approved: bool) -> dict: + self.approved = approved + return {"approved": self.approved} + + @set_approval.validator + def validate_set_approval(self, approved: bool) -> None: + if not isinstance(approved, bool): + raise ValueError("approved must be boolean") +``` + +Worker Python завершують маршрутизовані query поверненням результату завдання +Server. Для оголошеного валідатора update worker отримує окреме завдання +валідації, відновлює авторитетний стан і викликає лише валідатор. +Server не записує прийнятий update й не направляє його обробнику до успішної +валідації. Worker із валідатором відмовляється реєструватися без оголошеного +контракту синхронної перевірки до прийняття. Відмова, відсутні чи несумісні +worker, втрата worker, тайм-аут і відгороджені дублікати чи старі завершення +залишаються явними типізованими результатами. PHP і Rust зараз не надають +API авторства валідаторів update й оголошують цю відсутність у контрактах +workflow, замість заявляти паритет валідаторів. + +### Контекст workflow {#workflow-context} + +`WorkflowContext`, переданий до `run`, надає детерміновані операції: + +| Метод | Опис | +|--------|-------------| +| `ctx.schedule_activity(type, args)` | Планування activity з необов'язковими retry й тайм-аутами виклику | +| `ctx.start_timer(seconds)` | Надійний сон | +| `ctx.start_child_workflow(type, args)` | Запуск дочірнього workflow з необов'язковими retry й тайм-аутами | +| `yield [command, [...]]` | Очікування вкладеної детермінованої групи activity, дочірніх workflow, timer чи змішаної групи в порядку вводу | +| `yield ctx.select({key: command, ...})` | Продовження після першого надійно зафіксованого члена зі збереженням handle інших | +| `ctx.saga()` | Реєстрація й виконання надійних компенсацій activity у зворотному порядку | +| `ctx.side_effect(fn)` | Фіксація недетермінованого значення | +| `ctx.get_version(change_id, min, max)` | Безпечне версіонування коду workflow | +| `ctx.upsert_search_attributes(attrs)` | Оновлення атрибутів пошуку | +| `ctx.continue_as_new(*args)` | Перезапуск workflow з новим вводом | +| `ctx.now()` | Детермінований годинник з історії | +| `ctx.random()` | Генератор випадкових чисел зі сталим seed | +| `ctx.uuid4()` | Детермінований UUID | +| `ctx.logger` | Logger, що мовчить під час replay | + +### Повторні спроби й тайм-аути activity {#activity-retries-and-timeouts} + +Використовуйте `ActivityRetryPolicy` і параметри тайм-ауту +`ctx.schedule_activity(...)`, якщо виклик потребує іншого бюджету retry +чи дедлайну замість типової однієї спроби: + +```python +from durable_workflow import ActivityRetryPolicy + +receipt = yield ctx.schedule_activity( + "process_payment", + [validated], + retry_policy=ActivityRetryPolicy( + max_attempts=4, + initial_interval_seconds=1, + backoff_coefficient=2, + maximum_interval_seconds=30, + non_retryable_error_types=["ValidationError"], + ), + start_to_close_timeout=120, + schedule_to_close_timeout=300, + heartbeat_timeout=15, +) +``` + +`start_to_close_timeout` обмежує одну спробу після початку worker. +`schedule_to_close_timeout` обмежує все виконання activity між спробами. +`heartbeat_timeout` вимагає виклику `activity.context().heartbeat(...)` +до завершення інтервалу для тривалих activity. Політика retry фіксується +на надійному виконанні під час планування, тому наступні розгортання +не змінюють активних спроб. + +### Правила детермінізму {#determinism-rules} + +Код workflow відтворюється з історії та не має напряму виконувати +недетерміновані операції: + +- Без I/O: HTTP викликів, читання файлів, запитів бази +- Без `datetime.now()` чи `time.time()`: використовуйте `ctx.now()` +- Без `random.random()`: використовуйте `ctx.random()` +- Без `uuid.uuid4()`: використовуйте `ctx.uuid4()` + +Уся недетермінованість має проходити через контекст або фіксуватися `ctx.side_effect()`. + +### Fan-out {#fan-out} + +Передайте через yield список команд для паралельного виконання: + +```python +@workflow.defn(name="fan-out-example") +class FanOutWorkflow: + def run(self, ctx, *args): + items = args[0] + + # Nested lists may mix activities, children, and timers. + results = yield [ + ctx.schedule_activity("process_item", [items[0]]), + [ + ctx.start_child_workflow("process-batch", [items[1:]]), + ctx.start_timer(1), + ], + ] + + return results # same nested shape and input order +``` + +Worker видає одну звичайну команду на надійний листок і додає спільні сталі +поля `parallel_group_*` та повний шлях від зовнішньої до внутрішньої групи. +Історія може завершувати членів у будь-якому порядку, але replay прив'язує +результати й помилки за надійною позицією вводу. Точні дублікати термінального +доставлення ігноруються. Очікувана історія після restart worker і повністю +завершена історія відновлюють ту саму групу без повторного планування завершеної роботи. + +### Вибір першого завершення {#first-completion-selection} + +Використовуйте `ctx.select()`, коли незалежна робота має почати разом, +але workflow може просуватися після завершення одного члена: + +```python +selected = yield ctx.select({ + "resolver": ctx.schedule_activity("resolve-request", [request_id]), + "input": ctx.wait_condition(lambda: self.resolution is not None, key="resolution-ready"), + "deadline": ctx.start_timer(2), +}) + +if selected.key == "deadline": + yield selected.handles["resolver"].cancel() + return {"status": "timed_out"} + +yield selected.handles["deadline"].cancel() +return {"status": "resolved", "value": selected.result()} +``` + +`SelectionResult` містить сталий ключ переможця, індекс вводу, вид операції +та надійну ідентичність, типізований результат і всі `DurableOperationHandle`. +`yield handle.await_result()` дозволяє пізніше дочекатися іншого члена. +`yield handle.cancel()` записує явний запит без результату. Лише зафіксована +історія `SelectionOperationCancelled` доводить перемогу скасування. +Replay просувається після запиту без позначки, а `await_result()` повертає +завершення, що зафіксувалося першим. Replay використовує збереженого переможця, +навіть якщо пізніша історія спочатку містить інше завершення. +Дублікати й зовнішнє доставлення поза порядком не заміняють переможця. + +### Компенсація saga {#saga-compensation} + +```python +def forward(saga): + flight = yield ctx.schedule_activity("trip.reserve-flight", []) + saga.add_compensation("trip.cancel-flight", [flight]) + + hotel = yield ctx.schedule_activity("trip.reserve-hotel", []) + saga.add_compensation("trip.cancel-hotel", [hotel]) + + yield ctx.schedule_activity("trip.charge", []) + return {"status": "booked"} + +return (yield from ctx.saga().run(forward)) +``` + +Після помилки saga виконує звичайні команди activity послідовно у зворотному +порядку реєстрації. Термінальний `cancel_workflow` не відновлює workflow +для компенсацій. Помічник зупиняється на першій помилці компенсації. +`SagaCompensationFailed` зберігає початкову помилку, помилку компенсації, +її тип activity і детермінований порядок як структуровану діагностику. + +### Дочірні workflow {#child-workflows} + +```python +from durable_workflow import ChildWorkflowRetryPolicy + +result = yield ctx.start_child_workflow( + "child-workflow-type", + [{"input": "data"}], + task_queue="child-queue", + parent_close_policy="terminate", + retry_policy=ChildWorkflowRetryPolicy( + max_attempts=3, + initial_interval_seconds=2, + backoff_coefficient=2, + non_retryable_error_types=["ValidationError"], + ), + execution_timeout_seconds=600, + run_timeout_seconds=120, +) +``` + +`execution_timeout_seconds` обмежує логічне дочірнє виконання між retry й +continue-as-new. `run_timeout_seconds` обмежує кожну спробу run. +Retry backoff застосовується після помилки дочірнього виконання. +Неправильні команди запуску є помилками протоколу й не повторюються як дочірні спроби. + +### Continue-as-new {#continue-as-new} + +Для тривалих workflow використовуйте continue-as-new для скидання історії: + +```python +from durable_workflow import ContinueAsNew + +@workflow.defn(name="polling-workflow") +class PollingWorkflow: + def run(self, ctx, *args): + iteration = args[0] if args else 0 + + result = yield ctx.schedule_activity("poll_source", []) + + if result.get("done"): + return result + + # Continue with incremented iteration + return ContinueAsNew(arguments=[iteration + 1]) +``` + +## Визначення activity {#defining-activities} + +Activity є асинхронними функціями Python з `@activity.defn`. +На відміну від workflow, вони можуть вільно виконувати I/O. + +```python +from durable_workflow import activity + +@activity.defn(name="send_email") +async def send_email(to: str, subject: str, body: str) -> dict: + # Activities can do I/O: HTTP calls, database queries, etc. + response = await some_email_client.send(to=to, subject=subject, body=body) + return {"message_id": response.id, "sent": True} +``` + +`name` є ключем типу для всіх мов. PHP workflow може планувати activity +`"send_email"`, яку отримає worker Python, і навпаки. + +### Контекст activity {#activity-context} + +Усередині activity використовуйте `activity.context()` для метаданих +виконання й heartbeat: + +```python +@activity.defn(name="long_running_task") +async def long_running_task(items: list) -> dict: + ctx = activity.context() + + print(f"Attempt #{ctx.info.attempt_number}") + print(f"Task queue: {ctx.info.task_queue}") + + for i, item in enumerate(items): + # Check for cancellation + if ctx.is_cancelled: + return {"partial": True, "processed": i} + + await process(item) + + # Heartbeat to keep the task alive + await ctx.heartbeat({"progress": i + 1, "total": len(items)}) + + return {"processed": len(items)} +``` + +### Неповторювані помилки {#non-retryable-errors} + +Підніміть `NonRetryableError`, щоб завершити activity помилкою без retry: + +```python +from durable_workflow import NonRetryableError + +@activity.defn(name="validate") +async def validate(data: dict) -> dict: + if "required_field" not in data: + raise NonRetryableError("Missing required_field") + return data +``` + +## Worker {#worker} + +`Worker` реєструється на Server, опитує завдання й направляє їх до ваших +реалізацій workflow та activity. + +```python +from durable_workflow import Client, Worker + +async with Client("http://localhost:8080", token="secret") as client: + worker = Worker( + client, + task_queue="default", + workflows=[GreeterWorkflow, OrderWorkflow], + activities=[greet, send_email, validate], + max_concurrent_workflow_tasks=10, + max_concurrent_activity_tasks=10, + ) + + await worker.run() # blocks until worker.stop() is called +``` + +Для коротких тестів і прикладів одного workflow +`await worker.run_until(workflow_id="...", timeout=60.0)` реєструє того самого +worker та веде workflow до термінального стану послідовним опитуванням. +Для постійно розгорнутих worker використовуйте `run()`. + +| Параметр | Типове значення | Опис | +|-----------|---------|-------------| +| `task_queue` | обов'язковий | Черга завдань для poll | +| `workflows` | `()` | Класи workflow для реєстрації | +| `activities` | `()` | Функції activity для реєстрації | +| `worker_id` | автоматичний | Унікальний ID worker | +| `poll_timeout` | `35.0` | Тайм-аут long-poll у секундах | +| `max_concurrent_workflow_tasks` | `10` | Максимум паралельних завдань workflow | +| `max_concurrent_activity_tasks` | `10` | Максимум паралельних завдань activity | +| `shutdown_timeout` | `30.0` | Секунди drain активних завдань під час stop | +| `metrics` | recorder клієнта | Необов'язковий recorder лічильників/гістограм poll і завдань | +| `interceptors` | `()` | Упорядковані обгортки завдань для інструментування, трасування й політик | + +Два значення `max_concurrent_*` оголошуються Server під час реєстрації й +відображаються в діагностиці допуску. Це локальна місткість worker. +Використовуйте серверні [ліміти допуску черг](/docs/polyglot/task-queue-admission), +якщо namespace, черга чи downstream група бюджету потребує жорсткого +спільного обмеження для кількох worker. + +### Довідник API worker {#worker-api-reference} + +| Метод | Результат | Призначення | +| --- | --- | --- | +| `await worker.run()` | `None` | Тривалий процес під systemd, Docker, Kubernetes чи оболонкою розробки. Реєструється раз і опитує workflow, activity та query до зупинки чи скасування. | +| `await worker.run_until(workflow_id=..., timeout=60.0, poll_interval=0.5)` | `WorkflowExecution` | Короткі тести й приклади одного workflow, який той самий процес має вести до термінального стану. | +| `await worker.stop()` | `None` | Кооперативне завершення процесу. Зупиняє нові poll і чекає активні завдання до `shutdown_timeout`. | + +`run()` перевіряє сумісність Server перед poll. Worker вимагає відповідності +`control_plane.version`, `control_plane.request_contract`, +`worker_protocol.version`, `auth_composition_contract` підтримуваним версіям SDK. +Відсутній чи несумісний маніфест піднімає `RuntimeError` під час реєстрації, +щоб менеджер процесів одразу бачив неспроможність безпечно завершувати завдання. + +`run_until()` використовує ту саму реєстрацію й dispatch, але опитує +послідовно та повертає остаточний `WorkflowExecution` названого workflow. +Піднімає `TimeoutError`, якщо workflow не термінальний після тайм-ауту. + +Під час виконання завдань: + +- невідомі типи workflow чи activity повертаються Server як помилки завдань, + а не ховаються лише в локальних журналах +- `NonRetryableError` позначає помилку activity як неповторювану +- `ActivityCancelled` поширюється як результат скасування +- необроблені винятки activity є повторюваними помилками, якщо політика + retry чи дедлайн Server не визначають іншого +- винятки query повертаються як `QueryFailed` + +### Перехоплювачі worker {#worker-interceptors} + +Передайте `interceptors=[...]` для трасування, метрик, журналу аудиту чи +локальних перевірок політики навколо завдань. Вони працюють у переданому +порядку: перший є зовнішньою обгорткою й має викликати `next` для продовження. + +```python +from durable_workflow import ( + ActivityInterceptorContext, + PassthroughWorkerInterceptor, +) + +class AuditInterceptor(PassthroughWorkerInterceptor): + async def execute_activity(self, context: ActivityInterceptorContext, next): + print("activity started", context.activity_type, context.worker_id) + return await next(context) + +worker = Worker( + client, + task_queue="orders", + workflows=[OrderWorkflow], + activities=[charge_card], + interceptors=[AuditInterceptor()], +) +``` + +| Hook | Поля контексту | Результат next | +| --- | --- | --- | +| `execute_workflow_task(context, next)` | `worker_id`, `task_queue`, `task` | Команди workflow або `None` | +| `execute_activity(context, next)` | `worker_id`, `task_queue`, `task`, `activity_type`, `args` | Декодований результат activity | +| `execute_query_task(context, next)` | `worker_id`, `task_queue`, `task` | Закодований рядок результату query | + +Використовуйте `PassthroughWorkerInterceptor` як базовий клас для одного hook. +Реалізуйте `WorkerInterceptor` напряму, якщо перевірка типів має вимагати +наявності всіх hook. + +## Журналювання {#logging} + +SDK використовує стандартний модуль `logging` Python зі структурованими назвами: + +| Назва logger | Записи | +|-------------|--------------| +| `durable_workflow.worker` | Реєстрація worker, poll, завершення завдань, помилки | +| `durable_workflow.workflow.replay` | Події replay workflow, без записів під час replay | + +### Налаштування журналювання {#configuring-logging} + +Задайте рівень журналювання в точці входу застосунку: + +```python +import logging + +# Show INFO-level worker events (registration, task completion) +logging.basicConfig(level=logging.INFO) + +# Or configure specific loggers +logging.getLogger("durable_workflow.worker").setLevel(logging.DEBUG) +logging.getLogger("durable_workflow.workflow.replay").setLevel(logging.INFO) +``` + +### Рівні журналювання {#log-levels} + +- **INFO:** реєстрація worker, початок/завершення завдань і завершення workflow +- **DEBUG:** детальні обрізані payload завдань, цикли poll +- **WARNING:** повторювані помилки, невдалі API виклики, невідомі типи workflow +- **ERROR:** неповторювані помилки, збої replay + +### Журналювання з урахуванням replay {#replay-aware-logging} + +У workflow використовуйте `ctx.logger` для журналювання з урахуванням replay: + +```python +@workflow.defn(name="order_processor") +class OrderProcessor: + def run(self, ctx, order_id: str): + ctx.logger.info("Processing order %s", order_id) # Only logs during execution, not replay + result = yield ctx.schedule_activity("process_order", [order_id]) + ctx.logger.info("Order processed: %s", result) + return result +``` + +Журнали **мовчать під час replay**, щоб уникати дублікатів під час відновлення +чи продовження workflow. + +### Структуроване журналювання {#structured-logging} + +Для структурованих журналів JSON задайте JSON formatter кореневого logger застосунку: + +```python +import logging +import json + +class JSONFormatter(logging.Formatter): + def format(self, record): + return json.dumps({ + "timestamp": self.formatTime(record), + "level": record.levelname, + "logger": record.name, + "message": record.getMessage(), + }) + +handler = logging.StreamHandler() +handler.setFormatter(JSONFormatter()) +logging.getLogger("durable_workflow").addHandler(handler) +logging.getLogger("durable_workflow").setLevel(logging.INFO) +``` + +## Метрики {#metrics} + +`Client(metrics=...)` і `Worker(metrics=...)` приймають recorder із двома методами: + +```python +def increment(name: str, value: float = 1.0, tags: dict[str, str] | None = None) -> None: ... +def record(name: str, value: float, tags: dict[str, str] | None = None) -> None: ... +``` + +Типовий recorder не виконує дій, тому збір метрик не потребує налаштування. +Для тестів чи власних циклів експорту використовуйте `InMemoryMetrics`: + +```python +from durable_workflow import Client, InMemoryMetrics, Worker + +metrics = InMemoryMetrics() + +async with Client("http://localhost:8080", token="secret", metrics=metrics) as client: + worker = Worker(client, task_queue="default", workflows=[GreeterWorkflow], activities=[greet]) +``` + +Для Prometheus встановіть необов'язкове доповнення й передайте `PrometheusMetrics`: + +```bash +pip install 'durable-workflow[prometheus]' +``` + +```python +from durable_workflow import Client, PrometheusMetrics + +metrics = PrometheusMetrics() +client = Client("http://localhost:8080", token="secret", metrics=metrics) +``` + +SDK записує: + +| Метрика | Тип | Мітки | +|--------|------|------| +| `durable_workflow_client_requests` | Лічильник | `method`, `route`, `plane`, `status_code`, `outcome` | +| `durable_workflow_client_request_duration_seconds` | Гістограма | `method`, `route`, `plane`, `status_code`, `outcome` | +| `durable_workflow_worker_polls` | Лічильник | `task_kind`, `task_queue`, `outcome` | +| `durable_workflow_worker_poll_duration_seconds` | Гістограма | `task_kind`, `task_queue`, `outcome` | +| `durable_workflow_worker_tasks` | Лічильник | `task_kind`, `task_queue`, `outcome` | +| `durable_workflow_worker_task_duration_seconds` | Гістограма | `task_kind`, `task_queue`, `outcome` | + +## Schedule {#schedules-1} + +Створюйте й керуйте запланованими workflow через клієнт: + +```python +from durable_workflow import ScheduleSpec, ScheduleAction + +# Create a schedule +handle = await client.create_schedule( + schedule_id="hourly-report", + spec=ScheduleSpec(cron_expressions=["0 * * * *"]), + action=ScheduleAction( + workflow_type="generate-report", + task_queue="default", + input=[{"format": "pdf"}], + ), + overlap_policy="skip", + jitter_seconds=30, +) + +# List all schedules +schedule_list = await client.list_schedules() + +# Describe a schedule +desc = await handle.describe() +print(f"Next fire: {desc.next_fire_at}") +print(f"Total fires: {desc.fires_count}") + +# Pause and resume +await handle.pause(note="maintenance window") +await handle.resume(note="maintenance complete") + +# Trigger immediately +result = await handle.trigger() + +# Backfill missed runs +backfill = await handle.backfill( + start_time="2024-01-01T00:00:00Z", + end_time="2024-01-02T00:00:00Z", +) + +# Update the schedule +await handle.update( + spec=ScheduleSpec(cron_expressions=["*/30 * * * *"]), + note="Changed to every 30 minutes", +) + +# Read the audit history stream (paused/resumed/triggered/updated events) +page = await handle.history(limit=100) +for event in page.events: + print(event.sequence, event.event_type, event.recorded_at) +while page.has_more and page.next_cursor is not None: + page = await handle.history(limit=100, after_sequence=page.next_cursor) + for event in page.events: + print(event.sequence, event.event_type, event.recorded_at) + +# Or iterate every event without managing cursors +async for event in handle.iter_history(): + print(event.sequence, event.event_type) + +# Delete the schedule (history survives the delete for audit) +await handle.delete() +``` + +## Синхронний клієнт {#synchronous-client} + +Для скриптів, notebook і неасинхронних контекстів використовуйте синхронну обгортку: + + +```python +import uuid + +from durable_workflow.sync import Client as SyncClient + +client = SyncClient("http://localhost:8080", token="secret") +workflow_id = f"sync-greeting-{uuid.uuid4().hex}" + +handle = client.start_workflow( + workflow_type="greeter", + task_queue="default", + workflow_id=workflow_id, + input=["world"], +) + +execution = client.describe_workflow(workflow_id) +print(execution.status) +``` + +Синхронний клієнт відображає async API, але обгортає кожен виклик в `asyncio.run`. + +## Обробка помилок {#error-handling} + +SDK зіставляє коди помилок Server із типізованими винятками Python: + +| Виняток | Умова | +|-----------|------| +| `WorkflowNotFound` | ID workflow не існує | +| `WorkflowAlreadyStarted` | Дублікат ID workflow з конфліктною політикою | +| `WorkflowFailed` | Виконання workflow завершилося помилкою | +| `WorkflowCancelled` | Workflow скасований, успадковує `BaseException`, а не `Exception` | +| `WorkflowTerminated` | Workflow примусово завершений | +| `ActivityCancelled` | Activity скасована під час виконання, успадковує `BaseException`, а не `Exception` | +| `ChildWorkflowFailed` | Дочірній workflow завершився помилкою | +| `QueryFailed` | Обробник query повернув помилку | +| `UpdateRejected` | Workflow відхилив update | +| `ScheduleNotFound` | ID schedule не існує | +| `ScheduleAlreadyExists` | Дублікат ID schedule | +| `NamespaceNotFound` | Namespace не існує | +| `InvalidArgument` | Неправильні параметри запиту | +| `Unauthorized` | Автентифікація не пройшла | +| `ServerError` | Server повернув неочікувану помилку | + + +```python +from durable_workflow import WorkflowNotFound, WorkflowAlreadyStarted + +try: + handle = await client.start_workflow( + workflow_type="greeter", + task_queue="default", + workflow_id="existing-id", + input=["world"], + ) +except WorkflowAlreadyStarted: + handle = client.get_workflow_handle("existing-id") +except WorkflowNotFound: + print("Workflow type not registered on any worker") +``` + +### Скасування навмисно не перехоплюється except Exception {#cancellation-is-intentionally-uncatchable-by-except-exception} + +`WorkflowCancelled` і `ActivityCancelled` успадковують `BaseException`, а не +`Exception`. Загальний `except Exception:` усередині activity чи коду, що +чекає `client.get_result()`, їх **не перехопить**. Це навмисно: скасування +є результатом control-plane. Мовчазне поглинання в загальному catch могло +б дозволити activity повідомити успіх після запиту workflow зупинитися. + +Якщо activity потребує локального очищення за скасування завдання, +перехопіть конкретний клас і підніміть виняток знову. +Це не відновлює закритий workflow для компенсації: + +```python +from durable_workflow import ActivityCancelled, activity + +@activity.defn(name="long_task") +async def long_task(items: list) -> dict: + ctx = activity.context() + try: + for i, item in enumerate(items): + await process(item) + await ctx.heartbeat({"progress": i + 1}) + return {"done": True} + except ActivityCancelled: + await cleanup_partial_state() + raise +``` + +Це відповідає прецедентам стандартної бібліотеки `asyncio.CancelledError` і `KeyboardInterrupt`. + +## Тестування {#testing} + +Автор має мати змогу тестувати workflow без Server чи worker. +Модуль `durable_workflow.testing` надає два способи: + +- `WorkflowEnvironment` виконує workflow в одному процесі Python із + зареєстрованими користувачем mock activity. +- `replay_history` і `replay_history_file` відтворюють збережену production + історію проти поточного коду й піднімають виняток за недетермінізму. + +Обидва способи використовують той самий механізм `durable_workflow.workflow.replay`, +що й worker, тому тест перевіряє таку саму поведінку replay, як у runtime. + +### WorkflowEnvironment {#workflowenvironment} + +`WorkflowEnvironment` виконує команди через зареєстровані mock та автоматично +створює timer, side effect і upsert атрибутів пошуку. Тестам не потрібні +справжній годинник, Redis чи Server. + +```python +from durable_workflow import workflow +from durable_workflow.testing import WorkflowEnvironment + +@workflow.defn(name="greeter") +class Greeter: + def run(self, ctx, name: str): + greeting = yield ctx.schedule_activity("greet", [name]) + return greeting + + +def test_greeter_returns_activity_result() -> None: + env = WorkflowEnvironment() + env.register_activity_result("greet", "hello, world") + + result = env.execute_workflow(Greeter, "world") + + assert result == "hello, world" +``` + +| Метод | Призначення | +| --- | --- | +| `register_activity_result(name, result)` | Повертає `result` для кожної activity `name`, якщо аргументи неважливі тесту. | +| `register_activity(name, fn)` | Викликає `fn(*arguments)` для кожної activity `name`, якщо mock залежить від аргументів чи має фіксувати виклики. | +| `register_child_workflow_result(workflow_type, result)` | Повертає `result` для дочірнього workflow типу `workflow_type`. | +| `signal(name, args=None, run=None)` | Ставить signal перед наступним replay. Harness додає `SignalReceived` і викликає `@workflow.signal`. `run=N` вибирає ланку `N` continue-as-new. | +| `register_workflow(workflow_cls)` | Реєструє додатковий клас за назвою. Потрібно для `continue_as_new(workflow_type=...)` з іншим типом. | +| `execute_workflow(workflow_cls, *args, run_id="test-run")` | Веде workflow до термінального стану й повертає результат. Продовжує ланцюг `continue_as_new` до останнього виконання. Піднімає `WorkflowFailed` за failed. | +| `runs` / `run_count` | Після `execute_workflow` надає `WorkflowRunRecord` кожної ланки (ввід, тип, історія, термінальна команда) для перевірки всього ланцюга. | + +Harness явно повідомляє відсутні fixture: + +- activity без зареєстрованого mock піднімає `KeyError` +- дочірній workflow без mock піднімає `KeyError` +- workflow без термінального стану за ліміту ітерацій (типово `1000`) + піднімає `RuntimeError` + +Передайте `iteration_limit=...` у `WorkflowEnvironment(...)` для workflow, +які правомірно потребують більше ітерацій. + +#### Mock activity як функції {#callable-activity-mocks} + +Використовуйте `register_activity`, якщо mock має відповідати за +аргументами чи фіксувати виклики: + +```python +def test_callable_mock_captures_arguments() -> None: + captured: list[str] = [] + + def record_greet(name: str) -> str: + captured.append(name) + return f"greeted:{name}" + + env = WorkflowEnvironment() + env.register_activity("greet", record_greet) + + assert env.execute_workflow(Greeter, "alice") == "greeted:alice" + assert captured == ["alice"] +``` + +#### Signal {#signals} + +Signal, поставлені через `env.signal(...)`, доставляються перед наступною +ітерацією replay. Payload отримує ту саму обгортку `{codec, blob}`, +що й у runtime, і передається зареєстрованому обробнику `@workflow.signal`: + +```python +@workflow.defn(name="approval") +class Approval: + def __init__(self) -> None: + self.approved_by: str | None = None + + @workflow.signal("approve") + def on_approve(self, by: str) -> None: + self.approved_by = by + + def run(self, ctx): + yield ctx.schedule_activity("wait", []) + return {"approved_by": self.approved_by} + + +def test_signal_is_delivered_before_run_returns() -> None: + env = WorkflowEnvironment() + env.register_activity_result("wait", None) + env.signal("approve", ["alice"]) + + result = env.execute_workflow(Approval) + + assert result == {"approved_by": "alice"} +``` + +#### Timer, побічні ефекти й атрибути пошуку {#timers-side-effects-and-search-attributes} + +Harness автоматично створює відповідну подію історії для кожної команди, +тому workflow не блокується на реальному часі в тесті: + +- `ctx.sleep(seconds)` → `TimerFired` +- `ctx.side_effect(...)` → `SideEffectRecorded` +- `ctx.upsert_search_attributes(...)` → `SearchAttributesUpserted` +- `workflow.version(...)` маркери → `VersionMarkerRecorded` + +#### Ланцюги continue-as-new {#continue-as-new-chains} + +Коли workflow повертає `ctx.continue_as_new(...)`, harness додає +`WorkflowContinuedAsNew` до завершуваного виконання, скидає історію й +починає нове з аргументами команди. `execute_workflow` повертає +термінальний результат останньої ланки. + +```python +@workflow.defn(name="countdown") +class Countdown: + def run(self, ctx, counter: int): + yield ctx.schedule_activity("emit", [counter]) + if counter > 0: + return ctx.continue_as_new(counter - 1) + return {"final_counter": counter} + + +def test_chain_returns_final_run_result() -> None: + env = WorkflowEnvironment() + env.register_activity_result("emit", None) + + result = env.execute_workflow(Countdown, 3) + + assert result == {"final_counter": 0} + assert env.run_count == 4 + assert [r.input for r in env.runs] == [[3], [2], [1], [0]] +``` + +Якщо ланцюг змінює тип workflow, спочатку зареєструйте наступний клас: + +```python +@workflow.defn(name="stage-one") +class StageOne: + def run(self, ctx): + yield ctx.schedule_activity("stage_one", []) + return ctx.continue_as_new(workflow_type="stage-two") + + +@workflow.defn(name="stage-two") +class StageTwo: + def run(self, ctx): + return (yield ctx.schedule_activity("stage_two", [])) + + +def test_chain_can_switch_workflow_type() -> None: + env = WorkflowEnvironment() + env.register_workflow(StageTwo) + env.register_activity_result("stage_one", None) + env.register_activity_result("stage_two", "done") + + assert env.execute_workflow(StageOne) == "done" + assert [r.workflow_type for r in env.runs] == ["stage-one", "stage-two"] +``` + +Направляйте signal конкретній ланці через `run=N`: + +```python +env.signal("approve", ["alice"], run=2) # delivered to the second run +``` + +Довжина ланцюга обмежена `continue_as_new_limit` (типово `50`). +Перевищення піднімає `RuntimeError` для виявлення нескінченних продовжень +замість безкінечного циклу. Для правомірно довшого ланцюга задайте +`WorkflowEnvironment(continue_as_new_limit=...)`. + +#### Перевірка помилок {#failure-assertions} + +Workflow, що піднімає виняток Python, повертає `WorkflowFailed`: + +```python +import pytest +from durable_workflow.errors import WorkflowFailed + +@workflow.defn(name="failing") +class Failing: + def run(self, ctx): + yield ctx.schedule_activity("step", []) + raise RuntimeError("boom") + + +def test_workflow_failure_surfaces_as_workflow_failed() -> None: + env = WorkflowEnvironment() + env.register_activity_result("step", None) + + with pytest.raises(WorkflowFailed) as exc_info: + env.execute_workflow(Failing) + + assert "boom" in str(exc_info.value) +``` + +### Тестування replay production історії {#replay-testing-against-production-history} + +Використовуйте `replay_history` для регресійної перевірки зміни коду +проти справжньої історії Server. Replayer виконує поточний код за +записаною послідовністю й піднімає виняток за команди, що відрізняється +від записаної. Це визначення помилки недетермінізму. + +```python +from durable_workflow import Client +from durable_workflow.testing import replay_history + +async with Client("http://localhost:8080") as client: + history = await client.get_history("order-42", run_id="...") + +replay_history(OrderWorkflow, history["events"], start_input=["order-42"]) +``` + +Workflow, що раніше завершився, має завершитися при replay тієї самої +історії. Якщо код розходиться із записаною послідовністю (інший порядок +activity, вилучені гілки, змінені типи), `replay_history` піднімає виняток +для виявлення регресії в CI до production. + +`replay_history_file` читає JSON файл однієї з двох форм: верхній список +подій чи словник із ключем `events`, що відповідає `get_history`: + +```python +from durable_workflow.testing import replay_history_file + +replay_history_file( + OrderWorkflow, + "tests/histories/order-42.json", + start_input=["order-42"], +) +``` + +Обидві функції приймають необов'язкову підказку `payload_codec`. +Без неї використовується записаний тег історії. Явний чи записаний тег +має бути `avro`. `json`, невідомий чи відсутній тег надійного payload +відхиляється до декодування. + +#### Replayer як клас {#class-based-replayer} + +Для кількох типів workflow за один тест або вибору типу через збережену +подію `WorkflowStarted` використовуйте клас `Replayer`. +Зареєструйте всі класи заздалегідь і викличте `replay(...)` для кожної історії: + +```python +from durable_workflow import Replayer, ReplayOutcome + +replayer = Replayer(workflows=[OrderWorkflow, RefundWorkflow]) + +# Explicit type and input — equivalent to replay_history(OrderWorkflow, ...). +outcome: ReplayOutcome = replayer.replay( + history["events"], + start_input=["order-42"], + workflow_type="order", +) + +# Type and start input inferred from a WorkflowStarted event in the history. +outcome = replayer.replay(history) # history may be an events list or a + # dict with an "events" key + +for command in outcome.commands: + # Inspect commands the replayed workflow would have emitted next. + ... +``` + +`Replayer(workflows=[...])` відхиляє порожній набір чи дублікати з `ValueError`. +`replay(...)` піднімає `ValueError` за незареєстрованого типу історії або +кількох зареєстрованих workflow без `workflow_type` викликача й без +події `WorkflowStarted` в історії. + +`ReplayOutcome.commands` є тим самим списком команд, що й результат +`replay_history`. Клас дозволяє тестам використовувати одну реєстрацію +для багатьох історій. + +### Довідник тестового harness {#test-harness-reference} + +| Символ | Призначення | +| --- | --- | +| `durable_workflow.testing.WorkflowEnvironment` | Тестовий harness у процесі для завершення workflow з mock activity й дочірніх workflow. | +| `durable_workflow.testing.replay_history(workflow_cls, events, start_input=None, *, run_id="", payload_codec=None)` | Replay послідовності подій проти поточного коду з винятком за недетермінізму. | +| `durable_workflow.testing.replay_history_file(workflow_cls, path, start_input=None, *, run_id="", payload_codec=None)` | Завантаження історії JSON із диска й replay. Приймає список подій чи словник `events`. | +| `durable_workflow.Replayer(*, workflows=[...])` | Replayer як клас. Реєструйте workflow, потім викликайте `replay(history, start_input=None, *, workflow_type=None, workflow_id=None, run_id="", payload_codec=None)` для історій. Визначає `workflow_type` і `start_input` з `WorkflowStarted` за наявності. | +| `durable_workflow.ReplayOutcome` | Dataclass результату `Replayer.replay` з `commands: list[Command]`, наступними командами відтвореного workflow. | +| `durable_workflow.errors.WorkflowFailed` | Виняток `execute_workflow` за завершення failed. | +| `durable_workflow.errors.WorkflowCancelled` | Термінальний стан скасованого workflow, успадковує `BaseException`. | +| `durable_workflow.errors.WorkflowTerminated` | Термінальний стан workflow, примусово завершеного Server, успадковує `BaseException`. | + +## Кодек payload {#payload-codec} + +
+ Кожен payload межі протоколу worker має тег кодека. + Durable Workflow 2.0 має один публічний кодек: avro. + Python SDK відхиляє JSON, невідомі й нетеговані надійні payload + замість вибору іншого декодера. Див. протокол Avro Value. +
+ +### Avro вбудовано {#avro-support-is-built-in} + +Зафіксований артефакт SDK 2.0 встановлює `fastavro` як залежність runtime. +Тому `start_workflow`, `signal_workflow`, `query_workflow`, `update_workflow`, +кодування результату activity й дії schedule надсилають Avro payload +оптимізованим production шляхом. Додатковий codec пакет не потрібен. + +### Фіксовані типізовані значення {#fixed-typed-values} + +SDK явно вибирає іменовані гілки спільної схеми `durable_workflow.protocol.Value`. +Значення не кодуються як JSON усередині Avro. Цілі й дробові числа, +рядки й байти, bool і цілі, списки й мапи залишаються різними між SDK. +Мережевий формат використовує Avro single-object і fingerprint схеми. +Невідомі fingerprint відхиляються з `unsupported_payload_schema`. + +Кожна поверхня клієнта й worker працює наскрізно з типовим Avro: + +- **Запуск, signal, query, update клієнта:** `start_workflow`, `signal_workflow`, + `query_workflow`, `update_workflow` надсилають `payload_codec = "avro"` + через фіксовану Value. Python клієнт може керувати workflow, які + відтворюють PHP та інші SDK, і навпаки. +- **Worker activity:** Avro аргументи прозоро декодуються, а результати + кодуються як Avro для спільної межі PHP, Python і майбутніх SDK. +- **Помилки activity:** `fail_activity_task(..., details=...)` надсилає + `failure.details` як `{codec, blob}`. Server записує blob і + `details_payload_codec`, зберігаючи незалежність діагностики від мови + в експорті історії й оглядах спостережуваності. +- **Replay історії workflow:** Avro ввід запуску й події результату activity + декодуються під час replay для участі Python workflow в Avro виконанні. +- **Завдання query workflow:** маршрутизовані query містять Avro аргументи + workflow, query та історію replay. Результат повертається в Avro обгортці. + +### Worker activity Python для виконання v2 {#running-a-python-activity-worker-against-a-v2-run} + +Налаштування кодека не потрібне. SDK перевіряє `payload_codec` перед +декодуванням кожного отриманого завдання, приймає лише `avro`, виконує +activity й повертає Avro результат. Інший тег відхиляється з +`unsupported_payload_codec`. Worker не вгадує формат за вмістом. + +### Типи, що зберігаються між Python і PHP {#types-that-round-trip-cleanly-across-python-and-php} + +| Тип Python | Гілка Avro Value | Тип PHP | +|-------------|------|----------| +| `str` | `StringValue` | `string` | +| `bytes` | `BytesValue` | `AvroBinaryValue` | +| `int` | `LongValue` | `int` | +| `float` | `DoubleValue` | `float` | +| `bool` | `BooleanValue` | `bool` | +| `None` | `null` | `null` | +| `list` | `ArrayValue` | `array` (список) | +| `dict[str, ...]` | `MapValue` | `array` (мапа з рядковими ключами) | + +Перед передаванням значень workflow чи activity перетворюйте специфічні +типи Python (dataclass, множини, кортежі, datetime) на канонічні +рядки, цілі числа, мапи чи списки. + +## Запуск зі спільним Server {#running-against-a-shared-server} + +[Quickstart](#quickstart) вище запускає локальний Server з опублікованим +образом Docker. У командному середовищі worker Python зазвичай +підключається до наявного Server (тестового, production чи спільного dev): + +```python +from durable_workflow import Client + +client = Client( + "https://workflow.example.internal", + control_token="team-orders-operator-token", + worker_token="team-orders-worker-token", + namespace="team-orders", +) +``` + +Задайте `namespace` орендаря, наданий команді спільним Server, та видані +для нього облікові дані. Namespace створює оператор Server. +Докладніше в [посібнику Server](/docs/polyglot/server). + +Той самий конструктор підтримує керований runtime Cloud. Використовуйте +надані Cloud URL runtime і namespace, задайте `control_token` обліковими +даними клієнта runtime, а `worker_token` обліковими даними worker. +Cloud створює namespace. Не заміняйте URL адресою самостійного Server. +Див. [керований runtime Cloud](/docs/polyglot/cloud-control-plane). diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-api-reference.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-api-reference.md new file mode 100644 index 0000000000..e1157ac15d --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server-api-reference.md @@ -0,0 +1,724 @@ +--- +sidebar_position: 3 +title: Довідник API Server +description: HTTP довідник endpoint площини керування, worker, namespace, сховища, schedule і системних операцій Durable Workflow Server. +tags: + - server + - API + - reference + - control-plane +keywords: + - API Durable Workflow Server + - заголовок версії control-plane + - endpoint протоколу worker + - API зовнішнього сховища payload +--- + +# Довідник API Server {#server-api-reference} + +Самостійний Server надає версіонований HTTP+JSON API. Використовуйте цей довідник +для SDK, скриптів, адаптерів bridge та операторських інструкцій, які звертаються +безпосередньо до Server. Розгортання й конфігурацію описано в +[посібнику Server](/docs/polyglot/server), а виклики `dw` в оболонці в +[довіднику команд CLI](/docs/polyglot/cli-reference). + +## Заголовки й версіонування {#headers-and-versioning} + +Усі автентифіковані запити використовують bearer token, якщо Server +не налаштований на інший драйвер автентифікації: + +```http +Authorization: Bearer +X-Namespace: default +Content-Type: application/json +Accept: application/json +``` + +Маршрути площини керування вимагають: + +```http +X-Durable-Workflow-Control-Plane-Version: 2 +``` + +Маршрути площини worker вимагають: + +```http +X-Durable-Workflow-Protocol-Version: 1.0 +``` + +Server публікує підтримувані версії та машиночитані контракти через +`GET /api/cluster/info`. Клієнти мають визначати версії там перед запуском +тривалої автоматизації. Відсутня чи непідтримувана версія control-plane +відхиляється з іменованою причиною, наприклад `missing_control_plane_version` +чи `unsupported_control_plane_version`. + +Для перевірки, генерації коду й контролю відхилень використовуйте нормативний +каталог [специфікацій протоколів платформи](/docs/platform-protocol-specs). +Він посилається на OpenAPI площини керування, OpenAPI й AsyncAPI протоколу +worker, JSON Schema `cluster_info` та суміжні схеми MCP, історії, Waterline +і repair/actionability. + +## Виявлення й здоров'я {#discovery-and-health} + +Ці маршрути використовують балансувальники, початкове налаштування SDK +та перевірки сумісності. + +| Метод | Шлях | Автентифікація | Призначення | +| --- | --- | --- | --- | +| `GET` | `/api/health` | ні | Перевірка життя та машиночитане зведення топології вузла. | +| `GET` | `/api/ready` | ні | Перевірка готовності з тим самим зведенням топології й перевірками bootstrap безпечного розгортання. | +| `GET` | `/api/cluster/info` | так | Ідентичність Server, діапазони SDK, топологія ролей, стан координації, контракти control-plane і worker, кодеки payload та можливості функцій. | + +Приклад: + + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/cluster/info" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" | jq '.control_plane.version, .worker_protocol.version' +``` + +`/api/cluster/info` навмисно не вимагає заголовка версії control-plane, +адже саме цей endpoint оголошує підтримувані версії. Відповідь також містить +`coordination_health`: зведення розгортання й готовності всіх namespace, +що відображає перевірки готовності Server. + +### Публічне зведення топології {#public-topology-summary} + +`GET /api/health` і `GET /api/ready` повертають об'єкт верхнього рівня +`topology` за контрактом зведення здоров'я. Він визначає вузол, який відповів +на probe, до успішної автентифікації control-plane, визначення namespace +чи повного виявлення `/api/cluster/info`. + +Публічне зведення завжди містить: + +- `topology.schema` +- `topology.version` +- `topology.current_shape` +- `topology.current_process_class` +- `topology.current_roles` +- `topology.execution_mode` +- `topology.matching_role.queue_wake_enabled` +- `topology.matching_role.shape` +- `topology.matching_role.wake_owner` +- `topology.matching_role.task_dispatch_mode` +- `topology.matching_role.partition_primitives` +- `topology.matching_role.backpressure_model` +- `topology.matching_role.discovery_limits.poll_batch_cap` +- `topology.matching_role.discovery_limits.availability_ceiling_seconds` +- `topology.matching_role.discovery_limits.wake_signal_ttl_seconds` +- `topology.matching_role.discovery_limits.workflow_task_lease_seconds` +- `topology.matching_role.discovery_limits.activity_task_lease_seconds` + +`topology.matching_role.discovery_limits` є зафіксованим числовим контрактом +matching. `poll_batch_cap` задає максимальну кількість готових рядків на poll, +`availability_ceiling_seconds` є допуском `available_at` між бекендами для +нових готових завдань за розбіжності часових позначок менш ніж секунду, +`wake_signal_ttl_seconds` є типовим TTL сигналу `CacheLongPollWakeStore`, +а `workflow_task_lease_seconds` і `activity_task_lease_seconds` є типовими +тривалостями lease завдань workflow і activity. Зменшення цих значень є зміною +протоколу, адже worker і сторонні інструменти читають їх як авторитетний +контракт matching. Перейменування поля також порушує протокол. + +`/api/ready` повертає той самий `topology`, навіть якщо загальний `status` +дорівнює `not_ready`. Probe можуть розрізняти відповіді `server_http_node`, +`scheduler_node`, `matching_node`, `execution_node` за активних перешкод bootstrap. + +Приклад: + + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/health" | jq '{ + status, + topology: { + schema: .topology.schema, + version: .topology.version, + current_shape: .topology.current_shape, + current_process_class: .topology.current_process_class, + current_roles: .topology.current_roles, + execution_mode: .topology.execution_mode, + matching_role: .topology.matching_role + } +}' +``` + +### Перешкоди готовності {#readiness-blockers} + +`GET /api/ready` повертає `status` і машиночитані `checks`. +Дві перевірки визначають безпечність оцінювання розгортання Server: + +- `checks.migrations` є перевіркою bootstrap і міграцій. Вона публікує + `repository_exists`, `pending_migrations`, `adoptable_migrations`, + `blocking_migrations`, `missing_tables`, `operator_surface`, `readiness_contract`. +- `adoptable_migrations` означає, що наявним таблицям workflow потрібне лише + прийняття історії міграцій. Server залишається готовим зі `status: "warning"`, + щоб оператор запланував прийняття до наступного проходу міграцій. +- `blocking_migrations` означає, що записи міграцій безпечного розгортання ще + потрібні. Server відхиляє роботу зі `status: "pending"` і `remediation`, + замість удавати актуальність парку. +- `operator_surface.available` і `operator_surface.required_tables` показують, + чи операторська поверхня v2 має потрібні надійні таблиці після запуску. +- `readiness_contract.version` фіксує ревізію контракту інсталяції й прийняття, + яку мають очікувати скрипти під час читання готовності. +- `checks.workflow_v2` відображає висновок для всіх namespace. За відсутніх + передумов готовності повертає `status: "blocked"`, `blocked_by`, `message` + і `remediation`, замість повідомляти про здоровий парк. + +Приклад: + + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/ready" | jq '{ + status, + migrations: { + status: .checks.migrations.status, + adoptable_migrations: .checks.migrations.adoptable_migrations, + blocking_migrations: (.checks.migrations.blocking_migrations | map(.migration)), + missing_tables: .checks.migrations.missing_tables, + operator_surface: .checks.migrations.operator_surface, + readiness_contract: .checks.migrations.readiness_contract + }, + workflow_v2: { + status: .checks.workflow_v2.status, + blocked_by: .checks.workflow_v2.blocked_by, + remediation: .checks.workflow_v2.remediation + } +}' +``` + +### Маніфест топології кластера {#cluster-topology-manifest} + +`/api/cluster/info` також повертає маніфест `topology` вузла зі схемою +`durable-workflow.v2.role-topology`. Це підтримуваний спосіб визначити +поточну форму `standalone_server`, `embedded` чи `split_control_execution`, +ролі вузла та очікування Server від `matching_role`, `shape_assignments`, +`authority_boundaries`, `failure_domains`, `scaling_boundaries`, `migration_path`. +Відповідь також публікує поточний стан безпечності розгортання цього вузла. + +Читайте маніфест так: + +- `topology.current_shape`, `topology.current_process_class`, + `topology.current_roles`, `topology.execution_mode` визначають фактичну + форму ролей вузла. Це опис вузла, що відповідає, а не всього парку. +- `topology.role_vocabulary` є фіксованим переліком допустимих ролей v2. +- `topology.matching_role.queue_wake_enabled`, `topology.matching_role.shape`, + `topology.matching_role.wake_owner`, `topology.matching_role.task_dispatch_mode`, + `topology.matching_role.partition_primitives`, `topology.matching_role.backpressure_model` + показують, чи широкий пошук готових завдань працює у worker або окремим + проходом matching, хто ним володіє, сталі осі маршрутизації та поточну межу допуску v2. +- `topology.matching_role.discovery_limits` публікує числовий контракт: + `poll_batch_cap`, `availability_ceiling_seconds`, `wake_signal_ttl_seconds`, + `workflow_task_lease_seconds`, `activity_task_lease_seconds`. Перевіряйте + відповідність розгортання цим значенням. Пакет надає ті самі ідентифікатори + в `dw server:info`, знімку метрик оператора й здоров'ї namespace. +- `topology.role_catalog` і `topology.authority_surfaces` зіставляють ролі + з інтерфейсами, надійними записами й шляхами читання для автоматизації вузла. +- `topology.shape_assignments` є машиночитаним переліком класів процесів + кожної форми. Порівнюйте поточний набір ролей із ним для визначення класу вузла. +- `topology.supported_topologies` узагальнює підтримувані сімейства розгортання + та потрібні їм класи вузлів. +- `coordination_health` узагальнює ризик розгортання й сумісності всього парку. + Окрім `status` і `http_status`, може містити `blocked_by`, `message`, + `remediation`, якщо оцінювання заблоковане міграціями чи доступністю бази. +- `coordination_health.checks[]` завжди містить зафіксований `activity_path` + поруч із `worker_compatibility`, `task_transport`, `routing_health`, + `durable_resume_paths` і перевірками проєкцій/планувальника. Це відповідник + `task_transport` для activity: він показує минулі дедлайни schedule-to-start, + start-to-close, schedule-to-close чи heartbeat без застосування тайм-ауту + (`timeout_overdue`, `oldest_timeout_overdue_at`, `max_timeout_overdue_age_ms`) + та тривалий backlog retry (`retrying`, `oldest_retrying_started_at`, + `max_retrying_age_ms`). Перейменування перевірки є зміною протоколу. +- `coordination_health.routing_drains` узагальнює draining групи build-id + namespace і черг. `queues_with_drains` і записи `build_ids` показують, + де трафік навмисно не направляється до draining worker. +- `execution_mode` розрізняє вбудоване `local_queue_worker` і виконання + через протокол `remote_worker_protocol`. +- `split_control_execution` є підтримуваною топологією продукту, а не окремим + серверним продуктом чи іншим API. + +Приклад: + + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/cluster/info" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" \ + | jq '{ + current_shape: .topology.current_shape, + current_roles: .topology.current_roles, + execution_mode: .topology.execution_mode, + matching_role: .topology.matching_role, + coordination_health: { + status: .coordination_health.status, + http_status: .coordination_health.http_status, + blocked_by: .coordination_health.blocked_by, + queues_with_drains: .coordination_health.routing_drains.queues_with_drains + } + }' +``` + +Поняття цих полів, словник ролей і шлях міграції описані в +[топології ролей Server](/docs/polyglot/server-role-topology). + +### Відмова неправильному вузлу топології {#wrong-node-topology-rejections} + +Автентифіковані маршрути відхиляють запит, якщо вузол не має HTTP поверхні +керування цього endpoint. Перевірка працює після перевірки ролі й версії +протоколу, але до визначення namespace, тому не розкриває його існування. + +У такому разі Server повертає `503` із `reason: "topology_role_unavailable"` та: + +- `current_shape`: оголошена форма топології вузла. +- `current_process_class`: оголошений клас, наприклад `scheduler_node` чи `execution_node`. +- `current_roles`: фактично наявні ролі вузла. +- `required_roles`: потрібні endpoint ролі маршруту. +- `missing_roles`: відсутня на вузлі підмножина `required_roles`. + +Маршрути control-plane повертають цей payload в обгортці площини керування. +Маршрути worker повертають ті самі поля в обгортці протоколу worker і +зберігають звичайний заголовок версії worker. + +Приклад відповіді `GET /api/workflows` від вузла лише з планувальником: + +```json +{ + "reason": "topology_role_unavailable", + "message": "This node does not host the topology roles required for this endpoint.", + "current_shape": "standalone_server", + "current_process_class": "scheduler_node", + "current_roles": ["scheduler"], + "required_roles": ["api_ingress", "control_plane"], + "missing_roles": ["api_ingress", "control_plane"] +} +``` + +`GET /api/health`, `GET /api/ready` та автентифікований `GET /api/cluster/info` +залишаються доступними для перевірки життя й виявлення навіть без поточної +HTTP поверхні керування на вузлі. + +### Перевірка bootstrap workflow {#workflow-bootstrap-gate} + +Автентифіковані маршрути зміни чи обслуговування трафіку workflow v2 також +відхиляють запити за `checks.workflow_v2.status = blocked` на вузлі. +Перевірка працює після ролі й версії протоколу, але до namespace, тому запит +під час заблокованого розгортання не дізнається про існування namespace. + +У такому разі Server повертає `503` із `reason: "workflow_v2_blocked"` та: + +- `blocked_by`: впорядкований перелік попередніх перешкод готовності, + наприклад `migrations`, що заважають безпечному обслуговуванню v2. +- `remediation`: коротка інструкція усунення перешкод для оператора, + відображена з `/api/ready` поля `checks.workflow_v2.remediation`. + +Сімейства маршрутів із перевіркою bootstrap: + +- **Запуск і зміна workflow:** усі маршрути `/api/workflows` груп запуску, + опису, команд і команд вибраного виконання, наприклад `POST /api/workflows`, + `POST /api/workflows/{workflowId}/signal/{signalName}`, + `POST /api/workflows/{workflowId}/runs/{runId}/cancel`. +- **Зміна schedule:** `POST /api/schedules`, `PUT /api/schedules/{scheduleId}`, + `DELETE /api/schedules/{scheduleId}`, `POST /api/schedules/{scheduleId}/pause`, + `POST /api/schedules/{scheduleId}/resume`, `POST /api/schedules/{scheduleId}/trigger`, + `POST /api/schedules/{scheduleId}/backfill`. +- **Адаптери bridge:** `POST /api/bridge-adapters/webhook/{adapter}`. +- **Протокол worker:** усі маршрути `/api/worker` і `/api/worker/*`, включно + з register, heartbeat і poll/complete/fail/heartbeat завдань workflow, query й activity. + +**Читання** schedule навмисно виключене для перевірки стану під час відновлення. +`GET /api/schedules`, `GET /api/schedules/{scheduleId}` і +`GET /api/schedules/{scheduleId}/history` працюють за блокування інших маршрутів. + +Control-plane повертає payload в обгортці площини керування із заголовком +`X-Durable-Workflow-Control-Plane-Version`. Worker повертає ті самі `reason`, +`blocked_by`, `remediation` в обгортці worker із заголовком +`X-Durable-Workflow-Protocol-Version`, щоб worker використовував машинну +причину замість висновків про чергу лише з `503`. + +Приклад відповіді `POST /api/workflows` за відсутньої міграції безпечності розгортання: + +```json +{ + "reason": "workflow_v2_blocked", + "message": "This node is not ready to serve workflow v2 traffic until bootstrap blockers are cleared.", + "blocked_by": ["migrations"], + "remediation": "Restore database connectivity and migrate the workflow tables before relying on workflow v2 rollout-safety health." +} +``` + +Той самий payload повертається в обгортці worker для `/api/worker/*`. +SDK worker можуть перевіряти `reason` і повторювати спроби після усунення перешкоди. + +### Системне здоров'я namespace {#namespace-scoped-system-health} + +`GET /api/system/health` є автентифікованою поверхнею безпечності розгортання +й координації одного namespace. Вимагає admin і +`X-Durable-Workflow-Control-Plane-Version: 2`, визначає namespace за +звичайними правилами запиту control-plane й повертає точний оцінений +namespace та поточний знімок `health`: + + +```bash +curl -sS "$DURABLE_WORKFLOW_SERVER_URL/api/system/health" \ + -H "Authorization: Bearer $DW_ADMIN_TOKEN" \ + -H "X-Namespace: orders-prod" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + | jq '{namespace, status: .health.status, healthy: .health.healthy}' +``` + +Тлумачте payload так: + +- `namespace`: namespace, чий стан розгортання/координації оцінено. +- `health.status` і `health.healthy`: загальний машиночитаний висновок здоров'я namespace. +- `health.checks` і `health.categories`: готовність, сумісність, проєкції й координація поверхонь. +- `health.operator_metrics`: поточні метрики черг, worker і repair namespace в тому самому знімку. +- `health.structural_limits`: фактичні структурні межі й пов'язана діагностика namespace. + +## Площина керування workflow {#workflow-control-plane} + +Маршрути workflow належать операторам і площині керування. Вони вимагають +ролі operator чи admin та `X-Durable-Workflow-Control-Plane-Version: 2`. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/workflows` | Показати екземпляри workflow з фільтрами статусу, типу, тексту query й ліміту. | +| `POST` | `/api/workflows` | Запустити екземпляр workflow. | +| `GET` | `/api/workflows/{workflowId}` | Описати поточне виконання ID workflow. | +| `GET` | `/api/workflows/{workflowId}/debug` | Повернути обмежену діагностику застряглого виконання. | +| `GET` | `/api/workflows/{workflowId}/runs` | Показати виконання ID workflow. | +| `GET` | `/api/workflows/{workflowId}/runs/{runId}` | Описати конкретне виконання. | +| `GET` | `/api/workflows/{workflowId}/runs/{runId}/debug` | Повернути обмежену діагностику вибраного виконання. | +| `GET` | `/api/workflows/{workflowId}/runs/{runId}/history` | Прочитати події історії сторінками. | +| `GET` | `/api/workflows/{workflowId}/runs/{runId}/history/export` | Експортувати архівний набір replay виконання. | + +Запити запуску використовують незалежну від мови форму control-plane: + + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/workflows" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "workflow_type": "orders.fulfillment", + "workflow_id": "order-1001", + "task_queue": "orders", + "input": ["order-1001"], + "memo": {"source": "api-reference"}, + "search_attributes": {"CustomerId": "cust-42"}, + "duplicate_policy": "reject" + }' +``` + +### Команди workflow {#workflow-commands} + +Server також надає кооперативні маршрути `request-cancellation`. +[Кооперативне скасування](/docs/polyglot/cancellation/) описує контракт +обмеженого очищення й вимоги worker. + +Команди екземпляра діють на поточне виконання ID workflow: + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `POST` | `/api/workflows/{workflowId}/signal/{signalName}` | Надіслати signal. | +| `POST` | `/api/workflows/{workflowId}/query/{queryName}` | Виконати query лише для читання. | +| `POST` | `/api/workflows/{workflowId}/update/{updateName}` | Подати чи виконати update. | +| `POST` | `/api/workflows/{workflowId}/cancel` | Негайно закрити виконання як скасоване без продовження очищення workflow. | +| `POST` | `/api/workflows/{workflowId}/terminate` | Примусово завершити. | +| `POST` | `/api/workflows/{workflowId}/repair` | Попросити Server виправити застряглий стан, придатний до retry. | +| `POST` | `/api/workflows/{workflowId}/archive` | Архівувати закрите виконання workflow. | + +Команди вибраного виконання явно відхиляють історичне чи неправильне виконання: + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/signal/{signalName}` | Надіслати signal лише для поточного вибраного виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/query/{queryName}` | Виконати query вибраного виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/update/{updateName}` | Подати чи виконати update лише для поточного вибраного виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/cancel` | Скасувати лише поточне вибране виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/terminate` | Примусово завершити лише поточне вибране виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/repair` | Виправити лише поточне вибране виконання. | +| `POST` | `/api/workflows/{workflowId}/runs/{runId}/archive` | Архівувати лише поточне й закрите вибране виконання. | + +Команди з даними від викликача використовують масив `input`. SDK Python і +PHP кодують незалежні від мови обгортки payload. Прямі HTTP виклики мають +надсилати значення JSON, які цільовий workflow чи activity може декодувати. + +## Namespace і сховище {#namespace-and-storage} + +Маршрути namespace вимагають operator чи admin. Зміни namespace та +зовнішнього сховища вимагають admin. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/namespaces` | Показати namespace. | +| `POST` | `/api/namespaces` | Створити namespace. | +| `GET` | `/api/namespaces/{namespace}` | Описати retention, метадані й політику сховища namespace. | +| `PUT` | `/api/namespaces/{namespace}` | Оновити метадані чи retention namespace. | +| `PUT` | `/api/namespaces/{namespace}/external-storage` | Налаштувати політику зовнішнього сховища payload namespace. | +| `POST` | `/api/storage/test` | Перевірити запис і читання малого й великого payload через налаштований драйвер сховища. | + +Політики зовнішнього сховища payload дозволяють великим обгорткам містити +сталі посилання замість байтів. Локальні політики використовують налаштований +шлях файлової системи. Об'єктні політики `s3`, `gcs`, `azure` використовують +явно налаштований диск і параметри bucket/prefix на сервері. + +### Обгортка посилання на зовнішній payload {#external-payload-reference-envelope} + +Посилання на зовнішній payload є стабільною мережевою обгорткою. SDK можуть +декодувати його у власні типи-помічники, але HTTP клієнти мають вважати +назви полів контрактом: + +| Поле | Обов'язкове | Значення | +| --- | --- | --- | +| `schema` | так | Має бути `durable-workflow.v2.external-payload-reference.v1`. Невідомі схеми відхиляються. | +| `uri` | так | Розташування об'єкта драйвера, наприклад `file:///...`, `s3://bucket/prefix/object`, `gs://bucket/prefix/object`, `azure://container/prefix/object`. | +| `sha256` | так | SHA-256 збережених закодованих байтів шістнадцятковим нижнім регістром. SDK і Server перевіряють його перед декодуванням. | +| `size_bytes` | так | Довжина збереженого закодованого payload у байтах. Розбіжність є помилкою цілісності. | +| `codec` | так | Завжди `avro`: фіксована типізована схема Value з форматом Avro single-object. Зовнішній HTTP документ залишається JSON. | +| `expires_at` | ні | Підказка строку ISO-8601 для retention/GC. За відсутності очищення належить політиці retention namespace. | + +Винесення payload залежить від порогу політики namespace. Вбудований payload +використовує звичайну обгортку, доки байти не перевищать `threshold_bytes`. +Потім драйвер записує байти, а історія зберігає обгортку посилання. +Replay і експорт історії мають явно завершуватися помилкою, якщо blob +відсутній, змінений, поза префіксом чи належить недоступному провайдеру. +Не можна мовчки заміняти відсутній об'єкт на `null`, `{}` чи порожні байти. + +Повний контракт повноважень запиту, визначення namespace, облікові дані +ролей і поля реєстрації worker описані в +[namespace, автентифікації та реєстрації worker](/docs/polyglot/namespace-auth-workers). + +## Адміністративні API каталогу сервісів {#service-catalog-admin-apis} + +Маршрути каталогу сервісів є автентифікованими admin маршрутами control-plane. +Вони використовують ті самі визначення namespace, перевірку ролі топології й +вимогу `X-Durable-Workflow-Control-Plane-Version: 2`. + +Реєструйте метадані endpoint, сервісів і операцій у namespace для каталогу +викликів між namespace. Ввід назв нечутливий до регістру, а відповіді й +пошук нормалізують назви до нижнього регістру. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/service-endpoints` | Показати endpoint сервісів поточного namespace. | +| `POST` | `/api/service-endpoints` | Створити endpoint сервісу. | +| `GET` | `/api/service-endpoints/{endpointName}` | Описати endpoint. | +| `PUT` | `/api/service-endpoints/{endpointName}` | Оновити опис чи метадані endpoint. | +| `DELETE` | `/api/service-endpoints/{endpointName}` | Видалити невикористаний endpoint. | +| `GET` | `/api/service-endpoints/{endpointName}/services` | Показати сервіси одного endpoint. | +| `POST` | `/api/service-endpoints/{endpointName}/services` | Створити сервіс endpoint. | +| `GET` | `/api/service-endpoints/{endpointName}/services/{serviceName}` | Описати сервіс. | +| `PUT` | `/api/service-endpoints/{endpointName}/services/{serviceName}` | Оновити опис чи метадані сервісу. | +| `DELETE` | `/api/service-endpoints/{endpointName}/services/{serviceName}` | Видалити невикористаний сервіс. | +| `GET` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations` | Показати операції сервісу. | +| `POST` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations` | Створити прив'язку операції. | +| `GET` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations/{operationName}` | Описати операцію. | +| `GET` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations/{operationName}/service-calls/{serviceCallId}` | Описати надійний знімок виклику сервісу. | +| `PUT` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations/{operationName}` | Оновити прив'язку операції. | +| `DELETE` | `/api/service-endpoints/{endpointName}/services/{serviceName}/operations/{operationName}` | Видалити невикористану операцію. | + +Колекції відповіді використовують масиви `service_endpoints`, `services` чи +`operations`. Окремі ресурси містять сталі назви нижнім регістром, метадані й час: + +- Endpoint повертають `id`, `namespace`, `endpoint_name`, `description`, + `metadata`, `created_at`, `updated_at`. +- Сервіси додають `endpoint_id` і `service_name`. +- Операції додають `service_id`, `operation_name`, `operation_mode`, + `handler_binding_kind`, `handler_target_reference`, `handler_binding`, + `deadline_policy`, `idempotency_policy`, `cancellation_policy`, + `retry_policy`, `boundary_policy`, `metadata`. +- Знімки викликів сервісів додають `caller_namespace`, ID workflow викликача + й пов'язаного workflow, `status`, `resolved_binding_kind`, + `resolved_target_reference`, посилання payload, знімки політик і час + життєвого циклу: `accepted_at`, `started_at`, `completed_at`, `failed_at`, `cancelled_at`. + +Запити create/update операцій мають ті самі поля JSON, що й відповіді. +`operation_mode` має бути `sync` чи `async`. `handler_binding_kind`: +`start_workflow`, `signal_workflow`, `update_workflow`, `query_workflow`, +`activity_execution` чи `invocable_http`. Нова операція має містити +`handler_target_reference` або непорожній payload `handler_binding`. + +Delete відхиляється з HTTP `409` та іменованою причиною за наявних залежностей: +`endpoint_has_services`, `service_has_operations`, `operation_has_service_calls`. + +## Адаптери bridge {#bridge-adapters} + +Адаптери bridge є обмеженими endpoint приймання даних. Вони передають події +до площини керування й повертають іменований результат без виконання коду workflow. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `POST` | `/api/bridge-adapters/webhook/{adapter}` | Запустити workflow, надіслати signal чи update з події формату webhook. | + +Приклад: + +```bash +curl -sS -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/bridge-adapters/webhook/stripe" \ + -H "Authorization: Bearer $DURABLE_WORKFLOW_AUTH_TOKEN" \ + -H "X-Namespace: default" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "action": "start_workflow", + "idempotency_key": "evt_1001", + "target": { + "workflow_type": "orders.fulfillment", + "task_queue": "orders", + "business_key": "order-1001" + }, + "input": {"order_id": "order-1001"} + }' +``` + +Використовуйте `outcome`, `reason`, `idempotency_key`, `control_plane_outcome` +замість висновків про поведінку лише за HTTP статусом. + +## Протокол worker {#worker-protocol} + +Маршрути worker вимагають ролі worker та +`X-Durable-Workflow-Protocol-Version: 1.0`. SDK worker використовують їх +усередині, а власні worker інших мов можуть реалізувати той самий протокол. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `POST` | `/api/worker/register` | Зареєструвати ідентичність worker, черги, типи workflow/activity, місткість, runtime і метадані збірки. | +| `POST` | `/api/worker/heartbeat` | Оновити видимість парку worker і факти сумісності. | +| `POST` | `/api/worker/workflow-tasks/poll` | Довго опитувати завдання workflow. | +| `POST` | `/api/worker/workflow-tasks/{taskId}/history` | Отримати історію орендованого завдання workflow сторінками. | +| `POST` | `/api/worker/workflow-tasks/{taskId}/heartbeat` | Надіслати heartbeat орендованого завдання workflow. | +| `POST` | `/api/worker/workflow-tasks/{taskId}/complete` | Завершити завдання workflow командами. | +| `POST` | `/api/worker/workflow-tasks/{taskId}/fail` | Повідомити помилку завдання workflow. | +| `POST` | `/api/worker/query-tasks/poll` | Довго опитувати завдання query, маршрутизовані Server. | +| `POST` | `/api/worker/query-tasks/{queryTaskId}/complete` | Завершити завдання query. | +| `POST` | `/api/worker/query-tasks/{queryTaskId}/fail` | Повідомити помилку чи відхилити завдання query. | +| `POST` | `/api/worker/activity-tasks/poll` | Довго опитувати завдання activity. | +| `POST` | `/api/worker/activity-tasks/{taskId}/heartbeat` | Надіслати heartbeat орендованого завдання activity. | +| `POST` | `/api/worker/activity-tasks/{taskId}/complete` | Завершити завдання activity. | +| `POST` | `/api/worker/activity-tasks/{taskId}/fail` | Повідомити помилку завдання activity. | + +Worker мають сприймати ID lease, спроб, завдань та endpoint heartbeat як +непрозорі значення, видані Server. Застарілий lease чи неправильний ID +повертає іменовану помилку протоколу worker замість мовчазного завершення роботи. + +Якщо `worker_protocol.server_capabilities.poll_status = true`, кожна відповідь +poll завдань workflow, activity і query містить машиночитаний `poll_status`. +Перевіряйте його перед полями payload конкретного маршруту: + +| `poll_status` | Типовий HTTP статус | Значення | +| --- | --- | --- | +| `leased` | `200` | Server орендував завдання, `task` містить його payload. | +| `empty` | `200` | До повернення poll не було готового відповідного завдання. | +| `throttled` | `200` | Черга видима, але ліміти допуску lease чи dispatch не дали нового завдання. | +| `unavailable` | `503` або `200` | Server не зміг безпечно скоординувати poll черги й повернув типізовану недоступність замість удавати порожню чергу. | +| `draining` | `409` | Зареєстрована група worker у drain. Server відхиляє новий lease з `reason: "worker_draining"`. | + +## Видимість парку й черг завдань {#fleet-and-task-queue-visibility} + +Ці маршрути надають операторську діагностику парку worker і допуску черг. +Вони належать площині керування. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/workers` | Показати зареєстровані worker. | +| `GET` | `/api/workers/{workerId}` | Описати worker. | +| `DELETE` | `/api/workers/{workerId}` | Скасувати реєстрацію worker. | +| `GET` | `/api/task-queues` | Показати черги завдань і стан допуску. | +| `GET` | `/api/task-queues/{taskQueue}` | Описати місткість workflow/activity/query черги. | +| `GET` | `/api/task-queues/{taskQueue}/build-ids` | Показати build ID черги. | +| `POST` | `/api/task-queues/{taskQueue}/build-ids/drain` | Позначити групу build-id як draining для припинення отримання нових завдань. | +| `POST` | `/api/task-queues/{taskQueue}/build-ids/resume` | Зняти попередній drain для відновлення отримання нових завдань. | + +Відповіді черг розрізняють відсутність worker, насичені слоти, ліміти +активних lease, бюджети dispatch і зворотний тиск query. Drain і resume +приймають JSON `{"build_id": "..."}` (або `{"build_id": null}` для +неверсіонованої групи), є ідемпотентними та зберігають намір оператора, +навіть після вилучення worker. Коли heartbeat worker бачить +`drain_intent: "draining"`, poll повертає HTTP `409` із `poll_status: "draining"` +і `reason: "worker_draining"` без оренди нових завдань. +[Розгортання build-id worker](/docs/polyglot/worker-build-id-rollout) описує +повний перехід від неверсіонованих до версіонованих worker, canary, drain і відкат. + +## Schedule й атрибути пошуку {#schedules-and-search-attributes} + +Маршрути schedule належать площині керування. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/schedules` | Показати schedule. | +| `POST` | `/api/schedules` | Створити schedule. | +| `GET` | `/api/schedules/{scheduleId}` | Описати schedule. | +| `PUT` | `/api/schedules/{scheduleId}` | Оновити специфікацію, дію, примітку, memo чи атрибути пошуку schedule. | +| `DELETE` | `/api/schedules/{scheduleId}` | Видалити schedule. | +| `POST` | `/api/schedules/{scheduleId}/pause` | Призупинити майбутні запуски. | +| `POST` | `/api/schedules/{scheduleId}/resume` | Відновити призупинений schedule. | +| `POST` | `/api/schedules/{scheduleId}/trigger` | Негайно запустити schedule. | +| `POST` | `/api/schedules/{scheduleId}/backfill` | Виконати backfill часового вікна. | +| `GET` | `/api/search-attributes` | Показати зареєстровані атрибути пошуку. | +| `POST` | `/api/search-attributes` | Зареєструвати атрибут пошуку. | +| `DELETE` | `/api/search-attributes/{name}` | Видалити атрибут пошуку. | + +Назви й типи атрибутів пошуку є контрактом namespace. Не використовуйте +атрибути з великою кількістю унікальних значень для панелей чи міток метрик. + +## Системні операції {#system-operations} + +Системні маршрути вимагають admin. Це явні операторські проходи. +В автоматизації спочатку використовуйте status, а потім pass. + +| Метод | Шлях | Призначення | +| --- | --- | --- | +| `GET` | `/api/system/health` | Повернути знімок безпечності розгортання й координації namespace у `health`. | +| `GET` | `/api/system/metrics` | Повернути обмежені метрики JSON. | +| `GET` | `/api/system/operator-metrics` | Повернути метрики оператора namespace для виконань, завдань, backlog, repair, worker і структурних меж. | +| `GET` | `/api/system/repair` | Перевірити backlog repair workflow. | +| `POST` | `/api/system/repair/pass` | Виконати один прохід repair workflow. | +| `GET` | `/api/system/activity-timeouts` | Перевірити backlog тайм-аутів activity. | +| `POST` | `/api/system/activity-timeouts/pass` | Виконати один прохід застосування тайм-аутів activity. | +| `GET` | `/api/system/retention` | Перевірити backlog очищення retention. | +| `POST` | `/api/system/retention/pass` | Виконати один прохід очищення retention. | + +`/api/system/health` найшвидше показує достатність здоров'я namespace для +подальшого трафіку. Повертає `{namespace, health}`, де `health` містить +категоризовані перевірки безпечності розгортання, вкладений знімок +`health.operator_metrics` і зведення структурних меж. + +`/api/system/operator-metrics` доповнює `/api/cluster/info` для namespace, +якщо потрібні точні backlog, вік блокування сумісності, деталі парку worker +чи інші метрики за зведеною поверхнею здоров'я. + +`/api/system/metrics` є операторською поверхнею JSON, а не endpoint збору +Prometheus. Назви й виміри метрик обмежені політикою обмеженого росту Server. + +## Контракт помилок {#error-contract} + +Відповіді помилок мають HTTP статус і машиночитану іменовану причину. +Клієнти мають перевіряти `reason` чи вкладені причини control-plane/worker, +а не текст повідомлення. + +Поширені статуси: + +| Статус | Значення | +| --- | --- | +| `400` | Відсутній чи непідтримуваний заголовок протоколу/версії, неправильний query чи непідтримуваний метод маршруту. | +| `401` | Відсутня чи неправильна автентифікація. | +| `403` | Автентифікований token не має потрібної ролі. | +| `404` | Namespace, workflow, виконання, schedule, worker чи атрибут пошуку не знайдено. | +| `409` | Дублікат чи конфлікт, наприклад уже запущений workflow або неправильне цільове виконання. | +| `422` | Перевірка не пройшла. Відповідь містить деталі полів. | +| `429` | Місткість допуску чи черги завдань вичерпана. | +| `503` | Запит потрапив на вузол без потрібних ролей топології. Повертає `reason: "topology_role_unavailable"` та `current_shape`, `current_process_class`, `current_roles`, `required_roles`, `missing_roles`. Той самий статус із `reason: "workflow_v2_blocked"`, `blocked_by`, `remediation` охоплює start/mutation workflow, зміни schedule, bridge-adapter і worker за блокування bootstrap v2. Читання schedule (`GET /api/schedules`, `GET /api/schedules/{scheduleId}`, `GET /api/schedules/{scheduleId}/history`) доступне для перевірки під час відновлення. | +| `500` | Помилка Server. Повторюйте лише ідемпотентну операцію чи операцію з ключем ідемпотентності. | + +Відповіді перевірки містять `reason: "validation_failed"` і `errors` чи +`validation_errors`. Відповіді команд workflow також відображають деталі +перевірки й операції у вкладеному `control_plane`. + +## Дивіться також {#see-also} + +- [Посібник Server](/docs/polyglot/server) +- [Namespace, автентифікація та реєстрація worker](/docs/polyglot/namespace-auth-workers) +- [Протокол worker](/docs/polyglot/worker-protocol) +- [Допуск черг завдань](/docs/polyglot/task-queue-admission) +- [Зовнішнє виконання](/docs/polyglot/external-execution) +- [Довідник команд CLI](/docs/polyglot/cli-reference) diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server.md new file mode 100644 index 0000000000..c16698517f --- /dev/null +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/server.md @@ -0,0 +1,1168 @@ +--- +sidebar_position: 2 +title: Server +description: Розгортання й налаштування самостійного Durable Workflow Server. +tags: + - server + - control-plane + - operations + - polyglot +keywords: + - Durable Workflow Server + - самостійний Server + - API площини керування + - міжмовні workflow +--- + +# Server {#server} + +Опублікований Durable Workflow Server є самостійним сервісом оркестрації +workflow, реалізованим на PHP. Він надає той самий надійний рушій, що й +вбудований пакет PHP, через незалежні від мови HTTP+JSON площину керування +та протокол worker. PHP тут є деталлю реалізації, а не вимогою до мови застосунку. + +## Це лише для команд PHP? {#is-this-only-for-php-teams} + +Ні. PHP, Python і Rust мають офіційні SDK. Команда застосунку лише на Python +чи Rust може розгорнути опублікований Server як інфраструктуру, писати +workflow й activity своїм SDK та спілкуватися публічним протоколом. +Її застосунок не вбудовує Laravel і не стає застосунком Laravel. + +Шляхи впровадження відокремлені: + +- **Самостійний Server:** оператор розгортає опублікований образ, базу, + чергу/кеш, планувальник і ролі API. Команда запускає worker PHP, Python чи + Rust проти цього endpoint. Server володіє станом оркестрації та публічною + межею control-plane/worker. +- **Вбудований режим:** Laravel застосунок встановлює PHP пакет workflow + і володіє рушієм через власні черги, базу, конфігурацію й розгортання. + Це перевага інтеграції Laravel, а не передумова самостійного Server. +- **Керований Cloud:** Durable Workflow Cloud експлуатує runtime оркестрації, + сховище, розміщення й відновлення. Команда запускає клієнти SDK і worker + проти наданого namespace Cloud. Вона не розгортає цей Server для Cloud і + не підключає до нього власний Server. Див. [керований runtime Cloud](/docs/polyglot/cloud-control-plane/). + +Усі три варіанти використовують ті самі поняття надійного виконання. +Зовнішні worker самостійного Server і Cloud використовують сталі рядкові +назви типів workflow/activity та спільну обгортку кодека. Тому дочірній +workflow чи activity між мовами зберігає форму payload без серіалізації PHP. + +Для вибору між самостійним Server і вбудованим пакетом почніть із +[режимів розгортання](/docs/polyglot/deployment-modes). +Ця сторінка описує дистрибутив сервісного режиму. + +Використовуйте самостійний Server, якщо потрібні: +- **Міжмовні workflow:** worker workflow й activity PHP, Python і Rust зі спільним runtime +- **Оркестрація мікросервісів:** сервіси різними мовами +- **Централізований runtime workflow:** кілька застосунків на одному рушії +- **Середовища без Laravel:** Durable Workflow поза Laravel + +Якщо ви вже використовуєте вбудовану v2 у Laravel, скористайтеся +[посібником переходу до Server](/docs/polyglot/embedded-to-server): підготуйте +ключі типів, розгорніть Server поруч, підключіть worker і направляйте лише +нові запуски до Server. Під час переходу тримайте поруч +[режими розгортання](/docs/polyglot/deployment-modes), щоб ID, результати +команд, семантика завдань і володіння runtime залишалися явними. + +[Сумісність і маршрутизація worker](/docs/polyglot/worker-compatibility-routing) +допомагають розгортати групи збірок, виконувати drain старих груп і +зберігати прив'язку тривалих виконань до сумісних виконавців під час відкату. + +[Топологія ролей Server](/docs/polyglot/server-role-topology) визначає +словник ролей, класи процесів, повноваження, області відмов і шлях міграції, +які публікує `GET /api/cluster/info`. + +## Швидкий початок {#quick-start} + +### Опублікований образ і SQLite {#published-image--sqlite} + +Найшвидший спосіб запуску без вихідного коду: опублікований образ Docker. +Цей quickstart використовує SQLite, черги бази даних і файловий кеш +усередині контейнера. Змонтуйте `/app/database`, щоб bootstrap і API +використовували той самий файл SQLite: + +```bash +server_image=%%artifact.serverDockerHubImage%% +export DW_AUTH_TOKEN=dev-token + +docker volume create durable-workflow-server-quickstart + +docker run --rm \ + -v durable-workflow-server-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$server_image" server-bootstrap + +docker rm -f durable-workflow-server >/dev/null 2>&1 || true +docker run -d --name durable-workflow-server \ + -p 8080:8080 \ + -v durable-workflow-server-quickstart:/app/database \ + -e DW_AUTH_DRIVER=token \ + -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \ + "$server_image" + +until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done +curl http://localhost:8080/api/health +curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \ + http://localhost:8080/api/cluster/info \ + | jq '.topology | {current_shape, current_roles, execution_mode}' +``` + +Це запускає один контейнер API й створює типовий namespace. Достатньо для +локальних worker Python SDK і перевірок CLI. Опублікований Compose нижче +дає MySQL, Redis, окремі контейнери worker і планувальника та ближче +до production середовище для тренування. + +### Опублікований образ і Compose {#published-image--compose} + +Використовуйте опублікований Compose для багатоконтейнерного стека +з MySQL і Redis без вихідного коду: + +```bash +curl -fsSLO https://raw.githubusercontent.com/durable-workflow/server/main/docker-compose.published.yml + +server_image=%%artifact.serverDockerHubImage%% +export DW_AUTH_TOKEN=dev-token + +env DW_SERVER_IMAGE="$server_image" docker compose \ + -f docker-compose.published.yml up -d --wait +curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \ + http://localhost:8080/api/cluster/info \ + | jq '.topology | {current_shape, current_roles, execution_mode}' +``` + +### Порти {#ports} + +| Сервіс | Порт | Призначення | +|---------|------|---------| +| Server API | 8080 | Endpoint площини керування й протоколу worker | +| MySQL | 3306 | База даних, порт відкритий для зручності розробки | +| Redis | 6379 | Кеш і черга, порт відкритий для зручності розробки | + +## Конфігурація {#configuration} + +Server налаштовується змінними середовища. Основні параметри наведені нижче, +а повний операторський контракт `DW_*` описано в +[довіднику конфігурації Server](/docs/polyglot/server-config-reference). + +### База даних {#database} + +```bash +DB_CONNECTION=mysql +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=workflow +DB_USERNAME=workflow +DB_PASSWORD=secret +``` + +Підтримуються MySQL 8.0+, PostgreSQL 13+, SQLite 3.35+. + +### Кеш і черга {#cache-and-queue} + +```bash +CACHE_STORE=redis +QUEUE_CONNECTION=redis + +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=null +REDIS_DB=0 +``` + +Кеш має підтримувати [атомарні блокування](https://laravel.com/docs/12.x/cache#atomic-locks). +Драйвери черг: Redis, Amazon SQS, Beanstalkd, database. + +Атомарні блокування кешу потрібні для серверних +[лімітів допуску черг](/docs/polyglot/task-queue-admission) і зворотного тиску +query. Використовуйте Redis у багатовузловому розгортанні, якщо допуск +workflow, activity чи query має діяти між усіма процесами Server. + +### Автентифікація {#authentication} + +Server підтримує три режими автентифікації: + +**За token** (типово): + +```bash +DW_AUTH_DRIVER=token +DW_AUTH_TOKEN=your-secret-token-here +``` + +Усі запити мають надсилати `Authorization: Bearer your-secret-token-here`. + +Для мінімальних повноважень налаштуйте token окремих ролей замість одного спільного: + +```bash +DW_AUTH_DRIVER=token +DW_WORKER_TOKEN=worker-secret +DW_OPERATOR_TOKEN=operator-secret +DW_ADMIN_TOKEN=admin-secret +``` + +Token worker дозволяє register, poll, heartbeat і завершення роботи. +Token operator дозволяє start, list, signal, query, update, repair, cancel, +terminate, archive і спостереження workflow. Token admin дозволяє +адміністративні endpoint, наприклад керування namespace і retention. + +**Підпис HMAC**: + +```bash +DW_AUTH_DRIVER=signature +DW_SIGNATURE_KEY=your-signature-secret +``` + +Запити мають містити `X-Signature`, обчислений як +`hash_hmac('sha256', request_body, DW_SIGNATURE_KEY)`. +Server також приймає ключі підпису окремих ролей: + +```bash +DW_AUTH_DRIVER=signature +DW_WORKER_SIGNATURE_KEY=worker-signature-secret +DW_OPERATOR_SIGNATURE_KEY=operator-signature-secret +DW_ADMIN_SIGNATURE_KEY=admin-signature-secret +``` + +**Без автентифікації** (лише для розробки): + +```bash +DW_AUTH_DRIVER=none +``` + +⚠️ **Не використовуйте `none` у production.** Усі endpoint стають публічно доступними. + +### Пакет Workflow {#workflow-package} + +Образ Docker встановлює `durable-workflow/workflow`. Вибір версії: + +```bash +# Build-time arg (set in docker-compose.yml or pass to docker build) +WORKFLOW_PACKAGE_REF=v2 # branch, tag, or commit +WORKFLOW_PACKAGE_SOURCE= # custom Git remote (optional) +``` + +### Retention {#retention} + +Налаштуйте строк доступності завершених workflow для query: + +```bash +DW_HISTORY_RETENTION_DAYS=30 +``` + +Після завершення retention workflow видаляються. Налаштовуйте retention namespace через API. + +### Namespace {#namespaces} + +Команда `server-bootstrap` виконує міграції й створює namespace `default`. +`DW_DEFAULT_NAMESPACE` змінює namespace запитів без заголовка namespace: + +```bash +DW_DEFAULT_NAMESPACE=default +``` + +Створюйте namespace через API: + +```bash +curl -X POST http://localhost:8080/api/namespaces \ + -H "Authorization: Bearer $TOKEN" \ + -H "X-Durable-Workflow-Control-Plane-Version: 2" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "production", + "description": "Production workflows", + "retention_days": 90 + }' +``` + +## Перевірки здоров'я {#health-checks} + +### Здоров'я API {#api-health} + +```bash +curl http://localhost:8080/api/health +``` + +Повертає `200 OK` із: + +```json +{ + "status": "serving", + "timestamp": "2026-04-15T12:00:00Z", + "checks": { + "database": "ok" + }, + "topology": { + "schema": "durable-workflow.v2.role-topology", + "version": 4, + "current_shape": "standalone_server", + "current_process_class": "server_http_node", + "current_roles": ["api_ingress", "control_plane", "matching", "history_projection"], + "execution_mode": "remote_worker_protocol", + "matching_role": { + "queue_wake_enabled": true, + "shape": "in_worker", + "wake_owner": "worker_loop", + "task_dispatch_mode": "poll", + "partition_primitives": ["connection", "queue", "compatibility", "namespace"], + "backpressure_model": "lease_ownership", + "discovery_limits": { + "poll_batch_cap": 100, + "availability_ceiling_seconds": 1, + "wake_signal_ttl_seconds": 60, + "workflow_task_lease_seconds": 300, + "activity_task_lease_seconds": 300 + } + } + } +} +``` + +### Публічне зведення топології {#public-topology-summary} + +Неавтентифіковані `GET /api/health` і `GET /api/ready` публікують зведення +`topology` вузла. Цей блок менший за `/api/cluster/info`, але містить поля +для визначення вузлів розділених ролей до автентифікації control-plane чи namespace: + +- `topology.schema` +- `topology.version` +- `topology.current_shape` +- `topology.current_process_class` +- `topology.current_roles` +- `topology.execution_mode` +- `topology.matching_role.queue_wake_enabled` +- `topology.matching_role.shape` +- `topology.matching_role.wake_owner` +- `topology.matching_role.task_dispatch_mode` +- `topology.matching_role.partition_primitives` +- `topology.matching_role.backpressure_model` +- `topology.matching_role.discovery_limits.poll_batch_cap` +- `topology.matching_role.discovery_limits.availability_ceiling_seconds` +- `topology.matching_role.discovery_limits.wake_signal_ttl_seconds` +- `topology.matching_role.discovery_limits.workflow_task_lease_seconds` +- `topology.matching_role.discovery_limits.activity_task_lease_seconds` + +`topology.matching_role.discovery_limits` є зафіксованим числовим контрактом +matching пакета workflow. `poll_batch_cap` задає максимальну кількість готових +рядків на poll, `availability_ceiling_seconds` є допуском `available_at` +між бекендами для нових готових завдань за розбіжності часу менш ніж секунду, +`wake_signal_ttl_seconds` є типовим TTL `CacheLongPollWakeStore`, +`workflow_task_lease_seconds` і `activity_task_lease_seconds` є типовими +тривалостями lease workflow і activity. Зменшення значення є зміною протоколу, +адже worker та сторонні інструменти читають цей авторитетний контракт. + +Те саме зведення є в `/api/ready` навіть за неготового розгортання. +Probe розрізняють відповіді `server_http_node`, `scheduler_node`, +`matching_node`, `execution_node` за активних перешкод bootstrap. + +### Готовність {#readiness} + +```bash +curl http://localhost:8080/api/ready +``` + +`/api/ready` є перевіркою розгортання. Повертає `200 OK` лише якщо +передумови bootstrap і здоров'я безпечного розгортання готові чи мають warning. + +Тлумачте машиночитані поля так: + +- `checks.migrations.repository_exists` і `checks.migrations.pending_migrations` + показують наявність сховища міграцій і ще не застосовані записи. +- `checks.migrations.adoptable_migrations` перелічує міграції створення таблиць, + яким потрібне лише прийняття в історію міграцій. Це `warning`, тож Server + залишається готовим, поки оператор планує прийняття. +- `checks.migrations.blocking_migrations` перелічує записи безпечності + розгортання, потрібні до допуску трафіку. Непорожній масив блокує готовність + із `checks.migrations.status = "pending"`. +- `checks.migrations.missing_tables` показує відсутні надійні таблиці, + `checks.migrations.operator_surface` показує доступність поверхні v2 + для пояснення безпечності після запуску Server. +- `checks.migrations.readiness_contract.version` фіксує ревізію контракту + запуску й прийняття міграцій для скриптів. +- `checks.workflow_v2` відображає висновок всіх namespace. Якщо оцінювання + ще неможливе, повертає `status: "blocked"`, `blocked_by`, `message`, + `remediation`, щоб оператор виправив передумову замість симптомів черги. + +### Перевірка bootstrap workflow {#workflow-bootstrap-gate} + +`checks.workflow_v2.status: "blocked"` також блокує маршрути, а не лише +сигналізує готовність. Під час блокування bootstrap v2 Server відхиляє +start/mutation workflow, зміни schedule, bridge-adapter і worker з HTTP `503` +та `reason: "workflow_v2_blocked"`. Перевірка йде після ролі й версії +протоколу, але до namespace, тому не розкриває його існування. + +Відповідь перевірки завжди містить: + +- `reason: "workflow_v2_blocked"` для перевірки машинного імені замість тексту. +- `blocked_by`: впорядкований список передумов готовності, наприклад `migrations`. +- `remediation`: інструкція оператора для усунення перешкод із + `/api/ready` поля `checks.workflow_v2.remediation`. + +Сімейства маршрутів із перевіркою bootstrap: + +- **Запуск і зміна workflow:** маршрути start, команд і команд вибраного + виконання `/api/workflows`. +- **Зміна schedule:** `POST /api/schedules`, `PUT /api/schedules/{scheduleId}`, + `DELETE /api/schedules/{scheduleId}`, `POST /api/schedules/{scheduleId}/pause`, + `POST /api/schedules/{scheduleId}/resume`, `POST /api/schedules/{scheduleId}/trigger`, + `POST /api/schedules/{scheduleId}/backfill`. +- **Адаптери bridge:** `POST /api/bridge-adapters/webhook/{adapter}`. +- **Протокол worker:** усі `/api/worker` і `/api/worker/*`, включно з register, + heartbeat і діями workflow-task, query-task, activity-task. Payload + повертається в обгортці worker зі збереженням заголовка + `X-Durable-Workflow-Protocol-Version`, щоб SDK читав той самий + `reason: "workflow_v2_blocked"`, що й у площині керування. + +**Читання** schedule навмисно виключене для перевірки стану під час відновлення: +`GET /api/schedules`, `GET /api/schedules/{scheduleId}` і +`GET /api/schedules/{scheduleId}/history` працюють за блокування інших маршрутів. + +### Можливості Server {#server-capabilities} + +```bash +curl http://localhost:8080/api/cluster/info \ + -H "Authorization: Bearer $TOKEN" +``` + +Повертає версію збірки, підтримувані SDK, можливості рушія, політику +сумісності клієнтів та незалежно версіоновані маніфести control-plane і worker: + +```json +{ + "server_id": "server-1", + "version": "2.0.0", + "default_namespace": "default", + "supported_sdk_versions": { + "php": ">=1.0", + "python": ">=0.2,<1.0", + "cli": ">=0.1,<1.0" + }, + "client_compatibility": { + "schema": "durable-workflow.v2.client-compatibility", + "version": 1, + "authority": "protocol_manifests", + "top_level_version_role": "informational", + "fail_closed": true + }, + "capabilities": { + "workflow_tasks": true, + "activity_tasks": true, + "signals": true, + "queries": true, + "updates": true, + "schedules": true, + "child_workflow_retry_policy": true, + "child_workflow_timeouts": true, + "payload_codecs": ["avro"], + "response_compression": ["gzip", "deflate"] + }, + "control_plane": { + "version": "2", + "header": "X-Durable-Workflow-Control-Plane-Version", + "request_contract": { "schema": "durable-workflow.v2.control-plane-request.contract", "version": 1, "...": "..." }, + "response_contract": { "schema": "durable-workflow.v2.control-plane-response.contract", "version": 1, "...": "..." } + }, + "worker_protocol": { + "version": "1.0", + "server_capabilities": { + "long_poll_timeout": 30, + "supported_workflow_task_commands": [ + "complete_workflow", + "fail_workflow", + "continue_as_new", + "schedule_activity", + "start_timer", + "start_child_workflow" + ], + "workflow_task_poll_request_idempotency": true, + "poll_status": true, + "history_page_size_default": 500, + "history_page_size_max": 1000, + "activity_retry_policy": true, + "activity_timeouts": true, + "child_workflow_retry_policy": true, + "child_workflow_timeouts": true, + "parent_close_policy": true, + "non_retryable_failures": true, + "response_compression": ["gzip", "deflate"], + "history_compression": { + "supported_encodings": ["gzip"], + "compression_threshold": 8192 + } + } + } +} +``` + +`client_compatibility.authority: "protocol_manifests"` є правилом перевірки +клієнтів. Верхній `version` є ідентичністю збірки. CLI та SDK мають відхиляти +роботу за відсутніх чи непідтримуваних `control_plane.version`, +`control_plane.request_contract` або `worker_protocol.version`. + +### Топологія ролей і форма розгортання {#role-topology-and-deployment-shape} + +Довідник кожного поля маніфесту міститься в +[топології ролей Server](/docs/polyglot/server-role-topology). Цей розділ +зберігає приклад `cluster/info`. Окрема сторінка збирає форми, повноваження, +області відмов, межі масштабування й контракт шляху міграції. + +`GET /api/cluster/info` також публікує маніфест `topology`: машиночитану +мапу ролей вузла, що відповів. Оператор та автоматизація читають один +контракт замість виведення обов'язків із назв контейнерів чи інструкцій. + +```json +{ + "topology": { + "schema": "durable-workflow.v2.role-topology", + "version": 2, + "supported_shapes": [ + "embedded", + "standalone_server", + "split_control_execution" + ], + "role_vocabulary": [ + "api_ingress", + "control_plane", + "matching", + "history_projection", + "scheduler", + "execution_plane" + ], + "current_shape": "standalone_server", + "current_process_class": "server_http_node", + "current_roles": [ + "api_ingress", + "control_plane", + "matching", + "history_projection" + ], + "execution_mode": "remote_worker_protocol", + "matching_role": { + "queue_wake_enabled": true, + "shape": "in_worker", + "wake_owner": "worker_loop", + "task_dispatch_mode": "poll", + "partition_primitives": [ + "connection", + "queue", + "compatibility", + "namespace" + ], + "backpressure_model": "lease_ownership", + "discovery_limits": { + "poll_batch_cap": 100, + "availability_ceiling_seconds": 1, + "wake_signal_ttl_seconds": 60, + "workflow_task_lease_seconds": 300, + "activity_task_lease_seconds": 300 + } + }, + "shape_assignments": { + "embedded": { + "process_classes": [ + { + "name": "application_process", + "roles": [ + "control_plane", + "matching", + "history_projection", + "scheduler", + "execution_plane" + ] + } + ] + }, + "standalone_server": { + "process_classes": [ + { + "name": "server_http_node", + "roles": [ + "api_ingress", + "control_plane", + "matching", + "history_projection" + ] + }, + { + "name": "scheduler_node", + "roles": ["scheduler"] + }, + { + "name": "worker_node", + "roles": ["execution_plane"] + } + ] + }, + "split_control_execution": { + "process_classes": [ + { + "name": "ingress_node", + "roles": ["api_ingress"] + }, + { + "name": "control_plane_node", + "roles": ["control_plane", "history_projection"] + }, + { + "name": "scheduler_node", + "roles": ["scheduler"] + }, + { + "name": "matching_node", + "roles": ["matching"] + }, + { + "name": "execution_node", + "roles": ["execution_plane"] + } + ] + } + }, + "authority_boundaries": { + "control_plane": { + "writes": [ + "workflow_instances", + "workflow_runs.status", + "workflow_tasks.lifecycle" + ] + }, + "execution_plane": { + "writes": [ + "workflow_tasks.outcomes", + "activity_attempts", + "worker_compatibility_heartbeats" + ] + }, + "matching": { + "writes": [ + "workflow_tasks.leases", + "activity_tasks.leases" + ] + }, + "history_projection": { + "writes": [ + "history_events", + "workflow_run_summaries", + "workflow_history_exports" + ] + }, + "scheduler": { + "writes": [ + "workflow_schedules.fire_state", + "workflow_starts.scheduled" + ] + }, + "api_ingress": { + "writes": ["worker_registrations"] + } + }, + "failure_domains": { + "control_plane_down": { + "effect": "workers_continue_claimed_tasks_only_until_lease_expiry", + "operator_signal": "operator_commands_fail_fast" + }, + "execution_plane_down": { + "effect": "ready_tasks_accumulate_without_loss", + "operator_signal": "operators_see_ready_depth_growth" + }, + "matching_down": { + "effect": "claim_falls_back_to_direct_ready_task_discovery", + "operator_signal": "ready_depth_rises_while_claim_rate_falls" + }, + "history_projection_down": { + "effect": "projection_reads_may_stale_while_durable_writes_continue", + "operator_signal": "projection_lag_seconds_may_increase" + }, + "scheduler_down": { + "effect": "scheduled_workflows_stop_firing_and_record_missed_runs", + "operator_signal": "operators_see_missed_schedule_state" + }, + "api_ingress_down": { + "effect": "external_http_traffic_stops_at_the_edge", + "operator_signal": "embedded_in_process_calls_may_continue" + } + }, + "scaling_boundaries": { + "api_ingress": "incoming_http_request_rate", + "control_plane": "operator_commands_and_run_lifecycle_transitions", + "matching": "ready_task_rate_and_poller_count", + "history_projection": "durable_event_rate", + "scheduler": "active_schedule_count", + "execution_plane": "workflow_and_activity_task_rate" + }, + "migration_path": [ + { + "step": "audit_role_boundaries", + "result": "tooling flags cross-role writes before runtime shape changes", + "reversible": true + }, + { + "step": "expose_role_bindings", + "result": "container seams allow out-of-process adapters without patching the package", + "reversible": true + }, + { + "step": "introduce_dedicated_matching_shape", + "result": "matching can run as its own process class without changing the claim contract", + "reversible": true + }, + { + "step": "split_history_projection", + "result": "history and projections can move out of process without introducing a second writer", + "reversible": true + }, + { + "step": "split_scheduler", + "result": "schedule firing can move behind leader election while single-replica deployments stay legal", + "reversible": true + }, + { + "step": "optional_execution_partitioning", + "result": "workers can partition by namespace, connection, queue, and compatibility", + "reversible": true + } + ], + "kernel_invariants": [ + { + "id": "single_persistence_engine", + "summary": "one workflow database backs every topology shape; role split does not introduce a second persistence engine", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + }, + { + "id": "single_worker_protocol", + "summary": "one HTTP worker protocol carries claim, complete, fail, and heartbeat traffic across every topology; role split does not fork the worker contract", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + }, + { + "id": "single_history_writer", + "summary": "history_events has exactly one durable writer per logical event regardless of where the history/projection role runs", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + }, + { + "id": "single_control_authority_per_run", + "summary": "every mutation of a given workflow run routes through one control-plane authority; per-run row locks serialise transitions across replicas", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + }, + { + "id": "embedded_topology_remains_supported", + "summary": "the embedded shape where one process fills every role MUST stay legal; existing embedded hosts are never forced to migrate", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + }, + { + "id": "role_split_is_topology_only", + "summary": "splitting roles is a topology change, not a product fork; collapsing the roles back onto a single process is always a legal topology", + "applies_to": ["embedded", "standalone_server", "split_control_execution"] + } + ] + }, + "coordination_health": { + "schema": "durable-workflow.v2.coordination-health.contract", + "version": 2, + "namespace_scope": "all_namespaces", + "status": "ok", + "http_status": 200, + "warning_checks": [], + "error_checks": [], + "categories": { + "correctness": "ok" + }, + "checks": [ + { + "name": "worker_compatibility", + "status": "ok", + "category": "correctness", + "message": null + }, + { + "name": "activity_path", + "status": "ok", + "category": "correctness", + "message": null + } + ], + "routing_drains": { + "queues_with_drains": 0, + "draining_build_id_count": 0, + "active_worker_count": 0, + "draining_worker_count": 0, + "stale_worker_count": 0, + "queues": [] + } + } +} +``` + +`topology.version` є версією схеми ролей, а не верхньою версією збірки +Server. Перевіряйте її до використання полів новішої ревізії. Поточний +контракт містить `supported_shapes`, `role_vocabulary`, `current_shape`, +`current_process_class`, `current_roles`, `execution_mode`, `matching_role`, +`role_catalog`, `shape_assignments`, `authority_boundaries`, +`authority_surfaces`, `failure_domains`, `supported_topologies`, +`scaling_boundaries`, `migration_path`, `kernel_invariants`. + +Читайте поля так: + +- `supported_shapes` називає дозволені топології продукту. +- `role_vocabulary` є фіксованим словником ролей v2 для автоматизації й діагностики. +- `current_shape`, `current_process_class`, `current_roles` описують поточний + вузол. Використовуйте `current_process_class` як його оголошену ідентичність + і порівнюйте ролі з `shape_assignments` для її перевірки. +- `execution_mode` розрізняє вбудовану локальну чергу `local_queue_worker` + та протокол worker самостійного Server `remote_worker_protocol`. +- `matching_role.queue_wake_enabled`, `matching_role.shape`, + `matching_role.wake_owner`, `matching_role.task_dispatch_mode`, + `matching_role.partition_primitives`, `matching_role.backpressure_model` + визначають шлях пробудження у worker чи окремий цикл repair/matching, + сталі осі маршрутизації та надійну межу допуску matching. +- `matching_role.discovery_limits` фіксує числові значення пакета: + `poll_batch_cap` (максимальна кількість готових рядків на poll), + `availability_ceiling_seconds` (допуск `available_at` між бекендами + за розбіжності часу менш ніж секунду), `wake_signal_ttl_seconds` + (типовий TTL пробудження long-poll), `workflow_task_lease_seconds` + і `activity_task_lease_seconds` (типові lease). Оператор перевіряє + відповідність розгортання контракту без пошуку в коді пакета. + Зменшення значення є зміною протоколу. +- `role_catalog` і `authority_surfaces` визначають інтерфейси й надійні + шляхи змін, якими володіє кожна роль ревізії маніфесту. +- `shape_assignments` зіставляє форми з допустимими класами процесів і ролями. +- `supported_topologies` узагальнює сімейства розгортання й потрібні класи вузлів. +- `authority_boundaries` називає поверхні запису кожної ролі для виявлення + відхилень повноважень до розділення розгортання. +- `failure_domains` описує першу видиму оператору ознаку погіршення за втрати ролі. +- `scaling_boundaries` називає основний вимір навантаження кожної окремої ролі. +- Автентифіковані маршрути відхиляють вузол без потрібної HTTP поверхні: + `503`, `reason: "topology_role_unavailable"`, `current_shape`, + `current_process_class`, `current_roles`, `required_roles`, `missing_roles`. + Клієнт може направити запит на вузол із потрібною поверхнею. +- `coordination_health` є зведенням безпечності розгортання всього парку + зі scope `all_namespaces`, статусом і HTTP станом, нормалізованими назвами + warning/error перевірок готовності та `blocked_by`, `message`, `remediation` + за блокування оцінювання попередніми проблемами готовності. +- `coordination_health.checks[]` завжди містить зафіксований `activity_path` + поруч із `worker_compatibility`, `task_transport`, `routing_health`, + `durable_resume_paths` і проєкціями/планувальником. Це відповідник + `task_transport` для activity: він показує минулі дедлайни schedule-to-start, + start-to-close, schedule-to-close чи heartbeat без застосування тайм-ауту + (`timeout_overdue`, `oldest_timeout_overdue_at`, `max_timeout_overdue_age_ms`) + та тривалий backlog retry (`retrying`, `oldest_retrying_started_at`, + `max_retrying_age_ms`). Перейменування перевірки є зміною протоколу. +- `coordination_health.routing_drains` узагальнює draining групи build-id + черг і namespace. `queues_with_drains` понад нуль означає навмисне + утримання трафіку від хоча б однієї draining групи. +- `migration_path` перелічує кроки переходу від самостійного дистрибутива + до ізольованіших ролей зі збереженням одного рушія. `reversible: true` + дозволяє повернення до менш ізольованої форми. +- `kernel_invariants` перелічує гарантії ядра для всіх форм: + `single_persistence_engine`, `single_worker_protocol`, `single_history_writer`, + `single_control_authority_per_run`, `embedded_topology_remains_supported`, + `role_split_is_topology_only`. `applies_to` називає відповідні форми. + Автоматизація МОЖЕ перевіряти збереження ядра до зміни топології. + +Так розділення ролей залишається зміною топології зі спільними рушієм і +control-plane API. Під час переходу від вузького парку `standalone_server` +до явнішого `split_control_execution` оператор читає ту саму поверхню. +`current_shape`, `current_roles`, `execution_mode`, `matching_role`, +`shape_assignments`, `authority_boundaries`, `failure_domains`, +`scaling_boundaries`, `migration_path` версіонуються одним маніфестом, +щоб інструменти розгортання оцінювали ту саму топологію, яку надає Server. + +Це обмеження також надається машиночитано через `topology.kernel_invariants`. +Автоматизація може до зміни перевірити відсутність другого рушія зберігання, +розгалуженого протоколу worker, другого записувача історії чи незворотної міграції. + +Перевірка ролі маршруту стосується лише автентифікованих API й worker. +`GET /api/health`, `GET /api/ready`, автентифікований `GET /api/cluster/info` +працюють для виявлення, життя й топології навіть на `scheduler_node`, +`matching_node`, `execution_node` без поточної HTTP поверхні керування. + +Для незалежних від носія зовнішніх обробників endpoint також публікує +`worker_protocol.external_execution_surface_contract`. Маніфест визначає +[зовнішнє виконання рівня activity](/docs/polyglot/external-execution), +посилається на обгортки вводу/результату та залишає replay, `ContinueAsNew`, +порядок signal/update/query й тлумачення історії справжнім runtime. + +Примітки щодо полів для клієнтського коду: + +- Версія застосунку: `version`, а не `server_version`. +- Команди завдань workflow містяться в + `worker_protocol.server_capabilities.supported_workflow_task_commands`, + а не на верхньому рівні `worker_protocol`. Той самий об'єкт повертається + в кожній відповіді worker як `server_capabilities`. +- `worker_protocol.server_capabilities.poll_status` означає наявність + `poll_status` навіть без lease. Worker розрізняє `empty`, `throttled`, + `unavailable`, `draining` без розбору тексту помилок. +- Підтримка параметрів retry, тайм-аутів, parent-close та неповторюваних помилок + також повертається в `server_capabilities` для узгодження без окремого cluster-info. +- Єдиний кодек v2 міститься в `capabilities.payload_codecs`: точно `['avro']`. + PHP серіалізатори імпорту/завершення v1 є внутрішніми механізмами міграції + та ніколи не відображаються в можливостях runtime. + +## Підключення worker {#connecting-workers} + +Worker опитують Server і виконують код workflow чи activity. +[Протокол worker](/docs/polyglot/worker-protocol) визначає повний API. +Матриця ролей маршрутів, правила namespace і точний payload реєстрації +наведені в [namespace, автентифікації та реєстрації worker](/docs/polyglot/namespace-auth-workers). + +### Worker PHP {#php-workers} + +PHP застосунки без фреймворку й віддалені worker використовують SDK Server. +Їм не потрібні Laravel чи вбудований пакет Workflow: + + +```bash +composer require %%artifact.phpSdkComposerPackage%% +``` + +Створіть клієнт і зареєструйте функції workflow чи activity у worker: + +```php +use DurableWorkflow\Client; +use DurableWorkflow\Worker; +use DurableWorkflow\Worker\WorkflowContext; + +$client = new Client( + 'http://localhost:8080', + token: getenv('DURABLE_WORKFLOW_AUTH_TOKEN') ?: null, + namespace: 'default', +); + +$worker = new Worker($client, taskQueue: 'polyglot-php'); +$worker->registerWorkflow( + 'invoice', + static function (WorkflowContext $context, string $invoiceId): array { + $context->activity('charge-card', [$invoiceId]); + + return ['invoice_id' => $invoiceId, 'status' => 'paid']; + }, +); +$worker->run(); +``` + +[Посібник PHP SDK](/docs/polyglot/php/) описує клієнт, автентифікацію, payload +і життєвий цикл worker. Окремий `durable-workflow/workflow` є вбудованим +runtime Laravel і рушієм усередині опублікованого Server. Вбудовані workflow +Laravel виконують завдання пакета через чергу застосунку без самостійного Server. + +### Worker Python {#python-workers} + +Worker Python використовують SDK `durable-workflow`: + +```bash +pip install %%artifact.pythonPackagePin%% +``` + +Налаштування worker описано в [посібнику Python SDK](/docs/polyglot/python). + +### Worker власною мовою {#custom-language-workers} + +Будь-яка мова може реалізувати worker: +1. Реєстрація через `POST /api/worker/register` +2. Long-poll через `POST /api/worker/workflow-tasks/poll`, `POST /api/worker/activity-tasks/poll` чи `POST /api/worker/query-tasks/poll` +3. Завершення через `POST /api/worker/workflow-tasks/{id}/complete`, `POST /api/worker/activity-tasks/{id}/complete` чи `POST /api/worker/query-tasks/{id}/complete` + +Усі запити вимагають: +- `Authorization: Bearer $TOKEN` +- `X-Namespace: your-namespace` +- `X-Durable-Workflow-Protocol-Version: 1.0` + +Server перевіряє існування namespace. Зареєструйте його через +`POST /api/namespaces` до направлення worker чи клієнтів, інакше +Server поверне `404` із `reason: "namespace_not_found"`. + +Приклад із curl є в [README Server](https://github.com/durable-workflow/server#getting-started-end-to-end-workflow). + +[Допуск черг завдань](/docs/polyglot/task-queue-admission) описує слоти worker, +ліміти активних lease, хвилинні бюджети dispatch і зворотний тиск query. + +## CLI {#cli} + +[Durable Workflow CLI](/docs/polyglot/cli) надає інтерфейс оболонки до Server: + +```bash +# Install — Linux and macOS +curl -fsSL https://durable-workflow.com/install.sh | sh + +# Install — macOS (Homebrew alternative) +brew install durable-workflow/tap/dw + +# Install — Windows (PowerShell) +# irm https://durable-workflow.com/install.ps1 | iex + +# Configure +export DURABLE_WORKFLOW_SERVER_URL=http://localhost:8080 +export DURABLE_WORKFLOW_AUTH_TOKEN=your-token +export DURABLE_WORKFLOW_NAMESPACE=default + +# Use +dw server:health +dw workflow:list +dw workflow:start --type=my-workflow --input='["value"]' +dw workflow:start --type=my-workflow --input-file=input.json +``` + +[Встановлення CLI](/docs/polyglot/cli#install) містить інсталятор із +визначенням платформи й прямі завантаження бінарних файлів. + +Команди черг містять стан допуску workflow, activity і query. +Вони розрізняють відсутніх worker, насичені слоти, ліміти активних lease +чи швидкості dispatch та переповнення query. + +## Розгортання {#deployment} + +Перед production виберіть підтримувану топологію в +[посібнику самостійного розгортання](/docs/deployment). Він розділяє локальну +розробку, один production вузол, невеликі кластери, Kubernetes і топології з підтримкою. + +Контракт невеликого самостійного кластера вузький: 2–3 stateless API вузли за +балансувальником, спільна зовнішня MySQL чи PostgreSQL, спільний Redis, +незалежно масштабовані worker та рівно один планувальник чи процес обслуговування. +Для кожного релізу вибирайте повну зупинку чи +[поступове оновлення](/docs/rolling-upgrades) з гарантіями різниці версій, +схеми, drain, готовності й відкату. Кластери SQLite, багатовузлова робота +без Redis, дублікати планувальника, active/active між регіонами й failover +провайдера поза контрактом до окремої перевірки. Окремо версіонований шлях +Helm описано в [посібнику розгортання](/docs/deployment#helm-chart-for-kubernetes). +Матеріали [active/passive між регіонами](/docs/deployment#activepassive-multi-region) +є оцінюванням із підтримкою, а не доведеним самостійним контрактом 2.0. +Кожен регіон-кандидат має починати з описаної однорегіональної форми чи невеликого кластера. + +Для самостійного Server починайте з опублікованих образів, а не збірок вихідного коду: + +- Docker Hub: `%%artifact.serverDockerHubImage%%` +- GitHub Container Registry: `%%artifact.serverGhcrImage%%` +- Compose з опублікованим образом: + [`docker-compose.published.yml`](https://github.com/durable-workflow/server/blob/main/docker-compose.published.yml) +- Маніфести Kubernetes: + [`k8s/`](https://github.com/durable-workflow/server/tree/main/k8s) + +Production має фіксувати тег чи digest образу, використовувати облікові дані +окремих ролей, виконувати bootstrap/міграції до трафіку й доводити готовність +через `/api/ready`, `/api/cluster/info` і реєстрацію worker. +Не направляйте production трафік лише за `/api/health`. + +## Довідник API {#api-reference} + +Повний довідник endpoint, заголовків, ролей, протоколу worker, зовнішнього +сховища payload та іменованих помилок є в +[довіднику API Server](/docs/polyglot/server-api-reference). + +Server надає три поверхні API: + +### Площина керування {#control-plane} + +Запускайте, описуйте, надсилайте signal/query/update, скасовуйте й примусово +завершуйте workflow, керуйте namespace, чергами, schedule, атрибутами пошуку +та worker. Кожен запит control-plane вимагає +`X-Durable-Workflow-Control-Plane-Version: 2`. Без нього запит відхиляється +з `missing_control_plane_version`. + +Основні endpoint: +- `POST /api/workflows`: запуск workflow +- `GET /api/workflows/{id}`: опис workflow +- `POST /api/workflows/{id}/signal/{name}`: signal +- `POST /api/workflows/{id}/query/{name}`: query +- `POST /api/workflows/{id}/update/{name}`: update +- `POST /api/workflows/{id}/cancel`: негайне закриття як скасованого +- `POST /api/workflows/{id}/terminate`: негайне примусове завершення +- `GET /api/workflows/{id}/runs/{runId}/history`: події історії +- `GET /api/workflows/{id}/runs/{runId}/history/export`: експорт набору replay +- `GET /api/namespaces`, `POST /api/namespaces`, `GET|PUT /api/namespaces/{namespace}`: керування namespace +- `GET /api/workers`, `GET|DELETE /api/workers/{id}`: керування парком worker +- `GET /api/task-queues`, `GET /api/task-queues/{taskQueue}`: backlog, poller, lease і допуск черг +- `GET|POST /api/schedules`, `GET|PUT|DELETE /api/schedules/{id}`, `POST /api/schedules/{id}/{pause|resume|trigger|backfill}`: керування schedule +- `GET|POST|DELETE /api/search-attributes`: керування атрибутами пошуку +- `GET|POST|PUT|DELETE /api/service-endpoints...`: admin endpoint каталогу сервісів, вкладені сервіси, прив'язки операцій і надійні знімки викликів +- `POST /api/system/repair/pass`, `POST /api/system/activity-timeouts/pass`, `POST /api/system/retention/pass`: операторські проходи + +Відповіді workflow control-plane, включно зі списками історії, містять +вкладені метадані `control_plane` для ідентифікації операції й версії +контракту відповіді. Експорт історії навмисно повертає набір replay без +обгортки, щоб сума цілісності й необов'язковий підпис покривали точні +байти артефакту, отриманого клієнтом. + +Помилки перевірки повертають HTTP 422 із `reason: validation_failed`, +`errors`, `validation_errors`. Маршрути workflow також відображають причину +й деталі в `control_plane.reason` і `control_plane.validation_errors`. +Команди вибраного виконання повертають URL `run_id` у відповіді й +`control_plane.run_id` для розрізнення команд екземпляра й вибраного виконання. + +Видимість черги допомагає визначити, чи відставання спричинене ростом +надійного backlog, відсутністю слотів worker чи лімітами допуску Server. +`GET /api/task-queues` повертає зведення кожної черги, а +`GET /api/task-queues/{taskQueue}` додає `pollers` і `current_leases`. +Обидва надають `stats.approximate_backlog_count`, +`stats.approximate_backlog_age_seconds` і лічильники готовності/lease +`stats.workflow_tasks.*` та `stats.activity_tasks.*`. Детальний маршрут +містить `admission` для розрізнення місткості worker і обмежень Server. +Швидкості надходження й dispatch усього парку містяться в +`operator_metrics.backlog.tasks_added_last_minute` і +`operator_metrics.backlog.tasks_dispatched_last_minute`. Маршрути черг +також надають `stats.tasks_added_last_minute` і +`stats.tasks_dispatched_last_minute` для тієї самої оцінки окремої черги. + +### Протокол worker {#worker-protocol} + +Worker виконують register, poll, heartbeat і complete. +Потрібен `X-Durable-Workflow-Protocol-Version: 1.0`. + +Основні endpoint: +- `POST /api/worker/register`: реєстрація worker +- `POST /api/worker/workflow-tasks/poll`: long-poll завдань workflow +- `POST /api/worker/workflow-tasks/{id}/complete`: завершення завдання workflow +- `POST /api/worker/query-tasks/poll`: long-poll маршрутизованих query workflow +- `POST /api/worker/query-tasks/{id}/complete`: завершення query +- `POST /api/worker/query-tasks/{id}/fail`: помилка чи відмова query +- `POST /api/worker/activity-tasks/poll`: long-poll activity +- `POST /api/worker/activity-tasks/{id}/complete`: завершення activity + +Докладніше в [протоколі worker](/docs/polyglot/worker-protocol). + +### Виявлення без версії {#discovery-unversioned} + +Лише endpoint виявлення й здоров'я не вимагають +`X-Durable-Workflow-Control-Plane-Version`: + +- `GET /api/health`: перевірка життя та публічне `topology` без автентифікації +- `GET /api/ready`: перевірка готовності й те саме `topology` без автентифікації +- `GET /api/cluster/info`: можливості Server, версії протоколів і єдиний + кодек Avro. Клієнт має спочатку визначити тут версії control-plane й worker. + +## Усунення проблем {#troubleshooting} + +### Worker не отримують завдання {#workers-not-receiving-tasks} + +**Перевірте:** +1. Worker зареєстровані? `curl http://localhost:8080/api/workers -H "Authorization: Bearer $TOKEN" -H "X-Durable-Workflow-Control-Plane-Version: 2" -H "X-Namespace: default"` +2. Worker опитують правильну чергу? +3. Workflow запущений із тією самою чергою? +4. Бекенд кешу спільний між Server? + +### Long-poll одразу завершується тайм-аутом {#long-poll-connections-timing-out-immediately} + +**Перевірте:** +1. Драйвер кешу підтримує атомарні блокування? Виконайте `php artisan workflow:v2:doctor --strict` +2. Redis доступний із Server? +3. Тайм-аут балансувальника вищий за тайм-аут long-poll (типово 60 с)? + +### Помилки з'єднання з базою {#database-connection-errors} + +**Перевірте:** +1. Хост і порт бази правильні? +2. Облікові дані чинні? +3. База існує? +4. Міграції виконані? `php artisan migrate:status` + +### Помилки автентифікації {#auth-failures} + +**Перевірте:** +1. `DW_AUTH_DRIVER` відповідає методу клієнта? +2. Token чи HMAC секрет однакові на Server і клієнті? +3. Є заголовки автентифікації `Authorization: Bearer $TOKEN` чи підпису HMAC? + +## Докладніше {#learn-more} + +- [Довідник протоколу worker](/docs/polyglot/worker-protocol): повний API worker +- [Перехід із вбудованого режиму до Server](/docs/polyglot/embedded-to-server): впровадження з Laravel v2 +- [Python SDK](/docs/polyglot/python): створення worker Python +- [CLI](/docs/polyglot/cli): інтерфейс командного рядка +- [Репозиторій Server](https://github.com/durable-workflow/server): код, issues, релізи From 8e2e0ad327ff39a649ffec863bb6f9d250f6e688 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 04:02:45 +0000 Subject: [PATCH 10/10] Translate search field accessibility labels --- .../@easyops-cn+docusaurus-search-local+0.55.3.patch | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 patches/@easyops-cn+docusaurus-search-local+0.55.3.patch diff --git a/patches/@easyops-cn+docusaurus-search-local+0.55.3.patch b/patches/@easyops-cn+docusaurus-search-local+0.55.3.patch new file mode 100644 index 0000000000..e105f3c71c --- /dev/null +++ b/patches/@easyops-cn+docusaurus-search-local+0.55.3.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchBar/SearchBar.jsx b/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchBar/SearchBar.jsx +--- a/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchBar/SearchBar.jsx ++++ b/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchBar/SearchBar.jsx +@@ -401,1 +401,1 @@ +- })} aria-label="Search" className={`navbar__search-input ${styles.searchInput}`} onMouseEnter={onInputMouseEnter} onFocus={onInputFocus} onBlur={onInputBlur} onChange={onInputChange} ref={searchBarRef} value={inputValue}/> ++ })} aria-label={translate({ id: "theme.SearchBar.label", message: "Search" })} className={`navbar__search-input ${styles.searchInput}`} onMouseEnter={onInputMouseEnter} onFocus={onInputFocus} onBlur={onInputBlur} onChange={onInputChange} ref={searchBarRef} value={inputValue}/> +diff --git a/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchPage/SearchPage.jsx b/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchPage/SearchPage.jsx +--- a/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchPage/SearchPage.jsx ++++ b/node_modules/@easyops-cn/docusaurus-search-local/dist/client/client/theme/SearchPage/SearchPage.jsx +@@ -98,1 +98,1 @@ +- ++
RoleCurrent markerResolver authorityRoleCurrent markerResolver authority
Current published Server protocolCurrent published Server protocol {currentServer.marker}{' = '} {currentServer.protocolVersion} - Unversioned Server-backed mirrors:{' '} + Unversioned Server-backed mirrors:{' '} - Current Workflow conformance targetCurrent Workflow conformance target - Suite {currentConformance.suiteVersion}{' targets '} - protocol {currentConformance.protocolVersion} + {currentConformance.suiteVersion}, protocol: {currentConformance.protocolVersion}}}> + {'Suite {suite} targets protocol {protocol}'} + - Versioned, digest-bound fixtures:{' '} + Versioned, digest-bound fixtures:{' '} - Retained historical conformance bindingsRetained historical conformance bindings - Bindings marked historical for protocols{' '} - {historicalVersions} + historical, protocols: {historicalVersions}}}> + {'Bindings marked {marker} for protocols {protocols}'} + - Immutable resolver and digest records in the{' '} - conformance manifest + Immutable resolver and digest records in the{' '} + conformance manifest