diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index f45d44dd3..3bf698982 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -899,6 +899,60 @@ function columnExists(db, table, column) { function ensureColumn(db, table, column, ddl) { if (!columnExists(db, table, column)) db.exec(ddl); } +function populateSpecTablesFromExisting(db) { + // Tier 1.3 Phase 2: Migrate existing spec data to new spec tables + // This populates milestone_specs, slice_specs, task_specs from existing columns + // Evidence tables are left empty; they populate as tools create new evidence. + + const now = new Date().toISOString(); + + // Migrate milestone specs + db.prepare(` + INSERT OR IGNORE INTO milestone_specs ( + id, vision, success_criteria, key_risks, proof_strategy, + verification_contract, verification_integration, verification_operational, verification_uat, + definition_of_done, requirement_coverage, boundary_map_markdown, vision_meeting_json, + spec_version, created_at + ) + SELECT + id, vision, success_criteria, key_risks, proof_strategy, + verification_contract, verification_integration, verification_operational, verification_uat, + definition_of_done, requirement_coverage, boundary_map_markdown, vision_meeting_json, + 1, COALESCE(created_at, ?) + FROM milestones + WHERE id NOT IN (SELECT id FROM milestone_specs) + `).run(now); + + // Migrate slice specs + db.prepare(` + INSERT OR IGNORE INTO slice_specs ( + milestone_id, slice_id, goal, success_criteria, proof_level, + integration_closure, observability_impact, + adversarial_partner, adversarial_combatant, adversarial_architect, + planning_meeting_json, spec_version, created_at + ) + SELECT + milestone_id, id, goal, success_criteria, proof_level, + integration_closure, observability_impact, + adversarial_partner, adversarial_combatant, adversarial_architect, + planning_meeting_json, 1, COALESCE(created_at, ?) + FROM slices + WHERE (milestone_id, id) NOT IN (SELECT milestone_id, slice_id FROM slice_specs) + `).run(now); + + // Migrate task specs + db.prepare(` + INSERT OR IGNORE INTO task_specs ( + milestone_id, slice_id, task_id, verify, inputs, expected_output, + spec_version, created_at + ) + SELECT + milestone_id, slice_id, id, verify, inputs, expected_output, + 1, COALESCE(created_at, ?) + FROM tasks + WHERE (milestone_id, slice_id, id) NOT IN (SELECT milestone_id, slice_id, task_id FROM task_specs) + `).run(now); +} function migrateSchema(db) { const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get(); const currentVersion = row ? row["v"] : 0; @@ -1828,6 +1882,8 @@ function migrateSchema(db) { } if (currentVersion < 32) { ensureSpecSchemaTables(db); + // Populate spec tables from existing spec columns in runtime tables + populateSpecTablesFromExisting(db); db.prepare( "INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)", ).run({