@littlebearapps/platform-admin-sdk 1.4.2 → 2.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.
Files changed (189) hide show
  1. package/dist/templates.d.ts +1 -1
  2. package/dist/templates.js +232 -2
  3. package/package.json +1 -1
  4. package/templates/full/config/audit-targets.yaml +72 -0
  5. package/templates/full/dashboard/src/components/notifications/NotificationBell.tsx +30 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationList.tsx +116 -0
  7. package/templates/full/dashboard/src/components/notifications/index.ts +2 -0
  8. package/templates/full/dashboard/src/components/patterns/ActivePatterns.tsx +62 -0
  9. package/templates/full/dashboard/src/components/patterns/PatternStats.tsx +60 -0
  10. package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
  11. package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
  12. package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
  13. package/templates/full/dashboard/src/components/patterns/index.ts +5 -0
  14. package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
  15. package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
  16. package/templates/full/dashboard/src/components/reports/index.ts +2 -0
  17. package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
  18. package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
  19. package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
  20. package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
  21. package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
  22. package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
  23. package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
  24. package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
  25. package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
  26. package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
  27. package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
  28. package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
  29. package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
  30. package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
  31. package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
  32. package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
  33. package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
  34. package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
  35. package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
  36. package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
  37. package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
  38. package/templates/full/dashboard/src/pages/notifications.astro +11 -0
  39. package/templates/full/migrations/008_auditor.sql +99 -0
  40. package/templates/full/migrations/010_pricing_versions.sql +110 -0
  41. package/templates/full/migrations/011_multi_account.sql +51 -0
  42. package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
  43. package/templates/full/scripts/ops/universal-backfill.ts +147 -0
  44. package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
  45. package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
  46. package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
  47. package/templates/full/workers/lib/auditor/index.ts +9 -0
  48. package/templates/full/workers/lib/auditor/types.ts +167 -0
  49. package/templates/full/workers/platform-auditor.ts +1071 -0
  50. package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
  51. package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
  52. package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
  53. package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
  54. package/templates/shared/.github/workflows/security.yml +33 -0
  55. package/templates/shared/config/observability.yaml.hbs +276 -0
  56. package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
  57. package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
  58. package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
  59. package/templates/shared/dashboard/astro.config.mjs +21 -0
  60. package/templates/shared/dashboard/package.json.hbs +29 -0
  61. package/templates/shared/dashboard/src/components/Header.astro +29 -0
  62. package/templates/shared/dashboard/src/components/Nav.astro.hbs +59 -0
  63. package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
  64. package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
  65. package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
  66. package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
  67. package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
  68. package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
  69. package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
  70. package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
  71. package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
  72. package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
  73. package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
  74. package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
  75. package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
  76. package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
  77. package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
  78. package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
  79. package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
  80. package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
  81. package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
  82. package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
  83. package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
  84. package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
  85. package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
  86. package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
  87. package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
  88. package/templates/shared/dashboard/src/components/ui/index.ts +9 -0
  89. package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
  90. package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
  91. package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
  92. package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
  93. package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
  94. package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
  95. package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
  96. package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
  97. package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
  98. package/templates/shared/dashboard/src/lib/types.ts +72 -0
  99. package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
  100. package/templates/shared/dashboard/src/middleware/index.ts +1 -0
  101. package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
  102. package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
  103. package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
  104. package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
  105. package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
  106. package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
  107. package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
  108. package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
  109. package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
  110. package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
  111. package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
  112. package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
  113. package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
  114. package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
  115. package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
  116. package/templates/shared/dashboard/src/pages/index.astro +3 -0
  117. package/templates/shared/dashboard/src/pages/resources.astro +11 -0
  118. package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
  119. package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
  120. package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
  121. package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
  122. package/templates/shared/dashboard/src/styles/global.css +29 -0
  123. package/templates/shared/dashboard/tailwind.config.mjs +9 -0
  124. package/templates/shared/dashboard/tsconfig.json +9 -0
  125. package/templates/shared/dashboard/wrangler.json.hbs +47 -0
  126. package/templates/shared/docs/architecture.md +89 -0
  127. package/templates/shared/docs/post-deploy-runbook.md +126 -0
  128. package/templates/shared/docs/troubleshooting.md +91 -0
  129. package/templates/shared/package.json.hbs +17 -1
  130. package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
  131. package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
  132. package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
  133. package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
  134. package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
  135. package/templates/shared/scripts/ops/validate-controls.js +141 -0
  136. package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
  137. package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
  138. package/templates/shared/scripts/validate-schemas.js +61 -0
  139. package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
  140. package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
  141. package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
  142. package/templates/shared/tests/helpers/mock-d1.ts +61 -0
  143. package/templates/shared/tests/helpers/mock-kv.ts +37 -0
  144. package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
  145. package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
  146. package/templates/shared/vitest.config.ts +18 -0
  147. package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
  148. package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
  149. package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
  150. package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
  151. package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
  152. package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
  153. package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
  154. package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
  155. package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
  156. package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
  157. package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
  158. package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
  159. package/templates/shared/workers/platform-usage.ts +98 -8
  160. package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
  161. package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
  162. package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
  163. package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
  164. package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
  165. package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
  166. package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
  167. package/templates/standard/dashboard/src/components/health/index.ts +4 -0
  168. package/templates/standard/dashboard/src/lib/errors.ts +28 -0
  169. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
  170. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
  171. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
  172. package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
  173. package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
  174. package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
  175. package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
  176. package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
  177. package/templates/standard/dashboard/src/pages/errors.astro +13 -0
  178. package/templates/standard/dashboard/src/pages/health.astro +11 -0
  179. package/templates/standard/migrations/009_topology_mapper.sql +65 -0
  180. package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
  181. package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
  182. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
  183. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
  184. package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
  185. package/templates/standard/workers/lib/mapper/index.ts +7 -0
  186. package/templates/standard/workers/platform-mapper.ts +482 -0
  187. package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
  188. package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
  189. package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
@@ -0,0 +1,27 @@
1
+ import type { APIRoute } from 'astro';
2
+
3
+ export const GET: APIRoute = async ({ locals }) => {
4
+ const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
5
+
6
+ if (!db) {
7
+ return new Response(JSON.stringify({ indexed: 0, lastUpdated: null }), {
8
+ headers: { 'Content-Type': 'application/json' },
9
+ });
10
+ }
11
+
12
+ try {
13
+ const count = await db
14
+ .prepare(`SELECT COUNT(*) as count FROM search_index LIMIT 1`)
15
+ .first<{ count: number }>();
16
+
17
+ return new Response(
18
+ JSON.stringify({ indexed: count?.count ?? 0 }),
19
+ { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=60' } }
20
+ );
21
+ } catch {
22
+ return new Response(JSON.stringify({ indexed: 0 }), {
23
+ status: 500,
24
+ headers: { 'Content-Type': 'application/json' },
25
+ });
26
+ }
27
+ };
@@ -0,0 +1,37 @@
1
+ import type { APIRoute } from 'astro';
2
+
3
+ export const GET: APIRoute = async ({ locals }) => {
4
+ const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
5
+
6
+ if (!db) {
7
+ return new Response(JSON.stringify({ settings: {} }), {
8
+ headers: { 'Content-Type': 'application/json' },
9
+ });
10
+ }
11
+
12
+ try {
13
+ const rows = await db
14
+ .prepare(
15
+ `SELECT key, value, updated_at
16
+ FROM platform_settings
17
+ ORDER BY key ASC
18
+ LIMIT 200`
19
+ )
20
+ .all<{ key: string; value: string; updated_at: string }>();
21
+
22
+ const settings: Record<string, { value: string; updatedAt: string }> = {};
23
+ for (const row of rows.results ?? []) {
24
+ settings[row.key] = { value: row.value, updatedAt: row.updated_at };
25
+ }
26
+
27
+ return new Response(
28
+ JSON.stringify({ settings }),
29
+ { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=30' } }
30
+ );
31
+ } catch {
32
+ return new Response(JSON.stringify({ settings: {} }), {
33
+ status: 500,
34
+ headers: { 'Content-Type': 'application/json' },
35
+ });
36
+ }
37
+ };
@@ -0,0 +1,41 @@
1
+ import type { APIRoute } from 'astro';
2
+
3
+ export const POST: APIRoute = async ({ request, locals }) => {
4
+ const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
5
+
6
+ if (!db) {
7
+ return new Response(JSON.stringify({ error: 'Database not available' }), {
8
+ status: 503,
9
+ headers: { 'Content-Type': 'application/json' },
10
+ });
11
+ }
12
+
13
+ try {
14
+ const body = (await request.json()) as { key?: string; value?: string };
15
+ if (!body.key || body.value === undefined) {
16
+ return new Response(JSON.stringify({ error: 'key and value are required' }), {
17
+ status: 400,
18
+ headers: { 'Content-Type': 'application/json' },
19
+ });
20
+ }
21
+
22
+ await db
23
+ .prepare(
24
+ `INSERT INTO platform_settings (key, value, updated_at)
25
+ VALUES (?, ?, datetime('now'))
26
+ ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
27
+ )
28
+ .bind(body.key, body.value)
29
+ .run();
30
+
31
+ return new Response(
32
+ JSON.stringify({ ok: true, key: body.key }),
33
+ { headers: { 'Content-Type': 'application/json' } }
34
+ );
35
+ } catch {
36
+ return new Response(JSON.stringify({ error: 'Update failed' }), {
37
+ status: 500,
38
+ headers: { 'Content-Type': 'application/json' },
39
+ });
40
+ }
41
+ };
@@ -0,0 +1,56 @@
1
+ import type { APIRoute } from 'astro';
2
+
3
+ export const GET: APIRoute = async ({ locals }) => {
4
+ const db = (locals.runtime?.env as { PLATFORM_DB?: D1Database } | undefined)?.PLATFORM_DB;
5
+
6
+ if (!db) {
7
+ return new Response(JSON.stringify({ nodes: [], edges: [] }), {
8
+ headers: { 'Content-Type': 'application/json' },
9
+ });
10
+ }
11
+
12
+ try {
13
+ const [workers, bindings] = await Promise.all([
14
+ db
15
+ .prepare(
16
+ `SELECT script_name, project, worker_type, last_seen_at
17
+ FROM resource_project_mapping
18
+ WHERE resource_type = 'worker'
19
+ ORDER BY script_name ASC
20
+ LIMIT 100`
21
+ )
22
+ .all(),
23
+ db
24
+ .prepare(
25
+ `SELECT source_worker, target_worker, binding_type
26
+ FROM service_bindings
27
+ ORDER BY source_worker ASC
28
+ LIMIT 200`
29
+ )
30
+ .all(),
31
+ ]);
32
+
33
+ const nodes = (workers.results ?? []).map((w: Record<string, unknown>) => ({
34
+ id: w.script_name as string,
35
+ project: w.project as string,
36
+ type: w.worker_type as string,
37
+ lastSeen: w.last_seen_at as string,
38
+ }));
39
+
40
+ const edges = (bindings.results ?? []).map((b: Record<string, unknown>) => ({
41
+ from: b.source_worker as string,
42
+ to: b.target_worker as string,
43
+ type: b.binding_type as string,
44
+ }));
45
+
46
+ return new Response(
47
+ JSON.stringify({ nodes, edges }),
48
+ { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=300' } }
49
+ );
50
+ } catch {
51
+ return new Response(JSON.stringify({ nodes: [], edges: [] }), {
52
+ status: 500,
53
+ headers: { 'Content-Type': 'application/json' },
54
+ });
55
+ }
56
+ };
@@ -0,0 +1,11 @@
1
+ ---
2
+ import DashboardLayout from '../../layouts/DashboardLayout.astro';
3
+ import { NotificationList } from '../../components/notifications/NotificationList';
4
+ ---
5
+
6
+ <DashboardLayout title="Notifications">
7
+ <div class="max-w-4xl mx-auto">
8
+ <h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Notifications</h2>
9
+ <NotificationList client:load />
10
+ </div>
11
+ </DashboardLayout>
@@ -0,0 +1,99 @@
1
+ -- =============================================================================
2
+ -- 008_auditor.sql — Auditor Reports & Feature Coverage
3
+ -- =============================================================================
4
+ -- Additional tables for FULL tier SDK integration auditor.
5
+ -- Base audit_results table is in shared migration 004_settings_alerts.sql.
6
+ --
7
+ -- Tables:
8
+ -- attribution_reports — Resource-to-project mapping status
9
+ -- feature_coverage_audit — Feature active/dormant/undefined tracking
10
+ -- comprehensive_audit_reports — Weekly aggregated audit findings
11
+ -- =============================================================================
12
+
13
+
14
+ -- =============================================================================
15
+ -- ATTRIBUTION REPORTS (from platform-mapper discovery runs)
16
+ -- =============================================================================
17
+ -- Stores resource-to-project attribution status from topology discovery.
18
+
19
+ CREATE TABLE IF NOT EXISTS attribution_reports (
20
+ id TEXT PRIMARY KEY,
21
+ discovery_time TEXT NOT NULL,
22
+ total_resources INTEGER NOT NULL DEFAULT 0,
23
+ attributed_count INTEGER NOT NULL DEFAULT 0,
24
+ unattributed_count INTEGER NOT NULL DEFAULT 0,
25
+ report_json TEXT NOT NULL,
26
+ created_at TEXT DEFAULT (datetime('now'))
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_attribution_discovery_time
30
+ ON attribution_reports(discovery_time);
31
+
32
+
33
+ -- =============================================================================
34
+ -- FEATURE COVERAGE AUDIT
35
+ -- =============================================================================
36
+ -- Tracks which features from budgets.yaml are active vs dormant.
37
+ -- Populated by platform-auditor feature coverage checks.
38
+
39
+ CREATE TABLE IF NOT EXISTS feature_coverage_audit (
40
+ id TEXT PRIMARY KEY,
41
+ audit_time TEXT NOT NULL,
42
+ project TEXT NOT NULL,
43
+ feature TEXT NOT NULL,
44
+ -- Status: active (heartbeats in last 7d), dormant (defined but no heartbeats),
45
+ -- undefined (reporting telemetry but not in config)
46
+ status TEXT NOT NULL CHECK (status IN ('active', 'dormant', 'undefined')),
47
+ last_heartbeat TEXT,
48
+ events_last_7d INTEGER DEFAULT 0,
49
+ defined_budget INTEGER,
50
+ budget_unit TEXT,
51
+ created_at TEXT DEFAULT (datetime('now'))
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_feature_coverage_project
55
+ ON feature_coverage_audit(project);
56
+ CREATE INDEX IF NOT EXISTS idx_feature_coverage_status
57
+ ON feature_coverage_audit(status);
58
+ CREATE INDEX IF NOT EXISTS idx_feature_coverage_audit_time
59
+ ON feature_coverage_audit(audit_time);
60
+
61
+
62
+ -- =============================================================================
63
+ -- COMPREHENSIVE AUDIT REPORTS
64
+ -- =============================================================================
65
+ -- Weekly aggregated reports combining all audit dimensions:
66
+ -- gap analysis, resource attribution, feature coverage, and AI Judge scores.
67
+
68
+ CREATE TABLE IF NOT EXISTS comprehensive_audit_reports (
69
+ id TEXT PRIMARY KEY,
70
+ generated_at TEXT NOT NULL,
71
+ -- Gap summary (from sentinel gap_detection_log)
72
+ gap_events_count INTEGER NOT NULL DEFAULT 0,
73
+ total_missing_hours INTEGER NOT NULL DEFAULT 0,
74
+ worst_gap_day TEXT,
75
+ average_gap_severity TEXT,
76
+ -- Attribution summary (from mapper attribution_reports)
77
+ total_resources INTEGER NOT NULL DEFAULT 0,
78
+ attributed_count INTEGER NOT NULL DEFAULT 0,
79
+ unattributed_count INTEGER NOT NULL DEFAULT 0,
80
+ unattributed_resources TEXT, -- JSON array of {type, name}
81
+ -- Feature coverage summary
82
+ defined_features_count INTEGER NOT NULL DEFAULT 0,
83
+ active_features_count INTEGER NOT NULL DEFAULT 0,
84
+ dormant_features_count INTEGER NOT NULL DEFAULT 0,
85
+ undefined_features_count INTEGER NOT NULL DEFAULT 0,
86
+ -- AI Judge summary
87
+ ai_judge_avg_score REAL,
88
+ ai_judge_recommendations TEXT, -- JSON array
89
+ -- Action items
90
+ action_items_count INTEGER NOT NULL DEFAULT 0,
91
+ critical_items_count INTEGER NOT NULL DEFAULT 0,
92
+ action_items TEXT, -- JSON array
93
+ -- Full report
94
+ report_json TEXT NOT NULL,
95
+ created_at TEXT DEFAULT (datetime('now'))
96
+ );
97
+
98
+ CREATE INDEX IF NOT EXISTS idx_comprehensive_audit_generated
99
+ ON comprehensive_audit_reports(generated_at);
@@ -0,0 +1,110 @@
1
+ -- Migration 010: Pricing Version Tracking
2
+ --
3
+ -- Purpose: Audit trail for Cloudflare pricing changes with historical recomputation support.
4
+ --
5
+ -- Benefits:
6
+ -- - Track when pricing changes occur (Cloudflare updates rates periodically)
7
+ -- - Recompute historical costs when pricing is corrected
8
+ -- - Audit trail for billing reconciliation
9
+ -- - Support for pricing A/B comparisons
10
+
11
+ -- Create pricing_versions table
12
+ CREATE TABLE IF NOT EXISTS pricing_versions (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ version_name TEXT NOT NULL, -- Human-readable name (e.g., "2026-01", "2025-workers-update")
15
+ effective_from TEXT NOT NULL, -- ISO date when pricing became effective
16
+ effective_to TEXT, -- NULL = current, ISO date when superseded
17
+ source_url TEXT, -- CF pricing page reference for audit
18
+ pricing_json TEXT NOT NULL, -- Full pricing config as JSON
19
+ allowances_json TEXT NOT NULL, -- Paid plan allowances as JSON
20
+ notes TEXT, -- Optional notes about changes
21
+ created_at TEXT DEFAULT (datetime('now')),
22
+ created_by TEXT DEFAULT 'system' -- 'system' for auto-detected, 'manual' for user overrides
23
+ );
24
+
25
+ -- Index for querying current/historical pricing
26
+ CREATE INDEX IF NOT EXISTS idx_pricing_versions_effective
27
+ ON pricing_versions (effective_from, effective_to);
28
+
29
+ -- Add pricing_version_id to daily rollups for cost recomputation
30
+ -- Note: SQLite doesn't support adding constraints in ALTER, so we just add the column
31
+ ALTER TABLE daily_usage_rollups ADD COLUMN pricing_version_id INTEGER;
32
+
33
+ -- Add pricing_version_id to monthly rollups
34
+ ALTER TABLE monthly_usage_rollups ADD COLUMN pricing_version_id INTEGER;
35
+
36
+ -- Seed with current pricing (January 2026)
37
+ INSERT INTO pricing_versions (
38
+ version_name,
39
+ effective_from,
40
+ effective_to,
41
+ source_url,
42
+ pricing_json,
43
+ allowances_json,
44
+ notes,
45
+ created_by
46
+ ) VALUES (
47
+ '2026-01-initial',
48
+ '2026-01-01',
49
+ NULL,
50
+ 'https://developers.cloudflare.com/workers/platform/pricing/',
51
+ '{
52
+ "workers": {
53
+ "baseCostMonthly": 5.0,
54
+ "includedRequests": 10000000,
55
+ "requestsPerMillion": 0.3,
56
+ "cpuMsPerMillion": 0.02
57
+ },
58
+ "d1": {
59
+ "rowsReadPerBillion": 0.001,
60
+ "rowsWrittenPerMillion": 1.0,
61
+ "storagePerGb": 0.75
62
+ },
63
+ "kv": {
64
+ "readsPerMillion": 0.5,
65
+ "writesPerMillion": 5.0,
66
+ "deletesPerMillion": 5.0,
67
+ "listsPerMillion": 5.0,
68
+ "storagePerGb": 0.5
69
+ },
70
+ "r2": {
71
+ "storagePerGbMonth": 0.015,
72
+ "classAPerMillion": 4.5,
73
+ "classBPerMillion": 0.36
74
+ },
75
+ "durableObjects": {
76
+ "requestsPerMillion": 0.15,
77
+ "gbSecondsPerMillion": 12.5,
78
+ "storagePerGbMonth": 0.2,
79
+ "readsPerMillion": 0.2,
80
+ "writesPerMillion": 1.0,
81
+ "deletesPerMillion": 1.0
82
+ },
83
+ "vectorize": {
84
+ "storedDimensionsPerMillion": 0.01,
85
+ "queriedDimensionsPerMillion": 0.01
86
+ },
87
+ "aiGateway": { "free": true },
88
+ "workersAI": {
89
+ "neuronsPerThousand": 0.011
90
+ },
91
+ "pages": {
92
+ "buildCost": 0.15,
93
+ "bandwidthPerGb": 0.02
94
+ },
95
+ "queues": {
96
+ "messagesPerMillion": 0.4,
97
+ "operationsPerMillion": 0.4
98
+ },
99
+ "workflows": { "free": true }
100
+ }',
101
+ '{
102
+ "d1": { "rowsRead": 25000000000, "rowsWritten": 50000000 },
103
+ "kv": { "reads": 10000000, "writes": 1000000, "deletes": 1000000, "lists": 1000000 },
104
+ "r2": { "storage": 10000000000, "classA": 1000000, "classB": 10000000 },
105
+ "durableObjects": { "requests": 3000000, "gbSeconds": 400000 },
106
+ "vectorize": { "storedDimensions": 30000000, "queriedDimensions": 30000000 }
107
+ }',
108
+ 'Initial pricing version seeded from Cloudflare pricing pages (January 2026)',
109
+ 'migration'
110
+ );
@@ -0,0 +1,51 @@
1
+ -- Migration 011: Multi-Account Cloudflare Support
2
+ --
3
+ -- Purpose: Registry for managing multiple Cloudflare accounts from a single platform.
4
+ -- Enables federated infrastructure monitoring across accounts.
5
+ --
6
+ -- Use cases:
7
+ -- - Separate accounts for production, staging, client projects
8
+ -- - Consolidated billing and usage visibility across accounts
9
+ -- - Per-account API token management
10
+ --
11
+ -- Architecture:
12
+ -- - Each account has its own CLOUDFLARE_ACCOUNT_ID and API token
13
+ -- - The api_token_env_key column stores the ENV VAR name (not the token itself)
14
+ -- - Workers read the appropriate token from env at runtime
15
+ -- - Primary account is the default for backwards compatibility
16
+
17
+ CREATE TABLE IF NOT EXISTS account_registry (
18
+ account_id TEXT PRIMARY KEY, -- Internal identifier (e.g., "primary", "staging", "client-acme")
19
+ account_name TEXT NOT NULL, -- Human-readable name
20
+ cloudflare_account_id TEXT NOT NULL, -- Cloudflare account ID
21
+ api_token_env_key TEXT NOT NULL, -- ENV var name for the API token (e.g., "CLOUDFLARE_API_TOKEN")
22
+ is_primary INTEGER NOT NULL DEFAULT 0, -- 1 = default account, 0 = secondary
23
+ status TEXT NOT NULL DEFAULT 'active', -- active, disabled
24
+ notes TEXT, -- Optional notes
25
+ created_at TEXT DEFAULT (datetime('now')),
26
+ updated_at TEXT DEFAULT (datetime('now'))
27
+ );
28
+
29
+ -- Ensure only one primary account
30
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_account_registry_primary
31
+ ON account_registry (is_primary) WHERE is_primary = 1;
32
+
33
+ -- Seed with primary account placeholder
34
+ -- TODO: Update cloudflare_account_id with your actual account ID
35
+ INSERT INTO account_registry (
36
+ account_id,
37
+ account_name,
38
+ cloudflare_account_id,
39
+ api_token_env_key,
40
+ is_primary,
41
+ status,
42
+ notes
43
+ ) VALUES (
44
+ 'primary',
45
+ 'Primary Account',
46
+ 'YOUR_CLOUDFLARE_ACCOUNT_ID',
47
+ 'CLOUDFLARE_API_TOKEN',
48
+ 1,
49
+ 'active',
50
+ 'Default account seeded by migration. Update cloudflare_account_id after deployment.'
51
+ );
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Set KV Pricing Configuration
4
+ *
5
+ * This script sets the Cloudflare pricing configuration in the PLATFORM_CACHE KV namespace.
6
+ * The platform-usage worker reads this configuration at runtime to calculate costs.
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/ops/set-kv-pricing.ts [--dry-run]
10
+ *
11
+ * Prerequisites:
12
+ * - wrangler authenticated (npx wrangler login)
13
+ * - KV namespace ID set below (or via PLATFORM_CACHE_KV_ID env var)
14
+ *
15
+ * Pricing sources:
16
+ * - https://developers.cloudflare.com/workers/platform/pricing/
17
+ * - https://developers.cloudflare.com/d1/platform/pricing/
18
+ * - https://developers.cloudflare.com/kv/platform/pricing/
19
+ * - https://developers.cloudflare.com/r2/pricing/
20
+ * - https://developers.cloudflare.com/vectorize/platform/pricing/
21
+ * - https://developers.cloudflare.com/workers-ai/platform/pricing/
22
+ * - https://developers.cloudflare.com/durable-objects/platform/pricing/
23
+ * - https://developers.cloudflare.com/queues/platform/pricing/
24
+ * - https://developers.cloudflare.com/pages/platform/pricing/
25
+ */
26
+
27
+ const PRICING_KEY = 'platform-usage:pricing:v1';
28
+
29
+ // TODO: Set your PLATFORM_CACHE KV namespace ID here or via PLATFORM_CACHE_KV_ID env var
30
+ const KV_NAMESPACE_ID = process.env.PLATFORM_CACHE_KV_ID || 'YOUR_PLATFORM_CACHE_KV_ID';
31
+
32
+ /**
33
+ * Current Cloudflare pricing (as of January 2026)
34
+ *
35
+ * Notes:
36
+ * - All prices in USD
37
+ * - Workers Paid plan includes 10M requests/month and 30M CPU ms/month
38
+ * - D1: First 25B rows read and 50M rows written are included
39
+ * - KV: First 10M reads, 1M writes, 1M deletes, 1M lists, 1GB storage included
40
+ * - R2: First 10GB storage, 1M Class A ops, 10M Class B ops included
41
+ * - Vectorize: First 10M stored dimensions and 50M queried dimensions included
42
+ * - Workers AI: First 10,000 neurons/day included
43
+ * - Durable Objects: First 1M requests, 400K GB-seconds, 1GB storage included
44
+ * - Queues: First 1M operations included
45
+ * - Pages: 500 builds/month included
46
+ *
47
+ * When Cloudflare updates pricing, update this config and re-run the script.
48
+ * Also create a new row in pricing_versions (migration 010) for audit trail.
49
+ */
50
+ const PRICING_CONFIG = {
51
+ version: '2026-01-14',
52
+ lastUpdated: new Date().toISOString(),
53
+ source: 'cloudflare-pricing-pages',
54
+
55
+ workers: {
56
+ baseCostMonthly: 5.0,
57
+ includedRequests: 10_000_000,
58
+ requestsPerMillion: 0.3,
59
+ cpuMsPerMillion: 0.02,
60
+ },
61
+
62
+ d1: {
63
+ rowsReadPerBillion: 0.001,
64
+ rowsWrittenPerMillion: 1.0,
65
+ storagePerGb: 0.75,
66
+ },
67
+
68
+ kv: {
69
+ readsPerMillion: 0.5,
70
+ writesPerMillion: 5.0,
71
+ deletesPerMillion: 5.0,
72
+ listsPerMillion: 5.0,
73
+ storagePerGb: 0.5,
74
+ },
75
+
76
+ r2: {
77
+ storagePerGbMonth: 0.015,
78
+ classAPerMillion: 4.5,
79
+ classBPerMillion: 0.36,
80
+ },
81
+
82
+ vectorize: {
83
+ storedDimensionsPerMillion: 0.01,
84
+ queriedDimensionsPerMillion: 0.04,
85
+ },
86
+
87
+ workersAI: {
88
+ neuronsPerThousand: 0.011,
89
+ },
90
+
91
+ durableObjects: {
92
+ requestsPerMillion: 0.15,
93
+ gbSecondsPerMillion: 12.5,
94
+ storagePerGbMonth: 0.2,
95
+ readsPerMillion: 0.2,
96
+ writesPerMillion: 1.0,
97
+ deletesPerMillion: 1.0,
98
+ },
99
+
100
+ queues: {
101
+ messagesPerMillion: 0.4,
102
+ operationsPerMillion: 0.4,
103
+ },
104
+
105
+ pages: {
106
+ buildCost: 0.0,
107
+ bandwidthPerGb: 0.0,
108
+ },
109
+ };
110
+
111
+ async function main() {
112
+ const isDryRun = process.argv.includes('--dry-run');
113
+
114
+ if (KV_NAMESPACE_ID === 'YOUR_PLATFORM_CACHE_KV_ID') {
115
+ console.error('ERROR: Set PLATFORM_CACHE_KV_ID env var or update KV_NAMESPACE_ID in this script.');
116
+ console.error('Find your namespace ID: npx wrangler kv namespace list');
117
+ process.exit(1);
118
+ }
119
+
120
+ console.log('='.repeat(60));
121
+ console.log('Platform Usage Pricing Configuration');
122
+ console.log('='.repeat(60));
123
+ console.log(`Version: ${PRICING_CONFIG.version}`);
124
+ console.log(`KV Key: ${PRICING_KEY}`);
125
+ console.log(`Namespace: ${KV_NAMESPACE_ID}`);
126
+ console.log(`Dry Run: ${isDryRun}`);
127
+ console.log('');
128
+
129
+ console.log('Pricing Summary:');
130
+ console.log('-'.repeat(40));
131
+ console.log(`Workers: $${PRICING_CONFIG.workers.requestsPerMillion}/M requests`);
132
+ console.log(
133
+ `D1: $${PRICING_CONFIG.d1.rowsReadPerBillion}/B rows read, $${PRICING_CONFIG.d1.rowsWrittenPerMillion}/M rows written`
134
+ );
135
+ console.log(
136
+ `KV: $${PRICING_CONFIG.kv.readsPerMillion}/M reads, $${PRICING_CONFIG.kv.writesPerMillion}/M writes`
137
+ );
138
+ console.log(
139
+ `R2: $${PRICING_CONFIG.r2.storagePerGbMonth}/GB storage, $${PRICING_CONFIG.r2.classAPerMillion}/M Class A`
140
+ );
141
+ console.log(`Vectorize: $${PRICING_CONFIG.vectorize.storedDimensionsPerMillion}/M stored dims`);
142
+ console.log(`Workers AI: $${PRICING_CONFIG.workersAI.neuronsPerThousand}/1K neurons`);
143
+ console.log(`Durable Objects: $${PRICING_CONFIG.durableObjects.requestsPerMillion}/M requests`);
144
+ console.log(`Queues: $${PRICING_CONFIG.queues.messagesPerMillion}/M messages`);
145
+ console.log('');
146
+
147
+ if (isDryRun) {
148
+ console.log('[DRY RUN] Would write the following to KV:');
149
+ console.log(JSON.stringify(PRICING_CONFIG, null, 2));
150
+ return;
151
+ }
152
+
153
+ // Write to KV using wrangler
154
+ const { execSync } = await import('child_process');
155
+ const pricingJson = JSON.stringify(PRICING_CONFIG);
156
+
157
+ try {
158
+ console.log('Writing pricing to KV...');
159
+ execSync(
160
+ `npx wrangler kv key put "${PRICING_KEY}" '${pricingJson}' --namespace-id ${KV_NAMESPACE_ID} --remote`,
161
+ { stdio: 'inherit', cwd: process.cwd() }
162
+ );
163
+ console.log('');
164
+ console.log('Pricing configuration saved successfully.');
165
+ console.log('');
166
+ console.log('To verify:');
167
+ console.log(
168
+ ` npx wrangler kv key get "${PRICING_KEY}" --namespace-id ${KV_NAMESPACE_ID} --remote`
169
+ );
170
+ } catch (error) {
171
+ console.error('Failed to write pricing to KV:', error);
172
+ console.log('');
173
+ console.log('Alternative: Copy the JSON below and use Cloudflare dashboard:');
174
+ console.log('1. Go to Workers & Pages > KV > PLATFORM_CACHE');
175
+ console.log(`2. Create key: ${PRICING_KEY}`);
176
+ console.log('3. Paste value:');
177
+ console.log(JSON.stringify(PRICING_CONFIG, null, 2));
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ main();