@vibe-agent-toolkit/agent-skills 0.1.42 → 0.2.0-rc.10
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/README.md +5 -5
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +7 -17
- package/dist/builder.js.map +1 -1
- package/dist/content-type-routing.d.ts +32 -0
- package/dist/content-type-routing.d.ts.map +1 -1
- package/dist/content-type-routing.js +23 -0
- package/dist/content-type-routing.js.map +1 -1
- package/dist/files-config.d.ts +34 -1
- package/dist/files-config.d.ts.map +1 -1
- package/dist/files-config.js +277 -39
- 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/import.d.ts.map +1 -1
- package/dist/import.js +0 -4
- package/dist/import.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -7
- 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/inventory/types.d.ts +8 -0
- package/dist/inventory/types.d.ts.map +1 -1
- package/dist/inventory/types.js.map +1 -1
- package/dist/link-graph-members.d.ts +18 -0
- package/dist/link-graph-members.d.ts.map +1 -0
- package/dist/link-graph-members.js +18 -0
- package/dist/link-graph-members.js.map +1 -0
- package/dist/parsers/frontmatter-parser.d.ts.map +1 -1
- package/dist/parsers/frontmatter-parser.js +30 -6
- package/dist/parsers/frontmatter-parser.js.map +1 -1
- package/dist/plugin-distribution-layout.d.ts +70 -6
- package/dist/plugin-distribution-layout.d.ts.map +1 -1
- package/dist/plugin-distribution-layout.js +84 -11
- package/dist/plugin-distribution-layout.js.map +1 -1
- package/dist/post-build-checks.d.ts +38 -1
- package/dist/post-build-checks.d.ts.map +1 -1
- package/dist/post-build-checks.js +68 -17
- 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 +560 -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/schemas/allowed-tools.d.ts +31 -0
- package/dist/schemas/allowed-tools.d.ts.map +1 -0
- package/dist/schemas/allowed-tools.js +88 -0
- package/dist/schemas/allowed-tools.js.map +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/installed-plugins-registry.d.ts.map +1 -1
- package/dist/schemas/installed-plugins-registry.js +7 -1
- package/dist/schemas/installed-plugins-registry.js.map +1 -1
- package/dist/schemas/marketplace-manifest.d.ts.map +1 -1
- package/dist/schemas/marketplace-manifest.js +17 -6
- package/dist/schemas/marketplace-manifest.js.map +1 -1
- package/dist/skill-identity.d.ts +18 -3
- package/dist/skill-identity.d.ts.map +1 -1
- package/dist/skill-identity.js +25 -6
- package/dist/skill-identity.js.map +1 -1
- package/dist/skill-packager.d.ts +144 -20
- package/dist/skill-packager.d.ts.map +1 -1
- package/dist/skill-packager.js +447 -105
- package/dist/skill-packager.js.map +1 -1
- package/dist/skill-source/content-hash.d.ts.map +1 -1
- package/dist/skill-source/content-hash.js +21 -10
- package/dist/skill-source/content-hash.js.map +1 -1
- package/dist/skill-source/fetch-cache.d.ts.map +1 -1
- package/dist/skill-source/fetch-cache.js +0 -5
- package/dist/skill-source/fetch-cache.js.map +1 -1
- package/dist/skill-source/git-clone.d.ts +1 -1
- package/dist/skill-source/git-clone.d.ts.map +1 -1
- package/dist/skill-source/git-clone.js +10 -10
- package/dist/skill-source/git-clone.js.map +1 -1
- package/dist/skill-source/sources/url-source.d.ts.map +1 -1
- package/dist/skill-source/sources/url-source.js +2 -2
- package/dist/skill-source/sources/url-source.js.map +1 -1
- package/dist/skill-source/stage.d.ts.map +1 -1
- package/dist/skill-source/stage.js +0 -6
- package/dist/skill-source/stage.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 +3090 -0
- package/dist/skill-test/baseline-integrity.js.map +1 -0
- package/dist/skill-test/build-hook.d.ts +3 -2
- package/dist/skill-test/build-hook.d.ts.map +1 -1
- package/dist/skill-test/build-hook.js +4 -5
- package/dist/skill-test/build-hook.js.map +1 -1
- package/dist/skill-test/configure-writer.js +1 -1
- package/dist/skill-test/configure-writer.js.map +1 -1
- package/dist/skill-test/declared-env.d.ts +5 -4
- package/dist/skill-test/declared-env.d.ts.map +1 -1
- package/dist/skill-test/declared-env.js +8 -9
- package/dist/skill-test/declared-env.js.map +1 -1
- package/dist/skill-test/eval-executor.d.ts +21 -5
- package/dist/skill-test/eval-executor.d.ts.map +1 -1
- package/dist/skill-test/eval-executor.js +6 -6
- package/dist/skill-test/eval-executor.js.map +1 -1
- package/dist/skill-test/eval-fragment.d.ts +106 -28
- package/dist/skill-test/eval-fragment.d.ts.map +1 -1
- package/dist/skill-test/eval-fragment.js +78 -16
- package/dist/skill-test/eval-fragment.js.map +1 -1
- package/dist/skill-test/eval-grader.d.ts +65 -7
- package/dist/skill-test/eval-grader.d.ts.map +1 -1
- package/dist/skill-test/eval-grader.js +263 -64
- package/dist/skill-test/eval-grader.js.map +1 -1
- package/dist/skill-test/eval-inputs.d.ts +110 -8
- package/dist/skill-test/eval-inputs.d.ts.map +1 -1
- package/dist/skill-test/eval-inputs.js +177 -22
- package/dist/skill-test/eval-inputs.js.map +1 -1
- package/dist/skill-test/eval-lint.d.ts +17 -4
- package/dist/skill-test/eval-lint.d.ts.map +1 -1
- package/dist/skill-test/eval-lint.js +22 -9
- package/dist/skill-test/eval-lint.js.map +1 -1
- package/dist/skill-test/eval-suite-isolation.d.ts.map +1 -1
- package/dist/skill-test/eval-suite-isolation.js +8 -5
- package/dist/skill-test/eval-suite-isolation.js.map +1 -1
- package/dist/skill-test/evals-template.d.ts +1 -1
- package/dist/skill-test/evals-template.d.ts.map +1 -1
- package/dist/skill-test/evals-template.js +1 -3
- package/dist/skill-test/evals-template.js.map +1 -1
- package/dist/skill-test/executor-prompt.d.ts +15 -7
- package/dist/skill-test/executor-prompt.d.ts.map +1 -1
- package/dist/skill-test/executor-prompt.js +10 -7
- package/dist/skill-test/executor-prompt.js.map +1 -1
- package/dist/skill-test/failure-reason.d.ts +113 -0
- package/dist/skill-test/failure-reason.d.ts.map +1 -0
- package/dist/skill-test/failure-reason.js +124 -0
- package/dist/skill-test/failure-reason.js.map +1 -0
- package/dist/skill-test/fragment-merge.d.ts +3 -20
- package/dist/skill-test/fragment-merge.d.ts.map +1 -1
- package/dist/skill-test/fragment-merge.js +67 -8
- package/dist/skill-test/fragment-merge.js.map +1 -1
- package/dist/skill-test/grader-prompt.d.ts +29 -9
- package/dist/skill-test/grader-prompt.d.ts.map +1 -1
- package/dist/skill-test/grader-prompt.js +264 -68
- 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 +38 -2
- package/dist/skill-test/grading-adapter.d.ts.map +1 -1
- package/dist/skill-test/grading-adapter.js +57 -9
- 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/harness-location.d.ts +3 -2
- package/dist/skill-test/harness-location.d.ts.map +1 -1
- package/dist/skill-test/harness-location.js +5 -16
- package/dist/skill-test/harness-location.js.map +1 -1
- package/dist/skill-test/index.d.ts +2 -2
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +2 -2
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/lock.d.ts +58 -1
- package/dist/skill-test/lock.d.ts.map +1 -1
- package/dist/skill-test/lock.js +78 -6
- package/dist/skill-test/lock.js.map +1 -1
- package/dist/skill-test/pipeline.d.ts +19 -1
- package/dist/skill-test/pipeline.d.ts.map +1 -1
- package/dist/skill-test/pipeline.js +21 -10
- package/dist/skill-test/pipeline.js.map +1 -1
- package/dist/skill-test/preflight.d.ts +1 -1
- package/dist/skill-test/preflight.d.ts.map +1 -1
- package/dist/skill-test/preflight.js +59 -16
- package/dist/skill-test/preflight.js.map +1 -1
- package/dist/skill-test/prompt-invariants.d.ts +4 -2
- package/dist/skill-test/prompt-invariants.d.ts.map +1 -1
- package/dist/skill-test/prompt-invariants.js +5 -4
- package/dist/skill-test/prompt-invariants.js.map +1 -1
- package/dist/skill-test/run-harness.d.ts +665 -85
- package/dist/skill-test/run-harness.d.ts.map +1 -1
- package/dist/skill-test/run-harness.js +1522 -237
- package/dist/skill-test/run-harness.js.map +1 -1
- package/dist/skill-test/staging.d.ts +20 -8
- package/dist/skill-test/staging.d.ts.map +1 -1
- package/dist/skill-test/staging.js +25 -18
- package/dist/skill-test/staging.js.map +1 -1
- package/dist/skill-test/tier-plan.d.ts +1 -1
- package/dist/skill-test/tier-plan.js +1 -1
- package/dist/skill-test/tool-eval-schema.d.ts +6 -6
- package/dist/skill-test/tool-eval-schema.js +2 -2
- package/dist/skill-test/vendor-manifest.d.ts +2 -1
- package/dist/skill-test/vendor-manifest.d.ts.map +1 -1
- package/dist/skill-test/vendor-manifest.js +10 -10
- package/dist/skill-test/vendor-manifest.js.map +1 -1
- package/dist/test-input.d.ts +59 -8
- package/dist/test-input.d.ts.map +1 -1
- package/dist/test-input.js +64 -19
- package/dist/test-input.js.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/agent-instruction-presence.js +45 -2
- package/dist/validators/agent-instruction-presence.js.map +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts +6 -4
- package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
- package/dist/validators/bundled-resource-link-detection.js +39 -39
- package/dist/validators/bundled-resource-link-detection.js.map +1 -1
- package/dist/validators/compat-detectors.d.ts +18 -1
- package/dist/validators/compat-detectors.d.ts.map +1 -1
- package/dist/validators/compat-detectors.js +47 -20
- 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/format-detection.d.ts +3 -0
- package/dist/validators/format-detection.d.ts.map +1 -1
- package/dist/validators/format-detection.js +41 -26
- package/dist/validators/format-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 +2 -2
- 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.d.ts.map +1 -1
- package/dist/validators/marketplace-validator.js +7 -2
- package/dist/validators/marketplace-validator.js.map +1 -1
- package/dist/validators/mcp-tool-qualification.d.ts +158 -0
- package/dist/validators/mcp-tool-qualification.d.ts.map +1 -0
- package/dist/validators/mcp-tool-qualification.js +467 -0
- package/dist/validators/mcp-tool-qualification.js.map +1 -0
- package/dist/validators/packaged-size-limit.d.ts +296 -0
- package/dist/validators/packaged-size-limit.d.ts.map +1 -0
- package/dist/validators/packaged-size-limit.js +492 -0
- package/dist/validators/packaged-size-limit.js.map +1 -0
- package/dist/validators/packaging-validator.d.ts +149 -10
- package/dist/validators/packaging-validator.d.ts.map +1 -1
- package/dist/validators/packaging-validator.js +371 -55
- package/dist/validators/packaging-validator.js.map +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts +4 -2
- package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
- package/dist/validators/plugin-hosted-shape.js +6 -7
- 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/referenced-path-missing.d.ts +211 -0
- package/dist/validators/referenced-path-missing.d.ts.map +1 -0
- package/dist/validators/referenced-path-missing.js +378 -0
- package/dist/validators/referenced-path-missing.js.map +1 -0
- package/dist/validators/registry-validator.d.ts.map +1 -1
- package/dist/validators/registry-validator.js +1 -2
- package/dist/validators/registry-validator.js.map +1 -1
- package/dist/validators/rule-engine/index.d.ts +1 -1
- package/dist/validators/rule-engine/rule-context.d.ts +20 -3
- package/dist/validators/rule-engine/rule-context.d.ts.map +1 -1
- package/dist/validators/rule-engine/rule-context.js +3 -1
- package/dist/validators/rule-engine/rule-context.js.map +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts +2 -2
- package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
- package/dist/validators/rule-engine/rule-engine.js +52 -13
- 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 +91 -54
- package/dist/validators/skill-validator.js.map +1 -1
- package/dist/validators/source-only-codes.js +1 -1
- package/dist/validators/source-only-codes.js.map +1 -1
- package/dist/validators/types.d.ts +21 -1
- package/dist/validators/types.d.ts.map +1 -1
- package/dist/validators/unified-validator.d.ts.map +1 -1
- package/dist/validators/unified-validator.js +4 -5
- package/dist/validators/unified-validator.js.map +1 -1
- package/dist/validators/validation-rules.d.ts +4 -4
- package/dist/validators/validation-rules.d.ts.map +1 -1
- package/dist/validators/validation-rules.js +9 -38
- package/dist/validators/validation-rules.js.map +1 -1
- package/dist/validators/walker-to-issues.d.ts +9 -3
- package/dist/validators/walker-to-issues.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.js +64 -19
- package/dist/validators/walker-to-issues.js.map +1 -1
- package/dist/walk-link-graph.d.ts +64 -5
- package/dist/walk-link-graph.d.ts.map +1 -1
- package/dist/walk-link-graph.js +376 -92
- package/dist/walk-link-graph.js.map +1 -1
- package/package.json +14 -22
- package/dist/skill-test/exit-codes.d.ts +0 -97
- package/dist/skill-test/exit-codes.d.ts.map +0 -1
- package/dist/skill-test/exit-codes.js +0 -141
- package/dist/skill-test/exit-codes.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements the full harness sequence as a pure async function (no process.exit):
|
|
5
5
|
* lock → assert safe workdir + harness root → stage → resolve staged-subject
|
|
6
|
-
* eval path → bootstrap-check (scaffold template + exit
|
|
6
|
+
* eval path → bootstrap-check (scaffold template + exit 2 with `Reason: bootstrap` if absent)
|
|
7
7
|
* → preflight (return early exitCode 2 on failure) → ack enforcement
|
|
8
8
|
* → dry-run short-circuit → build per-eval work items → run the vat-owned
|
|
9
9
|
* executor→grader pipeline (bounded-parallel) → merge grader fragments →
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
* → release lock → return result
|
|
12
12
|
*/
|
|
13
13
|
import type { SkillSourceDescriptor } from '@vibe-agent-toolkit/resources';
|
|
14
|
-
import { type
|
|
14
|
+
import { ExitCode, type ExitCodeValue } from '@vibe-agent-toolkit/schema';
|
|
15
|
+
import { type spawnHeadlessClaude, type ResolvedAuth } from '@vibe-agent-toolkit/utils/skill-test';
|
|
15
16
|
import type { ResolveSkillSourceContext, SkillSource } from '../skill-source/types.js';
|
|
17
|
+
import { type BaselineDelta } from './baseline-delta.js';
|
|
18
|
+
import { type ArmEvalGrade, type BaselineIntegrity, type DetectBaselineContaminationInput } from './baseline-integrity.js';
|
|
16
19
|
import type { EvalFragment } from './eval-fragment.js';
|
|
17
|
-
import { type EvalEntry, type EvalSuite } from './eval-inputs.js';
|
|
18
|
-
import { type
|
|
20
|
+
import { type ArmWorkspaceDirs, type EvalArm, type EvalEntry, type EvalSuite } from './eval-inputs.js';
|
|
21
|
+
import { type SkillTestFailureReason } from './failure-reason.js';
|
|
19
22
|
import { type FrictionItem } from './friction-schema.js';
|
|
20
23
|
import { type GradingVerdict } from './grading-adapter.js';
|
|
21
24
|
import { type PreflightInput } from './preflight.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). */
|
|
@@ -115,6 +127,11 @@ export interface RunHarnessOptions {
|
|
|
115
127
|
* not the dist. Absent → derived from subject (legacy).
|
|
116
128
|
*/
|
|
117
129
|
subjectScaffoldDir?: string;
|
|
130
|
+
/**
|
|
131
|
+
* True when the subject is an IN-PLACE skill (`publish: false`): no bundle exists,
|
|
132
|
+
* so it is staged from source and the run's friction report says so.
|
|
133
|
+
*/
|
|
134
|
+
subjectInPlace: boolean;
|
|
118
135
|
/**
|
|
119
136
|
* True when run.ts actually rebuilt the subject (declared skill, no
|
|
120
137
|
* --no-build/--dry-run). Recorded in provenance. Absent/false → staged as-is.
|
|
@@ -141,18 +158,25 @@ export interface RunHarnessOptions {
|
|
|
141
158
|
* Declared executables the subject skill ships (name + kind + howInvoked),
|
|
142
159
|
* populated by run.ts from the resolved subject's packaging config WHEN cleanly
|
|
143
160
|
* reachable (a `buildable` ref carries `packagingConfig`; a plain path source
|
|
144
|
-
* does not
|
|
161
|
+
* does not). Passed to the grader on the WITH arm ONLY as
|
|
145
162
|
* a recognition aid alongside each eval's `toolExpectations`. Absent → the
|
|
146
163
|
* grader still matches tools by the commands it sees in the transcript.
|
|
164
|
+
*
|
|
165
|
+
* `path` serves a SECOND consumer with a different need: `detectBaselineContamination`
|
|
166
|
+
* matches an escaping reach against the skill-relative PATH, because an ambient copy
|
|
167
|
+
* of the skill reproduces `…/scripts/csvsum.py` under whatever root it sits at, while
|
|
168
|
+
* the control arm's own `/tmp/summary.txt` does not. Matching the extension-stripped
|
|
169
|
+
* `name` instead convicted clean control arms of reading their own scratch files.
|
|
147
170
|
*/
|
|
148
171
|
declaredExecutables?: Array<{
|
|
149
172
|
name: string;
|
|
150
173
|
howInvoked: string;
|
|
151
174
|
kind: string;
|
|
175
|
+
path: string;
|
|
152
176
|
}>;
|
|
153
177
|
/**
|
|
154
178
|
* Opt-OUT of eval gating (for interactive use). By DEFAULT (false/absent) a
|
|
155
|
-
* failing verdict returns exit
|
|
179
|
+
* failing verdict returns exit FINDINGS — fail-closed, so CI catches a
|
|
156
180
|
* regression without an extra flag. When true, a failing verdict is downgraded
|
|
157
181
|
* to Ok (0) and the pass/fail count lives only in the summary/grading.json.
|
|
158
182
|
* Harness-broke codes (1/2/3) are unaffected either way.
|
|
@@ -161,8 +185,35 @@ export interface RunHarnessOptions {
|
|
|
161
185
|
}
|
|
162
186
|
export interface RunHarnessResult {
|
|
163
187
|
harnessPath: string;
|
|
164
|
-
exitCode:
|
|
188
|
+
exitCode: ExitCodeValue;
|
|
189
|
+
/**
|
|
190
|
+
* Present exactly when `exitCode` is `ERROR`: why the harness could not run.
|
|
191
|
+
* The CLI prints it as `Reason: …` on stderr. Absent on a completed run,
|
|
192
|
+
* whatever its verdict.
|
|
193
|
+
*/
|
|
194
|
+
reason?: SkillTestFailureReason;
|
|
165
195
|
summary: string;
|
|
196
|
+
/**
|
|
197
|
+
* Where the executor's per-eval working directories were materialized. Lives
|
|
198
|
+
* OUTSIDE `harnessPath` on purpose (see {@link resolveWorkspacesRoot}), so it
|
|
199
|
+
* cannot be derived from the harness path and has to be reported.
|
|
200
|
+
*
|
|
201
|
+
* Present ONLY when those directories SURVIVE the run — i.e. under `--keep`,
|
|
202
|
+
* the sole thing that retains them (NOT `--out`/`--workdir`, unlike the harness
|
|
203
|
+
* root). Cleanup runs in the harness's own `finally`, before this result reaches
|
|
204
|
+
* any caller, so a populated value is a promise that the path is still there:
|
|
205
|
+
* anything that reports it (the CLI's `Workspaces:` line) may print it as-is.
|
|
206
|
+
* Also absent when the run ended before workspaces were staged.
|
|
207
|
+
*/
|
|
208
|
+
workspacesPath?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Where this run's artifacts (`grading.json`, `friction.json`, `tool-eval.json`,
|
|
211
|
+
* and `baseline.json` on a `--baseline` run) were written. Reported rather than
|
|
212
|
+
* left to be derived from `harnessPath`, because it is the ONLY part of the
|
|
213
|
+
* harness root that survives cleanup on a default run — see
|
|
214
|
+
* {@link cleanupHarness}. Absent when the run ended before Step 7 created it.
|
|
215
|
+
*/
|
|
216
|
+
resultsPath?: string;
|
|
166
217
|
}
|
|
167
218
|
/**
|
|
168
219
|
* Built-in cost/runtime safety ceilings applied PER executor/grader spawn. These
|
|
@@ -186,22 +237,24 @@ export declare const SKILL_TEST_BUILTIN_CAPS: {
|
|
|
186
237
|
};
|
|
187
238
|
/**
|
|
188
239
|
* Resolve the effective PER-EVAL wall-clock timeout (ms). Each executor and
|
|
189
|
-
* grader spawn is an independent, bounded-parallel unit
|
|
240
|
+
* grader spawn is an independent, bounded-parallel unit, so the
|
|
190
241
|
* budget is a flat per-spawn ceiling — NOT scaled by the suite size the way the
|
|
191
242
|
* old single serial run's budget was. An explicit `--timeout` (seconds)
|
|
192
243
|
* wins; otherwise the flat {@link DEFAULT_TIMEOUT_MS} default applies.
|
|
193
244
|
*/
|
|
194
|
-
|
|
245
|
+
declare function resolveTimeoutMs(opts: RunHarnessOptions): number;
|
|
195
246
|
/**
|
|
196
247
|
* Map an eval verdict to a process exit code. Default behavior (fail-closed): a
|
|
197
|
-
* failing verdict
|
|
198
|
-
*
|
|
248
|
+
* failing verdict is `FINDINGS` — the harness ran to completion and what it
|
|
249
|
+
* examined did not pass, exactly as `vat skills validate` reports an
|
|
250
|
+
* error-severity finding — while a harness that could not run is `ERROR`, so a
|
|
251
|
+
* CI consumer can `case $? in 0);; 1) tolerate;; *) hard fail;; esac`.
|
|
199
252
|
* When `tolerateEvalFailure` is set (interactive opt-out), a failing verdict is
|
|
200
|
-
* downgraded to
|
|
253
|
+
* downgraded to `OK` and the count lives only in the summary/grading.json.
|
|
201
254
|
*/
|
|
202
|
-
export declare function verdictExitCode(allPassed: boolean, tolerateEvalFailure: boolean):
|
|
203
|
-
|
|
204
|
-
|
|
255
|
+
export declare function verdictExitCode(allPassed: boolean, tolerateEvalFailure: boolean): typeof ExitCode.OK | typeof ExitCode.FINDINGS;
|
|
256
|
+
declare function resolveStallMs(opts: RunHarnessOptions): number | undefined;
|
|
257
|
+
declare function resolveKnobs(opts: RunHarnessOptions): {
|
|
205
258
|
model?: string;
|
|
206
259
|
maxTurns: number;
|
|
207
260
|
maxBudgetUsd: number;
|
|
@@ -215,30 +268,84 @@ export declare function resolveKnobs(opts: RunHarnessOptions): {
|
|
|
215
268
|
* sources (npm/url/vendored/workspace) have no local source tree to walk, so they
|
|
216
269
|
* are always staged flat (returns undefined). undefined → flat staging.
|
|
217
270
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
271
|
+
declare function detectItemPluginLayout(source: SkillSource, repoRoot: string): StageItem['pluginLayout'] | undefined;
|
|
272
|
+
declare function makeStageItem(name: string, source: SkillSource, repoRoot: string, role: 'subject' | undefined, optional?: true): StageItem;
|
|
220
273
|
/**
|
|
221
274
|
* Build the full set of items `stageHarness` will stage: the subject, every
|
|
222
275
|
* REQUIRED `--with` companion, and every OPTIONAL `--with-optional` companion.
|
|
223
276
|
* Both `with` and `optional` STAGE the named companion and make it invocable —
|
|
224
|
-
* they differ only in required-vs-optional resolution (
|
|
225
|
-
*
|
|
226
|
-
*
|
|
277
|
+
* they differ only in required-vs-optional resolution (a `--with` name that
|
|
278
|
+
* names no positional skill must never be silently dropped — unstaged, with no
|
|
279
|
+
* manifest trace). Fail-closed on a DUPLICATE staged name (subject / `with` /
|
|
227
280
|
* `optional` all share one namespace): the first repeat throws
|
|
228
281
|
* {@link DuplicateStagedSkillError} rather than silently letting a later item
|
|
229
282
|
* clobber an earlier one under the same staged slot.
|
|
230
283
|
*/
|
|
231
|
-
|
|
232
|
-
|
|
284
|
+
declare function buildStageItems(opts: RunHarnessOptions, repoRoot: string): StageItem[];
|
|
285
|
+
declare function buildResolveCtx(harnessRoot: string, repoRoot: string): ResolveSkillSourceContext;
|
|
233
286
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
287
|
+
* Match a flag name as a whole token in `claude --help` output.
|
|
288
|
+
*
|
|
289
|
+
* The boundary matters: a bare `includes('--plugin-dir')` also matches
|
|
290
|
+
* `--plugin-dirs` or `--plugin-dir-cache`, and vat's own flag names are prefixes
|
|
291
|
+
* of each other (`--max-turns` / `--max-turns-per-eval` would collide). Neither
|
|
292
|
+
* side of the token may be a letter or a dash.
|
|
236
293
|
*/
|
|
237
|
-
|
|
238
|
-
|
|
294
|
+
declare function helpTextDeclaresFlag(helpText: string, flag: string): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Build a token-free flag-support probe from `claude --help`.
|
|
297
|
+
*
|
|
298
|
+
* ⚠️ This REPLACES an exit-code probe (`claude <flag> <dummy> --help`, exit 0 ⇒
|
|
299
|
+
* supported) that could not discriminate at all. Verified against claude 2.x:
|
|
300
|
+
* `claude --no-such-flag-xyz 1 --help` exits **0**, because `--help` short-circuits
|
|
301
|
+
* before argument validation. Every one of preflight's `flag <name>` checks
|
|
302
|
+
* therefore reported "supported" unconditionally — including for flags that do not
|
|
303
|
+
* exist — so the gate that is supposed to stop vat spawning with an unsupported
|
|
304
|
+
* flag was decorative. (The same command without `--help` exits 1, but running it
|
|
305
|
+
* would start a real session and bill tokens, which is why `--help` was there.)
|
|
306
|
+
*
|
|
307
|
+
* Help-text matching does discriminate, and it is still token-free: one `--help`
|
|
308
|
+
* invocation for the whole run instead of one spawn per flag.
|
|
309
|
+
*
|
|
310
|
+
* The probe carries its own negative control ({@link FLAG_PROBE_SENTINEL}). If a
|
|
311
|
+
* future `claude` prints something that matches the sentinel — or `--help` fails
|
|
312
|
+
* outright — the probe reports EVERY flag unsupported rather than every flag
|
|
313
|
+
* supported. A probe that cannot tell must fail closed and be seen; the defect it
|
|
314
|
+
* replaces failed open and was invisible.
|
|
315
|
+
*
|
|
316
|
+
* `--help` runs on the FIRST flag query, not when the probe is built. Building
|
|
317
|
+
* it is part of assembling preflight's input, and a preflight that never asks
|
|
318
|
+
* (a stubbed one, or one that fails an earlier check) must not have already
|
|
319
|
+
* paid for a subprocess: with the spawn at build time every harness run cost
|
|
320
|
+
* one real `claude --help` — 75 ms where the CLI is installed, an instant
|
|
321
|
+
* ENOENT where it is not — which read as a 10× platform split in a test that
|
|
322
|
+
* had injected a fake spawn and stubbed preflight precisely so no process
|
|
323
|
+
* would start.
|
|
324
|
+
*/
|
|
325
|
+
declare function buildFlagParseProbe(runHelp?: () => string | null): (flag: string) => boolean;
|
|
326
|
+
/**
|
|
327
|
+
* ⚠️ KNOWN OPEN — `costEstimate` here is DEAD, in three independent ways, and none
|
|
328
|
+
* of them is fixable from inside this function:
|
|
329
|
+
*
|
|
330
|
+
* 1. `evalCount: 1` is a placeholder. The suite is not parsed until Step 5.5, which
|
|
331
|
+
* runs AFTER preflight, so the real count does not exist yet. Making it real means
|
|
332
|
+
* either moving preflight after the suite parse (it currently gates that parse) or
|
|
333
|
+
* adding a second reader of `evals.json` — and two readers of the answer key is
|
|
334
|
+
* precisely the drift this module refuses elsewhere.
|
|
335
|
+
* 2. `renderPreflightSummary` returns only FAILING checks, and it is called only
|
|
336
|
+
* inside the `if (!preflightResult.passed)` branch — so on a passing run the
|
|
337
|
+
* estimate never reaches a terminal at all.
|
|
338
|
+
* 3. Preflight runs after the `--dry-run` short-circuit, so a dry run never gets here.
|
|
339
|
+
*
|
|
340
|
+
* The consequence is that {@link buildDryRunSummary}'s spawn-count line is the ONLY
|
|
341
|
+
* pre-spend number an operator ever sees. Fix the count before making the check
|
|
342
|
+
* reachable: a reachable estimate that says "1 evals × 2 config" is worse than a
|
|
343
|
+
* silent one, because it is quotable.
|
|
344
|
+
*/
|
|
345
|
+
declare function buildPreflightInput(evalsPath: string, pluginDirs: string[], opts: RunHarnessOptions, knobs: {
|
|
239
346
|
maxBudgetUsd: number;
|
|
240
347
|
}): PreflightInput;
|
|
241
|
-
|
|
348
|
+
declare function renderPreflightSummary(checks: {
|
|
242
349
|
name: string;
|
|
243
350
|
passed: boolean;
|
|
244
351
|
message: string;
|
|
@@ -255,8 +362,27 @@ export declare function isAcknowledged(opts: Pick<RunHarnessOptions, 'dryRun' |
|
|
|
255
362
|
* Format a friction report for human consumption — one line per entry as
|
|
256
363
|
* `[<severity>] <category>: <message>`. Pure; returns the empty string for no
|
|
257
364
|
* entries so the caller can skip emitting anything.
|
|
365
|
+
*
|
|
366
|
+
* `severity` and `category` are closed enums vat owns, so they are safe to
|
|
367
|
+
* interpolate. `message` is GRADER text and is sanitized here even though
|
|
368
|
+
* {@link parseEvalFragment} already sanitized it on the way in — this is a
|
|
369
|
+
* second, independent boundary, not a redundant one: {@link emitFrictionReport}
|
|
370
|
+
* re-reads `friction.json` from the harness results dir, which same-uid skill
|
|
371
|
+
* code can reach and rewrite after vat wrote it.
|
|
258
372
|
*/
|
|
259
373
|
export declare function formatFrictionReport(items: readonly FrictionItem[]): string;
|
|
374
|
+
/**
|
|
375
|
+
* Read the run's friction.json (if present + valid) and echo a concise report to
|
|
376
|
+
* STDERR so users don't miss packaging-fidelity friction VAT merged from the grader
|
|
377
|
+
* fragments (it is otherwise only written to disk). Best-effort: a missing or empty
|
|
378
|
+
* report emits nothing. A report that is there but cannot be read or does not parse
|
|
379
|
+
* emits a one-line warning instead — this runs from the harness `finally`, so it
|
|
380
|
+
* must not throw, but vat is the sole writer of friction.json and a file it wrote
|
|
381
|
+
* that it cannot read back is worth a line. Never touches stdout (which stays
|
|
382
|
+
* machine-readable). Accepts `undefined` (a no-op) so the harness `finally` can
|
|
383
|
+
* call it unconditionally even when a throw preempted assignment of the path.
|
|
384
|
+
*/
|
|
385
|
+
declare function emitFrictionReport(frictionPath: string | undefined): void;
|
|
260
386
|
/**
|
|
261
387
|
* Resolve the PERSISTENT location where a bootstrap scaffold should be written
|
|
262
388
|
* so "fill it in and re-run" actually works for the user (the staged copy is
|
|
@@ -264,7 +390,7 @@ export declare function formatFrictionReport(items: readonly FrictionItem[]): st
|
|
|
264
390
|
* that real source dir; otherwise we anchor under the repo root by skill name.
|
|
265
391
|
*/
|
|
266
392
|
/** The subject skill's display name (trailing segment of the subject arg). */
|
|
267
|
-
|
|
393
|
+
declare function subjectSkillName(opts: RunHarnessOptions): string;
|
|
268
394
|
/**
|
|
269
395
|
* Where this run READS its eval suite from, anchored at the subject's authored
|
|
270
396
|
* source dir.
|
|
@@ -287,16 +413,7 @@ export declare function subjectSkillName(opts: RunHarnessOptions): string;
|
|
|
287
413
|
* into `<skillDir>/<path>`, which does not exist — so the run did not fail, it
|
|
288
414
|
* bootstrapped a starter template at the bogus location and graded that.
|
|
289
415
|
*/
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* The vat-only directory a subject's eval suite is relocated to when the resolved
|
|
293
|
-
* artifact is the only place it exists (npm/url/vendored, or a source tree that
|
|
294
|
-
* ships its own evals). Deliberately OUTSIDE the harness root — the harness root is
|
|
295
|
-
* the executor's sandbox, and the suite is the answer key to the task the executor
|
|
296
|
-
* is performing. Created 0700 and removed in the run's cleanup, exactly like
|
|
297
|
-
* {@link resolveGraderOutDir}. Pure (derives a path only).
|
|
298
|
-
*/
|
|
299
|
-
export declare function resolveEvalSuiteHoldDir(dirToken: string): string;
|
|
416
|
+
declare function resolveScaffoldEvalsPath(opts: RunHarnessOptions, repoRoot: string, evalsRef: string | undefined): string;
|
|
300
417
|
/**
|
|
301
418
|
* Locate the eval suite the run will read, WITHOUT it ever being reachable by the
|
|
302
419
|
* executor. Precedence, and why:
|
|
@@ -307,12 +424,12 @@ export declare function resolveEvalSuiteHoldDir(dirToken: string): string;
|
|
|
307
424
|
* 2. **The vat-only hold dir**, when staging harvested a suite that exists nowhere
|
|
308
425
|
* else (a fetched artifact). Its layout mirrors an authored evals dir, so
|
|
309
426
|
* `fixtures/` resolve relative to it unchanged.
|
|
310
|
-
* 3. Neither → `undefined`, and the caller bootstraps a template (exit
|
|
427
|
+
* 3. Neither → `undefined`, and the caller bootstraps a template (exit 2, `Reason: bootstrap`).
|
|
311
428
|
*
|
|
312
429
|
* Returns the suite's absolute path; its `dirname` is the base for each eval's
|
|
313
430
|
* declared input `files`.
|
|
314
431
|
*/
|
|
315
|
-
|
|
432
|
+
declare function resolveEvalSuitePath(input: {
|
|
316
433
|
opts: RunHarnessOptions;
|
|
317
434
|
repoRoot: string;
|
|
318
435
|
/** Explicit `test.evals` / `--evals` value, or `undefined` for the convention. */
|
|
@@ -322,17 +439,65 @@ export declare function resolveEvalSuitePath(input: {
|
|
|
322
439
|
holdDir: string;
|
|
323
440
|
subjectEvalSuiteHeld: boolean;
|
|
324
441
|
}): string | undefined;
|
|
442
|
+
/**
|
|
443
|
+
* The per-run executor workspace root: `<tmp>/vat-skill-test-ws-<dirToken>/`.
|
|
444
|
+
*
|
|
445
|
+
* Deliberately OUTSIDE `harnessRoot`, alongside {@link resolveGraderOutDir} and
|
|
446
|
+
* {@link resolveEvalSuiteHoldDir}. The executor's cwd lives under here, and the
|
|
447
|
+
* harness root is where vat stages runnable copies of the skill (`staged/` and
|
|
448
|
+
* the assembled plugin dir). While the two were parent and child, the skill-absent
|
|
449
|
+
* arm of a `--baseline` run sat one `ls ..` away from the treatment it was
|
|
450
|
+
* supposed to be denied — the control could reach vat's own copy without ever
|
|
451
|
+
* leaving its working directory.
|
|
452
|
+
*
|
|
453
|
+
* Named by an unpredictable token, like the other vat-only dirs, and removed by
|
|
454
|
+
* the same cleanup. Pure (derives a path only).
|
|
455
|
+
*/
|
|
456
|
+
declare function resolveWorkspacesRoot(dirToken: string): string;
|
|
325
457
|
/** 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
|
-
|
|
458
|
+
* `<workspacesRoot>/<arm>/<id>/`, once per arm this run will execute. Returns the
|
|
459
|
+
* workspaces root, the parsed {@link EvalSuite} (so the eval loop has the entries
|
|
460
|
+
* without re-reading), and the declared eval count (derived from the suite). The
|
|
461
|
+
* dir is wiped first so a reused root cannot leak a prior run's inputs. Throws
|
|
462
|
+
* {@link EvalInputError} (mapped by the caller to exit 2) on a bad suite or a
|
|
463
|
+
* missing input file. */
|
|
464
|
+
/**
|
|
465
|
+
* Mint this run's opaque per-arm workspace directory segments.
|
|
466
|
+
*
|
|
467
|
+
* Independent random tokens, NOT the arm names and not derived from one another:
|
|
468
|
+
* the segment ends up in the executor's cwd, which the prompt states verbatim, so
|
|
469
|
+
* a name like `without` unblinds the control arm outright — and a name like `with`
|
|
470
|
+
* hands it a guessable path to the treatment arm's live output one directory over.
|
|
471
|
+
* 8 bytes is ample for "not guessable within a run that lasts minutes"; these are
|
|
472
|
+
* blinding/anti-enumeration tokens, not secrets protecting data at rest.
|
|
473
|
+
*/
|
|
474
|
+
declare function mintArmWorkspaceDirs(baseline: boolean): ArmWorkspaceDirs;
|
|
475
|
+
declare function stageWorkspacesForRun(evalsPath: string, workspacesRoot: string, armDirs: ArmWorkspaceDirs): {
|
|
332
476
|
workspacesRoot: string;
|
|
333
477
|
declaredEvalCount: number;
|
|
334
478
|
suite: EvalSuite;
|
|
335
479
|
};
|
|
480
|
+
interface ResolveDeclaredChildEnvInput {
|
|
481
|
+
opts: RunHarnessOptions;
|
|
482
|
+
resolvedAuth: ResolvedAuth;
|
|
483
|
+
subjectStagedDir: string;
|
|
484
|
+
harnessRoot: string;
|
|
485
|
+
resultsDir: string;
|
|
486
|
+
evalsSubpath: string;
|
|
487
|
+
subjectPluginRoot: string | null;
|
|
488
|
+
/** The eval's staged input workspace — what `${fixturesDir}` resolves under. */
|
|
489
|
+
workspaceDir?: string;
|
|
490
|
+
/** Suppress the stderr transparency line (emitted once, by the first eval). */
|
|
491
|
+
quiet?: boolean;
|
|
492
|
+
}
|
|
493
|
+
/** Everything needed to re-assemble the executor env once an eval's workspace is known. */
|
|
494
|
+
interface PerEvalEnvAssembly {
|
|
495
|
+
input: Omit<ResolveDeclaredChildEnvInput, 'workspaceDir' | 'quiet'>;
|
|
496
|
+
/** Mutable latch so exactly one eval emits the stderr transparency line. */
|
|
497
|
+
logged: {
|
|
498
|
+
done: boolean;
|
|
499
|
+
};
|
|
500
|
+
}
|
|
336
501
|
/** Inputs for the dry-run summary string. */
|
|
337
502
|
export interface DryRunSummaryInput {
|
|
338
503
|
/** True when the resolved subject is buildable (a real run would build + stage it). */
|
|
@@ -342,7 +507,14 @@ export interface DryRunSummaryInput {
|
|
|
342
507
|
* rebuilding (may be stale); false = no dist existed, fell back to source dir.
|
|
343
508
|
*/
|
|
344
509
|
dryRunStagedExistingDist?: boolean;
|
|
345
|
-
/**
|
|
510
|
+
/**
|
|
511
|
+
* Absolute path where a REAL run would write provenance.json.
|
|
512
|
+
*
|
|
513
|
+
* Not written by a dry run, and the summary line says so. It used to be written —
|
|
514
|
+
* from the same block that wiped the previous run's grading/friction/baseline
|
|
515
|
+
* artifacts — which made "preview the cost" a destructive operation against the
|
|
516
|
+
* results the operator was about to read.
|
|
517
|
+
*/
|
|
346
518
|
provenancePath: string;
|
|
347
519
|
/** Content fingerprint from the staged manifest. */
|
|
348
520
|
provenanceFingerprint: string;
|
|
@@ -352,10 +524,29 @@ export interface DryRunSummaryInput {
|
|
|
352
524
|
modelFlag: string;
|
|
353
525
|
/** Number of declared evals a real run would execute. */
|
|
354
526
|
evalCount: number;
|
|
527
|
+
/**
|
|
528
|
+
* Whether `--baseline` is set, i.e. whether every eval runs TWICE.
|
|
529
|
+
*
|
|
530
|
+
* Without this the preview reported the SUITE SIZE and called it the spawn count,
|
|
531
|
+
* which under `--baseline` under-reports the run by exactly 2x: a 3-eval suite
|
|
532
|
+
* previewed as "3 evals as executor→grader spawn pairs" and then launched 6 pairs
|
|
533
|
+
* — twelve `claude` sessions. This is the ONLY pre-spend number an operator ever
|
|
534
|
+
* sees (see {@link buildPreflightInput} for why the other one never reaches a
|
|
535
|
+
* terminal), so a 2x understatement here is the whole cost estimate being wrong.
|
|
536
|
+
*/
|
|
537
|
+
baseline: boolean;
|
|
355
538
|
/** Bounded-parallel executor→grader pipeline width a real run would use. */
|
|
356
539
|
concurrency: number;
|
|
357
540
|
/** Pinned grader/judge model a real run would grade with. */
|
|
358
541
|
graderModel: string;
|
|
542
|
+
/**
|
|
543
|
+
* The PER-SPAWN budget ceiling (`--max-budget-usd`), not a run total. Reported
|
|
544
|
+
* because the two facts only mean something together: the worst case is
|
|
545
|
+
* `pairs × 2 × maxBudgetUsd`, and an operator who reads the flag name as a run
|
|
546
|
+
* budget under-provisions by the session count — which under `--baseline` is
|
|
547
|
+
* four times the eval count.
|
|
548
|
+
*/
|
|
549
|
+
maxBudgetUsd: number;
|
|
359
550
|
}
|
|
360
551
|
/**
|
|
361
552
|
* Build the "stale dist" warning lines for a `--dry-run` preview that staged an
|
|
@@ -372,19 +563,6 @@ export interface DryRunSummaryInput {
|
|
|
372
563
|
* than copying the string.
|
|
373
564
|
*/
|
|
374
565
|
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
566
|
export declare function buildDryRunSummary(input: DryRunSummaryInput): string;
|
|
389
567
|
export interface CleanupHarnessOptions {
|
|
390
568
|
/** User asked to retain the harness dir (`--keep`) — never remove. */
|
|
@@ -397,22 +575,37 @@ export interface CleanupHarnessOptions {
|
|
|
397
575
|
created: boolean;
|
|
398
576
|
}
|
|
399
577
|
/**
|
|
400
|
-
* Remove the harness directory after a run so
|
|
401
|
-
* prompts do not accumulate in OS tmp. No-op when the user asked to keep
|
|
402
|
-
* the dir is a user-supplied location (`--out`/`--workdir`), or when it is
|
|
403
|
-
* gone. Idempotent and never throws — it runs from a `finally`, so it must
|
|
404
|
-
* mask the run's real outcome.
|
|
578
|
+
* Remove the harness directory's staged contents after a run so untrusted skill
|
|
579
|
+
* bytes and prompts do not accumulate in OS tmp. No-op when the user asked to keep
|
|
580
|
+
* it, when the dir is a user-supplied location (`--out`/`--workdir`), or when it is
|
|
581
|
+
* already gone. Idempotent and never throws — it runs from a `finally`, so it must
|
|
582
|
+
* not mask the run's real outcome.
|
|
583
|
+
*
|
|
584
|
+
* `results/` SURVIVES. It holds `grading.json`, `friction.json`, `tool-eval.json`
|
|
585
|
+
* and — for a `--baseline` run — `baseline.json`, i.e. every artifact the command's
|
|
586
|
+
* own help text tells the operator to go read. Removing the whole root on the
|
|
587
|
+
* DEFAULT invocation (no `--out`, no `--workdir`, no `--keep`) deleted those files
|
|
588
|
+
* inside this function's own `finally`, before `run.ts` had printed a single line:
|
|
589
|
+
* the operator was handed a `Harness:` path to a directory that no longer existed.
|
|
590
|
+
* That is the invocation `vat-skill-testing.md`'s copy-paste example uses.
|
|
591
|
+
*
|
|
592
|
+
* The retention is deliberately UNCONDITIONAL rather than `--baseline`-only: every
|
|
593
|
+
* run's verdict detail lives in the same directory, and a rule that depends on a
|
|
594
|
+
* flag is a rule that will drift away from the flag. The cost is bounded — the
|
|
595
|
+
* harness root is a deterministic function of the subject set, so a re-run reuses
|
|
596
|
+
* (and {@link wipeStaleArtifacts} clears) the same `results/`, rather than
|
|
597
|
+
* accumulating one per run.
|
|
405
598
|
*
|
|
406
599
|
* SAFETY: re-asserts the root is not a symlink immediately before removal (via
|
|
407
600
|
* `lstat`, which does NOT follow the link). A root swapped to a symlink between
|
|
408
601
|
* the run and cleanup is left in place rather than followed — `rmSync(recursive)`
|
|
409
602
|
* could otherwise delete the symlink's target outside tmp.
|
|
410
603
|
*/
|
|
411
|
-
|
|
604
|
+
declare function cleanupHarness(harnessRoot: string, opts: CleanupHarnessOptions): void;
|
|
412
605
|
/** One unit of executor→grader work: an eval + which arm (with/without skill). */
|
|
413
606
|
export interface EvalWorkItem {
|
|
414
607
|
entry: EvalEntry;
|
|
415
|
-
arm:
|
|
608
|
+
arm: EvalArm;
|
|
416
609
|
}
|
|
417
610
|
/**
|
|
418
611
|
* Build the per-eval work items for a set of evals (one tier's worth, or a whole
|
|
@@ -422,14 +615,14 @@ export interface EvalWorkItem {
|
|
|
422
615
|
* (see {@link partitionFragmentsByArm}) and never drives tier gating. Pure +
|
|
423
616
|
* unit-testable.
|
|
424
617
|
*/
|
|
425
|
-
|
|
618
|
+
declare function buildEvalWorkItems(evals: readonly EvalEntry[], baseline: boolean): EvalWorkItem[];
|
|
426
619
|
/**
|
|
427
620
|
* Partition graded fragments into the WITH arm (the authoritative verdict +
|
|
428
621
|
* grading.json) and the WITHOUT arm (baseline.json, informational only). A
|
|
429
622
|
* fragment with no `arm` (or `arm: 'with'`) is a WITH-arm fragment. Pure +
|
|
430
623
|
* unit-testable.
|
|
431
624
|
*/
|
|
432
|
-
|
|
625
|
+
declare function partitionFragmentsByArm(fragments: EvalFragment[]): {
|
|
433
626
|
withArm: EvalFragment[];
|
|
434
627
|
withoutArm: EvalFragment[];
|
|
435
628
|
};
|
|
@@ -453,20 +646,232 @@ export declare function partitionFragmentsByArm(fragments: EvalFragment[]): {
|
|
|
453
646
|
* container; that is tracked as a follow-up (see CHANGELOG "Security" notes) and
|
|
454
647
|
* is the only thing that closes the residual race outright.
|
|
455
648
|
*/
|
|
456
|
-
|
|
649
|
+
declare function resolveGraderOutDir(dirToken: string): string;
|
|
457
650
|
/**
|
|
458
|
-
* The executor working directory for one eval: its staged input
|
|
459
|
-
* `<workspacesRoot>/<id
|
|
460
|
-
*
|
|
651
|
+
* The executor working directory for one eval ON ONE ARM: its staged input
|
|
652
|
+
* workspace `<workspacesRoot>/<arm>/<id>`, ALWAYS — empty when the eval declares
|
|
653
|
+
* no input `files` (see {@link stageEvalWorkspaces}).
|
|
654
|
+
*
|
|
655
|
+
* This used to return undefined for a file-less eval, which made the executor
|
|
656
|
+
* fall back to running inside the staged subject dir. That fallback was the
|
|
657
|
+
* quiet half of the `--baseline` control defect: the skill-absent arm's cwd
|
|
658
|
+
* was the skill.
|
|
659
|
+
*
|
|
660
|
+
* The per-arm segment closes the OTHER half, found a round later: without it both
|
|
661
|
+
* arms of one eval shared a single writable directory AND ran concurrently, so
|
|
662
|
+
* the control arm could read the treatment arm's output files and answer from
|
|
663
|
+
* them. Keyed on the eval id alone this is a pure function of the eval; the arm
|
|
664
|
+
* is what makes the two runs independent.
|
|
665
|
+
*
|
|
666
|
+
* The segment is an OPAQUE per-run token, never the arm's name — this path is
|
|
667
|
+
* quoted verbatim to the executor as its working directory, so `…/without/e1`
|
|
668
|
+
* told the control arm it was the control, and `../with/e1` told it where to look.
|
|
669
|
+
* See {@link mintArmWorkspaceDirs}. Pure + unit-testable.
|
|
461
670
|
*/
|
|
462
|
-
|
|
671
|
+
declare function resolvePerEvalWorkspaceDir(entry: EvalEntry, workspacesRoot: string, arm: EvalArm, armDirs: ArmWorkspaceDirs): string;
|
|
463
672
|
/**
|
|
464
673
|
* Best-effort removal of a vat-only tmp dir that lives OUTSIDE the harness root —
|
|
465
674
|
* the grader fragment dir and the held eval suite. Never throws (it runs from
|
|
466
675
|
* cleanup, where masking the run's real outcome would be worse than a leftover 0700
|
|
467
676
|
* tmp dir) and refuses to follow a symlinked root.
|
|
468
677
|
*/
|
|
469
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Run one cleanup step, swallowing whatever it throws.
|
|
680
|
+
*
|
|
681
|
+
* The discipline every other step in the harness `finally` already follows, stated
|
|
682
|
+
* once: a cleanup failure is not a run failure, and letting one out REPLACES an
|
|
683
|
+
* already-good result — verdict computed, artifacts written, summary composed — with
|
|
684
|
+
* an error and exit 1.
|
|
685
|
+
*
|
|
686
|
+
* ⚠️ A CALL-SITE GUARD IS THE WEAK FORM, and this helper is a last resort rather
|
|
687
|
+
* than the pattern to copy. It is invisible to the compiler and to the test suite:
|
|
688
|
+
* reverting either of its two original call sites to a bare call left the unit suite
|
|
689
|
+
* AND the integration suite green, so nothing but review stood between the fix and
|
|
690
|
+
* its own silent removal. `lock.release()` was therefore made non-throwing at its own
|
|
691
|
+
* definition instead (see {@link acquireHarnessLock}), which is a contract the type
|
|
692
|
+
* carries and no edit here can undo. That leaves ONE caller —
|
|
693
|
+
* `killAllActiveClaudeChildren`, which lives in `@vibe-agent-toolkit/utils` and is
|
|
694
|
+
* only contractually non-throwing as long as that package says so.
|
|
695
|
+
*
|
|
696
|
+
* The failure is reported on stderr rather than silently dropped: a lockfile that
|
|
697
|
+
* could not be removed will break the NEXT run with a "busy" error, and an operator
|
|
698
|
+
* who saw nothing here has no way to connect the two.
|
|
699
|
+
*/
|
|
700
|
+
declare function swallowCleanupFailure(step: () => void): void;
|
|
701
|
+
/**
|
|
702
|
+
* Remove one vat-only temp dir. Same discipline as {@link cleanupHarness}: a failed
|
|
703
|
+
* removal is not a run failure, and it is reported rather than swallowed.
|
|
704
|
+
*/
|
|
705
|
+
declare function removeVatOnlyDir(dir: string | undefined): void;
|
|
706
|
+
/** Everything one executor→grader pipeline worker needs, built once per run. */
|
|
707
|
+
interface EvalRunContext {
|
|
708
|
+
subjectStagedDir: string;
|
|
709
|
+
workspacesRoot: string;
|
|
710
|
+
/** This run's opaque per-arm workspace segments (see {@link mintArmWorkspaceDirs}). */
|
|
711
|
+
armDirs: ArmWorkspaceDirs;
|
|
712
|
+
pluginDirs: string[];
|
|
713
|
+
graderOutDir: string;
|
|
714
|
+
/**
|
|
715
|
+
* The vat-only dir a harvested eval suite is held in — the ANSWER KEY for a
|
|
716
|
+
* fetched-artifact subject. Carried on the context solely so the WITHOUT arm's
|
|
717
|
+
* contamination scan can look for it; nothing in the eval loop reads from it.
|
|
718
|
+
*/
|
|
719
|
+
evalSuiteHoldDir: string;
|
|
720
|
+
/**
|
|
721
|
+
* Verbatim lines of the staged SKILL.md, for the skill-absent arm's scan. The
|
|
722
|
+
* only contamination signal that does not need the arm to name a path or an
|
|
723
|
+
* executable, and so the only cover an instruction-only skill has.
|
|
724
|
+
*/
|
|
725
|
+
skillContentNeedles: readonly string[];
|
|
726
|
+
runNonce: string;
|
|
727
|
+
graderModel: string;
|
|
728
|
+
model?: string;
|
|
729
|
+
maxTurns: number;
|
|
730
|
+
maxBudgetUsd: number;
|
|
731
|
+
timeoutMs: number;
|
|
732
|
+
stallMs?: number;
|
|
733
|
+
/**
|
|
734
|
+
* Full assembled env (skill secrets included) for the executor spawn, WITHOUT
|
|
735
|
+
* the declared `env` injections — those carry `${fixturesDir}`, which names the
|
|
736
|
+
* eval's own workspace and so can only be resolved per eval (see `perEvalEnv`).
|
|
737
|
+
*/
|
|
738
|
+
executorEnv: NodeJS.ProcessEnv;
|
|
739
|
+
/**
|
|
740
|
+
* Present only when the subject declares an `env` block. Re-assembles the child
|
|
741
|
+
* env per eval so `${fixturesDir}` resolves under THAT eval's workspace. The
|
|
742
|
+
* transparency line is emitted once, by whichever eval assembles first.
|
|
743
|
+
*/
|
|
744
|
+
perEvalEnv: PerEvalEnvAssembly | undefined;
|
|
745
|
+
/** AUTH-ONLY env for the grader spawn (trusted vat infra loads no skill). */
|
|
746
|
+
graderEnv: NodeJS.ProcessEnv;
|
|
747
|
+
/** Declared executables (WITH-arm grader aid + the detector's path needles); absent when unreachable. */
|
|
748
|
+
declaredExecutables?: Array<{
|
|
749
|
+
name: string;
|
|
750
|
+
howInvoked: string;
|
|
751
|
+
kind: string;
|
|
752
|
+
path: string;
|
|
753
|
+
}>;
|
|
754
|
+
spawn?: typeof spawnHeadlessClaude;
|
|
755
|
+
/** Shared spend accumulator — each worker folds in its executor + grader session cost. */
|
|
756
|
+
costAccumulator: RunCostSummary;
|
|
757
|
+
/**
|
|
758
|
+
* The harness root, where vat stages runnable copies of the subject. Used ONLY
|
|
759
|
+
* as a contamination needle for the skill-absent arm (see baseline-integrity.ts):
|
|
760
|
+
* nothing points the control arm here any more, so a mention of this path in its
|
|
761
|
+
* transcript means it went looking and found vat's own copy.
|
|
762
|
+
*/
|
|
763
|
+
harnessRoot: string;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Strip the fields VAT attaches itself, before VAT attaches them.
|
|
767
|
+
*
|
|
768
|
+
* The grader's only input is the executor transcript, which untrusted skill code
|
|
769
|
+
* controls — so a prompt injection there could otherwise fabricate a
|
|
770
|
+
* `BASELINE CONTAMINATED` verdict carrying attacker-chosen strings into an
|
|
771
|
+
* operator-facing artifact, or (via `degraded`) claim a scan degraded that did not.
|
|
772
|
+
* Spreading VAT's value last is NOT sufficient: it is absent on a clean run, so it
|
|
773
|
+
* overwrites nothing — which is exactly why the omission direction matters more
|
|
774
|
+
* than the invention one for `degraded`. A grader that omits it cannot re-hide a
|
|
775
|
+
* degradation VAT detected, because VAT's value is spread after this strip; a
|
|
776
|
+
* grader that INVENTS it would otherwise stamp a blind-scan warning on a run that
|
|
777
|
+
* scanned properly, and an operator who learns to ignore that warning stops reading
|
|
778
|
+
* the real ones.
|
|
779
|
+
*/
|
|
780
|
+
declare function withoutGraderContamination(fragment: EvalFragment): EvalFragment;
|
|
781
|
+
/**
|
|
782
|
+
* Lift the run's content needles off the STAGED SKILL.md.
|
|
783
|
+
*
|
|
784
|
+
* Staged, not source: the staged copy is byte-identical to what the treatment arm
|
|
785
|
+
* is given, so a needle taken from it is exactly the text an ambient copy of the
|
|
786
|
+
* same build would carry.
|
|
787
|
+
*
|
|
788
|
+
* The exclusion set is EVERY channel through which vat itself hands the arm text:
|
|
789
|
+
* the suite's prompts, its `expected_output`, its expectations, and the CONTENTS
|
|
790
|
+
* of the input `files` fixtures vat stages into the arm's workspace and instructs
|
|
791
|
+
* it to operate on. An adopter who quotes a sentence of their SKILL.md through any
|
|
792
|
+
* of the four would otherwise stamp every run contaminated, including the arm that
|
|
793
|
+
* merely read the input vat gave it — the skill's words arriving THROUGH vat are
|
|
794
|
+
* not the arm reaching the skill. The fixture channel is the one that was missed,
|
|
795
|
+
* and it is the worst of the four to get wrong: the summary attached to that
|
|
796
|
+
* verdict tells the operator to go uninstall an ambient copy of the plugin that
|
|
797
|
+
* does not exist.
|
|
798
|
+
*
|
|
799
|
+
* Fixtures are read from the STAGED workspace rather than the suite directory, for
|
|
800
|
+
* the same reason the SKILL.md is: that is byte-for-byte what the arm was handed.
|
|
801
|
+
*
|
|
802
|
+
* Returns `[]` when there is no staged SKILL.md. A missing SKILL.md is not this
|
|
803
|
+
* function's error to raise (staging has already validated the subject), and an
|
|
804
|
+
* unarmed signal is reported as unarmed rather than claimed as clean. A SKILL.md
|
|
805
|
+
* that is THERE but refused is a different thing — `[]` would silently disarm the
|
|
806
|
+
* contamination signal for the whole run — so a refusal propagates. The same split
|
|
807
|
+
* applies to fixtures: see {@link collectFixtureText}.
|
|
808
|
+
*/
|
|
809
|
+
declare function resolveSkillContentNeedles(subjectStagedDir: string, evals: readonly EvalEntry[],
|
|
810
|
+
/**
|
|
811
|
+
* Where the staged fixtures live, as the two inputs an arm root is DERIVED from
|
|
812
|
+
* rather than as the pre-joined path itself — see {@link armWorkspaceRoot}. Taking
|
|
813
|
+
* a bare `string` here let the call site pass `workspacesRoot` (one level too high,
|
|
814
|
+
* so every `<id>/` fixture lookup missed) with the whole unit suite still green.
|
|
815
|
+
*
|
|
816
|
+
* The `with` arm, because the fixture BYTES are what matter and both arms are
|
|
817
|
+
* staged from the same source — and `with` is the only arm every run mints.
|
|
818
|
+
*/
|
|
819
|
+
workspaces: {
|
|
820
|
+
workspacesRoot: string;
|
|
821
|
+
armDirs: ArmWorkspaceDirs;
|
|
822
|
+
}): string[];
|
|
823
|
+
/**
|
|
824
|
+
* Baseline integrity for ONE skill-absent eval: vat has removed its OWN copies
|
|
825
|
+
* from the control arm's reach, but an ambient copy in the adopter's repo or
|
|
826
|
+
* installed plugin cache is not vat's to remove — so the control arm can still,
|
|
827
|
+
* in principle, find and run the skill. Detect it from the transcript and return
|
|
828
|
+
* the fragment patch that carries the evidence to the merge step, which turns it
|
|
829
|
+
* into a loud caveat on baseline.json. A wrong number that announces itself is
|
|
830
|
+
* recoverable; a silent one gets believed.
|
|
831
|
+
*
|
|
832
|
+
* Returns an empty patch when clean, so the caller spreads it unconditionally.
|
|
833
|
+
*/
|
|
834
|
+
/**
|
|
835
|
+
* Exactly the per-run values {@link buildContaminationInput} reads. A `Pick`, not a
|
|
836
|
+
* fresh interface, so it cannot drift from the context the run actually assembles.
|
|
837
|
+
*/
|
|
838
|
+
export type ContaminationCtx = Pick<EvalRunContext, 'armDirs' | 'workspacesRoot' | 'harnessRoot' | 'evalSuiteHoldDir' | 'graderOutDir' | 'skillContentNeedles' | 'declaredExecutables'>;
|
|
839
|
+
/**
|
|
840
|
+
* Build the detector input for the skill-absent arm. ONE builder, so the hits
|
|
841
|
+
* written per eval and the `signals` list stamped on the run-level integrity
|
|
842
|
+
* block are derived from the same values — a second construction site is how the
|
|
843
|
+
* block would come to claim a detector the scan never actually ran.
|
|
844
|
+
*
|
|
845
|
+
* `transcript` and `evalId` are supplied per call; everything else is per-run.
|
|
846
|
+
*
|
|
847
|
+
* Takes a NARROWED context rather than the whole {@link EvalRunContext} so the
|
|
848
|
+
* builder is directly unit-testable — the detector honours `armCwd` (dropping it
|
|
849
|
+
* there kills three tests) while the wiring that SUPPLIES it was droppable green,
|
|
850
|
+
* and a dropped `armCwd` anchors every relative climb one directory too high, where
|
|
851
|
+
* no needle can see it.
|
|
852
|
+
*
|
|
853
|
+
* `evalId` is what turns the arm's workspace ROOT into the arm's actual starting
|
|
854
|
+
* CWD (`<armWorkspaceDir>/<evalId>` — see {@link resolvePerEvalWorkspaceDir}).
|
|
855
|
+
* The detector needs both and they are one directory apart: containment is judged
|
|
856
|
+
* against the root (the arm's whole tree is its own), while the cwd walk must
|
|
857
|
+
* start exactly where the executor did, or every relative climb in the transcript
|
|
858
|
+
* resolves one level too high and lands where no needle can see it.
|
|
859
|
+
*
|
|
860
|
+
* `evalId` is REQUIRED here, and the signals-only caller gets its own entry point
|
|
861
|
+
* ({@link buildContaminationSignalsInput}) rather than sharing an optional third
|
|
862
|
+
* argument. An optional parameter is a pin the compiler cannot hold: dropping the
|
|
863
|
+
* argument at the SCAN call site left all 2270 unit tests green and was caught only
|
|
864
|
+
* by the integration suite — which this repo's fast implementer loop explicitly says
|
|
865
|
+
* not to run. That is the same shape as the dropped `armCwd` that anchored the whole
|
|
866
|
+
* walk one directory too high, also green.
|
|
867
|
+
*/
|
|
868
|
+
declare function buildContaminationInput(transcript: string, ctx: ContaminationCtx, evalId: string): DetectBaselineContaminationInput;
|
|
869
|
+
/**
|
|
870
|
+
* The same input built for the "which detectors are ARMED?" question, which is asked
|
|
871
|
+
* once per run with no transcript and no eval — so it can honestly supply neither,
|
|
872
|
+
* and `armCwd` is correctly absent rather than accidentally dropped.
|
|
873
|
+
*/
|
|
874
|
+
declare function buildContaminationSignalsInput(ctx: ContaminationCtx): DetectBaselineContaminationInput;
|
|
470
875
|
/** The results/ artifacts vat is the SOLE writer of, resolved for one run. */
|
|
471
876
|
export interface ArtifactPaths {
|
|
472
877
|
gradingOut: string;
|
|
@@ -477,7 +882,7 @@ export interface ArtifactPaths {
|
|
|
477
882
|
/** Resolve the run's grading/friction/baseline/tool-eval artifact paths under
|
|
478
883
|
* `resultsDir`. Single source of truth for the filenames (used by the
|
|
479
884
|
* pre-pipeline stale wipe AND the post-merge writer). */
|
|
480
|
-
|
|
885
|
+
declare function resolveArtifactPaths(resultsDir: string): ArtifactPaths;
|
|
481
886
|
/**
|
|
482
887
|
* Remove any PRIOR run's artifacts before this run writes its own. The harness
|
|
483
888
|
* root is deterministic per skill-set and reused across runs (`--keep`/`--out`, or
|
|
@@ -487,24 +892,103 @@ export declare function resolveArtifactPaths(resultsDir: string): ArtifactPaths;
|
|
|
487
892
|
* run's. Wiping all three up front closes that cross-run leak. Best-effort
|
|
488
893
|
* (`force: true`) — a missing file is fine.
|
|
489
894
|
*/
|
|
490
|
-
|
|
895
|
+
declare function wipeStaleArtifacts(paths: ArtifactPaths): void;
|
|
896
|
+
/**
|
|
897
|
+
* Where a results artifact goes when it fails the fail-closed gate.
|
|
898
|
+
*
|
|
899
|
+
* The gate throws and `results/` is deliberately RETAINED, so the file that failed
|
|
900
|
+
* used to sit there under its authoritative name — the one artifact whose schema
|
|
901
|
+
* violation is BY DEFINITION a harness bug, indistinguishable in a CI archive from a
|
|
902
|
+
* good one until the next run's wipe. Renamed rather than deleted: the bytes are what
|
|
903
|
+
* a maintainer needs in order to see which refine tripped, and a name ending in
|
|
904
|
+
* `.rejected` cannot be mistaken for the artifact a reader came for.
|
|
905
|
+
*/
|
|
906
|
+
declare function rejectedArtifactPath(path: string): string;
|
|
907
|
+
/**
|
|
908
|
+
* Merge the run's grader fragments and WRITE the run artifacts (vat is the SOLE
|
|
909
|
+
* writer): grading.json from the WITH-arm fragments, friction.json from ALL
|
|
910
|
+
* fragments, tool-eval.json from the WITH-arm fragments' `tool` verdicts (a
|
|
911
|
+
* SEPARATE channel — C2; the WITHOUT arm carries no toolExpectations), and — only
|
|
912
|
+
* when a WITHOUT arm ran (baseline) — baseline.json from the WITHOUT-arm fragments
|
|
913
|
+
* (informational A/B, never part of the verdict). Stale copies from a reused harness
|
|
914
|
+
* were already removed by {@link wipeStaleArtifacts} pre-pipeline. Returns the
|
|
915
|
+
* reconciled prose-expectation verdict AND the merged tool-eval report so the caller
|
|
916
|
+
* can compute the COMPOSITE verdict. Every fragment's per-run nonce is re-verified
|
|
917
|
+
* inside {@link mergeFragmentsToGrading}.
|
|
918
|
+
*/
|
|
919
|
+
/**
|
|
920
|
+
* Collect per-eval baseline-integrity findings from the WITHOUT-arm fragments.
|
|
921
|
+
* Fragments with no `contamination` are clean and contribute nothing. Pure.
|
|
922
|
+
*/
|
|
923
|
+
/**
|
|
924
|
+
* What one arm graded, per eval — the SINGLE derivation behind both the parity check
|
|
925
|
+
* ({@link armExpectationSkew}, which reads `total`) and the A/B delta (which reads
|
|
926
|
+
* `passed`). Deriving those separately would let the two blocks in `baseline.json`
|
|
927
|
+
* disagree about the same arm. Pure.
|
|
928
|
+
*
|
|
929
|
+
* Exported for its own unit test, not for reuse. Setting `passed` to `total` here
|
|
930
|
+
* failed zero unit tests and three integration ones: every unit fixture graded
|
|
931
|
+
* everything it declared, so the two fields were numerically identical in all of
|
|
932
|
+
* them and the ONE thing this function exists to keep distinct was unpinned.
|
|
933
|
+
*/
|
|
934
|
+
declare function gradedCounts(fragments: EvalFragment[]): ArmEvalGrade[];
|
|
935
|
+
/**
|
|
936
|
+
* The stderr report for a `--baseline` run: the delta line, then the ⚠️ banner when
|
|
937
|
+
* the run is unusable. Pure — the caller decides WHEN it goes out, which is the
|
|
938
|
+
* whole point (see the call site).
|
|
939
|
+
*
|
|
940
|
+
* Composed on EVERY baseline run, including a withheld one: "the delta is
|
|
941
|
+
* unavailable, and here is why" is the report an operator needs, and a run that
|
|
942
|
+
* silently prints nothing is indistinguishable from a vat that still does not
|
|
943
|
+
* compute a delta at all. The delta line comes FIRST so an unusable run reads as the
|
|
944
|
+
* number followed by the caveat that explains it — the banner is the only line that
|
|
945
|
+
* says the number means nothing, so it must never be the one scrolled away.
|
|
946
|
+
*
|
|
947
|
+
* ⚠️ IT IS NOT LITERALLY LAST ON THE SCREEN, and this docblock used to say it was.
|
|
948
|
+
* The CLI prints `Harness:`, `Results:`, `Workspaces:` and `Summary:` AFTER the
|
|
949
|
+
* harness returns, so 2–4 lines always follow the banner. What this ordering buys is
|
|
950
|
+
* that the banner is not separated from its delta by up to ~1250 rows of friction —
|
|
951
|
+
* a bounded, short tail after it, not none. Moving those CLI lines above the harness
|
|
952
|
+
* report is the CLI's call, not this module's.
|
|
953
|
+
*
|
|
954
|
+
* THREE conditions have to hold before the caveat is dropped, not two. A DEGRADED
|
|
955
|
+
* scan is neither contaminated nor incomparable, so a two-clause gate printed
|
|
956
|
+
* `Baseline delta: +0 (with skill: 2/2, without skill: 2/2).` and exited 0 while
|
|
957
|
+
* `baseline.json` carried `degraded: [...]` and an `integrity.summary` ending in a
|
|
958
|
+
* `⚠️ DEGRADED SCAN: …` sentence that was composed, written to the artifact, and
|
|
959
|
+
* never emitted anywhere. Everything this module says about telling "checked and clean"
|
|
960
|
+
* apart from "checked with the blunt instrument" was true of the artifact and false
|
|
961
|
+
* of the terminal — and the terminal is the only surface an operator reads.
|
|
962
|
+
*
|
|
963
|
+
* A CONTAMINATED run still reports its number, deliberately. Contamination does not
|
|
964
|
+
* make the subtraction illegal — both arms were graded to the same depth, so the
|
|
965
|
+
* arithmetic is sound; what it invalidates is INTERPRETING that number as skill
|
|
966
|
+
* lift, which is precisely what the ⚠️ line says ("The reported delta is NOT a
|
|
967
|
+
* measure of skill lift"), and what `vat-skill-testing.md` turns into the
|
|
968
|
+
* instruction "discard the delta". Withholding it here would (a) leave that sentence
|
|
969
|
+
* referring to a number that was never reported, and (b) spend `null` — which means
|
|
970
|
+
* "these arms cannot be subtracted", a different failure with a different remedy —
|
|
971
|
+
* on a run where they can be. A contaminated delta is also diagnostic in its own
|
|
972
|
+
* right: it collapses toward zero, which is corroboration, not noise.
|
|
973
|
+
*/
|
|
974
|
+
declare function formatBaselineReport(delta: BaselineDelta, integrity: BaselineIntegrity): string;
|
|
491
975
|
/**
|
|
492
|
-
* The COMPOSITE run verdict
|
|
976
|
+
* The COMPOSITE run verdict: the run passes only when BOTH
|
|
493
977
|
* the prose-expectation grading passed AND every tool-expectation verdict passed.
|
|
494
978
|
* Tool verdicts live in tool-eval.json (a SEPARATE channel — C2); this combines
|
|
495
979
|
* the two at the exit-code layer WITHOUT mixing the channels' data. When no eval
|
|
496
980
|
* declared `toolExpectations`, `toolEval.evals` is empty and this equals
|
|
497
981
|
* `outputAllPassed`. Pure + unit-testable.
|
|
498
982
|
*/
|
|
499
|
-
|
|
983
|
+
declare function computeCompositeVerdict(outputAllPassed: boolean, toolEval: ToolEvalReport): boolean;
|
|
500
984
|
/**
|
|
501
985
|
* The run's final pass/fail after cost-tiered fail-fast: the {@link
|
|
502
986
|
* computeCompositeVerdict} of the tiers that RAN, AND no tiers were skipped. A
|
|
503
987
|
* fail-fast run that gated higher tiers is NEVER a pass (skipped ≠ passed) — this
|
|
504
|
-
* forces `false` so the exit code is
|
|
988
|
+
* forces `false` so the exit code is FINDINGS, never downgraded to 0 by the
|
|
505
989
|
* composite path alone. Pure + unit-testable.
|
|
506
990
|
*/
|
|
507
|
-
|
|
991
|
+
declare function resolveCompositeAllPassed(outputAllPassed: boolean, toolEval: ToolEvalReport, skipped: SkippedEvalsSummary | undefined): boolean;
|
|
508
992
|
/**
|
|
509
993
|
* Running total of spend across every executor+grader session in a run (adopter
|
|
510
994
|
* follow-up). `sessions` counts only sessions that REPORTED a `total_cost_usd`, so
|
|
@@ -517,13 +1001,13 @@ export interface RunCostSummary {
|
|
|
517
1001
|
sessions: number;
|
|
518
1002
|
}
|
|
519
1003
|
/** Fold one session's `total_cost_usd` into the accumulator; a non-number (mock spawn / missing result) is ignored. */
|
|
520
|
-
|
|
1004
|
+
declare function recordSessionCost(acc: RunCostSummary, totalCostUsd: number | undefined): void;
|
|
521
1005
|
/**
|
|
522
1006
|
* The ` | ≈$0.42 across 6 sessions` spend suffix for the summary line, or `''`
|
|
523
1007
|
* when no session reported a cost (e.g. every spawn was a test mock) so the suffix
|
|
524
1008
|
* never adds noise to a run with no cost signal. Pure + unit-testable.
|
|
525
1009
|
*/
|
|
526
|
-
|
|
1010
|
+
declare function formatRunCostSuffix(cost: RunCostSummary | undefined): string;
|
|
527
1011
|
/**
|
|
528
1012
|
* The run's human summary line, computed from the COMPOSITE verdict so an
|
|
529
1013
|
* output-pass with a failing tool verdict still reads FAIL. The prose-expectation
|
|
@@ -531,7 +1015,7 @@ export declare function formatRunCostSuffix(cost: RunCostSummary | undefined): s
|
|
|
531
1015
|
* verdict failed, a `(N tool)` suffix names how many — so a composite FAIL whose
|
|
532
1016
|
* OUTPUT counts look all-green (e.g. `FAIL 3/3 (1 tool)`) is self-explaining. Pure.
|
|
533
1017
|
*/
|
|
534
|
-
|
|
1018
|
+
declare function buildRunSummary(verdict: GradingVerdict, toolEval: ToolEvalReport, compositeAllPassed: boolean): string;
|
|
535
1019
|
/**
|
|
536
1020
|
* The run summary line, appending the fail-fast SKIPPED note when the tier gate
|
|
537
1021
|
* stopped higher tiers. Legibility is required — the skipped tiers are named on
|
|
@@ -539,10 +1023,106 @@ export declare function buildRunSummary(verdict: GradingVerdict, toolEval: ToolE
|
|
|
539
1023
|
* tiers ALWAYS reads FAIL (skipped ≠ passed forces `compositeAllPassed` false at
|
|
540
1024
|
* the call site), so the base line is already FAIL when the note is present.
|
|
541
1025
|
*/
|
|
542
|
-
|
|
1026
|
+
declare function buildRunSummaryWithSkips(verdict: GradingVerdict, toolEval: ToolEvalReport, compositeAllPassed: boolean, skipped: SkippedEvalsSummary | undefined, cost?: RunCostSummary): string;
|
|
1027
|
+
/**
|
|
1028
|
+
* Post-merge D2 gate over the vat-written results artifacts (vat is SOLE writer):
|
|
1029
|
+
* grading.json, friction.json, tool-eval.json — and, on a baseline run, baseline.json
|
|
1030
|
+
* — must each exist + parse + validate. A separate, explicit check (NOT folded into
|
|
1031
|
+
* any prompt-invariant linter) — fail-closed and safe because vat wrote every one of
|
|
1032
|
+
* these itself just above.
|
|
1033
|
+
*
|
|
1034
|
+
* `baseline.json` was EXEMPT from this gate for as long as the gate has existed, and
|
|
1035
|
+
* it is the artifact the exemption fits worst: on a `--baseline` run it is the only
|
|
1036
|
+
* durable record of the thing the command sells, and this branch has already been
|
|
1037
|
+
* bitten once by a default run deleting `results/` out from under it. Its validation
|
|
1038
|
+
* is also the strictest of the four — {@link BaselineArtifactSchema} carries the
|
|
1039
|
+
* `passed <= total` and `delta === with − without` refines, so a merge that produced
|
|
1040
|
+
* a self-contradicting number fails the run here instead of being written down.
|
|
1041
|
+
*
|
|
1042
|
+
* `baselineRun` gates it rather than "does the file exist", deliberately: an
|
|
1043
|
+
* existence check would pass on the one failure that matters (vat asked for a
|
|
1044
|
+
* baseline and wrote nothing), which is exactly what a fail-CLOSED gate must not do.
|
|
1045
|
+
* A non-baseline run writes no baseline.json at all and is not asked about one.
|
|
1046
|
+
*/
|
|
1047
|
+
declare function assertVatWroteArtifacts(paths: ArtifactPaths, baselineRun: boolean): void;
|
|
543
1048
|
/**
|
|
544
1049
|
* Domain orchestrator for `vat skill test run`. Pure of process.exit — all
|
|
545
1050
|
* exit-code decisions live in the caller (run.ts).
|
|
546
1051
|
*/
|
|
1052
|
+
/**
|
|
1053
|
+
* Resolve WHERE this run lives, and whether the harness owns that location.
|
|
1054
|
+
*
|
|
1055
|
+
* `--out` names the harness root exactly; `--workdir` names the BASE the root is
|
|
1056
|
+
* derived under. They are mutually exclusive: passing both used to silently
|
|
1057
|
+
* discard `--workdir` — the run went entirely to `--out` and the `--workdir` path
|
|
1058
|
+
* was never even created — so an operator using the pair to separate the
|
|
1059
|
+
* executor's cwd from the staged trees got no separation and no warning.
|
|
1060
|
+
*
|
|
1061
|
+
* `harnessCreated` is true only for the derived-under-OS-tmp case; an explicit
|
|
1062
|
+
* `--out` or `--workdir` is a location the user owns and is never auto-removed.
|
|
1063
|
+
*/
|
|
1064
|
+
declare function resolveHarnessLocation(opts: Pick<RunHarnessOptions, 'out' | 'workdir' | 'subject'>): {
|
|
1065
|
+
harnessRoot: string;
|
|
1066
|
+
harnessCreated: boolean;
|
|
1067
|
+
};
|
|
547
1068
|
export declare function runSkillTestHarness(opts: RunHarnessOptions): Promise<RunHarnessResult>;
|
|
1069
|
+
/**
|
|
1070
|
+
* The test-facing seam — every helper the unit tests reach that is NOT part of
|
|
1071
|
+
* this module's API.
|
|
1072
|
+
*
|
|
1073
|
+
* These used to be forty-four separate `export`s, indistinguishable at the export
|
|
1074
|
+
* keyword from the seven names the skill-test barrel actually publishes, so
|
|
1075
|
+
* "is this API?" was a question for a JSDoc rather than a grep. Now it is a
|
|
1076
|
+
* grep: a name here is reachable by a test and by nothing else. Nothing under
|
|
1077
|
+
* `src/` imports `__internal` and no barrel re-exports it — a dev-tools test
|
|
1078
|
+
* holds both lines. Types stay exported on their own: an object cannot carry
|
|
1079
|
+
* them, and a type is not a call site.
|
|
1080
|
+
*/
|
|
1081
|
+
export declare const __internal: {
|
|
1082
|
+
readonly assertVatWroteArtifacts: typeof assertVatWroteArtifacts;
|
|
1083
|
+
readonly buildContaminationInput: typeof buildContaminationInput;
|
|
1084
|
+
readonly buildContaminationSignalsInput: typeof buildContaminationSignalsInput;
|
|
1085
|
+
readonly buildEvalWorkItems: typeof buildEvalWorkItems;
|
|
1086
|
+
readonly buildFlagParseProbe: typeof buildFlagParseProbe;
|
|
1087
|
+
readonly buildPreflightInput: typeof buildPreflightInput;
|
|
1088
|
+
readonly buildResolveCtx: typeof buildResolveCtx;
|
|
1089
|
+
readonly buildRunSummary: typeof buildRunSummary;
|
|
1090
|
+
readonly buildRunSummaryWithSkips: typeof buildRunSummaryWithSkips;
|
|
1091
|
+
readonly computeCompositeVerdict: typeof computeCompositeVerdict;
|
|
1092
|
+
readonly detectItemPluginLayout: typeof detectItemPluginLayout;
|
|
1093
|
+
readonly emitFrictionReport: typeof emitFrictionReport;
|
|
1094
|
+
readonly FLAG_PROBE_SENTINEL: "--vat-probe-flag-that-cannot-exist";
|
|
1095
|
+
readonly formatBaselineReport: typeof formatBaselineReport;
|
|
1096
|
+
readonly formatRunCostSuffix: typeof formatRunCostSuffix;
|
|
1097
|
+
readonly gradedCounts: typeof gradedCounts;
|
|
1098
|
+
readonly helpTextDeclaresFlag: typeof helpTextDeclaresFlag;
|
|
1099
|
+
readonly makeStageItem: typeof makeStageItem;
|
|
1100
|
+
readonly mintArmWorkspaceDirs: typeof mintArmWorkspaceDirs;
|
|
1101
|
+
readonly partitionFragmentsByArm: typeof partitionFragmentsByArm;
|
|
1102
|
+
readonly recordSessionCost: typeof recordSessionCost;
|
|
1103
|
+
readonly rejectedArtifactPath: typeof rejectedArtifactPath;
|
|
1104
|
+
readonly removeVatOnlyDir: typeof removeVatOnlyDir;
|
|
1105
|
+
readonly renderPreflightSummary: typeof renderPreflightSummary;
|
|
1106
|
+
readonly resolveArtifactPaths: typeof resolveArtifactPaths;
|
|
1107
|
+
readonly resolveCompositeAllPassed: typeof resolveCompositeAllPassed;
|
|
1108
|
+
readonly resolveHarnessLocation: typeof resolveHarnessLocation;
|
|
1109
|
+
readonly resolveKnobs: typeof resolveKnobs;
|
|
1110
|
+
readonly resolvePerEvalWorkspaceDir: typeof resolvePerEvalWorkspaceDir;
|
|
1111
|
+
readonly resolveScaffoldEvalsPath: typeof resolveScaffoldEvalsPath;
|
|
1112
|
+
readonly resolveSkillContentNeedles: typeof resolveSkillContentNeedles;
|
|
1113
|
+
readonly resolveStallMs: typeof resolveStallMs;
|
|
1114
|
+
readonly resolveTimeoutMs: typeof resolveTimeoutMs;
|
|
1115
|
+
readonly resolveWorkspacesRoot: typeof resolveWorkspacesRoot;
|
|
1116
|
+
readonly RETAINED_RESULTS_DIRNAME: "results";
|
|
1117
|
+
readonly stageWorkspacesForRun: typeof stageWorkspacesForRun;
|
|
1118
|
+
readonly subjectSkillName: typeof subjectSkillName;
|
|
1119
|
+
readonly swallowCleanupFailure: typeof swallowCleanupFailure;
|
|
1120
|
+
readonly wipeStaleArtifacts: typeof wipeStaleArtifacts;
|
|
1121
|
+
readonly withoutGraderContamination: typeof withoutGraderContamination;
|
|
1122
|
+
readonly cleanupHarness: typeof cleanupHarness;
|
|
1123
|
+
readonly buildStageItems: typeof buildStageItems;
|
|
1124
|
+
readonly resolveGraderOutDir: typeof resolveGraderOutDir;
|
|
1125
|
+
readonly resolveEvalSuitePath: typeof resolveEvalSuitePath;
|
|
1126
|
+
};
|
|
1127
|
+
export {};
|
|
548
1128
|
//# sourceMappingURL=run-harness.d.ts.map
|