al-sem 0.0.1

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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/package.json +64 -0
  4. package/scripts/d40-diff.ts +44 -0
  5. package/scripts/fetch-native-parser.ts +179 -0
  6. package/scripts/precision-sample.ts +99 -0
  7. package/scripts/precision-study.ts +42 -0
  8. package/scripts/precision-tabulate.ts +52 -0
  9. package/src/cli/baseline.ts +31 -0
  10. package/src/cli/diff.ts +199 -0
  11. package/src/cli/events-chains.ts +56 -0
  12. package/src/cli/events-fanout.ts +87 -0
  13. package/src/cli/exit-code.ts +30 -0
  14. package/src/cli/fingerprint-indexes.ts +130 -0
  15. package/src/cli/fingerprint-query.ts +543 -0
  16. package/src/cli/fingerprint-witness.ts +493 -0
  17. package/src/cli/fingerprint.ts +292 -0
  18. package/src/cli/format-compact-json.ts +45 -0
  19. package/src/cli/format-events.ts +77 -0
  20. package/src/cli/format-fingerprint.ts +295 -0
  21. package/src/cli/format-html.ts +503 -0
  22. package/src/cli/format-json.ts +13 -0
  23. package/src/cli/format-policy.ts +95 -0
  24. package/src/cli/format-sarif.ts +186 -0
  25. package/src/cli/format-terminal.ts +153 -0
  26. package/src/cli/index.ts +566 -0
  27. package/src/cli/policy.ts +204 -0
  28. package/src/config/roots-config.ts +302 -0
  29. package/src/deps/cache-versions.ts +74 -0
  30. package/src/deps/canonical-json.ts +27 -0
  31. package/src/deps/dependency-artifact.ts +144 -0
  32. package/src/deps/dependency-cache.ts +262 -0
  33. package/src/deps/dependency-dag.ts +128 -0
  34. package/src/deps/dependency-package-discovery.ts +85 -0
  35. package/src/deps/dependency-pipeline.ts +483 -0
  36. package/src/deps/dependency-projection.ts +211 -0
  37. package/src/deps/dependency-resolver.ts +154 -0
  38. package/src/deps/workspace-dependencies.ts +114 -0
  39. package/src/detectors/capability-query.ts +145 -0
  40. package/src/detectors/confidence.ts +52 -0
  41. package/src/detectors/d1-db-op-in-loop.ts +457 -0
  42. package/src/detectors/d10-self-modifying-loop.ts +114 -0
  43. package/src/detectors/d11-modify-without-get.ts +129 -0
  44. package/src/detectors/d12-dead-integration-event.ts +81 -0
  45. package/src/detectors/d13-cross-app-internal-call.ts +105 -0
  46. package/src/detectors/d14-dead-routine.ts +151 -0
  47. package/src/detectors/d16-obsolete-routine-call.ts +94 -0
  48. package/src/detectors/d17-min-version-drift.ts +157 -0
  49. package/src/detectors/d18-constant-filter-in-loop.ts +151 -0
  50. package/src/detectors/d19-unused-parameter.ts +116 -0
  51. package/src/detectors/d2-event-fanout-in-loop.ts +240 -0
  52. package/src/detectors/d20-unreachable-after-exit.ts +92 -0
  53. package/src/detectors/d21-read-without-load.ts +128 -0
  54. package/src/detectors/d22-flowfield-without-calcfields.ts +168 -0
  55. package/src/detectors/d29-subscriber-modify-on-event-record.ts +163 -0
  56. package/src/detectors/d3-load-state.ts +72 -0
  57. package/src/detectors/d3-missing-setloadfields.ts +234 -0
  58. package/src/detectors/d32-constant-boolean-parameter.ts +185 -0
  59. package/src/detectors/d33-unfiltered-bulk-write.ts +173 -0
  60. package/src/detectors/d34-commit-in-loop.ts +206 -0
  61. package/src/detectors/d35-commit-in-event-subscriber.ts +138 -0
  62. package/src/detectors/d36-late-setloadfields.ts +162 -0
  63. package/src/detectors/d37-validate-without-persist.ts +271 -0
  64. package/src/detectors/d38-subscriber-to-obsolete-event.ts +140 -0
  65. package/src/detectors/d39-record-left-dirty-across-chain.ts +165 -0
  66. package/src/detectors/d4-repeated-lookup-in-loop.ts +128 -0
  67. package/src/detectors/d40-transitive-load-missing.ts +217 -0
  68. package/src/detectors/d41-transitive-filter-loss.ts +200 -0
  69. package/src/detectors/d42-cross-call-wrong-setloadfields.ts +243 -0
  70. package/src/detectors/d43-event-ishandled-skip.ts +257 -0
  71. package/src/detectors/d44-event-multi-subscriber-overlap.ts +223 -0
  72. package/src/detectors/d45-event-transitive-table-exposure.ts +159 -0
  73. package/src/detectors/d5-set-based-opportunity.ts +162 -0
  74. package/src/detectors/d7-recursive-event-expansion.ts +151 -0
  75. package/src/detectors/d8-commit-in-transaction.ts +132 -0
  76. package/src/detectors/d9-transaction-span-summary.ts +107 -0
  77. package/src/detectors/detector-context.ts +121 -0
  78. package/src/detectors/finding-grouping.ts +61 -0
  79. package/src/detectors/path-merge.ts +174 -0
  80. package/src/detectors/registry.ts +176 -0
  81. package/src/detectors/table-display.ts +42 -0
  82. package/src/diff/diff-abi.ts +195 -0
  83. package/src/diff/diff-capabilities.ts +179 -0
  84. package/src/diff/diff-engine.ts +146 -0
  85. package/src/diff/diff-events.ts +323 -0
  86. package/src/diff/diff-identity.ts +73 -0
  87. package/src/diff/diff-indexes.ts +199 -0
  88. package/src/diff/diff-permissions.ts +260 -0
  89. package/src/diff/diff-policy.ts +101 -0
  90. package/src/diff/diff-preflight.ts +66 -0
  91. package/src/diff/diff-renames.ts +104 -0
  92. package/src/diff/diff-schema.ts +232 -0
  93. package/src/diff/format-diff.ts +148 -0
  94. package/src/engine/attribute-parser.ts +50 -0
  95. package/src/engine/capability-cone.ts +531 -0
  96. package/src/engine/combined-graph.ts +357 -0
  97. package/src/engine/control-flow-walker.ts +1317 -0
  98. package/src/engine/dispatch-sites.ts +199 -0
  99. package/src/engine/effect-lattice.ts +81 -0
  100. package/src/engine/entry-points.ts +57 -0
  101. package/src/engine/event-flow.ts +524 -0
  102. package/src/engine/event-relay.ts +92 -0
  103. package/src/engine/op-classification.ts +92 -0
  104. package/src/engine/path-walker.ts +189 -0
  105. package/src/engine/reverse-call-graph.ts +23 -0
  106. package/src/engine/root-classifier-overlay.ts +194 -0
  107. package/src/engine/root-classifier.ts +135 -0
  108. package/src/engine/scc.ts +110 -0
  109. package/src/engine/source-anchor.ts +25 -0
  110. package/src/engine/summary-context.ts +104 -0
  111. package/src/engine/summary-engine.ts +296 -0
  112. package/src/engine/summary-runner.ts +560 -0
  113. package/src/engine/transaction-spans.ts +112 -0
  114. package/src/engine/uncertainty-util.ts +54 -0
  115. package/src/hash.ts +31 -0
  116. package/src/index/attribute-from-node.ts +141 -0
  117. package/src/index/callee-from-node.ts +181 -0
  118. package/src/index/capability/background.ts +90 -0
  119. package/src/index/capability/commit.ts +44 -0
  120. package/src/index/capability/dispatch.ts +164 -0
  121. package/src/index/capability/events.ts +65 -0
  122. package/src/index/capability/extractor.ts +124 -0
  123. package/src/index/capability/file-blob.ts +137 -0
  124. package/src/index/capability/http.ts +159 -0
  125. package/src/index/capability/hyperlink.ts +60 -0
  126. package/src/index/capability/isolated-storage.ts +179 -0
  127. package/src/index/capability/table.ts +113 -0
  128. package/src/index/capability/telemetry.ts +84 -0
  129. package/src/index/capability/ui.ts +55 -0
  130. package/src/index/capability/value-source.ts +202 -0
  131. package/src/index/expression-from-node.ts +117 -0
  132. package/src/index/indexer.ts +102 -0
  133. package/src/index/intraprocedural-body.ts +1467 -0
  134. package/src/index/intraprocedural-ops.ts +253 -0
  135. package/src/index/intraprocedural-refs.ts +188 -0
  136. package/src/index/object-indexer.ts +279 -0
  137. package/src/index/routine-indexer.ts +282 -0
  138. package/src/index/routine-signature.ts +46 -0
  139. package/src/index/variable-indexer.ts +134 -0
  140. package/src/index/variable-initializer-extractor.ts +155 -0
  141. package/src/index/variable-type-normalizer.ts +83 -0
  142. package/src/index.ts +267 -0
  143. package/src/mcp/server.ts +72 -0
  144. package/src/mcp/session.ts +49 -0
  145. package/src/mcp/tools/explain-path.ts +75 -0
  146. package/src/mcp/tools/get-analysis-health.ts +62 -0
  147. package/src/mcp/tools/get-finding.ts +47 -0
  148. package/src/mcp/tools/get-routine-summary.ts +126 -0
  149. package/src/mcp/tools/list-findings.ts +85 -0
  150. package/src/mcp/tools/list-hotspots.ts +78 -0
  151. package/src/mcp/tools/list-rollups.ts +103 -0
  152. package/src/mcp/tools/validators.ts +25 -0
  153. package/src/model/attributes.ts +120 -0
  154. package/src/model/callee.ts +45 -0
  155. package/src/model/capability.ts +187 -0
  156. package/src/model/coverage.ts +85 -0
  157. package/src/model/entities.ts +628 -0
  158. package/src/model/expression.ts +98 -0
  159. package/src/model/finding.ts +110 -0
  160. package/src/model/graph-edge.ts +93 -0
  161. package/src/model/graph.ts +62 -0
  162. package/src/model/identity.ts +81 -0
  163. package/src/model/ids.ts +90 -0
  164. package/src/model/index.ts +13 -0
  165. package/src/model/model.ts +51 -0
  166. package/src/model/permission.ts +76 -0
  167. package/src/model/root-classification.ts +116 -0
  168. package/src/model/stable-identity.ts +102 -0
  169. package/src/model/summary.ts +96 -0
  170. package/src/parser/ast.ts +82 -0
  171. package/src/parser/native/ffi.ts +145 -0
  172. package/src/parser/native/parse-index-pool.ts +148 -0
  173. package/src/parser/native/parse-index-worker.ts +94 -0
  174. package/src/parser/native/wrapper.ts +353 -0
  175. package/src/parser/parser-init.ts +43 -0
  176. package/src/perf/profiler.ts +66 -0
  177. package/src/policy/policy-default.yaml +83 -0
  178. package/src/policy/policy-engine.ts +339 -0
  179. package/src/policy/policy-loader.ts +257 -0
  180. package/src/policy/policy-schema.json +379 -0
  181. package/src/policy/policy-types.ts +81 -0
  182. package/src/policy/predicate-compiler.ts +151 -0
  183. package/src/policy/predicate-evaluator.ts +267 -0
  184. package/src/policy/predicate-fields.ts +439 -0
  185. package/src/projection/actionable-anchor.ts +48 -0
  186. package/src/projection/finding-filters.ts +44 -0
  187. package/src/projection/finding-fingerprint.ts +54 -0
  188. package/src/projection/finding-groups.ts +41 -0
  189. package/src/projection/finding-summary.ts +110 -0
  190. package/src/projection/rollup-findings.ts +105 -0
  191. package/src/providers/discover.ts +88 -0
  192. package/src/providers/external.ts +46 -0
  193. package/src/providers/types.ts +36 -0
  194. package/src/providers/workspace.ts +117 -0
  195. package/src/resolve/call-resolver.ts +117 -0
  196. package/src/resolve/coverage.ts +61 -0
  197. package/src/resolve/event-graph.ts +166 -0
  198. package/src/resolve/implicit-edges.ts +53 -0
  199. package/src/resolve/record-types.ts +36 -0
  200. package/src/resolve/resolver.ts +23 -0
  201. package/src/resolve/semantic-graph.ts +29 -0
  202. package/src/resolve/symbol-table.ts +69 -0
  203. package/src/snapshot/app-snapshot.ts +74 -0
  204. package/src/snapshot/compose.ts +100 -0
  205. package/src/snapshot/derive/callsite-evidence.ts +76 -0
  206. package/src/snapshot/derive/capability-facts.ts +70 -0
  207. package/src/snapshot/derive/contracts.ts +131 -0
  208. package/src/snapshot/derive/coverage.ts +35 -0
  209. package/src/snapshot/derive/event-declarations.ts +140 -0
  210. package/src/snapshot/derive/identity-table.ts +58 -0
  211. package/src/snapshot/derive/inputs.ts +91 -0
  212. package/src/snapshot/derive/operation-evidence.ts +70 -0
  213. package/src/snapshot/derive/permissions.ts +186 -0
  214. package/src/snapshot/derive/root-classifications.ts +56 -0
  215. package/src/snapshot/derive/schema.ts +130 -0
  216. package/src/snapshot/derive/typed-edges.ts +60 -0
  217. package/src/snapshot/derive/workspace-fingerprint.ts +19 -0
  218. package/src/snapshot/deserialize.ts +40 -0
  219. package/src/snapshot/serialize-cbor-gz.ts +12 -0
  220. package/src/snapshot/serialize-cbor.ts +19 -0
  221. package/src/snapshot/serialize-json.ts +22 -0
  222. package/src/snapshot/shard.ts +134 -0
  223. package/src/snapshot/types.ts +181 -0
  224. package/src/symbols/app-manifest.ts +96 -0
  225. package/src/symbols/app-package-zip.ts +50 -0
  226. package/src/symbols/embedded-source-reader.ts +41 -0
  227. package/src/symbols/package-hash.ts +81 -0
  228. package/src/symbols/symbol-reader.ts +101 -0
  229. package/src/symbols/symbol-reference-parser.ts +378 -0
  230. package/src/symbols/symbol-reference-reader.ts +27 -0
  231. package/tsconfig.json +18 -0
@@ -0,0 +1,628 @@
1
+ import type { AttributeInfo } from "./attributes.ts";
2
+ import type { Callee } from "./callee.ts";
3
+ import type { ValueSource } from "./capability.ts";
4
+ import type { ExpressionInfo } from "./expression.ts";
5
+ import type { SourceAnchor } from "./identity.ts";
6
+ import type {
7
+ CallsiteId,
8
+ CanonicalRoutineKey,
9
+ FieldId,
10
+ KeyId,
11
+ LoopId,
12
+ ObjectId,
13
+ OperationId,
14
+ RecordVariableId,
15
+ RoutineId,
16
+ RoutineKind,
17
+ TableId,
18
+ } from "./ids.ts";
19
+ import type { RoutineSummary } from "./summary.ts";
20
+
21
+ /** Whether an entity is under analysis ("primary") or merged-in context only ("dependency"). */
22
+ export type AnalysisRole = "primary" | "dependency";
23
+
24
+ /** Resolve an entity's analysis role; an absent `analysisRole` means "primary". */
25
+ export function roleOf(x: { analysisRole?: AnalysisRole }): AnalysisRole {
26
+ return x.analysisRole ?? "primary";
27
+ }
28
+
29
+ /** Output scope for CLI/report surfaces. "primary" keeps only the workspace's own
30
+ * app(s); "all" keeps the full merged model (workspace + dependencies). */
31
+ export type Scope = "primary" | "all";
32
+
33
+ export type RecordOpType =
34
+ | "FindSet"
35
+ | "FindFirst"
36
+ | "FindLast"
37
+ | "Find"
38
+ | "Get"
39
+ | "CalcFields"
40
+ | "CalcSums"
41
+ | "TestField"
42
+ | "Modify"
43
+ | "ModifyAll"
44
+ | "Insert"
45
+ | "Delete"
46
+ | "DeleteAll"
47
+ | "SetLoadFields"
48
+ | "AddLoadFields"
49
+ | "SetRange"
50
+ | "SetFilter"
51
+ | "SetCurrentKey"
52
+ | "Reset"
53
+ | "Copy"
54
+ | "TransferFields"
55
+ | "Validate"
56
+ | "Init"
57
+ | "Next"
58
+ | "Count"
59
+ | "CountApprox"
60
+ | "IsEmpty"
61
+ | "LockTable";
62
+
63
+ /**
64
+ * Whether a record variable is temporary. Can be caller-dependent when the record is a
65
+ * by-var/value parameter — see spec Section 2.
66
+ */
67
+ export type TempState =
68
+ | { kind: "known"; value: boolean }
69
+ | { kind: "unknown" }
70
+ | { kind: "parameter-dependent"; parameterIndex: number };
71
+
72
+ export interface ParameterSymbol {
73
+ index: number;
74
+ name: string;
75
+ typeText: string; // raw type string, e.g. 'Record "Sales Line"'
76
+ isVar: boolean; // passed by reference
77
+ isRecord: boolean;
78
+ tableName?: string; // when isRecord
79
+ }
80
+
81
+ export interface App {
82
+ appGuid: string;
83
+ publisher: string;
84
+ name: string;
85
+ version: string;
86
+ analysisRole?: AnalysisRole;
87
+ }
88
+
89
+ export interface ObjectDecl {
90
+ id: ObjectId;
91
+ appGuid: string;
92
+ objectType: string; // "Codeunit", "Table", "TableExtension", "Page", ...
93
+ objectNumber: number;
94
+ name: string;
95
+ sourceUnitId: string;
96
+ sourceHash: string;
97
+ sourceAnchor: SourceAnchor;
98
+ analysisRole?: AnalysisRole;
99
+ /**
100
+ * Codeunit Subtype property value when declared (e.g. "Install", "Upgrade",
101
+ * "Test", "Normal"). Undefined for non-codeunit objects or when not declared.
102
+ * Verbatim from the AL source (preserves casing).
103
+ */
104
+ objectSubtype?: string;
105
+ /**
106
+ * Page PageType property value when declared (e.g. "API", "List", "Card").
107
+ * Undefined for non-page objects or when not declared. Verbatim from the AL source.
108
+ */
109
+ pageType?: string;
110
+ }
111
+
112
+ /** FieldClass + ownership. Ownership follows the declaring object (may be a tableextension). */
113
+ export interface Field {
114
+ id: FieldId;
115
+ physicalTableId: TableId;
116
+ declaringObjectId: ObjectId;
117
+ declaringAppId: string; // appGuid
118
+ fieldNumber: number;
119
+ name: string;
120
+ fieldClass: "Normal" | "FlowField" | "FlowFilter";
121
+ dataType: string;
122
+ isBlobLike: boolean;
123
+ }
124
+
125
+ export interface Key {
126
+ id: KeyId;
127
+ physicalTableId: TableId;
128
+ declaringObjectId: ObjectId;
129
+ fields: FieldId[];
130
+ sumIndexFields?: FieldId[];
131
+ isEnabled?: boolean | "unknown";
132
+ }
133
+
134
+ export interface Table {
135
+ id: TableId;
136
+ appGuid: string;
137
+ tableNumber: number;
138
+ name: string;
139
+ fields: Field[];
140
+ keys: Key[];
141
+ }
142
+
143
+ export type LoopType = "repeat" | "for" | "foreach" | "while";
144
+
145
+ export interface LoopNode {
146
+ id: LoopId;
147
+ type: LoopType;
148
+ sourceAnchor: SourceAnchor;
149
+ }
150
+
151
+ /**
152
+ * Per-argument structural binding at a CallSite. Replaces text-matching on
153
+ * argumentTexts (which is retained for display only). Populated by
154
+ * routine-indexer.ts when the callsite is constructed.
155
+ *
156
+ * Detectors must check `bindingResolution === "resolved"` before trusting
157
+ * `sourceParameterIndex` / `calleeParameterIsVar` for cross-call semantics —
158
+ * an unresolved callee still emits positional bindings, but the var-ness
159
+ * information is not authoritative.
160
+ */
161
+ export interface CallArgumentBinding {
162
+ /** Positional 0-based index, callee-side. */
163
+ parameterIndex: number;
164
+
165
+ /** Where the argument value comes from on the caller side. */
166
+ sourceKind: "parameter" | "local" | "global" | "implicit-rec" | "expression" | "unknown";
167
+
168
+ /** Lowercased source variable name when sourceKind is parameter/local/global/implicit-rec.
169
+ * Diagnostic / cross-reference only — never used to drive semantics by name. */
170
+ sourceVariableName?: string;
171
+
172
+ /** Stable record-variable id from routine.features.recordVariables[].id, when the
173
+ * source is a record variable. Detectors should prefer this over sourceVariableName. */
174
+ sourceRecordVariableId?: string;
175
+
176
+ /** When sourceKind === "parameter", the source parameter's index in the enclosing
177
+ * routine. Drives entry-requirement composition (c1a). */
178
+ sourceParameterIndex?: number;
179
+
180
+ /** Whether the source-side variable is declared `var` (when sourceKind === "parameter").
181
+ * Drives exit-effect composition (c1b). */
182
+ callerSourceParameterIsVar?: boolean;
183
+
184
+ /** Whether the callee-side parameter is declared `var`. Drives exit-effect composition. */
185
+ calleeParameterIsVar: boolean;
186
+
187
+ /** Resolved table for the source record when available. */
188
+ sourceTableId?: TableId | "unknown";
189
+
190
+ /** Tempness of the source record when known. */
191
+ sourceTempState?: TempState;
192
+
193
+ /** Source anchor for the argument expression itself. Detectors anchor evidence at
194
+ * the argument level when emitting record-flow findings. */
195
+ argumentAnchor: SourceAnchor;
196
+
197
+ /** Did al-sem resolve enough to trust this binding for semantics?
198
+ * "resolved" — callee signature known, positional binding sound.
199
+ * "unresolved-callee" — callee not resolved; var-ness must NOT be trusted.
200
+ * "ambiguous" — overload ambiguity; same caveats.
201
+ * "non-record-arg" — argument is not a record (literal/expression). */
202
+ bindingResolution: "resolved" | "unresolved-callee" | "ambiguous" | "non-record-arg";
203
+ }
204
+
205
+ export interface CallSite {
206
+ id: CallsiteId;
207
+ operationId: OperationId;
208
+ /** Raw callee expression text, e.g. 'EnrichLine' or 'Customer.Get'. Compatibility/
209
+ * display only — semantic consumers must read `callee` instead. */
210
+ calleeText: string;
211
+ /**
212
+ * Structured, tree-sitter-derived classification of the callee. Built at L2 index
213
+ * time from the `call_expression` node — bare / member / object-run / unknown.
214
+ * Drives `resolve/call-resolver.ts` dispatch directly; no re-parsing of `calleeText`.
215
+ */
216
+ callee: Callee;
217
+ /**
218
+ * Raw text of each positional argument. Compatibility/display only — semantic
219
+ * consumers must read `argumentInfos` (typed) instead.
220
+ */
221
+ argumentTexts: string[];
222
+ /**
223
+ * Structured, tree-sitter-derived classification of each positional argument —
224
+ * same order as `argumentTexts`. Detectors that need literal-vs-non-literal
225
+ * decisions (D18) or quoted-vs-unquoted name disambiguation read these via
226
+ * `model/expression.ts` helpers. Avoid switching on `kind` in call sites —
227
+ * use `isLiteralExpression` / `isStringLikeLiteral` / `unquotedFieldName`.
228
+ */
229
+ argumentInfos: ExpressionInfo[];
230
+ /** Structural per-argument bindings. Populated by routine-indexer.ts;
231
+ * UPGRADED IN PLACE by src/resolve/call-resolver.ts after edge resolution
232
+ * (calleeParameterIsVar + bindingResolution become "resolved").
233
+ *
234
+ * Mutation contract: ONLY upgradeBindings() in call-resolver.ts is permitted
235
+ * to write to existing entries. Detectors and downstream consumers must treat
236
+ * the array as readonly. Re-entrant resolution is not supported; any future
237
+ * incremental / parallel resolution must migrate ownership to CallEdge first.
238
+ *
239
+ * `argumentTexts` is retained for display only — use bindings for any
240
+ * semantic reasoning. */
241
+ argumentBindings: CallArgumentBinding[];
242
+ loopStack: LoopId[]; // loops in THIS routine enclosing the call
243
+ sourceAnchor: SourceAnchor;
244
+ }
245
+
246
+ export interface FieldAccess {
247
+ recordVariableName: string;
248
+ fieldName: string;
249
+ sourceAnchor: SourceAnchor;
250
+ }
251
+
252
+ export interface RecordVariable {
253
+ id: RecordVariableId;
254
+ name: string;
255
+ tableName?: string;
256
+ tableId?: TableId; // resolved in Phase 2; undefined in Phase 1
257
+ tempState: TempState;
258
+ isParameter: boolean;
259
+ parameterIndex?: number;
260
+ }
261
+
262
+ /**
263
+ * A general variable in a routine's lexical scope — parameter or local declaration.
264
+ * Covers ALL declared types, not just records. Used by Phase 0b capability
265
+ * extractors to resolve member-call receivers structurally (`MyVar.Send(...)`)
266
+ * rather than by text matching the receiver name.
267
+ *
268
+ * `name` is lowercased per AL's case-insensitive identifier rules.
269
+ * `declaredType` is the normalized type string — `"HttpClient"`,
270
+ * `"Codeunit \"Sales-Post\""`, `"Record Customer"`, `"List of [Text]"`, etc.
271
+ * Use `declaredType` for dispatch (e.g. capability extractors match on the
272
+ * string prefix `"HttpClient"`, the prefix `"Codeunit "`, `"Record "`, etc.).
273
+ *
274
+ * `targetObjectId` is populated for object-typed variables (Codeunit, Page,
275
+ * Report, XmlPort, Query) when the object can be resolved at index time.
276
+ * `tableId` is populated for record-typed variables when the table can be
277
+ * resolved. Both are undefined when resolution fails (opaque dep, unknown
278
+ * object/table name).
279
+ *
280
+ * `initializer` is the simplest `ValueSource` describing the right-hand side
281
+ * of the variable's initial assignment, when there is one. Phase 0b-α resolves
282
+ * ONE HOP — chains like `A := B; B := 'literal'` resolve A's initializer to
283
+ * `{kind: "constant-var", varName: "B", initializer: {kind: "unknown"}}`.
284
+ * The `{kind: "unknown"}` placeholder is Phase 0b-β's `value-source.ts`
285
+ * classifier's job to chase (it looks up B in the same routine's
286
+ * `variables[]` and reads B's `initializer`). For "no initializer present"
287
+ * the field is `undefined`.
288
+ *
289
+ * Record variables also appear in `IntraproceduralFeatures.recordVariables` for
290
+ * backward compatibility — the two arrays overlap by design. New code should
291
+ * prefer `variables` and let `recordVariables` deprecate naturally.
292
+ */
293
+ export interface VariableSymbol {
294
+ /** Lowercased per AL identifier rules. */
295
+ name: string;
296
+ /** Normalized type string — see JSDoc above. */
297
+ declaredType: string;
298
+ /** For object-typed variables, the resolved target object ID. */
299
+ targetObjectId?: ObjectId;
300
+ /** For record-typed variables, mirror of the corresponding
301
+ * `RecordVariable.tableId`. */
302
+ tableId?: TableId;
303
+ isParameter: boolean;
304
+ /** Set when `isParameter === true`. 0-based callee-side position. */
305
+ parameterIndex?: number;
306
+ /** Right-hand side of the initial assignment, as a one-hop ValueSource.
307
+ * Phase 0b-β chases chains; Phase 0b-α captures one level. */
308
+ initializer?: ValueSource;
309
+ sourceAnchor: SourceAnchor;
310
+ }
311
+
312
+ /**
313
+ * Identifier reference in a control-predicate position (if-test,
314
+ * while-test, repeat-until, case-of subject). Drives D43 dispatch-site
315
+ * guard recognition. The substrate is intentionally syntactic — it
316
+ * reports identifier participation, not guard semantics. Consumers
317
+ * (D43's slicer) interpret polarity (`if X` vs `if not X`).
318
+ *
319
+ * `identifier` is lowercased. `statementAnchor` is the owning if/while/
320
+ * repeat/case node; `referenceAnchor` is where the identifier appears.
321
+ */
322
+ export interface ConditionReference {
323
+ identifier: string;
324
+ conditionKind: "if" | "while" | "repeat-until" | "case";
325
+ statementAnchor: SourceAnchor;
326
+ referenceAnchor: SourceAnchor;
327
+ }
328
+
329
+ /**
330
+ * Minimal var-assignment stream captured by the indexer. Covers
331
+ * `<identifier> := <expression>` AND `<member_expression> := <expression>`
332
+ * statements in the routine body. Phase 3 D43 (IsHandled-skip) consumes
333
+ * this to detect explicit `IsHandled := true` writes.
334
+ *
335
+ * `lhsName` is lowercased per AL identifier rules. `rhsLiteralValue` is
336
+ * the textual literal when the RHS parses as a boolean / integer / string
337
+ * literal; absent otherwise.
338
+ */
339
+ export interface VarAssignment {
340
+ /** Lowercased LHS identifier. For member expressions (`Rec.Field := ...`),
341
+ * the trailing member name. */
342
+ lhsName: string;
343
+ /** Lowercased RHS literal text when the RHS is a `boolean` /
344
+ * `integer` / `string_literal`; absent otherwise. */
345
+ rhsLiteralValue?: string;
346
+ sourceAnchor: SourceAnchor;
347
+ }
348
+
349
+ export type OperationSiteKind =
350
+ | "record-op"
351
+ | "call"
352
+ | "event-publish"
353
+ | "commit"
354
+ | "lock"
355
+ | "external-call"
356
+ | "dynamic-dispatch";
357
+
358
+ export interface OperationSite {
359
+ id: OperationId;
360
+ routineId: RoutineId;
361
+ kind: OperationSiteKind;
362
+ sourceAnchor: SourceAnchor;
363
+ loopStack: LoopId[];
364
+ }
365
+
366
+ /** An OperationSite specialized for record operations. */
367
+ export interface RecordOperation {
368
+ id: OperationId;
369
+ routineId: RoutineId;
370
+ op: RecordOpType;
371
+ recordVariableName: string;
372
+ recordVariableId?: RecordVariableId;
373
+ tableId?: TableId; // resolved in Phase 2
374
+ tempState: TempState;
375
+ /**
376
+ * Raw text of each positional argument for SetRange/SetFilter/SetLoadFields/
377
+ * AddLoadFields/SetCurrentKey/CalcFields/Get/Find. Compatibility/display only —
378
+ * semantic consumers must use `fieldArgumentInfos`.
379
+ */
380
+ fieldArguments?: string[];
381
+ /**
382
+ * Structured arguments — same order as `fieldArguments`. D4 / D18 / D22 read
383
+ * these via `model/expression.ts` helpers (no quote/literal regex on the text).
384
+ */
385
+ fieldArgumentInfos?: ExpressionInfo[];
386
+ loopStack: LoopId[];
387
+ sourceAnchor: SourceAnchor;
388
+ }
389
+
390
+ /**
391
+ * One position in a code_block where a statement (and any siblings after it) is
392
+ * guaranteed to never execute because a previous sibling unconditionally exits the
393
+ * routine. The indexer recognises:
394
+ * - `exit_statement` (`Exit;` / `Exit(value);`)
395
+ * - bare call `Error(...)` / `error(...)` — al-sem treats the call as runtime-fatal
396
+ * - member call `CurrReport.Quit(...)` — report runtime exit
397
+ * The detector D20 walks these.
398
+ */
399
+ export interface UnreachableStatement {
400
+ id: string;
401
+ exitKind: "exit" | "error" | "currreport-quit";
402
+ exitAnchor: SourceAnchor;
403
+ unreachableAnchor: SourceAnchor;
404
+ }
405
+
406
+ /**
407
+ * Compact representation of a routine body's control flow, used by the
408
+ * path-aware control-flow walker (src/engine/control-flow-walker.ts) in
409
+ * Phase 6+ to do branch-aware analysis.
410
+ *
411
+ * Each node represents one syntactic position. Branching nodes (if/case/case-branch)
412
+ * use `children` for the primary branch(es) and `elseChildren` for the else branch
413
+ * (only `if` populates elseChildren). Loop nodes use `children` for the body.
414
+ *
415
+ * `op` and `call` leaves link back to entries in `IntraproceduralFeatures.recordOperations`
416
+ * and `.callSites` via their ids — the walker uses these to interleave structural
417
+ * traversal with the existing flat feature lists.
418
+ *
419
+ * `other` is a catch-all for statement kinds the walker doesn't need to distinguish
420
+ * (assignment, message, etc.) — they're treated as straight-line opaque.
421
+ *
422
+ * Kind contracts:
423
+ * - `if` nodes always have at least one of `children` (then-branch) or `elseChildren`
424
+ * (else-branch) populated under normal parses. Both being empty implies a
425
+ * parse-incomplete body and the walker should treat such a node conservatively.
426
+ * - `try` nodes currently have empty `children` because the AL grammar
427
+ * (tree-sitter-al) does not define a `try_statement` node — AL's TryFunction is an
428
+ * [TryFunction] attribute on a procedure, not a statement. Reserved for future grammar
429
+ * additions; the walker should treat `try` as opaque-with-possible-exit until the
430
+ * grammar+CFN builder catch up.
431
+ * - `error` covers ONLY bare unqualified `Error(...)` / `error(...)` / `ERROR(...)`
432
+ * calls (case-insensitive callee identifier). Qualified calls (`System.Error(...)`,
433
+ * codeunit-method dispatch, etc.) go through `call`. `CurrReport.Quit(...)` and
434
+ * similar exit-equivalents are NOT classified as `error`; see `unconditionalExitKind`
435
+ * in intraprocedural-body.ts for the full exit-shape inventory. P6.T2 walker should
436
+ * cross-reference there if it needs to widen the set of "raises and never returns" leaves.
437
+ * - `exit` is the bare `exit_statement` (covers `Exit;` and `Exit(value);`).
438
+ *
439
+ * Single-statement branch bodies: the AL grammar lets `if X then S` / `while X do S` /
440
+ * `case X of 1: S` omit `begin`/`end`. In those cases the `then_branch` / `body` field
441
+ * points DIRECTLY at the statement (not a wrapping `code_block`). The CFN builder
442
+ * synthesizes a `block` node wrapping the single statement so the walker can treat all
443
+ * branch bodies uniformly.
444
+ *
445
+ * Grammar notes (tree-sitter-al):
446
+ * - `if_statement` exposes `then_branch` and `else_branch` named fields.
447
+ * - `case_statement` children include `case_branch` and `case_else_branch` nodes.
448
+ * - `case_branch` exposes a `body` field; `case_else_branch` has no named body field
449
+ * (search namedChildren for a `code_block`, or fall back to the first non-keyword child).
450
+ * - All loop kinds (`for_statement`, `foreach_statement`, `while_statement`) expose a `body` field.
451
+ * - `repeat_statement` has no body field; its body children are all namedChildren before
452
+ * the `until_keyword`.
453
+ */
454
+ export interface ControlFlowNode {
455
+ kind:
456
+ | "block"
457
+ | "if"
458
+ | "case"
459
+ | "case-branch"
460
+ | "while"
461
+ | "repeat"
462
+ | "for"
463
+ | "foreach"
464
+ | "try"
465
+ | "exit"
466
+ | "error"
467
+ | "call"
468
+ | "op"
469
+ | "other";
470
+ sourceAnchor: SourceAnchor;
471
+ /** Children for branching/looping nodes' primary branch (then-branch of if; body of loops; cases of case). */
472
+ children?: ControlFlowNode[];
473
+ /** Else-branch children. Only `if` populates this. */
474
+ elseChildren?: ControlFlowNode[];
475
+ /** For `op` leaves: id of the RecordOperation (or OperationSite). */
476
+ operationId?: OperationId;
477
+ /** For `call` leaves: id of the CallSite. */
478
+ callsiteId?: CallsiteId;
479
+ /**
480
+ * Expression-position record-ops / callsites harvested from condition
481
+ * expressions, argument expressions, and chained receivers of this node (P7.5).
482
+ *
483
+ * Populated by the indexer for nodes whose AL syntax has expression-position
484
+ * sub-trees that may contain record-ops or function calls — e.g.
485
+ * `if Cust.FindSet() then ...`, `until Cust.Next() = 0`, `case Rec.Find('-') of`,
486
+ * `Helper(Cust.FindSet())`, `Helper(C).FindSet()`. The walker processes these
487
+ * leaves at the appropriate point relative to the node's main children:
488
+ *
489
+ * - `if` / `case` / `while` / `for` / `foreach`: BEFORE walking `children` /
490
+ * `elseChildren`. For `if`/`case` the condition runs once before branch
491
+ * selection; for loops it's a virtual prefix to each iteration (the
492
+ * condition / range / iterable evaluates before each loop body iteration).
493
+ * - `repeat`: AFTER the body inside each iteration (the `until` expression
494
+ * evaluates at the END of each iteration).
495
+ * - `op` / `call` / `error`: BEFORE the leaf's own effect — argument-position
496
+ * and chained-receiver expressions evaluate before the outer call/op runs.
497
+ * - `other`: emitted by `buildCFNForStatement` as a fallback wrapper for
498
+ * expression-position call_expressions that the visit pass did NOT
499
+ * register as op or callsite (e.g. unresolved or AST-shape-mismatched
500
+ * calls). The wrapper carries the harvested argument/receiver leaves so
501
+ * they aren't dropped from the tree.
502
+ *
503
+ * Nesting is possible. A `call` leaf may itself carry `conditionLeaves` —
504
+ * `Helper(Cust.FindSet())` produces a call-leaf for `Helper(...)` whose own
505
+ * `conditionLeaves` includes the inner FindSet op-leaf (from the argument).
506
+ * For chained-receiver shapes like `Helper(C).FindSet()`, the inner
507
+ * `Helper(C)` callsite is harvested as a SIBLING of the outer FindSet op
508
+ * leaf in the parent's `conditionLeaves` (AL evaluates the receiver before
509
+ * the outer call, and the walker preserves that left-to-right order).
510
+ *
511
+ * The harvest never reaches into statement-position sub-trees — e.g. an
512
+ * `if`-condition harvest stops at the boundary of the then-branch, which
513
+ * has its own statement-tree children.
514
+ */
515
+ conditionLeaves?: ControlFlowNode[];
516
+ }
517
+
518
+ /** Raw intraprocedural extraction for one routine body. */
519
+ export interface IntraproceduralFeatures {
520
+ loops: LoopNode[];
521
+ operationSites: OperationSite[];
522
+ recordOperations: RecordOperation[];
523
+ callSites: CallSite[];
524
+ fieldAccesses: FieldAccess[];
525
+ recordVariables: RecordVariable[];
526
+ nestingDepth: number;
527
+ unreachableStatements: UnreachableStatement[];
528
+ /**
529
+ * True when the routine body contains any branching control flow that simple
530
+ * straight-line analyses (e.g. the Phase 4 control-flow walker) cannot soundly
531
+ * reason about. Set by the indexer when it encounters an `if_statement`,
532
+ * `case_statement`, `case_branch`, or `try_statement`.
533
+ *
534
+ * Loops are counted separately by `nestingDepth`; unconditional early exits
535
+ * are counted by `unreachableStatements`. Consumers should treat
536
+ * `hasBranching || nestingDepth > 0 || unreachableStatements.length > 0`
537
+ * as "this routine is not straight-line".
538
+ */
539
+ hasBranching: boolean;
540
+ /**
541
+ * Compact control-flow tree for the routine body. Populated by the indexer
542
+ * in P6.T1; consumed by the path-aware walker in P6.T2. Undefined for
543
+ * routines without a body (symbol-only projections).
544
+ */
545
+ statementTree?: ControlFlowNode;
546
+ /**
547
+ * The set of identifier names referenced as VALUES anywhere in the routine
548
+ * body — lowercased, sorted, deduped. Field names (member of
549
+ * `member_expression`), enum member names (`value` of
550
+ * `qualified_enum_value`), and bare type names in enum-type position are
551
+ * excluded; receivers, callees, arguments, assignment targets, and loop
552
+ * iteration variables are all included.
553
+ *
554
+ * Used by D19 (unused-parameter) instead of regex-scanning body fragment
555
+ * text; future detectors that need name-reaches-routine should query this
556
+ * set as well.
557
+ */
558
+ identifierReferences: string[];
559
+ /**
560
+ * Phase 0b-α addition (capability-stack roadmap §3.9). All declared
561
+ * variables in the routine's lexical scope — parameters + local var
562
+ * declarations, covering ALL types not just records. Record variables
563
+ * also appear here (in addition to `recordVariables` for backward
564
+ * compatibility). Phase 0b-β capability extractors use this for
565
+ * structural receiver-type resolution (`MyVar.Send(...)`).
566
+ *
567
+ * Populated by `routine-indexer.ts` for primary routines and by
568
+ * dependency-projection's symbol-only path (`variables: []`) for
569
+ * opaque dep routines. Optional so pre-Phase-0b-α test mock-fixture
570
+ * sites that haven't been updated continue to compile cleanly.
571
+ */
572
+ variables?: VariableSymbol[];
573
+ /**
574
+ * Var-assignment stream. Captured by the indexer in Phase 3 — drives
575
+ * D43 IsHandled-skip detection. Sorted by sourceAnchor.
576
+ */
577
+ varAssignments: VarAssignment[];
578
+ /**
579
+ * Identifiers referenced in a control-predicate position. Captured by
580
+ * the indexer in Phase 3.1 — drives D43 dispatch-site guard detection.
581
+ * Sorted by referenceAnchor.
582
+ */
583
+ conditionReferences: ConditionReference[];
584
+ }
585
+
586
+ /**
587
+ * The `local`/`internal`/`protected` modifier on a primary-app procedure declaration.
588
+ * Absent = AL's default ("public"). Detectors use this to distinguish demonstrably
589
+ * scope-limited procedures (local) from procedures that may be called externally
590
+ * (default/public). Triggers and dependency-role routines do not carry this.
591
+ */
592
+ export type ProcedureAccessModifier = "local" | "internal" | "protected";
593
+
594
+ export interface Routine {
595
+ id: RoutineId;
596
+ canonical: CanonicalRoutineKey;
597
+ objectId: ObjectId;
598
+ name: string;
599
+ kind: RoutineKind;
600
+ parameters: ParameterSymbol[];
601
+ /**
602
+ * Raw text of each `attribute_item` immediately preceding the routine, in source order.
603
+ * Compatibility/display only — semantic consumers must use `attributesParsed`.
604
+ */
605
+ attributes: string[];
606
+ /**
607
+ * Structured, tree-sitter-derived attributes — same order as `attributes`. Built at
608
+ * L2 index time for native AL routines (from `attribute_item` nodes) and projected
609
+ * from `SymbolReference.json` for dependency routines. Detectors and resolvers
610
+ * query attributes through this field via `findAttribute` / `stringArg` /
611
+ * `qualifiedArg` helpers in `model/attributes.ts` — no `[…]` text shredding.
612
+ */
613
+ attributesParsed: AttributeInfo[];
614
+ /**
615
+ * Declaration-visibility modifier. For primary-app source this is the `modifier`
616
+ * field on a `procedure` node (`local`/`internal`/`protected`); for dependency
617
+ * (.app symbol) routines it is projected from `IsInternal` / `IsLocal` flags.
618
+ * Undefined = AL default ("public") or trigger.
619
+ */
620
+ accessModifier?: ProcedureAccessModifier;
621
+ bodyAvailable: boolean; // false = opaque .app symbol
622
+ parseIncomplete: boolean;
623
+ sourceHash: string;
624
+ sourceAnchor: SourceAnchor;
625
+ features: IntraproceduralFeatures;
626
+ summary?: RoutineSummary; // computed in Phase 2
627
+ analysisRole?: AnalysisRole;
628
+ }