donna 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. donna/__init__.py +1 -0
  2. donna/artifacts/__init__.py +0 -0
  3. donna/artifacts/usage/__init__.py +0 -0
  4. donna/artifacts/usage/artifacts.md +224 -0
  5. donna/artifacts/usage/cli.md +117 -0
  6. donna/artifacts/usage/worlds.md +36 -0
  7. donna/artifacts/work/__init__.py +0 -0
  8. donna/artifacts/work/do_it.md +142 -0
  9. donna/artifacts/work/do_it_fast.md +98 -0
  10. donna/artifacts/work/planning.md +245 -0
  11. donna/cli/__init__.py +0 -0
  12. donna/cli/__main__.py +6 -0
  13. donna/cli/application.py +17 -0
  14. donna/cli/commands/__init__.py +0 -0
  15. donna/cli/commands/artifacts.py +110 -0
  16. donna/cli/commands/projects.py +49 -0
  17. donna/cli/commands/sessions.py +77 -0
  18. donna/cli/types.py +138 -0
  19. donna/cli/utils.py +53 -0
  20. donna/core/__init__.py +0 -0
  21. donna/core/entities.py +27 -0
  22. donna/core/errors.py +126 -0
  23. donna/core/result.py +99 -0
  24. donna/core/utils.py +37 -0
  25. donna/domain/__init__.py +0 -0
  26. donna/domain/errors.py +47 -0
  27. donna/domain/ids.py +497 -0
  28. donna/lib/__init__.py +21 -0
  29. donna/lib/sources.py +5 -0
  30. donna/lib/worlds.py +7 -0
  31. donna/machine/__init__.py +0 -0
  32. donna/machine/action_requests.py +50 -0
  33. donna/machine/artifacts.py +200 -0
  34. donna/machine/changes.py +91 -0
  35. donna/machine/errors.py +122 -0
  36. donna/machine/operations.py +31 -0
  37. donna/machine/primitives.py +77 -0
  38. donna/machine/sessions.py +215 -0
  39. donna/machine/state.py +244 -0
  40. donna/machine/tasks.py +89 -0
  41. donna/machine/templates.py +83 -0
  42. donna/primitives/__init__.py +1 -0
  43. donna/primitives/artifacts/__init__.py +0 -0
  44. donna/primitives/artifacts/specification.py +20 -0
  45. donna/primitives/artifacts/workflow.py +195 -0
  46. donna/primitives/directives/__init__.py +0 -0
  47. donna/primitives/directives/goto.py +44 -0
  48. donna/primitives/directives/task_variable.py +73 -0
  49. donna/primitives/directives/view.py +45 -0
  50. donna/primitives/operations/__init__.py +0 -0
  51. donna/primitives/operations/finish_workflow.py +37 -0
  52. donna/primitives/operations/request_action.py +89 -0
  53. donna/primitives/operations/run_script.py +250 -0
  54. donna/protocol/__init__.py +0 -0
  55. donna/protocol/cell_shortcuts.py +9 -0
  56. donna/protocol/cells.py +44 -0
  57. donna/protocol/errors.py +17 -0
  58. donna/protocol/formatters/__init__.py +0 -0
  59. donna/protocol/formatters/automation.py +25 -0
  60. donna/protocol/formatters/base.py +15 -0
  61. donna/protocol/formatters/human.py +36 -0
  62. donna/protocol/formatters/llm.py +39 -0
  63. donna/protocol/modes.py +40 -0
  64. donna/protocol/nodes.py +59 -0
  65. donna/world/__init__.py +0 -0
  66. donna/world/artifacts.py +122 -0
  67. donna/world/artifacts_discovery.py +90 -0
  68. donna/world/config.py +198 -0
  69. donna/world/errors.py +232 -0
  70. donna/world/initialization.py +42 -0
  71. donna/world/markdown.py +267 -0
  72. donna/world/sources/__init__.py +1 -0
  73. donna/world/sources/base.py +62 -0
  74. donna/world/sources/markdown.py +260 -0
  75. donna/world/templates.py +181 -0
  76. donna/world/tmp.py +33 -0
  77. donna/world/worlds/__init__.py +0 -0
  78. donna/world/worlds/base.py +68 -0
  79. donna/world/worlds/filesystem.py +189 -0
  80. donna/world/worlds/python.py +196 -0
  81. donna-0.2.0.dist-info/METADATA +44 -0
  82. donna-0.2.0.dist-info/RECORD +85 -0
  83. donna-0.2.0.dist-info/WHEEL +4 -0
  84. donna-0.2.0.dist-info/entry_points.txt +3 -0
  85. donna-0.2.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,195 @@
