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,98 @@
1
+ // src/model/expression.ts
2
+ // Structured, tree-sitter-derived classification of one expression — used for
3
+ // CallSite arguments and RecordOperation field-arguments. Detectors read this
4
+ // instead of re-shredding `text` with regex.
5
+ //
6
+ // The `kind` mirrors tree-sitter-al's expression node types verbatim, so the
7
+ // builder in `src/index/expression-from-node.ts` is a 1:1 map; "other" is the
8
+ // fallback for unmodeled expression shapes (binary expressions, parenthesized
9
+ // expressions, subscripts, etc.). Detectors only need a handful of kinds for
10
+ // their literal/identity decisions — the helpers below encode those rules
11
+ // once so consumers never branch on `kind` strings directly.
12
+
13
+ /** Grammar-aligned expression kinds. Closed enum; `"other"` is the catch-all. */
14
+ export type ExpressionKind =
15
+ | "string_literal"
16
+ | "integer"
17
+ | "decimal"
18
+ | "boolean"
19
+ | "identifier"
20
+ | "quoted_identifier"
21
+ | "qualified_enum_value"
22
+ | "database_reference"
23
+ | "unary_expression"
24
+ | "member_expression"
25
+ | "call_expression"
26
+ | "parenthesized_expression"
27
+ | "other";
28
+
29
+ export interface ExpressionInfo {
30
+ kind: ExpressionKind;
31
+ /** Source-faithful text including any surrounding quotes / signs. */
32
+ text: string;
33
+ /**
34
+ * Consumer-friendly value:
35
+ * - string_literal: contents between the single quotes
36
+ * - quoted_identifier: contents between the double quotes
37
+ * - integer / decimal: decimal text (unchanged)
38
+ * - boolean: "true" / "false"
39
+ * - identifier: the bare name
40
+ * - qualified_enum_value: RHS of `::` (the `value` field)
41
+ * - database_reference: RHS of `::` (the `table_name` field, unquoted)
42
+ * Undefined for member_expression / call_expression / unary_expression /
43
+ * parenthesized_expression / other — those need full sub-tree inspection
44
+ * the model layer deliberately omits.
45
+ */
46
+ value?: string;
47
+ /** LHS of `::` for qualified_enum_value / database_reference. */
48
+ qualifier?: string;
49
+ /** RHS of `::` for qualified_enum_value / database_reference (unquoted for db-ref). */
50
+ member?: string;
51
+ }
52
+
53
+ /**
54
+ * A string-like literal — either single-quoted (`'val'`) or
55
+ * double-quoted-identifier (`"val"`). D4 and D22 want this: they treat
56
+ * either form as "the same key" for comparison after stripping the quotes.
57
+ * The `value` field is the unquoted contents in both cases.
58
+ */
59
+ export function isStringLikeLiteral(info: ExpressionInfo): boolean {
60
+ return info.kind === "string_literal" || info.kind === "quoted_identifier";
61
+ }
62
+
63
+ /**
64
+ * A loop-invariant literal — string, number, boolean, or qualified enum value.
65
+ * D18 uses this to decide whether a `SetRange` / `SetFilter` value is constant
66
+ * across iterations. Identifiers and calls are NOT literals (we cannot prove
67
+ * loop-invariance without dataflow). Unary `+`/`-` over a numeric literal is
68
+ * literal too — `expressionInfoFromNode` sets `value` on a `unary_expression`
69
+ * iff its operand is `integer` / `decimal`, so the value-set check is the
70
+ * predicate.
71
+ */
72
+ export function isLiteralExpression(info: ExpressionInfo): boolean {
73
+ switch (info.kind) {
74
+ case "string_literal":
75
+ case "quoted_identifier":
76
+ case "integer":
77
+ case "decimal":
78
+ case "boolean":
79
+ case "qualified_enum_value":
80
+ return true;
81
+ case "unary_expression":
82
+ return info.value !== undefined;
83
+ default:
84
+ return false;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Resolve a field-name argument to its lowered, unquoted form for
90
+ * case-insensitive comparison against `Field.name`. Quoted identifiers
91
+ * (`"Document No."`) and string literals (`'Discount Amount'`) yield their
92
+ * inner value; plain identifiers (`Quantity`) yield their text. Returns the
93
+ * source text lowercased as a safe fallback for anything else.
94
+ */
95
+ export function unquotedFieldName(info: ExpressionInfo): string {
96
+ if (info.value !== undefined) return info.value.toLowerCase();
97
+ return info.text.toLowerCase();
98
+ }
@@ -0,0 +1,110 @@
1
+ import type { Evidence } from "./graph.ts";
2
+ import type { SourceAnchor } from "./identity.ts";
3
+ import type { CallsiteId, LoopId, ObjectId, OperationId, RoutineId, TableId } from "./ids.ts";
4
+
5
+ export interface FixOption {
6
+ description: string;
7
+ safety: "high" | "medium" | "low";
8
+ }
9
+
10
+ export interface EvidenceStep {
11
+ routineId: RoutineId;
12
+ operationId?: OperationId;
13
+ callsiteId?: CallsiteId;
14
+ loopId?: LoopId;
15
+ sourceAnchor: SourceAnchor;
16
+ note: string;
17
+ }
18
+
19
+ export interface FindingConfidence {
20
+ level: "confirmed" | "likely" | "possible";
21
+ cappedBy?: (
22
+ | "unresolved-call"
23
+ | "opaque-callee"
24
+ | "dynamic-dispatch"
25
+ | "parse-incomplete"
26
+ | "version-mismatch"
27
+ )[];
28
+ evidence: Evidence[];
29
+ }
30
+
31
+ export interface Finding {
32
+ id: string;
33
+ rootCauseKey: string;
34
+ detector: string;
35
+ title: string;
36
+ rootCause: string;
37
+ severity: "critical" | "high" | "medium" | "low" | "info";
38
+ confidence: FindingConfidence;
39
+ primaryLocation: SourceAnchor;
40
+ evidencePath: EvidenceStep[];
41
+ /**
42
+ * Additional supporting traces for the SAME terminal anchor. Set only on detectors
43
+ * that emit one Finding per (terminal-op | publisher) regardless of how many
44
+ * in-loop ancestor routines reach it (D1, D2). The canonical, max-severity path
45
+ * is in `evidencePath`; this carries the others, sorted deterministically.
46
+ *
47
+ * Absent (undefined) when there's a single reaching path — the common case for
48
+ * all other detectors and for most D1/D2 findings.
49
+ */
50
+ additionalPaths?: EvidenceStep[][];
51
+ affectedObjects: ObjectId[];
52
+ affectedTables: TableId[];
53
+ fixOptions: FixOption[];
54
+ provenance: Evidence[];
55
+ /** Set in later phases. The primary-app source anchor a developer can act on, when primaryLocation falls in a dependency. */
56
+ actionableAnchor?: SourceAnchor;
57
+ /** Set in later phases. Stable edit-survival key used by baselines + SARIF. */
58
+ fingerprint?: string;
59
+ /**
60
+ * For event-flow detectors (D43/D44/D45). The kind of event whose
61
+ * dispatch / multi-subscriber / transitive exposure produced the finding.
62
+ * Mirrors EventKind from src/engine/event-flow.ts but kept loose
63
+ * (string union) to avoid a model→engine import cycle.
64
+ *
65
+ * Set to one of: "integration", "business", "internal".
66
+ * Absent on non-event-flow findings.
67
+ */
68
+ eventKind?: "integration" | "business" | "internal";
69
+ /**
70
+ * For event-flow detectors. Subset of the event's subscribers that
71
+ * live in a different app than the publisher (subscriberAppId !==
72
+ * publisherAppId). Sorted RoutineId list. Empty when all subscribers
73
+ * are in-app. Absent on non-event-flow findings.
74
+ *
75
+ * Evidence only — no detector filters on it. Consumers (baselines,
76
+ * dashboards, al-perf) may weight findings differently based on
77
+ * cross-app subscriber presence.
78
+ */
79
+ crossExtensionSubscribers?: readonly RoutineId[];
80
+ }
81
+
82
+ export interface Diagnostic {
83
+ severity: "error" | "warning" | "info";
84
+ stage: "discover" | "parse" | "symbol-read" | "index" | "resolve" | "summarize" | "detect";
85
+ message: string;
86
+ sourceRef?: string;
87
+ }
88
+
89
+ export interface DetectorStats {
90
+ detector: string;
91
+ candidatesConsidered: number;
92
+ findingsEmitted: number;
93
+ /** Named skip counters. The well-known fields below are documented for shared
94
+ * conventions (`temporaryRecord`, `parseIncomplete`, etc.); detector-specific
95
+ * counters are also allowed via the index signature so each detector can name
96
+ * its own skip paths (e.g. `nonModifyEvent`, `noPublisherRoutine`). */
97
+ skipped: {
98
+ opaqueCallee?: number;
99
+ dynamicDispatch?: number;
100
+ parseIncomplete?: number;
101
+ temporaryRecord?: number;
102
+ dependencyTerminal?: number;
103
+ /** Not skipped — downgraded to "info" severity (D1 temp records). */
104
+ downgradedToInfo?: number;
105
+ /** Event dispatch edge has no resolvable subscriber routine (D2). */
106
+ unresolvedSubscriber?: number;
107
+ other?: number;
108
+ [counter: string]: number | undefined;
109
+ };
110
+ }
@@ -0,0 +1,93 @@
1
+ import type { ValueSource } from "./capability.ts";
2
+ import type { SourceAnchor } from "./identity.ts";
3
+ import type { CallsiteId, EventId, ObjectId, OperationId, RoutineId } from "./ids.ts";
4
+
5
+ /**
6
+ * Typed graph edges. Per the capability-stack roadmap §3.2, the v2
7
+ * combined graph's generic call + event-dispatch edges with `uncertainty`
8
+ * annotations are promoted to a discriminated union. Every propagating
9
+ * edge is routine-to-routine and carries an anchor for witness
10
+ * reconstruction.
11
+ *
12
+ * - `direct-call` resolved bare or member call
13
+ * - `object-run-resolved` Codeunit.Run/Page.Run/Report.Run with
14
+ * resolved target entrypoint — composer walks
15
+ * through `to`
16
+ * - `object-run-unresolved` dispatch where target is dynamic or
17
+ * entrypoint isn't resolved — composer does
18
+ * NOT walk through (no `to`); produces an
19
+ * execute CapabilityFact directly + a coverage
20
+ * reason on the source routine
21
+ * - `event-dispatch` composed publisher→subscriber edge
22
+ * (one per resolved (publisher event,
23
+ * subscriber binding) pair) — composer walks
24
+ * - `implicit-trigger` record op raises a table OnInsert/OnModify/
25
+ * OnValidate/OnDelete trigger
26
+ * - `dependency-export` primary-app routine calls into a
27
+ * dependency-app exported routine
28
+ *
29
+ * Permission facts are NOT graph edges; see `src/model/permission.ts`.
30
+ */
31
+ export type GraphEdgeKind =
32
+ | "direct-call"
33
+ | "object-run-resolved"
34
+ | "object-run-unresolved"
35
+ | "event-dispatch"
36
+ | "implicit-trigger"
37
+ | "dependency-export";
38
+
39
+ /** Table-trigger kind raised by an implicit-trigger edge. */
40
+ export type TriggerKind = "OnInsert" | "OnModify" | "OnValidate" | "OnDelete" | "OnRename";
41
+
42
+ export type GraphEdge =
43
+ | {
44
+ kind: "direct-call";
45
+ callsiteId: CallsiteId;
46
+ from: RoutineId;
47
+ to: RoutineId;
48
+ sourceAnchor: SourceAnchor;
49
+ }
50
+ | {
51
+ kind: "object-run-resolved";
52
+ callsiteId: CallsiteId;
53
+ from: RoutineId;
54
+ to: RoutineId;
55
+ targetObject: ObjectId;
56
+ objectType: "Codeunit" | "Page" | "Report";
57
+ sourceAnchor: SourceAnchor;
58
+ }
59
+ | {
60
+ kind: "object-run-unresolved";
61
+ callsiteId: CallsiteId;
62
+ from: RoutineId;
63
+ /** Set when the target object is known but the entrypoint isn't. */
64
+ targetObject?: ObjectId;
65
+ /** Always set — describes the source of the runtime-resolved target id. */
66
+ targetIdSource: ValueSource;
67
+ objectType: "Codeunit" | "Page" | "Report";
68
+ sourceAnchor: SourceAnchor;
69
+ }
70
+ | {
71
+ kind: "event-dispatch";
72
+ from: RoutineId;
73
+ to: RoutineId;
74
+ eventId: EventId;
75
+ publishAnchor: SourceAnchor;
76
+ subscriberAnchor: SourceAnchor;
77
+ }
78
+ | {
79
+ kind: "implicit-trigger";
80
+ from: RoutineId;
81
+ to: RoutineId;
82
+ triggerKind: TriggerKind;
83
+ operationId: OperationId;
84
+ sourceAnchor: SourceAnchor;
85
+ }
86
+ | {
87
+ kind: "dependency-export";
88
+ callsiteId: CallsiteId;
89
+ from: RoutineId;
90
+ to: RoutineId;
91
+ targetAppGuid: string;
92
+ sourceAnchor: SourceAnchor;
93
+ };
@@ -0,0 +1,62 @@
1
+ import type { ParameterSymbol } from "./entities.ts";
2
+ import type { CallsiteId, EventId, ObjectId, OperationId, RoutineId } from "./ids.ts";
3
+
4
+ export interface Evidence {
5
+ source: "tree-sitter" | "symbol-package" | "external-source";
6
+ note?: string;
7
+ }
8
+
9
+ /** Shared provenance constant for tree-sitter-derived edges and symbols. */
10
+ export const TREE_SITTER_EVIDENCE: Evidence = { source: "tree-sitter" };
11
+
12
+ export type DispatchKind =
13
+ | "direct"
14
+ | "method"
15
+ | "interface"
16
+ | "codeunit-run"
17
+ | "report-run"
18
+ | "page-run"
19
+ | "event-dispatch"
20
+ | "implicit-trigger"
21
+ | "dynamic"
22
+ | "unresolved";
23
+
24
+ export type ResolutionQuality = "resolved" | "maybe" | "unknown" | "opaque";
25
+
26
+ export interface CallEdge {
27
+ from: RoutineId;
28
+ to?: RoutineId; // absent when unresolved
29
+ callsiteId: CallsiteId;
30
+ operationId: OperationId;
31
+ dispatchKind: DispatchKind;
32
+ resolution: ResolutionQuality;
33
+ provenance: Evidence[];
34
+ }
35
+
36
+ export interface EventSymbol {
37
+ id: EventId;
38
+ publisherObjectId: ObjectId;
39
+ publisherRoutineId?: RoutineId;
40
+ eventName: string;
41
+ eventKind: "integration" | "business" | "trigger" | "internal" | "unknown";
42
+ elementName?: string;
43
+ signatureHash: string;
44
+ parameters: ParameterSymbol[];
45
+ provenance: Evidence[];
46
+ }
47
+
48
+ export interface EventEdge {
49
+ eventId: EventId;
50
+ subscriberRoutineId: RoutineId;
51
+ subscriberAppId: string;
52
+ skipOnMissingLicense?: boolean;
53
+ skipOnMissingPermission?: boolean;
54
+ resolution: "resolved" | "maybe" | "unknown";
55
+ provenance: Evidence[];
56
+ }
57
+
58
+ /** The event graph: publisher symbols and subscriber edges. */
59
+ export interface EventGraph {
60
+ events: EventSymbol[];
61
+ edges: EventEdge[];
62
+ }
@@ -0,0 +1,81 @@
1
+ import type { ManifestDependency } from "../symbols/app-manifest.ts";
2
+
3
+ /** A character range in a source file. */
4
+ export interface SourceRange {
5
+ startLine: number; // 0-based
6
+ startColumn: number;
7
+ endLine: number;
8
+ endColumn: number;
9
+ }
10
+
11
+ /**
12
+ * A stable reference to a location in source. The fingerprint hash fields are a SEAM
13
+ * only in Phase 1 — left undefined; computation is deferred to sub-project D.
14
+ */
15
+ export interface SourceAnchor {
16
+ sourceUnitId: string;
17
+ range: SourceRange;
18
+ enclosingRoutineId: string;
19
+ syntaxKind: string;
20
+ normalizedTextHash?: string;
21
+ leadingContextHash?: string;
22
+ trailingContextHash?: string;
23
+ }
24
+
25
+ export type SourceKind = "workspace" | "app-source" | "symbol-only" | "external-source";
26
+
27
+ export interface AppIdentity {
28
+ appGuid: string;
29
+ publisher: string;
30
+ name: string;
31
+ version: string;
32
+ packageHash?: string;
33
+ symbolReferenceHash?: string;
34
+ sourceAggregateHash?: string;
35
+ sourceKind: SourceKind;
36
+ }
37
+
38
+ /** Top-level version identity of one analysis run. Keys the cache. */
39
+ export interface ModelIdentity {
40
+ schemaVersion: string;
41
+ analyzerVersion: string;
42
+ grammarVersion: string;
43
+ symbolReaderVersion: string;
44
+ createdAt: string;
45
+ workspace?: { rootHash?: string; appJsonHash?: string };
46
+ primaryApp?: AppIdentity;
47
+ apps: AppIdentity[];
48
+ dependencyGraphHash: string;
49
+ runtime?: { platform?: string; application?: string; runtime?: string };
50
+ /**
51
+ * Dependencies declared by the primary workspace's app.json (both explicit
52
+ * dependencies[] and implicit Microsoft platform-tier entries). Each carries
53
+ * the declared MinVersion the user committed to. Used by D17 to detect drift
54
+ * against the actually-resolved dependency version stored in apps[].version.
55
+ */
56
+ primaryDependencies?: ManifestDependency[];
57
+ /**
58
+ * App GUIDs (lowercased, sorted) listed in the primary workspace's
59
+ * `internalsVisibleTo` array — apps granted access to this app's `internal`
60
+ * procedures. Empty/undefined means no external app can reach `internal`, so
61
+ * D14 treats them as app-scoped for dead-routine reachability.
62
+ */
63
+ primaryInternalsVisibleTo?: string[];
64
+ /**
65
+ * Phase 1 §4.3 — `roots.config.json` provenance. Populated when the file
66
+ * was loaded AND hashed successfully (i.e., `loadRootsConfig` returned a
67
+ * `contentHash`). Undefined when the file is missing, when
68
+ * `--no-roots-config` / `options.noRootsConfig` skipped loading, or when
69
+ * a file-read error prevented hashing (the read error is still surfaced
70
+ * via diagnostics). Parse/validation errors that occur AFTER hashing
71
+ * keep the field populated so the fingerprint reflects the byte content
72
+ * that was attempted.
73
+ *
74
+ * Whether to surface read-failure as a `rootsConfigReadError?` metadata
75
+ * flag is deferred; today the diagnostic is the only signal.
76
+ *
77
+ * The path is the absolute path that was attempted; consumers may
78
+ * workspace-relativize it for stability across machines.
79
+ */
80
+ rootsConfig?: { path: string; contentHash: string };
81
+ }
@@ -0,0 +1,90 @@
1
+ import { sha256OfStrings } from "../hash.ts";
2
+
3
+ /** Routine kinds — table/page/report triggers are routines, indexed from day one. */
4
+ export type RoutineKind = "procedure" | "trigger" | "event-publisher" | "event-subscriber";
5
+
6
+ // --- ID string aliases. Opaque outside this module. ---
7
+ export type ObjectId = string; // "{appGuid}/{objectType}/{objectNumber}"
8
+ export type TableId = string; // "{appGuid}/table/{number}" (physical table)
9
+ export type FieldId = string; // "{tableId}/{fieldNumber}"
10
+ export type KeyId = string; // "{tableId}/key/{index}"
11
+ export type RoutineId = string; // encodes { canonicalKey, modelInstanceId }
12
+ export type CallsiteId = string; // "{routineId}/cs{index}"
13
+ export type LoopId = string; // "{routineId}/loop{index}"
14
+ export type OperationId = string; // "{routineId}/op{index}"
15
+ export type RecordVariableId = string; // "{routineId}/rv/{name}"
16
+ export type EventId = string; // "{publisherObjectId}/event/{eventName}"
17
+
18
+ /**
19
+ * Stable across app version bumps when the symbol is semantically the same.
20
+ * This is the key regression comparison (sub-project D) and profile fusion
21
+ * (sub-project B) join on.
22
+ */
23
+ export interface CanonicalRoutineKey {
24
+ appGuid: string;
25
+ objectType: string;
26
+ objectNumber: number;
27
+ routineKind: RoutineKind;
28
+ routineName: string;
29
+ normalizedSignatureHash: string;
30
+ }
31
+
32
+ export function encodeObjectId(
33
+ appGuid: string,
34
+ objectType: string,
35
+ objectNumber: number,
36
+ ): ObjectId {
37
+ return `${appGuid}/${objectType}/${objectNumber}`;
38
+ }
39
+
40
+ export function encodeTableId(appGuid: string, tableNumber: number): TableId {
41
+ return `${appGuid}/table/${tableNumber}`;
42
+ }
43
+
44
+ export function encodeFieldId(tableId: TableId, fieldNumber: number): FieldId {
45
+ return `${tableId}/${fieldNumber}`;
46
+ }
47
+
48
+ export function encodeKeyId(tableId: TableId, keyIndex: number): KeyId {
49
+ return `${tableId}/key/${keyIndex}`;
50
+ }
51
+
52
+ /** Order-sensitive, collision-free hash of the canonical key fields. */
53
+ export function encodeCanonicalRoutineKey(key: CanonicalRoutineKey): string {
54
+ return sha256OfStrings([
55
+ key.appGuid,
56
+ key.objectType,
57
+ String(key.objectNumber),
58
+ key.routineKind,
59
+ key.routineName.toLowerCase(),
60
+ key.normalizedSignatureHash,
61
+ ]);
62
+ }
63
+
64
+ /** Model-instance-scoped concrete routine identity. */
65
+ export function encodeRoutineId(key: CanonicalRoutineKey, modelInstanceId: string): RoutineId {
66
+ return `${modelInstanceId}/${encodeCanonicalRoutineKey(key)}`;
67
+ }
68
+
69
+ export function encodeCallsiteId(routineId: RoutineId, index: number): CallsiteId {
70
+ return `${routineId}/cs${index}`;
71
+ }
72
+
73
+ export function encodeLoopId(routineId: RoutineId, index: number): LoopId {
74
+ return `${routineId}/loop${index}`;
75
+ }
76
+
77
+ export function encodeOperationId(routineId: RoutineId, index: number): OperationId {
78
+ return `${routineId}/op${index}`;
79
+ }
80
+
81
+ export function encodeRecordVariableId(
82
+ routineId: RoutineId,
83
+ variableName: string,
84
+ ): RecordVariableId {
85
+ return `${routineId}/rv/${variableName.toLowerCase()}`;
86
+ }
87
+
88
+ export function encodeEventId(publisherObjectId: ObjectId, eventName: string): EventId {
89
+ return `${publisherObjectId}/event/${eventName.toLowerCase()}`;
90
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./ids.ts";
2
+ export * from "./identity.ts";
3
+ export * from "./entities.ts";
4
+ export * from "./summary.ts";
5
+ export * from "./graph.ts";
6
+ export * from "./finding.ts";
7
+ export * from "./model.ts";
8
+ // Phase 0a additions:
9
+ export * from "./stable-identity.ts";
10
+ export * from "./coverage.ts";
11
+ export * from "./capability.ts";
12
+ export * from "./graph-edge.ts";
13
+ export * from "./permission.ts";
@@ -0,0 +1,51 @@
1
+ import type { App, ObjectDecl, Routine, Table } from "./entities.ts";
2
+ import type { GraphEdge } from "./graph-edge.ts";
3
+ import type { CallEdge, EventGraph } from "./graph.ts";
4
+ import type { ModelIdentity } from "./identity.ts";
5
+ import type { CallsiteId, OperationId, RoutineId } from "./ids.ts";
6
+ import type { RootClassification } from "./root-classification.ts";
7
+
8
+ /** First-class "no silent clean" coverage record. Populated in Phase 2. */
9
+ export interface AnalysisCoverage {
10
+ sourceUnitsTotal: number;
11
+ sourceUnitsParsed: number;
12
+ routinesTotal: number;
13
+ routinesBodyAvailable: number;
14
+ routinesParseIncomplete: RoutineId[];
15
+ opaqueApps: string[];
16
+ unresolvedCallsites: CallsiteId[];
17
+ dynamicDispatchSites: OperationId[];
18
+ }
19
+
20
+ /**
21
+ * The Phase 1 deliverable: identity + apps + objects + routines (with intraprocedural
22
+ * features, no summaries) + tables. No call graph, no event graph.
23
+ */
24
+ export interface SemanticIndex {
25
+ identity: ModelIdentity;
26
+ apps: App[];
27
+ objects: ObjectDecl[];
28
+ routines: Routine[];
29
+ tables: Table[];
30
+ }
31
+
32
+ /** The full model — Phase 2 extends a SemanticIndex with graphs and coverage. */
33
+ export interface SemanticModel extends SemanticIndex {
34
+ callGraph: CallEdge[];
35
+ eventGraph: EventGraph;
36
+ coverage: AnalysisCoverage;
37
+ /**
38
+ * Typed discriminated-union graph edges. Populated by `buildCombinedGraph`
39
+ * (Phase 0b-β Task 19). Kinds emitted initially: `direct-call`,
40
+ * `object-run-resolved`, `object-run-unresolved`. Event-dispatch and
41
+ * implicit-trigger edges are composed in Task 20+.
42
+ */
43
+ typedEdges?: readonly GraphEdge[];
44
+ /**
45
+ * Phase 1 §4.3 — per-routine root classification with provenance.
46
+ * Populated by root-classifier (post-summary engine, pre-snapshot
47
+ * composition). Empty when no routine qualifies as a root. Routines
48
+ * with no qualifying RootKind are NOT in the array.
49
+ */
50
+ rootClassifications: RootClassification[];
51
+ }
@@ -0,0 +1,76 @@
1
+ import type { CapabilityOp } from "./capability.ts";
2
+ import type { CoverageStatus } from "./coverage.ts";
3
+ import type { CallsiteId } from "./ids.ts";
4
+ import type {
5
+ StableObjectId,
6
+ StablePermissionSetId,
7
+ StableRoutineId,
8
+ StableTableId,
9
+ } from "./stable-identity.ts";
10
+
11
+ /**
12
+ * AL permission right set. R/I/M/D apply to TableData; X is execute
13
+ * for codeunits/pages/reports/xmlports/queries.
14
+ */
15
+ export type PermissionRight = "R" | "I" | "M" | "D" | "X";
16
+
17
+ /**
18
+ * What an AL permission entry targets. `TableData` is the
19
+ * read/insert/modify/delete pseudo-target; `Table` is the table
20
+ * definition itself (rare). `System` covers a small set of system
21
+ * permissions.
22
+ */
23
+ export type PermissionTargetKind =
24
+ | "TableData"
25
+ | "Table"
26
+ | "Codeunit"
27
+ | "Page"
28
+ | "Report"
29
+ | "XmlPort"
30
+ | "Query"
31
+ | "System";
32
+
33
+ /**
34
+ * A permission entry from an AL `PermissionSet` object. Materialized at
35
+ * index time from the workspace and from `.app` symbol packages.
36
+ *
37
+ * `scope: "Inherent"` = permissions granted by inherent entitlements (set
38
+ * via object-level `InherentEntitlements` / `InherentPermissions`
39
+ * properties). `scope: "Assignable"` = permissions declared in a
40
+ * `PermissionSet` object that can be assigned to a user.
41
+ */
42
+ export interface DeclaredPermissionFact {
43
+ kind: "declared";
44
+ permissionSet: StablePermissionSetId;
45
+ target: StableTableId | StableObjectId;
46
+ targetKind: PermissionTargetKind;
47
+ rights: PermissionRight[];
48
+ scope: "Inherent" | "Assignable";
49
+ }
50
+
51
+ /**
52
+ * A permission inferred as REQUIRED by a routine's capability cone.
53
+ * Derived from `CapabilityFact`s on the routine's transitive reachable
54
+ * set (table read/insert/modify/delete → R/I/M/D on TableData; execute
55
+ * → X on codeunit/page/report).
56
+ *
57
+ * G6 enforcement: every RequiredPermissionFact carries the coverage
58
+ * status of the underlying cone. Required perms are "may be incomplete"
59
+ * when the cone is partial — Phase 4 policy must surface this.
60
+ *
61
+ * al-sem DOES NOT claim runtime authorization outcomes. RequiredPermissionFact
62
+ * only says "the reachable code surface implies this permission was
63
+ * needed at some path"; Phase 4 compares it against DeclaredPermissionFact
64
+ * sets in scope.
65
+ */
66
+ export interface RequiredPermissionFact {
67
+ kind: "required";
68
+ subject: StableRoutineId;
69
+ target: StableTableId | StableObjectId;
70
+ targetKind: PermissionTargetKind;
71
+ rights: PermissionRight[];
72
+ derivedFromCapability: { op: CapabilityOp; witnessCallsiteId?: CallsiteId };
73
+ coverage: CoverageStatus;
74
+ }
75
+
76
+ export type PermissionFact = DeclaredPermissionFact | RequiredPermissionFact;