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,74 @@
|
|
|
1
|
+
import type { DependencyPackageRef } from "../deps/dependency-artifact.ts";
|
|
2
|
+
import { buildAppModel } from "../deps/dependency-pipeline.ts";
|
|
3
|
+
import { classifyRoots } from "../engine/root-classifier.ts";
|
|
4
|
+
import type { Diagnostic } from "../model/finding.ts";
|
|
5
|
+
import type { SemanticModel } from "../model/model.ts";
|
|
6
|
+
import { readAppManifest } from "../symbols/app-manifest.ts";
|
|
7
|
+
import { packageHash } from "../symbols/package-hash.ts";
|
|
8
|
+
import { composeSnapshot } from "./compose.ts";
|
|
9
|
+
import type { CapabilitySnapshot } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
/** Build a DependencyPackageRef from a standalone .app file path. Throws on an unreadable
|
|
12
|
+
* or identity-less package (the caller surfaces the message). */
|
|
13
|
+
function refFromAppPath(appPath: string): DependencyPackageRef {
|
|
14
|
+
const manifest = readAppManifest(appPath);
|
|
15
|
+
if (manifest.error !== undefined || manifest.identity.appGuid === "") {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`not a readable .app package: ${appPath}${manifest.error ? ` (${manifest.error})` : ""}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
appPath,
|
|
22
|
+
appGuid: manifest.identity.appGuid,
|
|
23
|
+
publisher: manifest.identity.publisher,
|
|
24
|
+
name: manifest.identity.name,
|
|
25
|
+
version: manifest.identity.version,
|
|
26
|
+
packageHash: packageHash(appPath),
|
|
27
|
+
manifestDependencies: manifest.dependencies,
|
|
28
|
+
includesSource: manifest.includesSource,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Parse a single .app into a PRIMARY SemanticModel: self-contained (no external deps),
|
|
33
|
+
* entities marked primary, roots classified. Cross-app calls into unresolved deps leave
|
|
34
|
+
* capability cones partial (honestly reflected in per-routine coverage). */
|
|
35
|
+
export async function buildPrimaryAppModel(
|
|
36
|
+
appPath: string,
|
|
37
|
+
): Promise<{ model: SemanticModel; diagnostics: Diagnostic[] }> {
|
|
38
|
+
const ref = refFromAppPath(appPath);
|
|
39
|
+
const { model, diagnostics, summaryMode } = await buildAppModel(ref, [], ref.packageHash);
|
|
40
|
+
// Self-contained => every entity is this .app's own; promote dependency->primary.
|
|
41
|
+
for (const a of model.apps) a.analysisRole = "primary";
|
|
42
|
+
for (const o of model.objects) o.analysisRole = "primary";
|
|
43
|
+
for (const r of model.routines) r.analysisRole = "primary";
|
|
44
|
+
// Note: Table has no analysisRole field — tables are accessed through ObjectDecl.
|
|
45
|
+
// The dep path skips root classification; a primary snapshot needs it.
|
|
46
|
+
model.rootClassifications = classifyRoots(model);
|
|
47
|
+
// No silent clean: if the .app is symbol-only / unparseable, behavioral summaries are absent —
|
|
48
|
+
// surface it so a structural-only snapshot is not mistaken for a complete one. A
|
|
49
|
+
// symbol-only package (no embedded source by ResourceExposurePolicy) carries only
|
|
50
|
+
// bodyless ABI routines; `buildAppModel` still labels that `summaryMode: "full"`, so we
|
|
51
|
+
// derive the honest structural-only label from `includesSource` as well.
|
|
52
|
+
const effectiveMode = ref.includesSource ? summaryMode : "structural-only-symbol-package";
|
|
53
|
+
if (effectiveMode !== "full") {
|
|
54
|
+
// [APP###] codes are the .app-as-primary-subject diagnostic series (distinct from [DEP###]).
|
|
55
|
+
diagnostics.push({
|
|
56
|
+
severity: "info",
|
|
57
|
+
stage: "symbol-read",
|
|
58
|
+
message: `[APP001] ${ref.name}: ${effectiveMode} — snapshot carries structural ABI only (no behavioral capability facts)`,
|
|
59
|
+
sourceRef: ref.appGuid,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { model, diagnostics };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Compose a CapabilitySnapshot directly from a single .app file. */
|
|
66
|
+
export async function loadSnapshotFromApp(
|
|
67
|
+
appPath: string,
|
|
68
|
+
alsemVersion: string,
|
|
69
|
+
deterministic: boolean,
|
|
70
|
+
): Promise<{ snapshot: CapabilitySnapshot; diagnostics: Diagnostic[] }> {
|
|
71
|
+
const { model, diagnostics } = await buildPrimaryAppModel(appPath);
|
|
72
|
+
const snapshot = composeSnapshot(model, { workspaceDir: appPath, alsemVersion, deterministic });
|
|
73
|
+
return { snapshot, diagnostics };
|
|
74
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { SemanticModel } from "../model/model.ts";
|
|
2
|
+
import { deriveCallsiteEvidence } from "./derive/callsite-evidence.ts";
|
|
3
|
+
import { deriveCapabilityFacts } from "./derive/capability-facts.ts";
|
|
4
|
+
import { deriveContracts } from "./derive/contracts.ts";
|
|
5
|
+
import { deriveCoverage } from "./derive/coverage.ts";
|
|
6
|
+
import { deriveEventDeclarations } from "./derive/event-declarations.ts";
|
|
7
|
+
import { deriveIdentityTable } from "./derive/identity-table.ts";
|
|
8
|
+
import { deriveInputs } from "./derive/inputs.ts";
|
|
9
|
+
import { deriveOperationEvidence } from "./derive/operation-evidence.ts";
|
|
10
|
+
import { derivePermissions } from "./derive/permissions.ts";
|
|
11
|
+
import { deriveRootClassifications } from "./derive/root-classifications.ts";
|
|
12
|
+
import { deriveSchema } from "./derive/schema.ts";
|
|
13
|
+
import { deriveTypedEdges } from "./derive/typed-edges.ts";
|
|
14
|
+
import { computeWorkspaceFingerprint } from "./derive/workspace-fingerprint.ts";
|
|
15
|
+
import { type AppIdentity, type CapabilitySnapshot, SNAPSHOT_SCHEMA_VERSION } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
export interface ComposeOptions {
|
|
18
|
+
workspaceDir: string;
|
|
19
|
+
alsemVersion: string;
|
|
20
|
+
/** When true, timestamps are pinned to '1970-01-01T00:00:00Z' for
|
|
21
|
+
* byte-stable output. Default false. */
|
|
22
|
+
deterministic?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* True when `analyzeWorkspace` was invoked with `noRootsConfig: true`
|
|
25
|
+
* AND a `roots.config.json` existed on disk that would otherwise have
|
|
26
|
+
* been loaded. Surfaces as
|
|
27
|
+
* `snapshot.inputsMetadata.rootsConfigIgnored = true` so diff tools can
|
|
28
|
+
* distinguish "no config file" from "config existed but ignored".
|
|
29
|
+
*
|
|
30
|
+
* The CALLER is responsible for the disk-existence check; the composer
|
|
31
|
+
* just records the flag verbatim. Pass `false`/omit when the config
|
|
32
|
+
* either didn't exist or was actually loaded.
|
|
33
|
+
*/
|
|
34
|
+
rootsConfigIgnored?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compose a CapabilitySnapshot from a SemanticModel by running every deriver
|
|
39
|
+
* in order. Identity-table comes first; every other deriver receives it as
|
|
40
|
+
* an argument.
|
|
41
|
+
*
|
|
42
|
+
* Engine-never-throws contract: each deriver is pure and total over the
|
|
43
|
+
* model. If a deriver throws unexpectedly, the exception propagates here
|
|
44
|
+
* (we do NOT swallow — composeSnapshot is a CLI/library boundary and
|
|
45
|
+
* silent failure here would mask real bugs).
|
|
46
|
+
*/
|
|
47
|
+
export function composeSnapshot(model: SemanticModel, opts: ComposeOptions): CapabilitySnapshot {
|
|
48
|
+
const identities = deriveIdentityTable(model);
|
|
49
|
+
const inputs = deriveInputs(model, opts.workspaceDir);
|
|
50
|
+
const workspaceFingerprint = computeWorkspaceFingerprint(inputs, opts.alsemVersion);
|
|
51
|
+
|
|
52
|
+
const apps = projectApps(model);
|
|
53
|
+
|
|
54
|
+
const snap: CapabilitySnapshot = {
|
|
55
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
56
|
+
alsemVersion: opts.alsemVersion,
|
|
57
|
+
workspaceFingerprint,
|
|
58
|
+
generatedAt: opts.deterministic ? "1970-01-01T00:00:00Z" : new Date().toISOString(),
|
|
59
|
+
apps,
|
|
60
|
+
identities,
|
|
61
|
+
contractFacts: deriveContracts(model, identities),
|
|
62
|
+
schemaFacts: deriveSchema(model, identities),
|
|
63
|
+
permissionFacts: derivePermissions(model, identities),
|
|
64
|
+
rootClassifications: deriveRootClassifications(model, identities),
|
|
65
|
+
capabilityFacts: deriveCapabilityFacts(model, identities),
|
|
66
|
+
typedEdges: deriveTypedEdges(model, identities),
|
|
67
|
+
operationIndex: deriveOperationEvidence(model, identities),
|
|
68
|
+
callsiteIndex: deriveCallsiteEvidence(model, identities),
|
|
69
|
+
coverage: deriveCoverage(model, identities),
|
|
70
|
+
inputs,
|
|
71
|
+
eventDeclarations: deriveEventDeclarations(model, identities),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Only attach inputsMetadata when there's actually something to record —
|
|
75
|
+
// keeps `inputsMetadata: {}` out of the serialized output for the common
|
|
76
|
+
// "nothing overridden" case.
|
|
77
|
+
if (opts.rootsConfigIgnored === true) {
|
|
78
|
+
snap.inputsMetadata = { rootsConfigIgnored: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return snap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function projectApps(model: SemanticModel): AppIdentity[] {
|
|
85
|
+
const rawApps = (model as unknown as { apps?: Array<Record<string, unknown>> }).apps;
|
|
86
|
+
if (!Array.isArray(rawApps)) return [];
|
|
87
|
+
const out: AppIdentity[] = [];
|
|
88
|
+
for (const a of rawApps) {
|
|
89
|
+
const appGuid = String(a.appGuid ?? a.guid ?? a.id ?? "");
|
|
90
|
+
if (appGuid === "") continue;
|
|
91
|
+
out.push({
|
|
92
|
+
appGuid,
|
|
93
|
+
publisher: String(a.publisher ?? ""),
|
|
94
|
+
name: String(a.name ?? ""),
|
|
95
|
+
version: String(a.version ?? ""),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
out.sort((x, y) => x.appGuid.localeCompare(y.appGuid));
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
2
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
3
|
+
import type { StableRoutineId } from "../../model/stable-identity.ts";
|
|
4
|
+
import type { CallsiteEvidence, SnapshotIdentityTable } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emit CallsiteEvidence for every CallSite referenced as a CapabilityFact
|
|
8
|
+
* witness (direct or inherited) or as the anchor of a typed GraphEdge.
|
|
9
|
+
*
|
|
10
|
+
* Sort by callsiteId.
|
|
11
|
+
*/
|
|
12
|
+
export function deriveCallsiteEvidence(
|
|
13
|
+
model: SemanticModel,
|
|
14
|
+
_idx: SnapshotIdentityTable,
|
|
15
|
+
): CallsiteEvidence[] {
|
|
16
|
+
const idCvt = createIdentityIndex();
|
|
17
|
+
const referenced = new Set<string>();
|
|
18
|
+
|
|
19
|
+
// Collect callsite ids from CapabilityFact witnesses.
|
|
20
|
+
for (const r of model.routines ?? []) {
|
|
21
|
+
const summary = r.summary;
|
|
22
|
+
if (summary === undefined) continue;
|
|
23
|
+
for (const f of [
|
|
24
|
+
...(summary.capabilityFactsDirect ?? []),
|
|
25
|
+
...(summary.capabilityFactsInherited ?? []),
|
|
26
|
+
]) {
|
|
27
|
+
if (f.witnessCallsiteId !== undefined) {
|
|
28
|
+
referenced.add(String(f.witnessCallsiteId));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Collect callsite ids from typed graph edges.
|
|
34
|
+
for (const e of model.typedEdges ?? []) {
|
|
35
|
+
if ("callsiteId" in e && e.callsiteId !== undefined) {
|
|
36
|
+
referenced.add(String(e.callsiteId));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const out: CallsiteEvidence[] = [];
|
|
41
|
+
for (const r of model.routines ?? []) {
|
|
42
|
+
const stableObject = idCvt.toStableObjectId(r.objectId);
|
|
43
|
+
const stableRoutine = idCvt.toStableRoutineIdFromParts(
|
|
44
|
+
stableObject,
|
|
45
|
+
r.canonical?.normalizedSignatureHash ?? "",
|
|
46
|
+
) as StableRoutineId;
|
|
47
|
+
|
|
48
|
+
for (const cs of r.features?.callSites ?? []) {
|
|
49
|
+
if (!referenced.has(String(cs.id))) continue;
|
|
50
|
+
const a = cs.sourceAnchor;
|
|
51
|
+
out.push({
|
|
52
|
+
callsiteId: cs.id,
|
|
53
|
+
routine: stableRoutine,
|
|
54
|
+
sourceFile: a.sourceUnitId,
|
|
55
|
+
startLine: a.range.startLine,
|
|
56
|
+
startColumn: a.range.startColumn,
|
|
57
|
+
endLine: a.range.endLine,
|
|
58
|
+
endColumn: a.range.endColumn,
|
|
59
|
+
calleeDisplay: renderCallee(cs.callee),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out.sort((a, b) => String(a.callsiteId).localeCompare(String(b.callsiteId)));
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderCallee(callee: unknown): string {
|
|
68
|
+
if (callee === null || callee === undefined || typeof callee !== "object") return "?";
|
|
69
|
+
const c = callee as Record<string, unknown>;
|
|
70
|
+
if (c.kind === "bare" && typeof c.name === "string") return c.name;
|
|
71
|
+
if (c.kind === "member" && typeof c.receiver === "string" && typeof c.method === "string") {
|
|
72
|
+
return `${c.receiver}.${c.method}`;
|
|
73
|
+
}
|
|
74
|
+
if (c.kind === "object-run" && typeof c.objectKind === "string") return `${c.objectKind}.Run`;
|
|
75
|
+
return String(c.kind ?? "?");
|
|
76
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/snapshot/derive/capability-facts.ts
|
|
2
|
+
//
|
|
3
|
+
// Flatten every routine's capabilityFactsDirect ∪ capabilityFactsInherited
|
|
4
|
+
// into a single sorted CapabilityFact[] with `subject` rewritten from the
|
|
5
|
+
// internal RoutineId to a StableRoutineId.
|
|
6
|
+
//
|
|
7
|
+
// Sort key: (subject, op, resourceKind, resourceId, confidence, provenance,
|
|
8
|
+
// witnessCallsiteId, witnessOperationId) — drives determinism + round-trip tests.
|
|
9
|
+
|
|
10
|
+
import type { CapabilityFact } from "../../model/capability.ts";
|
|
11
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
12
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
13
|
+
import type { StableRoutineId } from "../../model/stable-identity.ts";
|
|
14
|
+
import type { SnapshotIdentityTable } from "../types.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Flatten every routine's capabilityFactsDirect ∪ capabilityFactsInherited
|
|
18
|
+
* into a single sorted CapabilityFact[] with subject rewritten to
|
|
19
|
+
* StableRoutineId.
|
|
20
|
+
*
|
|
21
|
+
* Sort key: (subject, op, resourceKind, resourceId, confidence, provenance,
|
|
22
|
+
* witnessCallsiteId, witnessOperationId). Used by the determinism + round-trip
|
|
23
|
+
* tests.
|
|
24
|
+
*
|
|
25
|
+
* `idx` is the SnapshotIdentityTable produced earlier; not consulted directly
|
|
26
|
+
* (id rewriting goes through createIdentityIndex), but accepting it keeps
|
|
27
|
+
* the deriver signature uniform with the rest of the family.
|
|
28
|
+
*/
|
|
29
|
+
export function deriveCapabilityFacts(
|
|
30
|
+
model: SemanticModel,
|
|
31
|
+
_idx: SnapshotIdentityTable,
|
|
32
|
+
): CapabilityFact[] {
|
|
33
|
+
const idCvt = createIdentityIndex();
|
|
34
|
+
const all: CapabilityFact[] = [];
|
|
35
|
+
for (const r of model.routines ?? []) {
|
|
36
|
+
const summary = r.summary;
|
|
37
|
+
if (summary === undefined) continue;
|
|
38
|
+
const stableObject = idCvt.toStableObjectId(r.objectId);
|
|
39
|
+
const sigHash = r.canonical.normalizedSignatureHash;
|
|
40
|
+
const stableSubject = idCvt.toStableRoutineIdFromParts(
|
|
41
|
+
stableObject,
|
|
42
|
+
sigHash,
|
|
43
|
+
) as StableRoutineId;
|
|
44
|
+
for (const f of summary.capabilityFactsDirect ?? []) {
|
|
45
|
+
all.push({ ...f, subject: stableSubject as unknown as typeof f.subject });
|
|
46
|
+
}
|
|
47
|
+
for (const f of summary.capabilityFactsInherited ?? []) {
|
|
48
|
+
all.push({ ...f, subject: stableSubject as unknown as typeof f.subject });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
all.sort(canonicalCompare);
|
|
52
|
+
return all;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function canonicalCompare(a: CapabilityFact, b: CapabilityFact): number {
|
|
56
|
+
return factKey(a).localeCompare(factKey(b));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function factKey(f: CapabilityFact): string {
|
|
60
|
+
return [
|
|
61
|
+
String(f.subject ?? ""),
|
|
62
|
+
f.op ?? "",
|
|
63
|
+
f.resourceKind ?? "",
|
|
64
|
+
String(f.resourceId ?? ""),
|
|
65
|
+
f.confidence ?? "",
|
|
66
|
+
f.provenance ?? "",
|
|
67
|
+
String(f.witnessCallsiteId ?? ""),
|
|
68
|
+
String(f.witnessOperationId ?? ""),
|
|
69
|
+
].join("|");
|
|
70
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/snapshot/derive/contracts.ts
|
|
2
|
+
//
|
|
3
|
+
// Project every routine + object in the SemanticModel to a ContractFact.
|
|
4
|
+
//
|
|
5
|
+
// - Routines: signatureFingerprint = SHA-256 of canonicalRoutineSignature
|
|
6
|
+
// (same text form hashed into normalizedSignatureHash at index time, but
|
|
7
|
+
// re-derived here to stay in the snapshot boundary — no dependency on the
|
|
8
|
+
// internal hash plumbing).
|
|
9
|
+
// - Objects: signatureFingerprint = SHA-256("${objectType}|${objectNumber}|${name}"),
|
|
10
|
+
// since objects have no parameter shape.
|
|
11
|
+
// - Attributes are normalised per-attribute to { name, argsHash } where argsHash
|
|
12
|
+
// is SHA-256 of the canonical JSON of the args array.
|
|
13
|
+
//
|
|
14
|
+
// Event-publisher + interface ContractFact kinds are reserved for Phase 1+
|
|
15
|
+
// (where the spec adds dedicated projections); covered today by routine/object kinds.
|
|
16
|
+
//
|
|
17
|
+
// Output is sorted by stableId for determinism.
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { canonicalRoutineSignature } from "../../index/routine-signature.ts";
|
|
21
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
22
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
23
|
+
import type { AttributeFingerprint, ContractFact, SnapshotIdentityTable } from "../types.ts";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Project every routine + object to a ContractFact.
|
|
27
|
+
*
|
|
28
|
+
* Output is sorted by stableId.
|
|
29
|
+
*/
|
|
30
|
+
export function deriveContracts(model: SemanticModel, _idx: SnapshotIdentityTable): ContractFact[] {
|
|
31
|
+
const idCvt = createIdentityIndex();
|
|
32
|
+
const out: ContractFact[] = [];
|
|
33
|
+
|
|
34
|
+
for (const obj of model.objects ?? []) {
|
|
35
|
+
const stableId = idCvt.toStableObjectId(obj.id);
|
|
36
|
+
// Objects don't carry accessModifier in the current model shape — default to "public".
|
|
37
|
+
const objAny = obj as unknown as Record<string, unknown>;
|
|
38
|
+
const visibility = mapVisibility(objAny.accessModifier);
|
|
39
|
+
// Objects don't carry attributesParsed in the current model shape — default to [].
|
|
40
|
+
const rawAttrs = objAny.attributesParsed;
|
|
41
|
+
const attributes = Array.isArray(rawAttrs) ? rawAttrs.map(fingerprintAttribute) : [];
|
|
42
|
+
const obsoleteState = objAny.obsoleteState as "Pending" | "Removed" | "No" | undefined;
|
|
43
|
+
const obsoleteReason = objAny.obsoleteReason as string | undefined;
|
|
44
|
+
|
|
45
|
+
const fact: ContractFact = {
|
|
46
|
+
kind: "object",
|
|
47
|
+
stableId,
|
|
48
|
+
visibility,
|
|
49
|
+
signatureFingerprint: sha256(`${obj.objectType}|${obj.objectNumber}|${obj.name}`),
|
|
50
|
+
attributes,
|
|
51
|
+
};
|
|
52
|
+
if (obsoleteState !== undefined && obsoleteState !== "No") {
|
|
53
|
+
fact.obsoleteState = obsoleteState;
|
|
54
|
+
}
|
|
55
|
+
if (obsoleteReason !== undefined) {
|
|
56
|
+
fact.obsoleteReason = obsoleteReason;
|
|
57
|
+
}
|
|
58
|
+
out.push(fact);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const r of model.routines ?? []) {
|
|
62
|
+
const stableObject = idCvt.toStableObjectId(r.objectId);
|
|
63
|
+
const stableId = idCvt.toStableRoutineIdFromParts(
|
|
64
|
+
stableObject,
|
|
65
|
+
r.canonical.normalizedSignatureHash,
|
|
66
|
+
);
|
|
67
|
+
const visibility = mapVisibility(r.accessModifier);
|
|
68
|
+
const attributes = (r.attributesParsed ?? []).map(fingerprintAttribute);
|
|
69
|
+
const rAny = r as unknown as Record<string, unknown>;
|
|
70
|
+
const obsoleteState = rAny.obsoleteState as "Pending" | "Removed" | "No" | undefined;
|
|
71
|
+
const obsoleteReason = rAny.obsoleteReason as string | undefined;
|
|
72
|
+
|
|
73
|
+
// Re-derive the signature text and hash it — keeps snapshot derivation
|
|
74
|
+
// self-contained and independent of the internal normalizedSignatureHash.
|
|
75
|
+
const sigText = canonicalRoutineSignature(
|
|
76
|
+
r.name,
|
|
77
|
+
r.parameters,
|
|
78
|
+
rAny.returnTypeText as string | undefined,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const fact: ContractFact = {
|
|
82
|
+
kind: "routine",
|
|
83
|
+
stableId,
|
|
84
|
+
visibility,
|
|
85
|
+
signatureFingerprint: sha256(sigText),
|
|
86
|
+
attributes,
|
|
87
|
+
};
|
|
88
|
+
if (obsoleteState !== undefined && obsoleteState !== "No") {
|
|
89
|
+
fact.obsoleteState = obsoleteState;
|
|
90
|
+
}
|
|
91
|
+
if (obsoleteReason !== undefined) {
|
|
92
|
+
fact.obsoleteReason = obsoleteReason;
|
|
93
|
+
}
|
|
94
|
+
out.push(fact);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
out.sort((a, b) => a.stableId.localeCompare(b.stableId));
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function mapVisibility(am: unknown): ContractFact["visibility"] {
|
|
102
|
+
if (typeof am !== "string") return "public";
|
|
103
|
+
const lc = am.toLowerCase();
|
|
104
|
+
if (lc === "public") return "public";
|
|
105
|
+
if (lc === "internal") return "internal";
|
|
106
|
+
if (lc === "protected") return "protected";
|
|
107
|
+
if (lc === "local") return "local";
|
|
108
|
+
return "public";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function fingerprintAttribute(a: {
|
|
112
|
+
name: string;
|
|
113
|
+
args?: unknown[];
|
|
114
|
+
}): AttributeFingerprint {
|
|
115
|
+
return {
|
|
116
|
+
name: a.name,
|
|
117
|
+
argsHash: sha256(canonicalJson(a.args ?? [])),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function canonicalJson(v: unknown): string {
|
|
122
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v);
|
|
123
|
+
if (Array.isArray(v)) return `[${v.map(canonicalJson).join(",")}]`;
|
|
124
|
+
const o = v as Record<string, unknown>;
|
|
125
|
+
const keys = Object.keys(o).sort();
|
|
126
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sha256(s: string): string {
|
|
130
|
+
return createHash("sha256").update(s, "utf8").digest("hex");
|
|
131
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CoverageRecord } from "../../model/coverage.ts";
|
|
2
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
3
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
4
|
+
import type { StableRoutineId } from "../../model/stable-identity.ts";
|
|
5
|
+
import type { SnapshotIdentityTable } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Flatten per-routine summary.coverage to CoverageRecord[] with subject
|
|
9
|
+
* rewritten internal RoutineId → StableRoutineId via routine.canonical
|
|
10
|
+
* .normalizedSignatureHash.
|
|
11
|
+
*
|
|
12
|
+
* Sorted by subject.
|
|
13
|
+
*/
|
|
14
|
+
export function deriveCoverage(
|
|
15
|
+
model: SemanticModel,
|
|
16
|
+
_idx: SnapshotIdentityTable,
|
|
17
|
+
): CoverageRecord[] {
|
|
18
|
+
const idCvt = createIdentityIndex();
|
|
19
|
+
const out: CoverageRecord[] = [];
|
|
20
|
+
for (const r of model.routines ?? []) {
|
|
21
|
+
const cov = r.summary?.coverage;
|
|
22
|
+
if (cov === undefined) continue;
|
|
23
|
+
const stableObject = idCvt.toStableObjectId(r.objectId);
|
|
24
|
+
const stableSubject = idCvt.toStableRoutineIdFromParts(
|
|
25
|
+
stableObject,
|
|
26
|
+
r.canonical?.normalizedSignatureHash ?? "",
|
|
27
|
+
) as StableRoutineId;
|
|
28
|
+
out.push({
|
|
29
|
+
...cov,
|
|
30
|
+
subject: stableSubject as unknown as typeof cov.subject,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
out.sort((a, b) => String(a.subject).localeCompare(String(b.subject)));
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/snapshot/derive/event-declarations.ts
|
|
2
|
+
//
|
|
3
|
+
// Project model.eventGraph into a bipartite EventDeclaration[]:
|
|
4
|
+
//
|
|
5
|
+
// publisher entries — one per EventSymbol (routines with
|
|
6
|
+
// [IntegrationEvent] / [BusinessEvent] / [InternalEvent] attributes),
|
|
7
|
+
// sourced from eventGraph.events.
|
|
8
|
+
//
|
|
9
|
+
// subscriber entries — one per EventEdge (routines with
|
|
10
|
+
// [EventSubscriber(…)] attributes), sourced from eventGraph.edges,
|
|
11
|
+
// carrying a SubscriberBinding = {publisherObject, eventName}.
|
|
12
|
+
//
|
|
13
|
+
// StableEventId format: `${stablePublisherObject}::${eventName}::${signatureHash}`
|
|
14
|
+
// (via IdentityIndex.toStableEventId).
|
|
15
|
+
//
|
|
16
|
+
// Sort key: (kind, routine, eventId).
|
|
17
|
+
|
|
18
|
+
import type { EventEdge, EventSymbol } from "../../model/graph.ts";
|
|
19
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
20
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
21
|
+
import type {
|
|
22
|
+
StableEventId,
|
|
23
|
+
StableObjectId,
|
|
24
|
+
StableRoutineId,
|
|
25
|
+
} from "../../model/stable-identity.ts";
|
|
26
|
+
import type { EventDeclaration, SnapshotIdentityTable, SubscriberBinding } from "../types.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Project model.eventGraph into bipartite EventDeclaration[]:
|
|
30
|
+
* publisher entries — one per routine with [IntegrationEvent] /
|
|
31
|
+
* [BusinessEvent] / [InternalEvent] attribute
|
|
32
|
+
* (from eventGraph.events)
|
|
33
|
+
* subscriber entries — one per routine with [EventSubscriber(…)]
|
|
34
|
+
* attribute (from eventGraph.edges), carrying
|
|
35
|
+
* SubscriberBinding = {publisherObject, eventName}
|
|
36
|
+
*
|
|
37
|
+
* Sorted by (kind, routine, eventId).
|
|
38
|
+
*/
|
|
39
|
+
export function deriveEventDeclarations(
|
|
40
|
+
model: SemanticModel,
|
|
41
|
+
_idx: SnapshotIdentityTable,
|
|
42
|
+
): EventDeclaration[] {
|
|
43
|
+
const idCvt = createIdentityIndex();
|
|
44
|
+
|
|
45
|
+
// Build a RoutineId → StableRoutineId lookup and a RoutineId → sourceAnchor lookup.
|
|
46
|
+
const routineToStable = new Map<string, StableRoutineId>();
|
|
47
|
+
const routineToAnchor = new Map<string, (typeof model.routines)[number]["sourceAnchor"]>();
|
|
48
|
+
|
|
49
|
+
for (const r of model.routines ?? []) {
|
|
50
|
+
const stableObject = idCvt.toStableObjectId(r.objectId);
|
|
51
|
+
const stable = idCvt.toStableRoutineIdFromParts(
|
|
52
|
+
stableObject,
|
|
53
|
+
r.canonical?.normalizedSignatureHash ?? "",
|
|
54
|
+
) as StableRoutineId;
|
|
55
|
+
const rid = r.id as unknown as string;
|
|
56
|
+
routineToStable.set(rid, stable);
|
|
57
|
+
routineToAnchor.set(rid, r.sourceAnchor);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Build an EventId → EventSymbol lookup for subscriber binding resolution.
|
|
61
|
+
const eventById = new Map<string, EventSymbol>();
|
|
62
|
+
for (const evt of model.eventGraph?.events ?? []) {
|
|
63
|
+
eventById.set(evt.id as unknown as string, evt);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const out: EventDeclaration[] = [];
|
|
67
|
+
|
|
68
|
+
// Publisher entries — one per EventSymbol that has a publisherRoutineId.
|
|
69
|
+
for (const evt of model.eventGraph?.events ?? []) {
|
|
70
|
+
const pubRoutineKey = evt.publisherRoutineId as unknown as string | undefined;
|
|
71
|
+
if (!pubRoutineKey) continue;
|
|
72
|
+
|
|
73
|
+
const stableRoutine = routineToStable.get(pubRoutineKey);
|
|
74
|
+
const anchor = routineToAnchor.get(pubRoutineKey);
|
|
75
|
+
if (stableRoutine === undefined || anchor === undefined) continue;
|
|
76
|
+
|
|
77
|
+
const stablePublisherObject = idCvt.toStableObjectId(
|
|
78
|
+
evt.publisherObjectId as unknown as string,
|
|
79
|
+
) as StableObjectId;
|
|
80
|
+
const eventId = idCvt.toStableEventId(
|
|
81
|
+
stablePublisherObject,
|
|
82
|
+
evt.eventName,
|
|
83
|
+
evt.signatureHash,
|
|
84
|
+
) as StableEventId;
|
|
85
|
+
|
|
86
|
+
out.push({
|
|
87
|
+
kind: "publisher",
|
|
88
|
+
routine: stableRoutine,
|
|
89
|
+
eventId,
|
|
90
|
+
sourceAnchor: anchor,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Subscriber entries — one per EventEdge.
|
|
95
|
+
for (const edge of model.eventGraph?.edges ?? []) {
|
|
96
|
+
const subRoutineKey = edge.subscriberRoutineId as unknown as string;
|
|
97
|
+
const stableRoutine = routineToStable.get(subRoutineKey);
|
|
98
|
+
const anchor = routineToAnchor.get(subRoutineKey);
|
|
99
|
+
if (stableRoutine === undefined || anchor === undefined) continue;
|
|
100
|
+
|
|
101
|
+
// Look up the associated EventSymbol to get publisher object + event name.
|
|
102
|
+
const eventKey = edge.eventId as unknown as string;
|
|
103
|
+
const evt = eventById.get(eventKey);
|
|
104
|
+
|
|
105
|
+
const stablePublisherObject = (
|
|
106
|
+
evt
|
|
107
|
+
? idCvt.toStableObjectId(evt.publisherObjectId as unknown as string)
|
|
108
|
+
: (eventKey as string)
|
|
109
|
+
) as StableObjectId;
|
|
110
|
+
|
|
111
|
+
const eventName = evt?.eventName ?? "";
|
|
112
|
+
const signatureHash = evt?.signatureHash ?? "";
|
|
113
|
+
|
|
114
|
+
const eventId = idCvt.toStableEventId(
|
|
115
|
+
stablePublisherObject,
|
|
116
|
+
eventName,
|
|
117
|
+
signatureHash,
|
|
118
|
+
) as StableEventId;
|
|
119
|
+
|
|
120
|
+
const binding: SubscriberBinding = {
|
|
121
|
+
publisherObject: stablePublisherObject,
|
|
122
|
+
eventName,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
out.push({
|
|
126
|
+
kind: "subscriber",
|
|
127
|
+
routine: stableRoutine,
|
|
128
|
+
eventId,
|
|
129
|
+
binding,
|
|
130
|
+
sourceAnchor: anchor,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
out.sort((a, b) =>
|
|
135
|
+
[a.kind, String(a.routine), String(a.eventId)]
|
|
136
|
+
.join("|")
|
|
137
|
+
.localeCompare([b.kind, String(b.routine), String(b.eventId)].join("|")),
|
|
138
|
+
);
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/snapshot/derive/identity-table.ts
|
|
2
|
+
//
|
|
3
|
+
// Walk the SemanticModel and produce a SnapshotIdentityTable — every
|
|
4
|
+
// Stable*Id referenced anywhere in the snapshot paired with a human-readable
|
|
5
|
+
// display name. Output is sorted by stableId and deduped so that
|
|
6
|
+
// (stableIds[i], displayNames[i]) is a 1:1 pairing.
|
|
7
|
+
//
|
|
8
|
+
// Currently covers objects, tables, and routines. Phase 1+ derivers add
|
|
9
|
+
// events, fields, and permission sets via the same map-then-sort pattern.
|
|
10
|
+
//
|
|
11
|
+
// Routine StableId: `${stableObjectId}#${normalizedSignatureHash}` — the
|
|
12
|
+
// hash comes from `routine.canonical.normalizedSignatureHash`, which both the
|
|
13
|
+
// native-AL and .app-symbol paths populate via canonicalRoutineSignature.
|
|
14
|
+
|
|
15
|
+
import type { SemanticModel } from "../../model/model.ts";
|
|
16
|
+
import { createIdentityIndex } from "../../model/stable-identity.ts";
|
|
17
|
+
import type { SnapshotIdentityTable } from "../types.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Walk the model, collect every Stable*Id referenced in the snapshot + pair
|
|
21
|
+
* with a human-readable display name. Output sorted by stableId and deduped —
|
|
22
|
+
* (stableIds[i], displayNames[i]) is a 1:1 pairing.
|
|
23
|
+
*
|
|
24
|
+
* Currently collects:
|
|
25
|
+
* - objects: every entry in model.objects → toStableObjectId
|
|
26
|
+
* - tables: every entry in model.tables → toStableTableId
|
|
27
|
+
* - routines: every entry in model.routines → toStableRoutineIdFromParts(stableObject, sigHash)
|
|
28
|
+
*
|
|
29
|
+
* Phase 1+ derivers add events, fields, permission sets via the same
|
|
30
|
+
* map-then-sort pattern.
|
|
31
|
+
*/
|
|
32
|
+
export function deriveIdentityTable(model: SemanticModel): SnapshotIdentityTable {
|
|
33
|
+
const idx = createIdentityIndex();
|
|
34
|
+
const pairs = new Map<string, string>();
|
|
35
|
+
|
|
36
|
+
for (const obj of model.objects ?? []) {
|
|
37
|
+
const stable = idx.toStableObjectId(obj.id);
|
|
38
|
+
pairs.set(stable, obj.name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const tbl of model.tables ?? []) {
|
|
42
|
+
const stable = idx.toStableTableId(tbl.id);
|
|
43
|
+
pairs.set(stable, tbl.name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const r of model.routines ?? []) {
|
|
47
|
+
const stableObject = idx.toStableObjectId(r.objectId);
|
|
48
|
+
// normalizedSignatureHash is populated on both native-AL and .app-symbol
|
|
49
|
+
// paths by canonicalRoutineSignature — use it directly.
|
|
50
|
+
const sigHash = r.canonical.normalizedSignatureHash;
|
|
51
|
+
const stable = idx.toStableRoutineIdFromParts(stableObject, sigHash);
|
|
52
|
+
pairs.set(stable, r.name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const stableIds = Array.from(pairs.keys()).sort();
|
|
56
|
+
const displayNames = stableIds.map((id) => pairs.get(id) ?? "");
|
|
57
|
+
return { stableIds, displayNames };
|
|
58
|
+
}
|