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.
- package/LICENSE +21 -0
- package/README.md +361 -0
- package/package.json +64 -0
- package/scripts/d40-diff.ts +44 -0
- package/scripts/fetch-native-parser.ts +179 -0
- package/scripts/precision-sample.ts +99 -0
- package/scripts/precision-study.ts +42 -0
- package/scripts/precision-tabulate.ts +52 -0
- package/src/cli/baseline.ts +31 -0
- package/src/cli/diff.ts +199 -0
- package/src/cli/events-chains.ts +56 -0
- package/src/cli/events-fanout.ts +87 -0
- package/src/cli/exit-code.ts +30 -0
- package/src/cli/fingerprint-indexes.ts +130 -0
- package/src/cli/fingerprint-query.ts +543 -0
- package/src/cli/fingerprint-witness.ts +493 -0
- package/src/cli/fingerprint.ts +292 -0
- package/src/cli/format-compact-json.ts +45 -0
- package/src/cli/format-events.ts +77 -0
- package/src/cli/format-fingerprint.ts +295 -0
- package/src/cli/format-html.ts +503 -0
- package/src/cli/format-json.ts +13 -0
- package/src/cli/format-policy.ts +95 -0
- package/src/cli/format-sarif.ts +186 -0
- package/src/cli/format-terminal.ts +153 -0
- package/src/cli/index.ts +566 -0
- package/src/cli/policy.ts +204 -0
- package/src/config/roots-config.ts +302 -0
- package/src/deps/cache-versions.ts +74 -0
- package/src/deps/canonical-json.ts +27 -0
- package/src/deps/dependency-artifact.ts +144 -0
- package/src/deps/dependency-cache.ts +262 -0
- package/src/deps/dependency-dag.ts +128 -0
- package/src/deps/dependency-package-discovery.ts +85 -0
- package/src/deps/dependency-pipeline.ts +483 -0
- package/src/deps/dependency-projection.ts +211 -0
- package/src/deps/dependency-resolver.ts +154 -0
- package/src/deps/workspace-dependencies.ts +114 -0
- package/src/detectors/capability-query.ts +145 -0
- package/src/detectors/confidence.ts +52 -0
- package/src/detectors/d1-db-op-in-loop.ts +457 -0
- package/src/detectors/d10-self-modifying-loop.ts +114 -0
- package/src/detectors/d11-modify-without-get.ts +129 -0
- package/src/detectors/d12-dead-integration-event.ts +81 -0
- package/src/detectors/d13-cross-app-internal-call.ts +105 -0
- package/src/detectors/d14-dead-routine.ts +151 -0
- package/src/detectors/d16-obsolete-routine-call.ts +94 -0
- package/src/detectors/d17-min-version-drift.ts +157 -0
- package/src/detectors/d18-constant-filter-in-loop.ts +151 -0
- package/src/detectors/d19-unused-parameter.ts +116 -0
- package/src/detectors/d2-event-fanout-in-loop.ts +240 -0
- package/src/detectors/d20-unreachable-after-exit.ts +92 -0
- package/src/detectors/d21-read-without-load.ts +128 -0
- package/src/detectors/d22-flowfield-without-calcfields.ts +168 -0
- package/src/detectors/d29-subscriber-modify-on-event-record.ts +163 -0
- package/src/detectors/d3-load-state.ts +72 -0
- package/src/detectors/d3-missing-setloadfields.ts +234 -0
- package/src/detectors/d32-constant-boolean-parameter.ts +185 -0
- package/src/detectors/d33-unfiltered-bulk-write.ts +173 -0
- package/src/detectors/d34-commit-in-loop.ts +206 -0
- package/src/detectors/d35-commit-in-event-subscriber.ts +138 -0
- package/src/detectors/d36-late-setloadfields.ts +162 -0
- package/src/detectors/d37-validate-without-persist.ts +271 -0
- package/src/detectors/d38-subscriber-to-obsolete-event.ts +140 -0
- package/src/detectors/d39-record-left-dirty-across-chain.ts +165 -0
- package/src/detectors/d4-repeated-lookup-in-loop.ts +128 -0
- package/src/detectors/d40-transitive-load-missing.ts +217 -0
- package/src/detectors/d41-transitive-filter-loss.ts +200 -0
- package/src/detectors/d42-cross-call-wrong-setloadfields.ts +243 -0
- package/src/detectors/d43-event-ishandled-skip.ts +257 -0
- package/src/detectors/d44-event-multi-subscriber-overlap.ts +223 -0
- package/src/detectors/d45-event-transitive-table-exposure.ts +159 -0
- package/src/detectors/d5-set-based-opportunity.ts +162 -0
- package/src/detectors/d7-recursive-event-expansion.ts +151 -0
- package/src/detectors/d8-commit-in-transaction.ts +132 -0
- package/src/detectors/d9-transaction-span-summary.ts +107 -0
- package/src/detectors/detector-context.ts +121 -0
- package/src/detectors/finding-grouping.ts +61 -0
- package/src/detectors/path-merge.ts +174 -0
- package/src/detectors/registry.ts +176 -0
- package/src/detectors/table-display.ts +42 -0
- package/src/diff/diff-abi.ts +195 -0
- package/src/diff/diff-capabilities.ts +179 -0
- package/src/diff/diff-engine.ts +146 -0
- package/src/diff/diff-events.ts +323 -0
- package/src/diff/diff-identity.ts +73 -0
- package/src/diff/diff-indexes.ts +199 -0
- package/src/diff/diff-permissions.ts +260 -0
- package/src/diff/diff-policy.ts +101 -0
- package/src/diff/diff-preflight.ts +66 -0
- package/src/diff/diff-renames.ts +104 -0
- package/src/diff/diff-schema.ts +232 -0
- package/src/diff/format-diff.ts +148 -0
- package/src/engine/attribute-parser.ts +50 -0
- package/src/engine/capability-cone.ts +531 -0
- package/src/engine/combined-graph.ts +357 -0
- package/src/engine/control-flow-walker.ts +1317 -0
- package/src/engine/dispatch-sites.ts +199 -0
- package/src/engine/effect-lattice.ts +81 -0
- package/src/engine/entry-points.ts +57 -0
- package/src/engine/event-flow.ts +524 -0
- package/src/engine/event-relay.ts +92 -0
- package/src/engine/op-classification.ts +92 -0
- package/src/engine/path-walker.ts +189 -0
- package/src/engine/reverse-call-graph.ts +23 -0
- package/src/engine/root-classifier-overlay.ts +194 -0
- package/src/engine/root-classifier.ts +135 -0
- package/src/engine/scc.ts +110 -0
- package/src/engine/source-anchor.ts +25 -0
- package/src/engine/summary-context.ts +104 -0
- package/src/engine/summary-engine.ts +296 -0
- package/src/engine/summary-runner.ts +560 -0
- package/src/engine/transaction-spans.ts +112 -0
- package/src/engine/uncertainty-util.ts +54 -0
- package/src/hash.ts +31 -0
- package/src/index/attribute-from-node.ts +141 -0
- package/src/index/callee-from-node.ts +181 -0
- package/src/index/capability/background.ts +90 -0
- package/src/index/capability/commit.ts +44 -0
- package/src/index/capability/dispatch.ts +164 -0
- package/src/index/capability/events.ts +65 -0
- package/src/index/capability/extractor.ts +124 -0
- package/src/index/capability/file-blob.ts +137 -0
- package/src/index/capability/http.ts +159 -0
- package/src/index/capability/hyperlink.ts +60 -0
- package/src/index/capability/isolated-storage.ts +179 -0
- package/src/index/capability/table.ts +113 -0
- package/src/index/capability/telemetry.ts +84 -0
- package/src/index/capability/ui.ts +55 -0
- package/src/index/capability/value-source.ts +202 -0
- package/src/index/expression-from-node.ts +117 -0
- package/src/index/indexer.ts +102 -0
- package/src/index/intraprocedural-body.ts +1467 -0
- package/src/index/intraprocedural-ops.ts +253 -0
- package/src/index/intraprocedural-refs.ts +188 -0
- package/src/index/object-indexer.ts +279 -0
- package/src/index/routine-indexer.ts +282 -0
- package/src/index/routine-signature.ts +46 -0
- package/src/index/variable-indexer.ts +134 -0
- package/src/index/variable-initializer-extractor.ts +155 -0
- package/src/index/variable-type-normalizer.ts +83 -0
- package/src/index.ts +267 -0
- package/src/mcp/server.ts +72 -0
- package/src/mcp/session.ts +49 -0
- package/src/mcp/tools/explain-path.ts +75 -0
- package/src/mcp/tools/get-analysis-health.ts +62 -0
- package/src/mcp/tools/get-finding.ts +47 -0
- package/src/mcp/tools/get-routine-summary.ts +126 -0
- package/src/mcp/tools/list-findings.ts +85 -0
- package/src/mcp/tools/list-hotspots.ts +78 -0
- package/src/mcp/tools/list-rollups.ts +103 -0
- package/src/mcp/tools/validators.ts +25 -0
- package/src/model/attributes.ts +120 -0
- package/src/model/callee.ts +45 -0
- package/src/model/capability.ts +187 -0
- package/src/model/coverage.ts +85 -0
- package/src/model/entities.ts +628 -0
- package/src/model/expression.ts +98 -0
- package/src/model/finding.ts +110 -0
- package/src/model/graph-edge.ts +93 -0
- package/src/model/graph.ts +62 -0
- package/src/model/identity.ts +81 -0
- package/src/model/ids.ts +90 -0
- package/src/model/index.ts +13 -0
- package/src/model/model.ts +51 -0
- package/src/model/permission.ts +76 -0
- package/src/model/root-classification.ts +116 -0
- package/src/model/stable-identity.ts +102 -0
- package/src/model/summary.ts +96 -0
- package/src/parser/ast.ts +82 -0
- package/src/parser/native/ffi.ts +145 -0
- package/src/parser/native/parse-index-pool.ts +148 -0
- package/src/parser/native/parse-index-worker.ts +94 -0
- package/src/parser/native/wrapper.ts +353 -0
- package/src/parser/parser-init.ts +43 -0
- package/src/perf/profiler.ts +66 -0
- package/src/policy/policy-default.yaml +83 -0
- package/src/policy/policy-engine.ts +339 -0
- package/src/policy/policy-loader.ts +257 -0
- package/src/policy/policy-schema.json +379 -0
- package/src/policy/policy-types.ts +81 -0
- package/src/policy/predicate-compiler.ts +151 -0
- package/src/policy/predicate-evaluator.ts +267 -0
- package/src/policy/predicate-fields.ts +439 -0
- package/src/projection/actionable-anchor.ts +48 -0
- package/src/projection/finding-filters.ts +44 -0
- package/src/projection/finding-fingerprint.ts +54 -0
- package/src/projection/finding-groups.ts +41 -0
- package/src/projection/finding-summary.ts +110 -0
- package/src/projection/rollup-findings.ts +105 -0
- package/src/providers/discover.ts +88 -0
- package/src/providers/external.ts +46 -0
- package/src/providers/types.ts +36 -0
- package/src/providers/workspace.ts +117 -0
- package/src/resolve/call-resolver.ts +117 -0
- package/src/resolve/coverage.ts +61 -0
- package/src/resolve/event-graph.ts +166 -0
- package/src/resolve/implicit-edges.ts +53 -0
- package/src/resolve/record-types.ts +36 -0
- package/src/resolve/resolver.ts +23 -0
- package/src/resolve/semantic-graph.ts +29 -0
- package/src/resolve/symbol-table.ts +69 -0
- package/src/snapshot/app-snapshot.ts +74 -0
- package/src/snapshot/compose.ts +100 -0
- package/src/snapshot/derive/callsite-evidence.ts +76 -0
- package/src/snapshot/derive/capability-facts.ts +70 -0
- package/src/snapshot/derive/contracts.ts +131 -0
- package/src/snapshot/derive/coverage.ts +35 -0
- package/src/snapshot/derive/event-declarations.ts +140 -0
- package/src/snapshot/derive/identity-table.ts +58 -0
- package/src/snapshot/derive/inputs.ts +91 -0
- package/src/snapshot/derive/operation-evidence.ts +70 -0
- package/src/snapshot/derive/permissions.ts +186 -0
- package/src/snapshot/derive/root-classifications.ts +56 -0
- package/src/snapshot/derive/schema.ts +130 -0
- package/src/snapshot/derive/typed-edges.ts +60 -0
- package/src/snapshot/derive/workspace-fingerprint.ts +19 -0
- package/src/snapshot/deserialize.ts +40 -0
- package/src/snapshot/serialize-cbor-gz.ts +12 -0
- package/src/snapshot/serialize-cbor.ts +19 -0
- package/src/snapshot/serialize-json.ts +22 -0
- package/src/snapshot/shard.ts +134 -0
- package/src/snapshot/types.ts +181 -0
- package/src/symbols/app-manifest.ts +96 -0
- package/src/symbols/app-package-zip.ts +50 -0
- package/src/symbols/embedded-source-reader.ts +41 -0
- package/src/symbols/package-hash.ts +81 -0
- package/src/symbols/symbol-reader.ts +101 -0
- package/src/symbols/symbol-reference-parser.ts +378 -0
- package/src/symbols/symbol-reference-reader.ts +27 -0
- 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";
|