@tailor-platform/sdk 2.26.0 → 2.27.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 (101) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/application-DFeuELRo.mjs +200 -0
  3. package/dist/application-DFeuELRo.mjs.map +1 -0
  4. package/dist/application-nT_4a5HO.mjs +1 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +2 -0
  6. package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +2 -1
  7. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +4 -6
  8. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +6 -0
  9. package/dist/cli/commands/tailordb/migrate/remote-state.d.mts +1 -0
  10. package/dist/cli/lib.d.mts +2 -2
  11. package/dist/cli/lib.mjs +1 -1
  12. package/dist/cli/lib.mjs.map +1 -1
  13. package/dist/cli/main.mjs +54 -54
  14. package/dist/cli/main.mjs.map +1 -1
  15. package/dist/cli/shared/workspace-name.d.mts +2 -0
  16. package/dist/completion/zsh-worker.zsh +4 -2
  17. package/dist/configure/config/types.d.mts +11 -0
  18. package/dist/configure/index.d.mts +6 -6
  19. package/dist/configure/index.mjs +1 -1
  20. package/dist/configure/index.mjs.map +1 -1
  21. package/dist/configure/services/auth/types.d.mts +6 -1
  22. package/dist/configure/services/executor/trigger/event.d.mts +1 -1
  23. package/dist/configure/services/executor/trigger/webhook.d.mts +1 -1
  24. package/dist/configure/services/idp/permission.d.mts +2 -2
  25. package/dist/configure/services/resolver/resolver.d.mts +1 -1
  26. package/dist/configure/services/tailordb/schema.d.mts +1 -1
  27. package/dist/configure/services/tailordb/types.d.mts +1 -1
  28. package/dist/configure/services/workflow/job.d.mts +1 -1
  29. package/dist/configure/types/field.types.d.mts +48 -11
  30. package/dist/configure/types/type.d.mts +1 -1
  31. package/dist/date-CgP7gMG_.mjs +2 -0
  32. package/dist/date-CgP7gMG_.mjs.map +1 -0
  33. package/dist/{secretmanager-5olfnI1b.mjs → date-representation-Cr7W7Pn2.mjs} +2 -2
  34. package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -0
  35. package/dist/{field-parse-DhnFs9WG.mjs → field-parse-C41e3UAF.mjs} +2 -2
  36. package/dist/{field-parse-DhnFs9WG.mjs.map → field-parse-C41e3UAF.mjs.map} +1 -1
  37. package/dist/idp-G_ojPBB5.mjs.map +1 -1
  38. package/dist/{manager-8-JOvuLl.mjs → manager-Bk-7vkwd.mjs} +2 -2
  39. package/dist/{manager-8-JOvuLl.mjs.map → manager-Bk-7vkwd.mjs.map} +1 -1
  40. package/dist/migration-steps-C6F3zNdQ.mjs +3 -0
  41. package/dist/migration-steps-C6F3zNdQ.mjs.map +1 -0
  42. package/dist/plugin/index.mjs +1 -1
  43. package/dist/register-ts-hook-D6KXrGrB.mjs +1009 -0
  44. package/dist/register-ts-hook-D6KXrGrB.mjs.map +1 -0
  45. package/dist/runtime/idp.d.mts +6 -0
  46. package/dist/runtime/index.mjs +1 -1
  47. package/dist/runtime/types.d.mts +5 -0
  48. package/dist/{schema-CRtAmDcl.mjs → schema-B6hJQWp6.mjs} +2 -2
  49. package/dist/{schema-CRtAmDcl.mjs.map → schema-B6hJQWp6.mjs.map} +1 -1
  50. package/dist/seed/index.d.mts +3 -1
  51. package/dist/seed/index.mjs +7 -9
  52. package/dist/seed/index.mjs.map +1 -1
  53. package/dist/service-Cc7OY0Oh.mjs +7 -0
  54. package/dist/service-Cc7OY0Oh.mjs.map +1 -0
  55. package/dist/service-sH2MZ4_W.mjs +1 -0
  56. package/dist/service_pb-Dw2ilc1O.mjs +2 -0
  57. package/dist/{service_pb-ZClWtj1X.mjs.map → service_pb-Dw2ilc1O.mjs.map} +1 -1
  58. package/dist/service_pb-GDOqfwb6.mjs +1 -0
  59. package/dist/types/field.generated.d.mts +2 -2
  60. package/dist/utils/test/index.mjs +1 -1
  61. package/dist/vitest/date-representation.d.mts +23 -0
  62. package/dist/vitest/index.d.mts +3 -1
  63. package/dist/vitest/index.mjs +1 -1
  64. package/dist/vitest/index.mjs.map +1 -1
  65. package/dist/vitest/migration-steps.d.mts +45 -0
  66. package/dist/vitest/mocks/file.d.mts +1 -1
  67. package/dist/vitest/mocks/idp.d.mts +1 -0
  68. package/dist/vitest/setup.d.mts +3 -1
  69. package/dist/vitest/setup.mjs +1 -1
  70. package/dist/vitest/setup.mjs.map +1 -1
  71. package/dist/{workspace_resource_pb-CxR_6fyN.mjs → workspace_resource_pb-ChWejcj0.mjs} +2 -2
  72. package/dist/workspace_resource_pb-ChWejcj0.mjs.map +1 -0
  73. package/docs/cli/application.md +24 -16
  74. package/docs/cli/function.md +2 -0
  75. package/docs/cli/workspace.md +1 -1
  76. package/docs/configuration.md +19 -6
  77. package/docs/github-actions.md +124 -58
  78. package/docs/plugin/custom.md +2 -1
  79. package/docs/services/auth.md +20 -0
  80. package/docs/services/executor.md +2 -2
  81. package/docs/services/idp.md +6 -1
  82. package/docs/services/resolver.md +3 -1
  83. package/docs/services/staticwebsite.md +5 -5
  84. package/docs/services/tailordb-migration.md +157 -35
  85. package/docs/services/tailordb.md +2 -2
  86. package/docs/testing.md +3 -0
  87. package/package.json +12 -12
  88. package/dist/application-ChqHuhZW.mjs +0 -1
  89. package/dist/application-DS0XKBtK.mjs +0 -200
  90. package/dist/application-DS0XKBtK.mjs.map +0 -1
  91. package/dist/date-Dri6Yas-.mjs +0 -2
  92. package/dist/date-Dri6Yas-.mjs.map +0 -1
  93. package/dist/register-ts-hook-BuwkLuxC.mjs +0 -925
  94. package/dist/register-ts-hook-BuwkLuxC.mjs.map +0 -1
  95. package/dist/secretmanager-5olfnI1b.mjs.map +0 -1
  96. package/dist/service-Bx-7Lz06.mjs +0 -1
  97. package/dist/service-CL1Bp4It.mjs +0 -7
  98. package/dist/service-CL1Bp4It.mjs.map +0 -1
  99. package/dist/service_pb-ZClWtj1X.mjs +0 -2
  100. package/dist/service_pb-dB3RrGeh.mjs +0 -1
  101. package/dist/workspace_resource_pb-CxR_6fyN.mjs.map +0 -1
@@ -56,22 +56,23 @@ tailor deploy [options]
56
56
 
57
57
  **Options**
58
58
 
59
- | Option | Alias | Description | Required | Default | Env |
60
- | --------------------------------------- | ----- | ------------------------------------------------------------------------------------ | -------- | -------------------- | --------------------------------- |
61
- | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
62
- | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
63
- | `--config <CONFIG>` | `-c` | Path to SDK config file. Use comma-separated paths to deploy multiple apps together. | No | `"tailor.config.ts"` | `TAILOR_PLATFORM_SDK_CONFIG_PATH` |
64
- | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
65
- | `--create-workspace` | - | Create a workspace when the account has none | No | - | - |
66
- | `--workspace-name <WORKSPACE_NAME>` | - | Name for a workspace created during deploy | No | - | - |
67
- | `--workspace-region <WORKSPACE_REGION>` | - | Region for a workspace created during deploy | No | - | - |
68
- | `--organization-id <ORGANIZATION_ID>` | - | Organization ID for a workspace created during deploy | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
69
- | `--folder-id <FOLDER_ID>` | - | Folder ID for a workspace created during deploy | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
70
- | `--dry-run` | `-d` | Run the command without making any changes | No | - | - |
71
- | `--no-schema-check` | - | Skip schema diff check against migration snapshots | No | - | - |
72
- | `--no-validate` | - | Skip client-side validation against platform resource constraints | No | - | - |
73
- | `--no-cache` | - | Disable bundle caching for this run | No | - | - |
74
- | `--clean-cache` | - | Clean the bundle cache before building | No | - | - |
59
+ | Option | Alias | Description | Required | Default | Env |
60
+ | ----------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------- | -------- | -------------------- | --------------------------------- |
61
+ | `--workspace-id <WORKSPACE_ID>` | `-w` | Workspace ID | No | - | `TAILOR_PLATFORM_WORKSPACE_ID` |
62
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
63
+ | `--config <CONFIG>` | `-c` | Path to SDK config file. Use comma-separated paths to deploy multiple apps together. | No | `"tailor.config.ts"` | `TAILOR_PLATFORM_SDK_CONFIG_PATH` |
64
+ | `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
65
+ | `--create-workspace` | - | Create a workspace when the account has none | No | - | - |
66
+ | `--workspace-name <WORKSPACE_NAME>` | - | Name for a workspace created during deploy | No | - | - |
67
+ | `--workspace-region <WORKSPACE_REGION>` | - | Region for a workspace created during deploy | No | - | - |
68
+ | `--organization-id <ORGANIZATION_ID>` | - | Organization ID for a workspace created during deploy | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
69
+ | `--folder-id <FOLDER_ID>` | - | Folder ID for a workspace created during deploy | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
70
+ | `--dry-run` | `-d` | Run the command without making any changes | No | - | - |
71
+ | `--no-schema-check` | - | Skip schema diff check against migration snapshots | No | - | - |
72
+ | `--migration-skip-steps <MIGRATION_SKIP_STEPS>` | - | Skip steps of a partially applied multi-step migration that already succeeded (comma-separated namespace/step entries) | No | - | - |
73
+ | `--no-validate` | - | Skip client-side validation against platform resource constraints | No | - | - |
74
+ | `--no-cache` | - | Disable bundle caching for this run | No | - | - |
75
+ | `--clean-cache` | - | Clean the bundle cache before building | No | - | - |
75
76
 
76
77
  See [Global Options](../cli-reference.md#global-options) for options available to all commands.
77
78
  **JSON result:**
@@ -93,6 +94,13 @@ tailor deploy --json > deploy-result.json
93
94
  jq '.applications[] | {name, url, staticWebsites, auth}' deploy-result.json
94
95
  ```
95
96
 
97
+ After a successful apply, stdout is exactly one line: the JSON object. A dry-run writes its
98
+ plan as one JSON object. Progress and diagnostics go to stderr, so the output can be captured
99
+ as it is, for example
100
+ `result=$(tailor deploy --json)`. A failed deploy exits non-zero and writes its error as
101
+ JSON to stderr, leaving stdout empty. A deploy plugin that writes to stdout itself makes
102
+ the output unparseable; plugins report through `ctx.logger` and run commands through `ctx.exec`.
103
+
96
104
  Dry-run and build-only deployments do not return deployed application information.
97
105
  If resources were applied but loading the JSON result fails, the command reports
98
106
  `DEPLOY_RESULT_LOAD_FAILED`; fix the error and run `tailor deploy` again.
