max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+119 insertions,
-0 deletions
| @@ -1,0 +1,119 @@ | |||
| 1 | + | -- Sando ships more than one product. Design: wiki [[sando-bento-boundary]]. | |
| 2 | + | -- | |
| 3 | + | -- Every table here was implicitly about the MNW server. `versions` keyed a | |
| 4 | + | -- version string with no room for two products releasing 0.4.1 in the same | |
| 5 | + | -- week; `tiers` keyed a tier name, so pom's `host` and MNW's `host` would be | |
| 6 | + | -- one row; `tier_state` recorded what is deployed with no way to say deployed | |
| 7 | + | -- *of what*. None of that was wrong while there was one product. All of it is | |
| 8 | + | -- wrong the moment there are two, and each collision would read as ordinary | |
| 9 | + | -- state rather than as an error. | |
| 10 | + | -- | |
| 11 | + | -- So `app` joins the key of everything that was keyed, and rides along on | |
| 12 | + | -- everything that was merely recorded. | |
| 13 | + | -- | |
| 14 | + | -- Existing rows backfill to 'mnw'. That is not a guess: this daemon has only | |
| 15 | + | -- ever deployed the MNW server, and a config with no [app.*] tables loads as | |
| 16 | + | -- that same id (see config.rs), so the rows and the config agree without | |
| 17 | + | -- anybody editing either. | |
| 18 | + | -- | |
| 19 | + | -- Four tables need their PRIMARY KEY widened, which SQLite cannot do in place, | |
| 20 | + | -- so they are rebuilt. `defer_foreign_keys` holds enforcement until COMMIT: | |
| 21 | + | -- mid-migration the referenced tables briefly do not exist, and every reference | |
| 22 | + | -- is satisfied again by the time the transaction closes. (`PRAGMA foreign_keys` | |
| 23 | + | -- itself is a no-op inside a transaction, which is why it is not used here.) | |
| 24 | + | ||
| 25 | + | PRAGMA defer_foreign_keys = ON; | |
| 26 | + | ||
| 27 | + | -- Tiers are per product. MNW's `a`/`b` and pom's tiers are different pipelines | |
| 28 | + | -- that happen to be spelled alike. | |
| 29 | + | CREATE TABLE tiers_new ( | |
| 30 | + | app TEXT NOT NULL, | |
| 31 | + | name TEXT NOT NULL, | |
| 32 | + | ord INTEGER NOT NULL, | |
| 33 | + | provisioned INTEGER NOT NULL DEFAULT 0, | |
| 34 | + | canary TEXT NOT NULL DEFAULT 'sequential', | |
| 35 | + | PRIMARY KEY (app, name) | |
| 36 | + | ); | |
| 37 | + | INSERT INTO tiers_new (app, name, ord, provisioned, canary) | |
| 38 | + | SELECT 'mnw', name, ord, provisioned, canary FROM tiers; | |
| 39 | + | ||
| 40 | + | -- A node name is only unique within its product: two products may each deploy | |
| 41 | + | -- to a node they both call `prod-1`, and they are not the same machine role. | |
| 42 | + | CREATE TABLE nodes_new ( | |
| 43 | + | app TEXT NOT NULL, | |
| 44 | + | name TEXT NOT NULL, | |
| 45 | + | tier TEXT NOT NULL, | |
| 46 | + | ssh_target TEXT NOT NULL, | |
| 47 | + | release_root TEXT NOT NULL, | |
| 48 | + | PRIMARY KEY (app, name), | |
| 49 | + | FOREIGN KEY (app, tier) REFERENCES tiers(app, name) | |
| 50 | + | ); | |
| 51 | + | INSERT INTO nodes_new (app, name, tier, ssh_target, release_root) | |
| 52 | + | SELECT 'mnw', name, tier, ssh_target, release_root FROM nodes; | |
| 53 | + | ||
| 54 | + | -- Two products releasing the same version string are two artifacts. | |
| 55 | + | CREATE TABLE versions_new ( | |
| 56 | + | app TEXT NOT NULL, | |
| 57 | + | version TEXT NOT NULL, | |
| 58 | + | git_sha TEXT NOT NULL, | |
| 59 | + | built_at TEXT NOT NULL, | |
| 60 | + | artifact_path TEXT NOT NULL, | |
| 61 | + | PRIMARY KEY (app, version) | |
| 62 | + | ); | |
| 63 | + | INSERT INTO versions_new (app, version, git_sha, built_at, artifact_path) | |
| 64 | + | SELECT 'mnw', version, git_sha, built_at, artifact_path FROM versions; | |
| 65 | + | ||
| 66 | + | -- What is deployed, per product per tier. Columns added since 001 are carried | |
| 67 | + | -- across explicitly rather than by SELECT *, so a future column cannot be | |
| 68 | + | -- silently dropped by this migration. | |
| 69 | + | CREATE TABLE tier_state_new ( | |
| 70 | + | app TEXT NOT NULL, | |
| 71 | + | tier TEXT NOT NULL, | |
| 72 | + | current_version TEXT, | |
| 73 | + | previous_version TEXT, | |
| 74 | + | burn_in_started_at TEXT, | |
| 75 | + | partial_reason TEXT, | |
| 76 | + | current_build_id INTEGER REFERENCES build_runs(id), | |
| 77 | + | previous_build_id INTEGER REFERENCES build_runs(id), | |
| 78 | + | advanced_at TEXT, | |
| 79 | + | PRIMARY KEY (app, tier), | |
| 80 | + | FOREIGN KEY (app, tier) REFERENCES tiers(app, name) | |
| 81 | + | ); | |
| 82 | + | INSERT INTO tier_state_new ( | |
| 83 | + | app, tier, current_version, previous_version, burn_in_started_at, | |
| 84 | + | partial_reason, current_build_id, previous_build_id, advanced_at | |
| 85 | + | ) | |
| 86 | + | SELECT 'mnw', tier, current_version, previous_version, burn_in_started_at, | |
| 87 | + | partial_reason, current_build_id, previous_build_id, advanced_at | |
| 88 | + | FROM tier_state; | |
| 89 | + | ||
| 90 | + | DROP TABLE tier_state; | |
| 91 | + | DROP TABLE nodes; | |
| 92 | + | DROP TABLE versions; | |
| 93 | + | DROP TABLE tiers; | |
| 94 | + | ||
| 95 | + | ALTER TABLE tiers_new RENAME TO tiers; | |
| 96 | + | ALTER TABLE nodes_new RENAME TO nodes; | |
| 97 | + | ALTER TABLE versions_new RENAME TO versions; | |
| 98 | + | ALTER TABLE tier_state_new RENAME TO tier_state; | |
| 99 | + | ||
| 100 | + | CREATE INDEX nodes_by_tier ON nodes(app, tier); | |
| 101 | + | ||
| 102 | + | -- The append-only tables keep their integer primary keys; `app` is a column, | |
| 103 | + | -- not part of an identity they already had. | |
| 104 | + | ALTER TABLE deploys ADD COLUMN app TEXT NOT NULL DEFAULT 'mnw'; | |
| 105 | + | ALTER TABLE gate_runs ADD COLUMN app TEXT NOT NULL DEFAULT 'mnw'; | |
| 106 | + | ALTER TABLE backups ADD COLUMN app TEXT NOT NULL DEFAULT 'mnw'; | |
| 107 | + | ALTER TABLE build_runs ADD COLUMN app TEXT NOT NULL DEFAULT 'mnw'; | |
| 108 | + | ||
| 109 | + | -- Every lookup that used to be by (tier, version) or by name is now by product | |
| 110 | + | -- first. Left as separate indexes from the 001 ones, which are dropped: an | |
| 111 | + | -- index that omits the leading column of the predicate cannot serve it. | |
| 112 | + | DROP INDEX IF EXISTS deploys_by_tier_version; | |
| 113 | + | DROP INDEX IF EXISTS gate_runs_lookup; | |
| 114 | + | DROP INDEX IF EXISTS backups_name_fetched; | |
| 115 | + | DROP INDEX IF EXISTS build_runs_by_sha; | |
| 116 | + | CREATE INDEX deploys_by_tier_version ON deploys(app, tier, version); | |
| 117 | + | CREATE INDEX gate_runs_lookup ON gate_runs(app, tier, version, gate_kind); | |
| 118 | + | CREATE INDEX backups_name_fetched ON backups(app, name, fetched_at); | |
| 119 | + | CREATE INDEX build_runs_by_sha ON build_runs(app, sha); |