M0001_Initial.vala 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. using InvercargillSql.Migrations;
  2. namespace ClankerSaddle.Data.Migrations {
  3. /**
  4. * The full ClankerSaddle schema in one migration (plan 11: every
  5. * earlier migration flattened — the database is not deployed, so
  6. * tear-down-and-recreate replaced incremental evolution; plan 17
  7. * flattened again over the force-wake era). Covers: projects and
  8. * their links; the plan-12 goals (with links — plan 15 C1/C2
  9. * deleted the goal-side block/comment tables: the goal agent's
  10. * blocks and comments ride the run task's own rows) and the
  11. * flattened task shape (`goal_id`, `kind`, `mode`, `creator`,
  12. * `enqueue_after_task_id`, plus the force-wake flags folded in
  13. * from the deleted M0002); tasks with comments and typed blocks;
  14. * saddle registrations with git identity and the per-saddle
  15. * token; sessions; the plan-17 UNIFIED EVENT LEDGER (`events`)
  16. * that replaced `session_commands`, `task_progress`,
  17. * `session_raw_log` and `saddle_entry_dedup` — every timeline,
  18. * narrative, resume-notice and log-view surface derives from it,
  19. * and daemon chunk pushes are idempotent on its
  20. * UNIQUE(session_id, seq); rules and their attachments; project
  21. * notes; the dashboard scratchpad notes (goal 8 task 3);
  22. * capability tags; the plan-11 notification tables.
  23. */
  24. public class M0001_Initial : Migration {
  25. public override string name { get { return "Create the ClankerSaddle schema"; } }
  26. public override uint64 serial { get { return 1; } }
  27. public override string migration_namespace { get { return "clanker-saddle"; } }
  28. public override void up(MigrationBuilder b) {
  29. b.create_table("projects", (t) => {
  30. t.column<int64?>("id")
  31. .auto_increment()
  32. .primary_key();
  33. t.column<string>("name")
  34. .not_null();
  35. t.column<string>("git_url")
  36. .not_null();
  37. t.column<string>("default_branch")
  38. .default_value<string>("main");
  39. t.column<string>("summary");
  40. t.column<DateTime?>("created_at");
  41. });
  42. b.create_table("project_links", (t) => {
  43. t.column<int64?>("id")
  44. .auto_increment()
  45. .primary_key();
  46. t.column<int64?>("parent_project_id")
  47. .not_null()
  48. .references("projects", "id");
  49. t.column<int64?>("linked_project_id")
  50. .not_null()
  51. .references("projects", "id");
  52. t.column<string>("relationship")
  53. .not_null();
  54. });
  55. // Stacks (goal 8 task 2): a named bundle of reference
  56. // projects applied one-click to a project's or a goal's
  57. // link set — the common reference collections a fleet of
  58. // repos always wants attached.
  59. b.create_table("stacks", (t) => {
  60. t.column<int64?>("id")
  61. .auto_increment()
  62. .primary_key();
  63. t.column<string>("name")
  64. .not_null()
  65. .unique();
  66. t.column<string>("summary");
  67. t.column<DateTime?>("created_at");
  68. });
  69. b.create_table("stack_projects", (t) => {
  70. t.column<int64?>("id")
  71. .auto_increment()
  72. .primary_key();
  73. t.column<int64?>("stack_id")
  74. .not_null()
  75. .references("stacks", "id")
  76. .indexed();
  77. t.column<int64?>("project_id")
  78. .not_null()
  79. .references("projects", "id")
  80. .indexed();
  81. t.column<string>("relationship")
  82. .not_null()
  83. .default_value<string>("reference");
  84. t.column<DateTime?>("created_at");
  85. t.unique("uq_stack_projects_pair", {"stack_id", "project_id"});
  86. });
  87. b.create_table("goals", (t) => {
  88. t.column<int64?>("id")
  89. .auto_increment()
  90. .primary_key();
  91. t.column<int64?>("project_id")
  92. .not_null()
  93. .references("projects", "id")
  94. .indexed();
  95. t.column<string>("title")
  96. .not_null();
  97. t.column<string>("brief")
  98. .not_null();
  99. t.column<string>("branch_name")
  100. .not_null();
  101. t.column<string>("base_branch");
  102. t.column<string>("status")
  103. .default_value<string>("draft")
  104. .indexed();
  105. t.column<int64?>("priority")
  106. .default_value<int64?>(3);
  107. t.column<string>("plan");
  108. t.column<string>("completion_report");
  109. t.column<string>("creator")
  110. .default_value<string>("human");
  111. t.column<DateTime?>("last_terminal_watermark");
  112. t.column<DateTime?>("created_at");
  113. t.column<DateTime?>("started_at");
  114. t.column<DateTime?>("completed_at");
  115. t.column<DateTime?>("paused_at");
  116. t.column<bool>("force_wake")
  117. .default_value<bool>(false);
  118. });
  119. b.create_table("goal_links", (t) => {
  120. t.column<int64?>("id")
  121. .auto_increment()
  122. .primary_key();
  123. t.column<int64?>("goal_id")
  124. .not_null()
  125. .references("goals", "id")
  126. .indexed();
  127. t.column<int64?>("project_id")
  128. .not_null()
  129. .references("projects", "id")
  130. .indexed();
  131. t.column<string>("relationship")
  132. .not_null();
  133. });
  134. b.create_table("tasks", (t) => {
  135. t.column<int64?>("id")
  136. .auto_increment()
  137. .primary_key();
  138. // Goal 8 task 4: 0 = a STANDALONE task — no project
  139. // row exists, the workspace holds no clones and the
  140. // deliverable is the completion report. No FK (0 is
  141. // not a project id) and no index (every browse path
  142. // filters by goal or lists all).
  143. t.column<int64?>("project_id")
  144. .not_null()
  145. .default_value<int64?>(0);
  146. t.column<int64?>("goal_id")
  147. .references("goals", "id")
  148. .indexed();
  149. t.column<string>("kind")
  150. .default_value<string>("task");
  151. t.column<string>("mode")
  152. .default_value<string>("implement");
  153. t.column<string>("creator")
  154. .default_value<string>("human");
  155. t.column<int64?>("enqueue_after_task_id")
  156. .references("tasks", "id");
  157. t.column<string>("title")
  158. .not_null();
  159. t.column<string>("description");
  160. t.column<string>("status")
  161. .default_value<string>("draft")
  162. .indexed();
  163. t.column<int64?>("priority")
  164. .default_value<int64?>(5);
  165. t.column<string>("summary");
  166. t.column<string>("task_branch");
  167. t.column<string>("branch_from");
  168. t.column<DateTime?>("created_at");
  169. t.column<DateTime?>("started_at");
  170. t.column<DateTime?>("completed_at");
  171. t.column<DateTime?>("suspended_at");
  172. t.column<bool>("force_wake")
  173. .default_value<bool>(false);
  174. });
  175. b.create_table("task_comments", (t) => {
  176. t.column<int64?>("id")
  177. .auto_increment()
  178. .primary_key();
  179. t.column<int64?>("task_id")
  180. .not_null()
  181. .references("tasks", "id")
  182. .indexed();
  183. t.column<string>("author")
  184. .not_null();
  185. t.column<string>("body")
  186. .not_null();
  187. t.column<string>("quote");
  188. t.column<DateTime?>("created_at");
  189. });
  190. b.create_table("task_blocks", (t) => {
  191. t.column<int64?>("id")
  192. .auto_increment()
  193. .primary_key();
  194. t.column<int64?>("task_id")
  195. .not_null()
  196. .references("tasks", "id")
  197. .indexed();
  198. t.column<string>("question")
  199. .not_null();
  200. t.column<string>("answer");
  201. t.column<string>("type")
  202. .default_value<string>("question");
  203. t.column<string>("options");
  204. t.column<string>("body");
  205. t.column<DateTime?>("answered_at");
  206. t.column<string>("answered_by");
  207. t.column<DateTime?>("created_at");
  208. });
  209. b.create_table("saddles", (t) => {
  210. t.column<int64?>("id")
  211. .auto_increment()
  212. .primary_key();
  213. t.column<string>("name")
  214. .not_null();
  215. t.column<string>("host")
  216. .not_null();
  217. t.column<string>("status")
  218. .default_value<string>("idle");
  219. t.column<string>("git_name");
  220. t.column<string>("git_email");
  221. t.column<string>("prompt_note");
  222. t.column<string>("ssh_public_key");
  223. t.column<string>("token");
  224. // goal 8 task 6: the workspace root the daemon reported at
  225. // /register — assignment paths are addressed on the ASKING
  226. // saddle's filesystem, not a shared /workspace default
  227. t.column<string>("workspace_root");
  228. });
  229. b.create_table("sessions", (t) => {
  230. t.column<int64?>("id")
  231. .auto_increment()
  232. .primary_key();
  233. t.column<int64?>("task_id")
  234. .not_null()
  235. .references("tasks", "id")
  236. .indexed();
  237. t.column<int64?>("saddle_id")
  238. .not_null()
  239. .references("saddles", "id");
  240. t.column<DateTime?>("started_at");
  241. t.column<DateTime?>("ended_at");
  242. t.column<string>("outcome");
  243. t.column<string>("resume_notice");
  244. });
  245. // Plan 17 §7.3: the unified append-only event ledger.
  246. // Daemon events carry their session and its monotonic seq
  247. // (idempotent upsert on UNIQUE(session_id, seq) — the
  248. // entire dedup machinery in one constraint); web-originated
  249. // events carry neither (NULL session_id rows never collide
  250. // under SQLite's NULLs-distinct unique indexes). Everything
  251. // downstream — timelines, goal narratives, resume-notice
  252. // coalescing, the event log view — derives from this table.
  253. b.create_table("events", (t) => {
  254. t.column<int64?>("id")
  255. .auto_increment()
  256. .primary_key();
  257. t.column<int64?>("session_id")
  258. .references("sessions", "id");
  259. t.column<int64?>("seq");
  260. t.column<string>("kind")
  261. .not_null();
  262. t.column<string>("actor")
  263. .not_null();
  264. t.column<string>("scope_type")
  265. .not_null();
  266. t.column<int64?>("scope_id")
  267. .not_null();
  268. t.column<string>("payload")
  269. .not_null();
  270. t.column<DateTime?>("created_at")
  271. .not_null();
  272. t.unique("uq_events_session_seq", {"session_id", "seq"});
  273. });
  274. b.create_table("rules", (t) => {
  275. t.column<int64?>("id")
  276. .auto_increment()
  277. .primary_key();
  278. t.column<string>("name")
  279. .not_null()
  280. .unique();
  281. t.column<string>("body")
  282. .not_null();
  283. t.column<DateTime?>("created_at");
  284. t.column<DateTime?>("updated_at");
  285. });
  286. b.create_table("rule_projects", (t) => {
  287. t.column<int64?>("id")
  288. .auto_increment()
  289. .primary_key();
  290. t.column<int64?>("rule_id")
  291. .not_null()
  292. .references("rules", "id")
  293. .indexed();
  294. t.column<int64?>("project_id")
  295. .not_null()
  296. .references("projects", "id")
  297. .indexed();
  298. t.column<DateTime?>("created_at");
  299. t.unique("uq_rule_projects_pair", {"rule_id", "project_id"});
  300. });
  301. b.create_table("task_rules", (t) => {
  302. t.column<int64?>("id")
  303. .auto_increment()
  304. .primary_key();
  305. t.column<int64?>("rule_id")
  306. .not_null()
  307. .references("rules", "id")
  308. .indexed();
  309. t.column<int64?>("task_id")
  310. .not_null()
  311. .references("tasks", "id")
  312. .indexed();
  313. t.column<DateTime?>("created_at");
  314. t.unique("uq_task_rules_pair", {"rule_id", "task_id"});
  315. });
  316. b.create_table("project_notes", (t) => {
  317. t.column<int64?>("id")
  318. .auto_increment()
  319. .primary_key();
  320. t.column<int64?>("project_id")
  321. .not_null()
  322. .references("projects", "id")
  323. .indexed();
  324. t.column<int64?>("source_task_id")
  325. .references("tasks", "id");
  326. t.column<string>("body")
  327. .not_null();
  328. t.column<DateTime?>("created_at");
  329. });
  330. // Personal scratchpad notes (goal 8 task 3): a lightweight
  331. // todo/idea list on the dashboard — jotted down before a
  332. // batch of changes is ready to become a full goal. A note
  333. // promotes into a DRAFT goal (title = first line, brief =
  334. // the whole body); goal_id then links the created goal and
  335. // freezes the note (no further promote).
  336. b.create_table("notes", (t) => {
  337. t.column<int64?>("id")
  338. .auto_increment()
  339. .primary_key();
  340. t.column<string>("body")
  341. .not_null();
  342. t.column<bool>("done")
  343. .not_null()
  344. .default_value<bool>(false);
  345. t.column<DateTime?>("done_at");
  346. t.column<int64?>("goal_id")
  347. .references("goals", "id");
  348. t.column<DateTime?>("created_at");
  349. });
  350. b.create_table("capabilities", (t) => {
  351. t.column<int64?>("id")
  352. .auto_increment()
  353. .primary_key();
  354. t.column<string>("name")
  355. .not_null()
  356. .unique();
  357. t.column<DateTime?>("created_at");
  358. });
  359. b.create_table("saddle_capabilities", (t) => {
  360. t.column<int64?>("id")
  361. .auto_increment()
  362. .primary_key();
  363. t.column<int64?>("capability_id")
  364. .not_null()
  365. .references("capabilities", "id")
  366. .indexed();
  367. t.column<int64?>("saddle_id")
  368. .not_null()
  369. .references("saddles", "id")
  370. .indexed();
  371. t.unique("uq_saddle_capabilities_pair", {"capability_id", "saddle_id"});
  372. });
  373. b.create_table("task_capabilities", (t) => {
  374. t.column<int64?>("id")
  375. .auto_increment()
  376. .primary_key();
  377. t.column<int64?>("capability_id")
  378. .not_null()
  379. .references("capabilities", "id")
  380. .indexed();
  381. t.column<int64?>("task_id")
  382. .not_null()
  383. .references("tasks", "id")
  384. .indexed();
  385. t.unique("uq_task_capabilities_pair", {"capability_id", "task_id"});
  386. });
  387. b.create_table("push_subscriptions", (t) => {
  388. t.column<int64?>("id")
  389. .auto_increment()
  390. .primary_key();
  391. t.column<int64?>("user_id")
  392. .not_null()
  393. .indexed();
  394. t.column<string>("endpoint")
  395. .not_null()
  396. .unique();
  397. t.column<string>("p256dh")
  398. .not_null();
  399. t.column<string>("auth")
  400. .not_null();
  401. t.column<DateTime?>("created_at");
  402. });
  403. b.create_table("task_notify_prefs", (t) => {
  404. t.column<int64?>("id")
  405. .auto_increment()
  406. .primary_key();
  407. t.column<int64?>("task_id")
  408. .not_null()
  409. .references("tasks", "id")
  410. .indexed();
  411. t.column<int64?>("user_id")
  412. .not_null()
  413. .indexed();
  414. t.unique("uq_task_notify_prefs_pair", {"task_id", "user_id"});
  415. });
  416. }
  417. public override void down(MigrationBuilder b) {
  418. b.drop_table("task_notify_prefs");
  419. b.drop_table("push_subscriptions");
  420. b.drop_table("task_capabilities");
  421. b.drop_table("saddle_capabilities");
  422. b.drop_table("capabilities");
  423. b.drop_table("notes");
  424. b.drop_table("project_notes");
  425. b.drop_table("task_rules");
  426. b.drop_table("rule_projects");
  427. b.drop_table("rules");
  428. b.drop_table("events");
  429. b.drop_table("sessions");
  430. b.drop_table("saddles");
  431. b.drop_table("task_blocks");
  432. b.drop_table("task_comments");
  433. b.drop_table("tasks");
  434. b.drop_table("goal_links");
  435. b.drop_table("goals");
  436. b.drop_table("project_links");
  437. b.drop_table("stack_projects");
  438. b.drop_table("stacks");
  439. b.drop_table("projects");
  440. }
  441. }
  442. }