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>
This commit is contained in:
Mikael Hugo 2026-05-07 04:24:31 +02:00
parent f3761d7f46
commit 076e8c4894

View file

@ -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);
}