History

Rename integration.tools_revised_at → config_revised_at

Tools are materialized per connection (the tool rows are keyed by owner/integration/connection); an integration has no tools, it has the config that derives them. So the integration-side staleness marker is about the config that changed, not the tools — name it for the cause. The connection keeps tools_synced_at, which is accurate (connections do have tools). The comparison now reads tools_synced_at < config_revised_at: 'this connection's tools predate the current config'. Pre-release column, so the cloud migration + snapshot are edited in place; the libSQL/D1 hosts pick up the new name via boot schema-ensure.

Rhys Sullivan committed 4 months ago

Commit c03bdb11 parent 4803f0c8 files · +15 −15Browse files

Unified · Split

8 files
  1. apps/cloud/drizzle/0003_warm_jimmy_woo.sql +1 −1
  2. apps/cloud/drizzle/meta/0003_snapshot.json +2 −2
  3. apps/cloud/src/db/executor-schema.ts +1 −1
  4. e2e/cloud/spec-update-convergence.test.ts +1 −1
  5. packages/core/fumadb/src/adapters/drizzle/runtime-ensure.test.ts +3 −3
  6. packages/core/sdk/src/core-schema.ts +2 −2
  7. packages/core/sdk/src/executor.ts +4 −4
  8. vendor/emulate +1 −1
