@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.
- package/CHANGELOG.md +86 -2
- package/README.md +155 -97
- package/bin/guardian.js +1345 -60
- package/config/README.md +59 -0
- package/config/profiles/landing-demo.yaml +16 -0
- package/package.json +21 -11
- package/policies/landing-demo.json +22 -0
- package/src/enterprise/audit-logger.js +166 -0
- package/src/enterprise/pdf-exporter.js +267 -0
- package/src/enterprise/rbac-gate.js +142 -0
- package/src/enterprise/rbac.js +239 -0
- package/src/enterprise/site-manager.js +180 -0
- package/src/founder/feedback-system.js +156 -0
- package/src/founder/founder-tracker.js +213 -0
- package/src/founder/usage-signals.js +141 -0
- package/src/guardian/alert-ledger.js +121 -0
- package/src/guardian/attempt-engine.js +568 -7
- package/src/guardian/attempt-registry.js +42 -1
- package/src/guardian/attempt-relevance.js +106 -0
- package/src/guardian/attempt.js +24 -0
- package/src/guardian/baseline.js +12 -4
- package/src/guardian/breakage-intelligence.js +1 -0
- package/src/guardian/ci-cli.js +121 -0
- package/src/guardian/ci-output.js +4 -3
- package/src/guardian/cli-summary.js +79 -92
- package/src/guardian/config-loader.js +162 -0
- package/src/guardian/drift-detector.js +100 -0
- package/src/guardian/enhanced-html-reporter.js +221 -4
- package/src/guardian/env-guard.js +127 -0
- package/src/guardian/failure-intelligence.js +173 -0
- package/src/guardian/first-run-profile.js +89 -0
- package/src/guardian/first-run.js +6 -1
- package/src/guardian/flag-validator.js +17 -3
- package/src/guardian/html-reporter.js +2 -0
- package/src/guardian/human-reporter.js +431 -0
- package/src/guardian/index.js +22 -19
- package/src/guardian/init-command.js +9 -5
- package/src/guardian/intent-detector.js +146 -0
- package/src/guardian/journey-definitions.js +132 -0
- package/src/guardian/journey-scan-cli.js +145 -0
- package/src/guardian/journey-scanner.js +583 -0
- package/src/guardian/junit-reporter.js +18 -1
- package/src/guardian/live-cli.js +95 -0
- package/src/guardian/live-scheduler-runner.js +137 -0
- package/src/guardian/live-scheduler.js +146 -0
- package/src/guardian/market-reporter.js +341 -81
- package/src/guardian/pattern-analyzer.js +348 -0
- package/src/guardian/policy.js +80 -3
- package/src/guardian/preset-loader.js +9 -6
- package/src/guardian/reality.js +1278 -117
- package/src/guardian/reporter.js +27 -41
- package/src/guardian/run-artifacts.js +212 -0
- package/src/guardian/run-cleanup.js +207 -0
- package/src/guardian/run-latest.js +90 -0
- package/src/guardian/run-list.js +211 -0
- package/src/guardian/scan-presets.js +100 -11
- package/src/guardian/selector-fallbacks.js +394 -0
- package/src/guardian/semantic-contact-finder.js +2 -1
- package/src/guardian/site-introspection.js +257 -0
- package/src/guardian/smoke.js +2 -2
- package/src/guardian/snapshot-schema.js +25 -1
- package/src/guardian/snapshot.js +46 -2
- package/src/guardian/stability-scorer.js +169 -0
- package/src/guardian/template-command.js +184 -0
- package/src/guardian/text-formatters.js +426 -0
- package/src/guardian/verdict.js +320 -0
- package/src/guardian/verdicts.js +74 -0
- package/src/guardian/watch-runner.js +3 -7
- package/src/payments/stripe-checkout.js +169 -0
- package/src/plans/plan-definitions.js +148 -0
- package/src/plans/plan-manager.js +211 -0
- package/src/plans/usage-tracker.js +210 -0
- package/src/recipes/recipe-engine.js +188 -0
- package/src/recipes/recipe-failure-analysis.js +159 -0
- package/src/recipes/recipe-registry.js +134 -0
- package/src/recipes/recipe-runtime.js +507 -0
- package/src/recipes/recipe-store.js +410 -0
- package/guardian-contract-v1.md +0 -149
- /package/{guardian.config.json → config/guardian.config.json} +0 -0
- /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
- /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
- /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
- /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
- /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
|
+
};
|
package/guardian-contract-v1.md
DELETED
|
@@ -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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|