create-qa-architect 5.0.6 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.github/workflows/auto-release.yml +49 -0
  2. package/.github/workflows/dependabot-auto-merge.yml +32 -0
  3. package/LICENSE +3 -3
  4. package/README.md +54 -15
  5. package/docs/ADOPTION-SUMMARY.md +41 -0
  6. package/docs/ARCHITECTURE-REVIEW.md +67 -0
  7. package/docs/ARCHITECTURE.md +29 -41
  8. package/docs/CODE-REVIEW.md +100 -0
  9. package/docs/PREFLIGHT_REPORT.md +32 -40
  10. package/docs/REQUIREMENTS.md +148 -0
  11. package/docs/SECURITY-AUDIT.md +68 -0
  12. package/docs/TESTING.md +3 -4
  13. package/docs/test-trace-matrix.md +28 -0
  14. package/lib/billing-dashboard.html +6 -12
  15. package/lib/commands/deps.js +245 -0
  16. package/lib/commands/index.js +25 -0
  17. package/lib/commands/validate.js +85 -0
  18. package/lib/error-reporter.js +13 -1
  19. package/lib/github-api.js +108 -13
  20. package/lib/license-signing.js +110 -0
  21. package/lib/license-validator.js +359 -71
  22. package/lib/licensing.js +343 -111
  23. package/lib/prelaunch-validator.js +828 -0
  24. package/lib/quality-tools-generator.js +495 -0
  25. package/lib/result-types.js +112 -0
  26. package/lib/security-enhancements.js +1 -1
  27. package/lib/smart-strategy-generator.js +28 -9
  28. package/lib/template-loader.js +52 -19
  29. package/lib/validation/cache-manager.js +36 -6
  30. package/lib/validation/config-security.js +82 -15
  31. package/lib/validation/workflow-validation.js +49 -23
  32. package/package.json +8 -10
  33. package/scripts/check-test-coverage.sh +46 -0
  34. package/setup.js +356 -285
  35. package/templates/QUALITY_TROUBLESHOOTING.md +32 -33
  36. package/templates/scripts/smart-test-strategy.sh +1 -1
  37. package/create-saas-monetization.js +0 -1513
package/lib/licensing.js CHANGED
@@ -7,6 +7,15 @@ const fs = require('fs')
7
7
  const path = require('path')
8
8
  const os = require('os')
9
9
  const crypto = require('crypto')
10
+ const {
11
+ LICENSE_KEY_PATTERN,
12
+ buildLicensePayload,
13
+ hashEmail,
14
+ signPayload,
15
+ stableStringify,
16
+ verifyPayload,
17
+ loadKeyFromEnv,
18
+ } = require('./license-signing')
10
19
 
11
20
  // License storage locations
12
21
  // Support environment variable override for testing (like telemetry/error-reporter)
@@ -36,17 +45,40 @@ Object.defineProperty(exports, 'LICENSE_FILE', {
36
45
  * Standardized to use SCREAMING_SNAKE_CASE for both keys and values
37
46
  * for consistency with ErrorCategory and other enums in the codebase.
38
47
  *
39
- * Pricing (effective Jan 15, 2026 - founder pricing retired):
48
+ * Pricing:
40
49
  * - FREE: $0 (Hobby/OSS - capped)
41
- * - PRO: $59/mo or $590/yr (Solo Devs/Small Teams)
42
- * - TEAM: $15/user/mo, 5-seat minimum (Organizations)
43
- * - ENTERPRISE: $249/mo annual + $499 onboarding (Large Orgs)
50
+ * - PRO: $19/mo or $190/yr (Solo Devs/Small Teams)
51
+ * - TEAM: Contact us (Organizations) - coming soon
52
+ * - ENTERPRISE: Contact us (Large Orgs) - coming soon
44
53
  */
45
- const LICENSE_TIERS = {
54
+ // DR23 fix: Freeze object to prevent accidental or malicious mutation
55
+ const LICENSE_TIERS = Object.freeze({
46
56
  FREE: 'FREE',
47
57
  PRO: 'PRO',
48
58
  TEAM: 'TEAM',
49
59
  ENTERPRISE: 'ENTERPRISE',
60
+ })
61
+
62
+ function normalizeLicenseKey(key) {
63
+ if (typeof key !== 'string') return ''
64
+ return key.trim().toUpperCase()
65
+ }
66
+
67
+ /**
68
+ * DR23 fix: Deep freeze helper to prevent mutation at any level
69
+ */
70
+ function deepFreeze(obj) {
71
+ Object.freeze(obj)
72
+ Object.getOwnPropertyNames(obj).forEach(prop => {
73
+ if (
74
+ obj[prop] !== null &&
75
+ (typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
76
+ !Object.isFrozen(obj[prop])
77
+ ) {
78
+ deepFreeze(obj[prop])
79
+ }
80
+ })
81
+ return obj
50
82
  }
51
83
 
52
84
  /**
@@ -56,8 +88,10 @@ const LICENSE_TIERS = {
56
88
  * PRO: Solo devs/small teams - unlimited, full features
57
89
  * TEAM: Organizations - per-seat, shared policies
58
90
  * ENTERPRISE: Large orgs - SSO, compliance, SLA
91
+ *
92
+ * DR23 fix: Deep frozen to prevent accidental or malicious mutation
59
93
  */
60
- const FEATURES = {
94
+ const FEATURES = deepFreeze({
61
95
  [LICENSE_TIERS.FREE]: {
62
96
  // Caps (enforced in setup.js and CLI)
63
97
  maxPrivateRepos: 1,
@@ -75,10 +109,26 @@ const FEATURES = {
75
109
  advancedWorkflows: false,
76
110
  notifications: false,
77
111
  multiRepo: false,
112
+ // Quality tools
113
+ lighthouseCI: true, // ✅ Basic Lighthouse (no thresholds)
114
+ lighthouseThresholds: false, // ❌ PRO feature - custom thresholds
115
+ bundleSizeLimits: false, // ❌ PRO feature
116
+ axeAccessibility: true, // ✅ Basic a11y testing
117
+ conventionalCommits: true, // ✅ Commit message enforcement
118
+ coverageThresholds: false, // ❌ PRO feature
119
+ // Pre-launch validation
120
+ prelaunchValidation: true, // ✅ Basic prelaunch checks
121
+ seoValidation: true, // ✅ Sitemap, robots, meta tags
122
+ linkValidation: true, // ✅ Broken link detection
123
+ docsValidation: true, // ✅ Documentation completeness
124
+ envValidation: false, // ❌ PRO feature - env vars audit
78
125
  roadmap: [
79
126
  '✅ ESLint, Prettier, Stylelint configuration',
80
127
  '✅ Basic Husky pre-commit hooks',
81
128
  '✅ Basic npm dependency monitoring (10 PRs/month)',
129
+ '✅ Lighthouse CI (basic, no thresholds)',
130
+ '✅ axe-core accessibility testing',
131
+ '✅ Conventional commits (commitlint)',
82
132
  '⚠️ Limited: 1 private repo, JS/TS only',
83
133
  '❌ No security scanning (Gitleaks, ESLint security)',
84
134
  '❌ No Smart Test Strategy',
@@ -102,6 +152,19 @@ const FEATURES = {
102
152
  advancedWorkflows: false,
103
153
  notifications: false,
104
154
  multiRepo: false,
155
+ // Quality tools - all enabled
156
+ lighthouseCI: true, // ✅ Full Lighthouse CI
157
+ lighthouseThresholds: true, // ✅ Custom performance/a11y thresholds
158
+ bundleSizeLimits: true, // ✅ Bundle size enforcement
159
+ axeAccessibility: true, // ✅ Advanced a11y testing
160
+ conventionalCommits: true, // ✅ Commit message enforcement
161
+ coverageThresholds: true, // ✅ Coverage threshold enforcement
162
+ // Pre-launch validation - all enabled
163
+ prelaunchValidation: true, // ✅ Full prelaunch suite
164
+ seoValidation: true, // ✅ Sitemap, robots, meta tags
165
+ linkValidation: true, // ✅ Broken link detection
166
+ docsValidation: true, // ✅ Documentation completeness
167
+ envValidation: true, // ✅ Env vars audit
105
168
  roadmap: [
106
169
  '✅ Unlimited repos and runs',
107
170
  '✅ Smart Test Strategy (70% faster pre-push validation)',
@@ -109,8 +172,11 @@ const FEATURES = {
109
172
  '✅ TypeScript production protection',
110
173
  '✅ Multi-language (Python, Rust, Ruby)',
111
174
  '✅ Framework-aware dependency grouping',
175
+ '✅ Lighthouse CI with custom thresholds',
176
+ '✅ Bundle size limits (size-limit)',
177
+ '✅ Coverage threshold enforcement',
178
+ '✅ Pre-launch validation with env vars audit',
112
179
  '✅ Email support (24-48h response)',
113
- '🔄 Performance budgets - Coming Q1 2026',
114
180
  ],
115
181
  },
116
182
  [LICENSE_TIERS.TEAM]: {
@@ -139,6 +205,19 @@ const FEATURES = {
139
205
  advancedWorkflows: true,
140
206
  notifications: true,
141
207
  multiRepo: true,
208
+ // Quality tools - all enabled (inherited from PRO)
209
+ lighthouseCI: true,
210
+ lighthouseThresholds: true,
211
+ bundleSizeLimits: true,
212
+ axeAccessibility: true,
213
+ conventionalCommits: true,
214
+ coverageThresholds: true,
215
+ // Pre-launch validation - all enabled (inherited from PRO)
216
+ prelaunchValidation: true,
217
+ seoValidation: true,
218
+ linkValidation: true,
219
+ docsValidation: true,
220
+ envValidation: true,
142
221
  roadmap: [
143
222
  '✅ All PRO features included',
144
223
  '✅ Per-seat licensing (5-seat minimum)',
@@ -175,6 +254,19 @@ const FEATURES = {
175
254
  advancedWorkflows: true,
176
255
  notifications: true,
177
256
  multiRepo: true,
257
+ // Quality tools - all enabled (inherited from TEAM)
258
+ lighthouseCI: true,
259
+ lighthouseThresholds: true,
260
+ bundleSizeLimits: true,
261
+ axeAccessibility: true,
262
+ conventionalCommits: true,
263
+ coverageThresholds: true,
264
+ // Pre-launch validation - all enabled (inherited from TEAM)
265
+ prelaunchValidation: true,
266
+ seoValidation: true,
267
+ linkValidation: true,
268
+ docsValidation: true,
269
+ envValidation: true,
178
270
  // Enterprise-specific
179
271
  ssoIntegration: true, // SSO/SAML
180
272
  scimReady: true,
@@ -198,13 +290,19 @@ const FEATURES = {
198
290
  '✅ Optional on-prem license server',
199
291
  ],
200
292
  },
201
- }
293
+ })
202
294
 
203
295
  /**
204
296
  * Check if developer/owner mode is enabled
205
297
  * Allows the tool creator to use all features without a license
298
+ * Security: Production mode always enforces license checks
206
299
  */
207
300
  function isDeveloperMode() {
301
+ // Security: Production mode never allows developer bypass
302
+ if (process.env.NODE_ENV === 'production') {
303
+ return false
304
+ }
305
+
208
306
  // Check environment variable
209
307
  if (process.env.QAA_DEVELOPER === 'true') {
210
308
  return true
@@ -216,8 +314,19 @@ function isDeveloperMode() {
216
314
  if (fs.existsSync(developerMarkerFile)) {
217
315
  return true
218
316
  }
219
- } catch {
220
- // Ignore errors checking for marker file
317
+ } catch (error) {
318
+ // DR10 fix: Halt on ELOOP in production (security issue)
319
+ if (error?.code === 'ELOOP') {
320
+ const message =
321
+ 'Symlink loop detected in license directory - possible security issue'
322
+ if (process.env.NODE_ENV === 'production') {
323
+ throw new Error(message)
324
+ } else {
325
+ console.warn(`⚠️ ${message}`)
326
+ }
327
+ } else if (process.env.DEBUG && error?.code !== 'ENOENT') {
328
+ console.warn(`⚠️ Developer mode check failed: ${error.message}`)
329
+ }
221
330
  }
222
331
 
223
332
  return false
@@ -248,23 +357,24 @@ function getLicenseInfo() {
248
357
  return { tier: LICENSE_TIERS.FREE, valid: true }
249
358
  }
250
359
 
251
- // Check if license is valid
252
- if (!localLicense.valid) {
360
+ const licenseKey = normalizeLicenseKey(
361
+ localLicense.licenseKey || localLicense.key
362
+ )
363
+ if (!licenseKey || !localLicense.email) {
253
364
  return {
254
365
  tier: LICENSE_TIERS.FREE,
255
366
  valid: true,
256
- error:
257
- 'License signature verification failed - license may have been tampered with',
367
+ error: 'Invalid license format',
258
368
  }
259
369
  }
260
370
 
261
- // Check for required fields
262
- const licenseKey = localLicense.licenseKey || localLicense.key
263
- if (!licenseKey || !localLicense.email) {
371
+ // Check if license is valid
372
+ if (!localLicense.valid) {
264
373
  return {
265
374
  tier: LICENSE_TIERS.FREE,
266
375
  valid: true,
267
- error: 'Invalid license format',
376
+ error:
377
+ 'License signature verification failed - license may have been tampered with',
268
378
  }
269
379
  }
270
380
 
@@ -309,17 +419,16 @@ function getLicenseInfo() {
309
419
  * Supports both legacy format and new Stripe-generated keys
310
420
  */
311
421
  function validateLicenseKey(key, tier) {
312
- // New Stripe format: QAA-XXXX-XXXX-XXXX-XXXX
313
- const stripeFormat = /^QAA-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}$/
314
-
315
- if (stripeFormat.test(key)) {
422
+ const normalizedKey = normalizeLicenseKey(key)
423
+ // TD15 fix: Use shared constant for license key pattern
424
+ if (LICENSE_KEY_PATTERN.test(normalizedKey)) {
316
425
  // Stripe-generated key - always valid if properly formatted
317
426
  return true
318
427
  }
319
428
 
320
429
  // Legacy format validation for backward compatibility
321
430
  const expectedPrefix = `QAA-${tier.toUpperCase()}-`
322
- return key.startsWith(expectedPrefix) && key.length > 20
431
+ return normalizedKey.startsWith(expectedPrefix) && normalizedKey.length > 20
323
432
  }
324
433
 
325
434
  /**
@@ -327,19 +436,27 @@ function validateLicenseKey(key, tier) {
327
436
  */
328
437
  function verifyLicenseSignature(payload, signature) {
329
438
  try {
330
- const secret =
331
- process.env.LICENSE_SIGNING_SECRET || 'cqa-dev-secret-change-in-prod'
332
- const expectedSignature = crypto
333
- .createHmac('sha256', secret)
334
- .update(JSON.stringify(payload))
335
- .digest('hex')
336
-
337
- return crypto.timingSafeEqual(
338
- Buffer.from(signature),
339
- Buffer.from(expectedSignature)
439
+ const publicKey = loadKeyFromEnv(
440
+ process.env.QAA_LICENSE_PUBLIC_KEY,
441
+ process.env.QAA_LICENSE_PUBLIC_KEY_PATH
340
442
  )
341
- } catch {
342
- // If signature verification fails, treat as invalid
443
+ if (!publicKey) {
444
+ // TD12 fix: Log warning when public key is missing in non-dev mode
445
+ // Security: Production mode never allows developer bypass
446
+ const isDevMode =
447
+ process.env.NODE_ENV !== 'production' &&
448
+ process.env.QAA_DEVELOPER === 'true'
449
+ if (!isDevMode) {
450
+ console.warn(
451
+ '⚠️ License public key not configured - signature verification skipped'
452
+ )
453
+ }
454
+ return isDevMode
455
+ }
456
+ return verifyPayload(payload, signature, publicKey)
457
+ } catch (error) {
458
+ // TD12 fix: Log verification failures instead of silently returning false
459
+ console.warn(`⚠️ Signature verification failed: ${error.message}`)
343
460
  return false
344
461
  }
345
462
  }
@@ -376,7 +493,6 @@ function getSupportedLanguages() {
376
493
  */
377
494
  function showUpgradeMessage(feature) {
378
495
  const license = getLicenseInfo()
379
- const _tierFeatures = FEATURES[license.tier] || FEATURES[LICENSE_TIERS.FREE]
380
496
 
381
497
  console.log(`\n🔒 ${feature} is a premium feature`)
382
498
  console.log(`📊 Current license: ${license.tier.toUpperCase()}`)
@@ -384,7 +500,7 @@ function showUpgradeMessage(feature) {
384
500
  if (license.tier === LICENSE_TIERS.FREE) {
385
501
  console.log('\n🚀 Upgrade to PRO')
386
502
  console.log('')
387
- console.log(' 💰 $59/month or $590/year (save $118)')
503
+ console.log(' 💰 $19/month or $190/year (save $38)')
388
504
  console.log('')
389
505
  console.log(' ✅ Unlimited repos, LOC, and runs')
390
506
  console.log(' ✅ Smart Test Strategy (70% faster pre-push)')
@@ -396,16 +512,14 @@ function showUpgradeMessage(feature) {
396
512
  console.log('')
397
513
  console.log(' 🎁 Start 14-day free trial - no credit card required')
398
514
  console.log('')
399
- console.log('🚀 Upgrade: https://vibebuildlab.com/tools/qa-architect')
515
+ console.log('🚀 Upgrade: https://vibebuildlab.com/qa-architect')
400
516
  console.log(
401
517
  '🔑 Activate: npx create-qa-architect@latest --activate-license'
402
518
  )
403
519
  } else if (license.tier === LICENSE_TIERS.PRO) {
404
520
  console.log('\n👥 Upgrade to TEAM')
405
521
  console.log('')
406
- console.log(
407
- ' 💰 $15/user/month (5-seat min) or $150/user/year (save $30/user)'
408
- )
522
+ console.log(' 💰 Contact us for Team pricing')
409
523
  console.log('')
410
524
  console.log(' ✅ All PRO features included')
411
525
  console.log(' ✅ Per-seat licensing for your org')
@@ -414,9 +528,9 @@ function showUpgradeMessage(feature) {
414
528
  console.log(' ✅ Slack/email alerts for failures')
415
529
  console.log(' ✅ Priority support (business hours)')
416
530
  console.log('')
417
- console.log('👥 Upgrade: https://vibebuildlab.com/tools/qa-architect')
531
+ console.log('👥 Upgrade: https://vibebuildlab.com/qa-architect')
418
532
  } else if (license.tier === LICENSE_TIERS.TEAM) {
419
- console.log('\n🏢 Upgrade to ENTERPRISE - $249/month (annual) + onboarding')
533
+ console.log('\n🏢 Upgrade to ENTERPRISE - Contact us for pricing')
420
534
  console.log('')
421
535
  console.log(' ✅ All TEAM features included')
422
536
  console.log(' ✅ SSO/SAML integration')
@@ -433,19 +547,52 @@ function showUpgradeMessage(feature) {
433
547
  */
434
548
  function saveLicense(tier, key, email, expires = null) {
435
549
  try {
550
+ // DR24 fix: Validate tier is a valid LICENSE_TIERS value
551
+ const validTiers = Object.values(LICENSE_TIERS)
552
+ if (!validTiers.includes(tier)) {
553
+ return {
554
+ success: false,
555
+ error: `Invalid tier "${tier}". Must be one of: ${validTiers.join(', ')}`,
556
+ }
557
+ }
558
+
436
559
  const licenseDir = getLicenseDir()
437
560
  const licenseFile = getLicenseFile()
561
+ const normalizedKey = normalizeLicenseKey(key)
562
+ const privateKey = loadKeyFromEnv(
563
+ process.env.LICENSE_REGISTRY_PRIVATE_KEY,
564
+ process.env.LICENSE_REGISTRY_PRIVATE_KEY_PATH
565
+ )
438
566
 
439
567
  if (!fs.existsSync(licenseDir)) {
440
568
  fs.mkdirSync(licenseDir, { recursive: true })
441
569
  }
442
570
 
571
+ if (!privateKey) {
572
+ return {
573
+ success: false,
574
+ error:
575
+ 'LICENSE_REGISTRY_PRIVATE_KEY or LICENSE_REGISTRY_PRIVATE_KEY_PATH is required to save a signed license',
576
+ }
577
+ }
578
+
579
+ const payload = buildLicensePayload({
580
+ licenseKey: normalizedKey,
581
+ tier,
582
+ isFounder: false,
583
+ emailHash: hashEmail(email),
584
+ issued: new Date().toISOString(),
585
+ })
586
+ const signature = signPayload(payload, privateKey)
587
+
443
588
  const licenseData = {
444
589
  tier,
445
- key,
590
+ licenseKey: normalizedKey,
446
591
  email,
447
592
  expires,
448
593
  activated: new Date().toISOString(),
594
+ payload,
595
+ signature,
449
596
  }
450
597
 
451
598
  fs.writeFileSync(licenseFile, JSON.stringify(licenseData, null, 2))
@@ -460,8 +607,18 @@ function saveLicense(tier, key, email, expires = null) {
460
607
  */
461
608
  function saveLicenseWithSignature(tier, key, email, validation) {
462
609
  try {
610
+ // DR24 fix: Validate tier is a valid LICENSE_TIERS value
611
+ const validTiers = Object.values(LICENSE_TIERS)
612
+ if (!validTiers.includes(tier)) {
613
+ return {
614
+ success: false,
615
+ error: `Invalid tier "${tier}". Must be one of: ${validTiers.join(', ')}`,
616
+ }
617
+ }
618
+
463
619
  const licenseDir = getLicenseDir()
464
620
  const licenseFile = getLicenseFile()
621
+ const normalizedKey = normalizeLicenseKey(key)
465
622
 
466
623
  if (!fs.existsSync(licenseDir)) {
467
624
  fs.mkdirSync(licenseDir, { recursive: true })
@@ -469,7 +626,7 @@ function saveLicenseWithSignature(tier, key, email, validation) {
469
626
 
470
627
  const licenseData = {
471
628
  tier,
472
- licenseKey: key, // ✅ Changed from 'key' to 'licenseKey' to match StripeIntegration
629
+ licenseKey: normalizedKey, // ✅ Changed from 'key' to 'licenseKey' to match StripeIntegration
473
630
  email,
474
631
  expires: validation.expires,
475
632
  activated: new Date().toISOString(),
@@ -537,11 +694,16 @@ async function addLegitimateKey(
537
694
  purchaseEmail = null
538
695
  ) {
539
696
  try {
697
+ const normalizedKey = normalizeLicenseKey(licenseKey)
540
698
  // Use the same license directory as the CLI
541
699
  const licenseDir =
542
700
  process.env.QAA_LICENSE_DIR ||
543
701
  path.join(os.homedir(), '.create-qa-architect')
544
702
  const legitimateDBFile = path.join(licenseDir, 'legitimate-licenses.json')
703
+ const privateKey = loadKeyFromEnv(
704
+ process.env.LICENSE_REGISTRY_PRIVATE_KEY,
705
+ process.env.LICENSE_REGISTRY_PRIVATE_KEY_PATH
706
+ )
545
707
 
546
708
  // Ensure directory exists
547
709
  if (!fs.existsSync(licenseDir)) {
@@ -553,43 +715,72 @@ async function addLegitimateKey(
553
715
  if (fs.existsSync(legitimateDBFile)) {
554
716
  try {
555
717
  database = JSON.parse(fs.readFileSync(legitimateDBFile, 'utf8'))
556
- } catch {
557
- console.error(
558
- 'Warning: Could not parse existing database, creating new one'
559
- )
718
+ } catch (parseError) {
719
+ // Silent failure fix: Backup corrupt file before overwriting
720
+ const backupPath = `${legitimateDBFile}.corrupted.${Date.now()}`
721
+ try {
722
+ fs.copyFileSync(legitimateDBFile, backupPath)
723
+ console.error(
724
+ `Warning: Could not parse existing database. Backed up to ${backupPath}`
725
+ )
726
+ } catch {
727
+ console.error(
728
+ 'Warning: Could not parse or backup existing database, creating new one'
729
+ )
730
+ }
731
+ console.error(`Parse error: ${parseError.message}`)
560
732
  }
561
733
  }
562
734
 
563
- // Initialize metadata if needed
564
- if (!database._metadata) {
565
- database._metadata = {
566
- version: '1.0',
567
- created: new Date().toISOString(),
568
- description: 'Legitimate license database - populated by admin/webhook',
735
+ if (!privateKey) {
736
+ return {
737
+ success: false,
738
+ error:
739
+ 'LICENSE_REGISTRY_PRIVATE_KEY or LICENSE_REGISTRY_PRIVATE_KEY_PATH is required to add legitimate keys',
569
740
  }
570
741
  }
571
742
 
572
- // Add license
573
- database[licenseKey] = {
574
- customerId,
743
+ const issued = new Date().toISOString()
744
+ const emailHash = hashEmail(purchaseEmail)
745
+ const payload = buildLicensePayload({
746
+ licenseKey: normalizedKey,
575
747
  tier,
576
748
  isFounder,
577
- email: purchaseEmail,
578
- addedDate: new Date().toISOString(),
579
- addedBy: 'admin_tool',
749
+ emailHash,
750
+ issued,
751
+ })
752
+ const signature = signPayload(payload, privateKey)
753
+
754
+ const { _metadata, ...existingLicenses } = database
755
+ const licenses = {
756
+ ...existingLicenses,
757
+ [normalizedKey]: {
758
+ tier,
759
+ isFounder,
760
+ issued,
761
+ emailHash,
762
+ signature,
763
+ keyId: process.env.LICENSE_REGISTRY_KEY_ID || 'default',
764
+ },
580
765
  }
581
766
 
582
- // Update metadata
583
- database._metadata.lastUpdate = new Date().toISOString()
584
- database._metadata.totalLicenses = Object.keys(database).length - 1 // Exclude metadata
585
-
586
- // Calculate SHA256 checksum for integrity verification (MANDATORY)
587
- const { _metadata, ...licensesOnly } = database
588
- const sha256 = crypto
767
+ const registrySignature = signPayload(licenses, privateKey)
768
+ const hash = crypto
589
769
  .createHash('sha256')
590
- .update(JSON.stringify(licensesOnly))
770
+ .update(stableStringify(licenses))
591
771
  .digest('hex')
592
- database._metadata.sha256 = sha256
772
+ const metadata = {
773
+ version: '1.0',
774
+ created: _metadata?.created || new Date().toISOString(),
775
+ lastUpdate: new Date().toISOString(),
776
+ description: 'Legitimate license database - populated by admin/webhook',
777
+ algorithm: 'ed25519',
778
+ keyId: process.env.LICENSE_REGISTRY_KEY_ID || 'default',
779
+ registrySignature,
780
+ hash,
781
+ totalLicenses: Object.keys(licenses).length,
782
+ }
783
+ database = { _metadata: metadata, ...licenses }
593
784
 
594
785
  // Save database
595
786
  fs.writeFileSync(legitimateDBFile, JSON.stringify(database, null, 2))
@@ -610,60 +801,60 @@ async function addLegitimateKey(
610
801
 
611
802
  /**
612
803
  * Interactive license activation prompt
804
+ * DR27 fix: Converted from callback-based readline to async/await pattern
613
805
  */
614
806
  async function promptLicenseActivation() {
615
- const readline = require('readline')
807
+ const readline = require('readline/promises')
616
808
 
617
809
  const rl = readline.createInterface({
618
810
  input: process.stdin,
619
811
  output: process.stdout,
620
812
  })
621
813
 
622
- return new Promise(resolve => {
814
+ try {
623
815
  console.log('\n🔑 License Activation')
624
816
  console.log(
625
817
  'Enter your license key from the purchase confirmation email.\n'
626
818
  )
627
819
 
628
- rl.question('License key (QAA-XXXX-XXXX-XXXX-XXXX): ', licenseKey => {
629
- if (!licenseKey.trim()) {
630
- console.log('❌ License key required')
631
- rl.close()
632
- resolve({ success: false })
633
- return
634
- }
820
+ const licenseKey = await rl.question(
821
+ 'License key (QAA-XXXX-XXXX-XXXX-XXXX): '
822
+ )
635
823
 
636
- rl.question('Email address: ', async email => {
637
- if (!email.trim()) {
638
- console.log('❌ Email address required')
639
- rl.close()
640
- resolve({ success: false })
641
- return
642
- }
824
+ if (!licenseKey.trim()) {
825
+ console.log('❌ License key required')
826
+ rl.close()
827
+ return { success: false }
828
+ }
643
829
 
644
- rl.close()
830
+ const email = await rl.question('Email address: ')
645
831
 
646
- const result = await activateLicense(licenseKey.trim(), email.trim())
832
+ if (!email.trim()) {
833
+ console.log('❌ Email address required')
834
+ rl.close()
835
+ return { success: false }
836
+ }
647
837
 
648
- if (
649
- !result.success &&
650
- result.error &&
651
- result.error.includes('not found')
652
- ) {
653
- console.log('\n📞 License activation assistance:')
654
- console.log(
655
- ' If you purchased this license, please contact support at:'
656
- )
657
- console.log(' Email: support@vibebuildlab.com')
658
- console.log(
659
- ' Include your license key and purchase email for verification.'
660
- )
661
- }
838
+ rl.close()
662
839
 
663
- resolve(result)
664
- })
665
- })
666
- })
840
+ const result = await activateLicense(licenseKey.trim(), email.trim())
841
+
842
+ if (!result.success && result.error && result.error.includes('not found')) {
843
+ console.log('\n📞 License activation assistance:')
844
+ console.log(
845
+ ' If you purchased this license, please contact support at:'
846
+ )
847
+ console.log(' Email: support@vibebuildlab.com')
848
+ console.log(
849
+ ' Include your license key and purchase email for verification.'
850
+ )
851
+ }
852
+
853
+ return result
854
+ } catch (error) {
855
+ rl.close()
856
+ return { success: false, error: error.message }
857
+ }
667
858
  }
668
859
 
669
860
  /**
@@ -745,14 +936,53 @@ function loadUsage() {
745
936
 
746
937
  return data
747
938
  }
748
- } catch {
749
- // Ignore errors reading usage file
939
+ } catch (error) {
940
+ // DR8 fix: Prevent quota bypass through file corruption
941
+ if (error instanceof SyntaxError) {
942
+ const usageFile = getUsageFile()
943
+ console.warn('⚠️ Usage tracking file corrupted')
944
+ console.warn(' Creating backup and resetting to current month start')
945
+
946
+ // Backup corrupted file for forensics
947
+ const backupPath = `${usageFile}.corrupted.${Date.now()}`
948
+ try {
949
+ fs.copyFileSync(usageFile, backupPath)
950
+ console.warn(` Backup saved: ${backupPath}`)
951
+ } catch (_backupError) {
952
+ // Ignore backup failures
953
+ }
954
+
955
+ // DR8 fix: For FREE tier, reset to max usage to prevent quota bypass
956
+ const license = getLicenseInfo()
957
+ if (license.tier === LICENSE_TIERS.FREE) {
958
+ console.warn(
959
+ ' ⚠️ FREE tier: Starting with maximum usage for security'
960
+ )
961
+ const caps = FEATURES[LICENSE_TIERS.FREE]
962
+ return {
963
+ month: getCurrentMonth(),
964
+ prePushRuns: caps.maxPrePushRunsPerMonth,
965
+ dependencyPRs: caps.maxDependencyPRsPerMonth,
966
+ repos: [],
967
+ }
968
+ }
969
+ } else if (process.env.DEBUG && error?.code !== 'ENOENT') {
970
+ console.warn(`⚠️ Could not read usage file: ${error.message}`)
971
+ }
750
972
  }
751
973
 
752
974
  // Default usage data
975
+ return getDefaultUsage()
976
+ }
977
+
978
+ function getCurrentMonth() {
753
979
  const now = new Date()
980
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
981
+ }
982
+
983
+ function getDefaultUsage() {
754
984
  return {
755
- month: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`,
985
+ month: getCurrentMonth(),
756
986
  prePushRuns: 0,
757
987
  dependencyPRs: 0,
758
988
  repos: [],
@@ -770,7 +1000,9 @@ function saveUsage(usage) {
770
1000
  }
771
1001
  fs.writeFileSync(getUsageFile(), JSON.stringify(usage, null, 2))
772
1002
  return true
773
- } catch {
1003
+ } catch (error) {
1004
+ // TD12 fix: Log errors saving usage data instead of silently failing
1005
+ console.warn(`⚠️ Failed to save usage data: ${error.message}`)
774
1006
  return false
775
1007
  }
776
1008
  }
@@ -958,7 +1190,7 @@ function showLicenseStatus() {
958
1190
  // Show upgrade path
959
1191
  if (license.tier === LICENSE_TIERS.FREE) {
960
1192
  console.log('\n💡 Upgrade to PRO for unlimited access + security scanning')
961
- console.log(' → https://vibebuildlab.com/tools/qa-architect')
1193
+ console.log(' → https://vibebuildlab.com/qa-architect')
962
1194
  }
963
1195
  }
964
1196