@stacknext/diagnostics 0.0.0-stage → 0.6.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 +18 -2
- package/dist/classify.d.ts +24 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +94 -0
- package/dist/classify.js.map +1 -0
- package/dist/engine.d.ts +17 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +213 -0
- package/dist/engine.js.map +1 -0
- package/dist/fingerprints.d.ts +22 -0
- package/dist/fingerprints.d.ts.map +1 -0
- package/dist/fingerprints.js +127 -0
- package/dist/fingerprints.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/rules.d.ts +8 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +401 -0
- package/dist/rules.js.map +1 -0
- package/package.json +48 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StackNext contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stacknext/diagnostics
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deterministic, evidence-backed diagnostics for [StackNext](https://github.com/marrrkkk/stacknext)
|
|
4
|
+
telemetry. The engine evaluates four conservative rules over the current
|
|
5
|
+
retention sample: repeated SQL shapes, slow spans, repeated external HTTP
|
|
6
|
+
endpoints and error fingerprints.
|
|
7
|
+
|
|
8
|
+
Findings are heuristics, not confirmed root causes. The same report is exposed
|
|
9
|
+
by the StackNext CLI, dashboard, JSON export and read-only MCP server.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @stacknext/cli @stacknext/next
|
|
13
|
+
npx stacknext diagnose
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This package is normally consumed through the StackNext CLI and collector. It
|
|
17
|
+
is ESM-only and requires Node.js 20.9 or newer.
|
|
18
|
+
|
|
19
|
+
License: MIT.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type DiagnosticSpanCategory, type DiagnosticSpanEvidence, type StackNextConfig, type StackNextSpan } from '@stacknext/core';
|
|
2
|
+
/**
|
|
3
|
+
* Classifies a span for threshold selection. Manual spans carrying documented
|
|
4
|
+
* database attributes are treated as database work, even though the explorer
|
|
5
|
+
* labels them app/custom.
|
|
6
|
+
*/
|
|
7
|
+
export declare function diagnosticSpanCategory(span: StackNextSpan): DiagnosticSpanCategory;
|
|
8
|
+
export declare function spanThresholdMs(category: DiagnosticSpanCategory, config: StackNextConfig['diagnostics']): number;
|
|
9
|
+
export declare function spanHttpMethod(span: StackNextSpan): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Extracts a bounded, credential-free endpoint from a fetch span. Returns null
|
|
12
|
+
* when no URL-like attribute or path can be identified.
|
|
13
|
+
*/
|
|
14
|
+
export declare function spanHttpEndpoint(span: StackNextSpan): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Defense-in-depth for evidence output. Stored telemetry is already redacted,
|
|
17
|
+
* but diagnostics must never reintroduce URL query values or credentials when
|
|
18
|
+
* it copies a span name into a finding.
|
|
19
|
+
*/
|
|
20
|
+
export declare function safeSpanName(name: string): string;
|
|
21
|
+
/** Bounded route evidence with URL query values and credentials removed. */
|
|
22
|
+
export declare function safeRoute(value: string | null | undefined): string | null;
|
|
23
|
+
export declare function spanEvidence(span: StackNextSpan, category?: DiagnosticSpanCategory): DiagnosticSpanEvidence;
|
|
24
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AAcxB;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,sBAAsB,CAclF;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,sBAAsB,EAChC,MAAM,EAAE,eAAe,CAAC,aAAa,CAAC,GACrC,MAAM,CAKR;AASD,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAiBnE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIzE;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,aAAa,EACnB,QAAQ,yBAA+B,GACtC,sBAAsB,CAYxB"}
|
package/dist/classify.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { isDatabaseSpan, isFetchSpan, redactText, stripUrlQueryInText, } from '@stacknext/core';
|
|
2
|
+
import { httpEndpoint } from './fingerprints.js';
|
|
3
|
+
function stringAttribute(attributes, ...keys) {
|
|
4
|
+
for (const key of keys) {
|
|
5
|
+
const value = attributes[key];
|
|
6
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Classifies a span for threshold selection. Manual spans carrying documented
|
|
13
|
+
* database attributes are treated as database work, even though the explorer
|
|
14
|
+
* labels them app/custom.
|
|
15
|
+
*/
|
|
16
|
+
export function diagnosticSpanCategory(span) {
|
|
17
|
+
if (isDatabaseSpan(span.attributes))
|
|
18
|
+
return 'database';
|
|
19
|
+
if (isFetchSpan(span.nextSpanType, span.kind, span.attributes))
|
|
20
|
+
return 'http';
|
|
21
|
+
const manualKind = span.attributes['stacknext.span.kind'];
|
|
22
|
+
if (manualKind === 'custom' ||
|
|
23
|
+
manualKind === 'action' ||
|
|
24
|
+
span.attributes['stacknext.manual'] === true) {
|
|
25
|
+
return 'custom';
|
|
26
|
+
}
|
|
27
|
+
if (span.nextSpanType !== undefined)
|
|
28
|
+
return 'framework';
|
|
29
|
+
if (span.kind === 'server' && !span.parentSpanId)
|
|
30
|
+
return 'framework';
|
|
31
|
+
return 'other';
|
|
32
|
+
}
|
|
33
|
+
export function spanThresholdMs(category, config) {
|
|
34
|
+
if (category === 'database')
|
|
35
|
+
return config.slowSpans.databaseMs;
|
|
36
|
+
if (category === 'http')
|
|
37
|
+
return config.slowSpans.httpMs;
|
|
38
|
+
if (category === 'custom')
|
|
39
|
+
return config.slowSpans.customMs;
|
|
40
|
+
return config.slowSpans.otherMs;
|
|
41
|
+
}
|
|
42
|
+
function methodFromName(name) {
|
|
43
|
+
const match = /^(?:fetch\s+)?(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|CONNECT)\s+/i.exec(name);
|
|
44
|
+
return match?.[1]?.toUpperCase();
|
|
45
|
+
}
|
|
46
|
+
export function spanHttpMethod(span) {
|
|
47
|
+
return (span.method ??
|
|
48
|
+
stringAttribute(span.attributes, 'http.method', 'http.request.method')?.toUpperCase() ??
|
|
49
|
+
methodFromName(span.name) ??
|
|
50
|
+
null);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extracts a bounded, credential-free endpoint from a fetch span. Returns null
|
|
54
|
+
* when no URL-like attribute or path can be identified.
|
|
55
|
+
*/
|
|
56
|
+
export function spanHttpEndpoint(span) {
|
|
57
|
+
const direct = stringAttribute(span.attributes, 'http.url', 'url.full', 'http.target', 'url.path', 'http.route');
|
|
58
|
+
if (direct) {
|
|
59
|
+
const endpoint = httpEndpoint(direct);
|
|
60
|
+
if (endpoint)
|
|
61
|
+
return endpoint;
|
|
62
|
+
}
|
|
63
|
+
const match = /^(?:fetch\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|CONNECT)\s+(\S+)/i.exec(span.name);
|
|
64
|
+
return match?.[1] ? httpEndpoint(match[1]) : null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Defense-in-depth for evidence output. Stored telemetry is already redacted,
|
|
68
|
+
* but diagnostics must never reintroduce URL query values or credentials when
|
|
69
|
+
* it copies a span name into a finding.
|
|
70
|
+
*/
|
|
71
|
+
export function safeSpanName(name) {
|
|
72
|
+
return stripUrlQueryInText(redactText(name, 300));
|
|
73
|
+
}
|
|
74
|
+
/** Bounded route evidence with URL query values and credentials removed. */
|
|
75
|
+
export function safeRoute(value) {
|
|
76
|
+
if (!value)
|
|
77
|
+
return null;
|
|
78
|
+
const sanitized = stripUrlQueryInText(redactText(value, 500));
|
|
79
|
+
return sanitized.length <= 500 ? sanitized : `${sanitized.slice(0, 500)}…`;
|
|
80
|
+
}
|
|
81
|
+
export function spanEvidence(span, category = diagnosticSpanCategory(span)) {
|
|
82
|
+
const route = safeRoute(span.route);
|
|
83
|
+
return {
|
|
84
|
+
spanId: span.spanId,
|
|
85
|
+
traceId: span.traceId,
|
|
86
|
+
name: safeSpanName(span.name),
|
|
87
|
+
durationMs: Math.round(span.durationMs * 10) / 10,
|
|
88
|
+
category,
|
|
89
|
+
status: span.status,
|
|
90
|
+
...(route ? { route } : {}),
|
|
91
|
+
...(span.nextSpanType ? { nextSpanType: span.nextSpanType } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type DiagnosticCounts, type DiagnosticDetailResponse, type DiagnosticFilters, type DiagnosticFinding, type DiagnosticListResponse, type DiagnosticOverview, type DiagnosticReport, type DiagnosticSnapshot, type StackNextConfig } from '@stacknext/core';
|
|
2
|
+
export declare const DIAGNOSTICS_ENGINE_VERSION = "0.6.1";
|
|
3
|
+
export interface DiagnosticEngineOptions {
|
|
4
|
+
projectName: string;
|
|
5
|
+
collectorVersion: string;
|
|
6
|
+
config: StackNextConfig['diagnostics'];
|
|
7
|
+
generatedAt?: number;
|
|
8
|
+
connected?: boolean;
|
|
9
|
+
connectedInstances?: number;
|
|
10
|
+
engineVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function countFindings(findings: DiagnosticFinding[]): DiagnosticCounts;
|
|
13
|
+
export declare function generateDiagnosticReport(snapshot: DiagnosticSnapshot, options: DiagnosticEngineOptions): DiagnosticReport;
|
|
14
|
+
export declare function listDiagnosticFindings(report: DiagnosticReport, filters?: DiagnosticFilters): DiagnosticListResponse;
|
|
15
|
+
export declare function getDiagnosticDetail(report: DiagnosticReport, id: string, snapshot: DiagnosticSnapshot): DiagnosticDetailResponse | null;
|
|
16
|
+
export declare function diagnosticOverview(report: DiagnosticReport, topLimit?: number): DiagnosticOverview;
|
|
17
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EAEvB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAA;AAGxB,eAAO,MAAM,0BAA0B,UAAU,CAAA;AAEjD,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AA6BD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,gBAAgB,CAS7E;AAoDD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,CA2BlB;AAOD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,iBAAsB,GAC9B,sBAAsB,CA4CxB;AAmDD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,kBAAkB,GAC3B,wBAAwB,GAAG,IAAI,CAQjC;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,SAAI,GACX,kBAAkB,CAUpB"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { STACKNEXT_DIAGNOSTICS_FORMAT, STACKNEXT_DIAGNOSTICS_FORMAT_VERSION, STACKNEXT_DIAGNOSTICS_RULE_SET_VERSION, } from '@stacknext/core';
|
|
2
|
+
import { evaluateDiagnosticRules } from './rules.js';
|
|
3
|
+
export const DIAGNOSTICS_ENGINE_VERSION = '0.6.1';
|
|
4
|
+
const REPORT_LIMITATIONS = [
|
|
5
|
+
'Findings are deterministic heuristics over the current in-memory sample, not confirmed root causes.',
|
|
6
|
+
'StackNext does not read application source, run EXPLAIN, inspect query plans or modify code.',
|
|
7
|
+
'Null durations are unknown, not zero, and span durations include child work.',
|
|
8
|
+
'Only OpenTelemetry and adapter evidence received by the collector is analyzed.',
|
|
9
|
+
'Redaction and truncation may reduce the specificity of SQL and error signatures.',
|
|
10
|
+
];
|
|
11
|
+
function emptyCounts() {
|
|
12
|
+
return {
|
|
13
|
+
total: 0,
|
|
14
|
+
bySeverity: { info: 0, warning: 0, error: 0 },
|
|
15
|
+
byCategory: {
|
|
16
|
+
database: 0,
|
|
17
|
+
http: 0,
|
|
18
|
+
performance: 0,
|
|
19
|
+
errors: 0,
|
|
20
|
+
runtime: 0,
|
|
21
|
+
},
|
|
22
|
+
byStatus: {
|
|
23
|
+
observed: 0,
|
|
24
|
+
'insufficient-evidence': 0,
|
|
25
|
+
possible: 0,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function countFindings(findings) {
|
|
30
|
+
const counts = emptyCounts();
|
|
31
|
+
counts.total = findings.length;
|
|
32
|
+
for (const finding of findings) {
|
|
33
|
+
counts.bySeverity[finding.severity] += 1;
|
|
34
|
+
counts.byCategory[finding.category] += 1;
|
|
35
|
+
counts.byStatus[finding.status] += 1;
|
|
36
|
+
}
|
|
37
|
+
return counts;
|
|
38
|
+
}
|
|
39
|
+
function sampleFor(snapshot, options) {
|
|
40
|
+
return {
|
|
41
|
+
firstEventTime: snapshot.firstEventTime,
|
|
42
|
+
lastEventTime: snapshot.lastEventTime,
|
|
43
|
+
retentionMs: snapshot.retentionMs,
|
|
44
|
+
requests: snapshot.requests.length,
|
|
45
|
+
spans: snapshot.spans.length,
|
|
46
|
+
errors: snapshot.errors.length,
|
|
47
|
+
databaseQueries: snapshot.databaseQueries.length,
|
|
48
|
+
connectedInstances: options.connectedInstances ?? 0,
|
|
49
|
+
connected: options.connected ?? false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildNotes(snapshot, totalFindings, visibleFindings, config) {
|
|
53
|
+
const notes = [];
|
|
54
|
+
if (!config.enabled) {
|
|
55
|
+
notes.push('Diagnostics are disabled by configuration.');
|
|
56
|
+
return notes;
|
|
57
|
+
}
|
|
58
|
+
if (snapshot.spans.length === 0 &&
|
|
59
|
+
snapshot.errors.length === 0 &&
|
|
60
|
+
snapshot.databaseQueries.length === 0) {
|
|
61
|
+
notes.push('No telemetry is available in the current retention window.');
|
|
62
|
+
return notes;
|
|
63
|
+
}
|
|
64
|
+
if (totalFindings === 0) {
|
|
65
|
+
notes.push('No diagnostic findings matched the configured rules in the current sample.');
|
|
66
|
+
}
|
|
67
|
+
if (totalFindings > visibleFindings) {
|
|
68
|
+
notes.push(`Showing the top ${visibleFindings} of ${totalFindings} findings. Narrow the filters or raise diagnostics.maxFindings to inspect the rest.`);
|
|
69
|
+
}
|
|
70
|
+
if (snapshot.databaseQueries.some((query) => query.durationMs === null)) {
|
|
71
|
+
notes.push('One or more database queries have no measured duration; null is not treated as zero.');
|
|
72
|
+
}
|
|
73
|
+
return notes;
|
|
74
|
+
}
|
|
75
|
+
export function generateDiagnosticReport(snapshot, options) {
|
|
76
|
+
const requestByTrace = new Map(snapshot.requests.map((request) => [request.traceId, request]));
|
|
77
|
+
const evaluated = options.config.enabled
|
|
78
|
+
? evaluateDiagnosticRules({
|
|
79
|
+
snapshot,
|
|
80
|
+
config: options.config,
|
|
81
|
+
requestByTrace,
|
|
82
|
+
})
|
|
83
|
+
: [];
|
|
84
|
+
const maxFindings = options.config.maxFindings;
|
|
85
|
+
const findings = evaluated.slice(0, maxFindings);
|
|
86
|
+
return {
|
|
87
|
+
format: STACKNEXT_DIAGNOSTICS_FORMAT,
|
|
88
|
+
version: STACKNEXT_DIAGNOSTICS_FORMAT_VERSION,
|
|
89
|
+
engineVersion: options.engineVersion ?? DIAGNOSTICS_ENGINE_VERSION,
|
|
90
|
+
ruleSetVersion: STACKNEXT_DIAGNOSTICS_RULE_SET_VERSION,
|
|
91
|
+
generatedAt: options.generatedAt ?? Date.now(),
|
|
92
|
+
projectName: options.projectName,
|
|
93
|
+
collectorVersion: options.collectorVersion,
|
|
94
|
+
sample: sampleFor(snapshot, options),
|
|
95
|
+
counts: countFindings(findings),
|
|
96
|
+
findings,
|
|
97
|
+
notes: buildNotes(snapshot, evaluated.length, findings.length, options.config),
|
|
98
|
+
limitations: [...REPORT_LIMITATIONS],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function clampLimit(value, fallback, maximum) {
|
|
102
|
+
if (!Number.isFinite(value) || value === undefined)
|
|
103
|
+
return fallback;
|
|
104
|
+
return Math.min(maximum, Math.max(1, Math.trunc(value)));
|
|
105
|
+
}
|
|
106
|
+
export function listDiagnosticFindings(report, filters = {}) {
|
|
107
|
+
const q = filters.q?.trim().toLowerCase();
|
|
108
|
+
const route = filters.route?.trim().toLowerCase();
|
|
109
|
+
const traceId = filters.traceId?.trim();
|
|
110
|
+
const items = report.findings.filter((finding) => {
|
|
111
|
+
if (filters.category && finding.category !== filters.category)
|
|
112
|
+
return false;
|
|
113
|
+
if (filters.severity && finding.severity !== filters.severity)
|
|
114
|
+
return false;
|
|
115
|
+
if (filters.status && finding.status !== filters.status)
|
|
116
|
+
return false;
|
|
117
|
+
if (filters.ruleId && finding.ruleId !== filters.ruleId)
|
|
118
|
+
return false;
|
|
119
|
+
if (route && !finding.route?.toLowerCase().includes(route))
|
|
120
|
+
return false;
|
|
121
|
+
if (traceId && !finding.traceIds.includes(traceId))
|
|
122
|
+
return false;
|
|
123
|
+
if (filters.since !== undefined && finding.lastSeen < filters.since)
|
|
124
|
+
return false;
|
|
125
|
+
if (filters.until !== undefined && finding.firstSeen > filters.until)
|
|
126
|
+
return false;
|
|
127
|
+
if (q &&
|
|
128
|
+
!(finding.title.toLowerCase().includes(q) ||
|
|
129
|
+
finding.summary.toLowerCase().includes(q) ||
|
|
130
|
+
finding.detail.toLowerCase().includes(q) ||
|
|
131
|
+
finding.fingerprint.toLowerCase().includes(q) ||
|
|
132
|
+
finding.route?.toLowerCase().includes(q) === true ||
|
|
133
|
+
finding.ruleId.toLowerCase().includes(q))) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
});
|
|
138
|
+
const total = items.length;
|
|
139
|
+
const limit = clampLimit(filters.limit, 50, 200);
|
|
140
|
+
const offset = Math.max(0, Math.trunc(filters.offset ?? 0));
|
|
141
|
+
return {
|
|
142
|
+
items: items.slice(offset, offset + limit),
|
|
143
|
+
total,
|
|
144
|
+
limit,
|
|
145
|
+
offset,
|
|
146
|
+
generatedAt: report.generatedAt,
|
|
147
|
+
engineVersion: report.engineVersion,
|
|
148
|
+
ruleSetVersion: report.ruleSetVersion,
|
|
149
|
+
sample: report.sample,
|
|
150
|
+
counts: countFindings(items),
|
|
151
|
+
notes: report.notes,
|
|
152
|
+
limitations: report.limitations,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function traceContext(traceId, snapshot) {
|
|
156
|
+
const spans = snapshot.spans
|
|
157
|
+
.filter((span) => span.traceId === traceId)
|
|
158
|
+
.sort((a, b) => a.startTime - b.startTime || a.spanId.localeCompare(b.spanId));
|
|
159
|
+
const errors = snapshot.errors.filter((error) => (error.resolvedTraceId ?? error.traceId ?? null) === traceId);
|
|
160
|
+
const databaseQueries = snapshot.databaseQueries.filter((query) => query.resolvedTraceId === traceId);
|
|
161
|
+
if (spans.length === 0 && errors.length === 0 && databaseQueries.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
const request = snapshot.requests.find((entry) => entry.traceId === traceId) ?? null;
|
|
164
|
+
const eventTimes = [
|
|
165
|
+
...spans.map((span) => span.startTime),
|
|
166
|
+
...errors.map((error) => error.timestamp),
|
|
167
|
+
...databaseQueries.map((query) => query.timestamp),
|
|
168
|
+
];
|
|
169
|
+
const startTime = eventTimes.length > 0 ? Math.min(...eventTimes) : 0;
|
|
170
|
+
const endTime = spans.reduce((maximum, span) => Math.max(maximum, span.endTime), startTime);
|
|
171
|
+
const presentSpanIds = new Set(spans.map((span) => span.spanId));
|
|
172
|
+
const missingParentSpanIds = [
|
|
173
|
+
...new Set(spans
|
|
174
|
+
.map((span) => span.parentSpanId)
|
|
175
|
+
.filter((parent) => Boolean(parent) && !presentSpanIds.has(parent))),
|
|
176
|
+
];
|
|
177
|
+
return {
|
|
178
|
+
traceId,
|
|
179
|
+
request,
|
|
180
|
+
startTime,
|
|
181
|
+
endTime,
|
|
182
|
+
durationMs: Math.max(0, endTime - startTime),
|
|
183
|
+
spanCount: spans.length,
|
|
184
|
+
errorCount: errors.length,
|
|
185
|
+
databaseQueryCount: databaseQueries.length,
|
|
186
|
+
spans,
|
|
187
|
+
errors,
|
|
188
|
+
databaseQueries,
|
|
189
|
+
missingParentSpanIds,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
export function getDiagnosticDetail(report, id, snapshot) {
|
|
193
|
+
const finding = report.findings.find((entry) => entry.id === id);
|
|
194
|
+
if (!finding)
|
|
195
|
+
return null;
|
|
196
|
+
const traceId = finding.traceIds[0] ?? null;
|
|
197
|
+
return {
|
|
198
|
+
finding,
|
|
199
|
+
trace: traceId ? traceContext(traceId, snapshot) : null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export function diagnosticOverview(report, topLimit = 5) {
|
|
203
|
+
return {
|
|
204
|
+
generatedAt: report.generatedAt,
|
|
205
|
+
engineVersion: report.engineVersion,
|
|
206
|
+
ruleSetVersion: report.ruleSetVersion,
|
|
207
|
+
sample: report.sample,
|
|
208
|
+
counts: report.counts,
|
|
209
|
+
topFindings: report.findings.slice(0, Math.max(1, Math.trunc(topLimit))),
|
|
210
|
+
limitations: report.limitations,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type StackNextError } from '@stacknext/core';
|
|
2
|
+
/**
|
|
3
|
+
* Produces a stable SQL shape without literals or bind parameters. The shape is
|
|
4
|
+
* used for grouping only; it is never treated as proof of an N+1 pattern.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sqlFingerprint(sql: string): string;
|
|
7
|
+
/** Safe, bounded SQL shape used in finding evidence instead of raw SQL. */
|
|
8
|
+
export declare function sqlPreview(sql: string, maxLength?: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Reduces a URL or path to an endpoint shape: credentials, query values,
|
|
11
|
+
* fragments and ID-like path segments are removed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function httpEndpoint(value: string | undefined): string | null;
|
|
14
|
+
export declare function normalizeErrorMessage(message: string): string;
|
|
15
|
+
export declare function errorOperationCategory(error: StackNextError): string;
|
|
16
|
+
/**
|
|
17
|
+
* Route-independent error signature. Routes and methods are deliberately
|
|
18
|
+
* excluded so the same failure can be grouped across entry points.
|
|
19
|
+
*/
|
|
20
|
+
export declare function diagnosticErrorFingerprint(error: StackNextError): string;
|
|
21
|
+
export declare function diagnosticFindingId(ruleId: string, findingFingerprint: string, scope?: string): string;
|
|
22
|
+
//# sourceMappingURL=fingerprints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprints.d.ts","sourceRoot":"","sources":["../src/fingerprints.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAA;AAMxB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAclD;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CAM/D;AAgBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAYrE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAU7D;AAmBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAOpE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAQxE;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,MAAM,EAC1B,KAAK,SAAK,GACT,MAAM,CAIR"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { fingerprint, stripUrlQuery, } from '@stacknext/core';
|
|
2
|
+
const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
3
|
+
const LONG_HEX_PATTERN = /\b[0-9a-f]{16,}\b/gi;
|
|
4
|
+
/**
|
|
5
|
+
* Produces a stable SQL shape without literals or bind parameters. The shape is
|
|
6
|
+
* used for grouping only; it is never treated as proof of an N+1 pattern.
|
|
7
|
+
*/
|
|
8
|
+
export function sqlFingerprint(sql) {
|
|
9
|
+
let value = sql.replace(/\/\*[\s\S]*?\*\//g, ' ');
|
|
10
|
+
value = value.replace(/--[^\n]*/g, ' ');
|
|
11
|
+
value = value.replace(/\$[a-z_]*\$[\s\S]*?\$[a-z_]*\$/gi, '?');
|
|
12
|
+
value = value.replace(/'(?:''|[^'])*'/g, '?');
|
|
13
|
+
value = value.replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, '?');
|
|
14
|
+
value = value.replace(/\$\d+/g, '?');
|
|
15
|
+
value = value.replace(/(?<!:):[a-zA-Z_][a-zA-Z0-9_]*/g, '?');
|
|
16
|
+
value = value.replace(/@[a-zA-Z_][a-zA-Z0-9_]*/g, '?');
|
|
17
|
+
value = value.replace(/\b[0-9a-f]{16,}\b/gi, '?');
|
|
18
|
+
value = value.replace(/\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/gi, '?');
|
|
19
|
+
value = value.replace(/\(\s*\?(?:\s*,\s*\?)+\s*\)/g, '(?)');
|
|
20
|
+
value = value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/** Safe, bounded SQL shape used in finding evidence instead of raw SQL. */
|
|
24
|
+
export function sqlPreview(sql, maxLength = 500) {
|
|
25
|
+
const shape = sqlFingerprint(sql);
|
|
26
|
+
if (shape.length <= maxLength)
|
|
27
|
+
return shape;
|
|
28
|
+
const suffix = '… [truncated]';
|
|
29
|
+
if (maxLength <= suffix.length)
|
|
30
|
+
return shape.slice(0, maxLength);
|
|
31
|
+
return `${shape.slice(0, maxLength - suffix.length)}${suffix}`;
|
|
32
|
+
}
|
|
33
|
+
function normalizePath(pathname) {
|
|
34
|
+
const segments = pathname.split('/').filter((segment) => segment.length > 0);
|
|
35
|
+
const normalized = segments.map((segment) => {
|
|
36
|
+
if (/^\d+$/.test(segment))
|
|
37
|
+
return ':id';
|
|
38
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {
|
|
39
|
+
return ':id';
|
|
40
|
+
}
|
|
41
|
+
if (/^[0-9a-f]{16,}$/i.test(segment))
|
|
42
|
+
return ':id';
|
|
43
|
+
if (/^[a-zA-Z0-9_-]{32,}$/.test(segment))
|
|
44
|
+
return ':id';
|
|
45
|
+
return segment;
|
|
46
|
+
});
|
|
47
|
+
return normalized.length === 0 ? '/' : `/${normalized.join('/')}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reduces a URL or path to an endpoint shape: credentials, query values,
|
|
51
|
+
* fragments and ID-like path segments are removed.
|
|
52
|
+
*/
|
|
53
|
+
export function httpEndpoint(value) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return null;
|
|
56
|
+
const stripped = stripUrlQuery(value.trim());
|
|
57
|
+
if (stripped === '')
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(stripped);
|
|
61
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
62
|
+
return null;
|
|
63
|
+
return `${url.host.toLowerCase()}${normalizePath(url.pathname)}`;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
if (!stripped.startsWith('/'))
|
|
67
|
+
return null;
|
|
68
|
+
return normalizePath(stripped);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function normalizeErrorMessage(message) {
|
|
72
|
+
let value = message.toLowerCase();
|
|
73
|
+
value = value.replace(/https?:\/\/[^\s]+/g, '<url>');
|
|
74
|
+
value = value.replace(UUID_PATTERN, '<id>');
|
|
75
|
+
value = value.replace(LONG_HEX_PATTERN, '<hex>');
|
|
76
|
+
value = value.replace(/\b\d+(?:\.\d+)?\b/g, '<n>');
|
|
77
|
+
value = value.replace(/'(?:''|[^'])*'/g, '<str>');
|
|
78
|
+
value = value.replace(/"[^"]*"/g, '<str>');
|
|
79
|
+
value = value.replace(/\s+/g, ' ').trim();
|
|
80
|
+
return value.slice(0, 600);
|
|
81
|
+
}
|
|
82
|
+
function stackSignature(stack) {
|
|
83
|
+
if (!stack)
|
|
84
|
+
return '';
|
|
85
|
+
const frames = stack
|
|
86
|
+
.split('\n')
|
|
87
|
+
.map((line) => line.trim())
|
|
88
|
+
.filter((line) => line.startsWith('at '))
|
|
89
|
+
.slice(0, 4)
|
|
90
|
+
.map((line) => line
|
|
91
|
+
.replace(/(?:[A-Za-z]:)?[\\/][^\s()]+[\\/]([^\\/()]+)/g, '$1')
|
|
92
|
+
.replace(/:\d+:\d+/g, ':?:?')
|
|
93
|
+
.replace(/\b\d+\b/g, '?')
|
|
94
|
+
.replace(/\s+/g, ' '));
|
|
95
|
+
return frames.join('|').slice(0, 600);
|
|
96
|
+
}
|
|
97
|
+
export function errorOperationCategory(error) {
|
|
98
|
+
if (error.routeType)
|
|
99
|
+
return error.routeType.toLowerCase().slice(0, 80);
|
|
100
|
+
const text = `${error.name} ${error.message}`.toLowerCase();
|
|
101
|
+
if (/(sql|database|drizzle|prisma|sqlite|postgres|mysql)/.test(text))
|
|
102
|
+
return 'database';
|
|
103
|
+
if (/(fetch|network|http|socket|econn)/.test(text))
|
|
104
|
+
return 'http';
|
|
105
|
+
if (/(render|component|jsx|hydration)/.test(text))
|
|
106
|
+
return 'render';
|
|
107
|
+
return 'runtime';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Route-independent error signature. Routes and methods are deliberately
|
|
111
|
+
* excluded so the same failure can be grouped across entry points.
|
|
112
|
+
*/
|
|
113
|
+
export function diagnosticErrorFingerprint(error) {
|
|
114
|
+
return fingerprint([
|
|
115
|
+
'error-fingerprint',
|
|
116
|
+
error.name.toLowerCase().slice(0, 200),
|
|
117
|
+
normalizeErrorMessage(error.message),
|
|
118
|
+
stackSignature(error.stack),
|
|
119
|
+
errorOperationCategory(error),
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
export function diagnosticFindingId(ruleId, findingFingerprint, scope = '') {
|
|
123
|
+
const first = fingerprint([ruleId, findingFingerprint]);
|
|
124
|
+
const second = fingerprint([ruleId, findingFingerprint, scope]);
|
|
125
|
+
return `diag-${first}-${second}`;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=fingerprints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprints.js","sourceRoot":"","sources":["../src/fingerprints.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DiagnosticFinding, type DiagnosticSnapshot, type StackNextConfig, type RequestSummary } from '@stacknext/core';
|
|
2
|
+
export interface DiagnosticRuleContext {
|
|
3
|
+
snapshot: DiagnosticSnapshot;
|
|
4
|
+
config: StackNextConfig['diagnostics'];
|
|
5
|
+
requestByTrace: Map<string, RequestSummary>;
|
|
6
|
+
}
|
|
7
|
+
export declare function evaluateDiagnosticRules(context: DiagnosticRuleContext): DiagnosticFinding[];
|
|
8
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,iBAAiB,EAEtB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAIpB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAA;AAkBxB,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,MAAM,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IACtC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;CAC5C;AAsZD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,iBAAiB,EAAE,CAkB3F"}
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { DIAGNOSTIC_RULES, fingerprint, redactText, stripUrlQueryInText, } from '@stacknext/core';
|
|
2
|
+
import { diagnosticSpanCategory, safeRoute, safeSpanName, spanEvidence, spanHttpEndpoint, spanHttpMethod, spanThresholdMs, } from './classify.js';
|
|
3
|
+
import { diagnosticErrorFingerprint, diagnosticFindingId, errorOperationCategory, sqlFingerprint, sqlPreview, } from './fingerprints.js';
|
|
4
|
+
const RULE_VERSION = 1;
|
|
5
|
+
const SAMPLE_LIMITATION = 'Only the current in-memory retention window is analyzed; findings disappear when evidence ages out.';
|
|
6
|
+
const NULL_DURATION_LIMITATION = 'Database durations can be null when an adapter does not measure them. Null is never treated as zero.';
|
|
7
|
+
function ruleSummary(ruleId) {
|
|
8
|
+
const rule = DIAGNOSTIC_RULES.find((entry) => entry.id === ruleId);
|
|
9
|
+
if (!rule)
|
|
10
|
+
throw new Error(`Unknown diagnostic rule: ${ruleId}`);
|
|
11
|
+
return rule;
|
|
12
|
+
}
|
|
13
|
+
function roundMs(value) {
|
|
14
|
+
if (!Number.isFinite(value))
|
|
15
|
+
return 0;
|
|
16
|
+
return Math.round(value * 10) / 10;
|
|
17
|
+
}
|
|
18
|
+
function formatTimestamp(value) {
|
|
19
|
+
const date = new Date(value);
|
|
20
|
+
return Number.isNaN(date.getTime()) ? 'unknown time' : date.toISOString();
|
|
21
|
+
}
|
|
22
|
+
function shortText(value, maxLength) {
|
|
23
|
+
if (value.length <= maxLength)
|
|
24
|
+
return value;
|
|
25
|
+
return `${value.slice(0, maxLength)}…`;
|
|
26
|
+
}
|
|
27
|
+
function queryEvidence(query) {
|
|
28
|
+
return {
|
|
29
|
+
id: query.id,
|
|
30
|
+
traceId: query.traceId ?? null,
|
|
31
|
+
...(query.spanId ? { spanId: query.spanId } : {}),
|
|
32
|
+
sqlFingerprint: sqlFingerprint(query.sql),
|
|
33
|
+
sqlPreview: sqlPreview(query.sql),
|
|
34
|
+
durationMs: query.durationMs,
|
|
35
|
+
databaseSystem: query.databaseSystem,
|
|
36
|
+
...(query.operation ? { operation: query.operation } : {}),
|
|
37
|
+
success: query.success,
|
|
38
|
+
timestamp: query.timestamp,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function errorEvidence(error, groupFingerprint) {
|
|
42
|
+
const route = safeRoute(error.route);
|
|
43
|
+
return {
|
|
44
|
+
id: error.id,
|
|
45
|
+
name: shortText(stripUrlQueryInText(redactText(error.name, 200)), 200),
|
|
46
|
+
message: shortText(stripUrlQueryInText(redactText(error.message, 600)), 600),
|
|
47
|
+
timestamp: error.timestamp,
|
|
48
|
+
...(route ? { route } : {}),
|
|
49
|
+
...(error.method ? { method: error.method } : {}),
|
|
50
|
+
traceId: error.resolvedTraceId ?? error.traceId ?? null,
|
|
51
|
+
fingerprint: groupFingerprint,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function routeForTrace(context, traceId) {
|
|
55
|
+
return safeRoute(context.requestByTrace.get(traceId)?.route);
|
|
56
|
+
}
|
|
57
|
+
function methodForTrace(context, traceId) {
|
|
58
|
+
return context.requestByTrace.get(traceId)?.method ?? null;
|
|
59
|
+
}
|
|
60
|
+
function nPlusOneFindings(context) {
|
|
61
|
+
const groups = new Map();
|
|
62
|
+
for (const query of context.snapshot.databaseQueries) {
|
|
63
|
+
const traceId = query.resolvedTraceId;
|
|
64
|
+
if (!traceId)
|
|
65
|
+
continue;
|
|
66
|
+
const shape = sqlFingerprint(query.sql);
|
|
67
|
+
if (shape === '')
|
|
68
|
+
continue;
|
|
69
|
+
const key = `${traceId}\u0000${query.databaseSystem.toLowerCase()}\u0000${shape}`;
|
|
70
|
+
const existing = groups.get(key);
|
|
71
|
+
if (existing)
|
|
72
|
+
existing.queries.push(query);
|
|
73
|
+
else {
|
|
74
|
+
groups.set(key, {
|
|
75
|
+
traceId,
|
|
76
|
+
databaseSystem: query.databaseSystem,
|
|
77
|
+
shape,
|
|
78
|
+
queries: [query],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const findings = [];
|
|
83
|
+
for (const group of groups.values()) {
|
|
84
|
+
if (group.queries.length < context.config.repeatedQueryMin)
|
|
85
|
+
continue;
|
|
86
|
+
const queries = [...group.queries].sort((a, b) => a.timestamp - b.timestamp);
|
|
87
|
+
const first = queries[0];
|
|
88
|
+
const last = queries[queries.length - 1];
|
|
89
|
+
if (!first || !last)
|
|
90
|
+
continue;
|
|
91
|
+
const rule = ruleSummary('n-plus-one-queries');
|
|
92
|
+
const route = safeRoute(first.route) ?? routeForTrace(context, group.traceId);
|
|
93
|
+
const method = methodForTrace(context, group.traceId);
|
|
94
|
+
const findingFingerprint = fingerprint([
|
|
95
|
+
'n-plus-one-queries',
|
|
96
|
+
group.databaseSystem.toLowerCase(),
|
|
97
|
+
group.shape,
|
|
98
|
+
]);
|
|
99
|
+
const preview = shortText(group.shape, 180);
|
|
100
|
+
findings.push({
|
|
101
|
+
id: diagnosticFindingId(rule.id, findingFingerprint, group.traceId),
|
|
102
|
+
ruleId: rule.id,
|
|
103
|
+
ruleVersion: RULE_VERSION,
|
|
104
|
+
category: rule.category,
|
|
105
|
+
severity: rule.severity,
|
|
106
|
+
status: 'possible',
|
|
107
|
+
title: `Possible N+1 queries: ${queries.length} similar ${group.databaseSystem} queries`,
|
|
108
|
+
summary: `${queries.length} queries with the same normalized SQL shape ran in one trace: ${preview}`,
|
|
109
|
+
detail: `The same normalized SQL shape ran ${queries.length} times in trace ${group.traceId}${route ? ` (${route})` : ''}. This is consistent with a per-item query loop, but StackNext cannot confirm an N+1 bug without reviewing the calling code.`,
|
|
110
|
+
route,
|
|
111
|
+
method,
|
|
112
|
+
traceIds: [group.traceId],
|
|
113
|
+
firstSeen: first.timestamp,
|
|
114
|
+
lastSeen: last.timestamp,
|
|
115
|
+
fingerprint: findingFingerprint,
|
|
116
|
+
evidence: {
|
|
117
|
+
traceIds: [group.traceId],
|
|
118
|
+
spans: [],
|
|
119
|
+
queries: queries.slice(0, 10).map(queryEvidence),
|
|
120
|
+
errors: [],
|
|
121
|
+
measurements: [
|
|
122
|
+
{ label: 'matching queries', value: queries.length },
|
|
123
|
+
{ label: 'threshold', value: context.config.repeatedQueryMin },
|
|
124
|
+
{ label: 'database system', value: group.databaseSystem },
|
|
125
|
+
{ label: 'normalized SQL shape', value: preview },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
verification: [
|
|
129
|
+
'Confirm that the query count matches a list size, page size or other per-item loop.',
|
|
130
|
+
'Inspect the calling code and ORM loading strategy; StackNext does not read source or run EXPLAIN.',
|
|
131
|
+
'Compare the behavior with a batched or joined query and verify the count drops.',
|
|
132
|
+
],
|
|
133
|
+
limitations: [
|
|
134
|
+
SAMPLE_LIMITATION,
|
|
135
|
+
NULL_DURATION_LIMITATION,
|
|
136
|
+
'SQL bind values are never captured; the fingerprint replaces literals and parameters.',
|
|
137
|
+
'Repeated query shapes can be intentional, for example when fetching independent resources.',
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return findings;
|
|
142
|
+
}
|
|
143
|
+
function slowSpanFindings(context) {
|
|
144
|
+
const findings = [];
|
|
145
|
+
for (const span of context.snapshot.spans) {
|
|
146
|
+
if (!Number.isFinite(span.durationMs) || span.durationMs <= 0)
|
|
147
|
+
continue;
|
|
148
|
+
const category = diagnosticSpanCategory(span);
|
|
149
|
+
const threshold = spanThresholdMs(category, context.config);
|
|
150
|
+
if (span.durationMs < threshold)
|
|
151
|
+
continue;
|
|
152
|
+
const rule = ruleSummary('slow-span');
|
|
153
|
+
const request = context.requestByTrace.get(span.traceId);
|
|
154
|
+
const route = safeRoute(span.route) ?? routeForTrace(context, span.traceId);
|
|
155
|
+
const method = span.method ?? request?.method ?? null;
|
|
156
|
+
const findingFingerprint = fingerprint([
|
|
157
|
+
'slow-span',
|
|
158
|
+
category,
|
|
159
|
+
span.name,
|
|
160
|
+
span.nextSpanType ?? '',
|
|
161
|
+
]);
|
|
162
|
+
const duration = roundMs(span.durationMs);
|
|
163
|
+
const displayName = safeSpanName(span.name);
|
|
164
|
+
const evidenceSpan = spanEvidence(span, category);
|
|
165
|
+
const relatedQueries = context.snapshot.databaseQueries
|
|
166
|
+
.filter((query) => query.spanId === span.spanId && query.traceId === span.traceId)
|
|
167
|
+
.slice(0, 5)
|
|
168
|
+
.map(queryEvidence);
|
|
169
|
+
findings.push({
|
|
170
|
+
id: diagnosticFindingId(rule.id, findingFingerprint, `${span.traceId}:${span.spanId}`),
|
|
171
|
+
ruleId: rule.id,
|
|
172
|
+
ruleVersion: RULE_VERSION,
|
|
173
|
+
category: rule.category,
|
|
174
|
+
severity: rule.severity,
|
|
175
|
+
status: 'observed',
|
|
176
|
+
title: `Slow ${category} span: ${shortText(displayName, 120)}`,
|
|
177
|
+
summary: `${duration} ms exceeded the ${threshold} ms ${category} threshold in trace ${span.traceId}.`,
|
|
178
|
+
detail: `Span ${displayName} (${span.spanId}) took ${duration} ms, above the configured ${threshold} ms threshold for ${category} spans. Span duration includes child work, so it must not be summed with overlapping spans.`,
|
|
179
|
+
route,
|
|
180
|
+
method,
|
|
181
|
+
traceIds: [span.traceId],
|
|
182
|
+
firstSeen: span.startTime,
|
|
183
|
+
lastSeen: span.startTime,
|
|
184
|
+
fingerprint: findingFingerprint,
|
|
185
|
+
evidence: {
|
|
186
|
+
traceIds: [span.traceId],
|
|
187
|
+
spans: [evidenceSpan],
|
|
188
|
+
queries: relatedQueries,
|
|
189
|
+
errors: [],
|
|
190
|
+
measurements: [
|
|
191
|
+
{ label: 'duration', value: duration, unit: 'ms' },
|
|
192
|
+
{ label: 'threshold', value: threshold, unit: 'ms' },
|
|
193
|
+
{ label: 'span category', value: category },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
verification: [
|
|
197
|
+
'Confirm the duration was measured rather than estimated.',
|
|
198
|
+
'Check whether child spans, queueing or cold starts explain the duration.',
|
|
199
|
+
'Reproduce under the same conditions before changing code or infrastructure.',
|
|
200
|
+
],
|
|
201
|
+
limitations: [
|
|
202
|
+
SAMPLE_LIMITATION,
|
|
203
|
+
'Span duration includes child work and is not additive across overlapping spans.',
|
|
204
|
+
'A threshold crossing is evidence of slow work, not proof of the cause.',
|
|
205
|
+
'Clock skew between processes can distort span timing.',
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return findings;
|
|
210
|
+
}
|
|
211
|
+
function repeatedHttpFindings(context) {
|
|
212
|
+
const groups = new Map();
|
|
213
|
+
for (const span of context.snapshot.spans) {
|
|
214
|
+
if (diagnosticSpanCategory(span) !== 'http')
|
|
215
|
+
continue;
|
|
216
|
+
const endpoint = spanHttpEndpoint(span);
|
|
217
|
+
if (!endpoint)
|
|
218
|
+
continue;
|
|
219
|
+
const method = spanHttpMethod(span) ?? 'UNKNOWN';
|
|
220
|
+
const key = `${span.traceId}\u0000${method}\u0000${endpoint}`;
|
|
221
|
+
const existing = groups.get(key);
|
|
222
|
+
if (existing)
|
|
223
|
+
existing.spans.push(span);
|
|
224
|
+
else
|
|
225
|
+
groups.set(key, { traceId: span.traceId, method, endpoint, spans: [span] });
|
|
226
|
+
}
|
|
227
|
+
const findings = [];
|
|
228
|
+
for (const group of groups.values()) {
|
|
229
|
+
if (group.spans.length < context.config.repeatedHttpMin)
|
|
230
|
+
continue;
|
|
231
|
+
const spans = [...group.spans].sort((a, b) => a.startTime - b.startTime);
|
|
232
|
+
const first = spans[0];
|
|
233
|
+
const last = spans[spans.length - 1];
|
|
234
|
+
if (!first || !last)
|
|
235
|
+
continue;
|
|
236
|
+
const rule = ruleSummary('repeated-external-http');
|
|
237
|
+
const request = context.requestByTrace.get(group.traceId);
|
|
238
|
+
const route = routeForTrace(context, group.traceId);
|
|
239
|
+
const method = request?.method ?? null;
|
|
240
|
+
const findingFingerprint = fingerprint([
|
|
241
|
+
'repeated-external-http',
|
|
242
|
+
group.method,
|
|
243
|
+
group.endpoint,
|
|
244
|
+
]);
|
|
245
|
+
findings.push({
|
|
246
|
+
id: diagnosticFindingId(rule.id, findingFingerprint, group.traceId),
|
|
247
|
+
ruleId: rule.id,
|
|
248
|
+
ruleVersion: RULE_VERSION,
|
|
249
|
+
category: rule.category,
|
|
250
|
+
severity: rule.severity,
|
|
251
|
+
status: 'possible',
|
|
252
|
+
title: `Repeated HTTP calls: ${group.method} ${shortText(group.endpoint, 140)}`,
|
|
253
|
+
summary: `${spans.length} outbound ${group.method} calls to ${group.endpoint} ran in trace ${group.traceId}.`,
|
|
254
|
+
detail: `The same outbound endpoint was called ${spans.length} times in one trace. This may indicate missing caching or request deduplication, but repeated calls can be intentional.`,
|
|
255
|
+
route,
|
|
256
|
+
method,
|
|
257
|
+
traceIds: [group.traceId],
|
|
258
|
+
firstSeen: first.startTime,
|
|
259
|
+
lastSeen: last.startTime,
|
|
260
|
+
fingerprint: findingFingerprint,
|
|
261
|
+
evidence: {
|
|
262
|
+
traceIds: [group.traceId],
|
|
263
|
+
spans: spans.slice(0, 10).map((span) => spanEvidence(span, 'http')),
|
|
264
|
+
queries: [],
|
|
265
|
+
errors: [],
|
|
266
|
+
measurements: [
|
|
267
|
+
{ label: 'matching calls', value: spans.length },
|
|
268
|
+
{ label: 'threshold', value: context.config.repeatedHttpMin },
|
|
269
|
+
{ label: 'method', value: group.method },
|
|
270
|
+
{ label: 'endpoint', value: group.endpoint },
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
verification: [
|
|
274
|
+
'Confirm the calls target the same logical resource and are not intentionally independent.',
|
|
275
|
+
'Check whether caching, request deduplication or batching is appropriate.',
|
|
276
|
+
'Verify the endpoint count changes after the fix.',
|
|
277
|
+
],
|
|
278
|
+
limitations: [
|
|
279
|
+
SAMPLE_LIMITATION,
|
|
280
|
+
'Only fetch spans that StackNext receives can be counted; missing instrumentation hides calls.',
|
|
281
|
+
'Query strings and credentials are removed, so different query values share one endpoint.',
|
|
282
|
+
'Repeated calls may be intentional, for example retries or independent resources.',
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return findings;
|
|
287
|
+
}
|
|
288
|
+
function errorGroupFindings(context) {
|
|
289
|
+
if (!context.config.errorGrouping)
|
|
290
|
+
return [];
|
|
291
|
+
const groups = new Map();
|
|
292
|
+
for (const error of context.snapshot.errors) {
|
|
293
|
+
const key = diagnosticErrorFingerprint(error);
|
|
294
|
+
const existing = groups.get(key);
|
|
295
|
+
if (existing)
|
|
296
|
+
existing.push(error);
|
|
297
|
+
else
|
|
298
|
+
groups.set(key, [error]);
|
|
299
|
+
}
|
|
300
|
+
const findings = [];
|
|
301
|
+
for (const [groupFingerprint, errors] of groups.entries()) {
|
|
302
|
+
if (errors.length < context.config.errorGroupMin)
|
|
303
|
+
continue;
|
|
304
|
+
const sorted = [...errors].sort((a, b) => b.timestamp - a.timestamp);
|
|
305
|
+
const latest = sorted[0];
|
|
306
|
+
if (!latest)
|
|
307
|
+
continue;
|
|
308
|
+
const rule = ruleSummary('error-fingerprint');
|
|
309
|
+
const routes = [
|
|
310
|
+
...new Set(sorted
|
|
311
|
+
.map((error) => error.route ?? error.requestPath ?? null)
|
|
312
|
+
.filter((route) => Boolean(route))
|
|
313
|
+
.map((route) => safeRoute(route))
|
|
314
|
+
.filter((route) => Boolean(route))),
|
|
315
|
+
];
|
|
316
|
+
const methods = [
|
|
317
|
+
...new Set(sorted
|
|
318
|
+
.map((error) => error.method ?? null)
|
|
319
|
+
.filter((method) => Boolean(method))),
|
|
320
|
+
];
|
|
321
|
+
const route = routes.length === 1 ? routes[0] ?? null : null;
|
|
322
|
+
const method = methods.length === 1 ? methods[0] ?? null : null;
|
|
323
|
+
const traceIds = [
|
|
324
|
+
...new Set(sorted
|
|
325
|
+
.map((error) => error.resolvedTraceId ?? error.traceId ?? null)
|
|
326
|
+
.filter((traceId) => Boolean(traceId))),
|
|
327
|
+
];
|
|
328
|
+
const traceLastSeen = new Map();
|
|
329
|
+
for (const error of sorted) {
|
|
330
|
+
const traceId = error.resolvedTraceId ?? error.traceId ?? null;
|
|
331
|
+
if (!traceId)
|
|
332
|
+
continue;
|
|
333
|
+
const previous = traceLastSeen.get(traceId);
|
|
334
|
+
if (previous === undefined || error.timestamp > previous) {
|
|
335
|
+
traceLastSeen.set(traceId, error.timestamp);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
traceIds.sort((a, b) => (traceLastSeen.get(b) ?? 0) - (traceLastSeen.get(a) ?? 0) || a.localeCompare(b));
|
|
339
|
+
const operation = errorOperationCategory(latest);
|
|
340
|
+
const safeName = shortText(stripUrlQueryInText(redactText(latest.name, 200)), 120);
|
|
341
|
+
findings.push({
|
|
342
|
+
id: diagnosticFindingId(rule.id, groupFingerprint),
|
|
343
|
+
ruleId: rule.id,
|
|
344
|
+
ruleVersion: RULE_VERSION,
|
|
345
|
+
category: rule.category,
|
|
346
|
+
severity: rule.severity,
|
|
347
|
+
status: 'observed',
|
|
348
|
+
title: `Repeated error: ${safeName}`,
|
|
349
|
+
summary: `${errors.length} occurrences across ${traceIds.length} trace${traceIds.length === 1 ? '' : 's'} share a route-independent error signature.`,
|
|
350
|
+
detail: `The latest occurrence is ${latest.id} at ${formatTimestamp(latest.timestamp)}. Grouping uses the normalized error name, message, stack shape and operation category (${operation}), not the route. Grouping is heuristic and does not establish a root cause.`,
|
|
351
|
+
route,
|
|
352
|
+
method,
|
|
353
|
+
traceIds,
|
|
354
|
+
firstSeen: Math.min(...errors.map((error) => error.timestamp)),
|
|
355
|
+
lastSeen: latest.timestamp,
|
|
356
|
+
fingerprint: groupFingerprint,
|
|
357
|
+
evidence: {
|
|
358
|
+
traceIds,
|
|
359
|
+
spans: [],
|
|
360
|
+
queries: [],
|
|
361
|
+
errors: sorted.slice(0, 10).map((error) => errorEvidence(error, groupFingerprint)),
|
|
362
|
+
measurements: [
|
|
363
|
+
{ label: 'occurrences', value: errors.length },
|
|
364
|
+
{ label: 'threshold', value: context.config.errorGroupMin },
|
|
365
|
+
{ label: 'traces', value: traceIds.length },
|
|
366
|
+
{ label: 'operation category', value: operation },
|
|
367
|
+
{ label: 'latest error id', value: latest.id },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
verification: [
|
|
371
|
+
'Confirm the grouped occurrences share the same code path and failure mode.',
|
|
372
|
+
'Inspect the latest trace and error record before changing code.',
|
|
373
|
+
'After a fix, verify the group stops growing in a fresh sample window.',
|
|
374
|
+
],
|
|
375
|
+
limitations: [
|
|
376
|
+
SAMPLE_LIMITATION,
|
|
377
|
+
'Grouping is heuristic; different root causes can share a normalized message or stack shape.',
|
|
378
|
+
'Redaction and truncation can reduce the specificity of error signatures.',
|
|
379
|
+
'Routes and methods are intentionally excluded from the group fingerprint.',
|
|
380
|
+
],
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return findings;
|
|
384
|
+
}
|
|
385
|
+
export function evaluateDiagnosticRules(context) {
|
|
386
|
+
const findings = [
|
|
387
|
+
...nPlusOneFindings(context),
|
|
388
|
+
...slowSpanFindings(context),
|
|
389
|
+
...repeatedHttpFindings(context),
|
|
390
|
+
...errorGroupFindings(context),
|
|
391
|
+
];
|
|
392
|
+
const severityRank = {
|
|
393
|
+
error: 0,
|
|
394
|
+
warning: 1,
|
|
395
|
+
info: 2,
|
|
396
|
+
};
|
|
397
|
+
return findings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity] ||
|
|
398
|
+
b.lastSeen - a.lastSeen ||
|
|
399
|
+
a.id.localeCompare(b.id));
|
|
400
|
+
}
|
|
401
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacknext/diagnostics",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Deterministic, evidence-backed runtime diagnostics for StackNext telemetry",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@stacknext/core": "0.6.1"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.9.0"
|
|
25
|
+
},
|
|
26
|
+
"author": "StackNext contributors",
|
|
27
|
+
"homepage": "https://github.com/marrrkkk/stacknext#readme",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/marrrkkk/stacknext.git",
|
|
31
|
+
"directory": "packages/diagnostics"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/marrrkkk/stacknext/issues"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"stacknext",
|
|
38
|
+
"nextjs",
|
|
39
|
+
"diagnostics",
|
|
40
|
+
"n-plus-one",
|
|
41
|
+
"observability",
|
|
42
|
+
"debugging"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.build.json",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
50
|
+
}
|
|
6
51
|
}
|