@forumone/throughline-core 0.0.1 → 0.2.1

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 (84) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +104 -28
  4. package/dist/audit/collection.d.ts +19 -0
  5. package/dist/audit/collection.d.ts.map +1 -0
  6. package/dist/audit/collection.js +107 -0
  7. package/dist/audit/collection.js.map +1 -0
  8. package/dist/audit/index.d.ts +9 -0
  9. package/dist/audit/index.d.ts.map +1 -0
  10. package/dist/audit/index.js +5 -0
  11. package/dist/audit/index.js.map +1 -0
  12. package/dist/audit/plugin.d.ts +20 -0
  13. package/dist/audit/plugin.d.ts.map +1 -0
  14. package/dist/audit/plugin.js +55 -0
  15. package/dist/audit/plugin.js.map +1 -0
  16. package/dist/audit/types.d.ts +11 -0
  17. package/dist/audit/types.d.ts.map +1 -0
  18. package/dist/audit/types.js +45 -0
  19. package/dist/audit/types.js.map +1 -0
  20. package/dist/audit/writer.d.ts +50 -0
  21. package/dist/audit/writer.d.ts.map +1 -0
  22. package/dist/audit/writer.js +101 -0
  23. package/dist/audit/writer.js.map +1 -0
  24. package/dist/auth/api-keys.d.ts +21 -0
  25. package/dist/auth/api-keys.d.ts.map +1 -0
  26. package/dist/auth/api-keys.js +115 -0
  27. package/dist/auth/api-keys.js.map +1 -0
  28. package/dist/auth/authenticator.d.ts +15 -0
  29. package/dist/auth/authenticator.d.ts.map +1 -0
  30. package/dist/auth/authenticator.js +63 -0
  31. package/dist/auth/authenticator.js.map +1 -0
  32. package/dist/auth/index.d.ts +5 -0
  33. package/dist/auth/index.d.ts.map +1 -0
  34. package/dist/auth/index.js +3 -0
  35. package/dist/auth/index.js.map +1 -0
  36. package/dist/env/index.d.ts +51 -0
  37. package/dist/env/index.d.ts.map +1 -0
  38. package/dist/env/index.js +59 -0
  39. package/dist/env/index.js.map +1 -0
  40. package/dist/events/index.d.ts +4 -0
  41. package/dist/events/index.d.ts.map +1 -0
  42. package/dist/events/index.js +2 -0
  43. package/dist/events/index.js.map +1 -0
  44. package/dist/events/inngest.d.ts +24 -0
  45. package/dist/events/inngest.d.ts.map +1 -0
  46. package/dist/events/inngest.js +24 -0
  47. package/dist/events/inngest.js.map +1 -0
  48. package/dist/events/taxonomy.d.ts +84 -0
  49. package/dist/events/taxonomy.d.ts.map +1 -0
  50. package/dist/events/taxonomy.js +10 -0
  51. package/dist/events/taxonomy.js.map +1 -0
  52. package/dist/index.d.ts +14 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +10 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/logger/index.d.ts +9 -0
  57. package/dist/logger/index.d.ts.map +1 -0
  58. package/dist/logger/index.js +41 -0
  59. package/dist/logger/index.js.map +1 -0
  60. package/dist/mcp/handler.d.ts +20 -0
  61. package/dist/mcp/handler.d.ts.map +1 -0
  62. package/dist/mcp/handler.js +109 -0
  63. package/dist/mcp/handler.js.map +1 -0
  64. package/dist/mcp/index.d.ts +5 -0
  65. package/dist/mcp/index.d.ts.map +1 -0
  66. package/dist/mcp/index.js +3 -0
  67. package/dist/mcp/index.js.map +1 -0
  68. package/dist/mcp/meta.d.ts +74 -0
  69. package/dist/mcp/meta.d.ts.map +1 -0
  70. package/dist/mcp/meta.js +27 -0
  71. package/dist/mcp/meta.js.map +1 -0
  72. package/dist/utils/diff.d.ts +13 -0
  73. package/dist/utils/diff.d.ts.map +1 -0
  74. package/dist/utils/diff.js +31 -0
  75. package/dist/utils/diff.js.map +1 -0
  76. package/dist/utils/id.d.ts +6 -0
  77. package/dist/utils/id.d.ts.map +1 -0
  78. package/dist/utils/id.js +13 -0
  79. package/dist/utils/id.js.map +1 -0
  80. package/dist/utils/index.d.ts +3 -0
  81. package/dist/utils/index.d.ts.map +1 -0
  82. package/dist/utils/index.js +3 -0
  83. package/dist/utils/index.js.map +1 -0
  84. package/package.json +82 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @forumone/throughline-core
2
+
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a4b5108: Initial release of the forms package. Wraps Payload's Form Builder plugin with the Throughline policy layer: mandatory privacy notice, consent enforcement (server-side), honeypot spam protection, Postgres-backed per-IP rate limiting, a destination allowlist (the security perimeter), and submitter confirmations. Six MCP tools (`list_allowed_destinations`, `validate_form`, `create_form`, `update_form_fields`, `update_form_destinations`, `get_form_submissions`) and four Inngest functions (`form-fan-out`, `form-email-destination`, `form-webhook-destination`, `form-submitter-confirmation`) drive the conversational flow and the async destination delivery. Includes `FormSubmissionEmail` and `SubmitterConfirmationEmail` React Email templates. Allowlist enforcement runs at three layers (MCP tool, collection beforeChange hook, fan-out worker) so prompt injection or admin direct-API writes can't bypass it. IPs are HMAC-hashed; raw IPs are never persisted. Adds `form.updated` to the core audit-action taxonomy used by the two update tools.
8
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#14](https://github.com/forumone/throughline/pull/14) [`5329d97`](https://github.com/forumone/throughline/commit/5329d97363099a54bcae2516a8aa9eff8cd735fc) Thanks [@briangraves](https://github.com/briangraves)! - Initial release. Provides the audit log (collection + fire-and-forget writer + plugin), MCP authentication (bearer-token authenticator + API-keys collection with SHA-256 hashed keys), event taxonomy and Inngest client factory, MCP handler infrastructure (JSON-RPC over HTTP) with the `_meta` helper for prompt/reasoning capture, standard env-var conventions, a default logger, and shared utilities. Every server package in the framework depends on this.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,121 @@
1
1
  # @forumone/throughline-core
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ The shared plumbing every Throughline server package depends on. Drop it into a Payload + Next.js app and you get the audit log, MCP authentication and request handling, the framework event taxonomy + Inngest client factory, env-var conventions, a logger, and a handful of small utilities.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## What's inside
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ | Subsystem | Subpath | Role |
8
+ |---|---|---|
9
+ | Audit | `./audit` | `auditPlugin`, `createAuditCollection`, `createAuditWriter`, `getAuditWriter`, `AUDIT_ACTIONS`, `AUDIT_MCP_SERVERS` |
10
+ | Auth | `./auth` | `createApiKeysCollection`, `createBearerTokenAuthenticator`, `generateApiKey`, `sha256Hex` |
11
+ | Events | `./events` | `createInngestClient`, `CoreEvents`, `FrameworkEvents` (module-augmentation seam) |
12
+ | MCP | `./mcp` | `createMcpHandler`, `McpMetaSchema`, `withMeta` |
13
+ | Env | `./env` | `ENV_VARS`, `validateBaseEnv`, `requireEnv`, `optionalEnv` |
14
+ | Logger | (main) | `defaultLogger`, `createNamedLogger` |
15
+ | Utils | (main) | `shallowDiff`, `generateId` |
8
16
 
9
- ## Purpose
17
+ The main entry re-exports everything; the subpath exports keep bundles smaller for consumers who only need one slice.
10
18
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-core`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
19
+ ## Installation
15
20
 
16
- ## What is OIDC Trusted Publishing?
21
+ ```bash
22
+ pnpm add @forumone/throughline-core
23
+ ```
17
24
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
25
+ Peers: `payload@^3.0.0` and `inngest@^4.0.0`.
19
26
 
20
- ## Setup Instructions
27
+ ## The audit log
21
28
 
22
- To properly configure OIDC trusted publishing for this package:
29
+ Every consequential action in the framework writes to a single immutable Payload collection. Plugins do not write directly — they call the writer attached to the Payload instance by `auditPlugin`.
23
30
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
31
+ ```ts
32
+ import { auditPlugin, createInngestClient } from '@forumone/throughline-core'
33
+ import { buildConfig } from 'payload'
28
34
 
