@tailor-platform/sdk 2.36.0 → 2.38.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 (164) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/dist/{application-DaJbleMi.mjs → application-Bz80INH4.mjs} +1 -1
  3. package/dist/application-CeqD1OK-.mjs +207 -0
  4. package/dist/application-CeqD1OK-.mjs.map +1 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +7 -1
  6. package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +3 -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 +3 -3
  23. package/dist/cli/lib.mjs +1 -1
  24. package/dist/cli/lib.mjs.map +1 -1
  25. package/dist/cli/main.mjs +65 -58
  26. package/dist/cli/main.mjs.map +1 -1
  27. package/dist/cli/shared/args.d.mts +14 -5
  28. package/dist/cli/shared/client.d.mts +40 -3
  29. package/dist/cli/shared/error-json.d.mts +12 -26
  30. package/dist/cli/shared/function-execution.d.mts +2 -0
  31. package/dist/cli/shared/prompt.d.mts +9 -4
  32. package/dist/client-CHXix4LD.mjs +1 -0
  33. package/dist/client-DOQv-BP8.mjs +2 -0
  34. package/dist/client-DOQv-BP8.mjs.map +1 -0
  35. package/dist/completion/zsh-worker.zsh +151 -79
  36. package/dist/configure/config/index.d.mts +2 -0
  37. package/dist/configure/config/types.d.mts +2 -0
  38. package/dist/configure/index.mjs.map +1 -1
  39. package/dist/configure/services/executor/operation.d.mts +5 -4
  40. package/dist/configure/services/http-adapter/http-adapter.d.mts +1 -1
  41. package/dist/configure/services/index.d.mts +0 -1
  42. package/dist/configure/services/tailordb/schema.d.mts +1 -0
  43. package/dist/configure/services/tailordb/types.d.mts +12 -0
  44. package/dist/context-DSi5kfqr.mjs +23 -0
  45. package/dist/context-DSi5kfqr.mjs.map +1 -0
  46. package/dist/context-OOASEQLA.mjs +1 -0
  47. package/dist/{crashreport-DGU6lwiX.mjs → crashreport-CQTAlnbA.mjs} +4 -4
  48. package/dist/{crashreport-DGU6lwiX.mjs.map → crashreport-CQTAlnbA.mjs.map} +1 -1
  49. package/dist/crashreport-GRnWyDnF.mjs +1 -0
  50. package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -1
  51. package/dist/{errors-BXmGBX-7.mjs → errors-9OOL3xOM.mjs} +6 -5
  52. package/dist/errors-9OOL3xOM.mjs.map +1 -0
  53. package/dist/field_behavior_pb-B196sUx6.mjs +2 -0
  54. package/dist/field_behavior_pb-B196sUx6.mjs.map +1 -0
  55. package/dist/{kysely-type-B0ri1-Xs.mjs → kysely-type-DhAZqdQg.mjs} +14 -10
  56. package/dist/kysely-type-DhAZqdQg.mjs.map +1 -0
  57. package/dist/notification_resource_pb-BZ0hCA01.mjs +1 -0
  58. package/dist/notification_resource_pb-Dwzah7gO.mjs +2 -0
  59. package/dist/notification_resource_pb-Dwzah7gO.mjs.map +1 -0
  60. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  61. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -3
  62. package/dist/plugin/get-generated-table.d.mts +4 -0
  63. package/dist/plugin/index.mjs.map +1 -1
  64. package/dist/plugin/types.d.mts +55 -3
  65. package/dist/plugin/with-context.d.mts +16 -0
  66. package/dist/register-ts-hook-CXYYYgCl.mjs +1094 -0
  67. package/dist/register-ts-hook-CXYYYgCl.mjs.map +1 -0
  68. package/dist/repl-editor-Bposemei.mjs +2 -0
  69. package/dist/repl-editor-Bposemei.mjs.map +1 -0
  70. package/dist/resource_pb-elY668ZB.mjs +2 -0
  71. package/dist/resource_pb-elY668ZB.mjs.map +1 -0
  72. package/dist/schema-BU1aIQ63.mjs.map +1 -1
  73. package/dist/{secret-file-BxZ-J_Nz.mjs → secret-file-CSXzcg4c.mjs} +2 -2
  74. package/dist/{secret-file-BxZ-J_Nz.mjs.map → secret-file-CSXzcg4c.mjs.map} +1 -1
  75. package/dist/{service-CaGmqg0r.mjs → service-B2vx6CGM.mjs} +2 -2
  76. package/dist/{service-CaGmqg0r.mjs.map → service-B2vx6CGM.mjs.map} +1 -1
  77. package/dist/service-DB-RHn25.mjs +9 -0
  78. package/dist/service-DB-RHn25.mjs.map +1 -0
  79. package/dist/service-DsAWXbVX.mjs +1 -0
  80. package/dist/service_pb-Bethc2NW.mjs +1 -0
  81. package/dist/service_pb-CfpSriCy.mjs +2 -0
  82. package/dist/service_pb-CfpSriCy.mjs.map +1 -0
  83. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +3 -3
  84. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +92 -0
  85. package/dist/tailor-proto/src/tailor/v1/notification_pb.d.mts +176 -0
  86. package/dist/tailor-proto/src/tailor/v1/notification_resource_pb.d.mts +172 -0
  87. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +78 -0
  88. package/dist/types/app-config.generated.d.mts +1 -1
  89. package/dist/types/auth-connection.generated.d.mts +2 -2
  90. package/dist/types/auth.generated.d.mts +13 -13
  91. package/dist/types/executor.generated.d.mts +25 -25
  92. package/dist/types/field.generated.d.mts +2 -2
  93. package/dist/types/http-adapter.generated.d.mts +4 -4
  94. package/dist/types/idp.generated.d.mts +272 -272
  95. package/dist/types/resolver.generated.d.mts +10 -10
  96. package/dist/types/tailordb.generated.d.mts +84 -80
  97. package/dist/types/workflow.generated.d.mts +2 -2
  98. package/dist/validation-errors-DqCeUW4L.mjs +3 -0
  99. package/dist/validation-errors-DqCeUW4L.mjs.map +1 -0
  100. package/{LICENSE → dist/vendor/graphql/LICENSE} +6 -6
  101. package/dist/vendor/graphql/language/KindTypeMap.d.mts +3 -0
  102. package/dist/vendor/graphql/language/ast.d.mts +784 -0
  103. package/dist/vendor/graphql/language/kinds_.d.mts +203 -0
  104. package/dist/vendor/graphql/language/source.d.mts +46 -0
  105. package/dist/vendor/graphql/language/tokenKind.d.mts +35 -0
  106. package/dist/vitest/date-representation.d.mts +2 -0
  107. package/dist/vitest/environment.d.mts +1 -0
  108. package/dist/vitest/environment.mjs.map +1 -1
  109. package/dist/vitest/index.mjs.map +1 -1
  110. package/dist/vitest/migration-steps.d.mts +13 -2
  111. package/dist/vitest/mock-kysely.d.mts +12 -2
  112. package/dist/vitest/mocks/aigateway.d.mts +7 -1
  113. package/dist/vitest/mocks/authconnection.d.mts +7 -1
  114. package/dist/vitest/mocks/file.d.mts +8 -2
  115. package/dist/vitest/mocks/iconv.d.mts +7 -1
  116. package/dist/vitest/mocks/idp.d.mts +7 -1
  117. package/dist/vitest/mocks/logger.d.mts +7 -1
  118. package/dist/vitest/mocks/secretmanager.d.mts +7 -1
  119. package/dist/vitest/mocks/tailordb-pglite.d.mts +12 -2
  120. package/dist/vitest/mocks/tailordb.d.mts +22 -4
  121. package/dist/vitest/mocks/workflow.d.mts +2 -1
  122. package/dist/vitest/pglite-kysely.d.mts +18 -2
  123. package/dist/vitest/pglite-temporal.d.mts +5 -1
  124. package/dist/vitest/workflow-local.d.mts +7 -0
  125. package/dist/workspace_resource_pb-Cow7mrJY.mjs +2 -0
  126. package/dist/workspace_resource_pb-Cow7mrJY.mjs.map +1 -0
  127. package/docs/cli/application.md +63 -29
  128. package/docs/cli/executor.md +2 -0
  129. package/docs/cli/function.md +3 -1
  130. package/docs/cli/notification.md +128 -0
  131. package/docs/cli/tailordb.md +45 -68
  132. package/docs/cli/workflow.md +2 -0
  133. package/docs/cli/workspace.md +20 -18
  134. package/docs/cli-reference.md +231 -50
  135. package/docs/configuration.md +2 -2
  136. package/docs/data-clone.md +1 -1
  137. package/docs/github-actions.md +266 -41
  138. package/docs/plugin/custom.md +4 -4
  139. package/docs/runtime.md +1 -1
  140. package/docs/services/auth.md +2 -0
  141. package/docs/services/idp.md +2 -0
  142. package/docs/services/tailordb/index.md +13 -4
  143. package/docs/services/tailordb/migration.md +109 -71
  144. package/docs/services/workflow.md +1 -1
  145. package/docs/testing.md +9 -10
  146. package/package.json +23 -19
  147. package/dist/application-BbORWCj-.mjs +0 -207
  148. package/dist/application-BbORWCj-.mjs.map +0 -1
  149. package/dist/configure/services/http-adapter/index.d.mts +0 -2
  150. package/dist/crashreport-WsYAWARW.mjs +0 -1
  151. package/dist/errors-BXmGBX-7.mjs.map +0 -1
  152. package/dist/kysely-type-B0ri1-Xs.mjs.map +0 -1
  153. package/dist/register-ts-hook-iAcG1dfz.mjs +0 -1057
  154. package/dist/register-ts-hook-iAcG1dfz.mjs.map +0 -1
  155. package/dist/repl-editor-DkrCZmmg.mjs +0 -2
  156. package/dist/repl-editor-DkrCZmmg.mjs.map +0 -1
  157. package/dist/service-DTrVVaDJ.mjs +0 -1
  158. package/dist/service-hIarp2Gl.mjs +0 -8
  159. package/dist/service-hIarp2Gl.mjs.map +0 -1
  160. package/dist/service_pb-6EkMfdOb.mjs +0 -2
  161. package/dist/service_pb-6EkMfdOb.mjs.map +0 -1
  162. package/dist/service_pb-CsJKokDl.mjs +0 -1
  163. package/dist/workspace_resource_pb-C4CkoYww.mjs +0 -2
  164. package/dist/workspace_resource_pb-C4CkoYww.mjs.map +0 -1
@@ -1,11 +1,12 @@
1
1
  ---
2
- description: "Generate GitHub Actions deploy workflows and Renovate configuration for your app with tailor setup, including approval gates, secrets, monorepos, and rollback."
2
+ description: "Generate GitHub Actions deploy workflows, a workflow that commits generated files to pull requests, and Renovate configuration for your app with tailor setup, including approval gates, secrets, monorepos, and rollback."
3
3
  ---
4
4
 
5
- # GitHub Actions Integration
5
+ # GitHub Actions Integration (Beta)
6
6
 
7
7
  `tailor setup` generates GitHub repository automation for your Tailor Platform
8
- application, including deploy workflows and Renovate configuration.
8
+ application, including deploy workflows, a workflow that commits generated
9
+ files to pull requests, and Renovate configuration.
9
10
 
10
11
  > **Beta:** This command is under active development. CLI flags, the generated
11
12
  > workflow, and the `.github/tailor.lock` schema may change before general
@@ -172,8 +173,8 @@ and file blobs are not copied.
172
173
 
173
174
  Two setups are supported:
174
175
 
175
- - **Source is the plan/deploy workspace (default).** The job uses the same GitHub
176
- Environment as the plan and deploy jobs, so it reads the same
176
+ - **Source is the deploy workspace (default).** The job uses the same GitHub
177
+ Environment as the deploy job, so it reads the same
177
178
  `TAILOR_PLATFORM_WORKSPACE_ID` variable and machine-user secrets. Nothing