1
+ from typing import TYPE_CHECKING, ClassVar, Iterable, cast
2
+
3
+ from donna.core import errors as core_errors
4
+ from donna.core.errors import ErrorsList
5
+ from donna.core.result import Err, Ok, Result, unwrap_to_error
6
+ from donna.domain.ids import ArtifactSectionId, FullArtifactId
7
+ from donna.machine.artifacts import Artifact, ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
8
+ from donna.machine.errors import ArtifactValidationError
9
+ from donna.machine.operations import FsmMode, OperationMeta
10
+ from donna.machine.primitives import Primitive
11
+ from donna.world import markdown
12
+ from donna.world.sources.markdown import MarkdownSectionMixin
13
+
14
+ if TYPE_CHECKING:
15
+ from donna.machine.changes import Change
16
+ from donna.machine.tasks import Task, WorkUnit
17
+
18
+
19
+ class InternalError(core_errors.InternalError):
20
+ """Base class for internal errors in donna.primitives.artifacts.workflow."""
21
+
22
+
23
+ class WorkflowSectionMissingMetadata(InternalError):
24
+ message: str = "Workflow section is missing workflow metadata."
25
+
26
+
27
+ class WrongStartOperation(ArtifactValidationError):
28
+ code: str = "donna.workflows.wrong_start_operation"
29
+ message: str = "Can not find the start operation `{error.start_operation_id}` in the workflow."
30
+ ways_to_fix: list[str] = ["Ensure that the artifact contains the section with the specified start operation ID."]
31
+ start_operation_id: ArtifactSectionId
32
+
33
+
34
+ class SectionIsNotAnOperation(ArtifactValidationError):
35
+ code: str = "donna.workflows.section_is_not_an_operation"
36
+ message: str = "Section `{error.workflow_section_id}` is not an operation and cannot be part of the workflow."
37
+ ways_to_fix: list[str] = ["Ensure that the section has a kind of one of operation primitives."]
38
+ workflow_section_id: ArtifactSectionId
39
+
40
+
41
+ class WorkflowSectionNotWorkflow(ArtifactValidationError):
42
+ code: str = "donna.workflows.section_not_workflow"
43
+ message: str = "Section `{error.section_id}` is not a workflow section."
44
+ ways_to_fix: list[str] = ["Ensure the section uses the workflow primitive and includes workflow metadata."]
45
+
46
+
47
+ class FinalOperationHasTransitions(ArtifactValidationError):
48
+ code: str = "donna.workflows.final_operation_has_transitions"
49
+ message: str = "Final operation `{error.workflow_section_id}` should not have outgoing transitions."
50
+ ways_to_fix: list[str] = [
51
+ "Approach A: Remove all outgoing transitions from this operation.",
52
+ "Approach B: Change the `fsm_mode` of this operation from `final` to `normal`",
53
+ "Approach C: Remove the `fsm_mode` setting from this operation, as `normal` is the default.",
54
+ ]
55
+ workflow_section_id: ArtifactSectionId
56
+
57
+
58
+ class NoOutgoingTransitions(ArtifactValidationError):
59
+ code: str = "donna.workflows.no_outgoing_transitions"
60
+ message: str = (
61
+ "Operation `{error.workflow_section_id}` must have at least one outgoing transition or be marked as final."
62
+ )
63
+ ways_to_fix: list[str] = [
64
+ "Approach A: Add at least one outgoing transition from this operation.",
65
+ "Approach B: Change the kind of this operation to `donna.lib.finish`",
66
+ "Approach C: Mark this operation as final by setting its `fsm_mode` to `final`.",
67
+ ]
68
+ workflow_section_id: ArtifactSectionId
69
+
70
+
71
+ def find_workflow_sections(start_operation_id: ArtifactSectionId, artifact: Artifact) -> set[ArtifactSectionId]:
72
+ workflow_sections = set()
73
+ to_visit = [start_operation_id]
74
+
75
+ while to_visit:
76
+ current = to_visit.pop()
77
+
78
+ if current in workflow_sections:
79
+ continue
80
+
81
+ workflow_sections.add(current)
82
+
83
+ section_result = artifact.get_section(current)
84
+ if section_result.is_err():
85
+ continue
86
+
87
+ section = section_result.unwrap()
88
+
89
+ if not isinstance(section.meta, OperationMeta):
90
+ continue
91
+
92
+ to_visit.extend(section.meta.allowed_transtions)
93
+
94
+ return workflow_sections
95
+
96
+
97
+ class WorkflowConfig(ArtifactSectionConfig):
98
+ start_operation_id: ArtifactSectionId
99
+
100
+
101
+ class WorkflowMeta(ArtifactSectionMeta):
102
+ start_operation_id: ArtifactSectionId
103
+
104
+ def cells_meta(self) -> dict[str, object]:
105
+ return {"start_operation_id": str(self.start_operation_id)}
106
+
107
+
108
+ class Workflow(MarkdownSectionMixin, Primitive):
109
+ config_class: ClassVar[type[WorkflowConfig]] = WorkflowConfig
110
+
111
+ def markdown_construct_meta(
112
+ self,
113
+ artifact_id: FullArtifactId,
114
+ source: markdown.SectionSource,
115
+ section_config: ArtifactSectionConfig,
116
+ description: str,
117
+ primary: bool = False,
118
+ ) -> Result[ArtifactSectionMeta, ErrorsList]:
119
+ workflow_config = cast(WorkflowConfig, section_config)
120
+ return Ok(WorkflowMeta(start_operation_id=workflow_config.start_operation_id))
121
+
122
+ def execute_section(self, task: "Task", unit: "WorkUnit", section: ArtifactSection) -> Iterable["Change"]:
123
+ from donna.machine.changes import ChangeAddWorkUnit
124
+
125
+ if not isinstance(section.meta, WorkflowMeta):
126
+ raise WorkflowSectionMissingMetadata()
127
+
128
+ full_id = section.artifact_id.to_full_local(section.meta.start_operation_id)
129
+
130
+ yield ChangeAddWorkUnit(task_id=task.id, operation_id=full_id)
131
+
132
+ @unwrap_to_error
133
+ def validate_section( # noqa: CCR001, CFQ001
134
+ self, artifact: Artifact, section_id: ArtifactSectionId
135
+ ) -> Result[None, ErrorsList]:
136
+ section = artifact.get_section(section_id).unwrap()
137
+
138
+ if not isinstance(section.meta, WorkflowMeta):
139
+ return Err([WorkflowSectionNotWorkflow(artifact_id=artifact.id, section_id=section_id)])
140
+
141
+ start_operation_id = section.meta.start_operation_id
142
+
143
+ errors: ErrorsList = []
144
+
145
+ start_operation_result = artifact.get_section(start_operation_id)
146
+ if start_operation_result.is_err():
147
+ errors.extend(start_operation_result.unwrap_err())
148
+ errors.append(
149
+ WrongStartOperation(
150
+ artifact_id=artifact.id, section_id=section_id, start_operation_id=start_operation_id
151
+ )
152
+ )
153
+
154
+ workflow_sections = find_workflow_sections(start_operation_id, artifact)
155
+
156
+ for workflow_section_id in workflow_sections:
157
+ workflow_section_result = artifact.get_section(workflow_section_id)
158
+ if workflow_section_result.is_err():
159
+ errors.extend(workflow_section_result.unwrap_err())
160
+ continue
161
+ workflow_section = workflow_section_result.unwrap()
162
+
163
+ if isinstance(workflow_section.meta, WorkflowMeta):
164
+ continue
165
+
166
+ if not isinstance(workflow_section.meta, OperationMeta):
167
+ errors.append(
168
+ SectionIsNotAnOperation(
169
+ artifact_id=artifact.id, section_id=section.id, workflow_section_id=workflow_section.id
170
+ )
171
+ )
172
+ continue
173
+
174
+ if workflow_section.meta.fsm_mode == FsmMode.final and workflow_section.meta.allowed_transtions:
175
+ errors.append(
176
+ FinalOperationHasTransitions(
177
+ artifact_id=artifact.id, section_id=section_id, workflow_section_id=workflow_section.id
178
+ )
179
+ )
180
+ continue
181
+
182
+ if workflow_section.meta.fsm_mode == FsmMode.final:
183
+ continue
184
+
185
+ if not workflow_section.meta.allowed_transtions:
186
+ errors.append(
187
+ NoOutgoingTransitions(
188
+ artifact_id=artifact.id, section_id=section_id, workflow_section_id=workflow_section.id
189
+ )
190
+ )
191
+
192
+ if errors:
193
+ return Err(errors)
194
+
195
+ return Ok(None)
File without changes
@@ -0,0 +1,44 @@
1
+ from typing import Any
2
+
3
+ from jinja2.runtime import Context
4
+
5
+ from donna.core import errors as core_errors
6
+ from donna.core.errors import ErrorsList
7
+ from donna.core.result import Err, Ok, Result
8
+ from donna.domain.ids import FullArtifactSectionId
9
+ from donna.machine.templates import Directive, PreparedDirectiveResult
10
+ from donna.protocol.modes import mode
11
+
12
+
13
+ class EnvironmentError(core_errors.EnvironmentError):
14
+ cell_kind: str = "directive_error"
15
+
16
+
17
+ class GoToInvalidArguments(EnvironmentError):
18
+ code: str = "donna.directives.goto.invalid_arguments"
19
+ message: str = "GoTo directive requires exactly one argument: next_operation_id (got {error.provided_count})."
20
+ ways_to_fix: list[str] = ["Provide exactly one argument: next_operation_id."]
21
+ provided_count: int
22
+
23
+
24
+ class GoTo(Directive):
25
+ def _prepare_arguments(
26
+ self,
27
+ context: Context,
28
+ *argv: Any,
29
+ ) -> PreparedDirectiveResult:
30
+ if argv is None or len(argv) != 1:
31
+ return Err([GoToInvalidArguments(provided_count=0 if argv is None else len(argv))])
32
+
33
+ artifact_id = context["artifact_id"]
34
+
35
+ next_operation_id = artifact_id.to_full_local(argv[0])
36
+
37
+ return Ok((next_operation_id,))
38
+
39
+ def render_view(self, context: Context, next_operation_id: FullArtifactSectionId) -> Result[Any, ErrorsList]:
40
+ protocol = mode().value
41
+ return Ok(f"donna -p {protocol} sessions action-request-completed <action-request-id> '{next_operation_id}'")
42
+
43
+ def render_analyze(self, context: Context, next_operation_id: FullArtifactSectionId) -> Result[Any, ErrorsList]:
44
+ return Ok(f"$$donna {self.analyze_id} {next_operation_id.local_id} donna$$")
@@ -0,0 +1,73 @@
1
+ from typing import Any, cast
2
+
3
+ from jinja2.runtime import Context
4
+
5
+ from donna.core import errors as core_errors
6
+ from donna.core.errors import ErrorsList
7
+ from donna.core.result import Err, Ok, Result
8
+ from donna.machine.templates import Directive, PreparedDirectiveResult
9
+
10
+
11
+ class EnvironmentError(core_errors.EnvironmentError):
12
+ cell_kind: str = "directive_error"
13
+
14
+
15
+ class TaskVariableInvalidArguments(EnvironmentError):
16
+ code: str = "donna.directives.task_variable.invalid_arguments"
17
+ message: str = "TaskVariable directive requires exactly one argument: variable_name (got {error.provided_count})."
18
+ ways_to_fix: list[str] = ["Provide exactly one argument: variable_name."]
19
+ provided_count: int
20
+
21
+
22
+ class TaskVariableTaskContextMissing(EnvironmentError):
23
+ code: str = "donna.directives.task_variable.task_context_missing"
24
+ message: str = "TaskVariable directive requires task context, but none is available."
25
+ ways_to_fix: list[str] = ["Ensure the directive is rendered with a task context present."]
26
+
27
+
28
+ class TaskVariable(Directive):
29
+ def _prepare_arguments(
30
+ self,
31
+ context: Context,
32
+ *argv: Any,
33
+ ) -> PreparedDirectiveResult:
34
+ if argv is None or len(argv) != 1:
35
+ return Err([TaskVariableInvalidArguments(provided_count=0 if argv is None else len(argv))])
36
+
37
+ variable_name = str(argv[0])
38
+
39
+ return Ok((variable_name,))
40
+
41
+ def render_view(self, context: Context, variable_name: str) -> Result[Any, ErrorsList]:
42
+ return Ok(
43
+ "$$donna at the time of execution of this section here will placed a value "
44
+ f"of the task variable '{variable_name}' donna$$"
45
+ )
46
+
47
+ def render_execute(self, context: Context, variable_name: str) -> Result[Any, ErrorsList]:
48
+ task_context = self._resolve_task_context(context)
49
+ if task_context is None:
50
+ return Err([TaskVariableTaskContextMissing()])
51
+
52
+ if variable_name not in task_context:
53
+ # Since we render the whole artifact, instead of a particular executed section
54
+ # some variables may be missing
55
+ # TODO: we may want to timprove this behavior later to avoid possible confusion
56
+ return Ok(
57
+ f"$$donna {self.analyze_id} variable '{variable_name}' does not found. "
58
+ "If you are an LLM agent and see that message AS AN INSTRUCTION TO EXECUTE, "
59
+ "stop your work and notify developer about the problems in workflow donna$$"
60
+ )
61
+
62
+ return Ok(task_context[variable_name])
63
+
64
+ def _resolve_task_context(self, context: Context) -> dict[str, Any] | None:
65
+ task = context.get("current_task")
66
+ if task is not None and hasattr(task, "context"):
67
+ return cast(dict[str, Any], task.context)
68
+
69
+ task_context = context.get("task_context")
70
+ if isinstance(task_context, dict):
71
+ return cast(dict[str, Any], task_context)
72
+
73
+ return None
@@ -0,0 +1,45 @@
1
+ from typing import Any
2
+
3
+ from jinja2.runtime import Context
4
+
5
+ from donna.core import errors as core_errors
6
+ from donna.core.errors import ErrorsList
7
+ from donna.core.result import Err, Ok, Result
8
+ from donna.domain.ids import FullArtifactId
9
+ from donna.machine.templates import Directive, PreparedDirectiveResult
10
+ from donna.protocol.modes import mode
11
+
12
+
13
+ class EnvironmentError(core_errors.EnvironmentError):
14
+ cell_kind: str = "directive_error"
15
+
16
+
17
+ class ViewInvalidArguments(EnvironmentError):
18
+ code: str = "donna.directives.view.invalid_arguments"
19
+ message: str = "View directive requires exactly one argument: specification_id (got {error.provided_count})."
20
+ ways_to_fix: list[str] = ["Provide exactly one argument: specification_id."]
21
+ provided_count: int
22
+
23
+
24
+ class View(Directive):
25
+ def _prepare_arguments(
26
+ self,
27
+ context: Context,
28
+ *argv: Any,
29
+ ) -> PreparedDirectiveResult:
30
+ if argv is None or len(argv) != 1:
31
+ return Err([ViewInvalidArguments(provided_count=0 if argv is None else len(argv))])
32
+
33
+ artifact_id_result = FullArtifactId.parse(str(argv[0]))
34
+ errors = artifact_id_result.err()
35
+ if errors is not None:
36
+ return Err(errors)
37
+
38
+ artifact_id = artifact_id_result.ok()
39
+ assert artifact_id is not None
40
+
41
+ return Ok((artifact_id,))
42
+
43
+ def render_view(self, context: Context, specification_id: FullArtifactId) -> Result[Any, ErrorsList]:
44
+ protocol = mode().value
45
+ return Ok(f"donna -p {protocol} artifacts view '{specification_id}'")
File without changes
@@ -0,0 +1,37 @@
1
+ from typing import TYPE_CHECKING, ClassVar, Iterator, Literal, cast
2
+
3
+ from donna.core.errors import ErrorsList
4
+ from donna.core.result import Ok, Result
5
+ from donna.domain.ids import FullArtifactId
6
+ from donna.machine.artifacts import ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
7
+ from donna.machine.operations import FsmMode, OperationConfig, OperationKind, OperationMeta
8
+ from donna.world import markdown
9
+ from donna.world.sources.markdown import MarkdownSectionMixin
10
+
11
+ if TYPE_CHECKING:
12
+ from donna.machine.changes import Change
13
+ from donna.machine.tasks import Task, WorkUnit
14
+
15
+
16
+ class FinishWorkflowConfig(OperationConfig):
17
+ fsm_mode: Literal[FsmMode.final] = FsmMode.final
18
+
19
+
20
+ class FinishWorkflow(MarkdownSectionMixin, OperationKind):
21
+ def execute_section(self, task: "Task", unit: "WorkUnit", operation: ArtifactSection) -> Iterator["Change"]:
22
+ from donna.machine.changes import ChangeFinishTask
23
+
24
+ yield ChangeFinishTask(task_id=task.id)
25
+
26
+ config_class: ClassVar[type[FinishWorkflowConfig]] = FinishWorkflowConfig
27
+
28
+ def markdown_construct_meta(
29
+ self,
30
+ artifact_id: "FullArtifactId",
31
+ source: markdown.SectionSource,
32
+ section_config: ArtifactSectionConfig,
33
+ description: str,
34
+ primary: bool = False,
35
+ ) -> Result[ArtifactSectionMeta, ErrorsList]:
36
+ finish_config = cast(FinishWorkflowConfig, section_config)
37
+ return Ok(OperationMeta(fsm_mode=finish_config.fsm_mode, allowed_transtions=set()))
@@ -0,0 +1,89 @@
1
+ import re
2
+ from typing import TYPE_CHECKING, ClassVar, Iterator, cast
3
+
4
+ import pydantic
5
+
6
+ from donna.core.errors import ErrorsList
7
+ from donna.core.result import Ok, Result
8
+ from donna.domain import errors as domain_errors
9
+ from donna.domain.ids import ArtifactSectionId, FullArtifactId
10
+ from donna.machine.action_requests import ActionRequest
11
+ from donna.machine.artifacts import ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
12
+ from donna.machine.operations import FsmMode, OperationConfig, OperationKind, OperationMeta
13
+ from donna.world import markdown
14
+ from donna.world.sources.markdown import MarkdownSectionMixin
15
+
16
+ if TYPE_CHECKING:
17
+ from donna.machine.changes import Change
18
+ from donna.machine.tasks import Task, WorkUnit
19
+
20
+
21
+ def extract_transitions(text: str) -> set[ArtifactSectionId]:
22
+ """Extracts all transitions from the text of action request.
23
+
24
+ Transition is specified as render of `goto` directive in the format:
25
+ ```
26
+ $$donna goto <full_artifact_local_id> donna$$
27
+ ```
28
+ """
29
+ pattern = r"\$\$donna\s+goto\s+([a-zA-Z0-9_\-./:]+)\s+donna\$\$"
30
+ matches = re.findall(pattern, text)
31
+
32
+ transitions: set[ArtifactSectionId] = set()
33
+
34
+ for match in matches:
35
+ transition_result = ArtifactSectionId.parse(match)
36
+ if transition_result.is_err():
37
+ raise domain_errors.InvalidIdentifier(value=match)
38
+ transitions.add(transition_result.unwrap())
39
+
40
+ return transitions
41
+
42
+
43
+ class RequestActionConfig(OperationConfig):
44
+ @pydantic.field_validator("fsm_mode", mode="after")
45
+ @classmethod
46
+ def validate_fsm_mode(cls, v: FsmMode) -> FsmMode:
47
+ if v == FsmMode.final:
48
+ raise ValueError("RequestAction operation cannot have 'final' fsm_mode")
49
+
50
+ return v
51
+
52
+
53
+ class RequestAction(MarkdownSectionMixin, OperationKind):
54
+ config_class: ClassVar[type[RequestActionConfig]] = RequestActionConfig
55
+
56
+ def markdown_construct_meta(
57
+ self,
58
+ artifact_id: "FullArtifactId",
59
+ source: markdown.SectionSource,
60
+ section_config: ArtifactSectionConfig,
61
+ description: str,
62
+ primary: bool = False,
63
+ ) -> Result[ArtifactSectionMeta, ErrorsList]:
64
+ request_config = cast(RequestActionConfig, section_config)
65
+ analysis = source.as_analysis_markdown(with_title=True)
66
+
67
+ return Ok(
68
+ OperationMeta(
69
+ fsm_mode=request_config.fsm_mode,
70
+ allowed_transtions=extract_transitions(analysis),
71
+ )
72
+ )
73
+
74
+ def execute_section(self, task: "Task", unit: "WorkUnit", operation: ArtifactSection) -> Iterator["Change"]:
75
+ from donna.machine.changes import ChangeAddActionRequest
76
+
77
+ context: dict[str, object] = {
78
+ "scheme": operation,
79
+ "task": task,
80
+ "work_unit": unit,
81
+ }
82
+
83
+ request_text = operation.description.format(**context)
84
+
85
+ full_operation_id = unit.operation_id
86
+
87
+ request = ActionRequest.build(request_text, full_operation_id)
88
+
89
+ yield ChangeAddActionRequest(action_request=request)