@holdyourvoice/hyv 3.2.0 → 3.3.0

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.
Files changed (50) hide show
  1. package/Readme.md +23 -10
  2. package/dist/ai-editor-rules.js +5 -2
  3. package/dist/ai-editor.js +52 -9
  4. package/dist/ai-editor.test.js +62 -10
  5. package/dist/approval-capability.js +111 -0
  6. package/dist/approval-capability.test.js +52 -0
  7. package/dist/approval-context.js +54 -0
  8. package/dist/approval-context.test.js +38 -0
  9. package/dist/benchmark.js +232 -0
  10. package/dist/benchmark.test.js +328 -0
  11. package/dist/canonical-json.js +123 -0
  12. package/dist/canonical-json.test.js +24 -0
  13. package/dist/cli.js +272 -19
  14. package/dist/cli.test.js +205 -8
  15. package/dist/hygiene.js +6 -0
  16. package/dist/hygiene.test.js +7 -1
  17. package/dist/judgment-task.js +171 -0
  18. package/dist/judgment-task.test.js +162 -0
  19. package/dist/learning.js +240 -100
  20. package/dist/learning.test.js +203 -3
  21. package/dist/lifecycle-adapter.js +75 -0
  22. package/dist/lifecycle-adapter.test.js +56 -0
  23. package/dist/mcp-tools.js +101 -7
  24. package/dist/mcp-tools.test.js +156 -6
  25. package/dist/mcp.js +213 -6
  26. package/dist/mcp.test.js +210 -11
  27. package/dist/pipeline.js +78 -14
  28. package/dist/pipeline.test.js +36 -2
  29. package/dist/preservation.js +89 -0
  30. package/dist/preservation.test.js +22 -0
  31. package/dist/profile.js +87 -0
  32. package/dist/profile.test.js +114 -0
  33. package/dist/rebuild-task.js +226 -0
  34. package/dist/rebuild-task.test.js +179 -0
  35. package/dist/release-audit.test.js +111 -2
  36. package/dist/rewrite-task.js +136 -16
  37. package/dist/rewrite-task.test.js +62 -7
  38. package/dist/rule-reconciliation.test.js +50 -0
  39. package/dist/semantic-review.js +176 -7
  40. package/dist/semantic-review.test.js +98 -14
  41. package/dist/stage1-dry-run.test.js +39 -0
  42. package/dist/stage1-evaluation.js +579 -0
  43. package/dist/stage1-evaluation.test.js +184 -0
  44. package/dist/stage1-human-packet.test.js +102 -0
  45. package/dist/stage1-schema-contract.test.js +95 -0
  46. package/dist/stage2-human-packet.test.js +81 -0
  47. package/dist/version.js +1 -1
  48. package/dist/voice-dna.js +53 -1
  49. package/dist/voice-dna.test.js +79 -1
  50. package/package.json +2 -2