29
- ## DO NOT USE THIS PACKAGE
35
+ const inngest = createInngestClient({ id: 'my-site' })
30
36
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
37
+ export default buildConfig({
38
+ // collections, db, secret...
39
+ plugins: [
40
+ auditPlugin({ inngest }),
41
+ // your other Throughline plugins
42
+ ],
43
+ })
44
+ ```
36
45
 
37
- ## More Information
46
+ In a downstream plugin's `onInit`:
38
47
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
48
+ ```ts
49
+ import { getAuditWriter } from '@forumone/throughline-core'
42
50
 
43
- ---
51
+ onInit: async (payload) => {
52
+ const writer = getAuditWriter(payload)
53
+ await writer({
54
+ actor: { type: 'user', userId: 'u1' },
55
+ action: 'publishing.publish',
56
+ mcpServer: 'publishing',
57
+ mcpTool: 'publishing.publish',
58
+ targetCollection: 'pages',
59
+ targetId: 'p1',
60
+ targetTitle: 'Homepage',
61
+ })
62
+ }
63
+ ```
44
64
 
45
- **Maintained for OIDC setup purposes only**
65
+ The writer is **fire-and-forget**: failures log but never throw. Audit failures must never break the originating action.
66
+
67
+ ## MCP authentication
68
+
69
+ `createApiKeysCollection` adds a Payload collection that stores SHA-256 hashes of bearer tokens (the raw key is shown to the operator once on create, then never persisted). `createBearerTokenAuthenticator` validates incoming requests against that collection.
70
+
71
+ ```ts
72
+ import { createApiKeysCollection, createBearerTokenAuthenticator, createMcpHandler } from '@forumone/throughline-core'
73
+ import type { Payload } from 'payload'
74
+
75
+ // In your Payload config:
76
+ collections: [createApiKeysCollection({ usersSlug: 'users' })],
77
+
78
+ // In your MCP route:
79
+ const authenticator = createBearerTokenAuthenticator({ payload })
80
+ const handleMcp = createMcpHandler({
81
+ payload,
82
+ serverName: 'publishing',
83
+ tools: [/* McpToolDefinition[] */],
84
+ authenticator,
85
+ })
86
+
87
+ // Next.js app router:
88
+ export const POST = (req: Request) => handleMcp(req)
89
+ ```
90
+
91
+ ## Events
92
+
93
+ `CoreEvents` enumerates the events the framework fires today. Server packages add their own via TypeScript module augmentation:
94
+
95
+ ```ts
96
+ declare module '@forumone/throughline-core/events' {
97
+ interface FrameworkEvents {
98
+ 'approval/decided': {
99
+ data: { approvalId: string; decision: 'granted' | 'declined' }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ After augmentation, `inngest.send({ name: 'approval/decided', data: { ... } })` is type-checked everywhere.
106
+
107
+ ## Env vars
108
+
109
+ `ENV_VARS` is the canonical list of names the framework reads. Plugins reach into `process.env` through these constants rather than hard-coded strings:
110
+
111
+ ```ts
112
+ import { ENV_VARS, requireEnv, validateBaseEnv } from '@forumone/throughline-core'
113
+
114
+ validateBaseEnv() // throws on missing PAYLOAD_SECRET / DATABASE_URI / NEXT_PUBLIC_SERVER_URL
115
+
116
+ const apiKey = requireEnv(ENV_VARS.PUBLISHING_SERVER_API_KEY)
117
+ ```
118
+
119
+ ## Why all of this lives in one package
120
+
121
+ Server packages (Component, Publishing, Approvals, Audit Query, Forms, Integrations) all depend on the same audit log, the same authentication pattern, and the same event taxonomy. Splitting these across packages would create circular dependencies — every server package would need the audit writer, and an audit-only package would need to know about every server. Consolidating the plumbing here keeps the dependency graph one-way: core → server packages → client app.
@@ -0,0 +1,19 @@
1
+ import type { Access, CollectionConfig } from 'payload';
2
+ export interface AuditCollectionOptions {
3
+ /** Override the collection slug. Default: `'audit-events'`. */
4
+ slug?: string;
5
+ /**
6
+ * Read access function. Receives the standard Payload access args.
7
+ * Defaults to admin/editor roles.
8
+ */
9
+ readAccess?: Access;
10
+ }
11
+ export declare const DEFAULT_AUDIT_SLUG = "audit-events";
12
+ /**
13
+ * Builds the audit-events collection config. The collection is read-only
14
+ * from outside the writer: `create`, `update`, and `delete` are denied
15
+ * for all callers; the writer uses Payload's local API which bypasses
16
+ * these checks.
17
+ */
18
+ export declare function createAuditCollection(options?: AuditCollectionOptions): CollectionConfig;
19
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/audit/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAGvD,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAOhD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,gBAAgB,CA+F5F"}
@@ -0,0 +1,107 @@
1
+ import { AUDIT_ACTIONS, AUDIT_MCP_SERVERS } from './types.js';
2
+ export const DEFAULT_AUDIT_SLUG = 'audit-events';
3
+ const defaultReadAccess = ({ req }) => {
4
+ const roles = req.user?.['roles'] ?? [];
5
+ return roles.includes('admin') || roles.includes('editor');
6
+ };
7
+ /**
8
+ * Builds the audit-events collection config. The collection is read-only
9
+ * from outside the writer: `create`, `update`, and `delete` are denied
10
+ * for all callers; the writer uses Payload's local API which bypasses
11
+ * these checks.
12
+ */
13
+ export function createAuditCollection(options = {}) {
14
+ const slug = options.slug ?? DEFAULT_AUDIT_SLUG;
15
+ return {
16
+ slug,
17
+ admin: {
18
+ useAsTitle: 'summary',
19
+ defaultColumns: ['createdAt', 'actor', 'action', 'targetCollection', 'targetId'],
20
+ description: 'Immutable record of every consequential action in the system. Read-only through the admin.',
21
+ },
22
+ access: {
23
+ read: options.readAccess ?? defaultReadAccess,
24
+ create: () => false,
25
+ update: () => false,
26
+ delete: () => false,
27
+ },
28
+ fields: [
29
+ {
30
+ name: 'createdAt',
31
+ type: 'date',
32
+ required: true,
33
+ defaultValue: () => new Date().toISOString(),
34
+ admin: { position: 'sidebar', readOnly: true },
35
+ },
36
+ {
37
+ name: 'actor',
38
+ type: 'group',
39
+ fields: [
40
+ {
41
+ name: 'type',
42
+ type: 'select',
43
+ required: true,
44
+ options: [
45
+ { label: 'User', value: 'user' },
46
+ { label: 'System', value: 'system' },
47
+ { label: 'Integration', value: 'integration' },
48
+ ],
49
+ },
50
+ { name: 'userId', type: 'text' },
51
+ { name: 'userName', type: 'text' },
52
+ { name: 'apiKeyName', type: 'text' },
53
+ { name: 'apiKeyId', type: 'text' },
54
+ { name: 'sessionId', type: 'text' },
55
+ ],
56
+ },
57
+ {
58
+ name: 'action',
59
+ type: 'select',
60
+ required: true,
61
+ options: AUDIT_ACTIONS.map((value) => ({ label: value, value })),
62
+ },
63
+ {
64
+ name: 'mcpServer',
65
+ type: 'select',
66
+ required: true,
67
+ options: AUDIT_MCP_SERVERS.map((value) => ({ label: value, value })),
68
+ },
69
+ { name: 'mcpTool', type: 'text', required: true },
70
+ { name: 'targetCollection', type: 'text' },
71
+ { name: 'targetId', type: 'text' },
72
+ { name: 'targetTitle', type: 'text' },
73
+ {
74
+ name: 'prompt',
75
+ type: 'textarea',
76
+ admin: { description: "The user's natural-language prompt, if surfaced via _meta." },
77
+ },
78
+ {
79
+ name: 'reasoning',
80
+ type: 'textarea',
81
+ admin: { description: "Claude's reasoning, if surfaced via _meta." },
82
+ },
83
+ { name: 'changesSummary', type: 'textarea' },
84
+ { name: 'summary', type: 'text', required: true },
85
+ {
86
+ name: 'diff',
87
+ type: 'json',
88
+ admin: {
89
+ description: 'Before/after fields for update operations; null for reads.',
90
+ },
91
+ },
92
+ { name: 'success', type: 'checkbox', defaultValue: true },
93
+ { name: 'errorMessage', type: 'text' },
94
+ { name: 'approvalRequestId', type: 'text' },
95
+ { name: 'integrationId', type: 'text' },
96
+ ],
97
+ indexes: [
98
+ { fields: ['createdAt'] },
99
+ { fields: ['actor.userId', 'createdAt'] },
100
+ { fields: ['targetCollection', 'targetId', 'createdAt'] },
101
+ { fields: ['action', 'createdAt'] },
102
+ { fields: ['mcpServer', 'createdAt'] },
103
+ ],
104
+ timestamps: false,
105
+ };
106
+ }
107
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../../src/audit/collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { auditPlugin, getAuditWriter } from './plugin.js';
2
+ export type { AuditPluginOptions } from './plugin.js';
3
+ export { createAuditCollection, DEFAULT_AUDIT_SLUG } from './collection.js';
4
+ export type { AuditCollectionOptions } from './collection.js';
5
+ export { createAuditWriter } from './writer.js';
6
+ export type { AuditWriter, AuditWriterOptions, AuditEventInput, AuditActor } from './writer.js';
7
+ export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS } from './types.js';
8
+ export type { AuditAction, AuditMcpServer } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACzD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC3E,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE/F,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC7D,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { auditPlugin, getAuditWriter } from './plugin.js';
2
+ export { createAuditCollection, DEFAULT_AUDIT_SLUG } from './collection.js';
3
+ export { createAuditWriter } from './writer.js';
4
+ export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS } from './types.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGzD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAG3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,20 @@
1
+ import type { Inngest } from 'inngest';
2
+ import { type BaseCorePluginOptions, type CorePlugin } from '@forumone/throughline-plugin-contract';
3
+ import { type AuditCollectionOptions } from './collection.js';
4
+ import { type AuditWriter } from './writer.js';
5
+ export interface AuditPluginOptions extends BaseCorePluginOptions, AuditCollectionOptions {
6
+ /**
7
+ * Inngest client used to fire `audit/event.recorded` events. If omitted,
8
+ * audit writes still persist; downstream subscribers simply won't receive
9
+ * events.
10
+ */
11
+ inngest?: Inngest;
12
+ }
13
+ export declare const auditPlugin: CorePlugin<AuditPluginOptions>;
14
+ /**
15
+ * Retrieves the audit writer attached to a Payload instance by `auditPlugin`.
16
+ * Plugins that depend on auditing call this in their own `onInit` to record
17
+ * events without importing this package's writer factory directly.
18
+ */
19
+ export declare function getAuditWriter(payload: object): AuditWriter;
20
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/audit/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EAEhB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACpF,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAOjE,MAAM,WAAW,kBAAmB,SAAQ,qBAAqB,EAAE,sBAAsB;IACvF;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,kBAAkB,CA8BtD,CAAA;AAWD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAQ3D"}
@@ -0,0 +1,55 @@
1
+ import { getPluginRegistry, } from '@forumone/throughline-plugin-contract';
2
+ import { createAuditCollection } from './collection.js';
3
+ import { createAuditWriter } from './writer.js';
4
+ import { defaultLogger } from '../logger/index.js';
5
+ const AUDIT_PLUGIN_ID = '@forumone/throughline-core/audit';
6
+ const AUDIT_PLUGIN_VERSION = '0.1.0';
7
+ const AUDIT_WRITER_SYMBOL = Symbol.for('@forumone/throughline/audit-writer');
8
+ export const auditPlugin = (options) => (incomingConfig) => {
9
+ if (options.enabled === false)
10
+ return incomingConfig;
11
+ const auditCollection = createAuditCollection(options);
12
+ return {
13
+ ...incomingConfig,
14
+ collections: [...(incomingConfig.collections ?? []), auditCollection],
15
+ onInit: async (payload) => {
16
+ if (incomingConfig.onInit) {
17
+ await incomingConfig.onInit(payload);
18
+ }
19
+ const writer = createAuditWriter({
20
+ payload,
21
+ inngest: options.inngest,
22
+ collectionSlug: options.slug,
23
+ logger: options.logger ?? defaultLogger,
24
+ });
25
+ attachAuditWriter(payload, writer);
26
+ const registry = getPluginRegistry(payload);
27
+ registry.register({
28
+ id: AUDIT_PLUGIN_ID,
29
+ version: AUDIT_PLUGIN_VERSION,
30
+ capabilities: ['audit-log', 'audit-write'],
31
+ });
32
+ },
33
+ };
34
+ };
35
+ function attachAuditWriter(target, writer) {
36
+ Object.defineProperty(target, AUDIT_WRITER_SYMBOL, {
37
+ value: writer,
38
+ enumerable: false,
39
+ writable: false,
40
+ configurable: false,
41
+ });
42
+ }
43
+ /**
44
+ * Retrieves the audit writer attached to a Payload instance by `auditPlugin`.
45
+ * Plugins that depend on auditing call this in their own `onInit` to record
46
+ * events without importing this package's writer factory directly.
47
+ */
48
+ export function getAuditWriter(payload) {
49
+ const writer = payload[AUDIT_WRITER_SYMBOL];
50
+ if (!writer) {
51
+ throw new Error('Audit writer not found. Ensure `auditPlugin` is registered in your Payload config before any plugin that depends on it.');
52
+ }
53
+ return writer;
54
+ }
55
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/audit/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,iBAAiB,GAClB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAA+B,MAAM,iBAAiB,CAAA;AACpF,OAAO,EAAE,iBAAiB,EAAoB,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,MAAM,eAAe,GAAG,kCAAkC,CAAA;AAC1D,MAAM,oBAAoB,GAAG,OAAO,CAAA;AACpC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAA;AAW5E,MAAM,CAAC,MAAM,WAAW,GAAmC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;IACzF,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,cAAc,CAAA;IAEpD,MAAM,eAAe,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAEtD,OAAO;QACL,GAAG,cAAc;QACjB,WAAW,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC;QACrE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,IAAI,cAAc,CAAC,MAAM,EAAE,CAAC;gBAC1B,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACtC,CAAC;YAED,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,OAAO;gBACP,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,cAAc,EAAE,OAAO,CAAC,IAAI;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa;aACxC,CAAC,CAAA;YAEF,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAElC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;YAC3C,QAAQ,CAAC,QAAQ,CAAC;gBAChB,EAAE,EAAE,eAAe;gBACnB,OAAO,EAAE,oBAAoB;gBAC7B,YAAY,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC;aAC3C,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAED,SAAS,iBAAiB,CAAC,MAAc,EAAE,MAAmB;IAC5D,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,mBAAmB,EAAE;QACjD,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,KAAK;QACf,YAAY,EAAE,KAAK;KACpB,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,MAAM,GAAI,OAAmC,CAAC,mBAAmB,CAAC,CAAA;IACxE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,yHAAyH,CAC1H,CAAA;IACH,CAAC;IACD,OAAO,MAAqB,CAAA;AAC9B,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Canonical action names for the audit log. Every server package picks
3
+ * actions from this list rather than introducing free-form strings, so
4
+ * audit queries (C8) can build on a known taxonomy.
5
+ */
6
+ export declare const AUDIT_ACTIONS: readonly ["content.find", "content.create", "content.update", "content.delete", "design.list", "design.suggest", "design.validate", "design.get_contract", "design.find_anti_pattern", "publishing.draft", "publishing.publish", "publishing.unpublish", "publishing.schedule", "publishing.rollback", "approval.requested", "approval.granted", "approval.declined", "approval.changes_requested", "approval.expired", "approval.discussed", "form.created", "form.updated", "form.submission_received", "integration.synced", "integration.failed", "system.error", "system.healthcheck"];
7
+ export type AuditAction = (typeof AUDIT_ACTIONS)[number];
8
+ /** MCP server identifiers. Keep in sync with the planned server packages. */
9
+ export declare const AUDIT_MCP_SERVERS: readonly ["payload", "component", "publishing", "approvals", "audit", "forms", "integrations"];
10
+ export type AuditMcpServer = (typeof AUDIT_MCP_SERVERS)[number];
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/audit/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,aAAa,6jBA4BhB,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,gGAQpB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAA"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Canonical action names for the audit log. Every server package picks
3
+ * actions from this list rather than introducing free-form strings, so
4
+ * audit queries (C8) can build on a known taxonomy.
5
+ */
6
+ export const AUDIT_ACTIONS = [
7
+ 'content.find',
8
+ 'content.create',
9
+ 'content.update',
10
+ 'content.delete',
11
+ 'design.list',
12
+ 'design.suggest',
13
+ 'design.validate',
14
+ 'design.get_contract',
15
+ 'design.find_anti_pattern',
16
+ 'publishing.draft',
17
+ 'publishing.publish',
18
+ 'publishing.unpublish',
19
+ 'publishing.schedule',
20
+ 'publishing.rollback',
21
+ 'approval.requested',
22
+ 'approval.granted',
23
+ 'approval.declined',
24
+ 'approval.changes_requested',
25
+ 'approval.expired',
26
+ 'approval.discussed',
27
+ 'form.created',
28
+ 'form.updated',
29
+ 'form.submission_received',
30
+ 'integration.synced',
31
+ 'integration.failed',
32
+ 'system.error',
33
+ 'system.healthcheck',
34
+ ];
35
+ /** MCP server identifiers. Keep in sync with the planned server packages. */
36
+ export const AUDIT_MCP_SERVERS = [
37
+ 'payload',
38
+ 'component',
39
+ 'publishing',
40
+ 'approvals',
41
+ 'audit',
42
+ 'forms',
43
+ 'integrations',
44
+ ];
45
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/audit/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,gBAAgB;IAChB,aAAa;IACb,gBAAgB;IAChB,iBAAiB;IACjB,qBAAqB;IACrB,0BAA0B;IAC1B,kBAAkB;IAClB,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;IACrB,qBAAqB;IACrB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,4BAA4B;IAC5B,kBAAkB;IAClB,oBAAoB;IACpB,cAAc;IACd,cAAc;IACd,0BAA0B;IAC1B,oBAAoB;IACpB,oBAAoB;IACpB,cAAc;IACd,oBAAoB;CACZ,CAAA;AAIV,6EAA6E;AAC7E,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,SAAS;IACT,WAAW;IACX,YAAY;IACZ,WAAW;IACX,OAAO;IACP,OAAO;IACP,cAAc;CACN,CAAA"}
@@ -0,0 +1,50 @@
1
+ import type { Inngest } from 'inngest';
2
+ import type { Payload } from 'payload';
3
+ import type { Logger } from '@forumone/throughline-plugin-contract';
4
+ import type { AuditAction, AuditMcpServer } from './types.js';
5
+ export interface AuditActor {
6
+ type: 'user' | 'system' | 'integration';
7
+ userId?: string | undefined;
8
+ userName?: string | undefined;
9
+ apiKeyName?: string | undefined;
10
+ apiKeyId?: string | undefined;
11
+ sessionId?: string | undefined;
12
+ }
13
+ export interface AuditEventInput {
14
+ actor: AuditActor;
15
+ action: AuditAction;
16
+ mcpServer: AuditMcpServer;
17
+ mcpTool: string;
18
+ targetCollection?: string | undefined;
19
+ targetId?: string | undefined;
20
+ targetTitle?: string | undefined;
21
+ prompt?: string | undefined;
22
+ reasoning?: string | undefined;
23
+ changesSummary?: string | undefined;
24
+ summary?: string | undefined;
25
+ diff?: Record<string, {
26
+ before: unknown;
27
+ after: unknown;
28
+ }> | null | undefined;
29
+ success?: boolean | undefined;
30
+ errorMessage?: string | undefined;
31
+ approvalRequestId?: string | undefined;
32
+ integrationId?: string | undefined;
33
+ }
34
+ export interface AuditWriterOptions {
35
+ payload: Payload;
36
+ inngest?: Inngest | undefined;
37
+ collectionSlug?: string | undefined;
38
+ logger?: Logger | undefined;
39
+ }
40
+ export type AuditWriter = (event: AuditEventInput) => Promise<void>;
41
+ /**
42
+ * Returns a fire-and-forget audit writer. The writer never throws — failures
43
+ * log via `options.logger` (or `console`). Audit failures must not break
44
+ * the originating action.
45
+ *
46
+ * If `inngest` is provided the writer also fires an `audit/event.recorded`
47
+ * event so subscribers (email, integrations, healthchecks) can react.
48
+ */
49
+ export declare function createAuditWriter(options: AuditWriterOptions): AuditWriter;
50
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/audit/writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uCAAuC,CAAA;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,aAAa,CAAA;IACvC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEnE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CA0D1E"}