guardrail-ship 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 (119) hide show
  1. package/dist/index.d.ts +7 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/mock-implementation.d.ts +1 -0
  6. package/dist/mock-implementation.d.ts.map +1 -0
  7. package/dist/mock-implementation.js +2 -0
  8. package/dist/mock-implementation.js.map +1 -0
  9. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts +5 -0
  10. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts.map +1 -0
  11. package/dist/mockproof/__tests__/import-graph-scanner.test.js +92 -0
  12. package/dist/mockproof/__tests__/import-graph-scanner.test.js.map +1 -0
  13. package/dist/mockproof/import-graph-scanner.d.ts +93 -0
  14. package/dist/mockproof/import-graph-scanner.d.ts.map +1 -0
  15. package/dist/mockproof/import-graph-scanner.js +411 -0
  16. package/dist/mockproof/import-graph-scanner.js.map +1 -0
  17. package/dist/mockproof/index.d.ts +10 -0
  18. package/dist/mockproof/index.d.ts.map +1 -0
  19. package/dist/mockproof/index.js +10 -0
  20. package/dist/mockproof/index.js.map +1 -0
  21. package/dist/reality-mode/auth-enforcer.d.ts +13 -0
  22. package/dist/reality-mode/auth-enforcer.d.ts.map +1 -0
  23. package/dist/reality-mode/auth-enforcer.js +90 -0
  24. package/dist/reality-mode/auth-enforcer.js.map +1 -0
  25. package/dist/reality-mode/explorer/critical-flows.d.ts +71 -0
  26. package/dist/reality-mode/explorer/critical-flows.d.ts.map +1 -0
  27. package/dist/reality-mode/explorer/critical-flows.js +463 -0
  28. package/dist/reality-mode/explorer/critical-flows.js.map +1 -0
  29. package/dist/reality-mode/explorer/flow-parser.d.ts +52 -0
  30. package/dist/reality-mode/explorer/flow-parser.d.ts.map +1 -0
  31. package/dist/reality-mode/explorer/flow-parser.js +250 -0
  32. package/dist/reality-mode/explorer/flow-parser.js.map +1 -0
  33. package/dist/reality-mode/explorer/index.d.ts +11 -0
  34. package/dist/reality-mode/explorer/index.d.ts.map +1 -0
  35. package/dist/reality-mode/explorer/index.js +11 -0
  36. package/dist/reality-mode/explorer/index.js.map +1 -0
  37. package/dist/reality-mode/explorer/runtime-explorer.d.ts +35 -0
  38. package/dist/reality-mode/explorer/runtime-explorer.d.ts.map +1 -0
  39. package/dist/reality-mode/explorer/runtime-explorer.js +688 -0
  40. package/dist/reality-mode/explorer/runtime-explorer.js.map +1 -0
  41. package/dist/reality-mode/explorer/surface-discovery.d.ts +60 -0
  42. package/dist/reality-mode/explorer/surface-discovery.d.ts.map +1 -0
  43. package/dist/reality-mode/explorer/surface-discovery.js +357 -0
  44. package/dist/reality-mode/explorer/surface-discovery.js.map +1 -0
  45. package/dist/reality-mode/explorer/types.d.ts +275 -0
  46. package/dist/reality-mode/explorer/types.d.ts.map +1 -0
  47. package/dist/reality-mode/explorer/types.js +8 -0
  48. package/dist/reality-mode/explorer/types.js.map +1 -0
  49. package/dist/reality-mode/fake-success-detector.d.ts +10 -0
  50. package/dist/reality-mode/fake-success-detector.d.ts.map +1 -0
  51. package/dist/reality-mode/fake-success-detector.js +76 -0
  52. package/dist/reality-mode/fake-success-detector.js.map +1 -0
  53. package/dist/reality-mode/index.d.ts +14 -0
  54. package/dist/reality-mode/index.d.ts.map +1 -0
  55. package/dist/reality-mode/index.js +14 -0
  56. package/dist/reality-mode/index.js.map +1 -0
  57. package/dist/reality-mode/reality-scanner.d.ts +48 -0
  58. package/dist/reality-mode/reality-scanner.d.ts.map +1 -0
  59. package/dist/reality-mode/reality-scanner.js +516 -0
  60. package/dist/reality-mode/reality-scanner.js.map +1 -0
  61. package/dist/reality-mode/report-generator.d.ts +11 -0
  62. package/dist/reality-mode/report-generator.d.ts.map +1 -0
  63. package/dist/reality-mode/report-generator.js +233 -0
  64. package/dist/reality-mode/report-generator.js.map +1 -0
  65. package/dist/reality-mode/traffic-classifier.d.ts +14 -0
  66. package/dist/reality-mode/traffic-classifier.d.ts.map +1 -0
  67. package/dist/reality-mode/traffic-classifier.js +131 -0
  68. package/dist/reality-mode/traffic-classifier.js.map +1 -0
  69. package/dist/reality-mode/types.d.ts +90 -0
  70. package/dist/reality-mode/types.d.ts.map +1 -0
  71. package/dist/reality-mode/types.js +2 -0
  72. package/dist/reality-mode/types.js.map +1 -0
  73. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts +5 -0
  74. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts.map +1 -0
  75. package/dist/ship-badge/__tests__/ship-badge-generator.test.js +146 -0
  76. package/dist/ship-badge/__tests__/ship-badge-generator.test.js.map +1 -0
  77. package/dist/ship-badge/index.d.ts +9 -0
  78. package/dist/ship-badge/index.d.ts.map +1 -0
  79. package/dist/ship-badge/index.js +9 -0
  80. package/dist/ship-badge/index.js.map +1 -0
  81. package/dist/ship-badge/ship-badge-generator.d.ts +136 -0
  82. package/dist/ship-badge/ship-badge-generator.d.ts.map +1 -0
  83. package/dist/ship-badge/ship-badge-generator.js +681 -0
  84. package/dist/ship-badge/ship-badge-generator.js.map +1 -0
  85. package/package.json +20 -0
  86. package/src/index.ts +7 -0
  87. package/src/mock-implementation.ts +0 -0
  88. package/src/mockproof/__tests__/import-graph-scanner.test.ts +115 -0
  89. package/src/mockproof/import-graph-scanner.d.ts +93 -0
  90. package/src/mockproof/import-graph-scanner.d.ts.map +1 -0
  91. package/src/mockproof/import-graph-scanner.js +482 -0
  92. package/src/mockproof/import-graph-scanner.ts +540 -0
  93. package/src/mockproof/index.ts +18 -0
  94. package/src/reality-mode/auth-enforcer.ts +97 -0
  95. package/src/reality-mode/explorer/critical-flows.ts +504 -0
  96. package/src/reality-mode/explorer/flow-parser.ts +293 -0
  97. package/src/reality-mode/explorer/index.ts +22 -0
  98. package/src/reality-mode/explorer/runtime-explorer.ts +715 -0
  99. package/src/reality-mode/explorer/surface-discovery.ts +498 -0
  100. package/src/reality-mode/explorer/templates/example-flows/auth-flow.yaml +41 -0
  101. package/src/reality-mode/explorer/templates/example-flows/checkout-flow.yaml +66 -0
  102. package/src/reality-mode/explorer/templates/example-flows/contact-form.yaml +43 -0
  103. package/src/reality-mode/explorer/templates/github-action.yml +132 -0
  104. package/src/reality-mode/explorer/types.ts +356 -0
  105. package/src/reality-mode/fake-success-detector.ts +89 -0
  106. package/src/reality-mode/index.ts +19 -0
  107. package/src/reality-mode/reality-scanner.d.ts +123 -0
  108. package/src/reality-mode/reality-scanner.d.ts.map +1 -0
  109. package/src/reality-mode/reality-scanner.js +526 -0
  110. package/src/reality-mode/reality-scanner.ts +576 -0
  111. package/src/reality-mode/report-generator.ts +253 -0
  112. package/src/reality-mode/traffic-classifier.ts +169 -0
  113. package/src/reality-mode/types.ts +95 -0
  114. package/src/ship-badge/__tests__/ship-badge-generator.test.ts +162 -0
  115. package/src/ship-badge/index.ts +16 -0
  116. package/src/ship-badge/ship-badge-generator.d.ts +136 -0
  117. package/src/ship-badge/ship-badge-generator.d.ts.map +1 -0
  118. package/src/ship-badge/ship-badge-generator.js +779 -0
  119. package/src/ship-badge/ship-badge-generator.ts +873 -0
@@ -0,0 +1,132 @@
1
+ # Guardrail Reality Mode - GitHub Actions Workflow
2
+ #
3
+ # This workflow runs Reality Mode tests on every PR and deployment.
4
+ # Copy this to .github/workflows/reality-mode.yml in your repo.
5
+ #
6
+ # Prerequisites:
7
+ # - Set REALITY_TEST_URL secret (your staging/preview URL)
8
+ # - Set REALITY_AUTH secret (optional: email:password for auth)
9
+
10
+ name: Reality Mode Tests
11
+
12
+ on:
13
+ pull_request:
14
+ branches: [main, master]
15
+ deployment_status:
16
+ workflow_dispatch:
17
+ inputs:
18
+ url:
19
+ description: "URL to test"
20
+ required: true
21
+ type: string
22
+
23
+ jobs:
24
+ reality-test:
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 15
27
+
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+
32
+ - name: Setup Node.js
33
+ uses: actions/setup-node@v4
34
+ with:
35
+ node-version: "20"
36
+
37
+ - name: Install Playwright
38
+ run: |
39
+ npm install -D @playwright/test
40
+ npx playwright install chromium --with-deps
41
+
42
+ - name: Install Guardrail
43
+ run: npm install -g guardrail
44
+
45
+ - name: Determine URL
46
+ id: url
47
+ run: |
48
+ if [ -n "${{ github.event.inputs.url }}" ]; then
49
+ echo "url=${{ github.event.inputs.url }}" >> $GITHUB_OUTPUT
50
+ elif [ -n "${{ secrets.REALITY_TEST_URL }}" ]; then
51
+ echo "url=${{ secrets.REALITY_TEST_URL }}" >> $GITHUB_OUTPUT
52
+ elif [ "${{ github.event.deployment_status.state }}" == "success" ]; then
53
+ echo "url=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT
54
+ else
55
+ echo "url=" >> $GITHUB_OUTPUT
56
+ fi
57
+
58
+ - name: Run Reality Mode
59
+ if: steps.url.outputs.url != ''
60
+ run: |
61
+ guardrail reality \
62
+ --url "${{ steps.url.outputs.url }}" \
63
+ ${{ secrets.REALITY_AUTH && format('--auth "{0}"', secrets.REALITY_AUTH) || '' }} \
64
+ --junit \
65
+ --output .guardrail/reality
66
+ continue-on-error: true
67
+
68
+ - name: Upload Results
69
+ if: always() && steps.url.outputs.url != ''
70
+ uses: actions/upload-artifact@v4
71
+ with:
72
+ name: reality-mode-results
73
+ path: |
74
+ .guardrail/reality/reality-report.html
75
+ .guardrail/reality/explorer-results.json
76
+ .guardrail/reality/videos/
77
+ .guardrail/reality/trace.zip
78
+ retention-days: 14
79
+
80
+ - name: Upload JUnit Results
81
+ if: always() && steps.url.outputs.url != ''
82
+ uses: actions/upload-artifact@v4
83
+ with:
84
+ name: junit-results
85
+ path: .guardrail/reality/junit-results.xml
86
+
87
+ - name: Publish Test Results
88
+ if: always() && steps.url.outputs.url != ''
89
+ uses: dorny/test-reporter@v1
90
+ with:
91
+ name: Reality Mode Tests
92
+ path: .guardrail/reality/junit-results.xml
93
+ reporter: java-junit
94
+ fail-on-error: false
95
+
96
+ - name: Comment on PR
97
+ if: github.event_name == 'pull_request' && steps.url.outputs.url != ''
98
+ uses: actions/github-script@v7
99
+ with:
100
+ script: |
101
+ const fs = require('fs');
102
+ const path = '.guardrail/reality/explorer-results.json';
103
+
104
+ if (!fs.existsSync(path)) {
105
+ console.log('No results file found');
106
+ return;
107
+ }
108
+
109
+ const results = JSON.parse(fs.readFileSync(path, 'utf8'));
110
+ const score = results.score || 0;
111
+ const emoji = score >= 80 ? '🟢' : score >= 60 ? '🟡' : '🔴';
112
+
113
+ const body = `## ${emoji} Reality Mode Score: ${score}/100
114
+
115
+ | Metric | Result |
116
+ |--------|--------|
117
+ | Routes | ${results.coverage?.routes || 0}/${results.routes?.length || 0} working |
118
+ | Elements | ${results.coverage?.elements || 0}/${results.elements?.length || 0} working |
119
+ | Forms | ${results.coverage?.forms || 0}/${results.forms?.length || 0} working |
120
+ | Errors | ${results.errors?.length || 0} captured |
121
+
122
+ ${score >= 80 ? '✅ **Ready to ship!**' : score >= 60 ? '⚠️ **Needs some work**' : '❌ **Critical issues found**'}
123
+
124
+ 📊 [View Full Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
125
+ `;
126
+
127
+ github.rest.issues.createComment({
128
+ issue_number: context.issue.number,
129
+ owner: context.repo.owner,
130
+ repo: context.repo.repo,
131
+ body
132
+ });
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Reality Explorer Types
3
+ *
4
+ * Types for the comprehensive app exploration system that actually
5
+ * tests everything - buttons, forms, modals, auth flows, etc.
6
+ */
7
+
8
+ // ============================================================================
9
+ // App Surface Discovery
10
+ // ============================================================================
11
+
12
+ export interface DiscoveredRoute {
13
+ path: string;
14
+ method: "GET" | "POST" | "PUT" | "DELETE";
15
+ source: "link" | "router" | "api-call" | "redirect";
16
+ requiresAuth: boolean;
17
+ visited: boolean;
18
+ status?: number;
19
+ error?: string;
20
+ }
21
+
22
+ export interface DiscoveredElement {
23
+ id: string;
24
+ selector: string;
25
+ type:
26
+ | "button"
27
+ | "link"
28
+ | "input"
29
+ | "form"
30
+ | "modal-trigger"
31
+ | "dropdown"
32
+ | "tab"
33
+ | "accordion";
34
+ text: string;
35
+ page: string;
36
+ isDestructive: boolean;
37
+ tested: boolean;
38
+ result?: ElementTestResult;
39
+ }
40
+
41
+ export interface DiscoveredForm {
42
+ id: string;
43
+ selector: string;
44
+ page: string;
45
+ action?: string;
46
+ method: string;
47
+ fields: FormField[];
48
+ submitButton?: string;
49
+ tested: boolean;
50
+ result?: FormTestResult;
51
+ }
52
+
53
+ export interface FormField {
54
+ name: string;
55
+ type: string;
56
+ required: boolean;
57
+ selector: string;
58
+ placeholder?: string;
59
+ pattern?: string;
60
+ }
61
+
62
+ export interface DiscoveredAPI {
63
+ url: string;
64
+ method: string;
65
+ calledFrom: string;
66
+ status?: number;
67
+ responseTime?: number;
68
+ error?: string;
69
+ }
70
+
71
+ export interface AppSurface {
72
+ routes: DiscoveredRoute[];
73
+ elements: DiscoveredElement[];
74
+ forms: DiscoveredForm[];
75
+ apis: DiscoveredAPI[];
76
+ timestamp: string;
77
+ }
78
+
79
+ // ============================================================================
80
+ // Test Results
81
+ // ============================================================================
82
+
83
+ export interface ElementTestResult {
84
+ success: boolean;
85
+ action: "click" | "hover" | "focus";
86
+ beforeState: PageState;
87
+ afterState: PageState;
88
+ changes: StateChange[];
89
+ errors: CapturedError[];
90
+ networkCalls: NetworkCall[];
91
+ duration: number;
92
+ screenshot?: string;
93
+ }
94
+
95
+ export interface FormTestResult {
96
+ success: boolean;
97
+ fieldsFilledCount: number;
98
+ submitAttempted: boolean;
99
+ submitSucceeded: boolean;
100
+ validationErrors: string[];
101
+ networkCalls: NetworkCall[];
102
+ errors: CapturedError[];
103
+ duration: number;
104
+ screenshot?: string;
105
+ }
106
+
107
+ export interface PageState {
108
+ url: string;
109
+ title: string;
110
+ modalsOpen: number;
111
+ loadingIndicators: number;
112
+ errorMessages: string[];
113
+ domHash: string;
114
+ }
115
+
116
+ export interface StateChange {
117
+ type: "url" | "modal" | "dom" | "console" | "network";
118
+ description: string;
119
+ significance: "major" | "minor" | "none";
120
+ }
121
+
122
+ export interface CapturedError {
123
+ type: "console" | "network" | "uncaught" | "react-boundary";
124
+ message: string;
125
+ stack?: string;
126
+ url?: string;
127
+ timestamp: number;
128
+ }
129
+
130
+ export interface NetworkCall {
131
+ url: string;
132
+ method: string;
133
+ status: number;
134
+ duration: number;
135
+ requestBody?: string;
136
+ responsePreview?: string;
137
+ error?: string;
138
+ }
139
+
140
+ // ============================================================================
141
+ // Critical Flows
142
+ // ============================================================================
143
+
144
+ export interface CriticalFlow {
145
+ id: string;
146
+ name: string;
147
+ description: string;
148
+ steps: FlowStep[];
149
+ assertions: FlowAssertion[];
150
+ required: boolean;
151
+ }
152
+
153
+ export interface FlowStep {
154
+ action: "navigate" | "click" | "fill" | "wait" | "assert";
155
+ target?: string;
156
+ value?: string;
157
+ timeout?: number;
158
+ }
159
+
160
+ export interface FlowAssertion {
161
+ type:
162
+ | "url-contains"
163
+ | "element-visible"
164
+ | "element-hidden"
165
+ | "cookie-exists"
166
+ | "localstorage-has"
167
+ | "network-success"
168
+ | "no-errors";
169
+ value: string;
170
+ critical: boolean;
171
+ }
172
+
173
+ export interface FlowResult {
174
+ flow: CriticalFlow;
175
+ success: boolean;
176
+ stepsCompleted: number;
177
+ stepsTotal: number;
178
+ assertionsPassed: number;
179
+ assertionsTotal: number;
180
+ failedAt?: string;
181
+ errors: CapturedError[];
182
+ duration: number;
183
+ trace?: string;
184
+ video?: string;
185
+ }
186
+
187
+ // ============================================================================
188
+ // Coverage & Scoring
189
+ // ============================================================================
190
+
191
+ export interface CoverageMetrics {
192
+ routes: {
193
+ discovered: number;
194
+ visited: number;
195
+ successful: number;
196
+ blocked: number;
197
+ percentage: number;
198
+ };
199
+ elements: {
200
+ discovered: number;
201
+ tested: number;
202
+ successful: number;
203
+ skippedDestructive: number;
204
+ percentage: number;
205
+ };
206
+ forms: {
207
+ discovered: number;
208
+ tested: number;
209
+ successful: number;
210
+ blockedByAuth: number;
211
+ percentage: number;
212
+ };
213
+ apis: {
214
+ discovered: number;
215
+ called: number;
216
+ successful: number;
217
+ failed: number;
218
+ percentage: number;
219
+ };
220
+ flows: {
221
+ total: number;
222
+ passed: number;
223
+ failed: number;
224
+ skipped: number;
225
+ percentage: number;
226
+ };
227
+ }
228
+
229
+ export interface RealityScore {
230
+ overall: number; // 0-100
231
+ breakdown: {
232
+ coverage: number; // 40 points max
233
+ functionality: number; // 35 points max
234
+ stability: number; // 15 points max
235
+ ux: number; // 10 points max
236
+ };
237
+ grade: "A" | "B" | "C" | "D" | "F";
238
+ verdict: "ship-it" | "needs-work" | "broken";
239
+ }
240
+
241
+ // ============================================================================
242
+ // Explorer Config
243
+ // ============================================================================
244
+
245
+ export interface ExplorerConfig {
246
+ baseUrl: string;
247
+ maxPages: number;
248
+ maxActionsPerPage: number;
249
+ timeout: number;
250
+ headless: boolean;
251
+
252
+ // Auth config
253
+ auth?: {
254
+ loginUrl: string;
255
+ credentials: {
256
+ emailField: string;
257
+ passwordField: string;
258
+ email: string;
259
+ password: string;
260
+ };
261
+ successIndicator: string;
262
+ };
263
+
264
+ // Safety
265
+ allowDestructive: boolean;
266
+ destructivePatterns: string[];
267
+
268
+ // Output
269
+ outputDir: string;
270
+ captureVideo: boolean;
271
+ captureTrace: boolean;
272
+ captureScreenshots: boolean;
273
+
274
+ // Custom flows
275
+ flows?: CriticalFlow[];
276
+ }
277
+
278
+ // ============================================================================
279
+ // Final Report
280
+ // ============================================================================
281
+
282
+ export interface ExplorerReport {
283
+ summary: {
284
+ score: RealityScore;
285
+ coverage: CoverageMetrics;
286
+ duration: number;
287
+ timestamp: string;
288
+ };
289
+
290
+ surface: AppSurface;
291
+
292
+ results: {
293
+ routes: RouteResult[];
294
+ elements: ElementResult[];
295
+ forms: FormResult[];
296
+ flows: FlowResult[];
297
+ };
298
+
299
+ failures: {
300
+ critical: FailureReport[];
301
+ warnings: FailureReport[];
302
+ };
303
+
304
+ recommendations: Recommendation[];
305
+
306
+ artifacts: {
307
+ traces: string[];
308
+ videos: string[];
309
+ screenshots: string[];
310
+ };
311
+ }
312
+
313
+ export interface RouteResult {
314
+ route: DiscoveredRoute;
315
+ status: "success" | "error" | "blocked" | "skipped";
316
+ responseTime?: number;
317
+ errors: CapturedError[];
318
+ }
319
+
320
+ export interface ElementResult {
321
+ element: DiscoveredElement;
322
+ status: "success" | "error" | "no-change" | "skipped";
323
+ result?: ElementTestResult;
324
+ }
325
+
326
+ export interface FormResult {
327
+ form: DiscoveredForm;
328
+ status:
329
+ | "success"
330
+ | "validation-error"
331
+ | "submit-error"
332
+ | "blocked"
333
+ | "skipped";
334
+ result?: FormTestResult;
335
+ }
336
+
337
+ export interface FailureReport {
338
+ id: string;
339
+ type: "route" | "element" | "form" | "flow" | "api";
340
+ severity: "critical" | "warning";
341
+ title: string;
342
+ description: string;
343
+ location: string;
344
+ rootCause?: string;
345
+ reproduction: string[];
346
+ screenshot?: string;
347
+ trace?: string;
348
+ }
349
+
350
+ export interface Recommendation {
351
+ priority: "high" | "medium" | "low";
352
+ category: "functionality" | "auth" | "ux" | "performance" | "stability";
353
+ title: string;
354
+ description: string;
355
+ fix?: string;
356
+ }
@@ -0,0 +1,89 @@
1
+ import { ReplayStep, FakeSuccessResult } from "./types";
2
+
3
+ export class FakeSuccessDetector {
4
+ /**
5
+ * Analyze a replay to find "Fake Success" patterns
6
+ * i.e., User clicked "Save" -> UI showed success -> No backend write happened
7
+ */
8
+ detect(replay: ReplayStep[]): FakeSuccessResult[] {
9
+ const results: FakeSuccessResult[] = [];
10
+ const saveActionPatterns = [
11
+ /save/i,
12
+ /update/i,
13
+ /create/i,
14
+ /submit/i,
15
+ /confirm/i,
16
+ /send/i,
17
+ /pay/i,
18
+ ];
19
+
20
+ // Iterate through replay to find "Write" actions
21
+ for (let i = 0; i < replay.length; i++) {
22
+ const step = replay[i];
23
+ if (!step || step.type !== "action" || !step.data?.selector) continue;
24
+
25
+ const selector = step.data.selector;
26
+ const isWriteAction = saveActionPatterns.some((p) => p.test(selector));
27
+
28
+ if (isWriteAction) {
29
+ // Look ahead for network activity (next 5 seconds or until next action)
30
+ const subsequentSteps = this.getSubsequentSteps(replay, i, 5000);
31
+ const writeRequests = subsequentSteps.filter(
32
+ (s) =>
33
+ s.type === "request" &&
34
+ ["POST", "PUT", "PATCH", "DELETE"].includes(s.data.method),
35
+ );
36
+
37
+ if (writeRequests.length === 0) {
38
+ // No write request found!
39
+ // But maybe it's a client-side only app?
40
+ // Or maybe the request happened but we missed it?
41
+ // Or maybe it's "Fake Success".
42
+
43
+ results.push({
44
+ isFake: true,
45
+ score: 0,
46
+ evidence: [
47
+ `Clicked "${selector}" but no POST/PUT/PATCH/DELETE request followed.`,
48
+ ],
49
+ actionStep: step,
50
+ });
51
+ } else {
52
+ // Write request found. Check if it looked real.
53
+ // (TrafficClassifier handles the quality of the request/response)
54
+ results.push({
55
+ isFake: false,
56
+ score: 100,
57
+ evidence: [
58
+ `Clicked "${selector}" triggered ${writeRequests.length} write request(s).`,
59
+ ],
60
+ actionStep: step,
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ return results;
67
+ }
68
+
69
+ private getSubsequentSteps(
70
+ replay: ReplayStep[],
71
+ startIndex: number,
72
+ timeWindow: number,
73
+ ): ReplayStep[] {
74
+ const steps: ReplayStep[] = [];
75
+ const startStep = replay[startIndex];
76
+ if (!startStep) return steps;
77
+
78
+ const startTime = startStep.timestamp;
79
+
80
+ for (let i = startIndex + 1; i < replay.length; i++) {
81
+ const step = replay[i];
82
+ if (!step) break;
83
+ if (step.timestamp - startStep.timestamp > timeWindow) break;
84
+ steps.push(step);
85
+ }
86
+
87
+ return steps;
88
+ }
89
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Reality Mode - Runtime Fake Detection
3
+ *
4
+ * "Stop shipping pretend features. Guardrail runs your app and catches the lies."
5
+ *
6
+ * A literal "flight recorder" for fake apps.
7
+ */
8
+
9
+ export {
10
+ RealityScanner,
11
+ realityScanner,
12
+ DEFAULT_FAKE_PATTERNS,
13
+ } from "./reality-scanner";
14
+
15
+ export * from "./types";
16
+ export { ReportGenerator } from "./report-generator";
17
+ export { TrafficClassifier } from "./traffic-classifier";
18
+ export { FakeSuccessDetector } from "./fake-success-detector";
19
+ export { AuthEnforcer } from "./auth-enforcer";
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Reality Mode - Runtime Fake Detection
3
+ *
4
+ * "Stop shipping pretend features. Guardrail runs your app and catches the lies."
5
+ *
6
+ * This module spins up the app, intercepts network calls, clicks through the UI,
7
+ * and detects:
8
+ * - Calls to localhost, jsonplaceholder, staging domains, ngrok
9
+ * - Routes returning demo/placeholder responses
10
+ * - Silent fallback success patterns
11
+ * - Mock billing and fake invoice IDs
12
+ *
13
+ * The killer feature: a "flight recorder" replay showing exactly what happened.
14
+ */
15
+ export interface FakePattern {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ severity: "critical" | "warning" | "info";
20
+ detect: (request: InterceptedRequest | InterceptedResponse) => boolean;
21
+ }
22
+ export interface InterceptedRequest {
23
+ type: "request";
24
+ url: string;
25
+ method: string;
26
+ headers: Record<string, string>;
27
+ body?: string;
28
+ timestamp: number;
29
+ }
30
+ export interface InterceptedResponse {
31
+ type: "response";
32
+ url: string;
33
+ status: number;
34
+ headers: Record<string, string>;
35
+ body?: string;
36
+ timestamp: number;
37
+ }
38
+ export interface UserAction {
39
+ type: "click" | "input" | "navigation" | "scroll";
40
+ selector?: string;
41
+ value?: string;
42
+ url?: string;
43
+ timestamp: number;
44
+ screenshot?: string;
45
+ }
46
+ export interface FakeDetection {
47
+ pattern: FakePattern;
48
+ request?: InterceptedRequest;
49
+ response?: InterceptedResponse;
50
+ action?: UserAction;
51
+ timestamp: number;
52
+ evidence: string;
53
+ }
54
+ export interface ReplayStep {
55
+ timestamp: number;
56
+ action?: UserAction;
57
+ request?: InterceptedRequest;
58
+ response?: InterceptedResponse;
59
+ detections: FakeDetection[];
60
+ screenshot?: string;
61
+ }
62
+ export interface RealityModeResult {
63
+ verdict: "real" | "fake" | "suspicious";
64
+ score: number;
65
+ detections: FakeDetection[];
66
+ replay: ReplayStep[];
67
+ summary: {
68
+ totalRequests: number;
69
+ fakeRequests: number;
70
+ totalActions: number;
71
+ criticalIssues: number;
72
+ warnings: number;
73
+ };
74
+ timestamp: string;
75
+ duration: number;
76
+ }
77
+ export interface RealityModeConfig {
78
+ baseUrl: string;
79
+ timeout: number;
80
+ patterns: FakePattern[];
81
+ clickPaths: string[][];
82
+ screenshotOnDetection: boolean;
83
+ headless: boolean;
84
+ }
85
+ export declare const DEFAULT_FAKE_PATTERNS: FakePattern[];
86
+ export declare class RealityScanner {
87
+ private config;
88
+ private replay;
89
+ private detections;
90
+ private requests;
91
+ private responses;
92
+ private actions;
93
+ constructor(config?: Partial<RealityModeConfig>);
94
+ /**
95
+ * Generate Playwright test code for Reality Mode scanning
96
+ */
97
+ generatePlaywrightTest(config: {
98
+ baseUrl: string;
99
+ clickPaths: string[][];
100
+ outputDir: string;
101
+ }): string;
102
+ /**
103
+ * Analyze intercepted network traffic for fake patterns
104
+ */
105
+ analyzeTraffic(
106
+ requests: InterceptedRequest[],
107
+ responses: InterceptedResponse[],
108
+ ): FakeDetection[];
109
+ /**
110
+ * Generate evidence description for a detection
111
+ */
112
+ private getEvidence;
113
+ /**
114
+ * Generate a human-readable Reality Mode report
115
+ */
116
+ generateReport(result: RealityModeResult): string;
117
+ /**
118
+ * Generate common click paths for testing
119
+ */
120
+ generateDefaultClickPaths(): string[][];
121
+ }
122
+ export declare const realityScanner: RealityScanner;
123
+ //# sourceMappingURL=reality-scanner.d.ts.map