repo-contract 0.3.0 → 0.3.2
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 +23 -0
- package/README.md +46 -0
- package/dist/.dts/config/define-repo-contract.d.ts +36 -0
- package/dist/.dts/config/define-repo-contract.d.ts.map +1 -0
- package/dist/.dts/config/tokenize-command.d.ts +33 -0
- package/dist/.dts/config/tokenize-command.d.ts.map +1 -0
- package/dist/.dts/config/validate-config.d.ts +32 -0
- package/dist/.dts/config/validate-config.d.ts.map +1 -0
- package/dist/.dts/errors.d.ts +172 -0
- package/dist/.dts/errors.d.ts.map +1 -0
- package/dist/.dts/evidence/build-evidence.d.ts +26 -0
- package/dist/.dts/evidence/build-evidence.d.ts.map +1 -0
- package/dist/.dts/execution/abort-signals.d.ts +29 -0
- package/dist/.dts/execution/abort-signals.d.ts.map +1 -0
- package/dist/.dts/execution/concurrency-pool.d.ts +14 -0
- package/dist/.dts/execution/concurrency-pool.d.ts.map +1 -0
- package/dist/.dts/execution/dependency-scheduler.d.ts +30 -0
- package/dist/.dts/execution/dependency-scheduler.d.ts.map +1 -0
- package/dist/.dts/execution/process-tree.d.ts +59 -0
- package/dist/.dts/execution/process-tree.d.ts.map +1 -0
- package/dist/.dts/execution/run-checks.d.ts +31 -0
- package/dist/.dts/execution/run-checks.d.ts.map +1 -0
- package/dist/.dts/execution/spawn-check.d.ts +49 -0
- package/dist/.dts/execution/spawn-check.d.ts.map +1 -0
- package/dist/.dts/index.d.ts +14 -0
- package/dist/.dts/index.d.ts.map +1 -0
- package/dist/.dts/parsing/format-schema-issues.d.ts +11 -0
- package/dist/.dts/parsing/format-schema-issues.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-json.d.ts +8 -0
- package/dist/.dts/parsing/parse-json.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-output.d.ts +25 -0
- package/dist/.dts/parsing/parse-output.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-text.d.ts +8 -0
- package/dist/.dts/parsing/parse-text.d.ts.map +1 -0
- package/dist/.dts/parsing/parse-yaml.d.ts +21 -0
- package/dist/.dts/parsing/parse-yaml.d.ts.map +1 -0
- package/dist/.dts/policy/run-policies.d.ts +38 -0
- package/dist/.dts/policy/run-policies.d.ts.map +1 -0
- package/dist/.dts/presets/arethetypeswrong.d.ts +39 -0
- package/dist/.dts/presets/arethetypeswrong.d.ts.map +1 -0
- package/dist/.dts/presets/broken-links.d.ts +16 -0
- package/dist/.dts/presets/broken-links.d.ts.map +1 -0
- package/dist/.dts/presets/commitlint.d.ts +22 -0
- package/dist/.dts/presets/commitlint.d.ts.map +1 -0
- package/dist/.dts/presets/dead-code.d.ts +23 -0
- package/dist/.dts/presets/dead-code.d.ts.map +1 -0
- package/dist/.dts/presets/duplication.d.ts +14 -0
- package/dist/.dts/presets/duplication.d.ts.map +1 -0
- package/dist/.dts/presets/e2e.d.ts +4 -0
- package/dist/.dts/presets/e2e.d.ts.map +1 -0
- package/dist/.dts/presets/format.d.ts +4 -0
- package/dist/.dts/presets/format.d.ts.map +1 -0
- package/dist/.dts/presets/index.d.ts +31 -0
- package/dist/.dts/presets/index.d.ts.map +1 -0
- package/dist/.dts/presets/license.d.ts +4 -0
- package/dist/.dts/presets/license.d.ts.map +1 -0
- package/dist/.dts/presets/lint.d.ts +20 -0
- package/dist/.dts/presets/lint.d.ts.map +1 -0
- package/dist/.dts/presets/markdownlint.d.ts +23 -0
- package/dist/.dts/presets/markdownlint.d.ts.map +1 -0
- package/dist/.dts/presets/publint.d.ts +13 -0
- package/dist/.dts/presets/publint.d.ts.map +1 -0
- package/dist/.dts/presets/security-deps.d.ts +4 -0
- package/dist/.dts/presets/security-deps.d.ts.map +1 -0
- package/dist/.dts/presets/security-secrets.d.ts +4 -0
- package/dist/.dts/presets/security-secrets.d.ts.map +1 -0
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts +13 -0
- package/dist/.dts/presets/shared/error-warning-pass-policy.d.ts.map +1 -0
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts +23 -0
- package/dist/.dts/presets/shared/exit-code-fail-rationale.d.ts.map +1 -0
- package/dist/.dts/presets/shared/missing-dependency.d.ts +19 -0
- package/dist/.dts/presets/shared/missing-dependency.d.ts.map +1 -0
- package/dist/.dts/presets/shared/read-json-report.d.ts +33 -0
- package/dist/.dts/presets/shared/read-json-report.d.ts.map +1 -0
- package/dist/.dts/presets/shared/terminal-status.d.ts +25 -0
- package/dist/.dts/presets/shared/terminal-status.d.ts.map +1 -0
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts +20 -0
- package/dist/.dts/presets/shared/vitest-json-policy.d.ts.map +1 -0
- package/dist/.dts/presets/stylelint.d.ts +17 -0
- package/dist/.dts/presets/stylelint.d.ts.map +1 -0
- package/dist/.dts/presets/test.d.ts +4 -0
- package/dist/.dts/presets/test.d.ts.map +1 -0
- package/dist/.dts/presets/typecheck.d.ts +4 -0
- package/dist/.dts/presets/typecheck.d.ts.map +1 -0
- package/dist/.dts/run-repo-contract.d.ts +38 -0
- package/dist/.dts/run-repo-contract.d.ts.map +1 -0
- package/dist/.dts/standard-schema/types.d.ts +76 -0
- package/dist/.dts/standard-schema/types.d.ts.map +1 -0
- package/dist/.dts/types.d.ts +424 -0
- package/dist/.dts/types.d.ts.map +1 -0
- package/dist/index.cjs +99 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +99 -5
- package/dist/index.js.map +1 -1
- package/dist/presets.d.cts +1 -0
- package/dist/presets.d.ts +1 -0
- package/package.json +1 -1
- package/schemas/evidence.schema.json +2 -2
- package/src/config/validate-config.ts +55 -1
- package/src/errors.ts +26 -0
- package/src/evidence/build-evidence.ts +6 -1
- package/src/index.ts +3 -0
- package/src/parsing/format-schema-issues.ts +56 -0
- package/src/parsing/parse-output.ts +50 -3
- package/src/standard-schema/types.ts +93 -0
- package/src/types.ts +48 -12
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type surface for repo-contract. Evidence describes what happened;
|
|
3
|
+
* Verdict describes whether it was acceptable. They are deliberately
|
|
4
|
+
* separate concepts (see specs/architecture.md) even though
|
|
5
|
+
* `runRepoContract` returns both together.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChildProcess, SpawnOptions, SpawnSyncOptions, SpawnSyncReturns } from "node:child_process";
|
|
8
|
+
import type { StandardSchemaV1 } from "./standard-schema/types.js";
|
|
9
|
+
/** Output interpretation a check can explicitly request. No format requested means no parsing -- the consumer gets raw stdout/stderr only. */
|
|
10
|
+
export type OutputFormat = "json" | "yaml" | "text";
|
|
11
|
+
/**
|
|
12
|
+
* Why a check's process ended up in its terminal state. `"completed"` means
|
|
13
|
+
* the process ran to exit on its own -- the exit code may still be
|
|
14
|
+
* non-zero, and that is for the check's policy to interpret, never this
|
|
15
|
+
* package. The other five values all mean the process did not exit on its
|
|
16
|
+
* own; repo-contract terminated it, or it was terminated for a reason
|
|
17
|
+
* repo-contract can observe but did not cause.
|
|
18
|
+
*
|
|
19
|
+
* `"signaled"` specifically means a signal repo-contract did *not* itself request -- an
|
|
20
|
+
* externally-caused termination. A check killed because the *host* process running repo-contract
|
|
21
|
+
* received its own SIGINT/SIGTERM (see `run-checks.ts`'s termination-handler cleanup) is instead
|
|
22
|
+
* `"host_terminated"`: repo-contract did request that signal, just not via `options.signal` or
|
|
23
|
+
* `timeoutMs` (see `"aborted"`/`"timed_out"`), so it must not be conflated with an externally-caused
|
|
24
|
+
* `"signaled"`.
|
|
25
|
+
*/
|
|
26
|
+
export type CheckStatus = "completed" | "timed_out" | "signaled" | "host_terminated" | "spawn_error" | "aborted";
|
|
27
|
+
/** A requested parse of a check's stdout succeeded. */
|
|
28
|
+
export interface ParsedOutputSuccess<T> {
|
|
29
|
+
/** The format that was requested and successfully parsed. */
|
|
30
|
+
readonly format: OutputFormat;
|
|
31
|
+
/** Always `true`. */
|
|
32
|
+
readonly success: true;
|
|
33
|
+
/** The parsed value. */
|
|
34
|
+
readonly value: T;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A requested parse of a check's stdout failed. The raw stdout on the
|
|
38
|
+
* parent `CheckEvidence` is preserved unchanged -- a parse failure is never
|
|
39
|
+
* silently reinterpreted or discarded.
|
|
40
|
+
*/
|
|
41
|
+
export interface ParsedOutputFailure {
|
|
42
|
+
/** The format that was requested (and failed to parse). */
|
|
43
|
+
readonly format: OutputFormat;
|
|
44
|
+
/** Always `false`. */
|
|
45
|
+
readonly success: false;
|
|
46
|
+
/** The parse error's message. */
|
|
47
|
+
readonly error: string;
|
|
48
|
+
}
|
|
49
|
+
/** The result of a check's requested output-format parse: either a successful `ParsedOutputSuccess`, or a `ParsedOutputFailure`. */
|
|
50
|
+
export type ParsedOutput<T> = ParsedOutputSuccess<T> | ParsedOutputFailure;
|
|
51
|
+
/**
|
|
52
|
+
* What actually happened when one configured check ran. `output` is present
|
|
53
|
+
* only if that check's config requested a format, and is otherwise
|
|
54
|
+
* `undefined` -- a policy narrows with `ctx.result.output?.success` (or an
|
|
55
|
+
* `if (ctx.result.output) { ... }` guard) before reading `.value`/`.error`.
|
|
56
|
+
*
|
|
57
|
+
* `output.value` is the value produced by the configured `format` parser, optionally validated
|
|
58
|
+
* and (if a schema transforms/coerces it) replaced by `output.schema` (see
|
|
59
|
+
* `CheckDefinitionConfig.output.schema`) -- so it is not necessarily the raw parser result once a
|
|
60
|
+
* check's config supplies a schema. It is typed `unknown` for every format regardless, including
|
|
61
|
+
* `"text"` (even though `parseText` always produces a `string` at runtime) and regardless of
|
|
62
|
+
* whether a schema was supplied -- neither repo-contract nor TypeScript's generic inference can
|
|
63
|
+
* reliably carry a specific check's own literal `output.format` (or a specific `schema`'s own
|
|
64
|
+
* inferred output type) through to that same check's `policy` parameter once several checks with
|
|
65
|
+
* heterogeneous formats live together in one `checks` record (a real TypeScript inference
|
|
66
|
+
* limitation hit and confirmed during implementation, not a hypothetical -- see specs/decisions/
|
|
67
|
+
* for the isolated repro). This is a deliberate, acknowledged tradeoff, not a gap this package
|
|
68
|
+
* is attempting to close: a schema still gives its own author real compile-time input/output
|
|
69
|
+
* typing via `StandardSchemaV1<Input, Output>` for their own code, just not threaded through this
|
|
70
|
+
* shared `CheckEvidence` shape. A policy author narrows or casts `.value` themselves, exactly as
|
|
71
|
+
* they already must for `"json"`/`"yaml"` with no schema supplied.
|
|
72
|
+
*/
|
|
73
|
+
export interface CheckEvidence {
|
|
74
|
+
/** The executable that was actually spawned (after tokenization, if `run` was a string). */
|
|
75
|
+
readonly command: string;
|
|
76
|
+
/** The arguments passed to `command`, exactly as spawned. */
|
|
77
|
+
readonly args: readonly string[];
|
|
78
|
+
/** ISO 8601 timestamp of when the process was spawned. */
|
|
79
|
+
readonly startedAt: string;
|
|
80
|
+
/** ISO 8601 timestamp of when the process reached its terminal state. */
|
|
81
|
+
readonly completedAt: string;
|
|
82
|
+
/** Wall-clock time from spawn to termination, in milliseconds. */
|
|
83
|
+
readonly durationMs: number;
|
|
84
|
+
/** `null` when the process never exited normally -- see `signal` and `status`. */
|
|
85
|
+
readonly exitCode: number | null;
|
|
86
|
+
/** The signal that terminated the process, if any. `null` for a normal exit or a spawn failure. */
|
|
87
|
+
readonly signal: NodeJS.Signals | null;
|
|
88
|
+
/** The process's raw standard output, captured verbatim up to an internal size cap (10 MiB); content beyond the cap is replaced with a truncation marker. */
|
|
89
|
+
readonly stdout: string;
|
|
90
|
+
/** The process's raw standard error, captured verbatim up to an internal size cap (10 MiB); content beyond the cap is replaced with a truncation marker. */
|
|
91
|
+
readonly stderr: string;
|
|
92
|
+
/** Why the process reached its terminal state; see `CheckStatus`. */
|
|
93
|
+
readonly status: CheckStatus;
|
|
94
|
+
/** Populated only for `status === "spawn_error"` -- the underlying Node error message (e.g. "spawn foo ENOENT"). Never populated for any other status. */
|
|
95
|
+
readonly spawnError?: string;
|
|
96
|
+
/** Populated only for `status === "spawn_error"` -- the underlying Node `ErrnoException`'s structured `.code` (e.g. `"ENOENT"`, `"EACCES"`), when Node provides one. Never populated for any other status. Distinguishes "the executable does not exist" from other spawn failures (permission denied, invalid executable format, etc.) without parsing `spawnError`'s free-text message. */
|
|
97
|
+
readonly spawnErrorCode?: string;
|
|
98
|
+
/**
|
|
99
|
+
* The parsed interpretation of `stdout`, present only if this check's config requested a
|
|
100
|
+
* `format`. If that config also supplied `output.schema`, a successful parse is additionally
|
|
101
|
+
* validated (and possibly transformed) by that schema before landing here -- see this
|
|
102
|
+
* interface's own doc comment above and `CheckDefinitionConfig.output.schema`.
|
|
103
|
+
*/
|
|
104
|
+
readonly output?: ParsedOutput<unknown>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Versioned, immutable record of one complete `runRepoContract` execution --
|
|
108
|
+
* every configured check's evidence, plus timing for the run as a whole.
|
|
109
|
+
* Says nothing about whether any of it was acceptable; see `Verdict`.
|
|
110
|
+
* Additive fields are a compatible change; changing or removing an existing
|
|
111
|
+
* field requires bumping this version number (see VERSIONING.md).
|
|
112
|
+
*/
|
|
113
|
+
export interface Evidence<TChecks extends CheckSchema = CheckSchema> {
|
|
114
|
+
/** Schema version of this shape; see VERSIONING.md. */
|
|
115
|
+
readonly version: 1;
|
|
116
|
+
/** ISO 8601 timestamp of when the run began. */
|
|
117
|
+
readonly startedAt: string;
|
|
118
|
+
/** ISO 8601 timestamp of when the last check finished. */
|
|
119
|
+
readonly completedAt: string;
|
|
120
|
+
/** Wall-clock time for the run as a whole, in milliseconds. */
|
|
121
|
+
readonly durationMs: number;
|
|
122
|
+
/** Each configured check's own evidence, keyed by check id. */
|
|
123
|
+
readonly checks: {
|
|
124
|
+
readonly [K in keyof TChecks]: CheckEvidence;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* What a check's `policy` function is called with. `result` is that check's
|
|
129
|
+
* own evidence; `evidence` is the complete run's evidence, including every
|
|
130
|
+
* sibling check -- present so a policy can make cross-check decisions (e.g.
|
|
131
|
+
* "only enforce the mutation-score threshold if the tests check passed").
|
|
132
|
+
* By the time any policy runs, every check has already finished executing
|
|
133
|
+
* and every check's evidence has already been assembled -- no policy ever
|
|
134
|
+
* observes a partially-populated `evidence` (see specs/architecture.md).
|
|
135
|
+
*/
|
|
136
|
+
export interface PolicyContext<TChecks extends CheckSchema = CheckSchema> {
|
|
137
|
+
/** This check's own evidence. */
|
|
138
|
+
readonly result: CheckEvidence;
|
|
139
|
+
/** The complete run's evidence, including every sibling check. */
|
|
140
|
+
readonly evidence: Evidence<TChecks>;
|
|
141
|
+
/**
|
|
142
|
+
* This check's own declared `dependsOn` dependencies' evidence, keyed by
|
|
143
|
+
* check id -- a convenience view, fully derivable from `evidence.checks`
|
|
144
|
+
* plus this check's own `dependsOn`, provided so no policy has to do that
|
|
145
|
+
* lookup itself. `{}` for a check with no `dependsOn` -- always an
|
|
146
|
+
* object, never `undefined`, matching how `evidence.checks` is already
|
|
147
|
+
* used today (a policy narrows a specific key's presence, never the
|
|
148
|
+
* field itself). Evidence only, not policy outcomes -- a dependency's
|
|
149
|
+
* policy result stays visible only via the top-level `Verdict`, exactly
|
|
150
|
+
* as today; this keeps policy evaluation itself fully parallel and
|
|
151
|
+
* unaffected by `dependsOn`.
|
|
152
|
+
*/
|
|
153
|
+
readonly dependencies: Readonly<Record<string, CheckEvidence>>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* `"pass"`: the repository-owned policy evaluated the captured evidence as
|
|
157
|
+
* satisfying its configured requirements. `"fail"`: the evidence did not
|
|
158
|
+
* satisfy them. `"warn"`: the evidence does not violate the policy's
|
|
159
|
+
* blocking requirements, but the policy has intentionally decided the
|
|
160
|
+
* condition is materially relevant and wants it surfaced -- not a synonym
|
|
161
|
+
* for "minor failure"; a `warn` never fails `Verdict.passed` (see
|
|
162
|
+
* `runPolicies` in `src/policy/run-policies.ts`).
|
|
163
|
+
*/
|
|
164
|
+
export type PolicyOutcome = "pass" | "fail" | "warn";
|
|
165
|
+
/**
|
|
166
|
+
* A repository-owned policy's interpretation of one check's captured
|
|
167
|
+
* evidence -- fully JSON-serializable (a plain object of primitives only,
|
|
168
|
+
* never an `Error`, class instance, function, or tool-specific object) so it
|
|
169
|
+
* can be persisted, transmitted, aggregated across parallel checks, and
|
|
170
|
+
* consumed directly by a human or an AI without rerunning anything.
|
|
171
|
+
*
|
|
172
|
+
* `rationale` is mandatory and must contain enough actionable detail --
|
|
173
|
+
* specific file/line locations, rule ids, test names, counts -- for a
|
|
174
|
+
* consumer to understand *why* the policy reached its outcome from this
|
|
175
|
+
* value alone. A rationale like "see output above" or "check the report for
|
|
176
|
+
* details" defeats the purpose: it forces the consumer back to raw,
|
|
177
|
+
* unstructured command output, exactly what this type exists to avoid. See
|
|
178
|
+
* specs/architecture.md for the evidence/rationale/judgment distinction this
|
|
179
|
+
* type is built around: evidence answers "what happened?", `rationale`
|
|
180
|
+
* answers "what does the repository's policy conclude about what
|
|
181
|
+
* happened?", and a policy's `outcome` is not the final word -- a human or
|
|
182
|
+
* AI consumer still makes the final judgment call using both.
|
|
183
|
+
*/
|
|
184
|
+
export interface PolicyResult {
|
|
185
|
+
/** The policy's pass/fail/warn decision. */
|
|
186
|
+
readonly outcome: PolicyOutcome;
|
|
187
|
+
/** Why the policy reached `outcome`, in enough detail to act on without rerunning anything. */
|
|
188
|
+
readonly rationale: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* A repository-owned decision about whether one check's evidence is
|
|
192
|
+
* acceptable. Returns a `PolicyResult` -- never a bare boolean or string --
|
|
193
|
+
* so a policy can communicate more than pass/fail even when the run is
|
|
194
|
+
* otherwise acceptable (see `PolicyResult`/`PolicyOutcome`). May be
|
|
195
|
+
* synchronous or return a `Promise`. repo-contract does not interpret
|
|
196
|
+
* `rationale` beyond storing and surfacing it verbatim; the package has no
|
|
197
|
+
* opinion about what makes a check pass, fail, or warrant a `warn`.
|
|
198
|
+
*/
|
|
199
|
+
export type Policy<TChecks extends CheckSchema = CheckSchema> = (ctx: PolicyContext<TChecks>) => PolicyResult | Promise<PolicyResult>;
|
|
200
|
+
/** One partially configured check: how to run it, how (if at all) to interpret its output, and the policy that decides whether its evidence is acceptable. */
|
|
201
|
+
export interface CheckDefinitionConfig {
|
|
202
|
+
/**
|
|
203
|
+
* The command to run. A `string` is tokenized into executable + arguments
|
|
204
|
+
* without invoking a shell -- shell operators (`;`, `&`, `|`, backticks,
|
|
205
|
+
* `$(`, `<`, `>`, newlines) are rejected with a configuration error rather
|
|
206
|
+
* than silently passed through as literal arguments, since a string
|
|
207
|
+
* containing one almost always reflects a mistaken assumption that shell
|
|
208
|
+
* interpretation is happening. Glob characters (`*`, `?`, `~`, `[`, `]`,
|
|
209
|
+
* `{`, `}`) are NOT rejected -- they are common, legitimate literal argv
|
|
210
|
+
* content (e.g. `eslint "src/**\/*.ts"`) that many CLI tools glob-expand
|
|
211
|
+
* internally, and carry no shell-injection risk when no shell is invoked.
|
|
212
|
+
* An array bypasses tokenization entirely and is used as argv verbatim.
|
|
213
|
+
*/
|
|
214
|
+
readonly run: string | readonly string[];
|
|
215
|
+
/**
|
|
216
|
+
* Opt into real shell execution instead of the safe argv-only default.
|
|
217
|
+
* When `true` (or left unset with `RepoContractConfig.shell: true` as the
|
|
218
|
+
* run-wide default -- see that field), `run` must be a `string` and is
|
|
219
|
+
* passed to the platform shell as-is (via the supplied `Spawner`'s own
|
|
220
|
+
* `shell` option) -- shell metacharacter rejection does not apply. See
|
|
221
|
+
* SECURITY.md before enabling this.
|
|
222
|
+
*/
|
|
223
|
+
readonly shell?: boolean;
|
|
224
|
+
/** Working directory for the spawned process. Defaults to the current process's `cwd`. */
|
|
225
|
+
readonly cwd?: string;
|
|
226
|
+
/** Additional environment variables for the spawned process, applied on top of (or, if `inheritEnv` is `false`, instead of) the inherited environment. */
|
|
227
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
228
|
+
/** Whether the spawned process inherits `process.env`. Defaults to `true` -- most check commands (npm scripts, locally-installed CLIs) need `PATH` and similar to resolve at all. Set to `false` for a minimal environment containing only `env` (plus whatever the OS itself always provides). */
|
|
229
|
+
readonly inheritEnv?: boolean;
|
|
230
|
+
/** Maximum time to let this check's process run before it is terminated and recorded with `status: "timed_out"`. No timeout by default. */
|
|
231
|
+
readonly timeoutMs?: number;
|
|
232
|
+
/** Request that stdout be parsed as this format. Omit for no parsing -- the consumer gets raw stdout/stderr only. */
|
|
233
|
+
readonly output?: {
|
|
234
|
+
readonly format: OutputFormat;
|
|
235
|
+
/**
|
|
236
|
+
* An optional Standard Schema-compliant validator (Zod, Valibot, ArkType, or any other
|
|
237
|
+
* implementation of https://standardschema.dev), run once the requested `format` parse
|
|
238
|
+
* itself succeeds. repo-contract never imports a schema library itself --
|
|
239
|
+
* `StandardSchemaV1` is hand-vendored (see `src/standard-schema/types.ts`) purely as a
|
|
240
|
+
* type-level contract, so accepting any consumer's own schema object costs zero new
|
|
241
|
+
* runtime or dev dependencies (see
|
|
242
|
+
* specs/decisions/0012-hand-vendored-standard-schema-support-for-optional-output-validation.md). A successful
|
|
243
|
+
* `~standard.validate()` result *replaces* the parsed value on `CheckEvidence.output.value`
|
|
244
|
+
* (letting a schema transform, not just check, its input); a failing result becomes an
|
|
245
|
+
* ordinary `ParsedOutputFailure`, indistinguishable in shape from a malformed-JSON/YAML
|
|
246
|
+
* parse failure -- both are "this check's output isn't what was expected," reported as
|
|
247
|
+
* data, never a throw (see `parseOutput`). `validate()` itself throwing or rejecting is a
|
|
248
|
+
* different case -- a bug in the supplied schema, not malformed output -- and surfaces as
|
|
249
|
+
* `StandardSchemaValidateThrewError` instead (see that error's own doc comment). Like every
|
|
250
|
+
* other format's `value`, `schema`'s inferred output type is *not* threaded to
|
|
251
|
+
* `CheckEvidence.output.value`'s declared type -- it stays `unknown`, for the same
|
|
252
|
+
* heterogeneous-checks-in-one-record TypeScript inference limitation already documented on
|
|
253
|
+
* `CheckEvidence` above; a policy still narrows or casts `.value` itself, now
|
|
254
|
+
* shaped/transformed by `schema` at runtime even though the type alone doesn't say so.
|
|
255
|
+
*/
|
|
256
|
+
readonly schema?: StandardSchemaV1;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* A full scheduling barrier at this check's own position in the `checks` object: it does not
|
|
260
|
+
* spawn until every check declared *earlier* has reached a terminal status (nothing "currently in
|
|
261
|
+
* flight" when its turn comes can be anything other than an earlier-declared check, since nothing
|
|
262
|
+
* declared later has even been reached yet), and every check declared *after* it waits for it in
|
|
263
|
+
* turn -- so nothing overlaps it in either direction. Purely a scheduling primitive for a check
|
|
264
|
+
* whose own tooling spawns concurrent workers that would otherwise contend with the rest of the
|
|
265
|
+
* run for machine resources (e.g. Stryker's own worker pool); it expresses no need for any other
|
|
266
|
+
* check's evidence, only for the machine to itself, and never appears in a policy's
|
|
267
|
+
* `ctx.dependencies` or in a partial `options.checks` run's transitive closure on its own (an
|
|
268
|
+
* explicit `dependsOn` alongside it still works exactly as it would on any other check). Defaults
|
|
269
|
+
* to `false` -- unaffected, fully-parallel scheduling in declaration order, unchanged. Two
|
|
270
|
+
* isolated checks are always sequential relative to each other (whichever is declared second
|
|
271
|
+
* waits for the first, as one of the "every check declared earlier" it's barred behind). See
|
|
272
|
+
* specs/decisions/0002-dependson-and-isolated-are-two-scheduling-primitives.md.
|
|
273
|
+
*/
|
|
274
|
+
readonly isolated?: boolean;
|
|
275
|
+
/** Decides whether this check's evidence is acceptable, once its process has finished running. */
|
|
276
|
+
readonly policy: Policy;
|
|
277
|
+
}
|
|
278
|
+
/** One fully configured check: how to run it, how (if at all) to interpret its output, and the policy that decides whether its evidence is acceptable. */
|
|
279
|
+
export interface CheckDefinition extends CheckDefinitionConfig {
|
|
280
|
+
/**
|
|
281
|
+
* Other check ids (from this same `checks` record) that must reach a
|
|
282
|
+
* terminal status -- not necessarily a passing policy -- before this
|
|
283
|
+
* check's own process is spawned. Execution ordering only: whether a
|
|
284
|
+
* dependency's *policy* passed is never consulted by repo-contract to
|
|
285
|
+
* decide whether this check's process spawns -- that is this check's
|
|
286
|
+
* own command or policy's decision (see `ctx.dependencies` on
|
|
287
|
+
* `PolicyContext`). Omit, or an empty array, for no dependencies -- the
|
|
288
|
+
* default, fully-parallel behavior, unchanged. Validated before any
|
|
289
|
+
* process spawns: every named id must exist in `checks`, a check cannot
|
|
290
|
+
* depend on itself, and -- since declaration order in the `checks` object
|
|
291
|
+
* doubles as the required topological order -- every named id must be
|
|
292
|
+
* declared *earlier* than the check declaring `dependsOn` on it (a
|
|
293
|
+
* forward reference throws `DependencyDeclaredLaterError`; a cycle is
|
|
294
|
+
* consequently impossible, since no edge can ever point forward). See
|
|
295
|
+
* specs/decisions/0002-dependson-and-isolated-are-two-scheduling-primitives.md.
|
|
296
|
+
*/
|
|
297
|
+
readonly dependsOn?: readonly string[];
|
|
298
|
+
}
|
|
299
|
+
/** The full set of checks in a `RepoContractConfig`, keyed by check id. */
|
|
300
|
+
export type CheckSchema = Record<string, CheckDefinition>;
|
|
301
|
+
/**
|
|
302
|
+
* Same shape as a check schema `T`, except each check's own `dependsOn` is
|
|
303
|
+
* narrowed from `readonly string[]` to only the *other* keys of that same
|
|
304
|
+
* `T` -- so a typo'd or self-referencing id fails to compile instead of
|
|
305
|
+
* only failing at runtime (`validate-config.ts` still enforces this at
|
|
306
|
+
* runtime too, for any config that reaches `runRepoContract` without having
|
|
307
|
+
* gone through `defineRepoContract`'s static checking first, e.g. one
|
|
308
|
+
* assembled dynamically from untyped data).
|
|
309
|
+
*
|
|
310
|
+
* Used as an additional constraint on `defineRepoContract`'s parameter,
|
|
311
|
+
* intersected with `RepoContractConfig<TChecks>` rather than substituted
|
|
312
|
+
* for it -- inferring `TChecks` from the *unwrapped* `RepoContractConfig<TChecks>`
|
|
313
|
+
* position first is what keeps every check's own `policy` callback
|
|
314
|
+
* contextually typed (a real TypeScript inference limitation: inferring
|
|
315
|
+
* `TChecks` directly from a mapped/conditional type over itself, as this
|
|
316
|
+
* type is, loses that contextual typing -- confirmed during implementation,
|
|
317
|
+
* not a hypothetical).
|
|
318
|
+
*/
|
|
319
|
+
export type ValidatedCheckSchema<T> = {
|
|
320
|
+
readonly [K in keyof T]: T[K] extends CheckDefinitionConfig ? Omit<T[K], "dependsOn"> & {
|
|
321
|
+
readonly dependsOn?: readonly (Exclude<keyof T, K> & string)[];
|
|
322
|
+
} : never;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Spawns a child process, given a resolved command, argv, and options --
|
|
326
|
+
* modeled directly on `node:child_process`'s own `spawn(command, args,
|
|
327
|
+
* options)` signature so both `node:child_process.spawn` and cross-spawn's
|
|
328
|
+
* exported `spawn` are valid, drop-in values with no adapter code required
|
|
329
|
+
* (see specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md).
|
|
330
|
+
* repo-contract treats whatever function is supplied as a trusted
|
|
331
|
+
* capability: it calls it with a resolved command/argv/options and does not
|
|
332
|
+
* inspect, wrap, or sanitize it -- the security properties of the spawned
|
|
333
|
+
* process are entirely the supplied function's own.
|
|
334
|
+
*/
|
|
335
|
+
export type Spawner = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
336
|
+
/**
|
|
337
|
+
* Synchronously spawns a child process and waits for it to exit -- modeled directly on
|
|
338
|
+
* `node:child_process`'s own `spawnSync(command, args, options)` signature, the same
|
|
339
|
+
* drop-in-compatibility approach as `Spawner`. Used only for `RepoContractConfig.killProcessTree`
|
|
340
|
+
* (Windows process-tree cleanup via `taskkill`, which fundamentally needs to run synchronously from
|
|
341
|
+
* a signal-handling context that cannot `await` anything else -- see
|
|
342
|
+
* specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md).
|
|
343
|
+
* `node:child_process.spawnSync` and cross-spawn's exported `sync` are both valid, drop-in values.
|
|
344
|
+
*/
|
|
345
|
+
export type SyncSpawner = (command: string, args: readonly string[], options: SpawnSyncOptions) => SpawnSyncReturns<Buffer | string>;
|
|
346
|
+
/** Top-level configuration passed to `defineRepoContract`/`runRepoContract`. */
|
|
347
|
+
export interface RepoContractConfig<TChecks extends CheckSchema = CheckSchema> {
|
|
348
|
+
/** Every check to run, keyed by check id. */
|
|
349
|
+
readonly checks: TChecks;
|
|
350
|
+
/** Maximum number of checks to execute concurrently. Defaults to `os.availableParallelism()`. Must be a positive integer. */
|
|
351
|
+
readonly concurrency?: number;
|
|
352
|
+
/**
|
|
353
|
+
* The trusted capability repo-contract calls to spawn every check's
|
|
354
|
+
* process -- e.g. `child_process.spawn` (from `"node:child_process"`) or
|
|
355
|
+
* cross-spawn's exported `spawn`. Required: repo-contract never imports a
|
|
356
|
+
* process-spawning implementation itself (see ADR 0011 above `Spawner`).
|
|
357
|
+
* `child_process.spawn` alone does not resolve Windows `.cmd`/`.bat`
|
|
358
|
+
* shims (most npm-installed CLI tools on Windows) without `shell: true`;
|
|
359
|
+
* pass cross-spawn instead for that correctness without enabling shell
|
|
360
|
+
* metacharacter interpretation -- cross-spawn is a spawn implementation
|
|
361
|
+
* choice, not a `shell: true` equivalent. See `shell` below.
|
|
362
|
+
*/
|
|
363
|
+
readonly spawn: Spawner;
|
|
364
|
+
/**
|
|
365
|
+
* The ambient environment repo-contract treats as inheritable by each
|
|
366
|
+
* check whose `inheritEnv` is not `false` (the default) -- typically
|
|
367
|
+
* `process.env`, passed straight through by reference (never copied
|
|
368
|
+
* internally) so a consumer that mutates `process.env` mid-run still sees
|
|
369
|
+
* that reflected in later-spawned checks, exactly as if repo-contract had
|
|
370
|
+
* read `process.env` itself. Required: repo-contract never reads
|
|
371
|
+
* `process.env` internally (see ADR 0011 above `Spawner`). Typed as
|
|
372
|
+
* `NodeJS.ProcessEnv` so `env: process.env` needs no casting.
|
|
373
|
+
*/
|
|
374
|
+
readonly env: NodeJS.ProcessEnv;
|
|
375
|
+
/**
|
|
376
|
+
* Global default for every check's own `shell` when that check doesn't
|
|
377
|
+
* set one itself (`check.shell ?? shell ?? false`). Defaults to `false`,
|
|
378
|
+
* the safe argv-only mode -- unrelated to which `spawn` is supplied; see
|
|
379
|
+
* `spawn`'s own doc comment for that distinction.
|
|
380
|
+
*/
|
|
381
|
+
readonly shell?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* The trusted capability repo-contract calls, on Windows only, to forcibly terminate a check's
|
|
384
|
+
* entire process tree (not just its immediate process) on a timeout, an aborted run, or a
|
|
385
|
+
* host-process SIGINT/SIGTERM -- e.g. `child_process.spawnSync` (from `"node:child_process"`) or
|
|
386
|
+
* cross-spawn's exported `sync`. Optional, unlike `spawn`/`env`: when omitted, Windows cleanup
|
|
387
|
+
* falls back to terminating only the check's own immediate process (not any subprocess it spawned
|
|
388
|
+
* internally) -- correct for the common case, but a check that spawns its own descendants (e.g.
|
|
389
|
+
* `npm test` spawning the real test runner) may leave them running. POSIX cleanup never needs
|
|
390
|
+
* this at all (`process.kill(-pid, signal)` reaches the whole process group directly, no spawning
|
|
391
|
+
* required) -- see specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md.
|
|
392
|
+
*/
|
|
393
|
+
readonly killProcessTree?: SyncSpawner;
|
|
394
|
+
}
|
|
395
|
+
/** Optional per-run controls for `runRepoContract`. */
|
|
396
|
+
export interface RunRepoContractOptions {
|
|
397
|
+
/** Abort the entire run. Checks already in flight are terminated; checks not yet started never spawn. Every configured check still receives a well-formed evidence entry (`status: "aborted"`) and still has its policy invoked. */
|
|
398
|
+
readonly signal?: AbortSignal;
|
|
399
|
+
/** Restrict this run to only these check ids (and whatever they `dependsOn`, transitively). Every configured check runs when omitted. */
|
|
400
|
+
readonly checks?: string[];
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Versioned, immutable aggregate result of evaluating every configured
|
|
404
|
+
* check's policy against its evidence -- each check's own `PolicyResult`,
|
|
405
|
+
* verbatim, keyed by check id. `passed` is `true` only if every check's
|
|
406
|
+
* `outcome` is `"pass"` or `"warn"` -- `"fail"` is the only outcome that
|
|
407
|
+
* fails the run; one failing check never collapses into a single generic
|
|
408
|
+
* message, every check remains individually inspectable under `checks`.
|
|
409
|
+
* Versioned independently of `Evidence` (see VERSIONING.md's
|
|
410
|
+
* schema-versioning policy) -- `version: 2` reflects `checks[id]` changing
|
|
411
|
+
* shape from `{ passed, reason? }` to a full `PolicyResult`
|
|
412
|
+
* (`{ outcome, rationale }`); see ADR 0001.
|
|
413
|
+
*/
|
|
414
|
+
export interface Verdict<TChecks extends CheckSchema = CheckSchema> {
|
|
415
|
+
/** Schema version of this shape; see VERSIONING.md. */
|
|
416
|
+
readonly version: 2;
|
|
417
|
+
/** `true` only if every check's `outcome` is `"pass"` or `"warn"`. */
|
|
418
|
+
readonly passed: boolean;
|
|
419
|
+
/** Each configured check's own `PolicyResult`, verbatim, keyed by check id. */
|
|
420
|
+
readonly checks: {
|
|
421
|
+
readonly [K in keyof TChecks]: PolicyResult;
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -96,6 +96,20 @@ var ParserDependencyMissingError = class extends RepoContractError {
|
|
|
96
96
|
this.format = format;
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
|
+
var StandardSchemaValidateThrewError = class extends RepoContractError {
|
|
100
|
+
/** Always `"REPO_CONTRACT_STANDARD_SCHEMA_VALIDATE_THREW"`. */
|
|
101
|
+
code = "REPO_CONTRACT_STANDARD_SCHEMA_VALIDATE_THREW";
|
|
102
|
+
/** The id of the check whose `output.schema` threw during validation. */
|
|
103
|
+
checkId;
|
|
104
|
+
constructor(checkId, cause) {
|
|
105
|
+
super(
|
|
106
|
+
`Check "${checkId}"'s output.schema["~standard"].validate() threw instead of returning a Result`,
|
|
107
|
+
{ cause }
|
|
108
|
+
);
|
|
109
|
+
this.name = "StandardSchemaValidateThrewError";
|
|
110
|
+
this.checkId = checkId;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
99
113
|
var PolicyThrewError = class extends RepoContractError {
|
|
100
114
|
/** Always `"REPO_CONTRACT_POLICY_THREW"`. */
|
|
101
115
|
code = "REPO_CONTRACT_POLICY_THREW";
|
|
@@ -380,13 +394,46 @@ function validateOutput(checkId, output) {
|
|
|
380
394
|
if (output === null || typeof output !== "object") {
|
|
381
395
|
throw new InvalidCheckConfigError(checkId, "output must be an object when provided.");
|
|
382
396
|
}
|
|
383
|
-
const { format } = output;
|
|
397
|
+
const { format, schema } = output;
|
|
384
398
|
if (typeof format !== "string" || !OUTPUT_FORMATS.includes(format)) {
|
|
385
399
|
throw new InvalidCheckConfigError(
|
|
386
400
|
checkId,
|
|
387
401
|
`output.format must be one of ${OUTPUT_FORMATS.map((f) => `"${f}"`).join(", ")}.`
|
|
388
402
|
);
|
|
389
403
|
}
|
|
404
|
+
validateOutputSchema(checkId, schema);
|
|
405
|
+
}
|
|
406
|
+
function validateOutputSchema(checkId, schema) {
|
|
407
|
+
if (schema === void 0) return;
|
|
408
|
+
if (schema === null || typeof schema !== "object" && typeof schema !== "function") {
|
|
409
|
+
throw new InvalidCheckConfigError(
|
|
410
|
+
checkId,
|
|
411
|
+
"output.schema must be a Standard Schema-compliant object or function when provided (see https://standardschema.dev)."
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const standard = schema["~standard"];
|
|
415
|
+
if (standard === null || typeof standard !== "object") {
|
|
416
|
+
throw new InvalidCheckConfigError(
|
|
417
|
+
checkId,
|
|
418
|
+
'output.schema must have a "~standard" property (see https://standardschema.dev).'
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
const { version, vendor, validate } = standard;
|
|
422
|
+
if (version !== 1) {
|
|
423
|
+
throw new InvalidCheckConfigError(checkId, 'output.schema["~standard"].version must be 1.');
|
|
424
|
+
}
|
|
425
|
+
if (typeof vendor !== "string") {
|
|
426
|
+
throw new InvalidCheckConfigError(
|
|
427
|
+
checkId,
|
|
428
|
+
'output.schema["~standard"].vendor must be a string.'
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (typeof validate !== "function") {
|
|
432
|
+
throw new InvalidCheckConfigError(
|
|
433
|
+
checkId,
|
|
434
|
+
'output.schema["~standard"].validate must be a function.'
|
|
435
|
+
);
|
|
436
|
+
}
|
|
390
437
|
}
|
|
391
438
|
function validatePolicy(checkId, policy) {
|
|
392
439
|
if (typeof policy !== "function") {
|
|
@@ -427,6 +474,28 @@ function validateDependencyGraph(checks) {
|
|
|
427
474
|
}
|
|
428
475
|
}
|
|
429
476
|
|
|
477
|
+
// src/parsing/format-schema-issues.ts
|
|
478
|
+
function formatSchemaIssues(issues) {
|
|
479
|
+
return `Schema validation failed: ${issues.map(formatIssue).join("; ")}`;
|
|
480
|
+
}
|
|
481
|
+
function formatIssue(issue) {
|
|
482
|
+
const path = formatPath(issue.path);
|
|
483
|
+
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
484
|
+
}
|
|
485
|
+
function formatPath(path) {
|
|
486
|
+
if (path === void 0 || path.length === 0) return "";
|
|
487
|
+
return path.map(
|
|
488
|
+
(segment) => (
|
|
489
|
+
// eslint-disable-next-line secure-coding/no-improper-type-validation -- `segment` is declared `PropertyKey | StandardSchemaV1.PathSegment` (never `null` or an array), so `typeof segment === "object"` can only match the `PathSegment` object form here; the null/array-safe rewrite this rule suggests is flagged as an unreachable condition by @typescript-eslint/no-unnecessary-condition given that exact type, so satisfying both rules at once is impossible -- the type itself is the guarantee this check would otherwise add at runtime.
|
|
490
|
+
typeof segment === "object" ? segment.key : segment
|
|
491
|
+
)
|
|
492
|
+
).map((key, index) => {
|
|
493
|
+
if (typeof key === "number") return `[${String(key)}]`;
|
|
494
|
+
const rendered = String(key);
|
|
495
|
+
return index === 0 ? rendered : `.${rendered}`;
|
|
496
|
+
}).join("");
|
|
497
|
+
}
|
|
498
|
+
|
|
430
499
|
// src/parsing/parse-json.ts
|
|
431
500
|
function parseJson(stdout) {
|
|
432
501
|
try {
|
|
@@ -465,7 +534,24 @@ async function parseYaml(stdout, checkId) {
|
|
|
465
534
|
}
|
|
466
535
|
|
|
467
536
|
// src/parsing/parse-output.ts
|
|
468
|
-
async function parseOutput(format, stdout, checkId) {
|
|
537
|
+
async function parseOutput(format, stdout, checkId, schema) {
|
|
538
|
+
const parsed = await dispatch(format, stdout, checkId);
|
|
539
|
+
if (!parsed.success || schema === void 0) return parsed;
|
|
540
|
+
let result;
|
|
541
|
+
try {
|
|
542
|
+
result = await schema["~standard"].validate(parsed.value);
|
|
543
|
+
if (typeof result !== "object" || result === null) {
|
|
544
|
+
throw new TypeError(`validate() returned ${String(result)} instead of a Result object`);
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
throw new StandardSchemaValidateThrewError(checkId, error);
|
|
548
|
+
}
|
|
549
|
+
if (result.issues === void 0) {
|
|
550
|
+
return { format, success: true, value: result.value };
|
|
551
|
+
}
|
|
552
|
+
return { format, success: false, error: formatSchemaIssues(result.issues) };
|
|
553
|
+
}
|
|
554
|
+
async function dispatch(format, stdout, checkId) {
|
|
469
555
|
switch (format) {
|
|
470
556
|
case "json":
|
|
471
557
|
return parseJson(stdout);
|
|
@@ -483,7 +569,12 @@ async function buildEvidence(results, startedAt, completedAt) {
|
|
|
483
569
|
results.map(async ([checkId, check, raw]) => {
|
|
484
570
|
if (check.output === void 0) return [checkId, check, raw];
|
|
485
571
|
try {
|
|
486
|
-
const output = await parseOutput(
|
|
572
|
+
const output = await parseOutput(
|
|
573
|
+
check.output.format,
|
|
574
|
+
raw.stdout,
|
|
575
|
+
checkId,
|
|
576
|
+
check.output.schema
|
|
577
|
+
);
|
|
487
578
|
return [checkId, check, { ...raw, output }];
|
|
488
579
|
} catch (error) {
|
|
489
580
|
thrown.push(error);
|
|
@@ -496,7 +587,10 @@ async function buildEvidence(results, startedAt, completedAt) {
|
|
|
496
587
|
throw only;
|
|
497
588
|
}
|
|
498
589
|
if (thrown.length > 1) {
|
|
499
|
-
throw new AggregateError(
|
|
590
|
+
throw new AggregateError(
|
|
591
|
+
thrown,
|
|
592
|
+
`${String(thrown.length)} check output(s) failed during output processing.`
|
|
593
|
+
);
|
|
500
594
|
}
|
|
501
595
|
const evidence = {
|
|
502
596
|
version: 1,
|
|
@@ -1096,6 +1190,7 @@ exports.PolicyReadFailedParseValueError = PolicyReadFailedParseValueError;
|
|
|
1096
1190
|
exports.PolicyReadUnrequestedOutputError = PolicyReadUnrequestedOutputError;
|
|
1097
1191
|
exports.PolicyThrewError = PolicyThrewError;
|
|
1098
1192
|
exports.RepoContractError = RepoContractError;
|
|
1193
|
+
exports.StandardSchemaValidateThrewError = StandardSchemaValidateThrewError;
|
|
1099
1194
|
exports.UnknownCheckIdError = UnknownCheckIdError;
|
|
1100
1195
|
exports.defineRepoContract = defineRepoContract;
|
|
1101
1196
|
exports.runRepoContract = runRepoContract;
|