apps/cloud/drizzle/0003_warm_jimmy_woo.sql+1−1
@@ -1,4 +1,4 @@
1ALTER TABLE "connection" ADD COLUMN "description" text;--> statement-breakpoint1ALTER TABLE "connection" ADD COLUMN "description" text;--> statement-breakpoint
2ALTER TABLE "connection" ADD COLUMN "tools_synced_at" bigint;--> statement-breakpoint2ALTER TABLE "connection" ADD COLUMN "tools_synced_at" bigint;--> statement-breakpoint
3ALTER TABLE "integration" ADD COLUMN "name" text;--> statement-breakpoint3ALTER TABLE "integration" ADD COLUMN "name" text;--> statement-breakpoint
4ALTER TABLE "integration" ADD COLUMN "tools_revised_at" bigint;4ALTER TABLE "integration" ADD COLUMN "config_revised_at" bigint;
apps/cloud/drizzle/meta/0003_snapshot.json+2−2
@@ -497,8 +497,8 @@ · 496 unmodified lines
497 "primaryKey": false,497 "primaryKey": false,
498 "notNull": true498 "notNull": true
499 },499 },
500 "tools_revised_at": {500 "config_revised_at": {
501 "name": "tools_revised_at",501 "name": "config_revised_at",
502 "type": "bigint",502 "type": "bigint",
503 "primaryKey": false,503 "primaryKey": false,
504 "notNull": false504 "notNull": false
apps/cloud/src/db/executor-schema.ts+1−1
@@ -17,7 +17,7 @@ export const integration = pgTable( · 16 unmodified lines
17 plugin_id: text("plugin_id").notNull(),17 plugin_id: text("plugin_id").notNull(),
18 name: text("name"),18 name: text("name"),
19 description: text("description").notNull(),19 description: text("description").notNull(),
20 tools_revised_at: bigint("tools_revised_at", { mode: "bigint" }),20 config_revised_at: bigint("config_revised_at", { mode: "bigint" }),
21 config: json("config"),21 config: json("config"),
22 can_remove: boolean("can_remove").notNull().default(true),22 can_remove: boolean("can_remove").notNull().default(true),
23 can_refresh: boolean("can_refresh").notNull().default(false),23 can_refresh: boolean("can_refresh").notNull().default(false),
e2e/cloud/spec-update-convergence.test.ts+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// the new tool catalog on that member's next read — not just the editor's.3// the new tool catalog on that member's next read — not just the editor's.
4//4//
5// This is the lazy-convergence design. Tools are stored per connection. The5// This is the lazy-convergence design. Tools are stored per connection. The
6// editor stamps the integration's `tools_revised_at`, but the owner policy lets6// editor stamps the integration's `config_revised_at`, but the owner policy lets
7// them rebuild catalogs only in their own partition — they cannot write a7// them rebuild catalogs only in their own partition — they cannot write a
8// co-worker's personal connection rows. So each member's connection carries a8// co-worker's personal connection rows. So each member's connection carries a
9// `tools_synced_at` and rebuilds itself the next time THAT member lists tools.9// `tools_synced_at` and rebuilds itself the next time THAT member lists tools.
packages/core/fumadb/src/adapters/drizzle/runtime-ensure.test.ts+3−3
@@ -23,13 +23,13 @@ const v1Tables = { · 22 unmodified lines
23};23};
24 24
25// The running schema adds two nullable columns the old file predates — the25// The running schema adds two nullable columns the old file predates — the
26// shape of the integration-descriptions feature (name + tools_revised_at).26// shape of the integration-descriptions feature (name + config_revised_at).
27const v2Tables = {27const v2Tables = {
28 integration: table("integration", {28 integration: table("integration", {
29 id: idColumn("id", "varchar(255)"),29 id: idColumn("id", "varchar(255)"),
30 slug: column("slug", "varchar(255)"),30 slug: column("slug", "varchar(255)"),
31 name: column("name", "string").nullable(),31 name: column("name", "string").nullable(),
32 tools_revised_at: column("tools_revised_at", "bigint").nullable(),32 config_revised_at: column("config_revised_at", "bigint").nullable(),
33 }),33 }),
34};34};
35 35
@@ -69,7 +69,7 @@ test("ensure evolves an existing table with the schema's new nullable columns", · 33 unmodified lines
69 });69 });
70 const evolved = await columnNames(client);70 const evolved = await columnNames(client);
71 expect(evolved).toContain("name");71 expect(evolved).toContain("name");
72 expect(evolved).toContain("tools_revised_at");72 expect(evolved).toContain("config_revised_at");
73 73
74 // Idempotent: a second boot tolerates the now-duplicate columns.74 // Idempotent: a second boot tolerates the now-duplicate columns.
75 await ensureDrizzleRuntimeSchemaFromTables(db, {75 await ensureDrizzleRuntimeSchemaFromTables(db, {
packages/core/sdk/src/core-schema.ts+2−2
@@ -129,7 +129,7 @@ export const coreTables = defineTables({ · 128 unmodified lines
129 // template edit). Compared against each connection's `tools_synced_at`129 // template edit). Compared against each connection's `tools_synced_at`
130 // so OTHER subjects' connections — whose tool rows the updater cannot130 // so OTHER subjects' connections — whose tool rows the updater cannot
131 // write under the owner policy — lazily rebuild on their next read.131 // write under the owner policy — lazily rebuild on their next read.
132 tools_revised_at: nullableBigintColumn("tools_revised_at"),132 config_revised_at: nullableBigintColumn("config_revised_at"),
133 can_remove: boolColumn("can_remove", true),133 can_remove: boolColumn("can_remove", true),
134 can_refresh: boolColumn("can_refresh", false),134 can_refresh: boolColumn("can_refresh", false),
135 created_at: dateColumn("created_at"),135 created_at: dateColumn("created_at"),
@@ -156,7 +156,7 @@ export const coreTables = defineTables({ · 20 unmodified lines
156 // create, editable after; never reset by OAuth re-mints.156 // create, editable after; never reset by OAuth re-mints.
157 description: nullableTextColumn("description"),157 description: nullableTextColumn("description"),
158 // Epoch ms of the last tool (re)production for this connection. Stale158 // Epoch ms of the last tool (re)production for this connection. Stale
159 // vs the integration's `tools_revised_at` → re-produced on next read.159 // vs the integration's `config_revised_at` → re-produced on next read.
160 tools_synced_at: nullableBigintColumn("tools_synced_at"),160 tools_synced_at: nullableBigintColumn("tools_synced_at"),
161 oauth_client: nullableTextColumn("oauth_client"),161 oauth_client: nullableTextColumn("oauth_client"),
162 // The OWNER of `oauth_client` (a Personal connection may be minted through162 // The OWNER of `oauth_client` (a Personal connection may be minted through
packages/core/sdk/src/executor.ts+4−4
@@ -1759,7 +1759,7 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 1758 unmodified lines
1759 // rebuild catalogs in its own partition (owner policy), so revise1759 // rebuild catalogs in its own partition (owner policy), so revise
1760 // the integration: other subjects' connections compare this stamp1760 // the integration: other subjects' connections compare this stamp
1761 // against their `tools_synced_at` and lazily rebuild on next read.1761 // against their `tools_synced_at` and lazily rebuild on next read.
1762 set.tools_revised_at = now.getTime();1762 set.config_revised_at = now.getTime();
1763 }1763 }
1764 yield* core.updateMany("integration", {1764 yield* core.updateMany("integration", {
1765 where: (b: AnyCb) => b("slug", "=", String(slug)),1765 where: (b: AnyCb) => b("slug", "=", String(slug)),
@@ -1837,7 +1837,7 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 71 unmodified lines
1837 b("connection", "=", String(ref.name)),1837 b("connection", "=", String(ref.name)),
1838 );1838 );
1839 // Every exit stamps the sync time — including the cleanup paths that1839 // Every exit stamps the sync time — including the cleanup paths that
1840 // produce zero tools — so the stale-catalog check (`tools_revised_at`1840 // produce zero tools — so the stale-catalog check (`config_revised_at`
1841 // vs `tools_synced_at`) doesn't re-attempt this connection per read.1841 // vs `tools_synced_at`) doesn't re-attempt this connection per read.
1842 const stampSynced = core.updateMany("connection", {1842 const stampSynced = core.updateMany("connection", {
1843 where: (b: AnyCb) =>1843 where: (b: AnyCb) =>
@@ -2403,11 +2403,11 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 559 unmodified lines
2403 // catalog in place and retries on the next read.2403 // catalog in place and retries on the next read.
2404 const syncStaleConnectionTools = Effect.gen(function* () {2404 const syncStaleConnectionTools = Effect.gen(function* () {
2405 const revised = yield* core.findMany("integration", {2405 const revised = yield* core.findMany("integration", {
2406 where: (b: AnyCb) => b.isNotNull("tools_revised_at"),2406 where: (b: AnyCb) => b.isNotNull("config_revised_at"),
2407 });2407 });
2408 if (revised.length === 0) return;2408 if (revised.length === 0) return;
2409 const revisedAt = new Map(2409 const revisedAt = new Map(
2410 revised.map((row) => [row.slug, Number(row.tools_revised_at)] as const),2410 revised.map((row) => [row.slug, Number(row.config_revised_at)] as const),
2411 );2411 );
2412 const connections = yield* core.findMany("connection", {2412 const connections = yield* core.findMany("connection", {
2413 where: (b: AnyCb) => b.or(...revised.map((row) => b("integration", "=", row.slug))),2413 where: (b: AnyCb) => b.or(...revised.map((row) => b("integration", "=", row.slug))),
vendor/emulate+1−1
@@ -1 +1 @@
1Subproject commit 894206f09681d77f1d9d708a8d49f1c7dc72fa1f1Subproject commit 36638da8c104e92cb3522e0dca336618cdfd8205