1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Extract a workflow's primitive structure from its entry-module Gleam source.
//!
//! A Rust crate cannot type-check Gleam, so — following the codegen precedent
//! (a generator operates over a canonical derived representation, never by
//! type-checking Gleam) — extraction scans the entry-module source for the
//! fixed `aion/workflow` primitive vocabulary. This is sound only because that
//! vocabulary is small and known (ADR-014, P2). The supported subset is stated
//! and enforced below; anything outside it is reported, never silently
//! mis-parsed (no silent failures).
//!
//! ## Supported import/call subset
//!
//! - The entry module imports `aion/workflow`, optionally aliased
//! (`import aion/workflow as <alias>`). Primitive calls are recognised as
//! `<alias>.run`, `<alias>.all`, and so on. Absence of the import is a loud
//! [`StructureError::NoWorkflowImport`], never an empty graph.
//! - A `run` node's activity name is read from the
//! `<wrappers_alias>.<name>_activity(` call passed to `run`, where the
//! wrappers module is imported as `import <pkg>_activity_wrappers as <alias>`
//! (the form `aion generate` emits). The extracted name is validated against
//! the manifest's declared activities.
//! - `start_timer`, `spawn`, and `spawn_and_wait` carry their first string
//! literal argument as a name; it is read directly from source.
//!
//! ## Control flow
//!
//! Extraction is control-flow faithful (the [`super::control_flow`] walker). It
//! starts at the manifest entry function, recurses the body in source order, and
//! models a `case` over a workflow-primitive result as a [`NodePrimitive::Branch`]
//! with labelled `Ok` / `Error` arm edges into the real success and compensation
//! subgraphs — following local-helper calls so a saga's compensations land on
//! the error arm, not in a false linear sequence. Control flow it cannot resolve
//! is surfaced as an explicit [`NodePrimitive::Opaque`] node, never flattened.
use cratePackage;
use ControlFlowExtractor;
use StructureError;
use WorkflowGraph;
use ;
/// Extracts the workflow graph model from a loaded package.
///
/// Reads the manifest entry module's verbatim Gleam source from
/// [`Package::source`], walks it from the entry function over the
/// `aion/workflow` primitive vocabulary, and builds a control-flow-faithful
/// node/edge graph whose nodes carry the correlation key a consumer overlays a
/// run's recorded events onto (C21, C22, C23).
///
/// # Errors
///
/// Returns [`StructureError::MissingEntrySource`] when the entry module has no
/// source, [`StructureError::EntrySourceNotUtf8`] when its bytes are not UTF-8,
/// [`StructureError::NoWorkflowImport`] when it does not import `aion/workflow`,
/// [`StructureError::EntryFunctionNotFound`] when the manifest entry function is
/// not defined in the source, and [`StructureError::UnknownActivity`] when a
/// `run` node names an activity the manifest does not declare.
/// Finds the alias the entry module imports `aion/workflow` under.
///
/// `import aion/workflow` yields `workflow` (the last path segment); an explicit
/// `import aion/workflow as <alias>` yields `<alias>`. Returns `None` when the
/// module does not import `aion/workflow` at all.
/// Whether the import path tokens beginning at `start` spell `aion/workflow`.
///
/// The scanner renders `aion/workflow` as `Ident("aion")`, `Other('/')`,
/// `Ident("workflow")`; an aliased or selective import keeps the same path
/// prefix, so matching the two path identifiers around the slash is sufficient
/// and avoids matching `aion/workflow/timer` style submodule imports the entry
/// module does not orchestrate through.
/// Reads the alias of `import aion/workflow as <alias>`.
///
/// The path occupies three tokens (`aion`, `/`, `workflow`) from `path_start`;
/// an alias, if present, is the token after a literal `as` immediately
/// following the path. Anything else (a bare import, a selective
/// `import aion/workflow.{...}`, or the next statement) yields no alias here and
/// the caller falls back to the default `workflow`.