@odavl/guardian 0.2.0 → 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 (84) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/README.md +155 -97
  3. package/bin/guardian.js +1345 -60
  4. package/config/README.md +59 -0
  5. package/config/profiles/landing-demo.yaml +16 -0
  6. package/package.json +21 -11
  7. package/policies/landing-demo.json +22 -0
  8. package/src/enterprise/audit-logger.js +166 -0
  9. package/src/enterprise/pdf-exporter.js +267 -0
  10. package/src/enterprise/rbac-gate.js +142 -0
  11. package/src/enterprise/rbac.js +239 -0
  12. package/src/enterprise/site-manager.js +180 -0
  13. package/src/founder/feedback-system.js +156 -0
  14. package/src/founder/founder-tracker.js +213 -0
  15. package/src/founder/usage-signals.js +141 -0
  16. package/src/guardian/alert-ledger.js +121 -0
  17. package/src/guardian/attempt-engine.js +568 -7
  18. package/src/guardian/attempt-registry.js +42 -1
  19. package/src/guardian/attempt-relevance.js +106 -0
  20. package/src/guardian/attempt.js +24 -0
  21. package/src/guardian/baseline.js +12 -4
  22. package/src/guardian/breakage-intelligence.js +1 -0
  23. package/src/guardian/ci-cli.js +121 -0
  24. package/src/guardian/ci-output.js +4 -3
  25. package/src/guardian/cli-summary.js +79 -92
  26. package/src/guardian/config-loader.js +162 -0
  27. package/src/guardian/drift-detector.js +100 -0
  28. package/src/guardian/enhanced-html-reporter.js +221 -4
  29. package/src/guardian/env-guard.js +127 -0
  30. package/src/guardian/failure-intelligence.js +173 -0
  31. package/src/guardian/first-run-profile.js +89 -0
  32. package/src/guardian/first-run.js +6 -1
  33. package/src/guardian/flag-validator.js +17 -3
  34. package/src/guardian/html-reporter.js +2 -0
  35. package/src/guardian/human-reporter.js +431 -0
  36. package/src/guardian/index.js +22 -19
  37. package/src/guardian/init-command.js +9 -5
  38. package/src/guardian/intent-detector.js +146 -0
  39. package/src/guardian/journey-definitions.js +132 -0
  40. package/src/guardian/journey-scan-cli.js +145 -0
  41. package/src/guardian/journey-scanner.js +583 -0
  42. package/src/guardian/junit-reporter.js +18 -1
  43. package/src/guardian/live-cli.js +95 -0
  44. package/src/guardian/live-scheduler-runner.js +137 -0
  45. package/src/guardian/live-scheduler.js +146 -0
  46. package/src/guardian/market-reporter.js +341 -81
  47. package/src/guardian/pattern-analyzer.js +348 -0
  48. package/src/guardian/policy.js +80 -3
  49. package/src/guardian/preset-loader.js +9 -6
  50. package/src/guardian/reality.js +1278 -117
  51. package/src/guardian/reporter.js +27 -41
  52. package/src/guardian/run-artifacts.js +212 -0
  53. package/src/guardian/run-cleanup.js +207 -0
  54. package/src/guardian/run-latest.js +90 -0
  55. package/src/guardian/run-list.js +211 -0
  56. package/src/guardian/scan-presets.js +100 -11
  57. package/src/guardian/selector-fallbacks.js +394 -0
  58. package/src/guardian/semantic-contact-finder.js +2 -1
  59. package/src/guardian/site-introspection.js +257 -0
  60. package/src/guardian/smoke.js +2 -2
  61. package/src/guardian/snapshot-schema.js +25 -1
  62. package/src/guardian/snapshot.js +46 -2
  63. package/src/guardian/stability-scorer.js +169 -0
  64. package/src/guardian/template-command.js +184 -0
  65. package/src/guardian/text-formatters.js +426 -0
  66. package/src/guardian/verdict.js +320 -0
  67. package/src/guardian/verdicts.js +74 -0
  68. package/src/guardian/watch-runner.js +3 -7
  69. package/src/payments/stripe-checkout.js +169 -0
  70. package/src/plans/plan-definitions.js +148 -0
  71. package/src/plans/plan-manager.js +211 -0
  72. package/src/plans/usage-tracker.js +210 -0
  73. package/src/recipes/recipe-engine.js +188 -0
  74. package/src/recipes/recipe-failure-analysis.js +159 -0
  75. package/src/recipes/recipe-registry.js +134 -0
  76. package/src/recipes/recipe-runtime.js +507 -0
  77. package/src/recipes/recipe-store.js +410 -0
  78. package/guardian-contract-v1.md +0 -149
  79. /package/{guardian.config.json → config/guardian.config.json} +0 -0
  80. /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
  81. /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
  82. /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
  83. /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
  84. /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Phase 12.1: Recipe Store
