guardrail-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/__tests__/autopilot.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot.test.js +156 -0
  4. package/dist/__tests__/tier-config.test.d.ts +9 -0
  5. package/dist/__tests__/tier-config.test.d.ts.map +1 -0
  6. package/dist/__tests__/tier-config.test.js +230 -0
  7. package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
  8. package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
  9. package/dist/__tests__/utils/hash-inline.test.js +62 -0
  10. package/dist/__tests__/utils/hash.test.d.ts +3 -0
  11. package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/hash.test.js +95 -0
  13. package/dist/__tests__/utils/simple.test.d.ts +1 -0
  14. package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
  15. package/dist/__tests__/utils/simple.test.js +10 -0
  16. package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
  17. package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
  18. package/dist/__tests__/utils/utils-simple.test.js +6 -0
  19. package/dist/__tests__/utils/utils.test.d.ts +15 -0
  20. package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/utils.test.js +172 -0
  22. package/dist/autopilot/autopilot-runner.d.ts +33 -0
  23. package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
  24. package/dist/autopilot/autopilot-runner.js +479 -0
  25. package/dist/autopilot/index.d.ts +6 -0
  26. package/dist/autopilot/index.d.ts.map +1 -0
  27. package/dist/autopilot/index.js +25 -0
  28. package/dist/autopilot/types.d.ts +102 -0
  29. package/dist/autopilot/types.d.ts.map +1 -0
  30. package/dist/autopilot/types.js +18 -0
  31. package/dist/cache/index.d.ts +7 -0
  32. package/dist/cache/index.d.ts.map +1 -0
  33. package/dist/cache/index.js +22 -0
  34. package/dist/cache/redis-cache.d.ts +145 -0
  35. package/dist/cache/redis-cache.d.ts.map +1 -0
  36. package/dist/cache/redis-cache.js +459 -0
  37. package/dist/ci/github-actions.d.ts +77 -0
  38. package/dist/ci/github-actions.d.ts.map +1 -0
  39. package/dist/ci/github-actions.js +277 -0
  40. package/dist/ci/index.d.ts +12 -0
  41. package/dist/ci/index.d.ts.map +1 -0
  42. package/dist/ci/index.js +27 -0
  43. package/dist/ci/pre-commit.d.ts +65 -0
  44. package/dist/ci/pre-commit.d.ts.map +1 -0
  45. package/dist/ci/pre-commit.js +286 -0
  46. package/dist/entitlements.d.ts +149 -0
  47. package/dist/entitlements.d.ts.map +1 -0
  48. package/dist/entitlements.js +464 -0
  49. package/dist/env.d.ts +113 -0
  50. package/dist/env.d.ts.map +1 -0
  51. package/dist/env.js +204 -0
  52. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
  53. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
  54. package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
  55. package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
  56. package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
  57. package/dist/fix-packs/generate-fix-packs.js +505 -0
  58. package/dist/fix-packs/index.d.ts +8 -0
  59. package/dist/fix-packs/index.d.ts.map +1 -0
  60. package/dist/fix-packs/index.js +23 -0
  61. package/dist/fix-packs/types.d.ts +113 -0
  62. package/dist/fix-packs/types.d.ts.map +1 -0
  63. package/dist/fix-packs/types.js +71 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +28 -0
  67. package/dist/metrics/prometheus.d.ts +99 -0
  68. package/dist/metrics/prometheus.d.ts.map +1 -0
  69. package/dist/metrics/prometheus.js +306 -0
  70. package/dist/quota-ledger.d.ts +119 -0
  71. package/dist/quota-ledger.d.ts.map +1 -0
  72. package/dist/quota-ledger.js +462 -0
  73. package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
  74. package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
  75. package/dist/rbac/__tests__/permissions.test.js +350 -0
  76. package/dist/rbac/index.d.ts +9 -0
  77. package/dist/rbac/index.d.ts.map +1 -0
  78. package/dist/rbac/index.js +32 -0
  79. package/dist/rbac/permissions.d.ts +71 -0
  80. package/dist/rbac/permissions.d.ts.map +1 -0
  81. package/dist/rbac/permissions.js +247 -0
  82. package/dist/rbac/types.d.ts +69 -0
  83. package/dist/rbac/types.d.ts.map +1 -0
  84. package/dist/rbac/types.js +213 -0
  85. package/dist/tier-config.d.ts +203 -0
  86. package/dist/tier-config.d.ts.map +1 -0
  87. package/dist/tier-config.js +675 -0
  88. package/dist/types.d.ts +365 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +5 -0
  91. package/dist/utils.d.ts +36 -0
  92. package/dist/utils.d.ts.map +1 -0
  93. package/dist/utils.js +127 -0
  94. package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
  95. package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
  96. package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
  97. package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
  98. package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
  99. package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
  100. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
  101. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
  102. package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
  103. package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
  104. package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
  105. package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
  106. package/dist/verified-autofix/format-validator.d.ts +101 -0
  107. package/dist/verified-autofix/format-validator.d.ts.map +1 -0
  108. package/dist/verified-autofix/format-validator.js +446 -0
  109. package/dist/verified-autofix/index.d.ts +14 -0
  110. package/dist/verified-autofix/index.d.ts.map +1 -0
  111. package/dist/verified-autofix/index.js +39 -0
  112. package/dist/verified-autofix/pipeline.d.ts +68 -0
  113. package/dist/verified-autofix/pipeline.d.ts.map +1 -0
  114. package/dist/verified-autofix/pipeline.js +330 -0
  115. package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
  116. package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
  117. package/dist/verified-autofix/repo-fingerprint.js +396 -0
  118. package/dist/verified-autofix/workspace.d.ts +83 -0
  119. package/dist/verified-autofix/workspace.d.ts.map +1 -0
  120. package/dist/verified-autofix/workspace.js +454 -0
  121. package/dist/verified-autofix.d.ts +182 -0
  122. package/dist/verified-autofix.d.ts.map +1 -0
  123. package/dist/verified-autofix.js +1021 -0
  124. package/dist/visualization/dependency-graph.d.ts +79 -0
  125. package/dist/visualization/dependency-graph.d.ts.map +1 -0
  126. package/dist/visualization/dependency-graph.js +399 -0
  127. package/dist/visualization/index.d.ts +5 -0
  128. package/dist/visualization/index.d.ts.map +1 -0
  129. package/dist/visualization/index.js +20 -0
  130. package/package.json +29 -0
  131. package/src/__tests__/autopilot.test.ts +196 -0
  132. package/src/__tests__/tier-config.test.ts +289 -0
  133. package/src/__tests__/utils/hash-inline.test.ts +76 -0
  134. package/src/__tests__/utils/hash.test.ts +119 -0
  135. package/src/__tests__/utils/simple.test.ts +10 -0
  136. package/src/__tests__/utils/utils-simple.test.ts +5 -0
  137. package/src/__tests__/utils/utils.test.ts +203 -0
  138. package/src/autopilot/autopilot-runner.ts +503 -0
  139. package/src/autopilot/index.ts +6 -0
  140. package/src/autopilot/types.ts +119 -0
  141. package/src/cache/index.ts +7 -0
  142. package/src/cache/redis-cache.d.ts +155 -0
  143. package/src/cache/redis-cache.d.ts.map +1 -0
  144. package/src/cache/redis-cache.ts +517 -0
  145. package/src/ci/github-actions.ts +335 -0
  146. package/src/ci/index.ts +12 -0
  147. package/src/ci/pre-commit.ts +338 -0
  148. package/src/db/usage-schema.prisma +114 -0
  149. package/src/entitlements.ts +570 -0
  150. package/src/env.d.ts +68 -0
  151. package/src/env.d.ts.map +1 -0
  152. package/src/env.ts +247 -0
  153. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
  154. package/src/fix-packs/generate-fix-packs.ts +577 -0
  155. package/src/fix-packs/index.ts +8 -0
  156. package/src/fix-packs/types.ts +206 -0
  157. package/src/index.d.ts +7 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.ts +12 -0
  160. package/src/metrics/prometheus.d.ts +104 -0
  161. package/src/metrics/prometheus.d.ts.map +1 -0
  162. package/src/metrics/prometheus.ts +446 -0
  163. package/src/quota-ledger.ts +548 -0
  164. package/src/rbac/__tests__/permissions.test.ts +446 -0
  165. package/src/rbac/index.ts +46 -0
  166. package/src/rbac/permissions.ts +301 -0
  167. package/src/rbac/types.ts +298 -0
  168. package/src/tier-config.json +157 -0
  169. package/src/tier-config.ts +815 -0
  170. package/src/types.d.ts +365 -0
  171. package/src/types.d.ts.map +1 -0
  172. package/src/types.ts +441 -0
  173. package/src/utils.d.ts +36 -0
  174. package/src/utils.d.ts.map +1 -0
  175. package/src/utils.ts +140 -0
  176. package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
  177. package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
  178. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
  179. package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
  180. package/src/verified-autofix/format-validator.ts +517 -0
  181. package/src/verified-autofix/index.ts +63 -0
  182. package/src/verified-autofix/pipeline.ts +403 -0
  183. package/src/verified-autofix/repo-fingerprint.ts +459 -0
  184. package/src/verified-autofix/workspace.ts +531 -0
  185. package/src/verified-autofix.ts +1187 -0
  186. package/src/visualization/dependency-graph.d.ts +85 -0
  187. package/src/visualization/dependency-graph.d.ts.map +1 -0
  188. package/src/visualization/dependency-graph.ts +495 -0
  189. package/src/visualization/index.ts +5 -0
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+ /**
3
+ * Quota Ledger - Server-Authoritative Usage Tracking
4
+ *
5
+ * Implements idempotent usage recording with request IDs to prevent double-counting.
6
+ * All quota checks are validated server-side before allowing operations.
7
+ *
8
+ * SECURITY: CLI cannot bypass quota checks by modifying local files.
9
+ * The server is the single source of truth for usage data.
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.getPendingCount = exports.syncPendingUsage = exports.getUsageSummary = exports.recordUsage = exports.checkQuota = exports.quotaLedger = exports.QuotaLedger = void 0;
46
+ const fs = __importStar(require("fs"));
47
+ const os = __importStar(require("os"));
48
+ const path = __importStar(require("path"));
49
+ const crypto = __importStar(require("crypto"));
50
+ // ============================================================================
51
+ // QUOTA LEDGER CLASS
52
+ // ============================================================================
53
+ class QuotaLedger {
54
+ configDir;
55
+ ledgerFile;
56
+ cacheFile;
57
+ apiUrl;
58
+ apiKey;
59
+ constructor() {
60
+ this.configDir = path.join(os.homedir(), '.guardrail');
61
+ this.ledgerFile = path.join(this.configDir, 'usage-ledger.json');
62
+ this.cacheFile = path.join(this.configDir, 'quota-cache.json');
63
+ this.apiUrl = process.env['GUARDRAIL_API_URL'] || 'https://api.getguardrail.io';
64
+ this.apiKey = process.env['GUARDRAIL_API_KEY'] || null;
65
+ }
66
+ /**
67
+ * Generate unique request ID for idempotency
68
+ */
69
+ generateRequestId() {
70
+ const timestamp = Date.now().toString(36);
71
+ const random = crypto.randomBytes(8).toString('hex');
72
+ return `req_${timestamp}_${random}`;
73
+ }
74
+ /**
75
+ * Check if action is allowed based on quota
76
+ * Always validates with server when possible
77
+ */
78
+ async checkQuota(action) {
79
+ const requestId = this.generateRequestId();
80
+ // Try server-authoritative check first
81
+ if (this.apiKey) {
82
+ try {
83
+ const response = await fetch(`${this.apiUrl}/api/usage/check`, {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ 'Authorization': `Bearer ${this.apiKey}`,
88
+ 'X-Request-ID': requestId,
89
+ },
90
+ body: JSON.stringify({ action }),
91
+ });
92
+ if (response.ok) {
93
+ const result = await response.json();
94
+ // Cache the result
95
+ await this.cacheQuotaResult(action, result);
96
+ return {
97
+ ...result,
98
+ source: 'server',
99
+ requestId,
100
+ };
101
+ }
102
+ }
103
+ catch {
104
+ // Server unreachable, fall back to cache/offline
105
+ }
106
+ }
107
+ // Fallback to cached data
108
+ const cached = await this.getCachedQuota(action);
109
+ if (cached) {
110
+ return {
111
+ ...cached,
112
+ source: 'cache',
113
+ requestId,
114
+ };
115
+ }
116
+ // Offline mode - allow with warning
117
+ return {
118
+ allowed: true,
119
+ current: 0,
120
+ limit: -1,
121
+ remaining: -1,
122
+ reason: 'Offline mode - usage will be synced when online',
123
+ source: 'offline',
124
+ requestId,
125
+ };
126
+ }
127
+ /**
128
+ * Record usage with idempotency guarantee
129
+ * Uses request ID to prevent double-counting on retries
130
+ */
131
+ async recordUsage(action, count = 1, requestId) {
132
+ const id = requestId || this.generateRequestId();
133
+ // Check for duplicate request
134
+ if (await this.isDuplicateRequest(id)) {
135
+ const existing = await this.getExistingResult(id);
136
+ if (existing)
137
+ return existing;
138
+ }
139
+ const entry = {
140
+ id,
141
+ action,
142
+ count,
143
+ timestamp: new Date().toISOString(),
144
+ synced: false,
145
+ };
146
+ // Try server-authoritative recording
147
+ if (this.apiKey) {
148
+ try {
149
+ const response = await fetch(`${this.apiUrl}/api/usage/record`, {
150
+ method: 'POST',
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ 'Authorization': `Bearer ${this.apiKey}`,
154
+ 'X-Request-ID': id,
155
+ 'X-Idempotency-Key': id,
156
+ },
157
+ body: JSON.stringify({ action, count, requestId: id }),
158
+ });
159
+ if (response.ok) {
160
+ const result = await response.json();
161
+ entry.synced = true;
162
+ await this.saveLedgerEntry(entry);
163
+ return {
164
+ success: true,
165
+ requestId: id,
166
+ newUsage: result.newUsage,
167
+ remaining: result.remaining,
168
+ source: 'server',
169
+ };
170
+ }
171
+ // Handle rate limit or quota exceeded
172
+ if (response.status === 429 || response.status === 402) {
173
+ const error = await response.json();
174
+ return {
175
+ success: false,
176
+ requestId: id,
177
+ newUsage: 0,
178
+ remaining: 0,
179
+ source: 'server',
180
+ error: error.message || 'Quota exceeded',
181
+ };
182
+ }
183
+ }
184
+ catch {
185
+ // Server unreachable, queue for later sync
186
+ }
187
+ }
188
+ // Queue for later sync
189
+ await this.saveLedgerEntry(entry);
190
+ return {
191
+ success: true,
192
+ requestId: id,
193
+ newUsage: count,
194
+ remaining: -1,
195
+ source: 'queued',
196
+ };
197
+ }
198
+ /**
199
+ * Get usage summary for current billing period
200
+ */
201
+ async getUsageSummary() {
202
+ if (this.apiKey) {
203
+ try {
204
+ const response = await fetch(`${this.apiUrl}/api/usage/summary`, {
205
+ method: 'GET',
206
+ headers: {
207
+ 'Authorization': `Bearer ${this.apiKey}`,
208
+ },
209
+ });
210
+ if (response.ok) {
211
+ return await response.json();
212
+ }
213
+ }
214
+ catch {
215
+ // Fall through to local
216
+ }
217
+ }
218
+ // Return local summary
219
+ return this.getLocalSummary();
220
+ }
221
+ /**
222
+ * Sync pending offline usage to server
223
+ */
224
+ async syncPendingUsage() {
225
+ const entries = await this.getUnsyncedEntries();
226
+ let synced = 0;
227
+ let failed = 0;
228
+ const errors = [];
229
+ if (!this.apiKey || entries.length === 0) {
230
+ return { synced: 0, failed: 0, errors: [] };
231
+ }
232
+ for (const entry of entries) {
233
+ try {
234
+ const response = await fetch(`${this.apiUrl}/api/usage/record`, {
235
+ method: 'POST',
236
+ headers: {
237
+ 'Content-Type': 'application/json',
238
+ 'Authorization': `Bearer ${this.apiKey}`,
239
+ 'X-Request-ID': entry.id,
240
+ 'X-Idempotency-Key': entry.id,
241
+ },
242
+ body: JSON.stringify({
243
+ action: entry.action,
244
+ count: entry.count,
245
+ requestId: entry.id,
246
+ timestamp: entry.timestamp,
247
+ }),
248
+ });
249
+ if (response.ok) {
250
+ entry.synced = true;
251
+ await this.updateLedgerEntry(entry);
252
+ synced++;
253
+ }
254
+ else {
255
+ failed++;
256
+ errors.push(`Failed to sync ${entry.id}: ${response.statusText}`);
257
+ }
258
+ }
259
+ catch (e) {
260
+ failed++;
261
+ errors.push(`Failed to sync ${entry.id}: ${e.message}`);
262
+ }
263
+ }
264
+ return { synced, failed, errors };
265
+ }
266
+ /**
267
+ * Get count of pending offline usage
268
+ */
269
+ async getPendingCount() {
270
+ const entries = await this.getUnsyncedEntries();
271
+ return entries.length;
272
+ }
273
+ // ============================================================================
274
+ // PRIVATE HELPERS
275
+ // ============================================================================
276
+ async ensureDir() {
277
+ try {
278
+ await fs.promises.mkdir(this.configDir, { recursive: true });
279
+ }
280
+ catch {
281
+ // Exists
282
+ }
283
+ }
284
+ async saveLedgerEntry(entry) {
285
+ await this.ensureDir();
286
+ let ledger = [];
287
+ try {
288
+ const content = await fs.promises.readFile(this.ledgerFile, 'utf8');
289
+ ledger = JSON.parse(content);
290
+ }
291
+ catch {
292
+ // New file
293
+ }
294
+ // Check for duplicate
295
+ const existing = ledger.findIndex(e => e.id === entry.id);
296
+ if (existing >= 0) {
297
+ ledger[existing] = entry;
298
+ }
299
+ else {
300
+ ledger.push(entry);
301
+ }
302
+ // Keep only last 1000 entries
303
+ if (ledger.length > 1000) {
304
+ ledger = ledger.slice(-1000);
305
+ }
306
+ await fs.promises.writeFile(this.ledgerFile, JSON.stringify(ledger, null, 2));
307
+ }
308
+ async updateLedgerEntry(entry) {
309
+ await this.saveLedgerEntry(entry);
310
+ }
311
+ async getUnsyncedEntries() {
312
+ try {
313
+ const content = await fs.promises.readFile(this.ledgerFile, 'utf8');
314
+ const ledger = JSON.parse(content);
315
+ return ledger.filter(e => !e.synced);
316
+ }
317
+ catch {
318
+ return [];
319
+ }
320
+ }
321
+ async isDuplicateRequest(id) {
322
+ try {
323
+ const content = await fs.promises.readFile(this.ledgerFile, 'utf8');
324
+ const ledger = JSON.parse(content);
325
+ return ledger.some(e => e.id === id);
326
+ }
327
+ catch {
328
+ return false;
329
+ }
330
+ }
331
+ async getExistingResult(id) {
332
+ try {
333
+ const content = await fs.promises.readFile(this.ledgerFile, 'utf8');
334
+ const ledger = JSON.parse(content);
335
+ const entry = ledger.find(e => e.id === id);
336
+ if (entry) {
337
+ return {
338
+ success: true,
339
+ requestId: entry.id,
340
+ newUsage: entry.count,
341
+ remaining: -1,
342
+ source: entry.synced ? 'server' : 'queued',
343
+ };
344
+ }
345
+ }
346
+ catch {
347
+ // Not found
348
+ }
349
+ return null;
350
+ }
351
+ async cacheQuotaResult(action, result) {
352
+ await this.ensureDir();
353
+ let cache = {};
354
+ try {
355
+ const content = await fs.promises.readFile(this.cacheFile, 'utf8');
356
+ cache = JSON.parse(content);
357
+ }
358
+ catch {
359
+ // New cache
360
+ }
361
+ cache[action] = {
362
+ ...result,
363
+ cachedAt: new Date().toISOString(),
364
+ };
365
+ await fs.promises.writeFile(this.cacheFile, JSON.stringify(cache, null, 2));
366
+ }
367
+ async getCachedQuota(action) {
368
+ try {
369
+ const content = await fs.promises.readFile(this.cacheFile, 'utf8');
370
+ const cache = JSON.parse(content);
371
+ const cached = cache[action];
372
+ if (cached) {
373
+ // Cache valid for 5 minutes
374
+ const age = Date.now() - new Date(cached.cachedAt).getTime();
375
+ if (age < 5 * 60 * 1000) {
376
+ return {
377
+ allowed: cached.allowed,
378
+ current: cached.current,
379
+ limit: cached.limit,
380
+ remaining: cached.remaining,
381
+ source: 'cache',
382
+ requestId: this.generateRequestId(),
383
+ };
384
+ }
385
+ }
386
+ }
387
+ catch {
388
+ // No cache
389
+ }
390
+ return null;
391
+ }
392
+ async getLocalSummary() {
393
+ try {
394
+ const content = await fs.promises.readFile(this.ledgerFile, 'utf8');
395
+ const ledger = JSON.parse(content);
396
+ // Calculate current month's usage
397
+ const now = new Date();
398
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
399
+ const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
400
+ const thisMonth = ledger.filter(e => {
401
+ const ts = new Date(e.timestamp);
402
+ return ts >= monthStart && ts <= monthEnd;
403
+ });
404
+ const usage = {
405
+ scans: 0,
406
+ reality: 0,
407
+ agent: 0,
408
+ fix: 0,
409
+ gate: 0,
410
+ };
411
+ for (const entry of thisMonth) {
412
+ switch (entry.action) {
413
+ case 'scan':
414
+ case 'scan_truth':
415
+ usage.scans += entry.count;
416
+ break;
417
+ case 'reality':
418
+ usage.reality += entry.count;
419
+ break;
420
+ case 'agent':
421
+ usage.agent += entry.count;
422
+ break;
423
+ case 'fix':
424
+ usage.fix += entry.count;
425
+ break;
426
+ case 'gate':
427
+ usage.gate += entry.count;
428
+ break;
429
+ }
430
+ }
431
+ return {
432
+ tier: 'unknown',
433
+ period: {
434
+ start: monthStart.toISOString(),
435
+ end: monthEnd.toISOString(),
436
+ },
437
+ usage,
438
+ limits: { scans: -1, reality: -1, agent: -1 },
439
+ remaining: { scans: -1, reality: -1, agent: -1 },
440
+ };
441
+ }
442
+ catch {
443
+ return null;
444
+ }
445
+ }
446
+ }
447
+ exports.QuotaLedger = QuotaLedger;
448
+ // ============================================================================
449
+ // SINGLETON EXPORT
450
+ // ============================================================================
451
+ exports.quotaLedger = new QuotaLedger();
452
+ // Convenience exports
453
+ const checkQuota = (action) => exports.quotaLedger.checkQuota(action);
454
+ exports.checkQuota = checkQuota;
455
+ const recordUsage = (action, count, requestId) => exports.quotaLedger.recordUsage(action, count, requestId);
456
+ exports.recordUsage = recordUsage;
457
+ const getUsageSummary = () => exports.quotaLedger.getUsageSummary();
458
+ exports.getUsageSummary = getUsageSummary;
459
+ const syncPendingUsage = () => exports.quotaLedger.syncPendingUsage();
460
+ exports.syncPendingUsage = syncPendingUsage;
461
+ const getPendingCount = () => exports.quotaLedger.getPendingCount();
462
+ exports.getPendingCount = getPendingCount;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * RBAC Permission Tests
3
+ *
4
+ * Tests for role-based access control permission checking logic.
5
+ * Validates acceptance criteria for different roles.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=permissions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.test.d.ts","sourceRoot":"","sources":["../../../src/rbac/__tests__/permissions.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}