Files
ECC/tests/lib/eval-harness/envelope.test.js
T
f8640355e4 Consolidate recovered eval framework and operator workflows (#3040)
* feat: consolidate offline eval and operator workflows

Compose the retained framework, operator skill, roadmap and cleanup ranges on current main. Preserve current release dependencies and keep candidate execution disabled pending OS containment. Repair draft/DOCX behavior, obligation uniqueness, trusted send and audience guidance, runner provenance and eval diagnostics.

Source-PR: 2930 0abe3727d2b500c6e4830bdeb47ed67cae3f4785
Source-PR: 2931 992b49c44ed872def49675b791168b8fcd091df6
Source-PR: 2932 4a193dd13041cb7a6bebf4d2e910a0cd32bcc797
Source-PR: 2933 59cdfe500a91949ba1415f1edd7279620f21e804
Source-Base: ca185ef5f7

* fix: repair foundation CI and update js-yaml

* fix: reconcile pending-delete capsule locks after close

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 13:20:52 +01:00

179 lines
8.6 KiB
JavaScript

/**
* Tests for scripts/lib/eval-harness/envelope.js
* Run with: node tests/lib/eval-harness/envelope.test.js
*/
'use strict';
const assert = require('assert');
const envelope = require('../../../scripts/lib/eval-harness/envelope');
const { canonicalJson, hashValue } = require('../../../scripts/lib/eval-harness/canonical');
const { test, finish } = require('./helpers');
// Generated synthetic fixture; no credential values are loaded from the host.
const awsCanary = 'AKIA' + 'A'.repeat(16);
function validEntry(overrides = {}) {
const entry = {
schema: envelope.SCHEMA_VERSION,
run_id: 'run-1',
capsule_id: 'capsule-1',
seq: 0,
ts: '2026-09-02T00:00:00.000Z',
lineage: 'plan',
kind: 'gate.start',
effect_class: 'SE0',
harness_version: 'test/1',
task_family: 'slugify',
parent_hash: envelope.GENESIS_HASH,
payload: { task_id: 't01', status: 'ok' },
...overrides,
};
entry.entry_hash = envelope.computeEntryHash(entry);
return entry;
}
test('canonical JSON sorts keys recursively and drops undefined', () => {
assert.strictEqual(canonicalJson({ b: 1, a: { d: 2, c: [3, { f: 4, e: 5 }] }, z: undefined }), '{"a":{"c":[3,{"e":5,"f":4}],"d":2},"b":1}');
assert.strictEqual(hashValue({ a: 1, b: 2 }), hashValue({ b: 2, a: 1 }));
});
test('a well-formed envelope validates with no errors', () => {
assert.deepStrictEqual(envelope.validateEnvelope(validEntry()), []);
});
test('valid v1 entry keeps the pinned pre-validation hash and serialized payload', () => {
const entry = validEntry();
assert.strictEqual(entry.entry_hash, 'b24439ebdbd58c19e3128d496a47739c59c7736cc82cecaacb00814d54c0c782');
assert.deepStrictEqual(JSON.parse(canonicalJson(entry)).payload, entry.payload);
assert.deepStrictEqual(envelope.validateEnvelope(Object.assign(Object.create(null), entry)), []);
});
test('lineage and effect_class are closed sets', () => {
assert.ok(envelope.validateEnvelope(validEntry({ lineage: 'thoughts' })).some((e) => e.includes('lineage')));
assert.ok(envelope.validateEnvelope(validEntry({ effect_class: 'SE9' })).some((e) => e.includes('effect_class')));
assert.deepStrictEqual([...envelope.LINEAGES], ['plan', 'attempt', 'interaction', 'environment', 'strategy']);
assert.deepStrictEqual([...envelope.EFFECT_CLASSES], ['SE0', 'SE1', 'SE2', 'SE3', 'SE4']);
});
test('entry_hash mismatch is reported', () => {
const entry = validEntry();
entry.payload.status = 'tampered';
assert.ok(envelope.validateEnvelope(entry).some((e) => e.includes('entry_hash')));
});
test('unknown top-level fields are rejected even with a matching hash', () => {
for (const extra of [{ future_field: 'x' }, JSON.parse('{"__proto__":{"note":"owned fixture"}}')]) {
const errors = envelope.validateEnvelope(validEntry(extra));
assert.ok(errors.some(error => error.includes('unknown')), errors.join('; '));
}
});
test('redactPayload is default-deny and reports dropped keys', () => {
const { payload, dropped, findings } = envelope.redactPayload({ task_id: 't', reasoning: 'private', prompt: 'p' });
assert.deepStrictEqual(payload, { task_id: 't' });
assert.deepStrictEqual(dropped, ['prompt', 'reasoning']);
assert.deepStrictEqual(findings, []);
});
test('secret canaries fire on common credential shapes', () => {
const samples = [
['aws_access_key', awsCanary],
['openai_style_key', 'sk-' + 'a'.repeat(24)],
['github_token', 'ghp_' + 'a'.repeat(36)],
['slack_token', 'xoxb-' + 'a'.repeat(24)],
['stripe_key', 'sk_test_' + 'a'.repeat(24)],
['private_key_block', ['-----BEGIN ', 'RSA PRIVATE KEY', '-----'].join('')],
['bearer_header', 'Bearer ' + 'a'.repeat(24)],
['jwt', ['eyJ' + 'a'.repeat(12), 'b'.repeat(12), 'c'.repeat(12)].join('.')],
['env_assignment', 'API_KEY=' + 'a'.repeat(24)],
];
assert.deepStrictEqual(samples.map(([name]) => name).sort(), envelope.SECRET_CANARIES.map(({ name }) => name).sort());
for (const [name, sample] of samples) {
const findings = envelope.scanForCanaries({ message: sample });
assert.ok(findings.some(finding => finding.canary === name), `expected canary family ${name}`);
}
assert.deepStrictEqual(envelope.scanForCanaries({ message: 'plain status text' }), []);
});
test('validateEnvelope refuses payloads that trip a canary', () => {
const entry = validEntry({ payload: { message: 'token ' + awsCanary + ' leaked' } });
assert.ok(envelope.validateEnvelope(entry).some((e) => e.includes('canary')));
});
test('every declared payload field enforces its schema scalar type', () => {
const schema = require('../../../schemas/capsule-envelope.schema.json');
const properties = schema.properties.payload.properties;
assert.deepStrictEqual([...envelope.DEFAULT_PAYLOAD_ALLOWLIST].sort(), Object.keys(properties).sort());
const specimens = [['string', 'sample'], ['number', -1.5], ['integer', -2], ['null', null],
['boolean', true], ['object', {}], ['array', []], ['undefined', undefined]];
for (const [key, rule] of Object.entries(properties)) {
const types = [].concat(rule.type);
for (const [type, value] of specimens) {
const accepted = types.includes(type) || (type === 'integer' && types.includes('number'));
const result = envelope.redactPayload({ [key]: value });
assert.ok(Array.isArray(result.errors), 'redaction exposes validation errors');
assert.strictEqual(result.errors.length === 0, accepted, `${key}: ${type}`);
const entry = validEntry();
entry.payload = { [key]: value };
if (value !== undefined) entry.entry_hash = envelope.computeEntryHash(entry);
assert.strictEqual(envelope.validateEnvelope(entry).length === 0, accepted, `envelope ${key}: ${type}`);
}
}
});
test('payload containers and non-JSON values are refused without recursion', () => {
for (const value of [null, [], 'invalid', 4, true, undefined, new Date(), new Map(), Object.create({ inherited: 1 })]) {
assert.ok(envelope.redactPayload(value).errors.length > 0);
}
const cyclic = {}; cyclic.message = cyclic;
for (const value of [undefined, () => 1, Symbol('synthetic'), 1n, NaN, Infinity, -Infinity, { note: 'synthetic-input-marker' }, [], cyclic]) {
const result = envelope.redactPayload({ message: value });
assert.ok(result.errors.length > 0);
assert.deepStrictEqual(result.findings, []);
assert.ok(!result.errors.join('; ').includes('synthetic-input-marker'));
const entry = validEntry(); entry.payload = { message: value };
assert.ok(envelope.validateEnvelope(entry).some(error => error.includes('payload')));
}
for (const value of [NaN, Infinity, -Infinity]) {
assert.ok(envelope.redactPayload({ score: value }).errors.length > 0);
}
});
test('payload accessors and hidden fields are rejected without evaluating them', () => {
let reads = 0;
for (const key of ['message', 'unknown']) {
const value = Object.defineProperty({}, key, { enumerable: true, get() { reads += 1; throw new Error('must not execute'); } });
assert.ok(envelope.redactPayload(value).errors.length > 0);
}
for (const value of [Object.defineProperty({}, 'message', { value: 'hidden' }), { [Symbol('hidden')]: 'value' }]) {
assert.ok(envelope.redactPayload(value).errors.length > 0);
}
assert.strictEqual(reads, 0);
const plain = Object.assign(Object.create(null), { message: 'plain', exit_code: null });
assert.deepStrictEqual(envelope.redactPayload(plain), { payload: { message: 'plain', exit_code: null }, dropped: [], findings: [], errors: [] });
});
test('custom allowlists narrow v1 fields and never widen persisted payloads', () => {
const narrowed = envelope.redactPayload({ message: 'text', status: 'ok' }, { allowlist: ['status'] });
assert.deepStrictEqual(narrowed.payload, { status: 'ok' });
assert.deepStrictEqual(narrowed.dropped, ['message']);
const widened = envelope.redactPayload({ future: 'text', status: 'ok' }, { allowlist: ['future', 'status'] });
assert.deepStrictEqual(widened.payload, { status: 'ok' });
assert.deepStrictEqual(widened.dropped, ['future']);
assert.ok(envelope.redactPayload({ status: 42 }, { allowlist: ['status'], strict: false }).errors.length > 0);
});
test('top-level accessors, missing own fields and exotic envelopes return errors', () => {
let reads = 0;
const accessor = validEntry();
Object.defineProperty(accessor, 'payload', { enumerable: true, get() { reads += 1; throw new Error('must not execute'); } });
assert.ok(envelope.validateEnvelope(accessor).length > 0);
assert.strictEqual(reads, 0);
const missing = validEntry(); delete missing.payload;
for (const entry of [missing, Object.create(validEntry()), new Date(), { ...validEntry(), [Symbol('extra')]: 'x' }]) {
assert.ok(envelope.validateEnvelope(entry).length > 0);
}
});
finish('envelope');