@tailor-platform/sdk 2.36.0 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/{application-DaJbleMi.mjs → application-83WVdH85.mjs} +1 -1
  3. package/dist/{application-BbORWCj-.mjs → application-Cida-j1g.mjs} +36 -36
  4. package/dist/application-Cida-j1g.mjs.map +1 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +6 -1
  6. package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +1 -0
  7. package/dist/cli/commands/executor/jobs.d.mts +3 -0
  8. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +3 -0
  9. package/dist/cli/commands/tailordb/migrate/config.d.mts +4 -0
  10. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +10 -0
  11. package/dist/cli/commands/tailordb/migrate/generate.d.mts +28 -2
  12. package/dist/cli/commands/tailordb/migrate/hints.d.mts +1 -0
  13. package/dist/cli/commands/tailordb/migrate/remote-state.d.mts +3 -0
  14. package/dist/cli/commands/tailordb/migrate/schema-checks.d.mts +3 -1
  15. package/dist/cli/commands/tailordb/migrate/snapshot-comparison.d.mts +4 -0
  16. package/dist/cli/commands/tailordb/migrate/snapshot-files.d.mts +44 -6
  17. package/dist/cli/commands/tailordb/migrate/snapshot-local.d.mts +2 -0
  18. package/dist/cli/commands/tailordb/migrate/snapshot-migrations.d.mts +2 -0
  19. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +8 -0
  20. package/dist/cli/commands/tailordb/migrate/types.d.mts +2 -0
  21. package/dist/cli/commands/workspace/create.d.mts +1 -1
  22. package/dist/cli/lib.d.mts +2 -2
  23. package/dist/cli/lib.mjs +1 -1
  24. package/dist/cli/lib.mjs.map +1 -1
  25. package/dist/cli/main.mjs +52 -51
  26. package/dist/cli/main.mjs.map +1 -1
  27. package/dist/cli/shared/client.d.mts +26 -0
  28. package/dist/cli/shared/function-execution.d.mts +2 -0
  29. package/dist/cli/shared/prompt.d.mts +9 -4
  30. package/dist/completion/zsh-worker.zsh +21 -13
  31. package/dist/configure/config/index.d.mts +2 -0
  32. package/dist/configure/config/types.d.mts +2 -0
  33. package/dist/configure/index.mjs.map +1 -1
  34. package/dist/configure/services/tailordb/schema.d.mts +1 -0
  35. package/dist/configure/services/tailordb/types.d.mts +3 -0
  36. package/dist/crashreport-C2AcQNTe.mjs +1 -0
  37. package/dist/{crashreport-DGU6lwiX.mjs → crashreport-D_wf4GZh.mjs} +4 -4
  38. package/dist/{crashreport-DGU6lwiX.mjs.map → crashreport-D_wf4GZh.mjs.map} +1 -1
  39. package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -1
  40. package/dist/{errors-BXmGBX-7.mjs → errors-Dmdnq0zi.mjs} +2 -2
  41. package/dist/errors-Dmdnq0zi.mjs.map +1 -0
  42. package/dist/{kysely-type-B0ri1-Xs.mjs → kysely-type-DVL90_oX.mjs} +6 -6
  43. package/dist/kysely-type-DVL90_oX.mjs.map +1 -0
  44. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  45. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -3
  46. package/dist/plugin/get-generated-table.d.mts +4 -0
  47. package/dist/plugin/index.mjs.map +1 -1
  48. package/dist/plugin/types.d.mts +55 -3
  49. package/dist/plugin/with-context.d.mts +16 -0
  50. package/dist/register-ts-hook-Ht4zCseV.mjs +1115 -0
  51. package/dist/register-ts-hook-Ht4zCseV.mjs.map +1 -0
  52. package/dist/schema-BU1aIQ63.mjs.map +1 -1
  53. package/dist/{secret-file-BxZ-J_Nz.mjs → secret-file-DiuEcLFP.mjs} +2 -2
  54. package/dist/{secret-file-BxZ-J_Nz.mjs.map → secret-file-DiuEcLFP.mjs.map} +1 -1
  55. package/dist/service-CZ0EdD2-.mjs +10 -0
  56. package/dist/service-CZ0EdD2-.mjs.map +1 -0
  57. package/dist/service-DDrbdyd-.mjs +2 -0
  58. package/dist/{service-CaGmqg0r.mjs.map → service-DDrbdyd-.mjs.map} +1 -1
  59. package/dist/service-WhSyhaho.mjs +1 -0
  60. package/dist/service_pb-BMHhVot2.mjs +1 -0
  61. package/dist/service_pb-BxMSK0gg.mjs +2 -0
  62. package/dist/service_pb-BxMSK0gg.mjs.map +1 -0
  63. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +3 -3
  64. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +92 -0
  65. package/dist/tailor-proto/src/tailor/v1/notification_pb.d.mts +176 -0
  66. package/dist/tailor-proto/src/tailor/v1/notification_resource_pb.d.mts +172 -0
  67. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +78 -0
  68. package/dist/types/app-config.generated.d.mts +1 -1
  69. package/dist/types/auth-connection.generated.d.mts +2 -2
  70. package/dist/types/auth.generated.d.mts +13 -13
  71. package/dist/types/executor.generated.d.mts +25 -25
  72. package/dist/types/field.generated.d.mts +2 -2
  73. package/dist/types/http-adapter.generated.d.mts +4 -4
  74. package/dist/types/idp.generated.d.mts +272 -272
  75. package/dist/types/resolver.generated.d.mts +10 -10
  76. package/dist/types/tailordb.generated.d.mts +84 -80
  77. package/dist/types/workflow.generated.d.mts +2 -2
  78. package/dist/vitest/date-representation.d.mts +2 -0
  79. package/dist/vitest/environment.d.mts +1 -0
  80. package/dist/vitest/environment.mjs.map +1 -1
  81. package/dist/vitest/index.mjs.map +1 -1
  82. package/dist/vitest/migration-steps.d.mts +13 -2
  83. package/dist/vitest/mock-kysely.d.mts +12 -2
  84. package/dist/vitest/mocks/aigateway.d.mts +7 -1
  85. package/dist/vitest/mocks/authconnection.d.mts +7 -1
  86. package/dist/vitest/mocks/file.d.mts +8 -2
  87. package/dist/vitest/mocks/iconv.d.mts +7 -1
  88. package/dist/vitest/mocks/idp.d.mts +7 -1
  89. package/dist/vitest/mocks/logger.d.mts +7 -1
  90. package/dist/vitest/mocks/secretmanager.d.mts +7 -1
  91. package/dist/vitest/mocks/tailordb-pglite.d.mts +12 -2
  92. package/dist/vitest/mocks/tailordb.d.mts +22 -4
  93. package/dist/vitest/mocks/workflow.d.mts +2 -1
  94. package/dist/vitest/pglite-kysely.d.mts +18 -2
  95. package/dist/vitest/pglite-temporal.d.mts +5 -1
  96. package/dist/vitest/workflow-local.d.mts +7 -0
  97. package/dist/{workspace_resource_pb-C4CkoYww.mjs → workspace_resource_pb-7smKERdZ.mjs} +2 -2
  98. package/dist/workspace_resource_pb-7smKERdZ.mjs.map +1 -0
  99. package/docs/cli/application.md +51 -21
  100. package/docs/cli/executor.md +2 -0
  101. package/docs/cli/function.md +3 -1
  102. package/docs/cli/tailordb.md +30 -22
  103. package/docs/cli/workflow.md +2 -0
  104. package/docs/cli/workspace.md +19 -17
  105. package/docs/cli-reference.md +5 -4
  106. package/docs/configuration.md +2 -2
  107. package/docs/data-clone.md +1 -1
  108. package/docs/github-actions.md +104 -15
  109. package/docs/plugin/custom.md +4 -4
  110. package/docs/runtime.md +1 -1
  111. package/docs/services/auth.md +2 -0
  112. package/docs/services/idp.md +2 -0
  113. package/docs/services/tailordb/index.md +7 -2
  114. package/docs/services/tailordb/migration.md +71 -30
  115. package/docs/services/workflow.md +1 -1
  116. package/docs/testing.md +9 -9
  117. package/package.json +13 -13
  118. package/dist/application-BbORWCj-.mjs.map +0 -1
  119. package/dist/crashreport-WsYAWARW.mjs +0 -1
  120. package/dist/errors-BXmGBX-7.mjs.map +0 -1
  121. package/dist/kysely-type-B0ri1-Xs.mjs.map +0 -1
  122. package/dist/register-ts-hook-iAcG1dfz.mjs +0 -1057
  123. package/dist/register-ts-hook-iAcG1dfz.mjs.map +0 -1
  124. package/dist/service-CaGmqg0r.mjs +0 -2
  125. package/dist/service-DTrVVaDJ.mjs +0 -1
  126. package/dist/service-hIarp2Gl.mjs +0 -8
  127. package/dist/service-hIarp2Gl.mjs.map +0 -1
  128. package/dist/service_pb-6EkMfdOb.mjs +0 -2
  129. package/dist/service_pb-6EkMfdOb.mjs.map +0 -1
  130. package/dist/service_pb-CsJKokDl.mjs +0 -1
  131. package/dist/workspace_resource_pb-C4CkoYww.mjs.map +0 -1
