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,164 @@
|
|
|
1
|
+
// src/index/capability/dispatch.ts
|
|
2
|
+
//
|
|
3
|
+
// Phase 0b-β dispatch family extractor. Detects dynamic object-dispatch calls:
|
|
4
|
+
//
|
|
5
|
+
// object-run callees (pre-classified by the Callee model at L2):
|
|
6
|
+
// Codeunit.Run(targetId, ...) → execute, codeunit
|
|
7
|
+
// Page.Run(targetId, ...) → execute, page
|
|
8
|
+
// Report.Run(targetId, ...) → execute, report
|
|
9
|
+
//
|
|
10
|
+
// member callees (not pre-classified as object-run by the L2 indexer):
|
|
11
|
+
// Page.RunModal(targetId, ...) → execute, page, modal: true
|
|
12
|
+
// Report.Execute(targetId, ...) → execute, report
|
|
13
|
+
//
|
|
14
|
+
// Target-id argument (1st positional) is classified via classifyValueSource:
|
|
15
|
+
// literal / enum / database_reference → static
|
|
16
|
+
// parameter → userDynamic
|
|
17
|
+
// table-field → configDynamic
|
|
18
|
+
// expression / unknown → unresolved
|
|
19
|
+
//
|
|
20
|
+
// resourceId resolution is deferred — stable-identity map wiring through to
|
|
21
|
+
// extraction time is a Phase 1c follow-up.
|
|
22
|
+
//
|
|
23
|
+
// Never throws.
|
|
24
|
+
|
|
25
|
+
import type { ObjectRunKind } from "../../model/callee.ts";
|
|
26
|
+
import type {
|
|
27
|
+
CapabilityConfidence,
|
|
28
|
+
CapabilityFact,
|
|
29
|
+
DispatchExtra,
|
|
30
|
+
ValueSource,
|
|
31
|
+
} from "../../model/capability.ts";
|
|
32
|
+
import type { CoverageReason } from "../../model/coverage.ts";
|
|
33
|
+
import type { ExtractionContext } from "./extractor.ts";
|
|
34
|
+
import { classifyValueSource } from "./value-source.ts";
|
|
35
|
+
|
|
36
|
+
// ─── Member-callee dispatch table ────────────────────────────────────────────
|
|
37
|
+
// Covers methods that the L2 indexer does NOT pre-classify as "object-run"
|
|
38
|
+
// because they use non-canonical method names (RunModal, Execute).
|
|
39
|
+
|
|
40
|
+
interface MemberDispatchSpec {
|
|
41
|
+
objectType: ObjectRunKind;
|
|
42
|
+
modal?: true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Keys are lowercased "receiver|method" pairs.
|
|
46
|
+
const MEMBER_DISPATCH_MAP = new Map<string, MemberDispatchSpec>([
|
|
47
|
+
["page|runmodal", { objectType: "Page", modal: true }],
|
|
48
|
+
["report|execute", { objectType: "Report" }],
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// ─── Public extractor ─────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Phase 0b-β dispatch family extractor.
|
|
55
|
+
*
|
|
56
|
+
* Scans `callSites` for two callee shapes:
|
|
57
|
+
* - `"object-run"` — pre-classified by the L2 indexer for `.Run` methods on
|
|
58
|
+
* Codeunit / Page / Report. `objectKind` gives the target object type.
|
|
59
|
+
* - `"member"` — for `Page.RunModal` and `Report.Execute`, which the L2
|
|
60
|
+
* indexer emits as member callees because their method name differs from
|
|
61
|
+
* the canonical `.Run`.
|
|
62
|
+
*
|
|
63
|
+
* Emits one `CapabilityFact` (op: "execute") per matched call site.
|
|
64
|
+
*
|
|
65
|
+
* Never throws.
|
|
66
|
+
*/
|
|
67
|
+
export function extractDispatch(ctx: ExtractionContext): {
|
|
68
|
+
facts: CapabilityFact[];
|
|
69
|
+
reasons: CoverageReason[];
|
|
70
|
+
} {
|
|
71
|
+
try {
|
|
72
|
+
const facts: CapabilityFact[] = [];
|
|
73
|
+
|
|
74
|
+
for (const cs of ctx.routine?.features?.callSites ?? []) {
|
|
75
|
+
const callee = cs.callee;
|
|
76
|
+
if (!callee) continue;
|
|
77
|
+
|
|
78
|
+
if (callee.kind === "object-run") {
|
|
79
|
+
// Pre-classified by the L2 indexer: Codeunit.Run, Page.Run, Report.Run
|
|
80
|
+
const objectType = callee.objectKind;
|
|
81
|
+
const targetArgSource = classifyTargetArg(cs.argumentInfos, 0, ctx);
|
|
82
|
+
const extra: DispatchExtra = { kind: "dispatch", objectType };
|
|
83
|
+
facts.push(buildFact(ctx, objectType, targetArgSource, extra, cs.id));
|
|
84
|
+
} else if (callee.kind === "member") {
|
|
85
|
+
// Page.RunModal / Report.Execute — not pre-classified as object-run
|
|
86
|
+
const key = `${callee.receiver.toLowerCase()}|${callee.method.toLowerCase()}`;
|
|
87
|
+
const spec = MEMBER_DISPATCH_MAP.get(key);
|
|
88
|
+
if (spec === undefined) continue;
|
|
89
|
+
|
|
90
|
+
const targetArgSource = classifyTargetArg(cs.argumentInfos, 0, ctx);
|
|
91
|
+
const extra: DispatchExtra = {
|
|
92
|
+
kind: "dispatch",
|
|
93
|
+
objectType: spec.objectType,
|
|
94
|
+
...(spec.modal === true ? { modal: true } : {}),
|
|
95
|
+
};
|
|
96
|
+
facts.push(buildFact(ctx, spec.objectType, targetArgSource, extra, cs.id));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { facts, reasons: [] };
|
|
101
|
+
} catch {
|
|
102
|
+
return { facts: [], reasons: ["extraction-failed"] };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function classifyTargetArg(
|
|
109
|
+
argumentInfos: readonly { kind: string; text: string }[] | undefined,
|
|
110
|
+
index: number,
|
|
111
|
+
ctx: ExtractionContext,
|
|
112
|
+
): ValueSource {
|
|
113
|
+
const arg = argumentInfos?.[index];
|
|
114
|
+
return arg !== undefined ? classifyValueSource(arg as never, ctx) : { kind: "unknown" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildFact(
|
|
118
|
+
ctx: ExtractionContext,
|
|
119
|
+
objectType: ObjectRunKind,
|
|
120
|
+
targetArgSource: ValueSource,
|
|
121
|
+
extra: DispatchExtra,
|
|
122
|
+
witnessCallsiteId: string,
|
|
123
|
+
): CapabilityFact {
|
|
124
|
+
const resourceKind = objectTypeToResourceKind(objectType);
|
|
125
|
+
return {
|
|
126
|
+
subject: ctx.routine.id,
|
|
127
|
+
op: "execute",
|
|
128
|
+
resourceKind,
|
|
129
|
+
resourceArgSource: targetArgSource,
|
|
130
|
+
confidence: confidenceFromSource(targetArgSource),
|
|
131
|
+
provenance: "direct",
|
|
132
|
+
via: "self",
|
|
133
|
+
witnessCallsiteId,
|
|
134
|
+
extra,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function objectTypeToResourceKind(objectType: ObjectRunKind): "codeunit" | "page" | "report" {
|
|
139
|
+
switch (objectType) {
|
|
140
|
+
case "Codeunit":
|
|
141
|
+
return "codeunit";
|
|
142
|
+
case "Page":
|
|
143
|
+
return "page";
|
|
144
|
+
case "Report":
|
|
145
|
+
return "report";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function confidenceFromSource(vs: ValueSource): CapabilityConfidence {
|
|
150
|
+
switch (vs.kind) {
|
|
151
|
+
case "literal":
|
|
152
|
+
case "enum":
|
|
153
|
+
return "static";
|
|
154
|
+
case "constant-var":
|
|
155
|
+
return confidenceFromSource(vs.initializer);
|
|
156
|
+
case "parameter":
|
|
157
|
+
return "userDynamic";
|
|
158
|
+
case "table-field":
|
|
159
|
+
return "configDynamic";
|
|
160
|
+
case "expression":
|
|
161
|
+
case "unknown":
|
|
162
|
+
return "unresolved";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/index/capability/events.ts
|
|
2
|
+
//
|
|
3
|
+
// Phase 0b-β events family extractor. Scope: SUBSCRIBE side only.
|
|
4
|
+
//
|
|
5
|
+
// A routine with an [EventSubscriber(...)] attribute is a subscriber to a
|
|
6
|
+
// published event. The structured attribute (already in routine.attributesParsed)
|
|
7
|
+
// tells us which publisher + event name; for this phase we emit a presence
|
|
8
|
+
// fact (op="subscribe", resourceKind="event") without resolving the publisher
|
|
9
|
+
// routine — that happens in Task 20 (event-dispatch composition).
|
|
10
|
+
//
|
|
11
|
+
// PUBLISH side: routines with [IntegrationEvent] / [BusinessEvent] /
|
|
12
|
+
// [InternalEvent] attributes emit NO fact here. publish facts arise on the
|
|
13
|
+
// CALLER of a publisher, via event-dispatch edges (Task 20) + capability
|
|
14
|
+
// composition (Task 22).
|
|
15
|
+
//
|
|
16
|
+
// Never throws.
|
|
17
|
+
|
|
18
|
+
import { findAttribute } from "../../model/attributes.ts";
|
|
19
|
+
import type { CapabilityFact, EventExtra } from "../../model/capability.ts";
|
|
20
|
+
import type { CoverageReason } from "../../model/coverage.ts";
|
|
21
|
+
import type { ExtractionContext } from "./extractor.ts";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Phase 0b-β events family extractor.
|
|
25
|
+
*
|
|
26
|
+
* Emits one `subscribe` CapabilityFact for routines with an
|
|
27
|
+
* [EventSubscriber(...)] attribute. Publisher-decorated routines
|
|
28
|
+
* ([IntegrationEvent] / [BusinessEvent] / [InternalEvent]) emit nothing —
|
|
29
|
+
* their publish facts arise from callers via event-dispatch edges (Task 20).
|
|
30
|
+
*
|
|
31
|
+
* Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export function extractEvents(ctx: ExtractionContext): {
|
|
34
|
+
facts: CapabilityFact[];
|
|
35
|
+
reasons: CoverageReason[];
|
|
36
|
+
} {
|
|
37
|
+
try {
|
|
38
|
+
const attrs = ctx.routine?.attributesParsed ?? [];
|
|
39
|
+
|
|
40
|
+
if (findAttribute(attrs, "EventSubscriber") === undefined) {
|
|
41
|
+
return { facts: [], reasons: [] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TODO Phase 0b-β.5: detect Business/Internal/Trigger via publisher's
|
|
45
|
+
// attribute lookup rather than defaulting to "Integration".
|
|
46
|
+
const extra: EventExtra = {
|
|
47
|
+
kind: "event",
|
|
48
|
+
eventClass: "Integration",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const fact: CapabilityFact = {
|
|
52
|
+
subject: ctx.routine.id,
|
|
53
|
+
op: "subscribe",
|
|
54
|
+
resourceKind: "event",
|
|
55
|
+
confidence: "static",
|
|
56
|
+
provenance: "direct",
|
|
57
|
+
via: "self",
|
|
58
|
+
extra,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return { facts: [fact], reasons: [] };
|
|
62
|
+
} catch {
|
|
63
|
+
return { facts: [], reasons: ["extraction-failed"] };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { CapabilityFact } from "../../model/capability.ts";
|
|
2
|
+
import type { CoverageReason, CoverageStatus } from "../../model/coverage.ts";
|
|
3
|
+
import type { Routine, VariableSymbol } from "../../model/entities.ts";
|
|
4
|
+
import type { Diagnostic } from "../../model/finding.ts";
|
|
5
|
+
import { extractBackground } from "./background.ts";
|
|
6
|
+
import { extractCommit } from "./commit.ts";
|
|
7
|
+
import { extractDispatch } from "./dispatch.ts";
|
|
8
|
+
import { extractEvents } from "./events.ts";
|
|
9
|
+
import { extractFileBlob } from "./file-blob.ts";
|
|
10
|
+
import { extractHttp } from "./http.ts";
|
|
11
|
+
import { extractHyperlink } from "./hyperlink.ts";
|
|
12
|
+
import { extractIsolatedStorage } from "./isolated-storage.ts";
|
|
13
|
+
import { extractTable } from "./table.ts";
|
|
14
|
+
import { extractTelemetry } from "./telemetry.ts";
|
|
15
|
+
import { extractUi } from "./ui.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context passed to capability extractors. Extractors emit diagnostics
|
|
19
|
+
* and coverage gaps via the sinks here — they MUST NEVER throw
|
|
20
|
+
* (engine-never-throws contract per CLAUDE.md, formalized for
|
|
21
|
+
* extractors in spec §3.10).
|
|
22
|
+
*
|
|
23
|
+
* Phase 0a defines the shape; Phase 0b populates `variables` and routes
|
|
24
|
+
* to family extractors. Phase 0a's orchestrator is a no-op shell.
|
|
25
|
+
*/
|
|
26
|
+
export interface ExtractionContext {
|
|
27
|
+
routine: Routine;
|
|
28
|
+
/** Per-name lookup over routine.features.variables (lowercased keys).
|
|
29
|
+
* Built once by the orchestrator before dispatching to family extractors. */
|
|
30
|
+
variables: Map<string, VariableSymbol>;
|
|
31
|
+
/** Resolves a member-call's receiver name to its declared type.
|
|
32
|
+
* Returns "unknown" when the variable isn't in the index. */
|
|
33
|
+
receiverTypeOf(receiverName: string): string;
|
|
34
|
+
reportDiagnostic(d: Diagnostic): void;
|
|
35
|
+
reportCoverageGap(reason: CoverageReason, target?: string): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What an extractor (orchestrator or family) returns.
|
|
40
|
+
* - facts — the capability facts the extractor was able to recognize
|
|
41
|
+
* - status — "complete" = ran cleanly; "partial" = some nodes skipped
|
|
42
|
+
* due to grammar/parse gaps (diagnostics emitted);
|
|
43
|
+
* "unknown" = extractor itself errored on input shape it
|
|
44
|
+
* didn't recognize. Phase 0a shell returns "unknown".
|
|
45
|
+
* - reasons — coverage reasons contributing to non-"complete" status.
|
|
46
|
+
* Phase 0a shell returns ["extraction-failed"] — the
|
|
47
|
+
* shell hasn't extracted anything, so callers must treat
|
|
48
|
+
* the result as "we don't know".
|
|
49
|
+
*
|
|
50
|
+
* Extractors return `facts: []` only when the absence is KNOWN (no calls
|
|
51
|
+
* of the relevant family in the body). Phase 0a's shell returns
|
|
52
|
+
* `facts: []` because no extraction logic exists yet; the "unknown"
|
|
53
|
+
* status tells callers not to confuse this with "known absent".
|
|
54
|
+
*/
|
|
55
|
+
export interface CapabilityExtractionResult {
|
|
56
|
+
facts: CapabilityFact[];
|
|
57
|
+
status: CoverageStatus;
|
|
58
|
+
reasons: CoverageReason[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Phase 0b-β orchestrator. Dispatches across all 11 family extractors,
|
|
63
|
+
* aggregates facts + reasons, rolls up overall status.
|
|
64
|
+
*
|
|
65
|
+
* Status roll-up:
|
|
66
|
+
* - "complete" when no family reported reasons
|
|
67
|
+
* - "partial" when some families reported coverage reasons
|
|
68
|
+
* - "unknown" when the orchestrator itself threw (caught by outer try/catch)
|
|
69
|
+
*
|
|
70
|
+
* The orchestrator always rebuilds ctx.variables from routine.features.variables
|
|
71
|
+
* internally — callers pass any ctx (even a partially-constructed one); the
|
|
72
|
+
* orchestrator uses its own variable index for family dispatch. This ensures
|
|
73
|
+
* extraction is reproducible regardless of caller setup.
|
|
74
|
+
*
|
|
75
|
+
* Engine-never-throws contract per CLAUDE.md.
|
|
76
|
+
*/
|
|
77
|
+
export function extractCapabilities(
|
|
78
|
+
routine: Routine,
|
|
79
|
+
ctx: ExtractionContext,
|
|
80
|
+
): CapabilityExtractionResult {
|
|
81
|
+
try {
|
|
82
|
+
// Rebuild variables Map from routine.features (caller's may be stale).
|
|
83
|
+
const variables = new Map<string, VariableSymbol>();
|
|
84
|
+
for (const v of routine?.features?.variables ?? []) {
|
|
85
|
+
variables.set(v.name, v);
|
|
86
|
+
}
|
|
87
|
+
const dispatchCtx: ExtractionContext = {
|
|
88
|
+
routine,
|
|
89
|
+
variables,
|
|
90
|
+
receiverTypeOf: (name) => variables.get(name.toLowerCase())?.declaredType ?? "unknown",
|
|
91
|
+
reportDiagnostic: ctx.reportDiagnostic,
|
|
92
|
+
reportCoverageGap: ctx.reportCoverageGap,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const allFacts: CapabilityFact[] = [];
|
|
96
|
+
const allReasons: CoverageReason[] = [];
|
|
97
|
+
|
|
98
|
+
for (const extractor of [
|
|
99
|
+
extractTable,
|
|
100
|
+
extractCommit,
|
|
101
|
+
extractDispatch,
|
|
102
|
+
extractHttp,
|
|
103
|
+
extractTelemetry,
|
|
104
|
+
extractIsolatedStorage,
|
|
105
|
+
extractHyperlink,
|
|
106
|
+
extractFileBlob,
|
|
107
|
+
extractBackground,
|
|
108
|
+
extractUi,
|
|
109
|
+
extractEvents,
|
|
110
|
+
]) {
|
|
111
|
+
const { facts, reasons } = extractor(dispatchCtx);
|
|
112
|
+
allFacts.push(...facts);
|
|
113
|
+
allReasons.push(...reasons);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const status: CoverageStatus = allReasons.length === 0 ? "complete" : "partial";
|
|
117
|
+
// Dedupe + sort reasons for determinism.
|
|
118
|
+
const dedupedReasons = Array.from(new Set(allReasons)).sort();
|
|
119
|
+
|
|
120
|
+
return { facts: allFacts, status, reasons: dedupedReasons };
|
|
121
|
+
} catch {
|
|
122
|
+
return { facts: [], status: "unknown", reasons: ["extraction-failed"] };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { CapabilityConfidence, CapabilityFact, ValueSource } from "../../model/capability.ts";
|
|
2
|
+
import type { CoverageReason } from "../../model/coverage.ts";
|
|
3
|
+
import type { ExtractionContext } from "./extractor.ts";
|
|
4
|
+
import { classifyValueSource } from "./value-source.ts";
|
|
5
|
+
|
|
6
|
+
// Probe result: F.Create / F.WriteAllText / TB.CreateOutStream all land in
|
|
7
|
+
// callSites as member callees. F.Copy lands in recordOperations (op="Copy")
|
|
8
|
+
// because RECORD_OP_MAP matches "copy" regardless of receiver type.
|
|
9
|
+
|
|
10
|
+
/** Write-side File methods found in callSites (member callee). */
|
|
11
|
+
const FILE_CALLSITE_METHODS = new Set(["create", "writealltext"]);
|
|
12
|
+
|
|
13
|
+
/** Write-side TempBlob methods found in callSites (member callee). */
|
|
14
|
+
const TEMPBLOB_CALLSITE_METHODS = new Set(["createoutstream"]);
|
|
15
|
+
|
|
16
|
+
/** Write-side ops found in recordOperations for File-typed receivers. */
|
|
17
|
+
const FILE_RECORD_OPS = new Set(["copy"]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Phase 0b-β file-blob family extractor. Detects write-side method calls on
|
|
21
|
+
* variables typed `File` or `Codeunit "Temp Blob"`.
|
|
22
|
+
*
|
|
23
|
+
* Maps to op="write-blob", resourceKind="file". Receiver type resolved via
|
|
24
|
+
* ctx.receiverTypeOf, keyed against the normalised declared-type strings:
|
|
25
|
+
* - File variables → "File"
|
|
26
|
+
* - TempBlob variables → 'Codeunit "Temp Blob"' (quotes preserved)
|
|
27
|
+
*
|
|
28
|
+
* Probed split: callSites handle Create / WriteAllText / CreateOutStream;
|
|
29
|
+
* recordOperations handles Copy (RECORD_OP_MAP picks it up by name
|
|
30
|
+
* independently of receiver type — the File-type guard filters it here).
|
|
31
|
+
*
|
|
32
|
+
* The first positional arg, when present (filename/path), is classified as a
|
|
33
|
+
* ValueSource and exposed via resourceArgSource. Methods with no filename arg
|
|
34
|
+
* (CreateOutStream, Copy-via-recordOp which carries no fieldArgumentInfos)
|
|
35
|
+
* receive { kind: "unknown" } → confidence "unresolved".
|
|
36
|
+
*
|
|
37
|
+
* Read-side methods (.CreateInStream, etc.) are deliberately out of scope.
|
|
38
|
+
*
|
|
39
|
+
* Never throws.
|
|
40
|
+
*/
|
|
41
|
+
export function extractFileBlob(ctx: ExtractionContext): {
|
|
42
|
+
facts: CapabilityFact[];
|
|
43
|
+
reasons: CoverageReason[];
|
|
44
|
+
} {
|
|
45
|
+
try {
|
|
46
|
+
const facts: CapabilityFact[] = [];
|
|
47
|
+
|
|
48
|
+
// Branch A: callSites — File.Create, File.WriteAllText, TempBlob.CreateOutStream.
|
|
49
|
+
for (const cs of ctx.routine?.features?.callSites ?? []) {
|
|
50
|
+
const callee = cs.callee;
|
|
51
|
+
if (!callee || callee.kind !== "member") continue;
|
|
52
|
+
if (typeof callee.receiver !== "string" || typeof callee.method !== "string") continue;
|
|
53
|
+
const methodLc = callee.method.toLowerCase();
|
|
54
|
+
const receiverType = ctx.receiverTypeOf(callee.receiver);
|
|
55
|
+
|
|
56
|
+
const isFile = receiverType === "File" && FILE_CALLSITE_METHODS.has(methodLc);
|
|
57
|
+
const isTempBlob = isTempBlobType(receiverType) && TEMPBLOB_CALLSITE_METHODS.has(methodLc);
|
|
58
|
+
if (!isFile && !isTempBlob) continue;
|
|
59
|
+
|
|
60
|
+
// First arg is the filename/path (Create, WriteAllText) or an OutStream
|
|
61
|
+
// (CreateOutStream — no filename semantics). Classify either way;
|
|
62
|
+
// non-string args will resolve to "expression"/"unknown" → unresolved.
|
|
63
|
+
const argInfo = cs.argumentInfos?.[0];
|
|
64
|
+
const argSource: ValueSource =
|
|
65
|
+
argInfo !== undefined ? classifyValueSource(argInfo, ctx) : { kind: "unknown" };
|
|
66
|
+
|
|
67
|
+
facts.push({
|
|
68
|
+
subject: ctx.routine.id,
|
|
69
|
+
op: "write-blob",
|
|
70
|
+
resourceKind: "file",
|
|
71
|
+
resourceArgSource: argSource,
|
|
72
|
+
confidence: confidenceFromSource(argSource),
|
|
73
|
+
provenance: "direct",
|
|
74
|
+
via: "self",
|
|
75
|
+
witnessCallsiteId: cs.id,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Branch B: recordOperations — File.Copy.
|
|
80
|
+
// RECORD_OP_MAP recognises "copy" by name regardless of receiver type,
|
|
81
|
+
// so it surfaces here. fieldArgumentInfos is absent for Copy (not in
|
|
82
|
+
// FIELD_ARGS_OPS), so arg source falls back to unknown → unresolved.
|
|
83
|
+
for (const ro of ctx.routine?.features?.recordOperations ?? []) {
|
|
84
|
+
const recv = ro.recordVariableName;
|
|
85
|
+
if (typeof recv !== "string") continue;
|
|
86
|
+
const receiverType = ctx.receiverTypeOf(recv);
|
|
87
|
+
if (receiverType !== "File") continue;
|
|
88
|
+
if (!FILE_RECORD_OPS.has(ro.op.toLowerCase())) continue;
|
|
89
|
+
|
|
90
|
+
const argInfo = ro.fieldArgumentInfos?.[0];
|
|
91
|
+
const argSource: ValueSource =
|
|
92
|
+
argInfo !== undefined ? classifyValueSource(argInfo, ctx) : { kind: "unknown" };
|
|
93
|
+
|
|
94
|
+
facts.push({
|
|
95
|
+
subject: ctx.routine.id,
|
|
96
|
+
op: "write-blob",
|
|
97
|
+
resourceKind: "file",
|
|
98
|
+
resourceArgSource: argSource,
|
|
99
|
+
confidence: confidenceFromSource(argSource),
|
|
100
|
+
provenance: "direct",
|
|
101
|
+
via: "self",
|
|
102
|
+
witnessOperationId: ro.id,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { facts, reasons: [] };
|
|
107
|
+
} catch {
|
|
108
|
+
return { facts: [], reasons: ["extraction-failed"] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Match the normalised declared-type for TempBlob variables.
|
|
114
|
+
* Probe result: `Codeunit "Temp Blob"` (with double-quotes around the object
|
|
115
|
+
* name, as the variable-symbol normaliser preserves them).
|
|
116
|
+
*/
|
|
117
|
+
function isTempBlobType(t: string): boolean {
|
|
118
|
+
const lc = t.toLowerCase();
|
|
119
|
+
return lc.includes("temp blob") || lc === "tempblob";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function confidenceFromSource(vs: ValueSource): CapabilityConfidence {
|
|
123
|
+
switch (vs.kind) {
|
|
124
|
+
case "literal":
|
|
125
|
+
case "enum":
|
|
126
|
+
return "static";
|
|
127
|
+
case "constant-var":
|
|
128
|
+
return confidenceFromSource(vs.initializer);
|
|
129
|
+
case "parameter":
|
|
130
|
+
return "userDynamic";
|
|
131
|
+
case "table-field":
|
|
132
|
+
return "configDynamic";
|
|
133
|
+
case "expression":
|
|
134
|
+
case "unknown":
|
|
135
|
+
return "unresolved";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CapabilityConfidence,
|
|
3
|
+
CapabilityFact,
|
|
4
|
+
HttpExtra,
|
|
5
|
+
ValueSource,
|
|
6
|
+
} from "../../model/capability.ts";
|
|
7
|
+
import type { CoverageReason } from "../../model/coverage.ts";
|
|
8
|
+
import type { CallsiteId, OperationId } from "../../model/ids.ts";
|
|
9
|
+
import type { ExtractionContext } from "./extractor.ts";
|
|
10
|
+
import { classifyValueSource } from "./value-source.ts";
|
|
11
|
+
|
|
12
|
+
const HTTP_METHOD_SET = new Set<HttpExtra["method"]>([
|
|
13
|
+
"Send",
|
|
14
|
+
"Get",
|
|
15
|
+
"Post",
|
|
16
|
+
"Put",
|
|
17
|
+
"Delete",
|
|
18
|
+
"Patch",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function isHttpMethod(v: string): v is HttpExtra["method"] {
|
|
22
|
+
return HTTP_METHOD_SET.has(v as HttpExtra["method"]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildHttpFact(
|
|
26
|
+
ctx: ExtractionContext,
|
|
27
|
+
method: HttpExtra["method"],
|
|
28
|
+
urlSource: ValueSource,
|
|
29
|
+
bodyArgSource: ValueSource | undefined,
|
|
30
|
+
witness: { kind: "operation"; id: OperationId } | { kind: "callsite"; id: CallsiteId },
|
|
31
|
+
): CapabilityFact {
|
|
32
|
+
const extra: HttpExtra = {
|
|
33
|
+
kind: "http",
|
|
34
|
+
method,
|
|
35
|
+
...(bodyArgSource !== undefined ? { bodyArgSource } : {}),
|
|
36
|
+
};
|
|
37
|
+
const witnessFields =
|
|
38
|
+
witness.kind === "operation"
|
|
39
|
+
? { witnessOperationId: witness.id }
|
|
40
|
+
: { witnessCallsiteId: witness.id };
|
|
41
|
+
return {
|
|
42
|
+
subject: ctx.routine.id,
|
|
43
|
+
op: "send",
|
|
44
|
+
resourceKind: "http",
|
|
45
|
+
resourceArgSource: urlSource,
|
|
46
|
+
confidence: confidenceFromSource(urlSource),
|
|
47
|
+
provenance: "direct",
|
|
48
|
+
via: "self",
|
|
49
|
+
...witnessFields,
|
|
50
|
+
extra,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Phase 0b-β http family extractor. Scans both `recordOperations` and
|
|
56
|
+
* `callSites` for HttpClient method calls.
|
|
57
|
+
*
|
|
58
|
+
* Why both? `intraprocedural-ops.ts` recognizes any `Receiver.Get(...)` /
|
|
59
|
+
* `.Delete(...)` as a RecordOperation regardless of receiver type — so HTTP
|
|
60
|
+
* `.Get` and `.Delete` land in `recordOperations`. The other methods (`.Post`,
|
|
61
|
+
* `.Put`, `.Patch`, `.Send`) surface as member callsites. The receiver-type
|
|
62
|
+
* guard via `ctx.receiverTypeOf(recv) === "HttpClient"` identifies which calls
|
|
63
|
+
* are actually HTTP.
|
|
64
|
+
*
|
|
65
|
+
* URL ValueSource (1st arg for url-taking methods) drives confidence:
|
|
66
|
+
* literal/enum → static, parameter → userDynamic, table-field → configDynamic,
|
|
67
|
+
* expression/unknown → unresolved, constant-var → recurse on initializer.
|
|
68
|
+
*
|
|
69
|
+
* Body ValueSource (2nd arg for url-taking methods, 1st arg for `.Send`)
|
|
70
|
+
* captured in HttpExtra.bodyArgSource for Phase 6 taint propagation.
|
|
71
|
+
*
|
|
72
|
+
* `.Delete` has no fieldArgumentInfos (not in FIELD_ARGS_OPS in
|
|
73
|
+
* intraprocedural-ops.ts) — urlSource falls back to `{ kind: "unknown" }` and
|
|
74
|
+
* confidence to "unresolved". This is intentional: Delete takes the key-filter
|
|
75
|
+
* state already set on the record, not a URL argument.
|
|
76
|
+
*
|
|
77
|
+
* Never throws.
|
|
78
|
+
*/
|
|
79
|
+
export function extractHttp(ctx: ExtractionContext): {
|
|
80
|
+
facts: CapabilityFact[];
|
|
81
|
+
reasons: CoverageReason[];
|
|
82
|
+
} {
|
|
83
|
+
try {
|
|
84
|
+
const facts: CapabilityFact[] = [];
|
|
85
|
+
|
|
86
|
+
// Branch A: HttpClient.Get / .Delete from recordOperations.
|
|
87
|
+
// `intraprocedural-ops.ts` maps `.get` and `.delete` via RECORD_OP_MAP
|
|
88
|
+
// regardless of the receiver's declared type — we filter here.
|
|
89
|
+
for (const ro of ctx.routine?.features?.recordOperations ?? []) {
|
|
90
|
+
const recv = ro.recordVariableName;
|
|
91
|
+
if (typeof recv !== "string") continue;
|
|
92
|
+
if (ctx.receiverTypeOf(recv) !== "HttpClient") continue;
|
|
93
|
+
const method = ro.op;
|
|
94
|
+
if (!isHttpMethod(method)) continue;
|
|
95
|
+
|
|
96
|
+
// Get captures fieldArgumentInfos (in FIELD_ARGS_OPS); Delete does not.
|
|
97
|
+
const urlInfo = ro.fieldArgumentInfos?.[0];
|
|
98
|
+
const urlSource: ValueSource =
|
|
99
|
+
urlInfo !== undefined ? classifyValueSource(urlInfo, ctx) : { kind: "unknown" };
|
|
100
|
+
|
|
101
|
+
const bodyInfo = ro.fieldArgumentInfos?.[1];
|
|
102
|
+
const bodyArgSource: ValueSource | undefined =
|
|
103
|
+
bodyInfo !== undefined ? classifyValueSource(bodyInfo, ctx) : undefined;
|
|
104
|
+
|
|
105
|
+
facts.push(
|
|
106
|
+
buildHttpFact(ctx, method, urlSource, bodyArgSource, { kind: "operation", id: ro.id }),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Branch B: HttpClient.Post / .Put / .Patch / .Send from callSites.
|
|
111
|
+
// These are member callsites (callee.kind === "member") not caught by
|
|
112
|
+
// RECORD_OP_MAP. Callee fields: `receiver` (receiver expr text) and
|
|
113
|
+
// `method` (method name). Both confirmed from the Callee model type.
|
|
114
|
+
for (const cs of ctx.routine?.features?.callSites ?? []) {
|
|
115
|
+
const callee = cs.callee;
|
|
116
|
+
if (!callee || callee.kind !== "member") continue;
|
|
117
|
+
const recv = callee.receiver;
|
|
118
|
+
const member = callee.method;
|
|
119
|
+
if (ctx.receiverTypeOf(recv) !== "HttpClient") continue;
|
|
120
|
+
if (!isHttpMethod(member)) continue;
|
|
121
|
+
|
|
122
|
+
// .Send(Request, Response) — no URL arg; first arg is the request body.
|
|
123
|
+
// .Post/.Put/.Patch(Url, Request, Response) — first arg is URL, second is body.
|
|
124
|
+
const isSend = member === "Send";
|
|
125
|
+
const urlInfo = isSend ? undefined : cs.argumentInfos?.[0];
|
|
126
|
+
const bodyInfo = isSend ? cs.argumentInfos?.[0] : cs.argumentInfos?.[1];
|
|
127
|
+
|
|
128
|
+
const urlSource: ValueSource =
|
|
129
|
+
urlInfo !== undefined ? classifyValueSource(urlInfo, ctx) : { kind: "unknown" };
|
|
130
|
+
const bodyArgSource: ValueSource | undefined =
|
|
131
|
+
bodyInfo !== undefined ? classifyValueSource(bodyInfo, ctx) : undefined;
|
|
132
|
+
|
|
133
|
+
facts.push(
|
|
134
|
+
buildHttpFact(ctx, member, urlSource, bodyArgSource, { kind: "callsite", id: cs.id }),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { facts, reasons: [] };
|
|
139
|
+
} catch {
|
|
140
|
+
return { facts: [], reasons: ["extraction-failed"] };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function confidenceFromSource(vs: ValueSource): CapabilityConfidence {
|
|
145
|
+
switch (vs.kind) {
|
|
146
|
+
case "literal":
|
|
147
|
+
case "enum":
|
|
148
|
+
return "static";
|
|
149
|
+
case "constant-var":
|
|
150
|
+
return confidenceFromSource(vs.initializer);
|
|
151
|
+
case "parameter":
|
|
152
|
+
return "userDynamic";
|
|
153
|
+
case "table-field":
|
|
154
|
+
return "configDynamic";
|
|
155
|
+
case "expression":
|
|
156
|
+
case "unknown":
|
|
157
|
+
return "unresolved";
|
|
158
|
+
}
|
|
159
|
+
}
|