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,103 @@
1
+ // src/mcp/tools/list-rollups.ts
2
+
3
+ import { filterFindings } from "../../projection/finding-filters.ts";
4
+ import { rollupFindings } from "../../projection/rollup-findings.ts";
5
+ import type { McpTool } from "../server.ts";
6
+ import { getSession } from "../session.ts";
7
+ import { validateMinSeverity } from "./validators.ts";
8
+
9
+ /**
10
+ * list_rollups — grouped view of findings. Each entry is either a singleton
11
+ * finding or a multi-detector rollup at the same source location. Designed for
12
+ * agents that want the consolidated "what to fix" view instead of the raw
13
+ * per-detector firings.
14
+ *
15
+ * The rollup is purely presentational — agents that need per-detector identity
16
+ * (for baselines, SARIF, or fingerprint dedup) should call list_findings
17
+ * instead. The two views describe the same underlying Findings.
18
+ */
19
+ export const listRollupsTool: McpTool = {
20
+ definition: {
21
+ name: "list_rollups",
22
+ description:
23
+ "List findings rolled up by source location. Multiple detectors firing at the same (file, line, column, tables) collapse into one entry — e.g. D1+D5+D10 on a single iterating-Modify. Each entry is either a singleton finding or a rollup with N contributors and aggregated fix recommendations. Use list_findings for per-detector identity.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ workspaceRoot: { type: "string", description: "Absolute path to the AL workspace root." },
28
+ alpackagesDir: { type: "string", description: "Optional explicit .alpackages path." },
29
+ minSeverity: {
30
+ type: "string",
31
+ enum: ["critical", "high", "medium", "low", "info"],
32
+ description: "Drop findings below this severity (applied before rollup).",
33
+ },
34
+ detectors: {
35
+ type: "array",
36
+ items: { type: "string" },
37
+ description: "Allow-list of detector ids (applied before rollup).",
38
+ },
39
+ objectIds: { type: "array", items: { type: "string" } },
40
+ tableIds: { type: "array", items: { type: "string" } },
41
+ files: {
42
+ type: "array",
43
+ items: { type: "string" },
44
+ description: "Match by file path suffix (applied before rollup).",
45
+ },
46
+ limit: {
47
+ type: "integer",
48
+ minimum: 1,
49
+ description: "Cap output at the first N rollup entries (after sort).",
50
+ },
51
+ },
52
+ required: ["workspaceRoot"],
53
+ },
54
+ },
55
+ async handle(args) {
56
+ const workspaceRoot = args.workspaceRoot;
57
+ if (typeof workspaceRoot !== "string") {
58
+ return {
59
+ content: [
60
+ { type: "text", text: "list_rollups: workspaceRoot is required and must be a string" },
61
+ ],
62
+ };
63
+ }
64
+ const sev = validateMinSeverity(args.minSeverity, "list_rollups");
65
+ if (!sev.ok) return { content: [{ type: "text", text: sev.error }] };
66
+ const session = await getSession(
67
+ workspaceRoot,
68
+ typeof args.alpackagesDir === "string" ? args.alpackagesDir : undefined,
69
+ );
70
+ // Filter BEFORE rollup so rollups reflect the user's intent ("show me
71
+ // high-or-worse" never groups in dropped info findings).
72
+ const filtered = filterFindings(session.findings, {
73
+ minSeverity: sev.value,
74
+ detectors: Array.isArray(args.detectors) ? (args.detectors as string[]) : undefined,
75
+ objectIds: Array.isArray(args.objectIds) ? (args.objectIds as string[]) : undefined,
76
+ tableIds: Array.isArray(args.tableIds) ? (args.tableIds as string[]) : undefined,
77
+ files: Array.isArray(args.files) ? (args.files as string[]) : undefined,
78
+ // No limit here — limit is applied to rollups, not raw findings.
79
+ });
80
+ const rolled = rollupFindings(filtered);
81
+ const limited =
82
+ typeof args.limit === "number" && args.limit > 0 ? rolled.slice(0, args.limit) : rolled;
83
+ const rollupCount = limited.filter((r) => r.kind === "rolled").length;
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text",
88
+ text: JSON.stringify(
89
+ {
90
+ entries: limited,
91
+ totalEntries: rolled.length,
92
+ rolledCount: rollupCount,
93
+ singletonCount: rolled.length - rollupCount,
94
+ totalUnderlyingFindings: filtered.length,
95
+ },
96
+ null,
97
+ 2,
98
+ ),
99
+ },
100
+ ],
101
+ };
102
+ },
103
+ };
@@ -0,0 +1,25 @@
1
+ // src/mcp/tools/validators.ts
2
+
3
+ import type { FindingSummary } from "../../projection/finding-summary.ts";
4
+
5
+ const SEVERITY_VALUES = ["critical", "high", "medium", "low", "info"] as const;
6
+
7
+ /**
8
+ * Validate an MCP `minSeverity` argument. Returns the typed severity, undefined when
9
+ * absent, or an error string when the value is set but not in the allowed set. Callers
10
+ * surface the error string as a tool error rather than silently dropping every finding
11
+ * (the failure mode if we passed an off-enum string straight through to the rank table).
12
+ */
13
+ export function validateMinSeverity(
14
+ value: unknown,
15
+ toolName: string,
16
+ ): { ok: true; value: FindingSummary["severity"] | undefined } | { ok: false; error: string } {
17
+ if (value === undefined) return { ok: true, value: undefined };
18
+ if (typeof value !== "string" || !SEVERITY_VALUES.includes(value as FindingSummary["severity"])) {
19
+ return {
20
+ ok: false,
21
+ error: `${toolName}: minSeverity must be one of: ${SEVERITY_VALUES.join(", ")}`,
22
+ };
23
+ }
24
+ return { ok: true, value: value as FindingSummary["severity"] };
25
+ }
@@ -0,0 +1,120 @@
1
+ // src/model/attributes.ts
2
+ // Structured, tree-sitter-derived attribute model. The grammar exposes every
3
+ // `attribute_item` as a typed tree (name + typed args); we serialize that into
4
+ // AttributeInfo at L2 index time so detectors and resolvers can query attributes
5
+ // without re-shredding `[…]` text via regex.
6
+ //
7
+ // Two producers populate this model:
8
+ // - Native AL source: `attributeInfoFromNode` (src/index/attribute-from-node.ts)
9
+ // walks the `attribute_content → arguments → attribute_argument_list` tree.
10
+ // - `.app` ABI symbols: the symbol-reference parser builds AttributeInfo from
11
+ // the structured RawAttr JSON — no AL grammar involved, just typed shape
12
+ // detection on already-deserialized values.
13
+ //
14
+ // The grammar's `_attribute_argument` choice is the canonical kind list — this
15
+ // type mirrors it. `"unknown"` is for ABI values we can't classify (shouldn't
16
+ // occur in practice; surfaced rather than silently coerced).
17
+
18
+ /** Grammar-aligned argument kinds — mirror `_attribute_argument` in tree-sitter-al. */
19
+ export type AttributeArgKind =
20
+ | "boolean"
21
+ | "integer"
22
+ | "string_literal"
23
+ | "identifier"
24
+ | "quoted_identifier"
25
+ | "qualified_enum_value"
26
+ | "database_reference"
27
+ | "member_expression"
28
+ | "unknown";
29
+
30
+ /**
31
+ * One positional argument inside an attribute's argument list.
32
+ *
33
+ * `text` is the raw, source-faithful slice (includes quotes for string_literal /
34
+ * quoted_identifier). `value`, when set, is the consumer-friendly unquoted form
35
+ * (string contents, qualified value RHS, database_reference table name). For
36
+ * `qualified_enum_value` and `database_reference` we also break out `qualifier`
37
+ * (LHS of `::`) and `member` (RHS) for direct access.
38
+ */
39
+ export interface AttributeArg {
40
+ kind: AttributeArgKind;
41
+ text: string;
42
+ /**
43
+ * Unquoted/derived value:
44
+ * - string_literal: contents between the single quotes
45
+ * - quoted_identifier: contents between the double quotes
46
+ * - qualified_enum_value: RHS of `::` (the "value" field)
47
+ * - database_reference: RHS of `::` (the "table_name" field, unquoted)
48
+ * - boolean: "true" / "false"
49
+ * - integer: decimal text
50
+ * - identifier: same as text
51
+ * Undefined for member_expression / unknown.
52
+ */
53
+ value?: string;
54
+ /** LHS of `::` for qualified_enum_value / database_reference. */
55
+ qualifier?: string;
56
+ /** RHS of `::` for qualified_enum_value / database_reference. */
57
+ member?: string;
58
+ }
59
+
60
+ /**
61
+ * One `[Name(args...)]` attribute on a routine.
62
+ *
63
+ * `name` is case-preserved as written. `raw` is the full `[…]` source slice,
64
+ * kept for diagnostics, dump-model fidelity, and any consumer that needs the
65
+ * original text. `args` are positional.
66
+ */
67
+ export interface AttributeInfo {
68
+ name: string;
69
+ args: AttributeArg[];
70
+ raw: string;
71
+ }
72
+
73
+ /**
74
+ * Case-insensitive lookup of the first attribute matching `name`. Returns
75
+ * undefined when absent — call sites should branch on that, not throw.
76
+ */
77
+ export function findAttribute(
78
+ attrs: readonly AttributeInfo[],
79
+ name: string,
80
+ ): AttributeInfo | undefined {
81
+ const lc = name.toLowerCase();
82
+ for (const a of attrs) {
83
+ if (a.name.toLowerCase() === lc) return a;
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ /** True when at least one attribute matches `name` (case-insensitive). */
89
+ export function hasAttribute(attrs: readonly AttributeInfo[], name: string): boolean {
90
+ return findAttribute(attrs, name) !== undefined;
91
+ }
92
+
93
+ /**
94
+ * Read the `value` of arg at `index` from `attr` when it's a string_literal.
95
+ * Returns undefined when the arg is absent, of the wrong kind, or value is unset.
96
+ */
97
+ export function stringArg(attr: AttributeInfo, index: number): string | undefined {
98
+ const a = attr.args[index];
99
+ if (a === undefined || a.kind !== "string_literal") return undefined;
100
+ return a.value;
101
+ }
102
+
103
+ /**
104
+ * Read a qualified_enum_value / database_reference arg's qualifier and member.
105
+ * Returns undefined when the arg is absent or of a non-qualified kind.
106
+ *
107
+ * Examples:
108
+ * `ObjectType::Codeunit` → { qualifier: "ObjectType", member: "Codeunit" }
109
+ * `Codeunit::"Sales-Post"` → { qualifier: "Codeunit", member: "Sales-Post" }
110
+ */
111
+ export function qualifiedArg(
112
+ attr: AttributeInfo,
113
+ index: number,
114
+ ): { qualifier: string; member: string } | undefined {
115
+ const a = attr.args[index];
116
+ if (a === undefined) return undefined;
117
+ if (a.kind !== "qualified_enum_value" && a.kind !== "database_reference") return undefined;
118
+ if (a.qualifier === undefined || a.member === undefined) return undefined;
119
+ return { qualifier: a.qualifier, member: a.member };
120
+ }
@@ -0,0 +1,45 @@
1
+ // src/model/callee.ts
2
+ // Structured classification of a CallSite's callee, derived from the
3
+ // `call_expression` node at L2 index time. Drives the call resolver's
4
+ // dispatch-kind decision without re-parsing the raw text.
5
+ //
6
+ // Two-layer rationale (per superpowers + GPT-5.5 review): cache tree-sitter-
7
+ // derived facts as serializable DTOs on the model, not parse trees. Detectors
8
+ // and resolvers read `CallSite.callee` directly — no `parseCallee(text)` round-
9
+ // trip, no regex shred.
10
+
11
+ /**
12
+ * Object-type keywords that prefix `Codeunit.Run` / `Page.Run` / `Report.Run`
13
+ * dispatch. Mirrors the AL object-type space; identical to the grammar's
14
+ * `object_type_keyword` alias minus Database/XMLport/Query (which have no
15
+ * `.Run` form).
16
+ */
17
+ export type ObjectRunKind = "Codeunit" | "Page" | "Report";
18
+
19
+ export type Callee =
20
+ | {
21
+ kind: "bare";
22
+ /** Procedure name, unquoted (quoted identifiers are stripped). */
23
+ name: string;
24
+ }
25
+ | {
26
+ kind: "member";
27
+ /** Receiver expression text (e.g. `Sales` for `Sales.Post()`). */
28
+ receiver: string;
29
+ /** Method name. */
30
+ method: string;
31
+ }
32
+ | {
33
+ kind: "object-run";
34
+ objectKind: ObjectRunKind;
35
+ /** Target object type — same as `objectKind` today; kept distinct in case AL ever permits a different prefix. */
36
+ targetType: ObjectRunKind;
37
+ /**
38
+ * The literal object name or decimal id (as text); undefined when the target
39
+ * is dynamic (a variable / expression we cannot resolve statically).
40
+ */
41
+ targetRef?: string;
42
+ /** true when `targetRef` is a name (quoted or unquoted), false for a numeric id, false for dynamic. */
43
+ targetIsName: boolean;
44
+ }
45
+ | { kind: "unknown" };
@@ -0,0 +1,187 @@
1
+ import type { RecordOpType, TempState } from "./entities.ts";
2
+ import type { CallsiteId, EventId, ObjectId, OperationId, RoutineId, TableId } from "./ids.ts";
3
+
4
+ /**
5
+ * Where a value at a capability extraction site comes from. The spec
6
+ * (§3.1) called this `SourceKind`; this implementation uses `ValueSource`
7
+ * to avoid a name collision with the existing
8
+ * `SourceKind = "workspace" | "app-source" | "symbol-only" | "external-source"`
9
+ * in `src/model/identity.ts` (which describes AL source provenance, not
10
+ * value provenance). Spec amendment scheduled post-Phase-0a.
11
+ *
12
+ * `constant-var` carries a recursive `initializer: ValueSource` so a chain
13
+ * `literal → constant-var → constant-var → ...` resolves all the way to
14
+ * its root literal for static endpoint/key diffing.
15
+ */
16
+ export type ValueSource =
17
+ | { kind: "literal"; value: string }
18
+ | { kind: "enum"; enumName: string; member?: string }
19
+ | { kind: "constant-var"; varName: string; initializer: ValueSource }
20
+ | { kind: "parameter"; index: number; varName: string }
21
+ | { kind: "table-field"; tableId: TableId | "unknown"; fieldName: string }
22
+ | { kind: "expression" }
23
+ | { kind: "unknown" };
24
+
25
+ /**
26
+ * What the capability does. Maps roughly onto an AL platform verb —
27
+ * `read`/`insert`/`modify`/`delete` for table data; `execute` for object
28
+ * run; `publish`/`subscribe` for events; `send` for HTTP; `log` for
29
+ * telemetry; `store-*` for isolated storage; `commit` standalone; `open`
30
+ * for hyperlink/file open; `write-blob` for file/tempblob write; `start`
31
+ * for background sessions/tasks; `ui-*` for user interaction prompts.
32
+ */
33
+ export type CapabilityOp =
34
+ | "read"
35
+ | "insert"
36
+ | "modify"
37
+ | "delete"
38
+ | "execute"
39
+ | "publish"
40
+ | "subscribe"
41
+ | "send"
42
+ | "log"
43
+ | "store-read"
44
+ | "store-write"
45
+ | "store-delete"
46
+ | "commit"
47
+ | "open"
48
+ | "write-blob"
49
+ | "start"
50
+ | "ui-confirm"
51
+ | "ui-message"
52
+ | "ui-error";
53
+
54
+ /** The resource family the capability acts on. */
55
+ export type CapabilityResourceKind =
56
+ | "table"
57
+ | "event"
58
+ | "codeunit"
59
+ | "page"
60
+ | "report"
61
+ | "http"
62
+ | "telemetry"
63
+ | "isolated-storage"
64
+ | "file"
65
+ | "transaction"
66
+ | "ui"
67
+ | "background";
68
+
69
+ /**
70
+ * How certain we are about the resource identity.
71
+ * - static target fully known at static time
72
+ * - boundedDynamic enum / whitelist / option-typed target
73
+ * - configDynamic target resolved from a setup/config table field
74
+ * - userDynamic user/external input flows into the target
75
+ * - unresolved target shape is unknown
76
+ */
77
+ export type CapabilityConfidence =
78
+ | "static"
79
+ | "boundedDynamic"
80
+ | "configDynamic"
81
+ | "userDynamic"
82
+ | "unresolved";
83
+
84
+ /** Where the fact came from: the routine itself (direct) or composed via
85
+ * an outgoing edge from a reachable routine (inherited). */
86
+ export type CapabilityProvenance = "direct" | "inherited";
87
+
88
+ /** The edge kind that brought an inherited fact in. `self` for direct facts. */
89
+ export type CapabilityVia =
90
+ | "self"
91
+ | "call"
92
+ | "object-run"
93
+ | "event-dispatch"
94
+ | "implicit-trigger"
95
+ | "dependency";
96
+
97
+ /**
98
+ * Per-resourceKind canonical semantics needed for witness rendering,
99
+ * permission inference, taint propagation, and policy matching.
100
+ *
101
+ * Substrate-discipline gate (spec §3.1.1): `extra` carries only resource
102
+ * semantics. It must NOT preserve detector-local query shapes. In
103
+ * particular, `SetRange`/`SetFilter`/`SetLoadFields` argument bags stay on
104
+ * `RecordOperation`, NOT on `CapabilityFact.extra.kind === "table"`.
105
+ */
106
+ export type CapabilityExtra = TableExtra | DispatchExtra | HttpExtra | EventExtra | StorageExtra;
107
+
108
+ export interface TableExtra {
109
+ kind: "table";
110
+ recordVariableId?: string;
111
+ tempState?: TempState;
112
+ /** FindSet vs FindFirst vs Get matters for permission inference;
113
+ * Init vs Modify matters for the modify-without-get family. */
114
+ opSubtype?: RecordOpType;
115
+ }
116
+
117
+ export interface DispatchExtra {
118
+ kind: "dispatch";
119
+ objectType: "Codeunit" | "Page" | "Report";
120
+ /** Page.RunModal vs Page.Run. */
121
+ modal?: boolean;
122
+ }
123
+
124
+ export interface HttpExtra {
125
+ kind: "http";
126
+ method: "Send" | "Get" | "Post" | "Put" | "Delete" | "Patch";
127
+ /** Phase 6 taint reads this. */
128
+ bodyArgSource?: ValueSource;
129
+ }
130
+
131
+ export interface EventExtra {
132
+ kind: "event";
133
+ eventClass: "Integration" | "Business" | "Internal" | "Trigger";
134
+ /** When op === "publish": whether the publisher passes IncludeSender. */
135
+ includeSender?: boolean;
136
+ }
137
+
138
+ export interface StorageExtra {
139
+ kind: "storage";
140
+ keyArgSource?: ValueSource;
141
+ /** For store-write. */
142
+ valueArgSource?: ValueSource;
143
+ scope?: "User" | "Company" | "Module" | "unknown";
144
+ }
145
+
146
+ /**
147
+ * Normalized capability fact — the canonical form for every capability
148
+ * claim the engine makes. Replaces the v1 boolean lattice
149
+ * (touchesDb/commits/writesTables/publishesEvents). Booleans / lists are
150
+ * derived views at consumption time.
151
+ *
152
+ * Two key dimensions:
153
+ * - Semantic capability key (consumers compare on this):
154
+ * (subject, op, resourceKind, resourceId, literalValueOf(resourceArgSource), confidence, provenance)
155
+ * - Evidence key (witness reconstruction):
156
+ * (witnessOperationId, witnessCallsiteId, via, sourceAnchor)
157
+ *
158
+ * Invariants:
159
+ * - `provenance === "direct"` → `via === "self"`
160
+ * - `provenance === "inherited"` → `via !== "self"` AND `witnessCallsiteId !== undefined`
161
+ * - `confidence === "static"` only when `resourceId` is fully determined
162
+ * AND `resourceArgSource` (if present) is a literal/enum/constant chain
163
+ * resolvable to a literal.
164
+ */
165
+ export interface CapabilityFact {
166
+ subject: RoutineId;
167
+ op: CapabilityOp;
168
+ resourceKind: CapabilityResourceKind;
169
+ /** Concrete stable target when known. Never a "static"/"dynamic"
170
+ * sentinel string — unresolved-ness is captured by `confidence` and
171
+ * `coverage`. */
172
+ resourceId?: TableId | EventId | ObjectId;
173
+ /** Normalized argument identity when the argument IS the resource (HTTP
174
+ * URL, storage key, telemetry event name, file path, static dispatch
175
+ * object id). When `resourceArgSource.kind === "literal"`, the literal
176
+ * value is part of the semantic capability key. */
177
+ resourceArgSource?: ValueSource;
178
+ confidence: CapabilityConfidence;
179
+ provenance: CapabilityProvenance;
180
+ via: CapabilityVia;
181
+ /** Direct facts always carry witnessOperationId or witnessCallsiteId.
182
+ * Inherited facts always carry witnessCallsiteId (the first-hop
183
+ * callsite from `subject`). */
184
+ witnessOperationId?: OperationId;
185
+ witnessCallsiteId?: CallsiteId;
186
+ extra?: CapabilityExtra;
187
+ }
@@ -0,0 +1,85 @@
1
+ import type { RoutineId } from "./ids.ts";
2
+ import type { StableRoutineId } from "./stable-identity.ts";
3
+
4
+ /**
5
+ * Three-valued lattice for capability-cone coverage. Per the capability-stack
6
+ * roadmap §3.7, this is a first-class substrate concept: every fingerprint /
7
+ * diff / policy verdict carries a coverage annotation. G6 admission gate
8
+ * enforces that the annotation reaches the output surface.
9
+ */
10
+ export type CoverageStatus = "complete" | "partial" | "unknown";
11
+
12
+ /**
13
+ * Why a capability cone is partial or unknown. Sorted, deduped in
14
+ * `CoverageRecord.reasons` for determinism.
15
+ */
16
+ export type CoverageReason =
17
+ | "opaque-dependency" // routine reachable through a symbol-only dep with no parseable body
18
+ | "parse-incomplete" // routine body had a parse error; structural extraction was partial
19
+ | "unresolved-call" // callsite couldn't be resolved to a target routine
20
+ | "object-run-unresolved" // dispatch target unknown (dynamic id, no resolved entrypoint)
21
+ | "event-subscriber-unknown" // event publisher reaches into a dependency cone we couldn't enumerate
22
+ | "missing-dep-package" // declared dependency package not available
23
+ | "grammar-unsupported" // AL shape outside tree-sitter-al's current grammar
24
+ | "extraction-failed"; // family extractor reported a diagnostic for this node
25
+
26
+ /**
27
+ * Per-routine coverage record. Computed by the Phase 0b coverage composer
28
+ * alongside capability fact composition; populated on `RoutineSummary` in
29
+ * Phase 0b (Phase 0a only declares the type).
30
+ *
31
+ * `directStatus` covers the routine's own DIRECT extraction.
32
+ * `inheritedStatus` covers the routine's INHERITED capability cone
33
+ * (the reachable closure via call / event-dispatch / object-run edges).
34
+ *
35
+ * Aggregated per-app and global coverage views are derived; not stored.
36
+ */
37
+ export interface CoverageRecord {
38
+ subject: RoutineId;
39
+ directStatus: CoverageStatus;
40
+ inheritedStatus: CoverageStatus;
41
+ reasons: CoverageReason[];
42
+ /** Stable IDs of the specific opaque/unresolved targets that contributed
43
+ * to the partial/unknown status. Drives "which dep is missing" diagnostics
44
+ * and the fingerprint dump's coverage explanation lines. */
45
+ unknownTargets: string[];
46
+ }
47
+
48
+ /**
49
+ * Coverage record specialized for fingerprint dump output (one per root
50
+ * routine). Stored in `CapabilitySnapshot` (Phase 0c) and rendered in the
51
+ * fingerprint CLI per-family coverage line (Phase 1).
52
+ *
53
+ * `perFamily` answers "for each capability family this fingerprint reports
54
+ * on, is the cone complete?" — the HTTP cone may be complete even when the
55
+ * storage cone is partial.
56
+ */
57
+ export interface FingerprintCoverage {
58
+ routine: StableRoutineId;
59
+ status: CoverageStatus;
60
+ reasons: CoverageReason[];
61
+ unknownTargets: string[];
62
+ perFamily: Record<CapabilityResourceKindForCoverage, CoverageStatus>;
63
+ }
64
+
65
+ /**
66
+ * The set of resource kinds a `FingerprintCoverage.perFamily` map keys on.
67
+ * Kept as a separate type alias so future additions to
68
+ * `CapabilityResourceKind` (in `capability.ts`) can be reflected here in
69
+ * lockstep. Phase 0a duplicates the literal union; Phase 1 may collapse it
70
+ * into a generic `Record<CapabilityResourceKind, CoverageStatus>` if all
71
+ * kinds are coverage-relevant.
72
+ */
73
+ export type CapabilityResourceKindForCoverage =
74
+ | "table"
75
+ | "event"
76
+ | "codeunit"
77
+ | "page"
78
+ | "report"
79
+ | "http"
80
+ | "telemetry"
81
+ | "isolated-storage"
82
+ | "file"
83
+ | "transaction"
84
+ | "ui"
85
+ | "background";