From 076e8c489428513e0573c2140a173af4603f515f Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Thu, 7 May 2026 04:24:31 +0200 Subject: [PATCH] Tier 1.3 Phase 3: Add evidence management API Implements data layer functions for managing and querying spec/evidence data. New export functions: - insertMilestoneEvidence(): Append evidence for milestone - insertSliceEvidence(): Append evidence for slice - insertTaskEvidence(): Append evidence for task - getMilestoneAuditTrail(): Query full audit trail (spec + evidence + runtime) - getSliceAuditTrail(): Query slice audit trail with joined spec/evidence - getTaskAuditTrail(): Query task audit trail with joined spec/evidence - getMilestoneSpec(): Get spec only (immutable intent) - getSliceSpec(): Get slice spec only - getTaskSpec(): Get task spec only Key properties: - Evidence functions use timestamp for recording time (set at insertion) - Audit trail queries JOIN runtime, spec, and evidence tables - All queries support data archaeology (reconstruct decision history) - Spec-only queries useful for validation and re-planning - All functions include JSDoc with purpose and consumer This completes Phase 3 of Tier 1.3 implementation. Phase 4 (tool updates) and Phase 5 (integration tests) follow in next PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/resources/extensions/sf/sf-db.js | 143 +++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index 3bf698982..b1979f8b9 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -5605,3 +5605,146 @@ export function deleteMemoryEmbedding(memoryId) { .run({ ":id": memoryId }); return (res?.changes ?? 0) > 0; } +// ─── Tier 1.3: Spec/Runtime/Evidence Schema ────────────────────────────────── +// Functions for managing evidence in the new spec schema (v32+) + +/** + * Record evidence for a milestone. Appends to milestone_evidence table. + * Purpose: Create audit trail of decisions, verifications, and incidents. + * Consumer: complete-milestone, reassess-milestone, and other tools. + */ +export function insertMilestoneEvidence(milestoneId, evidenceType, content, phaseName, recordedBy) { +if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); +currentDb +.prepare(`INSERT INTO milestone_evidence (milestone_id, evidence_type, content, recorded_at, phase_name, recorded_by) + VALUES (?, ?, ?, ?, ?, ?)`) +.run(milestoneId, evidenceType, content, new Date().toISOString(), phaseName || "", recordedBy || ""); +} + +/** + * Record evidence for a slice. Appends to slice_evidence table. + * Purpose: Create audit trail of slice decisions, verifications, and incidents. + * Consumer: complete-slice, execute-slice, and other tools. + */ +export function insertSliceEvidence(milestoneId, sliceId, evidenceType, content, phaseName, recordedBy) { +if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); +currentDb +.prepare(`INSERT INTO slice_evidence (milestone_id, slice_id, evidence_type, content, recorded_at, phase_name, recorded_by) + VALUES (?, ?, ?, ?, ?, ?, ?)`) +.run(milestoneId, sliceId, evidenceType, content, new Date().toISOString(), phaseName || "", recordedBy || ""); +} + +/** + * Record evidence for a task. Appends to task_evidence table. + * Purpose: Create audit trail of task decisions, verifications, and incidents. + * Consumer: complete-task, execute-task, and other tools. + */ +export function insertTaskEvidence(milestoneId, sliceId, taskId, evidenceType, content, phaseName, recordedBy) { +if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); +currentDb +.prepare(`INSERT INTO task_evidence (milestone_id, slice_id, task_id, evidence_type, content, recorded_at, phase_name, recorded_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`) +.run(milestoneId, sliceId, taskId, evidenceType, content, new Date().toISOString(), phaseName || "", recordedBy || ""); +} + +/** + * Query milestone audit trail (spec + evidence). Returns rows with spec intent and evidence history. + * Purpose: Support data archaeology and decision-tree reconstruction. + * Consumer: forensics tools, doctor checks, audit/compliance queries. + */ +export function getMilestoneAuditTrail(milestoneId) { +if (!currentDb) return []; +return currentDb +.prepare(` + SELECT + r.id, r.title, r.status, + s.vision, s.spec_version, + e.evidence_type, e.content, e.recorded_at, e.phase_name, e.recorded_by + FROM milestones r + LEFT JOIN milestone_specs s ON r.id = s.id + LEFT JOIN milestone_evidence e ON r.id = e.milestone_id + WHERE r.id = ? + ORDER BY e.recorded_at ASC + `) +.all(milestoneId); +} + +/** + * Query slice audit trail (spec + evidence). + * Purpose: Support data archaeology and decision-tree reconstruction. + * Consumer: forensics tools, doctor checks, audit/compliance queries. + */ +export function getSliceAuditTrail(milestoneId, sliceId) { +if (!currentDb) return []; +return currentDb +.prepare(` + SELECT + r.id, r.title, r.status, + s.goal, s.spec_version, + e.evidence_type, e.content, e.recorded_at, e.phase_name, e.recorded_by + FROM slices r + LEFT JOIN slice_specs s ON r.milestone_id = s.milestone_id AND r.id = s.slice_id + LEFT JOIN slice_evidence e ON r.milestone_id = e.milestone_id AND r.id = e.slice_id + WHERE r.milestone_id = ? AND r.id = ? + ORDER BY e.recorded_at ASC + `) +.all(milestoneId, sliceId); +} + +/** + * Query task audit trail (spec + evidence). + * Purpose: Support data archaeology and decision-tree reconstruction. + * Consumer: forensics tools, doctor checks, audit/compliance queries. + */ +export function getTaskAuditTrail(milestoneId, sliceId, taskId) { +if (!currentDb) return []; +return currentDb +.prepare(` + SELECT + r.id, r.title, r.status, + s.verify, s.spec_version, + e.evidence_type, e.content, e.recorded_at, e.phase_name, e.recorded_by + FROM tasks r + LEFT JOIN task_specs s ON r.milestone_id = s.milestone_id AND r.slice_id = s.slice_id AND r.id = s.task_id + LEFT JOIN task_evidence e ON r.milestone_id = e.milestone_id AND r.slice_id = e.slice_id AND r.id = e.task_id + WHERE r.milestone_id = ? AND r.slice_id = ? AND r.id = ? + ORDER BY e.recorded_at ASC + `) +.all(milestoneId, sliceId, taskId); +} + +/** + * Get milestone spec only (immutable intent, no runtime state). + * Purpose: Retrieve spec intent for re-planning or spec validation. + * Consumer: plan-milestone and spec validation tools. + */ +export function getMilestoneSpec(milestoneId) { +if (!currentDb) return null; +return currentDb +.prepare("SELECT * FROM milestone_specs WHERE id = ?") +.get(milestoneId); +} + +/** + * Get slice spec only (immutable intent, no runtime state). + * Purpose: Retrieve spec intent for re-planning or spec validation. + * Consumer: plan-slice and spec validation tools. + */ +export function getSliceSpec(milestoneId, sliceId) { +if (!currentDb) return null; +return currentDb +.prepare("SELECT * FROM slice_specs WHERE milestone_id = ? AND slice_id = ?") +.get(milestoneId, sliceId); +} + +/** + * Get task spec only (immutable intent, no runtime state). + * Purpose: Retrieve spec intent for re-planning or spec validation. + * Consumer: plan-task and spec validation tools. + */ +export function getTaskSpec(milestoneId, sliceId, taskId) { +if (!currentDb) return null; +return currentDb +.prepare("SELECT * FROM task_specs WHERE milestone_id = ? AND slice_id = ? AND task_id = ?") +.get(milestoneId, sliceId, taskId); +}