@tangle-network/browser-agent-driver 0.30.0 → 0.32.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 (252) hide show
  1. package/dist/brain/index.d.ts +12 -0
  2. package/dist/brain/index.d.ts.map +1 -1
  3. package/dist/brain/index.js +36 -8
  4. package/dist/brain/index.js.map +1 -1
  5. package/dist/cli-ack-patch.d.ts +39 -0
  6. package/dist/cli-ack-patch.d.ts.map +1 -0
  7. package/dist/cli-ack-patch.js +47 -0
  8. package/dist/cli-ack-patch.js.map +1 -0
  9. package/dist/cli-design-audit.d.ts +12 -0
  10. package/dist/cli-design-audit.d.ts.map +1 -1
  11. package/dist/cli-design-audit.js +78 -0
  12. package/dist/cli-design-audit.js.map +1 -1
  13. package/dist/cli-jobs.d.ts +13 -0
  14. package/dist/cli-jobs.d.ts.map +1 -0
  15. package/dist/cli-jobs.js +253 -0
  16. package/dist/cli-jobs.js.map +1 -0
  17. package/dist/cli-patterns.d.ts +26 -0
  18. package/dist/cli-patterns.d.ts.map +1 -0
  19. package/dist/cli-patterns.js +49 -0
  20. package/dist/cli-patterns.js.map +1 -0
  21. package/dist/cli-reports.d.ts +12 -0
  22. package/dist/cli-reports.d.ts.map +1 -0
  23. package/dist/cli-reports.js +97 -0
  24. package/dist/cli-reports.js.map +1 -0
  25. package/dist/cli.js +29 -1
  26. package/dist/cli.js.map +1 -1
  27. package/dist/design/audit/attribution/aggregate.d.ts +16 -0
  28. package/dist/design/audit/attribution/aggregate.d.ts.map +1 -0
  29. package/dist/design/audit/attribution/aggregate.js +81 -0
  30. package/dist/design/audit/attribution/aggregate.js.map +1 -0
  31. package/dist/design/audit/attribution/store.d.ts +36 -0
  32. package/dist/design/audit/attribution/store.d.ts.map +1 -0
  33. package/dist/design/audit/attribution/store.js +108 -0
  34. package/dist/design/audit/attribution/store.js.map +1 -0
  35. package/dist/design/audit/attribution/types.d.ts +13 -0
  36. package/dist/design/audit/attribution/types.d.ts.map +1 -0
  37. package/dist/design/audit/attribution/types.js +10 -0
  38. package/dist/design/audit/attribution/types.js.map +1 -0
  39. package/dist/design/audit/build-result.d.ts +68 -0
  40. package/dist/design/audit/build-result.d.ts.map +1 -0
  41. package/dist/design/audit/build-result.js +243 -0
  42. package/dist/design/audit/build-result.js.map +1 -0
  43. package/dist/design/audit/classify-ensemble.d.ts +46 -0
  44. package/dist/design/audit/classify-ensemble.d.ts.map +1 -0
  45. package/dist/design/audit/classify-ensemble.js +207 -0
  46. package/dist/design/audit/classify-ensemble.js.map +1 -0
  47. package/dist/design/audit/ethics/check.d.ts +59 -0
  48. package/dist/design/audit/ethics/check.d.ts.map +1 -0
  49. package/dist/design/audit/ethics/check.js +137 -0
  50. package/dist/design/audit/ethics/check.js.map +1 -0
  51. package/dist/design/audit/ethics/loader.d.ts +21 -0
  52. package/dist/design/audit/ethics/loader.d.ts.map +1 -0
  53. package/dist/design/audit/ethics/loader.js +207 -0
  54. package/dist/design/audit/ethics/loader.js.map +1 -0
  55. package/dist/design/audit/ethics/rules/finance.yaml +37 -0
  56. package/dist/design/audit/ethics/rules/kids.yaml +37 -0
  57. package/dist/design/audit/ethics/rules/legal.yaml +35 -0
  58. package/dist/design/audit/ethics/rules/medical.yaml +36 -0
  59. package/dist/design/audit/evaluate.d.ts.map +1 -1
  60. package/dist/design/audit/evaluate.js +4 -1
  61. package/dist/design/audit/evaluate.js.map +1 -1
  62. package/dist/design/audit/first-principles-mode.d.ts +42 -0
  63. package/dist/design/audit/first-principles-mode.d.ts.map +1 -0
  64. package/dist/design/audit/first-principles-mode.js +77 -0
  65. package/dist/design/audit/first-principles-mode.js.map +1 -0
  66. package/dist/design/audit/modality/android.d.ts +14 -0
  67. package/dist/design/audit/modality/android.d.ts.map +1 -0
  68. package/dist/design/audit/modality/android.js +18 -0
  69. package/dist/design/audit/modality/android.js.map +1 -0
  70. package/dist/design/audit/modality/html.d.ts +22 -0
  71. package/dist/design/audit/modality/html.d.ts.map +1 -0
  72. package/dist/design/audit/modality/html.js +51 -0
  73. package/dist/design/audit/modality/html.js.map +1 -0
  74. package/dist/design/audit/modality/index.d.ts +8 -0
  75. package/dist/design/audit/modality/index.d.ts.map +1 -0
  76. package/dist/design/audit/modality/index.js +15 -0
  77. package/dist/design/audit/modality/index.js.map +1 -0
  78. package/dist/design/audit/modality/ios.d.ts +16 -0
  79. package/dist/design/audit/modality/ios.d.ts.map +1 -0
  80. package/dist/design/audit/modality/ios.js +19 -0
  81. package/dist/design/audit/modality/ios.js.map +1 -0
  82. package/dist/design/audit/modality/types.d.ts +9 -0
  83. package/dist/design/audit/modality/types.d.ts.map +1 -0
  84. package/dist/design/audit/modality/types.js +9 -0
  85. package/dist/design/audit/modality/types.js.map +1 -0
  86. package/dist/design/audit/patches/generate.d.ts +45 -0
  87. package/dist/design/audit/patches/generate.d.ts.map +1 -0
  88. package/dist/design/audit/patches/generate.js +161 -0
  89. package/dist/design/audit/patches/generate.js.map +1 -0
  90. package/dist/design/audit/patches/index.d.ts +8 -0
  91. package/dist/design/audit/patches/index.d.ts.map +1 -0
  92. package/dist/design/audit/patches/index.js +5 -0
  93. package/dist/design/audit/patches/index.js.map +1 -0
  94. package/dist/design/audit/patches/parse.d.ts +29 -0
  95. package/dist/design/audit/patches/parse.d.ts.map +1 -0
  96. package/dist/design/audit/patches/parse.js +153 -0
  97. package/dist/design/audit/patches/parse.js.map +1 -0
  98. package/dist/design/audit/patches/render.d.ts +20 -0
  99. package/dist/design/audit/patches/render.d.ts.map +1 -0
  100. package/dist/design/audit/patches/render.js +55 -0
  101. package/dist/design/audit/patches/render.js.map +1 -0
  102. package/dist/design/audit/patches/severity-enforcement.d.ts +23 -0
  103. package/dist/design/audit/patches/severity-enforcement.d.ts.map +1 -0
  104. package/dist/design/audit/patches/severity-enforcement.js +42 -0
  105. package/dist/design/audit/patches/severity-enforcement.js.map +1 -0
  106. package/dist/design/audit/patches/validate.d.ts +40 -0
  107. package/dist/design/audit/patches/validate.d.ts.map +1 -0
  108. package/dist/design/audit/patches/validate.js +60 -0
  109. package/dist/design/audit/patches/validate.js.map +1 -0
  110. package/dist/design/audit/patterns/match.d.ts +24 -0
  111. package/dist/design/audit/patterns/match.d.ts.map +1 -0
  112. package/dist/design/audit/patterns/match.js +48 -0
  113. package/dist/design/audit/patterns/match.js.map +1 -0
  114. package/dist/design/audit/patterns/mine.d.ts +31 -0
  115. package/dist/design/audit/patterns/mine.d.ts.map +1 -0
  116. package/dist/design/audit/patterns/mine.js +35 -0
  117. package/dist/design/audit/patterns/mine.js.map +1 -0
  118. package/dist/design/audit/patterns/store.d.ts +14 -0
  119. package/dist/design/audit/patterns/store.d.ts.map +1 -0
  120. package/dist/design/audit/patterns/store.js +53 -0
  121. package/dist/design/audit/patterns/store.js.map +1 -0
  122. package/dist/design/audit/patterns/types.d.ts +53 -0
  123. package/dist/design/audit/patterns/types.d.ts.map +1 -0
  124. package/dist/design/audit/patterns/types.js +13 -0
  125. package/dist/design/audit/patterns/types.js.map +1 -0
  126. package/dist/design/audit/pipeline.d.ts +12 -0
  127. package/dist/design/audit/pipeline.d.ts.map +1 -1
  128. package/dist/design/audit/pipeline.js +81 -5
  129. package/dist/design/audit/pipeline.js.map +1 -1
  130. package/dist/design/audit/rubric/anchor-loader.d.ts +44 -0
  131. package/dist/design/audit/rubric/anchor-loader.d.ts.map +1 -0
  132. package/dist/design/audit/rubric/anchor-loader.js +210 -0
  133. package/dist/design/audit/rubric/anchor-loader.js.map +1 -0
  134. package/dist/design/audit/rubric/anchors/blog.yaml +33 -0
  135. package/dist/design/audit/rubric/anchors/dashboard.yaml +35 -0
  136. package/dist/design/audit/rubric/anchors/docs.yaml +35 -0
  137. package/dist/design/audit/rubric/anchors/ecommerce.yaml +33 -0
  138. package/dist/design/audit/rubric/anchors/marketing.yaml +34 -0
  139. package/dist/design/audit/rubric/anchors/saas-app.yaml +38 -0
  140. package/dist/design/audit/rubric/anchors/social.yaml +33 -0
  141. package/dist/design/audit/rubric/anchors/tool.yaml +33 -0
  142. package/dist/design/audit/rubric/anchors/utility.yaml +33 -0
  143. package/dist/design/audit/rubric/fragments/audience-clinician.md +42 -0
  144. package/dist/design/audit/rubric/fragments/audience-developer.md +40 -0
  145. package/dist/design/audit/rubric/fragments/audience-kids.md +35 -0
  146. package/dist/design/audit/rubric/fragments/audience-vulnerability-minor-facing.md +37 -0
  147. package/dist/design/audit/rubric/fragments/first-principles.md +65 -0
  148. package/dist/design/audit/rubric/fragments/modality-mobile.md +39 -0
  149. package/dist/design/audit/rubric/fragments/modality-tablet.md +35 -0
  150. package/dist/design/audit/rubric/fragments/regulatory-coppa.md +32 -0
  151. package/dist/design/audit/rubric/fragments/regulatory-gdpr.md +33 -0
  152. package/dist/design/audit/rubric/fragments/regulatory-hipaa.md +36 -0
  153. package/dist/design/audit/rubric/loader.d.ts +26 -6
  154. package/dist/design/audit/rubric/loader.d.ts.map +1 -1
  155. package/dist/design/audit/rubric/loader.js +45 -26
  156. package/dist/design/audit/rubric/loader.js.map +1 -1
  157. package/dist/design/audit/rubric/rollup-weights.d.ts +25 -0
  158. package/dist/design/audit/rubric/rollup-weights.d.ts.map +1 -0
  159. package/dist/design/audit/rubric/rollup-weights.js +51 -0
  160. package/dist/design/audit/rubric/rollup-weights.js.map +1 -0
  161. package/dist/design/audit/score-types.d.ts +431 -0
  162. package/dist/design/audit/score-types.d.ts.map +1 -0
  163. package/dist/design/audit/score-types.js +26 -0
  164. package/dist/design/audit/score-types.js.map +1 -0
  165. package/dist/design/audit/score.d.ts +49 -0
  166. package/dist/design/audit/score.d.ts.map +1 -0
  167. package/dist/design/audit/score.js +211 -0
  168. package/dist/design/audit/score.js.map +1 -0
  169. package/dist/design/audit/types.d.ts +28 -0
  170. package/dist/design/audit/types.d.ts.map +1 -1
  171. package/dist/discover/index.d.ts +16 -0
  172. package/dist/discover/index.d.ts.map +1 -0
  173. package/dist/discover/index.js +25 -0
  174. package/dist/discover/index.js.map +1 -0
  175. package/dist/discover/wayback.d.ts +49 -0
  176. package/dist/discover/wayback.d.ts.map +1 -0
  177. package/dist/discover/wayback.js +123 -0
  178. package/dist/discover/wayback.js.map +1 -0
  179. package/dist/jobs/anti-bot.d.ts +19 -0
  180. package/dist/jobs/anti-bot.d.ts.map +1 -0
  181. package/dist/jobs/anti-bot.js +64 -0
  182. package/dist/jobs/anti-bot.js.map +1 -0
  183. package/dist/jobs/cost-estimate.d.ts +12 -0
  184. package/dist/jobs/cost-estimate.d.ts.map +1 -0
  185. package/dist/jobs/cost-estimate.js +21 -0
  186. package/dist/jobs/cost-estimate.js.map +1 -0
  187. package/dist/jobs/cost-history.d.ts +19 -0
  188. package/dist/jobs/cost-history.d.ts.map +1 -0
  189. package/dist/jobs/cost-history.js +42 -0
  190. package/dist/jobs/cost-history.js.map +1 -0
  191. package/dist/jobs/index.d.ts +31 -0
  192. package/dist/jobs/index.d.ts.map +1 -0
  193. package/dist/jobs/index.js +37 -0
  194. package/dist/jobs/index.js.map +1 -0
  195. package/dist/jobs/orchestrator.d.ts +42 -0
  196. package/dist/jobs/orchestrator.d.ts.map +1 -0
  197. package/dist/jobs/orchestrator.js +282 -0
  198. package/dist/jobs/orchestrator.js.map +1 -0
  199. package/dist/jobs/queue.d.ts +45 -0
  200. package/dist/jobs/queue.d.ts.map +1 -0
  201. package/dist/jobs/queue.js +114 -0
  202. package/dist/jobs/queue.js.map +1 -0
  203. package/dist/jobs/retry.d.ts +36 -0
  204. package/dist/jobs/retry.d.ts.map +1 -0
  205. package/dist/jobs/retry.js +77 -0
  206. package/dist/jobs/retry.js.map +1 -0
  207. package/dist/jobs/store.d.ts +31 -0
  208. package/dist/jobs/store.d.ts.map +1 -0
  209. package/dist/jobs/store.js +92 -0
  210. package/dist/jobs/store.js.map +1 -0
  211. package/dist/jobs/types.d.ts +106 -0
  212. package/dist/jobs/types.d.ts.map +1 -0
  213. package/dist/jobs/types.js +13 -0
  214. package/dist/jobs/types.js.map +1 -0
  215. package/dist/reports/aggregate.d.ts +38 -0
  216. package/dist/reports/aggregate.d.ts.map +1 -0
  217. package/dist/reports/aggregate.js +145 -0
  218. package/dist/reports/aggregate.js.map +1 -0
  219. package/dist/reports/index.d.ts +21 -0
  220. package/dist/reports/index.d.ts.map +1 -0
  221. package/dist/reports/index.js +15 -0
  222. package/dist/reports/index.js.map +1 -0
  223. package/dist/reports/narrate.d.ts +19 -0
  224. package/dist/reports/narrate.d.ts.map +1 -0
  225. package/dist/reports/narrate.js +40 -0
  226. package/dist/reports/narrate.js.map +1 -0
  227. package/dist/reports/templates.d.ts +43 -0
  228. package/dist/reports/templates.d.ts.map +1 -0
  229. package/dist/reports/templates.js +200 -0
  230. package/dist/reports/templates.js.map +1 -0
  231. package/dist/reports/tokens.d.ts +69 -0
  232. package/dist/reports/tokens.d.ts.map +1 -0
  233. package/dist/reports/tokens.js +74 -0
  234. package/dist/reports/tokens.js.map +1 -0
  235. package/dist/reports/tools.d.ts +76 -0
  236. package/dist/reports/tools.d.ts.map +1 -0
  237. package/dist/reports/tools.js +205 -0
  238. package/dist/reports/tools.js.map +1 -0
  239. package/dist/reports/types.d.ts +51 -0
  240. package/dist/reports/types.d.ts.map +1 -0
  241. package/dist/reports/types.js +9 -0
  242. package/dist/reports/types.js.map +1 -0
  243. package/dist/run-state.d.ts +36 -1
  244. package/dist/run-state.d.ts.map +1 -1
  245. package/dist/run-state.js +36 -1
  246. package/dist/run-state.js.map +1 -1
  247. package/dist/runner/runner.d.ts.map +1 -1
  248. package/dist/runner/runner.js +73 -3
  249. package/dist/runner/runner.js.map +1 -1
  250. package/dist/types.d.ts +7 -0
  251. package/dist/types.d.ts.map +1 -1
  252. package/package.json +5 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Job runner — bounded-concurrency fan-out over the audit pipeline.