@@ -2,7 +2,7 @@
2
2
  description: "Generate, test, and apply TailorDB schema migrations with optional data migration scripts, and recover from failed migrations."
3
3
  ---
4
4
 
5
- # TailorDB Migrations
5
+ # TailorDB Migrations (Beta)
6
6
 
7
7
  > **Beta:** The `tailordb migration` command and the migration runtime are beta features. They may introduce breaking changes in future releases. The CLI emits a beta warning on every invocation.
8
8
 
@@ -58,14 +58,21 @@ If you already have a deployed workspace whose schema matches your local table d
58
58
 
59
59
  1. Add the `migration` block to `tailor.config.ts`.
60
60
  2. Run `tailor tailordb migration generate` to create `0000/schema.json` from current local tables.
61
- 3. Run `tailor deploy`. Because remote schema already matches, no script runs; only the migration label is set.
61
+ 3. Run `tailor deploy`. It first checks that the deployed tables match `0000`. Because they do, no script runs; only the migration label is set.
62
62
 
63
- If your local tables and remote schema have **diverged**, reconcile them before introducing migrations — either update local tables to match remote, or accept that the first non-`0000` migration will reflect that gap.
63
+ Until a deploy sets the migration label, `tailor deploy` and `tailor tailordb migration validate` compare the deployed tables with `0000`. When they differ, `tailor deploy` stops with [remote schema drift](#remote-schema-drift-detected) and `migration validate` fails, for example when a table was edited in the console, or when a local change had not been deployed before you generated `0000`. Run `tailor tailordb migration validate` before the first deploy to check, and reconcile the difference before deploying:
64
+
65
+ - To keep the deployed tables, update your local tables to match them, then recreate `0000` with `tailor tailordb migration generate --init`. `--init` deletes every migration of the namespace, so do this only while no workspace has been deployed with these migrations.
66
+ - To replace the deployed tables with `0000`, run `tailor tailordb migration sync 0000`. Tables and fields that `0000` does not have are deleted together with their data.
67
+
68
+ If an earlier deploy stopped after creating only some of the tables, the next deploy reports the missing tables as drift; `tailor tailordb migration sync 0000` creates them.
64
69
 
65
70
  ### Resetting
66
71
 
67
72
  `tailor tailordb migration generate --init` deletes the existing `migrations/` directory of every namespace with migrations configured and creates `0000` from the current local tables. Add `--namespace <name>` to reset only that namespace and leave the others untouched. Use it only before the project is deployed. For a deployed migration history, use [`migration rebaseline`](#re-baselining-a-deployed-migration-history), which verifies the history and connected workspace before replacing any files.
68
73
 
74
+ Declining the `--init` confirmation leaves existing migrations in place and fails with `MIGRATION_GENERATE_CANCELLED`.
75
+
69
76
  ## Migration Workflow
70
77
 
71
78
  A typical change cycle:
@@ -122,6 +129,10 @@ A typical change cycle:
122
129
  ```
123
130
  The pre-migration phase relaxes the new field to optional, the script runs and populates values, then the post-migration phase enforces `required: true`.
124
131
 
132
+ When you decline a namespace's breaking-change confirmation, `migration generate` skips that namespace, continues generating migrations for the others, and exits successfully with a `Skipped: <namespaces>` summary. Declining every namespace also succeeds without writing migrations. Check the summary before deploying if you intended to generate every namespace.
133
+
134
+ JSON output includes `skippedNamespaces` alongside `migrations`. It is always an empty array in actual `--json` runs because JSON mode does not prompt. Programmatic callers of `migrateGenerate()` receive a `MigrateGenerateResult` and can inspect `skippedNamespaces` before continuing.
135
+
125
136
  ### Warnings and optional migration scripts
126
137
 
127
138
  Some non-breaking changes can still cause data loss — most notably removing a field (`field_removed`), removing a table (`table_removed`), or removing a member inside a nested field (reported on the nested field's `field_modified` change). `migration generate` reports these as **warnings**:
@@ -226,9 +237,9 @@ tailor tailordb migration generate --rename "User:Person"
226
237
  tailor tailordb migration generate --drop "User"
227
238
  ```
228
239
 
229
- Two tables qualify as a rename pair only when copying every row preserves the data: every field must keep its name, type, array-ness, required/unique constraints, foreign key target, and decimal scale; enum fields may gain values but not lose them; indexes must match. A self-referential foreign key is compared against the new table name and must be optional. Tables with serial fields (their values cannot be written by a script) or file fields (file contents are not copied) are never candidates. Name-derived and data-independent settings — `pluralForm`, description, table settings, permissions, hooks, and validations — may differ.
240
+ Two tables qualify as a rename pair only when copying every row preserves the data: every field must keep its name, type, array-ness, required/unique constraints, foreign key target, and decimal scale; enum fields may gain values but not lose them; indexes must match. A self-referential foreign key, including one on a field inside a nested field, is compared against the new table name and must be optional. A table is also not a candidate when a self-referential foreign key points at another column of the table that is itself a self-referential foreign key, or when a nested field holding a self-referential foreign key is covered by a unique constraint. Tables with serial fields (their values cannot be written by a script) or file fields (file contents are not copied) are never candidates. Name-derived and data-independent settings — `pluralForm`, description, table settings, permissions, hooks, and validations — may differ.
230
241
 
231
- A confirmed rename is recorded as a single `table_renamed` change and treated as **breaking** for two reasons: existing records must be copied by the migration script, and the table's GraphQL API names (derived from the table name and `pluralForm`) change, which breaks API clients. The generated `migrate.ts` copies every row from the old table into the new one in id-ordered batches, preserving ids so stored foreign key references stay valid, and the generated `db.ts` exposes both the old table (readable) and the new table (writable). Self-referential foreign keys are inserted as null and backfilled after every row exists, so a reference to a row in a later batch cannot fail the copy.
242
+ A confirmed rename is recorded as a single `table_renamed` change and treated as **breaking** for two reasons: existing records must be copied by the migration script, and the table's GraphQL API names (derived from the table name and `pluralForm`) change, which breaks API clients. The generated `migrate.ts` copies every row from the old table into the new one in id-ordered batches, preserving ids so stored foreign key references stay valid, and the generated `db.ts` exposes both the old table (readable) and the new table (writable). Self-referential foreign keys, including those on fields inside nested fields, are inserted as null and backfilled after every row exists, so a reference to a row in a later batch cannot fail the copy.
232
243
 
233
244
  Two caveats apply to the copy. The old table is not write-protected: rows written to it after the script's transaction commits — and before post-migration cleanup drops it — are not carried over, so pause writers to the renamed table for the duration of the deploy. And platform-managed record metadata (creation/update timestamps and actors) cannot be written by the script, so the new table's records carry the migration run's metadata instead of the original values.
234
245
 
@@ -271,7 +282,8 @@ export default defineConfig({
271
282
  files: ["./tailordb/*.ts"],
272
283
  migration: {
273
284
  directory: "./migrations",
274
- // Optional. Defaults to the first machine user in auth.machineUsers.
285
+ // Optional. Defaults to the first machine user in auth.machineUsers,
286
+ // and must name one of them.
275
287
  machineUser: "admin-machine-user",
276
288
  },
277
289
  },
@@ -282,7 +294,7 @@ export default defineConfig({
282
294
  | Option | Type | Description |
283
295
  | ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
284
296
  | `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace. Each namespace needs its own directory. |
285
- | `migration.machineUser` | string | Machine user used to run migration scripts. Optional; defaults to the first entry in `auth.machineUsers`. |
297
+ | `migration.machineUser` | string | Machine user used to run migration scripts. Must be defined in `auth.machineUsers`. Optional; defaults to the first entry there. |
286
298
 
287
299
  ## Generated Files
288
300
 
@@ -439,7 +451,7 @@ Because steps commit separately, a failure does not undo the steps that already
439
451
  - If the steps changed since the earlier run, or that run is no longer available, every step runs again from the beginning. See [Editing steps of a migration in progress](#editing-steps-of-a-migration-in-progress).
440
452
  - If every step completed but the post-migration schema changes fail (for example, a field the migration makes required still has empty values), the migration also stays in progress with its pre-migration schema, and the next deploy retries those schema changes without running the steps again.
441
453
  - To run a completed step again (because it had a bug, or to fix rows the post-migration schema changes reject), change the steps: rename that step and update the `dependsOn` lists that name it, or add a step that fixes the rows. Every step then runs again from the beginning, so this relies on every step being safe to run again; to keep the other completed steps from running again, skip them as described below.
442
- - While a migration is in progress, `migration set`, `migration sync`, and `migration rebaseline` refuse to run, and `migration test` refuses to use that workspace as its source. Finish a multi-step migration left in progress with `tailor deploy` first, or settle it with `migration resolve`. A migration whose script run a deploy could not confirm is not finished by a later deploy, so settle that one with `migration resolve` (see [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress)). The one exception to these refusals is `migration set <N>` with the in-progress migration's own number: it marks that migration as completed and clears its in-progress record. Use it only when the migration's schema changes are already applied, for example after the deploy reported that the checkpoint write could not be confirmed.
454
+ - Finish a multi-step migration left in progress with `tailor deploy`, or settle it with `migration resolve`. Until then, the commands listed in [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress) refuse to run.
443
455
 
444
456
  #### Editing steps of a migration in progress
445
457
 
@@ -485,6 +497,8 @@ export const steps = {
485
497
 
486
498
  ### Settling a migration that stays in progress
487
499
 
500
+ While a migration is in progress, `migration set`, `migration sync`, and `migration rebaseline` refuse to run, and `migration test` refuses to use that workspace as its source. `tailor tailordb migration status` reports the migration as in progress and lists the commands that get it out of that state: `tailor deploy` to resume a multi-step migration, and `tailor tailordb migration resolve` to roll it back or complete it. For a migration whose script run a deploy could not confirm, it lists only `resolve`, because a deploy does not resume that migration.
501
+
488
502
  A deploy resumes a multi-step migration left in progress. When you do not want it to — you abandoned the migration, or you repaired the data by hand — settle the migration with `tailor tailordb migration resolve`. Pass exactly one of two options:
489
503
 
490
504
  - `--rollback` sends the schema of the migration before the failed one, clears the in-progress record, and leaves the checkpoint where it was. The next `tailor deploy` runs the failed migration again from its first step, so every step must be safe to run again. Data changes made by steps that already completed are not undone, and tables that exist only in the failed migration's schema are deleted with their data.
@@ -644,7 +658,7 @@ Some changes are still rejected and need a temporary field you add yourself —
644
658
 
645
659
  The command performs the following sequence:
646
660
 
647
- 1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history. A deployed namespace that has no checkpoint is treated as migration 0, as `tailor deploy` does, but only when its schema matches the `0000` snapshot; otherwise the command fails and lists the differences. A namespace that was never deployed also fails.
661
+ 1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history. A deployed namespace that has no checkpoint is treated as migration 0 when its schema matches the `0000` snapshot, as `tailor deploy` does; otherwise the command fails and lists the differences. A namespace that was never deployed, or has no tables yet, also fails.
648
662
  2. Creates a temporary workspace in the same region as the source, unless `--target-workspace-id` names an existing throwaway workspace. By default it is created in the source's organization and folder. `--organization-id` places it at the root of that organization, and `--folder-id` places it in that folder (together with `--organization-id` when the folder is in another organization or the source is not in an organization). With `--target-workspace-id`, the given values must match where that workspace already is.
649
663
  3. Deploys the checkpoint snapshots and writes their checkpoint labels.
650
664
  4. Loads fixture data or clones source records.
@@ -668,7 +682,7 @@ tailor tailordb migration test --data seed
668
682
 
669
683
  Rows are loaded only for tables present in the deployed pre-migration snapshots (and current schemas without migrations), in foreign-key dependency order. Fields introduced by pending migrations, including timestamp and nested fields, are removed before insertion so current fixtures can be loaded into the baseline schema. Missing table files are treated as empty. IdP `_User` fixtures are not loaded by this command.
670
684
 
671
- Use `--machine-user` to override the seed plugin's `machineUserName`, the namespace migration setting, and the first configured Auth machine user for seed and assertion execution.
685
+ Use `--machine-user` to run the seed, the pending migration scripts, and the assertion as that machine user, in place of `db.<namespace>.migration.machineUser` and the first entry in `auth.machineUsers` (and, for the seed, the seed plugin's `machineUserName`). For the pending migration scripts, it must be defined in `auth.machineUsers`.
672
686
 
673
687
  ### Clone mode
674
688
 
@@ -724,7 +738,7 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
724
738
  For each pending migration:
725
739
 
726
740
  1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. A foreign key added to an existing top-level field is withheld until Post-migration, so `migrate.ts` can update rows whose values point at no record; Post-migration adds it without checking the stored values. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
727
- 2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and runs on the platform as the configured machine user inside a transaction — or, for a script that exports [`steps`](#splitting-a-migration-into-steps), one transaction per step. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) — deploy fails if the file is missing, unless a `--no-script` acknowledgment was recorded (see [Breaking changes without a script](#breaking-changes-without-a-script)). It is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
741
+ 2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and runs on the platform as the [configured machine user](#machine-user-and-permissions) inside a transaction — or, for a script that exports [`steps`](#splitting-a-migration-into-steps), one transaction per step. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) — deploy fails if the file is missing, unless a `--no-script` acknowledgment was recorded (see [Breaking changes without a script](#breaking-changes-without-a-script)). It is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
728
742
  3. **Post-migration schema**: Required constraints and the target field definitions are applied. Do not assume that removing a field clears its underlying stored JSON value.
729
743
  4. **Checkpoint and cleanup**: The `sdk-migration` label is bumped to this migration's number, then removed GQL permissions and tables — including the old table left behind by a rename — are deleted. Advancing the checkpoint first prevents a failed checkpoint write from requiring the SDK to recreate irreversibly deleted records.
730
744
 
@@ -757,7 +771,7 @@ Maintenance mode restricts GraphQL writes and record events only. Resolvers, exe
757
771
 
758
772
  **When a deploy stops early:**
759
773
 
760
- - If a migration fails, the tables leave maintenance mode before the error is reported: they return to the settings of the last checkpoint that completed. A migration that did not reach its checkpoint returns existing tables to their previous settings and keeps the tables it created in maintenance mode until a later deploy applies it. The settings are not restored if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified. A table left behind by a failed post-checkpoint deletion also stays in maintenance mode for manual recovery.
774
+ - If a migration fails, the tables leave maintenance mode before the error is reported: they return to the settings of the last checkpoint that completed. A migration that did not reach its checkpoint returns existing tables to their previous settings and drops the tables it created (see [Failure Recovery](#failure-recovery)). A created table that the rollback cannot drop stays in maintenance mode, and the deploy names it for manual repair. The settings are not restored if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified. A table left behind by a failed post-checkpoint deletion also stays in maintenance mode for manual recovery.
761
775
  - A [multi-step migration](#splitting-a-migration-into-steps) left in progress keeps its tables in maintenance mode until a deploy completes it.
762
776
  - A migration whose script run a deploy could not confirm keeps its tables in maintenance mode until you settle it with `tailor tailordb migration resolve` (see [Failure Recovery](#failure-recovery)).
763
777
  - With `"deploy"`, if a later part of the deploy fails, the tables leave maintenance mode before the error is reported. When the deploy ends maintenance mode, it leaves the tables of a namespace unchanged if that namespace's checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified, and reports it.
@@ -795,7 +809,7 @@ Deploy checks the run every few seconds, so a boundary between `waiting to start
795
809
  Before running migrations, `apply` performs two checks:
796
810
 
797
811
  1. **Local schema check** — your current table definitions must match the latest snapshot in `migrations/`. If they don't, you forgot to run `migration generate`.
798
- 2. **Remote schema check** — the deployed schema is reconstructed from migration history; the actual remote schema must match. Drift here means someone applied a different set of migrations or edited the schema out-of-band.
812
+ 2. **Remote schema check** — the deployed schema is reconstructed from migration history; the actual remote schema must match. Drift here means someone applied a different set of migrations or edited the schema out-of-band. A namespace that has tables but no migration label yet must match `0000` (see [Adding migrations to an existing project](#adding-migrations-to-an-existing-project)).
799
813
 
800
814
  On drift you'll see something like:
801
815
 
@@ -826,6 +840,8 @@ To bypass both checks during deploy (not recommended outside of recovery scenari
826
840
  tailor deploy --no-schema-check
827
841
  ```
828
842
 
843
+ `--no-schema-check` does not bypass the check that the remote migration checkpoint is in the local migration history. See ["Remote migration checkpoint is not in the local migration history" error](#remote-migration-checkpoint-is-not-in-the-local-migration-history-error).
844
+
829
845
  ### Example output
830
846
 
831
847
  ```
@@ -858,13 +874,17 @@ tailor tailordb migration rebaseline --namespace tailordb
858
874
 
859
875
  The command validates the migration files, verifies that replaying the latest migration exactly reproduces the local table definitions, and checks that the connected workspace is at that latest migration with no schema drift. After confirmation, it replaces the local history with the reconstructed baseline, records a new migration history ID in both `0000/schema.json` and remote metadata, and resets the connected workspace's `sdk-migration` label to `0000`. Use `--yes` only after arranging the same operational preconditions in non-interactive automation.
860
876
 
877
+ When the connected workspace records another migration history ID, or one that cannot be read, the command refuses to run. Run it from a checkout of the migration history deployed to that workspace. To replace the workspace's history with the local one on purpose, first run `tailor tailordb migration set <N> --force` with the latest local migration as `<N>` (see [`migration set` Semantics](#migration-set-semantics)). When the workspace's tables differ from the latest migration, the command also refuses to run; see [Remote schema drift detected](#remote-schema-drift-detected), where `tailor tailordb migration sync <N>` rewrites the tables to the latest migration.
878
+
861
879
  Commit the resulting `migrations/` change before generating any new migrations. For another environment still carrying the exact checkpoint and history ID that the new baseline replaced, the next `tailor deploy` checks whether its remote schema exactly matches the new `0000`. If it does, deploy offers to reset the checkpoint to `0000` and move the environment to the new history ID before applying any later local migrations. A markerless history is eligible only for the first rebaseline, at the exact migration recorded as replaced. Any other checkpoint or history ID is rejected without changing remote metadata, even if its schema happens to match the baseline.
862
880
 
863
881
  Partial squashing is not supported: re-baselining always replaces the full history for one namespace.
864
882
 
865
883
  ## `migration set` Semantics
866
884
 
867
- `tailor tailordb migration set <N>` updates the `sdk-migration` label on the deployed namespace's metadata. **It does not modify any data or schema.** It only changes which migrations the next `apply` will consider pending. The command also aligns the remote migration history ID with the local baseline, removing a stale ID when the local history predates re-baselining.
885
+ `tailor tailordb migration set <N>` updates the `sdk-migration` label on the deployed namespace's metadata. **It does not modify any data or schema.** It only changes which migrations the next `apply` will consider pending. The command also records the migration history ID of the local baseline on the namespace.
886
+
887
+ The command refuses to run when the namespace records a migration history ID other than the local one, or one that cannot be read, unless the namespace has neither a checkpoint nor a history ID yet. Run it from a checkout of the migration history deployed to the workspace. To write the local history over another one on purpose, for example after `migration rebaseline` re-baselined the local history but could not update the workspace, pass `--force`: the command shows the history ID it replaces, records the local history ID or, when the local history has none, removes the namespace's one, and still asks for confirmation unless you pass `--yes`.
868
888
 
869
889
  The migration number is validated before anything is sent to the remote: it must be a 4-digit value (e.g. `0001`) or a bare integer (e.g. `1`) within 0–9999, and must exist in the working tree's migration history, which is itself validated (a gapped history is rejected). `0` is always accepted as the baseline (even when no migrations directory exists yet), provided the history passes validation.
870
890
 
@@ -883,7 +903,7 @@ Use cases:
883
903
 
884
904
  ## `migration sync` Semantics
885
905
 
886
- `tailor tailordb migration sync <N>` reconstructs the schema snapshot at migration `N` from the working tree's migration history and **overwrites the remote schema to match it**, then sets the `sdk-migration` label to `N` and aligns the remote migration history ID with the local baseline. Unlike `migration set`, it changes the remote schema as well as the bookkeeping. Like `set`, it never runs `migrate.ts` scripts itself — it only changes what the next `apply` considers pending:
906
+ `tailor tailordb migration sync <N>` reconstructs the schema snapshot at migration `N` from the working tree's migration history and **overwrites the remote schema to match it**, then sets the `sdk-migration` label to `N` and records the migration history ID of the local baseline. Unlike `migration set`, it changes the remote schema as well as the bookkeeping. Like `set`, it never runs `migrate.ts` scripts itself — it only changes what the next `apply` considers pending:
887
907
 
888
908
  | Movement | Effect on next `apply` | Effect on data |
889
909
  | -------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
@@ -892,9 +912,14 @@ Use cases:
892
912
 
893
913
  Before anything is sent to the remote, `sync` verifies that replaying the full migration history reproduces the current local table definitions. If it does not — because migration files were edited and no longer match, or because a schema change has not been recorded with `migration generate` yet — the command fails without touching the remote. This means a rewritten migration history is validated before it can overwrite the deployed schema.
894
914
 
915
+ Like `set`, `sync` refuses to run when the namespace records another migration history ID, or one that cannot be read, before it reads or changes any table. With `--force`, it overwrites both the tables and the history ID with those of the local history, and still asks for confirmation unless you pass `--yes`.
916
+
895
917
  Because syncing backward causes already-applied scripts to re-execute on the next deploy, **write `migrate.ts` scripts to be idempotent** (see [Performance and Large Tables](#performance-and-large-tables) for resumable `where` clauses).
896
918
 
897
- The main use case is recovering from drift after a `deploy --no-schema-check` from an older revision: instead of checking out that revision, run `migration sync <N>` to restore the remote to a known snapshot, then `tailor deploy` to apply the remaining migrations from the working tree.
919
+ The main use cases are:
920
+
921
+ - Recovering from drift after a `deploy --no-schema-check`, which deploys the local table definitions without checking them against the migration snapshots: run `migration sync <N>` to restore the remote to a known snapshot, then `tailor deploy` to apply the remaining migrations from the working tree.
922
+ - Moving a checkpoint that `tailor deploy` reports as not in the local migration history back to that history on purpose (see ["Remote migration checkpoint is not in the local migration history" error](#remote-migration-checkpoint-is-not-in-the-local-migration-history-error)).
898
923
 
899
924
  ## Team Workflow and CI/CD
900
925
 
@@ -938,7 +963,7 @@ A script that exports `steps` follows different rules once one of its steps has
938
963
 
939
964
  The rollback is best-effort per table; if reverting a table fails, a warning is logged and the original migration error is still reported. The next deploy then stops with remote schema drift on that table. Run `tailor tailordb migration validate` to see which tables differ, return the namespace to its checkpoint with `tailor tailordb migration sync <N>`, where `<N>` is the checkpoint `tailor tailordb migration status` reports, and deploy again. `sync` deletes the tables and fields that are not in that migration's snapshot, together with any rows the failed migration wrote to them; the next deploy runs the failed migration again.
940
965
 
941
- If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, the migration stays recorded as in progress, and the deploy reports that it could not confirm the outcome. The same holds when the deploy process stops while the script runs. Until you settle the migration, `tailor deploy` stops before changing anything, including the other namespaces and resources of the application, and reports how the run stands, even with `--no-schema-check`; `migration sync` and `migration rebaseline` refuse to run, and `tailor tailordb migration status` reports the migration as in progress. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished. If it succeeded, run `tailor tailordb migration resolve --complete`, which applies the migration's schema without running the script again; otherwise run `tailor tailordb migration resolve --rollback`, and the next deploy runs the script again. See [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress). `migration set <N>` with the migration's own number also marks it as completed, but it does not check the run or apply the schema, so use it only when the run succeeded and the post-migration schema is already in place.
966
+ If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, the migration stays recorded as in progress, and the deploy reports that it could not confirm the outcome. The same holds when the deploy process stops while the script runs. Until you settle the migration, `tailor deploy` stops before changing anything, including the other namespaces and resources of the application, and reports how the run stands, even with `--no-schema-check`. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished. If it succeeded, run `tailor tailordb migration resolve --complete`, which applies the migration's schema without running the script again; otherwise run `tailor tailordb migration resolve --rollback`, and the next deploy runs the script again. See [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress), which also lists the commands that refuse to run until then.
942
967
 
943
968
  If the post-migration schema changes fail after a `main` script succeeded and the deploy cannot clear the in-progress record, it keeps the pre-migration schema instead of rolling it back, so the data the script wrote stays. Settle the migration as above: `--complete` once the data meets the migration's schema, or `--rollback`.
944
969
 
@@ -952,9 +977,11 @@ After a failure:
952
977
 
953
978
  If a migration **succeeds in script** but its reversible **post-migration schema update** fails (rare; usually a constraint violation the script should have prevented), the SDK makes the same best-effort restoration to the prior-checkpoint schema. The script's committed data changes remain, so write migration scripts to tolerate re-execution.
954
979
 
980
+ Before a migration's script starts, `tailor deploy` checks that the namespace's checkpoint and migration history are still the ones it read before applying, or the checkpoint it committed itself. If another deploy or `tailor tailordb migration set` changed them in the meantime, or the deploy cannot read them, the script does not start and the migration is not recorded as in progress: the deploy handles the pre-migration schema as for a script that failed before it ran, and fails with `MIGRATION_CHECKPOINT_CONFLICT` (`MIGRATION_CHECKPOINT_UNVERIFIED` when it cannot read them). Run `tailor tailordb migration status` to see the checkpoint. Wait for the other deploy to finish, or, when no other deploy is running, return the namespace to the checkpoint the error names with `tailor tailordb migration sync <N>`, which also lifts the maintenance mode the deploy left in place, and deploy again. If the error says the checkpoint changed in a different migration history, or with a migration history label that cannot be read, `sync` also needs `--force`, which replaces the workspace's migration history ID with the local one. Pass it only when the local migration history should replace the deployed one; otherwise deploy from a checkout of the migration history deployed to the workspace.
981
+
955
982
  The checkpoint is advanced only after the reversible post-migration schema updates succeed. If the checkpoint write reports an error, the SDK reads it back: a matching value is treated as committed. Any other observed value leaves the post-migration schema unchanged rather than risk rolling back a concurrent deployment; a value beyond the current migration confirms a concurrent deploy, while an older or missing value means the checkpoint must be repaired before retrying. If read-back also fails, the SDK likewise leaves the post-migration schema unchanged; verify the remote checkpoint before retrying.
956
983
 
957
- Removed tables are deleted only after the checkpoint is committed. If that cleanup fails, the checkpoint remains at the new migration and the SDK fails closed: the leftover table is reported as remote schema drift on the next deploy. Remove the leftover GQL permission and table manually, verify the remote schema, and then retry. The SDK does not automatically ignore or delete a same-named remote table because it cannot distinguish failed cleanup from a table recreated after cleanup completed.
984
+ Removed tables are deleted only after the checkpoint is committed. If that cleanup fails, the checkpoint remains at the new migration and the SDK fails closed: the leftover table is reported as remote schema drift on the next deploy. Run `tailor tailordb migration sync <N>` with the committed migration as `<N>` (the warning names the command) to delete the leftover table and its GQL permission, or remove them manually, and then deploy again. The SDK does not automatically ignore or delete a same-named remote table because it cannot distinguish failed cleanup from a table recreated after cleanup completed.
958
985
 
959
986
  ## Rollback Strategy
960
987
 
@@ -973,7 +1000,9 @@ Migration scripts execute server-side under a machine user identity. The CLI sel
973
1000
  1. `db.<namespace>.migration.machineUser` if set in `tailor.config.ts`.
974
1001
  2. The first entry in `auth.machineUsers` otherwise.
975
1002
 
976
- The CLI logs the selected user before running scripts (`Using machine user: ...`).
1003
+ The selected user must be defined in `auth.machineUsers`. The CLI logs the selected user before running scripts (`Using machine user: ...`).
1004
+
1005
+ When a pending migration has a `migrate.ts`, `tailor deploy` checks while planning that auth is configured, that a machine user is selected and defined in `auth.machineUsers`, and that each script can be bundled. A failure stops the deploy before anything is applied, and `tailor deploy --dry-run` reports it too.
977
1006
 
978
1007
  **Permissions required**
979
1008
 
@@ -982,7 +1011,7 @@ The machine user needs read/write access to every table the migration script tou
982
1011
  If you see `No machine user available for migration execution`, either:
983
1012
 
984
1013
  - Add `machineUsers: { ... }` to your auth config and `tailor deploy` it, or
985
- - Set `migration.machineUser` to an existing machine user name in the db config.
1014
+ - Set `migration.machineUser` to a machine user defined in `auth.machineUsers`.
986
1015
 
987
1016
  ## Multi-Namespace Coordination
988
1017
 
@@ -1048,7 +1077,7 @@ describe("0005 add required email", () => {
1048
1077
  });
1049
1078
  ```
1050
1079
 
1051
- Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock) for the full mock API.
1080
+ Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock-beta) for the full mock API.
1052
1081
 
1053
1082
  These tests need no platform connection and no `tailor-runtime` environment — they run in a plain Vitest setup. Vitest's default `include` pattern already picks up `migrations/**/migrate.test.ts`; if your config narrows `include`, add the migrations directory. The test file is ignored by `tailor deploy` and never ships to the platform.
1054
1083
 
@@ -1174,7 +1203,7 @@ For genuinely different schemas across environments, prefer separate workspaces
1174
1203
 
1175
1204
  ### Remote schema drift detected
1176
1205
 
1177
- **Cause:** Remote schema doesn't match what the migration history says it should be.
1206
+ **Cause:** Remote schema doesn't match what the migration history says it should be. When `tailor deploy` shows `Remote migration: <unset>`, or `migration validate` shows `no checkpoint yet`, the namespace has no migration label yet and its tables differ from `0000`; see [Adding migrations to an existing project](#adding-migrations-to-an-existing-project) instead of the steps below.
1178
1207
 
1179
1208
  **Resolution:**
1180
1209
 
@@ -1182,11 +1211,11 @@ For genuinely different schemas across environments, prefer separate workspaces
1182
1211
  2. Compare with teammates — has someone applied different migrations?
1183
1212
  3. If remote was changed manually, decide whether to update local migrations to match or to use `migration set <N>` to align bookkeeping.
1184
1213
  4. To force the remote schema back to a known snapshot, use `migration sync <N>` (see [`migration sync` Semantics](#migration-sync-semantics)). If the drift follows a failed deploy that warned it could not restore a table's schema, see [Failure Recovery](#failure-recovery) instead.
1185
- 5. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround.
1214
+ 5. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround. It does not skip the checkpoint check described next.
1186
1215
 
1187
1216
  ### "Remote migration checkpoint is not in the local migration history" error
1188
1217
 
1189
- **Cause:** The deployed namespace's checkpoint refers to a migration number, or a migration history ID, that the local `migrations/` directory no longer has a record of. This is a different code path from schema drift above: it fires before any schema comparison, because the CLI cannot even locate the remote's recorded position in the local history.
1218
+ **Cause:** The deployed namespace's checkpoint refers to a migration number, or a migration history ID, that the local `migrations/` directory no longer has a record of. This is a different code path from schema drift above: it fires before any schema comparison, because the CLI cannot even locate the remote's recorded position in the local history. `tailor deploy` stops with this error even with `--no-schema-check`, so a deploy from an outdated checkout never moves the checkpoint back or overwrites the tables with an older schema.
1190
1219
 
1191
1220
  One specific cause is a `migration rebaseline` run on a different environment while this one had not yet caught up to the latest pre-rebaseline migration. `migration rebaseline` requires every other environment to already be at the latest migration (see [Re-baselining a deployed migration history](#re-baselining-a-deployed-migration-history)), but the CLI only verifies the one workspace it runs against. If this environment was behind, the error message names the migration this environment must reach (the migration every environment was required to be at before the rebaseline) alongside the migration it is actually at.
1192
1221
 
@@ -1196,7 +1225,13 @@ One specific cause is a `migration rebaseline` run on a different environment wh
1196
1225
  2. Deploy this environment against that restored history until its checkpoint reaches the migration named in the error, running any `migrate.ts` scripts it still needs.
1197
1226
  3. Switch back to the current (rebaselined) migration files and deploy again. The remote schema now matches the new baseline, so this deploy offers the automatic checkpoint reset to `0000` under the new history ID.
1198
1227
 
1199
- **Resolution (any other cause):** Run `tailor tailordb migration status` to compare local and remote, or pull the latest migration files if your checkout is stale.
1228
+ **Resolution (any other cause):** Run `tailor tailordb migration status` to compare local and remote, and pull the latest migration files if your checkout is stale. To move the checkpoint back to the local migration history on purpose, run `tailor tailordb migration sync <N>`, which also rewrites the tables to snapshot `<N>`, or `tailor tailordb migration set <N>`, which moves only the checkpoint, with the latest local migration as `<N>` (the error suggests both commands). When the namespace records another migration history ID, or one that cannot be read, both commands also need `--force`, which replaces that history ID with the local one, or removes it when the local history has none (see [`migration set` Semantics](#migration-set-semantics)). Pass `--force` only when the local migration history should replace the deployed one; otherwise work from a checkout of the migration history deployed to the workspace. Then deploy again.
1229
+
1230
+ ### `MIGRATION_FILES_INVALID` error
1231
+
1232
+ **Cause:** The `migrations/` directory of a namespace does not form a valid history: a migration number is missing, `0000` has no `schema.json` or has a `diff.json`, or a later migration has a `schema.json` or no `diff.json`. A deleted directory or a merge that kept only part of a migration is a common cause. `tailor deploy` and the `tailor tailordb migration` commands that read the history report this error, listing every problem.
1233
+
1234
+ **Resolution:** Follow the suggestion for each problem in the error. Otherwise restore the migration files from version control. If no workspace has been deployed with these migrations, you can instead recreate `0000` from the local tables with `tailor tailordb migration generate --init --namespace <name>`, which deletes every migration of the namespace.
1200
1235
 
1201
1236
  ### "Invalid schema snapshot" or "Invalid migration diff" error
1202
1237
 
@@ -1205,7 +1240,7 @@ One specific cause is a `migration rebaseline` run on a different environment wh
1205
1240
  **Resolution:**
1206
1241
 
1207
1242
  1. Read the error message — it includes the file path and the offending field.
1208
- 2. Restore the file from version control (`git checkout -- <path>`), or regenerate migration files with `migration generate` / `migration script`.
1243
+ 2. Restore the file from version control (`git checkout -- <path>`); `migration generate` reads every migration file and stops with the same error until then. If the broken file belongs to the latest migration and no environment has deployed it, you can instead recreate that migration: copy out the files you edited in its directory (such as `migrate.ts` and its tests), delete the directory, run `migration generate` with the options that created it (such as `--data-only`, `--rename`, or `--drop`), copy your edits back, and record any `--no-script` reason again with `migration script`. A migration pair created with `--expand-contract` cannot be recreated this way, so restore its files from version control.
1209
1244
  3. Do not hand-edit `schema.json` or `diff.json`; they are managed by the CLI.
1210
1245
 
1211
1246
  ### "Unsupported migration file format version" error
@@ -1216,15 +1251,21 @@ One specific cause is a `migration rebaseline` run on a different environment wh
1216
1251
 
1217
1252
  ### "No machine user available for migration execution"
1218
1253
 
1219
- **Cause:** Neither `migration.machineUser` is set nor are there any machine users in `auth.machineUsers`.
1254
+ **Cause:** Neither `migration.machineUser` is set nor are there any machine users in `auth.machineUsers`. `tailor deploy` reports this while planning, including with `--dry-run`, before anything is applied.
1255
+
1256
+ **Resolution:** Add a machine user to `auth.machineUsers`, then run `tailor deploy` again. The deploy creates the machine user before it runs the migration scripts.
1257
+
1258
+ ### "Machine user for migration execution is not defined in auth"
1259
+
1260
+ **Cause:** `migration.machineUser`, or `--machine-user` of `tailor tailordb migration test`, names a machine user that is not in `auth.machineUsers`. `tailor deploy` reports this while planning, including with `--dry-run`, before anything is applied.
1220
1261
 
1221
- **Resolution:** Add a machine user to auth, apply auth changes, then re-run.
1262
+ **Resolution:** Either add the machine user to `auth.machineUsers`, or name one that is defined there.
1222
1263
 
1223
1264
  ### "Machine user not found"
1224
1265
 
1225
- **Cause:** `migration.machineUser` references a name that doesn't exist in the deployed auth config.
1266
+ **Cause:** In `tailor tailordb migration test`, the machine user that runs the seed or the assertion (from `--machine-user`, the seed plugin's `machineUserName`, or `migration.machineUser`) is not in `auth.machineUsers`, so the temporary workspace does not have it.
1226
1267
 
1227
- **Resolution:** Either add the machine user to `auth.machineUsers` and apply, or change `migration.machineUser` to a valid name.
1268
+ **Resolution:** Either add the machine user to `auth.machineUsers`, or name one that is defined there.
1228
1269
 
1229
1270
  ### Migration script execution fails
1230
1271
 
@@ -352,7 +352,7 @@ export default createResolver({
352
352
  });
353
353
  ```
354
354
 
355
- Wait points can be imported and used in any file (workflow jobs, resolvers, executors). For local testing, see [Jobs that wait on approval](../testing.md#jobs-that-wait-on-approval) in the testing guide.
355
+ Wait points can be imported and used in any file (workflow jobs, resolvers, executors). For local testing, see [Jobs that wait on approval](../testing.md#jobs-that-wait-on-approval-beta) in the testing guide.
356
356
 
357
357
  ### Keys With Runtime Values
358
358
 
package/docs/testing.md CHANGED
@@ -18,14 +18,14 @@ Unit-test entrypoints exposed by the SDK:
18
18
  - `resolver.body({ input, caller, invoker, env })` — invoke a resolver
19
19
  - `workflowJob.body(input, { env, invoker })` — invoke a workflow job body directly
20
20
  - `workflowJob.start(input)` — chain a workflow job through the workflow runtime
21
- - `runWorkflowLocally(workflow, args)` — run a workflow chain locally with real job bodies
21
+ - `runWorkflowLocally(workflow, args)` (Beta) — run a workflow chain locally with real job bodies
22
22
  - `executor.operation.body({ ...args, invoker })` — invoke a function-kind executor
23
23
 
24
24
  For anonymous direct calls:
25
25
 
26
26
  - Pass `null` for anonymous `caller` / `invoker` context in direct unit tests.
27
27
 
28
- Platform API mocks under `@tailor-platform/sdk/vitest` (for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below):
28
+ Platform API mocks under `@tailor-platform/sdk/vitest` (Beta), for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below:
29
29
 
30
30
  - `mockTailordb` — TailorDB query stubs and call recording
31
31
  - `mockTailordbWithPGlite` — TailorDB backed by a real in-memory Postgres (PGlite)
@@ -227,7 +227,7 @@ To hand-write DDL instead — for a table not in the schema, or to add a constra
227
227
  - Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally). This only affects test setup; it cannot supply a `.serial()` value for an insert issued by the code under test.
228
228
  - With `kyselyTypePlugin({ temporal: true })`, the generated `getDB` reads top-level date, datetime, and time columns back from PGlite as `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, as it does on the platform. Values inside nested objects stay strings in PGlite (see above); on the platform, nested date/datetime fields are Temporal values while nested time fields remain strings.
229
229
  - Transactions on a shared instance are serialized: while one is open, queries from other `getDB` instances wait. Do not use `test.concurrent` with a shared instance, and do not query the same instance through a second `getDB` from inside a transaction — that waits on itself.
230
- - PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock) tests for query shape and error paths, and E2E tests for platform behavior.
230
+ - PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock-beta) tests for query shape and error paths, and E2E tests for platform behavior.
231
231
 
232
232
  ### Workflow Mock
233
233
 
@@ -642,7 +642,7 @@ describe("decrementUserAge", () => {
642
642
 
643
643
  **Use when:** multi-step business logic. The tests survive query rewrites because they assert high-level intent, not SQL shape.
644
644
 
645
- #### Kysely-layer mock (`createKyselyMock`)
645
+ #### Kysely-layer mock (`createKyselyMock`) (Beta)
646
646
 
647
647
  `createKyselyMock` returns a real Kysely instance whose execution is mocked. Stage the rows each query returns, run your code, then assert what it did — each query's SQL and parameters, how many `selects`/`inserts`/`updates`/`deletes` ran, and the value your code returned. Queries stay fully typed and compile to the same SQL as production.
648
648
 
@@ -697,7 +697,7 @@ Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want t
697
697
 
698
698
  TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally).
699
699
 
700
- #### Resolvers that resume a workflow
700
+ #### Resolvers that resume a workflow (Beta)
701
701
 
702
702
  Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().waitPoint(definition)` to invoke the callback with the payload that originally suspended the job:
703
703
 
@@ -731,7 +731,7 @@ describe("resolveApproval resolver", () => {
731
731
 
732
732
  `setResolvePayload` is typed from the wait-point definition. The lower-level `setResolveHandler` and `resolveCalls` APIs remain available when a test must control several wait points together.
733
733
 
734
- #### Resolvers that resume failed workflows
734
+ #### Resolvers that resume failed workflows (Beta)
735
735
 
736
736
  Resolvers or executors that call `workflow.resumeWorkflowExecution(executionId)` delegate to `tailor.workflow.resumeWorkflowExecution` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().setResumeHandler` to control the returned execution ID and inspect `resumeWorkflowExecution.mock.calls`:
737
737
 
@@ -838,7 +838,7 @@ describe("validateOrder", () => {
838
838
  });
839
839
  ```
840
840
 
841
- #### Jobs that start other jobs
841
+ #### Jobs that start other jobs (Beta)
842
842
 
843
843
  Use `mockWorkflow().job(definition)` to replace dependent jobs with deterministic results:
844
844
 
@@ -878,7 +878,7 @@ describe("fulfillOrder", () => {
878
878
 
879
879
  **Use when:** you want to isolate the orchestrating job from its dependencies.
880
880
 
881
- #### Jobs that wait on approval
881
+ #### Jobs that wait on approval (Beta)
882
882
 
883
883
  `.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `mockWorkflow().waitPoint(definition)` to drive each branch with a typed mock:
884
884
 
@@ -921,7 +921,7 @@ describe("processWithApproval", () => {
921
921
 
922
922
  The lower-level `setWaitHandler` and `waitCalls` APIs remain available when one handler must cover several wait points.
923
923
 
924
- #### Running a full workflow locally
924
+ #### Running a full workflow locally (Beta)
925
925
 
926
926
  To exercise the full chain with real job bodies, call `runWorkflowLocally(workflow, args)`. Dependent jobs run their real `.body()` functions, and start args/results cross the same JSON boundary as the platform, so a non-serializable payload fails the test exactly as it would in production:
927
927
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -160,8 +160,8 @@
160
160
  "@bufbuild/protovalidate": "1.3.0",
161
161
  "@connectrpc/connect": "2.2.0",
162
162
  "@connectrpc/connect-node": "2.2.0",
163
- "@inquirer/core": "12.0.5",
164
- "@inquirer/prompts": "8.7.4",
163
+ "@inquirer/core": "12.0.6",
164
+ "@inquirer/prompts": "8.7.5",
165
165
  "@jridgewell/trace-mapping": "0.3.31",
166
166
  "@napi-rs/keyring": "2.1.2",
167
167
  "@opentelemetry/api": "1.10.0",
@@ -170,10 +170,10 @@
170
170
  "@opentelemetry/resources": "3.0.0",
171
171
  "@opentelemetry/sdk-trace": "3.0.0",
172
172
  "@opentelemetry/semantic-conventions": "1.43.0",
173
- "@oxc-project/types": "0.155.0",
173
+ "@oxc-project/types": "0.156.0",
174
174
  "@politty/zod": "0.6.3",
175
- "@secretlint/core": "13.0.7",
176
- "@secretlint/secretlint-rule-preset-recommend": "13.0.7",
175
+ "@secretlint/core": "13.1.0",
176
+ "@secretlint/secretlint-rule-preset-recommend": "13.1.0",
177
177
  "@standard-schema/spec": "1.1.0",
178
178
  "@tailor-platform/function-kysely-tailordb": "0.1.3",
179
179
  "@toiroakr/read-multiline": "0.4.3",
@@ -189,10 +189,10 @@
189
189
  "graphql": "17.0.2",
190
190
  "inflection": "3.0.2",
191
191
  "kysely": "0.29.7",
192
- "lines-db": "0.18.0",
192
+ "lines-db": "0.18.1",
193
193
  "mime-types": "3.0.2",
194
194
  "open": "11.0.4",
195
- "oxc-parser": "0.155.0",
195
+ "oxc-parser": "0.156.0",
196
196
  "p-limit": "7.3.3",
197
197
  "pathe": "2.0.3",
198
198
  "pgsql-ast-parser": "12.0.2",
@@ -210,16 +210,16 @@
210
210
  },
211
211
  "devDependencies": {
212
212
  "@ark/attest": "0.58.0",
213
- "@electric-sql/pglite": "0.5.11",
213
+ "@electric-sql/pglite": "0.5.12",
214
214
  "@tailor-platform/shared": "^0.0.0",
215
215
  "@tailor-platform/tailor-proto": "^0.0.1",
216
216
  "@types/mime-types": "3.0.1",
217
- "@types/node": "24.19.2",
217
+ "@types/node": "26.6.5",
218
218
  "@types/semver": "7.8.0",
219
219
  "@vitest/coverage-v8": "5.0.5",
220
220
  "eslint-plugin-zod": "5.2.2",
221
- "oxfmt": "0.74.0",
222
- "oxlint": "1.89.0",
221
+ "oxfmt": "0.75.0",
222
+ "oxlint": "1.90.0",
223
223
  "oxlint-tsgolint": "7.0.2005",
224
224
  "sonda": "0.14.0",
225
225
  "tsdown": "0.23.2",
@@ -227,7 +227,7 @@
227
227
  "tsx": "4.23.15",
228
228
  "typescript": "6.0.3",
229
229
  "vitest": "5.0.5",
230
- "zinfer": "0.4.7"
230
+ "zinfer": "0.5.1"
231
231
  },
232
232
  "peerDependencies": {
233
233
  "@electric-sql/pglite": ">=0.2.0",