178
179
  extra to configure. If that workspace holds too little data to be a useful
179
180
  test, point the job at a workspace that does with the next setup.
@@ -203,6 +204,51 @@ the source workspace holds production data.
203
204
  Fork pull requests cannot read secrets, so the job is skipped for them.
204
205
  `--migration-test` is not available together with several `--dir` values.
205
206
 
207
+ #### Running the plan job in its own environment
208
+
209
+ The plan job runs on pull requests, and a pull request from a branch of the same
210
+ repository can change the workflow it runs, so its code can read every secret
211
+ and variable of the environment the plan job enters. When plan and deploy share
212
+ one environment, that includes the deploy machine user. Pass
213
+ `--split-plan-environment` on a branch target to run the plan job in an
214
+ environment of its own:
215
+
216
+ ```bash
217
+ tailor setup ci branch --name my-app-stg --split-plan-environment
218
+ ```
219
+
220
+ The plan job then enters an environment named after the deploy environment with
221
+ `-plan` appended (`my-app-stg-plan` here). The deploy job, and the migration test job unless you
222
+ pass `--migration-test-environment`, stay in the deploy environment. Set these
223
+ on the plan environment:
224
+
225
+ - `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` and
226
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` of a machine user of its own. The
227
+ plan only reads, so a viewer role on the organization or folder that holds the
228
+ workspace is enough. Set them on the plan environment itself: otherwise the job
229
+ reads the ones set on the repository or organization, and
230
+ `tailor setup ci env --check` reports them as missing.
231
+ - `TAILOR_PLATFORM_WORKSPACE_ID`, the same workspace the deploy environment
232
+ deploys to.
233
+
234
+ Keep the deploy machine user off the plan environment. `tailor setup ci env`
235
+ lists what each of the two environments needs.
236
+
237
+ The flag alone does not keep the deploy credentials from a pull request, which
238
+ can point the plan job back at the deploy environment. Under **Settings →
239
+ Environments**, limit the deploy environment's **Deployment branches and tags**
240
+ to the target branch, so that GitHub refuses a pull request job in it. With that
241
+ limit in place:
242
+
243
+ - The migration test job also runs on pull requests, so give it its own
244
+ environment with `--migration-test-environment` (see
245
+ [Choosing the source workspace](#choosing-the-source-workspace)).
246
+ - The jobs of a preview target run on pull requests too, so pass
247
+ `setup ci preview` an `--environment` other than the limited one.
248
+
249
+ `setup update` keeps the flag, but re-running `setup ci branch` does not carry it
250
+ over: pass it each time, or the plan job moves back into the deploy environment.
251
+
206
252
  ### Tag target (recommended for production)
207
253
 
208
254
  The tag target fires when a tag matching `--tag-pattern` (default `v*`) is
@@ -258,6 +304,35 @@ that workspace when the pull request is closed:
258
304
  tailor setup ci preview --name my-app --region asia-northeast
259
305
  ```
260
306
 
307
+ It writes two workflows. `tailor-my-app-preview.yml` deploys the pull request
308
+ when it is opened or pushed to, and `tailor-my-app-preview-cleanup.yml` deletes
309
+ the workspace when the pull request is closed. The cleanup workflow starts on
310
+ `pull_request_target`, which GitHub also triggers for a pull request with merge
311
+ conflicts, where workflows on `pull_request` do not start. A
312
+ `pull_request_target` workflow can read the repository's secrets whoever opened
313
+ the pull request, so GitHub runs it from the default branch: the workflow file
314
+ and the code it checks out are the default branch's, never the pull request's.
315
+ Therefore:
316
+
317
+ - The cleanup workflow runs only once it is on the default branch, and it
318
+ installs dependencies and runs `tailor` from the default branch's checkout.
319
+ If the preview target deploys pull requests into another branch
320
+ (`--branch`), commit the cleanup workflow, the lockfile, and the app's
321
+ `tailor.config.ts` to the default branch too.
322
+ - If the target's GitHub Environment restricts which branches can deploy,
323
+ allow the default branch: GitHub checks the cleanup job against it, and the
324
+ deploy job against the pull request's merge ref.
325
+
326
+ When `tailor setup update` regenerates a preview workflow generated before the
327
+ cleanup workflow existed, it moves the `tailor-preview-cleanup` job into the
328
+ cleanup workflow, together with the steps and settings you added to it, and
329
+ copies the top-level `env`, `defaults`, and `concurrency` of the preview
330
+ workflow into the cleanup workflow. Your steps then run from the default branch
331
+ as described above. A job of yours in the preview workflow that runs when the
332
+ pull request is closed no longer runs, because that workflow no longer starts
333
+ then; move it into the cleanup workflow. `setup` warns about each job of yours
334
+ in the preview workflow whose `if` contains an `== 'closed'` comparison.
335
+
261
336
  Fork pull requests are skipped. Every other pull request gets a preview, draft
262
337
  pull requests included. `--require` narrows that to one of:
263
338
 
@@ -272,6 +347,73 @@ When `tailor setup update` regenerates a preview target generated before
272
347
  second now also deploys a labeled draft. To deploy drafts as well, run
273
348
  `tailor setup ci preview` again without `--require`.
274
349
 
