@forumone/throughline 1.0.0-next.0 → 1.0.0-next.2
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.
- package/CHANGELOG.md +46 -0
- package/README.md +70 -324
- package/bin/throughline.mjs +6 -0
- package/dist/approvals/tokens.d.ts +2 -2
- package/dist/approvals/tokens.d.ts.map +1 -1
- package/dist/approvals/tokens.js +2 -2
- package/dist/audit/query/options.d.ts +6 -4
- package/dist/audit/query/options.d.ts.map +1 -1
- package/dist/audit/query/options.js.map +1 -1
- package/dist/audit/query/plugin.d.ts.map +1 -1
- package/dist/audit/query/plugin.js +5 -1
- package/dist/audit/query/plugin.js.map +1 -1
- package/dist/audit/query/tools/get-change-history.d.ts +3 -1
- package/dist/audit/query/tools/get-change-history.d.ts.map +1 -1
- package/dist/audit/query/tools/get-change-history.js +2 -2
- package/dist/audit/query/tools/get-change-history.js.map +1 -1
- package/dist/audit/query/tools/get-recent-failures.d.ts +3 -1
- package/dist/audit/query/tools/get-recent-failures.d.ts.map +1 -1
- package/dist/audit/query/tools/get-recent-failures.js +2 -2
- package/dist/audit/query/tools/get-recent-failures.js.map +1 -1
- package/dist/audit/query/tools/query-audit.d.ts +5 -3
- package/dist/audit/query/tools/query-audit.d.ts.map +1 -1
- package/dist/audit/query/tools/query-audit.js +2 -2
- package/dist/audit/query/tools/query-audit.js.map +1 -1
- package/dist/audit/query/tools/what-changed-in-range.d.ts +3 -1
- package/dist/audit/query/tools/what-changed-in-range.d.ts.map +1 -1
- package/dist/audit/query/tools/what-changed-in-range.js +2 -2
- package/dist/audit/query/tools/what-changed-in-range.js.map +1 -1
- package/dist/audit/query/tools/who-changed-what.d.ts +3 -1
- package/dist/audit/query/tools/who-changed-what.d.ts.map +1 -1
- package/dist/audit/query/tools/who-changed-what.js +2 -2
- package/dist/audit/query/tools/who-changed-what.js.map +1 -1
- package/dist/components/manifest-source.d.ts +1 -1
- package/dist/components/manifest-source.d.ts.map +1 -1
- package/dist/components/manifest-source.js +1 -1
- package/dist/components/manifest-source.js.map +1 -1
- package/dist/components/matching/tfidf.d.ts +1 -1
- package/dist/components/matching/tfidf.d.ts.map +1 -1
- package/dist/components/matching/types.d.ts +1 -1
- package/dist/components/matching/types.d.ts.map +1 -1
- package/dist/components/options.d.ts +1 -1
- package/dist/components/options.d.ts.map +1 -1
- package/dist/components/validation/composition.d.ts +1 -1
- package/dist/components/validation/composition.d.ts.map +1 -1
- package/dist/email/client.d.ts.map +1 -1
- package/dist/email/client.js +3 -2
- package/dist/email/client.js.map +1 -1
- package/dist/email/functions/_shared.d.ts +6 -0
- package/dist/email/functions/_shared.d.ts.map +1 -1
- package/dist/email/functions/_shared.js +9 -0
- package/dist/email/functions/_shared.js.map +1 -1
- package/dist/email/functions/notify-approval-decision.d.ts.map +1 -1
- package/dist/email/functions/notify-approval-decision.js +2 -1
- package/dist/email/functions/notify-approval-decision.js.map +1 -1
- package/dist/email/functions/notify-approval-expired.d.ts.map +1 -1
- package/dist/email/functions/notify-approval-expired.js +2 -1
- package/dist/email/functions/notify-approval-expired.js.map +1 -1
- package/dist/email/functions/notify-approval-request.d.ts.map +1 -1
- package/dist/email/functions/notify-approval-request.js +2 -1
- package/dist/email/functions/notify-approval-request.js.map +1 -1
- package/dist/email/index.d.ts +1 -1
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1 -1
- package/dist/email/index.js.map +1 -1
- package/dist/email/options.js +1 -1
- package/dist/email/options.js.map +1 -1
- package/dist/env/index.d.ts +0 -1
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js +0 -1
- package/dist/env/index.js.map +1 -1
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -11
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +1 -1
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/integrations/webhook/config-fields.d.ts.map +1 -1
- package/dist/integrations/integrations/webhook/config-fields.js +3 -0
- package/dist/integrations/integrations/webhook/config-fields.js.map +1 -1
- package/dist/integrations/integrations/webhook/functions.d.ts.map +1 -1
- package/dist/integrations/integrations/webhook/functions.js +0 -1
- package/dist/integrations/integrations/webhook/functions.js.map +1 -1
- package/dist/integrations/integrations/webhook/index.d.ts.map +1 -1
- package/dist/integrations/integrations/webhook/index.js +0 -1
- package/dist/integrations/integrations/webhook/index.js.map +1 -1
- package/dist/integrations/jobs/healthcheck.js +1 -1
- package/dist/integrations/jobs/healthcheck.js.map +1 -1
- package/dist/integrations/plugin.js +1 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/registry.d.ts +1 -1
- package/dist/integrations/registry.js +1 -1
- package/dist/integrations/types.d.ts +11 -9
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/jobs/failure-handler.d.ts.map +1 -1
- package/dist/jobs/failure-handler.js +5 -2
- package/dist/jobs/failure-handler.js.map +1 -1
- package/dist/jobs/workflow-types.d.ts +5 -1
- package/dist/jobs/workflow-types.d.ts.map +1 -1
- package/dist/mcp/audit-server.js +1 -1
- package/dist/mcp/audit-server.js.map +1 -1
- package/dist/mcp/meta.d.ts +8 -8
- package/dist/migrate/cli.d.ts +9 -0
- package/dist/migrate/cli.d.ts.map +1 -0
- package/dist/migrate/cli.js +105 -0
- package/dist/migrate/cli.js.map +1 -0
- package/dist/migrate/exports.json +601 -0
- package/dist/migrate/rewrite.d.ts +28 -0
- package/dist/migrate/rewrite.d.ts.map +1 -0
- package/dist/migrate/rewrite.js +188 -0
- package/dist/migrate/rewrite.js.map +1 -0
- package/dist/observability/collection.d.ts +3 -0
- package/dist/observability/collection.d.ts.map +1 -1
- package/dist/observability/collection.js +2 -0
- package/dist/observability/collection.js.map +1 -1
- package/dist/publishing/hooks/block-status-writes.d.ts +32 -0
- package/dist/publishing/hooks/block-status-writes.d.ts.map +1 -1
- package/dist/publishing/hooks/block-status-writes.js +52 -9
- package/dist/publishing/hooks/block-status-writes.js.map +1 -1
- package/dist/publishing/index.d.ts +6 -0
- package/dist/publishing/index.d.ts.map +1 -1
- package/dist/publishing/index.js +6 -0
- package/dist/publishing/index.js.map +1 -1
- package/dist/publishing/pipeline/steps/accessibility.d.ts +4 -3
- package/dist/publishing/pipeline/steps/accessibility.d.ts.map +1 -1
- package/dist/publishing/pipeline/steps/accessibility.js +10 -4
- package/dist/publishing/pipeline/steps/accessibility.js.map +1 -1
- package/dist/publishing/pipeline/steps/approval.js +1 -1
- package/dist/publishing/pipeline/steps/approval.js.map +1 -1
- package/dist/throughline.d.ts +123 -0
- package/dist/throughline.d.ts.map +1 -0
- package/dist/throughline.js +190 -0
- package/dist/throughline.js.map +1 -0
- package/dist/utils/optionalPeer.d.ts +12 -0
- package/dist/utils/optionalPeer.d.ts.map +1 -0
- package/dist/utils/optionalPeer.js +28 -0
- package/dist/utils/optionalPeer.js.map +1 -0
- package/package.json +33 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @forumone/throughline
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next.2
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- dcf84fc: `resend`, `@react-email/components` and `@react-email/render` are optional peers rather than dependencies, and `inngest` is an optional peer. `payload` is the only required one. Each optional peer is loaded only by the subpath that uses it, and the root loads none: the approval emails import their templates when they send, so registering the suite with `throughline()` no longer loads React or React Email. A missing optional peer fails when its feature runs, with an error naming the package to install.
|
|
8
|
+
- e7d34fa: Smaller gaps closed before 1.0:
|
|
9
|
+
|
|
10
|
+
- `Integration.createFunctions` is optional. `throughline()` runs `createJobs` and never called it.
|
|
11
|
+
- `auditQuery.readAccess` now applies: it takes the tool's context, `(ctx) => boolean`, and replaces the admin/editor rule for the five audit tools. It was declared with a `PayloadRequest` and read by nothing.
|
|
12
|
+
- `job-failures` takes an `admin` sidebar group like every other Throughline collection, and gets the suite's from `throughline()`.
|
|
13
|
+
- An accessibility issue of severity `warning` reaches the publish result's `warnings` instead of being dropped.
|
|
14
|
+
- The webhook integration no longer subscribes to `form/submission.received`, which nothing sends since forms left the suite; the stored filter option stays. Stale text naming forms, the "Approvals Server" and 0.x paths is corrected.
|
|
15
|
+
|
|
16
|
+
- 673ff70: `throughline()`: one call for the whole suite (`docs/spec/1.0-throughline-call.md`). It returns `{ plugin, mcpTools, jobs }`: one Payload plugin that registers every enabled Throughline plugin in order, the tool array for `mcpPlugin`, and every job the options call for. Audit, job failures and `check_slug` are always on; every other plugin is on when its key is present. Shared values are given once: `approvals.collectionSlug` reaches the collection, the emails and the expiry job, and `collections` reaches publishing, "Your work" and scheduled publishing. Its defaults are what every site wrote by hand: scheduled publishes go through the publishing pipeline (`publishScheduledThroughPipeline`), approval links are signed with approvals' secret, and a failing healthcheck is recorded in `job-failures`. On Payload Jobs it registers its jobs itself. It refuses an integration with no `createJobs`.
|
|
17
|
+
|
|
18
|
+
**Internal now:** `getPluginRegistry`, `resolveAdminGroup`, `DEFAULT_ADMIN_GROUP`, `PluginRegistry*`, `createMcpToolCollector` and the collector's option types, `toPayloadMcpTool(s)`, `getEmailFunctions`, `getIntegrationRegistry` and `getIntegrationContext`. Use `throughline()`. `McpToolCollector` and `PayloadMcpTool` stay exported as types.
|
|
19
|
+
|
|
20
|
+
`HealthcheckOptions.onFailure` receives `{ payload }` as a second argument, and `createHealthcheckFailureHandler()` made without a `payload` records on the run's own.
|
|
21
|
+
|
|
22
|
+
- 6c1409a: The trust boundary now covers every write that changes what the public sees. A create with `_status: 'published'` is refused (create a draft, then publish), and so is a non-draft save that changes a live document (save a draft, then publish). Before, both went live with no pipeline, approval or audit row, so "requires approval" held only for a page's first publish. Data a system derives from a live page and writes back to it, such as an audio URL, passes with `context: DERIVED_WRITE_CONTEXT` from `/publishing`, which can never change `_status`, create, or promote a draft.
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- 2e49ee7: `throughline migrate-imports [paths…] [--dry-run]`, a new bin: rewrites every 0.x `@forumone/throughline-*` import to its 1.0 home, by `docs/spec/1.0-exports.md`. It splits an import by where each name went, keeps `type` and aliases, rewrites admin component paths (`importMap.js` included), and points mocks, dynamic imports and module augmentation at the 1.0 counterpart for you to check. Names 1.0 removed or made internal are left in place and listed with what to use instead, as are the `package.json` dependencies to swap, and it exits 1 while anything is left. Run on forumone-2026, it rewrites 163 files and leaves the five imports `throughline()` replaces.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- @forumone/throughline-design-system@1.0.0-next.2
|
|
31
|
+
|
|
32
|
+
## 1.0.0-next.1
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- 825f4e9: `@forumone/throughline-design-system`: the 1.0 design-system package, from `@forumone/throughline-design-contract` and `@forumone/throughline-design-system-payload`, by `docs/spec/1.0-exports.md`. It is published for the first time: design-system-payload was private and shipped TypeScript source, and this package builds to `dist`.
|
|
37
|
+
|
|
38
|
+
- `@forumone/throughline-design-contract` is `/contract`, and its `/lint` is `/lint`.
|
|
39
|
+
- design-system-payload's `/generate`, `/render`, `/client` and `/testing` keep their names. Its root (`fieldOverride` and the override types) is part of `/generate`.
|
|
40
|
+
- The `check-block-props` bin is unchanged, and runs the built CLI.
|
|
41
|
+
- Admin component paths are `@forumone/throughline-design-system/client#BlockSummary`, `#BlockGuidance` and `#RowSummary`, so a site's `importMap.js` changes.
|
|
42
|
+
- `/contract` and `/lint` need no peers. `payload`, `react`, `@payloadcms/ui`, `typescript` (which `/generate` uses to read component source) and `vitest` (for `/testing`) are optional peers.
|
|
43
|
+
|
|
44
|
+
`@forumone/throughline/components` now reads manifests through `@forumone/throughline-design-system/contract`.
|
|
45
|
+
|
|
46
|
+
- Updated dependencies [825f4e9]
|
|
47
|
+
- @forumone/throughline-design-system@1.0.0-next.0
|
|
48
|
+
|
|
3
49
|
## 1.0.0-next.0
|
|
4
50
|
|
|
5
51
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -1,352 +1,98 @@
|
|
|
1
1
|
# @forumone/throughline
|
|
2
2
|
|
|
3
|
-
Throughline for Payload CMS:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Throughline for Payload CMS: publishing behind a policy pipeline, approvals, the audit log, editorial
|
|
4
|
+
reports, integrations, email and background jobs, with every feature reachable over MCP. One call
|
|
5
|
+
registers the suite; each part also lives on its own subpath.
|
|
6
6
|
|
|
7
7
|
> **1.0 is in progress.** Every 0.x server package has moved in: core, plugin-contract, publishing,
|
|
8
|
-
> workflows, audit, approvals, components, integrations and email.
|
|
9
|
-
> [
|
|
8
|
+
> workflows, audit, approvals, components, integrations and email.
|
|
9
|
+
> [Upgrading from 0.x](https://github.com/forumone/throughline/blob/main/docs/guides/upgrading.md) moves a 0.x site onto it.
|
|
10
10
|
> Pre-releases publish as `1.0.0-next.N` under the `next` dist-tag.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
| Subpath | Holds |
|
|
15
|
-
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
-
| (root) | `assertEnvironment`, `checkEnvValue`, `hardenCoreCollections`, `mcpApiKeyAccess`, `withMeta`, `auditContext`, the logger, utilities, and the plugin types |
|
|
17
|
-
| `/publishing` | `publishingPlugin`, the publishing service, `isDraftWrite`, the accessibility checks, and the revalidation and scheduled-publishing jobs; see [docs/publishing.md](docs/publishing.md) |
|
|
18
|
-
| `/editorial` | `editorialPlugin`: content health, the content calendar, "Your work", command palette search, and their MCP tools |
|
|
19
|
-
| `/approvals` | `approvalsPlugin`, signed action links, `expireStaleApprovalsJob`; see [docs/approvals.md](docs/approvals.md) |
|
|
20
|
-
| `/audit` | `auditPlugin`, `getAuditWriter`, `AUDIT_ACTIONS`, `auditQueryPlugin` and its tools, `auditEventEchoJob`; see [docs/audit.md](docs/audit.md) |
|
|
21
|
-
| `/components` | `componentsPlugin`: the design-system manifest over MCP; see [docs/components.md](docs/components.md) |
|
|
22
|
-
| `/integrations` | `integrationsPlugin`, the registry, the webhook integration, manual sync, `healthcheckJob`; see [docs/integrations.md](docs/integrations.md) |
|
|
23
|
-
| `/email` | `emailPlugin`, the Resend client, the approval notifications and their templates; see [docs/email.md](docs/email.md) |
|
|
24
|
-
| `/jobs` | `defineJob`, the job types, the failure handlers, and `CoreEvents` for module augmentation; see [docs/jobs.md](docs/jobs.md) |
|
|
25
|
-
| `/jobs/inngest` | `inngestJobs`, `createInngestClient`, `resolveInngestEnv`, `registrableInngestFunctions` |
|
|
26
|
-
| `/jobs/payload` | `payloadJobs` |
|
|
27
|
-
| `/media` | Blob client-upload hardening, and reference tracking: `referencesPlugin`, `findReferences`, the delete and trash guards |
|
|
28
|
-
| `/fields` | The field kit: `slugField`, `publishingFields`, `revisedAtField`, `unlistedField`, `characterCountPlugin`, `fieldsPlugin` |
|
|
29
|
-
| `/observability` | `jobFailuresPlugin`, `getJobFailureWriter`, `createErrorReporter`, `reportError`, `buildRequestErrorReport` |
|
|
30
|
-
| `/testing` | `describeAnonymousAccess`, `checkAnonymousAccess`: test helpers for a site |
|
|
31
|
-
| `/cache-tags` | `createCacheTags`, which imports nothing, for front-end readers |
|
|
32
|
-
| `/client`, `/rsc` | The admin's client and server components, named in Payload's import map |
|
|
33
|
-
| bin | `throughline-payload`, see [below](#running-the-payload-cli-throughline-payload) |
|
|
34
|
-
|
|
35
|
-
The MCP collector (`createMcpToolCollector`) is on the root until `throughline()` wires it.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
pnpm add @forumone/throughline@next
|
|
41
|
-
```
|
|
12
|
+
**Reference: [`docs/reference/throughline.md`](https://github.com/forumone/throughline/blob/main/docs/reference/throughline.md)**, with a page per plugin.
|
|
42
13
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## The audit log
|
|
14
|
+
## What's inside
|
|
46
15
|
|
|
47
|
-
|
|
16
|
+
| Subpath | Holds |
|
|
17
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| (root) | `assertEnvironment`, `checkEnvValue`, `hardenCoreCollections`, `mcpApiKeyAccess`, `withMeta`, `auditContext`, the logger, utilities, and the plugin types |
|
|
19
|
+
| `/publishing` | `publishingPlugin`, the publishing service, `isDraftWrite`, the accessibility checks, and the revalidation and scheduled-publishing jobs; see [publishing](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/publishing.md) |
|
|
20
|
+
| `/editorial` | `editorialPlugin`: content health, the content calendar, "Your work", command palette search, and their MCP tools |
|
|
21
|
+
| `/approvals` | `approvalsPlugin`, signed action links, `expireStaleApprovalsJob`; see [approvals](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/approvals.md) |
|
|
22
|
+
| `/audit` | `auditPlugin`, `getAuditWriter`, `AUDIT_ACTIONS`, `auditQueryPlugin` and its tools, `auditEventEchoJob`; see [audit](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/audit.md) |
|
|
23
|
+
| `/components` | `componentsPlugin`: the design-system manifest over MCP; see [components](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/components.md) |
|
|
24
|
+
| `/integrations` | `integrationsPlugin`, the registry, the webhook integration, manual sync, `healthcheckJob`; see [integrations](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/integrations.md) |
|
|
25
|
+
| `/email` | `emailPlugin`, the Resend client, the approval notifications and their templates; see [email](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/email.md) |
|
|
26
|
+
| `/jobs` | `defineJob`, the job types, the failure handlers, and `CoreEvents` for module augmentation; see [jobs](https://github.com/forumone/throughline/blob/main/docs/reference/throughline/jobs.md) |
|
|
27
|
+
| `/jobs/inngest` | `inngestJobs`, `createInngestClient`, `resolveInngestEnv`, `registrableInngestFunctions` |
|
|
28
|
+
| `/jobs/payload` | `payloadJobs` |
|
|
29
|
+
| `/media` | Blob client-upload hardening, and reference tracking: `referencesPlugin`, `findReferences`, the delete and trash guards |
|
|
30
|
+
| `/fields` | The field kit: `slugField`, `publishingFields`, `revisedAtField`, `unlistedField`, `characterCountPlugin`, `fieldsPlugin` |
|
|
31
|
+
| `/observability` | `jobFailuresPlugin`, `getJobFailureWriter`, `createErrorReporter`, `reportError`, `buildRequestErrorReport` |
|
|
32
|
+
| `/testing` | `describeAnonymousAccess`, `checkAnonymousAccess`: test helpers for a site |
|
|
33
|
+
| `/cache-tags` | `createCacheTags`, which imports nothing, for front-end readers |
|
|
34
|
+
| `/client`, `/rsc` | The admin's client and server components, named in Payload's import map |
|
|
35
|
+
| bin | `throughline migrate-imports`, see [Upgrading from 0.x](https://github.com/forumone/throughline/blob/main/docs/guides/upgrading.md); `throughline-payload`, see [the reference](https://github.com/forumone/throughline/blob/main/docs/reference/throughline.md#throughline-payload-bin) |
|
|
36
|
+
|
|
37
|
+
## `throughline()`
|
|
48
38
|
|
|
49
39
|
```ts
|
|
50
|
-
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
const
|
|
40
|
+
// payload.config.ts
|
|
41
|
+
import { mcpApiKeyAccess, throughline } from '@forumone/throughline'
|
|
42
|
+
import { inngestJobs } from '@forumone/throughline/jobs/inngest'
|
|
43
|
+
|
|
44
|
+
export const suite = throughline({
|
|
45
|
+
jobs: inngestJobs(inngest), // or payloadJobs() from /jobs/payload
|
|
46
|
+
collections: ['pages', 'posts'],
|
|
47
|
+
publishing: { urls: { pages: (slug) => `/${slug}`, posts: (slug) => `/blog/${slug}` } },
|
|
48
|
+
approvals: { groups, groupResolver },
|
|
49
|
+
email: { resolveApprover, resolveRequester },
|
|
50
|
+
integrations: {},
|
|
51
|
+
healthcheck: { checks: [createPayloadReachableCheck()] },
|
|
52
|
+
})
|
|
55
53
|
|
|
56
54
|
export default buildConfig({
|
|
57
|
-
//
|
|
55
|
+
// …
|
|
58
56
|
plugins: [
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
suite.plugin,
|
|
58
|
+
mcpPlugin({
|
|
59
|
+
mcp: { tools: suite.mcpTools },
|
|
60
|
+
overrideApiKeyCollection: mcpApiKeyAccess(isAdmin),
|
|
61
|
+
}),
|
|
61
62
|
],
|
|
62
63
|
})
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
In a downstream plugin's `onInit`:
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { getAuditWriter } from '@forumone/throughline/audit'
|
|
69
|
-
|
|
70
|
-
onInit: async (payload) => {
|
|
71
|
-
const writer = getAuditWriter(payload)
|
|
72
|
-
await writer({
|
|
73
|
-
actor: { type: 'user', userId: 'u1' },
|
|
74
|
-
action: 'publishing.publish',
|
|
75
|
-
mcpServer: 'publishing',
|
|
76
|
-
mcpTool: 'publishing.publish',
|
|
77
|
-
targetCollection: 'pages',
|
|
78
|
-
targetId: 'p1',
|
|
79
|
-
targetTitle: 'Homepage',
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The writer is **fire-and-forget**: failures log but never throw. Audit failures must never break the originating action.
|
|
85
|
-
|
|
86
|
-
### Sidebar group
|
|
87
|
-
|
|
88
|
-
The `audit-events` collection sits in the admin sidebar's `Throughline` group by default. `auditPlugin({ inngest, admin: { group: 'Workflow' } })` files it elsewhere; `admin: { group: false }` leaves it ungrouped. Every Throughline plugin that declares a collection takes the same option — see [the reference](https://github.com/forumone/throughline/blob/main/docs/reference/plugin-contract.md#admin-sidebar-group).
|
|
89
|
-
|
|
90
|
-
## Job failures and error reporting
|
|
91
|
-
|
|
92
|
-
The audit log records who did what through an MCP tool. A background job that
|
|
93
|
-
ran out of retries is not that, so it has its own collection:
|
|
94
|
-
`jobFailuresPlugin()` adds `job-failures` and attaches a writer that
|
|
95
|
-
the failure handlers in `/jobs` find. Adding it to an
|
|
96
|
-
existing site is a schema change — run `payload migrate:create` afterwards.
|
|
97
|
-
|
|
98
|
-
Error reports go to a webhook, not to a vendor SDK: `reportError` posts JSON to
|
|
99
|
-
`ERROR_WEBHOOK_URL` (a log drain, an alerting endpoint, a Slack incoming
|
|
100
|
-
webhook, a proxy in front of a tracker), with a 3-second timeout, and never
|
|
101
|
-
throws. `buildRequestErrorReport` shapes what Next's `onRequestError` hands
|
|
102
|
-
over, copying request headers from an allowlist — `cookie` and `authorization`
|
|
103
|
-
are never copied.
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// instrumentation.ts
|
|
107
|
-
import type { Instrumentation } from 'next'
|
|
108
|
-
import { buildRequestErrorReport, reportError } from '@forumone/throughline/observability'
|
|
109
|
-
|
|
110
|
-
export const onRequestError: Instrumentation.onRequestError = async (error, request, context) => {
|
|
111
|
-
await reportError(buildRequestErrorReport(error, request, context))
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`./observability` imports neither Payload nor Inngest at runtime, so it is safe
|
|
116
|
-
in `instrumentation.ts`. See `docs/operations/observability.md`.
|
|
117
|
-
|
|
118
|
-
## MCP authentication
|
|
119
|
-
|
|
120
|
-
`@payloadcms/plugin-mcp` handles MCP authentication. It adds the `payload-mcp-api-keys` collection, looks up `Authorization: Bearer <key>` on `/api/mcp`, and runs each tool as the person the key is bound to. This package's `createApiKeysCollection`, `createBearerTokenAuthenticator` and `createMcpHandler` are gone.
|
|
121
|
-
|
|
122
|
-
What this package adds is two access helpers for a site that registers that plugin.
|
|
123
|
-
|
|
124
|
-
**Only admins should manage keys.** A key runs every tool as its user, so minting, reading and revoking keys is an admin's job. The plugin doesn't enforce that by default. `mcpApiKeyAccess(isAdmin)` is an `overrideApiKeyCollection` that applies your admin rule to `read`, `create`, `update`, `delete` and `unlock`, and refuses an MCP key principal before it asks the rule:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import { createMcpToolCollector, mcpApiKeyAccess } from '@forumone/throughline'
|
|
128
|
-
import { mcpPlugin } from '@payloadcms/plugin-mcp'
|
|
129
|
-
import type { Access } from 'payload'
|
|
130
|
-
|
|
131
|
-
const isAdmin: Access = ({ req: { user } }) =>
|
|
132
|
-
user?.collection === 'users' && Array.isArray(user.roles) && user.roles.includes('admin')
|
|
133
|
-
|
|
134
|
-
mcpPlugin({
|
|
135
|
-
mcp: { tools: mcpTools.tools },
|
|
136
|
-
overrideApiKeyCollection: mcpApiKeyAccess(isAdmin),
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
It changes `access` and nothing else. To set other options too, call it inside your own override:
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
overrideApiKeyCollection: collection => {
|
|
144
|
-
const hardened = mcpApiKeyAccess(isAdmin)(collection)
|
|
145
|
-
return { ...hardened, admin: { ...hardened.admin, group: 'Throughline' } }
|
|
146
|
-
},
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**"Signed in" is not `Boolean(req.user)`.** Before Payload 3.89.0, the key collection's `auth.useAPIKey` registered Payload's API-key strategy on every REST route, so a key _document_ could become `req.user`. It has no roles, but it passes `Boolean(req.user)`, so a rule like "published, or anybody signed in" served drafts to anyone holding a key. Payload 3.89.0 fixed this, and it's this package's peer floor. Refuse the principal in your own rules as well:
|
|
150
|
-
|
|
151
|
-
| Export | What it does |
|
|
152
|
-
| ---------------------------- | ------------------------------------------------------------------------------- |
|
|
153
|
-
| `isSignedIn(user)` | `true` for a person, `false` for anonymous or an MCP key document. A type guard |
|
|
154
|
-
| `signedIn` | `isSignedIn` as an `Access` function |
|
|
155
|
-
| `isMcpApiKeyPrincipal(user)` | `true` only for a key document from `payload-mcp-api-keys` |
|
|
156
|
-
| `MCP_API_KEYS_SLUG` | `'payload-mcp-api-keys'` |
|
|
157
|
-
|
|
158
|
-
None of these affects `/api/mcp`. A tool call there arrives as the key's user, in `users`, with that person's roles. See [the security model](../../docs/operations/security-model.md#signed-in-is-not-booleanrequser).
|
|
159
|
-
|
|
160
|
-
### Scopes
|
|
161
|
-
|
|
162
|
-
A key carries `scopes`, and a tool may declare the one it needs:
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
const publishTool: McpToolDefinition = {
|
|
166
|
-
name: 'publish',
|
|
167
|
-
requiredScope: 'publishing.execute',
|
|
168
|
-
// …
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
> **Nothing enforces this today.** The paragraphs below describe how
|
|
173
|
-
> `requiredScope` behaved when each server mounted its own `/api/<server>/mcp`
|
|
174
|
-
> endpoint behind a hand-written JSON-RPC handler. That handler — and the
|
|
175
|
-
> `auth.ts` that held callers to their scopes — were removed by "one MCP
|
|
176
|
-
> transport, not seven" (#80), which consolidated every server onto Payload's
|
|
177
|
-
> `/api/mcp`. The scope declarations survived the refactor; the enforcement did
|
|
178
|
-
> not, and this section was not updated to say so. Audit 04 F-02.
|
|
179
|
-
>
|
|
180
|
-
> **What gates a tool now** is the per-key checkbox `@payloadcms/plugin-mcp`
|
|
181
|
-
> generates, one per tool name. Two consequences worth knowing before relying
|
|
182
|
-
> on either: all 27 of them **default to `true`**
|
|
183
|
-
> (`createApiKeysCollection.js:4-15`), and a checkbox list cannot express
|
|
184
|
-
> "writes off until granted, reads on" the way a scope can.
|
|
185
|
-
>
|
|
186
|
-
> The declarations are kept because they are the tool → scope mapping a
|
|
187
|
-
> scope-aware default would be built from — see the note on `requiredScope` in
|
|
188
|
-
> `McpToolDefinition`, which states the same thing at the
|
|
189
|
-
> type. Reinstating enforcement means putting it on the surviving transport.
|
|
190
|
-
|
|
191
|
-
Historically, and as the intended design: a tool that declares no
|
|
192
|
-
`requiredScope` is callable by any authenticated key, which is the right
|
|
193
|
-
default for a read. A tool that declares one was **hidden from `tools/list`**
|
|
194
|
-
and refused on a direct call unless the key named that scope — hidden as well
|
|
195
|
-
as refused, because an agent shown a tool it will be turned away from will try
|
|
196
|
-
it, fail, and report the tool as broken when what is narrow is the key.
|
|
197
|
-
|
|
198
|
-
A key carrying no scopes at all passed nothing scoped. Absent was read as none,
|
|
199
|
-
not as everything.
|
|
200
|
-
|
|
201
|
-
The consequential tools in this suite and the scopes they require:
|
|
202
|
-
|
|
203
|
-
| Scope | Tools |
|
|
204
|
-
| ---------------------- | --------------------------------------------------------------- |
|
|
205
|
-
| `publishing.execute` | `publish`, `unpublish`, `schedule_publish`, `rollback` |
|
|
206
|
-
| `approvals.request` | `request_approval` |
|
|
207
|
-
| `approvals.decide` | `respond_to_approval` |
|
|
208
|
-
| `forms.manage` | `create_form`, `update_form_fields`, `update_form_destinations` |
|
|
209
|
-
| `integrations.trigger` | `trigger_sync`, `test_integration` |
|
|
210
|
-
|
|
211
|
-
Everything else — the component tools, the audit queries, the read side of publishing and approvals — needs only a valid key.
|
|
212
|
-
|
|
213
|
-
## Events
|
|
214
|
-
|
|
215
|
-
`CoreEvents` enumerates the events the framework fires today. Server packages add their own via TypeScript module augmentation:
|
|
216
|
-
|
|
217
|
-
```ts
|
|
218
|
-
declare module '@forumone/throughline/jobs' {
|
|
219
|
-
interface FrameworkEvents {
|
|
220
|
-
'approval/decided': {
|
|
221
|
-
data: { approvalId: string; decision: 'granted' | 'declined' }
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
After augmentation, `inngest.send({ name: 'approval/decided', data: { ... } })` is type-checked everywhere.
|
|
228
|
-
|
|
229
|
-
## Checking the environment
|
|
230
|
-
|
|
231
|
-
Each plugin that falls back to `process.env` exports what it needs as data — `approvalsEnv`, `emailEnv`, `formsEnv` — and checks the same entries at init. A site passes every list, plus its own variables, to `assertEnvironment` first thing in `payload.config.ts`:
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
import { assertEnvironment } from '@forumone/throughline'
|
|
235
|
-
import { approvalsEnv } from '@forumone/throughline/approvals'
|
|
236
|
-
import { emailEnv } from '@forumone/throughline/email'
|
|
237
|
-
|
|
238
|
-
assertEnvironment(
|
|
239
|
-
approvalsEnv,
|
|
240
|
-
emailEnv,
|
|
241
|
-
{ name: 'PAYLOAD_SECRET', minLength: 32, why: 'Signs Payload sessions.' },
|
|
242
|
-
() => databaseConnectionString(),
|
|
243
|
-
)
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
It throws once, listing every missing or too-short value with the reason it is needed, under a first line that says `Configuration problem` — instead of each plugin failing on the first thing it finds, one deploy at a time, inside a `next build` stack trace. It never prints a value. A function argument is for a rule that is not "this name, this long": what it throws becomes a line in the report.
|
|
247
|
-
|
|
248
|
-
`checkEnvValue(requirement, value)` is the one-value check behind it, which a plugin uses for its own init backstop.
|
|
249
|
-
|
|
250
|
-
## Document content hashing
|
|
251
|
-
|
|
252
|
-
`documentContentHash(document)` reduces a Payload document to a hash of the part an editor authored, ignoring the metadata that moves without the content moving — `id`, `createdAt`, `updatedAt`, `_status`, `__v`, `_id`, `globalType`, stripped at every level of the document. Object key order does not affect the result, because blocks come back out of JSONB in no promised order; array order does, because that is the order of the blocks on the page.
|
|
253
|
-
|
|
254
66
|
```ts
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
const withExtras = await documentContentHash(page, { exclude: ['syncedAt'] })
|
|
67
|
+
// app/api/inngest/route.ts, on Inngest. On Payload Jobs, suite.plugin registers the jobs itself.
|
|
68
|
+
const jobs = inngestJobs(inngest, { onFailure: createTerminalFailureHandler({ payload }), payload })
|
|
69
|
+
export const { GET, POST, PUT } = serve({ client: inngest, functions: jobs.functions(suite.jobs) })
|
|
259
70
|
```
|
|
260
71
|
|
|
261
|
-
It
|
|
262
|
-
|
|
263
|
-
**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
`pnpm payload generate:types` is shell → pnpm → node, and pnpm (like npm and
|
|
268
|
-
yarn) does not forward signals to the node child it spawns. Kill the shell — a
|
|
269
|
-
Ctrl-C, an agent's tool-call timeout, a cancelled CI step — and node keeps
|
|
270
|
-
running, reparented to PID 1. When the run was hung rather than slow, it spins
|
|
271
|
-
on a core until something kills it; one machine collected 48 of them.
|
|
272
|
-
|
|
273
|
-
`throughline-payload` is a drop-in for the `payload` binary that cannot do that.
|
|
274
|
-
Point every script that runs the Payload CLI at it:
|
|
275
|
-
|
|
276
|
-
```json
|
|
277
|
-
{
|
|
278
|
-
"scripts": {
|
|
279
|
-
"payload": "throughline-payload",
|
|
280
|
-
"payload:reap": "throughline-payload --reap",
|
|
281
|
-
"generate:types": "throughline-payload generate:types",
|
|
282
|
-
"generate:importmap": "throughline-payload generate:importmap",
|
|
283
|
-
"migrate": "PAYLOAD_MIGRATING=1 throughline-payload migrate",
|
|
284
|
-
"migrate:create": "throughline-payload migrate:create",
|
|
285
|
-
"migrate:status": "throughline-payload migrate:status"
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
72
|
+
- **It registers every plugin in the order they need.** Audit, job failures and `check_slug` are always on. Every other plugin is on when its key is present.
|
|
73
|
+
- **It gives every plugin the one MCP collector.** `suite.mcpTools` is what `mcpPlugin` serves.
|
|
74
|
+
- **Shared values are given once.** `approvals.collectionSlug` reaches the approvals collection, the emails and the expiry job. `collections` reaches publishing, "Your work" and scheduled publishing. `admin` is every added collection's sidebar group, unless a plugin's own says otherwise.
|
|
75
|
+
- **`suite.jobs` is every job the options call for:** revalidation (given `publishing.urls`), scheduled publishing and its backstop, approval expiry, the audit echo, the healthcheck, the three approval emails, and each integration's jobs. Function ids are the ones the 0.x factories registered.
|
|
76
|
+
- **Its defaults are what every site wrote by hand.** Scheduled publishes go through the publishing pipeline. Approval links are signed with `APPROVAL_TOKEN_SECRET` against `NEXT_PUBLIC_SERVER_URL`. A failing healthcheck is recorded in `job-failures`.
|
|
289
77
|
|
|
290
|
-
|
|
78
|
+
The reasoning is in [`1.0-throughline-call.md`](https://github.com/forumone/throughline/blob/main/docs/spec/1.0-throughline-call.md). Each plugin is still exported from its subpath for a site that wires them by hand.
|
|
291
79
|
|
|
292
|
-
|
|
293
|
-
killed as a group, by pid. Nothing is ever matched by process name.
|
|
294
|
-
- **Signals forwarded.** SIGINT, SIGTERM and SIGHUP go to the group; after a
|
|
295
|
-
grace period, whatever ignored them gets SIGKILL. When Payload exits on its
|
|
296
|
-
own, anything it left running in its group goes with it.
|
|
297
|
-
- **A wall clock.** `PAYLOAD_CLI_TIMEOUT_MS`, default 300000 (five minutes); `0`
|
|
298
|
-
disables it. The `migrate` family (`migrate`, `migrate:create`, …) has no wall
|
|
299
|
-
clock unless you set one: a large migration is legitimately long and
|
|
300
|
-
`migrate:create` can stop at a prompt. A timed-out run exits 124.
|
|
301
|
-
`PAYLOAD_CLI_GRACE_MS` (default 5000) is the wait between the polite signal
|
|
302
|
-
and SIGKILL.
|
|
303
|
-
- **A sweep for what a killed runner left behind.** A SIGKILLed runner runs no
|
|
304
|
-
handlers, so each run is recorded in `.payload-cli-pids` and the next run —
|
|
305
|
-
or `throughline-payload --reap` — kills the group of any entry whose runner
|
|
306
|
-
is gone. An entry is acted on only if the pid is alive, its start time and
|
|
307
|
-
full command line still match what was recorded (so a recycled pid is never
|
|
308
|
-
touched), the command line is rooted in this workspace, and its runner is
|
|
309
|
-
gone (so a concurrent run keeps its child). Anything else is forgotten, not
|
|
310
|
-
signalled.
|
|
311
|
-
|
|
312
|
-
Payload is resolved from the directory the command runs in — the app — not
|
|
313
|
-
from this package's install. `.payload-cli-pids` lives at the workspace root:
|
|
314
|
-
the nearest ancestor with a `pnpm-workspace.yaml`, or, outside a pnpm
|
|
315
|
-
workspace, the nearest directory with a `package.json`. Add it to
|
|
316
|
-
`.gitignore`. On Windows, which has neither process groups nor `ps`, the runner
|
|
317
|
-
just runs Payload.
|
|
318
|
-
|
|
319
|
-
## Testing what an anonymous reader can read
|
|
320
|
-
|
|
321
|
-
`@forumone/throughline/testing` holds a site's access rules to a bucket map, as a vitest suite that needs no database. It imports `vitest`, an optional peer, so it lives on its own subpath and the main entry never loads it.
|
|
322
|
-
|
|
323
|
-
Every query the public site makes runs with nobody signed in, so a collection's own `read` rule decides whether a page can see its content. Close one by mistake and nothing errors: the page renders empty. Open the wrong one and the audit log or the MCP keys are on the internet.
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
// apps/web/src/access/anonymousAccess.test.ts
|
|
327
|
-
import { describeAnonymousAccess } from '@forumone/throughline/testing'
|
|
328
|
-
import config from '../payload.config'
|
|
80
|
+
## Installation
|
|
329
81
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
pages: 'the [...slug] route',
|
|
333
|
-
media: 'every populated upload',
|
|
334
|
-
},
|
|
335
|
-
private: ['users', 'payload-mcp-api-keys', 'audit-events', 'payload-preferences'],
|
|
336
|
-
// Optional. When given, every global must be in it.
|
|
337
|
-
globals: { renderPath: { navigation: 'the header and footer' } },
|
|
338
|
-
})
|
|
82
|
+
```bash
|
|
83
|
+
pnpm add @forumone/throughline@next
|
|
339
84
|
```
|
|
340
85
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
- **Every collection is in exactly one bucket.** That covers the collections plugins and Payload add as well as the site's own. A collection in no bucket fails, so adding one forces somebody to decide. An entry the config no longer has fails too.
|
|
344
|
-
- **Render-path collections allow an anonymous read.** The rule must answer `true` or a query. Having no rule fails, because Payload's default needs a user.
|
|
345
|
-
- **Collections with drafts narrow that read.** On a collection with `versions.drafts`, a bare `true` serves unpublished documents, so the rule has to answer a query, usually `{ _status: { equals: 'published' } }`.
|
|
346
|
-
- **Private collections refuse it.** `true` or a query fails. Having no rule passes, because Payload's default refuses.
|
|
86
|
+
`payload@^3.89.0` is the one required peer. The rest are optional, each needed only by the subpaths that use it, and none is loaded by the root:
|
|
347
87
|
|
|
348
|
-
|
|
88
|
+
| Install | When the site uses |
|
|
89
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
90
|
+
| `inngest` | `/jobs/inngest`: jobs on Inngest |
|
|
91
|
+
| `resend`, `@react-email/components`, `@react-email/render`, `react` | `/email`, or `email` in `throughline()`. Loaded when an email is sent, so a missing one says so then, naming the package |
|
|
92
|
+
| `next`, `react`, `@payloadcms/ui`, `@payloadcms/next` | `/client` and `/rsc`, which Payload's admin imports |
|
|
93
|
+
| `@vercel/blob`, `@payloadcms/plugin-cloud-storage` | `/media`'s client-upload hardening |
|
|
94
|
+
| `vitest` | `/testing` |
|
|
349
95
|
|
|
350
|
-
|
|
96
|
+
`src/peers.test.ts` checks the table: a static import that would load an optional peer from somewhere else fails it.
|
|
351
97
|
|
|
352
|
-
|
|
98
|
+
Every option, the jobs `suite.jobs` holds, the audit log, MCP authentication, the environment check and the `throughline-payload` bin are in [the reference](https://github.com/forumone/throughline/blob/main/docs/reference/throughline.md).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* HMAC-signed action tokens used in inline action emails (Approve / Decline /
|
|
3
3
|
* Request changes / Discuss). Each token is bound to one approval, one
|
|
4
4
|
* action, and one approver. Tokens are valid for a configurable window
|
|
5
|
-
* (default
|
|
5
|
+
* (default 72 hours) and consumed via the `consumedTokens` array on the
|
|
6
6
|
* approval record so they can't be replayed.
|
|
7
7
|
*
|
|
8
8
|
* Crypto uses the Web Crypto API (`crypto.subtle`) so the same code runs
|
|
@@ -22,7 +22,7 @@ export interface ActionToken {
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function generateActionToken(token: ActionToken, secret: string): Promise<string>;
|
|
24
24
|
export interface VerifyOptions {
|
|
25
|
-
/** Override the default
|
|
25
|
+
/** Override the default 72-hour token lifetime. */
|
|
26
26
|
maxAgeMs?: number;
|
|
27
27
|
/** Override "now" — useful in tests. Defaults to `Date.now()`. */
|
|
28
28
|
now?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/approvals/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAE7E,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;CACjB;AA2BD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,MAAM,WAAW,aAAa;IAC5B,
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/approvals/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAE7E,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;CACjB;AA2BD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhC;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAiDvB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvE"}
|
package/dist/approvals/tokens.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* HMAC-signed action tokens used in inline action emails (Approve / Decline /
|
|
3
3
|
* Request changes / Discuss). Each token is bound to one approval, one
|
|
4
4
|
* action, and one approver. Tokens are valid for a configurable window
|
|
5
|
-
* (default
|
|
5
|
+
* (default 72 hours) and consumed via the `consumedTokens` array on the
|
|
6
6
|
* approval record so they can't be replayed.
|
|
7
7
|
*
|
|
8
8
|
* Crypto uses the Web Crypto API (`crypto.subtle`) so the same code runs
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Seventy-two hours is what an approval actually needs. It covers a weekend,
|
|
19
19
|
* which is the realistic gap between sending a request and somebody opening
|
|
20
20
|
* their mail, and it is well inside the request's own expiry so the two cannot
|
|
21
|
-
* disagree. `
|
|
21
|
+
* disagree. `expireStaleApprovalsJob` handles anything that ages
|
|
22
22
|
* out either way.
|
|
23
23
|
*
|
|
24
24
|
* The token is otherwise well built — HMAC-SHA256, constant-time compare, bound
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { McpToolCollector } from '../../mcp/collector.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { McpToolContext } from '../../plugin-contract/mcp.js';
|
|
3
3
|
import type { BaseCorePluginOptions } from '../../plugin-contract/index.js';
|
|
4
4
|
export interface AuditQueryPluginOptions extends Omit<BaseCorePluginOptions, 'routePrefix'> {
|
|
5
5
|
/**
|
|
@@ -8,10 +8,12 @@ export interface AuditQueryPluginOptions extends Omit<BaseCorePluginOptions, 'ro
|
|
|
8
8
|
*/
|
|
9
9
|
collectionSlug?: string;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Who may read the whole audit log through the tools. Anyone else is
|
|
12
|
+
* scoped to their own actions: `who_changed_what` about themselves, and a
|
|
13
|
+
* `permission-denied` envelope from the broad tools. Default
|
|
14
|
+
* `isAuditReader`: the `admin` and `editor` roles.
|
|
13
15
|
*/
|
|
14
|
-
readAccess?: (
|
|
16
|
+
readAccess?: (ctx: McpToolContext) => boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Where to put this server's MCP tools so Payload's own MCP plugin can serve
|
|
17
19
|
* them.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/audit/query/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/audit/query/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAA;AAQ3E,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC;IACzF;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAA;IAE7C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,CAEzF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/audit/query/options.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/audit/query/options.ts"],"names":[],"mappings":"AAuCA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgC;IAC9D,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/audit/query/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAMhE,OAAO,EAAE,KAAK,uBAAuB,EAAmB,MAAM,cAAc,CAAA;AAY5E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,uBAAuB,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/audit/query/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAMhE,OAAO,EAAE,KAAK,uBAAuB,EAAmB,MAAM,cAAc,CAAA;AAY5E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,uBAAuB,CA8D9D,CAAA"}
|
|
@@ -41,7 +41,11 @@ export const auditQueryPlugin = (rawOptions) => (incomingConfig) => {
|
|
|
41
41
|
would look is the thing that broke.
|
|
42
42
|
*/
|
|
43
43
|
const auditWriter = getAuditWriter(payload);
|
|
44
|
-
const deps = {
|
|
44
|
+
const deps = {
|
|
45
|
+
payload,
|
|
46
|
+
collectionSlug,
|
|
47
|
+
...(options.readAccess ? { canRead: options.readAccess } : {}),
|
|
48
|
+
};
|
|
45
49
|
const tools = [
|
|
46
50
|
createQueryAuditTool(deps),
|
|
47
51
|
createGetChangeHistoryTool(deps),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/audit/query/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,EACpB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,SAAS,GAAG,mCAAmC,CAAA;AACrD,MAAM,cAAc,GAAG,OAAO,CAAA;AAC9B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;IACjC,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,cAAc,CAAA;IAEvD,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,kBAAkB,CAAA;IACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,CAAA;IAEhF;;;;;MAKE;IACF,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;IAE1E,OAAO;QACL,GAAG,cAAc;QACjB,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,IAAI,cAAc,CAAC,MAAM;gBAAE,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAE/D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;YAC3C,QAAQ,CAAC,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;YAElD;;;;;;cAME;YACF,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;YAC3C,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/audit/query/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,EACpB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,SAAS,GAAG,mCAAmC,CAAA;AACrD,MAAM,cAAc,GAAG,OAAO,CAAA;AAC9B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;IACjC,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,cAAc,CAAA;IAEvD,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,kBAAkB,CAAA;IACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,CAAA;IAEhF;;;;;MAKE;IACF,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;IAE1E,OAAO;QACL,GAAG,cAAc;QACjB,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,IAAI,cAAc,CAAC,MAAM;gBAAE,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAE/D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;YAC3C,QAAQ,CAAC,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;YAElD;;;;;;cAME;YACF,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;YAC3C,MAAM,IAAI,GAAG;gBACX,OAAO;gBACP,cAAc;gBACd,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/D,CAAA;YACD,MAAM,KAAK,GAAG;gBACZ,oBAAoB,CAAC,IAAI,CAAC;gBAC1B,0BAA0B,CAAC,IAAI,CAAC;gBAChC,wBAAwB,CAAC,IAAI,CAAC;gBAC9B,4BAA4B,CAAC,IAAI,CAAC;gBAClC,2BAA2B,CAAC,IAAI,CAAC;aACA,CAAA;YAEnC,qEAAqE;YACrE,sEAAsE;YACtE,8CAA8C;YAC9C,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YAEjF,QAAQ,CAAC,QAAQ,CAAC;gBAChB,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE,cAAc;gBACvB,YAAY,EAAE,CAAC,aAAa,CAAC;aAC9B,CAAC,CAAA;YAEF,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;gBACtC,cAAc;gBACd,SAAS,EAAE,KAAK,CAAC,MAAM;aACxB,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|