@debugbundle/cli 0.1.2 → 0.1.3
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/main.cjs +240 -45
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -14483,6 +14483,8 @@ var CaptureBreadcrumbsValues = ["local_only", "exception_only", "standalone"];
|
|
|
14483
14483
|
var CaptureBreadcrumbsSchema = external_exports.enum(CaptureBreadcrumbsValues);
|
|
14484
14484
|
var CaptureProbeEventsValues = ["buffer_only", "standalone_when_activated"];
|
|
14485
14485
|
var CaptureProbeEventsSchema = external_exports.enum(CaptureProbeEventsValues);
|
|
14486
|
+
var RequestSignalClassificationValues = ["incident_signal", "context_signal"];
|
|
14487
|
+
var RequestSignalClassificationSchema = external_exports.enum(RequestSignalClassificationValues);
|
|
14486
14488
|
var CapturePolicySchema = external_exports.object({
|
|
14487
14489
|
project_id: external_exports.string().uuid(),
|
|
14488
14490
|
preset: CapturePresetSchema,
|
|
@@ -14499,6 +14501,55 @@ var CapturePolicyUpdateSchema = external_exports.object({
|
|
|
14499
14501
|
capture_breadcrumbs: CaptureBreadcrumbsSchema.nullable().optional(),
|
|
14500
14502
|
capture_probe_events: CaptureProbeEventsSchema.nullable().optional()
|
|
14501
14503
|
});
|
|
14504
|
+
var BALANCED_IMMEDIATE_REQUEST_STATUSES = /* @__PURE__ */ new Set([408, 423, 424, 425, 429]);
|
|
14505
|
+
var INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES = /* @__PURE__ */ new Set([...BALANCED_IMMEDIATE_REQUEST_STATUSES, 409]);
|
|
14506
|
+
var BALANCED_STANDARD_ANOMALY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 409, 422]);
|
|
14507
|
+
var BALANCED_HIGH_VOLUME_ANOMALY_STATUSES = /* @__PURE__ */ new Set([400, 410]);
|
|
14508
|
+
var INVESTIGATIVE_ANOMALY_STATUSES = /* @__PURE__ */ new Set([...BALANCED_STANDARD_ANOMALY_STATUSES, ...BALANCED_HIGH_VOLUME_ANOMALY_STATUSES]);
|
|
14509
|
+
function classifyRequestStatus(input) {
|
|
14510
|
+
const { responseStatus, capturePreset } = input;
|
|
14511
|
+
if (responseStatus === null || !Number.isFinite(responseStatus)) {
|
|
14512
|
+
return "context_signal";
|
|
14513
|
+
}
|
|
14514
|
+
if (responseStatus >= 500) {
|
|
14515
|
+
return "incident_signal";
|
|
14516
|
+
}
|
|
14517
|
+
if (capturePreset === "investigative") {
|
|
14518
|
+
return INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES.has(responseStatus) ? "incident_signal" : "context_signal";
|
|
14519
|
+
}
|
|
14520
|
+
if (capturePreset === "balanced") {
|
|
14521
|
+
return BALANCED_IMMEDIATE_REQUEST_STATUSES.has(responseStatus) ? "incident_signal" : "context_signal";
|
|
14522
|
+
}
|
|
14523
|
+
return "context_signal";
|
|
14524
|
+
}
|
|
14525
|
+
function getRequestAnomalyThreshold(input) {
|
|
14526
|
+
const { responseStatus, capturePreset } = input;
|
|
14527
|
+
if (responseStatus === null || !Number.isFinite(responseStatus) || responseStatus < 400 || responseStatus >= 500) {
|
|
14528
|
+
return null;
|
|
14529
|
+
}
|
|
14530
|
+
if (capturePreset === "minimal") {
|
|
14531
|
+
return null;
|
|
14532
|
+
}
|
|
14533
|
+
if (capturePreset === "investigative") {
|
|
14534
|
+
return INVESTIGATIVE_ANOMALY_STATUSES.has(responseStatus) ? {
|
|
14535
|
+
minimum_occurrences_5m: 8,
|
|
14536
|
+
minimum_ratio_5m_to_1h: 2
|
|
14537
|
+
} : null;
|
|
14538
|
+
}
|
|
14539
|
+
if (BALANCED_STANDARD_ANOMALY_STATUSES.has(responseStatus)) {
|
|
14540
|
+
return {
|
|
14541
|
+
minimum_occurrences_5m: 20,
|
|
14542
|
+
minimum_ratio_5m_to_1h: 3
|
|
14543
|
+
};
|
|
14544
|
+
}
|
|
14545
|
+
if (BALANCED_HIGH_VOLUME_ANOMALY_STATUSES.has(responseStatus)) {
|
|
14546
|
+
return {
|
|
14547
|
+
minimum_occurrences_5m: 50,
|
|
14548
|
+
minimum_ratio_5m_to_1h: 5
|
|
14549
|
+
};
|
|
14550
|
+
}
|
|
14551
|
+
return null;
|
|
14552
|
+
}
|
|
14502
14553
|
|
|
14503
14554
|
// ../../packages/shared-types/src/index.ts
|
|
14504
14555
|
function createUuidV4() {
|
|
@@ -15124,7 +15175,7 @@ function buildCliReference() {
|
|
|
15124
15175
|
"- `debugbundle ingest <file> --format <format> [--json]`",
|
|
15125
15176
|
"- `debugbundle watch --log <file> --format <format> [--json]`",
|
|
15126
15177
|
"- `debugbundle watch --cloud --log <file> --format <format> [--json]`",
|
|
15127
|
-
"- `debugbundle process [--json]`",
|
|
15178
|
+
"- `debugbundle process [--preset <minimal|balanced|investigative>] [--json]`",
|
|
15128
15179
|
"",
|
|
15129
15180
|
"## Investigation",
|
|
15130
15181
|
"",
|
|
@@ -15670,7 +15721,7 @@ var import_node_path4 = require("node:path");
|
|
|
15670
15721
|
|
|
15671
15722
|
// ../../packages/retrieval-client/src/index.ts
|
|
15672
15723
|
var IncidentReasonSchema = external_exports.object({
|
|
15673
|
-
kind: external_exports.enum(["backend_exception", "frontend_exception", "
|
|
15724
|
+
kind: external_exports.enum(["backend_exception", "frontend_exception", "request_failure", "error_log"]),
|
|
15674
15725
|
description: external_exports.string(),
|
|
15675
15726
|
event_type: external_exports.enum(["backend_exception", "frontend_exception", "request_event", "log_event"]),
|
|
15676
15727
|
event_class: external_exports.literal("incident_signal"),
|
|
@@ -16091,7 +16142,8 @@ function buildRedactionRecord(bundleBody) {
|
|
|
16091
16142
|
function buildVisibilityRecord(input) {
|
|
16092
16143
|
const routeTarget = input.primarySignal.route_template ?? input.primarySignal.request_path;
|
|
16093
16144
|
const matchedFields = input.incident.matched_fields.length === 0 ? "none" : input.incident.matched_fields.join(", ");
|
|
16094
|
-
const
|
|
16145
|
+
const isRequestAnomaly = input.incident.matched_fields.includes("request_anomaly");
|
|
16146
|
+
const grouping = input.primarySignal.kind === "request_failure" && input.primarySignal.request_method !== null && routeTarget !== null ? isRequestAnomaly ? `Repeated request-anomaly incidents with the same normalized route template, request method, response status, service, and environment reuse this incident fingerprint once the anomaly threshold fires. This incident currently groups ${input.primarySignal.request_method} ${routeTarget} with matched fields ${matchedFields}.` : `Repeated request-failure incidents with the same normalized route template, request method, response status, service, and environment reuse this incident fingerprint. This incident currently groups ${input.primarySignal.request_method} ${routeTarget} with matched fields ${matchedFields}.` : `This incident groups repeated failures by fingerprint version ${input.incident.fingerprint_version} inside the service and environment boundary, with matched fields ${matchedFields}.`;
|
|
16095
16147
|
const spikeLead = input.incident.spike_detected_at === void 0 || input.incident.spike_detected_at === null ? "This incident is not currently marked as spiking." : `This incident was marked as spiking at ${input.incident.spike_detected_at}.`;
|
|
16096
16148
|
return {
|
|
16097
16149
|
grouping,
|
|
@@ -16102,15 +16154,16 @@ function buildVisibilityRecord(input) {
|
|
|
16102
16154
|
}
|
|
16103
16155
|
function buildSuggestedNextChecks(input) {
|
|
16104
16156
|
const suggestions = [];
|
|
16157
|
+
const isRequestAnomaly = input.incident.matched_fields.includes("request_anomaly");
|
|
16105
16158
|
if (input.bundle.status === "pending") {
|
|
16106
16159
|
suggestions.push("Wait for bundle generation to finish, then rerun the incident context command.");
|
|
16107
16160
|
} else if (input.bundle.status === "failed") {
|
|
16108
16161
|
suggestions.push("Inspect bundle generation status or retry bundle retrieval to recover missing context.");
|
|
16109
16162
|
}
|
|
16110
16163
|
const routeTarget = input.primarySignal.route_template ?? input.primarySignal.request_path;
|
|
16111
|
-
if (input.primarySignal.
|
|
16164
|
+
if (input.primarySignal.kind === "request_failure" && input.primarySignal.request_method !== null && routeTarget !== null) {
|
|
16112
16165
|
suggestions.push(
|
|
16113
|
-
`Inspect the ${input.primarySignal.request_method} ${routeTarget
|
|
16166
|
+
isRequestAnomaly ? `Inspect the ${input.primarySignal.request_method} ${routeTarget} handler behind this repeated request-anomaly path.` : `Inspect the ${input.primarySignal.request_method} ${routeTarget} handler behind this request-failure path.`
|
|
16114
16167
|
);
|
|
16115
16168
|
}
|
|
16116
16169
|
const firstApplicationFrame = input.primarySignal.first_application_frame;
|
|
@@ -16199,12 +16252,13 @@ function deriveIncidentReasonFromSignal(input) {
|
|
|
16199
16252
|
};
|
|
16200
16253
|
case "request_event": {
|
|
16201
16254
|
const responseStatus = typeof input.response_status === "number" && Number.isFinite(input.response_status) ? input.response_status : null;
|
|
16255
|
+
const isRequestAnomaly = input.request_anomaly === true;
|
|
16202
16256
|
return {
|
|
16203
|
-
kind: "
|
|
16204
|
-
description: responseStatus !== null
|
|
16257
|
+
kind: "request_failure",
|
|
16258
|
+
description: isRequestAnomaly ? responseStatus !== null ? `request_event response_status=${responseStatus} crossed the repeated request anomaly threshold` : "request_event crossed the repeated request anomaly threshold" : responseStatus !== null ? `request_event response_status=${responseStatus} matched the immediate request failure incident rule` : "request_event matched the immediate request failure incident rule",
|
|
16205
16259
|
event_type: "request_event",
|
|
16206
16260
|
event_class: "incident_signal",
|
|
16207
|
-
matched_policy: "
|
|
16261
|
+
matched_policy: isRequestAnomaly ? "Repeated contextual request failures crossed the request anomaly threshold" : "Immediate request failure statuses bypass capture_request_events suppression"
|
|
16208
16262
|
};
|
|
16209
16263
|
}
|
|
16210
16264
|
case "log_event": {
|
|
@@ -16511,7 +16565,7 @@ function getRequestResponseStatus(payload) {
|
|
|
16511
16565
|
const status = payload?.["response_status"];
|
|
16512
16566
|
return typeof status === "number" && Number.isFinite(status) ? status : null;
|
|
16513
16567
|
}
|
|
16514
|
-
function classifyEvent(eventType, logLevel, probeActivationId, payload) {
|
|
16568
|
+
function classifyEvent(eventType, logLevel, probeActivationId, payload, capturePreset = "minimal") {
|
|
16515
16569
|
switch (eventType) {
|
|
16516
16570
|
case "backend_exception":
|
|
16517
16571
|
case "frontend_exception":
|
|
@@ -16523,10 +16577,7 @@ function classifyEvent(eventType, logLevel, probeActivationId, payload) {
|
|
|
16523
16577
|
return "context_signal";
|
|
16524
16578
|
case "request_event": {
|
|
16525
16579
|
const responseStatus = getRequestResponseStatus(payload);
|
|
16526
|
-
|
|
16527
|
-
return "incident_signal";
|
|
16528
|
-
}
|
|
16529
|
-
return "context_signal";
|
|
16580
|
+
return classifyRequestStatus({ responseStatus, capturePreset });
|
|
16530
16581
|
}
|
|
16531
16582
|
case "frontend_breadcrumb":
|
|
16532
16583
|
case "deploy_metadata":
|
|
@@ -16596,6 +16647,13 @@ var STORAGE_SCHEMA_MIGRATIONS = [
|
|
|
16596
16647
|
ON github_device_authorizations (expires_at)
|
|
16597
16648
|
`
|
|
16598
16649
|
]
|
|
16650
|
+
}),
|
|
16651
|
+
defineStorageSchemaMigration({
|
|
16652
|
+
id: "202605130001_allow_synthetic_webhook_test_deliveries_without_incident_fk",
|
|
16653
|
+
description: "Allow webhook test deliveries to persist without requiring a backing incidents row.",
|
|
16654
|
+
statements: [
|
|
16655
|
+
"ALTER TABLE webhook_deliveries ALTER COLUMN incident_id DROP NOT NULL"
|
|
16656
|
+
]
|
|
16599
16657
|
})
|
|
16600
16658
|
];
|
|
16601
16659
|
|
|
@@ -16671,7 +16729,11 @@ function parseLocalIncident(candidate) {
|
|
|
16671
16729
|
}
|
|
16672
16730
|
const serviceRuntime = candidate["service_runtime"];
|
|
16673
16731
|
const serviceFramework = candidate["service_framework"];
|
|
16674
|
-
const incidentReason =
|
|
16732
|
+
const incidentReason = candidate["matched_fields"].includes("request_anomaly") ? deriveIncidentReasonFromSignal({
|
|
16733
|
+
event_type: "request_event",
|
|
16734
|
+
event_class: "incident_signal",
|
|
16735
|
+
request_anomaly: true
|
|
16736
|
+
}) : deriveIncidentReasonFromSourceEventTypes(candidate["source_event_types"]);
|
|
16675
16737
|
if (serviceRuntime !== null && typeof serviceRuntime !== "string") {
|
|
16676
16738
|
throw createReadError(400, "invalid_local_state");
|
|
16677
16739
|
}
|
|
@@ -19966,7 +20028,7 @@ function buildPrivacyPreview() {
|
|
|
19966
20028
|
sample_event_type: sampleEvent.event_type,
|
|
19967
20029
|
sample_event_class: sampleEventClass,
|
|
19968
20030
|
sample_can_create_incident: sampleEventClass === "incident_signal",
|
|
19969
|
-
incident_rule: "request_event
|
|
20031
|
+
incident_rule: "request_event incident classification follows the resolved capture preset: 5xx always create incidents, balanced also promotes 408/423/424/425/429, and investigative also promotes 409.",
|
|
19970
20032
|
redacted_fields,
|
|
19971
20033
|
omitted_fields: [],
|
|
19972
20034
|
retained_metadata: {
|
|
@@ -21668,11 +21730,17 @@ var EVENT_TYPE_SET = new Set(EventTypeValues);
|
|
|
21668
21730
|
function isEventType(value) {
|
|
21669
21731
|
return typeof value === "string" && EVENT_TYPE_SET.has(value);
|
|
21670
21732
|
}
|
|
21671
|
-
function inferSeverity2(
|
|
21672
|
-
if (
|
|
21733
|
+
function inferSeverity2(event, capturePreset, incidentKind = "immediate") {
|
|
21734
|
+
if (incidentKind === "request_anomaly") {
|
|
21735
|
+
return "medium";
|
|
21736
|
+
}
|
|
21737
|
+
if (event.event_type === "request_event") {
|
|
21738
|
+
return classifyRequestStatus({ responseStatus: event.payload.response_status, capturePreset }) === "incident_signal" ? "high" : "low";
|
|
21739
|
+
}
|
|
21740
|
+
if (event.event_type === "backend_exception" || event.event_type === "frontend_exception") {
|
|
21673
21741
|
return "high";
|
|
21674
21742
|
}
|
|
21675
|
-
if (
|
|
21743
|
+
if (event.event_type === "error_suppressed") {
|
|
21676
21744
|
return "medium";
|
|
21677
21745
|
}
|
|
21678
21746
|
return "low";
|
|
@@ -21696,15 +21764,17 @@ function compareEventEnvelopes(left, right) {
|
|
|
21696
21764
|
}
|
|
21697
21765
|
return left.event_id.localeCompare(right.event_id);
|
|
21698
21766
|
}
|
|
21699
|
-
function classifyEnvelope(envelope) {
|
|
21767
|
+
function classifyEnvelope(envelope, capturePreset) {
|
|
21700
21768
|
return classifyEvent(
|
|
21701
21769
|
envelope.event_type,
|
|
21702
21770
|
envelope.event_type === "log_event" ? envelope.payload.level : void 0,
|
|
21703
|
-
envelope.event_type === "probe_event" ? envelope.payload.activation_id : void 0
|
|
21771
|
+
envelope.event_type === "probe_event" ? envelope.payload.activation_id : void 0,
|
|
21772
|
+
envelope.payload,
|
|
21773
|
+
capturePreset
|
|
21704
21774
|
);
|
|
21705
21775
|
}
|
|
21706
|
-
function isIncidentSignalEnvelope(envelope) {
|
|
21707
|
-
return classifyEnvelope(envelope) === "incident_signal";
|
|
21776
|
+
function isIncidentSignalEnvelope(envelope, capturePreset) {
|
|
21777
|
+
return classifyEnvelope(envelope, capturePreset) === "incident_signal";
|
|
21708
21778
|
}
|
|
21709
21779
|
function getTraceId(envelope) {
|
|
21710
21780
|
return envelope.correlation?.trace_id ?? null;
|
|
@@ -21749,7 +21819,10 @@ function mergeAggregateGroup(aggregates) {
|
|
|
21749
21819
|
newEvents: [...canonicalAggregate.newEvents],
|
|
21750
21820
|
mergedIncidentIds: new Set(canonicalAggregate.mergedIncidentIds),
|
|
21751
21821
|
signalEventTypes: new Set(canonicalAggregate.signalEventTypes),
|
|
21752
|
-
traceIds: new Set(canonicalAggregate.traceIds)
|
|
21822
|
+
traceIds: new Set(canonicalAggregate.traceIds),
|
|
21823
|
+
title: canonicalAggregate.title,
|
|
21824
|
+
kind: canonicalAggregate.kind,
|
|
21825
|
+
severity: canonicalAggregate.severity
|
|
21753
21826
|
});
|
|
21754
21827
|
}
|
|
21755
21828
|
function hashIdentifier(parts, prefix, length) {
|
|
@@ -21775,6 +21848,111 @@ function mergeSourceEvents(existingEvents, nextEvents) {
|
|
|
21775
21848
|
}
|
|
21776
21849
|
return [...merged.values()].sort(compareEventEnvelopes);
|
|
21777
21850
|
}
|
|
21851
|
+
function stableJson2(value) {
|
|
21852
|
+
if (value === null || typeof value !== "object") {
|
|
21853
|
+
return JSON.stringify(value);
|
|
21854
|
+
}
|
|
21855
|
+
if (Array.isArray(value)) {
|
|
21856
|
+
return `[${value.map((entry) => stableJson2(entry)).join(",")}]`;
|
|
21857
|
+
}
|
|
21858
|
+
const record = value;
|
|
21859
|
+
const keys = Object.keys(record).sort();
|
|
21860
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableJson2(record[key])}`).join(",")}}`;
|
|
21861
|
+
}
|
|
21862
|
+
function buildRequestAnomalyFingerprint(input) {
|
|
21863
|
+
return (0, import_node_crypto4.createHash)("sha256").update(
|
|
21864
|
+
stableJson2({
|
|
21865
|
+
kind: "request_status_anomaly",
|
|
21866
|
+
project_id: input.projectId,
|
|
21867
|
+
service_name: input.serviceName,
|
|
21868
|
+
environment: input.environment,
|
|
21869
|
+
method: input.method,
|
|
21870
|
+
route_template: input.routeTemplate,
|
|
21871
|
+
response_status: input.responseStatus
|
|
21872
|
+
})
|
|
21873
|
+
).digest("hex");
|
|
21874
|
+
}
|
|
21875
|
+
function buildRequestAnomalyTitle(input) {
|
|
21876
|
+
return `Request anomaly: ${input.method} ${input.routeTemplate} returned ${input.responseStatus} repeatedly`;
|
|
21877
|
+
}
|
|
21878
|
+
function toUnixSeconds(occurredAt) {
|
|
21879
|
+
return Math.floor(new Date(occurredAt).getTime() / 1e3);
|
|
21880
|
+
}
|
|
21881
|
+
function countOccurrencesInWindow(events, windowSeconds) {
|
|
21882
|
+
const latestEvent = events.at(-1);
|
|
21883
|
+
if (latestEvent === void 0) {
|
|
21884
|
+
return 0;
|
|
21885
|
+
}
|
|
21886
|
+
const latestOccurredAt = toUnixSeconds(latestEvent.occurred_at);
|
|
21887
|
+
const lowerBound = latestOccurredAt - windowSeconds + 1;
|
|
21888
|
+
return events.filter((event) => {
|
|
21889
|
+
const occurredAt = toUnixSeconds(event.occurred_at);
|
|
21890
|
+
return occurredAt >= lowerBound && occurredAt <= latestOccurredAt;
|
|
21891
|
+
}).length;
|
|
21892
|
+
}
|
|
21893
|
+
function passesRequestAnomalyThreshold(events, threshold) {
|
|
21894
|
+
const occurrences5m = countOccurrencesInWindow(events, 5 * 60);
|
|
21895
|
+
const occurrences1h = countOccurrencesInWindow(events, 60 * 60);
|
|
21896
|
+
const baseline1hPer5m = occurrences1h / 12;
|
|
21897
|
+
const ratio = occurrences5m / Math.max(baseline1hPer5m, 1);
|
|
21898
|
+
return occurrences5m >= threshold.minimum_occurrences_5m && ratio >= threshold.minimum_ratio_5m_to_1h;
|
|
21899
|
+
}
|
|
21900
|
+
function collectRequestAnomalyAggregates(batches, capturePreset) {
|
|
21901
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
21902
|
+
for (const batch of batches) {
|
|
21903
|
+
for (const event of batch.events) {
|
|
21904
|
+
if (event.event_type !== "request_event" || classifyEnvelope(event, capturePreset) !== "context_signal") {
|
|
21905
|
+
continue;
|
|
21906
|
+
}
|
|
21907
|
+
const normalizedEvent = normalizeEvent(event);
|
|
21908
|
+
const responseStatus = normalizedEvent.http_status;
|
|
21909
|
+
const method = normalizedEvent.http_method;
|
|
21910
|
+
const routeTemplate = normalizedEvent.route_template;
|
|
21911
|
+
const threshold = getRequestAnomalyThreshold({ responseStatus, capturePreset });
|
|
21912
|
+
if (threshold === null || responseStatus === null || method === null || routeTemplate === null) {
|
|
21913
|
+
continue;
|
|
21914
|
+
}
|
|
21915
|
+
const projectId = requireProjectId(event);
|
|
21916
|
+
const incidentFingerprint = buildRequestAnomalyFingerprint({
|
|
21917
|
+
projectId,
|
|
21918
|
+
serviceName: event.service.name,
|
|
21919
|
+
environment: event.service.environment,
|
|
21920
|
+
method,
|
|
21921
|
+
routeTemplate,
|
|
21922
|
+
responseStatus
|
|
21923
|
+
});
|
|
21924
|
+
const incidentId = deriveIncidentId(projectId, event.service.name, event.service.environment, incidentFingerprint);
|
|
21925
|
+
const aggregate = grouped.get(incidentId) ?? {
|
|
21926
|
+
incidentId,
|
|
21927
|
+
projectId,
|
|
21928
|
+
serviceName: event.service.name,
|
|
21929
|
+
environment: event.service.environment,
|
|
21930
|
+
fingerprint: incidentFingerprint,
|
|
21931
|
+
matchedFields: /* @__PURE__ */ new Set(["request_anomaly", "route_template", "http_method", "http_status", "environment"]),
|
|
21932
|
+
newEvents: [],
|
|
21933
|
+
mergedIncidentIds: /* @__PURE__ */ new Set([incidentId]),
|
|
21934
|
+
signalEventTypes: /* @__PURE__ */ new Set(["request_event"]),
|
|
21935
|
+
traceIds: /* @__PURE__ */ new Set(),
|
|
21936
|
+
title: buildRequestAnomalyTitle({ method, routeTemplate, responseStatus }),
|
|
21937
|
+
kind: "request_anomaly",
|
|
21938
|
+
severity: "medium"
|
|
21939
|
+
};
|
|
21940
|
+
aggregate.newEvents.push(event);
|
|
21941
|
+
grouped.set(incidentId, aggregate);
|
|
21942
|
+
}
|
|
21943
|
+
}
|
|
21944
|
+
return [...grouped.values()].filter((aggregate) => {
|
|
21945
|
+
const latestEvent = aggregate.newEvents.at(-1);
|
|
21946
|
+
if (latestEvent === void 0 || latestEvent.event_type !== "request_event") {
|
|
21947
|
+
return false;
|
|
21948
|
+
}
|
|
21949
|
+
const threshold = getRequestAnomalyThreshold({
|
|
21950
|
+
responseStatus: normalizeEvent(latestEvent).http_status,
|
|
21951
|
+
capturePreset
|
|
21952
|
+
});
|
|
21953
|
+
return threshold !== null && passesRequestAnomalyThreshold(aggregate.newEvents, threshold);
|
|
21954
|
+
}).sort((left, right) => left.incidentId.localeCompare(right.incidentId));
|
|
21955
|
+
}
|
|
21778
21956
|
function buildBundleContext(incident) {
|
|
21779
21957
|
return {
|
|
21780
21958
|
incident_id: incident.incident_id,
|
|
@@ -21804,12 +21982,12 @@ function formatServiceSummary(services) {
|
|
|
21804
21982
|
}
|
|
21805
21983
|
function formatProcessOutput(summary) {
|
|
21806
21984
|
if (!summary.processed) {
|
|
21807
|
-
return summary.message
|
|
21985
|
+
return summary.message;
|
|
21808
21986
|
}
|
|
21809
21987
|
return [
|
|
21810
21988
|
`Processed ${summary.events_processed} events from ${summary.files_processed} files into ${summary.incidents_processed} incidents.`,
|
|
21811
21989
|
...formatServiceSummary(summary.services),
|
|
21812
|
-
`Last processed event file: ${summary.last_processed_event_file
|
|
21990
|
+
`Last processed event file: ${summary.last_processed_event_file}`
|
|
21813
21991
|
].join("\n");
|
|
21814
21992
|
}
|
|
21815
21993
|
async function pathExists4(path, stat) {
|
|
@@ -22049,6 +22227,7 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22049
22227
|
const statePath = (0, import_node_path11.join)(rootDirectory, LOCAL_STATE_FILE_PATH);
|
|
22050
22228
|
const bundleDirectoryPath = (0, import_node_path11.join)(rootDirectory, LOCAL_BUNDLE_DIRECTORY_PATH3);
|
|
22051
22229
|
const reproductionDirectoryPath = (0, import_node_path11.join)(rootDirectory, LOCAL_REPRODUCTION_DIRECTORY_PATH2);
|
|
22230
|
+
const capturePreset = input.preset ?? "minimal";
|
|
22052
22231
|
await mkdir((0, import_node_path11.join)(rootDirectory, ".debugbundle", "local"), { recursive: true });
|
|
22053
22232
|
await mkdir(bundleDirectoryPath, { recursive: true });
|
|
22054
22233
|
await mkdir(reproductionDirectoryPath, { recursive: true });
|
|
@@ -22056,22 +22235,26 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22056
22235
|
const eventFileNames = await pathExists4(eventsDirectoryPath, stat) ? (await readdir(eventsDirectoryPath)).filter((fileName) => fileName.endsWith(".events.json")).sort() : [];
|
|
22057
22236
|
const lastProcessedEventFile = previousState?.last_processed_event_file ?? null;
|
|
22058
22237
|
const newEventFileNames = lastProcessedEventFile === null ? eventFileNames : eventFileNames.filter((fileName) => fileName > lastProcessedEventFile);
|
|
22059
|
-
|
|
22238
|
+
const processAllEventFiles = input.preset !== void 0;
|
|
22239
|
+
const targetEventFileNames = processAllEventFiles ? eventFileNames : newEventFileNames;
|
|
22240
|
+
if (targetEventFileNames.length === 0) {
|
|
22060
22241
|
const summary2 = buildNoNewEventsSummary(previousState?.last_processed_event_file ?? eventFileNames.at(-1) ?? null);
|
|
22061
22242
|
return {
|
|
22062
22243
|
exitCode: 0,
|
|
22063
22244
|
output: input.json === true ? JSON.stringify(summary2) : formatProcessOutput(summary2)
|
|
22064
22245
|
};
|
|
22065
22246
|
}
|
|
22066
|
-
const batches = await readEventBatches(eventsDirectoryPath,
|
|
22067
|
-
const incidents = new Map(
|
|
22247
|
+
const batches = await readEventBatches(eventsDirectoryPath, targetEventFileNames, readFile);
|
|
22248
|
+
const incidents = new Map(
|
|
22249
|
+
processAllEventFiles ? [] : Object.entries(previousState?.incidents ?? {})
|
|
22250
|
+
);
|
|
22068
22251
|
const aggregates = /* @__PURE__ */ new Map();
|
|
22069
22252
|
const traceCorrelationGroups = /* @__PURE__ */ new Map();
|
|
22070
22253
|
let eventsProcessed = 0;
|
|
22071
22254
|
for (const batch of batches) {
|
|
22072
22255
|
for (const event of batch.events) {
|
|
22073
22256
|
eventsProcessed += 1;
|
|
22074
|
-
if (!isIncidentSignalEnvelope(event)) {
|
|
22257
|
+
if (!isIncidentSignalEnvelope(event, capturePreset)) {
|
|
22075
22258
|
continue;
|
|
22076
22259
|
}
|
|
22077
22260
|
const normalizedEvent = normalizeEvent(event);
|
|
@@ -22088,7 +22271,10 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22088
22271
|
newEvents: [],
|
|
22089
22272
|
mergedIncidentIds: /* @__PURE__ */ new Set([incidentId]),
|
|
22090
22273
|
signalEventTypes: /* @__PURE__ */ new Set(),
|
|
22091
|
-
traceIds: /* @__PURE__ */ new Set()
|
|
22274
|
+
traceIds: /* @__PURE__ */ new Set(),
|
|
22275
|
+
title: normalizedEvent.normalized_message,
|
|
22276
|
+
kind: "immediate",
|
|
22277
|
+
severity: inferSeverity2(event, capturePreset)
|
|
22092
22278
|
};
|
|
22093
22279
|
for (const matchedField of inferMatchedFields(normalizedEvent)) {
|
|
22094
22280
|
aggregate.matchedFields.add(matchedField);
|
|
@@ -22155,14 +22341,16 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22155
22341
|
mergedAggregatesByRoot.set(rootIncidentId, aggregateGroup);
|
|
22156
22342
|
}
|
|
22157
22343
|
const mergedAggregates = [...mergedAggregatesByRoot.values()].map((aggregateGroup) => mergeAggregateGroup(aggregateGroup)).sort((left, right) => left.incidentId.localeCompare(right.incidentId));
|
|
22344
|
+
const requestAnomalyAggregates = input.preset === void 0 ? [] : collectRequestAnomalyAggregates(batches, capturePreset);
|
|
22345
|
+
const finalizedAggregates = [...mergedAggregates, ...requestAnomalyAggregates].sort((left, right) => left.incidentId.localeCompare(right.incidentId));
|
|
22158
22346
|
const services = /* @__PURE__ */ new Map();
|
|
22159
|
-
for (const aggregate of
|
|
22347
|
+
for (const aggregate of finalizedAggregates) {
|
|
22160
22348
|
const incidentId = aggregate.incidentId;
|
|
22161
22349
|
const existingIncidents = [...aggregate.mergedIncidentIds].map((mergedIncidentId) => incidents.get(mergedIncidentId)).filter((incident2) => incident2 !== void 0);
|
|
22162
22350
|
const existing = existingIncidents.find((incident2) => incident2.incident_id === incidentId) ?? existingIncidents[0];
|
|
22163
22351
|
const existingSourceEvents = existingIncidents.flatMap((incident2) => incident2.source_events);
|
|
22164
22352
|
const combinedSourceEvents = mergeSourceEvents(existingSourceEvents, aggregate.newEvents);
|
|
22165
|
-
const signalEvents = combinedSourceEvents.filter(isIncidentSignalEnvelope);
|
|
22353
|
+
const signalEvents = aggregate.kind === "request_anomaly" ? combinedSourceEvents : combinedSourceEvents.filter((event) => isIncidentSignalEnvelope(event, capturePreset));
|
|
22166
22354
|
if (signalEvents.length === 0) {
|
|
22167
22355
|
continue;
|
|
22168
22356
|
}
|
|
@@ -22172,8 +22360,7 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22172
22360
|
continue;
|
|
22173
22361
|
}
|
|
22174
22362
|
const sourceEventTypes = [...new Set(signalEvents.map((event) => event.event_type))].sort();
|
|
22175
|
-
const severity = signalEvents.map((event) => inferSeverity2(event.
|
|
22176
|
-
const latestNormalizedEvent = normalizeEvent(latestSignalEvent);
|
|
22363
|
+
const severity = signalEvents.map((event) => inferSeverity2(event, capturePreset, aggregate.kind)).sort((left, right) => severityRank(right) - severityRank(left))[0] ?? aggregate.severity;
|
|
22177
22364
|
const generationNumber = signalEvents.length;
|
|
22178
22365
|
const bundlePath = `${LOCAL_BUNDLE_DIRECTORY_PATH3}/${incidentId}.bundle.json`;
|
|
22179
22366
|
const reproductionPath = `${LOCAL_REPRODUCTION_DIRECTORY_PATH2}/${incidentId}.reproduction.json`;
|
|
@@ -22188,7 +22375,7 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22188
22375
|
environment: aggregate.environment,
|
|
22189
22376
|
fingerprint: aggregate.fingerprint,
|
|
22190
22377
|
fingerprint_version: FINGERPRINT_VERSION,
|
|
22191
|
-
title:
|
|
22378
|
+
title: aggregate.title,
|
|
22192
22379
|
severity,
|
|
22193
22380
|
status: existingIncidents.some((incidentState) => incidentState.status === "resolved") ? "open" : existing?.status ?? "open",
|
|
22194
22381
|
first_seen_at: firstSignalEvent.occurred_at,
|
|
@@ -22249,21 +22436,22 @@ async function processCommand(input, dependencies = {}) {
|
|
|
22249
22436
|
incidents.set(incidentId, incident);
|
|
22250
22437
|
services.set(incident.service_name, (services.get(incident.service_name) ?? 0) + 1);
|
|
22251
22438
|
}
|
|
22439
|
+
const finalProcessedEventFile = targetEventFileNames[targetEventFileNames.length - 1];
|
|
22252
22440
|
const nextState = {
|
|
22253
22441
|
version: 1,
|
|
22254
|
-
last_processed_event_file:
|
|
22442
|
+
last_processed_event_file: finalProcessedEventFile,
|
|
22255
22443
|
incidents: Object.fromEntries([...incidents.entries()].sort(([left], [right]) => left.localeCompare(right)))
|
|
22256
22444
|
};
|
|
22257
22445
|
await writeFile(statePath, serializeState(nextState));
|
|
22258
22446
|
const summary = buildProcessedSummary({
|
|
22259
22447
|
filesProcessed: newEventFileNames.length,
|
|
22260
22448
|
eventsProcessed,
|
|
22261
|
-
incidentsProcessed:
|
|
22449
|
+
incidentsProcessed: finalizedAggregates.length,
|
|
22262
22450
|
services: [...services.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([service, count]) => ({
|
|
22263
22451
|
service,
|
|
22264
22452
|
incidents: count
|
|
22265
22453
|
})),
|
|
22266
|
-
lastProcessedEventFile:
|
|
22454
|
+
lastProcessedEventFile: finalProcessedEventFile
|
|
22267
22455
|
});
|
|
22268
22456
|
return {
|
|
22269
22457
|
exitCode: 0,
|
|
@@ -24248,14 +24436,14 @@ function localFailureStepName(checks) {
|
|
|
24248
24436
|
function cloudVerificationRunId(now) {
|
|
24249
24437
|
return now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
24250
24438
|
}
|
|
24251
|
-
function
|
|
24439
|
+
function requestFailureReason() {
|
|
24252
24440
|
const incidentReason = deriveIncidentReasonFromSignal({
|
|
24253
24441
|
event_type: "request_event",
|
|
24254
24442
|
event_class: "incident_signal",
|
|
24255
24443
|
response_status: 503
|
|
24256
24444
|
});
|
|
24257
24445
|
if (incidentReason === null) {
|
|
24258
|
-
throw new Error("
|
|
24446
|
+
throw new Error("request_failure_reason_unavailable");
|
|
24259
24447
|
}
|
|
24260
24448
|
return incidentReason;
|
|
24261
24449
|
}
|
|
@@ -24530,7 +24718,7 @@ async function verifyCloudCommand(input, dependencies = {}) {
|
|
|
24530
24718
|
const verification = {
|
|
24531
24719
|
mode: "active_5xx",
|
|
24532
24720
|
bundle_status: "unknown",
|
|
24533
|
-
classification_reason:
|
|
24721
|
+
classification_reason: requestFailureReason()
|
|
24534
24722
|
};
|
|
24535
24723
|
const errors = [];
|
|
24536
24724
|
let exitCode = 0;
|
|
@@ -24582,7 +24770,7 @@ async function verifyCloudCommand(input, dependencies = {}) {
|
|
|
24582
24770
|
if (candidate !== void 0) {
|
|
24583
24771
|
incidentId = candidate.incident_id;
|
|
24584
24772
|
verification.incident_id = candidate.incident_id;
|
|
24585
|
-
verification.classification_reason = candidate.incident_reason ??
|
|
24773
|
+
verification.classification_reason = candidate.incident_reason ?? requestFailureReason();
|
|
24586
24774
|
break;
|
|
24587
24775
|
}
|
|
24588
24776
|
if (attempt < pollAttempts) {
|
|
@@ -24857,7 +25045,7 @@ var CLI_USAGE_LINES = [
|
|
|
24857
25045
|
" debugbundle connect [--auth-file <path>] [--json]",
|
|
24858
25046
|
" debugbundle ingest <file> --format <debugbundle-ndjson|php-error|apache-error> [--json]",
|
|
24859
25047
|
" debugbundle watch [--cloud] --log <file> --format <debugbundle-ndjson|php-error|apache-error> [--json]",
|
|
24860
|
-
" debugbundle process [--json]",
|
|
25048
|
+
" debugbundle process [--preset <minimal|balanced|investigative>] [--json]",
|
|
24861
25049
|
" debugbundle clean [--events] [--bundles] [--all] [--older-than <Nd>] [--json]",
|
|
24862
25050
|
" debugbundle validate [--fix] [--json]",
|
|
24863
25051
|
" debugbundle verify local [--json]",
|
|
@@ -28761,10 +28949,17 @@ ${formatUsage()}`
|
|
|
28761
28949
|
});
|
|
28762
28950
|
}
|
|
28763
28951
|
if (command === "process") {
|
|
28764
|
-
expectNoUnknownOptions(parsedArgv, ["json"]);
|
|
28952
|
+
expectNoUnknownOptions(parsedArgv, ["json", "preset"]);
|
|
28765
28953
|
ensureNoExtraPositionals(parsedArgv, 1);
|
|
28766
28954
|
const json = readBooleanOption(parsedArgv, "json");
|
|
28767
|
-
|
|
28955
|
+
const preset = readStringOption(parsedArgv, "preset");
|
|
28956
|
+
if (preset !== void 0 && !CapturePresetSchema.safeParse(preset).success) {
|
|
28957
|
+
throw new CliInputError("Invalid value for --preset.");
|
|
28958
|
+
}
|
|
28959
|
+
return await (dependencies.processCommand ?? processCommand)({
|
|
28960
|
+
...json === true ? { json: true } : {},
|
|
28961
|
+
...preset !== void 0 ? { preset } : {}
|
|
28962
|
+
});
|
|
28768
28963
|
}
|
|
28769
28964
|
if (command === "clean") {
|
|
28770
28965
|
expectNoUnknownOptions(parsedArgv, ["events", "bundles", "all", "older-than", "json"]);
|