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,154 @@
|
|
|
1
|
+
import type { Diagnostic } from "../model/finding.ts";
|
|
2
|
+
import { profilingActive, recordPhase } from "../perf/profiler.ts";
|
|
3
|
+
import type { ManifestDependency } from "../symbols/app-manifest.ts";
|
|
4
|
+
import { packageSemanticHash } from "../symbols/package-hash.ts";
|
|
5
|
+
import type { DependencyArtifact, DirectDependencyRef } from "./dependency-artifact.ts";
|
|
6
|
+
import {
|
|
7
|
+
computeArtifactKey,
|
|
8
|
+
readCachedArtifact,
|
|
9
|
+
resolveCacheDir,
|
|
10
|
+
writeCachedArtifact,
|
|
11
|
+
} from "./dependency-cache.ts";
|
|
12
|
+
import { resolveDependencyDag } from "./dependency-dag.ts";
|
|
13
|
+
import { discoverDependencyPackages } from "./dependency-package-discovery.ts";
|
|
14
|
+
import { ingestDependencyApp } from "./dependency-pipeline.ts";
|
|
15
|
+
|
|
16
|
+
export interface ResolveDependencyArtifactsOptions {
|
|
17
|
+
/** Cache directory override. Default: ~/.al-sem/cache/. */
|
|
18
|
+
cacheDir?: string;
|
|
19
|
+
/** Skip the behavioral cold run — structural ABI only, conservative effects. */
|
|
20
|
+
noDepSummaries?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResolveDependencyArtifactsResult {
|
|
24
|
+
/** Artifacts in dependency order (a dependency precedes its dependents). */
|
|
25
|
+
artifacts: DependencyArtifact[];
|
|
26
|
+
diagnostics: Diagnostic[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The L1.5 dependency stage. Discover → resolve DAG → per package in topo order: compute the
|
|
31
|
+
* artifactKey from packageSemanticHash + resolved direct-dependency keys, then cache-hit or
|
|
32
|
+
* cold-run+write. Never throws — every failure is a diagnostic. Cold-run progress is written
|
|
33
|
+
* to stderr (operational), never to `Diagnostic[]` (which must be cache-presence-independent).
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveDependencyArtifacts(
|
|
36
|
+
alpackagesDir: string,
|
|
37
|
+
workspaceDependencies: ManifestDependency[],
|
|
38
|
+
options: ResolveDependencyArtifactsOptions,
|
|
39
|
+
): Promise<ResolveDependencyArtifactsResult> {
|
|
40
|
+
const diagnostics: Diagnostic[] = [];
|
|
41
|
+
const cacheDir = resolveCacheDir(options.cacheDir);
|
|
42
|
+
|
|
43
|
+
const discovered = discoverDependencyPackages(alpackagesDir);
|
|
44
|
+
diagnostics.push(...discovered.diagnostics);
|
|
45
|
+
|
|
46
|
+
const dag = resolveDependencyDag(discovered.packages, workspaceDependencies);
|
|
47
|
+
diagnostics.push(...dag.diagnostics);
|
|
48
|
+
|
|
49
|
+
if (options.noDepSummaries) {
|
|
50
|
+
diagnostics.push({
|
|
51
|
+
severity: "info",
|
|
52
|
+
stage: "symbol-read",
|
|
53
|
+
message:
|
|
54
|
+
"[DEP022] dependency effect summaries skipped (--no-dep-summaries) — calls into dependencies treated as conservative",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const artifacts: DependencyArtifact[] = [];
|
|
59
|
+
const keyByGuid = new Map<string, string>();
|
|
60
|
+
const artifactByGuid = new Map<string, DependencyArtifact>();
|
|
61
|
+
|
|
62
|
+
for (const pkg of dag.ordered) {
|
|
63
|
+
// direct-dependency refs, resolved from already-processed lower artifacts
|
|
64
|
+
const directDependencies: DirectDependencyRef[] = [];
|
|
65
|
+
const lowerArtifacts: DependencyArtifact[] = [];
|
|
66
|
+
for (const md of pkg.manifestDependencies) {
|
|
67
|
+
const depKey = keyByGuid.get(md.appGuid);
|
|
68
|
+
const depArtifact = artifactByGuid.get(md.appGuid);
|
|
69
|
+
if (depKey === undefined || depArtifact === undefined) continue; // dropped by DAG resolution
|
|
70
|
+
directDependencies.push({
|
|
71
|
+
appGuid: depArtifact.header.appIdentity.appGuid,
|
|
72
|
+
publisher: depArtifact.header.appIdentity.publisher,
|
|
73
|
+
name: depArtifact.header.appIdentity.name,
|
|
74
|
+
version: depArtifact.header.appIdentity.version,
|
|
75
|
+
artifactKey: depKey,
|
|
76
|
+
});
|
|
77
|
+
lowerArtifacts.push(depArtifact);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let semanticHash: string;
|
|
81
|
+
try {
|
|
82
|
+
semanticHash = packageSemanticHash(pkg.appPath);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
diagnostics.push({
|
|
85
|
+
severity: "warning",
|
|
86
|
+
stage: "symbol-read",
|
|
87
|
+
message: `[DEP002] ${pkg.name}: could not compute semantic hash: ${(err as Error).message}`,
|
|
88
|
+
sourceRef: pkg.appPath,
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const artifactMode = options.noDepSummaries ? "structural-only" : "full";
|
|
93
|
+
const artifactKey = computeArtifactKey(semanticHash, directDependencies, artifactMode);
|
|
94
|
+
|
|
95
|
+
// Cache is now namespaced by mode (full vs structural-only), so a `--no-dep-summaries`
|
|
96
|
+
// run hits its own cache and a flag-flipped run never picks up the wrong shape.
|
|
97
|
+
let artifact: DependencyArtifact | null = readCachedArtifact(artifactKey, cacheDir);
|
|
98
|
+
|
|
99
|
+
if (artifact === null) {
|
|
100
|
+
// cold run
|
|
101
|
+
const coldMessage = options.noDepSummaries
|
|
102
|
+
? `al-sem: building dependency cache for ${pkg.name} ${pkg.version} (one-time, structural-only)\n`
|
|
103
|
+
: `al-sem: building dependency cache for ${pkg.name} ${pkg.version} (one-time)\n`;
|
|
104
|
+
process.stderr.write(coldMessage);
|
|
105
|
+
const _tIngest = profilingActive() ? performance.now() : 0;
|
|
106
|
+
try {
|
|
107
|
+
artifact = await ingestDependencyApp(pkg, lowerArtifacts, artifactKey, {
|
|
108
|
+
structuralOnly: options.noDepSummaries ?? false,
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
severity: "warning",
|
|
113
|
+
stage: "symbol-read",
|
|
114
|
+
message: `[DEP030] ${pkg.name}: dependency ingestion failed, app left opaque: ${(err as Error).message}`,
|
|
115
|
+
sourceRef: pkg.appPath,
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// fill orchestrator-owned header fields
|
|
120
|
+
artifact.header.packageSemanticHash = semanticHash;
|
|
121
|
+
artifact.header.directDependencies = directDependencies;
|
|
122
|
+
if (artifact.header.summaryMode === "structural-only-parser-unavailable") {
|
|
123
|
+
// Don't persist a parser-unavailable degradation — it's an environmental
|
|
124
|
+
// failure (missing native lib), not a property of the dep package. Caching
|
|
125
|
+
// it would pin the degradation across runs even after the user reinstalls.
|
|
126
|
+
} else {
|
|
127
|
+
if (profilingActive())
|
|
128
|
+
recordPhase(`ingest:${pkg.name}:ingest-total`, performance.now() - _tIngest);
|
|
129
|
+
const _tWrite = profilingActive() ? performance.now() : 0;
|
|
130
|
+
try {
|
|
131
|
+
// writeCachedArtifact returns the canonical in-memory form (parse of the exact
|
|
132
|
+
// bytes a future cache hit reads), so cold-write output matches a warm hit
|
|
133
|
+
// without re-reading + re-validating the just-written multi-hundred-MB file.
|
|
134
|
+
artifact = writeCachedArtifact(artifact, cacheDir);
|
|
135
|
+
if (profilingActive())
|
|
136
|
+
recordPhase(`ingest:${pkg.name}:write`, performance.now() - _tWrite);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
process.stderr.write(
|
|
139
|
+
`al-sem: could not persist dependency cache for ${pkg.name}: ${(err as Error).message}\n`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// replay the artifact's own degradation diagnostics (skip behavioral ones in no-summaries mode)
|
|
146
|
+
if (!options.noDepSummaries) diagnostics.push(...artifact.diagnostics);
|
|
147
|
+
|
|
148
|
+
artifacts.push(artifact);
|
|
149
|
+
keyByGuid.set(pkg.appGuid, artifactKey);
|
|
150
|
+
artifactByGuid.set(pkg.appGuid, artifact);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { artifacts, diagnostics };
|
|
154
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ManifestDependency } from "../symbols/app-manifest.ts";
|
|
2
|
+
|
|
3
|
+
interface AppJsonDependency {
|
|
4
|
+
id?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
publisher?: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AppJsonInternalsVisibleEntry {
|
|
11
|
+
id?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
publisher?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AppJsonFull {
|
|
17
|
+
dependencies?: AppJsonDependency[];
|
|
18
|
+
/** Microsoft Application tier minimum (workspace's `application: "X.Y.Z.W"`). */
|
|
19
|
+
application?: string;
|
|
20
|
+
/** Microsoft Platform tier minimum (workspace's `platform: "X.Y.Z.W"`). */
|
|
21
|
+
platform?: string;
|
|
22
|
+
/** AL `internalsVisibleTo` — apps allowed to call this app's `internal` procedures. */
|
|
23
|
+
internalsVisibleTo?: AppJsonInternalsVisibleEntry[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Well-known stable Microsoft platform app identities. BC `.app`s never list these in
|
|
28
|
+
* `<Dependencies>` — they are implicit, declared via `application` / `platform` in app.json.
|
|
29
|
+
* The compiler treats them as required platform-tier dependencies; al-sem mirrors that so
|
|
30
|
+
* symbols like `Customer`, `Sales Header`, etc. resolve through the dependency stage.
|
|
31
|
+
*/
|
|
32
|
+
const MS_APPLICATION_TIER: readonly { appGuid: string; name: string; publisher: string }[] = [
|
|
33
|
+
{ appGuid: "c1335042-3002-4257-bf8a-75c898ccb1b8", name: "Application", publisher: "Microsoft" },
|
|
34
|
+
{
|
|
35
|
+
appGuid: "437dbf0e-84ff-417a-965d-ed2bb9650972",
|
|
36
|
+
name: "Base Application",
|
|
37
|
+
publisher: "Microsoft",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
appGuid: "f3552374-a1f2-4356-848e-196002525837",
|
|
41
|
+
name: "Business Foundation",
|
|
42
|
+
publisher: "Microsoft",
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const MS_PLATFORM_TIER: readonly { appGuid: string; name: string; publisher: string }[] = [
|
|
47
|
+
{
|
|
48
|
+
appGuid: "63ca2fa4-4f03-4f2b-a480-172fef340d3f",
|
|
49
|
+
name: "System Application",
|
|
50
|
+
publisher: "Microsoft",
|
|
51
|
+
},
|
|
52
|
+
{ appGuid: "8874ed3a-0643-4247-9ced-7a7002f7135d", name: "System", publisher: "Microsoft" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse a workspace app.json's dependency declarations into ManifestDependency[]. Includes
|
|
57
|
+
* both:
|
|
58
|
+
* - **explicit** `dependencies[]` entries (other extensions the workspace depends on);
|
|
59
|
+
* - **implicit** Microsoft platform-tier deps derived from the `application` / `platform`
|
|
60
|
+
* fields (BC compilation convention — these are never listed in `dependencies`).
|
|
61
|
+
*
|
|
62
|
+
* Implicit deps are tagged with well-known Microsoft appGuids; the DAG resolver matches by
|
|
63
|
+
* appGuid, so the Microsoft `.app`s present in `.alpackages` are selected automatically.
|
|
64
|
+
* Never throws.
|
|
65
|
+
*/
|
|
66
|
+
export function parseWorkspaceDependencies(appJsonText: string): ManifestDependency[] {
|
|
67
|
+
let parsed: AppJsonFull;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(appJsonText) as AppJsonFull;
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const explicit: ManifestDependency[] = (parsed.dependencies ?? []).map((d) => ({
|
|
75
|
+
appGuid: d.id ?? "",
|
|
76
|
+
name: d.name ?? "",
|
|
77
|
+
publisher: d.publisher ?? "",
|
|
78
|
+
minVersion: d.version ?? "0.0.0.0",
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const implicit: ManifestDependency[] = [];
|
|
82
|
+
if (typeof parsed.application === "string" && parsed.application !== "") {
|
|
83
|
+
for (const a of MS_APPLICATION_TIER) implicit.push({ ...a, minVersion: parsed.application });
|
|
84
|
+
}
|
|
85
|
+
if (typeof parsed.platform === "string" && parsed.platform !== "") {
|
|
86
|
+
for (const a of MS_PLATFORM_TIER) implicit.push({ ...a, minVersion: parsed.platform });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [...explicit, ...implicit];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse the workspace app.json's `internalsVisibleTo` array — the AL feature that names
|
|
94
|
+
* apps allowed to call this app's `internal` procedures. Returns the lowercased app GUIDs
|
|
95
|
+
* (entries are deduplicated; malformed/missing entries are skipped). Empty array means
|
|
96
|
+
* no apps are granted internal access → `internal` procedures are effectively app-scoped
|
|
97
|
+
* and the dead-routine detector treats them like `local procedure` for reachability.
|
|
98
|
+
*
|
|
99
|
+
* Never throws.
|
|
100
|
+
*/
|
|
101
|
+
export function parseWorkspaceInternalsVisibleTo(appJsonText: string): string[] {
|
|
102
|
+
let parsed: AppJsonFull;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(appJsonText) as AppJsonFull;
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const guids = new Set<string>();
|
|
109
|
+
for (const e of parsed.internalsVisibleTo ?? []) {
|
|
110
|
+
const id = typeof e.id === "string" ? e.id.trim().toLowerCase() : "";
|
|
111
|
+
if (id !== "") guids.add(id);
|
|
112
|
+
}
|
|
113
|
+
return [...guids].sort();
|
|
114
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { CapabilityFact, CapabilityOp, CapabilityResourceKind } from "../model/capability.ts";
|
|
2
|
+
import type { CoverageStatus } from "../model/coverage.ts";
|
|
3
|
+
import type { EventId, TableId } from "../model/ids.ts";
|
|
4
|
+
import type { EffectPresence, RoutineSummary } from "../model/summary.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Phase 1a capability-query helpers — pure functions over RoutineSummary.
|
|
8
|
+
*
|
|
9
|
+
* Every helper reads `capabilityFactsDirect ∪ capabilityFactsInherited` and
|
|
10
|
+
* returns a derived view. Helpers honour G6 coverage semantics: when a fact
|
|
11
|
+
* is absent and the cone is not "complete", tri-state helpers return
|
|
12
|
+
* "unknown" rather than "no".
|
|
13
|
+
*
|
|
14
|
+
* These helpers do NOT (in Phase 1a) replace the legacy boolean lattice
|
|
15
|
+
* (touchesDb / commits / writesTables / publishesEvents); they sit ALONGSIDE
|
|
16
|
+
* it, with a parity test (Phase 1a Task 7) asserting mechanical equivalence.
|
|
17
|
+
* Phase 1b migrates detectors over per-domain; Phase 1c deletes the legacy
|
|
18
|
+
* fields.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function reachable(s: RoutineSummary): readonly CapabilityFact[] {
|
|
22
|
+
const direct = s.capabilityFactsDirect ?? [];
|
|
23
|
+
const inherited = s.capabilityFactsInherited ?? [];
|
|
24
|
+
if (direct.length === 0 && inherited.length === 0) return [];
|
|
25
|
+
return [...direct, ...inherited];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filter reachable facts (direct + inherited) by an arbitrary predicate.
|
|
30
|
+
* Returns a fresh array; never mutates the summary.
|
|
31
|
+
*/
|
|
32
|
+
export function findCapabilities(
|
|
33
|
+
s: RoutineSummary,
|
|
34
|
+
predicate: (f: CapabilityFact) => boolean,
|
|
35
|
+
): CapabilityFact[] {
|
|
36
|
+
return reachable(s).filter(predicate);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when at least one reachable fact has the given (op, resourceKind).
|
|
41
|
+
* Strict discrimination — no fuzzy/substring matching.
|
|
42
|
+
*/
|
|
43
|
+
export function hasCapability(
|
|
44
|
+
s: RoutineSummary,
|
|
45
|
+
op: CapabilityOp,
|
|
46
|
+
kind: CapabilityResourceKind,
|
|
47
|
+
): boolean {
|
|
48
|
+
for (const f of reachable(s)) {
|
|
49
|
+
if (f.op === op && f.resourceKind === kind) return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TABLE_WRITE_OPS = new Set<CapabilityOp>(["insert", "modify", "delete"]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sorted + deduped TableIds targeted by any reachable insert/modify/delete
|
|
58
|
+
* fact on `resourceKind === "table"` with a known `resourceId`. Facts whose
|
|
59
|
+
* table identity is unresolved (no `resourceId`) are dropped — callers that
|
|
60
|
+
* need to know whether the cone is partial should consult `reachableCoverage`
|
|
61
|
+
* separately.
|
|
62
|
+
*
|
|
63
|
+
* Read facts are NOT included. Phase 4 framework detectors that care about
|
|
64
|
+
* full read+write surface read `findCapabilities` directly.
|
|
65
|
+
*/
|
|
66
|
+
export function writesTablesOf(s: RoutineSummary): TableId[] {
|
|
67
|
+
const ids = new Set<string>();
|
|
68
|
+
for (const f of reachable(s)) {
|
|
69
|
+
if (f.resourceKind !== "table") continue;
|
|
70
|
+
if (!TABLE_WRITE_OPS.has(f.op)) continue;
|
|
71
|
+
if (typeof f.resourceId !== "string") continue;
|
|
72
|
+
ids.add(f.resourceId);
|
|
73
|
+
}
|
|
74
|
+
return Array.from(ids).sort() as TableId[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns "yes" when any reachable fact is a commit on the transaction
|
|
79
|
+
* resource; "no" when no commit fact AND the inherited cone is "complete";
|
|
80
|
+
* "unknown" otherwise (G6 honesty — absence of evidence is not evidence of
|
|
81
|
+
* absence when the cone is partial or coverage data is missing).
|
|
82
|
+
*/
|
|
83
|
+
export function mayCommit(s: RoutineSummary): EffectPresence {
|
|
84
|
+
for (const f of reachable(s)) {
|
|
85
|
+
if (f.op === "commit" && f.resourceKind === "transaction") return "yes";
|
|
86
|
+
}
|
|
87
|
+
const status = s.coverage?.inheritedStatus ?? "unknown";
|
|
88
|
+
return status === "complete" ? "no" : "unknown";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns "yes" when any reachable fact has resourceKind === "table"
|
|
93
|
+
* (regardless of op — read or write); "no" when no such fact AND the
|
|
94
|
+
* inherited cone is "complete"; "unknown" otherwise.
|
|
95
|
+
*
|
|
96
|
+
* Note: state-only ops (SetRange, SetFilter, Init) are NOT in the
|
|
97
|
+
* capability fact stream per spec §3.1.1 substrate discipline — they
|
|
98
|
+
* stay on RecordOperation. So touchesDbOf reflects only "did the routine
|
|
99
|
+
* actually read or write a row," not "did it touch table state."
|
|
100
|
+
*/
|
|
101
|
+
export function touchesDbOf(s: RoutineSummary): EffectPresence {
|
|
102
|
+
for (const f of reachable(s)) {
|
|
103
|
+
if (f.resourceKind === "table") return "yes";
|
|
104
|
+
}
|
|
105
|
+
const status = s.coverage?.inheritedStatus ?? "unknown";
|
|
106
|
+
return status === "complete" ? "no" : "unknown";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sorted + deduped EventIds (as plain strings — matches legacy
|
|
111
|
+
* RoutineSummary.publishesEvents shape) from reachable op="publish" facts
|
|
112
|
+
* on resourceKind="event" with a known resourceId. Facts whose event
|
|
113
|
+
* identity is unresolved are dropped.
|
|
114
|
+
*/
|
|
115
|
+
export function publishesEventsOf(s: RoutineSummary): string[] {
|
|
116
|
+
const ids = new Set<string>();
|
|
117
|
+
for (const f of reachable(s)) {
|
|
118
|
+
if (f.op !== "publish") continue;
|
|
119
|
+
if (f.resourceKind !== "event") continue;
|
|
120
|
+
if (typeof f.resourceId !== "string") continue;
|
|
121
|
+
ids.add(f.resourceId);
|
|
122
|
+
}
|
|
123
|
+
return Array.from(ids).sort();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns the routine's inherited coverage status, optionally narrowed to
|
|
128
|
+
* a single resourceKind.
|
|
129
|
+
*
|
|
130
|
+
* Phase 1a: the per-routine `coverage.inheritedStatus` is the only roll-up
|
|
131
|
+
* the composer maintains — there is no per-family status table yet. So when
|
|
132
|
+
* a `kind` is supplied, the function returns the same overall status,
|
|
133
|
+
* NOT a kind-specific narrowing. Per-family roll-up arrives with the
|
|
134
|
+
* fingerprint dump CLI's coverage rendering (§4.4 / spec §3.7
|
|
135
|
+
* FingerprintCoverage.perFamily) once that field lands.
|
|
136
|
+
*
|
|
137
|
+
* Returns "unknown" when the summary has no coverage record at all
|
|
138
|
+
* (pre-Phase-0b routines).
|
|
139
|
+
*/
|
|
140
|
+
export function reachableCoverage(
|
|
141
|
+
s: RoutineSummary,
|
|
142
|
+
_kind?: CapabilityResourceKind,
|
|
143
|
+
): CoverageStatus {
|
|
144
|
+
return s.coverage?.inheritedStatus ?? "unknown";
|
|
145
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FindingConfidence } from "../model/finding.ts";
|
|
2
|
+
import type { Uncertainty } from "../model/summary.ts";
|
|
3
|
+
|
|
4
|
+
type CappedByKind = NonNullable<FindingConfidence["cappedBy"]>[number];
|
|
5
|
+
|
|
6
|
+
// The Uncertainty kinds that are also valid FindingConfidence.cappedBy values.
|
|
7
|
+
const VALID_CAPPED_BY = new Set<CappedByKind>([
|
|
8
|
+
"unresolved-call",
|
|
9
|
+
"opaque-callee",
|
|
10
|
+
"dynamic-dispatch",
|
|
11
|
+
"parse-incomplete",
|
|
12
|
+
"version-mismatch",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function describe(u: Uncertainty): string {
|
|
16
|
+
if ("callsiteId" in u) return `${u.kind} at ${u.callsiteId}`;
|
|
17
|
+
if ("operationId" in u) return `${u.kind} at ${u.operationId}`;
|
|
18
|
+
return `${u.kind} at ${u.routineId}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isCappedByKind(kind: string): kind is CappedByKind {
|
|
22
|
+
return (VALID_CAPPED_BY as ReadonlySet<string>).has(kind);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Map a list of uncertainties to a FindingConfidence. Any uncertainty caps `level` at
|
|
27
|
+
* `possible`. Uncertainty kinds that are valid `cappedBy` values are listed there; the
|
|
28
|
+
* others (`interface-dispatch`, `recordref-or-variant`) still cap the level but are recorded
|
|
29
|
+
* only in `evidence` — never as an invalid `cappedBy` string. `baseLevel` is never raised.
|
|
30
|
+
*/
|
|
31
|
+
export function toConfidence(
|
|
32
|
+
uncertainties: Uncertainty[],
|
|
33
|
+
baseLevel: FindingConfidence["level"],
|
|
34
|
+
): FindingConfidence {
|
|
35
|
+
if (uncertainties.length === 0) {
|
|
36
|
+
return { level: baseLevel, evidence: [] };
|
|
37
|
+
}
|
|
38
|
+
const cappedBySet = new Set(uncertainties.map((u) => u.kind).filter(isCappedByKind));
|
|
39
|
+
const cappedBy = cappedBySet.size > 0 ? ([...cappedBySet].sort() as CappedByKind[]) : undefined;
|
|
40
|
+
const evidence = uncertainties.map((u) => ({
|
|
41
|
+
source: "tree-sitter" as const,
|
|
42
|
+
note: describe(u),
|
|
43
|
+
}));
|
|
44
|
+
const result: FindingConfidence = {
|
|
45
|
+
level: "possible",
|
|
46
|
+
evidence,
|
|
47
|
+
};
|
|
48
|
+
if (cappedBy) {
|
|
49
|
+
result.cappedBy = cappedBy;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|