@holdyourvoice/hyv 3.6.1 → 4.0.1

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 (87) hide show
  1. package/Readme.md +91 -96
  2. package/dist/ai-editor-rules.js +1 -0
  3. package/dist/ai-editor.js +19 -13
  4. package/dist/analysis.js +33 -0
  5. package/dist/cli/agents.js +15 -35
  6. package/dist/cli/io.js +41 -37
  7. package/dist/copy-spec.js +6 -6
  8. package/dist/editorial-packs.js +4 -3
  9. package/dist/fact-linter.js +87 -37
  10. package/dist/hidden-text.js +6 -6
  11. package/dist/hold-your-voice.mcpb +0 -0
  12. package/dist/internal.js +8 -0
  13. package/dist/judgment-task.js +10 -14
  14. package/dist/learning.js +66 -67
  15. package/dist/lifecycle-adapter.js +2 -5
  16. package/dist/local-eval.js +16 -31
  17. package/dist/mcp-server.js +272 -0
  18. package/dist/mcp-tools.js +2 -2
  19. package/dist/mcp.js +2 -261
  20. package/dist/pipeline.js +9 -85
  21. package/dist/profile-compose.js +18 -11
  22. package/dist/profile-score.js +6 -6
  23. package/dist/profile.js +17 -19
  24. package/dist/provenance-status.js +1 -3
  25. package/dist/rebuild-task.js +14 -25
  26. package/dist/recomposition.js +18 -13
  27. package/dist/rewrite-prompt.js +125 -0
  28. package/dist/rewrite-task.js +117 -70
  29. package/dist/rule-allowances.js +6 -10
  30. package/dist/semantic-review.js +71 -47
  31. package/dist/stage1-evaluation.js +29 -14
  32. package/dist/strict-quality.js +10 -18
  33. package/dist/version.js +1 -1
  34. package/dist/voice-dna.js +34 -21
  35. package/dist/writing-examples.js +2 -7
  36. package/package.json +6 -3
  37. package/skills/hyv-prepare-judgment/SKILL.md +4 -0
  38. package/dist/agents/catalog.test.js +0 -60
  39. package/dist/agents/emit.test.js +0 -64
  40. package/dist/agents/load.test.js +0 -149
  41. package/dist/ai-editor.test.js +0 -265
  42. package/dist/approval-capability.test.js +0 -52
  43. package/dist/approval-context.test.js +0 -38
  44. package/dist/backtest.test.js +0 -20
  45. package/dist/benchmark.test.js +0 -328
  46. package/dist/canonical-json.test.js +0 -24
  47. package/dist/cli/context.test.js +0 -55
  48. package/dist/cli.test.js +0 -731
  49. package/dist/editorial-packs.test.js +0 -94
  50. package/dist/fact-linter.test.js +0 -85
  51. package/dist/hidden-text.test.js +0 -26
  52. package/dist/hygiene.test.js +0 -83
  53. package/dist/judgment-task.test.js +0 -162
  54. package/dist/learning.test.js +0 -325
  55. package/dist/lifecycle-adapter.test.js +0 -56
  56. package/dist/local-eval.test.js +0 -20
  57. package/dist/logic-linter-corpus.test.js +0 -22
  58. package/dist/logic-linter.test.js +0 -39
  59. package/dist/mcp-tools.test.js +0 -286
  60. package/dist/mcp.test.js +0 -312
  61. package/dist/mirror-refs.test.js +0 -63
  62. package/dist/pipeline.test.js +0 -247
  63. package/dist/preservation.test.js +0 -22
  64. package/dist/production-gates.test.js +0 -34
  65. package/dist/profile-compose.test.js +0 -32
  66. package/dist/profile-score.test.js +0 -22
  67. package/dist/profile-watch.test.js +0 -23
  68. package/dist/profile.test.js +0 -141
  69. package/dist/provenance-status.test.js +0 -22
  70. package/dist/rebuild-task.test.js +0 -206
  71. package/dist/recomposition.test.js +0 -34
  72. package/dist/release-audit.test.js +0 -292
  73. package/dist/rewrite-task.test.js +0 -166
  74. package/dist/rule-allowances.test.js +0 -17
  75. package/dist/rule-reconciliation.test.js +0 -50
  76. package/dist/sample-ingest.test.js +0 -52
  77. package/dist/semantic-review.test.js +0 -101
  78. package/dist/stage1-dry-run.test.js +0 -39
  79. package/dist/stage1-evaluation.test.js +0 -184
  80. package/dist/stage1-human-packet.test.js +0 -102
  81. package/dist/stage1-schema-contract.test.js +0 -95
  82. package/dist/stage2-human-packet.test.js +0 -81
  83. package/dist/strict-quality.test.js +0 -62
  84. package/dist/text-provenance.feature.test.js +0 -45
  85. package/dist/text.test.js +0 -16
  86. package/dist/voice-dna.test.js +0 -121
  87. package/dist/writing-examples.test.js +0 -35
