@flow-as-code/core 0.1.0

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 (164) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +30 -0
  3. package/SPEC.md +92 -0
  4. package/dist/actions.d.ts +87 -0
  5. package/dist/actions.d.ts.map +1 -0
  6. package/dist/actions.js +204 -0
  7. package/dist/actions.js.map +1 -0
  8. package/dist/aws.d.ts +27 -0
  9. package/dist/aws.d.ts.map +1 -0
  10. package/dist/aws.js +32 -0
  11. package/dist/aws.js.map +1 -0
  12. package/dist/blocks.d.ts +287 -0
  13. package/dist/blocks.d.ts.map +1 -0
  14. package/dist/blocks.js +352 -0
  15. package/dist/blocks.js.map +1 -0
  16. package/dist/codegen.d.ts +21 -0
  17. package/dist/codegen.d.ts.map +1 -0
  18. package/dist/codegen.js +1027 -0
  19. package/dist/codegen.js.map +1 -0
  20. package/dist/export.d.ts +256 -0
  21. package/dist/export.d.ts.map +1 -0
  22. package/dist/export.js +799 -0
  23. package/dist/export.js.map +1 -0
  24. package/dist/flow.d.ts +41 -0
  25. package/dist/flow.d.ts.map +1 -0
  26. package/dist/flow.js +85 -0
  27. package/dist/flow.js.map +1 -0
  28. package/dist/flowdoc.d.ts +111 -0
  29. package/dist/flowdoc.d.ts.map +1 -0
  30. package/dist/flowdoc.js +139 -0
  31. package/dist/flowdoc.js.map +1 -0
  32. package/dist/index.d.ts +16 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +26 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/layout.d.ts +11 -0
  37. package/dist/layout.d.ts.map +1 -0
  38. package/dist/layout.js +49 -0
  39. package/dist/layout.js.map +1 -0
  40. package/dist/lint/engine.d.ts +16 -0
  41. package/dist/lint/engine.d.ts.map +1 -0
  42. package/dist/lint/engine.js +37 -0
  43. package/dist/lint/engine.js.map +1 -0
  44. package/dist/lint/graph.d.ts +34 -0
  45. package/dist/lint/graph.d.ts.map +1 -0
  46. package/dist/lint/graph.js +79 -0
  47. package/dist/lint/graph.js.map +1 -0
  48. package/dist/lint/index.d.ts +7 -0
  49. package/dist/lint/index.d.ts.map +1 -0
  50. package/dist/lint/index.js +5 -0
  51. package/dist/lint/index.js.map +1 -0
  52. package/dist/lint/reporters.d.ts +6 -0
  53. package/dist/lint/reporters.d.ts.map +1 -0
  54. package/dist/lint/reporters.js +30 -0
  55. package/dist/lint/reporters.js.map +1 -0
  56. package/dist/lint/rules/action-allowed-in-flow-type.d.ts +11 -0
  57. package/dist/lint/rules/action-allowed-in-flow-type.d.ts.map +1 -0
  58. package/dist/lint/rules/action-allowed-in-flow-type.js +32 -0
  59. package/dist/lint/rules/action-allowed-in-flow-type.js.map +1 -0
  60. package/dist/lint/rules/action-count.d.ts +16 -0
  61. package/dist/lint/rules/action-count.d.ts.map +1 -0
  62. package/dist/lint/rules/action-count.js +32 -0
  63. package/dist/lint/rules/action-count.js.map +1 -0
  64. package/dist/lint/rules/error-branches.d.ts +10 -0
  65. package/dist/lint/rules/error-branches.d.ts.map +1 -0
  66. package/dist/lint/rules/error-branches.js +39 -0
  67. package/dist/lint/rules/error-branches.js.map +1 -0
  68. package/dist/lint/rules/index.d.ts +17 -0
  69. package/dist/lint/rules/index.d.ts.map +1 -0
  70. package/dist/lint/rules/index.js +36 -0
  71. package/dist/lint/rules/index.js.map +1 -0
  72. package/dist/lint/rules/module-depth-5.d.ts +14 -0
  73. package/dist/lint/rules/module-depth-5.d.ts.map +1 -0
  74. package/dist/lint/rules/module-depth-5.js +67 -0
  75. package/dist/lint/rules/module-depth-5.js.map +1 -0
  76. package/dist/lint/rules/no-literal-arn.d.ts +21 -0
  77. package/dist/lint/rules/no-literal-arn.d.ts.map +1 -0
  78. package/dist/lint/rules/no-literal-arn.js +58 -0
  79. package/dist/lint/rules/no-literal-arn.js.map +1 -0
  80. package/dist/lint/rules/no-unresolved-token.d.ts +8 -0
  81. package/dist/lint/rules/no-unresolved-token.d.ts.map +1 -0
  82. package/dist/lint/rules/no-unresolved-token.js +71 -0
  83. package/dist/lint/rules/no-unresolved-token.js.map +1 -0
  84. package/dist/lint/rules/prompt-length-3000.d.ts +16 -0
  85. package/dist/lint/rules/prompt-length-3000.d.ts.map +1 -0
  86. package/dist/lint/rules/prompt-length-3000.js +61 -0
  87. package/dist/lint/rules/prompt-length-3000.js.map +1 -0
  88. package/dist/lint/rules/reachable-blocks.d.ts +7 -0
  89. package/dist/lint/rules/reachable-blocks.d.ts.map +1 -0
  90. package/dist/lint/rules/reachable-blocks.js +45 -0
  91. package/dist/lint/rules/reachable-blocks.js.map +1 -0
  92. package/dist/lint/rules/recording-consent-before-record.d.ts +3 -0
  93. package/dist/lint/rules/recording-consent-before-record.d.ts.map +1 -0
  94. package/dist/lint/rules/recording-consent-before-record.js +87 -0
  95. package/dist/lint/rules/recording-consent-before-record.js.map +1 -0
  96. package/dist/lint/rules/terminal-blocks.d.ts +8 -0
  97. package/dist/lint/rules/terminal-blocks.d.ts.map +1 -0
  98. package/dist/lint/rules/terminal-blocks.js +40 -0
  99. package/dist/lint/rules/terminal-blocks.js.map +1 -0
  100. package/dist/lint/rules/unique-names.d.ts +7 -0
  101. package/dist/lint/rules/unique-names.d.ts.map +1 -0
  102. package/dist/lint/rules/unique-names.js +40 -0
  103. package/dist/lint/rules/unique-names.js.map +1 -0
  104. package/dist/lint/types.d.ts +30 -0
  105. package/dist/lint/types.d.ts.map +1 -0
  106. package/dist/lint/types.js +11 -0
  107. package/dist/lint/types.js.map +1 -0
  108. package/dist/materialize.d.ts +31 -0
  109. package/dist/materialize.d.ts.map +1 -0
  110. package/dist/materialize.js +192 -0
  111. package/dist/materialize.js.map +1 -0
  112. package/dist/package-names.d.ts +11 -0
  113. package/dist/package-names.d.ts.map +1 -0
  114. package/dist/package-names.js +25 -0
  115. package/dist/package-names.js.map +1 -0
  116. package/dist/refs.d.ts +35 -0
  117. package/dist/refs.d.ts.map +1 -0
  118. package/dist/refs.js +69 -0
  119. package/dist/refs.js.map +1 -0
  120. package/dist/serialize.d.ts +13 -0
  121. package/dist/serialize.d.ts.map +1 -0
  122. package/dist/serialize.js +86 -0
  123. package/dist/serialize.js.map +1 -0
  124. package/dist/simulate.d.ts +446 -0
  125. package/dist/simulate.d.ts.map +1 -0
  126. package/dist/simulate.js +1115 -0
  127. package/dist/simulate.js.map +1 -0
  128. package/dist/synth.d.ts +25 -0
  129. package/dist/synth.d.ts.map +1 -0
  130. package/dist/synth.js +93 -0
  131. package/dist/synth.js.map +1 -0
  132. package/package.json +73 -0
  133. package/src/actions.ts +221 -0
  134. package/src/aws.ts +55 -0
  135. package/src/blocks.ts +619 -0
  136. package/src/codegen.ts +1144 -0
  137. package/src/export.ts +1190 -0
  138. package/src/flow.ts +108 -0
  139. package/src/flowdoc.ts +261 -0
  140. package/src/index.ts +31 -0
  141. package/src/layout.ts +54 -0
  142. package/src/lint/engine.ts +56 -0
  143. package/src/lint/graph.ts +86 -0
  144. package/src/lint/index.ts +10 -0
  145. package/src/lint/reporters.ts +44 -0
  146. package/src/lint/rules/action-allowed-in-flow-type.ts +32 -0
  147. package/src/lint/rules/action-count.ts +35 -0
  148. package/src/lint/rules/error-branches.ts +45 -0
  149. package/src/lint/rules/index.ts +53 -0
  150. package/src/lint/rules/module-depth-5.ts +70 -0
  151. package/src/lint/rules/no-literal-arn.ts +64 -0
  152. package/src/lint/rules/no-unresolved-token.ts +80 -0
  153. package/src/lint/rules/prompt-length-3000.ts +66 -0
  154. package/src/lint/rules/reachable-blocks.ts +50 -0
  155. package/src/lint/rules/recording-consent-before-record.ts +90 -0
  156. package/src/lint/rules/terminal-blocks.ts +45 -0
  157. package/src/lint/rules/unique-names.ts +44 -0
  158. package/src/lint/types.ts +43 -0
  159. package/src/materialize.ts +230 -0
  160. package/src/package-names.ts +26 -0
  161. package/src/refs.ts +93 -0
  162. package/src/serialize.ts +97 -0
  163. package/src/simulate.ts +1610 -0
  164. package/src/synth.ts +110 -0
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { documentStrings, findingsForActions, walkStrings } from "../graph.js";
6
+ const ARN = /arn:aws[a-z-]*:/;
7
+ /** This rule's id, for tools that report the violation before lint can run. */
8
+ export const NO_LITERAL_ARN = "no-literal-arn";
9
+ /** The sentence every literal-ARN report ends with. */
10
+ export function literalArnMessage(path) {
11
+ return `"${path}" contains a literal ARN. Use a \${cdref:type:name} token instead.`;
12
+ }
13
+ /**
14
+ * Paths of every string holding a literal ARN in an arbitrary JSON value.
15
+ *
16
+ * Takes `unknown` rather than a FlowDoc on purpose: the CLI runs this over a
17
+ * document the schema has already rejected, because the schema's own pattern
18
+ * errors describe that one mistake several times over and never name the rule.
19
+ * See packages/cli/src/docs.ts.
20
+ */
21
+ export function literalArnPaths(value) {
22
+ return walkStrings(value)
23
+ .filter(([, v]) => ARN.test(v))
24
+ .map(([path]) => path);
25
+ }
26
+ /**
27
+ * References are tokens, never literal ARNs. A literal ARN pins a flow to one
28
+ * account, region, and instance, which is the whole thing this tooling exists
29
+ * to avoid.
30
+ */
31
+ export const noLiteralArn = {
32
+ id: NO_LITERAL_ARN,
33
+ description: "Authored content must not contain a literal ARN; use a ${cdref:...} token.",
34
+ hard: true,
35
+ check({ doc, report }) {
36
+ // `refs` is authored content too: it is what the studio's reference
37
+ // pickers and the emitters read, and an ARN placed there satisfied this
38
+ // rule while the schema was the only thing catching it.
39
+ const documentLevel = [...documentStrings(doc), ...walkStrings(doc.refs ?? [], "refs")];
40
+ for (const [path, value] of documentLevel) {
41
+ if (ARN.test(value)) {
42
+ report({ severity: "error", message: literalArnMessage(path) });
43
+ }
44
+ }
45
+ for (const { action, strings } of findingsForActions(doc)) {
46
+ for (const [path, value] of strings) {
47
+ if (ARN.test(value)) {
48
+ report({
49
+ severity: "error",
50
+ blockId: action.Identifier,
51
+ message: `${path.startsWith("Transitions") ? "Transition" : "Parameter"} ${literalArnMessage(path)}`,
52
+ });
53
+ }
54
+ }
55
+ }
56
+ },
57
+ };
58
+ //# sourceMappingURL=no-literal-arn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-literal-arn.js","sourceRoot":"","sources":["../../../src/lint/rules/no-literal-arn.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/E,MAAM,GAAG,GAAG,iBAAiB,CAAC;AAE9B,+EAA+E;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,IAAI,oEAAoE,CAAC;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,WAAW,CAAC,KAAK,CAAC;SACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,cAAc;IAClB,WAAW,EAAE,4EAA4E;IACzF,IAAI,EAAE,IAAI;IACV,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,oEAAoE;QACpE,wEAAwE;QACxE,wDAAwD;QACxD,MAAM,aAAa,GAAG,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QACxF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;YAC1C,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QAED,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;gBACpC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBACpB,MAAM,CAAC;wBACL,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;wBAC1B,OAAO,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE;qBACrG,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * Every token must be well formed, must occupy its entire field, and must
4
+ * appear in the refs index. A token Connect never resolves deploys a flow that
5
+ * fails at runtime with an opaque error.
6
+ */
7
+ export declare const noUnresolvedToken: Rule;
8
+ //# sourceMappingURL=no-unresolved-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unresolved-token.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/no-unresolved-token.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAexC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,IAqD/B,CAAC"}
@@ -0,0 +1,71 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { documentStrings, findingsForActions } from "../graph.js";
6
+ import { TOKEN_PATTERN, parseToken } from "../../refs.js";
7
+ /** Any ${...} placeholder, so malformed tokens are caught rather than ignored. */
8
+ /**
9
+ * Only placeholders that ATTEMPT to be a reference are this rule's business.
10
+ *
11
+ * Scanning every `${...}` produced false positives on ordinary prompt text:
12
+ * "Your balance is ${amount}" is a literal string Connect plays verbatim, since
13
+ * Connect's own dynamic syntax is JSONPath ($.Attributes.x), not ${}. Matching
14
+ * on the `cdref` prefix still catches the cases that matter, a well-formed
15
+ * token interpolated into a longer string and a misspelled cdref, without
16
+ * claiming authority over text we do not own.
17
+ */
18
+ const CDREF_PLACEHOLDER = /\$\{\s*cdref[^}]*\}/gi;
19
+ /**
20
+ * Every token must be well formed, must occupy its entire field, and must
21
+ * appear in the refs index. A token Connect never resolves deploys a flow that
22
+ * fails at runtime with an opaque error.
23
+ */
24
+ export const noUnresolvedToken = {
25
+ id: "no-unresolved-token",
26
+ description: "Every ${cdref:...} token must be well formed, stand alone, and appear in the refs index.",
27
+ hard: true,
28
+ check({ doc, report }) {
29
+ const indexed = new Set((doc.refs ?? []).map((r) => r.token));
30
+ const entries = [
31
+ ...documentStrings(doc).map(([path, value]) => ({ path, value })),
32
+ ...findingsForActions(doc).flatMap(({ action, strings }) => strings.map(([path, value]) => ({ blockId: action.Identifier, path, value }))),
33
+ ];
34
+ const where = (path) => path.startsWith("content.Metadata")
35
+ ? ""
36
+ : path.startsWith("Transitions")
37
+ ? "Transition "
38
+ : "Parameter ";
39
+ for (const { blockId, path, value } of entries) {
40
+ const placeholders = value.match(CDREF_PLACEHOLDER) ?? [];
41
+ if (placeholders.length === 0)
42
+ continue;
43
+ // Connect requires reference fields to be fully static or a single
44
+ // JSONPath identifier, so a token cannot be part of a longer string.
45
+ if (!TOKEN_PATTERN.test(value)) {
46
+ report({
47
+ severity: "error",
48
+ ...(blockId === undefined ? {} : { blockId }),
49
+ message: `${where(path)}"${path}" embeds a token in a larger string ("${value}"). A token must be the entire value.`,
50
+ });
51
+ continue;
52
+ }
53
+ const token = placeholders[0];
54
+ if (parseToken(token) === undefined) {
55
+ report({
56
+ severity: "error",
57
+ ...(blockId === undefined ? {} : { blockId }),
58
+ message: `${where(path)}"${path}" contains a malformed token "${token}".`,
59
+ });
60
+ }
61
+ else if (!indexed.has(token)) {
62
+ report({
63
+ severity: "error",
64
+ ...(blockId === undefined ? {} : { blockId }),
65
+ message: `Token "${token}" is missing from the refs index. Re-run synth to regenerate it.`,
66
+ });
67
+ }
68
+ }
69
+ },
70
+ };
71
+ //# sourceMappingURL=no-unresolved-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unresolved-token.js","sourceRoot":"","sources":["../../../src/lint/rules/no-unresolved-token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG1D,kFAAkF;AAClF;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,qBAAqB;IACzB,WAAW,EACT,0FAA0F;IAC5F,IAAI,EAAE,IAAI;IACV,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAE9D,MAAM,OAAO,GAAwD;YACnE,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CACzD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAC9E;SACF,CAAC;QAEF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE,CACrC,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC;YACjC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;gBAC9B,CAAC,CAAC,aAAa;gBACf,CAAC,CAAC,YAAY,CAAC;QAErB,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,EAAE,CAAC;YAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC1D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAExC,mEAAmE;YACnE,qEAAqE;YACrE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/B,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;oBAC7C,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,yCAAyC,KAAK,uCAAuC;iBACrH,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YAC/B,IAAI,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;gBACpC,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;oBAC7C,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,iCAAiC,KAAK,IAAI;iBAC1E,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/B,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;oBAC7C,OAAO,EAAE,UAAU,KAAK,kEAAkE;iBAC3F,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * "When you use text, either for text-to-speech or chat, you can use a maximum
4
+ * of 3,000 billed characters, which is 6,000 characters total."
5
+ * https://docs.aws.amazon.com/connect/latest/adminguide/play.html
6
+ *
7
+ * The Get customer input block states the same limit for its own text:
8
+ * https://docs.aws.amazon.com/connect/latest/adminguide/get-customer-input.html
9
+ *
10
+ * Billed characters are the spoken text. SSML markup counts toward the 6,000
11
+ * total but not the 3,000 billed, so the two are checked separately.
12
+ */
13
+ export declare const MAX_BILLED_CHARACTERS = 3000;
14
+ export declare const MAX_TOTAL_CHARACTERS = 6000;
15
+ export declare const promptLength3000: Rule;
16
+ //# sourceMappingURL=prompt-length-3000.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-length-3000.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/prompt-length-3000.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAUzC,eAAO,MAAM,gBAAgB,EAAE,IAoC9B,CAAC"}
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { ActionType } from "../../actions.js";
6
+ /**
7
+ * "When you use text, either for text-to-speech or chat, you can use a maximum
8
+ * of 3,000 billed characters, which is 6,000 characters total."
9
+ * https://docs.aws.amazon.com/connect/latest/adminguide/play.html
10
+ *
11
+ * The Get customer input block states the same limit for its own text:
12
+ * https://docs.aws.amazon.com/connect/latest/adminguide/get-customer-input.html
13
+ *
14
+ * Billed characters are the spoken text. SSML markup counts toward the 6,000
15
+ * total but not the 3,000 billed, so the two are checked separately.
16
+ */
17
+ export const MAX_BILLED_CHARACTERS = 3000;
18
+ export const MAX_TOTAL_CHARACTERS = 6000;
19
+ /** The modeled actions that carry a Text or SSML prompt. */
20
+ const PROMPT_ACTIONS = [
21
+ ActionType.MessageParticipant,
22
+ ActionType.GetParticipantInput,
23
+ ];
24
+ const stripSsmlTags = (s) => s.replace(/<[^>]*>/g, "");
25
+ export const promptLength3000 = {
26
+ id: "prompt-length-3000",
27
+ description: "Prompt text must stay within 3,000 billed characters and 6,000 total.",
28
+ check({ doc, report }) {
29
+ for (const action of doc.content.Actions) {
30
+ if (!PROMPT_ACTIONS.includes(action.Type))
31
+ continue;
32
+ const text = action.Parameters.Text;
33
+ if (typeof text === "string" && text.length > MAX_BILLED_CHARACTERS) {
34
+ report({
35
+ severity: "error",
36
+ blockId: action.Identifier,
37
+ message: `Text is ${text.length} characters; Connect allows ${MAX_BILLED_CHARACTERS} billed characters.`,
38
+ });
39
+ }
40
+ const ssml = action.Parameters.SSML;
41
+ if (typeof ssml === "string") {
42
+ if (ssml.length > MAX_TOTAL_CHARACTERS) {
43
+ report({
44
+ severity: "error",
45
+ blockId: action.Identifier,
46
+ message: `SSML is ${ssml.length} characters; Connect allows ${MAX_TOTAL_CHARACTERS} total.`,
47
+ });
48
+ }
49
+ const spoken = stripSsmlTags(ssml).length;
50
+ if (spoken > MAX_BILLED_CHARACTERS) {
51
+ report({
52
+ severity: "error",
53
+ blockId: action.Identifier,
54
+ message: `SSML contains ${spoken} spoken characters; Connect allows ${MAX_BILLED_CHARACTERS} billed characters.`,
55
+ });
56
+ }
57
+ }
58
+ }
59
+ },
60
+ };
61
+ //# sourceMappingURL=prompt-length-3000.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-length-3000.js","sourceRoot":"","sources":["../../../src/lint/rules/prompt-length-3000.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,4DAA4D;AAC5D,MAAM,cAAc,GAAsB;IACxC,UAAU,CAAC,kBAAkB;IAC7B,UAAU,CAAC,mBAAmB;CAC/B,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAEvE,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,oBAAoB;IACxB,WAAW,EAAE,uEAAuE;IACpF,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEpD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;YACpC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;gBACpE,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EAAE,WAAW,IAAI,CAAC,MAAM,+BAA+B,qBAAqB,qBAAqB;iBACzG,CAAC,CAAC;YACL,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;YACpC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,IAAI,IAAI,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;oBACvC,MAAM,CAAC;wBACL,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;wBAC1B,OAAO,EAAE,WAAW,IAAI,CAAC,MAAM,+BAA+B,oBAAoB,SAAS;qBAC5F,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;gBAC1C,IAAI,MAAM,GAAG,qBAAqB,EAAE,CAAC;oBACnC,MAAM,CAAC;wBACL,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;wBAC1B,OAAO,EAAE,iBAAiB,MAAM,sCAAsC,qBAAqB,qBAAqB;qBACjH,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * Unreachable actions are kept, never dropped, but they are almost always an
4
+ * editing mistake. Dangling targets are a hard error: Connect rejects the flow.
5
+ */
6
+ export declare const reachableBlocks: Rule;
7
+ //# sourceMappingURL=reachable-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachable-blocks.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/reachable-blocks.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,IAqC7B,CAAC"}
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { reachable, transitionTargets } from "../graph.js";
6
+ import { actionsById } from "../graph.js";
7
+ /**
8
+ * Unreachable actions are kept, never dropped, but they are almost always an
9
+ * editing mistake. Dangling targets are a hard error: Connect rejects the flow.
10
+ */
11
+ export const reachableBlocks = {
12
+ id: "reachable-blocks",
13
+ description: "Every action should be reachable from StartAction, and every transition target must exist.",
14
+ check({ doc, report }) {
15
+ const byId = actionsById(doc);
16
+ if (!byId.has(doc.content.StartAction)) {
17
+ report({
18
+ severity: "error",
19
+ message: `StartAction "${doc.content.StartAction}" does not match any action.`,
20
+ });
21
+ }
22
+ for (const action of doc.content.Actions) {
23
+ for (const target of transitionTargets(action)) {
24
+ if (!byId.has(target)) {
25
+ report({
26
+ severity: "error",
27
+ blockId: action.Identifier,
28
+ message: `Transition points at "${target}", which does not exist.`,
29
+ });
30
+ }
31
+ }
32
+ }
33
+ const live = reachable(doc);
34
+ for (const action of doc.content.Actions) {
35
+ if (!live.has(action.Identifier)) {
36
+ report({
37
+ severity: "warning",
38
+ blockId: action.Identifier,
39
+ message: "Action is not reachable from StartAction.",
40
+ });
41
+ }
42
+ }
43
+ },
44
+ };
45
+ //# sourceMappingURL=reachable-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachable-blocks.js","sourceRoot":"","sources":["../../../src/lint/rules/reachable-blocks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,kBAAkB;IACtB,WAAW,EACT,4FAA4F;IAC9F,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAE9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC;gBACL,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,gBAAgB,GAAG,CAAC,OAAO,CAAC,WAAW,8BAA8B;aAC/E,CAAC,CAAC;QACL,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,KAAK,MAAM,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtB,MAAM,CAAC;wBACL,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;wBAC1B,OAAO,EAAE,yBAAyB,MAAM,0BAA0B;qBACnE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC;oBACL,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EAAE,2CAA2C;iBACrD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const recordingConsentBeforeRecord: Rule;
3
+ //# sourceMappingURL=recording-consent-before-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-consent-before-record.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/recording-consent-before-record.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAkExC,eAAO,MAAM,4BAA4B,EAAE,IAgB1C,CAAC"}
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { ActionType } from "../../actions.js";
6
+ import { actionsById, transitionTargets } from "../graph.js";
7
+ /**
8
+ * Recording a caller without first telling them is a legal problem in many
9
+ * jurisdictions. This rule is deliberately generic: it requires that every path
10
+ * reaching an action which enables recording passes through an action that
11
+ * plays the participant something first. What that message says is the
12
+ * operator's business; that one exists is checkable here.
13
+ */
14
+ function enablesRecording(action) {
15
+ if (action.Type !== ActionType.UpdateContactRecordingBehavior)
16
+ return false;
17
+ const behavior = action.Parameters.RecordingBehavior;
18
+ if (behavior === null || typeof behavior !== "object")
19
+ return false;
20
+ const participants = behavior.RecordedParticipants;
21
+ // An empty list disables recording, per the action's documentation.
22
+ return Array.isArray(participants) && participants.length > 0;
23
+ }
24
+ /** The parameters that play something on the two announcing actions. */
25
+ const BODY_FIELDS = ["Text", "SSML", "PromptId"];
26
+ /**
27
+ * MessageParticipant carries exactly one of Text, SSML, or PromptId.
28
+ * GetParticipantInput plays its prompt before waiting for a key ("This call
29
+ * may be recorded. Press 1 to continue." is the usual consent menu), but every
30
+ * prompt field is optional on its action page, so a menu with none is silent.
31
+ * https://docs.aws.amazon.com/connect/latest/devguide/participant-actions-messageparticipant.html
32
+ * https://docs.aws.amazon.com/connect/latest/devguide/participant-actions-getparticipantinput.html
33
+ *
34
+ * Content is what counts, not the key: the studio's body switch writes
35
+ * `Text: ""` until the author types, and an empty or blank Text or SSML plays
36
+ * nothing on either action.
37
+ */
38
+ function announces(action) {
39
+ if (action.Type !== ActionType.MessageParticipant &&
40
+ action.Type !== ActionType.GetParticipantInput) {
41
+ return false;
42
+ }
43
+ return BODY_FIELDS.some((k) => {
44
+ const body = action.Parameters[k];
45
+ return typeof body === "string" && body.trim() !== "";
46
+ });
47
+ }
48
+ /** Ids from which `targetId` is reachable without passing an announcement. */
49
+ function pathsWithoutAnnouncement(doc, targetId) {
50
+ const byId = actionsById(doc);
51
+ const seen = new Set();
52
+ const queue = [doc.content.StartAction];
53
+ while (queue.length > 0) {
54
+ const id = queue.shift();
55
+ if (id === targetId)
56
+ return true;
57
+ if (seen.has(id))
58
+ continue;
59
+ seen.add(id);
60
+ const action = byId.get(id);
61
+ if (action === undefined)
62
+ continue;
63
+ // An announcement on this path satisfies the rule, so stop exploring it.
64
+ if (announces(action))
65
+ continue;
66
+ queue.push(...transitionTargets(action));
67
+ }
68
+ return false;
69
+ }
70
+ export const recordingConsentBeforeRecord = {
71
+ id: "recording-consent-before-record",
72
+ description: "Recording must not start before the participant has been played a message.",
73
+ check({ doc, report }) {
74
+ for (const action of doc.content.Actions) {
75
+ if (!enablesRecording(action))
76
+ continue;
77
+ if (pathsWithoutAnnouncement(doc, action.Identifier)) {
78
+ report({
79
+ severity: "error",
80
+ blockId: action.Identifier,
81
+ message: "Recording is enabled on a path that plays no message first. Announce recording before starting it.",
82
+ });
83
+ }
84
+ }
85
+ },
86
+ };
87
+ //# sourceMappingURL=recording-consent-before-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-consent-before-record.js","sourceRoot":"","sources":["../../../src/lint/rules/recording-consent-before-record.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAI7D;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,MAAkB;IAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,8BAA8B;QAAE,OAAO,KAAK,CAAC;IAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC;IACrD,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,YAAY,GAAI,QAA+C,CAAC,oBAAoB,CAAC;IAC3F,oEAAoE;IACpE,OAAO,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;AAChE,CAAC;AAED,wEAAwE;AACxE,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,SAAS,SAAS,CAAC,MAAkB;IACnC,IACE,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,kBAAkB;QAC7C,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,mBAAmB,EAC9C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,SAAS,wBAAwB,CAAC,GAAY,EAAE,QAAgB;IAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,KAAK,GAAa,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAElD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC1B,IAAI,EAAE,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACjC,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,yEAAyE;QACzE,IAAI,SAAS,CAAC,MAAM,CAAC;YAAE,SAAS;QAChC,KAAK,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAS;IAChD,EAAE,EAAE,iCAAiC;IACrC,WAAW,EAAE,4EAA4E;IACzF,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxC,IAAI,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBACrD,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EACL,oGAAoG;iBACvG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * A flow that cannot terminate strands the contact. Also catches the inverse
4
+ * mistake: an action with empty Transitions that is not a terminal type, which
5
+ * Connect treats as an unintended dead end.
6
+ */
7
+ export declare const terminalBlocks: Rule;
8
+ //# sourceMappingURL=terminal-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-blocks.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/terminal-blocks.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,IA8B5B,CAAC"}
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { TERMINAL_ACTIONS } from "../../actions.js";
6
+ import { isTerminal, reachable } from "../graph.js";
7
+ import { actionsById } from "../graph.js";
8
+ /**
9
+ * A flow that cannot terminate strands the contact. Also catches the inverse
10
+ * mistake: an action with empty Transitions that is not a terminal type, which
11
+ * Connect treats as an unintended dead end.
12
+ */
13
+ export const terminalBlocks = {
14
+ id: "terminal-blocks",
15
+ description: "A flow must reach a terminal action, and only terminal action types may have empty transitions.",
16
+ check({ doc, report }) {
17
+ const byId = actionsById(doc);
18
+ const live = reachable(doc);
19
+ const reachesTerminal = [...live].some((id) => {
20
+ const a = byId.get(id);
21
+ return a !== undefined && isTerminal(a) && TERMINAL_ACTIONS.includes(a.Type);
22
+ });
23
+ if (!reachesTerminal) {
24
+ report({
25
+ severity: "error",
26
+ message: "No terminal action is reachable from StartAction; the flow cannot end.",
27
+ });
28
+ }
29
+ for (const action of doc.content.Actions) {
30
+ if (isTerminal(action) && !TERMINAL_ACTIONS.includes(action.Type)) {
31
+ report({
32
+ severity: "warning",
33
+ blockId: action.Identifier,
34
+ message: `${action.Type} has no transitions but is not a terminal action type, so the flow dead-ends here.`,
35
+ });
36
+ }
37
+ }
38
+ },
39
+ };
40
+ //# sourceMappingURL=terminal-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-blocks.js","sourceRoot":"","sources":["../../../src/lint/rules/terminal-blocks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAS;IAClC,EAAE,EAAE,iBAAiB;IACrB,WAAW,EACT,iGAAiG;IACnG,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE;QACnB,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAE5B,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvB,OAAO,CAAC,KAAK,SAAS,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,CAAC;gBACL,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,wEAAwE;aAClF,CAAC,CAAC;QACL,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClE,MAAM,CAAC;oBACL,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,oFAAoF;iBAC5G,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * Identifiers are unique within a flow and document names unique within a set,
4
+ * because both are used as keys by the emitters.
5
+ */
6
+ export declare const uniqueNames: Rule;
7
+ //# sourceMappingURL=unique-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unique-names.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/unique-names.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,IAgCzB,CAAC"}
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { isValidIdentifier } from "../../flowdoc.js";
6
+ /**
7
+ * Identifiers are unique within a flow and document names unique within a set,
8
+ * because both are used as keys by the emitters.
9
+ */
10
+ export const uniqueNames = {
11
+ id: "unique-names",
12
+ description: "Action Identifiers are unique within a flow, document names unique within a set, and both are valid.",
13
+ check({ doc, all, report }) {
14
+ const seen = new Set();
15
+ for (const action of doc.content.Actions) {
16
+ if (seen.has(action.Identifier)) {
17
+ report({
18
+ severity: "error",
19
+ blockId: action.Identifier,
20
+ message: `Duplicate Identifier "${action.Identifier}".`,
21
+ });
22
+ }
23
+ seen.add(action.Identifier);
24
+ if (!isValidIdentifier(action.Identifier)) {
25
+ report({
26
+ severity: "error",
27
+ blockId: action.Identifier,
28
+ message: `Identifier "${action.Identifier}" is not valid: at most 50 characters, and none of % : ( \\ / ) = $ , ; [ ] { }.`,
29
+ });
30
+ }
31
+ }
32
+ if (all.filter((d) => d.name === doc.name).length > 1) {
33
+ report({
34
+ severity: "error",
35
+ message: `Duplicate document name "${doc.name}" in this flow set.`,
36
+ });
37
+ }
38
+ },
39
+ };
40
+ //# sourceMappingURL=unique-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unique-names.js","sourceRoot":"","sources":["../../../src/lint/rules/unique-names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,cAAc;IAClB,WAAW,EACT,sGAAsG;IACxG,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE;QACxB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBAChC,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EAAE,yBAAyB,MAAM,CAAC,UAAU,IAAI;iBACxD,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAE5B,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC;oBACL,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,MAAM,CAAC,UAAU;oBAC1B,OAAO,EAAE,eAAe,MAAM,CAAC,UAAU,kFAAkF;iBAC5H,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtD,MAAM,CAAC;gBACL,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,4BAA4B,GAAG,CAAC,IAAI,qBAAqB;aACnE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { FlowDoc } from "../flowdoc.js";
2
+ export type Severity = "error" | "warning";
3
+ export interface Finding {
4
+ /** Stable rule id. Never renamed; the Go provider keys off these. */
5
+ rule: string;
6
+ severity: Severity;
7
+ message: string;
8
+ /** Name of the FlowDoc the finding belongs to. */
9
+ doc: string;
10
+ /** Action Identifier, when the finding is about one action. */
11
+ blockId?: string;
12
+ }
13
+ export interface RuleContext {
14
+ doc: FlowDoc;
15
+ /** Every doc being linted, for rules that need to follow module references. */
16
+ all: readonly FlowDoc[];
17
+ report(finding: Omit<Finding, "rule" | "doc">): void;
18
+ }
19
+ export interface Rule {
20
+ id: string;
21
+ description: string;
22
+ /**
23
+ * A hard rule blocks a save in the studio rather than merely reporting.
24
+ * docs/02-studio-design.md: the studio can never save a doc that fails
25
+ * no-literal-arn or no-unresolved-token.
26
+ */
27
+ hard?: boolean;
28
+ check(ctx: RuleContext): void;
29
+ }
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lint/types.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,OAAO,CAAC;IACb,+EAA+E;IAC/E,GAAG,EAAE,SAAS,OAAO,EAAE,CAAC;IACxB,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B"}
@@ -0,0 +1,11 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ // Lint engine types.
6
+ //
7
+ // Nothing in this directory may import a Node builtin. The studio runs the
8
+ // engine in a web worker, so the whole module graph has to be browser-safe.
9
+ // Enforced by lint-browser-safe.test.ts, which walks the imports statically.
10
+ export {};
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lint/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAqB;AACrB,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E"}
@@ -0,0 +1,31 @@
1
+ import type { FlowContent, FlowDoc, RefEntry } from "./flowdoc.js";
2
+ /**
3
+ * Strict map materialization failed: one or more tokens in the document have
4
+ * no entry in the resource map. `missingTokens` is sorted and complete, so a
5
+ * caller fixes the map once, not one missing key per run.
6
+ */
7
+ export declare class MaterializeError extends Error {
8
+ readonly missingTokens: readonly string[];
9
+ constructor(missingTokens: readonly string[], reason?: string);
10
+ }
11
+ /**
12
+ * Strict materialization against a token -> value map. Every token in the
13
+ * content must have an entry; otherwise a MaterializeError lists ALL missing
14
+ * tokens (sorted), not just the first. Mapped values are not validated: at
15
+ * this point literal ARNs are the goal, not a mistake.
16
+ */
17
+ export declare function materializeWithMap(doc: FlowDoc, resourceMap: Record<string, string>): FlowContent;
18
+ /**
19
+ * Binder materialization for IaC backends (@flow-as-code/cdk). The binder returns an
20
+ * opaque string per reference, typically a CDK token that CloudFormation later
21
+ * resolves to an ARN. Output passes through exactly as returned, with no
22
+ * validation and no arn-shape lint (SPEC.md, Materialization).
23
+ */
24
+ export declare function materializeWithBinder(doc: FlowDoc, binder: (ref: RefEntry) => string): FlowContent;
25
+ /**
26
+ * Canonical JSON text of deployable content, two-space indented, newline
27
+ * terminated. Metadata leads with EntryPointPosition then ActionMetadata,
28
+ * matching the Flow language example; everything nested is key-sorted.
29
+ */
30
+ export declare function serializeContent(content: FlowContent): string;
31
+ //# sourceMappingURL=materialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../src/materialize.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,OAAO,EAAS,QAAQ,EAAE,MAAM,cAAc,CAAC;AAMtF;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;gBAE9B,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM;CAU9D;AAoGD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CA4BjG;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GAChC,WAAW,CAQb;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAmB7D"}