@littlebearapps/platform-admin-sdk 1.0.0
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/README.md +112 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +89 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +80 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +65 -0
- package/dist/templates.d.ts +16 -0
- package/dist/templates.js +131 -0
- package/package.json +46 -0
- package/templates/full/migrations/006_pattern_discovery.sql +199 -0
- package/templates/full/migrations/007_notifications_search.sql +127 -0
- package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
- package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
- package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
- package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
- package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
- package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
- package/templates/full/workers/pattern-discovery.ts +661 -0
- package/templates/full/workers/platform-alert-router.ts +1809 -0
- package/templates/full/workers/platform-notifications.ts +424 -0
- package/templates/full/workers/platform-search.ts +480 -0
- package/templates/full/workers/platform-settings.ts +436 -0
- package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
- package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
- package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
- package/templates/full/wrangler.search.jsonc.hbs +16 -0
- package/templates/full/wrangler.settings.jsonc.hbs +23 -0
- package/templates/shared/README.md.hbs +69 -0
- package/templates/shared/config/budgets.yaml.hbs +72 -0
- package/templates/shared/config/services.yaml.hbs +45 -0
- package/templates/shared/migrations/001_core_tables.sql +117 -0
- package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
- package/templates/shared/migrations/003_feature_tracking.sql +250 -0
- package/templates/shared/migrations/004_settings_alerts.sql +452 -0
- package/templates/shared/migrations/seed.sql.hbs +4 -0
- package/templates/shared/package.json.hbs +21 -0
- package/templates/shared/scripts/sync-config.ts +242 -0
- package/templates/shared/tsconfig.json +12 -0
- package/templates/shared/workers/lib/analytics-engine.ts +357 -0
- package/templates/shared/workers/lib/billing.ts +293 -0
- package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
- package/templates/shared/workers/lib/control.ts +292 -0
- package/templates/shared/workers/lib/economics.ts +368 -0
- package/templates/shared/workers/lib/metrics.ts +103 -0
- package/templates/shared/workers/lib/platform-settings.ts +407 -0
- package/templates/shared/workers/lib/shared/allowances.ts +333 -0
- package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
- package/templates/shared/workers/lib/shared/types.ts +58 -0
- package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
- package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
- package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
- package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
- package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
- package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
- package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
- package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
- package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
- package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
- package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
- package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
- package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
- package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
- package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
- package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
- package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
- package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
- package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
- package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
- package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
- package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
- package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
- package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
- package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
- package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
- package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
- package/templates/shared/workers/platform-usage.ts +1915 -0
- package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
- package/templates/standard/migrations/005_error_collection.sql +162 -0
- package/templates/standard/workers/error-collector.ts +2670 -0
- package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
- package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
- package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
- package/templates/standard/workers/lib/error-collector/github.ts +329 -0
- package/templates/standard/workers/lib/error-collector/types.ts +262 -0
- package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
- package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
- package/templates/standard/workers/platform-sentinel.ts +1744 -0
- package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
- package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Budget Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Cost-based circuit breaker enforcement for platform-usage queue processing.
|
|
5
|
+
* Implements rolling window cost accumulation and budget checking.
|
|
6
|
+
*
|
|
7
|
+
* Part of the real-time cost tracking feature for Platform SDK.
|
|
8
|
+
*
|
|
9
|
+
* Key Concepts:
|
|
10
|
+
* - Cost budgets stored in KV: CONFIG:FEATURE:{key}:COST_BUDGET
|
|
11
|
+
* - Accumulated costs stored in KV: STATE:COST:{key}:ACCUMULATED
|
|
12
|
+
* - Status stored in KV: CONFIG:FEATURE:{key}:STATUS (same key as resource CB)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Env } from '../shared';
|
|
16
|
+
import { createLoggerFromEnv } from '@littlebearapps/platform-consumer-sdk';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Cost budget configuration for a feature.
|
|
20
|
+
* Stored in KV at CONFIG:FEATURE:{key}:COST_BUDGET.
|
|
21
|
+
*/
|
|
22
|
+
export interface CostBudgetConfig {
|
|
23
|
+
/** Daily cost limit in USD */
|
|
24
|
+
daily_limit_usd: number;
|
|
25
|
+
/** Optional alert threshold percentage (e.g., 0.8 = 80%) */
|
|
26
|
+
alert_threshold_pct?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Accumulated cost state stored in KV.
|
|
31
|
+
*/
|
|
32
|
+
interface AccumulatedCostState {
|
|
33
|
+
/** Total cost accumulated in window */
|
|
34
|
+
cost: number;
|
|
35
|
+
/** Window start timestamp in milliseconds */
|
|
36
|
+
windowStart: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check and update cost budget status for a feature.
|
|
41
|
+
* Uses a rolling 24-hour window for cost accumulation.
|
|
42
|
+
*
|
|
43
|
+
* If total cost exceeds the configured daily limit, trips the circuit breaker
|
|
44
|
+
* using the same STATUS key as resource-based circuit breakers.
|
|
45
|
+
*
|
|
46
|
+
* @param featureKey - Feature identifier (e.g., 'my-app:scanner:harvest')
|
|
47
|
+
* @param costIncrement - Cost in USD to add to accumulator
|
|
48
|
+
* @param env - Worker environment
|
|
49
|
+
*/
|
|
50
|
+
export async function checkAndUpdateCostBudgetStatus(
|
|
51
|
+
featureKey: string,
|
|
52
|
+
costIncrement: number,
|
|
53
|
+
env: Env
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:cost-budget');
|
|
56
|
+
|
|
57
|
+
const budgetKey = `CONFIG:FEATURE:${featureKey}:COST_BUDGET`;
|
|
58
|
+
const statusKey = `CONFIG:FEATURE:${featureKey}:STATUS`;
|
|
59
|
+
const accumulatorKey = `STATE:COST:${featureKey}:ACCUMULATED`;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Check if cost budget is configured for this feature
|
|
63
|
+
const budgetJson = await env.PLATFORM_CACHE.get(budgetKey);
|
|
64
|
+
if (!budgetJson) {
|
|
65
|
+
// No cost budget configured - skip checking
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const budget = JSON.parse(budgetJson) as CostBudgetConfig;
|
|
70
|
+
|
|
71
|
+
// Rolling 24-hour window
|
|
72
|
+
const windowMs = 24 * 60 * 60 * 1000;
|
|
73
|
+
const windowStart = Date.now() - windowMs;
|
|
74
|
+
|
|
75
|
+
// Get current accumulated cost
|
|
76
|
+
let totalCost = costIncrement;
|
|
77
|
+
let existingWindowStart = Date.now();
|
|
78
|
+
|
|
79
|
+
const stored = await env.PLATFORM_CACHE.get(accumulatorKey);
|
|
80
|
+
if (stored) {
|
|
81
|
+
const data = JSON.parse(stored) as AccumulatedCostState;
|
|
82
|
+
// Only add to existing cost if within the same window
|
|
83
|
+
if (data.windowStart > windowStart) {
|
|
84
|
+
// Use fixed precision to prevent floating point accumulation errors
|
|
85
|
+
totalCost = Number((data.cost + costIncrement).toFixed(6));
|
|
86
|
+
existingWindowStart = data.windowStart;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Store updated cost with 25-hour TTL (allows for window overlap)
|
|
91
|
+
// Round to 6 decimal places to prevent floating point run-on
|
|
92
|
+
await env.PLATFORM_CACHE.put(
|
|
93
|
+
accumulatorKey,
|
|
94
|
+
JSON.stringify({ cost: Number(totalCost.toFixed(6)), windowStart: existingWindowStart }),
|
|
95
|
+
{ expirationTtl: 90000 } // 25 hours
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Check budget violation
|
|
99
|
+
if (totalCost > budget.daily_limit_usd) {
|
|
100
|
+
const reason = `cost_usd=${totalCost.toFixed(4)}>${budget.daily_limit_usd}`;
|
|
101
|
+
|
|
102
|
+
// Trip the circuit breaker in KV
|
|
103
|
+
await env.PLATFORM_CACHE.put(statusKey, 'STOP');
|
|
104
|
+
|
|
105
|
+
// Log to D1 for historical tracking
|
|
106
|
+
try {
|
|
107
|
+
await env.PLATFORM_DB.prepare(
|
|
108
|
+
`INSERT INTO feature_circuit_breaker_events
|
|
109
|
+
(id, feature_key, event_type, reason, violated_resource, current_value, budget_limit, created_at)
|
|
110
|
+
VALUES (?1, ?2, 'trip', ?3, 'cost_usd', ?4, ?5, unixepoch())`
|
|
111
|
+
)
|
|
112
|
+
.bind(crypto.randomUUID(), featureKey, reason, totalCost, budget.daily_limit_usd)
|
|
113
|
+
.run();
|
|
114
|
+
} catch (d1Error) {
|
|
115
|
+
// D1 logging failure should not prevent KV trip
|
|
116
|
+
log.error(`Failed to log cost CB event to D1 for ${featureKey}`, d1Error);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
log.warn(`Cost CB tripped: ${featureKey}`, {
|
|
120
|
+
totalCost: totalCost.toFixed(4),
|
|
121
|
+
limit: budget.daily_limit_usd,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Cost budget check failures should not fail the telemetry write
|
|
126
|
+
log.error(`Cost budget check failed for ${featureKey}`, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates CF resource cost from telemetry metrics.
|
|
5
|
+
* Uses pricing tiers from workers/lib/costs.ts for consistency.
|
|
6
|
+
*
|
|
7
|
+
* Part of the real-time cost tracking feature for Platform SDK.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { FeatureMetrics } from '@littlebearapps/platform-consumer-sdk';
|
|
11
|
+
import { PRICING_TIERS } from '@littlebearapps/platform-consumer-sdk';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calculate CF resource cost from telemetry metrics.
|
|
15
|
+
* Returns cost in USD based on current pricing tiers.
|
|
16
|
+
*
|
|
17
|
+
* @param metrics - Feature metrics from telemetry message
|
|
18
|
+
* @returns Total cost in USD for CF resources
|
|
19
|
+
*/
|
|
20
|
+
export function calculateCFCostFromMetrics(metrics: FeatureMetrics): number {
|
|
21
|
+
let cost = 0;
|
|
22
|
+
|
|
23
|
+
// D1: $0.001/billion reads, $1.00/million writes
|
|
24
|
+
if (metrics.d1RowsRead) {
|
|
25
|
+
cost += (metrics.d1RowsRead / 1e9) * PRICING_TIERS.d1.rowsReadPerBillion;
|
|
26
|
+
}
|
|
27
|
+
if (metrics.d1RowsWritten) {
|
|
28
|
+
cost += (metrics.d1RowsWritten / 1e6) * PRICING_TIERS.d1.rowsWrittenPerMillion;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// KV: $0.50/million reads, $5.00/million writes, $5.00/million deletes, $5.00/million lists
|
|
32
|
+
if (metrics.kvReads) {
|
|
33
|
+
cost += (metrics.kvReads / 1e6) * PRICING_TIERS.kv.readsPerMillion;
|
|
34
|
+
}
|
|
35
|
+
if (metrics.kvWrites) {
|
|
36
|
+
cost += (metrics.kvWrites / 1e6) * PRICING_TIERS.kv.writesPerMillion;
|
|
37
|
+
}
|
|
38
|
+
if (metrics.kvDeletes) {
|
|
39
|
+
cost += (metrics.kvDeletes / 1e6) * PRICING_TIERS.kv.deletesPerMillion;
|
|
40
|
+
}
|
|
41
|
+
if (metrics.kvLists) {
|
|
42
|
+
cost += (metrics.kvLists / 1e6) * PRICING_TIERS.kv.listsPerMillion;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// R2: $4.50/million Class A, $0.36/million Class B
|
|
46
|
+
if (metrics.r2ClassA) {
|
|
47
|
+
cost += (metrics.r2ClassA / 1e6) * PRICING_TIERS.r2.classAPerMillion;
|
|
48
|
+
}
|
|
49
|
+
if (metrics.r2ClassB) {
|
|
50
|
+
cost += (metrics.r2ClassB / 1e6) * PRICING_TIERS.r2.classBPerMillion;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Workers AI: $0.011/1000 neurons
|
|
54
|
+
if (metrics.aiNeurons) {
|
|
55
|
+
cost += (metrics.aiNeurons / 1000) * PRICING_TIERS.workersAI.neuronsPerThousand;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Durable Objects: $0.15/million requests, $12.50/million GB-seconds
|
|
59
|
+
if (metrics.doRequests) {
|
|
60
|
+
cost += (metrics.doRequests / 1e6) * PRICING_TIERS.durableObjects.requestsPerMillion;
|
|
61
|
+
}
|
|
62
|
+
if (metrics.doGbSeconds) {
|
|
63
|
+
cost += (metrics.doGbSeconds / 1e6) * PRICING_TIERS.durableObjects.gbSecondsPerMillion;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Vectorize: $0.01/million queried dimensions
|
|
67
|
+
if (metrics.vectorizeQueries) {
|
|
68
|
+
cost += (metrics.vectorizeQueries / 1e6) * PRICING_TIERS.vectorize.queriedDimensionsPerMillion;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Queues: $0.40/million messages
|
|
72
|
+
if (metrics.queueMessages) {
|
|
73
|
+
cost += (metrics.queueMessages / 1e6) * PRICING_TIERS.queues.messagesPerMillion;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return cost;
|
|
77
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Letter Queue Handler
|
|
3
|
+
*
|
|
4
|
+
* Consumes messages from the platform-telemetry-dlq queue and persists them
|
|
5
|
+
* to D1 for admin visibility, debugging, and replay.
|
|
6
|
+
*
|
|
7
|
+
* Messages land here after max_retries (5) failures in the main queue consumer.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MessageBatch } from '@cloudflare/workers-types';
|
|
11
|
+
import type { Env, TelemetryMessage } from '../shared';
|
|
12
|
+
import { createLoggerFromEnv } from '@littlebearapps/platform-consumer-sdk';
|
|
13
|
+
import { categoriseError } from '@littlebearapps/platform-consumer-sdk';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// DLQ CONSTANTS
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
const MAX_ERROR_MESSAGE_LENGTH = 1000;
|
|
20
|
+
const MAX_PAYLOAD_LENGTH = 10000;
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// DLQ MESSAGE PERSISTENCE
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Persist a DLQ message to D1 for admin visibility.
|
|
28
|
+
*/
|
|
29
|
+
async function persistDLQMessage(
|
|
30
|
+
telemetry: TelemetryMessage,
|
|
31
|
+
errorMessage: string | null,
|
|
32
|
+
errorCategory: string,
|
|
33
|
+
errorFingerprint: string,
|
|
34
|
+
retryCount: number,
|
|
35
|
+
env: Env
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const payload = JSON.stringify(telemetry);
|
|
38
|
+
const truncatedPayload =
|
|
39
|
+
payload.length > MAX_PAYLOAD_LENGTH ? payload.slice(0, MAX_PAYLOAD_LENGTH) + '...' : payload;
|
|
40
|
+
const truncatedError = errorMessage?.slice(0, MAX_ERROR_MESSAGE_LENGTH) || null;
|
|
41
|
+
|
|
42
|
+
await env.PLATFORM_DB.prepare(
|
|
43
|
+
`INSERT INTO dead_letter_queue (
|
|
44
|
+
id, message_payload, feature_key, project, category, feature,
|
|
45
|
+
error_message, error_category, error_fingerprint, retry_count,
|
|
46
|
+
correlation_id, original_timestamp, status, created_at, updated_at
|
|
47
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', unixepoch(), unixepoch())`
|
|
48
|
+
)
|
|
49
|
+
.bind(
|
|
50
|
+
crypto.randomUUID(),
|
|
51
|
+
truncatedPayload,
|
|
52
|
+
telemetry.feature_key,
|
|
53
|
+
telemetry.project,
|
|
54
|
+
telemetry.category,
|
|
55
|
+
telemetry.feature,
|
|
56
|
+
truncatedError,
|
|
57
|
+
errorCategory,
|
|
58
|
+
errorFingerprint,
|
|
59
|
+
retryCount,
|
|
60
|
+
telemetry.correlation_id || null,
|
|
61
|
+
telemetry.timestamp
|
|
62
|
+
)
|
|
63
|
+
.run();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// DLQ QUEUE HANDLER
|
|
68
|
+
// =============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle messages from the Dead Letter Queue.
|
|
72
|
+
*
|
|
73
|
+
* Messages arrive here after exhausting retries in the main queue.
|
|
74
|
+
* We persist them to D1 for visibility and always ack to prevent re-delivery.
|
|
75
|
+
*/
|
|
76
|
+
async function handleDLQ(batch: MessageBatch<TelemetryMessage>, env: Env): Promise<void> {
|
|
77
|
+
const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:dlq');
|
|
78
|
+
log.warn('Processing DLQ batch', { messages: batch.messages.length });
|
|
79
|
+
|
|
80
|
+
let successCount = 0;
|
|
81
|
+
let errorCount = 0;
|
|
82
|
+
|
|
83
|
+
for (const message of batch.messages) {
|
|
84
|
+
try {
|
|
85
|
+
const telemetry = message.body;
|
|
86
|
+
|
|
87
|
+
// Extract error info from the message metadata if available
|
|
88
|
+
// Cloudflare doesn't expose retry count directly, so we use max_retries setting
|
|
89
|
+
const retryCount = 5; // Matches max_retries in wrangler config
|
|
90
|
+
|
|
91
|
+
// Since we don't have the original error, categorise based on telemetry content
|
|
92
|
+
const errorCategory = telemetry.error_category || 'INTERNAL';
|
|
93
|
+
const errorFingerprint = `dlq:${telemetry.feature_key}:${errorCategory}`;
|
|
94
|
+
|
|
95
|
+
// Persist to D1
|
|
96
|
+
await persistDLQMessage(
|
|
97
|
+
telemetry,
|
|
98
|
+
'Message exhausted retries in telemetry queue',
|
|
99
|
+
errorCategory,
|
|
100
|
+
errorFingerprint,
|
|
101
|
+
retryCount,
|
|
102
|
+
env
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
log.info('DLQ message persisted', {
|
|
106
|
+
feature_key: telemetry.feature_key,
|
|
107
|
+
project: telemetry.project,
|
|
108
|
+
error_category: errorCategory,
|
|
109
|
+
correlation_id: telemetry.correlation_id,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
message.ack();
|
|
113
|
+
successCount++;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// Even if D1 write fails, ack the message to prevent infinite loop
|
|
116
|
+
// Log the error for investigation
|
|
117
|
+
const errorCategory = categoriseError(error);
|
|
118
|
+
log.error('Failed to persist DLQ message, acknowledging anyway', error, {
|
|
119
|
+
feature_key: message.body.feature_key,
|
|
120
|
+
error_category: errorCategory,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
message.ack();
|
|
124
|
+
errorCount++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
log.info('DLQ batch complete', {
|
|
129
|
+
persisted: successCount,
|
|
130
|
+
failed: errorCount,
|
|
131
|
+
total: batch.messages.length,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Send alert if DLQ is receiving messages (indicates systemic issue)
|
|
135
|
+
if (batch.messages.length > 0 && env.ALERT_ROUTER) {
|
|
136
|
+
try {
|
|
137
|
+
await env.ALERT_ROUTER.fetch('https://alert-router/errors', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
type: 'p1_digest',
|
|
142
|
+
feature_key: 'platform:usage:dlq',
|
|
143
|
+
project: 'platform',
|
|
144
|
+
category: 'usage',
|
|
145
|
+
feature: 'dlq',
|
|
146
|
+
total_errors: batch.messages.length,
|
|
147
|
+
distinct_types: new Set(batch.messages.map((m) => m.body.error_category || 'INTERNAL'))
|
|
148
|
+
.size,
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
} catch (alertError) {
|
|
152
|
+
log.error('Failed to send DLQ alert', alertError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// EXPORTS
|
|
159
|
+
// =============================================================================
|
|
160
|
+
|
|
161
|
+
export { handleDLQ, persistDLQMessage };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Barrel export for all queue processing modules.
|
|
5
|
+
* These handle telemetry queue consumption, budget enforcement, and circuit breakers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Telemetry processing (queue consumer, heartbeat handling)
|
|
9
|
+
export * from './telemetry-processor';
|
|
10
|
+
|
|
11
|
+
// Dead Letter Queue handler
|
|
12
|
+
export * from './dlq-handler';
|
|
13
|
+
|
|
14
|
+
// Budget enforcement (circuit breakers, status tracking)
|
|
15
|
+
export * from './budget-enforcement';
|
|
16
|
+
|
|
17
|
+
// Cost calculation and enforcement
|
|
18
|
+
export * from './cost-calculator';
|
|
19
|
+
export * from './cost-budget-enforcement';
|