@vibe-agent-toolkit/agent-skills 0.2.0-rc.2 → 0.2.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/files-config.d.ts +34 -1
- package/dist/files-config.d.ts.map +1 -1
- package/dist/files-config.js +280 -36
- package/dist/files-config.js.map +1 -1
- package/dist/fs-attribution.d.ts +45 -0
- package/dist/fs-attribution.d.ts.map +1 -0
- package/dist/fs-attribution.js +57 -0
- package/dist/fs-attribution.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts.map +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts.map +1 -1
- package/dist/inventory/index.d.ts +1 -1
- package/dist/inventory/index.d.ts.map +1 -1
- package/dist/inventory/index.js +1 -1
- package/dist/inventory/index.js.map +1 -1
- package/dist/inventory/serialize.d.ts +4 -4
- package/dist/inventory/serialize.d.ts.map +1 -1
- package/dist/inventory/serialize.js +6 -6
- package/dist/inventory/serialize.js.map +1 -1
- package/dist/post-build-checks.d.ts +1 -1
- package/dist/post-build-checks.d.ts.map +1 -1
- package/dist/post-build-checks.js.map +1 -1
- package/dist/projection/skill-extent.d.ts +247 -0
- package/dist/projection/skill-extent.d.ts.map +1 -0
- package/dist/projection/skill-extent.js +558 -0
- package/dist/projection/skill-extent.js.map +1 -0
- package/dist/schemas/agent-skill-frontmatter.js +1 -1
- package/dist/schemas/agent-skill-frontmatter.js.map +1 -1
- package/dist/skill-packager.d.ts +34 -4
- package/dist/skill-packager.d.ts.map +1 -1
- package/dist/skill-packager.js +113 -23
- package/dist/skill-packager.js.map +1 -1
- package/dist/skill-source/git-clone.d.ts.map +1 -1
- package/dist/skill-source/git-clone.js +9 -8
- package/dist/skill-source/git-clone.js.map +1 -1
- package/dist/skill-test/baseline-delta.d.ts +1575 -0
- package/dist/skill-test/baseline-delta.d.ts.map +1 -0
- package/dist/skill-test/baseline-delta.js +272 -0
- package/dist/skill-test/baseline-delta.js.map +1 -0
- package/dist/skill-test/baseline-integrity.d.ts +944 -0
- package/dist/skill-test/baseline-integrity.d.ts.map +1 -0
- package/dist/skill-test/baseline-integrity.js +3083 -0
- package/dist/skill-test/baseline-integrity.js.map +1 -0
- package/dist/skill-test/eval-executor.d.ts +19 -3
- package/dist/skill-test/eval-executor.d.ts.map +1 -1
- package/dist/skill-test/eval-executor.js +3 -3
- package/dist/skill-test/eval-executor.js.map +1 -1
- package/dist/skill-test/eval-fragment.d.ts +80 -6
- package/dist/skill-test/eval-fragment.d.ts.map +1 -1
- package/dist/skill-test/eval-fragment.js +61 -7
- package/dist/skill-test/eval-fragment.js.map +1 -1
- package/dist/skill-test/eval-grader.d.ts.map +1 -1
- package/dist/skill-test/eval-grader.js +143 -7
- package/dist/skill-test/eval-grader.js.map +1 -1
- package/dist/skill-test/eval-inputs.d.ts +107 -6
- package/dist/skill-test/eval-inputs.d.ts.map +1 -1
- package/dist/skill-test/eval-inputs.js +173 -16
- package/dist/skill-test/eval-inputs.js.map +1 -1
- package/dist/skill-test/eval-lint.d.ts +13 -0
- package/dist/skill-test/eval-lint.d.ts.map +1 -1
- package/dist/skill-test/eval-lint.js +17 -4
- package/dist/skill-test/eval-lint.js.map +1 -1
- package/dist/skill-test/executor-prompt.d.ts +13 -5
- package/dist/skill-test/executor-prompt.d.ts.map +1 -1
- package/dist/skill-test/executor-prompt.js +8 -5
- package/dist/skill-test/executor-prompt.js.map +1 -1
- package/dist/skill-test/exit-codes.d.ts +53 -16
- package/dist/skill-test/exit-codes.d.ts.map +1 -1
- package/dist/skill-test/exit-codes.js +123 -40
- package/dist/skill-test/exit-codes.js.map +1 -1
- package/dist/skill-test/fragment-merge.d.ts +1 -18
- package/dist/skill-test/fragment-merge.d.ts.map +1 -1
- package/dist/skill-test/fragment-merge.js +63 -4
- package/dist/skill-test/fragment-merge.js.map +1 -1
- package/dist/skill-test/grader-prompt.d.ts +27 -7
- package/dist/skill-test/grader-prompt.d.ts.map +1 -1
- package/dist/skill-test/grader-prompt.js +253 -61
- package/dist/skill-test/grader-prompt.js.map +1 -1
- package/dist/skill-test/grader-text.d.ts +145 -0
- package/dist/skill-test/grader-text.d.ts.map +1 -0
- package/dist/skill-test/grader-text.js +410 -0
- package/dist/skill-test/grader-text.js.map +1 -0
- package/dist/skill-test/grading-adapter.d.ts +32 -0
- package/dist/skill-test/grading-adapter.d.ts.map +1 -1
- package/dist/skill-test/grading-adapter.js +50 -3
- package/dist/skill-test/grading-adapter.js.map +1 -1
- package/dist/skill-test/grading-schema.d.ts +160 -5
- package/dist/skill-test/grading-schema.d.ts.map +1 -1
- package/dist/skill-test/grading-schema.js +66 -7
- package/dist/skill-test/grading-schema.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/lock.d.ts +56 -0
- package/dist/skill-test/lock.d.ts.map +1 -1
- package/dist/skill-test/lock.js +75 -2
- package/dist/skill-test/lock.js.map +1 -1
- package/dist/skill-test/pipeline.d.ts +16 -0
- package/dist/skill-test/pipeline.d.ts.map +1 -1
- package/dist/skill-test/pipeline.js +16 -9
- package/dist/skill-test/pipeline.js.map +1 -1
- package/dist/skill-test/preflight.d.ts.map +1 -1
- package/dist/skill-test/preflight.js +58 -14
- package/dist/skill-test/preflight.js.map +1 -1
- package/dist/skill-test/run-harness.d.ts +578 -35
- package/dist/skill-test/run-harness.d.ts.map +1 -1
- package/dist/skill-test/run-harness.js +1278 -147
- package/dist/skill-test/run-harness.js.map +1 -1
- package/dist/skill-test/staging.d.ts +8 -2
- package/dist/skill-test/staging.d.ts.map +1 -1
- package/dist/skill-test/staging.js +8 -4
- package/dist/skill-test/staging.js.map +1 -1
- package/dist/skill-test/tool-eval-schema.d.ts +6 -6
- package/dist/test-input.d.ts +1 -1
- package/dist/test-input.d.ts.map +1 -1
- package/dist/validators/agent-instruction-presence.d.ts +1 -1
- package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
- package/dist/validators/bundled-resource-link-detection.js +1 -1
- package/dist/validators/bundled-resource-link-detection.js.map +1 -1
- package/dist/validators/compat-detectors.d.ts +1 -1
- package/dist/validators/compat-detectors.d.ts.map +1 -1
- package/dist/validators/compat-detectors.js +1 -1
- package/dist/validators/compat-detectors.js.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.js +1 -1
- package/dist/validators/cross-skill-dependency-detection.js.map +1 -1
- package/dist/validators/description-style-detection.d.ts +1 -1
- package/dist/validators/description-style-detection.d.ts.map +1 -1
- package/dist/validators/description-style-detection.js +1 -1
- package/dist/validators/description-style-detection.js.map +1 -1
- package/dist/validators/frontmatter-validation.d.ts +1 -1
- package/dist/validators/frontmatter-validation.d.ts.map +1 -1
- package/dist/validators/frontmatter-validation.js +1 -1
- package/dist/validators/frontmatter-validation.js.map +1 -1
- package/dist/validators/imperative-body-detection.d.ts +1 -1
- package/dist/validators/imperative-body-detection.d.ts.map +1 -1
- package/dist/validators/imperative-body-detection.js +1 -1
- package/dist/validators/imperative-body-detection.js.map +1 -1
- package/dist/validators/index.d.ts +1 -1
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.d.ts +1 -1
- package/dist/validators/kebab-case-detection.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.js +1 -1
- package/dist/validators/kebab-case-detection.js.map +1 -1
- package/dist/validators/marketplace-validator.js +1 -1
- package/dist/validators/marketplace-validator.js.map +1 -1
- package/dist/validators/packaging-validator.d.ts +38 -5
- package/dist/validators/packaging-validator.d.ts.map +1 -1
- package/dist/validators/packaging-validator.js +87 -10
- package/dist/validators/packaging-validator.js.map +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
- package/dist/validators/plugin-hosted-shape.js +1 -1
- package/dist/validators/plugin-hosted-shape.js.map +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts.map +1 -1
- package/dist/validators/plugin-recommended-fields.js +1 -1
- package/dist/validators/plugin-recommended-fields.js.map +1 -1
- package/dist/validators/registry-validator.js +1 -1
- package/dist/validators/registry-validator.js.map +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
- package/dist/validators/rule-engine/rule-engine.js +20 -9
- package/dist/validators/rule-engine/rule-engine.js.map +1 -1
- package/dist/validators/skill-validator.d.ts.map +1 -1
- package/dist/validators/skill-validator.js +21 -3
- package/dist/validators/skill-validator.js.map +1 -1
- package/dist/validators/types.d.ts +1 -1
- package/dist/validators/types.d.ts.map +1 -1
- package/dist/validators/unified-validator.js +1 -1
- package/dist/validators/unified-validator.js.map +1 -1
- package/dist/validators/validation-rules.d.ts +1 -1
- package/dist/validators/validation-rules.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.d.ts +7 -3
- package/dist/validators/walker-to-issues.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.js +37 -8
- package/dist/validators/walker-to-issues.js.map +1 -1
- package/dist/walk-link-graph.d.ts.map +1 -1
- package/dist/walk-link-graph.js +36 -2
- package/dist/walk-link-graph.js.map +1 -1
- package/package.json +7 -7
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
* → release lock → return result
|
|
12
12
|
*/
|
|
13
13
|
import type { SkillSourceDescriptor } from '@vibe-agent-toolkit/resources';
|
|
14
|
-
import { type spawnHeadlessClaude } from '@vibe-agent-toolkit/utils';
|
|
14
|
+
import { type spawnHeadlessClaude, type ResolvedAuth } from '@vibe-agent-toolkit/utils';
|
|
15
15
|
import type { ResolveSkillSourceContext, SkillSource } from '../skill-source/types.js';
|
|
16
|
+
import { type BaselineDelta } from './baseline-delta.js';
|
|
17
|
+
import { type ArmEvalGrade, type BaselineControlArmFailure, type BaselineIntegrity, type DetectBaselineContaminationInput } from './baseline-integrity.js';
|
|
16
18
|
import type { EvalFragment } from './eval-fragment.js';
|
|
17
|
-
import { type EvalEntry, type EvalSuite } from './eval-inputs.js';
|
|
19
|
+
import { type ArmWorkspaceDirs, type EvalArm, type EvalEntry, type EvalSuite } from './eval-inputs.js';
|
|
18
20
|
import { type SkillTestExitCodeValue } from './exit-codes.js';
|
|
19
21
|
import { type FrictionItem } from './friction-schema.js';
|
|
20
22
|
import { type GradingVerdict } from './grading-adapter.js';
|
|
23
|
+
import { RateLimitSignal } from './pipeline.js';
|
|
21
24
|
import { type PreflightInput } from './preflight.js';
|
|
22
25
|
import { type StageItem } from './staging.js';
|
|
23
26
|
import { type SkippedEvalsSummary } from './tier-plan.js';
|
|
@@ -86,6 +89,15 @@ export interface RunHarnessOptions {
|
|
|
86
89
|
* install. Production callers leave it undefined (the real spawn is used).
|
|
87
90
|
*/
|
|
88
91
|
spawn?: typeof spawnHeadlessClaude;
|
|
92
|
+
/**
|
|
93
|
+
* Injectable rate-limit backoff (tests only) — the pipeline's `onRateLimit`,
|
|
94
|
+
* given the 1-based retry attempt and returning milliseconds. Returning `0`
|
|
95
|
+
* collapses the retry budget to nothing, which is what makes the EXHAUSTED
|
|
96
|
+
* control-arm path testable at all: the real backoff is exponential and spends
|
|
97
|
+
* 31 seconds per item before the budget runs out. Production callers leave it
|
|
98
|
+
* undefined (the real exponential backoff is used).
|
|
99
|
+
*/
|
|
100
|
+
rateLimitBackoffMs?: (attempt: number) => number;
|
|
89
101
|
/** Enable A/B baseline run (with/without skill). */
|
|
90
102
|
baseline?: boolean;
|
|
91
103
|
/** Allow unverified skill source (skip manifest check). */
|
|
@@ -144,11 +156,18 @@ export interface RunHarnessOptions {
|
|
|
144
156
|
* does not — issue #145 Phase T). Passed to the grader on the WITH arm ONLY as
|
|
145
157
|
* a recognition aid alongside each eval's `toolExpectations`. Absent → the
|
|
146
158
|
* grader still matches tools by the commands it sees in the transcript.
|
|
159
|
+
*
|
|
160
|
+
* `path` serves a SECOND consumer with a different need: `detectBaselineContamination`
|
|
161
|
+
* matches an escaping reach against the skill-relative PATH, because an ambient copy
|
|
162
|
+
* of the skill reproduces `…/scripts/csvsum.py` under whatever root it sits at, while
|
|
163
|
+
* the control arm's own `/tmp/summary.txt` does not. Matching the extension-stripped
|
|
164
|
+
* `name` instead convicted clean control arms of reading their own scratch files.
|
|
147
165
|
*/
|
|
148
166
|
declaredExecutables?: Array<{
|
|
149
167
|
name: string;
|
|
150
168
|
howInvoked: string;
|
|
151
169
|
kind: string;
|
|
170
|
+
path: string;
|
|
152
171
|
}>;
|
|
153
172
|
/**
|
|
154
173
|
* Opt-OUT of eval gating (for interactive use). By DEFAULT (false/absent) a
|
|
@@ -163,6 +182,27 @@ export interface RunHarnessResult {
|
|
|
163
182
|
harnessPath: string;
|
|
164
183
|
exitCode: number;
|
|
165
184
|
summary: string;
|
|
185
|
+
/**
|
|
186
|
+
* Where the executor's per-eval working directories were materialized. Lives
|
|
187
|
+
* OUTSIDE `harnessPath` on purpose (see {@link resolveWorkspacesRoot}), so it
|
|
188
|
+
* cannot be derived from the harness path and has to be reported.
|
|
189
|
+
*
|
|
190
|
+
* Present ONLY when those directories SURVIVE the run — i.e. under `--keep`,
|
|
191
|
+
* the sole thing that retains them (NOT `--out`/`--workdir`, unlike the harness
|
|
192
|
+
* root). Cleanup runs in the harness's own `finally`, before this result reaches
|
|
193
|
+
* any caller, so a populated value is a promise that the path is still there:
|
|
194
|
+
* anything that reports it (the CLI's `Workspaces:` line) may print it as-is.
|
|
195
|
+
* Also absent when the run ended before workspaces were staged.
|
|
196
|
+
*/
|
|
197
|
+
workspacesPath?: string;
|
|
198
|
+
/**
|
|
199
|
+
* Where this run's artifacts (`grading.json`, `friction.json`, `tool-eval.json`,
|
|
200
|
+
* and `baseline.json` on a `--baseline` run) were written. Reported rather than
|
|
201
|
+
* left to be derived from `harnessPath`, because it is the ONLY part of the
|
|
202
|
+
* harness root that survives cleanup on a default run — see
|
|
203
|
+
* {@link cleanupHarness}. Absent when the run ended before Step 7 created it.
|
|
204
|
+
*/
|
|
205
|
+
resultsPath?: string;
|
|
166
206
|
}
|
|
167
207
|
/**
|
|
168
208
|
* Built-in cost/runtime safety ceilings applied PER executor/grader spawn. These
|
|
@@ -231,10 +271,61 @@ export declare function makeStageItem(name: string, source: SkillSource, repoRoo
|
|
|
231
271
|
export declare function buildStageItems(opts: RunHarnessOptions, repoRoot: string): StageItem[];
|
|
232
272
|
export declare function buildResolveCtx(harnessRoot: string, repoRoot: string): ResolveSkillSourceContext;
|
|
233
273
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
274
|
+
* Match a flag name as a whole token in `claude --help` output.
|
|
275
|
+
*
|
|
276
|
+
* The boundary matters: a bare `includes('--plugin-dir')` also matches
|
|
277
|
+
* `--plugin-dirs` or `--plugin-dir-cache`, and vat's own flag names are prefixes
|
|
278
|
+
* of each other (`--max-turns` / `--max-turns-per-eval` would collide). Neither
|
|
279
|
+
* side of the token may be a letter or a dash.
|
|
280
|
+
*/
|
|
281
|
+
export declare function helpTextDeclaresFlag(helpText: string, flag: string): boolean;
|
|
282
|
+
/**
|
|
283
|
+
* A flag name no `claude` will ever accept. It is probed alongside the real ones
|
|
284
|
+
* as a NEGATIVE CONTROL: if the probe reports this as supported, the probe is
|
|
285
|
+
* broken and its answer about every other flag is worthless.
|
|
286
|
+
*/
|
|
287
|
+
export declare const FLAG_PROBE_SENTINEL = "--vat-probe-flag-that-cannot-exist";
|
|
288
|
+
/**
|
|
289
|
+
* Build a token-free flag-support probe from `claude --help`.
|
|
290
|
+
*
|
|
291
|
+
* ⚠️ This REPLACES an exit-code probe (`claude <flag> <dummy> --help`, exit 0 ⇒
|
|
292
|
+
* supported) that could not discriminate at all. Verified against claude 2.x:
|
|
293
|
+
* `claude --no-such-flag-xyz 1 --help` exits **0**, because `--help` short-circuits
|
|
294
|
+
* before argument validation. Every one of preflight's `flag <name>` checks
|
|
295
|
+
* therefore reported "supported" unconditionally — including for flags that do not
|
|
296
|
+
* exist — so the gate that is supposed to stop vat spawning with an unsupported
|
|
297
|
+
* flag was decorative. (The same command without `--help` exits 1, but running it
|
|
298
|
+
* would start a real session and bill tokens, which is why `--help` was there.)
|
|
299
|
+
*
|
|
300
|
+
* Help-text matching does discriminate, and it is still token-free: one `--help`
|
|
301
|
+
* invocation for the whole run instead of one spawn per flag.
|
|
302
|
+
*
|
|
303
|
+
* The probe carries its own negative control ({@link FLAG_PROBE_SENTINEL}). If a
|
|
304
|
+
* future `claude` prints something that matches the sentinel — or `--help` fails
|
|
305
|
+
* outright — the probe reports EVERY flag unsupported rather than every flag
|
|
306
|
+
* supported. A probe that cannot tell must fail closed and be seen; the defect it
|
|
307
|
+
* replaces failed open and was invisible.
|
|
308
|
+
*/
|
|
309
|
+
export declare function buildFlagParseProbe(runHelp?: () => string | null): (flag: string) => boolean;
|
|
310
|
+
/**
|
|
311
|
+
* ⚠️ KNOWN OPEN — `costEstimate` here is DEAD, in three independent ways, and none
|
|
312
|
+
* of them is fixable from inside this function:
|
|
313
|
+
*
|
|
314
|
+
* 1. `evalCount: 1` is a placeholder. The suite is not parsed until Step 5.5, which
|
|
315
|
+
* runs AFTER preflight, so the real count does not exist yet. Making it real means
|
|
316
|
+
* either moving preflight after the suite parse (it currently gates that parse) or
|
|
317
|
+
* adding a second reader of `evals.json` — and two readers of the answer key is
|
|
318
|
+
* precisely the drift this module refuses elsewhere.
|
|
319
|
+
* 2. `renderPreflightSummary` returns only FAILING checks, and it is called only
|
|
320
|
+
* inside the `if (!preflightResult.passed)` branch — so on a passing run the
|
|
321
|
+
* estimate never reaches a terminal at all.
|
|
322
|
+
* 3. Preflight runs after the `--dry-run` short-circuit, so a dry run never gets here.
|
|
323
|
+
*
|
|
324
|
+
* The consequence is that {@link buildDryRunSummary}'s spawn-count line is the ONLY
|
|
325
|
+
* pre-spend number an operator ever sees. Fix the count before making the check
|
|
326
|
+
* reachable: a reachable estimate that says "1 evals × 2 config" is worse than a
|
|
327
|
+
* silent one, because it is quotable.
|
|
236
328
|
*/
|
|
237
|
-
export declare function flagDummyValueFor(flag: string): string;
|
|
238
329
|
export declare function buildPreflightInput(evalsPath: string, pluginDirs: string[], opts: RunHarnessOptions, knobs: {
|
|
239
330
|
maxBudgetUsd: number;
|
|
240
331
|
}): PreflightInput;
|
|
@@ -255,6 +346,13 @@ export declare function isAcknowledged(opts: Pick<RunHarnessOptions, 'dryRun' |
|
|
|
255
346
|
* Format a friction report for human consumption — one line per entry as
|
|
256
347
|
* `[<severity>] <category>: <message>`. Pure; returns the empty string for no
|
|
257
348
|
* entries so the caller can skip emitting anything.
|
|
349
|
+
*
|
|
350
|
+
* `severity` and `category` are closed enums vat owns, so they are safe to
|
|
351
|
+
* interpolate. `message` is GRADER text and is sanitized here even though
|
|
352
|
+
* {@link parseEvalFragment} already sanitized it on the way in — this is a
|
|
353
|
+
* second, independent boundary, not a redundant one: {@link emitFrictionReport}
|
|
354
|
+
* re-reads `friction.json` from the harness results dir, which same-uid skill
|
|
355
|
+
* code can reach and rewrite after vat wrote it.
|
|
258
356
|
*/
|
|
259
357
|
export declare function formatFrictionReport(items: readonly FrictionItem[]): string;
|
|
260
358
|
/**
|
|
@@ -322,17 +420,65 @@ export declare function resolveEvalSuitePath(input: {
|
|
|
322
420
|
holdDir: string;
|
|
323
421
|
subjectEvalSuiteHeld: boolean;
|
|
324
422
|
}): string | undefined;
|
|
423
|
+
/**
|
|
424
|
+
* The per-run executor workspace root: `<tmp>/vat-skill-test-ws-<dirToken>/`.
|
|
425
|
+
*
|
|
426
|
+
* Deliberately OUTSIDE `harnessRoot`, alongside {@link resolveGraderOutDir} and
|
|
427
|
+
* {@link resolveEvalSuiteHoldDir}. The executor's cwd lives under here, and the
|
|
428
|
+
* harness root is where vat stages runnable copies of the skill (`staged/` and
|
|
429
|
+
* the assembled plugin dir). While the two were parent and child, the skill-absent
|
|
430
|
+
* arm of a `--baseline` run sat one `ls ..` away from the treatment it was
|
|
431
|
+
* supposed to be denied — the control could reach vat's own copy without ever
|
|
432
|
+
* leaving its working directory.
|
|
433
|
+
*
|
|
434
|
+
* Named by an unpredictable token, like the other vat-only dirs, and removed by
|
|
435
|
+
* the same cleanup. Pure (derives a path only).
|
|
436
|
+
*/
|
|
437
|
+
export declare function resolveWorkspacesRoot(dirToken: string): string;
|
|
325
438
|
/** Parse the staged eval suite and materialize each eval's input `files` into
|
|
326
|
-
* `<
|
|
327
|
-
* {@link EvalSuite} (so the eval loop has the entries
|
|
328
|
-
* declared eval count (derived from the suite). The
|
|
329
|
-
*
|
|
330
|
-
* (mapped by the caller to exit 2) on a bad suite or a
|
|
331
|
-
|
|
439
|
+
* `<workspacesRoot>/<arm>/<id>/`, once per arm this run will execute. Returns the
|
|
440
|
+
* workspaces root, the parsed {@link EvalSuite} (so the eval loop has the entries
|
|
441
|
+
* without re-reading), and the declared eval count (derived from the suite). The
|
|
442
|
+
* dir is wiped first so a reused root cannot leak a prior run's inputs. Throws
|
|
443
|
+
* {@link EvalInputError} (mapped by the caller to exit 2) on a bad suite or a
|
|
444
|
+
* missing input file. */
|
|
445
|
+
/**
|
|
446
|
+
* Mint this run's opaque per-arm workspace directory segments.
|
|
447
|
+
*
|
|
448
|
+
* Independent random tokens, NOT the arm names and not derived from one another:
|
|
449
|
+
* the segment ends up in the executor's cwd, which the prompt states verbatim, so
|
|
450
|
+
* a name like `without` unblinds the control arm outright — and a name like `with`
|
|
451
|
+
* hands it a guessable path to the treatment arm's live output one directory over.
|
|
452
|
+
* 8 bytes is ample for "not guessable within a run that lasts minutes"; these are
|
|
453
|
+
* blinding/anti-enumeration tokens, not secrets protecting data at rest.
|
|
454
|
+
*/
|
|
455
|
+
export declare function mintArmWorkspaceDirs(baseline: boolean): ArmWorkspaceDirs;
|
|
456
|
+
export declare function stageWorkspacesForRun(evalsPath: string, workspacesRoot: string, armDirs: ArmWorkspaceDirs): {
|
|
332
457
|
workspacesRoot: string;
|
|
333
458
|
declaredEvalCount: number;
|
|
334
459
|
suite: EvalSuite;
|
|
335
460
|
};
|
|
461
|
+
interface ResolveDeclaredChildEnvInput {
|
|
462
|
+
opts: RunHarnessOptions;
|
|
463
|
+
resolvedAuth: ResolvedAuth;
|
|
464
|
+
subjectStagedDir: string;
|
|
465
|
+
harnessRoot: string;
|
|
466
|
+
resultsDir: string;
|
|
467
|
+
evalsSubpath: string;
|
|
468
|
+
subjectPluginRoot: string | null;
|
|
469
|
+
/** The eval's staged input workspace — what `${fixturesDir}` resolves under. */
|
|
470
|
+
workspaceDir?: string;
|
|
471
|
+
/** Suppress the stderr transparency line (emitted once, by the first eval). */
|
|
472
|
+
quiet?: boolean;
|
|
473
|
+
}
|
|
474
|
+
/** Everything needed to re-assemble the executor env once an eval's workspace is known. */
|
|
475
|
+
interface PerEvalEnvAssembly {
|
|
476
|
+
input: Omit<ResolveDeclaredChildEnvInput, 'workspaceDir' | 'quiet'>;
|
|
477
|
+
/** Mutable latch so exactly one eval emits the stderr transparency line. */
|
|
478
|
+
logged: {
|
|
479
|
+
done: boolean;
|
|
480
|
+
};
|
|
481
|
+
}
|
|
336
482
|
/** Inputs for the dry-run summary string. */
|
|
337
483
|
export interface DryRunSummaryInput {
|
|
338
484
|
/** True when the resolved subject is buildable (a real run would build + stage it). */
|
|
@@ -342,7 +488,14 @@ export interface DryRunSummaryInput {
|
|
|
342
488
|
* rebuilding (may be stale); false = no dist existed, fell back to source dir.
|
|
343
489
|
*/
|
|
344
490
|
dryRunStagedExistingDist?: boolean;
|
|
345
|
-
/**
|
|
491
|
+
/**
|
|
492
|
+
* Absolute path where a REAL run would write provenance.json.
|
|
493
|
+
*
|
|
494
|
+
* Not written by a dry run, and the summary line says so. It used to be written —
|
|
495
|
+
* from the same block that wiped the previous run's grading/friction/baseline
|
|
496
|
+
* artifacts — which made "preview the cost" a destructive operation against the
|
|
497
|
+
* results the operator was about to read.
|
|
498
|
+
*/
|
|
346
499
|
provenancePath: string;
|
|
347
500
|
/** Content fingerprint from the staged manifest. */
|
|
348
501
|
provenanceFingerprint: string;
|
|
@@ -352,10 +505,29 @@ export interface DryRunSummaryInput {
|
|
|
352
505
|
modelFlag: string;
|
|
353
506
|
/** Number of declared evals a real run would execute. */
|
|
354
507
|
evalCount: number;
|
|
508
|
+
/**
|
|
509
|
+
* Whether `--baseline` is set, i.e. whether every eval runs TWICE.
|
|
510
|
+
*
|
|
511
|
+
* Without this the preview reported the SUITE SIZE and called it the spawn count,
|
|
512
|
+
* which under `--baseline` under-reports the run by exactly 2x: a 3-eval suite
|
|
513
|
+
* previewed as "3 evals as executor→grader spawn pairs" and then launched 6 pairs
|
|
514
|
+
* — twelve `claude` sessions. This is the ONLY pre-spend number an operator ever
|
|
515
|
+
* sees (see {@link buildPreflightInput} for why the other one never reaches a
|
|
516
|
+
* terminal), so a 2x understatement here is the whole cost estimate being wrong.
|
|
517
|
+
*/
|
|
518
|
+
baseline: boolean;
|
|
355
519
|
/** Bounded-parallel executor→grader pipeline width a real run would use. */
|
|
356
520
|
concurrency: number;
|
|
357
521
|
/** Pinned grader/judge model a real run would grade with. */
|
|
358
522
|
graderModel: string;
|
|
523
|
+
/**
|
|
524
|
+
* The PER-SPAWN budget ceiling (`--max-budget-usd`), not a run total. Reported
|
|
525
|
+
* because the two facts only mean something together: the worst case is
|
|
526
|
+
* `pairs × 2 × maxBudgetUsd`, and an operator who reads the flag name as a run
|
|
527
|
+
* budget under-provisions by the session count — which under `--baseline` is
|
|
528
|
+
* four times the eval count.
|
|
529
|
+
*/
|
|
530
|
+
maxBudgetUsd: number;
|
|
359
531
|
}
|
|
360
532
|
/**
|
|
361
533
|
* Build the "stale dist" warning lines for a `--dry-run` preview that staged an
|
|
@@ -372,19 +544,6 @@ export interface DryRunSummaryInput {
|
|
|
372
544
|
* than copying the string.
|
|
373
545
|
*/
|
|
374
546
|
export declare function buildStaleDistWarningLines(roleLabel?: string): string[];
|
|
375
|
-
/**
|
|
376
|
-
* Build the dry-run summary string. Pure function so it can be unit-tested
|
|
377
|
-
* without running the full harness.
|
|
378
|
-
*
|
|
379
|
-
* The summary covers three scenarios:
|
|
380
|
-
* 1. Declared (buildable) subject — no dist existed, fell back to source dir.
|
|
381
|
-
* 2. Declared (buildable) subject — existing dist was staged WITHOUT rebuilding
|
|
382
|
-
* (potentially stale).
|
|
383
|
-
* 3. Plain source subject — staged as-is, no build step.
|
|
384
|
-
*
|
|
385
|
-
* Always includes the assembled spawn command, the staged-manifest entry count +
|
|
386
|
-
* fingerprint, and the provenance.json path so a stale tree is visible at a glance.
|
|
387
|
-
*/
|
|
388
547
|
export declare function buildDryRunSummary(input: DryRunSummaryInput): string;
|
|
389
548
|
export interface CleanupHarnessOptions {
|
|
390
549
|
/** User asked to retain the harness dir (`--keep`) — never remove. */
|
|
@@ -397,11 +556,32 @@ export interface CleanupHarnessOptions {
|
|
|
397
556
|
created: boolean;
|
|
398
557
|
}
|
|
399
558
|
/**
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
|
|
404
|
-
|
|
559
|
+
* The one child of the harness root that cleanup NEVER removes. Cleanup exists to
|
|
560
|
+
* evict staged untrusted skill bytes and prompts from OS tmp; `results/` is the
|
|
561
|
+
* opposite — it is the run's product, written solely by vat.
|
|
562
|
+
*/
|
|
563
|
+
export declare const RETAINED_RESULTS_DIRNAME = "results";
|
|
564
|
+
/**
|
|
565
|
+
* Remove the harness directory's staged contents after a run so untrusted skill
|
|
566
|
+
* bytes and prompts do not accumulate in OS tmp. No-op when the user asked to keep
|
|
567
|
+
* it, when the dir is a user-supplied location (`--out`/`--workdir`), or when it is
|
|
568
|
+
* already gone. Idempotent and never throws — it runs from a `finally`, so it must
|
|
569
|
+
* not mask the run's real outcome.
|
|
570
|
+
*
|
|
571
|
+
* `results/` SURVIVES. It holds `grading.json`, `friction.json`, `tool-eval.json`
|
|
572
|
+
* and — for a `--baseline` run — `baseline.json`, i.e. every artifact the command's
|
|
573
|
+
* own help text tells the operator to go read. Removing the whole root on the
|
|
574
|
+
* DEFAULT invocation (no `--out`, no `--workdir`, no `--keep`) deleted those files
|
|
575
|
+
* inside this function's own `finally`, before `run.ts` had printed a single line:
|
|
576
|
+
* the operator was handed a `Harness:` path to a directory that no longer existed.
|
|
577
|
+
* That is the invocation `vat-skill-testing.md`'s copy-paste example uses.
|
|
578
|
+
*
|
|
579
|
+
* The retention is deliberately UNCONDITIONAL rather than `--baseline`-only: every
|
|
580
|
+
* run's verdict detail lives in the same directory, and a rule that depends on a
|
|
581
|
+
* flag is a rule that will drift away from the flag. The cost is bounded — the
|
|
582
|
+
* harness root is a deterministic function of the subject set, so a re-run reuses
|
|
583
|
+
* (and {@link wipeStaleArtifacts} clears) the same `results/`, rather than
|
|
584
|
+
* accumulating one per run.
|
|
405
585
|
*
|
|
406
586
|
* SAFETY: re-asserts the root is not a symlink immediately before removal (via
|
|
407
587
|
* `lstat`, which does NOT follow the link). A root swapped to a symlink between
|
|
@@ -412,7 +592,7 @@ export declare function cleanupHarness(harnessRoot: string, opts: CleanupHarness
|
|
|
412
592
|
/** One unit of executor→grader work: an eval + which arm (with/without skill). */
|
|
413
593
|
export interface EvalWorkItem {
|
|
414
594
|
entry: EvalEntry;
|
|
415
|
-
arm:
|
|
595
|
+
arm: EvalArm;
|
|
416
596
|
}
|
|
417
597
|
/**
|
|
418
598
|
* Build the per-eval work items for a set of evals (one tier's worth, or a whole
|
|
@@ -455,18 +635,264 @@ export declare function partitionFragmentsByArm(fragments: EvalFragment[]): {
|
|
|
455
635
|
*/
|
|
456
636
|
export declare function resolveGraderOutDir(dirToken: string): string;
|
|
457
637
|
/**
|
|
458
|
-
* The executor working directory for one eval: its staged input
|
|
459
|
-
* `<workspacesRoot>/<id
|
|
460
|
-
*
|
|
638
|
+
* The executor working directory for one eval ON ONE ARM: its staged input
|
|
639
|
+
* workspace `<workspacesRoot>/<arm>/<id>`, ALWAYS — empty when the eval declares
|
|
640
|
+
* no input `files` (see {@link stageEvalWorkspaces}).
|
|
641
|
+
*
|
|
642
|
+
* This used to return undefined for a file-less eval, which made the executor
|
|
643
|
+
* fall back to running inside the staged subject dir. That fallback was the
|
|
644
|
+
* quiet half of the `--baseline` control defect: the skill-absent arm's cwd
|
|
645
|
+
* was the skill.
|
|
646
|
+
*
|
|
647
|
+
* The per-arm segment closes the OTHER half, found a round later: without it both
|
|
648
|
+
* arms of one eval shared a single writable directory AND ran concurrently, so
|
|
649
|
+
* the control arm could read the treatment arm's output files and answer from
|
|
650
|
+
* them. Keyed on the eval id alone this is a pure function of the eval; the arm
|
|
651
|
+
* is what makes the two runs independent.
|
|
652
|
+
*
|
|
653
|
+
* The segment is an OPAQUE per-run token, never the arm's name — this path is
|
|
654
|
+
* quoted verbatim to the executor as its working directory, so `…/without/e1`
|
|
655
|
+
* told the control arm it was the control, and `../with/e1` told it where to look.
|
|
656
|
+
* See {@link mintArmWorkspaceDirs}. Pure + unit-testable.
|
|
461
657
|
*/
|
|
462
|
-
export declare function resolvePerEvalWorkspaceDir(entry: EvalEntry, workspacesRoot: string): string
|
|
658
|
+
export declare function resolvePerEvalWorkspaceDir(entry: EvalEntry, workspacesRoot: string, arm: EvalArm, armDirs: ArmWorkspaceDirs): string;
|
|
463
659
|
/**
|
|
464
660
|
* Best-effort removal of a vat-only tmp dir that lives OUTSIDE the harness root —
|
|
465
661
|
* the grader fragment dir and the held eval suite. Never throws (it runs from
|
|
466
662
|
* cleanup, where masking the run's real outcome would be worse than a leftover 0700
|
|
467
663
|
* tmp dir) and refuses to follow a symlinked root.
|
|
468
664
|
*/
|
|
665
|
+
/**
|
|
666
|
+
* Run one cleanup step, swallowing whatever it throws.
|
|
667
|
+
*
|
|
668
|
+
* The discipline every other step in the harness `finally` already follows, stated
|
|
669
|
+
* once: a cleanup failure is not a run failure, and letting one out REPLACES an
|
|
670
|
+
* already-good result — verdict computed, artifacts written, summary composed — with
|
|
671
|
+
* an error and exit 1.
|
|
672
|
+
*
|
|
673
|
+
* ⚠️ A CALL-SITE GUARD IS THE WEAK FORM, and this helper is a last resort rather
|
|
674
|
+
* than the pattern to copy. It is invisible to the compiler and to the test suite:
|
|
675
|
+
* reverting either of its two original call sites to a bare call left the unit suite
|
|
676
|
+
* AND the integration suite green, so nothing but review stood between the fix and
|
|
677
|
+
* its own silent removal. `lock.release()` was therefore made non-throwing at its own
|
|
678
|
+
* definition instead (see {@link acquireHarnessLock}), which is a contract the type
|
|
679
|
+
* carries and no edit here can undo. That leaves ONE caller —
|
|
680
|
+
* `killAllActiveClaudeChildren`, which lives in `@vibe-agent-toolkit/utils` and is
|
|
681
|
+
* only contractually non-throwing as long as that package says so.
|
|
682
|
+
*
|
|
683
|
+
* The failure is reported on stderr rather than silently dropped: a lockfile that
|
|
684
|
+
* could not be removed will break the NEXT run with a "busy" error, and an operator
|
|
685
|
+
* who saw nothing here has no way to connect the two.
|
|
686
|
+
*/
|
|
687
|
+
export declare function swallowCleanupFailure(step: () => void): void;
|
|
469
688
|
export declare function removeVatOnlyDir(dir: string | undefined): void;
|
|
689
|
+
/** Everything one executor→grader pipeline worker needs, built once per run. */
|
|
690
|
+
interface EvalRunContext {
|
|
691
|
+
subjectStagedDir: string;
|
|
692
|
+
workspacesRoot: string;
|
|
693
|
+
/** This run's opaque per-arm workspace segments (see {@link mintArmWorkspaceDirs}). */
|
|
694
|
+
armDirs: ArmWorkspaceDirs;
|
|
695
|
+
pluginDirs: string[];
|
|
696
|
+
graderOutDir: string;
|
|
697
|
+
/**
|
|
698
|
+
* The vat-only dir a harvested eval suite is held in — the ANSWER KEY for a
|
|
699
|
+
* fetched-artifact subject. Carried on the context solely so the WITHOUT arm's
|
|
700
|
+
* contamination scan can look for it; nothing in the eval loop reads from it.
|
|
701
|
+
*/
|
|
702
|
+
evalSuiteHoldDir: string;
|
|
703
|
+
/**
|
|
704
|
+
* Verbatim lines of the staged SKILL.md, for the skill-absent arm's scan. The
|
|
705
|
+
* only contamination signal that does not need the arm to name a path or an
|
|
706
|
+
* executable, and so the only cover an instruction-only skill has.
|
|
707
|
+
*/
|
|
708
|
+
skillContentNeedles: readonly string[];
|
|
709
|
+
runNonce: string;
|
|
710
|
+
graderModel: string;
|
|
711
|
+
model?: string;
|
|
712
|
+
maxTurns: number;
|
|
713
|
+
maxBudgetUsd: number;
|
|
714
|
+
timeoutMs: number;
|
|
715
|
+
stallMs?: number;
|
|
716
|
+
/**
|
|
717
|
+
* Full assembled env (skill secrets included) for the executor spawn, WITHOUT
|
|
718
|
+
* the declared `env` injections — those carry `${fixturesDir}`, which names the
|
|
719
|
+
* eval's own workspace and so can only be resolved per eval (see `perEvalEnv`).
|
|
720
|
+
*/
|
|
721
|
+
executorEnv: NodeJS.ProcessEnv;
|
|
722
|
+
/**
|
|
723
|
+
* Present only when the subject declares an `env` block. Re-assembles the child
|
|
724
|
+
* env per eval so `${fixturesDir}` resolves under THAT eval's workspace. The
|
|
725
|
+
* transparency line is emitted once, by whichever eval assembles first.
|
|
726
|
+
*/
|
|
727
|
+
perEvalEnv: PerEvalEnvAssembly | undefined;
|
|
728
|
+
/** AUTH-ONLY env for the grader spawn (trusted vat infra loads no skill). */
|
|
729
|
+
graderEnv: NodeJS.ProcessEnv;
|
|
730
|
+
/** Declared executables (WITH-arm grader aid + the detector's path needles); absent when unreachable. */
|
|
731
|
+
declaredExecutables?: Array<{
|
|
732
|
+
name: string;
|
|
733
|
+
howInvoked: string;
|
|
734
|
+
kind: string;
|
|
735
|
+
path: string;
|
|
736
|
+
}>;
|
|
737
|
+
spawn?: typeof spawnHeadlessClaude;
|
|
738
|
+
/** Shared spend accumulator — each worker folds in its executor + grader session cost. */
|
|
739
|
+
costAccumulator: RunCostSummary;
|
|
740
|
+
/**
|
|
741
|
+
* The harness root, where vat stages runnable copies of the subject. Used ONLY
|
|
742
|
+
* as a contamination needle for the skill-absent arm (see baseline-integrity.ts):
|
|
743
|
+
* nothing points the control arm here any more, so a mention of this path in its
|
|
744
|
+
* transcript means it went looking and found vat's own copy.
|
|
745
|
+
*/
|
|
746
|
+
harnessRoot: string;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* The control arm ran out of rate-limit retries — record it and let the run live.
|
|
750
|
+
*
|
|
751
|
+
* The retryable case is handled correctly by {@link runEvalWorker} and must stay that
|
|
752
|
+
* way; this is the EXHAUSTED case, which used to be indistinguishable from it. A
|
|
753
|
+
* control executor rate-limited on every attempt threw after five retries, taking
|
|
754
|
+
* down a run in which both treatment executors and both treatment graders had already
|
|
755
|
+
* completed and been billed, leaving `results/` holding `provenance.json` and nothing
|
|
756
|
+
* else — the exact failure {@link runEvalWorker} exists to prevent, reached by the one
|
|
757
|
+
* error class it had to let through.
|
|
758
|
+
*
|
|
759
|
+
* The treatment arm still hard-fails: there is no treatment result to salvage, which
|
|
760
|
+
* is the same asymmetry {@link EvalWorkOutcome} documents.
|
|
761
|
+
*
|
|
762
|
+
* Wired as the pipeline's `onRetriesExhausted`, and `--baseline` — which doubles the
|
|
763
|
+
* spawn count — is the run most likely to need it.
|
|
764
|
+
*/
|
|
765
|
+
export declare function rateLimitExhaustedOutcome(item: EvalWorkItem, error: RateLimitSignal): EvalWorkOutcome;
|
|
766
|
+
/**
|
|
767
|
+
* Strip the fields VAT attaches itself, before VAT attaches them.
|
|
768
|
+
*
|
|
769
|
+
* The grader's only input is the executor transcript, which untrusted skill code
|
|
770
|
+
* controls — so a prompt injection there could otherwise fabricate a
|
|
771
|
+
* `BASELINE CONTAMINATED` verdict carrying attacker-chosen strings into an
|
|
772
|
+
* operator-facing artifact, or (via `degraded`) claim a scan degraded that did not.
|
|
773
|
+
* Spreading VAT's value last is NOT sufficient: it is absent on a clean run, so it
|
|
774
|
+
* overwrites nothing — which is exactly why the omission direction matters more
|
|
775
|
+
* than the invention one for `degraded`. A grader that omits it cannot re-hide a
|
|
776
|
+
* degradation VAT detected, because VAT's value is spread after this strip; a
|
|
777
|
+
* grader that INVENTS it would otherwise stamp a blind-scan warning on a run that
|
|
778
|
+
* scanned properly, and an operator who learns to ignore that warning stops reading
|
|
779
|
+
* the real ones.
|
|
780
|
+
*/
|
|
781
|
+
export declare function withoutGraderContamination(fragment: EvalFragment): EvalFragment;
|
|
782
|
+
/**
|
|
783
|
+
* What one work item produced: a graded fragment, or — CONTROL ARM ONLY — a
|
|
784
|
+
* recorded failure that the run carries on past.
|
|
785
|
+
*
|
|
786
|
+
* The asymmetry is the whole point and it is not a subjective call. A treatment-arm
|
|
787
|
+
* failure leaves nothing to salvage: `grading.json` IS the treatment arm, so a run
|
|
788
|
+
* that cannot produce it has produced nothing and may as well stop. A control-arm
|
|
789
|
+
* failure destroys only the second half of a comparison whose first half is already
|
|
790
|
+
* bought and paid for — and the artifact that reports that comparison already has a
|
|
791
|
+
* way to say "I do not exist here" (`delta: null`). Failing the whole run on a
|
|
792
|
+
* control-arm timeout threw away every treatment executor and grader spawn that had
|
|
793
|
+
* already completed, wrote no `grading.json` at all, and left `results/` holding
|
|
794
|
+
* only `provenance.json`.
|
|
795
|
+
*/
|
|
796
|
+
type EvalWorkOutcome = {
|
|
797
|
+
readonly fragment: EvalFragment;
|
|
798
|
+
readonly controlFailure?: undefined;
|
|
799
|
+
} | {
|
|
800
|
+
readonly fragment?: undefined;
|
|
801
|
+
readonly controlFailure: BaselineControlArmFailure;
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
* Lift the run's content needles off the STAGED SKILL.md.
|
|
805
|
+
*
|
|
806
|
+
* Staged, not source: the staged copy is byte-identical to what the treatment arm
|
|
807
|
+
* is given, so a needle taken from it is exactly the text an ambient copy of the
|
|
808
|
+
* same build would carry.
|
|
809
|
+
*
|
|
810
|
+
* The exclusion set is EVERY channel through which vat itself hands the arm text:
|
|
811
|
+
* the suite's prompts, its `expected_output`, its expectations, and the CONTENTS
|
|
812
|
+
* of the input `files` fixtures vat stages into the arm's workspace and instructs
|
|
813
|
+
* it to operate on. An adopter who quotes a sentence of their SKILL.md through any
|
|
814
|
+
* of the four would otherwise stamp every run contaminated, including the arm that
|
|
815
|
+
* merely read the input vat gave it — the skill's words arriving THROUGH vat are
|
|
816
|
+
* not the arm reaching the skill. The fixture channel is the one that was missed,
|
|
817
|
+
* and it is the worst of the four to get wrong: the summary attached to that
|
|
818
|
+
* verdict tells the operator to go uninstall an ambient copy of the plugin that
|
|
819
|
+
* does not exist.
|
|
820
|
+
*
|
|
821
|
+
* Fixtures are read from the STAGED workspace rather than the suite directory, for
|
|
822
|
+
* the same reason the SKILL.md is: that is byte-for-byte what the arm was handed.
|
|
823
|
+
*
|
|
824
|
+
* Returns `[]` when the file cannot be read. A missing SKILL.md is not this
|
|
825
|
+
* function's error to raise (staging has already validated the subject), and an
|
|
826
|
+
* unarmed signal is reported as unarmed rather than claimed as clean. An
|
|
827
|
+
* unreadable FIXTURE is skipped for the same reason, and errs the safe way: a
|
|
828
|
+
* fixture we could not exclude can only make the signal noisier, never blinder.
|
|
829
|
+
*/
|
|
830
|
+
export declare function resolveSkillContentNeedles(subjectStagedDir: string, evals: readonly EvalEntry[],
|
|
831
|
+
/**
|
|
832
|
+
* Where the staged fixtures live, as the two inputs an arm root is DERIVED from
|
|
833
|
+
* rather than as the pre-joined path itself — see {@link armWorkspaceRoot}. Taking
|
|
834
|
+
* a bare `string` here let the call site pass `workspacesRoot` (one level too high,
|
|
835
|
+
* so every `<id>/` fixture lookup missed) with the whole unit suite still green.
|
|
836
|
+
*
|
|
837
|
+
* The `with` arm, because the fixture BYTES are what matter and both arms are
|
|
838
|
+
* staged from the same source — and `with` is the only arm every run mints.
|
|
839
|
+
*/
|
|
840
|
+
workspaces: {
|
|
841
|
+
workspacesRoot: string;
|
|
842
|
+
armDirs: ArmWorkspaceDirs;
|
|
843
|
+
}): string[];
|
|
844
|
+
/**
|
|
845
|
+
* Baseline integrity for ONE skill-absent eval: vat has removed its OWN copies
|
|
846
|
+
* from the control arm's reach, but an ambient copy in the adopter's repo or
|
|
847
|
+
* installed plugin cache is not vat's to remove — so the control arm can still,
|
|
848
|
+
* in principle, find and run the skill. Detect it from the transcript and return
|
|
849
|
+
* the fragment patch that carries the evidence to the merge step, which turns it
|
|
850
|
+
* into a loud caveat on baseline.json. A wrong number that announces itself is
|
|
851
|
+
* recoverable; a silent one gets believed.
|
|
852
|
+
*
|
|
853
|
+
* Returns an empty patch when clean, so the caller spreads it unconditionally.
|
|
854
|
+
*/
|
|
855
|
+
/**
|
|
856
|
+
* Exactly the per-run values {@link buildContaminationInput} reads. A `Pick`, not a
|
|
857
|
+
* fresh interface, so it cannot drift from the context the run actually assembles.
|
|
858
|
+
*/
|
|
859
|
+
export type ContaminationCtx = Pick<EvalRunContext, 'armDirs' | 'workspacesRoot' | 'harnessRoot' | 'evalSuiteHoldDir' | 'graderOutDir' | 'skillContentNeedles' | 'declaredExecutables'>;
|
|
860
|
+
/**
|
|
861
|
+
* Build the detector input for the skill-absent arm. ONE builder, so the hits
|
|
862
|
+
* written per eval and the `signals` list stamped on the run-level integrity
|
|
863
|
+
* block are derived from the same values — a second construction site is how the
|
|
864
|
+
* block would come to claim a detector the scan never actually ran.
|
|
865
|
+
*
|
|
866
|
+
* `transcript` and `evalId` are supplied per call; everything else is per-run.
|
|
867
|
+
*
|
|
868
|
+
* Takes a NARROWED context rather than the whole {@link EvalRunContext} so the
|
|
869
|
+
* builder is directly unit-testable — the detector honours `armCwd` (dropping it
|
|
870
|
+
* there kills three tests) while the wiring that SUPPLIES it was droppable green,
|
|
871
|
+
* and a dropped `armCwd` anchors every relative climb one directory too high, where
|
|
872
|
+
* no needle can see it.
|
|
873
|
+
*
|
|
874
|
+
* `evalId` is what turns the arm's workspace ROOT into the arm's actual starting
|
|
875
|
+
* CWD (`<armWorkspaceDir>/<evalId>` — see {@link resolvePerEvalWorkspaceDir}).
|
|
876
|
+
* The detector needs both and they are one directory apart: containment is judged
|
|
877
|
+
* against the root (the arm's whole tree is its own), while the cwd walk must
|
|
878
|
+
* start exactly where the executor did, or every relative climb in the transcript
|
|
879
|
+
* resolves one level too high and lands where no needle can see it.
|
|
880
|
+
*
|
|
881
|
+
* `evalId` is REQUIRED here, and the signals-only caller gets its own entry point
|
|
882
|
+
* ({@link buildContaminationSignalsInput}) rather than sharing an optional third
|
|
883
|
+
* argument. An optional parameter is a pin the compiler cannot hold: dropping the
|
|
884
|
+
* argument at the SCAN call site left all 2270 unit tests green and was caught only
|
|
885
|
+
* by the integration suite — which this repo's fast implementer loop explicitly says
|
|
886
|
+
* not to run. That is the same shape as the dropped `armCwd` that anchored the whole
|
|
887
|
+
* walk one directory too high, also green.
|
|
888
|
+
*/
|
|
889
|
+
export declare function buildContaminationInput(transcript: string, ctx: ContaminationCtx, evalId: string): DetectBaselineContaminationInput;
|
|
890
|
+
/**
|
|
891
|
+
* The same input built for the "which detectors are ARMED?" question, which is asked
|
|
892
|
+
* once per run with no transcript and no eval — so it can honestly supply neither,
|
|
893
|
+
* and `armCwd` is correctly absent rather than accidentally dropped.
|
|
894
|
+
*/
|
|
895
|
+
export declare function buildContaminationSignalsInput(ctx: ContaminationCtx): DetectBaselineContaminationInput;
|
|
470
896
|
/** The results/ artifacts vat is the SOLE writer of, resolved for one run. */
|
|
471
897
|
export interface ArtifactPaths {
|
|
472
898
|
gradingOut: string;
|
|
@@ -488,6 +914,85 @@ export declare function resolveArtifactPaths(resultsDir: string): ArtifactPaths;
|
|
|
488
914
|
* (`force: true`) — a missing file is fine.
|
|
489
915
|
*/
|
|
490
916
|
export declare function wipeStaleArtifacts(paths: ArtifactPaths): void;
|
|
917
|
+
/**
|
|
918
|
+
* Where a results artifact goes when it fails the fail-closed gate.
|
|
919
|
+
*
|
|
920
|
+
* The gate throws and `results/` is deliberately RETAINED, so the file that failed
|
|
921
|
+
* used to sit there under its authoritative name — the one artifact whose schema
|
|
922
|
+
* violation is BY DEFINITION a harness bug, indistinguishable in a CI archive from a
|
|
923
|
+
* good one until the next run's wipe. Renamed rather than deleted: the bytes are what
|
|
924
|
+
* a maintainer needs in order to see which refine tripped, and a name ending in
|
|
925
|
+
* `.rejected` cannot be mistaken for the artifact a reader came for.
|
|
926
|
+
*/
|
|
927
|
+
export declare function rejectedArtifactPath(path: string): string;
|
|
928
|
+
/**
|
|
929
|
+
* Merge the run's grader fragments and WRITE the run artifacts (vat is the SOLE
|
|
930
|
+
* writer): grading.json from the WITH-arm fragments, friction.json from ALL
|
|
931
|
+
* fragments, tool-eval.json from the WITH-arm fragments' `tool` verdicts (a
|
|
932
|
+
* SEPARATE channel — C2; the WITHOUT arm carries no toolExpectations), and — only
|
|
933
|
+
* when a WITHOUT arm ran (baseline) — baseline.json from the WITHOUT-arm fragments
|
|
934
|
+
* (informational A/B, never part of the verdict). Stale copies from a reused harness
|
|
935
|
+
* were already removed by {@link wipeStaleArtifacts} pre-pipeline. Returns the
|
|
936
|
+
* reconciled prose-expectation verdict AND the merged tool-eval report so the caller
|
|
937
|
+
* can compute the COMPOSITE verdict. Every fragment's per-run nonce is re-verified
|
|
938
|
+
* inside {@link mergeFragmentsToGrading}.
|
|
939
|
+
*/
|
|
940
|
+
/**
|
|
941
|
+
* Collect per-eval baseline-integrity findings from the WITHOUT-arm fragments.
|
|
942
|
+
* Fragments with no `contamination` are clean and contribute nothing. Pure.
|
|
943
|
+
*/
|
|
944
|
+
/**
|
|
945
|
+
* What one arm graded, per eval — the SINGLE derivation behind both the parity check
|
|
946
|
+
* ({@link armExpectationSkew}, which reads `total`) and the A/B delta (which reads
|
|
947
|
+
* `passed`). Deriving those separately would let the two blocks in `baseline.json`
|
|
948
|
+
* disagree about the same arm. Pure.
|
|
949
|
+
*
|
|
950
|
+
* Exported for its own unit test, not for reuse. Setting `passed` to `total` here
|
|
951
|
+
* failed zero unit tests and three integration ones: every unit fixture graded
|
|
952
|
+
* everything it declared, so the two fields were numerically identical in all of
|
|
953
|
+
* them and the ONE thing this function exists to keep distinct was unpinned.
|
|
954
|
+
*/
|
|
955
|
+
export declare function gradedCounts(fragments: EvalFragment[]): ArmEvalGrade[];
|
|
956
|
+
/**
|
|
957
|
+
* The stderr report for a `--baseline` run: the delta line, then the ⚠️ banner when
|
|
958
|
+
* the run is unusable. Pure — the caller decides WHEN it goes out, which is the
|
|
959
|
+
* whole point (see the call site).
|
|
960
|
+
*
|
|
961
|
+
* Composed on EVERY baseline run, including a withheld one: "the delta is
|
|
962
|
+
* unavailable, and here is why" is the report an operator needs, and a run that
|
|
963
|
+
* silently prints nothing is indistinguishable from a vat that still does not
|
|
964
|
+
* compute a delta at all. The delta line comes FIRST so an unusable run reads as the
|
|
965
|
+
* number followed by the caveat that explains it — the banner is the only line that
|
|
966
|
+
* says the number means nothing, so it must never be the one scrolled away.
|
|
967
|
+
*
|
|
968
|
+
* ⚠️ IT IS NOT LITERALLY LAST ON THE SCREEN, and this docblock used to say it was.
|
|
969
|
+
* The CLI prints `Harness:`, `Results:`, `Workspaces:` and `Summary:` AFTER the
|
|
970
|
+
* harness returns, so 2–4 lines always follow the banner. What this ordering buys is
|
|
971
|
+
* that the banner is not separated from its delta by up to ~1250 rows of friction —
|
|
972
|
+
* a bounded, short tail after it, not none. Moving those CLI lines above the harness
|
|
973
|
+
* report is the CLI's call, not this module's.
|
|
974
|
+
*
|
|
975
|
+
* THREE conditions have to hold before the caveat is dropped, not two. A DEGRADED
|
|
976
|
+
* scan is neither contaminated nor incomparable, so a two-clause gate printed
|
|
977
|
+
* `Baseline delta: +0 (with skill: 2/2, without skill: 2/2).` and exited 0 while
|
|
978
|
+
* `baseline.json` carried `degraded: [...]` and an `integrity.summary` ending in a
|
|
979
|
+
* `⚠️ DEGRADED SCAN: …` sentence that was composed, written to the artifact, and
|
|
980
|
+
* never emitted anywhere. Everything this module says about telling "checked and clean"
|
|
981
|
+
* apart from "checked with the blunt instrument" was true of the artifact and false
|
|
982
|
+
* of the terminal — and the terminal is the only surface an operator reads.
|
|
983
|
+
*
|
|
984
|
+
* A CONTAMINATED run still reports its number, deliberately. Contamination does not
|
|
985
|
+
* make the subtraction illegal — both arms were graded to the same depth, so the
|
|
986
|
+
* arithmetic is sound; what it invalidates is INTERPRETING that number as skill
|
|
987
|
+
* lift, which is precisely what the ⚠️ line says ("The reported delta is NOT a
|
|
988
|
+
* measure of skill lift"), and what `vat-skill-testing.md` turns into the
|
|
989
|
+
* instruction "discard the delta". Withholding it here would (a) leave that sentence
|
|
990
|
+
* referring to a number that was never reported, and (b) spend `null` — which means
|
|
991
|
+
* "these arms cannot be subtracted", a different failure with a different remedy —
|
|
992
|
+
* on a run where they can be. A contaminated delta is also diagnostic in its own
|
|
993
|
+
* right: it collapses toward zero, which is corroboration, not noise.
|
|
994
|
+
*/
|
|
995
|
+
export declare function formatBaselineReport(delta: BaselineDelta, integrity: BaselineIntegrity): string;
|
|
491
996
|
/**
|
|
492
997
|
* The COMPOSITE run verdict (issue #145 Phase T): the run passes only when BOTH
|
|
493
998
|
* the prose-expectation grading passed AND every tool-expectation verdict passed.
|
|
@@ -540,9 +1045,47 @@ export declare function buildRunSummary(verdict: GradingVerdict, toolEval: ToolE
|
|
|
540
1045
|
* the call site), so the base line is already FAIL when the note is present.
|
|
541
1046
|
*/
|
|
542
1047
|
export declare function buildRunSummaryWithSkips(verdict: GradingVerdict, toolEval: ToolEvalReport, compositeAllPassed: boolean, skipped: SkippedEvalsSummary | undefined, cost?: RunCostSummary): string;
|
|
1048
|
+
/**
|
|
1049
|
+
* Post-merge D2 gate over the vat-written results artifacts (vat is SOLE writer):
|
|
1050
|
+
* grading.json, friction.json, tool-eval.json — and, on a baseline run, baseline.json
|
|
1051
|
+
* — must each exist + parse + validate. A separate, explicit check (NOT folded into
|
|
1052
|
+
* any prompt-invariant linter) — fail-closed and safe because vat wrote every one of
|
|
1053
|
+
* these itself just above.
|
|
1054
|
+
*
|
|
1055
|
+
* `baseline.json` was EXEMPT from this gate for as long as the gate has existed, and
|
|
1056
|
+
* it is the artifact the exemption fits worst: on a `--baseline` run it is the only
|
|
1057
|
+
* durable record of the thing the command sells, and this branch has already been
|
|
1058
|
+
* bitten once by a default run deleting `results/` out from under it. Its validation
|
|
1059
|
+
* is also the strictest of the four — {@link BaselineArtifactSchema} carries the
|
|
1060
|
+
* `passed <= total` and `delta === with − without` refines, so a merge that produced
|
|
1061
|
+
* a self-contradicting number fails the run here instead of being written down.
|
|
1062
|
+
*
|
|
1063
|
+
* `baselineRun` gates it rather than "does the file exist", deliberately: an
|
|
1064
|
+
* existence check would pass on the one failure that matters (vat asked for a
|
|
1065
|
+
* baseline and wrote nothing), which is exactly what a fail-CLOSED gate must not do.
|
|
1066
|
+
* A non-baseline run writes no baseline.json at all and is not asked about one.
|
|
1067
|
+
*/
|
|
1068
|
+
export declare function assertVatWroteArtifacts(paths: ArtifactPaths, baselineRun: boolean): void;
|
|
543
1069
|
/**
|
|
544
1070
|
* Domain orchestrator for `vat skill test run`. Pure of process.exit — all
|
|
545
1071
|
* exit-code decisions live in the caller (run.ts).
|
|
546
1072
|
*/
|
|
1073
|
+
/**
|
|
1074
|
+
* Resolve WHERE this run lives, and whether the harness owns that location.
|
|
1075
|
+
*
|
|
1076
|
+
* `--out` names the harness root exactly; `--workdir` names the BASE the root is
|
|
1077
|
+
* derived under. They are mutually exclusive: passing both used to silently
|
|
1078
|
+
* discard `--workdir` — the run went entirely to `--out` and the `--workdir` path
|
|
1079
|
+
* was never even created — so an operator using the pair to separate the
|
|
1080
|
+
* executor's cwd from the staged trees got no separation and no warning.
|
|
1081
|
+
*
|
|
1082
|
+
* `harnessCreated` is true only for the derived-under-OS-tmp case; an explicit
|
|
1083
|
+
* `--out` or `--workdir` is a location the user owns and is never auto-removed.
|
|
1084
|
+
*/
|
|
1085
|
+
export declare function resolveHarnessLocation(opts: Pick<RunHarnessOptions, 'out' | 'workdir' | 'subject'>): {
|
|
1086
|
+
harnessRoot: string;
|
|
1087
|
+
harnessCreated: boolean;
|
|
1088
|
+
};
|
|
547
1089
|
export declare function runSkillTestHarness(opts: RunHarnessOptions): Promise<RunHarnessResult>;
|
|
1090
|
+
export {};
|
|
548
1091
|
//# sourceMappingURL=run-harness.d.ts.map
|