History

Replace openapi output-schema migration script with a drizzle SQL migration

The envelope unwrap is mechanical jsonb surgery, so it belongs in the regular migration chain (db:migrate:prod) rather than a hand-run script: - 0002_unwrap_openapi_output_envelope.sql rewrites envelope-shaped openapi tool output schemas to the payload schema (NULL when the envelope carried an empty data schema), using structural jsonb comparisons casted from the json column. Rows produced before the envelope existed don't match the predicates; re-running is a no-op.

Rhys Sullivan committed 5 months ago

Commit 18826551 parent b4b89025 files · +1250 −108Browse files

Unified · Split

5 files
  1. apps/cloud/drizzle/0002_unwrap_openapi_output_envelope.sql +23 −0
  2. apps/cloud/drizzle/meta/0002_snapshot.json +1220 −0
  3. apps/cloud/drizzle/meta/_journal.json +7 −0
  4. apps/cloud/package.json +0 −2
  5. apps/cloud/scripts/migrate-openapi-output-schemas.ts +0 −106
apps/cloud/drizzle/0002_unwrap_openapi_output_envelope.sql+23−0
@@ -0,0 +1,23 @@
1-- Unwrap the retired {status, headers, data} transport envelope from
2-- persisted OpenAPI tool output schemas. The runtime now returns the
3-- upstream payload as `data` (status/headers moved to the ToolResult
4-- `http` side channel), so persisted schemas must describe the payload
5-- only. Rows produced before the envelope existed (pre-#854) are already
6-- payload-shaped and don't match the predicates; re-running is a no-op.
7--
8-- The old producer emitted `"data": {}` when an operation declared no
9-- response schema; the new producer persists NULL for those.
10--
11-- output_schema is a `json` column, so structural comparisons cast to
12-- jsonb (json has no equality operator).
13UPDATE "tool"
14SET "output_schema" = CASE
15 WHEN ("output_schema" -> 'properties' -> 'data')::jsonb = '{}'::jsonb THEN NULL
16 ELSE "output_schema" -> 'properties' -> 'data'
17END
18WHERE "plugin_id" = 'openapi'
19 AND "output_schema" IS NOT NULL
20 AND "output_schema" ->> 'type' = 'object'
21 AND ("output_schema" -> 'required')::jsonb = '["status", "headers", "data"]'::jsonb
22 AND ("output_schema" -> 'properties' -> 'status')::jsonb = '{"type": "integer"}'::jsonb
23 AND ("output_schema" -> 'properties' -> 'headers')::jsonb = '{"type": "object", "additionalProperties": {"type": "string"}}'::jsonb;
apps/cloud/drizzle/meta/0002_snapshot.json+1220−0
@@ -0,0 +1,1220 @@
1{
2 "id": "288497ac-d4ea-4e84-9426-ce46d01b909e",
3 "prevId": "c4b68576-7550-4ed0-98df-69072f0ec71e",
4 "version": "7",
5 "dialect": "postgresql",
6 "tables": {
7 "public.accounts": {
8 "name": "accounts",
9 "schema": "",
10 "columns": {
11 "id": {
12 "name": "id",
13 "type": "text",
14 "primaryKey": true,
15 "notNull": true
16 },
17 "created_at": {
18 "name": "created_at",
19 "type": "timestamp with time zone",
20 "primaryKey": false,
21 "notNull": true,
22 "default": "now()"
23 }
24 },
25 "indexes": {},
26 "foreignKeys": {},
27 "compositePrimaryKeys": {},
28 "uniqueConstraints": {},
29 "policies": {},
30 "checkConstraints": {},
31 "isRLSEnabled": false
32 },
33 "public.memberships": {
34 "name": "memberships",
35 "schema": "",
36 "columns": {
37 "account_id": {
38 "name": "account_id",
39 "type": "text",
40 "primaryKey": false,
41 "notNull": true
42 },
43 "organization_id": {
44 "name": "organization_id",
45 "type": "text",
46 "primaryKey": false,
47 "notNull": true
48 },
49 "created_at": {
50 "name": "created_at",
51 "type": "timestamp with time zone",
52 "primaryKey": false,
53 "notNull": true,
54 "default": "now()"
55 }
56 },
57 "indexes": {},
58 "foreignKeys": {
59 "memberships_account_id_accounts_id_fk": {
60 "name": "memberships_account_id_accounts_id_fk",
61 "tableFrom": "memberships",
62 "columnsFrom": ["account_id"],
63 "tableTo": "accounts",
64 "columnsTo": ["id"],
65 "onUpdate": "no action",
66 "onDelete": "cascade"
67 },
68 "memberships_organization_id_organizations_id_fk": {
69 "name": "memberships_organization_id_organizations_id_fk",
70 "tableFrom": "memberships",
71 "columnsFrom": ["organization_id"],
72 "tableTo": "organizations",
73 "columnsTo": ["id"],
74 "onUpdate": "no action",
75 "onDelete": "cascade"
76 }
77 },
78 "compositePrimaryKeys": {
79 "memberships_account_id_organization_id_pk": {
80 "name": "memberships_account_id_organization_id_pk",
81 "columns": ["account_id", "organization_id"]
82 }
83 },
84 "uniqueConstraints": {},
85 "policies": {},
86 "checkConstraints": {},
87 "isRLSEnabled": false
88 },
89 "public.organizations": {
90 "name": "organizations",
91 "schema": "",
92 "columns": {
93 "id": {
94 "name": "id",
95 "type": "text",
96 "primaryKey": true,
97 "notNull": true
98 },
99 "name": {
100 "name": "name",
101 "type": "text",
102 "primaryKey": false,
103 "notNull": true
104 },
105 "created_at": {
106 "name": "created_at",
107 "type": "timestamp with time zone",
108 "primaryKey": false,
109 "notNull": true,
110 "default": "now()"
111 }
112 },
113 "indexes": {},
114 "foreignKeys": {},
115 "compositePrimaryKeys": {},
116 "uniqueConstraints": {},
117 "policies": {},
118 "checkConstraints": {},
119 "isRLSEnabled": false
120 },
121 "public.blob": {
122 "name": "blob",
123 "schema": "",
124 "columns": {
125 "namespace": {
126 "name": "namespace",
127 "type": "varchar(255)",
128 "primaryKey": false,
129 "notNull": true
130 },
131 "key": {
132 "name": "key",
133 "type": "varchar(255)",
134 "primaryKey": false,
135 "notNull": true
136 },
137 "value": {
138 "name": "value",
139 "type": "text",
140 "primaryKey": false,
141 "notNull": true
142 },
143 "row_id": {
144 "name": "row_id",
145 "type": "varchar(255)",
146 "primaryKey": true,
147 "notNull": true
148 },
149 "id": {
150 "name": "id",
151 "type": "varchar(255)",
152 "primaryKey": false,
153 "notNull": true
154 }
155 },
156 "indexes": {
157 "blob_id_uidx": {
158 "name": "blob_id_uidx",
159 "columns": [
160 {
161 "expression": "id",
162 "isExpression": false,
163 "asc": true,
164 "nulls": "last"
165 }
166 ],
167 "isUnique": true,
168 "with": {},
169 "method": "btree",
170 "concurrently": false
171 }
172 },
173 "foreignKeys": {},
174 "compositePrimaryKeys": {},
175 "uniqueConstraints": {},
176 "policies": {},
177 "checkConstraints": {},
178 "isRLSEnabled": false
179 },
180 "public.connection": {
181 "name": "connection",
182 "schema": "",
183 "columns": {
184 "integration": {
185 "name": "integration",
186 "type": "varchar(255)",
187 "primaryKey": false,
188 "notNull": true
189 },
190 "name": {
191 "name": "name",
192 "type": "varchar(255)",
193 "primaryKey": false,
194 "notNull": true
195 },
196 "template": {
197 "name": "template",
198 "type": "text",
199 "primaryKey": false,
200 "notNull": true
201 },
202 "provider": {
203 "name": "provider",
204 "type": "text",
205 "primaryKey": false,
206 "notNull": true
207 },
208 "item_ids": {
209 "name": "item_ids",
210 "type": "json",
211 "primaryKey": false,
212 "notNull": true
213 },
214 "identity_label": {
215 "name": "identity_label",
216 "type": "text",
217 "primaryKey": false,
218 "notNull": false
219 },
220 "oauth_client": {
221 "name": "oauth_client",
222 "type": "text",
223 "primaryKey": false,
224 "notNull": false
225 },
226 "oauth_client_owner": {
227 "name": "oauth_client_owner",
228 "type": "text",
229 "primaryKey": false,
230 "notNull": false
231 },
232 "refresh_item_id": {
233 "name": "refresh_item_id",
234 "type": "text",
235 "primaryKey": false,
236 "notNull": false
237 },
238 "expires_at": {
239 "name": "expires_at",
240 "type": "bigint",
241 "primaryKey": false,
242 "notNull": false
243 },
244 "oauth_scope": {
245 "name": "oauth_scope",
246 "type": "text",
247 "primaryKey": false,
248 "notNull": false
249 },
250 "provider_state": {
251 "name": "provider_state",
252 "type": "json",
253 "primaryKey": false,
254 "notNull": false
255 },
256 "created_at": {
257 "name": "created_at",
258 "type": "timestamp",
259 "primaryKey": false,
260 "notNull": true
261 },
262 "updated_at": {
263 "name": "updated_at",
264 "type": "timestamp",
265 "primaryKey": false,
266 "notNull": true
267 },
268 "row_id": {
269 "name": "row_id",
270 "type": "varchar(255)",
271 "primaryKey": true,
272 "notNull": true
273 },
274 "tenant": {
275 "name": "tenant",
276 "type": "varchar(255)",
277 "primaryKey": false,
278 "notNull": true
279 },
280 "owner": {
281 "name": "owner",
282 "type": "varchar(255)",
283 "primaryKey": false,
284 "notNull": true
285 },
286 "subject": {
287 "name": "subject",
288 "type": "varchar(255)",
289 "primaryKey": false,
290 "notNull": true
291 }
292 },
293 "indexes": {
294 "connection_uidx": {
295 "name": "connection_uidx",
296 "columns": [
297 {
298 "expression": "tenant",
299 "isExpression": false,
300 "asc": true,
301 "nulls": "last"
302 },
303 {
304 "expression": "owner",
305 "isExpression": false,
306 "asc": true,
307 "nulls": "last"
308 },
309 {
310 "expression": "subject",
311 "isExpression": false,
312 "asc": true,
313 "nulls": "last"
314 },
315 {
316 "expression": "integration",
317 "isExpression": false,
318 "asc": true,
319 "nulls": "last"
320 },
321 {
322 "expression": "name",
323 "isExpression": false,
324 "asc": true,
325 "nulls": "last"
326 }
327 ],
328 "isUnique": true,
329 "with": {},
330 "method": "btree",
331 "concurrently": false
332 }
333 },
334 "foreignKeys": {},
335 "compositePrimaryKeys": {},
336 "uniqueConstraints": {},
337 "policies": {},
338 "checkConstraints": {},
339 "isRLSEnabled": false
340 },
341 "public.definition": {
342 "name": "definition",
343 "schema": "",
344 "columns": {
345 "integration": {
346 "name": "integration",
347 "type": "varchar(255)",
348 "primaryKey": false,
349 "notNull": true
350 },
351 "connection": {
352 "name": "connection",
353 "type": "varchar(255)",
354 "primaryKey": false,
355 "notNull": true
356 },
357 "plugin_id": {
358 "name": "plugin_id",
359 "type": "text",
360 "primaryKey": false,
361 "notNull": true
362 },
363 "name": {
364 "name": "name",
365 "type": "text",
366 "primaryKey": false,
367 "notNull": true
368 },
369 "schema": {
370 "name": "schema",
371 "type": "json",
372 "primaryKey": false,
373 "notNull": true
374 },
375 "created_at": {
376 "name": "created_at",
377 "type": "timestamp",
378 "primaryKey": false,
379 "notNull": true
380 },
381 "row_id": {
382 "name": "row_id",
383 "type": "varchar(255)",
384 "primaryKey": true,
385 "notNull": true
386 },
387 "tenant": {
388 "name": "tenant",
389 "type": "varchar(255)",
390 "primaryKey": false,
391 "notNull": true
392 },
393 "owner": {
394 "name": "owner",
395 "type": "varchar(255)",
396 "primaryKey": false,
397 "notNull": true
398 },
399 "subject": {
400 "name": "subject",
401 "type": "varchar(255)",
402 "primaryKey": false,
403 "notNull": true
404 }
405 },
406 "indexes": {
407 "definition_uidx": {
408 "name": "definition_uidx",
409 "columns": [
410 {
411 "expression": "tenant",
412 "isExpression": false,
413 "asc": true,
414 "nulls": "last"
415 },
416 {
417 "expression": "owner",
418 "isExpression": false,
419 "asc": true,
420 "nulls": "last"
421 },
422 {
423 "expression": "subject",
424 "isExpression": false,
425 "asc": true,
426 "nulls": "last"
427 },
428 {
429 "expression": "integration",
430 "isExpression": false,
431 "asc": true,
432 "nulls": "last"
433 },
434 {
435 "expression": "connection",
436 "isExpression": false,
437 "asc": true,
438 "nulls": "last"
439 },
440 {
441 "expression": "name",
442 "isExpression": false,
443 "asc": true,
444 "nulls": "last"
445 }
446 ],
447 "isUnique": true,
448 "with": {},
449 "method": "btree",
450 "concurrently": false
451 }
452 },
453 "foreignKeys": {},
454 "compositePrimaryKeys": {},
455 "uniqueConstraints": {},
456 "policies": {},
457 "checkConstraints": {},
458 "isRLSEnabled": false
459 },
460 "public.integration": {
461 "name": "integration",
462 "schema": "",
463 "columns": {
464 "slug": {
465 "name": "slug",
466 "type": "varchar(255)",
467 "primaryKey": false,
468 "notNull": true
469 },
470 "plugin_id": {
471 "name": "plugin_id",
472 "type": "text",
473 "primaryKey": false,
474 "notNull": true
475 },
476 "description": {
477 "name": "description",
478 "type": "text",
479 "primaryKey": false,
480 "notNull": true
481 },
482 "config": {
483 "name": "config",
484 "type": "json",
485 "primaryKey": false,
486 "notNull": false
487 },
488 "can_remove": {
489 "name": "can_remove",
490 "type": "boolean",
491 "primaryKey": false,
492 "notNull": true,
493 "default": true
494 },
495 "can_refresh": {
496 "name": "can_refresh",
497 "type": "boolean",
498 "primaryKey": false,
499 "notNull": true,
500 "default": false
501 },
502 "created_at": {
503 "name": "created_at",
504 "type": "timestamp",
505 "primaryKey": false,
506 "notNull": true
507 },
508 "updated_at": {
509 "name": "updated_at",
510 "type": "timestamp",
511 "primaryKey": false,
512 "notNull": true
513 },
514 "row_id": {
515 "name": "row_id",
516 "type": "varchar(255)",
517 "primaryKey": true,
518 "notNull": true
519 },
520 "tenant": {
521 "name": "tenant",
522 "type": "varchar(255)",
523 "primaryKey": false,
524 "notNull": true
525 }
526 },
527 "indexes": {
528 "integration_uidx": {
529 "name": "integration_uidx",
530 "columns": [
531 {
532 "expression": "tenant",
533 "isExpression": false,
534 "asc": true,
535 "nulls": "last"
536 },
537 {
538 "expression": "slug",
539 "isExpression": false,
540 "asc": true,
541 "nulls": "last"
542 }
543 ],
544 "isUnique": true,
545 "with": {},
546 "method": "btree",
547 "concurrently": false
548 }
549 },
550 "foreignKeys": {},
551 "compositePrimaryKeys": {},
552 "uniqueConstraints": {},
553 "policies": {},
554 "checkConstraints": {},
555 "isRLSEnabled": false
556 },
557 "public.oauth_client": {
558 "name": "oauth_client",
559 "schema": "",
560 "columns": {
561 "slug": {
562 "name": "slug",
563 "type": "varchar(255)",
564 "primaryKey": false,
565 "notNull": true
566 },
567 "authorization_url": {
568 "name": "authorization_url",
569 "type": "text",
570 "primaryKey": false,
571 "notNull": true
572 },
573 "token_url": {
574 "name": "token_url",
575 "type": "text",
576 "primaryKey": false,
577 "notNull": true
578 },
579 "grant": {
580 "name": "grant",
581 "type": "text",
582 "primaryKey": false,
583 "notNull": true
584 },
585 "client_id": {
586 "name": "client_id",
587 "type": "text",
588 "primaryKey": false,
589 "notNull": true
590 },
591 "client_secret_item_id": {
592 "name": "client_secret_item_id",
593 "type": "text",
594 "primaryKey": false,
595 "notNull": false
596 },
597 "resource": {
598 "name": "resource",
599 "type": "text",
600 "primaryKey": false,
601 "notNull": false
602 },
603 "origin_kind": {
604 "name": "origin_kind",
605 "type": "text",
606 "primaryKey": false,
607 "notNull": false
608 },
609 "origin_integration": {
610 "name": "origin_integration",
611 "type": "text",
612 "primaryKey": false,
613 "notNull": false
614 },
615 "created_at": {
616 "name": "created_at",
617 "type": "timestamp",
618 "primaryKey": false,
619 "notNull": true
620 },
621 "row_id": {
622 "name": "row_id",
623 "type": "varchar(255)",
624 "primaryKey": true,
625 "notNull": true
626 },
627 "tenant": {
628 "name": "tenant",
629 "type": "varchar(255)",
630 "primaryKey": false,
631 "notNull": true
632 },
633 "owner": {
634 "name": "owner",
635 "type": "varchar(255)",
636 "primaryKey": false,
637 "notNull": true
638 },
639 "subject": {
640 "name": "subject",
641 "type": "varchar(255)",
642 "primaryKey": false,
643 "notNull": true
644 }
645 },
646 "indexes": {
647 "oauth_client_uidx": {
648 "name": "oauth_client_uidx",
649 "columns": [
650 {
651 "expression": "tenant",
652 "isExpression": false,
653 "asc": true,
654 "nulls": "last"
655 },
656 {
657 "expression": "owner",
658 "isExpression": false,
659 "asc": true,
660 "nulls": "last"
661 },
662 {
663 "expression": "subject",
664 "isExpression": false,
665 "asc": true,
666 "nulls": "last"
667 },
668 {
669 "expression": "slug",
670 "isExpression": false,
671 "asc": true,
672 "nulls": "last"
673 }
674 ],
675 "isUnique": true,
676 "with": {},
677 "method": "btree",
678 "concurrently": false
679 }
680 },
681 "foreignKeys": {},
682 "compositePrimaryKeys": {},
683 "uniqueConstraints": {},
684 "policies": {},
685 "checkConstraints": {},
686 "isRLSEnabled": false
687 },
688 "public.oauth_session": {
689 "name": "oauth_session",
690 "schema": "",
691 "columns": {
692 "state": {
693 "name": "state",
694 "type": "varchar(255)",
695 "primaryKey": false,
696 "notNull": true
697 },
698 "client_slug": {
699 "name": "client_slug",
700 "type": "text",
701 "primaryKey": false,
702 "notNull": true
703 },
704 "integration": {
705 "name": "integration",
706 "type": "text",
707 "primaryKey": false,
708 "notNull": true
709 },
710 "name": {
711 "name": "name",
712 "type": "text",
713 "primaryKey": false,
714 "notNull": true
715 },
716 "template": {
717 "name": "template",
718 "type": "text",
719 "primaryKey": false,
720 "notNull": true
721 },
722 "redirect_url": {
723 "name": "redirect_url",
724 "type": "text",
725 "primaryKey": false,
726 "notNull": true
727 },
728 "pkce_verifier": {
729 "name": "pkce_verifier",
730 "type": "text",
731 "primaryKey": false,
732 "notNull": false
733 },
734 "identity_label": {
735 "name": "identity_label",
736 "type": "text",
737 "primaryKey": false,
738 "notNull": false
739 },
740 "payload": {
741 "name": "payload",
742 "type": "json",
743 "primaryKey": false,
744 "notNull": true
745 },
746 "expires_at": {
747 "name": "expires_at",
748 "type": "bigint",
749 "primaryKey": false,
750 "notNull": true
751 },
752 "created_at": {
753 "name": "created_at",
754 "type": "timestamp",
755 "primaryKey": false,
756 "notNull": true
757 },
758 "row_id": {
759 "name": "row_id",
760 "type": "varchar(255)",
761 "primaryKey": true,
762 "notNull": true
763 },
764 "tenant": {
765 "name": "tenant",
766 "type": "varchar(255)",
767 "primaryKey": false,
768 "notNull": true
769 },
770 "owner": {
771 "name": "owner",
772 "type": "varchar(255)",
773 "primaryKey": false,
774 "notNull": true
775 },
776 "subject": {
777 "name": "subject",
778 "type": "varchar(255)",
779 "primaryKey": false,
780 "notNull": true
781 }
782 },
783 "indexes": {
784 "oauth_session_uidx": {
785 "name": "oauth_session_uidx",
786 "columns": [
787 {
788 "expression": "tenant",
789 "isExpression": false,
790 "asc": true,
791 "nulls": "last"
792 },
793 {
794 "expression": "state",
795 "isExpression": false,
796 "asc": true,
797 "nulls": "last"
798 }
799 ],
800 "isUnique": true,
801 "with": {},
802 "method": "btree",
803 "concurrently": false
804 }
805 },
806 "foreignKeys": {},
807 "compositePrimaryKeys": {},
808 "uniqueConstraints": {},
809 "policies": {},
810 "checkConstraints": {},
811 "isRLSEnabled": false
812 },
813 "public.plugin_storage": {
814 "name": "plugin_storage",
815 "schema": "",
816 "columns": {
817 "plugin_id": {
818 "name": "plugin_id",
819 "type": "varchar(255)",
820 "primaryKey": false,
821 "notNull": true
822 },
823 "collection": {
824 "name": "collection",
825 "type": "varchar(255)",
826 "primaryKey": false,
827 "notNull": true
828 },
829 "key": {
830 "name": "key",
831 "type": "varchar(255)",
832 "primaryKey": false,
833 "notNull": true
834 },
835 "data": {
836 "name": "data",
837 "type": "json",
838 "primaryKey": false,
839 "notNull": true
840 },
841 "created_at": {
842 "name": "created_at",
843 "type": "timestamp",
844 "primaryKey": false,
845 "notNull": true
846 },
847 "updated_at": {
848 "name": "updated_at",
849 "type": "timestamp",
850 "primaryKey": false,
851 "notNull": true
852 },
853 "row_id": {
854 "name": "row_id",
855 "type": "varchar(255)",
856 "primaryKey": true,
857 "notNull": true
858 },
859 "tenant": {
860 "name": "tenant",
861 "type": "varchar(255)",
862 "primaryKey": false,
863 "notNull": true
864 },
865 "owner": {
866 "name": "owner",
867 "type": "varchar(255)",
868 "primaryKey": false,
869 "notNull": true
870 },
871 "subject": {
872 "name": "subject",
873 "type": "varchar(255)",
874 "primaryKey": false,
875 "notNull": true
876 }
877 },
878 "indexes": {
879 "plugin_storage_uidx": {
880 "name": "plugin_storage_uidx",
881 "columns": [
882 {
883 "expression": "tenant",
884 "isExpression": false,
885 "asc": true,
886 "nulls": "last"
887 },
888 {
889 "expression": "owner",
890 "isExpression": false,
891 "asc": true,
892 "nulls": "last"
893 },
894 {
895 "expression": "subject",
896 "isExpression": false,
897 "asc": true,
898 "nulls": "last"
899 },
900 {
901 "expression": "plugin_id",
902 "isExpression": false,
903 "asc": true,
904 "nulls": "last"
905 },
906 {
907 "expression": "collection",
908 "isExpression": false,
909 "asc": true,
910 "nulls": "last"
911 },
912 {
913 "expression": "key",
914 "isExpression": false,
915 "asc": true,
916 "nulls": "last"
917 }
918 ],
919 "isUnique": true,
920 "with": {},
921 "method": "btree",
922 "concurrently": false
923 }
924 },
925 "foreignKeys": {},
926 "compositePrimaryKeys": {},
927 "uniqueConstraints": {},
928 "policies": {},
929 "checkConstraints": {},
930 "isRLSEnabled": false
931 },
932 "public.private_executor_cloud_settings": {
933 "name": "private_executor_cloud_settings",
934 "schema": "",
935 "columns": {
936 "id": {
937 "name": "id",
938 "type": "varchar(255)",
939 "primaryKey": true,
940 "notNull": true
941 },
942 "version": {
943 "name": "version",
944 "type": "varchar(255)",
945 "primaryKey": false,
946 "notNull": true,
947 "default": "'1.0.0'"
948 }
949 },
950 "indexes": {},
951 "foreignKeys": {},
952 "compositePrimaryKeys": {},
953 "uniqueConstraints": {},
954 "policies": {},
955 "checkConstraints": {},
956 "isRLSEnabled": false
957 },
958 "public.tool": {
959 "name": "tool",
960 "schema": "",
961 "columns": {
962 "integration": {
963 "name": "integration",
964 "type": "varchar(255)",
965 "primaryKey": false,
966 "notNull": true
967 },
968 "connection": {
969 "name": "connection",
970 "type": "varchar(255)",
971 "primaryKey": false,
972 "notNull": true
973 },
974 "plugin_id": {
975 "name": "plugin_id",
976 "type": "text",
977 "primaryKey": false,
978 "notNull": true
979 },
980 "name": {
981 "name": "name",
982 "type": "varchar(255)",
983 "primaryKey": false,
984 "notNull": true
985 },
986 "description": {
987 "name": "description",
988 "type": "text",
989 "primaryKey": false,
990 "notNull": true
991 },
992 "input_schema": {
993 "name": "input_schema",
994 "type": "json",
995 "primaryKey": false,
996 "notNull": false
997 },
998 "output_schema": {
999 "name": "output_schema",
1000 "type": "json",
1001 "primaryKey": false,
1002 "notNull": false
1003 },
1004 "annotations": {
1005 "name": "annotations",
1006 "type": "json",
1007 "primaryKey": false,
1008 "notNull": false
1009 },
1010 "created_at": {
1011 "name": "created_at",
1012 "type": "timestamp",
1013 "primaryKey": false,
1014 "notNull": true
1015 },
1016 "updated_at": {
1017 "name": "updated_at",
1018 "type": "timestamp",
1019 "primaryKey": false,
1020 "notNull": true
1021 },
1022 "row_id": {
1023 "name": "row_id",
1024 "type": "varchar(255)",
1025 "primaryKey": true,
1026 "notNull": true
1027 },
1028 "tenant": {
1029 "name": "tenant",
1030 "type": "varchar(255)",
1031 "primaryKey": false,
1032 "notNull": true
1033 },
1034 "owner": {
1035 "name": "owner",
1036 "type": "varchar(255)",
1037 "primaryKey": false,
1038 "notNull": true
1039 },
1040 "subject": {
1041 "name": "subject",
1042 "type": "varchar(255)",
1043 "primaryKey": false,
1044 "notNull": true
1045 }
1046 },
1047 "indexes": {
1048 "tool_uidx": {
1049 "name": "tool_uidx",
1050 "columns": [
1051 {
1052 "expression": "tenant",
1053 "isExpression": false,
1054 "asc": true,
1055 "nulls": "last"
1056 },
1057 {
1058 "expression": "owner",
1059 "isExpression": false,
1060 "asc": true,
1061 "nulls": "last"
1062 },
1063 {
1064 "expression": "subject",
1065 "isExpression": false,
1066 "asc": true,
1067 "nulls": "last"
1068 },
1069 {
1070 "expression": "integration",
1071 "isExpression": false,
1072 "asc": true,
1073 "nulls": "last"
1074 },
1075 {
1076 "expression": "connection",
1077 "isExpression": false,
1078 "asc": true,
1079 "nulls": "last"
1080 },
1081 {
1082 "expression": "name",
1083 "isExpression": false,
1084 "asc": true,
1085 "nulls": "last"
1086 }
1087 ],
1088 "isUnique": true,
1089 "with": {},
1090 "method": "btree",
1091 "concurrently": false
1092 }
1093 },
1094 "foreignKeys": {},
1095 "compositePrimaryKeys": {},
1096 "uniqueConstraints": {},
1097 "policies": {},
1098 "checkConstraints": {},
1099 "isRLSEnabled": false
1100 },
1101 "public.tool_policy": {
1102 "name": "tool_policy",
1103 "schema": "",
1104 "columns": {
1105 "id": {
1106 "name": "id",
1107 "type": "varchar(255)",
1108 "primaryKey": false,
1109 "notNull": true
1110 },
1111 "pattern": {
1112 "name": "pattern",
1113 "type": "text",
1114 "primaryKey": false,
1115 "notNull": true
1116 },
1117 "action": {
1118 "name": "action",
1119 "type": "text",
1120 "primaryKey": false,
1121 "notNull": true
1122 },
1123 "position": {
1124 "name": "position",
1125 "type": "text",
1126 "primaryKey": false,
1127 "notNull": true
1128 },
1129 "created_at": {
1130 "name": "created_at",
1131 "type": "timestamp",
1132 "primaryKey": false,
1133 "notNull": true
1134 },
1135 "updated_at": {
1136 "name": "updated_at",
1137 "type": "timestamp",
1138 "primaryKey": false,
1139 "notNull": true
1140 },
1141 "row_id": {
1142 "name": "row_id",
1143 "type": "varchar(255)",
1144 "primaryKey": true,
1145 "notNull": true
1146 },
1147 "tenant": {
1148 "name": "tenant",
1149 "type": "varchar(255)",
1150 "primaryKey": false,
1151 "notNull": true
1152 },
1153 "owner": {
1154 "name": "owner",
1155 "type": "varchar(255)",
1156 "primaryKey": false,
1157 "notNull": true
1158 },
1159 "subject": {
1160 "name": "subject",
1161 "type": "varchar(255)",
1162 "primaryKey": false,
1163 "notNull": true
1164 }
1165 },
1166 "indexes": {
1167 "tool_policy_uidx": {
1168 "name": "tool_policy_uidx",
1169 "columns": [
1170 {
1171 "expression": "tenant",
1172 "isExpression": false,
1173 "asc": true,
1174 "nulls": "last"
1175 },
1176 {
1177 "expression": "owner",
1178 "isExpression": false,
1179 "asc": true,
1180 "nulls": "last"
1181 },
1182 {
1183 "expression": "subject",
1184 "isExpression": false,
1185 "asc": true,
1186 "nulls": "last"
1187 },
1188 {
1189 "expression": "id",
1190 "isExpression": false,
1191 "asc": true,
1192 "nulls": "last"
1193 }
1194 ],
1195 "isUnique": true,
1196 "with": {},
1197 "method": "btree",
1198 "concurrently": false
1199 }
1200 },
1201 "foreignKeys": {},
1202 "compositePrimaryKeys": {},
1203 "uniqueConstraints": {},
1204 "policies": {},
1205 "checkConstraints": {},
1206 "isRLSEnabled": false
1207 }
1208 },
1209 "enums": {},
1210 "schemas": {},
1211 "views": {},
1212 "sequences": {},
1213 "roles": {},
1214 "policies": {},
1215 "_meta": {
1216 "columns": {},
1217 "schemas": {},
1218 "tables": {}
1219 }
1220}
apps/cloud/drizzle/meta/_journal.json+7−0
@@ -15,6 +15,13 @@ · 14 unmodified lines
15 "when": 1780991784259,15 "when": 1780991784259,
16 "tag": "0001_illegal_wolverine",16 "tag": "0001_illegal_wolverine",
17 "breakpoints": true17 "breakpoints": true
18 },
19 {
20 "idx": 2,
21 "version": "7",
22 "when": 1781220171898,
23 "tag": "0002_unwrap_openapi_output_envelope",
24 "breakpoints": true
18 }25 }
19 ]26 ]
20}27}
apps/cloud/package.json+0−2
@@ -15,8 +15,6 @@ · 14 unmodified lines
15 "db:migrate:prod": "op run --env-file=.env.production -- bun --bun ../../node_modules/.bun/node_modules/drizzle-kit/bin.cjs migrate",15 "db:migrate:prod": "op run --env-file=.env.production -- bun --bun ../../node_modules/.bun/node_modules/drizzle-kit/bin.cjs migrate",
16 "db:migrate-auth:prod": "op run --env-file=.env.production -- bun run scripts/migrate-auth-configs.ts",16 "db:migrate-auth:prod": "op run --env-file=.env.production -- bun run scripts/migrate-auth-configs.ts",
17 "db:migrate-auth:dev": "op run --env-file=.env.op -- bun run scripts/migrate-auth-configs.ts",17 "db:migrate-auth:dev": "op run --env-file=.env.op -- bun run scripts/migrate-auth-configs.ts",
18 "db:migrate-openapi-output:prod": "op run --env-file=.env.production -- bun run scripts/migrate-openapi-output-schemas.ts",
19 "db:migrate-openapi-output:dev": "op run --env-file=.env.op -- bun run scripts/migrate-openapi-output-schemas.ts",
20 "db:repartition-vault:prod": "op run --env-file=.env.production -- bun run scripts/repartition-vault-metadata.ts",18 "db:repartition-vault:prod": "op run --env-file=.env.production -- bun run scripts/repartition-vault-metadata.ts",
21 "db:repartition-vault:dev": "op run --env-file=.env.op -- bun run scripts/repartition-vault-metadata.ts",19 "db:repartition-vault:dev": "op run --env-file=.env.op -- bun run scripts/repartition-vault-metadata.ts",
22 "db:migrate-specs:prod": "op run --env-file=.env.production -- bun run scripts/migrate-specs-to-blobs.ts",20 "db:migrate-specs:prod": "op run --env-file=.env.production -- bun run scripts/migrate-specs-to-blobs.ts",
apps/cloud/scripts/migrate-openapi-output-schemas.ts+0−106
@@ -1,106 +0,0 @@
1/* oxlint-disable executor/no-try-catch-or-throw, executor/no-json-parse -- boundary: out-of-band migration script over a raw postgres connection */
2// ---------------------------------------------------------------------------
3// One-off data migration: unwrap the retired {status, headers, data}
4// transport envelope from persisted OpenAPI tool output schemas. The
5// runtime now returns the upstream payload as `data` (with status/headers
6// in the ToolResult `http` side channel), so persisted schemas must
7// describe the payload only — otherwise describe.tool previews show an
8// envelope that invocations no longer return. Run OUT-OF-BAND against the
9// database BEFORE deploying the payload-first runtime:
10//
11// bun run db:migrate-openapi-output:prod # op run --env-file=.env.production
12// bun run db:migrate-openapi-output:dev # against the local dev db
13//
14// Idempotent — payload-shaped rows don't match the envelope signature, so
15// re-running plans zero updates. Pass --dry-run to print the plan without
16// writing.
17// ---------------------------------------------------------------------------
18
19import postgres from "postgres";
20
21interface EnvelopeSchema {
22 readonly type: "object";
23 readonly additionalProperties: false;
24 readonly required: readonly string[];
25 readonly properties: {
26 readonly status: unknown;
27 readonly headers: unknown;
28 readonly data: unknown;
29 };
30}
31
32// Matches exactly what openApiTransportOutputSchema used to emit.
33const isTransportEnvelope = (schema: unknown): schema is EnvelopeSchema => {
34 if (typeof schema !== "object" || schema === null) return false;
35 const s = schema as Record<string, unknown>;
36 if (s.type !== "object" || s.additionalProperties !== false) return false;
37 const required = s.required;
38 if (!Array.isArray(required) || required.length !== 3) return false;
39 if (!["status", "headers", "data"].every((key) => required.includes(key))) return false;
40 const properties = s.properties;
41 if (typeof properties !== "object" || properties === null) return false;
42 const props = properties as Record<string, unknown>;
43 return (
44 JSON.stringify(props.status) === '{"type":"integer"}' &&
45 JSON.stringify(props.headers) ===
46 '{"type":"object","additionalProperties":{"type":"string"}}' &&
47 "data" in props
48 );
49};
50
51// The old producer filled `data: {}` when the operation declared no output
52// schema; the new producer persists NULL for those.
53const unwrappedSchema = (envelope: EnvelopeSchema): unknown => {
54 const data = envelope.properties.data;
55 if (typeof data === "object" && data !== null && Object.keys(data).length === 0) return null;
56 return data;
57};
58
59const connectionString = process.env.DATABASE_URL;
60if (!connectionString) {
61 console.error("DATABASE_URL is not set");
62 process.exit(1);
63}
64const dryRun = process.argv.includes("--dry-run");
65
66// Direct (non-Hyperdrive) connection — PlanetScale requires TLS.
67const sql = postgres(connectionString, { max: 1, prepare: false, ssl: "require" });
68
69try {
70 const rows = await sql<{ row_id: string; name: string; output_schema: unknown }[]>`
71 SELECT row_id, name, output_schema FROM tool
72 WHERE plugin_id = 'openapi' AND output_schema IS NOT NULL
73 `;
74
75 const updates = rows.flatMap((row) => {
76 const schema =
77 typeof row.output_schema === "string" ? JSON.parse(row.output_schema) : row.output_schema;
78 if (!isTransportEnvelope(schema)) return [];
79 return [{ rowId: row.row_id, name: row.name, outputSchema: unwrappedSchema(schema) }];
80 });
81
82 console.log(
83 `${rows.length} openapi tool row(s) with output schemas, ${updates.length} to unwrap`,
84 );
85
86 if (dryRun) {
87 for (const update of updates) {
88 console.log(
89 ` would unwrap ${update.rowId} (${update.name})${update.outputSchema === null ? " -> NULL" : ""}`,
90 );
91 }
92 } else if (updates.length > 0) {
93 await sql.begin(async (tx) => {
94 for (const update of updates) {
95 await tx`
96 UPDATE tool
97 SET output_schema = ${update.outputSchema === null ? null : tx.json(update.outputSchema as never)}
98 WHERE row_id = ${update.rowId}
99 `;
100 }
101 });
102 console.log(`unwrapped ${updates.length} row(s)`);
103 }
104} finally {
105 await sql.end();
106}