@@ -129,6 +129,8 @@ $ tailor function logs <execution-id> --follow
129
129
 
130
130
  **Notes**
131
131
 
132
+ Each execution includes `errorKind`, which tells you where a failure came from: `USER_RUNTIME` (your code threw while running, or exceeded its time, memory, or CPU limit), `USER_NON_RUNTIME` (your script failed while loading, before your function was called, such as when its top-level code threw), `PLATFORM` (a failure on the Platform side, or the Platform aborting the run because of its own deadline or cancellation), `NONE` (no error), or `UNSPECIFIED` (not recorded, or a kind this CLI version does not recognize). A workflow job waiting on another job also reports `USER_NON_RUNTIME` when that job fails or is canceled. `errorName` and `errorMessage` hold the error's name and message, or `null` when none was recorded. The list table omits `errorMessage`; use `--json` or the execution details to read it.
133
+
132
134
  Execution details include `logEntries`, the structured log lines (message, severity, timestamp) recorded while the function ran. They are available while the execution is still running. The `logs` string joins their messages with newlines.
133
135
 
134
136
  Use `--follow` to keep polling a running execution and print new log entries as they arrive until it completes. Polling continues while the execution is suspended at a wait point, and indefinitely unless `--timeout` is set. With `--json`, `--follow` waits for completion and then emits the final execution details once.
@@ -103,7 +103,7 @@ tailor workspace create [options]
103
103
  | Option | Alias | Description | Required | Default | Env |
104
104
  | ------------------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------- | --------------------------------- |
105
105
  | `--name <NAME>` | `-n` | Workspace name | Yes | - | - |
106
- | `--region <REGION>` | `-r` | Workspace region (us-west, asia-northeast) | Yes | - | - |
106
+ | `--region <REGION>` | `-r` | Workspace region (asia-northeast, us-west) | Yes | - | - |
107
107
  | `--delete-protection` | `-d` | Enable delete protection | No | `false` | - |
108
108
  | `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID to workspace associate with | No | - | `TAILOR_PLATFORM_ORGANIZATION_ID` |
109
109
  | `--folder-id <FOLDER_ID>` | `-f` | Folder ID to workspace associate with | No | - | `TAILOR_PLATFORM_FOLDER_ID` |
@@ -77,6 +77,19 @@ This is a bundle-time setting. Changing `TAILOR_APP_LOG_LEVEL` affects newly bun
77
77
 
78
78
  Only `logger.*` calls made through the SDK's `logger` wrapper (from `@tailor-platform/sdk/runtime` or its `@tailor-platform/sdk/runtime/logger` subpath), or written as `globalThis.tailor.logger.*`, are covered. Other equivalent forms — such as the bare `tailor.logger.*` global or `self.tailor.logger.*` — are not affected by `logLevel`.
79
79
 
80
+ **Date Representation** (`defaultDateRepresentation`): Sets the value representation of `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as`. With `"temporal"`, those fields carry `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime` values instead of strings, so a project that uses Temporal everywhere does not have to repeat `as: "temporal"` on every field; with `"date"`, they carry `Date` values, as `as: "date"` gives them. A field's own `as` still wins, including `as: "string"`. Run `tailor generate` after changing the setting so `tailor.d.ts` updates the field types.
81
+
82
+ ```typescript
83
+ export default defineConfig({
84
+ name: "my-app",
85
+ defaultDateRepresentation: "temporal",
86
+ });
87
+ ```
88
+
89
+ The default applies wherever the SDK parses or serializes `t` fields on your behalf: resolver input and output in deployed functions and in `tailor function run`, `.parse()` and `parseDateFields` inside any bundled function (resolvers, executors, workflow jobs, auth hooks, TailorDB hooks and validators, migration scripts), and the [`tailor-runtime` Vitest environment](./testing.md#loading-secrets-from-config) when `tailorRuntime({ config })` points at the config. It does not apply to `db.*` fields, or to `t` fields parsed outside those places, such as a plain Node.js script or a module that runs while `tailor.config.ts` itself is being loaded; there the fields keep string values. A migration script keeps the representation that was in effect when it was generated, recorded in its `diff.json`, so changing the setting later does not alter migrations a new workspace has yet to run. That record covers the values at run time only; the TypeScript types of `t` fields follow the current `tailor.d.ts` everywhere, so a `t` date field declared inside a migration script should set `as` explicitly to keep its type and its values aligned after the setting changes.
90
+
91
+ The setting is per `tailor.config.ts`, but the field types are declared through `tailor.d.ts`, which TypeScript merges across every file in a program. If one `tsconfig.json` includes the `tailor.d.ts` of several applications, they must agree on `defaultDateRepresentation`. When they differ, `t.date()`, `t.datetime()`, and `t.time()` called without `as` fail to type-check in that program until the configs are aligned or each application gets its own `tsconfig.json`.
92
+
80
93
  ### Service Configuration
81
94
 
82
95
  Specify glob patterns to load service files:
@@ -185,11 +198,11 @@ import { defineIdp } from "@tailor-platform/sdk";
185
198
  const idp = defineIdp("my-idp", {
186
199
  clients: ["my-client"],
187
200
  permission: {
188
- create: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
189
- read: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
190
- update: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
191
- delete: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
192
- sendPasswordResetEmail: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
201
+ create: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
202
+ read: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
203
+ update: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
204
+ delete: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
205
+ sendPasswordResetEmail: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
193
206
  },
194
207
  });
195
208
 
@@ -310,7 +323,7 @@ When the SDK deploys application code or runs detected service code with `functi
310
323
  | Executor callbacks | `body: ({ env }) => ...`, `url: ({ env }) => String(env.bar)`, `variables: ({ env }) => ({ enabled: env.baz })`, or similar callback args |
311
324
  | Workflow job body | `body: (input, { env }) => ...` |