@@ -1,328 +0,0 @@
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
- });
@@ -1,24 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
- import { canonicalJson, canonicalJsonBytes, parseCanonicalJson } from './canonical-json.js';
4
- test('canonicalizes nested objects, arrays, escapes, unicode, and numbers', () => {
5
- const value = { z: [3, { b: '\u20ac', a: '\n' }], a: 1e-7 };
6
- assert.equal(canonicalJson(value), '{"a":1e-7,"z":[3,{"a":"\\n","b":"€"}]}');
7
- assert.deepEqual(canonicalJsonBytes(value), Buffer.from(canonicalJson(value), 'utf8'));
8
- });
9
- test('rejects duplicate keys, lone surrogates, unsafe integers, negative zero, and unsupported values', () => {
10
- assert.throws(() => parseCanonicalJson(Buffer.from('{"a":1,"a":1}')), /duplicate/i);
11
- for (const value of ['\ud800', Number.MAX_SAFE_INTEGER + 1, -0, undefined]) {
12
- assert.throws(() => canonicalJson(value), /canonical/i);
13
- }
14
- });
15
- test('requires exact canonical payload bytes', () => {
16
- assert.deepEqual(parseCanonicalJson(Buffer.from('{"a":1,"b":2}')), { a: 1, b: 2 });
17
- assert.throws(() => parseCanonicalJson(Buffer.from('{ "a": 1, "b": 2 }')), /canonical/i);
18
- });
19
- test('rejects inputs beyond the canonical complexity budget', () => {
20
- let value = 1;
21
- for (let index = 0; index < 70; index += 1)
22
- value = [value];
23
- assert.throws(() => canonicalJson(value), /complexity limit/);
24
- });
@@ -1,55 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { spawnSync } from 'node:child_process';
3
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import test from 'node:test';
7
- import { buildProfile } from '../voice-dna.js';
8
- test('prepare-rewrite recognizes contexts in either order and rejects duplicates before writing', () => {
9
- const directory = mkdtempSync(join(tmpdir(), 'hyv-cli-context-'));
10
- try {
11
- const draft = join(directory, 'draft.md');
12
- const profile = join(directory, 'profile.json');
13
- const task = join(directory, 'task.json');
14
- const spec = join(directory, 'spec.json');
15
- const brief = join(directory, 'brief.json');
16
- const ambiguous = join(directory, 'ambiguous.json');
17
- const invalid = join(directory, 'invalid.json');
18
- const copySpec = { version: '1', audience: 'writers', intent: 'explain', channel: 'general', claims: [{ id: 'mechanism', text: 'I keep the mechanism clear.', evidence: 'The source draft.' }] };
19
- const writingBrief = { version: '1', audience: 'writers', intent: 'explain', format: 'general' };
20
- writeFileSync(draft, 'I keep the mechanism clear.');
21
- writeFileSync(profile, JSON.stringify(buildProfile(['I write plainly. I name the work.', 'I keep the mechanism clear. I avoid filler.'])));
22
- writeFileSync(spec, JSON.stringify(copySpec));
23
- writeFileSync(brief, JSON.stringify(writingBrief));
24
- writeFileSync(ambiguous, JSON.stringify({ ...copySpec, ...writingBrief }));
25
- writeFileSync(invalid, '{}');
26
- const run = (contexts) => spawnSync(process.execPath, [new URL('../cli.js', import.meta.url).pathname, 'prepare-rewrite', draft, profile, task, ...contexts], { encoding: 'utf8' });
27
- for (const contexts of [[spec, brief], [brief, spec]]) {
28
- const result = run(contexts);
29
- assert.equal(result.status, 0, result.stderr);
30
- const prepared = JSON.parse(readFileSync(task, 'utf8'));
31
- assert.deepEqual(prepared.copySpec, copySpec);
32
- assert.deepEqual(prepared.writingBrief, writingBrief);
33
- }
34
- const result = run([ambiguous]);
35
- assert.equal(result.status, 0, result.stderr);
36
- const prepared = JSON.parse(readFileSync(task, 'utf8'));
37
- assert.deepEqual(prepared.copySpec, { ...copySpec, ...writingBrief });
38
- assert.equal(prepared.writingBrief, undefined);
39
- for (const [contexts, error] of [
40
- [[spec, spec], 'Prepare-rewrite accepts at most one CopySpec.'],
41
- [[brief, brief], 'Prepare-rewrite accepts at most one WritingBrief.'],
42
- [[invalid], `Expected a valid CopySpec or WritingBrief at ${invalid}.`],
43
- ]) {
44
- const before = readFileSync(task, 'utf8');
45
- const rejected = run([...contexts]);
46
- assert.equal(rejected.status, 1);
47
- assert.equal(rejected.stdout, '');
48
- assert.equal(rejected.stderr, `${error}\n`);
49
- assert.equal(readFileSync(task, 'utf8'), before);
50
- }
51
- }
52
- finally {
53
- rmSync(directory, { recursive: true, force: true });
54
- }
55
- });