@kirrosh/zond 0.22.0 → 0.23.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 +648 -0
- package/README.md +58 -6
- package/package.json +9 -6
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +134 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +525 -0
- package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
- package/src/cli/commands/api/annotate/overlay.ts +206 -0
- package/src/cli/commands/api/annotate/pagination.ts +60 -0
- package/src/cli/commands/api/annotate/prompts.ts +183 -0
- package/src/cli/commands/api/annotate/readback.ts +58 -0
- package/src/cli/commands/api/annotate/resources.ts +91 -0
- package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
- package/src/cli/commands/audit.ts +480 -0
- package/src/cli/commands/bootstrap.ts +710 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +348 -0
- package/src/cli/commands/checks.ts +756 -0
- package/src/cli/commands/ci-init.ts +43 -0
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +16 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +178 -7
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1236 -0
- package/src/cli/commands/doctor.ts +607 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +420 -46
- package/src/cli/commands/init/bootstrap.ts +30 -1
- package/src/cli/commands/{init.ts → init/index.ts} +99 -5
- package/src/cli/commands/init/skills.ts +56 -3
- package/src/cli/commands/init/templates/agents.md +65 -61
- package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
- package/src/cli/commands/init/templates/skills/zond.md +592 -125
- package/src/cli/commands/init/templates/zond-config.yml +8 -9
- package/src/cli/commands/prepare-fixtures.ts +135 -0
- package/src/cli/commands/probe/mass-assignment.ts +503 -0
- package/src/cli/commands/probe/security.ts +454 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +161 -0
- package/src/cli/commands/probe.ts +459 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +169 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +318 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +379 -4
- package/src/cli/commands/run.ts +842 -53
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +18 -1
- package/src/cli/index.ts +20 -3
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +198 -635
- package/src/cli/resolve.ts +105 -0
- package/src/cli/status-filter.ts +124 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +5 -0
- package/src/core/anti-fp/bootstrap.ts +34 -0
- package/src/core/anti-fp/index.ts +33 -0
- package/src/core/anti-fp/registry.ts +44 -0
- package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
- package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
- package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
- package/src/core/anti-fp/types.ts +68 -0
- package/src/core/checks/checks/_crud-helpers.ts +133 -0
- package/src/core/checks/checks/_negative_mutator.ts +133 -0
- package/src/core/checks/checks/_readback-helpers.ts +133 -0
- package/src/core/checks/checks/content_type_conformance.ts +39 -0
- package/src/core/checks/checks/cross_call_references.ts +134 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +246 -0
- package/src/core/checks/checks/ignored_auth.ts +211 -0
- package/src/core/checks/checks/index.ts +65 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +45 -0
- package/src/core/checks/checks/not_a_server_error.ts +27 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
- package/src/core/checks/checks/pagination_invariants.ts +238 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
- package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
- package/src/core/checks/checks/response_headers_conformance.ts +74 -0
- package/src/core/checks/checks/response_schema_conformance.ts +30 -0
- package/src/core/checks/checks/status_code_conformance.ts +61 -0
- package/src/core/checks/checks/unsupported_method.ts +63 -0
- package/src/core/checks/checks/use_after_free.ts +78 -0
- package/src/core/checks/index.ts +30 -0
- package/src/core/checks/mode.ts +79 -0
- package/src/core/checks/recommended-action.ts +64 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +874 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +189 -0
- package/src/core/classifier/recommended-action.ts +222 -0
- package/src/core/context/current.ts +22 -6
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +185 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +151 -11
- package/src/core/diagnostics/failure-class.ts +120 -0
- package/src/core/diagnostics/failure-hints.ts +212 -9
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +156 -0
- package/src/core/exporter/case-study/index.ts +270 -0
- package/src/core/exporter/curl.ts +40 -0
- package/src/core/exporter/exporter.ts +48 -0
- package/src/core/exporter/html-report/escape.ts +24 -0
- package/src/core/exporter/html-report/index.ts +479 -0
- package/src/core/exporter/html-report/script.ts +100 -0
- package/src/core/exporter/html-report/styles.ts +408 -0
- package/src/core/generator/chunker.ts +42 -16
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/create-body.ts +89 -0
- package/src/core/generator/data-factory.ts +445 -19
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +325 -0
- package/src/core/generator/index.ts +7 -5
- package/src/core/generator/openapi-reader.ts +37 -3
- package/src/core/generator/path-param-disambig.ts +114 -0
- package/src/core/generator/resources-builder.ts +648 -0
- package/src/core/generator/schema-utils.ts +11 -3
- package/src/core/generator/serializer.ts +103 -13
- package/src/core/generator/suite-generator.ts +419 -111
- package/src/core/generator/types.ts +8 -0
- package/src/core/identity/identity-file.ts +129 -0
- package/src/core/lint/affects.ts +28 -0
- package/src/core/lint/config.ts +96 -0
- package/src/core/lint/format.ts +42 -0
- package/src/core/lint/index.ts +94 -0
- package/src/core/lint/reporter.ts +128 -0
- package/src/core/lint/rules/consistency.ts +158 -0
- package/src/core/lint/rules/heuristics.ts +97 -0
- package/src/core/lint/rules/strictness.ts +109 -0
- package/src/core/lint/types.ts +96 -0
- package/src/core/lint/walker.ts +248 -0
- package/src/core/meta/meta-store.ts +6 -73
- package/src/core/output/README.md +91 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +126 -0
- package/src/core/output/types.ts +129 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +129 -4
- package/src/core/parser/types.ts +19 -1
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +58 -12
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +57 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +1122 -0
- package/src/core/probe/mass-assignment-template.ts +212 -0
- package/src/core/probe/method-probe.ts +43 -76
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +183 -149
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +440 -0
- package/src/core/probe/probe-harness.ts +120 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security-probe-class.ts +201 -0
- package/src/core/probe/security-probe.ts +1453 -0
- package/src/core/probe/shared.ts +505 -0
- package/src/core/probe/static-probe-class.ts +125 -0
- package/src/core/probe/types.ts +165 -0
- package/src/core/probe/verdict-aggregator.ts +33 -0
- package/src/core/probe/webhooks-probe.ts +284 -0
- package/src/core/reporter/console.ts +41 -2
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +11 -1
- package/src/core/reporter/junit.ts +27 -12
- package/src/core/reporter/ndjson.ts +37 -0
- package/src/core/reporter/types.ts +3 -0
- package/src/core/runner/assertions.ts +58 -1
- package/src/core/runner/async-pool.ts +108 -0
- package/src/core/runner/auth-path.ts +8 -0
- package/src/core/runner/ci-context.ts +72 -0
- package/src/core/runner/executor.ts +264 -20
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +75 -2
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +149 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +89 -17
- package/src/core/runner/run-kind.ts +39 -0
- package/src/core/runner/schema-validator.ts +312 -0
- package/src/core/runner/send-request.ts +153 -20
- package/src/core/runner/types.ts +38 -0
- package/src/core/secrets/registry.ts +164 -0
- package/src/core/secrets/secrets-file.ts +115 -0
- package/src/core/selectors/operation-filter.ts +144 -0
- package/src/core/setup-api.ts +415 -16
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +121 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +126 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/sql.d.ts +4 -0
- package/src/db/queries/collections.ts +133 -0
- package/src/db/queries/coverage.ts +9 -0
- package/src/db/queries/dashboard.ts +59 -0
- package/src/db/queries/results.ts +128 -0
- package/src/db/queries/runs.ts +235 -0
- package/src/db/queries/sessions.ts +42 -0
- package/src/db/queries/settings.ts +28 -0
- package/src/db/queries/types.ts +172 -0
- package/src/db/queries.ts +72 -802
- package/src/db/schema.ts +178 -50
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
- package/src/cli/commands/probe-methods.ts +0 -108
- package/src/cli/commands/probe-validation.ts +0 -124
- package/src/cli/commands/serve.ts +0 -114
- package/src/cli/commands/sync.ts +0 -268
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/diagnostics/render-md.ts +0 -112
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -19
- package/src/core/parser/index.ts +0 -21
- package/src/core/runner/execute-run.ts +0 -132
- package/src/core/runner/index.ts +0 -12
- package/src/core/sync/spec-differ.ts +0 -38
- package/src/web/data/collection-state.ts +0 -362
- package/src/web/routes/api.ts +0 -314
- package/src/web/routes/dashboard.ts +0 -350
- package/src/web/routes/runs.ts +0 -64
- package/src/web/schemas.ts +0 -121
- package/src/web/server.ts +0 -134
- package/src/web/static/htmx.min.cjs +0 -1
- package/src/web/static/style.css +0 -1148
- package/src/web/views/endpoints-tab.ts +0 -174
- package/src/web/views/explorer-tab.ts +0 -402
- package/src/web/views/health-strip.ts +0 -92
- package/src/web/views/layout.ts +0 -48
- package/src/web/views/results.ts +0 -210
- package/src/web/views/runs-tab.ts +0 -126
- package/src/web/views/suites-tab.ts +0 -181
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARV-10 (m-15): NDJSON streaming reporter for `zond checks run`.
|
|
3
|
+
*
|
|
4
|
+
* Each event is a single JSON line on stdout — agents pipe the stream
|
|
5
|
+
* into `jq` / ajv / their own consumer and act on findings *as they
|
|
6
|
+
* happen* instead of waiting for the run to wrap up. The discriminated
|
|
7
|
+
* union of event shapes lives in `src/cli/json-schemas.ts` (zod source
|
|
8
|
+
* of truth) and ships as `docs/json-schema/ndjson-events.schema.json`.
|
|
9
|
+
*
|
|
10
|
+
* The CLI passes `emitToStdout` as the `onEvent` callback to runChecks;
|
|
11
|
+
* tests can pass an in-memory accumulator instead.
|
|
12
|
+
*/
|
|
13
|
+
import type { z } from "zod";
|
|
14
|
+
import type {
|
|
15
|
+
NdjsonCheckStartEventSchema,
|
|
16
|
+
NdjsonCheckResultEventSchema,
|
|
17
|
+
NdjsonFindingEventSchema,
|
|
18
|
+
NdjsonSummaryEventSchema,
|
|
19
|
+
NdjsonEventSchema,
|
|
20
|
+
} from "../../cli/json-schemas.ts";
|
|
21
|
+
|
|
22
|
+
export type NdjsonCheckStartEvent = z.infer<typeof NdjsonCheckStartEventSchema>;
|
|
23
|
+
export type NdjsonCheckResultEvent = z.infer<typeof NdjsonCheckResultEventSchema>;
|
|
24
|
+
export type NdjsonFindingEvent = z.infer<typeof NdjsonFindingEventSchema>;
|
|
25
|
+
export type NdjsonSummaryEvent = z.infer<typeof NdjsonSummaryEventSchema>;
|
|
26
|
+
export type NdjsonEvent = z.infer<typeof NdjsonEventSchema>;
|
|
27
|
+
|
|
28
|
+
/** Write one event to stdout as a single line. AC #5 — when the CLI is
|
|
29
|
+
* in `--ndjson` mode, *every* user-readable message goes to stderr, so
|
|
30
|
+
* stdout stays a clean NDJSON stream that pipes into `jq` / ajv. */
|
|
31
|
+
export function emitToStdout(ev: NdjsonEvent): void {
|
|
32
|
+
process.stdout.write(`${JSON.stringify(ev)}\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function nowIso(): string {
|
|
36
|
+
return new Date().toISOString();
|
|
37
|
+
}
|
|
@@ -3,6 +3,9 @@ import type { TestRunResult } from "../runner/types.ts";
|
|
|
3
3
|
export interface ReporterOptions {
|
|
4
4
|
/** Whether to use ANSI colors. Default: auto-detect via isTTY. */
|
|
5
5
|
color?: boolean;
|
|
6
|
+
/** TASK-265: suppress per-suite/per-test detail and emit only the
|
|
7
|
+
* grand-total summary line. Exit code still carries pass/fail. */
|
|
8
|
+
quiet?: boolean;
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export type ReporterName = "console" | "json" | "junit";
|
|
@@ -233,6 +233,7 @@ export function checkAssertions(expect: TestStepExpect, response: HttpResponse):
|
|
|
233
233
|
passed: allowed.includes(response.status),
|
|
234
234
|
actual: response.status,
|
|
235
235
|
expected: expect.status,
|
|
236
|
+
kind: "primary",
|
|
236
237
|
});
|
|
237
238
|
}
|
|
238
239
|
|
|
@@ -243,6 +244,7 @@ export function checkAssertions(expect: TestStepExpect, response: HttpResponse):
|
|
|
243
244
|
passed: response.duration_ms <= expect.duration,
|
|
244
245
|
actual: response.duration_ms,
|
|
245
246
|
expected: expect.duration,
|
|
247
|
+
kind: "auxiliary",
|
|
246
248
|
});
|
|
247
249
|
}
|
|
248
250
|
|
|
@@ -256,12 +258,14 @@ export function checkAssertions(expect: TestStepExpect, response: HttpResponse):
|
|
|
256
258
|
passed: actual === rule,
|
|
257
259
|
actual,
|
|
258
260
|
expected: rule,
|
|
261
|
+
kind: "auxiliary",
|
|
259
262
|
});
|
|
260
263
|
} else {
|
|
261
264
|
// AssertionRule in header — supports capture and other checks
|
|
262
265
|
const ruleResults = checkRule(key, rule, actual).map(r => ({
|
|
263
266
|
...r,
|
|
264
267
|
field: r.field.replace(/^body\./, "headers."),
|
|
268
|
+
kind: "auxiliary" as const,
|
|
265
269
|
}));
|
|
266
270
|
results.push(...ruleResults);
|
|
267
271
|
}
|
|
@@ -278,7 +282,11 @@ export function checkAssertions(expect: TestStepExpect, response: HttpResponse):
|
|
|
278
282
|
} else {
|
|
279
283
|
actual = getByPath(response.body_parsed, path);
|
|
280
284
|
}
|
|
281
|
-
|
|
285
|
+
const bodyResults = checkRule(path, rule, actual).map((r) => ({
|
|
286
|
+
...r,
|
|
287
|
+
kind: "primary" as const,
|
|
288
|
+
}));
|
|
289
|
+
results.push(...bodyResults);
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
@@ -324,3 +332,52 @@ export function extractCaptures(
|
|
|
324
332
|
|
|
325
333
|
return captures;
|
|
326
334
|
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Find capture rules whose path didn't resolve in the response, returning
|
|
338
|
+
* `{ var, source, path }` per miss. Surfaced as auxiliary assertion failures
|
|
339
|
+
* so the user sees "captures: {}" with a reason instead of silent emptiness —
|
|
340
|
+
* the empty-captures pitfall is the #1 footgun in CRUD chains because the
|
|
341
|
+
* downstream step is silently skipped or runs with `undefined`. TASK-256.
|
|
342
|
+
*/
|
|
343
|
+
export function findMissedCaptures(
|
|
344
|
+
bodyRules: Record<string, AssertionRule> | undefined,
|
|
345
|
+
responseBody: unknown,
|
|
346
|
+
headerRules?: Record<string, string | AssertionRule>,
|
|
347
|
+
responseHeaders?: Record<string, string>,
|
|
348
|
+
): Array<{ var: string; source: "body" | "header"; path: string }> {
|
|
349
|
+
const misses: Array<{ var: string; source: "body" | "header"; path: string }> = [];
|
|
350
|
+
|
|
351
|
+
if (bodyRules) {
|
|
352
|
+
for (const [path, rule] of Object.entries(bodyRules)) {
|
|
353
|
+
if (!rule.capture) continue;
|
|
354
|
+
if (responseBody === undefined) {
|
|
355
|
+
misses.push({ var: rule.capture, source: "body", path });
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
let value: unknown;
|
|
359
|
+
if (path === "_body") {
|
|
360
|
+
value = responseBody;
|
|
361
|
+
} else if (path.startsWith("_body.")) {
|
|
362
|
+
value = getByPath(responseBody, path.slice(6));
|
|
363
|
+
} else {
|
|
364
|
+
value = getByPath(responseBody, path);
|
|
365
|
+
}
|
|
366
|
+
if (value === undefined) {
|
|
367
|
+
misses.push({ var: rule.capture, source: "body", path });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (headerRules) {
|
|
373
|
+
for (const [key, rule] of Object.entries(headerRules)) {
|
|
374
|
+
if (typeof rule === "string" || !rule.capture) continue;
|
|
375
|
+
const value = responseHeaders ? responseHeaders[key.toLowerCase()] : undefined;
|
|
376
|
+
if (value === undefined) {
|
|
377
|
+
misses.push({ var: rule.capture, source: "header", path: key });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return misses;
|
|
383
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARV-8 (m-15): bounded async-pool for `zond checks run --workers N`.
|
|
3
|
+
*
|
|
4
|
+
* Cooperative concurrency on a single Bun event loop — no threading,
|
|
5
|
+
* no Workers, just N coroutines that pull from a shared cursor. The
|
|
6
|
+
* design choice is deliberate:
|
|
7
|
+
*
|
|
8
|
+
* * Threads/Workers would need request-context plumbing (auth headers,
|
|
9
|
+
* rate-limiter state, per-run schema validators are not transferable
|
|
10
|
+
* without serialization), and Bun's HTTP client is already
|
|
11
|
+
* non-blocking — so cooperative concurrency is faster *and* simpler.
|
|
12
|
+
* * `Promise.all(items.map(fn))` would saturate at items.length, which
|
|
13
|
+
* defeats the rate-limiter and tends to drown small mock servers.
|
|
14
|
+
*
|
|
15
|
+
* `runPool` preserves input order in the result array — callers (the
|
|
16
|
+
* checks runner, mainly) want to merge per-op findings deterministically
|
|
17
|
+
* regardless of which worker finished first.
|
|
18
|
+
*/
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
|
|
21
|
+
const WORKERS_MIN = 1;
|
|
22
|
+
const WORKERS_MAX = 64;
|
|
23
|
+
/** `--workers auto` ceiling — beyond ~8 the gains on a typical mock
|
|
24
|
+
* server are dominated by network/IO contention, not parallelism. */
|
|
25
|
+
const WORKERS_AUTO_CEILING = 8;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run `fn` over `items` with at most `workers` in-flight at once.
|
|
29
|
+
*
|
|
30
|
+
* Results are returned in input order, *not* completion order — keep
|
|
31
|
+
* call-sites that compose findings/snapshots deterministic.
|
|
32
|
+
*
|
|
33
|
+
* Errors propagate: the first rejection cancels remaining workers'
|
|
34
|
+
* dispatch (they finish their in-flight task and exit). Caller should
|
|
35
|
+
* `try/catch` if it wants partial results — none of zond's callers do
|
|
36
|
+
* today (a runner crash is fatal anyway).
|
|
37
|
+
*/
|
|
38
|
+
export async function runPool<T, R>(
|
|
39
|
+
items: readonly T[],
|
|
40
|
+
workers: number,
|
|
41
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
42
|
+
): Promise<R[]> {
|
|
43
|
+
if (items.length === 0) return [];
|
|
44
|
+
// Effective worker count is clamped both ways: never more than items
|
|
45
|
+
// (idle workers waste a closure), never below 1 (else nothing runs).
|
|
46
|
+
const effective = Math.max(1, Math.min(workers, items.length));
|
|
47
|
+
// Sequential fast-path — preserves the *exact* old behaviour
|
|
48
|
+
// (microtask ordering, error timing) for AC #4 backward-compat.
|
|
49
|
+
if (effective === 1) {
|
|
50
|
+
const out: R[] = new Array(items.length);
|
|
51
|
+
for (let i = 0; i < items.length; i++) {
|
|
52
|
+
out[i] = await fn(items[i]!, i);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
const out: R[] = new Array(items.length);
|
|
57
|
+
let cursor = 0;
|
|
58
|
+
let aborted: unknown = null;
|
|
59
|
+
async function worker(): Promise<void> {
|
|
60
|
+
while (true) {
|
|
61
|
+
if (aborted !== null) return;
|
|
62
|
+
const i = cursor++;
|
|
63
|
+
if (i >= items.length) return;
|
|
64
|
+
try {
|
|
65
|
+
out[i] = await fn(items[i]!, i);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// First rejection wins — store it, drain in-flight tasks, then
|
|
68
|
+
// re-throw at the join point.
|
|
69
|
+
if (aborted === null) aborted = err;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const swarm: Promise<void>[] = [];
|
|
75
|
+
for (let w = 0; w < effective; w++) swarm.push(worker());
|
|
76
|
+
await Promise.all(swarm);
|
|
77
|
+
if (aborted !== null) throw aborted;
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse a `--workers` flag value:
|
|
83
|
+
*
|
|
84
|
+
* undefined → 1 (backward-compat default — AC #4)
|
|
85
|
+
* "auto" / "AUTO"→ min(cpus, WORKERS_AUTO_CEILING) (AC #5)
|
|
86
|
+
* numeric → clamp [1, 64] (AC #5)
|
|
87
|
+
* anything else → throws (caller maps to a friendly CLI error)
|
|
88
|
+
*/
|
|
89
|
+
export function parseWorkers(value: string | number | undefined): number {
|
|
90
|
+
if (value === undefined || value === null || value === "") return 1;
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
const trimmed = value.trim().toLowerCase();
|
|
93
|
+
if (trimmed === "auto") {
|
|
94
|
+
return Math.max(WORKERS_MIN, Math.min(os.cpus().length, WORKERS_AUTO_CEILING));
|
|
95
|
+
}
|
|
96
|
+
const n = Number.parseInt(trimmed, 10);
|
|
97
|
+
if (!Number.isFinite(n)) throw new Error(`Invalid --workers value: "${value}"`);
|
|
98
|
+
return Math.max(WORKERS_MIN, Math.min(n, WORKERS_MAX));
|
|
99
|
+
}
|
|
100
|
+
if (!Number.isFinite(value)) throw new Error(`Invalid --workers value: ${value}`);
|
|
101
|
+
return Math.max(WORKERS_MIN, Math.min(Math.trunc(value), WORKERS_MAX));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const WORKERS_LIMITS = {
|
|
105
|
+
min: WORKERS_MIN,
|
|
106
|
+
max: WORKERS_MAX,
|
|
107
|
+
autoCeiling: WORKERS_AUTO_CEILING,
|
|
108
|
+
} as const;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic for "auth-shaped" endpoint paths used by `--safe` mode and
|
|
3
|
+
* the env-symptom diagnostics in `db-analysis.ts`. Anything matching is
|
|
4
|
+
* treated as a login/refresh route — `--safe` whitelists it for live
|
|
5
|
+
* runs, and the diagnostics flag concentrated POST failures here as
|
|
6
|
+
* `auth_required` rather than per-endpoint bugs.
|
|
7
|
+
*/
|
|
8
|
+
export const AUTH_PATH_RE = /\/(auth|login|signin|token|oauth)\b/i;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-116: detect CI context (commit sha, branch, trigger) from common
|
|
3
|
+
* environment variables. Returns `null` for fields when nothing is present
|
|
4
|
+
* — caller decides whether to default `trigger` to `"manual"`.
|
|
5
|
+
*
|
|
6
|
+
* Supported providers (autodetected — no opt-in required):
|
|
7
|
+
* GitHub Actions GITHUB_ACTIONS=true, GITHUB_SHA, GITHUB_REF_NAME
|
|
8
|
+
* GitLab CI GITLAB_CI=true, CI_COMMIT_SHA, CI_COMMIT_REF_NAME
|
|
9
|
+
* CircleCI CIRCLECI=true, CIRCLE_SHA1, CIRCLE_BRANCH
|
|
10
|
+
* Buildkite BUILDKITE=true, BUILDKITE_COMMIT, BUILDKITE_BRANCH
|
|
11
|
+
* Jenkins JENKINS_URL set, GIT_COMMIT, BRANCH_NAME / GIT_BRANCH
|
|
12
|
+
* Generic CI=true triggers `trigger=ci` even when no provider
|
|
13
|
+
* matches — caller can still pass nullable commit/branch.
|
|
14
|
+
*
|
|
15
|
+
* Manual override is via the explicit `--commit-sha` / `--branch` /
|
|
16
|
+
* `--trigger` flags or the env vars `ZOND_COMMIT_SHA` / `ZOND_BRANCH` /
|
|
17
|
+
* `ZOND_TRIGGER`. These win over autodetection.
|
|
18
|
+
*/
|
|
19
|
+
export interface CiContext {
|
|
20
|
+
trigger: "ci" | "manual";
|
|
21
|
+
commit_sha: string | null;
|
|
22
|
+
branch: string | null;
|
|
23
|
+
/** Provider tag (github-actions / gitlab-ci / circleci / …) for diagnostics. */
|
|
24
|
+
provider: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function detectCiContext(env: NodeJS.ProcessEnv = process.env): CiContext {
|
|
28
|
+
const overrideCommit = env.ZOND_COMMIT_SHA?.trim() || null;
|
|
29
|
+
const overrideBranch = env.ZOND_BRANCH?.trim() || null;
|
|
30
|
+
const overrideTrigger = env.ZOND_TRIGGER?.trim() || null;
|
|
31
|
+
|
|
32
|
+
let provider: string | null = null;
|
|
33
|
+
let commit: string | null = null;
|
|
34
|
+
let branch: string | null = null;
|
|
35
|
+
|
|
36
|
+
if (env.GITHUB_ACTIONS === "true") {
|
|
37
|
+
provider = "github-actions";
|
|
38
|
+
commit = env.GITHUB_SHA?.trim() || null;
|
|
39
|
+
branch = env.GITHUB_REF_NAME?.trim() || env.GITHUB_HEAD_REF?.trim() || null;
|
|
40
|
+
} else if (env.GITLAB_CI === "true") {
|
|
41
|
+
provider = "gitlab-ci";
|
|
42
|
+
commit = env.CI_COMMIT_SHA?.trim() || null;
|
|
43
|
+
branch = env.CI_COMMIT_REF_NAME?.trim() || null;
|
|
44
|
+
} else if (env.CIRCLECI === "true") {
|
|
45
|
+
provider = "circleci";
|
|
46
|
+
commit = env.CIRCLE_SHA1?.trim() || null;
|
|
47
|
+
branch = env.CIRCLE_BRANCH?.trim() || null;
|
|
48
|
+
} else if (env.BUILDKITE === "true") {
|
|
49
|
+
provider = "buildkite";
|
|
50
|
+
commit = env.BUILDKITE_COMMIT?.trim() || null;
|
|
51
|
+
branch = env.BUILDKITE_BRANCH?.trim() || null;
|
|
52
|
+
} else if (env.JENKINS_URL) {
|
|
53
|
+
provider = "jenkins";
|
|
54
|
+
commit = env.GIT_COMMIT?.trim() || null;
|
|
55
|
+
branch = env.BRANCH_NAME?.trim() || env.GIT_BRANCH?.trim() || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const inferredCi = !!provider || env.CI === "true" || env.CI === "1";
|
|
59
|
+
const trigger: "ci" | "manual" =
|
|
60
|
+
overrideTrigger === "ci" || overrideTrigger === "manual"
|
|
61
|
+
? overrideTrigger
|
|
62
|
+
: inferredCi
|
|
63
|
+
? "ci"
|
|
64
|
+
: "manual";
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
trigger,
|
|
68
|
+
commit_sha: overrideCommit ?? commit,
|
|
69
|
+
branch: overrideBranch ?? branch,
|
|
70
|
+
provider: provider ?? (inferredCi ? "generic" : null),
|
|
71
|
+
};
|
|
72
|
+
}
|