hub-proposal-evaluation.test.mjs
139 lines 4.5 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Proposal evaluation store + merge checklist.
3 */
4 import { describe, it, after } from 'node:test';
5 import assert from 'node:assert';
6 import fs from 'fs';
7 import path from 'path';
8 import { fileURLToPath } from 'url';
9 import {
10 createProposal,
11 getProposal,
12 submitProposalEvaluation,
13 updateProposalStatus,
14 mergeEvaluationChecklist,
15 evaluationAllowsApprove,
16 getEvaluationStatus,
17 } from '../hub/proposals-store.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const dataDir = path.join(__dirname, 'fixtures', 'hub-proposal-eval-data');
21 const proposalsPath = path.join(dataDir, 'hub_proposals.json');
22
23 function cleanup() {
24 try {
25 fs.unlinkSync(proposalsPath);
26 } catch (_) {}
27 try {
28 fs.rmdirSync(dataDir);
29 } catch (_) {}
30 }
31
32 describe('hub proposal evaluation', () => {
33 after(cleanup);
34
35 it('createProposal sets pending when evaluationRequired', () => {
36 cleanup();
37 const p = createProposal(dataDir, {
38 path: 'inbox/x.md',
39 body: 'hi',
40 evaluationRequired: true,
41 });
42 assert.strictEqual(p.evaluation_status, 'pending');
43 assert.strictEqual(evaluationAllowsApprove(p), false);
44 });
45
46 it('createProposal sets none when evaluation not required', () => {
47 cleanup();
48 const p = createProposal(dataDir, {
49 path: 'inbox/y.md',
50 body: 'hi',
51 evaluationRequired: false,
52 });
53 assert.strictEqual(getEvaluationStatus(p), 'none');
54 assert.strictEqual(evaluationAllowsApprove(p), true);
55 });
56
57 it('submitProposalEvaluation pass requires all checklist items when checklist non-empty', () => {
58 cleanup();
59 const p = createProposal(dataDir, {
60 path: 'inbox/z.md',
61 body: 'hi',
62 evaluationRequired: true,
63 });
64 const rubric = [
65 { id: 'a', label: 'A' },
66 { id: 'b', label: 'B' },
67 ];
68 const bad = mergeEvaluationChecklist(rubric, [{ id: 'a', passed: true }]);
69 const r1 = submitProposalEvaluation(dataDir, p.proposal_id, {
70 outcome: 'pass',
71 evaluation_checklist: bad,
72 evaluated_by: 'u:1',
73 });
74 assert.strictEqual(r1.ok, false);
75
76 const good = mergeEvaluationChecklist(rubric, [
77 { id: 'a', passed: true },
78 { id: 'b', passed: true },
79 ]);
80 const r2 = submitProposalEvaluation(dataDir, p.proposal_id, {
81 outcome: 'pass',
82 evaluation_checklist: good,
83 evaluated_by: 'u:1',
84 });
85 assert.strictEqual(r2.ok, true);
86 const again = getProposal(dataDir, p.proposal_id);
87 assert.strictEqual(again.evaluation_status, 'passed');
88 assert.strictEqual(evaluationAllowsApprove(again), true);
89 });
90
91 it('fail outcome requires comment', () => {
92 cleanup();
93 const p = createProposal(dataDir, { path: 'inbox/f.md', body: 'x', evaluationRequired: false });
94 const r1 = submitProposalEvaluation(dataDir, p.proposal_id, {
95 outcome: 'fail',
96 evaluation_checklist: [],
97 evaluated_by: 'u:1',
98 });
99 assert.strictEqual(r1.ok, false);
100 const r2 = submitProposalEvaluation(dataDir, p.proposal_id, {
101 outcome: 'fail',
102 evaluation_checklist: [],
103 evaluation_comment: 'Missing sources',
104 evaluated_by: 'u:1',
105 });
106 assert.strictEqual(r2.ok, true);
107 assert.strictEqual(getProposal(dataDir, p.proposal_id).evaluation_status, 'failed');
108 });
109
110 it('updateProposalStatus attaches evaluation_waiver on approve', () => {
111 cleanup();
112 const p = createProposal(dataDir, { path: 'inbox/w.md', body: 'x', evaluationRequired: false });
113 const w = { by: 'admin:1', at: '2026-01-01T00:00:00.000Z', reason: 'Emergency publish' };
114 const u = updateProposalStatus(dataDir, p.proposal_id, 'approved', { evaluation_waiver: w });
115 assert(u);
116 assert.deepStrictEqual(u.evaluation_waiver, w);
117 });
118
119 it('updateProposalStatus persists external_ref on approve when valid', () => {
120 cleanup();
121 const p = createProposal(dataDir, { path: 'inbox/muse.md', body: 'x', evaluationRequired: false });
122 const u = updateProposalStatus(dataDir, p.proposal_id, 'approved', { external_ref: 'branch:abc123' });
123 assert(u);
124 assert.strictEqual(u.external_ref, 'branch:abc123');
125 });
126
127 it('updateProposalStatus approve without external_ref extras preserves prior external_ref', () => {
128 cleanup();
129 const p = createProposal(dataDir, {
130 path: 'inbox/keep-ref.md',
131 body: 'x',
132 evaluationRequired: false,
133 external_ref: 'pre-set-at-create',
134 });
135 const u = updateProposalStatus(dataDir, p.proposal_id, 'approved', {});
136 assert(u);
137 assert.strictEqual(u.external_ref, 'pre-set-at-create');
138 });
139 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago