sf snapshot: uncommitted changes after 32m inactivity
This commit is contained in:
parent
7318af029a
commit
533d1ce83c
1 changed files with 163 additions and 1 deletions
|
|
@ -114,7 +114,7 @@ function openRawDb(path) {
|
|||
loadProvider();
|
||||
return new DatabaseSync(path);
|
||||
}
|
||||
const SCHEMA_VERSION = 45;
|
||||
const SCHEMA_VERSION = 46;
|
||||
function indexExists(db, name) {
|
||||
return !!db
|
||||
.prepare(
|
||||
|
|
@ -2541,6 +2541,55 @@ function migrateSchema(db) {
|
|||
":applied_at": new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
if (currentVersion < 46) {
|
||||
// validation_runs: mirrors droid's validation-contract.md + validation-state.json
|
||||
// pattern. Each run stores the contract spec inline and its execution state.
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS validation_runs (
|
||||
run_id TEXT PRIMARY KEY,
|
||||
milestone_id TEXT NOT NULL,
|
||||
slice_id TEXT,
|
||||
task_id TEXT,
|
||||
contract TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
verdict TEXT NOT NULL DEFAULT '',
|
||||
rationale TEXT NOT NULL DEFAULT '',
|
||||
findings TEXT NOT NULL DEFAULT '',
|
||||
started_at TEXT,
|
||||
completed_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
superseded_by TEXT,
|
||||
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
||||
)
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_validation_runs_scope
|
||||
ON validation_runs(milestone_id, slice_id, task_id)
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE VIEW IF NOT EXISTS latest_validation_state AS
|
||||
SELECT vr.*
|
||||
FROM validation_runs vr
|
||||
INNER JOIN (
|
||||
SELECT milestone_id,
|
||||
COALESCE(slice_id, '') AS slice_id,
|
||||
COALESCE(task_id, '') AS task_id,
|
||||
MAX(created_at) AS max_created
|
||||
FROM validation_runs
|
||||
GROUP BY milestone_id, slice_id, task_id
|
||||
) latest
|
||||
ON vr.milestone_id = latest.milestone_id
|
||||
AND COALESCE(vr.slice_id, '') = latest.slice_id
|
||||
AND COALESCE(vr.task_id, '') = latest.task_id
|
||||
AND vr.created_at = latest.max_created
|
||||
`);
|
||||
db.prepare(
|
||||
"INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
|
||||
).run({
|
||||
":version": 46,
|
||||
":applied_at": new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
db.exec("COMMIT");
|
||||
} catch (err) {
|
||||
db.exec("ROLLBACK");
|
||||
|
|
@ -7215,3 +7264,116 @@ export function getTaskSpec(milestoneId, sliceId, taskId) {
|
|||
)
|
||||
.get(milestoneId, sliceId, taskId);
|
||||
}
|
||||
|
||||
// ─── Validation Runs ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Start a validation run for a milestone, slice, or task.
|
||||
* Mirrors droid's validation-state.json creation from validation-contract.md.
|
||||
*
|
||||
* Purpose: Track explicit validation contracts and their execution state in the
|
||||
* DB so any surface (CLI, TUI, headless) can answer "what are we validating and
|
||||
* where are we" with a single query.
|
||||
*
|
||||
* Consumer: autonomous-solver, plan-slice, quality gates, eval runners.
|
||||
*/
|
||||
export function startValidationRun({ milestoneId, sliceId, taskId, contract }) {
|
||||
if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
|
||||
const runId = crypto.randomUUID();
|
||||
currentDb
|
||||
.prepare(
|
||||
`INSERT INTO validation_runs
|
||||
(run_id, milestone_id, slice_id, task_id, contract, status, started_at)
|
||||
VALUES (:run_id, :milestone_id, :slice_id, :task_id, :contract, 'running', datetime('now'))`,
|
||||
)
|
||||
.run({
|
||||
":run_id": runId,
|
||||
":milestone_id": milestoneId,
|
||||
":slice_id": sliceId ?? null,
|
||||
":task_id": taskId ?? null,
|
||||
":contract": contract ?? "",
|
||||
});
|
||||
return runId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a validation run with verdict and findings.
|
||||
* Mirrors droid's update of validation-state.json after run finishes.
|
||||
*
|
||||
* Consumer: autonomous-solver after eval execution, quality gate evaluators.
|
||||
*/
|
||||
export function completeValidationRun({
|
||||
runId,
|
||||
verdict,
|
||||
rationale = "",
|
||||
findings = "",
|
||||
}) {
|
||||
if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
|
||||
currentDb
|
||||
.prepare(
|
||||
`UPDATE validation_runs SET
|
||||
status = :status,
|
||||
verdict = :verdict,
|
||||
rationale = :rationale,
|
||||
findings = :findings,
|
||||
completed_at = datetime('now')
|
||||
WHERE run_id = :run_id`,
|
||||
)
|
||||
.run({
|
||||
":run_id": runId,
|
||||
":status": verdict === "pass" ? "pass" : verdict === "fail" ? "fail" : "error",
|
||||
":verdict": verdict ?? "",
|
||||
":rationale": rationale ?? "",
|
||||
":findings": findings ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest validation state for a scope (milestone, slice, or task).
|
||||
* Returns the most recent run — mirrors droid's validation-state.json read.
|
||||
*
|
||||
* Consumer: any surface that needs "are we passing?" for a milestone/slice/task.
|
||||
*/
|
||||
export function getLatestValidationState(milestoneId, sliceId, taskId) {
|
||||
if (!currentDb) return null;
|
||||
const rows = currentDb
|
||||
.prepare(
|
||||
`SELECT * FROM validation_runs
|
||||
WHERE milestone_id = :milestone_id
|
||||
AND (:slice_id IS NULL OR slice_id = :slice_id)
|
||||
AND (:task_id IS NULL OR task_id = :task_id)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1`,
|
||||
)
|
||||
.all({
|
||||
":milestone_id": milestoneId,
|
||||
":slice_id": sliceId ?? null,
|
||||
":task_id": taskId ?? null,
|
||||
});
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation run history for a scope.
|
||||
* Mirrors droid's historical validation-state.json files.
|
||||
*
|
||||
* Consumer: forensics, eval review, audit trail queries.
|
||||
*/
|
||||
export function getValidationHistory(milestoneId, sliceId, taskId, limit = 20) {
|
||||
if (!currentDb) return [];
|
||||
return currentDb
|
||||
.prepare(
|
||||
`SELECT * FROM validation_runs
|
||||
WHERE milestone_id = :milestone_id
|
||||
AND (:slice_id IS NULL OR slice_id = :slice_id)
|
||||
AND (:task_id IS NULL OR task_id = :task_id)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT :limit`,
|
||||
)
|
||||
.all({
|
||||
":milestone_id": milestoneId,
|
||||
":slice_id": sliceId ?? null,
|
||||
":task_id": taskId ?? null,
|
||||
":limit": limit,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue