mirror of
https://github.com/merlinhu1/truthmark.git
synced 2026-08-25 07:53:25 +02:00
* feat: add OpenSpec-driven workflow state * docs: plan generated playbooks workflow contract * feat: add truth health scorecard Release Truthmark 2.1.0 with check JSON scorecard output. Archive the completed OpenSpec pass 4 scorecard change and update generated truth surfaces. * fix: unblock truthmark scorecard review - restore and update coverage for scorecard/evidence/workflow behavior - make truthmark-sync select a cheap local base when --base is omitted - report malformed evidence YAML blocks as diagnostics instead of throwing * fix: tighten workflow boundaries * fix: fail closed when sync has no comparison base * fix: tighten truth health review coverage - only parse fenced YAML evidence blocks with top-level evidence markers - convert coverage fixtures to valid v2 config and assert non-vacuous diagnostics - update Truthmark truth docs for source traceability and workflow-state tests * docs: clarify source traceability diagnostics * docs: harden optional CLI workflow boundary * docs: clarify product boundary is repo-local * fix: bound truthmark workflow fallback behavior - make live workflow preflight use the one-call instructions contract - keep missing-CLI fallback bounded for Sync, Document, Structure, and Realize - add routing and generated-surface coverage plus refreshed host surfaces * test: cover no-cli workflow fallback bounds - assert route-first fallback wording in generated workflow surfaces - clarify progressive-disclosure support files are conditional - align overview docs with bounded no-CLI behavior * docs: route workflow eval coverage * fix: remove workflow instructions preflight * fix: remove stale workflow preflight wording * fix: remove generic workflow validation blocks * fix: avoid duplicated markdown context JSON * fix: keep workflow status manifest-only * docs: align truth docs with v2 hierarchy --------- Co-authored-by: MerlinH <merlinh221@gmail.com>
200 lines
5.4 KiB
TypeScript
200 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { parseTruthDocUpdateDraft } from "../../src/generation/validate.js";
|
|
import type { ContextPack } from "../../src/generation/types.js";
|
|
|
|
const contextPack = {
|
|
task: "truth-sync",
|
|
changedFiles: ["src/auth/session.ts"],
|
|
owningAreas: ["Authentication"],
|
|
relevantDocs: ["docs/truthmark/truth/authentication/session-timeout.md"],
|
|
evidenceSnippets: [
|
|
{
|
|
id: "E1",
|
|
path: "src/auth/session.ts",
|
|
startLine: 10,
|
|
endLine: 18,
|
|
reason: "changed timeout behavior",
|
|
text: "export const SESSION_TIMEOUT_MS = 900000;",
|
|
},
|
|
],
|
|
openQuestions: [],
|
|
} satisfies ContextPack;
|
|
|
|
const validDraft = {
|
|
status: "drafted",
|
|
targetDocs: ["docs/truthmark/truth/authentication/session-timeout.md"],
|
|
claims: [
|
|
{
|
|
text: "Session timeout is 15 minutes.",
|
|
evidenceIds: ["E1"],
|
|
support: "supported",
|
|
},
|
|
],
|
|
patches: [
|
|
{
|
|
path: "docs/truthmark/truth/authentication/session-timeout.md",
|
|
section: "Current Behavior",
|
|
operation: "append",
|
|
markdown: "- Sessions expire after 15 minutes of inactivity.",
|
|
},
|
|
],
|
|
openQuestions: [],
|
|
};
|
|
|
|
describe("parseTruthDocUpdateDraft", () => {
|
|
it("accepts a valid evidence-backed draft", () => {
|
|
const draft = parseTruthDocUpdateDraft(JSON.stringify(validDraft), contextPack);
|
|
|
|
expect(draft.status).toBe("drafted");
|
|
expect(draft.claims[0]?.evidenceIds).toEqual(["E1"]);
|
|
});
|
|
|
|
it("accepts blocked output with open questions and no doc changes", () => {
|
|
const draft = parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
status: "blocked",
|
|
targetDocs: [],
|
|
claims: [],
|
|
patches: [],
|
|
openQuestions: ["Which bounded doc owns this behavior?"],
|
|
}),
|
|
contextPack,
|
|
);
|
|
|
|
expect(draft.status).toBe("blocked");
|
|
expect(draft.openQuestions).toEqual(["Which bounded doc owns this behavior?"]);
|
|
});
|
|
|
|
it("rejects non-json output", () => {
|
|
expect(() => parseTruthDocUpdateDraft("not json", contextPack)).toThrow("Invalid JSON");
|
|
});
|
|
|
|
it("rejects claims without evidence IDs", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
claims: [{ ...validDraft.claims[0], evidenceIds: [] }],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("truth-doc-update-draft validation failed");
|
|
});
|
|
|
|
it("rejects unknown evidence IDs", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
claims: [{ ...validDraft.claims[0], evidenceIds: ["E2"] }],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("unknown evidence id");
|
|
});
|
|
|
|
it("rejects patch paths outside relevant docs", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
targetDocs: ["docs/truthmark/truth/other.md"],
|
|
patches: [{ ...validDraft.patches[0], path: "docs/truthmark/truth/other.md" }],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("patch path is not in relevant docs");
|
|
});
|
|
|
|
it("rejects path traversal", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
targetDocs: ["../outside.md"],
|
|
patches: [{ ...validDraft.patches[0], path: "../outside.md" }],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("unsafe doc path");
|
|
});
|
|
|
|
it("rejects targetDocs that do not match patch paths", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
targetDocs: ["docs/truthmark/truth/authentication/session-timeout.md"],
|
|
patches: [],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("targetDocs must match patch paths");
|
|
});
|
|
|
|
it("rejects drafted outputs without claims", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
claims: [],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("drafted output requires at least one claim");
|
|
});
|
|
|
|
it("rejects drafted outputs without patches", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
targetDocs: [],
|
|
patches: [],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("drafted output requires at least one patch");
|
|
});
|
|
|
|
it("rejects drafted outputs with unsupported claims", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
claims: [{ ...validDraft.claims[0], support: "unsupported" }],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("drafted output cannot contain unsupported claims");
|
|
});
|
|
|
|
it("rejects blocked outputs with claims or patches", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
...validDraft,
|
|
status: "blocked",
|
|
openQuestions: ["Need more evidence"],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("blocked output cannot include claims, target docs, or patches");
|
|
});
|
|
|
|
it("rejects blocked outputs without open questions", () => {
|
|
expect(() =>
|
|
parseTruthDocUpdateDraft(
|
|
JSON.stringify({
|
|
status: "blocked",
|
|
targetDocs: [],
|
|
claims: [],
|
|
patches: [],
|
|
openQuestions: [],
|
|
}),
|
|
contextPack,
|
|
),
|
|
).toThrow("blocked output requires at least one open question");
|
|
});
|
|
}); |