@littlebearapps/platform-admin-sdk 1.4.2 → 1.5.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 (111) hide show
  1. package/dist/templates.d.ts +1 -1
  2. package/dist/templates.js +121 -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/PatternStats.tsx +60 -0
  9. package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
  10. package/templates/full/dashboard/src/components/patterns/index.ts +2 -0
  11. package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
  12. package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
  13. package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
  14. package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
  15. package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
  16. package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
  17. package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
  18. package/templates/full/dashboard/src/pages/notifications.astro +11 -0
  19. package/templates/full/migrations/008_auditor.sql +99 -0
  20. package/templates/full/migrations/010_pricing_versions.sql +110 -0
  21. package/templates/full/migrations/011_multi_account.sql +51 -0
  22. package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
  23. package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
  24. package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
  25. package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
  26. package/templates/full/workers/lib/auditor/index.ts +9 -0
  27. package/templates/full/workers/lib/auditor/types.ts +167 -0
  28. package/templates/full/workers/platform-auditor.ts +1071 -0
  29. package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
  30. package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
  31. package/templates/shared/config/observability.yaml.hbs +276 -0
  32. package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
  33. package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
  34. package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
  35. package/templates/shared/dashboard/astro.config.mjs +21 -0
  36. package/templates/shared/dashboard/package.json.hbs +29 -0
  37. package/templates/shared/dashboard/src/components/Header.astro +29 -0
  38. package/templates/shared/dashboard/src/components/Nav.astro.hbs +57 -0
  39. package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
  40. package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
  41. package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
  42. package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
  43. package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
  44. package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
  45. package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
  46. package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
  47. package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
  48. package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
  49. package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
  50. package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
  51. package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
  52. package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
  53. package/templates/shared/dashboard/src/components/ui/index.ts +3 -0
  54. package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
  55. package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
  56. package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
  57. package/templates/shared/dashboard/src/lib/types.ts +72 -0
  58. package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
  59. package/templates/shared/dashboard/src/middleware/index.ts +1 -0
  60. package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
  61. package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
  62. package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
  63. package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
  64. package/templates/shared/dashboard/src/pages/index.astro +3 -0
  65. package/templates/shared/dashboard/src/pages/resources.astro +11 -0
  66. package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
  67. package/templates/shared/dashboard/src/styles/global.css +29 -0
  68. package/templates/shared/dashboard/tailwind.config.mjs +9 -0
  69. package/templates/shared/dashboard/tsconfig.json +9 -0
  70. package/templates/shared/dashboard/wrangler.json.hbs +47 -0
  71. package/templates/shared/package.json.hbs +12 -1
  72. package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
  73. package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
  74. package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
  75. package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
  76. package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
  77. package/templates/shared/scripts/validate-schemas.js +61 -0
  78. package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
  79. package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
  80. package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
  81. package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
  82. package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
  83. package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
  84. package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
  85. package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
  86. package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
  87. package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
  88. package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
  89. package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
  90. package/templates/shared/workers/platform-usage.ts +98 -8
  91. package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
  92. package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
  93. package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
  94. package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
  95. package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
  96. package/templates/standard/dashboard/src/components/health/index.ts +2 -0
  97. package/templates/standard/dashboard/src/lib/errors.ts +28 -0
  98. package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
  99. package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
  100. package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
  101. package/templates/standard/dashboard/src/pages/errors.astro +13 -0
  102. package/templates/standard/dashboard/src/pages/health.astro +11 -0
  103. package/templates/standard/migrations/009_topology_mapper.sql +65 -0
  104. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
  105. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
  106. package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
  107. package/templates/standard/workers/lib/mapper/index.ts +7 -0
  108. package/templates/standard/workers/platform-mapper.ts +482 -0
  109. package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
  110. package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
  111. package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { Tier } from './prompts.js';
8
8
  /** Single source of truth for the SDK version. */
9
- export declare const SDK_VERSION = "1.4.2";
9
+ export declare const SDK_VERSION = "1.5.0";
10
10
  /** Returns true if `to` is the same or higher tier than `from`. */
11
11
  export declare function isTierUpgradeOrSame(from: Tier, to: Tier): boolean;
12
12
  /** Check if a template file is a numbered migration (not seed.sql). */
package/dist/templates.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * All other files are copied verbatim.
6
6
  */
7
7
  /** Single source of truth for the SDK version. */
8
- export const SDK_VERSION = '1.4.2';
8
+ export const SDK_VERSION = '1.5.0';
9
9
  /** Tier ordering for upgrade validation. */
10
10
  const TIER_ORDER = { minimal: 0, standard: 1, full: 2 };
11
11
  /** Returns true if `to` is the same or higher tier than `from`. */