350
+ ## Committing generated files to pull requests
351
+
352
+ Each deploy workflow checks that the files `tailor generate` writes, such as
353
+ seed data and enum constants, are committed, and fails when they are out of
354
+ date. To have CI commit them instead, generate the autofix workflow:
355
+
356
+ ```bash
357
+ tailor setup ci autofix # commit with the workflow token
358
+ tailor setup ci autofix --provider github-app # commit with your GitHub App
359
+ tailor setup ci autofix --dir apps/erp --dir apps/users # run tailor generate in each app
360
+ ```
361
+
362
+ It writes `.github/workflows/tailor-autofix.yml`, which runs `tailor generate`
363
+ on every pull request and, when that changes files, commits them to the pull
364
+ request branch as `chore: run tailor generate`. Only the changes
365
+ `tailor generate` makes are committed: files that earlier steps leave in the
366
+ checkout, such as an `.npmrc`, are not. The deploy workflows keep their check,
367
+ which fails on the commit that needed the fix and passes on the fix commit.
368
+
369
+ `--provider` chooses what makes the commit:
370
+
371
+ | Provider | Commits as | Checks on the fix commit | Setup |
372
+ | ------------------------ | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
373
+ | `github-token` (default) | `github-actions[bot]`, with the workflow token | Wait until someone with write access selects **Approve workflows to run** on the pull request | None |
374
+ | `github-app` | Your GitHub App | Run automatically | Create a GitHub App with the Contents read and write permission, install it on the repository, and set `TAILOR_AUTOFIX_APP_CLIENT_ID` and `TAILOR_AUTOFIX_APP_PRIVATE_KEY` (see [Setting secrets and variables](#setting-secrets-and-variables)) |
375
+ | `autofix-ci` | The [autofix.ci](https://autofix.ci) GitHub App | Run automatically | Install the autofix.ci App on the repository |
376
+ | `securefix` | Your [securefix](https://github.com/csm-actions/securefix-action) server workflow | Run automatically | Set up the securefix server repository and its GitHub Apps, pass the server repository's name with `--securefix-server-repository`, and set the client App's `TAILOR_AUTOFIX_APP_CLIENT_ID` and `TAILOR_AUTOFIX_APP_PRIVATE_KEY` |
377
+
378
+ GitHub does not start workflows on its own for a commit pushed with the workflow
379
+ token, so with `github-token` the pull request shows that its workflows are
380
+ awaiting approval after each fix commit. Use another provider to have them run
381
+ right away.
382
+
383
+ - **Fork and Dependabot pull requests** get no write token or secrets, so the
384
+ autofix workflow skips them, except with `autofix-ci`, whose App commits to
385
+ fork pull requests. Their authors run `tailor generate` and commit the result
386
+ as before.
387
+ - **A branch that moved on:** if someone pushes to the branch while the
388
+ workflow runs, it does not commit; the run for the new commit regenerates the
389
+ files.
390
+ - **Output that keeps changing:** if `tailor generate` changes files again
391
+ right after the fix commit, the workflow fails instead of committing again.
392
+ Check that running it twice in a row changes nothing.
393
+ - **Approval rules:** the fix commit becomes the most recent push to the pull
394
+ request, and a bot pushed it. A branch protection rule that requires the most
395
+ recent push to be approved by someone other than its pusher is then satisfied
396
+ by an approval from anyone else, including whoever pushed the commit before
397
+ it.
398
+ - **Renovate** stops updating a pull request that has commits from someone
399
+ else. Add the fix commits' author email to `gitIgnoredAuthors` in your
400
+ Renovate configuration, such as
401
+ `41898282+github-actions[bot]@users.noreply.github.com` for `github-token`.
402
+ - **Secrets:** with `github-app`, every workflow in the repository can read the
403
+ App's private key, which can write to the repository. Give the App only the
404
+ Contents permission and install it only where it commits. `securefix` keeps
405
+ the key that writes in the server repository instead.
406
+ - **Dependabot** stops rebasing a pull request that has commits from someone
407
+ else, which happens when `autofix-ci` commits to one of its pull requests.
408
+ - With `autofix-ci` and `securefix`, the workflow is named `autofix.ci` and
409
+ `securefix`, the names those services accept requests from by default. With
410
+ `autofix-ci`, it also gives git its read-only token after `tailor generate`
411
+ finishes, because autofix.ci fetches the pull request head itself.
412
+ - GitHub Enterprise Server is not supported.
413
+
414
+ To stop committing, delete the workflow with
415
+ `tailor setup delete .github/workflows/tailor-autofix.yml`.
416
+
275
417
  ## Targeting a workspace
276
418
 
277
419
  The generated `plan` and `deploy` jobs target a workspace by **id**, read from
@@ -281,7 +423,9 @@ environment before the first deploy.
281
423
 
282
424
  Because the variable is scoped to a GitHub Environment, both the `plan` and
283
425
  `deploy` jobs declare `environment:` so the value resolves. When you omit
284
- `--environment`, the environment name defaults to the workspace name.
426
+ `--environment`, the environment name defaults to the workspace name. With
427
+ `--split-plan-environment`, set the variable on the plan environment as well (see
428
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
285
429
 
286
430
  1. Provision the workspace (once per environment) and obtain its id. For now
287
431
  this is a manual step:
@@ -306,8 +450,9 @@ provisioned yet instead of running a dry-run.
306
450
 
307
451
  If the target environment has required reviewers, that approval gate applies to
308
452
  the `plan` job as well as `deploy`, because `plan` must enter the environment to
309
- read the variable. (A token-based read that lets `plan` run without entering the
310
- environment is planned.)
453
+ read the variable, unless a branch target runs `plan` in its own environment with
454
+ `--split-plan-environment`. (A token-based read that lets `plan` run without
455
+ entering the environment is planned.)
311
456
 
312
457
  ## Package manager
313
458
 
@@ -349,6 +494,12 @@ that require authentication, configure the credentials in your own step.
349
494
  See [Customizing the generated workflow](#customizing-the-generated-workflow)
350
495
  for what you can edit.
351
496
 
497
+ ### `.github/workflows/tailor-autofix.yml`
498
+
499
+ The workflow `setup ci autofix` generates (see
500
+ [Committing generated files to pull requests](#committing-generated-files-to-pull-requests)).
501
+ You can customize it the same way.
502
+
352
503
  ### Customizing the generated workflow
353
504
 
354
505
  The SDK owns the jobs and steps whose `id` starts with `tailor-`, and the
@@ -363,9 +514,9 @@ Everything else is yours, and re-running `setup` keeps it:
363
514
  (such as `playwright install`) _after_ it. A job of your own can depend on a
364
515
  managed job with `needs: tailor-deploy`.
365
516
  - **Your own top-level keys**, such as a workflow-level `env:` or `defaults:`.
366
- - **The top-level `concurrency:` of a preview workflow.** The SDK writes a
367
- default, and re-running `setup` keeps your edits to it. If you delete it,
368
- re-running `setup` adds the default back.
517
+ - **The top-level `concurrency:` of the preview and cleanup workflows.** The
518
+ SDK writes a default, and re-running `setup` keeps your edits to it. If you
519
+ delete it, re-running `setup` adds the default back.
369
520
  - **Runtime settings of managed jobs:** `runs-on`, `timeout-minutes`,
370
521
  `container`, and `env`. If every managed job that re-running `setup` keeps
371
522
  uses the same `runs-on` you set, such as self-hosted runners, a managed job
@@ -407,9 +558,9 @@ A job that runs `tailor` against the workspace needs the machine-user
407
558
  (the `--environment` value, or the workspace name when omitted) as above. A job
408
559
  that only uses `app-url`, such as end-to-end tests, does not.
409
560
 
410
- The job is skipped whenever `tailor-preview-deploy` is skipped: when a pull
411
- request is closed, for fork pull requests, for draft pull requests with
412
- `--require ready`, and for unlabeled ones with `--require label`.
561
+ The job is skipped whenever `tailor-preview-deploy` is skipped: for fork pull
562
+ requests, for draft pull requests with `--require ready`, and for unlabeled
563
+ ones with `--require label`.
413
564
 
414
565
  A preview workspace expires 7 days (`ttl: 7d` on `tailor-preview-deploy`) after
415
566
  the last push to its pull request. When a pull request is closed,
@@ -422,13 +573,32 @@ open; the next push creates a fresh one. Change `ttl` to keep previews longer or
422
573
  shorter. Workflows generated before this was added get these settings the next
423
574
  time you run `tailor setup update`.
424
575
 
425
- Each new preview workflow run for a pull request, such as after a push or when
426
- the pull request is closed, cancels the run still in progress for it, including
427
- your jobs, so they do not keep running against a workspace that the new run
428
- may redeploy or delete. Events on a pull request that stays closed, such as a
429
- label added after merging, do not cancel anything. A job with `if: always()`
430
- still runs when its run is cancelled, and the new run waits for it to finish;
431
- use `if: ${{ !cancelled() }}` instead so it stops with the run.
576
+ A preview workspace last deployed before `ttl` was added to its workflow records
577
+ no expiry, so this sweep never deletes it. To find such leftovers, list the
578
+ preview workspaces created more than 7 days ago:
579
+
580
+ ```bash
581
+ tailor workspace prune --name 'my-app-pr-[0-9]+' --older-than 7d \
582
+ --folder-id <folder id> --limit 0 --dry-run
583
+ ```
584
+
585
+ Pass `--organization-root <organization id>` instead of `--folder-id` when
586
+ `TAILOR_PLATFORM_FOLDER_ID` is unset. The list can include the previews of pull
587
+ requests that are still open: keep each of them with
588
+ `--exclude my-app-pr-<number>`, then run the command again without `--dry-run`.
589
+
590
+ Each new preview workflow run for a pull request, such as after a push, cancels
591
+ the run still in progress for it, including your jobs, so they do not keep
592
+ running against a workspace that the new run may redeploy. Closing the pull
593
+ request cancels it too, because the cleanup workflow runs in the same
594
+ concurrency group. If you change the group in one of the two workflows, change
595
+ it in the other, and build it only from values that are the same in both, such
596
+ as `github.event.pull_request.number`: `github.ref`, `github.workflow`, and
597
+ `github.event_name` differ between them, and `github.ref` is the default branch
598
+ for every pull request in the cleanup workflow. Events on a pull request that
599
+ stays closed, such as a label added after merging, do not cancel anything. A job with `if: always()` still
600
+ runs when its run is cancelled, and the new run waits for it to finish; use
601
+ `if: ${{ !cancelled() }}` instead so it stops with the run.
432
602
 
433
603
  Likewise, the `tailor-deploy` job of a branch or tag workflow exposes the
434
604
  deployed workspace as the outputs `workspace-id` and `app-url` to a job with
@@ -454,7 +624,9 @@ job back. To start over from a clean template, delete the file and re-run
454
624
  When a template update removes a managed job that contains steps of yours, or
455
625
  a job of yours `needs` a removed job, `setup` stops and names them. Move those
456
626
  steps into a job of your own (or update the `needs`) and re-run. `--force` drops
457
- steps left inside a removed job.
627
+ steps left inside a removed job. The `tailor-preview-cleanup` job, which moved
628
+ into its own workflow, keeps your steps instead (see
629
+ [Preview target](#preview-target-per-pull-request-workspace)).
458
630
 
459
631
  `setup check` reports a job or step of yours whose `id` starts with `tailor-`,
460
632
  and re-running `setup` stops on it, naming the id. Rename it; `--force` does
@@ -488,7 +660,6 @@ tailor-preview-result:
488
660
  name: tailor-preview-result (my-app)
489
661
  needs:
490
662
  - tailor-preview-deploy
491
- - tailor-preview-cleanup
492
663
  - seed
493
664
  - e2e
494
665
  ```
@@ -586,14 +757,21 @@ keeps them alongside that environment's `TAILOR_PLATFORM_WORKSPACE_ID`
586
757
  variable. You can also set the client secret as a repository-level secret and the
587
758
  client ID as a repository-level variable if every target shares one machine user,
588
759
  but then any workflow on any branch can read them, so the environment's
589
- protection rules no longer guard your deploys.
760
+ protection rules no longer guard your deploys, and `tailor setup ci env --check`
761
+ warns about the client secret (see
762
+ [Checking what is registered](#checking-what-is-registered)). To share one machine
763
+ user and keep that protection, set the same client secret on each environment
764
+ instead. The plan environment of `--split-plan-environment` is the exception: give
765
+ it a machine user of its own (see
766
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
590
767
 
591
768
  ### Setting secrets and variables
592
769
 
593
770
  `tailor setup ci env` reads `.github/tailor.lock` and prints, for every GitHub
594
771
  Environment the generated workflows use, the commands that create the
595
772
  environment (only when it does not exist yet) and set its secrets and
596
- variables. It is read-only, so re-run it whenever you add a target. When the
773
+ variables, followed by the repository secrets and variables the autofix
774
+ workflow needs. It is read-only, so re-run it whenever you add a target. When the
597
775
  `origin` remote points at github.com, the output names that repository;
598
776
  otherwise it names the repository `gh repo view` reports for the current
599
777
  directory, with its host on GitHub Enterprise Server. Either way the commands
@@ -622,6 +800,22 @@ The list follows what each generated workflow actually reads:
622
800
  | `TAILOR_SLACK_BOT_TOKEN` | secret | branch, tag | no | Bot User OAuth Token (`xoxb-...`) of a Slack app with the `chat:write` scope |
623
801
  | `TAILOR_SLACK_CHANNEL_ID` | variable | branch, tag | no | Channel ID (`C...`) from the channel details in Slack; invite the bot to the channel |
624
802
  | `TAILOR_SLACK_USER_MAPPING` | variable | branch, tag | no | JSON object mapping GitHub usernames to Slack member IDs (for example `{"alice":"U0123456"}`) so notifications mention the actor; read only after you uncomment the `user-mapping` input of the `tailor-notify` step |
803
+ | `TAILOR_AUTOFIX_APP_CLIENT_ID` | variable | autofix (`github-app`, `securefix`) | yes | Client ID of the GitHub App that commits for the autofix workflow (with `securefix`, the securefix client App), from the App's settings page |
804
+ | `TAILOR_AUTOFIX_APP_PRIVATE_KEY` | secret | autofix (`github-app`, `securefix`) | yes | Private key of the same App, generated on its settings page; pass the `.pem` file on standard input (`gh secret set TAILOR_AUTOFIX_APP_PRIVATE_KEY < key.pem`) |
805
+
806
+ The autofix workflow uses no GitHub Environment, so its two entries are set on
807
+ the repository; the output lists them under the repository instead of an
808
+ environment.
809
+
810
+ A branch target generated with `--split-plan-environment` reads from two
811
+ environments. The plan environment needs its own
812
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` and
813
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET`, for which a viewer role is enough,
814
+ and `TAILOR_PLATFORM_WORKSPACE_ID`; of the optional entries, only the plan job's
815
+ `TAILOR_PLATFORM_FAIL_ON_DRIFT` and `SEED_USER_PASSWORD` apply to it. A manual dry
816
+ run reads `SEED_USER_PASSWORD` from there, and pull requests can read it too, like
817
+ everything on that environment. The deploy environment needs the rest, without
818
+ `TAILOR_PLATFORM_FAIL_ON_DRIFT`.
625
819
 
626
820
  See [Account management](https://docs.tailor.tech/administration/account-management)
627
821
  for how organizations, folders, workspaces, and machine users relate.
@@ -651,15 +845,32 @@ to manage those settings in Terraform instead.
651
845
  ### Checking what is registered
652
846
 
653
847
  `tailor setup ci env --check` reads GitHub with `gh` and reports, for each
654
- environment, which of these entries are registered. It fails when a required
848
+ environment and for the repository entries of the autofix workflow, which of
849
+ these entries are registered. It fails when a required
655
850
  entry is missing, reports missing optional entries without failing, and prints
656
- the `gh` commands that set only the missing entries, with where each value comes
657
- from. It changes nothing, and it checks names only, since GitHub never returns
658
- secret values.
851
+ the `gh` commands that set the missing entries, with where each value comes
852
+ from, and that move the client secret onto its environment (see below). It
853
+ changes nothing, and it checks names only, since GitHub never returns secret
854
+ values.
659
855
 
660
856
  - An entry set on the repository, or on the organization and shared with the
661
857
  repository, counts as registered, because the workflow reads it from there when
662
858
  the environment does not set it. The report says where each entry was found.
859
+ - `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` set on the repository or the
860
+ organization is reported with ⚠, even when the environment also sets it: any
861
+ workflow on any branch can read it there, so the environment's protection rules
862
+ do not guard it. The check still passes. The printed commands set it on the
863
+ environment, with the value you set on the repository or organization, and list
864
+ a commented-out `gh secret delete` for the repository copy: run it once every
865
+ environment and workflow that reads that secret has its own. An organization
866
+ owner has to stop sharing an organization secret with the repository. The
867
+ warning stops once the client secret is set only on the environments; when
868
+ every target shares one machine user, set the same client secret on each of
869
+ them.
870
+ - On the plan environment of `--split-plan-environment`, the machine user's
871
+ client ID and client secret count only when set on that environment, since
872
+ the plan job would otherwise sign in as the machine user set on the repository
873
+ or organization.
663
874
  - An entry registered as the other kind, such as `TAILOR_PLATFORM_WORKSPACE_ID`
664
875
  set as a secret, is reported as missing, because the workflow does not read it.
665
876
  `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` set as a secret still counts, with a
@@ -669,8 +880,8 @@ secret values.
669
880
  answers 404 for a private repository the account cannot see, so the check
670
881
  reports that as an access problem, not as missing entries.
671
882
 
672
- `--environment` narrows the check the same way. `--check` cannot be combined with
673
- `--format terraform`.
883
+ `--environment` narrows the check the same way, leaving out the repository
884
+ entries. `--check` cannot be combined with `--format terraform`.
674
885
 
675
886
  ## GitHub Environments (approval gate)
676
887
 
@@ -691,7 +902,10 @@ environment-scoped secrets there.
691
902
 
692
903
  Required reviewers gate the `plan` job as well, because `plan` must enter the
693
904
  environment to read `TAILOR_PLATFORM_WORKSPACE_ID`. (A token-based read that
694
- lets `plan` run without entering the environment is planned.)
905
+ lets `plan` run without entering the environment is planned.) On a branch target,
906
+ `--split-plan-environment` moves `plan` into an environment of its own, whose
907
+ settings then apply to it instead (see
908
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
695
909
 
696
910
  ## Manual runs and dry-run
697
911
 
@@ -726,7 +940,11 @@ can deploy, configure **Deployment branches and tags** on the target's GitHub
726
940
  Environment and keep the machine-user secrets on that environment. Because the
727
941
  plan job also enters the environment, allow `refs/pull/*/merge` as well so pull
728
942
  request plans keep running, and note that dry runs from other branches are then
729
- rejected too.
943
+ rejected too. On a branch target, `--split-plan-environment` moves the plan job,
944
+ dry runs included, into an environment of its own instead, so the deploy
945
+ environment can allow the target branch alone once the migration test job, if
946
+ any, has its own environment as well (see
947
+ [Running the plan job in its own environment](#running-the-plan-job-in-its-own-environment)).
730
948
 
731
949
  ## Seeding the workspace after deploy
732
950
 
@@ -965,6 +1183,7 @@ server and its runners first:
965
1183
  the pinned versions: update them with `actions-sync`, or, with GitHub
966
1184
  Connect, delete them from the server's `actions` organization (see GitHub's
967
1185
  [Using the latest version of the official bundled actions](https://docs.github.com/en/enterprise-server@latest/admin/managing-github-actions-for-your-enterprise/managing-access-to-actions-from-githubcom/using-the-latest-version-of-the-official-bundled-actions)).
1186
+ - **Autofix workflow:** `tailor setup ci autofix` is not supported.
968
1187
  - **Environment settings:** sign `gh` in to the server
969
1188
  (`gh auth login --hostname <host>`) before you run `tailor setup ci env` in a
970
1189
  clone of the repository, so that the `gh` commands it prints and its Terraform
@@ -1031,24 +1250,30 @@ writing anything. It reports when a workflow file is missing or its SDK-managed
1031
1250
  parts were edited by hand, a job or step of yours uses the reserved `tailor-`
1032
1251
  prefix, a newer template is available, `tailor.config.ts` is no longer under the recorded
1033
1252
  `--dir`, or the repository default branch no longer matches a branch target's
1034
- trigger. It exits non-zero when it finds drift, so you can run it in CI. Each
1253
+ trigger. It audits the autofix workflow the same way, along with each app
1254
+ directory it runs `tailor generate` in. It also reports a `.github/tailor-sdk.lock` left by an older setup,
1255
+ with the files it generated that `.github/tailor.lock` does not track, since
1256
+ the workflows among them still run, and prints the `git rm` command that
1257
+ removes them.
1258
+ It exits non-zero when it finds drift, so you can run it in CI. Each
1035
1259
  finding names a stable rule key for future suppression. Run
1036
1260
  [`tailor setup update`](#updating-the-generated-workflow) to regenerate the
1037
- targets it reports.
1261
+ workflows it reports.
1038
1262
 
1039
1263
  Workflows generated by `setup ci branch`, `setup ci tag`, and `setup ci preview`
1040
1264
  self-audit: each contains a `tailor-drift-check` step that
1041
1265
  runs the check in CI. Preview workflows run it alongside each preview deploy,
1042
1266
  so pull requests that deploy no preview (drafts with `--require ready`, fork
1043
- PRs, and unlabeled PRs with `--require label`) skip the check. A single run audits every target
1267
+ PRs, and unlabeled PRs with `--require label`) skip the check. A single run audits every workflow
1044
1268
  recorded in `.github/tailor.lock`.
1045
1269
 
1046
1270
  Drift findings are advisory by default. Set the repository variable
1047
1271
  `TAILOR_PLATFORM_FAIL_ON_DRIFT` to `true` to make unsuppressed findings fail
1048
1272
  the job. Execution and configuration errors fail regardless of this variable.
1049
1273
 
1050
- `check` compares only the generated files, `.github/tailor.lock`, and the
1051
- config; it does not read the GitHub Environment secrets and variables, so it
1274
+ `check` compares only the generated files, `.github/tailor.lock` (and a
1275
+ leftover `.github/tailor-sdk.lock`), and the config; it does not read the
1276
+ GitHub Environment secrets and variables, so it
1052
1277
  runs the same on your own machine and in CI. Run `tailor setup ci env` to list
1053
1278
  what each environment needs.
1054
1279
 
@@ -1072,10 +1297,10 @@ are detected again, and the parts that follow your config — the migration drif
1072
1297
  check, seed validation, and ERD preview namespaces — are derived from the
1073
1298
  current `tailor.config.ts`.
1074
1299
 
1075
- A target that cannot be regenerated, for example because you edited a managed
1300
+ A workflow that cannot be regenerated, for example because you edited a managed
1076
1301
  part, does not stop the others: `update` regenerates the rest, then lists the
1077
- targets it could not update and exits non-zero. Revert the edit, or run
1078
- `tailor setup update --force` to reset the managed parts of every target.
1302
+ workflows it could not update and exits non-zero. Revert the edit, or run
1303
+ `tailor setup update --force` to reset the managed parts of every workflow.
1079
1304
 
1080
1305
  To change a flag for one target, re-run its `setup ci` subcommand with the new
1081
1306
  flags. `.github/tailor.lock` records them, and later updates reuse them.
@@ -204,10 +204,10 @@ onNamespaceLoaded(context) {
204
204
 
205
205
  **Returns** (`GeneratorResult`):
206
206
 
207
- | Field | Type | Description |
208
- | -------- | ------------------------------------------------------ | ------------------------ |
209
- | `files` | `Array<{ path, content, skipIfExists?, executable? }>` | Files to write to disk |
210
- | `errors` | `string[]?` | Error messages to report |
207
+ | Field | Type | Description |
208
+ | -------- | ----------------------------------------- | ------------------------ |
209
+ | `files` | `Array<{ path, content, skipIfExists? }>` | Files to write to disk |
210
+ | `errors` | `string[]?` | Error messages to report |
211
211
 
212
212
  **Use cases**:
213
213
 
package/docs/runtime.md CHANGED
@@ -133,7 +133,7 @@ The runtime entry re-exports the following namespaces. Detailed signatures, para
133
133
  - `aigateway` — AI Gateway URL resolution (`get`)
134
134
  - `logger` — structured logging with severity and attributes; the message is written to standard output, the full entry with its attributes is exported over OpenTelemetry where the attributes are queryable (`debug`, `info`, `warn`, `error`, `setAttributes`)
135
135
 
136
- ## Testing
136
+ ## Testing (Beta)
137
137
 
138
138
  `@tailor-platform/sdk/vitest` ships mock controllers for every runtime namespace. Pair them with the `tailor-runtime` Vitest environment so your unit tests run against the same wrappers your production code does. Each controller is a factory — acquire it with a `using` declaration and its state is reset automatically when the test scope exits (no `beforeEach(() => mock.reset())` needed). Requires TypeScript ≥ 5.2 and a runtime with `Symbol.dispose` (Node ≥ 20.4; the SDK targets Node ≥ 22).
139
139
 
@@ -334,6 +334,8 @@ oauth2Clients: {
334
334
  },
335
335
  ```
336
336
 
337
+ Client names must be 3 to 63 lowercase letters, digits, or hyphens, starting and ending with a letter or digit.
338
+
337
339
  **redirectURIs**: Allowed redirect URIs after authentication.
338
340
 
339
341
  **description**: Optional description of the client.
@@ -133,6 +133,8 @@ defineIdp("my-idp", {
133
133
  });
134
134
  ```
135
135
 
136
+ Client names must be 3 to 63 lowercase letters, digits, or hyphens, starting and ending with a letter or digit.
137
+
136
138
  ### userAuthPolicy
137
139
 
138
140
  User authentication policy. Controls password requirements, the identifier used for login, allowed email domains, and social login providers. Every field is optional. The boolean options default to disabled, and the password length fields default to a minimum of 6 and a maximum of 4096.
@@ -226,7 +226,9 @@ const user = db.table("User", {
226
226
  });
227
227
  ```
228
228
 
229
- A relation can also be set on a field inside `db.object()`. The inner field gets the foreign key constraint only: no index, no unique constraint, and no forward or backward relationship, as with `type: "keyOnly"`.
229
+ A `keyOnly` relation does not make the field unique; add `.unique()` to the field to make it unique.
230
+
231
+ A relation can also be set on a field inside `db.object()`, where it adds only the foreign key constraint to the inner field, and `.unique()` and `.index()` set on that field work as described in [Index / Unique](#index--unique).
230
232
 
231
233
  ```typescript
232
234
  const order = db.table("Order", {
@@ -243,7 +245,7 @@ const order = db.table("Order", {
243
245
  });
244
246
  ```
245
247
 
246
- A relation inside `db.object()` that points at its own table is not yet handled by `tailor seed apply` or by the data copy a table rename generates. Both write rows in batches, and the platform rejects a row whose parent is in the same or a later batch.
248
+ A relation inside `db.object()` can point at its own table, for example `links.parentID` on `Node`. `tailor seed apply` inserts a row before the rows that point at it, in whatever order the seed data lists them, and follows every element of an array. This needs a unique `id` on every row, and a reference must match the referenced value exactly, including letter case. Otherwise the rows can be inserted in the order the file lists them. The data copy that a table rename generates inserts such references as null and restores them once every row exists.
247
249
 
248
250
  Create relations against different fields using `toward.key`:
249
251
 
@@ -263,6 +265,11 @@ const userProfile = db.table("UserProfile", {
263
265
  `userEmail` does not end in `ID`, `Id`, or `id`, so this example specifies the forward relation
264
266
  name with `toward.as`.
265
267
 
268
+ A relation field must have the same type as the field it points at: `db.uuid()` for the default
269
+ `id`, and the type of the `toward.key` field otherwise. The SDK rejects a relation whose types
270
+ differ, such as a `db.string()` field related to `id`. A custom `toward.key` must be unique,
271
+ through `.unique()` or a one-to-one relation.
272
+
266
273
  Customize relation names using `toward.as` / `backward` options:
267
274
 
268
275
  ```typescript
@@ -472,6 +479,8 @@ db.string().serial({
472
479
  });
473
480
  ```
474
481
 
482
+ `format` holds exactly one of `%d` (decimal), `%o` (octal), `%x` (lowercase hexadecimal), or `%X` (uppercase hexadecimal), optionally with a width: `%5d` pads with spaces and `%05d` with zeros, to at least 5 characters. Write a literal `%` as `%%`. The format is at most 32 characters.
483
+
475
484
  A serial field cannot be used inside a `db.object()` field.
476
485
 
477
486
  > **Preview:** The `gapless` option is a preview feature. Its behavior and configuration may change before general availability.
@@ -566,7 +575,7 @@ db.table("User", {
566
575
  - When not specified, `deploy` sets it from the executors taking part in the same run: `true` while one of them uses this table with `recordCreatedTrigger`, `recordUpdatedTrigger`, or `recordDeletedTrigger`, and `false` once none does. Removing the last such trigger turns publishing back off on the next `deploy`
567
576
  - When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
568
577
  - An executor declared with `disabled: true` never runs, so it does not count as using the table
569
- - While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
578
+ - While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is also listed: when it still has the migration's pre-migration schema, deploying again runs the migration again; otherwise the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only, and the deploy does not list it by name; the next deploy deletes it
570
579
 
571
580
  **Use cases:**
572
581
 
@@ -773,7 +782,7 @@ db.table("User", {
773
782
 
774
783
  **Warning**: Do not use `unsafeAllowAllTypePermission` or `unsafeAllowAllGqlPermission` in production environments as they effectively disable authorization checks.
775
784
 
776
- ## Migrations
785
+ ## Migrations (Beta)
777
786
 
778
787
  When you change a TailorDB table definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor deploy`. See the [TailorDB Migrations guide](./migration.md) for the full workflow, configuration, supported change types, team coordination, and troubleshooting.
779
788