test(voyage): pin forward-compat for revision/source_annotations/annotation_digest/revision_reason — v4.2 Step 2
3 new test files, 24 cases (8 per validator): - baseline (no annotation fields) still valid - revision: 0 / revision: 5 accepted - source_annotations list-of-dict accepted - annotation_digest string accepted - revision_reason accepted - all 4 fields together accepted - unrecognized future field tolerated (forward-compat policy) Pin against future strict-key refactors. No production code change — pure regression pin.
This commit is contained in:
parent
dcf0c7ad02
commit
ff7a5c63da
3 changed files with 255 additions and 0 deletions
|
|
@ -0,0 +1,87 @@
|
|||
// tests/validators/brief-validator-annotation-fields.test.mjs
|
||||
// Pin forward-compat for v4.2 annotation frontmatter fields on brief.md.
|
||||
// Adding revision/source_annotations/annotation_digest/revision_reason must NOT
|
||||
// trigger BRIEF_UNKNOWN_FIELD or similar — validator is purely additive-tolerant
|
||||
// per source_findings precedent. No code change required; this test pins the policy.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { validateBriefContent } from '../../lib/validators/brief-validator.mjs';
|
||||
|
||||
const BASE_BRIEF = `---
|
||||
type: trekbrief
|
||||
brief_version: "2.0"
|
||||
created: 2026-05-09
|
||||
task: "Annotated revision for testing forward-compat"
|
||||
slug: ann-fwd-compat
|
||||
project_dir: .claude/projects/2026-05-09-ann-fwd-compat/
|
||||
research_topics: 0
|
||||
research_status: skipped
|
||||
auto_research: false
|
||||
interview_turns: 1
|
||||
source: interview
|
||||
---
|
||||
|
||||
# Task: Annotated revision
|
||||
|
||||
## Intent
|
||||
|
||||
Why.
|
||||
|
||||
## Goal
|
||||
|
||||
What.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Done.
|
||||
`;
|
||||
|
||||
test('brief-validator forward-compat — baseline (no annotation fields) still valid', () => {
|
||||
const r = validateBriefContent(BASE_BRIEF, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — accepts revision: 0', () => {
|
||||
const t = BASE_BRIEF.replace('---\ninterview_turns: 1', '---\ninterview_turns: 1\nrevision: 0');
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — accepts revision: 5', () => {
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview\nrevision: 5');
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — accepts source_annotations list-of-dict', () => {
|
||||
const inject = `\nrevision: 1\nsource_annotations:\n - id: ANN-0001\n target_artifact: brief.md\n target_anchor: intent\n intent: change\n comment: "tighten the intent paragraph"\n timestamp: "2026-05-09T10:00:00Z"`;
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview' + inject);
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — accepts annotation_digest string', () => {
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview\nrevision: 1\nannotation_digest: abc123def4567890');
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — accepts revision_reason for non-additive revision', () => {
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview\nrevision: 2\nrevision_reason: "restructured Goals section"');
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — all 4 fields together still valid', () => {
|
||||
const inject = `\nrevision: 3\nrevision_reason: "applied 5 annotations"\nannotation_digest: 0123456789abcdef\nsource_annotations:\n - id: ANN-0001\n target_artifact: brief.md\n target_anchor: goal\n intent: change`;
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview' + inject);
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('brief-validator forward-compat — unrecognized future field still tolerated (forward-compat policy)', () => {
|
||||
const t = BASE_BRIEF.replace('source: interview', 'source: interview\nfuture_v4_3_field: "anything"');
|
||||
const r = validateBriefContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// tests/validators/plan-validator-annotation-fields.test.mjs
|
||||
// Pin forward-compat for v4.2 annotation frontmatter fields on plan.md.
|
||||
// Adding revision/source_annotations/annotation_digest/revision_reason must NOT
|
||||
// trigger PLAN_UNKNOWN_FIELD or similar — validator is purely additive-tolerant
|
||||
// per source_findings precedent. No code change required; this test pins the policy.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
|
||||
|
||||
const STEP_BLOCK = `### Step 1: Do thing
|
||||
|
||||
- Files: a.ts
|
||||
- Manifest:
|
||||
\`\`\`yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- a.ts
|
||||
min_file_count: 1
|
||||
commit_message_pattern: "^feat:"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths: []
|
||||
must_contain: []
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
const baseFm = (extra = '') => `---
|
||||
plan_version: "1.7"
|
||||
profile: balanced${extra}
|
||||
---
|
||||
|
||||
# Plan
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
${STEP_BLOCK}
|
||||
`;
|
||||
|
||||
test('plan-validator forward-compat — baseline (no annotation fields) still valid', () => {
|
||||
const r = validatePlanContent(baseFm(), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — accepts revision: 0', () => {
|
||||
const r = validatePlanContent(baseFm('\nrevision: 0'), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — accepts revision: 5', () => {
|
||||
const r = validatePlanContent(baseFm('\nrevision: 5'), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — accepts source_annotations list-of-dict', () => {
|
||||
const inject = `\nrevision: 1\nsource_annotations:\n - id: ANN-0001\n target_artifact: plan.md\n target_anchor: step-3\n intent: change\n comment: "reorder ahead of step 4"\n timestamp: "2026-05-09T10:00:00Z"`;
|
||||
const r = validatePlanContent(baseFm(inject), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — accepts annotation_digest string', () => {
|
||||
const r = validatePlanContent(baseFm('\nrevision: 1\nannotation_digest: 0123456789abcdef'), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — accepts revision_reason', () => {
|
||||
const r = validatePlanContent(baseFm('\nrevision: 2\nrevision_reason: "structural step reorder"'), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — all 4 fields together with source_findings', () => {
|
||||
const inject = `\nrevision: 3\nrevision_reason: "applied 5 annotations"\nannotation_digest: abc1234567890def\nsource_annotations:\n - id: ANN-0001\n target_artifact: plan.md\n target_anchor: step-3\n intent: change\nsource_findings:\n - 0123456789abcdef0123456789abcdef01234567`;
|
||||
const r = validatePlanContent(baseFm(inject), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('plan-validator forward-compat — unrecognized future field tolerated', () => {
|
||||
const r = validatePlanContent(baseFm('\nfuture_v4_3_key: "any"'), { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// tests/validators/review-validator-annotation-fields.test.mjs
|
||||
// Pin forward-compat for v4.2 annotation frontmatter fields on review.md.
|
||||
// Adding revision/source_annotations/annotation_digest/revision_reason must NOT
|
||||
// trigger REVIEW_UNKNOWN_FIELD or similar — validator is purely additive-tolerant
|
||||
// per source_findings precedent. No code change required; this test pins the policy.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { validateReviewContent } from '../../lib/validators/review-validator.mjs';
|
||||
|
||||
const BASE_REVIEW = `---
|
||||
type: trekreview
|
||||
review_version: "1.0"
|
||||
created: 2026-05-09
|
||||
task: "Annotated revision forward-compat"
|
||||
slug: ann-fwd-compat
|
||||
project_dir: .claude/projects/2026-05-09-ann-fwd-compat/
|
||||
brief_path: .claude/projects/2026-05-09-ann-fwd-compat/brief.md
|
||||
scope_sha_start: abc123
|
||||
scope_sha_end: def456
|
||||
reviewed_files_count: 1
|
||||
findings: []
|
||||
---
|
||||
|
||||
# Review
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Verdict: ALLOW.
|
||||
|
||||
## Coverage
|
||||
|
||||
| File | Treatment | Reason |
|
||||
|------|-----------|--------|
|
||||
| lib/foo.mjs | deep-review | risk |
|
||||
|
||||
## Remediation Summary
|
||||
|
||||
None.
|
||||
`;
|
||||
|
||||
test('review-validator forward-compat — baseline (no annotation fields) still valid', () => {
|
||||
const r = validateReviewContent(BASE_REVIEW, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — accepts revision: 0', () => {
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []\nrevision: 0');
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — accepts revision: 5', () => {
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []\nrevision: 5');
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — accepts source_annotations alongside source-style findings', () => {
|
||||
const inject = `\nrevision: 1\nsource_annotations:\n - id: ANN-0001\n target_artifact: review.md\n target_anchor: executive-summary\n intent: question\n comment: "wording is ambiguous"\n timestamp: "2026-05-09T10:00:00Z"`;
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []' + inject);
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — accepts annotation_digest string', () => {
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []\nrevision: 1\nannotation_digest: 0123456789abcdef');
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — accepts revision_reason for non-additive revision', () => {
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []\nrevision: 2\nrevision_reason: "removed coverage section"');
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — all 4 annotation fields together still valid', () => {
|
||||
const inject = `\nrevision: 3\nrevision_reason: "applied 2 annotations"\nannotation_digest: 0123456789abcdef\nsource_annotations:\n - id: ANN-0001\n target_artifact: review.md\n target_anchor: coverage\n intent: change`;
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []' + inject);
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
|
||||
test('review-validator forward-compat — unrecognized future field tolerated', () => {
|
||||
const t = BASE_REVIEW.replace('findings: []', 'findings: []\nfuture_v4_3_key: "any"');
|
||||
const r = validateReviewContent(t, { strict: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue