@vibe-agent-toolkit/agent-skills 0.2.0-rc.1 → 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
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare function harnessNeedles(harnessRoot: string): PathNeedle[];
|
|
3
|
+
/**
|
|
4
|
+
* Strip every channel by which the executor ENVIRONMENT would tell the skill-absent
|
|
5
|
+
* arm where the skill is staged.
|
|
6
|
+
*
|
|
7
|
+
* A path reaches a child process through four channels — prompt, argv, cwd, and
|
|
8
|
+
* env. The first three are handled at the call site; this is the fourth, and it is
|
|
9
|
+
* the one that is easy to miss because nothing at the spawn call mentions it: the
|
|
10
|
+
* run assembles ONE env and both arms were given it verbatim.
|
|
11
|
+
*
|
|
12
|
+
* Two rules, deliberately in this order:
|
|
13
|
+
* 1. drop {@link CONTROL_ARM_FORBIDDEN_ENV_KEYS} by name;
|
|
14
|
+
* 2. drop ANY entry whose value contains the harness root — which covers
|
|
15
|
+
* adopter-declared `env:` values that interpolated `${stagedSkillDir}` or
|
|
16
|
+
* `${harnessRoot}`, and, more importantly, covers the next such channel
|
|
17
|
+
* somebody adds without knowing this function exists.
|
|
18
|
+
*
|
|
19
|
+
* Rule 2 is a value scan rather than a longer key list on purpose: a key list only
|
|
20
|
+
* ever knows about the leaks already found. It is accident-catching HYGIENE, not an
|
|
21
|
+
* adversarial control — a determined skill author splits the path across two vars
|
|
22
|
+
* (`A=/tmp/vat-skill-test` + `B=/<key>/…`) and the per-value scan never joins them.
|
|
23
|
+
* The adversarial control is that the path is not REACHABLE, not that it is unnamed.
|
|
24
|
+
*
|
|
25
|
+
* Rule 2 exempts {@link protectedEnvNames}, which is WIDER than the three names the
|
|
26
|
+
* paragraph below uses as its example: it is every process essential, the auth
|
|
27
|
+
* allowlist, every inference credential, the whole `CREDENTIAL_ROUTING_DENY` list,
|
|
28
|
+
* AND this run's `modelVars`. The model vars are the ones that matter most here and
|
|
29
|
+
* were missing until they were threaded in: dropping a model var because its value
|
|
30
|
+
* happens to sit under `--out` runs the control arm ON A DIFFERENT MODEL, which is
|
|
31
|
+
* a far worse confound than the missing PATH this exemption exists to prevent, and
|
|
32
|
+
* one no operator would think to look for.
|
|
33
|
+
*
|
|
34
|
+
* The three named below are the illustration. An `--out` under any of their values (e.g.
|
|
35
|
+
* `--out .` from a repo root, which puts `<repo>/node_modules/.bin` on PATH inside a
|
|
36
|
+
* `bun run`) would otherwise spawn the control arm with no PATH at all. That does not
|
|
37
|
+
* fail; it DEGRADES the control, which scores lower, which reports as skill lift. A
|
|
38
|
+
* scrub that manufactures the very delta the product sells is worse than the leak it
|
|
39
|
+
* closes, so a protected var that names the harness root is RETAINED and reported
|
|
40
|
+
* loudly instead — the operator can see it and move `--out`.
|
|
41
|
+
*
|
|
42
|
+
* `${fixturesDir}` resolves under the WORKSPACES root, not the harness root, so the
|
|
43
|
+
* control arm keeps its declared input files — the arms stay identical in
|
|
44
|
+
* everything except the skill itself.
|
|
45
|
+
*
|
|
46
|
+
* Pure. Returns the scrubbed env and three disjoint name lists for the transparency
|
|
47
|
+
* lines: the rule-1 drops, the rule-2 drops, and the protected names RETAINED
|
|
48
|
+
* despite naming the harness root.
|
|
49
|
+
*
|
|
50
|
+
* The two drop lists are separate because they mean opposite things to an operator.
|
|
51
|
+
* A rule-1 drop is routine — `CLAUDE_PLUGIN_ROOT` is withheld on every plugin-layout
|
|
52
|
+
* baseline run by design, and its value may name nothing in the harness at all (the
|
|
53
|
+
* installed-plugin-cache case). A rule-2 drop says the operator's OWN declared `env:`
|
|
54
|
+
* named the harness root, which is the one worth acting on. Merged into one list under
|
|
55
|
+
* one "naming the harness root" sentence, the routine drop was both mislabelled and
|
|
56
|
+
* loud enough to bury the interesting one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scrubControlArmEnv(env: NodeJS.ProcessEnv, harnessRoot: string,
|
|
59
|
+
/**
|
|
60
|
+
* This run's model env var names. REQUIRED, not defaulted: an omitted list
|
|
61
|
+
* silently narrows the exemption set, and the var it drops decides which model
|
|
62
|
+
* the control arm runs — a defect that reads as skill lift and is invisible in
|
|
63
|
+
* the output. Pass `[]` when the run genuinely has none.
|
|
64
|
+
*/
|
|
65
|
+
modelVars: readonly string[],
|
|
66
|
+
/**
|
|
67
|
+
* The platform whose env-name casing rules apply. Defaults to this process's.
|
|
68
|
+
* Threaded in ONLY so the win32 branch of {@link isProtectedName} is testable:
|
|
69
|
+
* without it the case-insensitive lookup this function deliberately uses instead
|
|
70
|
+
* of `protectedNames.has` was unexercised on every platform CI runs the unit
|
|
71
|
+
* suite on, and swapping the two was green.
|
|
72
|
+
*/
|
|
73
|
+
platform?: string): {
|
|
74
|
+
env: NodeJS.ProcessEnv;
|
|
75
|
+
/** Rule 1: dropped by NAME, whatever the value. */
|
|
76
|
+
droppedForbiddenKey: string[];
|
|
77
|
+
/** Rule 2: dropped because the VALUE names the harness root. */
|
|
78
|
+
droppedNamingRoot: string[];
|
|
79
|
+
retainedLeaks: string[];
|
|
80
|
+
};
|
|
81
|
+
export declare const BaselineContaminationHitSchema: z.ZodObject<{
|
|
82
|
+
/**
|
|
83
|
+
* What matched: a path under vat's harness root, a declared executable's name,
|
|
84
|
+
* the OTHER arm's workspace directory, or one of vat's private tmp dirs.
|
|
85
|
+
*
|
|
86
|
+
* `sibling-arm` is a materially different finding from the first two. Those mean
|
|
87
|
+
* the arm found a copy of the skill; this one means it read the treatment arm's
|
|
88
|
+
* live working directory, so the control may be echoing an answer the treatment
|
|
89
|
+
* produced seconds earlier rather than solving anything.
|
|
90
|
+
*
|
|
91
|
+
* `vat-private-dir` is worse than all of them. It means the arm reached the held
|
|
92
|
+
* eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
|
|
93
|
+
* integrity nonce). Reaching a copy of the skill inflates the control arm;
|
|
94
|
+
* reaching the answer key inflates BOTH arms, so it does not even show up as a
|
|
95
|
+
* shrunken delta — see {@link vatPrivateDirNeedles}.
|
|
96
|
+
*/
|
|
97
|
+
kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
|
|
98
|
+
/**
|
|
99
|
+
* The matched token — a path SUFFIX, a verbatim skill line, or the executable
|
|
100
|
+
* basename.
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
|
|
103
|
+
* is not cosmetic. Needle sets run longest-first, so the needle that matches a
|
|
104
|
+
* full-path reach is the ENTIRE absolute root — on Windows
|
|
105
|
+
* `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
|
|
106
|
+
* operator's login name in an artifact adopters attach to bug reports, in the one
|
|
107
|
+
* field that reads like a short opaque token. The last two segments identify the
|
|
108
|
+
* directory just as well (`vat-skill-test/<key>`) and name nobody. See
|
|
109
|
+
* {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
|
|
110
|
+
* which carries its own warning.
|
|
111
|
+
*
|
|
112
|
+
* A skill-content match is a verbatim line of the skill's own body and is NOT
|
|
113
|
+
* truncated — it has no `/`-separated structure to trim, and the whole point of
|
|
114
|
+
* that signal is that the operator can find the line in SKILL.md by eye.
|
|
115
|
+
*/
|
|
116
|
+
match: z.ZodString;
|
|
117
|
+
/**
|
|
118
|
+
* A short excerpt of the transcript around the match, for triage.
|
|
119
|
+
*
|
|
120
|
+
* RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
|
|
121
|
+
* bounded and whitespace-collapsed. That is a promise this field has to keep to
|
|
122
|
+
* be worth anything: its only use is to be carried back to the transcript and
|
|
123
|
+
* searched for, and it once came from the NORMALIZED haystack instead, so
|
|
124
|
+
* `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
|
|
125
|
+
* operator grepping for the reported excerpt found nothing, in a field that
|
|
126
|
+
* exists solely so they can. The structured scan quotes the raw tool input; the
|
|
127
|
+
* flat and content scans map their match back through {@link ScanHaystack}.
|
|
128
|
+
* (Whitespace collapse is the one transform that survives, and it is stated here:
|
|
129
|
+
* grep for a distinctive WORD from the excerpt, not for the whole line.)
|
|
130
|
+
*
|
|
131
|
+
* NOT redacted. The most likely way a harness path reaches a transcript is an
|
|
132
|
+
* `env` dump, so this window can capture adjacent environment values —
|
|
133
|
+
* `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
|
|
134
|
+
* `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
|
|
135
|
+
* `grading.json` already quotes transcripts verbatim, so this is the same
|
|
136
|
+
* exposure, not a new one.
|
|
137
|
+
*/
|
|
138
|
+
excerpt: z.ZodString;
|
|
139
|
+
}, "strict", z.ZodTypeAny, {
|
|
140
|
+
match: string;
|
|
141
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
142
|
+
excerpt: string;
|
|
143
|
+
}, {
|
|
144
|
+
match: string;
|
|
145
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
146
|
+
excerpt: string;
|
|
147
|
+
}>;
|
|
148
|
+
export type BaselineContaminationHit = z.infer<typeof BaselineContaminationHitSchema>;
|
|
149
|
+
/** Per-eval contamination finding for the WITHOUT arm. */
|
|
150
|
+
export declare const BaselineContaminationSchema: z.ZodObject<{
|
|
151
|
+
evalId: z.ZodString;
|
|
152
|
+
hits: z.ZodArray<z.ZodObject<{
|
|
153
|
+
/**
|
|
154
|
+
* What matched: a path under vat's harness root, a declared executable's name,
|
|
155
|
+
* the OTHER arm's workspace directory, or one of vat's private tmp dirs.
|
|
156
|
+
*
|
|
157
|
+
* `sibling-arm` is a materially different finding from the first two. Those mean
|
|
158
|
+
* the arm found a copy of the skill; this one means it read the treatment arm's
|
|
159
|
+
* live working directory, so the control may be echoing an answer the treatment
|
|
160
|
+
* produced seconds earlier rather than solving anything.
|
|
161
|
+
*
|
|
162
|
+
* `vat-private-dir` is worse than all of them. It means the arm reached the held
|
|
163
|
+
* eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
|
|
164
|
+
* integrity nonce). Reaching a copy of the skill inflates the control arm;
|
|
165
|
+
* reaching the answer key inflates BOTH arms, so it does not even show up as a
|
|
166
|
+
* shrunken delta — see {@link vatPrivateDirNeedles}.
|
|
167
|
+
*/
|
|
168
|
+
kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
|
|
169
|
+
/**
|
|
170
|
+
* The matched token — a path SUFFIX, a verbatim skill line, or the executable
|
|
171
|
+
* basename.
|
|
172
|
+
*
|
|
173
|
+
* ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
|
|
174
|
+
* is not cosmetic. Needle sets run longest-first, so the needle that matches a
|
|
175
|
+
* full-path reach is the ENTIRE absolute root — on Windows
|
|
176
|
+
* `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
|
|
177
|
+
* operator's login name in an artifact adopters attach to bug reports, in the one
|
|
178
|
+
* field that reads like a short opaque token. The last two segments identify the
|
|
179
|
+
* directory just as well (`vat-skill-test/<key>`) and name nobody. See
|
|
180
|
+
* {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
|
|
181
|
+
* which carries its own warning.
|
|
182
|
+
*
|
|
183
|
+
* A skill-content match is a verbatim line of the skill's own body and is NOT
|
|
184
|
+
* truncated — it has no `/`-separated structure to trim, and the whole point of
|
|
185
|
+
* that signal is that the operator can find the line in SKILL.md by eye.
|
|
186
|
+
*/
|
|
187
|
+
match: z.ZodString;
|
|
188
|
+
/**
|
|
189
|
+
* A short excerpt of the transcript around the match, for triage.
|
|
190
|
+
*
|
|
191
|
+
* RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
|
|
192
|
+
* bounded and whitespace-collapsed. That is a promise this field has to keep to
|
|
193
|
+
* be worth anything: its only use is to be carried back to the transcript and
|
|
194
|
+
* searched for, and it once came from the NORMALIZED haystack instead, so
|
|
195
|
+
* `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
|
|
196
|
+
* operator grepping for the reported excerpt found nothing, in a field that
|
|
197
|
+
* exists solely so they can. The structured scan quotes the raw tool input; the
|
|
198
|
+
* flat and content scans map their match back through {@link ScanHaystack}.
|
|
199
|
+
* (Whitespace collapse is the one transform that survives, and it is stated here:
|
|
200
|
+
* grep for a distinctive WORD from the excerpt, not for the whole line.)
|
|
201
|
+
*
|
|
202
|
+
* NOT redacted. The most likely way a harness path reaches a transcript is an
|
|
203
|
+
* `env` dump, so this window can capture adjacent environment values —
|
|
204
|
+
* `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
|
|
205
|
+
* `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
|
|
206
|
+
* `grading.json` already quotes transcripts verbatim, so this is the same
|
|
207
|
+
* exposure, not a new one.
|
|
208
|
+
*/
|
|
209
|
+
excerpt: z.ZodString;
|
|
210
|
+
}, "strict", z.ZodTypeAny, {
|
|
211
|
+
match: string;
|
|
212
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
213
|
+
excerpt: string;
|
|
214
|
+
}, {
|
|
215
|
+
match: string;
|
|
216
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
217
|
+
excerpt: string;
|
|
218
|
+
}>, "many">;
|
|
219
|
+
}, "strict", z.ZodTypeAny, {
|
|
220
|
+
evalId: string;
|
|
221
|
+
hits: {
|
|
222
|
+
match: string;
|
|
223
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
224
|
+
excerpt: string;
|
|
225
|
+
}[];
|
|
226
|
+
}, {
|
|
227
|
+
evalId: string;
|
|
228
|
+
hits: {
|
|
229
|
+
match: string;
|
|
230
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
231
|
+
excerpt: string;
|
|
232
|
+
}[];
|
|
233
|
+
}>;
|
|
234
|
+
export type BaselineContamination = z.infer<typeof BaselineContaminationSchema>;
|
|
235
|
+
/**
|
|
236
|
+
* The run-level `baselineIntegrity` block stamped into `baseline.json`.
|
|
237
|
+
*
|
|
238
|
+
* `contaminated: true` means at least one WITHOUT-arm eval demonstrably reached
|
|
239
|
+
* the skill; the A/B delta for the run is then NOT interpretable as skill lift.
|
|
240
|
+
* The block is written on EVERY baseline run, clean or not, so its absence
|
|
241
|
+
* always means "this baseline.json predates integrity checking" and never
|
|
242
|
+
* "checked and clean".
|
|
243
|
+
*/
|
|
244
|
+
/**
|
|
245
|
+
* A detector that can be ARMED for a run.
|
|
246
|
+
*
|
|
247
|
+
* Deliberately the SAME enum as a hit's `kind`, not a parallel list: each
|
|
248
|
+
* detector produces exactly one kind of hit, so two lists would be a drift
|
|
249
|
+
* hazard for no expressive gain. Which detectors were armed is the difference
|
|
250
|
+
* between "checked and clean" and "nothing was looking":
|
|
251
|
+
*
|
|
252
|
+
* - `harness-path` — vat's staged trees ({@link harnessNeedles}). Always armed.
|
|
253
|
+
* - `sibling-arm` — the other arm's live workspace. Armed only when there is one.
|
|
254
|
+
* - `vat-private-dir` — the held answer key and the grader dir
|
|
255
|
+
* ({@link vatPrivateDirNeedles}).
|
|
256
|
+
* - `declared-executable` — the skill's executables by name. Armed ONLY for a
|
|
257
|
+
* skill that ships executables; an instruction-only skill (the common case)
|
|
258
|
+
* has no such name, so this signal is absent.
|
|
259
|
+
* - `skill-content` — verbatim lines of the staged SKILL.md
|
|
260
|
+
* ({@link skillContentNeedles}). The one signal that does not need the arm to
|
|
261
|
+
* name a path or an executable, and therefore the only cover an
|
|
262
|
+
* instruction-only skill has. Unarmed when the body yields no line distinctive
|
|
263
|
+
* enough to accuse anyone with, which is itself worth seeing.
|
|
264
|
+
*/
|
|
265
|
+
export declare const ContaminationSignalSchema: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
|
|
266
|
+
export type ContaminationSignal = z.infer<typeof ContaminationSignalSchema>;
|
|
267
|
+
/** One eval whose two arms were graded against a different number of expectations. */
|
|
268
|
+
export declare const BaselineArmSkewSchema: z.ZodObject<{
|
|
269
|
+
evalId: z.ZodString;
|
|
270
|
+
withTotal: z.ZodNumber;
|
|
271
|
+
withoutTotal: z.ZodNumber;
|
|
272
|
+
}, "strict", z.ZodTypeAny, {
|
|
273
|
+
evalId: string;
|
|
274
|
+
withTotal: number;
|
|
275
|
+
withoutTotal: number;
|
|
276
|
+
}, {
|
|
277
|
+
evalId: string;
|
|
278
|
+
withTotal: number;
|
|
279
|
+
withoutTotal: number;
|
|
280
|
+
}>;
|
|
281
|
+
export type BaselineArmSkew = z.infer<typeof BaselineArmSkewSchema>;
|
|
282
|
+
/**
|
|
283
|
+
* One CONTROL-arm eval that produced no grade at all, and why.
|
|
284
|
+
*
|
|
285
|
+
* Distinct from {@link BaselineArmSkewSchema}, which describes two arms that both
|
|
286
|
+
* graded and disagreed about how deep. This describes an arm that never got as far
|
|
287
|
+
* as a verdict: its executor failed to spawn, stalled, hit the wall clock, or its
|
|
288
|
+
* grader exited non-zero / wrote no fragment / wrote something unparseable / echoed
|
|
289
|
+
* the wrong nonce / graded a different number of expectations than the eval declares.
|
|
290
|
+
*
|
|
291
|
+
* Every one of those used to propagate out of `runPipeline` and fail the WHOLE run
|
|
292
|
+
* (exit 1) — discarding fully-billed treatment work that was perfectly good, and
|
|
293
|
+
* writing no `grading.json` at all. The treatment arm still hard-fails that way,
|
|
294
|
+
* because without a treatment result there is nothing to salvage; the control arm
|
|
295
|
+
* does not, because the thing it feeds (the delta) has a way to say "I cannot be
|
|
296
|
+
* computed" and the thing the operator paid for (the treatment grading) does not.
|
|
297
|
+
*
|
|
298
|
+
* `detail` is the arm-named error message verbatim — it already carries the eval id
|
|
299
|
+
* and the failing stage, and it is the text an operator would otherwise have seen on
|
|
300
|
+
* stderr before the run died.
|
|
301
|
+
*/
|
|
302
|
+
export declare const BaselineControlArmFailureSchema: z.ZodObject<{
|
|
303
|
+
evalId: z.ZodString;
|
|
304
|
+
detail: z.ZodString;
|
|
305
|
+
}, "strict", z.ZodTypeAny, {
|
|
306
|
+
detail: string;
|
|
307
|
+
evalId: string;
|
|
308
|
+
}, {
|
|
309
|
+
detail: string;
|
|
310
|
+
evalId: string;
|
|
311
|
+
}>;
|
|
312
|
+
export type BaselineControlArmFailure = z.infer<typeof BaselineControlArmFailureSchema>;
|
|
313
|
+
/**
|
|
314
|
+
* What ONE arm actually graded for ONE eval: how many expectations it was graded
|
|
315
|
+
* against, and how many of them passed.
|
|
316
|
+
*
|
|
317
|
+
* Deliberately ONE type feeding two consumers. {@link armExpectationSkew} reads only
|
|
318
|
+
* `total` — whether subtracting the arms is legal at all — while the delta block
|
|
319
|
+
* reads `passed` to do the subtraction. Deriving those from two separate shapes is
|
|
320
|
+
* exactly the drift this module keeps warning about: the two blocks would be free to
|
|
321
|
+
* disagree about what each arm graded, and the one that disagreed quietly would be
|
|
322
|
+
* believed. `passed` is carried here even though the parity check ignores it so that
|
|
323
|
+
* a single derivation at the call site feeds both.
|
|
324
|
+
*/
|
|
325
|
+
export interface ArmEvalGrade {
|
|
326
|
+
evalId: string;
|
|
327
|
+
passed: number;
|
|
328
|
+
total: number;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Evals whose two arms were graded against DIFFERENT numbers of expectations.
|
|
332
|
+
*
|
|
333
|
+
* `--baseline` sells a DELTA, and a delta between two differently-sized
|
|
334
|
+
* denominators is not a delta. vat computes each arm's `summary` from the
|
|
335
|
+
* fragments it received, so both reports are internally consistent by
|
|
336
|
+
* construction and `reconcileGrading` cannot see this — a grader that emitted 2
|
|
337
|
+
* entries for a 3-expectation eval on the control arm yields
|
|
338
|
+
* `baseline.summary = {passed:2,total:2}`, which reads as **100% without the
|
|
339
|
+
* skill**. That is the most damaging direction the number can be wrong in: it
|
|
340
|
+
* says the skill did nothing.
|
|
341
|
+
*
|
|
342
|
+
* Both directions count, and so does an eval graded on only one arm — the missing
|
|
343
|
+
* side is reported as 0 rather than dropped, since a silently absent eval skews
|
|
344
|
+
* the run total exactly as a short-graded one does.
|
|
345
|
+
*
|
|
346
|
+
* THE ONE-ARM CASE IS NOW THE LOAD-BEARING ONE, and it is why this function did not
|
|
347
|
+
* become redundant when the declared-count check landed. Both arms are pinned to the
|
|
348
|
+
* eval's DECLARED expectation count at the grader boundary
|
|
349
|
+
* (`assertExpectationCountDeclared` in eval-grader.ts), so two arms that both
|
|
350
|
+
* produced a fragment can no longer disagree about `total` — which reads like this
|
|
351
|
+
* check has nothing left to find. It has two live inputs it always had and only now
|
|
352
|
+
* carries alone:
|
|
353
|
+
*
|
|
354
|
+
* - an eval the CONTROL arm never graded at all, because its executor or grader
|
|
355
|
+
* broke (see {@link BaselineControlArmFailureSchema}). That is not a hypothetical:
|
|
356
|
+
* it is the routine outcome the run no longer aborts on, so it arrives here on
|
|
357
|
+
* every such run.
|
|
358
|
+
* - an eval graded on one arm only for any other reason — a fragment lost, an arm
|
|
359
|
+
* mislabelled, a future path that grades the arms from different suites.
|
|
360
|
+
*
|
|
361
|
+
* It is also the SINGLE AUTHORITY `computeBaselineDelta` consults before withholding
|
|
362
|
+
* (see baseline-delta.ts). Deleting it would leave the delta with no way to say
|
|
363
|
+
* "these two totals cannot be subtracted", which is the whole reason `null` exists.
|
|
364
|
+
*
|
|
365
|
+
* The declared-count check is the STRONGER cousin this docblock used to say was "NOT
|
|
366
|
+
* here … it needs the suite threaded into the merge". That premise was wrong: the
|
|
367
|
+
* declared list is already in hand at the grader boundary, one call before the
|
|
368
|
+
* fragment is returned, so no threading was needed and the check now lives there.
|
|
369
|
+
* It catches what parity structurally cannot — both graders drifting the SAME way,
|
|
370
|
+
* which keeps parity satisfied while every reported number is wrong.
|
|
371
|
+
*/
|
|
372
|
+
export declare function armExpectationSkew(withArm: readonly ArmEvalGrade[], withoutArm: readonly ArmEvalGrade[]): BaselineArmSkew[];
|
|
373
|
+
/**
|
|
374
|
+
* Why one eval's scan could not run at full strength.
|
|
375
|
+
*
|
|
376
|
+
* The structured scan ({@link detectBaselineContamination}) needs three things the
|
|
377
|
+
* transcript is not contractually obliged to provide: parseable stream-json with no
|
|
378
|
+
* dropped lines, and a cwd it can follow from the arm's own workspace through every
|
|
379
|
+
* `cd`.
|
|
380
|
+
*
|
|
381
|
+
* ⚠️ THE TWO FAILURES ARE NOT THE SAME SHAPE, and treating them alike was a defect
|
|
382
|
+
* in its own right. An UNPARSEABLE transcript leaves nothing to walk, so the scan
|
|
383
|
+
* falls back to the flat text match this module used to be — which both over-reports
|
|
384
|
+
* (a `find` that PRINTS a path reads as a reach) and under-reports (a relative reach
|
|
385
|
+
* after a `cd` loses the leading slash a bare-name needle requires). An unevaluable
|
|
386
|
+
* `cd`, or a dropped line, leaves a HOLE instead: everything resolved before it is
|
|
387
|
+
* still validly resolved and is kept, and only what came after and needed a cwd is
|
|
388
|
+
* lost. Falling back for that case discarded good evidence AND reintroduced the flat
|
|
389
|
+
* scanner's over-reporting — one leading `cd $HOME` was enough to turn a pure
|
|
390
|
+
* directory listing into the "reached the answer key" verdict.
|
|
391
|
+
*
|
|
392
|
+
* A fallback that is not announced is the failure mode this whole module exists
|
|
393
|
+
* to prevent: `contaminated: false` from a degraded scan is written with exactly
|
|
394
|
+
* the same bytes as `contaminated: false` from a scan that actually looked.
|
|
395
|
+
* `signals` already separates "checked and clean" from "nothing was armed"; this
|
|
396
|
+
* separates "checked and clean" from "checked with the blunt instrument".
|
|
397
|
+
*/
|
|
398
|
+
export declare const BASELINE_SCAN_DEGRADATION_REASONS: readonly ["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"];
|
|
399
|
+
export declare const BaselineScanDegradationSchema: z.ZodObject<{
|
|
400
|
+
reason: z.ZodEnum<["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"]>;
|
|
401
|
+
/** Human-readable specifics — the offending `cd`, or what the parse produced. */
|
|
402
|
+
detail: z.ZodString;
|
|
403
|
+
/** Which eval degraded. Absent on the value the detector returns; stamped by the caller. */
|
|
404
|
+
evalId: z.ZodOptional<z.ZodString>;
|
|
405
|
+
}, "strict", z.ZodTypeAny, {
|
|
406
|
+
detail: string;
|
|
407
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
408
|
+
evalId?: string | undefined;
|
|
409
|
+
}, {
|
|
410
|
+
detail: string;
|
|
411
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
412
|
+
evalId?: string | undefined;
|
|
413
|
+
}>;
|
|
414
|
+
export type BaselineScanDegradation = z.infer<typeof BaselineScanDegradationSchema>;
|
|
415
|
+
export declare const BaselineIntegritySchema: z.ZodObject<{
|
|
416
|
+
contaminated: z.ZodBoolean;
|
|
417
|
+
/**
|
|
418
|
+
* The evals whose scan did not run at full strength, and why. Empty means every
|
|
419
|
+
* eval got a complete structured scan — the only state in which
|
|
420
|
+
* `contaminated: false` means "looked properly and found nothing".
|
|
421
|
+
*
|
|
422
|
+
* NOT all the same failure: `transcript-unparsed` fell back to the flat text
|
|
423
|
+
* match, while `cwd-untracked` and `transcript-malformed` kept every reach they
|
|
424
|
+
* had resolved and lost only what came after the hole. See
|
|
425
|
+
* {@link BASELINE_SCAN_DEGRADATION_REASONS}.
|
|
426
|
+
*/
|
|
427
|
+
degraded: z.ZodArray<z.ZodObject<{
|
|
428
|
+
reason: z.ZodEnum<["transcript-unparsed", "cwd-unknown", "cwd-untracked", "transcript-malformed", "glob-unexpanded"]>;
|
|
429
|
+
/** Human-readable specifics — the offending `cd`, or what the parse produced. */
|
|
430
|
+
detail: z.ZodString;
|
|
431
|
+
/** Which eval degraded. Absent on the value the detector returns; stamped by the caller. */
|
|
432
|
+
evalId: z.ZodOptional<z.ZodString>;
|
|
433
|
+
}, "strict", z.ZodTypeAny, {
|
|
434
|
+
detail: string;
|
|
435
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
436
|
+
evalId?: string | undefined;
|
|
437
|
+
}, {
|
|
438
|
+
detail: string;
|
|
439
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
440
|
+
evalId?: string | undefined;
|
|
441
|
+
}>, "many">;
|
|
442
|
+
/**
|
|
443
|
+
* Whether the two arms were graded against the same expectations, and so
|
|
444
|
+
* whether subtracting one summary from the other means anything.
|
|
445
|
+
*
|
|
446
|
+
* Separate from `contaminated` because they fail differently: contamination
|
|
447
|
+
* says the control HAD the treatment, comparability says the two numbers were
|
|
448
|
+
* never measuring the same thing. A run can be clean and incomparable.
|
|
449
|
+
*/
|
|
450
|
+
comparable: z.ZodBoolean;
|
|
451
|
+
/** The evals behind a `comparable: false`; empty when the arms agree. */
|
|
452
|
+
skew: z.ZodArray<z.ZodObject<{
|
|
453
|
+
evalId: z.ZodString;
|
|
454
|
+
withTotal: z.ZodNumber;
|
|
455
|
+
withoutTotal: z.ZodNumber;
|
|
456
|
+
}, "strict", z.ZodTypeAny, {
|
|
457
|
+
evalId: string;
|
|
458
|
+
withTotal: number;
|
|
459
|
+
withoutTotal: number;
|
|
460
|
+
}, {
|
|
461
|
+
evalId: string;
|
|
462
|
+
withTotal: number;
|
|
463
|
+
withoutTotal: number;
|
|
464
|
+
}>, "many">;
|
|
465
|
+
/**
|
|
466
|
+
* Control-arm evals that produced no grade at all — see
|
|
467
|
+
* {@link BaselineControlArmFailureSchema}.
|
|
468
|
+
*
|
|
469
|
+
* A SEPARATE field from `skew` even though every entry here also produces a skew
|
|
470
|
+
* entry (an eval the control arm never graded has `withoutTotal: 0`), because the
|
|
471
|
+
* two say different things to an operator: skew says "the two graders disagreed
|
|
472
|
+
* about the job", this says "half the experiment did not run, and here is the
|
|
473
|
+
* spawn/grader failure that stopped it". Collapsing them would report a dead
|
|
474
|
+
* control arm as a grading-depth disagreement, which points triage at the grader
|
|
475
|
+
* prompt instead of at the timeout.
|
|
476
|
+
*/
|
|
477
|
+
controlArmFailures: z.ZodArray<z.ZodObject<{
|
|
478
|
+
evalId: z.ZodString;
|
|
479
|
+
detail: z.ZodString;
|
|
480
|
+
}, "strict", z.ZodTypeAny, {
|
|
481
|
+
detail: string;
|
|
482
|
+
evalId: string;
|
|
483
|
+
}, {
|
|
484
|
+
detail: string;
|
|
485
|
+
evalId: string;
|
|
486
|
+
}>, "many">;
|
|
487
|
+
/** Human-readable verdict, safe to print verbatim. */
|
|
488
|
+
summary: z.ZodString;
|
|
489
|
+
/**
|
|
490
|
+
* Which detectors were ARMED for this run.
|
|
491
|
+
*
|
|
492
|
+
* Without it, `contaminated: false` is written identically whether four signals
|
|
493
|
+
* were active or one, so a clean verdict cannot be told apart from a blind one
|
|
494
|
+
* — and the difference is large: the executable-name signal is the only one
|
|
495
|
+
* that sees an ambient copy of the skill in the adopter's own repo, and it does
|
|
496
|
+
* not exist for a skill that ships no executables.
|
|
497
|
+
*
|
|
498
|
+
* An empty array is the loudest case: nothing was looking, and `contaminated`
|
|
499
|
+
* says nothing at all.
|
|
500
|
+
*/
|
|
501
|
+
signals: z.ZodArray<z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>, "many">;
|
|
502
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
503
|
+
evalId: z.ZodString;
|
|
504
|
+
hits: z.ZodArray<z.ZodObject<{
|
|
505
|
+
/**
|
|
506
|
+
* What matched: a path under vat's harness root, a declared executable's name,
|
|
507
|
+
* the OTHER arm's workspace directory, or one of vat's private tmp dirs.
|
|
508
|
+
*
|
|
509
|
+
* `sibling-arm` is a materially different finding from the first two. Those mean
|
|
510
|
+
* the arm found a copy of the skill; this one means it read the treatment arm's
|
|
511
|
+
* live working directory, so the control may be echoing an answer the treatment
|
|
512
|
+
* produced seconds earlier rather than solving anything.
|
|
513
|
+
*
|
|
514
|
+
* `vat-private-dir` is worse than all of them. It means the arm reached the held
|
|
515
|
+
* eval suite (the `expected_output` ANSWER KEY) or the grader dir (the run's
|
|
516
|
+
* integrity nonce). Reaching a copy of the skill inflates the control arm;
|
|
517
|
+
* reaching the answer key inflates BOTH arms, so it does not even show up as a
|
|
518
|
+
* shrunken delta — see {@link vatPrivateDirNeedles}.
|
|
519
|
+
*/
|
|
520
|
+
kind: z.ZodEnum<["harness-path", "declared-executable", "sibling-arm", "vat-private-dir", "skill-content"]>;
|
|
521
|
+
/**
|
|
522
|
+
* The matched token — a path SUFFIX, a verbatim skill line, or the executable
|
|
523
|
+
* basename.
|
|
524
|
+
*
|
|
525
|
+
* ⚠️ PATH MATCHES ARE TRUNCATED TO THEIR LAST TWO SEGMENTS ON PURPOSE, and that
|
|
526
|
+
* is not cosmetic. Needle sets run longest-first, so the needle that matches a
|
|
527
|
+
* full-path reach is the ENTIRE absolute root — on Windows
|
|
528
|
+
* `C:/Users/<username>/AppData/Local/Temp/vat-skill-test/<key>`, which puts the
|
|
529
|
+
* operator's login name in an artifact adopters attach to bug reports, in the one
|
|
530
|
+
* field that reads like a short opaque token. The last two segments identify the
|
|
531
|
+
* directory just as well (`vat-skill-test/<key>`) and name nobody. See
|
|
532
|
+
* {@link redactPathMatch}; the untruncated path is still visible in `excerpt`,
|
|
533
|
+
* which carries its own warning.
|
|
534
|
+
*
|
|
535
|
+
* A skill-content match is a verbatim line of the skill's own body and is NOT
|
|
536
|
+
* truncated — it has no `/`-separated structure to trim, and the whole point of
|
|
537
|
+
* that signal is that the operator can find the line in SKILL.md by eye.
|
|
538
|
+
*/
|
|
539
|
+
match: z.ZodString;
|
|
540
|
+
/**
|
|
541
|
+
* A short excerpt of the transcript around the match, for triage.
|
|
542
|
+
*
|
|
543
|
+
* RAW TRANSCRIPT — the arm's own bytes, its own casing, its own separators,
|
|
544
|
+
* bounded and whitespace-collapsed. That is a promise this field has to keep to
|
|
545
|
+
* be worth anything: its only use is to be carried back to the transcript and
|
|
546
|
+
* searched for, and it once came from the NORMALIZED haystack instead, so
|
|
547
|
+
* `SKILL.md` was reported as `skill.md` and a JSON-escaped `\n` as `/n` — an
|
|
548
|
+
* operator grepping for the reported excerpt found nothing, in a field that
|
|
549
|
+
* exists solely so they can. The structured scan quotes the raw tool input; the
|
|
550
|
+
* flat and content scans map their match back through {@link ScanHaystack}.
|
|
551
|
+
* (Whitespace collapse is the one transform that survives, and it is stated here:
|
|
552
|
+
* grep for a distinctive WORD from the excerpt, not for the whole line.)
|
|
553
|
+
*
|
|
554
|
+
* NOT redacted. The most likely way a harness path reaches a transcript is an
|
|
555
|
+
* `env` dump, so this window can capture adjacent environment values —
|
|
556
|
+
* `GITHUB_TOKEN=…`, `HOME=/Users/<name>` have both been observed — and
|
|
557
|
+
* `baseline.json` is a file adopters attach to reports. Treat it as sensitive;
|
|
558
|
+
* `grading.json` already quotes transcripts verbatim, so this is the same
|
|
559
|
+
* exposure, not a new one.
|
|
560
|
+
*/
|
|
561
|
+
excerpt: z.ZodString;
|
|
562
|
+
}, "strict", z.ZodTypeAny, {
|
|
563
|
+
match: string;
|
|
564
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
565
|
+
excerpt: string;
|
|
566
|
+
}, {
|
|
567
|
+
match: string;
|
|
568
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
569
|
+
excerpt: string;
|
|
570
|
+
}>, "many">;
|
|
571
|
+
}, "strict", z.ZodTypeAny, {
|
|
572
|
+
evalId: string;
|
|
573
|
+
hits: {
|
|
574
|
+
match: string;
|
|
575
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
576
|
+
excerpt: string;
|
|
577
|
+
}[];
|
|
578
|
+
}, {
|
|
579
|
+
evalId: string;
|
|
580
|
+
hits: {
|
|
581
|
+
match: string;
|
|
582
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
583
|
+
excerpt: string;
|
|
584
|
+
}[];
|
|
585
|
+
}>, "many">;
|
|
586
|
+
}, "strict", z.ZodTypeAny, {
|
|
587
|
+
summary: string;
|
|
588
|
+
contaminated: boolean;
|
|
589
|
+
degraded: {
|
|
590
|
+
detail: string;
|
|
591
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
592
|
+
evalId?: string | undefined;
|
|
593
|
+
}[];
|
|
594
|
+
comparable: boolean;
|
|
595
|
+
skew: {
|
|
596
|
+
evalId: string;
|
|
597
|
+
withTotal: number;
|
|
598
|
+
withoutTotal: number;
|
|
599
|
+
}[];
|
|
600
|
+
controlArmFailures: {
|
|
601
|
+
detail: string;
|
|
602
|
+
evalId: string;
|
|
603
|
+
}[];
|
|
604
|
+
signals: ("harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content")[];
|
|
605
|
+
findings: {
|
|
606
|
+
evalId: string;
|
|
607
|
+
hits: {
|
|
608
|
+
match: string;
|
|
609
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
610
|
+
excerpt: string;
|
|
611
|
+
}[];
|
|
612
|
+
}[];
|
|
613
|
+
}, {
|
|
614
|
+
summary: string;
|
|
615
|
+
contaminated: boolean;
|
|
616
|
+
degraded: {
|
|
617
|
+
detail: string;
|
|
618
|
+
reason: "transcript-unparsed" | "cwd-unknown" | "cwd-untracked" | "transcript-malformed" | "glob-unexpanded";
|
|
619
|
+
evalId?: string | undefined;
|
|
620
|
+
}[];
|
|
621
|
+
comparable: boolean;
|
|
622
|
+
skew: {
|
|
623
|
+
evalId: string;
|
|
624
|
+
withTotal: number;
|
|
625
|
+
withoutTotal: number;
|
|
626
|
+
}[];
|
|
627
|
+
controlArmFailures: {
|
|
628
|
+
detail: string;
|
|
629
|
+
evalId: string;
|
|
630
|
+
}[];
|
|
631
|
+
signals: ("harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content")[];
|
|
632
|
+
findings: {
|
|
633
|
+
evalId: string;
|
|
634
|
+
hits: {
|
|
635
|
+
match: string;
|
|
636
|
+
kind: "harness-path" | "declared-executable" | "sibling-arm" | "vat-private-dir" | "skill-content";
|
|
637
|
+
excerpt: string;
|
|
638
|
+
}[];
|
|
639
|
+
}[];
|
|
640
|
+
}>;
|
|
641
|
+
export type BaselineIntegrity = z.infer<typeof BaselineIntegritySchema>;
|
|
642
|
+
/**
|
|
643
|
+
* One needle plus the string that is safe to REPORT when it matches.
|
|
644
|
+
*
|
|
645
|
+
* The two are separate because they answer different questions and a single string
|
|
646
|
+
* cannot do both: the needle has to be the arm's own spelling or it matches nothing,
|
|
647
|
+
* while `match` is quoted into bug reports and must name no person. Computing the
|
|
648
|
+
* report at CONSTRUCTION is also the only place the provenance is known — a
|
|
649
|
+
* suffix needle looks like a complete two-segment path by the time a hit is built,
|
|
650
|
+
* and that is exactly how it came to be emitted with no truncation marker.
|
|
651
|
+
*/
|
|
652
|
+
export interface PathNeedle {
|
|
653
|
+
/** Normalized; matched at a path boundary. */
|
|
654
|
+
needle: string;
|
|
655
|
+
/** Redacted, and marked `…/` whenever it is not the whole thing it came from. */
|
|
656
|
+
match: string;
|
|
657
|
+
}
|
|
658
|
+
export interface DetectBaselineContaminationInput {
|
|
659
|
+
/** The WITHOUT arm's captured stream-json transcript. */
|
|
660
|
+
transcript: string;
|
|
661
|
+
/**
|
|
662
|
+
* vat's harness root. Any mention of it in the control arm's transcript means
|
|
663
|
+
* the arm reached into vat's own staged trees — which, now that neither the
|
|
664
|
+
* prompt nor the cwd points there, it could only do by going looking.
|
|
665
|
+
*/
|
|
666
|
+
harnessRoot: string;
|
|
667
|
+
/**
|
|
668
|
+
* The skill's declared executables as SKILL-RELATIVE PATHS, exactly as the
|
|
669
|
+
* packaging config spells them (`scripts/csvsum.py`), from
|
|
670
|
+
* `deriveDeclaredExecutableNames`.
|
|
671
|
+
*
|
|
672
|
+
* ⚠️ PATHS, NOT NAMES, and the difference is the whole signal — see
|
|
673
|
+
* {@link reachIsDeclaredExecutable}. This field used to carry the
|
|
674
|
+
* extension-stripped basename, which convicted a clean control arm for reading
|
|
675
|
+
* its own `/tmp/summary.txt`.
|
|
676
|
+
*/
|
|
677
|
+
executablePaths?: readonly string[];
|
|
678
|
+
/**
|
|
679
|
+
* ⛔ NOT A FIELD — A TRIPWIRE. `executableNames` was the previous spelling and
|
|
680
|
+
* carried extension-stripped basenames; it is gone, and `never` is here so that a
|
|
681
|
+
* caller still passing it FAILS TO COMPILE.
|
|
682
|
+
*
|
|
683
|
+
* Declared because the renaming alone was silent. The harness builds this object
|
|
684
|
+
* with conditional SPREADS (`...(x === undefined ? {} : { executableNames: … })`),
|
|
685
|
+
* and a spread is not a fresh object literal, so TypeScript applies no
|
|
686
|
+
* excess-property check: dropping the old name simply left the property unread and
|
|
687
|
+
* the `declared-executable` signal unarmed on every run — reported as
|
|
688
|
+
* `contaminated: false` by a detector that was no longer looking. That is precisely
|
|
689
|
+
* the "silently clean" failure this module exists to prevent, so the rename gets a
|
|
690
|
+
* compile error rather than a comment.
|
|
691
|
+
*/
|
|
692
|
+
executableNames?: never;
|
|
693
|
+
/**
|
|
694
|
+
* The control arm's OWN workspace root, so a path under it is recognised as the
|
|
695
|
+
* arm's own scratch file rather than a reach. The executor prompt states the
|
|
696
|
+
* working directory absolutely, so the arm reuses that absolute path routinely;
|
|
697
|
+
* without this every such mention would read as an escape.
|
|
698
|
+
*
|
|
699
|
+
* Absent → nothing is suppressed, which is the conservative direction for a
|
|
700
|
+
* caller that has not threaded it: the check gets noisier, never blinder.
|
|
701
|
+
*/
|
|
702
|
+
armWorkspaceDir?: string;
|
|
703
|
+
/**
|
|
704
|
+
* The arm's actual STARTING working directory — the per-eval workspace
|
|
705
|
+
* `<armWorkspaceDir>/<evalId>`, which is what the executor is spawned in.
|
|
706
|
+
*
|
|
707
|
+
* Distinct from `armWorkspaceDir` on purpose, and the difference is one
|
|
708
|
+
* directory level that decides every relative reach in the transcript. The cwd
|
|
709
|
+
* walk anchors here; containment ("is this the arm's own file?") is judged
|
|
710
|
+
* against `armWorkspaceDir`, which covers the arm's whole tree. Anchoring the
|
|
711
|
+
* walk one level too high would resolve `cd ../../..` to the temp dir's PARENT,
|
|
712
|
+
* and every subsequent relative path — the entire class of reach this scan
|
|
713
|
+
* exists to catch — would land somewhere no needle can see, reporting clean.
|
|
714
|
+
*
|
|
715
|
+
* Defaults to `armWorkspaceDir` when absent, which is the closest anchor
|
|
716
|
+
* available and still better than none.
|
|
717
|
+
*/
|
|
718
|
+
armCwd?: string;
|
|
719
|
+
/**
|
|
720
|
+
* Verbatim lines lifted from the staged SKILL.md by {@link skillContentNeedles}
|
|
721
|
+
* — the ONLY signal that sees an ambient copy of an instruction-only skill,
|
|
722
|
+
* which ships nothing this module could match by name or path.
|
|
723
|
+
*/
|
|
724
|
+
skillContentNeedles?: readonly string[];
|
|
725
|
+
/**
|
|
726
|
+
* The OTHER arm's workspace directory — for a skill-absent eval, the treatment
|
|
727
|
+
* arm's live working directory.
|
|
728
|
+
*
|
|
729
|
+
* Not a path into vat's staged copies, so no harness needle can see it: the two
|
|
730
|
+
* arm dirs are siblings under one workspaces root, and `existsSync` on
|
|
731
|
+
* `../<sibling>/<id>/fixtures/input.md` from the control arm returns TRUE. Round 3
|
|
732
|
+
* gave each arm its own directory, which stopped them writing over each other; it
|
|
733
|
+
* did not stop one READING the other, and reading the treatment arm's output
|
|
734
|
+
* mid-run lets the control score like the treatment while producing a transcript
|
|
735
|
+
* with no harness path in it at all.
|
|
736
|
+
*
|
|
737
|
+
* Prevention is now the opaque per-arm token (the sibling's directory name is
|
|
738
|
+
* unguessable and no longer spells `with`), so this is the detection half: if the
|
|
739
|
+
* arm names it anyway — because it enumerated `..` — say so.
|
|
740
|
+
*
|
|
741
|
+
* The ABSOLUTE path to that directory; the needles are derived from it.
|
|
742
|
+
*/
|
|
743
|
+
siblingArmDir?: string;
|
|
744
|
+
/**
|
|
745
|
+
* VAT's private tmp dirs for this run — the held eval suite (the answer key) and
|
|
746
|
+
* the grader dir (the integrity nonce). Absolute paths; see
|
|
747
|
+
* {@link vatPrivateDirNeedles} for why these need a detector of their own and
|
|
748
|
+
* why the useful needle is a name PREFIX rather than the token.
|
|
749
|
+
*
|
|
750
|
+
* Never pass the workspaces root here: it is the arm's own legitimate cwd, and a
|
|
751
|
+
* prefix needle built from it fires on every clean run.
|
|
752
|
+
*/
|
|
753
|
+
vatPrivateDirs?: readonly (string | undefined)[];
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* The needles that mean "this transcript named the OTHER arm's workspace".
|
|
757
|
+
*
|
|
758
|
+
* Two: the full path, and the bare per-arm token. The token is 16 hex characters
|
|
759
|
+
* minted per run, so a boundary-anchored match on it is essentially free of false
|
|
760
|
+
* positives — the arm's own token is different, and nothing else on the machine
|
|
761
|
+
* spells it.
|
|
762
|
+
*
|
|
763
|
+
* The LEADING `/` requirement (via {@link needsLeadingBoundary}) is what keeps
|
|
764
|
+
* `ls ..` honest: listing the parent prints the sibling's directory name as a bare
|
|
765
|
+
* basename on its own line, which is "I looked around", not "I read the other arm".
|
|
766
|
+
* Every genuine reach — `cat ../<token>/e1/out.md`, an absolute path, `find ..
|
|
767
|
+
* -name '*.md'` printing `../<token>/…` — carries the slash.
|
|
768
|
+
*/
|
|
769
|
+
export declare function siblingArmNeedles(siblingArmDir: string): PathNeedle[];
|
|
770
|
+
/**
|
|
771
|
+
* The needles that mean "this transcript reached a dir only VAT is supposed to
|
|
772
|
+
* see" — the held eval suite (`<tmp>/vat-skill-evals-<token>`, which holds the
|
|
773
|
+
* `expected_output` ANSWER KEY for a fetched-artifact subject) and the grader dir
|
|
774
|
+
* (`<tmp>/vat-skill-grade-<token>`, which holds the run's integrity nonce).
|
|
775
|
+
*
|
|
776
|
+
* These are a FOURTH reachable class the harness-path detector cannot see: they
|
|
777
|
+
* are siblings of the arm's cwd under the OS temp dir, contain no harness path,
|
|
778
|
+
* and are two hops (`ls ../..`) or one `$TMPDIR` expansion away. Worse than
|
|
779
|
+
* reaching the skill — reaching the answer key inflates BOTH arms, so it cannot
|
|
780
|
+
* show up as a shrunken delta at all.
|
|
781
|
+
*
|
|
782
|
+
* Three needles, longest-first: the full path, the whole dir NAME, and the
|
|
783
|
+
* dir-name prefix with the per-run token stripped (`vat-skill-evals-`).
|
|
784
|
+
*
|
|
785
|
+
* The prefix is what catches the reach an arm can actually type. The token is 16
|
|
786
|
+
* random hex it has no way to know, so it globs — `cat $TMPDIR/vat-skill-evals-*/
|
|
787
|
+
* evals.json` — and that string contains neither the full path nor the name. (One
|
|
788
|
+
* `*` defeating a needle that embeds a random key is the same trap that once
|
|
789
|
+
* made the whole harness-suffix scheme inert.)
|
|
790
|
+
*
|
|
791
|
+
* The prefix needle carries no `/`, so {@link needsLeadingBoundary} requires one
|
|
792
|
+
* before it — without that, `ls $TMPDIR` printing `vat-skill-evals-abc` on its own
|
|
793
|
+
* line stamps a clean run contaminated. That rule is also why this must NEVER be
|
|
794
|
+
* built for the workspaces root: `vat-skill-test-ws-` is the arm's own legitimate
|
|
795
|
+
* cwd and its absolute path is in the arm's own prompt, so the needle would fire
|
|
796
|
+
* on every single clean run.
|
|
797
|
+
*/
|
|
798
|
+
export declare function vatPrivateDirNeedles(dir: string): PathNeedle[];
|
|
799
|
+
/**
|
|
800
|
+
* Verbatim lines from the skill body that mean "this transcript SAW the skill".
|
|
801
|
+
*
|
|
802
|
+
* THE GAP THIS FILLS. Every other signal in this module needs the arm to name a
|
|
803
|
+
* PATH or an executable. An instruction-only skill — the common case — ships no
|
|
804
|
+
* executable, and the two ambient classes vat cannot remove (the adopter's own
|
|
805
|
+
* repo/build output, the installed plugin cache) produce no harness path. So
|
|
806
|
+
* `grep -rl "<phrase>" .` → `Read` → answer was completely invisible, and
|
|
807
|
+
* `contaminated: false` meant "saw no evidence" while reading as "verified none".
|
|
808
|
+
* Content is the only thing that survives all three of those steps: a `grep` hit
|
|
809
|
+
* line, a `Read` echo, and an answer that quotes the skill with no path attached.
|
|
810
|
+
*
|
|
811
|
+
* Selection is deterministic and needs no corpus: LONGEST first, because length is
|
|
812
|
+
* rarity for free. A candidate must be plain body prose —
|
|
813
|
+
*
|
|
814
|
+
* - frontmatter is skipped (the `name:` is the skill's own identifier and shows
|
|
815
|
+
* up in ordinary config and prose; it is not distinctive enough to accuse
|
|
816
|
+
* anyone with);
|
|
817
|
+
* - fenced code is skipped, since a code block is often copied from a shared
|
|
818
|
+
* upstream and is the same in every skill that wraps the same tool;
|
|
819
|
+
* - headings, table rows and block quotes are skipped as structural.
|
|
820
|
+
*
|
|
821
|
+
* 📌 A rule that lines carrying `"` or `\` are unusable has been REMOVED. It was
|
|
822
|
+
* true while the haystack was the RAW stream-json, where such characters are
|
|
823
|
+
* escaped and {@link normalizeForMatch} then rewrites the escape, so the needle
|
|
824
|
+
* could never match its own text. The content signal now reads
|
|
825
|
+
* {@link contentHaystackFor} — the DECODED assistant text, tool inputs and tool
|
|
826
|
+
* results — where there is no escaping to defeat, so those lines are usable
|
|
827
|
+
* needles again, and dropping them was silently costing quote-heavy skills their
|
|
828
|
+
* only signal. Re-add the rule only if the content haystack goes back to raw JSON.
|
|
829
|
+
*
|
|
830
|
+
* `excludedText` is every channel through which VAT ITSELF hands the arm text:
|
|
831
|
+
* the run's eval prompts, its `expected_output`, its expectations, AND the
|
|
832
|
+
* contents of the input `files` fixtures VAT stages into the arm's workspace and
|
|
833
|
+
* tells it to work on. This is not optional care in any of the four. An adopter
|
|
834
|
+
* who quotes a sentence of their SKILL.md in a prompt — or ships a fixture that
|
|
835
|
+
* does — would otherwise stamp EVERY run contaminated, including the arm that
|
|
836
|
+
* merely read the input it was given, and the attached triage instruction
|
|
837
|
+
* ("uninstall the ambient copy of the plugin") would send the operator hunting
|
|
838
|
+
* something that does not exist. The skill's own words reaching the arm through
|
|
839
|
+
* vat are not the arm reaching the skill.
|
|
840
|
+
*/
|
|
841
|
+
export declare function skillContentNeedles(skillMarkdown: string, excludedText?: string): string[];
|
|
842
|
+
/**
|
|
843
|
+
* What a tool call DOES with the paths in its input.
|
|
844
|
+
*
|
|
845
|
+
* `write` is the one that is not about reading at all, and it exists because its
|
|
846
|
+
* absence was a false-positive engine. {@link segmentReaches} used to stamp every
|
|
847
|
+
* path word in a segment with the segment's single intent, so the DESTINATION of a
|
|
848
|
+
* redirect or a copy inherited `retrieval` from the command that produced the
|
|
849
|
+
* bytes — and declared executable names are basenames minus extension, i.e.
|
|
850
|
+
* `summary`, `report`, `index`, `run`. `sort data.csv > /tmp/summary.txt`,
|
|
851
|
+
* `cp notes.md /tmp/summary.md` and `cat parts/*.json > /tmp/index.json` each
|
|
852
|
+
* reported `declared-executable`: the arm was accused of RUNNING the skill's tool
|
|
853
|
+
* on the strength of having written a file with an ordinary name. A write target
|
|
854
|
+
* is not evidence that anything ran.
|
|
855
|
+
*/
|
|
856
|
+
export type ToolIntent = 'retrieval' | 'enumeration' | 'write' | 'other';
|
|
857
|
+
/** What a scan of one skill-absent transcript produced. */
|
|
858
|
+
export interface BaselineContaminationScan {
|
|
859
|
+
/**
|
|
860
|
+
* Stable order: harness path, sibling arm, vat private dirs, skill content,
|
|
861
|
+
* then declared executables in declared order. At most one per group.
|
|
862
|
+
*/
|
|
863
|
+
hits: BaselineContaminationHit[];
|
|
864
|
+
/** Present only when the structured scan could not run — see the schema. */
|
|
865
|
+
degraded?: BaselineScanDegradation;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Scan the skill-absent arm's transcript for proof it reached the skill.
|
|
869
|
+
*
|
|
870
|
+
* Pure + unit-testable. See the module-level block above {@link ToolIntent} for
|
|
871
|
+
* the architecture and for the two demonstrated defects that forced it.
|
|
872
|
+
*/
|
|
873
|
+
export declare function detectBaselineContamination(input: DetectBaselineContaminationInput): BaselineContaminationScan;
|
|
874
|
+
/**
|
|
875
|
+
* Which contamination detectors are ARMED for a run, derived from the SAME input
|
|
876
|
+
* object handed to {@link detectBaselineContamination} so the two cannot disagree
|
|
877
|
+
* about what was checked. A signal counts as armed only when it has a non-empty
|
|
878
|
+
* needle set — a dir that was never threaded through produces no needles and must
|
|
879
|
+
* not be reported as "checked".
|
|
880
|
+
*/
|
|
881
|
+
export declare function activeContaminationSignals(input: DetectBaselineContaminationInput): ContaminationSignal[];
|
|
882
|
+
/**
|
|
883
|
+
* Everything the run-level integrity block is assembled from.
|
|
884
|
+
*
|
|
885
|
+
* An OBJECT, and every field REQUIRED — no optionals, no defaults. Each of these
|
|
886
|
+
* five is a coverage claim, and every one of them is believed in the same
|
|
887
|
+
* direction: an absent `signals` reads as "checked by nothing", an absent
|
|
888
|
+
* `degraded` reads as "every scan was structured", an absent `controlArmFailures`
|
|
889
|
+
* reads as "both arms ran". A default on any of them lets a caller overclaim
|
|
890
|
+
* silently; a required field is a compile error the day a new call site appears.
|
|
891
|
+
*
|
|
892
|
+
* `degraded` was defaulted to `[]` for exactly one release and the harness never
|
|
893
|
+
* passed it, so `baseline.json` read identically whether the contamination scan
|
|
894
|
+
* walked the transcript or fell back blind. That is the whole reason the rule is
|
|
895
|
+
* written down here rather than assumed.
|
|
896
|
+
*/
|
|
897
|
+
export interface SummarizeBaselineIntegrityInput {
|
|
898
|
+
/** Per-eval contamination findings; empty on a clean run. */
|
|
899
|
+
findings: readonly BaselineContamination[];
|
|
900
|
+
/** Which detectors were ARMED for this run — see {@link activeContaminationSignals}. */
|
|
901
|
+
signals: readonly ContaminationSignal[];
|
|
902
|
+
/** Evals whose two arms are not comparable — see {@link armExpectationSkew}. */
|
|
903
|
+
skew: readonly BaselineArmSkew[];
|
|
904
|
+
/** Evals whose scan fell back to flat matching — see {@link BaselineScanDegradationSchema}. */
|
|
905
|
+
degraded: readonly BaselineScanDegradation[];
|
|
906
|
+
/** Control-arm evals that never produced a grade — see {@link BaselineControlArmFailureSchema}. */
|
|
907
|
+
controlArmFailures: readonly BaselineControlArmFailure[];
|
|
908
|
+
/**
|
|
909
|
+
* How many CONTROL-ARM evals were actually scanned for contamination.
|
|
910
|
+
*
|
|
911
|
+
* ⛔ REQUIRED, and for the same reason the other four are: every one of these is a
|
|
912
|
+
* coverage claim, and a default lets a caller overclaim silently in the direction
|
|
913
|
+
* where the claim gets believed. Confirmed on a real run where both control arms
|
|
914
|
+
* died on an executor timeout, so ZERO transcripts were scanned and `baseline.json`
|
|
915
|
+
* still opened with "No skill-absent eval was observed reaching the skill. The A/B
|
|
916
|
+
* delta is interpretable as instruction lift" — two false sentences, with the
|
|
917
|
+
* correction arriving third. A count of zero means nothing was looked at, which is
|
|
918
|
+
* not the same finding as looking and seeing nothing, and
|
|
919
|
+
* {@link summarizeBaselineIntegrity} now leads with the absence rather than
|
|
920
|
+
* burying it.
|
|
921
|
+
*
|
|
922
|
+
* Pass the number of control-arm evals whose transcript reached
|
|
923
|
+
* {@link detectBaselineContamination}, NOT the number of evals declared.
|
|
924
|
+
*/
|
|
925
|
+
observedEvals: number;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Assemble the run-level integrity block from every WITHOUT-arm eval's findings.
|
|
929
|
+
* `findings` is empty on a clean run — the block is still emitted, with
|
|
930
|
+
* `contaminated: false`, so "checked and clean" is distinguishable from
|
|
931
|
+
* "never checked".
|
|
932
|
+
*
|
|
933
|
+
* `comparable` is derived from `skew` ALONE, deliberately, even though a
|
|
934
|
+
* `controlArmFailures` entry also means the arms cannot be subtracted. `skew` is
|
|
935
|
+
* the single authority `computeBaselineDelta` consults, and a second input to
|
|
936
|
+
* `comparable` here would let this block say "not comparable" while the delta block
|
|
937
|
+
* beside it printed a number — the exact two-blocks-disagreeing failure this module
|
|
938
|
+
* keeps warning about. It costs nothing: a control-arm failure removes that eval's
|
|
939
|
+
* control grade entirely, and {@link armExpectationSkew} reports a one-armed eval as
|
|
940
|
+
* skew by construction, so the two always agree. See the run-harness test that pins
|
|
941
|
+
* that derivation end to end rather than trusting this sentence.
|
|
942
|
+
*/
|
|
943
|
+
export declare function summarizeBaselineIntegrity(input: SummarizeBaselineIntegrityInput): BaselineIntegrity;
|
|
944
|
+
//# sourceMappingURL=baseline-integrity.d.ts.map
|