@aplaytest/core 0.1.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/dist/.tsbuildinfo +1 -0
- package/dist/config/schema.d.ts +247 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +279 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/evidence/id.d.ts +17 -0
- package/dist/evidence/id.d.ts.map +1 -0
- package/dist/evidence/id.js +20 -0
- package/dist/evidence/id.js.map +1 -0
- package/dist/evidence/redact.d.ts +22 -0
- package/dist/evidence/redact.d.ts.map +1 -0
- package/dist/evidence/redact.js +97 -0
- package/dist/evidence/redact.js.map +1 -0
- package/dist/evidence/store.d.ts +51 -0
- package/dist/evidence/store.d.ts.map +1 -0
- package/dist/evidence/store.js +148 -0
- package/dist/evidence/store.js.map +1 -0
- package/dist/evidence/types.d.ts +183 -0
- package/dist/evidence/types.d.ts.map +1 -0
- package/dist/evidence/types.js +21 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/history/ingest-playwright.d.ts +26 -0
- package/dist/history/ingest-playwright.d.ts.map +1 -0
- package/dist/history/ingest-playwright.js +73 -0
- package/dist/history/ingest-playwright.js.map +1 -0
- package/dist/history/ingest.d.ts +29 -0
- package/dist/history/ingest.d.ts.map +1 -0
- package/dist/history/ingest.js +86 -0
- package/dist/history/ingest.js.map +1 -0
- package/dist/history/memory-index.d.ts +88 -0
- package/dist/history/memory-index.d.ts.map +1 -0
- package/dist/history/memory-index.js +236 -0
- package/dist/history/memory-index.js.map +1 -0
- package/dist/history/playwright-report.d.ts +102 -0
- package/dist/history/playwright-report.d.ts.map +1 -0
- package/dist/history/playwright-report.js +274 -0
- package/dist/history/playwright-report.js.map +1 -0
- package/dist/history/store.d.ts +73 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/store.js +243 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/types.d.ts +69 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/history/types.js +18 -0
- package/dist/history/types.js.map +1 -0
- package/dist/history/url.d.ts +79 -0
- package/dist/history/url.d.ts.map +1 -0
- package/dist/history/url.js +121 -0
- package/dist/history/url.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/locator/stability.d.ts +63 -0
- package/dist/locator/stability.d.ts.map +1 -0
- package/dist/locator/stability.js +127 -0
- package/dist/locator/stability.js.map +1 -0
- package/dist/taxonomy/classify.d.ts +63 -0
- package/dist/taxonomy/classify.d.ts.map +1 -0
- package/dist/taxonomy/classify.js +344 -0
- package/dist/taxonomy/classify.js.map +1 -0
- package/dist/taxonomy/kinds.d.ts +49 -0
- package/dist/taxonomy/kinds.d.ts.map +1 -0
- package/dist/taxonomy/kinds.js +139 -0
- package/dist/taxonomy/kinds.js.map +1 -0
- package/dist/taxonomy/parse-error.d.ts +85 -0
- package/dist/taxonomy/parse-error.d.ts.map +1 -0
- package/dist/taxonomy/parse-error.js +179 -0
- package/dist/taxonomy/parse-error.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/dist-cjs/.tsbuildinfo +1 -0
- package/dist-cjs/config/schema.js +283 -0
- package/dist-cjs/config/schema.js.map +1 -0
- package/dist-cjs/evidence/id.js +24 -0
- package/dist-cjs/evidence/id.js.map +1 -0
- package/dist-cjs/evidence/redact.js +103 -0
- package/dist-cjs/evidence/redact.js.map +1 -0
- package/dist-cjs/evidence/store.js +156 -0
- package/dist-cjs/evidence/store.js.map +1 -0
- package/dist-cjs/evidence/types.js +25 -0
- package/dist-cjs/evidence/types.js.map +1 -0
- package/dist-cjs/history/ingest-playwright.js +76 -0
- package/dist-cjs/history/ingest-playwright.js.map +1 -0
- package/dist-cjs/history/ingest.js +89 -0
- package/dist-cjs/history/ingest.js.map +1 -0
- package/dist-cjs/history/memory-index.js +242 -0
- package/dist-cjs/history/memory-index.js.map +1 -0
- package/dist-cjs/history/playwright-report.js +277 -0
- package/dist-cjs/history/playwright-report.js.map +1 -0
- package/dist-cjs/history/store.js +247 -0
- package/dist-cjs/history/store.js.map +1 -0
- package/dist-cjs/history/types.js +23 -0
- package/dist-cjs/history/types.js.map +1 -0
- package/dist-cjs/history/url.js +127 -0
- package/dist-cjs/history/url.js.map +1 -0
- package/dist-cjs/index.js +88 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/locator/stability.js +134 -0
- package/dist-cjs/locator/stability.js.map +1 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/taxonomy/classify.js +348 -0
- package/dist-cjs/taxonomy/classify.js.map +1 -0
- package/dist-cjs/taxonomy/kinds.js +145 -0
- package/dist-cjs/taxonomy/kinds.js.map +1 -0
- package/dist-cjs/taxonomy/parse-error.js +186 -0
- package/dist-cjs/taxonomy/parse-error.js.map +1 -0
- package/dist-cjs/version.js +13 -0
- package/dist-cjs/version.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration: one file, fully typed, Zod-validated at load, every field
|
|
3
|
+
* optional with a documented default.
|
|
4
|
+
*
|
|
5
|
+
* The defaults encode the project's core stance — `mode: 'strict'`,
|
|
6
|
+
* `heal.apply: 'propose'`, quarantine expiry ON — so that a consumer who
|
|
7
|
+
* writes `export default defineAtestConfig({})` gets the safe system rather
|
|
8
|
+
* than an unconfigured one.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { LOCATOR_STRATEGIES, MAX_STABILITY_RANK } from '../locator/stability.js';
|
|
12
|
+
export const ExecutionModeSchema = z.enum(['strict', 'assisted', 'agentic']);
|
|
13
|
+
export const AggressivenessSchema = z.enum(['off', 'conservative', 'balanced', 'aggressive']);
|
|
14
|
+
const PlaywrightSchema = z.object({
|
|
15
|
+
/** Named Playwright configs this project can drive. */
|
|
16
|
+
configs: z.record(z.string(), z.string()).prefault({}),
|
|
17
|
+
defaultConfig: z.string().default('playwright.config.ts'),
|
|
18
|
+
});
|
|
19
|
+
const HistorySchema = z.object({
|
|
20
|
+
/**
|
|
21
|
+
* Chosen from `url`, not configured separately. Two fields naming one thing
|
|
22
|
+
* can disagree, and `driver: 'sqlite'` beside an `azblob://` url has no
|
|
23
|
+
* correct interpretation — so this is derived, and left here only because
|
|
24
|
+
* `aplaytest doctor` and the report header print it.
|
|
25
|
+
*/
|
|
26
|
+
driver: z.enum(['sqlite', 'azure-blob', 'memory']).default('sqlite'),
|
|
27
|
+
/**
|
|
28
|
+
* Where history lives. A path is a local SQLite file; `azblob://<account>/
|
|
29
|
+
* <container>[/<prefix>]` is Azure Blob Storage; `:memory:` is a throwaway.
|
|
30
|
+
*
|
|
31
|
+
* `:memory:` is a footgun in CI and is deliberately NOT the default here —
|
|
32
|
+
* with it, every run sees one attempt per test and flake scoring reports
|
|
33
|
+
* "insufficient data" forever, which reads as the engine working.
|
|
34
|
+
*/
|
|
35
|
+
url: z.string().default('.atest/history.sqlite'),
|
|
36
|
+
/** Attempts older than this are pruned by `aplaytest history prune`. */
|
|
37
|
+
retainDays: z.number().int().positive().default(90),
|
|
38
|
+
/**
|
|
39
|
+
* Days the blob driver downloads before scoring. Bounded on purpose: an
|
|
40
|
+
* unbounded read gets slower every week until someone turns the feature off.
|
|
41
|
+
* Keep it at or below `retainDays` — reading further back than prune keeps
|
|
42
|
+
* only buys empty listings.
|
|
43
|
+
*/
|
|
44
|
+
windowDays: z.number().int().positive().default(90),
|
|
45
|
+
});
|
|
46
|
+
const EvidenceSchema = z.object({
|
|
47
|
+
dir: z.string().default('.atest/evidence'),
|
|
48
|
+
retainRuns: z.number().int().positive().default(50),
|
|
49
|
+
/**
|
|
50
|
+
* Header/body/console keys scrubbed before anything is written to disk or
|
|
51
|
+
* sent to a model. A suite with MSAL auth WILL capture bearer tokens.
|
|
52
|
+
*/
|
|
53
|
+
redact: z
|
|
54
|
+
.array(z.string())
|
|
55
|
+
.default(['password', 'token', 'authorization', 'cookie', 'secret', 'api-key']),
|
|
56
|
+
/** Token ceiling for the ARIA snapshot before truncation (marked explicitly). */
|
|
57
|
+
ariaMaxTokens: z.number().int().positive().default(6_000),
|
|
58
|
+
});
|
|
59
|
+
const LlmSchema = z.object({
|
|
60
|
+
/**
|
|
61
|
+
* Model provider. Only `anthropic` is implemented; `none` disables the
|
|
62
|
+
* model tier entirely. OpenAI and Ollama are not advertised here because
|
|
63
|
+
* a config that parses and then silently uses a different provider is
|
|
64
|
+
* worse than a config that fails at load.
|
|
65
|
+
*/
|
|
66
|
+
provider: z.enum(['anthropic', 'none']).default('anthropic'),
|
|
67
|
+
models: z
|
|
68
|
+
.object({
|
|
69
|
+
classify: z.string().default('claude-haiku-4-5'),
|
|
70
|
+
heal: z.string().default('claude-sonnet-5'),
|
|
71
|
+
author: z.string().default('claude-opus-5'),
|
|
72
|
+
vision: z.string().default('claude-sonnet-5'),
|
|
73
|
+
})
|
|
74
|
+
.prefault({}),
|
|
75
|
+
/**
|
|
76
|
+
* Effort replaces temperature: sampling parameters are rejected outright by
|
|
77
|
+
* current Opus/Sonnet models. See docs/10-recommendations.md.
|
|
78
|
+
*/
|
|
79
|
+
effort: z
|
|
80
|
+
.object({
|
|
81
|
+
classify: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).default('low'),
|
|
82
|
+
heal: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).default('medium'),
|
|
83
|
+
author: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).default('xhigh'),
|
|
84
|
+
})
|
|
85
|
+
.prefault({}),
|
|
86
|
+
budget: z
|
|
87
|
+
.object({
|
|
88
|
+
perFailureUsd: z.number().positive().default(0.05),
|
|
89
|
+
perRunUsd: z.number().positive().default(2.0),
|
|
90
|
+
})
|
|
91
|
+
.prefault({}),
|
|
92
|
+
});
|
|
93
|
+
const HealSchema = z.object({
|
|
94
|
+
aggressiveness: AggressivenessSchema.default('balanced'),
|
|
95
|
+
strategies: z.array(z.enum(['selector', 'assertion', 'flow'])).default(['selector']),
|
|
96
|
+
/** Re-runs required before a proposal is considered validated. */
|
|
97
|
+
validationRuns: z.number().int().min(1).default(3),
|
|
98
|
+
/** Also re-run the rest of the spec file, to catch collateral damage. */
|
|
99
|
+
validateCollateral: z.boolean().default(true),
|
|
100
|
+
/** Reject candidates weaker than this rank. 4 = text; excludes css/xpath. */
|
|
101
|
+
minStabilityRank: z.number().int().min(0).max(MAX_STABILITY_RANK).default(4),
|
|
102
|
+
allowedStrategies: z.array(z.enum(LOCATOR_STRATEGIES)).default(['testid', 'role', 'label', 'text']),
|
|
103
|
+
apply: z.enum(['propose', 'pr', 'local']).default('propose'),
|
|
104
|
+
/** Glob patterns a heal patch is permitted to touch. */
|
|
105
|
+
targets: z
|
|
106
|
+
.array(z.string())
|
|
107
|
+
.default([
|
|
108
|
+
'src/**/*.constants.ts',
|
|
109
|
+
'src/**/*.page.ts',
|
|
110
|
+
'src/**/*.section.ts',
|
|
111
|
+
'tests/**/*.spec.ts',
|
|
112
|
+
'tests/**/*.test.ts',
|
|
113
|
+
]),
|
|
114
|
+
});
|
|
115
|
+
const FlakySchema = z.object({
|
|
116
|
+
window: z
|
|
117
|
+
.object({
|
|
118
|
+
runs: z.number().int().positive().default(50),
|
|
119
|
+
days: z.number().int().positive().default(14),
|
|
120
|
+
})
|
|
121
|
+
.prefault({}),
|
|
122
|
+
/** Recency weighting: a failure this old counts half as much as one today. */
|
|
123
|
+
halfLifeDays: z.number().positive().default(7),
|
|
124
|
+
threshold: z.number().min(0).max(1).default(0.15),
|
|
125
|
+
minRuns: z.number().int().positive().default(10),
|
|
126
|
+
quarantine: z
|
|
127
|
+
.object({
|
|
128
|
+
policy: z.enum(['off', 'propose', 'auto']).default('propose'),
|
|
129
|
+
expiryDays: z.number().int().positive().default(14),
|
|
130
|
+
maxTests: z.number().int().nonnegative().default(5),
|
|
131
|
+
maxRatio: z.number().min(0).max(1).default(0.02),
|
|
132
|
+
tag: z.string().default('@quarantine'),
|
|
133
|
+
})
|
|
134
|
+
.prefault({}),
|
|
135
|
+
});
|
|
136
|
+
const ImpactSchema = z.object({
|
|
137
|
+
enabled: z.boolean().default(true),
|
|
138
|
+
/** Changing any of these runs the whole suite, no questions asked. */
|
|
139
|
+
fullSuiteTriggers: z
|
|
140
|
+
.array(z.string())
|
|
141
|
+
.default([
|
|
142
|
+
'package.json',
|
|
143
|
+
'package-lock.json',
|
|
144
|
+
'pnpm-lock.yaml',
|
|
145
|
+
'playwright*.config.ts',
|
|
146
|
+
'src/shared/**',
|
|
147
|
+
'atest.config.ts',
|
|
148
|
+
'.github/workflows/**',
|
|
149
|
+
'Dockerfile',
|
|
150
|
+
]),
|
|
151
|
+
/** Tags that always run regardless of impact selection. */
|
|
152
|
+
alwaysRunTags: z.array(z.string()).default(['@smoke']),
|
|
153
|
+
/** Above this share of the suite, just run everything. */
|
|
154
|
+
fullSuiteThreshold: z.number().min(0).max(1).default(0.6),
|
|
155
|
+
});
|
|
156
|
+
const ConventionsSchema = z.object({
|
|
157
|
+
titlePattern: z.string().default('^Given .+, when .+, then .+$'),
|
|
158
|
+
requiredTags: z.array(z.string()).default([]),
|
|
159
|
+
seededDataDir: z.string().default('tests/testdata/seeded'),
|
|
160
|
+
/**
|
|
161
|
+
* Paths no agent may write to, ever. The seeded data is the oracle: an
|
|
162
|
+
* agent that can edit expected values can make any test pass.
|
|
163
|
+
*/
|
|
164
|
+
forbidWriteTo: z
|
|
165
|
+
.array(z.string())
|
|
166
|
+
.default([
|
|
167
|
+
'tests/testdata/seeded/**',
|
|
168
|
+
'**/__screenshots__/**',
|
|
169
|
+
'**/__aria__/**',
|
|
170
|
+
'.env*',
|
|
171
|
+
'.github/workflows/**',
|
|
172
|
+
'atest.config.ts',
|
|
173
|
+
]),
|
|
174
|
+
pageObjectDir: z.string().default('src/ui/pages'),
|
|
175
|
+
/** Run against generated code before it is ever proposed. */
|
|
176
|
+
verifyCommands: z.array(z.string()).default(['npm run typecheck', 'npm run lint']),
|
|
177
|
+
});
|
|
178
|
+
const IntegrationsSchema = z.object({
|
|
179
|
+
github: z
|
|
180
|
+
.object({
|
|
181
|
+
autoComment: z.boolean().default(true),
|
|
182
|
+
autoIssue: z.boolean().default(false),
|
|
183
|
+
healBranchPrefix: z.string().default('atest/heal/'),
|
|
184
|
+
})
|
|
185
|
+
.prefault({}),
|
|
186
|
+
otel: z
|
|
187
|
+
.object({
|
|
188
|
+
endpoint: z.string().nullable().default(null),
|
|
189
|
+
queryUrl: z.string().nullable().default(null),
|
|
190
|
+
})
|
|
191
|
+
.prefault({}),
|
|
192
|
+
});
|
|
193
|
+
export const AtestConfigSchema = z.object({
|
|
194
|
+
mode: ExecutionModeSchema.default('strict'),
|
|
195
|
+
playwright: PlaywrightSchema.prefault({}),
|
|
196
|
+
history: HistorySchema.prefault({}),
|
|
197
|
+
evidence: EvidenceSchema.prefault({}),
|
|
198
|
+
llm: LlmSchema.prefault({}),
|
|
199
|
+
heal: HealSchema.prefault({}),
|
|
200
|
+
flaky: FlakySchema.prefault({}),
|
|
201
|
+
impact: ImpactSchema.prefault({}),
|
|
202
|
+
conventions: ConventionsSchema.prefault({}),
|
|
203
|
+
integrations: IntegrationsSchema.prefault({}),
|
|
204
|
+
});
|
|
205
|
+
/**
|
|
206
|
+
* Validate user config and apply the safe defaults.
|
|
207
|
+
*
|
|
208
|
+
* An empty object yields `mode: 'strict'`, propose-only healing, and
|
|
209
|
+
* quarantine expiry — a consumer who configures nothing must not get an
|
|
210
|
+
* unguarded system. A misconfigured run fails here, at startup, not
|
|
211
|
+
* halfway through a suite.
|
|
212
|
+
*
|
|
213
|
+
* @param input - Partial config plus optional identity hooks for run/trace ids.
|
|
214
|
+
* @returns Every field filled, with `identity` attached (empty object if omitted).
|
|
215
|
+
* @throws {Error} When a field fails Zod validation. The message is prefixed
|
|
216
|
+
* with `Invalid atest.config.ts:` and lists every issue.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* // atest.config.ts
|
|
221
|
+
* import { defineAtestConfig } from '@aplaytest/core';
|
|
222
|
+
*
|
|
223
|
+
* export default defineAtestConfig({
|
|
224
|
+
* mode: 'strict',
|
|
225
|
+
* heal: { apply: 'propose' },
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
export function defineAtestConfig(input = {}) {
|
|
230
|
+
const { identity, ...rest } = input;
|
|
231
|
+
// Presets apply only when the user named an aggressiveness. Empty config
|
|
232
|
+
// must stay propose-only — the balanced preset's `apply: 'pr'` would
|
|
233
|
+
// otherwise override the safe default the rest of this function documents.
|
|
234
|
+
const named = rest.heal?.aggressiveness;
|
|
235
|
+
const preset = named === undefined ? undefined : AGGRESSIVENESS_PRESETS[named];
|
|
236
|
+
const merged = preset === undefined
|
|
237
|
+
? rest
|
|
238
|
+
: {
|
|
239
|
+
...rest,
|
|
240
|
+
heal: { ...preset, ...rest.heal },
|
|
241
|
+
};
|
|
242
|
+
const parsed = AtestConfigSchema.safeParse(merged);
|
|
243
|
+
if (!parsed.success) {
|
|
244
|
+
const issues = parsed.error.issues
|
|
245
|
+
.map(i => ` • ${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
246
|
+
.join('\n');
|
|
247
|
+
throw new Error(`Invalid atest.config.ts:\n${issues}`);
|
|
248
|
+
}
|
|
249
|
+
return { ...parsed.data, identity: identity ?? {} };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Aggressiveness presets. Explicit fields in `heal` always win — the preset
|
|
253
|
+
* only fills what the user did not state.
|
|
254
|
+
*/
|
|
255
|
+
export const AGGRESSIVENESS_PRESETS = {
|
|
256
|
+
off: { strategies: [], apply: 'propose' },
|
|
257
|
+
conservative: {
|
|
258
|
+
strategies: ['selector'],
|
|
259
|
+
allowedStrategies: ['testid', 'role'],
|
|
260
|
+
minStabilityRank: 1,
|
|
261
|
+
validationRuns: 5,
|
|
262
|
+
apply: 'propose',
|
|
263
|
+
},
|
|
264
|
+
balanced: {
|
|
265
|
+
strategies: ['selector'],
|
|
266
|
+
allowedStrategies: ['testid', 'role', 'label', 'text'],
|
|
267
|
+
minStabilityRank: 4,
|
|
268
|
+
validationRuns: 3,
|
|
269
|
+
apply: 'pr',
|
|
270
|
+
},
|
|
271
|
+
aggressive: {
|
|
272
|
+
strategies: ['selector', 'flow'],
|
|
273
|
+
allowedStrategies: ['testid', 'role', 'label', 'text'],
|
|
274
|
+
minStabilityRank: 4,
|
|
275
|
+
validationRuns: 3,
|
|
276
|
+
apply: 'local',
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence ids are DERIVED, never random.
|
|
3
|
+
*
|
|
4
|
+
* The same failure, re-analysed on another machine or in another shard, must
|
|
5
|
+
* produce the same id — otherwise the heal ledger, the history store, and CI
|
|
6
|
+
* artifacts cannot be joined after the fact.
|
|
7
|
+
*/
|
|
8
|
+
import type { EvidenceId } from './types.js';
|
|
9
|
+
export interface EvidenceIdParts {
|
|
10
|
+
readonly runId: string;
|
|
11
|
+
readonly testId: string;
|
|
12
|
+
readonly project: string;
|
|
13
|
+
readonly retry: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function evidenceId(parts: EvidenceIdParts): EvidenceId;
|
|
16
|
+
export declare function isEvidenceId(value: string): value is EvidenceId;
|
|
17
|
+
//# sourceMappingURL=id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/evidence/id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK7C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,CAK7D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence ids are DERIVED, never random.
|
|
3
|
+
*
|
|
4
|
+
* The same failure, re-analysed on another machine or in another shard, must
|
|
5
|
+
* produce the same id — otherwise the heal ledger, the history store, and CI
|
|
6
|
+
* artifacts cannot be joined after the fact.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
const ID_PREFIX = 'ev_';
|
|
10
|
+
const ID_LENGTH = 12; // 48 bits — ample for per-run uniqueness, short enough to type
|
|
11
|
+
export function evidenceId(parts) {
|
|
12
|
+
const digest = createHash('sha256')
|
|
13
|
+
.update([parts.runId, parts.testId, parts.project, String(parts.retry)].join('\x00'))
|
|
14
|
+
.digest('hex');
|
|
15
|
+
return `${ID_PREFIX}${digest.slice(0, ID_LENGTH)}`;
|
|
16
|
+
}
|
|
17
|
+
export function isEvidenceId(value) {
|
|
18
|
+
return new RegExp(`^${ID_PREFIX}[0-9a-f]{${ID_LENGTH}}$`).test(value);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/evidence/id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,MAAM,SAAS,GAAG,KAAK,CAAC;AACxB,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC,+DAA+D;AASrF,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;SAChC,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SACpF,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAgB,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,IAAI,MAAM,CAAC,IAAI,SAAS,YAAY,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction.
|
|
3
|
+
*
|
|
4
|
+
* Evidence bundles are written to disk, uploaded as CI artifacts, and sent to
|
|
5
|
+
* a model. A suite that authenticates — MSAL, OAuth, an API key header — WILL
|
|
6
|
+
* capture bearer tokens in its request ledger. Scrubbing happens once, at the
|
|
7
|
+
* boundary where a bundle is created, so that no downstream consumer has to
|
|
8
|
+
* remember to do it.
|
|
9
|
+
*
|
|
10
|
+
* The rule is fail-safe: an unrecognised structure is traversed, not skipped.
|
|
11
|
+
*/
|
|
12
|
+
export declare const REDACTED = "[redacted]";
|
|
13
|
+
export declare function redactString(input: string, keys: readonly string[]): string;
|
|
14
|
+
/**
|
|
15
|
+
* Deep-redact any JSON-shaped value. Keys matching `keys` have their values
|
|
16
|
+
* replaced wholesale; every string is additionally scanned for embedded
|
|
17
|
+
* credentials, because secrets do not always arrive under an obvious key.
|
|
18
|
+
*/
|
|
19
|
+
export declare function redact<T>(value: T, keys: readonly string[]): T;
|
|
20
|
+
/** Redact a URL's query string while keeping the path readable. */
|
|
21
|
+
export declare function redactUrl(rawUrl: string, keys: readonly string[]): string;
|
|
22
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/evidence/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,QAAQ,eAAe,CAAC;AAiCrC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAmB3E;AAOD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,CAE9D;AAmBD,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWzE"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction.
|
|
3
|
+
*
|
|
4
|
+
* Evidence bundles are written to disk, uploaded as CI artifacts, and sent to
|
|
5
|
+
* a model. A suite that authenticates — MSAL, OAuth, an API key header — WILL
|
|
6
|
+
* capture bearer tokens in its request ledger. Scrubbing happens once, at the
|
|
7
|
+
* boundary where a bundle is created, so that no downstream consumer has to
|
|
8
|
+
* remember to do it.
|
|
9
|
+
*
|
|
10
|
+
* The rule is fail-safe: an unrecognised structure is traversed, not skipped.
|
|
11
|
+
*/
|
|
12
|
+
export const REDACTED = '[redacted]';
|
|
13
|
+
/**
|
|
14
|
+
* Header and field names arrive in every casing and separator style —
|
|
15
|
+
* `api-key`, `API_KEY`, `apiKey`, `x-api-key-header`. Comparing them
|
|
16
|
+
* literally means a real credential slips through on a spelling difference,
|
|
17
|
+
* so both sides are normalised to letters and digits before matching.
|
|
18
|
+
*/
|
|
19
|
+
function normaliseKey(key) {
|
|
20
|
+
return key.toLowerCase().replace(/[-_\s.]/g, '');
|
|
21
|
+
}
|
|
22
|
+
/** Matches `authorization: Bearer xyz`, `"token": "xyz"`, `API_KEY=xyz`. */
|
|
23
|
+
function keyValuePattern(key) {
|
|
24
|
+
// Allow any separator style between the key's own word parts.
|
|
25
|
+
const flexible = key
|
|
26
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
27
|
+
.replace(/[-_\s]+/g, '[-_\\s]?');
|
|
28
|
+
// The value group optionally swallows an auth scheme, so
|
|
29
|
+
// `Authorization: Bearer <token>` redacts the token rather than the word
|
|
30
|
+
// "Bearer".
|
|
31
|
+
return new RegExp(`(["']?${flexible}["']?\\s*[:=]\\s*["']?)((?:Bearer|Basic|Token)\\s+)?([^"'\\s,;&}]+)`, 'gi');
|
|
32
|
+
}
|
|
33
|
+
/** Matches a bearer token anywhere, even without a recognisable key. */
|
|
34
|
+
const BEARER_PATTERN = /\b(Bearer\s+)[A-Za-z0-9._~+/-]{12,}=*/gi;
|
|
35
|
+
/** Matches a JWT anywhere — three base64url segments. */
|
|
36
|
+
const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g;
|
|
37
|
+
export function redactString(input, keys) {
|
|
38
|
+
// ORDER IS LOAD-BEARING: value patterns run BEFORE key patterns.
|
|
39
|
+
//
|
|
40
|
+
// Running keys first leaks. On `Authorization: Bearer <token>` the key
|
|
41
|
+
// pattern matches `Authorization: ` and captures only `Bearer` — its value
|
|
42
|
+
// group stops at whitespace — so it rewrites the string to
|
|
43
|
+
// `Authorization: [redacted] <token>`. The bearer pattern then finds no
|
|
44
|
+
// `Bearer ` to anchor on, and the credential survives redaction entirely.
|
|
45
|
+
let out = input;
|
|
46
|
+
out = out.replace(BEARER_PATTERN, (_m, prefix) => `${prefix}${REDACTED}`);
|
|
47
|
+
out = out.replace(JWT_PATTERN, REDACTED);
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
out = out.replace(keyValuePattern(key), (_m, prefix, scheme) => `${prefix}${scheme ?? ''}${REDACTED}`);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function keyMatches(key, keys) {
|
|
54
|
+
const normalised = normaliseKey(key);
|
|
55
|
+
return keys.some(k => normalised.includes(normaliseKey(k)));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Deep-redact any JSON-shaped value. Keys matching `keys` have their values
|
|
59
|
+
* replaced wholesale; every string is additionally scanned for embedded
|
|
60
|
+
* credentials, because secrets do not always arrive under an obvious key.
|
|
61
|
+
*/
|
|
62
|
+
export function redact(value, keys) {
|
|
63
|
+
return redactValue(value, keys, new WeakSet());
|
|
64
|
+
}
|
|
65
|
+
function redactValue(value, keys, seen) {
|
|
66
|
+
if (typeof value === 'string')
|
|
67
|
+
return redactString(value, keys);
|
|
68
|
+
if (value === null || typeof value !== 'object')
|
|
69
|
+
return value;
|
|
70
|
+
// Cycles are possible in captured objects; degrade rather than blow the stack.
|
|
71
|
+
if (seen.has(value))
|
|
72
|
+
return '[circular]';
|
|
73
|
+
seen.add(value);
|
|
74
|
+
if (Array.isArray(value))
|
|
75
|
+
return value.map(v => redactValue(v, keys, seen));
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const [k, v] of Object.entries(value)) {
|
|
78
|
+
out[k] = keyMatches(k, keys) ? REDACTED : redactValue(v, keys, seen);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** Redact a URL's query string while keeping the path readable. */
|
|
83
|
+
export function redactUrl(rawUrl, keys) {
|
|
84
|
+
try {
|
|
85
|
+
const url = new URL(rawUrl);
|
|
86
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
87
|
+
if (keyMatches(key, keys))
|
|
88
|
+
url.searchParams.set(key, REDACTED);
|
|
89
|
+
}
|
|
90
|
+
return url.toString();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Not a parseable URL — fall back to string scrubbing rather than dropping it.
|
|
94
|
+
return redactString(rawUrl, keys);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/evidence/redact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evidence store: plain JSON on disk, one directory per run.
|
|
3
|
+
*
|
|
4
|
+
* <dir>/<runId>/<evidenceId>.json
|
|
5
|
+
*
|
|
6
|
+
* Deliberately not a binary format and not a database. A human with `jq` must
|
|
7
|
+
* be able to read a bundle, and a CI job must be able to upload the directory
|
|
8
|
+
* as an artifact with no export step. Every bundle carries `schemaVersion` so
|
|
9
|
+
* a reader can refuse politely rather than mis-parse.
|
|
10
|
+
*/
|
|
11
|
+
import { type EvidenceBundle, type EvidenceId } from './types.js';
|
|
12
|
+
export interface EvidenceStoreOptions {
|
|
13
|
+
readonly dir: string;
|
|
14
|
+
readonly redactKeys: readonly string[];
|
|
15
|
+
readonly retainRuns: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SkippedBundle {
|
|
18
|
+
readonly file: string;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
}
|
|
21
|
+
export interface LoadRunBundlesResult {
|
|
22
|
+
readonly runId: string | null;
|
|
23
|
+
readonly bundles: readonly EvidenceBundle[];
|
|
24
|
+
readonly skipped: readonly SkippedBundle[];
|
|
25
|
+
}
|
|
26
|
+
export declare function isEvidenceBundle(value: unknown): value is EvidenceBundle;
|
|
27
|
+
export declare function parseEvidenceBundle(value: unknown): EvidenceBundle | null;
|
|
28
|
+
export declare function loadRunBundles(evidenceDir: string, runId?: string | undefined): Promise<LoadRunBundlesResult>;
|
|
29
|
+
export declare class SchemaVersionError extends Error {
|
|
30
|
+
readonly found: number;
|
|
31
|
+
readonly expected: number;
|
|
32
|
+
readonly path: string;
|
|
33
|
+
constructor(found: number, expected: number, path: string);
|
|
34
|
+
}
|
|
35
|
+
export declare class EvidenceStore {
|
|
36
|
+
private readonly options;
|
|
37
|
+
constructor(options: EvidenceStoreOptions);
|
|
38
|
+
private bundlePath;
|
|
39
|
+
/**
|
|
40
|
+
* Redaction happens HERE, on the write path, so there is exactly one place
|
|
41
|
+
* that can forget to do it.
|
|
42
|
+
*/
|
|
43
|
+
write(bundle: EvidenceBundle): Promise<string>;
|
|
44
|
+
read(runId: string, id: EvidenceId): Promise<EvidenceBundle>;
|
|
45
|
+
/** Run ids, newest first by directory mtime. */
|
|
46
|
+
listRuns(): Promise<string[]>;
|
|
47
|
+
listBundles(runId: string): Promise<EvidenceId[]>;
|
|
48
|
+
/** Drop the oldest runs beyond the retention limit. Returns what it removed. */
|
|
49
|
+
prune(): Promise<string[]>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/evidence/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAA2B,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAI3F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAMD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAOxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAEzE;AAYD,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAyC/B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAFZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM;CAQxB;AAED,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAE1D,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACG,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ9C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAWlE,gDAAgD;IAC1C,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAc7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAOvD,gFAAgF;IAC1E,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAMjC"}
|