@holdyourvoice/hyv 3.6.1 → 4.0.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.
- package/Readme.md +91 -96
- package/dist/ai-editor-rules.js +1 -0
- package/dist/ai-editor.js +19 -13
- package/dist/analysis.js +33 -0
- package/dist/cli/agents.js +15 -35
- package/dist/cli/io.js +41 -37
- package/dist/copy-spec.js +6 -6
- package/dist/editorial-packs.js +4 -3
- package/dist/fact-linter.js +45 -27
- package/dist/hidden-text.js +6 -6
- package/dist/hold-your-voice.mcpb +0 -0
- package/dist/internal.js +8 -0
- package/dist/judgment-task.js +10 -14
- package/dist/learning.js +66 -67
- package/dist/lifecycle-adapter.js +2 -5
- package/dist/local-eval.js +16 -31
- package/dist/mcp-server.js +272 -0
- package/dist/mcp-tools.js +2 -2
- package/dist/mcp.js +2 -261
- package/dist/pipeline.js +9 -85
- package/dist/profile-compose.js +18 -11
- package/dist/profile-score.js +6 -6
- package/dist/profile.js +17 -19
- package/dist/provenance-status.js +1 -3
- package/dist/rebuild-task.js +14 -25
- package/dist/recomposition.js +18 -13
- package/dist/rewrite-prompt.js +82 -0
- package/dist/rewrite-task.js +66 -65
- package/dist/rule-allowances.js +6 -10
- package/dist/semantic-review.js +71 -47
- package/dist/stage1-evaluation.js +29 -14
- package/dist/strict-quality.js +10 -18
- package/dist/version.js +1 -1
- package/dist/voice-dna.js +34 -21
- package/dist/writing-examples.js +2 -7
- package/package.json +6 -3
- package/skills/hyv-prepare-judgment/SKILL.md +4 -0
- package/dist/agents/catalog.test.js +0 -60
- package/dist/agents/emit.test.js +0 -64
- package/dist/agents/load.test.js +0 -149
- package/dist/ai-editor.test.js +0 -265
- package/dist/approval-capability.test.js +0 -52
- package/dist/approval-context.test.js +0 -38
- package/dist/backtest.test.js +0 -20
- package/dist/benchmark.test.js +0 -328
- package/dist/canonical-json.test.js +0 -24
- package/dist/cli/context.test.js +0 -55
- package/dist/cli.test.js +0 -731
- package/dist/editorial-packs.test.js +0 -94
- package/dist/fact-linter.test.js +0 -85
- package/dist/hidden-text.test.js +0 -26
- package/dist/hygiene.test.js +0 -83
- package/dist/judgment-task.test.js +0 -162
- package/dist/learning.test.js +0 -325
- package/dist/lifecycle-adapter.test.js +0 -56
- package/dist/local-eval.test.js +0 -20
- package/dist/logic-linter-corpus.test.js +0 -22
- package/dist/logic-linter.test.js +0 -39
- package/dist/mcp-tools.test.js +0 -286
- package/dist/mcp.test.js +0 -312
- package/dist/mirror-refs.test.js +0 -63
- package/dist/pipeline.test.js +0 -247
- package/dist/preservation.test.js +0 -22
- package/dist/production-gates.test.js +0 -34
- package/dist/profile-compose.test.js +0 -32
- package/dist/profile-score.test.js +0 -22
- package/dist/profile-watch.test.js +0 -23
- package/dist/profile.test.js +0 -141
- package/dist/provenance-status.test.js +0 -22
- package/dist/rebuild-task.test.js +0 -206
- package/dist/recomposition.test.js +0 -34
- package/dist/release-audit.test.js +0 -292
- package/dist/rewrite-task.test.js +0 -166
- package/dist/rule-allowances.test.js +0 -17
- package/dist/rule-reconciliation.test.js +0 -50
- package/dist/sample-ingest.test.js +0 -52
- package/dist/semantic-review.test.js +0 -101
- package/dist/stage1-dry-run.test.js +0 -39
- package/dist/stage1-evaluation.test.js +0 -184
- package/dist/stage1-human-packet.test.js +0 -102
- package/dist/stage1-schema-contract.test.js +0 -95
- package/dist/stage2-human-packet.test.js +0 -81
- package/dist/strict-quality.test.js +0 -62
- package/dist/text-provenance.feature.test.js +0 -45
- package/dist/text.test.js +0 -16
- package/dist/voice-dna.test.js +0 -121
- package/dist/writing-examples.test.js +0 -35
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { chmodSync, mkdtempSync, 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 { approvalContextMetadataIsSafe, loadApprovalContext } from './approval-context.js';
|
|
7
|
-
const context = { now: 0, trustStore: { version: '1', audience: '@holdyourvoice/hyv', maxCapabilityLifetimeSeconds: 300, keys: [] }, authorizedSemanticEvaluatorIds: { normal: ['reviewer-1'], highAssurance: [] }, authorizedHumanFinalizerIds: ['human-1'] };
|
|
8
|
-
test('loads only a permission-checked installed approval context and replaces its clock', () => {
|
|
9
|
-
const root = mkdtempSync(join(tmpdir(), 'hyv-approval-context-'));
|
|
10
|
-
try {
|
|
11
|
-
const safe = join(root, 'safe.json');
|
|
12
|
-
writeFileSync(safe, JSON.stringify(context), { mode: 0o600 });
|
|
13
|
-
const loaded = loadApprovalContext(safe);
|
|
14
|
-
assert.deepEqual(loaded.authorizedSemanticEvaluatorIds.normal, ['reviewer-1']);
|
|
15
|
-
assert.notEqual(loaded.now, 0);
|
|
16
|
-
chmodSync(safe, 0o644);
|
|
17
|
-
assert.throws(() => loadApprovalContext(safe), /unavailable or unsafe/);
|
|
18
|
-
chmodSync(safe, 0o600);
|
|
19
|
-
const link = join(root, 'link.json');
|
|
20
|
-
symlinkSync(safe, link);
|
|
21
|
-
assert.throws(() => loadApprovalContext(link), /unavailable or unsafe/);
|
|
22
|
-
const malformed = join(root, 'malformed.json');
|
|
23
|
-
writeFileSync(malformed, JSON.stringify({ ...context, authorizedSemanticEvaluatorIds: { normal: ['bad id'], highAssurance: [] } }), { mode: 0o600 });
|
|
24
|
-
assert.throws(() => loadApprovalContext(malformed), /unavailable or unsafe/);
|
|
25
|
-
const malformedTrust = join(root, 'malformed-trust.json');
|
|
26
|
-
writeFileSync(malformedTrust, JSON.stringify({ ...context, trustStore: { ...context.trustStore, maxCapabilityLifetimeSeconds: 0 } }), { mode: 0o600 });
|
|
27
|
-
assert.throws(() => loadApprovalContext(malformedTrust), /unavailable or unsafe/);
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
rmSync(root, { recursive: true, force: true });
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
test('fails closed when POSIX ownership metadata is unavailable on this host', () => {
|
|
34
|
-
const metadata = { isFile: () => true, nlink: 1, size: 10, uid: 501, mode: 0o600 };
|
|
35
|
-
assert.equal(approvalContextMetadataIsSafe(metadata, undefined), process.platform === 'win32');
|
|
36
|
-
assert.equal(approvalContextMetadataIsSafe(metadata, 501), true);
|
|
37
|
-
assert.equal(approvalContextMetadataIsSafe({ ...metadata, mode: 0o644 }, 501), false);
|
|
38
|
-
});
|
package/dist/backtest.test.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { evaluateIsolatedBacktest } from './backtest.js';
|
|
4
|
-
import { buildProfile } from './voice-dna.js';
|
|
5
|
-
test('keeps an isolated backtest generation-free and text-free in its report', () => {
|
|
6
|
-
const samples = [
|
|
7
|
-
'I name the constraint first and explain the mechanism with direct words for the person doing the work. The owner checks each source before the release moves into the production queue.',
|
|
8
|
-
'The owner checks the source before the release moves into the production queue and records the evidence for later review. I keep the next step clear for the person handling the release.',
|
|
9
|
-
'I keep evidence visible and choose one next step for the person doing the work before the deployment window opens. The operator records the trade-off and names the owner for the rollback.',
|
|
10
|
-
];
|
|
11
|
-
const target = 'The owner checks the source before the release and records the rollback evidence for the operator doing the production work.';
|
|
12
|
-
const candidate = 'The owner checks the source before the release and uses leverage for the operator doing the production work while the team records each rollback decision in the release log.';
|
|
13
|
-
const report = evaluateIsolatedBacktest('Reply to the rollout question with the owner and check.', target, candidate, buildProfile(samples), samples);
|
|
14
|
-
assert.equal(report.version, '1');
|
|
15
|
-
assert.ok(report.preservation.score < 100);
|
|
16
|
-
assert.equal(report.heldout.selfSimilarity?.ceiling, 100);
|
|
17
|
-
assert.equal(JSON.stringify(report).includes(target), false);
|
|
18
|
-
assert.equal(JSON.stringify(report).includes(candidate), false);
|
|
19
|
-
assert.equal(JSON.stringify(report).includes('uses leverage for the operator'), false);
|
|
20
|
-
});
|
package/dist/benchmark.test.js
DELETED
|
@@ -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
|
-
});
|
package/dist/cli/context.test.js
DELETED
|
@@ -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
|
-
});
|