@@ -22,6 +22,16 @@ const SHARED_FILES = [
22
22
  { src: 'shared/config/budgets.yaml.hbs', dest: 'platform/config/budgets.yaml', template: true },
23
23
  // Scripts
24
24
  { src: 'shared/scripts/sync-config.ts', dest: 'scripts/sync-config.ts', template: false },
25
+ { src: 'shared/scripts/validate-schemas.js', dest: 'scripts/validate-schemas.js', template: false },
26
+ // Operational scripts
27
+ { src: 'shared/scripts/ops/backfill-cloudflare-hourly.ts', dest: 'scripts/ops/backfill-cloudflare-hourly.ts', template: false },
28
+ { src: 'shared/scripts/ops/reset-budget-state.ts', dest: 'scripts/ops/reset-budget-state.ts', template: false },
29
+ { src: 'shared/scripts/ops/verify-account-completeness.ts', dest: 'scripts/ops/verify-account-completeness.ts', template: false },
30
+ { src: 'shared/scripts/ops/validate-pipeline.ts', dest: 'scripts/ops/validate-pipeline.ts', template: false },
31
+ // Contracts — JSON schemas + TypeScript types
32
+ { src: 'shared/contracts/schemas/envelope.v1.schema.json', dest: 'contracts/schemas/envelope.v1.schema.json', template: false },
33
+ { src: 'shared/contracts/schemas/error_report.v1.schema.json', dest: 'contracts/schemas/error_report.v1.schema.json', template: false },
34
+ { src: 'shared/contracts/types/telemetry-envelope.types.ts', dest: 'contracts/types/telemetry-envelope.types.ts', template: false },
25
35
  // Migrations (minimal tier)
26
36
  { src: 'shared/migrations/001_core_tables.sql', dest: 'storage/d1/migrations/001_core_tables.sql', template: false },
27
37
  { src: 'shared/migrations/002_usage_warehouse.sql', dest: 'storage/d1/migrations/002_usage_warehouse.sql', template: false },
@@ -79,11 +89,64 @@ const SHARED_FILES = [
79
89
  { src: 'shared/workers/lib/usage/scheduled/anomaly-detection.ts', dest: 'workers/lib/usage/scheduled/anomaly-detection.ts', template: false },
80
90
  { src: 'shared/workers/lib/usage/scheduled/rollups.ts', dest: 'workers/lib/usage/scheduled/rollups.ts', template: false },
81
91
  { src: 'shared/workers/lib/usage/scheduled/error-digest.ts', dest: 'workers/lib/usage/scheduled/error-digest.ts', template: false },
82
- // Workers — lib/usage/collectors (pluggable interface + example)
92
+ // Workers — lib/usage/collectors (framework + 10 provider collectors + example)
83
93
  { src: 'shared/workers/lib/usage/collectors/index.ts', dest: 'workers/lib/usage/collectors/index.ts', template: false },
84
94
  { src: 'shared/workers/lib/usage/collectors/example.ts', dest: 'workers/lib/usage/collectors/example.ts', template: false },
95
+ { src: 'shared/workers/lib/usage/collectors/openai.ts', dest: 'workers/lib/usage/collectors/openai.ts', template: false },
96
+ { src: 'shared/workers/lib/usage/collectors/anthropic.ts', dest: 'workers/lib/usage/collectors/anthropic.ts', template: false },
97
+ { src: 'shared/workers/lib/usage/collectors/github.ts', dest: 'workers/lib/usage/collectors/github.ts', template: false },
98
+ { src: 'shared/workers/lib/usage/collectors/stripe.ts', dest: 'workers/lib/usage/collectors/stripe.ts', template: false },
99
+ { src: 'shared/workers/lib/usage/collectors/apify.ts', dest: 'workers/lib/usage/collectors/apify.ts', template: false },
100
+ { src: 'shared/workers/lib/usage/collectors/resend.ts', dest: 'workers/lib/usage/collectors/resend.ts', template: false },
101
+ { src: 'shared/workers/lib/usage/collectors/deepseek.ts', dest: 'workers/lib/usage/collectors/deepseek.ts', template: false },
102
+ { src: 'shared/workers/lib/usage/collectors/minimax.ts', dest: 'workers/lib/usage/collectors/minimax.ts', template: false },
103
+ { src: 'shared/workers/lib/usage/collectors/gemini.ts', dest: 'workers/lib/usage/collectors/gemini.ts', template: false },
104
+ { src: 'shared/workers/lib/usage/collectors/custom-http.ts', dest: 'workers/lib/usage/collectors/custom-http.ts', template: false },
85
105
  // Documentation
86
106
  { src: 'shared/docs/kv-key-patterns.md', dest: 'docs/kv-key-patterns.md', template: false },
107
+ // Observability config
108
+ { src: 'shared/config/observability.yaml.hbs', dest: 'platform/config/observability.yaml', template: true },
109
+ // GraphQL dataset discovery script
110
+ { src: 'shared/scripts/ops/discover-graphql-datasets.ts', dest: 'scripts/ops/discover-graphql-datasets.ts', template: false },
111
+ // CI workflow (caller for reusable consumer-check)
112
+ { src: 'shared/.github/workflows/platform-check.yml.hbs', dest: '.github/workflows/platform-check.yml', template: true },
113
+ // Dashboard — shared tier (Astro SSR + Cloudflare Pages)
114
+ { src: 'shared/dashboard/package.json.hbs', dest: 'dashboard/package.json', template: true },
115
+ { src: 'shared/dashboard/astro.config.mjs', dest: 'dashboard/astro.config.mjs', template: false },
116
+ { src: 'shared/dashboard/tailwind.config.mjs', dest: 'dashboard/tailwind.config.mjs', template: false },
117
+ { src: 'shared/dashboard/tsconfig.json', dest: 'dashboard/tsconfig.json', template: false },
118
+ { src: 'shared/dashboard/wrangler.json.hbs', dest: 'dashboard/wrangler.json', template: true },
119
+ { src: 'shared/dashboard/src/env.d.ts.hbs', dest: 'dashboard/src/env.d.ts', template: true },
120
+ { src: 'shared/dashboard/src/styles/global.css', dest: 'dashboard/src/styles/global.css', template: false },
121
+ { src: 'shared/dashboard/src/middleware/auth.ts', dest: 'dashboard/src/middleware/auth.ts', template: false },
122
+ { src: 'shared/dashboard/src/middleware/index.ts', dest: 'dashboard/src/middleware/index.ts', template: false },
123
+ { src: 'shared/dashboard/src/layouts/DashboardLayout.astro', dest: 'dashboard/src/layouts/DashboardLayout.astro', template: false },
124
+ { src: 'shared/dashboard/src/components/Nav.astro.hbs', dest: 'dashboard/src/components/Nav.astro', template: true },
125
+ { src: 'shared/dashboard/src/components/Header.astro', dest: 'dashboard/src/components/Header.astro', template: false },
126
+ { src: 'shared/dashboard/src/pages/index.astro', dest: 'dashboard/src/pages/index.astro', template: false },
127
+ { src: 'shared/dashboard/src/pages/dashboard.astro', dest: 'dashboard/src/pages/dashboard.astro', template: false },
128
+ { src: 'shared/dashboard/src/pages/resources.astro', dest: 'dashboard/src/pages/resources.astro', template: false },
129
+ { src: 'shared/dashboard/src/pages/settings/index.astro', dest: 'dashboard/src/pages/settings/index.astro', template: false },
130
+ { src: 'shared/dashboard/src/pages/api/overview/summary.ts', dest: 'dashboard/src/pages/api/overview/summary.ts', template: false },
131
+ { src: 'shared/dashboard/src/pages/api/usage/circuit-breakers.ts', dest: 'dashboard/src/pages/api/usage/circuit-breakers.ts', template: false },
132
+ { src: 'shared/dashboard/src/pages/api/usage/status.ts', dest: 'dashboard/src/pages/api/usage/status.ts', template: false },
133
+ { src: 'shared/dashboard/src/components/overview/MissionControl.tsx', dest: 'dashboard/src/components/overview/MissionControl.tsx', template: false },
134
+ { src: 'shared/dashboard/src/components/overview/HealthQuadrant.tsx', dest: 'dashboard/src/components/overview/HealthQuadrant.tsx', template: false },
135
+ { src: 'shared/dashboard/src/components/overview/ErrorsQuadrant.tsx', dest: 'dashboard/src/components/overview/ErrorsQuadrant.tsx', template: false },
136
+ { src: 'shared/dashboard/src/components/overview/CostQuadrant.tsx', dest: 'dashboard/src/components/overview/CostQuadrant.tsx', template: false },
137
+ { src: 'shared/dashboard/src/components/overview/ActivityFeed.tsx', dest: 'dashboard/src/components/overview/ActivityFeed.tsx', template: false },
138
+ { src: 'shared/dashboard/src/components/ui/AlertBanner.tsx', dest: 'dashboard/src/components/ui/AlertBanner.tsx', template: false },
139
+ { src: 'shared/dashboard/src/components/ui/Sparkline.tsx', dest: 'dashboard/src/components/ui/Sparkline.tsx', template: false },
140
+ { src: 'shared/dashboard/src/components/ui/StatusDot.tsx', dest: 'dashboard/src/components/ui/StatusDot.tsx', template: false },
141
+ { src: 'shared/dashboard/src/components/ui/index.ts', dest: 'dashboard/src/components/ui/index.ts', template: false },
142
+ { src: 'shared/dashboard/src/components/resources/ResourceTabs.tsx', dest: 'dashboard/src/components/resources/ResourceTabs.tsx', template: false },
143
+ { src: 'shared/dashboard/src/components/resources/CostCentreOverview.tsx', dest: 'dashboard/src/components/resources/CostCentreOverview.tsx', template: false },
144
+ { src: 'shared/dashboard/src/components/resources/AllowanceStatus.tsx', dest: 'dashboard/src/components/resources/AllowanceStatus.tsx', template: false },
145
+ { src: 'shared/dashboard/src/components/resources/index.ts', dest: 'dashboard/src/components/resources/index.ts', template: false },
146
+ { src: 'shared/dashboard/src/components/settings/SettingsCard.tsx', dest: 'dashboard/src/components/settings/SettingsCard.tsx', template: false },
147
+ { src: 'shared/dashboard/src/components/settings/index.ts', dest: 'dashboard/src/components/settings/index.ts', template: false },
148
+ { src: 'shared/dashboard/src/lib/types.ts', dest: 'dashboard/src/lib/types.ts', template: false },
149
+ { src: 'shared/dashboard/src/lib/fetch.ts', dest: 'dashboard/src/lib/fetch.ts', template: false },
87
150
  ];
88
151
  const STANDARD_FILES = [
89
152
  // Additional migrations
@@ -105,6 +168,28 @@ const STANDARD_FILES = [
105
168
  { src: 'standard/workers/lib/sentinel/gap-detection.ts', dest: 'workers/lib/sentinel/gap-detection.ts', template: false },
106
169
  // Workers — shared lib (used by standard-tier workers)
107
170
  { src: 'standard/workers/lib/shared/slack-alerts.ts', dest: 'workers/lib/shared/slack-alerts.ts', template: false },
171
+ // Topology mapper (infrastructure discovery + attribution)
172
+ { src: 'standard/migrations/009_topology_mapper.sql', dest: 'storage/d1/migrations/009_topology_mapper.sql', template: false },
173
+ { src: 'standard/wrangler.mapper.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-mapper.jsonc', template: true },
174
+ { src: 'standard/workers/platform-mapper.ts', dest: 'workers/platform-mapper.ts', template: false },
175
+ { src: 'standard/workers/lib/mapper/index.ts', dest: 'workers/lib/mapper/index.ts', template: false },
176
+ { src: 'standard/workers/lib/mapper/attribution-check.ts', dest: 'workers/lib/mapper/attribution-check.ts', template: false },
177
+ // SDK test client (telemetry + circuit breaker validation)
178
+ { src: 'standard/wrangler.sdk-test-client.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-sdk-test-client.jsonc', template: true },
179
+ { src: 'standard/workers/platform-sdk-test-client.ts', dest: 'workers/platform-sdk-test-client.ts', template: false },
180
+ // Dashboard — standard tier (error management, health, DLQ)
181
+ { src: 'standard/dashboard/src/pages/health.astro', dest: 'dashboard/src/pages/health.astro', template: false },
182
+ { src: 'standard/dashboard/src/pages/errors.astro', dest: 'dashboard/src/pages/errors.astro', template: false },
183
+ { src: 'standard/dashboard/src/pages/api/errors/index.ts', dest: 'dashboard/src/pages/api/errors/index.ts', template: false },
184
+ { src: 'standard/dashboard/src/pages/api/errors/stats.ts', dest: 'dashboard/src/pages/api/errors/stats.ts', template: false },
185
+ { src: 'standard/dashboard/src/pages/api/health/dlq.ts', dest: 'dashboard/src/pages/api/health/dlq.ts', template: false },
186
+ { src: 'standard/dashboard/src/components/health/HealthTabs.tsx', dest: 'dashboard/src/components/health/HealthTabs.tsx', template: false },
187
+ { src: 'standard/dashboard/src/components/health/DlqStatusCard.tsx', dest: 'dashboard/src/components/health/DlqStatusCard.tsx', template: false },
188
+ { src: 'standard/dashboard/src/components/health/index.ts', dest: 'dashboard/src/components/health/index.ts', template: false },
189
+ { src: 'standard/dashboard/src/components/errors/ErrorsTable.tsx', dest: 'dashboard/src/components/errors/ErrorsTable.tsx', template: false },
190
+ { src: 'standard/dashboard/src/components/errors/ErrorStats.tsx', dest: 'dashboard/src/components/errors/ErrorStats.tsx', template: false },
191
+ { src: 'standard/dashboard/src/components/errors/index.ts', dest: 'dashboard/src/components/errors/index.ts', template: false },
192
+ { src: 'standard/dashboard/src/lib/errors.ts', dest: 'dashboard/src/lib/errors.ts', template: false },
108
193
  ];
109
194
  const FULL_FILES = [
110
195
  // Additional migrations
@@ -133,6 +218,40 @@ const FULL_FILES = [
133
218
  { src: 'full/workers/platform-search.ts', dest: 'workers/platform-search.ts', template: false },
134
219
  // Workers — platform-settings (settings management API)
135
220
  { src: 'full/workers/platform-settings.ts', dest: 'workers/platform-settings.ts', template: false },
221
+ // Additional migrations — auditor
222
+ { src: 'full/migrations/008_auditor.sql', dest: 'storage/d1/migrations/008_auditor.sql', template: false },
223
+ // Auditor config
224
+ { src: 'full/config/audit-targets.yaml', dest: 'config/audit-targets.yaml', template: false },
225
+ // Wrangler config — auditor
226
+ { src: 'full/wrangler.auditor.jsonc.hbs', dest: 'wrangler.{{projectSlug}}-auditor.jsonc', template: true },
227
+ // Workers — platform-auditor (SDK integration auditor + AI Judge)
228
+ { src: 'full/workers/platform-auditor.ts', dest: 'workers/platform-auditor.ts', template: false },
229
+ { src: 'full/workers/lib/ai-judge-schema.ts', dest: 'workers/lib/ai-judge-schema.ts', template: false },
230
+ { src: 'full/workers/lib/auditor/index.ts', dest: 'workers/lib/auditor/index.ts', template: false },
231
+ { src: 'full/workers/lib/auditor/types.ts', dest: 'workers/lib/auditor/types.ts', template: false },
232
+ { src: 'full/workers/lib/auditor/feature-coverage.ts', dest: 'workers/lib/auditor/feature-coverage.ts', template: false },
233
+ { src: 'full/workers/lib/auditor/comprehensive-report.ts', dest: 'workers/lib/auditor/comprehensive-report.ts', template: false },
234
+ // Pricing version tracking
235
+ { src: 'full/migrations/010_pricing_versions.sql', dest: 'storage/d1/migrations/010_pricing_versions.sql', template: false },
236
+ // Multi-account Cloudflare support
237
+ { src: 'full/migrations/011_multi_account.sql', dest: 'storage/d1/migrations/011_multi_account.sql', template: false },
238
+ // KV pricing configuration script
239
+ { src: 'full/scripts/ops/set-kv-pricing.ts', dest: 'scripts/ops/set-kv-pricing.ts', template: false },
240
+ // Dashboard — full tier (patterns, notifications, search)
241
+ { src: 'full/dashboard/src/pages/notifications.astro', dest: 'dashboard/src/pages/notifications.astro', template: false },
242
+ { src: 'full/dashboard/src/pages/api/patterns/index.ts', dest: 'dashboard/src/pages/api/patterns/index.ts', template: false },
243
+ { src: 'full/dashboard/src/pages/api/patterns/approve.ts', dest: 'dashboard/src/pages/api/patterns/approve.ts', template: false },
244
+ { src: 'full/dashboard/src/pages/api/patterns/reject.ts', dest: 'dashboard/src/pages/api/patterns/reject.ts', template: false },
245
+ { src: 'full/dashboard/src/pages/api/notifications/index.ts', dest: 'dashboard/src/pages/api/notifications/index.ts', template: false },
246
+ { src: 'full/dashboard/src/pages/api/notifications/unread-count.ts', dest: 'dashboard/src/pages/api/notifications/unread-count.ts', template: false },
247
+ { src: 'full/dashboard/src/pages/api/search/index.ts', dest: 'dashboard/src/pages/api/search/index.ts', template: false },
248
+ { src: 'full/dashboard/src/components/patterns/SuggestionsQueue.tsx', dest: 'dashboard/src/components/patterns/SuggestionsQueue.tsx', template: false },
249
+ { src: 'full/dashboard/src/components/patterns/PatternStats.tsx', dest: 'dashboard/src/components/patterns/PatternStats.tsx', template: false },
250
+ { src: 'full/dashboard/src/components/patterns/index.ts', dest: 'dashboard/src/components/patterns/index.ts', template: false },
251
+ { src: 'full/dashboard/src/components/notifications/NotificationBell.tsx', dest: 'dashboard/src/components/notifications/NotificationBell.tsx', template: false },
252
+ { src: 'full/dashboard/src/components/notifications/NotificationList.tsx', dest: 'dashboard/src/components/notifications/NotificationList.tsx', template: false },
253
+ { src: 'full/dashboard/src/components/notifications/index.ts', dest: 'dashboard/src/components/notifications/index.ts', template: false },
254
+ { src: 'full/dashboard/src/components/search/SearchModal.tsx', dest: 'dashboard/src/components/search/SearchModal.tsx', template: false },
136
255
  ];
137
256
  export function getFilesForTier(tier) {
138
257
  const files = [...SHARED_FILES];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlebearapps/platform-admin-sdk",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Platform Admin SDK — scaffold backend infrastructure with workers, circuit breakers, and cost protection for Cloudflare",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,72 @@
1
+ # =============================================================================
2
+ # Audit Targets Configuration
3
+ # =============================================================================
4
+ # Define the projects that the platform-auditor worker should audit.
5
+ # Sync this to KV via: npm run sync:config
6
+ #
7
+ # Each target specifies:
8
+ # repo — GitHub org/repo path (for fetching source files)
9
+ # wranglerPath — Path to main wrangler config (for config checks)
10
+ # entryPoint — Primary SDK integration file (for code smell detection)
11
+ # expectedStatus — Expected audit result: HEALTHY, ZOMBIE, UNTRACKED, BROKEN
12
+ # notes — Human-readable description
13
+ # deepScanFiles — Files per audit dimension (for AI Judge analysis)
14
+ #
15
+ # Status definitions:
16
+ # HEALTHY — SDK integrated, heartbeats fresh, config correct
17
+ # ZOMBIE — Was integrated but dormant (no recent heartbeats)
18
+ # UNTRACKED — Running in production but not using SDK
19
+ # BROKEN — SDK integration present but misconfigured
20
+ # NOT_INTEGRATED — No SDK integration detected
21
+ # =============================================================================
22
+
23
+ targets: {}
24
+ # Example target (uncomment and customise):
25
+ #
26
+ # my-project:
27
+ # repo: my-org/my-project
28
+ # wranglerPath: wrangler.jsonc
29
+ # entryPoint: src/index.ts
30
+ # entryPointFallback: src/worker.ts
31
+ # expectedStatus: HEALTHY
32
+ # notes: Main application worker
33
+ # deepScanFiles:
34
+ # sdk:
35
+ # - src/index.ts
36
+ # - src/middleware/platform-sdk.ts
37
+ # - src/lib/features.ts
38
+ # observability:
39
+ # - src/lib/logger.ts
40
+ # - src/middleware/tracing.ts
41
+ # cost:
42
+ # - src/lib/budgets.ts
43
+ # - src/middleware/circuit-breaker.ts
44
+ # security:
45
+ # - src/middleware/auth.ts
46
+ # - src/lib/validation.ts
47
+
48
+ # SDK patterns to detect in source code (for code smell checks)
49
+ sdkPatterns:
50
+ - withFeatureBudget
51
+ - withCronBudget
52
+ - withQueueBudget
53
+ - trackedEnv
54
+ - platform-sdk
55
+ - completeTracking
56
+ - CircuitBreakerError
57
+ - PLATFORM_CACHE
58
+ - PLATFORM_TELEMETRY
59
+
60
+ # Rubric weights for composite score calculation (must sum to 1.0)
61
+ rubricWeights:
62
+ sdk: 0.3
63
+ observability: 0.25
64
+ costProtection: 0.25
65
+ security: 0.2
66
+
67
+ # Thresholds
68
+ heartbeatFreshnessHours: 24
69
+ focusedScanThreshold: 90
70
+ aiCacheTtlSeconds: 259200 # 3 days
71
+ behavioralAnalysisDays: 30
72
+ hotspotMinCommits: 3
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+
3
+ export function NotificationBell() {
4
+ const [count, setCount] = useState(0);
5
+ const intervalRef = useRef<ReturnType<typeof setInterval>>();
6
+
7
+ useEffect(() => {
8
+ function fetchCount() {
9
+ fetch('/api/notifications/unread-count')
10
+ .then(res => res.json())
11
+ .then((data: { count: number }) => setCount(data.count ?? 0))
12
+ .catch(() => {});
13
+ }
14
+
15
+ fetchCount();
16
+ intervalRef.current = setInterval(fetchCount, 30000);
17
+ return () => clearInterval(intervalRef.current);
18
+ }, []);
19
+
20
+ return (
21
+ <a href="/notifications" className="relative p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700">
22
+ <span className="text-lg">&#128276;</span>
23
+ {count > 0 && (
24
+ <span className="absolute -top-1 -right-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full">
25
+ {count > 99 ? '99+' : count}
26
+ </span>
27
+ )}
28
+ </a>
29
+ );
30
+ }
@@ -0,0 +1,116 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ interface Notification {
4
+ id: string;
5
+ title: string;
6
+ body: string | null;
7
+ category: string;
8
+ priority: string;
9
+ source: string;
10
+ created_at: number;
11
+ read_at: number | null;
12
+ action_url: string | null;
13
+ }
14
+
15
+ export function NotificationList() {
16
+ const [notifications, setNotifications] = useState<Notification[]>([]);
17
+ const [total, setTotal] = useState(0);
18
+ const [page, setPage] = useState(1);
19
+
20
+ useEffect(() => {
21
+ fetch(`/api/notifications?page=${page}&limit=20`)
22
+ .then(res => res.json())
23
+ .then((data: { notifications: Notification[]; total: number }) => {
24
+ setNotifications(data.notifications ?? []);
25
+ setTotal(data.total ?? 0);
26
+ })
27
+ .catch(() => {});
28
+ }, [page]);
29
+
30
+ function formatTime(ts: number): string {
31
+ const now = Date.now();
32
+ const diffMs = now - ts * 1000;
33
+ const diffMins = Math.floor(diffMs / 60000);
34
+ const diffHours = Math.floor(diffMs / 3600000);
35
+ const diffDays = Math.floor(diffMs / 86400000);
36
+
37
+ if (diffMins < 1) return 'Just now';
38
+ if (diffMins < 60) return `${diffMins}m ago`;
39
+ if (diffHours < 24) return `${diffHours}h ago`;
40
+ if (diffDays < 7) return `${diffDays}d ago`;
41
+ return new Date(ts * 1000).toLocaleDateString('en-AU', { day: 'numeric', month: 'short' });
42
+ }
43
+
44
+ const categoryIcons: Record<string, string> = {
45
+ error: '[ERR]',
46
+ warning: '[WARN]',
47
+ info: '[INFO]',
48
+ success: '[OK]',
49
+ };
50
+
51
+ return (
52
+ <div className="space-y-3">
53
+ {notifications.length === 0 ? (
54
+ <div className="text-center py-8 text-gray-500 dark:text-gray-400">
55
+ No notifications.
56
+ </div>
57
+ ) : (
58
+ notifications.map(n => (
59
+ <div
60
+ key={n.id}
61
+ className={`bg-white dark:bg-gray-800 rounded-lg border p-4 ${
62
+ n.read_at ? 'border-gray-200 dark:border-gray-700' : 'border-blue-300 dark:border-blue-700'
63
+ }`}
64
+ >
65
+ <div className="flex items-start gap-3">
66
+ <span className="text-xs font-mono text-gray-500 dark:text-gray-400 mt-0.5">
67
+ {categoryIcons[n.category] ?? '[INFO]'}
68
+ </span>
69
+ <div className="flex-1 min-w-0">
70
+ <div className="flex items-center justify-between gap-2">
71
+ <h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
72
+ {n.action_url ? (
73
+ <a href={n.action_url} className="hover:text-blue-600 dark:hover:text-blue-400">{n.title}</a>
74
+ ) : n.title}
75
+ </h4>
76
+ <span className="text-xs text-gray-400 dark:text-gray-500 flex-shrink-0">
77
+ {formatTime(n.created_at)}
78
+ </span>
79
+ </div>
80
+ {n.body && (
81
+ <p className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">{n.body}</p>
82
+ )}
83
+ <div className="flex items-center gap-2 mt-1.5">
84
+ <span className="text-xs text-gray-400 dark:text-gray-500">{n.source}</span>
85
+ <span className="text-xs text-gray-400 dark:text-gray-500">{n.priority}</span>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ ))
91
+ )}
92
+
93
+ {total > 20 && (
94
+ <div className="flex items-center justify-between pt-2">
95
+ <button
96
+ onClick={() => setPage(p => Math.max(1, p - 1))}
97
+ disabled={page === 1}
98
+ className="text-sm text-blue-600 dark:text-blue-400 disabled:opacity-50"
99
+ >
100
+ Previous
101
+ </button>
102
+ <span className="text-sm text-gray-500 dark:text-gray-400">
103
+ Page {page} of {Math.ceil(total / 20)}
104
+ </span>
105
+ <button
106
+ onClick={() => setPage(p => p + 1)}
107
+ disabled={page >= Math.ceil(total / 20)}
108
+ className="text-sm text-blue-600 dark:text-blue-400 disabled:opacity-50"
109
+ >
110
+ Next
111
+ </button>
112
+ </div>
113
+ )}
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,2 @@
1
+ export { NotificationBell } from './NotificationBell';
2
+ export { NotificationList } from './NotificationList';
@@ -0,0 +1,60 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ interface Stats {
4
+ pending: number;
5
+ shadow: number;
6
+ approved: number;
7
+ rejected: number;
8
+ }
9
+
10
+ export function PatternStats() {
11
+ const [stats, setStats] = useState<Stats | null>(null);
12
+
13
+ useEffect(() => {
14
+ Promise.all([
15
+ fetch('/api/patterns?status=pending').then(r => r.json()),
16
+ fetch('/api/patterns?status=shadow').then(r => r.json()),
17
+ fetch('/api/patterns?status=approved').then(r => r.json()),
18
+ ])
19
+ .then(([pending, shadow, approved]) => {
20
+ setStats({
21
+ pending: (pending as { suggestions: unknown[] }).suggestions?.length ?? 0,
22
+ shadow: (shadow as { suggestions: unknown[] }).suggestions?.length ?? 0,
23
+ approved: (approved as { suggestions: unknown[] }).suggestions?.length ?? 0,
24
+ rejected: 0,
25
+ });
26
+ })
27
+ .catch(() => {});
28
+ }, []);
29
+
30
+ if (!stats) {
31
+ return (
32
+ <div className="grid grid-cols-4 gap-4">
33
+ {Array.from({ length: 4 }).map((_, i) => (
34
+ <div key={i} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 animate-pulse">
35
+ <div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-12 mb-2" />
36
+ <div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-8" />
37
+ </div>
38
+ ))}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ const items = [
44
+ { label: 'Pending', value: stats.pending, colour: 'text-yellow-600 dark:text-yellow-400' },
45
+ { label: 'Shadow', value: stats.shadow, colour: 'text-purple-600 dark:text-purple-400' },
46
+ { label: 'Approved', value: stats.approved, colour: 'text-green-600 dark:text-green-400' },
47
+ { label: 'Rejected', value: stats.rejected, colour: 'text-red-600 dark:text-red-400' },
48
+ ];
49
+
50
+ return (
51
+ <div className="grid grid-cols-4 gap-4">
52
+ {items.map(item => (
53
+ <div key={item.label} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
54
+ <div className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{item.label}</div>
55
+ <div className={`text-2xl font-bold mt-1 ${item.colour}`}>{item.value}</div>
56
+ </div>
57
+ ))}
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,115 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ interface PatternSuggestion {
4
+ id: number;
5
+ pattern_type: string;
6
+ pattern_value: string;
7
+ error_type: string;
8
+ priority: string;
9
+ status: string;
10
+ match_count: number;
11
+ source: string;
12
+ created_at: string;
13
+ reviewed_at: string | null;
14
+ reviewer_notes: string | null;
15
+ }
16
+
17
+ export function SuggestionsQueue() {
18
+ const [suggestions, setSuggestions] = useState<PatternSuggestion[]>([]);
19
+ const [filter, setFilter] = useState<string>('pending');
20
+ const [actioningId, setActioningId] = useState<number | null>(null);
21
+
22
+ const loadSuggestions = useCallback(() => {
23
+ fetch(`/api/patterns?status=${filter}`)
24
+ .then(res => res.json())
25
+ .then((data: { suggestions: PatternSuggestion[] }) => setSuggestions(data.suggestions ?? []))
26
+ .catch(() => {});
27
+ }, [filter]);
28
+
29
+ useEffect(() => { loadSuggestions(); }, [loadSuggestions]);
30
+
31
+ async function handleAction(id: number, action: 'approve' | 'reject') {
32
+ setActioningId(id);
33
+ try {
34
+ const res = await fetch(`/api/patterns/${action}`, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({ id }),
38
+ });
39
+ if (res.ok) {
40
+ setSuggestions(prev => prev.filter(s => s.id !== id));
41
+ }
42
+ } catch {
43
+ // Silently fail
44
+ }
45
+ setActioningId(null);
46
+ }
47
+
48
+ return (
49
+ <div className="space-y-4">
50
+ <div className="flex gap-2">
51
+ {['pending', 'shadow', 'approved', 'rejected'].map(s => (
52
+ <button
53
+ key={s}
54
+ onClick={() => setFilter(s)}
55
+ className={`text-sm px-3 py-1 rounded-full transition-colors ${
56
+ filter === s
57
+ ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300'
58
+ : 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'
59
+ }`}
60
+ >
61
+ {s.charAt(0).toUpperCase() + s.slice(1)}
62
+ </button>
63
+ ))}
64
+ </div>
65
+
66
+ {suggestions.length === 0 ? (
67
+ <div className="text-center py-8 text-gray-500 dark:text-gray-400">
68
+ No {filter} patterns.
69
+ </div>
70
+ ) : (
71
+ <div className="space-y-3">
72
+ {suggestions.map(s => (
73
+ <div key={s.id} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
74
+ <div className="flex items-start justify-between gap-4">
75
+ <div className="flex-1 min-w-0">
76
+ <div className="flex items-center gap-2 mb-1">
77
+ <span className="text-xs px-1.5 py-0.5 rounded bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300 font-mono">
78
+ {s.pattern_type}
79
+ </span>
80
+ <span className="text-xs text-gray-500 dark:text-gray-400">
81
+ {s.source} · {s.match_count} matches
82
+ </span>
83
+ </div>
84
+ <p className="text-sm text-gray-900 dark:text-white font-mono truncate">{s.pattern_value}</p>
85
+ <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
86
+ {s.error_type} · {s.priority} · {new Date(s.created_at).toLocaleDateString('en-AU')}
87
+ </p>
88
+ </div>
89
+
90
+ {filter === 'pending' && (
91
+ <div className="flex gap-2 flex-shrink-0">
92
+ <button
93
+ onClick={() => handleAction(s.id, 'approve')}
94
+ disabled={actioningId === s.id}
95
+ className="text-xs px-3 py-1.5 rounded bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-900/60 disabled:opacity-50"
96
+ >
97
+ Approve
98
+ </button>
99
+ <button
100
+ onClick={() => handleAction(s.id, 'reject')}
101
+ disabled={actioningId === s.id}
102
+ className="text-xs px-3 py-1.5 rounded bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-900/60 disabled:opacity-50"
103
+ >
104
+ Reject
105
+ </button>
106
+ </div>
107
+ )}
108
+ </div>
109
+ </div>
110
+ ))}
111
+ </div>
112
+ )}
113
+ </div>
114
+ );
115
+ }
@@ -0,0 +1,2 @@
1
+ export { SuggestionsQueue } from './SuggestionsQueue';
2
+ export { PatternStats } from './PatternStats';