3
+ * Manage built-in and custom recipes
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+ const { validateRecipe } = require('./recipe-engine');
10
+ const {
11
+ registerRecipe,
12
+ ensureBuiltInRegistry,
13
+ computeRecipeChecksum,
14
+ getRegistryEntry,
15
+ removeRegistryEntry,
16
+ } = require('./recipe-registry');
17
+
18
+ const RECIPES_DIR = path.join(os.homedir(), '.odavl-guardian', 'recipes');
19
+ const CUSTOM_RECIPES_FILE = path.join(RECIPES_DIR, 'custom.json');
20
+
21
+ // Built-in recipes
22
+ const BUILT_IN_RECIPES = [
23
+ {
24
+ id: 'shopify-checkout',
25
+ name: 'Shopify Checkout Flow',
26
+ platform: 'shopify',
27
+ version: '1.0.0',
28
+ intent: 'Complete a customer checkout to verify payment processing',
29
+ steps: [
30
+ 'Navigate to product page',
31
+ 'Add item to cart',
32
+ 'Open cart',
33
+ 'Proceed to checkout',
34
+ 'Fill shipping address',
35
+ 'Select shipping method',
36
+ 'Enter payment information',
37
+ 'Complete purchase'
38
+ ],
39
+ expectedGoal: 'Order confirmation page loads with order number',
40
+ notes: 'Assumes public storefront with at least one product available'
41
+ },
42
+ {
43
+ id: 'saas-signup',
44
+ name: 'SaaS Signup Flow',
45
+ platform: 'saas',
46
+ version: '1.0.0',
47
+ intent: 'Create new account to verify user onboarding',
48
+ steps: [
49
+ 'Navigate to signup page',
50
+ 'Fill email address',
51
+ 'Create password',
52
+ 'Accept terms',
53
+ 'Submit signup form',
54
+ 'Verify email verification sent',
55
+ 'Complete email verification',
56
+ 'Access dashboard'
57
+ ],
58
+ expectedGoal: 'User lands on authenticated dashboard or onboarding',
59
+ notes: 'May require temporary email or mock SMTP setup for verification'
60
+ },
61
+ {
62
+ id: 'landing-contact',
63
+ name: 'Landing Page Contact Form',
64
+ platform: 'landing',
65
+ version: '1.0.0',
66
+ intent: 'Submit contact form to verify lead capture',
67
+ steps: [
68
+ 'Navigate to landing page',
69
+ 'Locate contact form',
70
+ 'Enter name',
71
+ 'Enter email',
72
+ 'Enter message',
73
+ 'Submit form',
74
+ 'Verify success message',
75
+ 'Check form was cleared'
76
+ ],
77
+ expectedGoal: 'Success message displays and form resets',
78
+ notes: 'No email sending required, tests frontend only'
79
+ }
80
+ ];
81
+
82
+ /**
83
+ * Ensure recipes directory exists
84
+ */
85
+ function ensureRecipesDir() {
86
+ if (!fs.existsSync(RECIPES_DIR)) {
87
+ fs.mkdirSync(RECIPES_DIR, { recursive: true });
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get all recipes (built-in + custom)
93
+ */
94
+ function getAllRecipes() {
95
+ ensureBuiltInRegistry(BUILT_IN_RECIPES);
96
+ const recipes = [...BUILT_IN_RECIPES];
97
+
98
+ const customRecipes = getCustomRecipes();
99
+ recipes.push(...customRecipes);
100
+
101
+ return recipes;
102
+ }
103
+
104
+ /**
105
+ * Get recipe by ID
106
+ */
107
+ function getRecipe(id) {
108
+ // Check built-in first
109
+ const builtIn = BUILT_IN_RECIPES.find(r => r.id === id);
110
+ if (builtIn) return builtIn;
111
+
112
+ // Check custom
113
+ const custom = getCustomRecipes();
114
+ return custom.find(r => r.id === id);
115
+ }
116
+
117
+ /**
118
+ * Get recipes by platform
119
+ */
120
+ function getRecipesByPlatform(platform) {
121
+ return getAllRecipes().filter(r => r.platform === platform);
122
+ }
123
+
124
+ /**
125
+ * Get custom recipes
126
+ */
127
+ function getCustomRecipes() {
128
+ ensureRecipesDir();
129
+
130
+ if (!fs.existsSync(CUSTOM_RECIPES_FILE)) {
131
+ return [];
132
+ }
133
+
134
+ try {
135
+ const data = JSON.parse(fs.readFileSync(CUSTOM_RECIPES_FILE, 'utf-8'));
136
+ return data.recipes || [];
137
+ } catch (error) {
138
+ return [];
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Save custom recipes
144
+ */
145
+ function saveCustomRecipes(recipes) {
146
+ ensureRecipesDir();
147
+
148
+ const data = { recipes, updatedAt: new Date().toISOString() };
149
+ fs.writeFileSync(CUSTOM_RECIPES_FILE, JSON.stringify(data, null, 2), 'utf-8');
150
+ }
151
+
152
+ /**
153
+ * Add custom recipe
154
+ */
155
+ function addRecipe(recipe) {
156
+ // Validate
157
+ const validation = validateRecipe(recipe);
158
+ if (!validation.valid) {
159
+ throw new Error(`Invalid recipe: ${validation.errors.join(', ')}`);
160
+ }
161
+ if (!recipe.version) {
162
+ recipe.version = '1.0.0';
163
+ }
164
+
165
+ // Check for duplicates
166
+ const existing = getRecipe(recipe.id);
167
+ if (existing) {
168
+ throw new Error(`Recipe with id '${recipe.id}' already exists`);
169
+ }
170
+
171
+ // Add to custom recipes
172
+ const customs = getCustomRecipes();
173
+ customs.push(recipe);
174
+ saveCustomRecipes(customs);
175
+ registerRecipe({
176
+ id: recipe.id,
177
+ name: recipe.name,
178
+ platform: recipe.platform,
179
+ version: recipe.version,
180
+ source: 'imported',
181
+ checksum: computeRecipeChecksum(recipe),
182
+ });
183
+
184
+ return recipe;
185
+ }
186
+
187
+ /**
188
+ * Remove custom recipe
189
+ */
190
+ function removeRecipe(id) {
191
+ // Can't remove built-in
192
+ if (BUILT_IN_RECIPES.find(r => r.id === id)) {
193
+ throw new Error(`Cannot remove built-in recipe: ${id}`);
194
+ }
195
+
196
+ const customs = getCustomRecipes();
197
+ const index = customs.findIndex(r => r.id === id);
198
+
199
+ if (index === -1) {
200
+ throw new Error(`Recipe not found: ${id}`);
201
+ }
202
+
203
+ const recipe = customs[index];
204
+ customs.splice(index, 1);
205
+ saveCustomRecipes(customs);
206
+ removeRegistryEntry(id);
207
+
208
+ return recipe;
209
+ }
210
+
211
+ function replaceCustomRecipe(recipe) {
212
+ const customs = getCustomRecipes();
213
+ const index = customs.findIndex(r => r.id === recipe.id);
214
+ if (index === -1) {
215
+ customs.push(recipe);
216
+ } else {
217
+ customs[index] = recipe;
218
+ }
219
+ saveCustomRecipes(customs);
220
+ }
221
+
222
+ /**
223
+ * Import recipes from file
224
+ */
225
+ function importRecipes(filePath) {
226
+ if (!fs.existsSync(filePath)) {
227
+ throw new Error(`File not found: ${filePath}`);
228
+ }
229
+
230
+ let data;
231
+ try {
232
+ data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
233
+ } catch (error) {
234
+ throw new Error(`Invalid JSON in file: ${error.message}`);
235
+ }
236
+
237
+ const recipes = Array.isArray(data) ? data : data.recipes || [];
238
+
239
+ if (!Array.isArray(recipes)) {
240
+ throw new Error('File must contain array of recipes or object with "recipes" array');
241
+ }
242
+
243
+ const imported = [];
244
+ const errors = [];
245
+
246
+ for (const recipe of recipes) {
247
+ try {
248
+ // Skip if already exists
249
+ if (getRecipe(recipe.id)) {
250
+ errors.push(`${recipe.id}: Already exists (skipped)`);
251
+ continue;
252
+ }
253
+
254
+ // Validate
255
+ const validation = validateRecipe(recipe);
256
+ if (!validation.valid) {
257
+ errors.push(`${recipe.id}: ${validation.errors.join(', ')}`);
258
+ continue;
259
+ }
260
+
261
+ // Add to customs
262
+ const customs = getCustomRecipes();
263
+ customs.push(recipe);
264
+ saveCustomRecipes(customs);
265
+
266
+ registerRecipe({
267
+ id: recipe.id,
268
+ name: recipe.name,
269
+ platform: recipe.platform,
270
+ version: recipe.version || '1.0.0',
271
+ source: 'imported',
272
+ checksum: computeRecipeChecksum(recipe),
273
+ });
274
+
275
+ imported.push(recipe.id);
276
+ } catch (error) {
277
+ errors.push(`${recipe.id}: ${error.message}`);
278
+ }
279
+ }
280
+
281
+ return {
282
+ imported,
283
+ errors,
284
+ count: imported.length
285
+ };
286
+ }
287
+
288
+ function importRecipeWithMetadata(filePath, options = {}) {
289
+ const { force = false } = options;
290
+ if (!fs.existsSync(filePath)) {
291
+ throw new Error(`File not found: ${filePath}`);
292
+ }
293
+
294
+ let data;
295
+ try {
296
+ data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
297
+ } catch (error) {
298
+ throw new Error(`Invalid JSON in file: ${error.message}`);
299
+ }
300
+
301
+ // Support Phase 12.1 format (array) for backwards compatibility
302
+ const recipe = data.recipe || (Array.isArray(data.recipes) ? data.recipes[0] : data);
303
+ if (!recipe || typeof recipe !== 'object') {
304
+ throw new Error('Import file must include a recipe object');
305
+ }
306
+
307
+ const validation = validateRecipe(recipe);
308
+ if (!validation.valid) {
309
+ throw new Error(`Invalid recipe: ${validation.errors.join(', ')}`);
310
+ }
311
+
312
+ if (!recipe.version) {
313
+ recipe.version = data.version || '1.0.0';
314
+ }
315
+
316
+ const checksum = computeRecipeChecksum(recipe);
317
+ if (data.checksum && data.checksum !== checksum) {
318
+ console.warn(`Warning: checksum mismatch for recipe ${recipe.id}. Expected ${data.checksum}, got ${checksum}.`);
319
+ }
320
+
321
+ const existing = getRecipe(recipe.id);
322
+ if (existing) {
323
+ const isBuiltIn = BUILT_IN_RECIPES.some(r => r.id === recipe.id);
324
+ if (isBuiltIn) {
325
+ throw new Error(`Recipe '${recipe.id}' is built-in and cannot be overwritten.`);
326
+ }
327
+ if (!force) {
328
+ throw new Error(`Recipe '${recipe.id}' already exists. Use --force to overwrite.`);
329
+ }
330
+ }
331
+
332
+ replaceCustomRecipe(recipe);
333
+ registerRecipe({
334
+ id: recipe.id,
335
+ name: recipe.name,
336
+ platform: recipe.platform,
337
+ version: recipe.version,
338
+ source: 'imported',
339
+ checksum,
340
+ });
341
+
342
+ return { recipe, checksum };
343
+ }
344
+
345
+ /**
346
+ * Export recipes
347
+ */
348
+ function exportRecipes(ids, outputPath) {
349
+ const recipes = ids.map(id => {
350
+ const recipe = getRecipe(id);
351
+ if (!recipe) {
352
+ throw new Error(`Recipe not found: ${id}`);
353
+ }
354
+ return recipe;
355
+ });
356
+
357
+ const data = {
358
+ exportedAt: new Date().toISOString(),
359
+ count: recipes.length,
360
+ recipes
361
+ };
362
+
363
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf-8');
364
+ return data;
365
+ }
366
+
367
+ function exportRecipeWithMetadata(id, outputPath) {
368
+ const recipe = getRecipe(id);
369
+ if (!recipe) {
370
+ throw new Error(`Recipe not found: ${id}`);
371
+ }
372
+
373
+ const checksum = computeRecipeChecksum(recipe);
374
+ const payload = {
375
+ id: recipe.id,
376
+ name: recipe.name,
377
+ platform: recipe.platform,
378
+ version: recipe.version || '1.0.0',
379
+ checksum,
380
+ exportedAt: new Date().toISOString(),
381
+ recipe,
382
+ };
383
+
384
+ fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8');
385
+ return payload;
386
+ }
387
+
388
+ /**
389
+ * Reset custom recipes (for testing)
390
+ */
391
+ function resetCustomRecipes() {
392
+ if (fs.existsSync(CUSTOM_RECIPES_FILE)) {
393
+ fs.unlinkSync(CUSTOM_RECIPES_FILE);
394
+ }
395
+ }
396
+
397
+ module.exports = {
398
+ getAllRecipes,
399
+ getRecipe,
400
+ getRecipesByPlatform,
401
+ getCustomRecipes,
402
+ addRecipe,
403
+ removeRecipe,
404
+ importRecipes,
405
+ exportRecipes,
406
+ exportRecipeWithMetadata,
407
+ importRecipeWithMetadata,
408
+ resetCustomRecipes,
409
+ BUILT_IN_RECIPES
410
+ };
@@ -1,149 +0,0 @@
1
- # ODAVL Guardian — Contract v1.0
2
-
3
- Status: Active
4
-
5
- Scope: Guardian MVP
6
-
7
- Version: v1.0
8
-
9
- Decision-Grade: Yes
10
-
11
- ## 0) Purpose
12
-
13
- This contract defines the canonical, non-negotiable rules and guarantees for ODAVL Guardian’s Market Reality Testing Engine at MVP scope. It specifies final decisions, confidence semantics, evidence requirements, flow execution rules, honesty constraints, prohibited behaviors, verifiability, and stability expectations. All implementations, outputs, and operator-facing UX must conform to this contract.
14
-
15
- ## 1) Definition
16
-
17
- ODAVL Guardian is a headless browser-based reality testing engine that:
18
- - Launches a real browser (Playwright/Chromium) to inspect a site via crawl and/or execute predefined flows.
19
- - Collects evidence artifacts (screenshots, logs, reports, traces, optional HAR) for verifiable outcomes.
20
- - Computes coverage and behavior metrics, forms a conservative confidence judgment, and issues a final decision.
21
- - Emits machine-readable `report.json`, human-readable `report.html`, `logs.txt`, and run directory with artifacts.
22
-
23
- Terminology:
24
- - "Coverage Confidence": Confidence derived from how much of the site was observed (depth/pages vs discovered).
25
- - "Behavioral Confidence": Confidence derived from observed runtime stability and health (navigation/http/page/console errors).
26
- - "Evidence Policy": The resolved set of required vs optional artifacts that must be present for a decision to be valid.
27
- - "Flow": A predefined deterministic sequence of real user interactions (navigate/click/type/submit/waitFor).
28
-
29
- ## 2) Final Decisions
30
-
31
- Guardian issues exactly three decisions:
32
- - READY — Site is acceptable for MVP launch given the evidence and confidence model.
33
- - DO_NOT_LAUNCH — Launch is blocked due to critical failure or missing required evidence.
34
- - INSUFFICIENT_CONFIDENCE — Evidence and/or behavior do not justify readiness.
35
-
36
- Exit codes:
37
- - READY → 0
38
- - DO_NOT_LAUNCH → 1
39
- - INSUFFICIENT_CONFIDENCE → 1
40
- - TOOL ERROR → 2
41
-
42
- Decision drivers:
43
- - Any Flow FAIL → DO_NOT_LAUNCH.
44
- - Required evidence missing per Evidence Policy → DO_NOT_LAUNCH.
45
- - Overall confidence HIGH or MEDIUM → READY.
46
- - Overall confidence LOW → INSUFFICIENT_CONFIDENCE.
47
-
48
- ## 3) Confidence Model
49
-
50
- Guardian separates confidence into two dimensions and a resolved overall level.
51
-
52
- ### 3.1 Coverage Confidence
53
- - HIGH: Sufficient coverage (e.g., deep exploration and/or high coverage percentage).
54
- - MEDIUM: Moderate coverage.
55
- - LOW: Limited coverage (e.g., very few pages visited).
56
-
57
- ### 3.2 Behavioral Confidence
58
- - HIGH: No critical runtime instability (no navigation failures, no page errors, no server 5xx), acceptable client logs.
59
- - MEDIUM: Minor issues (e.g., 4xx or console errors) without critical failures.
60
- - LOW: Critical instability (navigation failure, unhandled page errors, or server 5xx).
61
-
62
- ### 3.3 Overall Confidence Resolution
63
- - HIGH: Sufficient coverage OR a successful Flow with no critical errors.
64
- - MEDIUM: Limited coverage BUT clean behavior (no page errors or critical failures); minor issues allowed.
65
- - LOW: Insufficient coverage AND/OR unstable behavior (critical failures present).
66
-
67
- Guardian must record a human-readable "reasoning" for the resolved confidence.
68
-
69
- ## 4) Evidence Contract
70
-
71
- Guardian enforces an explicit Evidence Policy defining required vs optional artifacts.
72
-
73
- ### 4.1 Policy Modes
74
- - normal (default):
75
- - REQUIRED: screenshots (pages and flow steps), `report.json`, `report.html`.
76
- - REQUIRED when enabled: `trace.zip`.
77
- - OPTIONAL: `network.har`/`network.json`.
78
- - strict:
79
- - REQUIRED: screenshots, `report.json`, `report.html`.
80
- - REQUIRED when enabled: `trace.zip` and `network.har`/`network.json`.
81
- - custom (via CLI overrides):
82
- - `--require-har` forces HAR required.
83
- - `--optional-har` forces HAR optional.
84
- - Precedence: `require-har` > `optional-har` > `--evidence`.
85
-
86
- ### 4.2 Missing Evidence Handling
87
- - Missing REQUIRED evidence → DO_NOT_LAUNCH.
88
- - Missing OPTIONAL evidence → does not block READY; recorded as warnings and may reduce confidence.
89
-
90
- ### 4.3 Evidence Reporting (report.json)
91
- Guardian must include:
92
- ```
93
- evidence: {
94
- policy: { mode: "normal|strict|custom", requirements: { screenshots: true, traceWhenEnabled: true, harWhenEnabled: true|false } },
95
- present: { screenshots: true|false, trace: true|false|null, har: true|false|null },
96
- requiredMissing: [ ... ],
97
- optionalMissing: [ ... ],
98
- warnings: [ { code, message } ]
99
- }
100
- ```
101
-
102
- The HTML report must present the Evidence Policy, list required/optional missing items, and warnings.
103
-
104
- ## 5) Flow Contract
105
-
106
- Flows are executed as real interactions through the browser and must:
107
- - Support step types: `navigate <url>`, `click <selector(s)>`, `type <selector> <value>`, `submit <selector>`, `waitFor <selector|url:pattern>`.
108
- - Capture a full-page screenshot for each step with deterministic naming (`flow-XX-type.png`).
109
- - Stop immediately on step failure; record `failedStepIndex`, the error, and mark Flow result FAIL.
110
- - Treat any Flow FAIL as REVENUE-BLOCKING: the final decision must be DO_NOT_LAUNCH.
111
- - Log step-by-step outcomes in `report.json` and `report.html`.
112
-
113
- ## 6) Honesty & Transparency
114
-
115
- Guardian must:
116
- - Never overstate certainty; only issue READY when justified by the confidence model and evidence policy.
117
- - Explicitly list reasons for final decisions and confidence (human-readable) in outputs.
118
- - Disclose coverage limitations (e.g., LOW coverage warning) visibly in CLI and HTML.
119
- - Preserve conservative defaults that favor truthful reporting.
120
-
121
- ## 7) Prohibited Behaviors
122
-
123
- Guardian must not:
124
- - Ignore missing REQUIRED evidence.
125
- - Fabricate or simulate evidence artifacts.
126
- - Issue READY when overall confidence is LOW.
127
- - Suppress critical errors (navigation failures, page errors, server 5xx) from decisions.
128
- - Alter artifacts or reports after generation except for permitted `--clean` on PASS.
129
-
130
- ## 8) Verifiability
131
-
132
- Guardian must be verifiable via:
133
- - Reproducible commands and exit codes (PowerShell examples in README).
134
- - Stable artifact paths under `artifacts/run-YYYYMMDD-HHMMSS/` including `report.json`, `report.html`, `logs.txt`, screenshots, traces, and HAR/network when present.
135
- - Machine-readable fields capturing coverage, behavior, evidence, flow results, blockers, and final decisions.
136
-
137
- CLI must surface final decision, confidence summary (coverage + behavior + reasoning), evidence policy, and warnings.
138
-
139
- ## 9) Stability Clause
140
-
141
- Guardian aims for stable, repeatable outcomes under identical inputs:
142
- - Flow runs must be deterministic with consistent PASS/FAIL given the same site state.
143
- - Crawl outcomes may vary in timing, but decisions should remain consistent when behavior and evidence are unchanged.
144
- - Evidence presence (trace/HAR) may vary by environment; the Evidence Policy governs whether such variance affects decisions.
145
- - Any non-deterministic behavior should be logged; the final decision must remain conservative.
146
-
147
- ## Final Seal / Closing Statement
148
-
149
- This contract is canonical for ODAVL Guardian MVP. All implementations and outputs must adhere strictly to these rules. Operators rely on Guardian’s conservative, evidence-driven judgments; deviations from this contract are prohibited. Changes to this contract require explicit versioning and are not implied by code modifications.