@neat.is/core 0.3.6 → 0.3.8
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/dist/{chunk-4ASCXBZF.js → chunk-7TYESDAI.js} +101 -5
- package/dist/chunk-7TYESDAI.js.map +1 -0
- package/dist/{chunk-ZU2RQRCN.js → chunk-CZ3T6TE2.js} +251 -245
- package/dist/{chunk-ZU2RQRCN.js.map → chunk-CZ3T6TE2.js.map} +1 -1
- package/dist/{chunk-YHQYHFI3.js → chunk-LQ3JFBTX.js} +99 -17
- package/dist/chunk-LQ3JFBTX.js.map +1 -0
- package/dist/{chunk-G3PDTGOW.js → chunk-V4TU7OKZ.js} +16 -2
- package/dist/chunk-V4TU7OKZ.js.map +1 -0
- package/dist/cli.cjs +1264 -396
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +935 -153
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +215 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +228 -19
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +14 -4
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-J4O2SIBZ.js → otel-grpc-S3AENOZ6.js} +3 -3
- package/dist/server.cjs +143 -10
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +25 -7
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-4ASCXBZF.js.map +0 -1
- package/dist/chunk-G3PDTGOW.js.map +0 -1
- package/dist/chunk-YHQYHFI3.js.map +0 -1
- /package/dist/{otel-grpc-J4O2SIBZ.js.map → otel-grpc-S3AENOZ6.js.map} +0 -0
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mountBearerAuth
|
|
3
|
+
} from "./chunk-7TYESDAI.js";
|
|
4
|
+
|
|
1
5
|
// src/graph.ts
|
|
2
6
|
import GraphDefault from "graphology";
|
|
3
7
|
var MultiDirectedGraph = GraphDefault.MultiDirectedGraph;
|
|
@@ -3759,10 +3763,252 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3759
3763
|
return result;
|
|
3760
3764
|
}
|
|
3761
3765
|
|
|
3766
|
+
// src/divergences.ts
|
|
3767
|
+
import {
|
|
3768
|
+
DivergenceResultSchema,
|
|
3769
|
+
EdgeType as EdgeType9,
|
|
3770
|
+
NodeType as NodeType10,
|
|
3771
|
+
parseEdgeId,
|
|
3772
|
+
Provenance as Provenance9
|
|
3773
|
+
} from "@neat.is/types";
|
|
3774
|
+
function bucketKey(source, target, type) {
|
|
3775
|
+
return `${type}|${source}|${target}`;
|
|
3776
|
+
}
|
|
3777
|
+
function bucketEdges(graph) {
|
|
3778
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
3779
|
+
graph.forEachEdge((id, attrs) => {
|
|
3780
|
+
const e = attrs;
|
|
3781
|
+
const parsed = parseEdgeId(id);
|
|
3782
|
+
const provenance = parsed?.provenance ?? e.provenance;
|
|
3783
|
+
const key = bucketKey(e.source, e.target, e.type);
|
|
3784
|
+
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3785
|
+
switch (provenance) {
|
|
3786
|
+
case Provenance9.EXTRACTED:
|
|
3787
|
+
cur.extracted = e;
|
|
3788
|
+
break;
|
|
3789
|
+
case Provenance9.OBSERVED:
|
|
3790
|
+
cur.observed = e;
|
|
3791
|
+
break;
|
|
3792
|
+
case Provenance9.INFERRED:
|
|
3793
|
+
cur.inferred = e;
|
|
3794
|
+
break;
|
|
3795
|
+
default:
|
|
3796
|
+
if (e.provenance === Provenance9.STALE) cur.stale = e;
|
|
3797
|
+
}
|
|
3798
|
+
buckets.set(key, cur);
|
|
3799
|
+
});
|
|
3800
|
+
return buckets;
|
|
3801
|
+
}
|
|
3802
|
+
function nodeIsFrontier(graph, nodeId) {
|
|
3803
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
3804
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
3805
|
+
return attrs.type === NodeType10.FrontierNode;
|
|
3806
|
+
}
|
|
3807
|
+
function clampConfidence(n) {
|
|
3808
|
+
if (!Number.isFinite(n)) return 0;
|
|
3809
|
+
return Math.max(0, Math.min(1, n));
|
|
3810
|
+
}
|
|
3811
|
+
function reasonForMissingObserved(source, target, type) {
|
|
3812
|
+
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
3813
|
+
}
|
|
3814
|
+
function reasonForMissingExtracted(source, target, type) {
|
|
3815
|
+
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
3816
|
+
}
|
|
3817
|
+
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
3818
|
+
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
3819
|
+
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
3820
|
+
function gradedConfidence(edge) {
|
|
3821
|
+
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
3822
|
+
return clampConfidence(confidenceForEdge(edge));
|
|
3823
|
+
}
|
|
3824
|
+
function detectMissingDivergences(graph, bucket) {
|
|
3825
|
+
const out = [];
|
|
3826
|
+
if (bucket.extracted && !bucket.observed) {
|
|
3827
|
+
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
3828
|
+
out.push({
|
|
3829
|
+
type: "missing-observed",
|
|
3830
|
+
source: bucket.source,
|
|
3831
|
+
target: bucket.target,
|
|
3832
|
+
edgeType: bucket.type,
|
|
3833
|
+
extracted: bucket.extracted,
|
|
3834
|
+
confidence: gradedConfidence(bucket.extracted),
|
|
3835
|
+
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
3836
|
+
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
if (bucket.observed && !bucket.extracted) {
|
|
3841
|
+
out.push({
|
|
3842
|
+
type: "missing-extracted",
|
|
3843
|
+
source: bucket.source,
|
|
3844
|
+
target: bucket.target,
|
|
3845
|
+
edgeType: bucket.type,
|
|
3846
|
+
observed: bucket.observed,
|
|
3847
|
+
confidence: gradedConfidence(bucket.observed),
|
|
3848
|
+
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
3849
|
+
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
3850
|
+
});
|
|
3851
|
+
}
|
|
3852
|
+
return out;
|
|
3853
|
+
}
|
|
3854
|
+
function declaredHostFor(svc) {
|
|
3855
|
+
const raw = svc.dbConnectionTarget?.trim();
|
|
3856
|
+
if (!raw) return null;
|
|
3857
|
+
const colon = raw.lastIndexOf(":");
|
|
3858
|
+
if (colon === -1) return raw;
|
|
3859
|
+
const port = raw.slice(colon + 1);
|
|
3860
|
+
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
3861
|
+
return raw;
|
|
3862
|
+
}
|
|
3863
|
+
function hasExtractedConfiguredBy(graph, svcId) {
|
|
3864
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3865
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
3866
|
+
if (e.type === EdgeType9.CONFIGURED_BY && e.provenance === Provenance9.EXTRACTED) {
|
|
3867
|
+
return true;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
return false;
|
|
3871
|
+
}
|
|
3872
|
+
function detectHostMismatch(graph, svcId, svc) {
|
|
3873
|
+
const declaredHost = declaredHostFor(svc);
|
|
3874
|
+
if (!declaredHost) return [];
|
|
3875
|
+
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
3876
|
+
const out = [];
|
|
3877
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3878
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
3879
|
+
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
3880
|
+
if (edge.provenance !== Provenance9.OBSERVED) continue;
|
|
3881
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
3882
|
+
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
3883
|
+
const observedHost = target.host?.trim();
|
|
3884
|
+
if (!observedHost) continue;
|
|
3885
|
+
if (observedHost === declaredHost) continue;
|
|
3886
|
+
out.push({
|
|
3887
|
+
type: "host-mismatch",
|
|
3888
|
+
source: svcId,
|
|
3889
|
+
target: edge.target,
|
|
3890
|
+
extractedHost: declaredHost,
|
|
3891
|
+
observedHost,
|
|
3892
|
+
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
3893
|
+
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
3894
|
+
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
return out;
|
|
3898
|
+
}
|
|
3899
|
+
function detectCompatDivergences(graph, svcId, svc) {
|
|
3900
|
+
const out = [];
|
|
3901
|
+
const deps = svc.dependencies ?? {};
|
|
3902
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3903
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
3904
|
+
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
3905
|
+
if (edge.provenance !== Provenance9.OBSERVED) continue;
|
|
3906
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
3907
|
+
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
3908
|
+
for (const pair of compatPairs()) {
|
|
3909
|
+
if (pair.engine !== target.engine) continue;
|
|
3910
|
+
const declared = deps[pair.driver];
|
|
3911
|
+
if (!declared) continue;
|
|
3912
|
+
const result = checkCompatibility(
|
|
3913
|
+
pair.driver,
|
|
3914
|
+
declared,
|
|
3915
|
+
target.engine,
|
|
3916
|
+
target.engineVersion
|
|
3917
|
+
);
|
|
3918
|
+
if (!result.compatible && result.reason) {
|
|
3919
|
+
out.push({
|
|
3920
|
+
type: "version-mismatch",
|
|
3921
|
+
source: svcId,
|
|
3922
|
+
target: edge.target,
|
|
3923
|
+
extractedVersion: declared,
|
|
3924
|
+
observedVersion: target.engineVersion,
|
|
3925
|
+
compatibility: "incompatible",
|
|
3926
|
+
confidence: 1,
|
|
3927
|
+
reason: result.reason,
|
|
3928
|
+
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
for (const rule of deprecatedApis()) {
|
|
3933
|
+
const declared = deps[rule.package];
|
|
3934
|
+
if (!declared) continue;
|
|
3935
|
+
const result = checkDeprecatedApi(rule, declared);
|
|
3936
|
+
if (!result.compatible && result.reason) {
|
|
3937
|
+
const ruleRef = {
|
|
3938
|
+
kind: rule.kind ?? "deprecated-api",
|
|
3939
|
+
reason: result.reason,
|
|
3940
|
+
package: rule.package
|
|
3941
|
+
};
|
|
3942
|
+
out.push({
|
|
3943
|
+
type: "compat-violation",
|
|
3944
|
+
source: svcId,
|
|
3945
|
+
target: edge.target,
|
|
3946
|
+
rule: ruleRef,
|
|
3947
|
+
observed: edge,
|
|
3948
|
+
confidence: 1,
|
|
3949
|
+
reason: result.reason,
|
|
3950
|
+
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
return out;
|
|
3956
|
+
}
|
|
3957
|
+
function involvesNode(d, nodeId) {
|
|
3958
|
+
return d.source === nodeId || d.target === nodeId;
|
|
3959
|
+
}
|
|
3960
|
+
function computeDivergences(graph, opts = {}) {
|
|
3961
|
+
const all = [];
|
|
3962
|
+
const buckets = bucketEdges(graph);
|
|
3963
|
+
for (const bucket of buckets.values()) {
|
|
3964
|
+
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
3965
|
+
}
|
|
3966
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
3967
|
+
const n = attrs;
|
|
3968
|
+
if (n.type !== NodeType10.ServiceNode) return;
|
|
3969
|
+
const svc = n;
|
|
3970
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
3971
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
3972
|
+
});
|
|
3973
|
+
let filtered = all;
|
|
3974
|
+
if (opts.type) {
|
|
3975
|
+
const allowed = opts.type;
|
|
3976
|
+
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
3977
|
+
}
|
|
3978
|
+
if (opts.minConfidence !== void 0) {
|
|
3979
|
+
const threshold = opts.minConfidence;
|
|
3980
|
+
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
3981
|
+
}
|
|
3982
|
+
if (opts.node) {
|
|
3983
|
+
const target = opts.node;
|
|
3984
|
+
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
3985
|
+
}
|
|
3986
|
+
const TYPE_LEADERSHIP = {
|
|
3987
|
+
"missing-extracted": 0,
|
|
3988
|
+
"missing-observed": 1,
|
|
3989
|
+
"version-mismatch": 2,
|
|
3990
|
+
"host-mismatch": 3,
|
|
3991
|
+
"compat-violation": 4
|
|
3992
|
+
};
|
|
3993
|
+
filtered.sort((a, b) => {
|
|
3994
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
3995
|
+
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
3996
|
+
if (lead !== 0) return lead;
|
|
3997
|
+
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
3998
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
3999
|
+
return a.target.localeCompare(b.target);
|
|
4000
|
+
});
|
|
4001
|
+
return DivergenceResultSchema.parse({
|
|
4002
|
+
divergences: filtered,
|
|
4003
|
+
totalAffected: filtered.length,
|
|
4004
|
+
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
|
|
3762
4008
|
// src/persist.ts
|
|
3763
4009
|
import { promises as fs17 } from "fs";
|
|
3764
4010
|
import path31 from "path";
|
|
3765
|
-
import { Provenance as
|
|
4011
|
+
import { Provenance as Provenance10, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
3766
4012
|
var SCHEMA_VERSION = 3;
|
|
3767
4013
|
function migrateV1ToV2(payload) {
|
|
3768
4014
|
const nodes = payload.graph.nodes;
|
|
@@ -3781,7 +4027,7 @@ function migrateV2ToV3(payload) {
|
|
|
3781
4027
|
for (const edge of edges) {
|
|
3782
4028
|
const attrs = edge.attributes;
|
|
3783
4029
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
3784
|
-
attrs.provenance =
|
|
4030
|
+
attrs.provenance = Provenance10.OBSERVED;
|
|
3785
4031
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
3786
4032
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
3787
4033
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -4170,248 +4416,6 @@ import Fastify from "fastify";
|
|
|
4170
4416
|
import cors from "@fastify/cors";
|
|
4171
4417
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
4172
4418
|
|
|
4173
|
-
// src/divergences.ts
|
|
4174
|
-
import {
|
|
4175
|
-
DivergenceResultSchema,
|
|
4176
|
-
EdgeType as EdgeType9,
|
|
4177
|
-
NodeType as NodeType10,
|
|
4178
|
-
parseEdgeId,
|
|
4179
|
-
Provenance as Provenance10
|
|
4180
|
-
} from "@neat.is/types";
|
|
4181
|
-
function bucketKey(source, target, type) {
|
|
4182
|
-
return `${type}|${source}|${target}`;
|
|
4183
|
-
}
|
|
4184
|
-
function bucketEdges(graph) {
|
|
4185
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
4186
|
-
graph.forEachEdge((id, attrs) => {
|
|
4187
|
-
const e = attrs;
|
|
4188
|
-
const parsed = parseEdgeId(id);
|
|
4189
|
-
const provenance = parsed?.provenance ?? e.provenance;
|
|
4190
|
-
const key = bucketKey(e.source, e.target, e.type);
|
|
4191
|
-
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4192
|
-
switch (provenance) {
|
|
4193
|
-
case Provenance10.EXTRACTED:
|
|
4194
|
-
cur.extracted = e;
|
|
4195
|
-
break;
|
|
4196
|
-
case Provenance10.OBSERVED:
|
|
4197
|
-
cur.observed = e;
|
|
4198
|
-
break;
|
|
4199
|
-
case Provenance10.INFERRED:
|
|
4200
|
-
cur.inferred = e;
|
|
4201
|
-
break;
|
|
4202
|
-
default:
|
|
4203
|
-
if (e.provenance === Provenance10.STALE) cur.stale = e;
|
|
4204
|
-
}
|
|
4205
|
-
buckets.set(key, cur);
|
|
4206
|
-
});
|
|
4207
|
-
return buckets;
|
|
4208
|
-
}
|
|
4209
|
-
function nodeIsFrontier(graph, nodeId) {
|
|
4210
|
-
if (!graph.hasNode(nodeId)) return false;
|
|
4211
|
-
const attrs = graph.getNodeAttributes(nodeId);
|
|
4212
|
-
return attrs.type === NodeType10.FrontierNode;
|
|
4213
|
-
}
|
|
4214
|
-
function clampConfidence(n) {
|
|
4215
|
-
if (!Number.isFinite(n)) return 0;
|
|
4216
|
-
return Math.max(0, Math.min(1, n));
|
|
4217
|
-
}
|
|
4218
|
-
function reasonForMissingObserved(source, target, type) {
|
|
4219
|
-
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
4220
|
-
}
|
|
4221
|
-
function reasonForMissingExtracted(source, target, type) {
|
|
4222
|
-
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
4223
|
-
}
|
|
4224
|
-
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
4225
|
-
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
4226
|
-
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
4227
|
-
function gradedConfidence(edge) {
|
|
4228
|
-
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
4229
|
-
return clampConfidence(confidenceForEdge(edge));
|
|
4230
|
-
}
|
|
4231
|
-
function detectMissingDivergences(graph, bucket) {
|
|
4232
|
-
const out = [];
|
|
4233
|
-
if (bucket.extracted && !bucket.observed) {
|
|
4234
|
-
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4235
|
-
out.push({
|
|
4236
|
-
type: "missing-observed",
|
|
4237
|
-
source: bucket.source,
|
|
4238
|
-
target: bucket.target,
|
|
4239
|
-
edgeType: bucket.type,
|
|
4240
|
-
extracted: bucket.extracted,
|
|
4241
|
-
confidence: gradedConfidence(bucket.extracted),
|
|
4242
|
-
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
4243
|
-
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
4244
|
-
});
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
if (bucket.observed && !bucket.extracted) {
|
|
4248
|
-
out.push({
|
|
4249
|
-
type: "missing-extracted",
|
|
4250
|
-
source: bucket.source,
|
|
4251
|
-
target: bucket.target,
|
|
4252
|
-
edgeType: bucket.type,
|
|
4253
|
-
observed: bucket.observed,
|
|
4254
|
-
confidence: gradedConfidence(bucket.observed),
|
|
4255
|
-
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4256
|
-
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4257
|
-
});
|
|
4258
|
-
}
|
|
4259
|
-
return out;
|
|
4260
|
-
}
|
|
4261
|
-
function declaredHostFor(svc) {
|
|
4262
|
-
const raw = svc.dbConnectionTarget?.trim();
|
|
4263
|
-
if (!raw) return null;
|
|
4264
|
-
const colon = raw.lastIndexOf(":");
|
|
4265
|
-
if (colon === -1) return raw;
|
|
4266
|
-
const port = raw.slice(colon + 1);
|
|
4267
|
-
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
4268
|
-
return raw;
|
|
4269
|
-
}
|
|
4270
|
-
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4271
|
-
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4272
|
-
const e = graph.getEdgeAttributes(edgeId);
|
|
4273
|
-
if (e.type === EdgeType9.CONFIGURED_BY && e.provenance === Provenance10.EXTRACTED) {
|
|
4274
|
-
return true;
|
|
4275
|
-
}
|
|
4276
|
-
}
|
|
4277
|
-
return false;
|
|
4278
|
-
}
|
|
4279
|
-
function detectHostMismatch(graph, svcId, svc) {
|
|
4280
|
-
const declaredHost = declaredHostFor(svc);
|
|
4281
|
-
if (!declaredHost) return [];
|
|
4282
|
-
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
4283
|
-
const out = [];
|
|
4284
|
-
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4285
|
-
const edge = graph.getEdgeAttributes(edgeId);
|
|
4286
|
-
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
4287
|
-
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
4288
|
-
const target = graph.getNodeAttributes(edge.target);
|
|
4289
|
-
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
4290
|
-
const observedHost = target.host?.trim();
|
|
4291
|
-
if (!observedHost) continue;
|
|
4292
|
-
if (observedHost === declaredHost) continue;
|
|
4293
|
-
out.push({
|
|
4294
|
-
type: "host-mismatch",
|
|
4295
|
-
source: svcId,
|
|
4296
|
-
target: edge.target,
|
|
4297
|
-
extractedHost: declaredHost,
|
|
4298
|
-
observedHost,
|
|
4299
|
-
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
4300
|
-
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
4301
|
-
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
4302
|
-
});
|
|
4303
|
-
}
|
|
4304
|
-
return out;
|
|
4305
|
-
}
|
|
4306
|
-
function detectCompatDivergences(graph, svcId, svc) {
|
|
4307
|
-
const out = [];
|
|
4308
|
-
const deps = svc.dependencies ?? {};
|
|
4309
|
-
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4310
|
-
const edge = graph.getEdgeAttributes(edgeId);
|
|
4311
|
-
if (edge.type !== EdgeType9.CONNECTS_TO) continue;
|
|
4312
|
-
if (edge.provenance !== Provenance10.OBSERVED) continue;
|
|
4313
|
-
const target = graph.getNodeAttributes(edge.target);
|
|
4314
|
-
if (target.type !== NodeType10.DatabaseNode) continue;
|
|
4315
|
-
for (const pair of compatPairs()) {
|
|
4316
|
-
if (pair.engine !== target.engine) continue;
|
|
4317
|
-
const declared = deps[pair.driver];
|
|
4318
|
-
if (!declared) continue;
|
|
4319
|
-
const result = checkCompatibility(
|
|
4320
|
-
pair.driver,
|
|
4321
|
-
declared,
|
|
4322
|
-
target.engine,
|
|
4323
|
-
target.engineVersion
|
|
4324
|
-
);
|
|
4325
|
-
if (!result.compatible && result.reason) {
|
|
4326
|
-
out.push({
|
|
4327
|
-
type: "version-mismatch",
|
|
4328
|
-
source: svcId,
|
|
4329
|
-
target: edge.target,
|
|
4330
|
-
extractedVersion: declared,
|
|
4331
|
-
observedVersion: target.engineVersion,
|
|
4332
|
-
compatibility: "incompatible",
|
|
4333
|
-
confidence: 1,
|
|
4334
|
-
reason: result.reason,
|
|
4335
|
-
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
4336
|
-
});
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
for (const rule of deprecatedApis()) {
|
|
4340
|
-
const declared = deps[rule.package];
|
|
4341
|
-
if (!declared) continue;
|
|
4342
|
-
const result = checkDeprecatedApi(rule, declared);
|
|
4343
|
-
if (!result.compatible && result.reason) {
|
|
4344
|
-
const ruleRef = {
|
|
4345
|
-
kind: rule.kind ?? "deprecated-api",
|
|
4346
|
-
reason: result.reason,
|
|
4347
|
-
package: rule.package
|
|
4348
|
-
};
|
|
4349
|
-
out.push({
|
|
4350
|
-
type: "compat-violation",
|
|
4351
|
-
source: svcId,
|
|
4352
|
-
target: edge.target,
|
|
4353
|
-
rule: ruleRef,
|
|
4354
|
-
observed: edge,
|
|
4355
|
-
confidence: 1,
|
|
4356
|
-
reason: result.reason,
|
|
4357
|
-
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
4358
|
-
});
|
|
4359
|
-
}
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
return out;
|
|
4363
|
-
}
|
|
4364
|
-
function involvesNode(d, nodeId) {
|
|
4365
|
-
return d.source === nodeId || d.target === nodeId;
|
|
4366
|
-
}
|
|
4367
|
-
function computeDivergences(graph, opts = {}) {
|
|
4368
|
-
const all = [];
|
|
4369
|
-
const buckets = bucketEdges(graph);
|
|
4370
|
-
for (const bucket of buckets.values()) {
|
|
4371
|
-
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
4372
|
-
}
|
|
4373
|
-
graph.forEachNode((nodeId, attrs) => {
|
|
4374
|
-
const n = attrs;
|
|
4375
|
-
if (n.type !== NodeType10.ServiceNode) return;
|
|
4376
|
-
const svc = n;
|
|
4377
|
-
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4378
|
-
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
4379
|
-
});
|
|
4380
|
-
let filtered = all;
|
|
4381
|
-
if (opts.type) {
|
|
4382
|
-
const allowed = opts.type;
|
|
4383
|
-
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
4384
|
-
}
|
|
4385
|
-
if (opts.minConfidence !== void 0) {
|
|
4386
|
-
const threshold = opts.minConfidence;
|
|
4387
|
-
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
4388
|
-
}
|
|
4389
|
-
if (opts.node) {
|
|
4390
|
-
const target = opts.node;
|
|
4391
|
-
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4392
|
-
}
|
|
4393
|
-
const TYPE_LEADERSHIP = {
|
|
4394
|
-
"missing-extracted": 0,
|
|
4395
|
-
"missing-observed": 1,
|
|
4396
|
-
"version-mismatch": 2,
|
|
4397
|
-
"host-mismatch": 3,
|
|
4398
|
-
"compat-violation": 4
|
|
4399
|
-
};
|
|
4400
|
-
filtered.sort((a, b) => {
|
|
4401
|
-
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4402
|
-
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
4403
|
-
if (lead !== 0) return lead;
|
|
4404
|
-
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4405
|
-
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4406
|
-
return a.target.localeCompare(b.target);
|
|
4407
|
-
});
|
|
4408
|
-
return DivergenceResultSchema.parse({
|
|
4409
|
-
divergences: filtered,
|
|
4410
|
-
totalAffected: filtered.length,
|
|
4411
|
-
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4412
|
-
});
|
|
4413
|
-
}
|
|
4414
|
-
|
|
4415
4419
|
// src/streaming.ts
|
|
4416
4420
|
var SSE_HEARTBEAT_MS = 3e4;
|
|
4417
4421
|
var SSE_BACKPRESSURE_CAP = 1e3;
|
|
@@ -4832,6 +4836,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4832
4836
|
async function buildApi(opts) {
|
|
4833
4837
|
const app = Fastify({ logger: false });
|
|
4834
4838
|
await app.register(cors, { origin: true });
|
|
4839
|
+
mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
|
|
4835
4840
|
const startedAt = opts.startedAt ?? Date.now();
|
|
4836
4841
|
const registry = buildLegacyRegistry(opts);
|
|
4837
4842
|
const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
|
|
@@ -4930,6 +4935,7 @@ export {
|
|
|
4930
4935
|
addInfra,
|
|
4931
4936
|
retireEdgesByFile,
|
|
4932
4937
|
extractFromDirectory,
|
|
4938
|
+
computeDivergences,
|
|
4933
4939
|
saveGraphToDisk,
|
|
4934
4940
|
loadGraphFromDisk,
|
|
4935
4941
|
startPersistLoop,
|
|
@@ -4952,4 +4958,4 @@ export {
|
|
|
4952
4958
|
removeProject,
|
|
4953
4959
|
buildApi
|
|
4954
4960
|
};
|
|
4955
|
-
//# sourceMappingURL=chunk-
|
|
4961
|
+
//# sourceMappingURL=chunk-CZ3T6TE2.js.map
|