312
325
  | Auth before-login hook | `handler: async ({ env }) => ...` |
313
- | TailorDB migration script | `main(trx, { env }: MigrationContext)` |
326
+ | TailorDB migration script | `main(trx, { env }: MigrationContext)`, or each step's `run(trx, { env })` |
314
327
  | `function run` | Same `env` argument shape as the detected resolver, executor, or workflow job |
315
328
 
316
329
  ```typescript
@@ -153,6 +153,10 @@ or end with a hyphen. It is used for the generated file name, the workflow
153
153
  select which workspace gets deployed (see
154
154
  [Targeting a workspace](#targeting-a-workspace)).
155
155
 
156
+ For a preview target, the name can be at most 50 characters: each pull request
157
+ gets its own workspace named `<name>-pr-<number>`, which must fit in 63
158
+ characters.
159
+
156
160
  ## Targeting a workspace
157
161
 
158
162
  The generated `plan` and `deploy` jobs target a workspace by **id**, read from
@@ -213,9 +217,8 @@ for what you can edit.
213
217
  ### Customizing the generated workflow
214
218
 
215
219
  The SDK owns the jobs and steps whose `id` starts with `tailor-`, and the
216
- top-level keys it writes (`name:`, `on:`, and `permissions:` in a workflow; the
217
- metadata, inputs, and outputs of a composite action). Do not edit or rename
218
- them. Everything else is yours, and re-running `setup` keeps it:
220
+ top-level `name:`, `on:`, and `permissions:`. Do not edit or rename them.
221
+ Everything else is yours, and re-running `setup` keeps it:
219
222
 
220
223
  - **Your own jobs and steps.** Add them anywhere, with an `id` (if any) that
221
224
  does not start with `tailor-`: the prefix is reserved for the SDK, even in a
@@ -225,19 +228,27 @@ them. Everything else is yours, and re-running `setup` keeps it:
225
228
  (such as `playwright install`) _after_ it. A job of your own can depend on a
226
229
  managed job with `needs: tailor-deploy`.
227
230
  - **Your own top-level keys**, such as a workflow-level `env:` or `defaults:`.
231
+ - **The top-level `concurrency:` of a preview workflow.** The SDK writes a
232
+ default, and re-running `setup` keeps your edits to it. If you delete it,
233
+ re-running `setup` adds the default back.
228
234
  - **Runtime settings of managed jobs:** `runs-on`, `timeout-minutes`,
229
- `container`, and `env`. On a managed job generated without an
230
- `environment:` (such as `tailor-tag-guard` or `tailor-erd-preview`), you can
231
- also add one, for example so a step you added there can read that GitHub
232
- Environment's secrets. The `environment:` of `tailor-plan`, `tailor-deploy`,
233
- and the preview jobs comes from `--environment`, so change it with that
234
- option instead.
235
+ `container`, and `env`. If every managed job that re-running `setup` keeps
236
+ uses the same `runs-on` you set, such as self-hosted runners, a managed job
237
+ it adds (for example, the ERD preview jobs when you add `--erd-preview`)
238
+ uses it too; otherwise, set the new job's `runs-on` yourself. On a managed
239
+ job generated without an `environment:` (such as `tailor-tag-guard` or
240
+ `tailor-erd-preview`), you can also add one, for example so a step you added
241
+ there can read that GitHub Environment's secrets. The `environment:` of
242
+ `tailor-plan`, `tailor-deploy`, and the preview jobs comes from
243
+ `--environment`, so change it with that option instead.
235
244
  - **These inputs of managed steps:** `ignore` on `tailor-generate-check` and
236
245
  `tailor-drift-check`, `fail-on-drift` on `tailor-drift-check`,
237
246
  `install-command` on `tailor-install`, `node-version-file` on
238
247
  `tailor-setup`, `label` on `tailor-plan`, and `user-mapping` on
239
- `tailor-notify` and on a coordinator's steps that call an app action.
240
- - **The `run:` command of the `tailor-build-site` step** in a composite action.
248
+ `tailor-notify`.
249
+ - **Your own jobs in the `needs` of `tailor-result` and
250
+ `tailor-preview-result`**, so they count toward the workflow's result (see
251
+ [Requiring the workflow in branch protection](#requiring-the-workflow-in-branch-protection)).
241
252
 
242
253
  In a preview workflow, the `tailor-preview-deploy` job exposes the per-PR
243
254
  workspace as the outputs `workspace-id`, `workspace-name`, and `app-url`, so a
@@ -250,7 +261,7 @@ deploy-assets:
250
261
  environment: my-app
251
262
  env:
252
263
  TAILOR_PLATFORM_WORKSPACE_ID: ${{ needs.tailor-preview-deploy.outputs.workspace-id }}
253
- TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}
264
+ TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ vars.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID || secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}
254
265
  TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}
255
266
  steps:
256
267
  # checkout, dependency installation, and your tailor commands
@@ -265,6 +276,14 @@ The job is skipped whenever `tailor-preview-deploy` is skipped: when a pull
265
276
  request is closed, for draft and fork pull requests, and for unlabeled ones with
266
277
  `--require-preview-label`.
267
278
 
279
+ Each new preview workflow run for a pull request, such as after a push or when
280
+ the pull request is closed, cancels the run still in progress for it, including
281
+ your jobs, so they do not keep running against a workspace that the new run
282
+ may redeploy or delete. Events on a pull request that stays closed, such as a
283
+ label added after merging, do not cancel anything. A job with `if: always()`
284
+ still runs when its run is cancelled, and the new run waits for it to finish;
285
+ use `if: ${{ !cancelled() }}` instead so it stops with the run.
286
+
268
287
  Likewise, the `tailor-deploy` job of a branch or tag workflow exposes the
269
288
  deployed workspace as the outputs `workspace-id` and `app-url` to a job with
270
289
  `needs: tailor-deploy`. Reading them does not require the target's GitHub
@@ -299,6 +318,48 @@ Files generated by an older SDK version are compared as a whole, so the first
299
318
  re-run after upgrading stops if you edited the file in any way. Re-run once with
300
319
  `--force` to switch it to this model; your own jobs and steps are kept.
301
320
 
321
+ ### Requiring the workflow in branch protection
322
+
323
+ Branch and preview workflows end with a job that reports the result of the
324
+ whole workflow: `tailor-result` in a branch workflow and
325
+ `tailor-preview-result` in a preview workflow. Its check is named after the
326
+ workspace, such as `tailor-result (my-app)`, so each workflow in the repository
327
+ has its own. Require that one check in branch protection instead of listing
328
+ the jobs one by one.
329
+
330
+ The job runs after every other SDK-managed job of the workflow, even when one
331
+ fails or the run is cancelled. It fails when a job in its `needs` failed or was
332
+ cancelled, and passes when they succeeded or were skipped, for example when the
333
+ preview deploy is skipped for a fork pull request. When a newer run cancels a
334
+ job of an older one, such as the preview deploy of a commit you pushed over,
335
+ the older run's check fails; the newer run reports its own.
336
+
337
+ To make jobs of your own count, add them to its `needs`. A matrix job is listed
338
+ once by its job id, however many combinations it expands to:
339
+
340
+ ```yaml
341
+ tailor-preview-result:
342
+ name: tailor-preview-result (my-app)
343
+ needs:
344
+ - tailor-preview-deploy
345
+ - tailor-preview-cleanup
346
+ - seed
347
+ - e2e
348
+ ```
349
+
350
+ Re-running `setup` keeps the jobs you add there and updates the `tailor-`
351
+ entries to the jobs the SDK generates. A job skipped because a job it needs
352
+ failed also counts as passing, so list every job of yours whose failure should
353
+ block merging, not only the last one: above, `seed` is listed because `e2e`
354
+ needs it.
355
+
356
+ With `--require-preview-label`, the preview workflow does not start when a pull
357
+ request is opened, so its check is not reported until the pull request is
358
+ labeled, reopened, or pushed to. A draft pull request skips the preview deploy,
359
+ and marking it ready for review does not start the workflow, so the check that
360
+ passed while it was a draft stays until the next push. Tag workflows have no
361
+ such job: they run on tags, not on pull requests.
362
+
302
363
  ### `.github/tailor.lock`
303
364
 
304
365
  A machine-owned JSON file that tracks which files the SDK manages, the inputs
@@ -353,23 +414,33 @@ the move.
353
414
 
354
415
  ## Secrets
355
416
 
356
- The generated workflow requires two secrets (the optional Slack token is listed in
417
+ The generated workflow requires one secret (the optional Slack token is listed in
357
418
  [Setting secrets and variables](#setting-secrets-and-variables)):
358
419
 
359
420
  | Secret | Description |
360
421
  | -------------------------------------------- | -------------------------- |
361
- | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` | Machine user client ID |
362
422
  | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` | Machine user client secret |
363
423
 
364
- Set them on the target GitHub Environment (the `--environment` value, or the
365
- workspace name when omitted); `tailor setup ci env` prints the commands (see
424
+ The machine user client ID is a variable, not a secret: it cannot authenticate
425
+ without the client secret. The generated workflow reads
426
+ `vars.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` and falls back to
427
+ `secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID`, so a repository that already
428
+ stores the client ID as a secret keeps working unchanged. Storing the client ID
429
+ as a secret is deprecated, and the fallback will be removed in a future release,
430
+ so move it to a variable (`gh variable set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID`)
431
+ and delete the secret.
432
+
433
+ Set the client secret as a secret and the client ID as a variable on the target
434
+ GitHub Environment (the `--environment` value, or the workspace name when
435
+ omitted); `tailor setup ci env` prints the commands (see
366
436
  [Setting secrets and variables](#setting-secrets-and-variables)).
367
437
 
368
438
  Setting them at the environment level isolates each target's credentials and
369
439
  keeps them alongside that environment's `TAILOR_PLATFORM_WORKSPACE_ID`
370
- variable. You can also set them as repository-level secrets if every target
371
- shares one machine user, but then any workflow on any branch can read them, so
372
- the environment's protection rules no longer guard your deploys.
440
+ variable. You can also set the client secret as a repository-level secret and the
441
+ client ID as a repository-level variable if every target shares one machine user,
442
+ but then any workflow on any branch can read them, so the environment's
443
+ protection rules no longer guard your deploys.
373
444
 
374
445
  ### Setting secrets and variables
375
446
 
@@ -389,24 +460,23 @@ tailor setup ci env --environment my-app-stg # only one environment (repeat for
389
460
 
390
461
  The list follows what each generated workflow actually reads:
391
462
 
392
- | Name | Kind | Targets | Required | Where the value comes from |
393
- | -------------------------------------------- | -------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
394
- | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` | secret | all | yes | Client ID of the platform machine user CI signs in as; it needs an editor or admin role on the organization or folder that holds the workspace. Contact [Tailor support](https://docs.tailor.tech/administration/support) to get one |
395
- | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` | secret | all | yes | Client secret of the same platform machine user |
396
- | `TAILOR_PLATFORM_WORKSPACE_ID` | variable | branch, tag, coordinate | yes | `id` printed by `tailor workspace create`, or listed by `tailor workspace list` |
397
- | `TAILOR_PLATFORM_ORGANIZATION_ID` | variable | preview | yes | Organization to create the per-PR workspaces in (a machine user cannot create a workspace without one): `organizationId` listed by `tailor organization list` |
398
- | `TAILOR_PLATFORM_FOLDER_ID` | variable | preview | no | Folder to create the per-PR workspaces in: `id` listed by `tailor organization folder list -o <organization id>`. When unset they go directly under the organization, which needs the machine user's role on the organization itself |
399
- | `TAILOR_PLATFORM_FAIL_ON_DRIFT` | variable | all | no | `true` to fail the drift check when it finds drift |
400
- | `TAILOR_SLACK_BOT_TOKEN` | secret | branch, tag, coordinate | no | Bot User OAuth Token (`xoxb-...`) of a Slack app with the `chat:write` scope |
401
- | `TAILOR_SLACK_CHANNEL_ID` | variable | branch, tag, coordinate | no | Channel ID (`C...`) from the channel details in Slack; invite the bot to the channel |
402
- | `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 |
463
+ | Name | Kind | Targets | Required | Where the value comes from |
464
+ | -------------------------------------------- | -------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
465
+ | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` | variable | all | yes | Client ID of the platform machine user CI signs in as (an existing secret of the same name still works as a deprecated fallback); it needs an editor or admin role on the organization or folder that holds the workspace. An organization or folder admin creates one in the Tailor Console and grants it a role; without the required permission you cannot view or create machine users |
466
+ | `TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET` | secret | all | yes | Client secret of the same platform machine user |
467
+ | `TAILOR_PLATFORM_WORKSPACE_ID` | variable | branch, tag | yes | `id` printed by `tailor workspace create`, or listed by `tailor workspace list` |
468
+ | `TAILOR_PLATFORM_ORGANIZATION_ID` | variable | preview | yes | Organization to create the per-PR workspaces in (a machine user cannot create a workspace without one): `organizationId` listed by `tailor organization list` |
469
+ | `TAILOR_PLATFORM_FOLDER_ID` | variable | preview | no | Folder to create the per-PR workspaces in: `id` listed by `tailor organization folder list -o <organization id>`. When unset they go directly under the organization, which needs the machine user's role on the organization itself |
470
+ | `TAILOR_PLATFORM_FAIL_ON_DRIFT` | variable | all | no | `true` to fail the drift check when it finds drift |
471
+ | `TAILOR_SLACK_BOT_TOKEN` | secret | branch, tag | no | Bot User OAuth Token (`xoxb-...`) of a Slack app with the `chat:write` scope |
472
+ | `TAILOR_SLACK_CHANNEL_ID` | variable | branch, tag | no | Channel ID (`C...`) from the channel details in Slack; invite the bot to the channel |
473
+ | `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 |
403
474
 
404
475
  See [Account management](https://docs.tailor.tech/administration/account-management)
405
476
  for how organizations, folders, workspaces, and machine users relate.
406
477
 
407
- Composite actions (`setup ci action`) read nothing themselves; the coordinator
408
- that calls them does. Set `TAILOR_SLACK_BOT_TOKEN` and `TAILOR_SLACK_CHANNEL_ID`
409
- together to enable Slack deploy notifications.
478
+ Set `TAILOR_SLACK_BOT_TOKEN` and `TAILOR_SLACK_CHANNEL_ID` together to enable
479
+ Slack deploy notifications.
410
480
 
411
481
  The `gh` output creates an environment only when GitHub reports it missing and
412
482
  leaves optional entries commented out. Run the commands one at a time: each
@@ -463,8 +533,7 @@ dispatches, so you can deploy any commit regardless of branch membership.
463
533
 
464
534
  ### Restricting manual deploys
465
535
 
466
- Pass `--restrict-dispatch` to `setup ci branch`, `setup ci tag`, or
467
- `setup ci coordinate` to limit what a manual dispatch with `dry-run` off can
536
+ Pass `--restrict-dispatch` to `setup ci branch` or `setup ci tag` to limit what a manual dispatch with `dry-run` off can
468
537
  deploy:
469
538
 
470
539
  - **Branch target:** only the target branch. Dispatching another branch skips
@@ -493,12 +562,14 @@ tailor setup ci branch --name my-app --dir apps/backend
493
562
  ```
494
563
 
495
564
  The `working-directory` for SDK commands is set accordingly, and the workflow
496
- only plans and deploys when that subdirectory changes. The workflow itself
497
- starts on every pull request and push: a `tailor-changes` job checks whether
498
- the change touches `apps/backend/**`, and the plan, deploy, and ERD preview jobs
499
- are skipped when it does not. A skipped job reports success, so you can make
500
- these checks required in branch protection; a workflow that a `paths` trigger
501
- filter never started would leave them pending instead. If the `tailor-changes` job
565
+ only plans and deploys when that subdirectory or the generated workflow file
566
+ changes. The workflow itself starts on every pull request and push: a
567
+ `tailor-changes` job checks whether the change touches `apps/backend/**` or the
568
+ generated workflow file itself, and the plan, deploy, and ERD preview jobs are
569
+ skipped when it does not. A skipped job reports success, so you can require the
570
+ workflow's [result check](#requiring-the-workflow-in-branch-protection) in
571
+ branch protection; a workflow that a `paths` trigger filter never started would
572
+ leave it pending instead. If the `tailor-changes` job
502
573
  itself fails (for example, on a GitHub API error), the plan, deploy, and ERD
503
574
  preview jobs fail too instead of being skipped, so a required check blocks
504
575
  merging and nothing is deployed. Re-run the failed jobs once the cause is gone.
@@ -539,8 +610,13 @@ tailor setup ci preview --name erp --region asia-northeast \
539
610
  --paths "apps/*/frontend/**" --paths "modules/**" --paths pnpm-lock.yaml
540
611
  ```
541
612
 
542
- The patterns are checked after the app directories, in order, and the last
543
- pattern that matches a changed file decides whether it counts. `*` matches
613
+ The patterns are checked after the app directories and the generated workflow
614
+ file, in order, and the last pattern that matches a changed file decides whether
615
+ it counts. A change to the workflow file alone runs the generated plan, deploy,
616
+ and preview jobs, and with them any job of yours that needs them, in the pull
617
+ request or push that makes the change; to skip the jobs for such a change,
618
+ exclude the file, for example `--paths '!.github/workflows/tailor-erp.yml'`
619
+ (`tailor-erp-preview.yml` for the preview workflow). `*` matches
544
620
  within one path segment, `**` matches any number of segments, and a pattern
545
621
  starting with `!` excludes matching paths, so it can also exclude files inside
546
622
  an app directory, for example `--paths '!apps/erp/backend/**/*.md'`. Other glob
@@ -675,14 +751,12 @@ finding names a stable rule key for future suppression. Run
675
751
  [`tailor setup update`](#updating-the-generated-workflow) to regenerate the
676
752
  targets it reports.
677
753
 
678
- Workflows generated by `setup ci branch`, `setup ci tag`, `setup ci preview`, and
679
- `setup ci coordinate` self-audit: each contains a `tailor-drift-check` step that
754
+ Workflows generated by `setup ci branch`, `setup ci tag`, and `setup ci preview`
755
+ self-audit: each contains a `tailor-drift-check` step that
680
756
  runs the check in CI. Preview workflows run it alongside each preview deploy,
681
757
  so pull requests that deploy no preview (drafts, fork PRs, and unlabeled PRs
682
758
  in label-triggered mode) skip the check. A single run audits every target
683
- recorded in `.github/tailor.lock`, so per-app composite actions generated by
684
- `setup ci action` are covered by their coordinator's step and do not carry one
685
- of their own.
759
+ recorded in `.github/tailor.lock`.
686
760
 
687
761
  Drift findings are advisory by default. Set the repository variable
688
762
  `TAILOR_PLATFORM_FAIL_ON_DRIFT` to `true` to make unsuppressed findings fail
@@ -702,14 +776,14 @@ pick up template improvements:
702
776
  tailor setup update
703
777
  ```
704
778
 
705
- It regenerates every workflow and composite action recorded in
779
+ It regenerates every workflow recorded in
706
780
  `.github/tailor.lock` with the flags each one was generated with, so you do not
707
781
  have to re-type `setup ci branch`, `setup ci tag`, and the rest one by one.
708
782
  Your own jobs, steps, and settings are kept (see
709
783
  [Customizing the generated workflow](#customizing-the-generated-workflow)). A
710
784
  branch that was detected from the repository default branch is detected again,
711
785
  and the parts that follow your config — the migration drift check, seed
712
- validation, static website builds, and ERD preview namespaces — are derived
786
+ validation, and ERD preview namespaces — are derived
713
787
  from the current `tailor.config.ts`.
714
788
 
715
789
  A target that cannot be regenerated, for example because you edited a managed
@@ -717,13 +791,5 @@ part, does not stop the others: `update` regenerates the rest, then lists the
717
791
  targets it could not update and exits non-zero. Revert the edit, or run
718
792
  `tailor setup update --force` to reset the managed parts of every target.
719
793
 
720
- Coordinators generated by an older plugin version did not record how their
721
- `--action` values were grouped, so `update` lists them instead of guessing
722
- which apps deploy together. Re-run `tailor setup ci coordinate` once with all
723
- of its original flags and its original `--action` groups. The message `update`
724
- prints fills in the recorded flags (`--tag`, `--branch`, `--environment`,
725
- `--restrict-dispatch`), so you only add the `--action` values. Later updates
726
- pick the grouping up from the lock.
727
-
728
794
  To change a flag for one target, re-run its `setup ci` subcommand with the new
729
795
  flags. `.github/tailor.lock` records them, and later updates reuse them.
@@ -272,7 +272,8 @@ same fields. OAuth client secrets are never included.
272
272
  `ctx.configPath` is the absolute path of the registering config. `ctx.workspaceId`
273
273
  and `ctx.pluginConfig` identify the deployment workspace and the plugin's options.
274
274
  Use `ctx.logger.info`, `warn`, and `success` for diagnostics; they write to stderr
275
- and preserve deploy's JSON stdout.
275
+ and preserve deploy's JSON stdout. A hook that writes to stdout itself, such as
276
+ with `console.log`, makes `tailor deploy --json` output unparseable.
276
277
 
277
278
  ```typescript
278
279
  import { definePlugins, type Plugin } from "@tailor-platform/sdk";
@@ -526,6 +526,26 @@ hooks: {
526
526
  }
527
527
  ```
528
528
 
529
+ ### Password age claim
530
+
531
+ For a user with a Built-in IdP password, `claims.password_updated_at` holds when the password was last set, in seconds since the Unix epoch. It is `undefined` for users without a password. Throw from the handler to reject a sign-in whose password is older than your policy allows.
532
+
533
+ ```typescript
534
+ const MAX_PASSWORD_AGE_SECONDS = 90 * 24 * 60 * 60;
535
+
536
+ hooks: {
537
+ beforeLogin: {
538
+ handler: async ({ claims }) => {
539
+ const updatedAt = claims.password_updated_at;
540
+ if (updatedAt !== undefined && Date.now() / 1000 - updatedAt > MAX_PASSWORD_AGE_SECONDS) {
541
+ throw new Error("Password expired");
542
+ }
543
+ },
544
+ invoker: "hook-invoker",
545
+ },
546
+ }
547
+ ```
548
+
529
549
  ## CLI Commands
530
550
 
531
551
  Manage Auth resources using the CLI:
@@ -278,7 +278,7 @@ Executor callbacks receive the trigger args, including `env` from `defineConfig(
278
278
 
279
279
  ### Job Function Operation
280
280
 
281
- For long-running operations, use `jobFunction` which runs asynchronously and supports extended execution times. See [Job Function Operation](https://docs.tailor.tech/guides/executor/job-function-operation) for details.
281
+ For work that needs more than the 60-second `function` limit, use `jobFunction`, which can run for up to 5 minutes. See [Job Function Operation](https://docs.tailor.tech/guides/executor/job-function-operation) for details.
282
282
 
283
283
  ```typescript
284
284
  import { createExecutor, scheduleTrigger } from "@tailor-platform/sdk";
@@ -292,7 +292,7 @@ export default createExecutor({
292
292
  kind: "jobFunction",
293
293
  body: async () => {
294
294
  const db = getDB("tailordb");
295
- // Long-running report generation logic
295
+ // Report generation logic (runs up to 5 minutes)
296
296
  const records = await db.selectFrom("Order").selectAll().execute();
297
297
  // Process records...
298
298
  },
@@ -105,6 +105,9 @@ defineIdp("my-idp", {
105
105
 
106
106
  **Operators:** `"="`, `"!="`, `"in"`, `"not in"`
107
107
 
108
+ > [!WARNING]
109
+ > A policy of `[{ user: "_loggedIn" }, "=", true]` permits every authenticated user to run the operation against any IdP user. For `read` and `update` this exposes and lets them change other users' login information (such as email addresses), and for `sendPasswordResetEmail` it lets them send reset emails to anyone. Use it only when every user who can sign in to the project is trusted. For an IdP used by external users (such as business partners), restrict each operation to an administrator role as shown above.
110
+
108
111
  **Helper:** `unsafeAllowAllIdPPermission` grants full access without conditions. Intended only for development and testing.
109
112
 
110
113
  ```typescript
@@ -161,6 +164,8 @@ defineIdp("my-idp", {
161
164
  - `passwordMinLength` - Minimum password length. Must be between 6 and 30. Default `6`.
162
165
  - `passwordMaxLength` - Maximum password length. Must be between 6 and 4096. Default `4096`.
163
166
 
167
+ **Password expiration:** the IdP does not expire passwords on its own, but it records when each user's password was last set so you can enforce your own policy. The `User` records returned by `idp.Client` expose it as `passwordUpdatedAt` (an ISO 8601 string, or `null` for users without a password), for example to find expired passwords in a scheduled executor. To block sign-in with an expired password, read `claims.password_updated_at` in the Auth `beforeLogin` hook (see [Auth](./auth.md#password-age-claim)).
168
+
164
169
  **Email domains and social login:**
165
170
 
166
171
  - `allowedEmailDomains` - Restrict registration to these email domains. Each entry is a hostname (`example.com`), matched exactly and case-insensitively, so `example.com` does not cover `sub.example.com`. Up to 100 entries.
@@ -189,7 +194,7 @@ defineIdp("my-idp", {
189
194
  read: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
190
195
  update: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
191
196
  delete: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
192
- sendPasswordResetEmail: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
197
+ sendPasswordResetEmail: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
193
198
  unenrollMfa: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
194
199
  },
195
200
  });
@@ -141,7 +141,7 @@ GraphQL still accepts and returns `YYYY-MM-DD` strings. The SDK converts input t
141
141
 
142
142
  This option also works in nested objects and with `array: true` or `optional: true`. Input must be a valid calendar date, and output must be a valid `Date` with a 4-digit UTC year (0000-9999). Both deployed resolvers and `tailor function run` perform these conversions.
143
143
 
144
- A resolver bundle only includes the conversion code for the representations (`as: "date"` or `as: "temporal"`) its `input` and `output` use. To parse other values with such fields inside `body`, for example an external API response, use `parseDateFields` instead of `.parse()`. It takes the same arguments and returns the same result as `.parse()`. Calling `.parse()` on a field whose representation the bundle leaves out throws an error that points to `parseDateFields`.
144
+ A resolver bundle only includes the conversion code for the representations (`as: "date"` or `as: "temporal"`) its `input` and `output` use; with [`defaultDateRepresentation: "temporal"`](../configuration.md#application-settings), the Temporal conversion is always included. To parse other values with such fields inside `body`, for example an external API response, use `parseDateFields` instead of `.parse()`. It takes the same arguments and returns the same result as `.parse()`. Calling `.parse()` on a field whose representation the bundle leaves out throws an error that points to `parseDateFields`.
145
145
 
146
146
  ```typescript
147
147
  import { parseDateFields } from "@tailor-platform/sdk/runtime";
@@ -204,6 +204,8 @@ createResolver({
204
204
 
205
205
  All three field types support `array`, `optional`, nested objects, and input validators with the selected representation. Both deployed resolvers and `tailor function run` convert input and output. Executors using `resolverExecutedTrigger` receive date, datetime, and time results as strings.
206
206
 
207
+ To use Temporal values without writing `as: "temporal"` on every field, set [`defaultDateRepresentation: "temporal"`](../configuration.md#application-settings) in `defineConfig()`. Fields that omit `as` then use `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, and `as: "string"` opts a single field back to strings. `defaultDateRepresentation: "date"` does the same for `Date` values. Run `tailor generate` after changing the setting so `tailor.d.ts` updates the field types.
208
+
207
209
  The SDK supplies Temporal types, so existing projects can use `as: "temporal"` without changing `compilerOptions.lib`. To construct values or name their types, import `Temporal` from the SDK:
208
210
 
209
211
  ```typescript
@@ -145,11 +145,11 @@ const website = defineStaticWebSite("my-frontend", {
145
145
  const idp = defineIdp("my-idp", {
146
146
  clients: ["default-client"],
147
147
  permission: {
148
- create: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
149
- read: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
150
- update: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
151
- delete: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
152
- sendPasswordResetEmail: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
148
+ create: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
149
+ read: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
150
+ update: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
151
+ delete: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
152
+ sendPasswordResetEmail: [{ conditions: [[{ user: "role" }, "=", "ADMIN"]], permit: true }],
153
153
  },
154
154
  });
155
155