3
+ *
4
+ * Synchronous in the sense that `runJob()` returns when every target has been
5
+ * processed (ok, failed, or skipped). Crash safety: every individual result
6
+ * is persisted to disk as soon as it lands, so killing the process leaves a
7
+ * resumable record (resume is a future addition; today the partial state
8
+ * is just observable).
9
+ *
10
+ * `auditFn` is injected so tests can run the queue without touching
11
+ * Playwright/LLMs.
12
+ */
13
+ import { saveJob, appendIndexEntry } from './store.js';
14
+ import { withRetry, DEFAULT_RETRY_POLICY } from './retry.js';
15
+ const DEFAULT_CONCURRENCY = 2;
16
+ export async function runJob(job, opts) {
17
+ const concurrency = opts.concurrency ?? job.spec.concurrency ?? DEFAULT_CONCURRENCY;
18
+ job.status = 'running';
19
+ job.startedAt = job.startedAt ?? new Date().toISOString();
20
+ saveJob(job, opts.dir);
21
+ appendIndexEntry(job, opts.dir);
22
+ // Resume support: skip targets that already have a non-failed result.
23
+ const completed = new Set();
24
+ if (opts.resume) {
25
+ for (const r of job.results) {
26
+ if (r.status === 'ok' || r.status === 'skipped')
27
+ completed.add(targetKey(r));
28
+ }
29
+ }
30
+ const queue = job.targets.filter(t => !completed.has(targetKey(t)));
31
+ let aborted = false;
32
+ async function worker() {
33
+ while (queue.length > 0 && !aborted) {
34
+ const target = queue.shift();
35
+ if (!target)
36
+ break;
37
+ const entry = await runOne(target, job.spec.audit, opts.auditFn, opts.retryPolicy, opts.onError);
38
+ if (entry === 'abort') {
39
+ aborted = true;
40
+ return;
41
+ }
42
+ job.results.push(entry);
43
+ if (entry.costUSD)
44
+ job.totalCostUSD = round2(job.totalCostUSD + entry.costUSD);
45
+ saveJob(job, opts.dir);
46
+ }
47
+ }
48
+ const workers = Array.from({ length: concurrency }, () => worker());
49
+ await Promise.all(workers);
50
+ job.completedAt = new Date().toISOString();
51
+ job.status = aborted ? 'cancelled' : finalStatus(job);
52
+ saveJob(job, opts.dir);
53
+ appendIndexEntry(job, opts.dir);
54
+ return job;
55
+ }
56
+ async function runOne(target, audit, auditFn, retryPolicy, onError) {
57
+ try {
58
+ const policy = retryPolicy === null ? undefined : (retryPolicy ?? DEFAULT_RETRY_POLICY);
59
+ const call = () => auditFn(target, audit);
60
+ const out = policy ? await withRetry(call, policy) : await call();
61
+ if (out.blockedReason) {
62
+ return {
63
+ ...target,
64
+ status: 'skipped',
65
+ runId: out.runId,
66
+ resultPath: out.resultPath,
67
+ error: out.blockedReason,
68
+ costUSD: out.costUSD,
69
+ };
70
+ }
71
+ return {
72
+ ...target,
73
+ status: 'ok',
74
+ runId: out.runId,
75
+ resultPath: out.resultPath,
76
+ rollupScore: out.rollupScore,
77
+ pageType: out.pageType,
78
+ costUSD: out.costUSD,
79
+ tokensPath: out.tokensPath,
80
+ };
81
+ }
82
+ catch (err) {
83
+ const error = err;
84
+ const decision = onError?.(target, error) ?? 'continue';
85
+ if (decision === 'abort')
86
+ return 'abort';
87
+ return {
88
+ ...target,
89
+ status: 'failed',
90
+ error: error.message,
91
+ };
92
+ }
93
+ }
94
+ function targetKey(t) {
95
+ return t.snapshotUrl ?? t.url;
96
+ }
97
+ function finalStatus(job) {
98
+ const total = job.results.length;
99
+ if (total === 0)
100
+ return 'failed';
101
+ const ok = job.results.filter(r => r.status === 'ok').length;
102
+ const failed = job.results.filter(r => r.status === 'failed').length;
103
+ // 'skipped' is a deterministic non-failure (e.g. anti-bot block detected).
104
+ // Treat it as a clean outcome — we recorded the reason and moved on.
105
+ if (ok === 0 && failed > 0)
106
+ return 'failed';
107
+ if (failed > 0 || ok < total)
108
+ return 'partial';
109
+ return 'completed';
110
+ }
111
+ function round2(n) {
112
+ return Math.round(n * 100) / 100;
113
+ }
114
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/jobs/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Retry-with-backoff helper used by the jobs queue. Deliberately not an LLM
3
+ * decision — exponential backoff with jitter is a protocol, not a judgment
4
+ * call. Wrapping it in prose adds latency without adding intelligence.
5
+ *
6
+ * Default policy:
7
+ * - 3 attempts total (one initial + two retries)
8
+ * - Base delay 500ms, doubles each attempt, capped at 5s
9
+ * - Adds ±20% jitter so concurrent workers don't synchronize their retries
10
+ * - Retries network errors, HTTP 429, HTTP 5xx, and timeouts
11
+ * - Does NOT retry HTTP 4xx, anti-bot blocks, or schema-validation errors
12
+ */
13
+ export interface RetryPolicy {
14
+ maxAttempts: number;
15
+ baseDelayMs: number;
16
+ maxDelayMs: number;
17
+ /** Returns true if the error is retryable. */
18
+ isRetryable: (err: Error) => boolean;
19
+ /** Override default backoff (for tests). */
20
+ backoffFn?: (attempt: number, base: number, cap: number) => number;
21
+ /** Hook fired before each retry — useful for telemetry. */
22
+ onRetry?: (attempt: number, err: Error, delayMs: number) => void;
23
+ }
24
+ /**
25
+ * Whitelist-only: an error is retryable iff it matches one of the patterns
26
+ * above. Anything else (4xx, anti-bot, schema-validation, unknown errors) is
27
+ * deterministic and we don't waste round-trips retrying it.
28
+ */
29
+ export declare function isRetryableDefault(err: Error): boolean;
30
+ export declare const DEFAULT_RETRY_POLICY: RetryPolicy;
31
+ /**
32
+ * Run `fn` with retry-on-retryable-failure. Throws the last error after
33
+ * `maxAttempts` exhausted, or immediately on a non-retryable error.
34
+ */
35
+ export declare function withRetry<T>(fn: () => Promise<T>, policy?: RetryPolicy): Promise<T>;
36
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/jobs/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAA;IACpC,4CAA4C;IAC5C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAClE,2DAA2D;IAC3D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACjE;AAiBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAEtD;AAED,eAAO,MAAM,oBAAoB,EAAE,WAKlC,CAAA;AAQD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,GAAE,WAAkC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmB/G"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Retry-with-backoff helper used by the jobs queue. Deliberately not an LLM
3
+ * decision — exponential backoff with jitter is a protocol, not a judgment
4
+ * call. Wrapping it in prose adds latency without adding intelligence.
5
+ *
6
+ * Default policy:
7
+ * - 3 attempts total (one initial + two retries)
8
+ * - Base delay 500ms, doubles each attempt, capped at 5s
9
+ * - Adds ±20% jitter so concurrent workers don't synchronize their retries
10
+ * - Retries network errors, HTTP 429, HTTP 5xx, and timeouts
11
+ * - Does NOT retry HTTP 4xx, anti-bot blocks, or schema-validation errors
12
+ */
13
+ const RETRYABLE_PATTERNS = [
14
+ /\b429\b/, // rate limit
15
+ /\b5\d\d\b/, // 5xx
16
+ /timeout/i,
17
+ /timed out/i,
18
+ /etimedout/i,
19
+ /econnreset/i,
20
+ /econnrefused/i,
21
+ /enotfound/i,
22
+ /socket hang up/i,
23
+ /network/i,
24
+ /fetch failed/i,
25
+ /CDX returned 5\d\d/i,
26
+ ];
27
+ /**
28
+ * Whitelist-only: an error is retryable iff it matches one of the patterns
29
+ * above. Anything else (4xx, anti-bot, schema-validation, unknown errors) is
30
+ * deterministic and we don't waste round-trips retrying it.
31
+ */
32
+ export function isRetryableDefault(err) {
33
+ return RETRYABLE_PATTERNS.some(re => re.test(err.message));
34
+ }
35
+ export const DEFAULT_RETRY_POLICY = {
36
+ maxAttempts: 3,
37
+ baseDelayMs: 500,
38
+ maxDelayMs: 5000,
39
+ isRetryable: isRetryableDefault,
40
+ };
41
+ function defaultBackoff(attempt, base, cap) {
42
+ const exp = Math.min(base * 2 ** attempt, cap);
43
+ const jitter = exp * (0.8 + Math.random() * 0.4); // ±20%
44
+ return Math.round(jitter);
45
+ }
46
+ /**
47
+ * Run `fn` with retry-on-retryable-failure. Throws the last error after
48
+ * `maxAttempts` exhausted, or immediately on a non-retryable error.
49
+ */
50
+ export async function withRetry(fn, policy = DEFAULT_RETRY_POLICY) {
51
+ const max = Math.max(1, policy.maxAttempts);
52
+ const backoff = policy.backoffFn ?? defaultBackoff;
53
+ let lastErr;
54
+ for (let attempt = 0; attempt < max; attempt++) {
55
+ try {
56
+ return await fn();
57
+ }
58
+ catch (err) {
59
+ lastErr = err;
60
+ if (!policy.isRetryable(lastErr))
61
+ throw lastErr;
62
+ if (attempt === max - 1)
63
+ break;
64
+ const delay = backoff(attempt, policy.baseDelayMs, policy.maxDelayMs);
65
+ policy.onRetry?.(attempt + 1, lastErr, delay);
66
+ await sleep(delay);
67
+ }
68
+ }
69
+ // Tag the final error so callers can tell retried-and-failed apart from never-retried.
70
+ if (lastErr)
71
+ lastErr.message = `[after ${max} attempts] ${lastErr.message}`;
72
+ throw lastErr ?? new Error('withRetry: no error captured but no result returned');
73
+ }
74
+ function sleep(ms) {
75
+ return new Promise(resolve => setTimeout(resolve, ms));
76
+ }
77
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/jobs/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,MAAM,kBAAkB,GAAG;IACzB,SAAS,EAAE,aAAa;IACxB,WAAW,EAAE,MAAM;IACnB,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,eAAe;IACf,YAAY;IACZ,iBAAiB;IACjB,UAAU;IACV,eAAe;IACf,qBAAqB;CACtB,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAU;IAC3C,OAAO,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,kBAAkB;CAChC,CAAA;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,IAAY,EAAE,GAAW;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,OAAO,EAAE,GAAG,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAA,CAAC,OAAO;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,EAAoB,EAAE,SAAsB,oBAAoB;IACjG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,IAAI,cAAc,CAAA;IAClD,IAAI,OAA0B,CAAA;IAC9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,GAAG,GAAY,CAAA;YACtB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC;gBAAE,MAAM,OAAO,CAAA;YAC/C,IAAI,OAAO,KAAK,GAAG,GAAG,CAAC;gBAAE,MAAK;YAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;YACrE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,KAAK,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;IACD,uFAAuF;IACvF,IAAI,OAAO;QAAE,OAAO,CAAC,OAAO,GAAG,UAAU,GAAG,cAAc,OAAO,CAAC,OAAO,EAAE,CAAA;IAC3E,MAAM,OAAO,IAAI,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;AACnF,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AACxD,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Job store — whole-file JSON per job + an append-only index for listing.
3
+ *
4
+ * Layout:
5
+ * ~/.bad/jobs/<jobId>.json — Job record (atomic write via tmp + rename)
6
+ * ~/.bad/jobs/index.jsonl — append-only one line per job ({jobId, status, createdAt, label})
7
+ *
8
+ * Whole-file rewrite is intentional: a job is a single coherent record with
9
+ * monotonically-extending `results[]`. Append-only at the entry level would
10
+ * duplicate target rows; append-only at the file level would force readers
11
+ * to fold N updates into one logical record. Atomic rename gives us crash
12
+ * safety without that complexity.
13
+ */
14
+ import type { Job, JobStatus } from './types.js';
15
+ export declare function jobsDir(override?: string): string;
16
+ export declare function jobPath(jobId: string, dir?: string): string;
17
+ export declare function newJobId(now?: Date): string;
18
+ export declare function saveJob(job: Job, dir?: string): void;
19
+ export declare function loadJob(jobId: string, dir?: string): Job | null;
20
+ export declare function appendIndexEntry(job: Job, dir?: string): void;
21
+ export interface JobIndexEntry {
22
+ jobId: string;
23
+ status: JobStatus;
24
+ createdAt: string;
25
+ label: string | null;
26
+ targetCount: number;
27
+ }
28
+ /** Reads the index file. Later entries for the same jobId override earlier ones. */
29
+ export declare function listJobs(dir?: string): JobIndexEntry[];
30
+ export declare function updateJobStatus(jobId: string, status: JobStatus, dir?: string): Job | null;
31
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAIhD,wBAAgB,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAMD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAMD,wBAAgB,QAAQ,CAAC,GAAG,GAAE,IAAiB,GAAG,MAAM,CAIvD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAW7D;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE,CActD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAW1F"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Job store — whole-file JSON per job + an append-only index for listing.
3
+ *
4
+ * Layout:
5
+ * ~/.bad/jobs/<jobId>.json — Job record (atomic write via tmp + rename)
6
+ * ~/.bad/jobs/index.jsonl — append-only one line per job ({jobId, status, createdAt, label})
7
+ *
8
+ * Whole-file rewrite is intentional: a job is a single coherent record with
9
+ * monotonically-extending `results[]`. Append-only at the entry level would
10
+ * duplicate target rows; append-only at the file level would force readers
11
+ * to fold N updates into one logical record. Atomic rename gives us crash
12
+ * safety without that complexity.
13
+ */
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import * as os from 'node:os';
17
+ const DEFAULT_DIR = path.join(os.homedir(), '.bad', 'jobs');
18
+ export function jobsDir(override) {
19
+ return override ?? DEFAULT_DIR;
20
+ }
21
+ function ensureDir(dir) {
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+ export function jobPath(jobId, dir) {
25
+ return path.join(jobsDir(dir), `${jobId}.json`);
26
+ }
27
+ function indexPath(dir) {
28
+ return path.join(jobsDir(dir), 'index.jsonl');
29
+ }
30
+ export function newJobId(now = new Date()) {
31
+ const stamp = now.toISOString().replace(/[-:.]/g, '').slice(0, 15); // YYYYMMDDTHHMMSS
32
+ const rand = Math.random().toString(36).slice(2, 8);
33
+ return `job_${stamp}_${rand}`;
34
+ }
35
+ export function saveJob(job, dir) {
36
+ const target = jobPath(job.jobId, dir);
37
+ ensureDir(path.dirname(target));
38
+ const tmp = `${target}.tmp`;
39
+ fs.writeFileSync(tmp, JSON.stringify(job, null, 2));
40
+ fs.renameSync(tmp, target);
41
+ }
42
+ export function loadJob(jobId, dir) {
43
+ const target = jobPath(jobId, dir);
44
+ if (!fs.existsSync(target))
45
+ return null;
46
+ return JSON.parse(fs.readFileSync(target, 'utf-8'));
47
+ }
48
+ export function appendIndexEntry(job, dir) {
49
+ const file = indexPath(dir);
50
+ ensureDir(path.dirname(file));
51
+ const line = JSON.stringify({
52
+ jobId: job.jobId,
53
+ status: job.status,
54
+ createdAt: job.createdAt,
55
+ label: job.spec.label ?? null,
56
+ targetCount: job.targets.length,
57
+ });
58
+ fs.appendFileSync(file, line + '\n');
59
+ }
60
+ /** Reads the index file. Later entries for the same jobId override earlier ones. */
61
+ export function listJobs(dir) {
62
+ const file = indexPath(dir);
63
+ if (!fs.existsSync(file))
64
+ return [];
65
+ const lines = fs.readFileSync(file, 'utf-8').split('\n').filter(Boolean);
66
+ const map = new Map();
67
+ for (const line of lines) {
68
+ try {
69
+ const entry = JSON.parse(line);
70
+ map.set(entry.jobId, entry);
71
+ }
72
+ catch {
73
+ // Skip malformed lines — index is best-effort.
74
+ }
75
+ }
76
+ return Array.from(map.values()).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
77
+ }
78
+ export function updateJobStatus(jobId, status, dir) {
79
+ const job = loadJob(jobId, dir);
80
+ if (!job)
81
+ return null;
82
+ job.status = status;
83
+ if (status === 'running' && !job.startedAt)
84
+ job.startedAt = new Date().toISOString();
85
+ if (status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'partial') {
86
+ job.completedAt = new Date().toISOString();
87
+ }
88
+ saveJob(job, dir);
89
+ appendIndexEntry(job, dir);
90
+ return job;
91
+ }
92
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/jobs/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAG7B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAE3D,MAAM,UAAU,OAAO,CAAC,QAAiB;IACvC,OAAO,QAAQ,IAAI,WAAW,CAAA;AAChC,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,GAAY;IACjD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;AACjD,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAY,IAAI,IAAI,EAAE;IAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,CAAC,kBAAkB;IACrF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACnD,OAAO,OAAO,KAAK,IAAI,IAAI,EAAE,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAQ,EAAE,GAAY;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACtC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IAC/B,MAAM,GAAG,GAAG,GAAG,MAAM,MAAM,CAAA;IAC3B,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IACnD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,GAAY;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAQ,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAQ,EAAE,GAAY;IACrD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3B,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI;QAC7B,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;KAChC,CAAC,CAAA;IACF,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,CAAA;AACtC,CAAC;AAUD,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,GAAY;IACnC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACxE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAyB,CAAA;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAA;YAC/C,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;QACjD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;AACxF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,MAAiB,EAAE,GAAY;IAC5E,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAC/B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,GAAG,CAAC,MAAM,GAAG,MAAM,CAAA;IACnB,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,SAAS;QAAE,GAAG,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpF,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACpG,GAAG,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC5C,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACjB,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC1B,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Job orchestration types — RFC-003: comparative-audit jobs.
3
+ *
4
+ * A job is a declarative spec ("audit these N URLs, optionally with M historical
5
+ * snapshots each") that fans out to the existing design-audit pipeline and
6
+ * persists the aggregate result so report generation can run later.
7
+ *
8
+ * Persistence: append-only JSONL at `~/.bad/jobs/<jobId>.json` (whole-file
9
+ * rewrites are fine — jobs are small) plus a one-line index entry at
10
+ * `~/.bad/jobs/index.jsonl` for fast listing.
11
+ */
12
+ import type { AudienceTag, ModalityTag, RegulatoryContextTag, AudienceVulnerabilityTag } from '../design/audit/score-types.js';
13
+ export type JobKind = 'comparative-audit';
14
+ export type JobStatus = 'queued' | 'running' | 'partial' | 'completed' | 'failed' | 'cancelled';
15
+ export type DiscoverSource = 'list' | 'wayback';
16
+ /** What targets to audit. `list` = explicit URLs; `wayback` = expand each URL into historical snapshots. */
17
+ export interface DiscoverSpec {
18
+ source: DiscoverSource;
19
+ /** Explicit list of URLs (always required — it's the seed even for wayback). */
20
+ urls: string[];
21
+ /** wayback only: how many evenly-spaced snapshots to fetch per URL. Default 5. */
22
+ snapshotsPerUrl?: number;
23
+ /** wayback only: ISO date lower bound (e.g. "2010-01-01"). */
24
+ since?: string;
25
+ /** wayback only: ISO date upper bound (e.g. "2026-01-01"). */
26
+ until?: string;
27
+ }
28
+ /** Pass-through audit options — mirrors the design-audit CLI flags. */
29
+ export interface AuditOptions {
30
+ pages?: number;
31
+ modality?: 'html' | 'ios' | 'android';
32
+ audience?: AudienceTag[];
33
+ audienceVulnerability?: AudienceVulnerabilityTag[];
34
+ modalityTag?: ModalityTag[];
35
+ regulatoryContext?: RegulatoryContextTag[];
36
+ headless?: boolean;
37
+ skipEthics?: boolean;
38
+ /**
39
+ * Layer 8 add-on: also run the deterministic brand/design-token extractor at
40
+ * every target. Adds ~10s/target (no LLM). Output lands at
41
+ * `<resultPath dir>/tokens.json` and is surfaced via `JobResultEntry.tokensPath`.
42
+ */
43
+ extractTokens?: boolean;
44
+ }
45
+ export interface JobSpec {
46
+ kind: JobKind;
47
+ discover: DiscoverSpec;
48
+ audit?: AuditOptions;
49
+ /** Bounded concurrency for the audit fan-out. Default 2 — Playwright + LLM rate limits cap real-world throughput. */
50
+ concurrency?: number;
51
+ /** Hard cost cap. The job aborts pre-flight if estimated cost exceeds this. */
52
+ maxCostUSD?: number;
53
+ /** Free-form label for humans (shows up in `bad jobs list`). */
54
+ label?: string;
55
+ }
56
+ /** One target = one (url, optional snapshot) pair the auditor will run on. */
57
+ export interface JobTarget {
58
+ /** The original seed URL. For non-wayback discovery, this is also the audit URL. */
59
+ url: string;
60
+ /** For wayback targets: the actual snapshot URL passed to the audit. */
61
+ snapshotUrl?: string;
62
+ /** For wayback targets: ISO datetime when the snapshot was captured. */
63
+ capturedAt?: string;
64
+ }
65
+ export type JobResultStatus = 'ok' | 'failed' | 'skipped';
66
+ export interface JobResultEntry extends JobTarget {
67
+ status: JobResultStatus;
68
+ /** runId of the design-audit run when status === 'ok'. */
69
+ runId?: string;
70
+ /** Path to the run's report.json (relative to cwd or absolute). */
71
+ resultPath?: string;
72
+ /** Failure reason when status === 'failed' or 'skipped'. */
73
+ error?: string;
74
+ /** Estimated USD cost of this audit (if available from telemetry). */
75
+ costUSD?: number;
76
+ /** Roll-up score copied out of the report for fast aggregation queries. */
77
+ rollupScore?: number;
78
+ /** Page-type classification. */
79
+ pageType?: string;
80
+ /** Path to the tokens.json from the brand-kit extractor (when extractTokens=true). */
81
+ tokensPath?: string;
82
+ }
83
+ export interface Job {
84
+ jobId: string;
85
+ spec: JobSpec;
86
+ status: JobStatus;
87
+ createdAt: string;
88
+ startedAt?: string;
89
+ completedAt?: string;
90
+ /** All discovered targets (length = number of audits the job will / did run). */
91
+ targets: JobTarget[];
92
+ /** Per-target outcome. Length matches `targets` once the job has progressed past discovery. */
93
+ results: JobResultEntry[];
94
+ /** Sum of `results[*].costUSD` for completed entries. */
95
+ totalCostUSD: number;
96
+ /** Free-form notes (errors, warnings, telemetry summary). */
97
+ notes?: string[];
98
+ }
99
+ export interface CostEstimate {
100
+ targetCount: number;
101
+ estimatedTotalUSD: number;
102
+ perAuditUSD: number;
103
+ /** Whether the estimate is above `spec.maxCostUSD`. */
104
+ exceedsCap: boolean;
105
+ }
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AAE9H,MAAM,MAAM,OAAO,GAAG,mBAAmB,CAAA;AAEzC,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAA;AAE/F,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,CAAA;AAE/C,4GAA4G;AAC5G,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,cAAc,CAAA;IACtB,gFAAgF;IAChF,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB,qBAAqB,CAAC,EAAE,wBAAwB,EAAE,CAAA;IAClD,WAAW,CAAC,EAAE,WAAW,EAAE,CAAA;IAC3B,iBAAiB,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,qHAAqH;IACrH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEzD,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,MAAM,EAAE,eAAe,CAAA;IACvB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,+FAA+F;IAC/F,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAA;CACpB"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Job orchestration types — RFC-003: comparative-audit jobs.
3
+ *
4
+ * A job is a declarative spec ("audit these N URLs, optionally with M historical
5
+ * snapshots each") that fans out to the existing design-audit pipeline and
6
+ * persists the aggregate result so report generation can run later.
7
+ *
8
+ * Persistence: append-only JSONL at `~/.bad/jobs/<jobId>.json` (whole-file
9
+ * rewrites are fine — jobs are small) plus a one-line index entry at
10
+ * `~/.bad/jobs/index.jsonl` for fast listing.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Deterministic aggregation over a job's per-target audit results.
3
+ *
4
+ * Every number that shows up in a report flows through here — never through
5
+ * an LLM. The narration layer can describe / contextualize / dramatize, but
6
+ * counts, scores, deltas, and rankings are all computed here. Same pattern
7
+ * we use for the audit patches contract: agent narrates, code computes.
8
+ */
9
+ import type { Job } from '../jobs/types.js';
10
+ import type { AggregateRow, CompareRunsResult, LongitudinalRow } from './types.js';
11
+ /**
12
+ * Read each ok result's `report.json` from disk and project to AggregateRow.
13
+ * Skipped/failed entries become rows with `rollupScore: NaN` so callers can
14
+ * filter them — keeping them in the list preserves index alignment with
15
+ * `job.results` for tools that want to drill in.
16
+ */
17
+ export declare function aggregateJob(job: Job): AggregateRow[];
18
+ export interface LeaderboardOptions {
19
+ /** Filter by page type (e.g. only saas-app rows). */
20
+ byType?: string;
21
+ /** Top N entries. Default = no cap. */
22
+ topN?: number;
23
+ /** Sort direction. Default 'desc' (highest first). */
24
+ direction?: 'asc' | 'desc';
25
+ }
26
+ export declare function leaderboard(rows: AggregateRow[], opts?: LeaderboardOptions): AggregateRow[];
27
+ /** Longitudinal view: all snapshots for one URL, sorted by capture time. */
28
+ export declare function longitudinalFor(rows: AggregateRow[], url: string): LongitudinalRow[];
29
+ export declare function compareRuns(a: AggregateRow, b: AggregateRow): CompareRunsResult;
30
+ /** Group rows into tier buckets for "top 10 vs 100-200" style reports. */
31
+ export interface TierBucket {
32
+ label: string;
33
+ rows: AggregateRow[];
34
+ meanScore: number;
35
+ medianScore: number;
36
+ }
37
+ export declare function tierBuckets(rows: AggregateRow[], boundaries: number[]): TierBucket[];
38
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/reports/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAkB,MAAM,kBAAkB,CAAA;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAkB,eAAe,EAAE,MAAM,YAAY,CAAA;AAkBlG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,EAAE,CAMrD;AAwCD,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CAC3B;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE,kBAAuB,GAAG,YAAY,EAAE,CAQ/F;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAUpF;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,iBAAiB,CAkB/E;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,YAAY,EAAE,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAoBpF"}