M0007_ParkingGuard.vala 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. using InvercargillSql.Migrations;
  2. using Sqlite;
  3. namespace ClankerSaddle.Data.Migrations {
  4. /**
  5. * Goal 27 refinements — the parking guard. `saddle suspend` on a
  6. * goal run that owns NO live task is refused by the web (there is
  7. * nothing whose terminal transition could ever wake the parked
  8. * agent): the park mechanics still run, but the run carries a
  9. * one-shot `pending_resume_notice`, so the daemon's next ask hands
  10. * it the refusal instruction instead of a sleep nothing ends.
  11. *
  12. * tasks.pending_resume_notice is that one-shot channel. It is set
  13. * by the refused park and consumed by the allocation that hands
  14. * the assignment out (the resume_notice discipline): while set,
  15. * a suspended task is immediately offerable — the exact lift the
  16. * task-level force_wake flag gives a plain parked task, but
  17. * carrying the guard's own instruction text. Nullable, NULL when
  18. * no refusal is pending.
  19. *
  20. * IDEMPOTENCE: same contract as M0002–M0006 — up() runs before the
  21. * apply transaction opens (the builder only queues), so it
  22. * introspects sqlite_master through a separate read-only handle
  23. * and adds the column only when the schema still lacks it.
  24. */
  25. public class M0007_ParkingGuard : Migration {
  26. public override string name { get { return "Parking guard"; } }
  27. public override uint64 serial { get { return 7; } }
  28. public override string migration_namespace { get { return "clanker-saddle"; } }
  29. // Same resolution as M0006: the runner never hands up() a
  30. // connection, so the schema is read through a read-only handle
  31. // on the SPRY_DB_PATH main.vala connects to.
  32. private string db_path() {
  33. var path = Environment.get_variable("SPRY_DB_PATH") ?? "";
  34. if (path.strip().length == 0) {
  35. return "./clanker-saddle.sqlite";
  36. }
  37. return path;
  38. }
  39. private bool column_exists() {
  40. Database db;
  41. int rc = Database.open_v2(db_path(), out db, Sqlite.OPEN_READONLY);
  42. if (rc != Sqlite.OK) {
  43. return false;
  44. }
  45. Statement statement;
  46. rc = db.prepare_v2(
  47. "select 1 from pragma_table_info('tasks') where name = 'pending_resume_notice'",
  48. -1, out statement);
  49. if (rc != Sqlite.OK) {
  50. return false;
  51. }
  52. return statement.step() == Sqlite.ROW;
  53. }
  54. public override void up(MigrationBuilder b) {
  55. if (column_exists()) {
  56. return;
  57. }
  58. b.alter_table("tasks", (t) => {
  59. t.add_column<string>("pending_resume_notice");
  60. });
  61. }
  62. public override void down(MigrationBuilder b) {
  63. // SQLite cannot drop a column through the builder's
  64. // alter_table path; the raw statement is the honest form.
  65. b.execute_sql("alter table tasks drop column pending_resume_notice");
  66. }
  67. }
  68. }