@@ -0,0 +1,232 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
4
+ export class BenchmarkAccessError extends Error {
5
+ code;
6
+ constructor(code) {
7
+ super(`Private benchmark unavailable (${code}).`);
8
+ this.code = code;
9
+ this.name = 'BenchmarkAccessError';
10
+ }
11
+ }
12
+ function sha256(value) {
13
+ return createHash('sha256').update(value).digest('hex');
14
+ }
15
+ function isInside(parent, child) {
16
+ const path = relative(parent, child);
17
+ return path === '' || (!path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path));
18
+ }
19
+ function isInsideGitRepository(path) {
20
+ let current = path;
21
+ while (true) {
22
+ if (existsSync(join(current, '.git')))
23
+ return true;
24
+ const parent = dirname(current);
25
+ if (parent === current)
26
+ return false;
27
+ current = parent;
28
+ }
29
+ }
30
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
31
+ const GIT_COMMIT_PATTERN = /^[a-f0-9]{40}$/;
32
+ const PUBLIC_MEASURES = ['writer_preference', 'correction_versus_confirm', 'workflow_completion', 'workflow_abandonment'];
33
+ const PUBLIC_PARTITIONS = ['development', 'calibration', 'locked-test'];
34
+ function hasExactKeys(value, keys) {
35
+ const actual = Object.keys(value).sort();
36
+ const expected = [...keys].sort();
37
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
38
+ }
39
+ function isSha256(value) {
40
+ return typeof value === 'string' && SHA256_PATTERN.test(value);
41
+ }
42
+ function isNormalizedRelativeFile(value) {
43
+ return typeof value === 'string' && value.length > 0 && !isAbsolute(value) && !value.includes('\\')
44
+ && posix.normalize(value) === value && value !== '..' && !value.startsWith('../') && !value.startsWith('/');
45
+ }
46
+ function parsePublicManifest(value) {
47
+ if (!isRecord(value) || !hasExactKeys(value, ['version', 'provenance', 'baseline', 'partitions', 'preregisteredMeasures'])
48
+ || value.version !== '1' || !isNonEmptyString(value.provenance) || !isRecord(value.baseline)
49
+ || !hasExactKeys(value.baseline, ['packageVersion', 'sourceCommit', 'rulesetVersion', 'ruleCount', 'orderedRuleIdsSha256', 'serializedRulesSha256', 'sha256'])
50
+ || !isNonEmptyString(value.baseline.packageVersion) || typeof value.baseline.sourceCommit !== 'string' || !GIT_COMMIT_PATTERN.test(value.baseline.sourceCommit)
51
+ || !isNonEmptyString(value.baseline.rulesetVersion) || !Number.isSafeInteger(value.baseline.ruleCount) || value.baseline.ruleCount < 1
52
+ || !isSha256(value.baseline.orderedRuleIdsSha256) || !isSha256(value.baseline.serializedRulesSha256) || !isSha256(value.baseline.sha256)
53
+ || !Array.isArray(value.partitions) || value.partitions.length !== PUBLIC_PARTITIONS.length
54
+ || !Array.isArray(value.preregisteredMeasures) || value.preregisteredMeasures.length !== PUBLIC_MEASURES.length
55
+ || value.preregisteredMeasures.some((measure, index) => measure !== PUBLIC_MEASURES[index]))
56
+ return undefined;
57
+ const partitionIds = new Set();
58
+ for (const [index, partition] of value.partitions.entries()) {
59
+ if (!isRecord(partition) || !hasExactKeys(partition, ['id', 'sha256', 'cases']) || !isNonEmptyString(partition.id) || partition.id !== PUBLIC_PARTITIONS[index]
60
+ || partitionIds.has(partition.id) || !isSha256(partition.sha256) || !Array.isArray(partition.cases) || partition.cases.length === 0)
61
+ return undefined;
62
+ partitionIds.add(partition.id);
63
+ for (const entry of partition.cases) {
64
+ if (!isRecord(entry) || !hasExactKeys(entry, ['id', 'file', 'sha256']) || !isNonEmptyString(entry.id)
65
+ || !isNormalizedRelativeFile(entry.file) || !entry.file.startsWith('cases/') || !isSha256(entry.sha256))
66
+ return undefined;
67
+ }
68
+ }
69
+ return value;
70
+ }
71
+ const HYV_320_BASELINE = {
72
+ packageVersion: '3.2.0',
73
+ sourceCommit: '4e6269121d551c008a34db73077e1e4fea41b3f9',
74
+ rulesetVersion: '2.9.24-static.2',
75
+ ruleCount: 145,
76
+ orderedRuleIdsSha256: '04bc3d8f5631cc1eb3b42c1cd8b627d0d9a30fa02d8db094bae312f4f37b35b4',
77
+ serializedRulesSha256: 'f24434156d237a00c3f86b7a745661841f79e011930d7fb978cb07b1a0d8ebe4',
78
+ };
79
+ export function validatePublicBenchmark(benchmarkRoot) {
80
+ const root = realpathSync(benchmarkRoot);
81
+ const manifestPath = realpathSync(resolve(root, 'manifest.json'));
82
+ if (!isInside(root, manifestPath))
83
+ throw new Error('Public benchmark manifest is invalid.');
84
+ const manifest = parsePublicManifest(JSON.parse(readFileSync(manifestPath, 'utf8')));
85
+ if (!manifest)
86
+ throw new Error('Public benchmark manifest is invalid.');
87
+ const { sha256: baselineSha256, ...baseline } = manifest.baseline;
88
+ if (JSON.stringify(baseline) !== JSON.stringify(HYV_320_BASELINE) || sha256(JSON.stringify(baseline)) !== baselineSha256)
89
+ throw new Error('Public benchmark baseline has drifted.');
90
+ const seenIds = new Set();
91
+ const seenFiles = new Set();
92
+ for (const partition of manifest.partitions) {
93
+ const partitionBinding = { version: manifest.version, provenance: manifest.provenance, baselineSha256, partition: { id: partition.id, cases: partition.cases } };
94
+ if (sha256(JSON.stringify(partitionBinding)) !== partition.sha256)
95
+ throw new Error('Public benchmark partition digest has drifted.');
96
+ for (const entry of partition.cases) {
97
+ if (seenIds.has(entry.id))
98
+ throw new Error('Public benchmark case belongs to multiple partitions.');
99
+ seenIds.add(entry.id);
100
+ const file = realpathSync(resolve(root, entry.file));
101
+ if (!isInside(root, file))
102
+ throw new Error('Public benchmark case digest has drifted.');
103
+ const contents = readFileSync(file);
104
+ if (sha256(contents) !== entry.sha256)
105
+ throw new Error('Public benchmark case digest has drifted.');
106
+ if (seenFiles.has(file))
107
+ throw new Error('Public benchmark fixture belongs to multiple partitions.');
108
+ seenFiles.add(file);
109
+ const fixture = JSON.parse(contents.toString('utf8'));
110
+ if (!isRecord(fixture) || !hasExactKeys(fixture, ['version', 'id', 'provenance', 'partition', 'task_class', 'draft', 'expectation', ...('candidate' in fixture ? ['candidate'] : []), ...('counterexample' in fixture ? ['counterexample'] : []), ...('copy_spec' in fixture ? ['copy_spec'] : [])])
111
+ || fixture.version !== '1' || !isNonEmptyString(fixture.task_class) || typeof fixture.draft !== 'string' || !isRecord(fixture.expectation)
112
+ || ('candidate' in fixture && typeof fixture.candidate !== 'string') || ('counterexample' in fixture && typeof fixture.counterexample !== 'string')
113
+ || ('copy_spec' in fixture && !isRecord(fixture.copy_spec)))
114
+ throw new Error('Public benchmark case is invalid.');
115
+ if (fixture.id !== entry.id)
116
+ throw new Error('Public benchmark case identity has drifted.');
117
+ if (fixture.partition !== partition.id || fixture.provenance !== manifest.provenance)
118
+ throw new Error('Public benchmark fixture placement has drifted.');
119
+ }
120
+ }
121
+ return manifest;
122
+ }
123
+ function isRecord(value) {
124
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
125
+ }
126
+ function isNonEmptyString(value) {
127
+ return typeof value === 'string' && value.length > 0;
128
+ }
129
+ function isIsoInstant(value) {
130
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value))
131
+ return false;
132
+ const parsed = new Date(value);
133
+ const canonical = value.includes('.') ? value : `${value.slice(0, -1)}.000Z`;
134
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString() === canonical;
135
+ }
136
+ function parsePrivateRightsManifest(value) {
137
+ if (!isRecord(value) || !hasExactKeys(value, ['version', 'custodian', 'rights', 'encryptedLocalStorageAttestation', 'permittedUsers', 'permittedEnvironments', 'retention', 'incidentOwner', 'corpus'])
138
+ || value.version !== '1' || !isNonEmptyString(value.custodian) || !isRecord(value.rights) || !hasExactKeys(value.rights, ['basis', 'status', 'approvedBy', 'approvedAt'])
139
+ || !isNonEmptyString(value.rights.basis) || value.rights.status !== 'approved' || !isNonEmptyString(value.rights.approvedBy) || !isIsoInstant(value.rights.approvedAt)
140
+ || !isRecord(value.encryptedLocalStorageAttestation) || !hasExactKeys(value.encryptedLocalStorageAttestation, ['approved', 'attestedBy', 'attestedAt']) || value.encryptedLocalStorageAttestation.approved !== true
141
+ || !isNonEmptyString(value.encryptedLocalStorageAttestation.attestedBy) || !isIsoInstant(value.encryptedLocalStorageAttestation.attestedAt)
142
+ || !Array.isArray(value.permittedUsers) || value.permittedUsers.length === 0 || !value.permittedUsers.every(isNonEmptyString) || new Set(value.permittedUsers).size !== value.permittedUsers.length
143
+ || !Array.isArray(value.permittedEnvironments) || value.permittedEnvironments.length === 0 || !value.permittedEnvironments.every(isNonEmptyString) || new Set(value.permittedEnvironments).size !== value.permittedEnvironments.length
144
+ || !isRecord(value.retention) || !hasExactKeys(value.retention, ['expiresAt', 'deletionProcedure']) || !isIsoInstant(value.retention.expiresAt) || !isNonEmptyString(value.retention.deletionProcedure)
145
+ || !isNonEmptyString(value.incidentOwner) || !isRecord(value.corpus) || !hasExactKeys(value.corpus, ['sha256', 'cases']) || !isSha256(value.corpus.sha256)
146
+ || !Array.isArray(value.corpus.cases) || value.corpus.cases.length === 0)
147
+ return undefined;
148
+ const caseIds = new Set();
149
+ for (const entry of value.corpus.cases) {
150
+ if (!isRecord(entry) || !hasExactKeys(entry, ['id', 'sha256', 'provenance']) || !isNonEmptyString(entry.id) || caseIds.has(entry.id) || !isSha256(entry.sha256)
151
+ || !isRecord(entry.provenance) || !hasExactKeys(entry.provenance, ['sourceId', 'rightsBasis', 'approvedBy', 'approvedAt'])
152
+ || !isNonEmptyString(entry.provenance.sourceId) || !isNonEmptyString(entry.provenance.rightsBasis)
153
+ || !isNonEmptyString(entry.provenance.approvedBy) || !isIsoInstant(entry.provenance.approvedAt))
154
+ return undefined;
155
+ caseIds.add(entry.id);
156
+ }
157
+ return value;
158
+ }
159
+ function validatePrivateCorpus(contents, manifest) {
160
+ const lines = contents.split('\n');
161
+ if (lines.at(-1) === '')
162
+ lines.pop();
163
+ if (lines.length !== manifest.corpus.cases.length)
164
+ return false;
165
+ const seen = new Set();
166
+ for (const [index, line] of lines.entries()) {
167
+ let value;
168
+ try {
169
+ value = JSON.parse(line);
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ const expected = manifest.corpus.cases[index];
175
+ if (!isRecord(value) || !isNonEmptyString(value.id) || value.id !== expected.id || seen.has(value.id) || sha256(line) !== expected.sha256)
176
+ return false;
177
+ seen.add(value.id);
178
+ }
179
+ return true;
180
+ }
181
+ export function validatePrivateBenchmark(options) {
182
+ if (!options.enabled)
183
+ throw new BenchmarkAccessError('opt_in_required');
184
+ if (Boolean(process.env.CI) || options.ci === true)
185
+ throw new BenchmarkAccessError('ci_forbidden');
186
+ let root;
187
+ let approvedRoot;
188
+ let repositoryRoot;
189
+ try {
190
+ root = realpathSync(options.privateRoot);
191
+ approvedRoot = realpathSync(options.approvedStorageRoot);
192
+ repositoryRoot = realpathSync(options.repositoryRoot);
193
+ }
194
+ catch {
195
+ throw new BenchmarkAccessError('location_unavailable');
196
+ }
197
+ if (!isInside(approvedRoot, root) || isInside(repositoryRoot, root) || isInsideGitRepository(root))
198
+ throw new BenchmarkAccessError('location_unapproved');
199
+ let manifest;
200
+ try {
201
+ const manifestPath = realpathSync(resolve(root, 'rights-manifest.json'));
202
+ if (!isInside(root, manifestPath))
203
+ throw new Error('outside root');
204
+ manifest = parsePrivateRightsManifest(JSON.parse(readFileSync(manifestPath, 'utf8')));
205
+ }
206
+ catch {
207
+ throw new BenchmarkAccessError('manifest_unavailable');
208
+ }
209
+ if (!manifest || !manifest.permittedUsers.includes(options.user) || !manifest.permittedEnvironments.includes(options.environment))
210
+ throw new BenchmarkAccessError('manifest_unapproved');
211
+ const expiresAt = Date.parse(manifest.retention.expiresAt);
212
+ if (!Number.isFinite(expiresAt) || expiresAt <= (options.now ?? new Date()).getTime())
213
+ throw new BenchmarkAccessError('retention_expired');
214
+ let corpusSha256;
215
+ try {
216
+ const corpusPath = realpathSync(resolve(root, 'corpus.ndjson'));
217
+ if (!isInside(root, corpusPath))
218
+ throw new Error('outside root');
219
+ const corpusContents = readFileSync(corpusPath, 'utf8');
220
+ corpusSha256 = sha256(corpusContents);
221
+ if (!validatePrivateCorpus(corpusContents, manifest))
222
+ throw new BenchmarkAccessError('corpus_digest_mismatch');
223
+ }
224
+ catch (error) {
225
+ if (error instanceof BenchmarkAccessError)
226
+ throw error;
227
+ throw new BenchmarkAccessError('corpus_unavailable');
228
+ }
229
+ if (corpusSha256 !== manifest.corpus.sha256)
230
+ throw new BenchmarkAccessError('corpus_digest_mismatch');
231
+ return { version: '1', corpusSha256 };
232
+ }
@@ -0,0 +1,328 @@
1
+ import assert from 'node:assert/strict';
2
+ import { cpSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { createHash } from 'node:crypto';
7
+ import { analyzeAiEditor } from './ai-editor.js';
8
+ import { BenchmarkAccessError, validatePrivateBenchmark, validatePublicBenchmark } from './benchmark.js';
9
+ import { finalOutputCheck } from './hygiene.js';
10
+ import { comparePreservation } from './preservation.js';
11
+ import { applyRewriteResponse, prepareRewriteTask } from './rewrite-task.js';
12
+ import { buildProfile } from './voice-dna.js';
13
+ const repositoryRoot = process.cwd();
14
+ function sha256Json(value) {
15
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
16
+ }
17
+ function bindPublicPartition(manifest, index = 0) {
18
+ const partition = manifest.partitions[index];
19
+ partition.sha256 = sha256Json({
20
+ version: manifest.version,
21
+ provenance: manifest.provenance,
22
+ baselineSha256: manifest.baseline.sha256,
23
+ partition: { id: partition.id, cases: partition.cases },
24
+ });
25
+ }
26
+ function withPublicBenchmarkCopy(check) {
27
+ const temporaryRoot = mkdtempSync(join(tmpdir(), 'hyv-public-benchmark-'));
28
+ const benchmarkRoot = join(temporaryRoot, 'benchmarks');
29
+ cpSync(join(repositoryRoot, 'benchmarks'), benchmarkRoot, { recursive: true });
30
+ try {
31
+ check(benchmarkRoot);
32
+ }
33
+ finally {
34
+ rmSync(temporaryRoot, { recursive: true, force: true });
35
+ }
36
+ }
37
+ function withoutCi(check) {
38
+ const previousCi = process.env.CI;
39
+ delete process.env.CI;
40
+ try {
41
+ check();
42
+ }
43
+ finally {
44
+ if (previousCi === undefined)
45
+ delete process.env.CI;
46
+ else
47
+ process.env.CI = previousCi;
48
+ }
49
+ }
50
+ test('locks the historical public partitions, Hyv 3.2.0 baseline, and preregistered measures independently of the live catalog', () => {
51
+ const manifest = validatePublicBenchmark(join(repositoryRoot, 'benchmarks'));
52
+ assert.deepEqual(manifest.preregisteredMeasures, ['writer_preference', 'correction_versus_confirm', 'workflow_completion', 'workflow_abandonment']);
53
+ assert.deepEqual(manifest.partitions.map((partition) => partition.id), ['development', 'calibration', 'locked-test']);
54
+ assert.equal(manifest.partitions.flatMap((partition) => partition.cases).length, 5);
55
+ const polarity = JSON.parse(readFileSync(join(repositoryRoot, 'benchmarks/cases/synthetic-004.json'), 'utf8'));
56
+ assert.ok(comparePreservation(polarity.draft, polarity.candidate).orderedToken.wordSurvival >= 0.8);
57
+ assert.equal(polarity.expectation.semantic_polarity, 'reject');
58
+ });
59
+ test('rejects public baseline, partition membership, identity, and fixture digest drift', () => {
60
+ const manifestPath = (benchmarkRoot) => join(benchmarkRoot, 'manifest.json');
61
+ const readManifest = (benchmarkRoot) => JSON.parse(readFileSync(manifestPath(benchmarkRoot), 'utf8'));
62
+ const writeManifest = (benchmarkRoot, manifest) => writeFileSync(manifestPath(benchmarkRoot), JSON.stringify(manifest));
63
+ withPublicBenchmarkCopy((benchmarkRoot) => {
64
+ const manifest = readManifest(benchmarkRoot);
65
+ manifest.baseline.packageVersion = 'mutated-baseline';
66
+ writeManifest(benchmarkRoot, manifest);
67
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark baseline has drifted/);
68
+ });
69
+ withPublicBenchmarkCopy((benchmarkRoot) => {
70
+ const manifest = readManifest(benchmarkRoot);
71
+ manifest.partitions[0].cases.push({ ...manifest.partitions[0].cases[0], id: 'synthetic-001-alias' });
72
+ writeManifest(benchmarkRoot, manifest);
73
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark partition digest has drifted/);
74
+ });
75
+ withPublicBenchmarkCopy((benchmarkRoot) => {
76
+ const manifest = readManifest(benchmarkRoot);
77
+ const partition = manifest.partitions[0];
78
+ partition.cases[0] = { ...manifest.partitions[0].cases[1], id: 'synthetic-001-alias' };
79
+ bindPublicPartition(manifest);
80
+ writeManifest(benchmarkRoot, manifest);
81
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark case identity has drifted/);
82
+ });
83
+ withPublicBenchmarkCopy((benchmarkRoot) => {
84
+ writeFileSync(join(benchmarkRoot, 'cases/synthetic-001.json'), '{"mutated":true}\n');
85
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark case digest has drifted/);
86
+ });
87
+ });
88
+ test('rejects malformed or under-bound public benchmark manifests before reading fixtures', () => {
89
+ const manifestPath = (benchmarkRoot) => join(benchmarkRoot, 'manifest.json');
90
+ const readManifest = (benchmarkRoot) => JSON.parse(readFileSync(manifestPath(benchmarkRoot), 'utf8'));
91
+ const writeManifest = (benchmarkRoot, manifest) => writeFileSync(manifestPath(benchmarkRoot), JSON.stringify(manifest));
92
+ for (const mutate of [
93
+ (manifest) => { manifest.unknown = true; },
94
+ (manifest) => { manifest.provenance = ''; },
95
+ (manifest) => { manifest.baseline.unknown = true; },
96
+ (manifest) => { manifest.partitions = 'development'; },
97
+ (manifest) => { manifest.partitions[0].sha256 = 'not-a-digest'; },
98
+ (manifest) => { manifest.partitions[0].cases[0].unknown = true; },
99
+ (manifest) => { manifest.preregisteredMeasures.push('made_up_measure'); },
100
+ (manifest) => { manifest.preregisteredMeasures[1] = manifest.preregisteredMeasures[0]; },
101
+ ]) {
102
+ withPublicBenchmarkCopy((benchmarkRoot) => {
103
+ const manifest = readManifest(benchmarkRoot);
104
+ mutate(manifest);
105
+ writeManifest(benchmarkRoot, manifest);
106
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark manifest is invalid/);
107
+ });
108
+ }
109
+ withPublicBenchmarkCopy((benchmarkRoot) => {
110
+ const manifest = readManifest(benchmarkRoot);
111
+ manifest.partitions[0].cases[0].file = '../outside.json';
112
+ bindPublicPartition(manifest);
113
+ writeManifest(benchmarkRoot, manifest);
114
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark manifest is invalid/);
115
+ });
116
+ withPublicBenchmarkCopy((benchmarkRoot) => {
117
+ const manifest = readManifest(benchmarkRoot);
118
+ manifest.partitions[0].cases[0].file = 'cases/../cases/synthetic-001.json';
119
+ bindPublicPartition(manifest);
120
+ writeManifest(benchmarkRoot, manifest);
121
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark manifest is invalid/);
122
+ });
123
+ withPublicBenchmarkCopy((benchmarkRoot) => {
124
+ const manifest = readManifest(benchmarkRoot);
125
+ manifest.provenance = `${manifest.provenance} mutated`;
126
+ writeManifest(benchmarkRoot, manifest);
127
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark partition digest has drifted/);
128
+ });
129
+ });
130
+ test('rejects a public case symlink outside the benchmark before reading it', () => {
131
+ withPublicBenchmarkCopy((benchmarkRoot) => {
132
+ const outsideRoot = mkdtempSync(join(tmpdir(), 'hyv-public-outside-'));
133
+ const outsideFile = join(outsideRoot, 'outside.json');
134
+ writeFileSync(outsideFile, '{"private":"must not be read"}\n');
135
+ const casePath = join(benchmarkRoot, 'cases/synthetic-001.json');
136
+ rmSync(casePath);
137
+ symlinkSync(outsideFile, casePath);
138
+ try {
139
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark case digest has drifted/);
140
+ }
141
+ finally {
142
+ rmSync(outsideRoot, { recursive: true, force: true });
143
+ }
144
+ });
145
+ });
146
+ test('rejects a public manifest symlink outside the benchmark before reading it', () => {
147
+ withPublicBenchmarkCopy((benchmarkRoot) => {
148
+ const outsideRoot = mkdtempSync(join(tmpdir(), 'hyv-public-manifest-outside-'));
149
+ const outsideFile = join(outsideRoot, 'manifest.json');
150
+ writeFileSync(outsideFile, '{"private":"must not be read"}\n');
151
+ const manifestPath = join(benchmarkRoot, 'manifest.json');
152
+ rmSync(manifestPath);
153
+ symlinkSync(outsideFile, manifestPath);
154
+ try {
155
+ assert.throws(() => validatePublicBenchmark(benchmarkRoot), /Public benchmark manifest is invalid/);
156
+ }
157
+ finally {
158
+ rmSync(outsideRoot, { recursive: true, force: true });
159
+ }
160
+ });
161
+ });
162
+ test('executes the synthetic rule, byte-preservation, and hygiene expectations', () => {
163
+ const readCase = (id) => JSON.parse(readFileSync(join(repositoryRoot, `benchmarks/cases/${id}.json`), 'utf8'));
164
+ for (const id of ['synthetic-001', 'synthetic-002']) {
165
+ const fixture = readCase(id);
166
+ assert.deepEqual(analyzeAiEditor(fixture.draft).findings.map((finding) => finding.id), fixture.expectation.finding_ids);
167
+ if (fixture.counterexample)
168
+ assert.deepEqual(analyzeAiEditor(fixture.counterexample).findings.map((finding) => finding.id), fixture.expectation.counterexample_finding_ids);
169
+ }
170
+ const clean = readCase('synthetic-003');
171
+ const profile = buildProfile([clean.draft, clean.draft]);
172
+ const task = prepareRewriteTask(clean.draft, profile);
173
+ const result = applyRewriteResponse(task, { version: '1', taskFingerprint: task.fingerprint, replacements: [] });
174
+ assert.deepEqual(task.eligibleSentenceIds, []);
175
+ assert.equal(result.candidate, clean.draft);
176
+ const hygiene = readCase('synthetic-005');
177
+ const checked = finalOutputCheck(hygiene.candidate);
178
+ assert.equal(checked.accepted, false);
179
+ assert.deepEqual(checked.remaining.hits.map((hit) => hit.codepoint), hygiene.expectation.codepoints);
180
+ });
181
+ test('private evaluation is opt-in, local-only, rights-gated, current, and digest-locked', () => {
182
+ withoutCi(() => {
183
+ const approvedRoot = mkdtempSync(join(tmpdir(), 'hyv-private-approved-'));
184
+ const privateRoot = join(approvedRoot, 'corpus');
185
+ mkdirSync(privateRoot);
186
+ const privateCase = { id: 'private-001', text: 'private' };
187
+ const source = `${JSON.stringify(privateCase)}\n`;
188
+ writeFileSync(join(privateRoot, 'corpus.ndjson'), source);
189
+ const manifest = {
190
+ version: '1', custodian: 'custodian-1',
191
+ rights: { basis: 'documented distribution approval', status: 'approved', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' },
192
+ encryptedLocalStorageAttestation: { approved: true, attestedBy: 'security-1', attestedAt: '2026-08-01T00:00:00Z' },
193
+ permittedUsers: ['reviewer-1'], permittedEnvironments: ['local-evaluation'],
194
+ retention: { expiresAt: '2026-09-01T00:00:00Z', deletionProcedure: 'Secure deletion ticket RET-1.' },
195
+ incidentOwner: 'incident-owner-1', corpus: {
196
+ sha256: createHash('sha256').update(source).digest('hex'),
197
+ cases: [{
198
+ id: privateCase.id,
199
+ sha256: createHash('sha256').update(JSON.stringify(privateCase)).digest('hex'),
200
+ provenance: { sourceId: 'source-001', rightsBasis: 'documented distribution approval', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' },
201
+ }],
202
+ },
203
+ };
204
+ writeFileSync(join(privateRoot, 'rights-manifest.json'), JSON.stringify(manifest));
205
+ const options = { enabled: true, privateRoot, approvedStorageRoot: approvedRoot, repositoryRoot, user: 'reviewer-1', environment: 'local-evaluation', now: new Date('2026-08-13T00:00:00Z'), ci: false };
206
+ try {
207
+ assert.equal(validatePrivateBenchmark(options).corpusSha256, manifest.corpus.sha256);
208
+ assert.throws(() => validatePrivateBenchmark({ ...options, enabled: false }), (error) => error instanceof BenchmarkAccessError && error.code === 'opt_in_required');
209
+ assert.throws(() => validatePrivateBenchmark({ ...options, ci: true }), (error) => error instanceof BenchmarkAccessError && error.code === 'ci_forbidden');
210
+ assert.throws(() => validatePrivateBenchmark({ ...options, privateRoot: join(approvedRoot, 'missing') }), (error) => error instanceof BenchmarkAccessError && error.code === 'location_unavailable');
211
+ const previousCi = process.env.CI;
212
+ process.env.CI = '1';
213
+ try {
214
+ assert.throws(() => validatePrivateBenchmark({ ...options, ci: false }), (error) => error instanceof BenchmarkAccessError && error.code === 'ci_forbidden');
215
+ }
216
+ finally {
217
+ if (previousCi === undefined)
218
+ delete process.env.CI;
219
+ else
220
+ process.env.CI = previousCi;
221
+ }
222
+ assert.throws(() => validatePrivateBenchmark({ ...options, now: new Date('2026-10-01T00:00:00Z') }), (error) => error instanceof BenchmarkAccessError && error.code === 'retention_expired');
223
+ rmSync(join(privateRoot, 'rights-manifest.json'));
224
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'manifest_unavailable');
225
+ writeFileSync(join(privateRoot, 'rights-manifest.json'), 'null');
226
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'manifest_unapproved');
227
+ writeFileSync(join(privateRoot, 'rights-manifest.json'), JSON.stringify({ ...manifest, rights: { ...manifest.rights, status: 'pending' } }));
228
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'manifest_unapproved');
229
+ writeFileSync(join(privateRoot, 'rights-manifest.json'), JSON.stringify(manifest));
230
+ writeFileSync(join(privateRoot, 'corpus.ndjson'), `${source}source text that must stay private`);
231
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'corpus_digest_mismatch' && !error.message.includes('source text'));
232
+ rmSync(join(privateRoot, 'corpus.ndjson'));
233
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'corpus_unavailable');
234
+ }
235
+ finally {
236
+ rmSync(approvedRoot, { recursive: true, force: true });
237
+ }
238
+ });
239
+ });
240
+ test('private evaluation requires strict per-case rights and provenance without disclosing corpus data', () => {
241
+ withoutCi(() => {
242
+ const approvedRoot = mkdtempSync(join(tmpdir(), 'hyv-private-case-rights-'));
243
+ const privateRoot = join(approvedRoot, 'corpus');
244
+ mkdirSync(privateRoot);
245
+ const privateCase = { id: 'private-001', text: 'sensitive source text' };
246
+ const source = `${JSON.stringify(privateCase)}\n`;
247
+ const caseRecord = {
248
+ id: privateCase.id,
249
+ sha256: createHash('sha256').update(JSON.stringify(privateCase)).digest('hex'),
250
+ provenance: { sourceId: 'source-001', rightsBasis: 'approved study use', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' },
251
+ };
252
+ const manifest = {
253
+ version: '1', custodian: 'custodian-1',
254
+ rights: { basis: 'documented distribution approval', status: 'approved', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' },
255
+ encryptedLocalStorageAttestation: { approved: true, attestedBy: 'security-1', attestedAt: '2026-08-01T00:00:00Z' },
256
+ permittedUsers: ['reviewer-1'], permittedEnvironments: ['local-evaluation'],
257
+ retention: { expiresAt: '2026-09-01T00:00:00Z', deletionProcedure: 'Secure deletion ticket RET-1.' },
258
+ incidentOwner: 'incident-owner-1',
259
+ corpus: { sha256: createHash('sha256').update(source).digest('hex'), cases: [caseRecord] },
260
+ };
261
+ writeFileSync(join(privateRoot, 'corpus.ndjson'), source);
262
+ const manifestPath = join(privateRoot, 'rights-manifest.json');
263
+ const options = { enabled: true, privateRoot, approvedStorageRoot: approvedRoot, repositoryRoot, user: 'reviewer-1', environment: 'local-evaluation', now: new Date('2026-08-13T00:00:00Z'), ci: false };
264
+ try {
265
+ for (const corpus of [
266
+ { sha256: manifest.corpus.sha256 },
267
+ { ...manifest.corpus, cases: [{ ...caseRecord, provenance: { ...caseRecord.provenance, approvedBy: '' } }] },
268
+ { ...manifest.corpus, cases: [{ ...caseRecord, provenance: { ...caseRecord.provenance, approvedAt: '2026-02-31T00:00:00Z' } }] },
269
+ { ...manifest.corpus, cases: [{ ...caseRecord, sha256: 'bad' }] },
270
+ { ...manifest.corpus, cases: [caseRecord, caseRecord] },
271
+ ]) {
272
+ writeFileSync(manifestPath, JSON.stringify({ ...manifest, corpus }));
273
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'manifest_unapproved' && !error.message.includes(privateCase.text));
274
+ }
275
+ writeFileSync(manifestPath, JSON.stringify({ ...manifest, corpus: { ...manifest.corpus, cases: [{ ...caseRecord, id: 'private-002' }] } }));
276
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'corpus_digest_mismatch' && !error.message.includes(privateCase.text));
277
+ writeFileSync(manifestPath, JSON.stringify(manifest));
278
+ writeFileSync(join(privateRoot, 'corpus.ndjson'), `${JSON.stringify({ ...privateCase, id: 'private-002' })}\n`);
279
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'corpus_digest_mismatch' && !error.message.includes(privateCase.text));
280
+ }
281
+ finally {
282
+ rmSync(approvedRoot, { recursive: true, force: true });
283
+ }
284
+ });
285
+ });
286
+ test('private evaluation refuses repository storage and missing manifests without disclosing a path', () => {
287
+ const insideRepository = join(repositoryRoot, 'benchmarks');
288
+ for (const options of [
289
+ { enabled: true, privateRoot: insideRepository, approvedStorageRoot: repositoryRoot, repositoryRoot, user: 'x', environment: 'x', ci: false },
290
+ { enabled: true, privateRoot: tmpdir(), approvedStorageRoot: tmpdir(), repositoryRoot, user: 'x', environment: 'x', ci: false },
291
+ ]) {
292
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && !error.message.includes(options.privateRoot));
293
+ }
294
+ });
295
+ test('private evaluation refuses a benchmark root carrying nested Git metadata', () => {
296
+ withoutCi(() => {
297
+ const approvedRoot = mkdtempSync(join(tmpdir(), 'hyv-private-git-metadata-'));
298
+ const privateRoot = join(approvedRoot, 'corpus');
299
+ mkdirSync(privateRoot);
300
+ const privateCase = { id: 'private-001', text: 'private' };
301
+ const source = `${JSON.stringify(privateCase)}\n`;
302
+ const manifest = {
303
+ version: '1', custodian: 'custodian-1',
304
+ rights: { basis: 'documented distribution approval', status: 'approved', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' },
305
+ encryptedLocalStorageAttestation: { approved: true, attestedBy: 'security-1', attestedAt: '2026-08-01T00:00:00Z' },
306
+ permittedUsers: ['reviewer-1'], permittedEnvironments: ['local-evaluation'],
307
+ retention: { expiresAt: '2026-09-01T00:00:00Z', deletionProcedure: 'Secure deletion ticket RET-1.' },
308
+ incidentOwner: 'incident-owner-1', corpus: {
309
+ sha256: createHash('sha256').update(source).digest('hex'),
310
+ cases: [{ id: privateCase.id, sha256: createHash('sha256').update(JSON.stringify(privateCase)).digest('hex'), provenance: { sourceId: 'source-001', rightsBasis: 'approved study use', approvedBy: 'approver-1', approvedAt: '2026-08-01T00:00:00Z' } }],
311
+ },
312
+ };
313
+ writeFileSync(join(privateRoot, 'corpus.ndjson'), source);
314
+ writeFileSync(join(privateRoot, 'rights-manifest.json'), JSON.stringify(manifest));
315
+ const options = { enabled: true, privateRoot, approvedStorageRoot: approvedRoot, repositoryRoot, user: 'reviewer-1', environment: 'local-evaluation', now: new Date('2026-08-13T00:00:00Z'), ci: false };
316
+ try {
317
+ writeFileSync(join(privateRoot, '.git'), 'gitdir: /private/tmp/elsewhere\n');
318
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'location_unapproved');
319
+ rmSync(join(privateRoot, '.git'));
320
+ mkdirSync(join(privateRoot, '.git'));
321
+ writeFileSync(join(privateRoot, '.git/HEAD'), 'ref: refs/heads/main\n');
322
+ assert.throws(() => validatePrivateBenchmark(options), (error) => error instanceof BenchmarkAccessError && error.code === 'location_unapproved');
323
+ }
324
+ finally {
325
+ rmSync(approvedRoot, { recursive: true, force: true });
326
+ }
327
+ });
328
+ });