@vibe-agent-toolkit/agent-skills 0.1.39-rc.9 → 0.1.39
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 +61 -0
- package/dist/files-config.d.ts.map +1 -1
- package/dist/files-config.js +262 -6
- package/dist/files-config.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin-distribution-layout.d.ts +70 -0
- package/dist/plugin-distribution-layout.d.ts.map +1 -0
- package/dist/plugin-distribution-layout.js +97 -0
- package/dist/plugin-distribution-layout.js.map +1 -0
- package/dist/skill-packager.d.ts +43 -1
- package/dist/skill-packager.d.ts.map +1 -1
- package/dist/skill-packager.js +131 -53
- package/dist/skill-packager.js.map +1 -1
- package/dist/skill-source/git-clone.d.ts +2 -2
- package/dist/skill-source/git-clone.d.ts.map +1 -1
- package/dist/skill-source/git-clone.js +33 -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 +23 -8
- package/dist/skill-source/sources/url-source.js.map +1 -1
- package/dist/skill-source/sources/workspace-source.d.ts.map +1 -1
- package/dist/skill-source/sources/workspace-source.js +16 -8
- package/dist/skill-source/sources/workspace-source.js.map +1 -1
- package/dist/skill-source/stage.js +6 -2
- package/dist/skill-source/stage.js.map +1 -1
- package/dist/skill-test/configure-writer.d.ts +19 -11
- package/dist/skill-test/configure-writer.d.ts.map +1 -1
- package/dist/skill-test/configure-writer.js +23 -15
- package/dist/skill-test/configure-writer.js.map +1 -1
- package/dist/skill-test/declared-env.d.ts +72 -0
- package/dist/skill-test/declared-env.d.ts.map +1 -0
- package/dist/skill-test/declared-env.js +85 -0
- package/dist/skill-test/declared-env.js.map +1 -0
- package/dist/skill-test/eval-inputs.d.ts +159 -0
- package/dist/skill-test/eval-inputs.d.ts.map +1 -0
- package/dist/skill-test/eval-inputs.js +158 -0
- package/dist/skill-test/eval-inputs.js.map +1 -0
- package/dist/skill-test/evals-template.d.ts.map +1 -1
- package/dist/skill-test/evals-template.js +27 -10
- package/dist/skill-test/evals-template.js.map +1 -1
- package/dist/skill-test/exit-codes.d.ts +43 -5
- package/dist/skill-test/exit-codes.d.ts.map +1 -1
- package/dist/skill-test/exit-codes.js +60 -9
- package/dist/skill-test/exit-codes.js.map +1 -1
- package/dist/skill-test/experimenter-prompt.d.ts +22 -0
- package/dist/skill-test/experimenter-prompt.d.ts.map +1 -1
- package/dist/skill-test/experimenter-prompt.js +38 -2
- package/dist/skill-test/experimenter-prompt.js.map +1 -1
- package/dist/skill-test/grading-adapter.d.ts +42 -0
- package/dist/skill-test/grading-adapter.d.ts.map +1 -1
- package/dist/skill-test/grading-adapter.js +63 -1
- package/dist/skill-test/grading-adapter.js.map +1 -1
- package/dist/skill-test/grading-schema.d.ts +73 -5
- package/dist/skill-test/grading-schema.d.ts.map +1 -1
- package/dist/skill-test/grading-schema.js +20 -5
- package/dist/skill-test/grading-schema.js.map +1 -1
- package/dist/skill-test/harness-location.d.ts +22 -4
- package/dist/skill-test/harness-location.d.ts.map +1 -1
- package/dist/skill-test/harness-location.js +83 -18
- package/dist/skill-test/harness-location.js.map +1 -1
- package/dist/skill-test/index.d.ts +7 -4
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +7 -4
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/lock.d.ts +23 -0
- package/dist/skill-test/lock.d.ts.map +1 -1
- package/dist/skill-test/lock.js +34 -0
- package/dist/skill-test/lock.js.map +1 -1
- package/dist/skill-test/preflight.d.ts.map +1 -1
- package/dist/skill-test/preflight.js +1 -2
- package/dist/skill-test/preflight.js.map +1 -1
- package/dist/skill-test/run-harness.d.ts +193 -19
- package/dist/skill-test/run-harness.d.ts.map +1 -1
- package/dist/skill-test/run-harness.js +366 -76
- package/dist/skill-test/run-harness.js.map +1 -1
- package/dist/skill-test/staging.d.ts.map +1 -1
- package/dist/skill-test/staging.js +31 -15
- package/dist/skill-test/staging.js.map +1 -1
- package/dist/skill-test/vendor-manifest.d.ts +3 -3
- package/dist/skill-test/vendor-manifest.d.ts.map +1 -1
- package/dist/skill-test/vendor-manifest.js +13 -3
- package/dist/skill-test/vendor-manifest.js.map +1 -1
- package/dist/validators/packaging-validator.d.ts.map +1 -1
- package/dist/validators/packaging-validator.js +134 -1
- package/dist/validators/packaging-validator.js.map +1 -1
- package/dist/validators/skill-validator.d.ts.map +1 -1
- package/dist/validators/skill-validator.js +18 -14
- package/dist/validators/skill-validator.js.map +1 -1
- package/dist/walk-link-graph.d.ts.map +1 -1
- package/dist/walk-link-graph.js +26 -1
- package/dist/walk-link-graph.js.map +1 -1
- package/package.json +6 -5
|
@@ -6,6 +6,7 @@ export interface BuildPromptOptions {
|
|
|
6
6
|
evalsPath: string;
|
|
7
7
|
gradingOut: string;
|
|
8
8
|
frictionOut: string;
|
|
9
|
+
workspacesRoot: string;
|
|
9
10
|
baseline: boolean;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
@@ -16,5 +17,26 @@ export interface BuildPromptOptions {
|
|
|
16
17
|
*/
|
|
17
18
|
export declare const DEFAULT_EXPERIMENTER_PROMPT: string;
|
|
18
19
|
export declare function buildExperimenterPrompt(opts: BuildPromptOptions): string;
|
|
20
|
+
/**
|
|
21
|
+
* Sentinel the persisted (audit) copy of the prompt shows in place of the real
|
|
22
|
+
* nonce, so the on-disk artifact never leaks the secret to skill code.
|
|
23
|
+
*/
|
|
24
|
+
export declare const REDACTED_NONCE_PLACEHOLDER = "<run-nonce redacted>";
|
|
25
|
+
/**
|
|
26
|
+
* Append the per-run integrity-nonce directive to the effective prompt. The
|
|
27
|
+
* experimenter must copy `nonce` verbatim into grading.json's top-level
|
|
28
|
+
* `runNonce` field; the harness then rejects any grading.json whose nonce is
|
|
29
|
+
* absent or wrong (see run-harness.ts). This is defense-in-depth against
|
|
30
|
+
* untrusted skill code forging a passing grading.json in the shared sandbox —
|
|
31
|
+
* without the secret nonce (delivered only via stdin, never written to disk) a
|
|
32
|
+
* forged grading cannot be authenticated.
|
|
33
|
+
*
|
|
34
|
+
* Appended AFTER assertPromptInvariants and AFTER any user `experimenterPrompt`
|
|
35
|
+
* override, so the nonce requirement is ALWAYS enforced and a committed config
|
|
36
|
+
* cannot opt out of it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function appendIntegrityNonceDirective(prompt: string, nonce: string): string;
|
|
39
|
+
/** Redact the per-run nonce from a prompt for the on-disk audit copy. */
|
|
40
|
+
export declare function redactNonce(prompt: string, nonce: string): string;
|
|
19
41
|
export declare function assertPromptInvariants(prompt: string): void;
|
|
20
42
|
//# sourceMappingURL=experimenter-prompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experimenter-prompt.d.ts","sourceRoot":"","sources":["../../src/skill-test/experimenter-prompt.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"experimenter-prompt.d.ts","sourceRoot":"","sources":["../../src/skill-test/experimenter-prompt.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,QA2B5B,CAAC;AAKb,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAQxE;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQnF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjE;AAmBD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAI3D"}
|
|
@@ -15,11 +15,15 @@ export const DEFAULT_EXPERIMENTER_PROMPT = [
|
|
|
15
15
|
'',
|
|
16
16
|
'For each eval in {{EVALS_PATH}}:',
|
|
17
17
|
' 1. Dispatch ONE executor subagent. Tell it ONLY the task prompt and the staged subject path {{SUBJECT_PATH}}.',
|
|
18
|
-
'
|
|
18
|
+
' When the eval declares input `files`, ALSO tell the executor its working directory is {{WORKSPACES_ROOT}}/<id>',
|
|
19
|
+
' (substitute the eval\'s own id) — the project it should operate on. Never tell the executor it is being tested.',
|
|
19
20
|
' 2. Grade the executor output against the eval\'s `expectations` using skill-creator\'s grader.md rubric.',
|
|
21
|
+
' If the eval includes an `expected_output`, treat it as the author\'s prose description of a',
|
|
22
|
+
' correct result and use it as CONTEXT when judging — but the pass/fail verdict is still decided',
|
|
23
|
+
' per `expectations` entry; `expected_output` informs your judgment, it is not itself a checklist item.',
|
|
20
24
|
' 3. Append each graded expectation to the SINGLE top-level `expectations` array in {{GRADING_OUT}} IMMEDIATELY',
|
|
21
25
|
' (incremental flush — a mid-run kill must leave partial results).',
|
|
22
|
-
' 4. Record any packaging-fidelity friction to {{FRICTION_OUT}} using the vat friction schema.',
|
|
26
|
+
' 4. Record any packaging-fidelity friction to {{FRICTION_OUT}} using the vat friction.json schema.',
|
|
23
27
|
' If a file referenced by the skill is absent from the staged tree, record a `missing-bundled-file` friction entry.',
|
|
24
28
|
'',
|
|
25
29
|
'{{GRADING_OUT}} MUST be ONE flat JSON object in skill-creator\'s grading.json shape (references/schemas.md):',
|
|
@@ -39,10 +43,42 @@ export function buildExperimenterPrompt(opts) {
|
|
|
39
43
|
return DEFAULT_EXPERIMENTER_PROMPT
|
|
40
44
|
.replace('{{EVALS_PATH}}', opts.evalsPath)
|
|
41
45
|
.replace('{{SUBJECT_PATH}}', opts.subjectPath)
|
|
46
|
+
.replaceAll('{{WORKSPACES_ROOT}}', opts.workspacesRoot)
|
|
42
47
|
.replaceAll('{{GRADING_OUT}}', opts.gradingOut)
|
|
43
48
|
.replace('{{FRICTION_OUT}}', opts.frictionOut)
|
|
44
49
|
.replace('{{BASELINE_BLOCK}}', opts.baseline ? BASELINE_BLOCK : '');
|
|
45
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Sentinel the persisted (audit) copy of the prompt shows in place of the real
|
|
53
|
+
* nonce, so the on-disk artifact never leaks the secret to skill code.
|
|
54
|
+
*/
|
|
55
|
+
export const REDACTED_NONCE_PLACEHOLDER = '<run-nonce redacted>';
|
|
56
|
+
/**
|
|
57
|
+
* Append the per-run integrity-nonce directive to the effective prompt. The
|
|
58
|
+
* experimenter must copy `nonce` verbatim into grading.json's top-level
|
|
59
|
+
* `runNonce` field; the harness then rejects any grading.json whose nonce is
|
|
60
|
+
* absent or wrong (see run-harness.ts). This is defense-in-depth against
|
|
61
|
+
* untrusted skill code forging a passing grading.json in the shared sandbox —
|
|
62
|
+
* without the secret nonce (delivered only via stdin, never written to disk) a
|
|
63
|
+
* forged grading cannot be authenticated.
|
|
64
|
+
*
|
|
65
|
+
* Appended AFTER assertPromptInvariants and AFTER any user `experimenterPrompt`
|
|
66
|
+
* override, so the nonce requirement is ALWAYS enforced and a committed config
|
|
67
|
+
* cannot opt out of it.
|
|
68
|
+
*/
|
|
69
|
+
export function appendIntegrityNonceDirective(prompt, nonce) {
|
|
70
|
+
return [
|
|
71
|
+
prompt,
|
|
72
|
+
'',
|
|
73
|
+
'INTEGRITY (required, do this LAST): the grading.json you write MUST include a',
|
|
74
|
+
`top-level string field "runNonce" whose value is EXACTLY: ${nonce}`,
|
|
75
|
+
'Copy it verbatim. vat rejects a grading.json whose runNonce is missing or wrong.',
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
|
78
|
+
/** Redact the per-run nonce from a prompt for the on-disk audit copy. */
|
|
79
|
+
export function redactNonce(prompt, nonce) {
|
|
80
|
+
return prompt.replaceAll(nonce, REDACTED_NONCE_PLACEHOLDER);
|
|
81
|
+
}
|
|
46
82
|
const REQUIRED_PATTERNS = [
|
|
47
83
|
{ test: /\bSTOP\b/, label: 'must instruct the experimenter to STOP' },
|
|
48
84
|
{ test: /grading\.json|\{\{GRADING_OUT\}\}/i, label: 'must write grading.json' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experimenter-prompt.js","sourceRoot":"","sources":["../../src/skill-test/experimenter-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,2CAA2C,OAAO,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"experimenter-prompt.js","sourceRoot":"","sources":["../../src/skill-test/experimenter-prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,48 @@ export interface NormalizedGrading {
|
|
|
17
17
|
passed: boolean;
|
|
18
18
|
evidence?: string;
|
|
19
19
|
}[];
|
|
20
|
+
/** Per-run integrity nonce the experimenter copied from its prompt (if present). */
|
|
21
|
+
runNonce?: string;
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when grading.json's per-run integrity nonce is absent or does not match
|
|
25
|
+
* the nonce the harness stamped into the experimenter prompt for THIS run. A
|
|
26
|
+
* missing/wrong nonce means the grading was not produced by the experimenter we
|
|
27
|
+
* prompted — most likely a grading.json forged or left behind by untrusted skill
|
|
28
|
+
* code in the shared sandbox — so the verdict cannot be trusted.
|
|
29
|
+
*/
|
|
30
|
+
export declare class GradingNonceError extends Error {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Assert grading.json carries the exact per-run nonce the harness expected.
|
|
35
|
+
* Called before the verdict is trusted. Throws {@link GradingNonceError} on a
|
|
36
|
+
* missing or mismatched nonce.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertGradingNonce(actual: string | undefined, expected: string): void;
|
|
21
39
|
export declare function parseGradingJson(raw: unknown): NormalizedGrading;
|
|
40
|
+
/** The authoritative eval verdict, recomputed from per-expectation `passed` flags. */
|
|
41
|
+
export interface GradingVerdict {
|
|
42
|
+
passed: number;
|
|
43
|
+
total: number;
|
|
44
|
+
allPassed: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reconcile the grader's self-reported `summary` against the authoritative
|
|
48
|
+
* per-expectation `passed` flags and return the recomputed verdict.
|
|
49
|
+
*
|
|
50
|
+
* The grader emits BOTH an aggregate `summary.{passed,total}` AND one
|
|
51
|
+
* `expectations[]` entry per graded expectation (each with its own `passed`).
|
|
52
|
+
* The schema (docs/skill-test-grading-schema.md) defines `summary` as the exact
|
|
53
|
+
* aggregate of `expectations`, so the two MUST agree. We never trust the
|
|
54
|
+
* self-reported summary alone — a grader that emits `summary {5,5}` alongside a
|
|
55
|
+
* failing expectation would otherwise be a false green.
|
|
56
|
+
*
|
|
57
|
+
* Throws {@link GradingSkewError} when:
|
|
58
|
+
* - there are zero expectations (the grader graded NOTHING — an error, never a
|
|
59
|
+
* pass); or
|
|
60
|
+
* - the summary disagrees with the recomputed counts (a grader bug, surfaced
|
|
61
|
+
* loudly rather than silently flowing a wrong verdict downstream).
|
|
62
|
+
*/
|
|
63
|
+
export declare function reconcileGrading(report: NormalizedGrading): GradingVerdict;
|
|
22
64
|
//# sourceMappingURL=grading-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grading-adapter.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAQ5B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"grading-adapter.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAQ5B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrE,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAS5B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOrF;AAaD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAwBhE;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc,CAqB1E"}
|
|
@@ -13,6 +13,35 @@ export class GradingSkewError extends Error {
|
|
|
13
13
|
this.name = 'GradingSkewError';
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Thrown when grading.json's per-run integrity nonce is absent or does not match
|
|
18
|
+
* the nonce the harness stamped into the experimenter prompt for THIS run. A
|
|
19
|
+
* missing/wrong nonce means the grading was not produced by the experimenter we
|
|
20
|
+
* prompted — most likely a grading.json forged or left behind by untrusted skill
|
|
21
|
+
* code in the shared sandbox — so the verdict cannot be trusted.
|
|
22
|
+
*/
|
|
23
|
+
export class GradingNonceError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(`grading.json integrity check failed: ${message}. The harness stamps a secret ` +
|
|
26
|
+
'per-run nonce into the experimenter prompt (never written to disk) and requires ' +
|
|
27
|
+
'grading.json to echo it; a missing or wrong nonce means the grading was not ' +
|
|
28
|
+
'produced by the prompted experimenter and is rejected.');
|
|
29
|
+
this.name = 'GradingNonceError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Assert grading.json carries the exact per-run nonce the harness expected.
|
|
34
|
+
* Called before the verdict is trusted. Throws {@link GradingNonceError} on a
|
|
35
|
+
* missing or mismatched nonce.
|
|
36
|
+
*/
|
|
37
|
+
export function assertGradingNonce(actual, expected) {
|
|
38
|
+
if (actual === undefined) {
|
|
39
|
+
throw new GradingNonceError('grading.json has no top-level `runNonce`');
|
|
40
|
+
}
|
|
41
|
+
if (actual !== expected) {
|
|
42
|
+
throw new GradingNonceError('grading.json `runNonce` does not match this run');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
16
45
|
/**
|
|
17
46
|
* Detect the common per-eval nested mistake: `{ evals: [ { expectations, ... } ] }`
|
|
18
47
|
* with no top-level `expectations`. The grader (an LLM) reaches for this when the
|
|
@@ -36,7 +65,7 @@ export function parseGradingJson(raw) {
|
|
|
36
65
|
const path = firstIssue?.path.join('.') ?? '(root)';
|
|
37
66
|
throw new GradingSkewError(`missing/invalid field at "${path}" (${firstIssue?.message ?? 'unknown'})`);
|
|
38
67
|
}
|
|
39
|
-
const { summary, expectations } = result.data;
|
|
68
|
+
const { summary, expectations, runNonce } = result.data;
|
|
40
69
|
return {
|
|
41
70
|
summary: { passed: summary.passed, total: summary.total },
|
|
42
71
|
expectations: expectations.map(e => ({
|
|
@@ -44,6 +73,39 @@ export function parseGradingJson(raw) {
|
|
|
44
73
|
passed: e.passed,
|
|
45
74
|
...(e.evidence === undefined ? {} : { evidence: e.evidence }),
|
|
46
75
|
})),
|
|
76
|
+
...(runNonce === undefined ? {} : { runNonce }),
|
|
47
77
|
};
|
|
48
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Reconcile the grader's self-reported `summary` against the authoritative
|
|
81
|
+
* per-expectation `passed` flags and return the recomputed verdict.
|
|
82
|
+
*
|
|
83
|
+
* The grader emits BOTH an aggregate `summary.{passed,total}` AND one
|
|
84
|
+
* `expectations[]` entry per graded expectation (each with its own `passed`).
|
|
85
|
+
* The schema (docs/skill-test-grading-schema.md) defines `summary` as the exact
|
|
86
|
+
* aggregate of `expectations`, so the two MUST agree. We never trust the
|
|
87
|
+
* self-reported summary alone — a grader that emits `summary {5,5}` alongside a
|
|
88
|
+
* failing expectation would otherwise be a false green.
|
|
89
|
+
*
|
|
90
|
+
* Throws {@link GradingSkewError} when:
|
|
91
|
+
* - there are zero expectations (the grader graded NOTHING — an error, never a
|
|
92
|
+
* pass); or
|
|
93
|
+
* - the summary disagrees with the recomputed counts (a grader bug, surfaced
|
|
94
|
+
* loudly rather than silently flowing a wrong verdict downstream).
|
|
95
|
+
*/
|
|
96
|
+
export function reconcileGrading(report) {
|
|
97
|
+
const computedPassed = report.expectations.filter(e => e.passed).length;
|
|
98
|
+
const computedTotal = report.expectations.length;
|
|
99
|
+
if (computedTotal === 0) {
|
|
100
|
+
throw new GradingSkewError('the grader recorded zero expectations — nothing was graded, so this can never be a pass. ' +
|
|
101
|
+
'A valid grading.json lists every graded expectation in top-level `expectations`');
|
|
102
|
+
}
|
|
103
|
+
const { passed, total } = report.summary;
|
|
104
|
+
if (passed !== computedPassed || total !== computedTotal) {
|
|
105
|
+
throw new GradingSkewError(`the grader's summary disagrees with its expectations: summary={passed:${passed},total:${total}} ` +
|
|
106
|
+
`but expectations recompute to {passed:${computedPassed},total:${computedTotal}}. ` +
|
|
107
|
+
'`summary` must be the exact aggregate of `expectations` (one entry per graded expectation)');
|
|
108
|
+
}
|
|
109
|
+
return { passed: computedPassed, total: computedTotal, allPassed: computedPassed === computedTotal };
|
|
110
|
+
}
|
|
49
111
|
//# sourceMappingURL=grading-adapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grading-adapter.js","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CACH,4BAA4B,OAAO,gDAAgD;YACjF,0EAA0E;YAC1E,yFAAyF,CAC5F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"grading-adapter.js","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,8 +41,12 @@ export declare const GradedExpectationSchema: z.ZodObject<{
|
|
|
41
41
|
evidence: z.ZodOptional<z.ZodString>;
|
|
42
42
|
}, z.ZodTypeAny, "passthrough">>;
|
|
43
43
|
export type GradedExpectation = z.infer<typeof GradedExpectationSchema>;
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Aggregate pass/fail counts. `failed`/`pass_rate` are documented but optional.
|
|
46
|
+
* Counts are non-negative integers (a float or negative count is a grader bug),
|
|
47
|
+
* and `passed` can never exceed `total`.
|
|
48
|
+
*/
|
|
49
|
+
export declare const GradingSummarySchema: z.ZodEffects<z.ZodObject<{
|
|
46
50
|
passed: z.ZodNumber;
|
|
47
51
|
total: z.ZodNumber;
|
|
48
52
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -57,6 +61,16 @@ export declare const GradingSummarySchema: z.ZodObject<{
|
|
|
57
61
|
total: z.ZodNumber;
|
|
58
62
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
59
63
|
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
}, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
|
|
65
|
+
passed: z.ZodNumber;
|
|
66
|
+
total: z.ZodNumber;
|
|
67
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
70
|
+
passed: z.ZodNumber;
|
|
71
|
+
total: z.ZodNumber;
|
|
72
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
60
74
|
}, z.ZodTypeAny, "passthrough">>;
|
|
61
75
|
export type GradingSummary = z.infer<typeof GradingSummarySchema>;
|
|
62
76
|
/**
|
|
@@ -78,7 +92,7 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
78
92
|
passed: z.ZodBoolean;
|
|
79
93
|
evidence: z.ZodOptional<z.ZodString>;
|
|
80
94
|
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
81
|
-
summary: z.ZodObject<{
|
|
95
|
+
summary: z.ZodEffects<z.ZodObject<{
|
|
82
96
|
passed: z.ZodNumber;
|
|
83
97
|
total: z.ZodNumber;
|
|
84
98
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -93,7 +107,25 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
93
107
|
total: z.ZodNumber;
|
|
94
108
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
95
109
|
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
110
|
+
}, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
|
|
111
|
+
passed: z.ZodNumber;
|
|
112
|
+
total: z.ZodNumber;
|
|
113
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
116
|
+
passed: z.ZodNumber;
|
|
117
|
+
total: z.ZodNumber;
|
|
118
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
96
120
|
}, z.ZodTypeAny, "passthrough">>;
|
|
121
|
+
/**
|
|
122
|
+
* Per-run integrity nonce the experimenter is told to copy verbatim from its
|
|
123
|
+
* prompt (see experimenter-prompt.ts). Optional in the schema (a grading.json
|
|
124
|
+
* validated by external tooling need not carry it), but the harness REQUIRES
|
|
125
|
+
* it to match the run's secret nonce before trusting the verdict — this is how
|
|
126
|
+
* a forged grading.json written by untrusted skill code is rejected.
|
|
127
|
+
*/
|
|
128
|
+
runNonce: z.ZodOptional<z.ZodString>;
|
|
97
129
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
98
130
|
expectations: z.ZodArray<z.ZodObject<{
|
|
99
131
|
text: z.ZodString;
|
|
@@ -108,7 +140,7 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
108
140
|
passed: z.ZodBoolean;
|
|
109
141
|
evidence: z.ZodOptional<z.ZodString>;
|
|
110
142
|
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
111
|
-
summary: z.ZodObject<{
|
|
143
|
+
summary: z.ZodEffects<z.ZodObject<{
|
|
112
144
|
passed: z.ZodNumber;
|
|
113
145
|
total: z.ZodNumber;
|
|
114
146
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -123,7 +155,25 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
123
155
|
total: z.ZodNumber;
|
|
124
156
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
125
157
|
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
158
|
+
}, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
|
|
159
|
+
passed: z.ZodNumber;
|
|
160
|
+
total: z.ZodNumber;
|
|
161
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
162
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
163
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
164
|
+
passed: z.ZodNumber;
|
|
165
|
+
total: z.ZodNumber;
|
|
166
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
167
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
126
168
|
}, z.ZodTypeAny, "passthrough">>;
|
|
169
|
+
/**
|
|
170
|
+
* Per-run integrity nonce the experimenter is told to copy verbatim from its
|
|
171
|
+
* prompt (see experimenter-prompt.ts). Optional in the schema (a grading.json
|
|
172
|
+
* validated by external tooling need not carry it), but the harness REQUIRES
|
|
173
|
+
* it to match the run's secret nonce before trusting the verdict — this is how
|
|
174
|
+
* a forged grading.json written by untrusted skill code is rejected.
|
|
175
|
+
*/
|
|
176
|
+
runNonce: z.ZodOptional<z.ZodString>;
|
|
127
177
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
128
178
|
expectations: z.ZodArray<z.ZodObject<{
|
|
129
179
|
text: z.ZodString;
|
|
@@ -138,7 +188,7 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
138
188
|
passed: z.ZodBoolean;
|
|
139
189
|
evidence: z.ZodOptional<z.ZodString>;
|
|
140
190
|
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
141
|
-
summary: z.ZodObject<{
|
|
191
|
+
summary: z.ZodEffects<z.ZodObject<{
|
|
142
192
|
passed: z.ZodNumber;
|
|
143
193
|
total: z.ZodNumber;
|
|
144
194
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -153,7 +203,25 @@ export declare const GradingReportSchema: z.ZodObject<{
|
|
|
153
203
|
total: z.ZodNumber;
|
|
154
204
|
failed: z.ZodOptional<z.ZodNumber>;
|
|
155
205
|
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
206
|
+
}, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
|
|
207
|
+
passed: z.ZodNumber;
|
|
208
|
+
total: z.ZodNumber;
|
|
209
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
210
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
211
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
212
|
+
passed: z.ZodNumber;
|
|
213
|
+
total: z.ZodNumber;
|
|
214
|
+
failed: z.ZodOptional<z.ZodNumber>;
|
|
215
|
+
pass_rate: z.ZodOptional<z.ZodNumber>;
|
|
156
216
|
}, z.ZodTypeAny, "passthrough">>;
|
|
217
|
+
/**
|
|
218
|
+
* Per-run integrity nonce the experimenter is told to copy verbatim from its
|
|
219
|
+
* prompt (see experimenter-prompt.ts). Optional in the schema (a grading.json
|
|
220
|
+
* validated by external tooling need not carry it), but the harness REQUIRES
|
|
221
|
+
* it to match the run's secret nonce before trusting the verdict — this is how
|
|
222
|
+
* a forged grading.json written by untrusted skill code is rejected.
|
|
223
|
+
*/
|
|
224
|
+
runNonce: z.ZodOptional<z.ZodString>;
|
|
157
225
|
}, z.ZodTypeAny, "passthrough">>;
|
|
158
226
|
export type GradingReport = z.infer<typeof GradingReportSchema>;
|
|
159
227
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grading-schema.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;gCAMpB,CAAC;AAEjB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE
|
|
1
|
+
{"version":3,"file":"grading-schema.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;gCAMpB,CAAC;AAEjB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;gCAU7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAI5B;;;;;;OAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IANH;;;;;;OAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IANH;;;;;;OAMG;;gCAGS,CAAC;AAEjB,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;CAAyD,CAAC"}
|
|
@@ -35,15 +35,22 @@ export const GradedExpectationSchema = z
|
|
|
35
35
|
evidence: z.string().optional(),
|
|
36
36
|
})
|
|
37
37
|
.passthrough();
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Aggregate pass/fail counts. `failed`/`pass_rate` are documented but optional.
|
|
40
|
+
* Counts are non-negative integers (a float or negative count is a grader bug),
|
|
41
|
+
* and `passed` can never exceed `total`.
|
|
42
|
+
*/
|
|
39
43
|
export const GradingSummarySchema = z
|
|
40
44
|
.object({
|
|
41
|
-
passed: z.number(),
|
|
42
|
-
total: z.number(),
|
|
43
|
-
failed: z.number().optional(),
|
|
45
|
+
passed: z.number().int().nonnegative(),
|
|
46
|
+
total: z.number().int().nonnegative(),
|
|
47
|
+
failed: z.number().int().nonnegative().optional(),
|
|
44
48
|
pass_rate: z.number().optional(),
|
|
45
49
|
})
|
|
46
|
-
.passthrough()
|
|
50
|
+
.passthrough()
|
|
51
|
+
.refine(s => s.passed <= s.total, {
|
|
52
|
+
message: 'summary.passed must not exceed summary.total',
|
|
53
|
+
});
|
|
47
54
|
/**
|
|
48
55
|
* The full grading.json contract. Required: top-level `expectations[]` and
|
|
49
56
|
* `summary`. Everything else passes through untouched (forward-compatible with
|
|
@@ -53,6 +60,14 @@ export const GradingReportSchema = z
|
|
|
53
60
|
.object({
|
|
54
61
|
expectations: z.array(GradedExpectationSchema),
|
|
55
62
|
summary: GradingSummarySchema,
|
|
63
|
+
/**
|
|
64
|
+
* Per-run integrity nonce the experimenter is told to copy verbatim from its
|
|
65
|
+
* prompt (see experimenter-prompt.ts). Optional in the schema (a grading.json
|
|
66
|
+
* validated by external tooling need not carry it), but the harness REQUIRES
|
|
67
|
+
* it to match the run's secret nonce before trusting the verdict — this is how
|
|
68
|
+
* a forged grading.json written by untrusted skill code is rejected.
|
|
69
|
+
*/
|
|
70
|
+
runNonce: z.string().optional(),
|
|
56
71
|
})
|
|
57
72
|
.passthrough();
|
|
58
73
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grading-schema.js","sourceRoot":"","sources":["../../src/skill-test/grading-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC;KACD,WAAW,EAAE,CAAC;AAIjB
|
|
1
|
+
{"version":3,"file":"grading-schema.js","sourceRoot":"","sources":["../../src/skill-test/grading-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC;KACD,WAAW,EAAE,CAAC;AAIjB;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACtC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACjD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC;KACD,WAAW,EAAE;KACb,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,EAAE;IAChC,OAAO,EAAE,8CAA8C;CACxD,CAAC,CAAC;AAIL;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC;KACjC,MAAM,CAAC;IACN,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC;IAC9C,OAAO,EAAE,oBAAoB;IAC7B;;;;;;OAMG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC;KACD,WAAW,EAAE,CAAC;AAIjB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,eAAe,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAC"}
|
|
@@ -15,8 +15,16 @@ export declare function resolveHarnessRoot(skillNames: string[], tmpRoot?: strin
|
|
|
15
15
|
* Refuse a --workdir whose ancestry contains CLAUDE.md or .claude/ — cwd
|
|
16
16
|
* discovery would re-pollute the run (spec §7). Defense in depth with
|
|
17
17
|
* --setting-sources "".
|
|
18
|
+
*
|
|
19
|
+
* `stopAt` (optional) bounds the ancestry walk: when the walk reaches that
|
|
20
|
+
* directory it stops *before* inspecting it (exclusive boundary), mirroring
|
|
21
|
+
* `assertSafeHarnessRoot`'s `trustedRoot`. Default (undefined) walks to the
|
|
22
|
+
* filesystem root, preserving the strictest production behavior. Tests use it
|
|
23
|
+
* to scope the walk to a sandbox — necessary on Windows, where the OS tmp dir
|
|
24
|
+
* lives inside the user's home and would otherwise surface an ambient
|
|
25
|
+
* ~/.claude during the walk.
|
|
18
26
|
*/
|
|
19
|
-
export declare function assertSafeWorkdir(dir: string): void;
|
|
27
|
+
export declare function assertSafeWorkdir(dir: string, stopAt?: string): void;
|
|
20
28
|
/**
|
|
21
29
|
* Prepare the harness root directory so that `assertSafeHarnessRoot` will
|
|
22
30
|
* pass on the next call. If the path does not exist, this is a no-op (the
|
|
@@ -31,10 +39,20 @@ export declare function assertSafeWorkdir(dir: string): void;
|
|
|
31
39
|
*/
|
|
32
40
|
export declare function prepareHarnessRoot(dir: string): void;
|
|
33
41
|
/**
|
|
34
|
-
* FS-bound hardening for the shared-tmp harness root (spec §7)
|
|
35
|
-
*
|
|
42
|
+
* FS-bound hardening for the shared-tmp harness root (spec §7). Validates EVERY
|
|
43
|
+
* path component from the leaf up to (but excluding) `trustedRoot`: no component
|
|
44
|
+
* may be a symlink, and on POSIX each must be owned by the current uid. This
|
|
45
|
+
* closes the shared-/tmp TOCTOU where the recursively-created intermediate parent
|
|
46
|
+
* (`<tmp>/vat-skill-test`) — not just the leaf — could be pre-created as a symlink
|
|
47
|
+
* or under another user's ownership. The leaf must additionally be 0700.
|
|
48
|
+
*
|
|
49
|
+
* `trustedRoot` (default: the OS tmp dir) is the boundary: it is system-owned
|
|
50
|
+
* (sticky-bit /tmp) so ownership/mode checks are not applied to it. When the leaf
|
|
51
|
+
* is not a descendant of `trustedRoot` (an explicit --out elsewhere), validation
|
|
52
|
+
* degrades to the leaf alone.
|
|
53
|
+
*
|
|
36
54
|
* Integration-tested (requires real lstat/stat). On Windows, uid checks are
|
|
37
55
|
* skipped (process.getuid is undefined) but the symlink refusal still applies.
|
|
38
56
|
*/
|
|
39
|
-
export declare function assertSafeHarnessRoot(dir: string, currentUid: number): void;
|
|
57
|
+
export declare function assertSafeHarnessRoot(dir: string, currentUid: number, trustedRoot?: string): void;
|
|
40
58
|
//# sourceMappingURL=harness-location.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-location.d.ts","sourceRoot":"","sources":["../../src/skill-test/harness-location.ts"],"names":[],"mappings":"AAKA,sDAAsD;AACtD,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,QAAQ,EAAG,CAAC,CAAU;gBACnB,OAAO,EAAE,MAAM;CAI5B;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7D;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED
|
|
1
|
+
{"version":3,"file":"harness-location.d.ts","sourceRoot":"","sources":["../../src/skill-test/harness-location.ts"],"names":[],"mappings":"AAKA,sDAAsD;AACtD,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,QAAQ,EAAG,CAAC,CAAU;gBACnB,OAAO,EAAE,MAAM;CAI5B;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7D;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAgBpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAkDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,MAA2B,GACvC,IAAI,CAkBN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { chmodSync, existsSync, lstatSync, statSync } from 'node:fs';
|
|
3
|
-
import { normalizedTmpdir, safePath } from '@vibe-agent-toolkit/utils';
|
|
3
|
+
import { isAbsolutePath, normalizedTmpdir, safePath } from '@vibe-agent-toolkit/utils';
|
|
4
4
|
/** Harness-location failure — maps to exit code 2. */
|
|
5
5
|
export class HarnessLocationError extends Error {
|
|
6
6
|
exitCode = 2;
|
|
@@ -36,11 +36,20 @@ export function resolveHarnessRoot(skillNames, tmpRoot) {
|
|
|
36
36
|
* Refuse a --workdir whose ancestry contains CLAUDE.md or .claude/ — cwd
|
|
37
37
|
* discovery would re-pollute the run (spec §7). Defense in depth with
|
|
38
38
|
* --setting-sources "".
|
|
39
|
+
*
|
|
40
|
+
* `stopAt` (optional) bounds the ancestry walk: when the walk reaches that
|
|
41
|
+
* directory it stops *before* inspecting it (exclusive boundary), mirroring
|
|
42
|
+
* `assertSafeHarnessRoot`'s `trustedRoot`. Default (undefined) walks to the
|
|
43
|
+
* filesystem root, preserving the strictest production behavior. Tests use it
|
|
44
|
+
* to scope the walk to a sandbox — necessary on Windows, where the OS tmp dir
|
|
45
|
+
* lives inside the user's home and would otherwise surface an ambient
|
|
46
|
+
* ~/.claude during the walk.
|
|
39
47
|
*/
|
|
40
|
-
export function assertSafeWorkdir(dir) {
|
|
48
|
+
export function assertSafeWorkdir(dir, stopAt) {
|
|
49
|
+
const boundary = stopAt === undefined ? undefined : safePath.resolve(stopAt);
|
|
41
50
|
let current = safePath.resolve(dir);
|
|
42
51
|
let previous = '';
|
|
43
|
-
while (current !== previous) {
|
|
52
|
+
while (current !== previous && current !== boundary) {
|
|
44
53
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- ancestry walk over a user-supplied workdir
|
|
45
54
|
if (existsSync(safePath.join(current, 'CLAUDE.md'))) {
|
|
46
55
|
throw new HarnessLocationError(`--workdir is inside a project: CLAUDE.md found at ${current}. Use an OS-tmp location.`);
|
|
@@ -83,29 +92,85 @@ export function prepareHarnessRoot(dir) {
|
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
}
|
|
95
|
+
/** True when `child` is a strict descendant of `root` (neither equal nor escaping). */
|
|
96
|
+
function isStrictlyUnder(root, child) {
|
|
97
|
+
if (child === root)
|
|
98
|
+
return false;
|
|
99
|
+
const rel = safePath.relative(root, child);
|
|
100
|
+
return rel !== '' && rel !== '..' && !rel.startsWith('../') && !isAbsolutePath(rel);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The harness path components to validate, leaf-first: the leaf and every
|
|
104
|
+
* ancestor strictly between it and `trustedRoot` (exclusive). When the leaf is
|
|
105
|
+
* not a descendant of `trustedRoot` (e.g. an explicit --out elsewhere), only the
|
|
106
|
+
* leaf is returned — we cannot bound the walk without a trusted boundary, so we
|
|
107
|
+
* fall back to leaf-only validation.
|
|
108
|
+
*/
|
|
109
|
+
function harnessAncestry(leaf, trustedRoot) {
|
|
110
|
+
if (!isStrictlyUnder(trustedRoot, leaf))
|
|
111
|
+
return [leaf];
|
|
112
|
+
const components = [];
|
|
113
|
+
let current = leaf;
|
|
114
|
+
while (current !== trustedRoot) {
|
|
115
|
+
const parent = safePath.join(current, '..');
|
|
116
|
+
if (parent === current)
|
|
117
|
+
break; // reached the filesystem root defensively
|
|
118
|
+
components.push(current);
|
|
119
|
+
current = parent;
|
|
120
|
+
}
|
|
121
|
+
return components;
|
|
122
|
+
}
|
|
86
123
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
124
|
+
* Reject a single harness path component that is a symlink or not owned by the
|
|
125
|
+
* current user. A non-existent component is skipped (an absent ancestor cannot
|
|
126
|
+
* be a live attacker-controlled symlink). On Windows the ownership check is
|
|
127
|
+
* skipped (no real uids); the symlink refusal still applies.
|
|
128
|
+
*/
|
|
129
|
+
function assertComponentSafe(component, currentUid) {
|
|
130
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived harness path component
|
|
131
|
+
if (!existsSync(component))
|
|
132
|
+
return;
|
|
133
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived harness path component
|
|
134
|
+
const ls = lstatSync(component);
|
|
135
|
+
if (ls.isSymbolicLink()) {
|
|
136
|
+
throw new HarnessLocationError(`Refusing to use a symlinked harness path component: ${component}.`);
|
|
137
|
+
}
|
|
138
|
+
if (process.platform !== 'win32' && typeof ls.uid === 'number' && ls.uid !== currentUid) {
|
|
139
|
+
throw new HarnessLocationError(`Harness path component ${component} is not owned by the current user (uid ${ls.uid} != ${currentUid}).`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* FS-bound hardening for the shared-tmp harness root (spec §7). Validates EVERY
|
|
144
|
+
* path component from the leaf up to (but excluding) `trustedRoot`: no component
|
|
145
|
+
* may be a symlink, and on POSIX each must be owned by the current uid. This
|
|
146
|
+
* closes the shared-/tmp TOCTOU where the recursively-created intermediate parent
|
|
147
|
+
* (`<tmp>/vat-skill-test`) — not just the leaf — could be pre-created as a symlink
|
|
148
|
+
* or under another user's ownership. The leaf must additionally be 0700.
|
|
149
|
+
*
|
|
150
|
+
* `trustedRoot` (default: the OS tmp dir) is the boundary: it is system-owned
|
|
151
|
+
* (sticky-bit /tmp) so ownership/mode checks are not applied to it. When the leaf
|
|
152
|
+
* is not a descendant of `trustedRoot` (an explicit --out elsewhere), validation
|
|
153
|
+
* degrades to the leaf alone.
|
|
154
|
+
*
|
|
89
155
|
* Integration-tested (requires real lstat/stat). On Windows, uid checks are
|
|
90
156
|
* skipped (process.getuid is undefined) but the symlink refusal still applies.
|
|
91
157
|
*/
|
|
92
|
-
export function assertSafeHarnessRoot(dir, currentUid) {
|
|
158
|
+
export function assertSafeHarnessRoot(dir, currentUid, trustedRoot = normalizedTmpdir()) {
|
|
159
|
+
const resolved = safePath.resolve(dir);
|
|
160
|
+
// eslint-disable-next-line local/no-unsafe-root-join -- single-arg normalization of the trusted boundary itself (no caller-controlled segment is joined here); it is only used as an equality boundary for the ancestry walk.
|
|
161
|
+
const root = safePath.resolve(trustedRoot);
|
|
162
|
+
for (const component of harnessAncestry(resolved, root)) {
|
|
163
|
+
assertComponentSafe(component, currentUid);
|
|
164
|
+
}
|
|
165
|
+
// Leaf-only 0700 check (intermediates are created 0700 by recursive mkdir and
|
|
166
|
+
// need only the symlink/ownership guarantees above). Skipped on win32.
|
|
93
167
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived harness root
|
|
94
|
-
if (!existsSync(
|
|
168
|
+
if (!existsSync(resolved))
|
|
95
169
|
return; // not yet created — caller creates it 0700
|
|
96
170
|
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived harness root
|
|
97
|
-
const
|
|
98
|
-
if (ls.isSymbolicLink()) {
|
|
99
|
-
throw new HarnessLocationError(`Refusing to use a symlinked harness root: ${dir}.`);
|
|
100
|
-
}
|
|
101
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived harness root
|
|
102
|
-
const st = statSync(dir);
|
|
103
|
-
if (typeof st.uid === 'number' && st.uid !== currentUid) {
|
|
104
|
-
throw new HarnessLocationError(`Harness root ${dir} is not owned by the current user (uid ${st.uid} != ${currentUid}).`);
|
|
105
|
-
}
|
|
106
|
-
const mode = st.mode & 0o777;
|
|
171
|
+
const mode = statSync(resolved).mode & 0o777;
|
|
107
172
|
if (mode !== 0o700 && process.platform !== 'win32') {
|
|
108
|
-
throw new HarnessLocationError(`Harness root ${
|
|
173
|
+
throw new HarnessLocationError(`Harness root ${resolved} must be 0700 (found ${mode.toString(8)}).`);
|
|
109
174
|
}
|
|
110
175
|
}
|
|
111
176
|
//# sourceMappingURL=harness-location.js.map
|